From dwmw2 at infradead.org  Fri May  1 01:56:25 2026
From: dwmw2 at infradead.org (David Woodhouse)
Date: Fri, 01 May 2026 09:56:25 +0100
Subject: [RFC] proposal: KVM: Orphaned VMs: The Caretaker approach for
 Live Update
In-Reply-To: <3ff53353-3842-4a63-80a1-90a60d09fe02@redhat.com>
References: <afEwWZksU0Fw61oT@plex>
	 <0a71472c-b397-4699-a518-61faffcf4ab2@redhat.com>
	 <c004f333ed4f7dedae1383f22d43b009cd83837e.camel@infradead.org>
	 <3ff53353-3842-4a63-80a1-90a60d09fe02@redhat.com>
Message-ID: <718a82870c8f3c913791f12a993e11b2d26d08d9.camel@infradead.org>

On Fri, 2026-05-01 at 05:32 +0200, Paolo Bonzini wrote:
> On 4/30/26 17:27, David Woodhouse wrote:
> > On Thu, 2026-04-30 at 15:28 +0200, Paolo Bonzini wrote:
> > > I even wonder if, for long term simplicity, the interface for
> > > host->caretaker should be just for the caretaker to swallow the host
> > > into non-root mode, again as in Arm nVHE.
> > 
> > There's a lot of merit in that approach.
> > 
> > I talked about wanting to use this 'caretaker' for secret hiding.? But
> > why have *voluntary* secret hiding with the kernel hiding things from
> > its own address space, when you have have *mandatory* secret hiding
> > with something running in EL2, like pKVM.
> 
> Well, other than because it's a lot of work? :)

If we avoided those things then we've never have any fun!

And in a week where there seems to be a new user-to-root exploit posted
every day, the 'deprivilege the VMM and assume the guest has owned it'
security model is looking rather scary. So the additional defence in
depth of knowing that even *root* can't get the kernel to access other
guests' memory might be the only thing that lets you sleep at night :)

Yes, it's a lot of work. But I think we've reached the point where
mandatory secret hiding is... well... mandatory.

> > The *userspace* ABI considerations are all about how you make a vCPU
> > that runs asynchronously (should it conceptually just be an async
> > KVM_RUN call, which allows the vCPU to run in a kernel thread up to the
> > point of kexec? Why is it fundamentally tied to kexec at all?).
> 
> It's not tied to kexec.? kexec is just forcing a handoff + forcing an
> update.
> 
> The big difference is that:
> 
> 1) if you don't tie it to kexec, a detached vCPU thread is a struct 
> vhost_task and a blocking vmexit schedules out the thread; while during 
> kexec you have s/kthread/pCPU/ and halting the CPU instead of scheduling 
> it out.

For now maybe. But "how does the caretaker do scheduling" is on
definitely the list of future problems, for any environment where a
physical host with N pCPUs is hosting >= N vCPUs.

(In the case of a true mandatory-secret-hiding caretaker at EL2, the
scheduling part *could* be done by the residual purgatory-caretaker-
thing at EL1 that all the secondary CPUs go to instead of being turned
off. It would just be calling into EL2 to run the actual vCPUs. Thus
leaving the EL2 code just to do its *one* job, which has the added
benefit that the automated reasoning people put the knives down and no
longer have that look in their eyes that they got when they thought you
wanted to put a scheduler in their formally-proven EL2 code...)

> 2) if you don't tie it to kexec, address space isolation is the only 
> real reason for the complication of treating the caretaker as a separate 
> bare metal program.? OTOH maybe that's a feature - you could do:
> 
> - ioctl(KVM_RUN_ASYNC)
> 
> - then vmfd/vcpufd handoff to a new mm on top

This much gives you a seamless upgrade of the userspace VMM without
having to play fd-handover tricks. The old VMM detaches, the new one
attaches. If you're quick, and the guests aren't doing much "admin"
work but only passing traffic through passthrough PCI devices, the
guests might not experience any non-negligible steal time at all.

> - then address space isolation on top

Even voluntary secret hiding lets you sleep at night when the next
Retbleed happens.

> - then kexec (de)serialization on top

... and this one is the holy grail.

So yes, that's exactly the kind of thing I was thinking, rather than
trying to boil the ocean. There are sensible milestones along the way
which give practical benefits.

But my point was *also* about understanding the actual userspace
interface for this, even if we were to just focus on the live update
and do it all in one amphetamine-and-tokens-fueled epic. What does it
even look like, from the VMM point of view??How does the new VMM under
the new kernel 'reattach' to the existing vCPUs?

I think we need the userspace API concepts for 'detach' and 'attach',
including the permissions model for reattach, and we might as well
implement and test them without the kexec in the middle to start with.

> > I'd love to start without kexec in the picture at all. Just show me the
> > KVM API for starting a *confidential* guest (pKVM, SEV-SNP, whatever),
> > leaving it running, completely stopping the VMM and then starting a new
> > VMM to pick up from where it left off.
> 
> Why confidential?

Mostly so that confidential VMs aren't an *afterthought*, and the
design of the detach/attach userspace ABI gets them right from the
start.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 5069 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/kexec/attachments/20260501/b4854ec9/attachment.p7s>

From crisjacobmaamor at gmail.com  Fri May  1 02:46:32 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:32 +0800
Subject: [PATCH RFC 0/5] liveupdate: validate restored LUO metadata
Message-ID: <20260501094637.38650-1-crisjacobmaamor@gmail.com>

LUO restores metadata from KHO/FDT during liveupdate. The restored
metadata contains physical addresses and count fields used to access and
walk preserved session, file-set, and FLB arrays.

This series adds a non-consuming KHO preserved-range check and uses it
before phys_to_virt() on restored metadata addresses. It also rejects
restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
before traversal.

As far as I can tell, this is root/admin-only; I do not have evidence
that a normal unprivileged user can trigger it directly.

I have not reproduced this in a VM yet, so I may be missing a KHO
invariant or a preferred restore helper pattern. Feedback on the helper
semantics is welcome.

Cris Jacob Maamor (5):
  kexec: handover: add helper to check preserved page ranges
  liveupdate: validate restored LUO FDT before use
  liveupdate: validate restored LUO session metadata
  liveupdate: validate restored LUO file-set metadata
  liveupdate: validate restored LUO FLB metadata

 include/linux/kexec_handover.h     |  6 +++++
 kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++
 kernel/liveupdate/luo_core.c       | 10 ++++++++-
 kernel/liveupdate/luo_file.c       | 14 ++++++++++--
 kernel/liveupdate/luo_flb.c        | 23 +++++++++++++++++++-
 kernel/liveupdate/luo_session.c    | 22 +++++++++++++++++--
 6 files changed, 104 insertions(+), 6 deletions(-)

-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 02:46:33 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:33 +0800
Subject: [PATCH RFC 1/5] kexec: handover: add helper to check preserved page ranges
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501094637.38650-2-crisjacobmaamor@gmail.com>

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 include/linux/kexec_handover.h     |  6 +++++
 kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..fb09943ab232 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -19,6 +19,7 @@ struct page;
 #ifdef CONFIG_KEXEC_HANDOVER
 bool kho_is_enabled(void);
 bool is_kho_boot(void);
+bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages);
 
 int kho_preserve_folio(struct folio *folio);
 void kho_unpreserve_folio(struct folio *folio);
@@ -51,6 +52,11 @@ static inline bool is_kho_boot(void)
 	return false;
 }
 
+static inline bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	return false;
+}
+
 static inline int kho_preserve_folio(struct folio *folio)
 {
 	return -EOPNOTSUPP;
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 94762de1fe5f..fe9f11190705 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -10,6 +10,7 @@
 
 #define pr_fmt(fmt) "KHO: " fmt
 
+#include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
@@ -429,6 +430,40 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
 	return page;
 }
 
+/**
+ * kho_is_preserved - Verify that a physical page range belongs to KHO.
+ * @phys: physical address of the first page in the range.
+ * @nr_pages: number of pages that the caller expects to access.
+ *
+ * Use this before phys_to_virt() when a physical address comes from restored
+ * metadata. It checks that @phys starts a KHO-preserved allocation large
+ * enough to cover @nr_pages.
+ *
+ * This only checks the KHO marker. It does not restore, free, or take
+ * ownership of the pages.
+ *
+ * Return: true if @phys starts a preserved KHO allocation large enough to cover
+ * @nr_pages, false otherwise.
+ */
+bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	struct page *page;
+	union kho_page_info info;
+
+	if (!nr_pages || !IS_ALIGNED(phys, PAGE_SIZE))
+		return false;
+
+	page = pfn_to_online_page(PHYS_PFN(phys));
+	if (!page)
+		return false;
+
+	info.page_private = page->private;
+	if (info.magic != KHO_PAGE_MAGIC || info.order >= BITS_PER_LONG)
+		return false;
+
+	return nr_pages <= BIT(info.order);
+}
+
 /**
  * kho_restore_folio - recreates the folio from the preserved memory.
  * @phys: physical address of the folio.
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 02:46:34 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:34 +0800
Subject: [PATCH RFC 2/5] liveupdate: validate restored LUO FDT before use
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501094637.38650-3-crisjacobmaamor@gmail.com>

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_core.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..633a16434164 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -82,6 +82,7 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	size_t fdt_size;
 	phys_addr_t fdt_phys;
 	int err, ln_size;
 	const void *ptr;
@@ -94,7 +95,8 @@ static int __init luo_early_startup(void)
 	}
 
 	/* Retrieve LUO subtree, and verify its format. */
-	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
+	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys,
+				   &fdt_size);
 	if (err) {
 		if (err != -ENOENT) {
 			pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
@@ -105,6 +107,12 @@ static int __init luo_early_startup(void)
 		return 0;
 	}
 
+	if (!fdt_size || fdt_size > LUO_FDT_SIZE ||
+	    !kho_is_preserved(fdt_phys, DIV_ROUND_UP(fdt_size, PAGE_SIZE))) {
+		pr_err("Invalid LUO FDT from KHO\n");
+		return -EINVAL;
+	}
+
 	luo_global.fdt_in = phys_to_virt(fdt_phys);
 	err = fdt_node_check_compatible(luo_global.fdt_in, 0,
 					LUO_FDT_COMPATIBLE);
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 02:46:35 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:35 +0800
Subject: [PATCH RFC 3/5] liveupdate: validate restored LUO session metadata
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501094637.38650-4-crisjacobmaamor@gmail.com>

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_session.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..0244c071936d 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -501,7 +501,18 @@ int __init luo_session_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!kho_is_preserved(header_ser_pa, LUO_SESSION_PGCNT)) {
+		pr_err("Session header is not KHO preserved: %#llx\n",
+		       (unsigned long long)header_ser_pa);
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
+	if (header_ser->count > LUO_SESSION_MAX) {
+		pr_err("Invalid session count: %llu\n",
+		       (unsigned long long)header_ser->count);
+		return -EINVAL;
+	}
 
 	luo_session_global.incoming.header_ser = header_ser;
 	luo_session_global.incoming.ser = (void *)(header_ser + 1);
@@ -515,6 +526,7 @@ int luo_session_deserialize(void)
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
 	static int err;
+	u64 count;
 
 	/* If has been deserialized, always return the same error code */
 	if (is_deserialized)
@@ -524,6 +536,13 @@ int luo_session_deserialize(void)
 	if (!sh->active)
 		return 0;
 
+	count = sh->header_ser->count;
+	if (count > LUO_SESSION_MAX) {
+		pr_err("Invalid session count: %llu\n",
+		       (unsigned long long)count);
+		return -EINVAL;
+	}
+
 	/*
 	 * Note on error handling:
 	 *
@@ -539,7 +558,7 @@ int luo_session_deserialize(void)
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	for (int i = 0; i < sh->header_ser->count; i++) {
+	for (u64 i = 0; i < count; i++) {
 		struct luo_session *session;
 
 		session = luo_session_alloc(sh->ser[i].name);
@@ -606,4 +625,3 @@ int luo_session_serialize(void)
 
 	return err;
 }
-
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 02:46:36 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:36 +0800
Subject: [PATCH RFC 4/5] liveupdate: validate restored LUO file-set metadata
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501094637.38650-5-crisjacobmaamor@gmail.com>

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_file.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..cde43d822f8f 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -783,11 +783,21 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	struct luo_file_ser *file_ser;
 	u64 i;
 
-	if (!file_set_ser->files) {
-		WARN_ON(file_set_ser->count);
+	if (!file_set_ser->count) {
+		if (file_set_ser->files)
+			return -EINVAL;
 		return 0;
 	}
 
+	if (file_set_ser->count > LUO_FILE_MAX)
+		return -EINVAL;
+
+	if (!file_set_ser->files)
+		return -EINVAL;
+
+	if (!kho_is_preserved(file_set_ser->files, LUO_FILE_PGCNT))
+		return -EINVAL;
+
 	file_set->count = file_set_ser->count;
 	file_set->files = phys_to_virt(file_set_ser->files);
 
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 02:46:37 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Fri,  1 May 2026 17:46:37 +0800
Subject: [PATCH RFC 5/5] liveupdate: validate restored LUO FLB metadata
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501094637.38650-6-crisjacobmaamor@gmail.com>

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_flb.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 00f5494812c4..e80032669cea 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -162,6 +162,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	struct luo_flb_header *fh = &luo_flb_global.incoming;
 	struct liveupdate_flb_op_args args = {0};
 	bool found = false;
+	u64 count;
 	int err;
 
 	guard(mutex)(&private->incoming.lock);
@@ -175,7 +176,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	if (!fh->active)
 		return -ENODATA;
 
-	for (int i = 0; i < fh->header_ser->count; i++) {
+	count = fh->header_ser->count;
+	if (count > LUO_FLB_MAX) {
+		pr_err("Invalid FLB count: %llu\n",
+		       (unsigned long long)count);
+		return -EINVAL;
+	}
+
+	for (u64 i = 0; i < count; i++) {
 		if (!strcmp(fh->ser[i].name, flb->compatible)) {
 			private->incoming.data = fh->ser[i].data;
 			private->incoming.count = fh->ser[i].count;
@@ -620,7 +628,20 @@ int __init luo_flb_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!kho_is_preserved(header_ser_pa, LUO_FLB_PGCNT)) {
+		pr_err("FLB header is not KHO preserved: %#llx\n",
+		       (unsigned long long)header_ser_pa);
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
+	if (header_ser->pgcnt != LUO_FLB_PGCNT ||
+	    header_ser->count > LUO_FLB_MAX) {
+		pr_err("Invalid FLB header: pgcnt %llu count %llu\n",
+		       (unsigned long long)header_ser->pgcnt,
+		       (unsigned long long)header_ser->count);
+		return -EINVAL;
+	}
 
 	luo_flb_global.incoming.header_ser = header_ser;
 	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
-- 
2.53.0



From gregkh at linuxfoundation.org  Fri May  1 03:11:55 2026
From: gregkh at linuxfoundation.org (Greg Kroah-Hartman)
Date: Fri, 1 May 2026 12:11:55 +0200
Subject: [PATCH RFC 1/5] kexec: handover: add helper to check preserved
 page ranges
In-Reply-To: <20260501094637.38650-2-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501094637.38650-2-crisjacobmaamor@gmail.com>
Message-ID: <2026050114-glazing-bankbook-28bc@gregkh>

On Fri, May 01, 2026 at 05:46:33PM +0800, Cris Jacob Maamor wrote:
> Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>

For obvious reasons, we can't take patches without any changelog text
(nor do you want us to.)

And when a pathc is "RFC", what specific comments are you asking for?
Why don't you feel this is good enough to take now?

thanks,

greg k-h


From dmatlack at google.com  Fri May  1 09:57:25 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 1 May 2026 09:57:25 -0700
Subject: [PATCH v4 01/11] PCI: liveupdate: Set up FLB handler for the PCI core
In-Reply-To: <20260430210642.GA439800@bhelgaas>
References: <afFHC6C1Qbwio4pH@google.com> <20260430210642.GA439800@bhelgaas>
Message-ID: <CALzav=exRqf3XZWDwUx-OXp8TgzpmQ-iUTixpT4SVRk8nxctrQ@mail.gmail.com>

On Thu, Apr 30, 2026 at 2:06?PM Bjorn Helgaas <helgaas at kernel.org> wrote:
> On Tue, Apr 28, 2026 at 11:47:23PM +0000, David Matlack wrote:
> > On 2026-04-28 05:50 PM, Pasha Tatashin wrote:

> > > This is the way we agreed to handle kexec changes: Baoquan He is the
> > > maintainer, and without his Reviewed-by tag, we won't take changes to
> > > kexec. This is the approach we follow with MM for KHO changes to
> > > memblock and memfd preservation, as well as the upcoming
> > > hugetlb/guestmemfd preservation.
> > >
> > > This is also the approach we should continue using when adding LUO
> > > support to other components like PCI, VFIO, IOMMU, and KVM. It keeps
> > > life easier for the core component maintainers and ensures we do not
> > > regress LU by staging everything in the same tree and sending LU merge
> > > requests from a single tree.
> >
> > Ok it sounds like we are aligned on keeping drivers/pci/liveupdate.c,
> > include/linux/kho/abi/pci.h, and Documentation/PCI/liveupdate.rst in the
> > PCI LIVE UPDATE entry and not duplicating them in the LIVE UPDATE entry.
> >
> > I think the only open question is what tree to use for the PCI LIVE
> > UPDATE entry, PCI tree or Live Update tree. You are proposing the Live
> > Update tree.
> >
> > Bjorn are you ok with that approach?
>
> Yes, I think that makes sense, at least to start.  In early days, it
> would probably be a headache to coordinate LU things across multiple
> trees.

Sounds good. I will swap the trees in v5. Thanks all.


From crisjacobmaamor at gmail.com  Fri May  1 10:30:48 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:48 +0800
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-1-crisjacobmaamor@gmail.com>

LUO restores metadata from KHO/FDT during liveupdate. The restored
metadata contains physical addresses and count fields used to access and
walk preserved session, file set, and FLB arrays.

This series adds a non-consuming KHO preserved-range check and uses it
before phys_to_virt() on restored metadata addresses. It also rejects
restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
before traversal.

As far as I can tell, this is root/admin-only; I do not have evidence
that a normal unprivileged user can trigger it directly.

Changes since v1:
- Dropped RFC marking.
- Added changelog text to each patch.
- No code changes.

Cris Jacob Maamor (5):
  kexec: handover: add helper to check preserved page ranges
  liveupdate: validate LUO FDT physical address before mapping
  liveupdate: validate restored LUO session metadata
  liveupdate: validate restored LUO file set metadata
  liveupdate: validate restored LUO FLB metadata

 include/linux/kexec_handover.h     |  6 +++++
 kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++
 kernel/liveupdate/luo_core.c       | 10 ++++++++-
 kernel/liveupdate/luo_file.c       | 14 ++++++++++--
 kernel/liveupdate/luo_flb.c        | 23 +++++++++++++++++++-
 kernel/liveupdate/luo_session.c    | 22 +++++++++++++++++--
 6 files changed, 104 insertions(+), 6 deletions(-)

-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 10:30:49 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:49 +0800
Subject: [PATCH v2 1/5] kexec: handover: add helper to check preserved page ranges
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-2-crisjacobmaamor@gmail.com>

Restore code should not use phys_to_virt() on physical addresses from
restored metadata until it verifies that the range was preserved by KHO.

Add kho_is_preserved() so callers can check a preserved page range without
restoring, freeing, or taking ownership of the pages.

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 include/linux/kexec_handover.h     |  6 +++++
 kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..fb09943ab232 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -19,6 +19,7 @@ struct page;
 #ifdef CONFIG_KEXEC_HANDOVER
 bool kho_is_enabled(void);
 bool is_kho_boot(void);
+bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages);
 
 int kho_preserve_folio(struct folio *folio);
 void kho_unpreserve_folio(struct folio *folio);
@@ -51,6 +52,11 @@ static inline bool is_kho_boot(void)
 	return false;
 }
 
+static inline bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	return false;
+}
+
 static inline int kho_preserve_folio(struct folio *folio)
 {
 	return -EOPNOTSUPP;
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 94762de1fe5f..fe9f11190705 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -10,6 +10,7 @@
 
 #define pr_fmt(fmt) "KHO: " fmt
 
+#include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
@@ -429,6 +430,40 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
 	return page;
 }
 
+/**
+ * kho_is_preserved - Verify that a physical page range belongs to KHO.
+ * @phys: physical address of the first page in the range.
+ * @nr_pages: number of pages that the caller expects to access.
+ *
+ * Use this before phys_to_virt() when a physical address comes from restored
+ * metadata. It checks that @phys starts a KHO-preserved allocation large
+ * enough to cover @nr_pages.
+ *
+ * This only checks the KHO marker. It does not restore, free, or take
+ * ownership of the pages.
+ *
+ * Return: true if @phys starts a preserved KHO allocation large enough to cover
+ * @nr_pages, false otherwise.
+ */
+bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	struct page *page;
+	union kho_page_info info;
+
+	if (!nr_pages || !IS_ALIGNED(phys, PAGE_SIZE))
+		return false;
+
+	page = pfn_to_online_page(PHYS_PFN(phys));
+	if (!page)
+		return false;
+
+	info.page_private = page->private;
+	if (info.magic != KHO_PAGE_MAGIC || info.order >= BITS_PER_LONG)
+		return false;
+
+	return nr_pages <= BIT(info.order);
+}
+
 /**
  * kho_restore_folio - recreates the folio from the preserved memory.
  * @phys: physical address of the folio.
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 10:30:50 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:50 +0800
Subject: [PATCH v2 2/5] liveupdate: validate LUO FDT physical address before mapping
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-3-crisjacobmaamor@gmail.com>

LUO gets the restored FDT address from KHO and maps it with phys_to_virt().

Check the FDT size and make sure the address range is KHO-preserved before
mapping it. Reject empty or oversized FDT metadata.

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_core.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..633a16434164 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -82,6 +82,7 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	size_t fdt_size;
 	phys_addr_t fdt_phys;
 	int err, ln_size;
 	const void *ptr;
@@ -94,7 +95,8 @@ static int __init luo_early_startup(void)
 	}
 
 	/* Retrieve LUO subtree, and verify its format. */
-	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
+	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys,
+				   &fdt_size);
 	if (err) {
 		if (err != -ENOENT) {
 			pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
@@ -105,6 +107,12 @@ static int __init luo_early_startup(void)
 		return 0;
 	}
 
+	if (!fdt_size || fdt_size > LUO_FDT_SIZE ||
+	    !kho_is_preserved(fdt_phys, DIV_ROUND_UP(fdt_size, PAGE_SIZE))) {
+		pr_err("Invalid LUO FDT from KHO\n");
+		return -EINVAL;
+	}
+
 	luo_global.fdt_in = phys_to_virt(fdt_phys);
 	err = fdt_node_check_compatible(luo_global.fdt_in, 0,
 					LUO_FDT_COMPATIBLE);
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 10:30:51 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:51 +0800
Subject: [PATCH v2 3/5] liveupdate: validate restored LUO session metadata
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-4-crisjacobmaamor@gmail.com>

The restored FDT contains the physical address of the LUO session header,
which LUO maps before reading session metadata. Check that physical range
against KHO-preserved memory before calling phys_to_virt(), and abort the
restore if the physical range is not covered.

The session header has a count that controls how far LUO walks the
serialized session array. Check that count against LUO_SESSION_MAX before
deserialization, and abort the restore if the value is out of range.

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_session.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..0244c071936d 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -501,7 +501,18 @@ int __init luo_session_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!kho_is_preserved(header_ser_pa, LUO_SESSION_PGCNT)) {
+		pr_err("Session header is not KHO preserved: %#llx\n",
+		       (unsigned long long)header_ser_pa);
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
+	if (header_ser->count > LUO_SESSION_MAX) {
+		pr_err("Invalid session count: %llu\n",
+		       (unsigned long long)header_ser->count);
+		return -EINVAL;
+	}
 
 	luo_session_global.incoming.header_ser = header_ser;
 	luo_session_global.incoming.ser = (void *)(header_ser + 1);
@@ -515,6 +526,7 @@ int luo_session_deserialize(void)
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
 	static int err;
+	u64 count;
 
 	/* If has been deserialized, always return the same error code */
 	if (is_deserialized)
@@ -524,6 +536,13 @@ int luo_session_deserialize(void)
 	if (!sh->active)
 		return 0;
 
+	count = sh->header_ser->count;
+	if (count > LUO_SESSION_MAX) {
+		pr_err("Invalid session count: %llu\n",
+		       (unsigned long long)count);
+		return -EINVAL;
+	}
+
 	/*
 	 * Note on error handling:
 	 *
@@ -539,7 +558,7 @@ int luo_session_deserialize(void)
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	for (int i = 0; i < sh->header_ser->count; i++) {
+	for (u64 i = 0; i < count; i++) {
 		struct luo_session *session;
 
 		session = luo_session_alloc(sh->ser[i].name);
@@ -606,4 +625,3 @@ int luo_session_serialize(void)
 
 	return err;
 }
-
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 10:30:52 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:52 +0800
Subject: [PATCH v2 4/5] liveupdate: validate restored LUO file set metadata
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-5-crisjacobmaamor@gmail.com>

The restored session metadata provides the LUO file set address and count.
LUO maps that address with phys_to_virt() and uses the restored count to
walk the serialized file array.

Reject invalid empty file set metadata, reject counts above LUO_FILE_MAX,
and check that the physical range is KHO-preserved before mapping it.

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_file.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..cde43d822f8f 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -783,11 +783,21 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	struct luo_file_ser *file_ser;
 	u64 i;
 
-	if (!file_set_ser->files) {
-		WARN_ON(file_set_ser->count);
+	if (!file_set_ser->count) {
+		if (file_set_ser->files)
+			return -EINVAL;
 		return 0;
 	}
 
+	if (file_set_ser->count > LUO_FILE_MAX)
+		return -EINVAL;
+
+	if (!file_set_ser->files)
+		return -EINVAL;
+
+	if (!kho_is_preserved(file_set_ser->files, LUO_FILE_PGCNT))
+		return -EINVAL;
+
 	file_set->count = file_set_ser->count;
 	file_set->files = phys_to_virt(file_set_ser->files);
 
-- 
2.53.0



From crisjacobmaamor at gmail.com  Fri May  1 10:30:53 2026
From: crisjacobmaamor at gmail.com (Cris Jacob Maamor)
Date: Sat,  2 May 2026 01:30:53 +0800
Subject: [PATCH v2 5/5] liveupdate: validate restored LUO FLB metadata
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <20260501173053.73116-6-crisjacobmaamor@gmail.com>

The restored FDT contains the physical address of the LUO FLB header, which
LUO maps before using the restored FLB metadata.

Check that the FLB header range is KHO-preserved before calling
phys_to_virt(). Reject invalid page counts and counts above LUO_FLB_MAX
before walking the restored FLB array.

Signed-off-by: Cris Jacob Maamor <crisjacobmaamor at gmail.com>
---
 kernel/liveupdate/luo_flb.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 00f5494812c4..e80032669cea 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -162,6 +162,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	struct luo_flb_header *fh = &luo_flb_global.incoming;
 	struct liveupdate_flb_op_args args = {0};
 	bool found = false;
+	u64 count;
 	int err;
 
 	guard(mutex)(&private->incoming.lock);
@@ -175,7 +176,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	if (!fh->active)
 		return -ENODATA;
 
-	for (int i = 0; i < fh->header_ser->count; i++) {
+	count = fh->header_ser->count;
+	if (count > LUO_FLB_MAX) {
+		pr_err("Invalid FLB count: %llu\n",
+		       (unsigned long long)count);
+		return -EINVAL;
+	}
+
+	for (u64 i = 0; i < count; i++) {
 		if (!strcmp(fh->ser[i].name, flb->compatible)) {
 			private->incoming.data = fh->ser[i].data;
 			private->incoming.count = fh->ser[i].count;
@@ -620,7 +628,20 @@ int __init luo_flb_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!kho_is_preserved(header_ser_pa, LUO_FLB_PGCNT)) {
+		pr_err("FLB header is not KHO preserved: %#llx\n",
+		       (unsigned long long)header_ser_pa);
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
+	if (header_ser->pgcnt != LUO_FLB_PGCNT ||
+	    header_ser->count > LUO_FLB_MAX) {
+		pr_err("Invalid FLB header: pgcnt %llu count %llu\n",
+		       (unsigned long long)header_ser->pgcnt,
+		       (unsigned long long)header_ser->count);
+		return -EINVAL;
+	}
 
 	luo_flb_global.incoming.header_ser = header_ser;
 	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
-- 
2.53.0



From skhawaja at google.com  Fri May  1 11:17:10 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Fri, 1 May 2026 18:17:10 +0000
Subject: [PATCH v4 02/11] PCI: liveupdate: Track outgoing preserved PCI
 devices
In-Reply-To: <afPGYp145FbrvURR@google.com>
References: <20260423212316.3431746-1-dmatlack@google.com>
 <20260423212316.3431746-3-dmatlack@google.com>
 <afDqs6oqWlhoVqMN@google.com>
 <afPGYp145FbrvURR@google.com>
Message-ID: <afTgOXpUNWMapPAS@google.com>

On Thu, Apr 30, 2026 at 09:15:14PM +0000, David Matlack wrote:
>On 2026-04-28 05:24 PM, Samiullah Khawaja wrote:
>> On Thu, Apr 23, 2026 at 09:23:06PM +0000, David Matlack wrote:
>
>> > +	for (i = 0; i < ser->max_nr_devices; i++) {
>> > +		/*
>> > +		 * Start searching at index ser->nr_devices. This should result
>> > +		 * in a constant time search under expected conditions (devices
>> > +		 * are not getting unpreserved).
>> > +		 */
>> > +		int index = (ser->nr_devices + i) % ser->max_nr_devices;
>> > +		struct pci_dev_ser *dev_ser = &ser->devices[index];
>>
>> nit: Maybe we can move this logic in a separate function as down the road
>> when we expand this to add VFs and Hotpluggable devices, this might
>> change significantly? It's good if it is self-contained.
>
>Did you mean to leave this comment on pci_flb_preserve() where it
>decides how many devices to allocate room for?

I was talking about this one, as I think depending on the scheme we take
this might change significantly. Just a nit, you can ignore it.
>
>> > +static inline struct pci_dev_ser *pci_liveupdate_outgoing(struct pci_dev *dev)
>> > +{
>> > +	return dev->liveupdate_outgoing;
>> > +}
>>
>> Is this expected to be called under the outgoing lock?
>
>For now this API is only used during shutdown, at which point userspace
>should have already been stopped so drivers should not be changing the
>preservation status of an outgoing device. So I don't think this needs
>to be under the outgoing lock, but it would be nice to have some more
>explicit synchronization.

Ok that makes sense. I have similar cases in my series, but maybe we can
add kdoc regarding these stating in which context this is expected to be
used?


From dmatlack at google.com  Fri May  1 11:25:26 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 1 May 2026 11:25:26 -0700
Subject: [PATCH v4 02/11] PCI: liveupdate: Track outgoing preserved PCI devices
In-Reply-To: <afTgOXpUNWMapPAS@google.com>
References: <20260423212316.3431746-1-dmatlack@google.com> <20260423212316.3431746-3-dmatlack@google.com>
 <afDqs6oqWlhoVqMN@google.com> <afPGYp145FbrvURR@google.com> <afTgOXpUNWMapPAS@google.com>
Message-ID: <CALzav=cJLA3-gK=Ljfqf=YCPML60F=hM_teB67M_3PNCZE4T9w@mail.gmail.com>

On Fri, May 1, 2026 at 11:17?AM Samiullah Khawaja <skhawaja at google.com> wrote:
>
> On Thu, Apr 30, 2026 at 09:15:14PM +0000, David Matlack wrote:
> >On 2026-04-28 05:24 PM, Samiullah Khawaja wrote:
> >> On Thu, Apr 23, 2026 at 09:23:06PM +0000, David Matlack wrote:
> >
> >> > +  for (i = 0; i < ser->max_nr_devices; i++) {
> >> > +          /*
> >> > +           * Start searching at index ser->nr_devices. This should result
> >> > +           * in a constant time search under expected conditions (devices
> >> > +           * are not getting unpreserved).
> >> > +           */
> >> > +          int index = (ser->nr_devices + i) % ser->max_nr_devices;
> >> > +          struct pci_dev_ser *dev_ser = &ser->devices[index];
> >>
> >> nit: Maybe we can move this logic in a separate function as down the road
> >> when we expand this to add VFs and Hotpluggable devices, this might
> >> change significantly? It's good if it is self-contained.
> >
> >Did you mean to leave this comment on pci_flb_preserve() where it
> >decides how many devices to allocate room for?
>
> I was talking about this one, as I think depending on the scheme we take
> this might change significantly. Just a nit, you can ignore it.

It would depend on if userspace is doing a lot of unpreserving as
well... I'll see what I can do in v5.

> >
> >> > +static inline struct pci_dev_ser *pci_liveupdate_outgoing(struct pci_dev *dev)
> >> > +{
> >> > +  return dev->liveupdate_outgoing;
> >> > +}
> >>
> >> Is this expected to be called under the outgoing lock?
> >
> >For now this API is only used during shutdown, at which point userspace
> >should have already been stopped so drivers should not be changing the
> >preservation status of an outgoing device. So I don't think this needs
> >to be under the outgoing lock, but it would be nice to have some more
> >explicit synchronization.
>
> Ok that makes sense. I have similar cases in my series, but maybe we can
> add kdoc regarding these stating in which context this is expected to be
> used?

Yes will do. I'll add some documentation and also restrict it to avoid
misuse (move to internal pci.h, return a bool instead of pointer, and
use READ_ONCE()).


From pasha.tatashin at soleen.com  Fri May  1 12:34:25 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 1 May 2026 19:34:25 +0000
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <20260501173053.73116-1-crisjacobmaamor@gmail.com>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
Message-ID: <afT_xHDuFwAUSR0X@plex>

On 05-02 01:30, Cris Jacob Maamor wrote:
> LUO restores metadata from KHO/FDT during liveupdate. The restored
> metadata contains physical addresses and count fields used to access and
> walk preserved session, file set, and FLB arrays.
> 
> This series adds a non-consuming KHO preserved-range check and uses it
> before phys_to_virt() on restored metadata addresses. It also rejects
> restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
> before traversal.
> 
> As far as I can tell, this is root/admin-only; I do not have evidence
> that a normal unprivileged user can trigger it directly.
> 
> Changes since v1:
> - Dropped RFC marking.
> - Added changelog text to each patch.
> - No code changes.
> 
> Cris Jacob Maamor (5):
>   kexec: handover: add helper to check preserved page ranges
>   liveupdate: validate LUO FDT physical address before mapping
>   liveupdate: validate restored LUO session metadata
>   liveupdate: validate restored LUO file set metadata
>   liveupdate: validate restored LUO FLB metadata

I have replied separately in the security report to clarify that this is 
not a bug. The behavior follows the ABI specification exactly: we use 
the PA addresses and ranges provided by the KHO FDT tree.

NAK

> 
>  include/linux/kexec_handover.h     |  6 +++++
>  kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++
>  kernel/liveupdate/luo_core.c       | 10 ++++++++-
>  kernel/liveupdate/luo_file.c       | 14 ++++++++++--
>  kernel/liveupdate/luo_flb.c        | 23 +++++++++++++++++++-
>  kernel/liveupdate/luo_session.c    | 22 +++++++++++++++++--
>  6 files changed, 104 insertions(+), 6 deletions(-)
> 
> -- 
> 2.53.0
> 


From pasha.tatashin at soleen.com  Fri May  1 14:48:12 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 1 May 2026 21:48:12 +0000
Subject: [RFC] proposal: KVM: Orphaned VMs: The Caretaker approach for
 Live Update
In-Reply-To: <0a71472c-b397-4699-a518-61faffcf4ab2@redhat.com>
References: <afEwWZksU0Fw61oT@plex>
 <0a71472c-b397-4699-a518-61faffcf4ab2@redhat.com>
Message-ID: <afUNlL_lRDP6olHE@plex>

On 04-30 15:28, Paolo Bonzini wrote:
> I have some very similar observations to Alex and some very similar
> observations to David.  This has to imply that everyone will agree with me.
> :)
> 
> Seriously, the main contention point, from reading the thread, is the
> placement and lifecycle of the caretaker.  More on this later...
> 
> On 4/29/26 00:29, Pasha Tatashin wrote:
> > While this proposal focuses on its critical role in minimally disruptive
> > Live Update, the Caretaker is fundamentally designed as an extensible
> > primitive. Its architecture allows it to be leveraged for a variety of
> > other advanced virtualization use cases, such as running custom
> > lightweight hypervisors or completely offloading virtualization duties
> > to an accelerator card.
> 
> One step at a time please---and as an initial step, just place it inside the
> kernel, a la Arm nVHE.
> 
> Since your design would have anyway the ability to update the caretaker, you
> can embed that part into the reattachment process, so that the new kernel
> can use its own caretaker.

This is a good point. If we make the Caretaker built-in and always 
enabled via a module parameter, the update to new Caretaker can be 
handled in the new kernel when we adopt the Orphaned VM thread during 
reattachment.

> This reduces a lot the need to establish a stable-ish ABI.  Only the
> handover (kexec/LUO) needs to be stable, so that the new kernel can populate
> its kvm and kvm_vcpu structs.  And for that we mostly have a solution
> already: a stream of serialized ioctls.

The way I see it, vmfd and vcpufd need to support LUO preservation by 
implementing the liveupdate_file_ops callbacks (.preserve, .restore).

When userspace preserves the vcpufd, the kernel isolates the assigned 
pCPU (probably requiring the vCPU to be pinned to the CPU, and the CPU 
to be in an isolated cpuset), offlines it from the kernel's perspective, 
and signals to the Caretaker that KVM is detached.

We can start with the simplest form of the Caretaker for a PoC:

* No ASI. It duplicates the kernel page tables and uses its own mappings.
* It does not handle any VM exits locally. It passes everything down to 
KVM during normal operation, and treats all exits as blocking events 
when detached.

The PoC can be demonstrated without kexec: execute vcpufd .preserve() to 
isolate and offline the pCPU, and later use LUO .retrieve() to online 
the pCPU and re-adopt the running vCPU thread.

Even for this simplest form, we still need a defined ABI between the 
host kernel and the Caretaker. The host must send an IPI to 
synchronously notify the Caretaker of attach and detach transitions. 
This ABI must also handle Caretaker replacement during the adoption 
phase: when the new kernel retrieves the vCPU, it requires a protocol to 
notify the running Caretaker that the pCPU is being onlined. The 
Caretaker must reach a known, state at that moment so the kernel can 
seamlessly replace the previous kernel's Caretaker version with the 
current one. Finally, the Caretaker still relies on the CCB to access 
KVM routing pointers for forwarding VM exits back to the host during 
normal operation.

> > During the execution of the KVM_SET_CARETAKER ioctl, instead of
> > pointing the hardware's return path to standard KVM entry points (e.g.,
> > vmx_vmexit or svm_vcpu_run), KVM reprograms the host-state return area
> > of the CPU's hardware virtualization control structures (e.g., Intel
> > VMCS, AMD VMCB, or ARM equivalent) to point directly into the
> > bare-metal Caretaker environment.
> 
> This can be done unconditionally for all VMs based on a module parameter,
> again as in Arm nVHE.

Makes sense.

> 
> > Note on Optimization vs. Security: Constantly switching the page table
> > (CR3) on every VM Exit can be expensive due to TLB flushing. To
> > optimize performance, the Caretaker can share the host kernel's page
> > tables while the kernel is still around, and dynamically replace
> > HOST_CR3 with the dedicated, isolated page tables only when the vCPU is
> > orphaned (during the detachment phase). On the other hand, maintaining
> > a permanently isolated CR3 for the Caretaker adds a strong security
> > boundary, achieving hardware-enforced separation similar to KVM Address
> > Space Isolation (ASI).
> 
> Agreed on this.
> 
> > The Caretaker requires a defined ABI to communicate with the host KVM
> > subsystem. This ABI is implemented via the shared, identity-mapped .ccb
> > section of the ELF payload, acting as the Caretaker Control Block
> > (CCB).
> > 
> > The CCB acts as the source of truth for the Caretaker's execution loop
> > and contains three primary elements:
> > 
> >    * Attachment State Flag: An atomic variable indicating the current
> >      relationship with the host KVM subsystem (e.g., KVM_ATTACHED or
> >      KVM_DETACHED).
> 
> This must be done atomically at the time Linux offlines/onlines a pCPU. The
> interface from Linux to the caretaker must use some kind of IPI so that the
> new kernel can force a VMEXIT (if needed) in the caretaker, ask it to
> serialize the vm state, and pass it down to the new kernel's caretaker.

Yes, agree.

> 
> >    * KVM Routing Pointers: The physical function pointers that the
> >      Caretaker uses to safely jump into the host KVM's standard VM Exit
> >      handlers when operating in normal mode.
> >    * Shared Configuration Metadata: A physical pointer to dedicated
> >      memory pages used by the kernel to share dynamic vCPU configuration
> >      data with the Caretaker. Because every guest is configured
> >      differently, KVM populates these pages with the specific parameters
> >      negotiated during VM initialization (such as CPUID feature masks,
> >      APIC routing, and timer states). These pages also include a
> >      pre-allocated Telemetry Buffer for the Caretaker to log VM Exits
> >      and spin-wait durations. These dedicated pages are explicitly
> >      preserved across the host reboot via KHO, ensuring the Caretaker
> >      maintains continuous access to the exact context required to
> >      accurately emulate trivial exits during the gap.
> 
> All this is mostly unnecessary if the caretaker is provided by the kernel.
> The recently introduced remote ring buffers can be used for tracing too.

I will need to learn more about how remote ring buffers work, but it 
would have to be possible to use them even when there is no kernel. So, 
as I understand it, something like this would happen:

The old kernel allocates the remote ring buffer, passes the physical 
address to the Caretaker, and preserves this memory via KHO.

During the kexec gap (when the host kernel is completely offline), the 
Caretaker acts purely as a producer, writing trace events directly to 
this physical memory block and advancing the ring buffer pointers.

When the new kernel boots and re-adopts the orphaned vCPU, it retrieves 
this memory from KHO and attaches it back to the tracing subsystem.

> > The Caretaker first evaluates the VM Exit reason. If the exit belongs to
> > a category that the Caretaker is programmed to resolve natively, it
> > handles it internally. For example, profiling of guests has identified
> > the following exit categories for potential local resolution:
> > 
> >    * Guest Idle Exits (e.g., HLT): When the guest OS goes idle, it
> >      triggers idle exits. The Caretaker intercepts these and halts the
> >      physical core until the next guest-bound interrupt fires, preserving
> >      host power.
> 
> I don't think HLT can be handled entirely here.  Either you skip the exit
> completely or you have to go out to the scheduler.  The HLT exit could be
> skipped unconditionally for an orphaned VM, but while there is a running
> kernel the caretaker has to run entirely with interrupts off and that limits
> what you can do.
> 
> In fact there is already a blueprint of what can be handled easily in the
> caretaker, namely vmx_exit_handlers_fastpath()/svm_exit_handlers_fastpath().
> Stick to what exists already.

I agree regarding HLT: during the gap, the simplest approach is to just 
skip the exit and return directly to the VM without attempting to handle 
it.

Also, yes, vmx_exit_handlers_fastpath() and svm_exit_handlers_fastpath() 
provide a good blueprint for the VM exits that can be handled directly 
in the Caretaker.

> 
> >    * Timer and APIC Exits: Even an idle guest frequently writes to
> >      interrupt controllers and system registers to configure internal
> >      timers. The Caretaker handles these trivial writes directly,
> >      acknowledging the timer updates.
> 
> This depends heavily on the implementation of the hypervisor, for example it
> can be done on Intel via the preemption timer but not on AMD where an actual
> hrtimer is needed.

The Caretaker will have varying degrees of support depending on the 
architecture.

For the initial PoC, we can start with the most primitive form: treating 
these as blocking exits across all relevant architectures (Intel, AMD, 
ARM).

Beyond the PoC, we will expand this functionality independently per 
platform to reduce the blackout window, berpaps based on fastpath 
support. If a specific architecture lacks the hardware features to 
handle a certain exit natively in the Caretaker, it will remain a 
blocking exit on that platform during the gap.

> 
> [...]
> 
> > When the new VMM process spawns, it retrieves the
> >     preserved session and issues LIVEUPDATE_SESSION_RETRIEVE_FD using
> >     its token. LUO invokes KVM's .retrieve() callback to map the
> >     preserved vcpufd back into the new VMM's file descriptor table. As
> >     part of this retrieval process, the host formally brings the
> >     isolated pCPU back online, and the new VMM userspace thread is
> >     attached back to the active VM thread running on the vCPU. Finally,
> >     KVM populates the new KVM Routing Pointers in the CCB and
> >     atomically flips the Host State Flag back to KVM_ATTACHED. This
> >     breaks the Caretaker's spin-wait loop (if it is in this state),
> >     allowing standard KVM operation to resume.
> 
> This would also include some kind of serialization of the old VM into the
> new kernel's struct kvm_vcpu.

> 
> Also some kind of feature negotiation is needed (if that fails, the VMs are
> terminated unceremoniously) so I believe that the transition into and out of
> the gap must be synchronous.  For example with INIT/SIPI for the entry, and
> an IPI for the exit?

Yes, the serialization of the old VM state into the new kernel's struct 
kvm_vcpu is handled via KHO (Kexec Handover) during the LUO .preserve() 
and .retrieve() phases.

Regarding feature negotiation, compatibility checks are a fundamental 
requirement for live update. If the new kernel does not support the 
features required by the preserved VM, the update should ideally be 
aborted before the point of no return, or as you said, the VM must be 
terminated unceremoniously.

I completely agree that the transition into and out of the gap must be 
synchronous. As discussed above, using an IPI is the right approach. For 
entry, the host kernel signals the Caretaker via IPI to ensure it 
reaches a known state before the pCPU is offlined. For exit, the new 
kernel sends an IPI to the orphaned pCPU to force a VM exit, allowing 
the new kernel to take control, update the Caretaker environment, 
and complete the reattachment.

> > Guest-to-Guest IPIs
> > -------------------
> > 
> >    * The Problem: If the guest OS attempts to wake up a sleeping thread,
> >      one orphaned vCPU will send an Inter-Processor Interrupt (IPI) to
> >      another orphaned vCPU. In standard virtualization without hardware
> >      assistance, writing to the APIC ICR (or sending an ARM SGI) causes
> >      a VM Exit so the host KVM can emulate the message delivery. During
> >      the gap, KVM is unavailable to route this message.
> > 
> >    * Proposed Solution: The architecture may leverage hardware virtualized
> >      interrupts (Intel APICv, AMD AVIC, or ARM GICv4.1 virtual SGIs).
> >      This allows the hardware silicon to handle IPI delivery between the
> >      isolated pCPUs natively, eliminating the VM Exit. Alternatively,
> >      the Caretaker can be programmed to emulate the IPI delivery. By
> >      utilizing the shared memory metadata, the Caretaker can determine
> >      the target vCPU and directly update its pending interrupt state.
> 
> Yeah, I think APIC emulation to some extent must be moved into the VMX/SVM
> fastpaths.  The good news is that this can be done already as a PoC without
> needing the whole caretaker and LUO infrastructure.

Moving APIC emulation into the VMX/SVM fastpaths makes sense as a 
standalone effort.

> 
> >    * The Problem: What happens if a Non-Maskable Interrupt (NMI), a
> >      hardware timer tick, or a Machine Check Exception / System Error
> >      (MCE / ARM SError) arrives while the CPU is actively executing
> >      Caretaker code in KVM_DETACHED mode?
> > 
> >    * Proposed Solution: To safely handle these asynchronous events, [...]
> >      on x86, when transitioning into the gap, KVM explicitly programs
> >      HOST_IDTR and HOST_GDTR to [the caretaker's] tables.
> 
> Agreed and this also shows that the transition must be synchronous.
> >    * The Problem: As the guest executes, it may attempt to access memory
> >      that has not yet been mapped by the hypervisor, or it may interact
> >      with MMIO regions. Normally, this triggers an EPT Violation (Intel)
> >      or NPT Page Fault (AMD), prompting KVM to allocate host pages and
> >      update the secondary page tables. How are these updates handled
> >      when the host KVM subsystem is offline during the gap?
> > 
> >    * Proposed Solution: During the "Management Gap," there are absolutely
> >      no updates made to the NPT/EPT. The existing secondary page tables
> >      are fully preserved in memory via LUO kvmfd preservation prior to
> >      detachment, allowing the guest to seamlessly access all previously
> >      mapped memory. If the guest triggers a new page fault (requiring an
> >      NPT/EPT update) during the gap, the Caretaker simply categorizes it
> >      as a Blocking Exit.
> 
> Yes, by default everything is a blocking exit.  In particular, unless one
> day we do x86/pKVM, page tables can be handled entirely by Linux rather than
> the caretaker with no change to the existing MMU notifier architecture.
> 
> As a consequence, the caretaker is absolutely not going to be a TCB---at
> least not in the beginning.

+1.

> 
> > Compromised Caretaker
> > ---------------------
> > 
> >    * The Problem: The Caretaker runs in Host Mode. If left unprotected,
> >      this could allow a lightly privileged userspace process (e.g., QEMU
> >      or crosvm) to inject arbitrary executable code directly into the
> >      CPU's most privileged hardware state (VMX Root / Ring 0 / EL2).
> > 
> >    * Proposed Solution: To mitigate this risk, the KVM_SET_CARETAKER
> >      ioctl may adopt the security model used by the kexec_file_load()
> >      syscall. Rather than trusting userspace to pass physical addresses,
> >      the kernel must take full ownership of payload validation:
> 
> -EOVERENGINEERED.  Just shove it into the kernel.

Ok.

> 
> > Caretaker Update
> > ----------------
> > 
> >    * The Problem: Given that the Caretaker is permanently installed
> >      during VM setup, how does it get updated on long-running VMs?
> 
> Via kexec. :)  I understand you have bigger plans, but we need to crawl
> before walk^Wattempting a marathon.

Sure.

> 
> I even wonder if, for long term simplicity, the interface for
> host->caretaker should be just for the caretaker to swallow the host into
> non-root mode, again as in Arm nVHE.  That would make it much harder to
> implement some kind of live update, but my answer to that *really* is just
> to use kexec.
> 
> Paolo
> 


From pasha.tatashin at soleen.com  Fri May  1 15:07:20 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 1 May 2026 22:07:20 +0000
Subject: [RFC] proposal: KVM: Orphaned VMs: The Caretaker approach for
 Live Update
In-Reply-To: <718a82870c8f3c913791f12a993e11b2d26d08d9.camel@infradead.org>
References: <afEwWZksU0Fw61oT@plex>
 <0a71472c-b397-4699-a518-61faffcf4ab2@redhat.com>
 <c004f333ed4f7dedae1383f22d43b009cd83837e.camel@infradead.org>
 <3ff53353-3842-4a63-80a1-90a60d09fe02@redhat.com>
 <718a82870c8f3c913791f12a993e11b2d26d08d9.camel@infradead.org>
Message-ID: <afUf7d3Zg2pBCfxs@plex>

On 05-01 09:56, David Woodhouse wrote:
> On Fri, 2026-05-01 at 05:32 +0200, Paolo Bonzini wrote:
> > On 4/30/26 17:27, David Woodhouse wrote:
> > > On Thu, 2026-04-30 at 15:28 +0200, Paolo Bonzini wrote:
> > > > I even wonder if, for long term simplicity, the interface for
> > > > host->caretaker should be just for the caretaker to swallow the host
> > > > into non-root mode, again as in Arm nVHE.
> > > 
> > > There's a lot of merit in that approach.
> > > 
> > > I talked about wanting to use this 'caretaker' for secret hiding.? But
> > > why have *voluntary* secret hiding with the kernel hiding things from
> > > its own address space, when you have have *mandatory* secret hiding
> > > with something running in EL2, like pKVM.
> > 
> > Well, other than because it's a lot of work? :)
> 
> If we avoided those things then we've never have any fun!
> 
> And in a week where there seems to be a new user-to-root exploit posted
> every day, the 'deprivilege the VMM and assume the guest has owned it'
> security model is looking rather scary. So the additional defence in
> depth of knowing that even *root* can't get the kernel to access other
> guests' memory might be the only thing that lets you sleep at night :)
> 
> Yes, it's a lot of work. But I think we've reached the point where
> mandatory secret hiding is... well... mandatory.
> 
> > > The *userspace* ABI considerations are all about how you make a vCPU
> > > that runs asynchronously (should it conceptually just be an async
> > > KVM_RUN call, which allows the vCPU to run in a kernel thread up to the
> > > point of kexec? Why is it fundamentally tied to kexec at all?).
> > 
> > It's not tied to kexec.? kexec is just forcing a handoff + forcing an
> > update.
> > 
> > The big difference is that:
> > 
> > 1) if you don't tie it to kexec, a detached vCPU thread is a struct 
> > vhost_task and a blocking vmexit schedules out the thread; while during 
> > kexec you have s/kthread/pCPU/ and halting the CPU instead of scheduling 
> > it out.
> 
> For now maybe. But "how does the caretaker do scheduling" is on
> definitely the list of future problems, for any environment where a
> physical host with N pCPUs is hosting >= N vCPUs.
> 
> (In the case of a true mandatory-secret-hiding caretaker at EL2, the
> scheduling part *could* be done by the residual purgatory-caretaker-
> thing at EL1 that all the secondary CPUs go to instead of being turned
> off. It would just be calling into EL2 to run the actual vCPUs. Thus
> leaving the EL2 code just to do its *one* job, which has the added
> benefit that the automated reasoning people put the knives down and no
> longer have that look in their eyes that they got when they thought you
> wanted to put a scheduler in their formally-proven EL2 code...)

For the initial PoC, however, we will bypass the scheduling problem 
entirely by enforcing a 1:1 mapping of active vCPUs to isolated pCPUs. 
If a system is overcommitted, we can either pin some vCPUs, or simply be 
suspend the across the kexec gap and wait for the new kernel's scheduler 
to resume them, i.e. the same as what we have now without Orphaned VM 
support.

> > 2) if you don't tie it to kexec, address space isolation is the only 
> > real reason for the complication of treating the caretaker as a separate 
> > bare metal program.? OTOH maybe that's a feature - you could do:
> > 
> > - ioctl(KVM_RUN_ASYNC)
> > 
> > - then vmfd/vcpufd handoff to a new mm on top
> 
> This much gives you a seamless upgrade of the userspace VMM without
> having to play fd-handover tricks. The old VMM detaches, the new one
> attaches. If you're quick, and the guests aren't doing much "admin"
> work but only passing traffic through passthrough PCI devices, the
> guests might not experience any non-negligible steal time at all.

I agree. During development, we should maintain a workflow that is 
functionally identical to the kexec transition. Even without a kernel 
reboot, the process should be: preserve resources via LUO, isolate and 
offline the pCPU, launch the new VMM, and then retrieve the resources to 
online the pCPU and re-adopt the vCPU.

> 
> > - then address space isolation on top
> 
> Even voluntary secret hiding lets you sleep at night when the next
> Retbleed happens.
> 
> > - then kexec (de)serialization on top
> 
> ... and this one is the holy grail.
> 
> So yes, that's exactly the kind of thing I was thinking, rather than
> trying to boil the ocean. There are sensible milestones along the way
> which give practical benefits.
> 
> But my point was *also* about understanding the actual userspace
> interface for this, even if we were to just focus on the live update
> and do it all in one amphetamine-and-tokens-fueled epic. What does it
> even look like, from the VMM point of view??How does the new VMM under
> the new kernel 'reattach' to the existing vCPUs?
> 
> I think we need the userspace API concepts for 'detach' and 'attach',
> including the permissions model for reattach, and we might as well
> implement and test them without the kexec in the middle to start with.

>From the VMM point of view, the interface would follow the standard Live 
Update Orchestrator flow for file descriptor preservation. This is 
the same mechanism used to preserve and restore resources like vfiofd, 
iommufd, and memfd across a transition.

> 
> > > I'd love to start without kexec in the picture at all. Just show me the
> > > KVM API for starting a *confidential* guest (pKVM, SEV-SNP, whatever),
> > > leaving it running, completely stopping the VMM and then starting a new
> > > VMM to pick up from where it left off.
> > 
> > Why confidential?
> 
> Mostly so that confidential VMs aren't an *afterthought*, and the
> design of the detach/attach userspace ABI gets them right from the
> start.




From coiby.xu at gmail.com  Fri May  1 16:43:29 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:29 +0800
Subject: [PATCH v2 0/9] Bug fixes and enhancements for kdump LUKS support
Message-ID: <20260501234342.2518281-1-coiby.xu@gmail.com>

Hi all,

This patch set brings some stability fixes and improvements to the
recently introduced CONFIG_CRASH_DM_CRYPT feature [1][2] (which is to
support LUKS-encrypted device dump target),
  1. resolve dumping failure when there are >=5 dm-crypt keys
  2. address several memory management issues
  3. fix race conditions like configfs writing happens concurrently with
     kexec_file_load syscall
  4. improve configfs handling
  5. update documentation 


v2
 - A different way to address potential double and UAF issues
 - Address several issues [3] scrutinized by Sourabh based on
   Sashiko's review feedback [4] on v1 patch
 - Other improvements like logging fix and doc updates

[1] http://lists.infradead.org/pipermail/kexec/2025-February/031850.html
[2] https://lore.kernel.org/all/20260225060347.718905-1-coxu at redhat.com/
[3] https://lore.kernel.org/all/972b9a73-d066-4a38-8a4b-fe7d1ba2944b at linux.ibm.com/
[4] https://sashiko.dev/#/patchset/20260403100126.1468200-1-coxu%40redhat.com

Coiby Xu (9):
  crash_dump: Release reference to a keyring at correct time
  crash_dump: Fix potential double free and UAF of keys_header
  crash_dump: Disallow writing to dm-crypt configfs during
    kexec_file_load syscall
  crash_dump: Read the number of dm-crypt keys from reserved memory
  crash_dump: Free temporary dm-crypt keys_header buffer in kdump kernel
  crash_dump: Only use kexec_dprintk during the kexec_file_load syscall
  crash_dump: Improve readability of config_keys_restore_store
  crash_dump: Disallow configfs/crash_dm_crypt_key/reuse if
    CONFIG_CRASH_HOTPLUG enabled
  Documentation: kdump: Add arm64 and ppc64le to encrypted dump target
    support list

 Documentation/admin-guide/kdump/kdump.rst |  13 ++-
 include/linux/kexec.h                     |   6 +
 kernel/crash_dump_dm_crypt.c              | 131 ++++++++++++++++------
 kernel/kexec_file.c                       |   2 +
 4 files changed, 111 insertions(+), 41 deletions(-)


base-commit: 08d0d3466664000ba0670e0ef0d447f23459e0d4
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:30 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:30 +0800
Subject: [PATCH v2 1/9] crash_dump: Release reference to a keyring at correct time
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-2-coiby.xu@gmail.com>

It's incorrect to drop the reference only after adding one key to the
specified keyring. If there are many keys to be added, it can lead
"refcount_t: underflow; use-after-free" error and some keys will fail to
be added to the keyring. My testing shows the error can occur when there
are more than five keys.

Fixes: 62f17d9df692 ("crash_dump: retrieve dm crypt keys in kdump kernel")
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index cb875ddb6ba6..eac4f436a8d4 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -81,7 +81,6 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
 		kexec_dprintk("Error when adding key");
 	}
 
-	key_ref_put(keyring_ref);
 	return r;
 }
 
@@ -126,8 +125,10 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
 
 	keys_header_size = get_keys_header_size(key_count);
 	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
-	if (!keys_header)
+	if (!keys_header) {
+		key_ref_put(keyring_ref);
 		return -ENOMEM;
+	}
 
 	dm_crypt_keys_read((char *)keys_header, keys_header_size, &addr);
 
@@ -137,6 +138,7 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
 		add_key_to_keyring(key, keyring_ref);
 	}
 
+	key_ref_put(keyring_ref);
 	return 0;
 }
 
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:31 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:31 +0800
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of keys_header
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-3-coiby.xu@gmail.com>

If kexec_add_buffer somehow fails, keys_header will be freed. Depending
on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
following two problems if the kexec_file_load syscall is called again,
  1. Double free of keys_header if reuse=false
  2. UAF of keys_header if reuse=true

To address these problems and also make it easier to reason about the
code, keep two invariants,
  1. keys_header will always be freed at the end of kexec_file_load
     syscall except during kdump image unloading for CPU/memory
     hot-plugging support
  2. There will always be valid keys_header if reuse=true

Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 include/linux/kexec.h        |  6 ++++
 kernel/crash_dump_dm_crypt.c | 65 ++++++++++++++++++++++++++----------
 kernel/kexec_file.c          |  2 ++
 3 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index 8a22bc9b8c6c..91256d7ff434 100644
--- a/include/linux/kexec.h
+++ b/include/linux/kexec.h
@@ -552,6 +552,12 @@ void set_kexec_sig_enforced(void);
 static inline void set_kexec_sig_enforced(void) {}
 #endif
 
+#ifdef CONFIG_CRASH_DM_CRYPT
+void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image);
+#else
+static inline void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image) {}
+#endif
+
 #endif /* !defined(__ASSEBMLY__) */
 
 #endif /* LINUX_KEXEC_H */
diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index eac4f436a8d4..4d8a3331bbe7 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -84,18 +84,25 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
 	return r;
 }
 
-static void get_keys_from_kdump_reserved_memory(void)
+static int get_keys_from_kdump_reserved_memory(void)
 {
 	struct keys_header *keys_header_loaded;
+	size_t keys_header_size;
 
-	arch_kexec_unprotect_crashkres();
+	keys_header_size = get_keys_header_size(key_count);
+	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
+	if (!keys_header)
+		return -ENOMEM;
 
+	arch_kexec_unprotect_crashkres();
 	keys_header_loaded = kmap_local_page(pfn_to_page(
 		kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));
 
-	memcpy(keys_header, keys_header_loaded, get_keys_header_size(key_count));
+	memcpy(keys_header, keys_header_loaded, keys_header_size);
 	kunmap_local(keys_header_loaded);
 	arch_kexec_protect_crashkres();
+
+	return 0;
 }
 
 static int restore_dm_crypt_keys_to_thread_keyring(void)
@@ -283,17 +290,28 @@ static ssize_t config_keys_reuse_show(struct config_item *item, char *page)
 static ssize_t config_keys_reuse_store(struct config_item *item,
 					   const char *page, size_t count)
 {
+	bool val;
+	int r;
+
 	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {
 		kexec_dprintk(
 			"dm-crypt keys haven't be saved to crash-reserved memory\n");
 		return -EINVAL;
 	}
 
-	if (kstrtobool(page, &is_dm_key_reused))
+	if (kstrtobool(page, &val) || !val)
 		return -EINVAL;
 
-	if (is_dm_key_reused)
-		get_keys_from_kdump_reserved_memory();
+	if (is_dm_key_reused) {
+		pr_info("Already got dm-crypt keys, please continue with kexec_file_load syscall\n");
+	} else {
+		r = get_keys_from_kdump_reserved_memory();
+		if (r) {
+			pr_warn("Failed to get dm-crypt keys from reserved memory\n");
+			return r;
+		}
+		is_dm_key_reused = true;
+	}
 
 	return count;
 }
@@ -366,9 +384,6 @@ static int build_keys_header(void)
 	struct config_key *key;
 	int i, r;
 
-	if (keys_header != NULL)
-		kvfree(keys_header);
-
 	keys_header = kzalloc(get_keys_header_size(key_count), GFP_KERNEL);
 	if (!keys_header)
 		return -ENOMEM;
@@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 		.top_down = false,
 		.random = true,
 	};
-	int r;
+	int r = 0;
 
 
 	if (key_count <= 0) {
@@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 	}
 
 	if (!is_dm_key_reused) {
-		image->dm_crypt_keys_addr = 0;
 		r = build_keys_header();
-		if (r) {
-			pr_err("Failed to build dm-crypt keys header, ret=%d\n", r);
-			return r;
-		}
+		if (r)
+			goto out;
 	}
 
+	/*
+	 * keys_header will be copied to reserver memory later and then be
+	 * cleaned up at the end of kexec_file_load syscall
+	 */
 	kbuf.buffer = keys_header;
 	kbuf.bufsz = get_keys_header_size(key_count);
 
@@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 	r = kexec_add_buffer(&kbuf);
 	if (r) {
 		pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
-		kvfree((void *)kbuf.buffer);
-		return r;
+		goto out;
 	}
+
 	image->dm_crypt_keys_addr = kbuf.mem;
 	image->dm_crypt_keys_sz = kbuf.bufsz;
 	kexec_dprintk(
 		"Loaded dm crypt keys to kexec_buffer bufsz=0x%lx memsz=0x%lx\n",
 		kbuf.bufsz, kbuf.memsz);
 
+out:
+	is_dm_key_reused = false;
 	return r;
 }
 
+void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
+{
+	/*
+	 * For CPU/memory hot-plugging, the kdump image will be reloaded. Prevent
+	 * keys_header from being cleaned up during unloading when
+	 * is_dm_key_reused=true
+	 */
+	if (!is_dm_key_reused) {
+		kfree_sensitive(keys_header);
+		keys_header = NULL;
+	}
+}
+
 static int __init configfs_dmcrypt_keys_init(void)
 {
 	int ret;
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 2bfbb2d144e6..0421f1e89791 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -139,6 +139,8 @@ void kimage_file_post_load_cleanup(struct kimage *image)
 	kfree(image->image_loader_data);
 	image->image_loader_data = NULL;
 
+	kexec_file_post_load_cleanup_dm_crypt(image);
+
 	kexec_file_dbg_print = false;
 }
 
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:32 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:32 +0800
Subject: [PATCH v2 3/9] crash_dump: Disallow writing to dm-crypt configfs during kexec_file_load syscall
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-4-coiby.xu@gmail.com>

If writing to the configfs group happens concurrently during
kexec_file_load syscall, it may lead to the following issues,
  - buffer overflow if dm-crypt keys are added after allocation
  - stale total_keys if dm-crypt keys are removed during iteration
  - keys_header will not be freed if config/crash_dm_crypt_key/reuse is
    set true

So hold config_keys_subsys.su_mutex for the entire sequence during the
kexec_file_load syscall to ensure a consistent snapshot.

Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index 4d8a3331bbe7..6377ee86ec50 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -429,6 +429,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 	};
 	int r = 0;
 
+	mutex_lock(&config_keys_subsys.su_mutex);
 
 	if (key_count <= 0) {
 		kexec_dprintk("No dm-crypt keys\n");
@@ -479,6 +480,9 @@ void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
 		kfree_sensitive(keys_header);
 		keys_header = NULL;
 	}
+
+	if (mutex_is_locked(&config_keys_subsys.su_mutex))
+		mutex_unlock(&config_keys_subsys.su_mutex);
 }
 
 static int __init configfs_dmcrypt_keys_init(void)
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:33 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:33 +0800
Subject: [PATCH v2 4/9] crash_dump: Read the number of dm-crypt keys from reserved memory
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-5-coiby.xu@gmail.com>

In case user adds/deletes the keys by mistake, it's safer to read the
number of keys from reserved memory.

Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
Reported-and-Suggested-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 36 +++++++++++++++++++++++-------------
 1 file changed, 23 insertions(+), 13 deletions(-)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index 6377ee86ec50..a3e460714d23 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -88,21 +88,31 @@ static int get_keys_from_kdump_reserved_memory(void)
 {
 	struct keys_header *keys_header_loaded;
 	size_t keys_header_size;
-
-	keys_header_size = get_keys_header_size(key_count);
-	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
-	if (!keys_header)
-		return -ENOMEM;
+	int r = 0;
 
 	arch_kexec_unprotect_crashkres();
 	keys_header_loaded = kmap_local_page(pfn_to_page(
 		kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));
 
+	if (keys_header_loaded->total_keys <= 0 ||
+	    keys_header_loaded->total_keys > KEY_NUM_MAX) {
+		pr_warn("keys_header saved to reserved memory may be corrupt\n");
+		r = -EINVAL;
+		goto kunmap;
+	}
+
+	keys_header_size = get_keys_header_size(keys_header_loaded->total_keys);
+	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
+	if (!keys_header) {
+		r = -ENOMEM;
+		goto kunmap;
+	}
+
 	memcpy(keys_header, keys_header_loaded, keys_header_size);
+kunmap:
 	kunmap_local(keys_header_loaded);
 	arch_kexec_protect_crashkres();
-
-	return 0;
+	return r;
 }
 
 static int restore_dm_crypt_keys_to_thread_keyring(void)
@@ -431,12 +441,12 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 
 	mutex_lock(&config_keys_subsys.su_mutex);
 
-	if (key_count <= 0) {
-		kexec_dprintk("No dm-crypt keys\n");
-		return 0;
-	}
-
 	if (!is_dm_key_reused) {
+		if (key_count <= 0) {
+			kexec_dprintk("No dm-crypt keys\n");
+			return 0;
+		}
+
 		r = build_keys_header();
 		if (r)
 			goto out;
@@ -447,7 +457,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 	 * cleaned up at the end of kexec_file_load syscall
 	 */
 	kbuf.buffer = keys_header;
-	kbuf.bufsz = get_keys_header_size(key_count);
+	kbuf.bufsz = get_keys_header_size(keys_header->total_keys);
 
 	kbuf.memsz = kbuf.bufsz;
 	kbuf.buf_align = ELF_CORE_HEADER_ALIGN;
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:34 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:34 +0800
Subject: [PATCH v2 5/9] crash_dump: Free temporary dm-crypt keys_header buffer in kdump kernel
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-6-coiby.xu@gmail.com>

Although we expect the system to reboot immediately after vmcore dumping
is finished, it's still good to free the temporary keys_header buffer.

Fixes: 62f17d9df692 ("crash_dump: retrieve dm crypt keys in kdump kernel")
Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index a3e460714d23..72ae2dffb077 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -117,6 +117,7 @@ static int get_keys_from_kdump_reserved_memory(void)
 
 static int restore_dm_crypt_keys_to_thread_keyring(void)
 {
+	struct keys_header *keys_header __free(kfree_sensitive) = NULL;
 	struct dm_crypt_key *key;
 	size_t keys_header_size;
 	key_ref_t keyring_ref;
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:35 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:35 +0800
Subject: [PATCH v2 6/9] crash_dump: Only use kexec_dprintk during the kexec_file_load syscall
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-7-coiby.xu@gmail.com>

kexec_dprintk will only be activated by "kexec -d" during
kexec_file_load syscall. So use pr_* outside of this syscall.

Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
Fixes: 62f17d9df692 ("crash_dump: retrieve dm crypt keys in kdump kernel")
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index 72ae2dffb077..545d712428d3 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -75,10 +75,10 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
 	if (!IS_ERR(key_ref)) {
 		r = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
-		kexec_dprintk("Success adding key %s", dm_key->key_desc);
+		pr_debug("Success adding key %s", dm_key->key_desc);
 	} else {
 		r = PTR_ERR(key_ref);
-		kexec_dprintk("Error when adding key");
+		pr_warn("Error when adding key");
 	}
 
 	return r;
@@ -127,18 +127,18 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
 	keyring_ref =
 		lookup_user_key(KEY_SPEC_USER_KEYRING, 0x01, KEY_NEED_WRITE);
 	if (IS_ERR(keyring_ref)) {
-		kexec_dprintk("Failed to get the user keyring\n");
+		pr_warn("Failed to get the user keyring\n");
 		return PTR_ERR(keyring_ref);
 	}
 
 	addr = dm_crypt_keys_addr;
 	dm_crypt_keys_read((char *)&key_count, sizeof(key_count), &addr);
 	if (key_count > KEY_NUM_MAX) {
-		kexec_dprintk("Failed to read the number of dm-crypt keys\n");
+		pr_warn("Failed to read the number of dm-crypt keys\n");
 		return -1;
 	}
 
-	kexec_dprintk("There are %u keys\n", key_count);
+	pr_debug("There are %u keys\n", key_count);
 	addr = dm_crypt_keys_addr;
 
 	keys_header_size = get_keys_header_size(key_count);
@@ -152,7 +152,7 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
 
 	for (int i = 0; i < keys_header->total_keys; i++) {
 		key = &keys_header->keys[i];
-		kexec_dprintk("Get key (size=%u)\n", key->key_size);
+		pr_debug("Get key (size=%u)\n", key->key_size);
 		add_key_to_keyring(key, keyring_ref);
 	}
 
@@ -305,8 +305,7 @@ static ssize_t config_keys_reuse_store(struct config_item *item,
 	int r;
 
 	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {
-		kexec_dprintk(
-			"dm-crypt keys haven't be saved to crash-reserved memory\n");
+		pr_info("dm-crypt keys haven't be saved to crash-reserved memory\n");
 		return -EINVAL;
 	}
 
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:36 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:36 +0800
Subject: [PATCH v2 7/9] crash_dump: Improve readability of config_keys_restore_store
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-8-coiby.xu@gmail.com>

config_keys_restore_store currently doesn't validate the user input
before restoring dm-crypt keys. Although it's not necessary for the case
of vmcore dumping, it's better to do it for the sake of consistency and
code readability. Also check the return code of
restore_dm_crypt_keys_to_thread_keyring.

Fixes: 62f17d9df692 ("crash_dump: retrieve dm crypt keys in kdump kernel")
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 kernel/crash_dump_dm_crypt.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index 545d712428d3..36e51807d94f 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -358,12 +358,16 @@ static ssize_t config_keys_restore_show(struct config_item *item, char *page)
 static ssize_t config_keys_restore_store(struct config_item *item,
 					  const char *page, size_t count)
 {
-	if (!restore)
-		restore_dm_crypt_keys_to_thread_keyring();
+	bool val;
 
-	if (kstrtobool(page, &restore))
+	if (kstrtobool(page, &val))
 		return -EINVAL;
 
+	if (val && !restore) {
+		if (!restore_dm_crypt_keys_to_thread_keyring())
+			restore = true;
+	}
+
 	return count;
 }
 
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:37 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:37 +0800
Subject: [PATCH v2 8/9] crash_dump: Disallow configfs/crash_dm_crypt_key/reuse if CONFIG_CRASH_HOTPLUG enabled
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-9-coiby.xu@gmail.com>

If CONFIG_CRASH_HOTPLUG is enabled, dm-crypt keys saved to reserved
memory will be took care of automatically. Thus it doesn't make sense
to use configfs/crash_dm_crypt_key/reuse. Reserving
image->dm_crypt_keys_addr is also unnecessary. Currently x86_64 and
ppc64le have implemented CONFIG_CRASH_HOTPLUG feature.

Also update the doc accordingly. Note two doc issues are fixed as well.

Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 Documentation/admin-guide/kdump/kdump.rst |  9 ++++++---
 kernel/crash_dump_dm_crypt.c              | 14 +++++++++++---
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/Documentation/admin-guide/kdump/kdump.rst b/Documentation/admin-guide/kdump/kdump.rst
index 7587caadbae1..73f2e9500c60 100644
--- a/Documentation/admin-guide/kdump/kdump.rst
+++ b/Documentation/admin-guide/kdump/kdump.rst
@@ -577,9 +577,10 @@ with /sys/kernel/config/crash_dm_crypt_keys for setup,
 
 1. Tell the first kernel what logon keys are needed to unlock the disk volumes,
     # Add key #1
-    mkdir /sys/kernel/config/crash_dm_crypt_keys/7d26b7b4-e342-4d2d-b660-7426b0996720
+    VOL1_UUID=7d26b7b4-e342-4d2d-b660-7426b0996720
+    mkdir /sys/kernel/config/crash_dm_crypt_keys/$VOL1_UUID
     # Add key #1's description
-    echo cryptsetup:7d26b7b4-e342-4d2d-b660-7426b0996720 > /sys/kernel/config/crash_dm_crypt_keys/description
+    echo cryptsetup:$VOL1_UUID > /sys/kernel/config/crash_dm_crypt_keys/$VOL1_UUID/description
 
     # how many keys do we have now?
     cat /sys/kernel/config/crash_dm_crypt_keys/count
@@ -593,7 +594,9 @@ with /sys/kernel/config/crash_dm_crypt_keys for setup,
 
     # To support CPU/memory hot-plugging, reuse keys already saved to reserved
     # memory
-    echo true > /sys/kernel/config/crash_dm_crypt_key/reuse
+    # Note if CONFIG_CRASH_HOTPLUG is enabled, this API is totally unnecessary
+    # thus will be disabled.
+    echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
 
 2. Load the dump-capture kernel
 
diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index 36e51807d94f..7a7cae17f578 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -304,6 +304,11 @@ static ssize_t config_keys_reuse_store(struct config_item *item,
 	bool val;
 	int r;
 
+	if (IS_ENABLED(CONFIG_CRASH_HOTPLUG)) {
+		pr_info("CONFIG_CRASH_HOTPLUG already enabled");
+		return -EINVAL;
+	}
+
 	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {
 		pr_info("dm-crypt keys haven't be saved to crash-reserved memory\n");
 		return -EINVAL;
@@ -486,15 +491,18 @@ int crash_load_dm_crypt_keys(struct kimage *image)
 void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
 {
 	/*
-	 * For CPU/memory hot-plugging, the kdump image will be reloaded. Prevent
-	 * keys_header from being cleaned up during unloading when
-	 * is_dm_key_reused=true
+	 * For CPU/memory hot-plugging without CONFIG_CRASH_HOTPLUG, the whole kdump
+	 * image will be reloaded. Prevent keys_header from being cleaned up during
+	 * unloading when is_dm_key_reused=true
 	 */
 	if (!is_dm_key_reused) {
 		kfree_sensitive(keys_header);
 		keys_header = NULL;
 	}
 
+	if (IS_ENABLED(CONFIG_CRASH_HOTPLUG))
+		image->dm_crypt_keys_addr = 0;
+
 	if (mutex_is_locked(&config_keys_subsys.su_mutex))
 		mutex_unlock(&config_keys_subsys.su_mutex);
 }
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:43:38 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat,  2 May 2026 07:43:38 +0800
Subject: [PATCH v2 9/9] Documentation: kdump: Add arm64 and ppc64le to encrypted dump target support list
In-Reply-To: <20260501234342.2518281-1-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
Message-ID: <20260501234342.2518281-10-coiby.xu@gmail.com>

The encrypted dump target support is now extended to arm64 and ppc64le.

Fixes: e3a84be1ec2f ("arm64,ppc64le/kdump: pass dm-crypt keys to kdump kernel")
Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
---
 Documentation/admin-guide/kdump/kdump.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/kdump/kdump.rst b/Documentation/admin-guide/kdump/kdump.rst
index 73f2e9500c60..8708ef394212 100644
--- a/Documentation/admin-guide/kdump/kdump.rst
+++ b/Documentation/admin-guide/kdump/kdump.rst
@@ -572,8 +572,8 @@ Write the dump file to encrypted disk volume
 ============================================
 
 CONFIG_CRASH_DM_CRYPT can be enabled to support saving the dump file to an
-encrypted disk volume (only x86_64 supported for now). User space can interact
-with /sys/kernel/config/crash_dm_crypt_keys for setup,
+encrypted disk volume (only x86_64, arm64, ppc64le supported for now). User
+space can interact with /sys/kernel/config/crash_dm_crypt_keys for setup,
 
 1. Tell the first kernel what logon keys are needed to unlock the disk volumes,
     # Add key #1
-- 
2.54.0



From coiby.xu at gmail.com  Fri May  1 16:49:48 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat, 2 May 2026 07:49:48 +0800
Subject: [PATCH] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <adRIwaLxqIoIDkTF@Rk>
References: <20260403100126.1468200-1-coxu@redhat.com>
 <972b9a73-d066-4a38-8a4b-fe7d1ba2944b@linux.ibm.com>
 <adRIwaLxqIoIDkTF@Rk>
Message-ID: <afU7EcMFfM4PZZfy@Rk>

On Tue, Apr 07, 2026 at 08:44:39AM +0800, Coiby Xu wrote:
>On Fri, Apr 03, 2026 at 07:48:29PM +0530, Sourabh Jain wrote:
>>Hello Coiby,
>
>Hi Sourabh,
>
>>
>>On 03/04/26 15:31, Coiby Xu wrote:
>>>If kexec_add_buffer fails, keys_header will be freed. And depending on
>>>/sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
>>>following two problems if the kexec_file_load syscall is called again,
>>>  1. Double free of keys_header if reuse=false
>>>  2. UAF of keys_header if reuse=true
>>>
>>>Address these problems by setting keys_header to NULL after freeing
>>>kbuf.buffer and re-building keys_header when necessary respectively.
>>>
>>>Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
>>>Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
>>>Cc: stable at vger.kernel.org
>>>Cc: Andrew Morton <akpm at linux-foundation.org>
>>>Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
>>>Signed-off-by: Coiby Xu <coxu at redhat.com>
>>>---
>>> kernel/crash_dump_dm_crypt.c | 3 ++-
>>> 1 file changed, 2 insertions(+), 1 deletion(-)
>>>
>>>diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
>>>index a20d4097744a..92eebef27156 100644
>>>--- a/kernel/crash_dump_dm_crypt.c
>>>+++ b/kernel/crash_dump_dm_crypt.c
>>>@@ -417,7 +417,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>> 		return -ENOENT;
>>> 	}
>>>-	if (!is_dm_key_reused) {
>>>+	if (!is_dm_key_reused || !keys_header) {
>>> 		image->dm_crypt_keys_addr = 0;
>>> 		r = build_keys_header();
>>> 		if (r)
>>>@@ -433,6 +433,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>> 	r = kexec_add_buffer(&kbuf);
>>> 	if (r) {
>>> 		kvfree((void *)kbuf.buffer);
>>>+		keys_header = NULL;
>>> 		return r;
>>> 	}
>>> 	image->dm_crypt_keys_addr = kbuf.mem;
>>>
>>>base-commit: d8a9a4b11a137909e306e50346148fc5c3b63f9d
>>
>>Sashiko raised seven concerns on this patch. Most of them are
>>not directly related to the changes introduced here, but I
>>think they can be addressed along with this fix.
>>
>>https://sashiko.dev/#/patchset/20260403100126.1468200-1-coxu%40redhat.com
>
>Thanks for pointing me to the Sashiko's code review and also sharing
>your meticulous analysis!
>
>>
>>
[...]
>>
>>4. get_keys_from_kdump_reserved_memory() may run into issues
>>? ?if kexec_crash_image->dm_crypt_keys_addr is larger than a
>>? ?page size during memcpy. Because kmap_local_page only maps
>>? ?one page.
>>
>>How about moving this in a loop and do map and copy page by page?
>
>Yeah, looping over the pages should be a robust solution.

After failing to reproduce the predicted issue, I realized there is no
need for looping page by page because kexec_add_buffer will try to find
a continuous physical memory region. So I dropped this idea in v2. But
thanks for helping me learning something new:)

-- 
Best regards,
Coiby


From coiby.xu at gmail.com  Fri May  1 16:54:05 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sat, 2 May 2026 07:54:05 +0800
Subject: [PATCH] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <401693ba-1455-4b45-8596-b81625f01201@linux.ibm.com>
References: <20260403100126.1468200-1-coxu@redhat.com>
 <972b9a73-d066-4a38-8a4b-fe7d1ba2944b@linux.ibm.com>
 <adRIwaLxqIoIDkTF@Rk>
 <401693ba-1455-4b45-8596-b81625f01201@linux.ibm.com>
Message-ID: <afU8HsdlkQo0oM00@Rk>

On Tue, Apr 07, 2026 at 03:29:18PM +0530, Sourabh Jain wrote:
>
[...]
>>>As per kdump.rst, restore was introduced to handle CPU and
>>>memory hotplug cases. Is it needed when there is no in-kernel
>>>update to the kdump image on CPU or memory hotplug events?
>>>
>>>But in that case, we rely on a udev rule to reload the kdump image
>>>again.
>>>
>>>I am confused about when exactly we need to restore.
>>
>>To clarify, reuse other than restore is needed for non in-kernel update
>>when handing CPU/memory hotplugging. Yes, a udev rule is also needed in
>>this case.
>
>Below commit explains how the reuse is utilized:
>
>commit 9ebfa8dcaea77a8ef02d0f9478717a138b0ad828
>Author: Coiby Xu <coxu at redhat.com>
>Date:? ?Fri May 2 09:12:38 2025 +0800
>
>? ? crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging
>
>It got it now. This is helpful when kdump needs to be reloaded due to
>CPU/memory hotplug events using the kexec_file_load system call,
>but only when CONFIG_CRASH_HOTPLUG is not enabled.


>
>IIUC this feature is not support on crash image loaded using 
>kexec_load syscall, right?

Glad you've figured it out!  Yes, you are correct. If
CONFIG_CRASH_HOTPLUG is enabled, there is no need for configfs/reuse. In
v2, I've improved the doc and also added a patch to prevented using this
API when CONFIG_CRASH_HOTPLUG is enabled.


-- 
Best regards,
Coiby


From lkp at intel.com  Sat May  2 23:19:19 2026
From: lkp at intel.com (kernel test robot)
Date: Sun, 03 May 2026 14:19:19 +0800
Subject: [liveupdate:next] BUILD SUCCESS
 74d1099bb4edfca3ce6edc61dbacbed96720034c
Message-ID: <202605031409.xe6Iph9b-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git next
branch HEAD: 74d1099bb4edfca3ce6edc61dbacbed96720034c  liveupdate: Reference count incoming FLB data

elapsed time: 2057m

configs tested: 167
configs skipped: 2

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              alldefconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260503    gcc-10.5.0
arc                   randconfig-002-20260503    gcc-10.5.0
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                   randconfig-001-20260503    gcc-10.5.0
arm                   randconfig-002-20260503    gcc-10.5.0
arm                   randconfig-003-20260503    gcc-10.5.0
arm                   randconfig-004-20260503    gcc-10.5.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260503    gcc-15.2.0
arm64                 randconfig-002-20260503    gcc-15.2.0
arm64                 randconfig-003-20260503    gcc-15.2.0
arm64                 randconfig-004-20260503    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260503    gcc-15.2.0
csky                  randconfig-002-20260503    gcc-15.2.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260503    clang-23
hexagon               randconfig-002-20260503    clang-23
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260503    clang-20
i386        buildonly-randconfig-002-20260503    clang-20
i386        buildonly-randconfig-003-20260503    clang-20
i386        buildonly-randconfig-004-20260503    clang-20
i386        buildonly-randconfig-005-20260503    clang-20
i386        buildonly-randconfig-006-20260503    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260503    clang-20
i386                  randconfig-002-20260503    clang-20
i386                  randconfig-003-20260503    clang-20
i386                  randconfig-004-20260503    clang-20
i386                  randconfig-005-20260503    clang-20
i386                  randconfig-006-20260503    clang-20
i386                  randconfig-007-20260503    clang-20
i386                  randconfig-011-20260503    clang-20
i386                  randconfig-012-20260503    clang-20
i386                  randconfig-013-20260503    clang-20
i386                  randconfig-014-20260503    clang-20
i386                  randconfig-015-20260503    clang-20
i386                  randconfig-016-20260503    clang-20
i386                  randconfig-017-20260503    clang-20
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260503    clang-23
loongarch             randconfig-002-20260503    clang-23
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
m68k                          multi_defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260503    clang-23
nios2                 randconfig-002-20260503    clang-23
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260503    gcc-15.2.0
parisc                randconfig-002-20260503    gcc-15.2.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc               randconfig-001-20260503    gcc-15.2.0
powerpc               randconfig-002-20260503    gcc-15.2.0
powerpc64             randconfig-001-20260503    gcc-15.2.0
powerpc64             randconfig-002-20260503    gcc-15.2.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260503    clang-23
riscv                 randconfig-002-20260503    clang-23
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260503    clang-23
s390                  randconfig-002-20260503    clang-23
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                    randconfig-001-20260503    clang-23
sh                    randconfig-002-20260503    clang-23
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260503    gcc-15.2.0
sparc                 randconfig-002-20260503    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64               randconfig-001-20260503    gcc-15.2.0
sparc64               randconfig-002-20260503    gcc-15.2.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                    randconfig-001-20260503    gcc-15.2.0
um                    randconfig-002-20260503    gcc-15.2.0
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260503    clang-20
x86_64      buildonly-randconfig-002-20260503    clang-20
x86_64      buildonly-randconfig-003-20260503    clang-20
x86_64      buildonly-randconfig-004-20260503    clang-20
x86_64      buildonly-randconfig-005-20260503    clang-20
x86_64      buildonly-randconfig-006-20260503    clang-20
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260503    clang-20
x86_64                randconfig-002-20260503    clang-20
x86_64                randconfig-003-20260503    clang-20
x86_64                randconfig-004-20260503    clang-20
x86_64                randconfig-005-20260503    clang-20
x86_64                randconfig-006-20260503    clang-20
x86_64                randconfig-011-20260503    clang-20
x86_64                randconfig-012-20260503    clang-20
x86_64                randconfig-013-20260503    clang-20
x86_64                randconfig-014-20260503    clang-20
x86_64                randconfig-015-20260503    clang-20
x86_64                randconfig-016-20260503    clang-20
x86_64                randconfig-071-20260503    clang-20
x86_64                randconfig-072-20260503    clang-20
x86_64                randconfig-073-20260503    clang-20
x86_64                randconfig-074-20260503    clang-20
x86_64                randconfig-075-20260503    clang-20
x86_64                randconfig-076-20260503    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                randconfig-001-20260503    gcc-15.2.0
xtensa                randconfig-002-20260503    gcc-15.2.0

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


From rppt at kernel.org  Sun May  3 03:01:50 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 3 May 2026 12:01:50 +0200
Subject: [PATCH v9 0/3] kho: add support for deferred struct page init
In-Reply-To: <20260423122538.140993-1-mclapinski@google.com>
References: <20260423122538.140993-1-mclapinski@google.com>
Message-ID: <afcdDm4aAJvNaQqH@kernel.org>

Hi Michal,

On Thu, Apr 23, 2026 at 02:25:35PM +0200, Michal Clapinski wrote:
> When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, struct page
> initialization is deferred to parallel kthreads that run later in
> the boot process.

This patchset breaks memblock tests:
https://github.com/linux-mm/linux-mm/actions/runs/25259346624/job/74063911769

Can you please send a fix?
 
> Michal Clapinski (2):
>   kho: fix deferred initialization of scratch areas
>   selftests: kho: test with deferred struct page init
> 
>  include/linux/memblock.h              | 21 +++++++++-
>  kernel/liveupdate/Kconfig             |  2 -
>  kernel/liveupdate/kexec_handover.c    | 52 ++++++++++++-------------
>  mm/memblock.c                         | 56 +++++++++++----------------
>  mm/mm_init.c                          | 30 +++++++-------
>  tools/testing/selftests/kho/vmtest.sh |  4 ++
>  6 files changed, 88 insertions(+), 77 deletions(-)
> 
> -- 
> 2.54.0.rc2.533.g4f5dca5207-goog
> 

-- 
Sincerely yours,
Mike.


From pbonzini at redhat.com  Sun May  3 09:57:06 2026
From: pbonzini at redhat.com (Paolo Bonzini)
Date: Sun, 3 May 2026 18:57:06 +0200
Subject: [RFC] proposal: KVM: Orphaned VMs: The Caretaker approach for
 Live Update
In-Reply-To: <afUNlL_lRDP6olHE@plex>
References: <afEwWZksU0Fw61oT@plex> <0a71472c-b397-4699-a518-61faffcf4ab2@redhat.com>
 <afUNlL_lRDP6olHE@plex>
Message-ID: <CABgObfaqZ8GGaLV_gp8o+nQSe+JfnBbZP0X9cDxdvEpyUZQRnQ@mail.gmail.com>

On Fri, May 1, 2026 at 11:48?PM Pasha Tatashin
<pasha.tatashin at soleen.com> wrote:
> The way I see it, vmfd and vcpufd need to support LUO preservation by
> implementing the liveupdate_file_ops callbacks (.preserve, .restore).
>
> When userspace preserves the vcpufd, the kernel isolates the assigned
> pCPU (probably requiring the vCPU to be pinned to the CPU, and the CPU
> to be in an isolated cpuset),

Generally speaking vCPUs do not care if they are pinned. Do we need a
preparatory ioctl for this on the vcpufd side, even just a
KVM_ENABLE_CAP or a new bit for
KVM_ENABLE_CAP(KVM_CAP_X86_DISABLE_EXITS)?  If
LIVEUPDATE_SESSION_PRESERVE_FD suddenly can start offlining pCPUs,
that would require a capability check.

> Even for this simplest form, we still need a defined ABI between the
> host kernel and the Caretaker. The host must send an IPI to
> synchronously notify the Caretaker of attach and detach transitions.

Yes, but it's unlikely to need stability unlike the KHO serialization.

> This ABI must also handle Caretaker replacement during the adoption
> phase: when the new kernel retrieves the vCPU, it requires a protocol to
> notify the running Caretaker that the pCPU is being onlined. The
> Caretaker must reach a known, state at that moment so the kernel can
> seamlessly replace the previous kernel's Caretaker version with the
> current one. Finally, the Caretaker still relies on the CCB to access
> KVM routing pointers for forwarding VM exits back to the host during
> normal operation.

I'm not sure if routing pointers are needed, as opposed to just a
call/ret (ignoring detachment and reattachment which are different
anyway). In the end, the x86 caretaker is basically the
non-preemptable part of vcpu_enter_guest(). Even once you add
attach/detach, the body of code that runs in the caretaker is roughly
the same and what changes is setting up the address space, the IDT,
etc.

> > This must be done atomically at the time Linux offlines/onlines a pCPU. The
> > interface from Linux to the caretaker must use some kind of IPI so that the
> > new kernel can force a VMEXIT (if needed) in the caretaker, ask it to
> > serialize the vm state, and pass it down to the new kernel's caretaker.
>
> Yes, agree.

BTW, the same IPI is needed to force a VMEXIT even before kexec, if
the old kernel does anything that breaks the running VM such as
madvise(MADV_FREE).  Should not happen with properly behaving
userspace, but it must be accounted for.

> During the kexec gap (when the host kernel is completely offline), the
> Caretaker acts purely as a producer, writing trace events directly to
> this physical memory block and advancing the ring buffer pointers.
>
> When the new kernel boots and re-adopts the orphaned vCPU, it retrieves
> this memory from KHO and attaches it back to the tracing subsystem.

Yes, this should work with remote trace buffers.

> I agree regarding HLT: during the gap, the simplest approach is to just
> skip the exit and return directly to the VM without attempting to handle
> it.

Or even disable intercepts for HLT/PAUSE/MONITOR/MWAIT.

> I completely agree that the transition into and out of the gap must be
> synchronous. As discussed above, using an IPI is the right approach. For
> entry, the host kernel signals the Caretaker via IPI to ensure it
> reaches a known state before the pCPU is offlined.

And then does INIT/SIPI on the offlined pCPU to re-enter the caretaker
in detached mode.

> For exit, the new
> kernel sends an IPI to the orphaned pCPU to force a VM exit, allowing
> the new kernel to take control, update the Caretaker environment,
> and complete the reattachment.

> > Yeah, I think APIC emulation to some extent must be moved into the VMX/SVM
> > fastpaths.  The good news is that this can be done already as a PoC without
> > needing the whole caretaker and LUO infrastructure.
>
> Moving APIC emulation into the VMX/SVM fastpaths makes sense as a
> standalone effort.

Yup, that's nice to have.

Thanks for the detailed reply, I limited mine to where I wanted your
input as the author of LUO - especially with respect to privilege
separation.

Paolo



From pratyush at kernel.org  Mon May  4 00:53:01 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 04 May 2026 09:53:01 +0200
Subject: [PATCH v9 0/3] kho: add support for deferred struct page init
In-Reply-To: <afcdDm4aAJvNaQqH@kernel.org> (Mike Rapoport's message of "Sun, 3
	May 2026 12:01:50 +0200")
References: <20260423122538.140993-1-mclapinski@google.com>
	<afcdDm4aAJvNaQqH@kernel.org>
Message-ID: <2vxzse87392a.fsf@kernel.org>

On Sun, May 03 2026, Mike Rapoport wrote:

> Hi Michal,
>
> On Thu, Apr 23, 2026 at 02:25:35PM +0200, Michal Clapinski wrote:
>> When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, struct page
>> initialization is deferred to parallel kthreads that run later in
>> the boot process.
>
> This patchset breaks memblock tests:
> https://github.com/linux-mm/linux-mm/actions/runs/25259346624/job/74063911769
>
> Can you please send a fix?

Would something like this do the trick? It fixes the build for me and
the tests pass.

I can send this as a proper patch if this looks good.

--- 8< ---
diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h
index bb682659a12d..8d934ff5b080 100644
--- a/tools/testing/memblock/linux/mmzone.h
+++ b/tools/testing/memblock/linux/mmzone.h
@@ -35,4 +35,8 @@ typedef struct pglist_data {
 
 } pg_data_t;
 
+enum migratetype {
+	MIGRATE_CMA,
+};
+
 #endif
-- 
Regards,
Pratyush Yadav


From rppt at kernel.org  Mon May  4 03:03:07 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 4 May 2026 12:03:07 +0200
Subject: [PATCH v9 0/3] kho: add support for deferred struct page init
In-Reply-To: <2vxzse87392a.fsf@kernel.org>
References: <20260423122538.140993-1-mclapinski@google.com>
 <afcdDm4aAJvNaQqH@kernel.org>
 <2vxzse87392a.fsf@kernel.org>
Message-ID: <afhu23BqEOW_6_jz@kernel.org>

On Mon, May 04, 2026 at 09:53:01AM +0200, Pratyush Yadav wrote:
> On Sun, May 03 2026, Mike Rapoport wrote:
> 
> > Hi Michal,
> >
> > On Thu, Apr 23, 2026 at 02:25:35PM +0200, Michal Clapinski wrote:
> >> When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, struct page
> >> initialization is deferred to parallel kthreads that run later in
> >> the boot process.
> >
> > This patchset breaks memblock tests:
> > https://github.com/linux-mm/linux-mm/actions/runs/25259346624/job/74063911769
> >
> > Can you please send a fix?
> 
> Would something like this do the trick? It fixes the build for me and
> the tests pass.
> 
> I can send this as a proper patch if this looks good.

Yes, please :)
 
> --- 8< ---
> diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h
> index bb682659a12d..8d934ff5b080 100644
> --- a/tools/testing/memblock/linux/mmzone.h
> +++ b/tools/testing/memblock/linux/mmzone.h
> @@ -35,4 +35,8 @@ typedef struct pglist_data {
>  
>  } pg_data_t;
>  
> +enum migratetype {
> +	MIGRATE_CMA,
> +};
> +
>  #endif
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From pratyush at kernel.org  Mon May  4 03:27:40 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon,  4 May 2026 12:27:40 +0200
Subject: [PATCH] memblock tests: define MIGRATE_CMA
Message-ID: <20260504102742.3833159-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

kho_scratch_migratetype(), defined in include/linux/memblock.h uses enum
migratetype. This breaks build for memblock tests with:

./linux/memblock.h:634:73: error: parameter 2 (?mt?) has incomplete type
  634 |                                                        enum migratetype mt)

Fix it by defining enum migratetype and MIGRATE_CMA. As is the case with
the other headers in tools/testing/memblock, do not bring in the whole 
thing, only what is needed.

Reported-by: Mike Rapoport <rppt at kernel.org>
Closes: https://lore.kernel.org/linux-mm/afcdDm4aAJvNaQqH at kernel.org/
Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---

Notes:
    I didn't add a Fixes here since the commit hash for the deferred series
    isn't stable yet. Anyway, I think we should fold this into "kho: fix
    deferred initialization of scratch areas". So random bisects don't land
    on a broken test.

 tools/testing/memblock/linux/mmzone.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h
index bb682659a12d..8d934ff5b080 100644
--- a/tools/testing/memblock/linux/mmzone.h
+++ b/tools/testing/memblock/linux/mmzone.h
@@ -35,4 +35,8 @@ typedef struct pglist_data {
 
 } pg_data_t;
 
+enum migratetype {
+	MIGRATE_CMA,
+};
+
 #endif

base-commit: 74d1099bb4edfca3ce6edc61dbacbed96720034c
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Mon May  4 03:32:11 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 4 May 2026 06:32:11 -0400
Subject: [PATCH] memblock tests: define MIGRATE_CMA
In-Reply-To: <20260504102742.3833159-1-pratyush@kernel.org>
References: <20260504102742.3833159-1-pratyush@kernel.org>
Message-ID: <CA+CK2bChn3bAFBnmD-fNw2zh-JrAaA73DNmm416EbjXr=mTakA@mail.gmail.com>

On Mon, May 4, 2026 at 6:27?AM Pratyush Yadav <pratyush at kernel.org> wrote:
>
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
> kho_scratch_migratetype(), defined in include/linux/memblock.h uses enum
> migratetype. This breaks build for memblock tests with:
>
> ./linux/memblock.h:634:73: error: parameter 2 (?mt?) has incomplete type
>   634 |                                                        enum migratetype mt)
>
> Fix it by defining enum migratetype and MIGRATE_CMA. As is the case with
> the other headers in tools/testing/memblock, do not bring in the whole
> thing, only what is needed.
>
> Reported-by: Mike Rapoport <rppt at kernel.org>
> Closes: https://lore.kernel.org/linux-mm/afcdDm4aAJvNaQqH at kernel.org/

Link does not open for me.

Pasha

> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>
> Notes:
>     I didn't add a Fixes here since the commit hash for the deferred series
>     isn't stable yet. Anyway, I think we should fold this into "kho: fix
>     deferred initialization of scratch areas". So random bisects don't land
>     on a broken test.
>
>  tools/testing/memblock/linux/mmzone.h | 4 ++++
>  1 file changed, 4 insertions(+)
>
> diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h
> index bb682659a12d..8d934ff5b080 100644
> --- a/tools/testing/memblock/linux/mmzone.h
> +++ b/tools/testing/memblock/linux/mmzone.h
> @@ -35,4 +35,8 @@ typedef struct pglist_data {
>
>  } pg_data_t;
>
> +enum migratetype {
> +       MIGRATE_CMA,
> +};
> +
>  #endif
>
> base-commit: 74d1099bb4edfca3ce6edc61dbacbed96720034c
> --
> 2.54.0.545.g6539524ca2-goog
>


From pratyush at kernel.org  Mon May  4 05:31:06 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 04 May 2026 14:31:06 +0200
Subject: [PATCH] memblock tests: define MIGRATE_CMA
In-Reply-To: <CA+CK2bChn3bAFBnmD-fNw2zh-JrAaA73DNmm416EbjXr=mTakA@mail.gmail.com>
	(Pasha Tatashin's message of "Mon, 4 May 2026 06:32:11 -0400")
References: <20260504102742.3833159-1-pratyush@kernel.org>
	<CA+CK2bChn3bAFBnmD-fNw2zh-JrAaA73DNmm416EbjXr=mTakA@mail.gmail.com>
Message-ID: <2vxz8q9z2w6t.fsf@kernel.org>

On Mon, May 04 2026, Pasha Tatashin wrote:

> On Mon, May 4, 2026 at 6:27?AM Pratyush Yadav <pratyush at kernel.org> wrote:
>>
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>
>> kho_scratch_migratetype(), defined in include/linux/memblock.h uses enum
>> migratetype. This breaks build for memblock tests with:
>>
>> ./linux/memblock.h:634:73: error: parameter 2 (?mt?) has incomplete type
>>   634 |                                                        enum migratetype mt)
>>
>> Fix it by defining enum migratetype and MIGRATE_CMA. As is the case with
>> the other headers in tools/testing/memblock, do not bring in the whole
>> thing, only what is needed.
>>
>> Reported-by: Mike Rapoport <rppt at kernel.org>
>> Closes: https://lore.kernel.org/linux-mm/afcdDm4aAJvNaQqH at kernel.org/
>
> Link does not open for me.

Dunno why. Works fine for me... Maybe some temporary issue with lore?

[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May  4 05:33:14 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 04 May 2026 14:33:14 +0200
Subject: [PATCH] memblock tests: define MIGRATE_CMA
In-Reply-To: <2vxz8q9z2w6t.fsf@kernel.org> (Pratyush Yadav's message of "Mon,
	04 May 2026 14:31:06 +0200")
References: <20260504102742.3833159-1-pratyush@kernel.org>
	<CA+CK2bChn3bAFBnmD-fNw2zh-JrAaA73DNmm416EbjXr=mTakA@mail.gmail.com>
	<2vxz8q9z2w6t.fsf@kernel.org>
Message-ID: <2vxz4ikn2w39.fsf@kernel.org>

On Mon, May 04 2026, Pratyush Yadav wrote:

> On Mon, May 04 2026, Pasha Tatashin wrote:
>
>> On Mon, May 4, 2026 at 6:27?AM Pratyush Yadav <pratyush at kernel.org> wrote:
>>>
>>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>>
>>> kho_scratch_migratetype(), defined in include/linux/memblock.h uses enum
>>> migratetype. This breaks build for memblock tests with:
>>>
>>> ./linux/memblock.h:634:73: error: parameter 2 (?mt?) has incomplete type
>>>   634 |                                                        enum migratetype mt)
>>>
>>> Fix it by defining enum migratetype and MIGRATE_CMA. As is the case with
>>> the other headers in tools/testing/memblock, do not bring in the whole
>>> thing, only what is needed.
>>>
>>> Reported-by: Mike Rapoport <rppt at kernel.org>
>>> Closes: https://lore.kernel.org/linux-mm/afcdDm4aAJvNaQqH at kernel.org/
>>
>> Link does not open for me.
>
> Dunno why. Works fine for me... Maybe some temporary issue with lore?

Or, look for the thread under "Re: [PATCH v9 0/3] kho: add support for
deferred struct page init" in your mailbox.

-- 
Regards,
Pratyush Yadav


From rppt at kernel.org  Mon May  4 06:46:01 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 4 May 2026 15:46:01 +0200
Subject: [PATCH] memblock tests: define MIGRATE_CMA
In-Reply-To: <20260504102742.3833159-1-pratyush@kernel.org>
References: <20260504102742.3833159-1-pratyush@kernel.org>
Message-ID: <afijGcl4xgpPwqAI@kernel.org>

On Mon, May 04, 2026 at 12:27:40PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> kho_scratch_migratetype(), defined in include/linux/memblock.h uses enum
> migratetype. This breaks build for memblock tests with:
> 
> ./linux/memblock.h:634:73: error: parameter 2 (?mt?) has incomplete type
>   634 |                                                        enum migratetype mt)
> 
> Fix it by defining enum migratetype and MIGRATE_CMA. As is the case with
> the other headers in tools/testing/memblock, do not bring in the whole 
> thing, only what is needed.
> 
> Reported-by: Mike Rapoport <rppt at kernel.org>
> Closes: https://lore.kernel.org/linux-mm/afcdDm4aAJvNaQqH at kernel.org/
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
> 
> Notes:
>     I didn't add a Fixes here since the commit hash for the deferred series
>     isn't stable yet. Anyway, I think we should fold this into "kho: fix
>     deferred initialization of scratch areas". So random bisects don't land
>     on a broken test.

I applied it to next as a separate patch for now.
 
>  tools/testing/memblock/linux/mmzone.h | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h
> index bb682659a12d..8d934ff5b080 100644
> --- a/tools/testing/memblock/linux/mmzone.h
> +++ b/tools/testing/memblock/linux/mmzone.h
> @@ -35,4 +35,8 @@ typedef struct pglist_data {
>  
>  } pg_data_t;
>  
> +enum migratetype {
> +	MIGRATE_CMA,
> +};
> +
>  #endif
> 
> base-commit: 74d1099bb4edfca3ce6edc61dbacbed96720034c
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

-- 
Sincerely yours,
Mike.


From pasha.tatashin at soleen.com  Mon May  4 07:44:48 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 4 May 2026 14:44:48 +0000
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <20260429133928.850721-2-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-2-pratyush@kernel.org>
Message-ID: <afiwy31X4WANSk1q@plex>

On 04-29 15:39, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The KHO radix tree is a data structure that can track the presence or
> absence of an arbitrary key, with nothing inherently tied to KHO memory
> preservation tracking. This was one of the design goals of the radix
> tree. This was done to enable it to be re-used by other users of KHO.
> 
> Despite that, the radix tree APIs are very closely tied to KHO memory
> preservation tracking. Adding a key is done by kho_radix_add_page(),
> which encodes it as a page tracking operation and takes in PFN and
> order. kho_radix_del_page() does the same. These functions encode the
> key internally that goes into the radix tree. kho_radix_walk_tree() does
> the same by baking the PFN and order into the callback arguments.
> 
> Generalize the APIs by taking the key directly and doing the encoding at
> the callers. Rename the functions to kho_radix_add_key() and
> kho_radix_del_key(). In practice, this removes a line each from the
> functions and moves the encoding function call to the callers.
> Similarly, update kho_radix_tree_walk_callback_t to take the key
> directly.
> 
> To keep the naming convention clearer, rename
> kho_radix_{encode,decode}_key() to kho_{encode,decode}_radix_key().
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

> ---
>  include/linux/kho_radix_tree.h     | 18 +++----
>  kernel/liveupdate/kexec_handover.c | 76 ++++++++++++++----------------
>  2 files changed, 42 insertions(+), 52 deletions(-)
> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index 84e918b96e53..f368f3b9f923 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -34,30 +34,24 @@ struct kho_radix_tree {
>  	struct mutex lock; /* protects the tree's structure and root pointer */
>  };
>  
> -typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,
> -					      unsigned int order);
> +typedef int (*kho_radix_tree_walk_callback_t)(unsigned long key);
>  
>  #ifdef CONFIG_KEXEC_HANDOVER
>  
> -int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,
> -		       unsigned int order);
> -
> -void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
> -			unsigned int order);
> -
> +int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
> +void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
>  int kho_radix_walk_tree(struct kho_radix_tree *tree,
>  			kho_radix_tree_walk_callback_t cb);
>  
>  #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
>  
> -static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,
> -				     unsigned int order)
> +static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
>  {
>  	return -EOPNOTSUPP;
>  }
>  
> -static inline void kho_radix_del_page(struct kho_radix_tree *tree,
> -				      unsigned long pfn, unsigned int order) { }
> +static inline void kho_radix_del_key(struct kho_radix_tree *tree,
> +				     unsigned long key) { }
>  
>  static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
>  				      kho_radix_tree_walk_callback_t cb)
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 33fcf848ef95..ba568d34c5b4 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -85,7 +85,7 @@ static struct kho_out kho_out = {
>  };
>  
>  /**
> - * kho_radix_encode_key - Encodes a physical address and order into a radix key.
> + * kho_encode_radix_key - Encodes a physical address and order into a radix key.
>   * @phys: The physical address of the page.
>   * @order: The order of the page.
>   *
> @@ -95,7 +95,7 @@ static struct kho_out kho_out = {
>   *
>   * Return: The encoded unsigned long radix key.
>   */
> -static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
> +static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)
>  {
>  	/* Order bits part */
>  	unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
> @@ -106,17 +106,17 @@ static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
>  }
>  
>  /**
> - * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
> + * kho_decode_radix_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(),
> + * This function reverses the encoding performed by kho_encode_radix_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)
> +static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)
>  {
>  	unsigned int order_bit = fls64(key);
>  	phys_addr_t phys;
> @@ -144,24 +144,21 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
>  }
>  
>  /**
> - * kho_radix_add_page - Marks a page as preserved in the radix tree.
> + * kho_radix_add_key - Add a key to the radix tree.
>   * @tree: The KHO radix tree.
> - * @pfn: The page frame number of the page to preserve.
> - * @order: The order of the page.
> + * @key: The key to add.
>   *
> - * 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.
> + * This function traverses the radix tree based on the key provided. It sets the
> + * corresponding bit in the leaf bitmap to mark the key as present. 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)
> +int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
>  {
>  	/* 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;
> @@ -224,22 +221,19 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
>  
>  	return err;
>  }
> -EXPORT_SYMBOL_GPL(kho_radix_add_page);
> +EXPORT_SYMBOL_GPL(kho_radix_add_key);
>  
>  /**
> - * kho_radix_del_page - Removes a page's preservation status from the radix tree.
> + * kho_radix_del_key - Removes the key 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.
> + * @key: The key to remove.
>   *
>   * 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.
> + * the key, effectively removing it from the tree. 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)
> +void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>  {
> -	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;
> @@ -270,21 +264,18 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
>  	idx = kho_radix_get_bitmap_index(key);
>  	__clear_bit(idx, leaf->bitmap);
>  }
> -EXPORT_SYMBOL_GPL(kho_radix_del_page);
> +EXPORT_SYMBOL_GPL(kho_radix_del_key);
>  
>  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);
> +		err = cb(key | i);
>  		if (err)
>  			return err;
>  	}
> @@ -332,15 +323,14 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
>  }
>  
>  /**
> - * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
> + * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
>   * @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.
> + *      invoked for each key in the tree.
>   *
>   * 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.
> + * down to the lowest level (level 0). For each key found, it invokes the
> + * provided callback.
>   *
>   * Return: 0 if the walk completed the specified tree, or the non-zero return
>   *         value from the callback that stopped the walk.
> @@ -365,7 +355,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,
>  	while (pfn < end_pfn) {
>  		order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
>  
> -		kho_radix_del_page(tree, pfn, order);
> +		kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
> +							     order));
>  
>  		pfn += 1 << order;
>  	}
> @@ -498,13 +489,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
>  	return pfn_to_page(pfn);
>  }
>  
> -static int __init kho_preserved_memory_reserve(phys_addr_t phys,
> -					       unsigned int order)
> +static int __init kho_preserved_memory_reserve(unsigned long key)
>  {
>  	union kho_page_info info;
>  	struct page *page;
> +	unsigned int order;
> +	phys_addr_t phys;
>  	u64 sz;
>  
> +	phys = kho_decode_radix_key(key, &order);
> +
>  	sz = 1 << (order + PAGE_SHIFT);
>  	page = kho_get_preserved_page(phys, order);
>  
> @@ -858,7 +852,8 @@ int kho_preserve_folio(struct folio *folio)
>  	if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
>  		return -EINVAL;
>  
> -	return kho_radix_add_page(tree, pfn, order);
> +	return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
> +							    order));
>  }
>  EXPORT_SYMBOL_GPL(kho_preserve_folio);
>  
> @@ -876,7 +871,7 @@ void kho_unpreserve_folio(struct folio *folio)
>  	const unsigned long pfn = folio_pfn(folio);
>  	const unsigned int order = folio_order(folio);
>  
> -	kho_radix_del_page(tree, pfn, order);
> +	kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));
>  }
>  EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
>  
> @@ -916,7 +911,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
>  		while (pfn_to_nid(pfn) != pfn_to_nid(pfn + (1UL << order) - 1))
>  			order--;
>  
> -		err = kho_radix_add_page(tree, pfn, order);
> +		err = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
> +								   order));
>  		if (err) {
>  			failed_pfn = pfn;
>  			break;
> -- 
> 2.54.0.545.g6539524ca2-goog
> 


From dmatlack at google.com  Mon May  4 10:23:48 2026
From: dmatlack at google.com (David Matlack)
Date: Mon, 4 May 2026 10:23:48 -0700
Subject: [PATCH v4 07/11] PCI: liveupdate: Inherit ACS flags in incoming
 preserved devices
In-Reply-To: <20260423212316.3431746-8-dmatlack@google.com>
References: <20260423212316.3431746-1-dmatlack@google.com> <20260423212316.3431746-8-dmatlack@google.com>
Message-ID: <CALzav=cmZzNetCqWjQaG=VO+Fs=TTL3izU01vDUFd-=mRnDAuw@mail.gmail.com>

On Thu, Apr 23, 2026 at 2:23?PM David Matlack <dmatlack at google.com> wrote:
>
> Inherit Access Control Services (ACS) flags on all incoming preserved
> devices (endpoints and upstream bridges) during a Live Update.
>
> Inheriting ACS flags avoids changing routing rules while memory
> transactions are in flight from preserved devices. This is also strictly
> necessary to ensure that IOMMU group assignments do not change across
> a Live Update for preserved devices, as changing ACS configurations can
> split or merge IOMMU groups.
>
> Signed-off-by: David Matlack <dmatlack at google.com>

> @@ -1017,6 +1017,15 @@ void pci_enable_acs(struct pci_dev *dev)
>         bool enable_acs = false;
>         int pos;
>
> +       /*
> +        * ACS flags must be inherited from the previous kernel during a Live
> +        * Update for preserved devices (which includes endpoints and any
> +        * upstream bridges) to avoid changing routing while memory transactions
> +        * are in flight.
> +        */
> +       if (pci_liveupdate_incoming(dev))
> +               return;

Sashiko caught that pci_enable_acs() is also called from
pci_restore_state() so this patch needs a fix to ensure that ACS flags
do not change or get lost after a save+reset+restore.


From skhawaja at google.com  Mon May  4 17:23:16 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:23:16 +0000
Subject: [RFC PATCH 0/4] dma-mapping: Add preservation of direct allocations
Message-ID: <20260505002320.2210067-1-skhawaja@google.com>

This is an RFC to discuss the preservation of DMA allocations for
devices that are using direct allocation mode. Note preservation here
means preservation of physical memory only using KHO. IOVAs and IOMMU
mappings are out of scope.

The complexity and need of this was discussed in the thread mentioned
below. It was proposed to allow preservation of only a subset of
allocations with minimum support.

https://lore.kernel.org/all/aepfYzkI7NsVGCF0 at google.com/
https://lore.kernel.org/all/aepRy7Gp7Ng85Zr7 at willie-the-truck/

Note DMA allocation allows allocation from various allocators in various
configurations. This RFC supports preservation with,

- Direct Allocation Only.
- Pages allocated through alloc_page (Buddy allocator).
- No CMA support.
- No DMA allocators.

The changes are also pushed here:
https://github.com/samikhawaja/linux/tree/dma-alloc-preserve-direct

Looking forward to your feedback on this.

Thanks

Samiullah Khawaja (4):
  dma: Add DMA allocation preservation KHO ABI
  dma/pool: Add an API to check if DMA allocation is from pool
  dma-direct: Add API to preserve/restore allocations
  dma-mapping: Add API to preserve/restore DMA allocation

 include/linux/dma-direct.h        |  29 ++++++
 include/linux/dma-map-ops.h       |   1 +
 include/linux/dma-mapping.h       |  50 +++++++++
 include/linux/kho/abi/dma_alloc.h |  30 ++++++
 kernel/dma/Kconfig                |   3 +
 kernel/dma/direct.c               | 163 ++++++++++++++++++++++++++++++
 kernel/dma/mapping.c              |  52 ++++++++++
 kernel/dma/pool.c                 |  13 +++
 8 files changed, 341 insertions(+)
 create mode 100644 include/linux/kho/abi/dma_alloc.h


base-commit: 9974969c14031a097d6b45bcb7a06bb4aa525c40
prerequisite-patch-id: 41f13f37cb3b439f17dfa4e3f2d5c767aaef0346
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:27:33 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:27:33 +0000
Subject: [RFC PATCH 0/4] dma-mapping: Add preservation of direct allocations
Message-ID: <20260505002737.2213734-1-skhawaja@google.com>

This is an RFC to discuss the preservation of DMA allocations for
devices that are using direct allocation mode. Note preservation here
means preservation of physical memory only using KHO. IOVAs and IOMMU
mappings are out of scope.

The complexity and need of this was discussed in the thread mentioned
below. It was proposed to allow preservation of only a subset of
allocations with minimum support.

https://lore.kernel.org/all/aepfYzkI7NsVGCF0 at google.com/
https://lore.kernel.org/all/aepRy7Gp7Ng85Zr7 at willie-the-truck/

Note DMA allocation allows allocation from various allocators in various
configurations. This RFC supports preservation with,

- Direct Allocation Only.
- Pages allocated through alloc_page (Buddy allocator).
- No CMA support.
- No DMA allocators.

The changes are also pushed here:
https://github.com/samikhawaja/linux/tree/dma-alloc-preserve-direct

Looking forward to your feedback on this.

Thanks

Samiullah Khawaja (4):
  dma: Add DMA allocation preservation KHO ABI
  dma/pool: Add an API to check if DMA allocation is from pool
  dma-direct: Add API to preserve/restore allocations
  dma-mapping: Add API to preserve/restore DMA allocation

 include/linux/dma-direct.h        |  29 ++++++
 include/linux/dma-map-ops.h       |   1 +
 include/linux/dma-mapping.h       |  50 +++++++++
 include/linux/kho/abi/dma_alloc.h |  30 ++++++
 kernel/dma/Kconfig                |   3 +
 kernel/dma/direct.c               | 163 ++++++++++++++++++++++++++++++
 kernel/dma/mapping.c              |  52 ++++++++++
 kernel/dma/pool.c                 |  13 +++
 8 files changed, 341 insertions(+)
 create mode 100644 include/linux/kho/abi/dma_alloc.h


base-commit: 9974969c14031a097d6b45bcb7a06bb4aa525c40
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:27:34 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:27:34 +0000
Subject: [RFC PATCH 1/4] dma: Add DMA allocation preservation KHO ABI
In-Reply-To: <20260505002737.2213734-1-skhawaja@google.com>
References: <20260505002737.2213734-1-skhawaja@google.com>
Message-ID: <20260505002737.2213734-2-skhawaja@google.com>

The DMA allocations can be backed by a variety of allocators. Add KHO
ABI for the preservation of contiguous allocations that are done through
dma-direct.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/kho/abi/dma_alloc.h | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 include/linux/kho/abi/dma_alloc.h

diff --git a/include/linux/kho/abi/dma_alloc.h b/include/linux/kho/abi/dma_alloc.h
new file mode 100644
index 000000000000..46e61db81abe
--- /dev/null
+++ b/include/linux/kho/abi/dma_alloc.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_KHO_ABI_DMA_ALLOC_H
+#define _LINUX_KHO_ABI_DMA_ALLOC_H
+
+#include <linux/types.h>
+
+/**
+ * DOC: DMA Alloc ABI
+ *
+ * This header defines the structures used to serialize the state of DMA
+ * allocations, done by device driver, across a Live Update.
+ *
+ * Only DMA allocations done through dma-direct that are contiguous and
+ * allocated using alloc_page are supported.
+ */
+
+/**
+ * struct dma_alloc_ser - Serialized state of a single DMA allocation
+ * @page_phys: Physical address of the preserved pages
+ * @size: Size of the DMA allocation
+ * @force_decrypted: Whether the memory is force decrypted in previous kernel
+ */
+struct dma_alloc_ser {
+	u64 page_phys;
+	u64 size;
+	u8 force_decrypted;
+	u8 padding[7];
+} __packed;
+
+#endif /* _LINUX_KHO_ABI_DMA_ALLOC_H */
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:27:35 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:27:35 +0000
Subject: [RFC PATCH 2/4] dma/pool: Add an API to check if DMA allocation is
 from pool
In-Reply-To: <20260505002737.2213734-1-skhawaja@google.com>
References: <20260505002737.2213734-1-skhawaja@google.com>
Message-ID: <20260505002737.2213734-3-skhawaja@google.com>

DMA allocations can be done through DMA pools, add an API that can be
used to check if an allocation is done from a pool. This will be used in
the later commit during preservation of DMA allocation.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/dma-map-ops.h |  1 +
 kernel/dma/pool.c           | 13 +++++++++++++
 2 files changed, 14 insertions(+)

diff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h
index 6a1832a73cad..6a0bc4ea2467 100644
--- a/include/linux/dma-map-ops.h
+++ b/include/linux/dma-map-ops.h
@@ -216,6 +216,7 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t size,
 		bool (*phys_addr_ok)(struct device *, phys_addr_t, size_t));
 bool dma_free_from_pool(struct device *dev, void *start, size_t size);
 
+bool dma_is_from_pool(struct device *dev, void *start, size_t size);
 int dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,
 		dma_addr_t dma_start, u64 size);
 
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c
index 2b2fbb709242..32ce4d6d7683 100644
--- a/kernel/dma/pool.c
+++ b/kernel/dma/pool.c
@@ -307,3 +307,16 @@ bool dma_free_from_pool(struct device *dev, void *start, size_t size)
 
 	return false;
 }
+
+bool dma_is_from_pool(struct device *dev, void *start, size_t size)
+{
+	struct gen_pool *pool = NULL;
+
+	while ((pool = dma_guess_pool(pool, 0))) {
+		if (!gen_pool_has_addr(pool, (unsigned long)start, size))
+			continue;
+		return true;
+	}
+
+	return false;
+}
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:27:36 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:27:36 +0000
Subject: [RFC PATCH 3/4] dma-direct: Add API to preserve/restore allocations
In-Reply-To: <20260505002737.2213734-1-skhawaja@google.com>
References: <20260505002737.2213734-1-skhawaja@google.com>
Message-ID: <20260505002737.2213734-4-skhawaja@google.com>

Add an API to preserve/restore the DMA direct allocation for liveupdate.
The underlying memory is preserved/restored using KHO. During restore
the memory is setup based on the device configuration, gfp flags and
allocation attributes. Once restored, the driver can use the usual
dma_free* API to deallocate the restored DMA allocation.

This API will be used to add support in dma_alloc* APIs to
preseve/restore the DMA allocations.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/dma-direct.h |  29 +++++++
 kernel/dma/Kconfig         |   3 +
 kernel/dma/direct.c        | 163 +++++++++++++++++++++++++++++++++++++
 3 files changed, 195 insertions(+)

diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h
index c249912456f9..0efe2bc1a815 100644
--- a/include/linux/dma-direct.h
+++ b/include/linux/dma-direct.h
@@ -141,6 +141,35 @@ static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size,
 u64 dma_direct_get_required_mask(struct device *dev);
 void *dma_direct_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,
 		gfp_t gfp, unsigned long attrs);
+
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state);
+void dma_direct_unpreserve_allocation(struct device *dev, u64 state);
+void *dma_direct_restore_allocation(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state);
+#else
+static inline int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+						 size_t size, dma_addr_t dma_handle,
+						 unsigned long attrs, u64 *state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void dma_direct_unpreserve_allocation(struct device *dev, u64 state)
+{
+}
+
+static inline void *dma_direct_restore_allocation(struct device *dev, size_t size,
+						  dma_addr_t *dma_handle, gfp_t gfp,
+						  unsigned long attrs, u64 state)
+{
+	return NULL;
+}
+#endif
+
 void dma_direct_free(struct device *dev, size_t size, void *cpu_addr,
 		dma_addr_t dma_addr, unsigned long attrs);
 struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
diff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig
index bfef21b4a9ae..d92852942c6c 100644
--- a/kernel/dma/Kconfig
+++ b/kernel/dma/Kconfig
@@ -265,3 +265,6 @@ config DMA_MAP_BENCHMARK
 	  performance of dma_(un)map_page.
 
 	  See tools/testing/selftests/dma/dma_map_benchmark.c
+
+config DMA_LIVEUPDATE
+	bool "Enable preservation of DMA direct allocations"
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index ec887f443741..c2b98f91900a 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -6,6 +6,8 @@
  */
 #include <linux/memblock.h> /* for max_pfn */
 #include <linux/export.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/dma_alloc.h>
 #include <linux/mm.h>
 #include <linux/dma-map-ops.h>
 #include <linux/scatterlist.h>
@@ -307,6 +309,167 @@ void *dma_direct_alloc(struct device *dev, size_t size,
 	return NULL;
 }
 
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state)
+{
+	struct dma_alloc_ser *ser;
+	int ret;
+
+	if (!kho_is_enabled())
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_DMA_CMA))
+		return -EOPNOTSUPP;
+
+	if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
+	    !force_dma_unencrypted(dev) && !is_swiotlb_for_alloc(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC) &&
+	    !dev_is_dma_coherent(dev) &&
+	    !is_swiotlb_for_alloc(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL) &&
+	    !dev_is_dma_coherent(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) &&
+	    dma_is_from_pool(dev, cpu_addr, PAGE_ALIGN(size)))
+		return -EOPNOTSUPP;
+
+	ser = kho_alloc_preserve(sizeof(*ser));
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	ser->page_phys = dma_to_phys(dev, dma_handle);
+	ser->force_decrypted = force_dma_unencrypted(dev);
+	ser->size = size;
+
+	ret = kho_preserve_pages(phys_to_page(ser->page_phys),
+				 size >> PAGE_SHIFT);
+	if (ret) {
+		kho_unpreserve_free(ser);
+		return ret;
+	}
+
+	*state = virt_to_phys(ser);
+	return 0;
+}
+
+void dma_direct_unpreserve_allocation(struct device *dev, u64 state)
+{
+	struct dma_alloc_ser *ser;
+
+	if (!kho_is_enabled())
+		return;
+
+	ser = phys_to_virt(state);
+	kho_unpreserve_pages(phys_to_page(ser->page_phys),
+			     ser->size >> PAGE_SHIFT);
+	kho_unpreserve_free(ser);
+}
+
+void *dma_direct_restore_allocation(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state)
+{
+	bool remap = false, set_uncached = false;
+	struct dma_alloc_ser *ser = NULL;
+	struct page *page;
+	void *cpu_addr;
+
+	if (!kho_is_enabled())
+		return NULL;
+
+	ser = phys_to_virt(state);
+	page = phys_to_page(ser->page_phys);
+
+	if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
+	    !force_dma_unencrypted(dev) && !is_swiotlb_for_alloc(dev))
+		return NULL;
+
+	if (!dev_is_dma_coherent(dev)) {
+		if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC) &&
+		    !is_swiotlb_for_alloc(dev))
+			return NULL;
+
+		if (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL))
+			return NULL;
+
+		set_uncached = IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED);
+		remap = IS_ENABLED(CONFIG_DMA_DIRECT_REMAP);
+		if (!set_uncached && !remap)
+			return NULL;
+	}
+
+	if (PageHighMem(page)) {
+		remap = true;
+		set_uncached = false;
+	}
+
+	/*
+	 * Remapping will be blocking so return error. The preserved memory
+	 * might be already decrypted in the previous kernel, but the decryption
+	 * call is not guaranteed to be non-blocking so return error always if
+	 * decryption is required.
+	 */
+	if ((remap || force_dma_unencrypted(dev)) &&
+	    dma_direct_use_pool(dev, gfp))
+		return NULL;
+
+	/*
+	 * Encryption scheme changed between two kernels and this might cause
+	 * issues if device/driver is not handling it properly.
+	 */
+	WARN_ON_ONCE(ser->force_decrypted != force_dma_unencrypted(dev));
+
+	/*
+	 * arch_dma_prep_coherent() should make sure that any cache lines from
+	 * the previous kernel, if the device was coherent previously or cached
+	 * mapping in this kernel during init are not problamatic for
+	 * non-coherent allocations.
+	 */
+	if (remap) {
+		pgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);
+
+		if (force_dma_unencrypted(dev))
+			prot = pgprot_decrypted(prot);
+
+		arch_dma_prep_coherent(page, size);
+
+		cpu_addr = dma_common_contiguous_remap(page, size, prot,
+						       __builtin_return_address(0));
+		if (!cpu_addr)
+			return NULL;
+	} else {
+		cpu_addr = page_address(page);
+		if (dma_set_decrypted(dev, cpu_addr, size))
+			return NULL;
+	}
+
+	if (set_uncached) {
+		arch_dma_prep_coherent(page, size);
+		cpu_addr = arch_dma_set_uncached(cpu_addr, size);
+		if (IS_ERR(cpu_addr))
+			return NULL;
+	}
+
+	*dma_handle = phys_to_dma_direct(dev, ser->page_phys);
+
+	/*
+	 * Cannot free the restored pages on error here as these might be in use
+	 * by a device with direct allocation in the previous kernel.
+	 */
+	WARN_ON(!kho_restore_pages(ser->page_phys,
+				   ser->size >> PAGE_SHIFT));
+	kho_restore_free(ser);
+	return cpu_addr;
+}
+#endif
+
 void dma_direct_free(struct device *dev, size_t size,
 		void *cpu_addr, dma_addr_t dma_addr, unsigned long attrs)
 {
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:27:37 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue,  5 May 2026 00:27:37 +0000
Subject: [RFC PATCH 4/4] dma-mapping: Add API to preserve/restore DMA allocation
In-Reply-To: <20260505002737.2213734-1-skhawaja@google.com>
References: <20260505002737.2213734-1-skhawaja@google.com>
Message-ID: <20260505002737.2213734-5-skhawaja@google.com>

Add new DMA APIs that allow preserving/restoring DMA allocations across
live update.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/dma-mapping.h | 50 +++++++++++++++++++++++++++++++++++
 kernel/dma/mapping.c        | 52 +++++++++++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+)

diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h
index db8ab24a54f4..3756fc15467b 100644
--- a/include/linux/dma-mapping.h
+++ b/include/linux/dma-mapping.h
@@ -210,6 +210,15 @@ void *dma_vmap_noncontiguous(struct device *dev, size_t size,
 void dma_vunmap_noncontiguous(struct device *dev, void *vaddr);
 int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,
 		size_t size, struct sg_table *sgt);
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				  size_t size, dma_addr_t dma_handle,
+				  unsigned long attrs, u64 *state);
+void dma_unpreserve_allocation(struct device *dev, u64 state);
+void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+				   dma_addr_t *dma_handle, gfp_t gfp,
+				   unsigned long attrs, u64 state);
+#endif
 #else /* CONFIG_HAS_DMA */
 static inline dma_addr_t dma_map_page_attrs(struct device *dev,
 		struct page *page, size_t offset, size_t size,
@@ -496,6 +505,26 @@ static inline bool dma_need_unmap(struct device *dev)
 }
 #endif /* !CONFIG_HAS_DMA || !CONFIG_DMA_NEED_SYNC */
 
+#if !defined(CONFIG_DMA_LIVEUPDATE) || !defined(CONFIG_HAS_DMA)
+static inline int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+						size_t size, dma_addr_t dma_handle,
+						unsigned long attrs, u64 *state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void dma_unpreserve_allocation(struct device *dev, u64 state)
+{
+}
+
+static inline void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+						 dma_addr_t *dma_handle, gfp_t gfp,
+						 unsigned long attrs, u64 state)
+{
+	return NULL;
+}
+#endif
+
 struct page *dma_alloc_pages(struct device *dev, size_t size,
 		dma_addr_t *dma_handle, enum dma_data_direction dir, gfp_t gfp);
 void dma_free_pages(struct device *dev, size_t size, struct page *page,
@@ -618,6 +647,27 @@ static inline void *dma_alloc_coherent(struct device *dev, size_t size,
 			(gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);
 }
 
+static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,
+						   size_t size, dma_addr_t dma_handle, u64 *state)
+{
+	return dma_preserve_allocation_attrs(dev, cpu_addr, size,
+					     dma_handle, 0, state);
+}
+
+static inline void dma_unpreserve_coherent_allocation(struct device *dev, u64 state)
+{
+	dma_unpreserve_allocation(dev, state);
+}
+
+static inline void *dma_restore_coherent_allocation(struct device *dev, size_t size,
+						    dma_addr_t *dma_handle,
+						    gfp_t gfp, u64 state)
+{
+	return dma_restore_allocation_attrs(dev, size, dma_handle, gfp,
+					    (gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0,
+					    state);
+}
+
 static inline void dma_free_coherent(struct device *dev, size_t size,
 		void *cpu_addr, dma_addr_t dma_handle)
 {
diff --git a/kernel/dma/mapping.c b/kernel/dma/mapping.c
index 23ed8eb9233e..c315b74a0884 100644
--- a/kernel/dma/mapping.c
+++ b/kernel/dma/mapping.c
@@ -12,6 +12,8 @@
 #include <linux/gfp.h>
 #include <linux/iommu-dma.h>
 #include <linux/kmsan.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/dma_alloc.h>
 #include <linux/of_device.h>
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
@@ -628,6 +630,56 @@ u64 dma_get_required_mask(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(dma_get_required_mask);
 
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				  size_t size, dma_addr_t dma_handle,
+				  unsigned long attrs, u64 *state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+
+#ifdef CONFIG_DMA_DECLARE_COHERENT
+	return -EOPNOTSUPP;
+#endif
+
+	if (dma_alloc_direct(dev, ops))
+		return dma_direct_preserve_allocation(dev, cpu_addr, size,
+						      dma_handle, attrs,
+						      state);
+
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(dma_preserve_allocation_attrs);
+
+void dma_unpreserve_allocation(struct device *dev, u64 state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+
+	if (dma_alloc_direct(dev, ops))
+		dma_direct_unpreserve_allocation(dev, state);
+}
+EXPORT_SYMBOL(dma_unpreserve_allocation);
+
+void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+				   dma_addr_t *dma_handle, gfp_t gfp,
+				   unsigned long attrs, u64 state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+
+	WARN_ON_ONCE(!dev->coherent_dma_mask);
+
+#ifdef CONFIG_DMA_DECLARE_COHERENT
+	return NULL;
+#endif
+
+	if (dma_alloc_direct(dev, ops))
+		return dma_direct_restore_allocation(dev, size, dma_handle,
+						     gfp, attrs, state);
+
+	return NULL;
+}
+EXPORT_SYMBOL(dma_restore_allocation_attrs);
+#endif
+
 void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,
 		gfp_t flag, unsigned long attrs)
 {
-- 
2.54.0.545.g6539524ca2-goog



From skhawaja at google.com  Mon May  4 17:29:58 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 5 May 2026 00:29:58 +0000
Subject: [RFC PATCH 0/4] dma-mapping: Add preservation of direct
 allocations
In-Reply-To: <20260505002320.2210067-1-skhawaja@google.com>
References: <20260505002320.2210067-1-skhawaja@google.com>
Message-ID: <afk5m8xwYqNstzPw@google.com>

On Tue, May 05, 2026 at 12:23:16AM +0000, Samiullah Khawaja wrote:
>This is an RFC to discuss the preservation of DMA allocations for
>devices that are using direct allocation mode. Note preservation here
>means preservation of physical memory only using KHO. IOVAs and IOMMU
>mappings are out of scope.
>

Sorry, please ignore this separate cover letter email. It was sent by
mistake and has wrong prerequisite-patch-id.


From jloeser at linux.microsoft.com  Tue May  5 04:20:14 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Tue, 5 May 2026 04:20:14 -0700 (PDT)
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <20260429133928.850721-2-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org> <20260429133928.850721-2-pratyush@kernel.org>
Message-ID: <331da42-b255-326b-cd5-28d07a7e2cb2@linux.microsoft.com>

On Wed, 29 Apr 2026, Pratyush Yadav wrote:

> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
> The KHO radix tree is a data structure that can track the presence or
> absence of an arbitrary key, with nothing inherently tied to KHO memory
> preservation tracking. This was one of the design goals of the radix
> tree. This was done to enable it to be re-used by other users of KHO.

"Arbitrary key": Not quite the complete 64-bits, rather 64 - PAGE_SIZE, 
correct?

> + * kho_radix_add_key - Add a key to the radix tree.
>  * @tree: The KHO radix tree.
> + * @key: The key to add.
>  *
> + * This function traverses the radix tree based on the key provided. It sets the
> + * corresponding bit in the leaf bitmap to mark the key as present. If
> + * intermediate nodes do not exist along the path, they are allocated and added
> + * to the tree.

Consider adding a note on the key-width limitation.

Best,
Jork


From pratyush at kernel.org  Tue May  5 05:54:28 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 05 May 2026 14:54:28 +0200
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <331da42-b255-326b-cd5-28d07a7e2cb2@linux.microsoft.com> (Jork
	Loeser's message of "Tue, 5 May 2026 04:20:14 -0700 (PDT)")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-2-pratyush@kernel.org>
	<331da42-b255-326b-cd5-28d07a7e2cb2@linux.microsoft.com>
Message-ID: <2vxztssm10fv.fsf@kernel.org>

On Tue, May 05 2026, Jork Loeser wrote:

> On Wed, 29 Apr 2026, Pratyush Yadav wrote:
>
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>
>> The KHO radix tree is a data structure that can track the presence or
>> absence of an arbitrary key, with nothing inherently tied to KHO memory
>> preservation tracking. This was one of the design goals of the radix
>> tree. This was done to enable it to be re-used by other users of KHO.
>
> "Arbitrary key": Not quite the complete 64-bits, rather 64 - PAGE_SIZE, correct?

Right, 64 - PAGE_SHIFT.

>
>> + * kho_radix_add_key - Add a key to the radix tree.
>>  * @tree: The KHO radix tree.
>> + * @key: The key to add.
>>  *
>> + * This function traverses the radix tree based on the key provided. It sets the
>> + * corresponding bit in the leaf bitmap to mark the key as present. If
>> + * intermediate nodes do not exist along the path, they are allocated and added
>> + * to the tree.
>
> Consider adding a note on the key-width limitation.

I think adding a runtime check would also be a good idea here.

-- 
Regards,
Pratyush Yadav


From lkp at intel.com  Tue May  5 07:39:37 2026
From: lkp at intel.com (kernel test robot)
Date: Tue, 05 May 2026 22:39:37 +0800
Subject: [liveupdate:next] BUILD SUCCESS
 34e8f02817e31826e76bb2ded48bf28fe921f20b
Message-ID: <202605052226.KhaMXhKJ-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git next
branch HEAD: 34e8f02817e31826e76bb2ded48bf28fe921f20b  memblock tests: define MIGRATE_CMA

elapsed time: 731m

configs tested: 221
configs skipped: 14

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260505    gcc-8.5.0
arc                   randconfig-002-20260505    gcc-8.5.0
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                            mmp2_defconfig    gcc-15.2.0
arm                   randconfig-001-20260505    gcc-8.5.0
arm                   randconfig-002-20260505    gcc-8.5.0
arm                   randconfig-003-20260505    gcc-8.5.0
arm                   randconfig-004-20260505    gcc-8.5.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260505    gcc-14.3.0
arm64                 randconfig-002-20260505    gcc-14.3.0
arm64                 randconfig-003-20260505    gcc-14.3.0
arm64                 randconfig-004-20260505    gcc-14.3.0
csky                             alldefconfig    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260505    gcc-14.3.0
csky                  randconfig-002-20260505    gcc-14.3.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260505    clang-23
hexagon               randconfig-001-20260505    gcc-11.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260505    clang-23
hexagon               randconfig-002-20260505    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                 buildonly-randconfig-001    gcc-14
i386        buildonly-randconfig-001-20260505    gcc-14
i386                 buildonly-randconfig-002    gcc-14
i386        buildonly-randconfig-002-20260505    gcc-14
i386                 buildonly-randconfig-003    gcc-14
i386        buildonly-randconfig-003-20260505    gcc-14
i386                 buildonly-randconfig-004    gcc-14
i386        buildonly-randconfig-004-20260505    gcc-14
i386                 buildonly-randconfig-005    gcc-14
i386        buildonly-randconfig-005-20260505    gcc-14
i386                 buildonly-randconfig-006    gcc-14
i386        buildonly-randconfig-006-20260505    gcc-14
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260505    clang-20
i386                  randconfig-002-20260505    clang-20
i386                  randconfig-003-20260505    clang-20
i386                  randconfig-004-20260505    clang-20
i386                  randconfig-005-20260505    clang-20
i386                  randconfig-006-20260505    clang-20
i386                  randconfig-007-20260505    clang-20
i386                  randconfig-011-20260505    clang-20
i386                  randconfig-012-20260505    clang-20
i386                  randconfig-013-20260505    clang-20
i386                  randconfig-014-20260505    clang-20
i386                  randconfig-015-20260505    clang-20
i386                  randconfig-016-20260505    clang-20
i386                  randconfig-017-20260505    clang-20
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260505    clang-23
loongarch             randconfig-001-20260505    gcc-11.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260505    clang-23
loongarch             randconfig-002-20260505    gcc-11.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                  decstation_64_defconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260505    clang-23
nios2                 randconfig-001-20260505    gcc-11.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260505    clang-23
nios2                 randconfig-002-20260505    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc         de0_nano_multicore_defconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                generic-32bit_defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-14.3.0
parisc                randconfig-001-20260505    gcc-14.3.0
parisc                         randconfig-002    gcc-14.3.0
parisc                randconfig-002-20260505    gcc-14.3.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                        randconfig-001    gcc-14.3.0
powerpc               randconfig-001-20260505    gcc-14.3.0
powerpc                        randconfig-002    gcc-14.3.0
powerpc               randconfig-002-20260505    gcc-14.3.0
powerpc                     tqm8541_defconfig    clang-23
powerpc64                      randconfig-001    gcc-14.3.0
powerpc64             randconfig-001-20260505    gcc-14.3.0
powerpc64                      randconfig-002    gcc-14.3.0
powerpc64             randconfig-002-20260505    gcc-14.3.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260505    gcc-10.5.0
riscv                 randconfig-002-20260505    gcc-10.5.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260505    gcc-10.5.0
s390                  randconfig-002-20260505    gcc-10.5.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                    randconfig-001-20260505    gcc-10.5.0
sh                    randconfig-002-20260505    gcc-10.5.0
sh                          urquell_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-15.2.0
sparc                 randconfig-001-20260505    gcc-15.2.0
sparc                          randconfig-002    gcc-15.2.0
sparc                 randconfig-002-20260505    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-15.2.0
sparc64               randconfig-001-20260505    gcc-15.2.0
sparc64                        randconfig-002    gcc-15.2.0
sparc64               randconfig-002-20260505    gcc-15.2.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-15.2.0
um                    randconfig-001-20260505    gcc-15.2.0
um                             randconfig-002    gcc-15.2.0
um                    randconfig-002-20260505    gcc-15.2.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260505    clang-20
x86_64      buildonly-randconfig-002-20260505    clang-20
x86_64      buildonly-randconfig-003-20260505    clang-20
x86_64      buildonly-randconfig-004-20260505    clang-20
x86_64      buildonly-randconfig-005-20260505    clang-20
x86_64      buildonly-randconfig-006-20260505    clang-20
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                         randconfig-001    clang-20
x86_64                randconfig-001-20260505    clang-20
x86_64                         randconfig-002    clang-20
x86_64                randconfig-002-20260505    clang-20
x86_64                         randconfig-003    clang-20
x86_64                randconfig-003-20260505    clang-20
x86_64                         randconfig-004    clang-20
x86_64                randconfig-004-20260505    clang-20
x86_64                         randconfig-005    clang-20
x86_64                randconfig-005-20260505    clang-20
x86_64                         randconfig-006    clang-20
x86_64                randconfig-006-20260505    clang-20
x86_64                         randconfig-011    clang-20
x86_64                randconfig-011-20260505    clang-20
x86_64                         randconfig-012    clang-20
x86_64                randconfig-012-20260505    clang-20
x86_64                         randconfig-013    clang-20
x86_64                randconfig-013-20260505    clang-20
x86_64                         randconfig-014    clang-20
x86_64                randconfig-014-20260505    clang-20
x86_64                         randconfig-015    clang-20
x86_64                randconfig-015-20260505    clang-20
x86_64                         randconfig-016    clang-20
x86_64                randconfig-016-20260505    clang-20
x86_64                randconfig-071-20260505    clang-20
x86_64                randconfig-071-20260505    gcc-14
x86_64                randconfig-072-20260505    clang-20
x86_64                randconfig-073-20260505    clang-20
x86_64                randconfig-073-20260505    gcc-14
x86_64                randconfig-074-20260505    clang-20
x86_64                randconfig-074-20260505    gcc-14
x86_64                randconfig-075-20260505    clang-20
x86_64                randconfig-076-20260505    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                         randconfig-001    gcc-15.2.0
xtensa                randconfig-001-20260505    gcc-15.2.0
xtensa                         randconfig-002    gcc-15.2.0
xtensa                randconfig-002-20260505    gcc-15.2.0

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


From lkp at intel.com  Tue May  5 07:41:30 2026
From: lkp at intel.com (kernel test robot)
Date: Tue, 05 May 2026 22:41:30 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 7b0b68b2b95606e65594958686833e53423f58f2
Message-ID: <202605052220.9ieZmn1z-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: 7b0b68b2b95606e65594958686833e53423f58f2  mm/memfd_luo: document preservation of file seals

elapsed time: 733m

configs tested: 221
configs skipped: 14

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260505    gcc-8.5.0
arc                   randconfig-002-20260505    gcc-8.5.0
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                            mmp2_defconfig    gcc-15.2.0
arm                   randconfig-001-20260505    gcc-8.5.0
arm                   randconfig-002-20260505    gcc-8.5.0
arm                   randconfig-003-20260505    gcc-8.5.0
arm                   randconfig-004-20260505    gcc-8.5.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260505    gcc-14.3.0
arm64                 randconfig-002-20260505    gcc-14.3.0
arm64                 randconfig-003-20260505    gcc-14.3.0
arm64                 randconfig-004-20260505    gcc-14.3.0
csky                             alldefconfig    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260505    gcc-14.3.0
csky                  randconfig-002-20260505    gcc-14.3.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260505    clang-23
hexagon               randconfig-001-20260505    gcc-11.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260505    clang-23
hexagon               randconfig-002-20260505    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                 buildonly-randconfig-001    gcc-14
i386        buildonly-randconfig-001-20260505    gcc-14
i386                 buildonly-randconfig-002    gcc-14
i386        buildonly-randconfig-002-20260505    gcc-14
i386                 buildonly-randconfig-003    gcc-14
i386        buildonly-randconfig-003-20260505    gcc-14
i386                 buildonly-randconfig-004    gcc-14
i386        buildonly-randconfig-004-20260505    gcc-14
i386                 buildonly-randconfig-005    gcc-14
i386        buildonly-randconfig-005-20260505    gcc-14
i386                 buildonly-randconfig-006    gcc-14
i386        buildonly-randconfig-006-20260505    gcc-14
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260505    clang-20
i386                  randconfig-002-20260505    clang-20
i386                  randconfig-003-20260505    clang-20
i386                  randconfig-004-20260505    clang-20
i386                  randconfig-005-20260505    clang-20
i386                  randconfig-006-20260505    clang-20
i386                  randconfig-007-20260505    clang-20
i386                  randconfig-011-20260505    clang-20
i386                  randconfig-012-20260505    clang-20
i386                  randconfig-013-20260505    clang-20
i386                  randconfig-014-20260505    clang-20
i386                  randconfig-015-20260505    clang-20
i386                  randconfig-016-20260505    clang-20
i386                  randconfig-017-20260505    clang-20
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260505    clang-23
loongarch             randconfig-001-20260505    gcc-11.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260505    clang-23
loongarch             randconfig-002-20260505    gcc-11.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                  decstation_64_defconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260505    clang-23
nios2                 randconfig-001-20260505    gcc-11.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260505    clang-23
nios2                 randconfig-002-20260505    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc         de0_nano_multicore_defconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                generic-32bit_defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-14.3.0
parisc                randconfig-001-20260505    gcc-14.3.0
parisc                         randconfig-002    gcc-14.3.0
parisc                randconfig-002-20260505    gcc-14.3.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                        randconfig-001    gcc-14.3.0
powerpc               randconfig-001-20260505    gcc-14.3.0
powerpc                        randconfig-002    gcc-14.3.0
powerpc               randconfig-002-20260505    gcc-14.3.0
powerpc                     tqm8541_defconfig    clang-23
powerpc64                      randconfig-001    gcc-14.3.0
powerpc64             randconfig-001-20260505    gcc-14.3.0
powerpc64                      randconfig-002    gcc-14.3.0
powerpc64             randconfig-002-20260505    gcc-14.3.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260505    gcc-10.5.0
riscv                 randconfig-002-20260505    gcc-10.5.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260505    gcc-10.5.0
s390                  randconfig-002-20260505    gcc-10.5.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                    randconfig-001-20260505    gcc-10.5.0
sh                    randconfig-002-20260505    gcc-10.5.0
sh                          urquell_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-15.2.0
sparc                 randconfig-001-20260505    gcc-15.2.0
sparc                          randconfig-002    gcc-15.2.0
sparc                 randconfig-002-20260505    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-15.2.0
sparc64               randconfig-001-20260505    gcc-15.2.0
sparc64                        randconfig-002    gcc-15.2.0
sparc64               randconfig-002-20260505    gcc-15.2.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-15.2.0
um                    randconfig-001-20260505    gcc-15.2.0
um                             randconfig-002    gcc-15.2.0
um                    randconfig-002-20260505    gcc-15.2.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260505    clang-20
x86_64      buildonly-randconfig-002-20260505    clang-20
x86_64      buildonly-randconfig-003-20260505    clang-20
x86_64      buildonly-randconfig-004-20260505    clang-20
x86_64      buildonly-randconfig-005-20260505    clang-20
x86_64      buildonly-randconfig-006-20260505    clang-20
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                         randconfig-001    clang-20
x86_64                randconfig-001-20260505    clang-20
x86_64                         randconfig-002    clang-20
x86_64                randconfig-002-20260505    clang-20
x86_64                         randconfig-003    clang-20
x86_64                randconfig-003-20260505    clang-20
x86_64                         randconfig-004    clang-20
x86_64                randconfig-004-20260505    clang-20
x86_64                         randconfig-005    clang-20
x86_64                randconfig-005-20260505    clang-20
x86_64                         randconfig-006    clang-20
x86_64                randconfig-006-20260505    clang-20
x86_64                         randconfig-011    clang-20
x86_64                randconfig-011-20260505    clang-20
x86_64                         randconfig-012    clang-20
x86_64                randconfig-012-20260505    clang-20
x86_64                         randconfig-013    clang-20
x86_64                randconfig-013-20260505    clang-20
x86_64                         randconfig-014    clang-20
x86_64                randconfig-014-20260505    clang-20
x86_64                         randconfig-015    clang-20
x86_64                randconfig-015-20260505    clang-20
x86_64                         randconfig-016    clang-20
x86_64                randconfig-016-20260505    clang-20
x86_64                randconfig-071-20260505    clang-20
x86_64                randconfig-071-20260505    gcc-14
x86_64                randconfig-072-20260505    clang-20
x86_64                randconfig-073-20260505    clang-20
x86_64                randconfig-073-20260505    gcc-14
x86_64                randconfig-074-20260505    clang-20
x86_64                randconfig-074-20260505    gcc-14
x86_64                randconfig-075-20260505    clang-20
x86_64                randconfig-076-20260505    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                         randconfig-001    gcc-15.2.0
xtensa                randconfig-001-20260505    gcc-15.2.0
xtensa                         randconfig-002    gcc-15.2.0
xtensa                randconfig-002-20260505    gcc-15.2.0

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


From robh at kernel.org  Tue May  5 18:47:52 2026
From: robh at kernel.org (Rob Herring)
Date: Tue, 5 May 2026 20:47:52 -0500
Subject: [PATCH 01/11] of: reserved_mem: fix region count for nodes with
 multiple reg entries
In-Reply-To: <20260429065831.1510858-2-chenwandun@lixiang.com>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-2-chenwandun@lixiang.com>
Message-ID: <20260506014752.GA280279-robh@kernel.org>

On Wed, Apr 29, 2026 at 02:58:21PM +0800, Chen Wandun wrote:
> When a reserved-memory node contains multiple reg entries (e.g.,
> reg = <base1 size1>, <base2 size2>), the count used for
> total_reserved_mem_cnt is wrong in two places:
> 
> 1) __reserved_mem_reserve_reg() returns 0 on success regardless of how
>    many regions it reserved in memblock. The caller in
>    fdt_scan_reserved_mem() then increments count by just 1.

Just to make sure, more than 1 worked before the referenced commits? It 
would be easier to just define we only expect/support 1 entry.

> 
> 2) fdt_scan_reserved_mem_late() uses of_flat_dt_get_addr_size() which
>    only reads the first reg entry. Subsequent entries are never
>    initialized via fdt_init_reserved_mem_node(), so their metadata is
>    lost.
> 
> Fix both issues:
>  - Make __reserved_mem_reserve_reg() return the actual number of
>    regions successfully reserved. Update the caller to accumulate
>    the returned count.
>  - Rewrite fdt_scan_reserved_mem_late() to use
>    of_flat_dt_get_addr_size_prop() and iterate all reg entries,
>    initializing each one via fdt_init_reserved_mem_node().
> 
> Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed")
> Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array")


From robh at kernel.org  Tue May  5 18:51:12 2026
From: robh at kernel.org (Rob Herring)
Date: Tue, 5 May 2026 20:51:12 -0500
Subject: [PATCH 02/11] of: reserved_mem: reject reserved memory outside
 physical address range
In-Reply-To: <20260429065831.1510858-3-chenwandun@lixiang.com>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-3-chenwandun@lixiang.com>
Message-ID: <20260506015112.GA286568-robh@kernel.org>

On Wed, Apr 29, 2026 at 02:58:22PM +0800, Chen Wandun wrote:
> early_init_dt_reserve_memory() does not validate whether the region
> falls within physical memory. If a device tree incorrectly specifies a
> reserved memory region outside the physical address range:
> 
>  - For the non-nomap path, memblock_reserve() blindly adds the region
>    to memblock.reserved, creating a stale entry that refers to
>    non-existent memory.
> 
>  - For the nomap path, memblock_mark_nomap() silently fails to match
>    any region in memblock.memory, but still returns success.
> 
> Add a memblock_overlaps_region() check at the entry of
> early_init_dt_reserve_memory() to reject such regions before any
> memblock operation takes place. This also simplifies the existing nomap
> guard: the original "overlaps && is_reserved" condition reduces to just
> "is_reserved", since the overlap with physical memory is already
> guaranteed by the new check.

While I agree, I suspect we already have cases abusing reserved-memory 
like this.

> 
> Signed-off-by: Chen Wandun <chenwandun at lixiang.com>
> Tested-by: Zhao Meijing <zhaomeijing at lixiang.com>
> ---
>  drivers/of/of_reserved_mem.c | 15 +++++++++++----
>  1 file changed, 11 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
> index 9d1b0193864c..03c676052dab 100644
> --- a/drivers/of/of_reserved_mem.c
> +++ b/drivers/of/of_reserved_mem.c
> @@ -112,14 +112,21 @@ static int fdt_fixup_reserved_mem_node(unsigned long node,
>  static int __init early_init_dt_reserve_memory(phys_addr_t base,
>  					       phys_addr_t size, bool nomap)
>  {
> +	if (!memblock_overlaps_region(&memblock.memory, base, size)) {
> +		phys_addr_t end = base + size - 1;
> +
> +		pr_warn("Reserved memory region %pa..%pa is outside of physical memory\n",
> +			&base, &end);
> +		return -EINVAL;
> +	}
> +
>  	if (nomap) {
>  		/*
>  		 * If the memory is already reserved (by another region), we
> -		 * should not allow it to be marked nomap, but don't worry
> -		 * if the region isn't memory as it won't be mapped.
> +		 * should not allow it to be marked nomap. The region being
> +		 * physical memory is guaranteed by the overlap check above.
>  		 */
> -		if (memblock_overlaps_region(&memblock.memory, base, size) &&
> -		    memblock_is_region_reserved(base, size))
> +		if (memblock_is_region_reserved(base, size))
>  			return -EBUSY;
>  
>  		return memblock_mark_nomap(base, size);
> -- 
> 2.43.0
> 


From pasha.tatashin at soleen.com  Tue May  5 21:32:02 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 00:32:02 -0400
Subject: [PATCH v1 1/3] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
Message-ID: <20260506043200.2025677-6-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which
is unrelated to live update where the state is passed to the next
kernel. Skip liveupdate_reboot() in this case to avoid serialization
and prevent sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Tue May  5 21:32:01 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 00:32:01 -0400
Subject: [PATCH v1 0/3] liveupdate: serialization safety and race fixes
Message-ID: <20260506043200.2025677-5-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization between
the reboot process and LUO session management.

1. Skip serialization for context-preserving kexec:
   A preserve_context kexec returns to the current kernel, which is
   unrelated to live update where state is passed to the next kernel.
   Skipping serialization avoids unnecessary work and prevents sessions
   from being left in a frozen state upon return.

2. Block session mutations during serialization:
   Once serialization has started, no new outgoing session mutations
   (creations or file preservations) should occur. A 'rebooting' flag
   is introduced to ensure this.

3. Pin sessions and handle inactive ones:
   Ensures that outgoing sessions are pinned by taking a reference to
   their struct file during serialization. This prevents race conditions
   where a user process might close a session while it is being
   serialized. Inactive sessions are cleaned up and removed.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git
Branch: luo-reboot-sync/v1

Pasha Tatashin (3):
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: block outgoing session mutations during serialization
  liveupdate: pin sessions and handle inactive ones during serialization

 kernel/kexec_core.c              |  8 +--
 kernel/liveupdate/luo_internal.h |  1 +
 kernel/liveupdate/luo_session.c  | 90 ++++++++++++++++++++++++++++++--
 3 files changed, 91 insertions(+), 8 deletions(-)


base-commit: 7b0b68b2b95606e65594958686833e53423f58f2
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Tue May  5 21:32:03 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 00:32:03 -0400
Subject: [PATCH v1 2/3] liveupdate: block outgoing session mutations during serialization
In-Reply-To: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
Message-ID: <20260506043200.2025677-7-pasha.tatashin@soleen.com>

Introduce a 'rebooting' flag in the session header to ensure that once
serialization has started, no new outgoing session mutations (creations
or file preservations) can occur.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..996adc995514 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -84,6 +84,7 @@
  * @header_ser: The header data of serialization array.
  * @ser:        The serialized session data (an array of
  *              `struct luo_session_ser`).
+ * @rebooting:  Indicates that the session list is being serialized.
  * @active:     Set to true when first initialized. If previous kernel did not
  *              send session data, active stays false for incoming.
  */
@@ -93,6 +94,7 @@ struct luo_session_header {
 	struct rw_semaphore rwsem;
 	struct luo_session_header_ser *header_ser;
 	struct luo_session_ser *ser;
+	bool rebooting;
 	bool active;
 };
 
@@ -147,6 +149,9 @@ static int luo_session_insert(struct luo_session_header *sh,
 
 	guard(rwsem_write)(&sh->rwsem);
 
+	if (sh->rebooting)
+		return -EBUSY;
+
 	/*
 	 * For outgoing we should make sure there is room in serialization array
 	 * for new session.
@@ -230,10 +235,15 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 static int luo_session_preserve_fd(struct luo_session *session,
 				   struct luo_ucmd *ucmd)
 {
+	struct luo_session_header *sh = &luo_session_global.outgoing;
 	struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
 	int err;
 
 	guard(mutex)(&session->mutex);
+
+	if (READ_ONCE(sh->rebooting))
+		return -EBUSY;
+
 	err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
 	if (err)
 		return err;
@@ -584,6 +594,8 @@ int luo_session_serialize(void)
 	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
+
+	sh->rebooting = true;
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -598,6 +610,7 @@ int luo_session_serialize(void)
 	return 0;
 
 err_undo:
+	sh->rebooting = false;
 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
 		i--;
 		luo_session_unfreeze_one(session, &sh->ser[i]);
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Tue May  5 21:32:04 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 00:32:04 -0400
Subject: [PATCH v1 3/3] liveupdate: pin sessions and handle inactive ones during serialization
In-Reply-To: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
Message-ID: <20260506043200.2025677-8-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes are still running
concurrently. Even though the system is actively serializing LUO
sessions and will not return to userspace unless liveupdate_reboot()
fails, it is still possible for a user process to close a preserved LUO
session. This creates a race condition where a session could be
destroyed while it is being serialized.

To fix this, we must ensure that we only preserve sessions that are not
closed at the time of serialization. Take a reference to the session's
struct file for all outgoing sessions to pin them during this process.

Handle inactive sessions (where get_file_active() fails) by cleaning
them up and removing them from the outgoing list during the pinning
phase. This ensures serialization can safely proceed with the remaining
active sessions.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h |  1 +
 kernel/liveupdate/luo_session.c  | 77 +++++++++++++++++++++++++++++---
 2 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..1124bab6e0de 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -73,6 +73,7 @@ struct luo_session {
 	struct luo_session_ser *ser;
 	struct list_head list;
 	bool retrieved;
+	struct file *file;
 	struct luo_file_set file_set;
 	struct mutex mutex;
 };
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 996adc995514..b344b64bbced 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -177,12 +177,27 @@ static int luo_session_insert(struct luo_session_header *sh,
 	return 0;
 }
 
+static void __luo_session_remove(struct luo_session_header *sh,
+				 struct luo_session *session)
+{
+	lockdep_assert_held_write(&sh->rwsem);
+	if (!list_empty(&session->list)) {
+		list_del_init(&session->list);
+		sh->count--;
+	}
+}
+
+static void luo_session_unpreserve_files(struct luo_session *session)
+{
+	scoped_guard(mutex, &session->mutex)
+		luo_file_unpreserve_files(&session->file_set);
+}
+
 static void luo_session_remove(struct luo_session_header *sh,
 			       struct luo_session *session)
 {
-	guard(rwsem_write)(&sh->rwsem);
-	list_del(&session->list);
-	sh->count--;
+	scoped_guard(rwsem_write, &sh->rwsem)
+		__luo_session_remove(sh, session);
 }
 
 static int luo_session_finish_one(struct luo_session *session)
@@ -217,12 +232,13 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 		if (err) {
 			pr_warn("Unable to finish session [%s] on release\n",
 				session->name);
+			scoped_guard(mutex, &session->mutex)
+				session->file = NULL;
 			return err;
 		}
 		sh = &luo_session_global.incoming;
 	} else {
-		scoped_guard(mutex, &session->mutex)
-			luo_file_unpreserve_files(&session->file_set);
+		luo_session_unpreserve_files(session);
 		sh = &luo_session_global.outgoing;
 	}
 
@@ -380,16 +396,65 @@ static int luo_session_getfile(struct luo_session *session, struct file **filep)
 	struct file *file;
 
 	lockdep_assert_held(&session->mutex);
+
+	/* serialization may evict partially initialized session */
+	if (list_empty(&session->list))
+		return -EBUSY;
+
 	snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
 	file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
 	if (IS_ERR(file))
 		return PTR_ERR(file);
 
+	session->file = file;
 	*filep = file;
 
 	return 0;
 }
 
+/**
+ * luo_session_get_all_outgoing - Pins all active outgoing sessions.
+ *
+ * This function iterates through all sessions in the outgoing list and
+ * attempts to pin their associated file descriptors. If a session's file
+ * is no longer active (refcount 0), the session is cleaned up and removed.
+ *
+ * Note: Successfully pinned files have their reference count increased.
+ * These references are intentionally leaked in the current kernel upon
+ * successful live update, as the system will transition to a new kernel
+ * image which will reclaim all memory. In case of failure, they are
+ * released in luo_session_put_all_outgoing().
+ */
+static void luo_session_get_all_outgoing(void)
+{
+	struct luo_session_header *sh = &luo_session_global.outgoing;
+	struct luo_session *session, *tmp;
+
+	lockdep_assert_held_write(&sh->rwsem);
+	list_for_each_entry_safe(session, tmp, &sh->list, list) {
+		guard(mutex)(&session->mutex);
+		if (!session->file) {
+			__luo_session_remove(sh, session);
+		} else if (!get_file_active(&session->file)) {
+			luo_session_unpreserve_files(session);
+			__luo_session_remove(sh, session);
+		}
+	}
+}
+
+static void luo_session_put_all_outgoing(void)
+{
+	struct luo_session_header *sh = &luo_session_global.outgoing;
+	struct luo_session *session;
+
+	lockdep_assert_held_write(&sh->rwsem);
+	list_for_each_entry(session, &sh->list, list) {
+		guard(mutex)(&session->mutex);
+		if (session->file)
+			fput(session->file);
+	}
+}
+
 int luo_session_create(const char *name, struct file **filep)
 {
 	struct luo_session *session;
@@ -596,6 +661,7 @@ int luo_session_serialize(void)
 	guard(rwsem_write)(&sh->rwsem);
 
 	sh->rebooting = true;
+	luo_session_get_all_outgoing();
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -616,6 +682,7 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	luo_session_put_all_outgoing();
 
 	return err;
 }
-- 
2.54.0.545.g6539524ca2-goog



From pratyush at kernel.org  Wed May  6 01:31:44 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 10:31:44 +0200
Subject: [PATCH v1 1/3] liveupdate: skip serialization for
 context-preserving kexec
In-Reply-To: <20260506043200.2025677-6-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 6 May 2026 00:32:02 -0400")
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
	<20260506043200.2025677-6-pasha.tatashin@soleen.com>
Message-ID: <2vxz34050wi7.fsf@kernel.org>

Hi Pasha,

On Wed, May 06 2026, Pasha Tatashin wrote:

> A preserve_context kexec returns to the current kernel, which
> is unrelated to live update where the state is passed to the next
> kernel. Skip liveupdate_reboot() in this case to avoid serialization
> and prevent sessions from being left in a frozen state upon return.

Should we also stop KHO? Now it is stateless and always finalized. I am
not sure how these kexec jump images will interact with KHO data, but
seems like we shouldn't be handing over memory if the image will jump
back.

Anyway, for this patch:

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

>
> Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Wed May  6 01:47:45 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 10:47:45 +0200
Subject: [PATCH v1 2/3] liveupdate: block outgoing session mutations
 during serialization
In-Reply-To: <20260506043200.2025677-7-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 6 May 2026 00:32:03 -0400")
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
	<20260506043200.2025677-7-pasha.tatashin@soleen.com>
Message-ID: <2vxzy0hwzzym.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> Introduce a 'rebooting' flag in the session header to ensure that once
> serialization has started, no new outgoing session mutations (creations
> or file preservations) can occur.

Would it be a better idea to hold the session header lock and locks of
each session? This would prevent anyone else from getting access to any
of the sessions, and we don't have to worry about all the weird cases
when one might add a file to a serialized session or something similar.

Once liveupdate_reboot() returns success, there is no going back anyway
so I don't think it matters much that some tasks will be left waiting.

>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Wed May  6 01:53:34 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 10:53:34 +0200
Subject: [PATCH v1 3/3] liveupdate: pin sessions and handle inactive
 ones during serialization
In-Reply-To: <20260506043200.2025677-8-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 6 May 2026 00:32:04 -0400")
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
	<20260506043200.2025677-8-pasha.tatashin@soleen.com>
Message-ID: <2vxztsskzzox.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> During the reboot() syscall, user processes are still running
> concurrently. Even though the system is actively serializing LUO
> sessions and will not return to userspace unless liveupdate_reboot()
> fails, it is still possible for a user process to close a preserved LUO
> session. This creates a race condition where a session could be
> destroyed while it is being serialized.
>
> To fix this, we must ensure that we only preserve sessions that are not
> closed at the time of serialization. Take a reference to the session's
> struct file for all outgoing sessions to pin them during this process.
>
> Handle inactive sessions (where get_file_active() fails) by cleaning
> them up and removing them from the outgoing list during the pinning
> phase. This ensures serialization can safely proceed with the remaining
> active sessions.
>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Same here. If we hold the session header and session locks, we don't
have to worry about all these corner cases. It is too late to touch the
session once liveupdate_reboot() starts, and if you try you just get put
to sleep.

[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Wed May  6 02:02:15 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 11:02:15 +0200
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <afT_xHDuFwAUSR0X@plex> (Pasha Tatashin's message of "Fri, 1 May
	2026 19:34:25 +0000")
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
	<20260501173053.73116-1-crisjacobmaamor@gmail.com> <afT_xHDuFwAUSR0X@plex>
Message-ID: <2vxzse84zzag.fsf@kernel.org>

Hi Pasha,

On Fri, May 01 2026, Pasha Tatashin wrote:

> On 05-02 01:30, Cris Jacob Maamor wrote:
>> LUO restores metadata from KHO/FDT during liveupdate. The restored
>> metadata contains physical addresses and count fields used to access and
>> walk preserved session, file set, and FLB arrays.
>> 
>> This series adds a non-consuming KHO preserved-range check and uses it
>> before phys_to_virt() on restored metadata addresses. It also rejects
>> restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
>> before traversal.
>> 
>> As far as I can tell, this is root/admin-only; I do not have evidence
>> that a normal unprivileged user can trigger it directly.
>> 
>> Changes since v1:
>> - Dropped RFC marking.
>> - Added changelog text to each patch.
>> - No code changes.
>> 
>> Cris Jacob Maamor (5):
>>   kexec: handover: add helper to check preserved page ranges
>>   liveupdate: validate LUO FDT physical address before mapping
>>   liveupdate: validate restored LUO session metadata
>>   liveupdate: validate restored LUO file set metadata
>>   liveupdate: validate restored LUO FLB metadata
>
> I have replied separately in the security report to clarify that this is 
> not a bug. The behavior follows the ABI specification exactly: we use 
> the PA addresses and ranges provided by the KHO FDT tree.
>
> NAK

I really do think we should do a restore-only variant for the
kho_alloc_preserve() family of allocators and use it everywhere. It
would prevent problems in the future. Not because the previous kernel is
malicious, but because we might have bugs and the KHO page magic sanity
check acts as a defense in depth.

For example, I am currently looking at a LUO bug where LUO does not
track if a session is outgoing or incoming. So you can do a retrieve()
or finish() on an outgoing session. A lot of nastiness is saved because
of the page magic check. Things like kho_restore_vmalloc() or
kho_restore_folio() fail early and loudly.

If we want to squeeze out more performance later down the line we can
move it behind a debug config, but having this usage pattern of always
restoring before using is going to be a lot more sane than just using
physical addresses willy nilly.

The approach this series takes with kho_is_preserved() is the wrong
design. But a kho_restore() or something similar (maybe we can find a
better name?) is really where we should be going.

-- 
Regards,
Pratyush Yadav


From jloeser at linux.microsoft.com  Wed May  6 03:51:07 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 6 May 2026 03:51:07 -0700 (PDT)
Subject: [PATCH 08/12] kho: add kho_radix_init_tree()
In-Reply-To: <20260429133928.850721-9-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org> <20260429133928.850721-9-pratyush@kernel.org>
Message-ID: <e5e9c15f-a030-423e-de79-757c20d084e1@linux.microsoft.com>

On Wed, 29 Apr 2026, Pratyush Yadav wrote:

> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
> Move the initialization logic of the radix tree into
> kho_radix_init_tree() instead of having users open-code it. Makes the
> boundaries cleaner and reduces code duplication when a new user of the
> radix tree will be added in a future commit.

Consider using kho_radix_init_tree() in kho_init() as well. This will also 
remove the kzalloc() one-off use. Similar for kho_radix_destroy_tree().

Best,
Jork


From qirui.001 at bytedance.com  Wed May  6 04:57:56 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Wed,  6 May 2026 19:57:56 +0800
Subject: [PATCH][makedumpfile] Fix riscv64 virtual-to-physical address translation
Message-ID: <20260506115758.2497610-1-qirui.001@bytedance.com>

This patchset fixes two bugs in the riscv64 architecture support of
makedumpfile related to virtual-to-physical address translation.

Patch 1 fixes vaddr_to_paddr_riscv64() to exclude vmalloc and vmemmap
regions from the VTOP() fast path. These regions are not linearly mapped
and must go through page table walk.

Patch 2 fixes vtop_riscv64() to correctly handle huge pages when
_PAGE_LEAF is set at PGD/P4D/PUD/PMD level. The original code incorrectly
fell through to the next level, causing wrong physical address computation.

Shuan He (2):
  riscv64: Fix vaddr_to_paddr for vmalloc and vmemmap regions
  riscv64: Fix huge page translation in vtop_riscv64()

 arch/riscv64.c | 28 ++++++++++++++++++++--------
 1 file changed, 20 insertions(+), 8 deletions(-)

-- 
2.20.1


From qirui.001 at bytedance.com  Wed May  6 04:57:57 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Wed,  6 May 2026 19:57:57 +0800
Subject: [PATCH][makedumpfile] 1/2 riscv64: Fix vaddr_to_paddr for vmalloc and vmemmap regions
In-Reply-To: <20260506115758.2497610-1-qirui.001@bytedance.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com>
Message-ID: <20260506115758.2497610-2-qirui.001@bytedance.com>

The current vaddr_to_paddr_riscv64() incorrectly uses VTOP() linear
mapping for vmalloc and vmemmap addresses, which are not linearly
mapped. Add checks to exclude these regions so they fall through to
page table walk via vtop_riscv64().

Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
Signed-off-by: Shuan He <heshuan at bytedance.com>
---
 arch/riscv64.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/riscv64.c b/arch/riscv64.c
index 1b43ec4f62a1..28365fa0cc5b 100644
--- a/arch/riscv64.c
+++ b/arch/riscv64.c
@@ -202,6 +202,8 @@ vaddr_to_paddr_riscv64(unsigned long vaddr)
 	unsigned long long swapper_phys;
 
 	if (vaddr >= PAGE_OFFSET &&
+	    !(vaddr >= NUMBER(vmalloc_start) && vaddr <= NUMBER(vmalloc_end)) &&
+	    !(vaddr >= NUMBER(vmemmap_start) && vaddr <= NUMBER(vmemmap_end)) &&
 	    !(vaddr >= NUMBER(modules_vaddr) && vaddr <= NUMBER(modules_end))){
 		return VTOP(vaddr);
 	}
-- 
2.20.1


From qirui.001 at bytedance.com  Wed May  6 04:57:58 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Wed,  6 May 2026 19:57:58 +0800
Subject: [PATCH][makedumpfile] 2/2 riscv64: Fix huge page translation in vtop_riscv64()
In-Reply-To: <20260506115758.2497610-1-qirui.001@bytedance.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com>
Message-ID: <20260506115758.2497610-3-qirui.001@bytedance.com>

When a page table entry has _PAGE_LEAF set at PGD/P4D/PUD/PMD level,
the current code incorrectly falls through to the next level instead
of computing the correct physical address. Fix this by calculating
the paddr with the proper level shift and returning directly.

Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
Signed-off-by: Shuan He <heshuan at bytedance.com>
---
 arch/riscv64.c | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/arch/riscv64.c b/arch/riscv64.c
index 28365fa0cc5b..67755331de24 100644
--- a/arch/riscv64.c
+++ b/arch/riscv64.c
@@ -107,8 +107,12 @@ vtop_riscv64(pgd_t * pgd, unsigned long vaddr, long va_bits)
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		unsigned long shift = (va_bits == VA_BITS_SV57) ? PGD_SHIFT_L5 :
+				      (va_bits == VA_BITS_SV48) ? PGD_SHIFT_L4 : PGD_SHIFT_L3;
+		paddr = (pt_phys & ~((1ULL << shift) - 1)) + (vaddr & ((1ULL << shift) - 1));
+		goto invalid;
+	}
 
 	if (va_bits == VA_BITS_SV57)
 		goto p4d;
@@ -133,8 +137,10 @@ p4d:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << P4D_SHIFT) - 1)) + (vaddr & ((1ULL << P4D_SHIFT) - 1));
+		goto invalid;
+	}
 pud:
 	/* PUD */
 	puda = (pud_t *)(pt_phys) + pud_index(vaddr);
@@ -152,8 +158,10 @@ pud:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if(pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << PUD_SHIFT) - 1)) + (vaddr & ((1ULL << PUD_SHIFT) - 1));
+		goto invalid;
+	}
 pmd:
 	/* PMD */
 	pmda = (pmd_t *)(pt_phys) + pmd_index(vaddr);
@@ -171,8 +179,10 @@ pmd:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << PMD_SHIFT) - 1)) + (vaddr & ((1ULL << PMD_SHIFT) - 1));
+		goto invalid;
+	}
 
 	/* PTE */
 	ptea = (pte_t *)(pt_phys) + pte_index(vaddr);
-- 
2.20.1


From sourabhjain at linux.ibm.com  Wed May  6 05:28:31 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Wed, 6 May 2026 17:58:31 +0530
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <20260501234342.2518281-3-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-3-coiby.xu@gmail.com>
Message-ID: <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com>

Hello Coiby,

On 02/05/26 05:13, Coiby Xu wrote:
> If kexec_add_buffer somehow fails, keys_header will be freed. Depending
> on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
> following two problems if the kexec_file_load syscall is called again,
>    1. Double free of keys_header if reuse=false
>    2. UAF of keys_header if reuse=true
>
> To address these problems and also make it easier to reason about the
> code, keep two invariants,
>    1. keys_header will always be freed at the end of kexec_file_load
>       syscall except during kdump image unloading for CPU/memory
>       hot-plugging support
>    2. There will always be valid keys_header if reuse=true
>
> Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
> Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
> Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
> ---
>   include/linux/kexec.h        |  6 ++++
>   kernel/crash_dump_dm_crypt.c | 65 ++++++++++++++++++++++++++----------
>   kernel/kexec_file.c          |  2 ++
>   3 files changed, 56 insertions(+), 17 deletions(-)
>
> diff --git a/include/linux/kexec.h b/include/linux/kexec.h
> index 8a22bc9b8c6c..91256d7ff434 100644
> --- a/include/linux/kexec.h
> +++ b/include/linux/kexec.h
> @@ -552,6 +552,12 @@ void set_kexec_sig_enforced(void);
>   static inline void set_kexec_sig_enforced(void) {}
>   #endif
>   
> +#ifdef CONFIG_CRASH_DM_CRYPT
> +void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image);
> +#else
> +static inline void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image) {}
> +#endif
> +
>   #endif /* !defined(__ASSEBMLY__) */
>   
>   #endif /* LINUX_KEXEC_H */
> diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
> index eac4f436a8d4..4d8a3331bbe7 100644
> --- a/kernel/crash_dump_dm_crypt.c
> +++ b/kernel/crash_dump_dm_crypt.c
> @@ -84,18 +84,25 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
>   	return r;
>   }
>   
> -static void get_keys_from_kdump_reserved_memory(void)
> +static int get_keys_from_kdump_reserved_memory(void)
>   {
>   	struct keys_header *keys_header_loaded;
> +	size_t keys_header_size;
>   
> -	arch_kexec_unprotect_crashkres();
> +	keys_header_size = get_keys_header_size(key_count);
> +	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
> +	if (!keys_header)
> +		return -ENOMEM;
>   
> +	arch_kexec_unprotect_crashkres();
>   	keys_header_loaded = kmap_local_page(pfn_to_page(
>   		kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));

Accessing kexec_crash_image without holding the kexec lock could lead to
undefined behavior.

I think this section of code should be called by holding kexec lock.


>   
> -	memcpy(keys_header, keys_header_loaded, get_keys_header_size(key_count));
> +	memcpy(keys_header, keys_header_loaded, keys_header_size);
>   	kunmap_local(keys_header_loaded);
>   	arch_kexec_protect_crashkres();
> +
> +	return 0;
>   }
>   
>   static int restore_dm_crypt_keys_to_thread_keyring(void)
> @@ -283,17 +290,28 @@ static ssize_t config_keys_reuse_show(struct config_item *item, char *page)
>   static ssize_t config_keys_reuse_store(struct config_item *item,
>   					   const char *page, size_t count)
>   {
> +	bool val;
> +	int r;
> +
>   	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {

The above check should be performed after acquiring the kexec lock.



>   		kexec_dprintk(
>   			"dm-crypt keys haven't be saved to crash-reserved memory\n");
>   		return -EINVAL;
>   	}
>   
> -	if (kstrtobool(page, &is_dm_key_reused))
> +	if (kstrtobool(page, &val) || !val)
>   		return -EINVAL;

Why can?t we allow the user to set is_dm_key_reused = false and free the
key_header? That way, the next kdump kernel load will recreate the 
key_header.

For example, a user may add more keys and want the new keys to be included
in the kdump image from next kdump kernel load.


>   
> -	if (is_dm_key_reused)
> -		get_keys_from_kdump_reserved_memory();
> +	if (is_dm_key_reused) {
> +		pr_info("Already got dm-crypt keys, please continue with kexec_file_load syscall\n");
> +	} else {
> +		r = get_keys_from_kdump_reserved_memory();
> +		if (r) {
> +			pr_warn("Failed to get dm-crypt keys from reserved memory\n");
> +			return r;
> +		}
> +		is_dm_key_reused = true;
> +	}
>   
>   	return count;
>   }
> @@ -366,9 +384,6 @@ static int build_keys_header(void)
>   	struct config_key *key;
>   	int i, r;
>   
> -	if (keys_header != NULL)
> -		kvfree(keys_header);
> -
>   	keys_header = kzalloc(get_keys_header_size(key_count), GFP_KERNEL);
>   	if (!keys_header)
>   		return -ENOMEM;
> @@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   		.top_down = false,
>   		.random = true,
>   	};
> -	int r;
> +	int r = 0;
>   
>   
>   	if (key_count <= 0) {
> @@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   	}
>   
>   	if (!is_dm_key_reused) {
> -		image->dm_crypt_keys_addr = 0;
>   		r = build_keys_header();
> -		if (r) {
> -			pr_err("Failed to build dm-crypt keys header, ret=%d\n", r);
> -			return r;
> -		}
> +		if (r)
> +			goto out;
>   	}
>   
> +	/*
> +	 * keys_header will be copied to reserver memory later and then be
> +	 * cleaned up at the end of kexec_file_load syscall
> +	 */
>   	kbuf.buffer = keys_header;
>   	kbuf.bufsz = get_keys_header_size(key_count);
>   
> @@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   	r = kexec_add_buffer(&kbuf);
>   	if (r) {
>   		pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
> -		kvfree((void *)kbuf.buffer);
> -		return r;
> +		goto out;
>   	}
> +
>   	image->dm_crypt_keys_addr = kbuf.mem;
>   	image->dm_crypt_keys_sz = kbuf.bufsz;
>   	kexec_dprintk(
>   		"Loaded dm crypt keys to kexec_buffer bufsz=0x%lx memsz=0x%lx\n",
>   		kbuf.bufsz, kbuf.memsz);
>   
> +out:
> +	is_dm_key_reused = false;
>   	return r;
>   }
>   
> +void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
> +{
> +	/*
> +	 * For CPU/memory hot-plugging, the kdump image will be reloaded. Prevent
> +	 * keys_header from being cleaned up during unloading when
> +	 * is_dm_key_reused=true
> +	 */
> +	if (!is_dm_key_reused) {
> +		kfree_sensitive(keys_header);
> +		keys_header = NULL;

Since crash_load_dm_crypt_keys() sets is_dm_key_reused = false, 
keys_header will
always be released here, right? Then why is the above free under an if 
condition?

IIUC, for the case where CONFIG_CRASH_HOTPLUG is not enabled, this is 
how key
restore works:

After loading the kdump kernel for the first time, the state of the 
variables is:

is_dm_key_reused = false
keys_header = NULL

For example, if 2 CPUs are hot-removed and kdump is reloaded twice:

Then the sequence of operations needed to ensure the loaded keys can be 
reused is:

Udev rule triggered on the 1st CPU hotplug:
echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
Restore the key header from the reserved area
Reload the kdump service/kernel

Udev rule triggered on the 2nd CPU hotplug:
echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
Restore the key header from the reserved area
Reload the kdump service/kernel

What I don?t understand is the need to restore the key header from 
crashkernel
memory for every hotplug operation.


- Sourabh Jain

> +	}
> +}
> +
>   static int __init configfs_dmcrypt_keys_init(void)
>   {
>   	int ret;
> diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
> index 2bfbb2d144e6..0421f1e89791 100644
> --- a/kernel/kexec_file.c
> +++ b/kernel/kexec_file.c
> @@ -139,6 +139,8 @@ void kimage_file_post_load_cleanup(struct kimage *image)
>   	kfree(image->image_loader_data);
>   	image->image_loader_data = NULL;
>   
> +	kexec_file_post_load_cleanup_dm_crypt(image);
> +
>   	kexec_file_dbg_print = false;
>   }
>   



From sourabhjain at linux.ibm.com  Wed May  6 06:56:16 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Wed, 6 May 2026 19:26:16 +0530
Subject: [PATCH v2 3/9] crash_dump: Disallow writing to dm-crypt configfs
 during kexec_file_load syscall
In-Reply-To: <20260501234342.2518281-4-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-4-coiby.xu@gmail.com>
Message-ID: <b6a29516-ff17-4a36-b90c-812160fa8d19@linux.ibm.com>



On 02/05/26 05:13, Coiby Xu wrote:
> If writing to the configfs group happens concurrently during
> kexec_file_load syscall, it may lead to the following issues,
>    - buffer overflow if dm-crypt keys are added after allocation
>    - stale total_keys if dm-crypt keys are removed during iteration
>    - keys_header will not be freed if config/crash_dm_crypt_key/reuse is
>      set true
>
> So hold config_keys_subsys.su_mutex for the entire sequence during the
> kexec_file_load syscall to ensure a consistent snapshot.


Yes, this will solve many synchronization problems when a? user tries to
perform any operation under our configfs_subsystem while the kernel is
executing the kexec_file_load system call.

Now, regarding the third point about freeing key_header: this will work
only if configfs takes the su_mutex lock before invoking the store callback.
I am not sure whether it actually does.

However, based on my previous comment on (2/9), if we keep 
config_keys_reuse_store()
under the kexec lock, this will be taken care of. This is because the entire
kexec_file_load system call already runs under the kexec lock.


>
> Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
> ---
>   kernel/crash_dump_dm_crypt.c | 4 ++++
>   1 file changed, 4 insertions(+)
>
> diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
> index 4d8a3331bbe7..6377ee86ec50 100644
> --- a/kernel/crash_dump_dm_crypt.c
> +++ b/kernel/crash_dump_dm_crypt.c
> @@ -429,6 +429,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   	};
>   	int r = 0;
>   
> +	mutex_lock(&config_keys_subsys.su_mutex);
>   
>   	if (key_count <= 0) {
>   		kexec_dprintk("No dm-crypt keys\n");
> @@ -479,6 +480,9 @@ void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>   		kfree_sensitive(keys_header);
>   		keys_header = NULL;
>   	}
> +
> +	if (mutex_is_locked(&config_keys_subsys.su_mutex))
> +		mutex_unlock(&config_keys_subsys.su_mutex);
How about release the lock in crash_load_dm_crypt_keys() itself? Given 
that config_keys_reuse_store() is placed under kexec lock?


- Sourabh Jain

>   }
>   
>   static int __init configfs_dmcrypt_keys_init(void)



From sourabhjain at linux.ibm.com  Wed May  6 07:18:00 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Wed, 6 May 2026 19:48:00 +0530
Subject: [PATCH v2 4/9] crash_dump: Read the number of dm-crypt keys from
 reserved memory
In-Reply-To: <20260501234342.2518281-5-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-5-coiby.xu@gmail.com>
Message-ID: <0e2328f1-a057-45d5-8889-c5c333826b45@linux.ibm.com>



On 02/05/26 05:13, Coiby Xu wrote:
> In case user adds/deletes the keys by mistake, it's safer to read the
> number of keys from reserved memory.
>
> Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
> Reported-and-Suggested-by: Sourabh Jain <sourabhjain at linux.ibm.com>
> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
> ---
>   kernel/crash_dump_dm_crypt.c | 36 +++++++++++++++++++++++-------------
>   1 file changed, 23 insertions(+), 13 deletions(-)
>
> diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
> index 6377ee86ec50..a3e460714d23 100644
> --- a/kernel/crash_dump_dm_crypt.c
> +++ b/kernel/crash_dump_dm_crypt.c
> @@ -88,21 +88,31 @@ static int get_keys_from_kdump_reserved_memory(void)
>   {
>   	struct keys_header *keys_header_loaded;
>   	size_t keys_header_size;
> -
> -	keys_header_size = get_keys_header_size(key_count);
> -	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
> -	if (!keys_header)
> -		return -ENOMEM;
> +	int r = 0;
>   
>   	arch_kexec_unprotect_crashkres();
>   	keys_header_loaded = kmap_local_page(pfn_to_page(
>   		kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));
>   
> +	if (keys_header_loaded->total_keys <= 0 ||
> +	    keys_header_loaded->total_keys > KEY_NUM_MAX) {
> +		pr_warn("keys_header saved to reserved memory may be corrupt\n");
> +		r = -EINVAL;
> +		goto kunmap;
> +	}

Yes it is good to do a sanity check before using it.

- Sourabh Jain

> +
> +	keys_header_size = get_keys_header_size(keys_header_loaded->total_keys);
> +	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
> +	if (!keys_header) {
> +		r = -ENOMEM;
> +		goto kunmap;
> +	}
> +
>   	memcpy(keys_header, keys_header_loaded, keys_header_size);
> +kunmap:
>   	kunmap_local(keys_header_loaded);
>   	arch_kexec_protect_crashkres();
> -
> -	return 0;
> +	return r;
>   }
>   
>   static int restore_dm_crypt_keys_to_thread_keyring(void)
> @@ -431,12 +441,12 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   
>   	mutex_lock(&config_keys_subsys.su_mutex);
>   
> -	if (key_count <= 0) {
> -		kexec_dprintk("No dm-crypt keys\n");
> -		return 0;
> -	}
> -
>   	if (!is_dm_key_reused) {
> +		if (key_count <= 0) {
> +			kexec_dprintk("No dm-crypt keys\n");
> +			return 0;
> +		}
> +
>   		r = build_keys_header();
>   		if (r)
>   			goto out;
> @@ -447,7 +457,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   	 * cleaned up at the end of kexec_file_load syscall
>   	 */
>   	kbuf.buffer = keys_header;
> -	kbuf.bufsz = get_keys_header_size(key_count);
> +	kbuf.bufsz = get_keys_header_size(keys_header->total_keys);
>   
>   	kbuf.memsz = kbuf.bufsz;
>   	kbuf.buf_align = ELF_CORE_HEADER_ALIGN;



From sourabhjain at linux.ibm.com  Wed May  6 07:33:44 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Wed, 6 May 2026 20:03:44 +0530
Subject: [PATCH v2 7/9] crash_dump: Improve readability of
 config_keys_restore_store
In-Reply-To: <20260501234342.2518281-8-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-8-coiby.xu@gmail.com>
Message-ID: <23784559-1e11-4e14-bf23-6ffebd7b2d30@linux.ibm.com>



On 02/05/26 05:13, Coiby Xu wrote:
> config_keys_restore_store currently doesn't validate the user input
> before restoring dm-crypt keys. Although it's not necessary for the case
> of vmcore dumping, it's better to do it for the sake of consistency and
> code readability. Also check the return code of
> restore_dm_crypt_keys_to_thread_keyring.
>
> Fixes: 62f17d9df692 ("crash_dump: retrieve dm crypt keys in kdump kernel")
> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
> ---
>   kernel/crash_dump_dm_crypt.c | 10 +++++++---
>   1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
> index 545d712428d3..36e51807d94f 100644
> --- a/kernel/crash_dump_dm_crypt.c
> +++ b/kernel/crash_dump_dm_crypt.c
> @@ -358,12 +358,16 @@ static ssize_t config_keys_restore_show(struct config_item *item, char *page)
>   static ssize_t config_keys_restore_store(struct config_item *item,
>   					  const char *page, size_t count)
>   {
> -	if (!restore)
> -		restore_dm_crypt_keys_to_thread_keyring();
> +	bool val;
>   
> -	if (kstrtobool(page, &restore))
> +	if (kstrtobool(page, &val))
>   		return -EINVAL;
>   
> +	if (val && !restore) {
> +		if (!restore_dm_crypt_keys_to_thread_keyring())
> +			restore = true;
> +	}

Nit:
I think it would be good to warn the user if they try to
restore it when it has already been restored.


- Sourabh Jain
> +
>   	return count;
>   }
>   



From robh at kernel.org  Wed May  6 07:45:42 2026
From: robh at kernel.org (Rob Herring)
Date: Wed, 6 May 2026 09:45:42 -0500
Subject: [PATCH 05/11] of: reserved_mem: add linux,no-dump property
 support for reserved memory regions
In-Reply-To: <20260429065831.1510858-6-chenwandun@lixiang.com>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-6-chenwandun@lixiang.com>
Message-ID: <20260506144542.GA2072596-robh@kernel.org>

On Wed, Apr 29, 2026 at 02:58:25PM +0800, Chen Wandun wrote:
> Add a 'no_dump' field to struct reserved_mem and parse the
> 'linux,no-dump' device tree property during reserved memory node
> initialization. This property allows device tree authors to mark
> specific reserved memory regions that should be excluded from kdump
> vmcore dumps.
> 
> Reserved memory regions used by device firmware (e.g., GPU, DSP, modem)
> typically contain data that is not useful for kernel crash analysis and
> can significantly increase vmcore size. The 'linux,no-dump' property
> provides a declarative way to indicate these regions should be filtered
> out when constructing the elfcorehdr for kdump.
> 
> The property is named with a 'linux,' prefix because kdump/vmcore is
> Linux-specific and the property is an OS hint rather than a hardware
> description, matching existing properties such as 'linux,cma-default'
> and 'linux,usable-memory-range'.
> 
> The 'linux,no-dump' property is only effective when the region:
>  - Does not have 'no-map': these regions are already excluded from
>    vmcore since they are removed from the linear mapping (MEMBLOCK_NOMAP).
>  - Does not have 'reusable': CMA reusable regions are actively used by
>    the kernel for movable page allocations, and their contents are
>    valuable for crash analysis.
> 
> The no-dump status is also printed in the boot log alongside the
> existing nomap and reusable flags for diagnostic purposes.

I think this property is the wrong way around and probably not needed. 
The default should be exclude the regions, but if Linux is using the 
regions (like CMA) then it can decide on its own to include them.

With the restructuring that went into 7.1, it should be possible for the 
CMA code (and code for any other regions) to set some flag for the 
region.

Rob


From robh at kernel.org  Wed May  6 07:50:22 2026
From: robh at kernel.org (Rob Herring)
Date: Wed, 6 May 2026 09:50:22 -0500
Subject: [PATCH 07/11] of: reserved_mem: add no-dump crash_mem exclusion
 helpers
In-Reply-To: <20260429065831.1510858-8-chenwandun@lixiang.com>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-8-chenwandun@lixiang.com>
Message-ID: <20260506145022.GA2084721-robh@kernel.org>

On Wed, Apr 29, 2026 at 02:58:27PM +0800, Chen Wandun wrote:
> Provide two kdump-oriented helpers so that arch kexec_file code does
> not have to open-code the no-dump filtering loop:
> 
>  - of_reserved_mem_no_dump_nr_ranges() returns the number of reserved
>    regions flagged with linux,no-dump. Each exclusion may split one
>    existing crash_mem range into two, so callers use this count to
>    pre-size their crash_mem allocation.
> 
>  - of_reserved_mem_exclude_no_dump() walks the reserved_mem[] array
>    and calls crash_exclude_mem_range() for each no-dump region.
> 
> Both helpers are guarded by CONFIG_KEXEC_FILE; empty inline stubs are
> provided for the !KEXEC_FILE case so architecture code can call them
> unconditionally.
> 
> The consumers are added in the following arm64, riscv and loongarch
> patches in this series.
> 
> Signed-off-by: Chen Wandun <chenwandun at lixiang.com>
> Tested-by: Zhao Meijing <zhaomeijing at lixiang.com>
> ---
>  drivers/of/of_reserved_mem.c    | 54 +++++++++++++++++++++++++++++++++
>  include/linux/of_reserved_mem.h | 15 +++++++++
>  2 files changed, 69 insertions(+)
> 
> diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
> index 4b80420da2d2..038056a6408a 100644
> --- a/drivers/of/of_reserved_mem.c
> +++ b/drivers/of/of_reserved_mem.c
> @@ -27,6 +27,10 @@
>  
>  #include "of_private.h"
>  
> +#ifdef CONFIG_KEXEC_FILE
> +#include <linux/crash_core.h>
> +#endif

You shouldn't need ifdef around includes.

> +
>  static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
>  static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
>  static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
> @@ -916,6 +920,56 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
>  }
>  EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
>  
> +#ifdef CONFIG_KEXEC_FILE

Use 'if (IS_ENABLED())' within the function.

> +/**
> + * of_reserved_mem_no_dump_nr_ranges() - count reserved regions flagged
> + * with the linux,no-dump property.
> + *
> + * Each such region may split an existing crash_mem range into two when
> + * it is excluded, so callers can use this count to pre-size their
> + * crash_mem allocation.
> + */
> +unsigned int of_reserved_mem_no_dump_nr_ranges(void)
> +{
> +	unsigned int i, n = 0;
> +
> +	for (i = 0; i < reserved_mem_count; i++)
> +		if (reserved_mem[i].no_dump)
> +			n++;
> +	return n;
> +}
> +
> +/**
> + * of_reserved_mem_exclude_no_dump() - exclude no-dump reserved regions
> + * from a crash_mem list.
> + * @cmem: crash memory list to modify
> + *
> + * Walks the reserved_mem[] array and calls crash_exclude_mem_range() for
> + * every region with no_dump set. Intended to be called from arch kdump
> + * code when constructing the elfcorehdr.
> + *
> + * Returns 0 on success, or a negative error returned by
> + * crash_exclude_mem_range() on the first failure.
> + */
> +int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < reserved_mem_count; i++) {
> +		struct reserved_mem *r = &reserved_mem[i];
> +
> +		if (!r->no_dump || !r->size)
> +			continue;
> +		ret = crash_exclude_mem_range(cmem, r->base,
> +					      r->base + r->size - 1);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +#endif /* CONFIG_KEXEC_FILE */


From pasha.tatashin at soleen.com  Wed May  6 08:05:32 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 6 May 2026 11:05:32 -0400
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <2vxzse84zzag.fsf@kernel.org>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
 <afT_xHDuFwAUSR0X@plex>
 <2vxzse84zzag.fsf@kernel.org>
Message-ID: <aftVd5RnpnAi2jdc@google.com>

On 05-06 11:02, Pratyush Yadav wrote:
> Hi Pasha,
> 
> On Fri, May 01 2026, Pasha Tatashin wrote:
> 
> > On 05-02 01:30, Cris Jacob Maamor wrote:
> >> LUO restores metadata from KHO/FDT during liveupdate. The restored
> >> metadata contains physical addresses and count fields used to access and
> >> walk preserved session, file set, and FLB arrays.
> >> 
> >> This series adds a non-consuming KHO preserved-range check and uses it
> >> before phys_to_virt() on restored metadata addresses. It also rejects
> >> restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
> >> before traversal.
> >> 
> >> As far as I can tell, this is root/admin-only; I do not have evidence
> >> that a normal unprivileged user can trigger it directly.
> >> 
> >> Changes since v1:
> >> - Dropped RFC marking.
> >> - Added changelog text to each patch.
> >> - No code changes.
> >> 
> >> Cris Jacob Maamor (5):
> >>   kexec: handover: add helper to check preserved page ranges
> >>   liveupdate: validate LUO FDT physical address before mapping
> >>   liveupdate: validate restored LUO session metadata
> >>   liveupdate: validate restored LUO file set metadata
> >>   liveupdate: validate restored LUO FLB metadata
> >
> > I have replied separately in the security report to clarify that this is 
> > not a bug. The behavior follows the ABI specification exactly: we use 
> > the PA addresses and ranges provided by the KHO FDT tree.
> >
> > NAK
> 
> I really do think we should do a restore-only variant for the
> kho_alloc_preserve() family of allocators and use it everywhere. It

That is unrelated to the provided patch series. The author of this 
series reported this as a security issue to the Linux security ML, and 
submitted this series at their request.

This is not a security issue, and in fact, it is not an issue at all. A 
restore-only variant can be added, but I do not see a reason for LUO to 
use it.

> would prevent problems in the future. Not because the previous kernel is
> malicious, but because we might have bugs and the KHO page magic sanity
> check acts as a defense in depth.
> 
> For example, I am currently looking at a LUO bug where LUO does not
> track if a session is outgoing or incoming. So you can do a retrieve()
> or finish() on an outgoing session. A lot of nastiness is saved because
> of the page magic check. Things like kho_restore_vmalloc() or
> kho_restore_folio() fail early and loudly.

I am not sure what bug you are looking at (please share the details!), 
but the fix absolutely should be to use outgoing/incoming sessions 
properly, and if we mixed them up somewhere, THAT should be fixed. Using 
KHO restore is not going to help much; however, I agree it can add 
some extra scrutiny (i.e., similar to an ASSERT), but it is not really 
something that would help improve correctness in any meaningful way. The 
correctness should lie in the LUO logic using incoming as incoming, and 
outgoing as outgoing.

> 
> If we want to squeeze out more performance later down the line we can
> move it behind a debug config, but having this usage pattern of always
> restoring before using is going to be a lot more sane than just using
> physical addresses willy nilly.
> 
> The approach this series takes with kho_is_preserved() is the wrong
> design. But a kho_restore() or something similar (maybe we can find a
> better name?) is really where we should be going.
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Wed May  6 08:12:26 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 6 May 2026 11:12:26 -0400
Subject: [PATCH v1 1/3] liveupdate: skip serialization for
 context-preserving kexec
In-Reply-To: <2vxz34050wi7.fsf@kernel.org>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
 <20260506043200.2025677-6-pasha.tatashin@soleen.com>
 <2vxz34050wi7.fsf@kernel.org>
Message-ID: <aftZZ5AuQcNuqInP@google.com>

On 05-06 10:31, Pratyush Yadav wrote:
> Hi Pasha,
> 
> On Wed, May 06 2026, Pasha Tatashin wrote:
> 
> > A preserve_context kexec returns to the current kernel, which
> > is unrelated to live update where the state is passed to the next
> > kernel. Skip liveupdate_reboot() in this case to avoid serialization
> > and prevent sessions from being left in a frozen state upon return.
> 
> Should we also stop KHO? Now it is stateless and always finalized. I am

No. KHO preserves kernel internal memory; for context-preserved 
kexec, the 2nd kernel and the 1st kernel do not have overlapping 
memory. So, whatever the 1st kernel maintains in KHO is an internal 
detail of the 1st kernel. Once we return from the 2nd kernel to the 1st 
kernel, all KHO memory is going to stay as-is, and the 1st kernel can 
even perform a regular kexec or live update later.

Pasha

> not sure how these kexec jump images will interact with KHO data, but
> seems like we shouldn't be handing over memory if the image will jump
> back.
> 
> Anyway, for this patch:
> 
> Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> 
> >
> > Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
> > Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> [...]
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Wed May  6 08:25:43 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 6 May 2026 11:25:43 -0400
Subject: [PATCH v1 2/3] liveupdate: block outgoing session mutations
 during serialization
In-Reply-To: <2vxzy0hwzzym.fsf@kernel.org>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
 <20260506043200.2025677-7-pasha.tatashin@soleen.com>
 <2vxzy0hwzzym.fsf@kernel.org>
Message-ID: <aftaoEKcC_rpB6jm@google.com>

On 05-06 10:47, Pratyush Yadav wrote:
> On Wed, May 06 2026, Pasha Tatashin wrote:
> 
> > Introduce a 'rebooting' flag in the session header to ensure that once
> > serialization has started, no new outgoing session mutations (creations
> > or file preservations) can occur.
> 
> Would it be a better idea to hold the session header lock and locks of
> each session? This would prevent anyone else from getting access to any
> of the sessions, and we don't have to worry about all the weird cases
> when one might add a file to a serialized session or something similar.
> 
> Once liveupdate_reboot() returns success, there is no going back anyway
> so I don't think it matters much that some tasks will be left waiting.

Overall, we can do that. The only possible issue I can think of is that 
we might get some stupid warnings if the shutdown takes too long:

INFO: task ... blocked for more than ... seconds
followed by a call trace.

But that is unlikely, and it also means that userspace has been trying 
to mutate sessions when it should not have, so I think your approach is 
workable. Let me update the implementation.

Pasha


From pratyush at kernel.org  Wed May  6 08:33:52 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 17:33:52 +0200
Subject: [PATCH v1 1/3] liveupdate: skip serialization for
 context-preserving kexec
In-Reply-To: <aftZZ5AuQcNuqInP@google.com> (Pasha Tatashin's message of "Wed,
	6 May 2026 11:12:26 -0400")
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
	<20260506043200.2025677-6-pasha.tatashin@soleen.com>
	<2vxz34050wi7.fsf@kernel.org> <aftZZ5AuQcNuqInP@google.com>
Message-ID: <2vxzjytgzh5r.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> On 05-06 10:31, Pratyush Yadav wrote:
>> Hi Pasha,
>> 
>> On Wed, May 06 2026, Pasha Tatashin wrote:
>> 
>> > A preserve_context kexec returns to the current kernel, which
>> > is unrelated to live update where the state is passed to the next
>> > kernel. Skip liveupdate_reboot() in this case to avoid serialization
>> > and prevent sessions from being left in a frozen state upon return.
>> 
>> Should we also stop KHO? Now it is stateless and always finalized. I am
>
> No. KHO preserves kernel internal memory; for context-preserved 
> kexec, the 2nd kernel and the 1st kernel do not have overlapping 
> memory. So, whatever the 1st kernel maintains in KHO is an internal 
> detail of the 1st kernel. Once we return from the 2nd kernel to the 1st 
> kernel, all KHO memory is going to stay as-is, and the 1st kernel can 
> even perform a regular kexec or live update later.

My point is that we keep KHO data in the 1st kernel but do not pass it
to the 2nd kernel via setup_data or the devicetree because it should not
use that memory anyway. So essentially we add a check in
kho_fill_kimage() and return early if preserve_context is set.

I don't think it makes much difference in practice so no strong
opinions. Fine either way.

[...]

-- 
Regards,
Pratyush Yadav


From pasha.tatashin at soleen.com  Wed May  6 09:06:33 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 6 May 2026 12:06:33 -0400
Subject: [PATCH v1 1/3] liveupdate: skip serialization for
 context-preserving kexec
In-Reply-To: <2vxzjytgzh5r.fsf@kernel.org>
References: <20260506043200.2025677-5-pasha.tatashin@soleen.com>
 <20260506043200.2025677-6-pasha.tatashin@soleen.com>
 <2vxz34050wi7.fsf@kernel.org>
 <aftZZ5AuQcNuqInP@google.com>
 <2vxzjytgzh5r.fsf@kernel.org>
Message-ID: <aftm3HQ-XrraUBJU@google.com>

On 05-06 17:33, Pratyush Yadav wrote:
> On Wed, May 06 2026, Pasha Tatashin wrote:
> 
> > On 05-06 10:31, Pratyush Yadav wrote:
> >> Hi Pasha,
> >> 
> >> On Wed, May 06 2026, Pasha Tatashin wrote:
> >> 
> >> > A preserve_context kexec returns to the current kernel, which
> >> > is unrelated to live update where the state is passed to the next
> >> > kernel. Skip liveupdate_reboot() in this case to avoid serialization
> >> > and prevent sessions from being left in a frozen state upon return.
> >> 
> >> Should we also stop KHO? Now it is stateless and always finalized. I am
> >
> > No. KHO preserves kernel internal memory; for context-preserved 
> > kexec, the 2nd kernel and the 1st kernel do not have overlapping 
> > memory. So, whatever the 1st kernel maintains in KHO is an internal 
> > detail of the 1st kernel. Once we return from the 2nd kernel to the 1st 
> > kernel, all KHO memory is going to stay as-is, and the 1st kernel can 
> > even perform a regular kexec or live update later.
> 
> My point is that we keep KHO data in the 1st kernel but do not pass it
> to the 2nd kernel via setup_data or the devicetree because it should not
> use that memory anyway. So essentially we add a check in
> kho_fill_kimage() and return early if preserve_context is set.

This is a good point, I think we should do that to prevent passing via 
setup_data on preserve_context kexec.

> 
> I don't think it makes much difference in practice so no strong
> opinions. Fine either way.
> 
> [...]
> 
> -- 
> Regards,
> Pratyush Yadav


From sourabhjain at linux.ibm.com  Wed May  6 09:09:35 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Wed, 6 May 2026 21:39:35 +0530
Subject: [PATCH v2 8/9] crash_dump: Disallow
 configfs/crash_dm_crypt_key/reuse if CONFIG_CRASH_HOTPLUG enabled
In-Reply-To: <20260501234342.2518281-9-coiby.xu@gmail.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-9-coiby.xu@gmail.com>
Message-ID: <5af6fd0b-0eca-4089-b76c-45dcc4bf01ed@linux.ibm.com>



On 02/05/26 05:13, Coiby Xu wrote:
> If CONFIG_CRASH_HOTPLUG is enabled, dm-crypt keys saved to reserved
> memory will be took care of automatically. Thus it doesn't make sense
> to use configfs/crash_dm_crypt_key/reuse. Reserving
> image->dm_crypt_keys_addr is also unnecessary. Currently x86_64 and
> ppc64le have implemented CONFIG_CRASH_HOTPLUG feature.
>
> Also update the doc accordingly. Note two doc issues are fixed as well.
>
> Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
> ---
>   Documentation/admin-guide/kdump/kdump.rst |  9 ++++++---
>   kernel/crash_dump_dm_crypt.c              | 14 +++++++++++---
>   2 files changed, 17 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/admin-guide/kdump/kdump.rst b/Documentation/admin-guide/kdump/kdump.rst
> index 7587caadbae1..73f2e9500c60 100644
> --- a/Documentation/admin-guide/kdump/kdump.rst
> +++ b/Documentation/admin-guide/kdump/kdump.rst
> @@ -577,9 +577,10 @@ with /sys/kernel/config/crash_dm_crypt_keys for setup,
>   
>   1. Tell the first kernel what logon keys are needed to unlock the disk volumes,
>       # Add key #1
> -    mkdir /sys/kernel/config/crash_dm_crypt_keys/7d26b7b4-e342-4d2d-b660-7426b0996720
> +    VOL1_UUID=7d26b7b4-e342-4d2d-b660-7426b0996720
> +    mkdir /sys/kernel/config/crash_dm_crypt_keys/$VOL1_UUID
>       # Add key #1's description
> -    echo cryptsetup:7d26b7b4-e342-4d2d-b660-7426b0996720 > /sys/kernel/config/crash_dm_crypt_keys/description
> +    echo cryptsetup:$VOL1_UUID > /sys/kernel/config/crash_dm_crypt_keys/$VOL1_UUID/description
>   
>       # how many keys do we have now?
>       cat /sys/kernel/config/crash_dm_crypt_keys/count
> @@ -593,7 +594,9 @@ with /sys/kernel/config/crash_dm_crypt_keys for setup,
>   
>       # To support CPU/memory hot-plugging, reuse keys already saved to reserved
>       # memory
> -    echo true > /sys/kernel/config/crash_dm_crypt_key/reuse
> +    # Note if CONFIG_CRASH_HOTPLUG is enabled, this API is totally unnecessary
> +    # thus will be disabled.
> +    echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>   
>   2. Load the dump-capture kernel
>   
> diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
> index 36e51807d94f..7a7cae17f578 100644
> --- a/kernel/crash_dump_dm_crypt.c
> +++ b/kernel/crash_dump_dm_crypt.c
> @@ -304,6 +304,11 @@ static ssize_t config_keys_reuse_store(struct config_item *item,
>   	bool val;
>   	int r;
>   
> +	if (IS_ENABLED(CONFIG_CRASH_HOTPLUG)) {
> +		pr_info("CONFIG_CRASH_HOTPLUG already enabled");
> +		return -EINVAL;
> +	}
> +

Deciding this solely at compile time can create issues. For example, the 
kernel
may be built with CONFIG_CRASH_HOTPLUG, but if kexec tool loads the kdump
kernel using the kexec_load system call without hotplug support, it can
cause problems. It is rare but possible.

How about this:

#ifdef CONFIG_CRASH_HOTPLUG
 ? ? if (kexec_crash_image->hotplug_support) {
pr_info("crash image is loaded with hotplug support\n");return -EINVAL;
 ? ? }
#endif

This code should be placed after validating kexec_crash_image.


- Sourabh Jain

>   	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {
>   		pr_info("dm-crypt keys haven't be saved to crash-reserved memory\n");
>   		return -EINVAL;
> @@ -486,15 +491,18 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>   void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>   {
>   	/*
> -	 * For CPU/memory hot-plugging, the kdump image will be reloaded. Prevent
> -	 * keys_header from being cleaned up during unloading when
> -	 * is_dm_key_reused=true
> +	 * For CPU/memory hot-plugging without CONFIG_CRASH_HOTPLUG, the whole kdump
> +	 * image will be reloaded. Prevent keys_header from being cleaned up during
> +	 * unloading when is_dm_key_reused=true
>   	 */
>   	if (!is_dm_key_reused) {
>   		kfree_sensitive(keys_header);
>   		keys_header = NULL;
>   	}
>   
> +	if (IS_ENABLED(CONFIG_CRASH_HOTPLUG))
> +		image->dm_crypt_keys_addr = 0;
> +
>   	if (mutex_is_locked(&config_keys_subsys.su_mutex))
>   		mutex_unlock(&config_keys_subsys.su_mutex);
>   }



From pratyush at kernel.org  Wed May  6 09:15:03 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Wed, 06 May 2026 18:15:03 +0200
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <aftVd5RnpnAi2jdc@google.com> (Pasha Tatashin's message of "Wed,
	6 May 2026 11:05:32 -0400")
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
	<20260501173053.73116-1-crisjacobmaamor@gmail.com> <afT_xHDuFwAUSR0X@plex>
	<2vxzse84zzag.fsf@kernel.org> <aftVd5RnpnAi2jdc@google.com>
Message-ID: <2vxzfr44zf94.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> On 05-06 11:02, Pratyush Yadav wrote:
>> Hi Pasha,
>> 
>> On Fri, May 01 2026, Pasha Tatashin wrote:
>> 
>> > On 05-02 01:30, Cris Jacob Maamor wrote:
>> >> LUO restores metadata from KHO/FDT during liveupdate. The restored
>> >> metadata contains physical addresses and count fields used to access and
>> >> walk preserved session, file set, and FLB arrays.
>> >> 
>> >> This series adds a non-consuming KHO preserved-range check and uses it
>> >> before phys_to_virt() on restored metadata addresses. It also rejects
>> >> restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
>> >> before traversal.
>> >> 
>> >> As far as I can tell, this is root/admin-only; I do not have evidence
>> >> that a normal unprivileged user can trigger it directly.
>> >> 
>> >> Changes since v1:
>> >> - Dropped RFC marking.
>> >> - Added changelog text to each patch.
>> >> - No code changes.
>> >> 
>> >> Cris Jacob Maamor (5):
>> >>   kexec: handover: add helper to check preserved page ranges
>> >>   liveupdate: validate LUO FDT physical address before mapping
>> >>   liveupdate: validate restored LUO session metadata
>> >>   liveupdate: validate restored LUO file set metadata
>> >>   liveupdate: validate restored LUO FLB metadata
>> >
>> > I have replied separately in the security report to clarify that this is 
>> > not a bug. The behavior follows the ABI specification exactly: we use 
>> > the PA addresses and ranges provided by the KHO FDT tree.
>> >
>> > NAK
>> 
>> I really do think we should do a restore-only variant for the
>> kho_alloc_preserve() family of allocators and use it everywhere. It
>
> That is unrelated to the provided patch series. The author of this 
> series reported this as a security issue to the Linux security ML, and 
> submitted this series at their request.

Oh yes, sure. I am not arguing for taking this series. I just figured
this would be a good point to have this discussion.

>
> This is not a security issue, and in fact, it is not an issue at all. A 
> restore-only variant can be added, but I do not see a reason for LUO to 
> use it.
>
>> would prevent problems in the future. Not because the previous kernel is
>> malicious, but because we might have bugs and the KHO page magic sanity
>> check acts as a defense in depth.
>> 
>> For example, I am currently looking at a LUO bug where LUO does not
>> track if a session is outgoing or incoming. So you can do a retrieve()
>> or finish() on an outgoing session. A lot of nastiness is saved because
>> of the page magic check. Things like kho_restore_vmalloc() or
>> kho_restore_folio() fail early and loudly.
>
> I am not sure what bug you are looking at (please share the details!), 

I was looking at LUO code and realized that we do not separate outgoing
and incoming sessions when dealing with preserve/retrieve/finish ioctls.
So you can create a session, preserve a FD, and then immediately call
finish or retrieve without doing a kexec. Of course, LUO file handlers
aren't able to cope with it.

So for example, you can preserve a memfd and then immediately call
finish. This will call memfd_luo_finish(), where it will try to
kho_restore_vmalloc(). That fails with a bit WARN splat. And then later
it calls kho_restore_free() which also fails in a similar fashion.

You can do the same thing with retrieve(), but that also fails early and
loudly and does not cause any problems.

I am working on a fix for it. Should have something out shortly.

> but the fix absolutely should be to use outgoing/incoming sessions 
> properly, and if we mixed them up somewhere, THAT should be fixed. Using 
> KHO restore is not going to help much; however, I agree it can add 
> some extra scrutiny (i.e., similar to an ASSERT), but it is not really 
> something that would help improve correctness in any meaningful way. The 
> correctness should lie in the LUO logic using incoming as incoming, and 
> outgoing as outgoing.

I am not arguing that we shouldn't fix the logic bugs. Of course we
should.

My point is that this sanity check acts as another layer of defence.
Bugs happen, but the earlier we catch them the better and this sanity
check helps us do exactly that.

For example, if we did not have these sanity checks, the loud errors I
described above would be replaced by silent use-after-free, double-free,
struct page corruption, or other problems.

So I would like to understand why you _don't_ want to have this line of
defence. What's the problem? If you are worried about performance, we
can go and measure it. If the overhead is too high this can be behind a
debug config.

>
>> 
>> If we want to squeeze out more performance later down the line we can
>> move it behind a debug config, but having this usage pattern of always
>> restoring before using is going to be a lot more sane than just using
>> physical addresses willy nilly.
>> 
>> The approach this series takes with kho_is_preserved() is the wrong
>> design. But a kho_restore() or something similar (maybe we can find a
>> better name?) is really where we should be going.
>> 
>> -- 
>> Regards,
>> Pratyush Yadav

-- 
Regards,
Pratyush Yadav


From pasha.tatashin at soleen.com  Wed May  6 09:34:10 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 6 May 2026 12:34:10 -0400
Subject: [PATCH v2 0/5] liveupdate: validate restored LUO metadata
In-Reply-To: <2vxzfr44zf94.fsf@kernel.org>
References: <20260501094637.38650-1-crisjacobmaamor@gmail.com>
 <20260501173053.73116-1-crisjacobmaamor@gmail.com>
 <afT_xHDuFwAUSR0X@plex>
 <2vxzse84zzag.fsf@kernel.org>
 <aftVd5RnpnAi2jdc@google.com>
 <2vxzfr44zf94.fsf@kernel.org>
Message-ID: <aftsRFhwjE0mq7pb@google.com>

On 05-06 18:15, Pratyush Yadav wrote:
> On Wed, May 06 2026, Pasha Tatashin wrote:
> 
> > On 05-06 11:02, Pratyush Yadav wrote:
> >> Hi Pasha,
> >> 
> >> On Fri, May 01 2026, Pasha Tatashin wrote:
> >> 
> >> > On 05-02 01:30, Cris Jacob Maamor wrote:
> >> >> LUO restores metadata from KHO/FDT during liveupdate. The restored
> >> >> metadata contains physical addresses and count fields used to access and
> >> >> walk preserved session, file set, and FLB arrays.
> >> >> 
> >> >> This series adds a non-consuming KHO preserved-range check and uses it
> >> >> before phys_to_virt() on restored metadata addresses. It also rejects
> >> >> restored counts above LUO_SESSION_MAX, LUO_FILE_MAX, and LUO_FLB_MAX
> >> >> before traversal.
> >> >> 
> >> >> As far as I can tell, this is root/admin-only; I do not have evidence
> >> >> that a normal unprivileged user can trigger it directly.
> >> >> 
> >> >> Changes since v1:
> >> >> - Dropped RFC marking.
> >> >> - Added changelog text to each patch.
> >> >> - No code changes.
> >> >> 
> >> >> Cris Jacob Maamor (5):
> >> >>   kexec: handover: add helper to check preserved page ranges
> >> >>   liveupdate: validate LUO FDT physical address before mapping
> >> >>   liveupdate: validate restored LUO session metadata
> >> >>   liveupdate: validate restored LUO file set metadata
> >> >>   liveupdate: validate restored LUO FLB metadata
> >> >
> >> > I have replied separately in the security report to clarify that this is 
> >> > not a bug. The behavior follows the ABI specification exactly: we use 
> >> > the PA addresses and ranges provided by the KHO FDT tree.
> >> >
> >> > NAK
> >> 
> >> I really do think we should do a restore-only variant for the
> >> kho_alloc_preserve() family of allocators and use it everywhere. It
> >
> > That is unrelated to the provided patch series. The author of this 
> > series reported this as a security issue to the Linux security ML, and 
> > submitted this series at their request.
> 
> Oh yes, sure. I am not arguing for taking this series. I just figured
> this would be a good point to have this discussion.
> 
> >
> > This is not a security issue, and in fact, it is not an issue at all. A 
> > restore-only variant can be added, but I do not see a reason for LUO to 
> > use it.
> >
> >> would prevent problems in the future. Not because the previous kernel is
> >> malicious, but because we might have bugs and the KHO page magic sanity
> >> check acts as a defense in depth.
> >> 
> >> For example, I am currently looking at a LUO bug where LUO does not
> >> track if a session is outgoing or incoming. So you can do a retrieve()
> >> or finish() on an outgoing session. A lot of nastiness is saved because
> >> of the page magic check. Things like kho_restore_vmalloc() or
> >> kho_restore_folio() fail early and loudly.
> >
> > I am not sure what bug you are looking at (please share the details!), 
> 
> I was looking at LUO code and realized that we do not separate outgoing
> and incoming sessions when dealing with preserve/retrieve/finish ioctls.
> So you can create a session, preserve a FD, and then immediately call
> finish or retrieve without doing a kexec. Of course, LUO file handlers
> aren't able to cope with it.

Oh, this makes sense, please add a self-test for that as well :-)

> 
> So for example, you can preserve a memfd and then immediately call
> finish. This will call memfd_luo_finish(), where it will try to
> kho_restore_vmalloc(). That fails with a bit WARN splat. And then later
> it calls kho_restore_free() which also fails in a similar fashion.
> 
> You can do the same thing with retrieve(), but that also fails early and
> loudly and does not cause any problems.
> 
> I am working on a fix for it. Should have something out shortly.
> 
> > but the fix absolutely should be to use outgoing/incoming sessions 
> > properly, and if we mixed them up somewhere, THAT should be fixed. Using 
> > KHO restore is not going to help much; however, I agree it can add 
> > some extra scrutiny (i.e., similar to an ASSERT), but it is not really 
> > something that would help improve correctness in any meaningful way. The 
> > correctness should lie in the LUO logic using incoming as incoming, and 
> > outgoing as outgoing.
> 
> I am not arguing that we shouldn't fix the logic bugs. Of course we
> should.
> 
> My point is that this sanity check acts as another layer of defence.
> Bugs happen, but the earlier we catch them the better and this sanity
> check helps us do exactly that.
> 
> For example, if we did not have these sanity checks, the loud errors I
> described above would be replaced by silent use-after-free, double-free,
> struct page corruption, or other problems.
> 
> So I would like to understand why you _don't_ want to have this line of
> defence. What's the problem? If you are worried about performance, we
> can go and measure it. If the overhead is too high this can be behind a
> debug config.

Most likely, there is no performance cost, because when we free 
preserved memory, we still need to do a KHO restore. The only difference 
is that it may occur after a blackout not during blackout. Anyway, if 
you would like to add this sanity check, please send it out, and we can 
review and discuss how it looks.

> 
> >
> >> 
> >> If we want to squeeze out more performance later down the line we can
> >> move it behind a debug config, but having this usage pattern of always
> >> restoring before using is going to be a lot more sane than just using
> >> physical addresses willy nilly.
> >> 
> >> The approach this series takes with kho_is_preserved() is the wrong
> >> design. But a kho_restore() or something similar (maybe we can find a
> >> better name?) is really where we should be going.
> >> 
> >> -- 
> >> Regards,
> >> Pratyush Yadav
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Wed May  6 11:20:40 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 14:20:40 -0400
Subject: [PATCH v2 0/3] liveupdate: serialization safety and race fixes
Message-ID: <20260506182039.2623553-5-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization
between the reboot process and LUO session management.

Changes in v2:
- Added a patch to skip KHO data for context-preserving kexec (suggested
  by Pratyush Yadav).
- Dropped the 'rebooting' flag and manual session pinning patches from
  v1.
- Implemented a locking approach in luo_session_serialize() to prevent
  concurrent session mutations and release (suggested by Pratyush).

1. Skip KHO data for context-preserving kexec: A preserve_context kexec
returns to the current kernel, which means the 2nd kernel does not use
KHO data. Passing KHO data via setup_data or devicetree is unnecessary.

2. Skip LUO serialization for context-preserving kexec: A
preserve_context kexec returns to the current kernel, which is unrelated
to live update where state is passed to the next kernel. Skipping
serialization avoids unnecessary work and prevents sessions from being
left in a frozen state upon return.

3. Block outgoing session mutations using locks: During the reboot()
syscall, user processes may still be running concurrently and attempting
to mutate sessions. To prevent this, we hold the session header's rwsem
and each session's mutex indefinitely on successful serialization. This
effectively pins the sessions and blocks concurrent modifications
without needing custom flags.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
luo-reboot-sync/v2

Pasha Tatashin (3):
  kho: skip KHO data for context-preserving kexec
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: block outgoing session mutations

 kernel/kexec_core.c                |  8 +++++---
 kernel/liveupdate/kexec_handover.c |  3 ++-
 kernel/liveupdate/luo_session.c    | 12 +++++++++---
 3 files changed, 16 insertions(+), 7 deletions(-)


base-commit: 7b0b68b2b95606e65594958686833e53423f58f2
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Wed May  6 11:20:41 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 14:20:41 -0400
Subject: [PATCH v2 1/3] kho: skip KHO data for context-preserving kexec
In-Reply-To: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
Message-ID: <20260506182039.2623553-6-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which means the
2nd kernel does not use KHO data (and their memory spaces don't
overlap). Passing KHO data to the 2nd kernel via setup_data or
devicetree is unnecessary.

Add a check in kho_fill_kimage() and return early if preserve_context is
set, to avoid passing KHO state during context-preserving kexecs.

Fixes: 3bdecc3c93f9 ("kexec: add KHO support to kexec file loads")
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/kexec_handover.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4fde8325c49f..2eec2a169c83 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1702,7 +1702,8 @@ int kho_fill_kimage(struct kimage *image)
 	int err = 0;
 	struct kexec_buf scratch;
 
-	if (!kho_enable || image->type == KEXEC_TYPE_CRASH)
+	if (!kho_enable || image->type == KEXEC_TYPE_CRASH ||
+	    image->preserve_context)
 		return 0;
 
 	image->kho.fdt = virt_to_phys(kho_out.fdt);
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Wed May  6 11:20:42 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 14:20:42 -0400
Subject: [PATCH v2 2/3] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
Message-ID: <20260506182039.2623553-7-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which is
unrelated to live update where the state is passed to the next kernel.
Skip liveupdate_reboot() in this case to avoid serialization and prevent
sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.54.0.545.g6539524ca2-goog



From pasha.tatashin at soleen.com  Wed May  6 11:20:43 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed,  6 May 2026 14:20:43 -0400
Subject: [PATCH v2 3/3] liveupdate: block outgoing session mutations
In-Reply-To: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
Message-ID: <20260506182039.2623553-8-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., adding files or
closing sessions). To prevent this, hold the session header's rwsem and
each session's mutex indefinitely on successful serialization.

If serialization succeeds, these locks will never be released as the
system transitions to the new kernel, effectively blocking any
concurrent task attempting to modify or release a session. If
liveupdate_reboot() fails, the locks are released in the error path,
allowing normal operation to resume.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..12808c696def 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -189,14 +189,14 @@ static int luo_session_finish_one(struct luo_session *session)
 static void luo_session_unfreeze_one(struct luo_session *session,
 				     struct luo_session_ser *ser)
 {
-	guard(mutex)(&session->mutex);
+	lockdep_assert_held(&session->mutex);
 	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
 }
 
 static int luo_session_freeze_one(struct luo_session *session,
 				  struct luo_session_ser *ser)
 {
-	guard(mutex)(&session->mutex);
+	lockdep_assert_held(&session->mutex);
 	return luo_file_freeze(&session->file_set, &ser->file_set_ser);
 }
 
@@ -583,7 +583,10 @@ int luo_session_serialize(void)
 	int i = 0;
 	int err;
 
-	guard(rwsem_write)(&sh->rwsem);
+	down_write(&sh->rwsem);
+	list_for_each_entry(session, &sh->list, list)
+		mutex_lock_nest_lock(&session->mutex, &sh->rwsem);
+
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -603,6 +606,9 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	list_for_each_entry(session, &sh->list, list)
+		mutex_unlock(&session->mutex);
+	up_write(&sh->rwsem);
 
 	return err;
 }
-- 
2.54.0.545.g6539524ca2-goog



From helgaas at kernel.org  Wed May  6 11:28:01 2026
From: helgaas at kernel.org (Bjorn Helgaas)
Date: Wed, 6 May 2026 13:28:01 -0500
Subject: [PATCH 4/5] PCI: Enable async shutdown support
In-Reply-To: <20260429175016.7915-5-djeffery@redhat.com>
Message-ID: <20260506182801.GA805231@bhelgaas>

On Wed, Apr 29, 2026 at 01:50:15PM -0400, David Jeffery wrote:
> Like its async suspend support, allow PCI device shutdown to be performed
> asynchronously to reduce shutdown time.
> 
> Signed-off-by: David Jeffery <djeffery at redhat.com>
> Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
> Tested-by: Laurence Oberman <loberman at redhat.com>
> Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
> Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Bjorn Helgaas <bhelgaas at google.com>

I'm concerned about tripping over driver issues, but it's a pretty big
benefit.  I think it's worth mentioning the "async_shutdown" module
parameter somewhere in the commit logs and putting an example in
Documentation/admin-guide/kernel-parameters.txt.

Might even consider keeping in -next for a cycle+ and targeting v7.3.

> ---
>  drivers/pci/probe.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
> index b63cd0c310bc..86e855090553 100644
> --- a/drivers/pci/probe.c
> +++ b/drivers/pci/probe.c
> @@ -1045,6 +1045,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
>  
>  	bus->bridge = get_device(&bridge->dev);
>  	device_enable_async_suspend(bus->bridge);
> +	device_enable_async_shutdown(bus->bridge);
>  	pci_set_bus_of_node(bus);
>  	pci_set_bus_msi_domain(bus);
>  	if (bridge->msi_domain && !dev_get_msi_domain(&bus->dev) &&
> @@ -2753,6 +2754,7 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
>  	pci_reassigndev_resource_alignment(dev);
>  
>  	pci_init_capabilities(dev);
> +	device_enable_async_shutdown(&dev->dev);
>  
>  	/*
>  	 * Add the device to our list of discovered devices
> -- 
> 2.53.0
> 


From djeffery at redhat.com  Wed May  6 14:10:19 2026
From: djeffery at redhat.com (David Jeffery)
Date: Wed, 6 May 2026 17:10:19 -0400
Subject: [PATCH 4/5] PCI: Enable async shutdown support
In-Reply-To: <20260506182801.GA805231@bhelgaas>
References: <20260429175016.7915-5-djeffery@redhat.com> <20260506182801.GA805231@bhelgaas>
Message-ID: <CA+-xHTFj_J4r2oqG8cPOU_wx7pChjHt9SgyHzKbTD8+KgeSFXw@mail.gmail.com>

On Wed, May 6, 2026 at 2:28?PM Bjorn Helgaas <helgaas at kernel.org> wrote:
>
> On Wed, Apr 29, 2026 at 01:50:15PM -0400, David Jeffery wrote:
> > Like its async suspend support, allow PCI device shutdown to be performed
> > asynchronously to reduce shutdown time.
> >
> > Signed-off-by: David Jeffery <djeffery at redhat.com>
> > Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
> > Tested-by: Laurence Oberman <loberman at redhat.com>
> > Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
> > Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
>
> Reviewed-by: Bjorn Helgaas <bhelgaas at google.com>
>
> I'm concerned about tripping over driver issues, but it's a pretty big
> benefit.  I think it's worth mentioning the "async_shutdown" module
> parameter somewhere in the commit logs and putting an example in
> Documentation/admin-guide/kernel-parameters.txt.

Sure, I can see about adding a document patch to it.

> Might even consider keeping in -next for a cycle+ and targeting v7.3.

I would be happy for it to spend as much time in linux-next as needed
for people to be comfortable with the risk of odd interactions with
all the pci drivers it will indirectly impact.

But I must admit my ignorance with some of the details of linux-next.
Looking at the main code changes, the patchset would seem to want to
go through driver-core to get to linux-next. But as far as indirect
impact, it is the diverse collection of devices and drivers under PCI
which are most affected by the changes. Is there a consensus on how to
steward a patchset like this one into linux-next?

David Jeffery



From helgaas at kernel.org  Wed May  6 14:24:56 2026
From: helgaas at kernel.org (Bjorn Helgaas)
Date: Wed, 6 May 2026 16:24:56 -0500
Subject: [PATCH 4/5] PCI: Enable async shutdown support
In-Reply-To: <CA+-xHTFj_J4r2oqG8cPOU_wx7pChjHt9SgyHzKbTD8+KgeSFXw@mail.gmail.com>
Message-ID: <20260506212456.GA880761@bhelgaas>

On Wed, May 06, 2026 at 05:10:19PM -0400, David Jeffery wrote:
> On Wed, May 6, 2026 at 2:28?PM Bjorn Helgaas <helgaas at kernel.org> wrote:
> > On Wed, Apr 29, 2026 at 01:50:15PM -0400, David Jeffery wrote:
> > > Like its async suspend support, allow PCI device shutdown to be performed
> > > asynchronously to reduce shutdown time.
> > >
> > > Signed-off-by: David Jeffery <djeffery at redhat.com>
> > > Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
> > > Tested-by: Laurence Oberman <loberman at redhat.com>
> > > Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
> > > Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> >
> > Reviewed-by: Bjorn Helgaas <bhelgaas at google.com>
> >
> > I'm concerned about tripping over driver issues, but it's a pretty big
> > benefit.  I think it's worth mentioning the "async_shutdown" module
> > parameter somewhere in the commit logs and putting an example in
> > Documentation/admin-guide/kernel-parameters.txt.
> 
> Sure, I can see about adding a document patch to it.
> 
> > Might even consider keeping in -next for a cycle+ and targeting v7.3.
> 
> I would be happy for it to spend as much time in linux-next as needed
> for people to be comfortable with the risk of odd interactions with
> all the pci drivers it will indirectly impact.
> 
> But I must admit my ignorance with some of the details of linux-next.
> Looking at the main code changes, the patchset would seem to want to
> go through driver-core to get to linux-next. But as far as indirect
> impact, it is the diverse collection of devices and drivers under PCI
> which are most affected by the changes. Is there a consensus on how to
> steward a patchset like this one into linux-next?

My guess is it should all go via the driver-core tree, given acks or
reviewed-by from the other subsystems it touches.


From chenwandun1 at gmail.com  Thu May  7 01:41:15 2026
From: chenwandun1 at gmail.com (Wandun)
Date: Thu, 7 May 2026 16:41:15 +0800
Subject: [PATCH 01/11] of: reserved_mem: fix region count for nodes with
 multiple reg entries
In-Reply-To: <20260506014752.GA280279-robh@kernel.org>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-2-chenwandun@lixiang.com>
 <20260506014752.GA280279-robh@kernel.org>
Message-ID: <126f4fd0-0a1c-41e0-8746-fa7ab85d6773@gmail.com>


? 2026/5/6 09:47, Rob Herring ??:
> On Wed, Apr 29, 2026 at 02:58:21PM +0800, Chen Wandun wrote:
>> When a reserved-memory node contains multiple reg entries (e.g.,
>> reg = <base1 size1>, <base2 size2>), the count used for
>> total_reserved_mem_cnt is wrong in two places:
>>
>> 1) __reserved_mem_reserve_reg() returns 0 on success regardless of how
>>     many regions it reserved in memblock. The caller in
>>     fdt_scan_reserved_mem() then increments count by just 1.
> Just to make sure, more than 1 worked before the referenced commits? It
> would be easier to just define we only expect/support 1 entry.
Looking at the pre-8a6e02d0c00e code, __reserved_mem_reserve_reg()
reserved memblock memory for all reg?entries, but only called
fdt_reserved_mem_save_node() for the first one (guarded by an 'if 
(first)' flag).

So multiple reg entries?were never fully supported: subsequent entries
got their memory reserved in memblock, but their metadata was lost
and driver-specific init callbacks were never invoked for them.

The referenced commits made this worse by also breaking the count
tracking, but the root limitation predates them.

I support documenting "only 1 entry supported" based on the
following?reasons:

 ? - of_reserved_mem_lookup() does a name-based linear scan and returns
 ? ? the *first* matching entry. A node with N reg entries would create
 ? ? N entries with identical names; entries [1..N-1] are permanently
 ? ? unreachable via lookup.

 ? - Drivers like CMA and DMA coherent are designed to initialize a
 ? ? single contiguous pool per node_init call. Calling node_init
 ? ? multiple times with the same FDT node is not a supported usage.


>
>> 2) fdt_scan_reserved_mem_late() uses of_flat_dt_get_addr_size() which
>>     only reads the first reg entry. Subsequent entries are never
>>     initialized via fdt_init_reserved_mem_node(), so their metadata is
>>     lost.
>>
>> Fix both issues:
>>   - Make __reserved_mem_reserve_reg() return the actual number of
>>     regions successfully reserved. Update the caller to accumulate
>>     the returned count.
>>   - Rewrite fdt_scan_reserved_mem_late() to use
>>     of_flat_dt_get_addr_size_prop() and iterate all reg entries,
>>     initializing each one via fdt_init_reserved_mem_node().
>>
>> Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed")
>> Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array")


From chenwandun1 at gmail.com  Thu May  7 01:48:09 2026
From: chenwandun1 at gmail.com (Wandun)
Date: Thu, 7 May 2026 16:48:09 +0800
Subject: [PATCH 07/11] of: reserved_mem: add no-dump crash_mem exclusion
 helpers
In-Reply-To: <20260506145022.GA2084721-robh@kernel.org>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-8-chenwandun@lixiang.com>
 <20260506145022.GA2084721-robh@kernel.org>
Message-ID: <6056bd1f-3998-44ff-8c4f-972704e0b896@gmail.com>


On 5/6/26 22:50, Rob Herring wrote:
> On Wed, Apr 29, 2026 at 02:58:27PM +0800, Chen Wandun wrote:
>> Provide two kdump-oriented helpers so that arch kexec_file code does
>> not have to open-code the no-dump filtering loop:
>>
>>   - of_reserved_mem_no_dump_nr_ranges() returns the number of reserved
>>     regions flagged with linux,no-dump. Each exclusion may split one
>>     existing crash_mem range into two, so callers use this count to
>>     pre-size their crash_mem allocation.
>>
>>   - of_reserved_mem_exclude_no_dump() walks the reserved_mem[] array
>>     and calls crash_exclude_mem_range() for each no-dump region.
>>
>> Both helpers are guarded by CONFIG_KEXEC_FILE; empty inline stubs are
>> provided for the !KEXEC_FILE case so architecture code can call them
>> unconditionally.
>>
>> The consumers are added in the following arm64, riscv and loongarch
>> patches in this series.
>>
>> Signed-off-by: Chen Wandun <chenwandun at lixiang.com>
>> Tested-by: Zhao Meijing <zhaomeijing at lixiang.com>
>> ---
>>   drivers/of/of_reserved_mem.c    | 54 +++++++++++++++++++++++++++++++++
>>   include/linux/of_reserved_mem.h | 15 +++++++++
>>   2 files changed, 69 insertions(+)
>>
>> diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
>> index 4b80420da2d2..038056a6408a 100644
>> --- a/drivers/of/of_reserved_mem.c
>> +++ b/drivers/of/of_reserved_mem.c
>> @@ -27,6 +27,10 @@
>>   
>>   #include "of_private.h"
>>   
>> +#ifdef CONFIG_KEXEC_FILE
>> +#include <linux/crash_core.h>
>> +#endif
> You shouldn't need ifdef around includes.
>
>> +
>>   static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
>>   static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
>>   static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
>> @@ -916,6 +920,56 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
>>   }
>>   EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
>>   
>> +#ifdef CONFIG_KEXEC_FILE
> Use 'if (IS_ENABLED())' within the function.
Get it, thanks.
>
>> +/**
>> + * of_reserved_mem_no_dump_nr_ranges() - count reserved regions flagged
>> + * with the linux,no-dump property.
>> + *
>> + * Each such region may split an existing crash_mem range into two when
>> + * it is excluded, so callers can use this count to pre-size their
>> + * crash_mem allocation.
>> + */
>> +unsigned int of_reserved_mem_no_dump_nr_ranges(void)
>> +{
>> +	unsigned int i, n = 0;
>> +
>> +	for (i = 0; i < reserved_mem_count; i++)
>> +		if (reserved_mem[i].no_dump)
>> +			n++;
>> +	return n;
>> +}
>> +
>> +/**
>> + * of_reserved_mem_exclude_no_dump() - exclude no-dump reserved regions
>> + * from a crash_mem list.
>> + * @cmem: crash memory list to modify
>> + *
>> + * Walks the reserved_mem[] array and calls crash_exclude_mem_range() for
>> + * every region with no_dump set. Intended to be called from arch kdump
>> + * code when constructing the elfcorehdr.
>> + *
>> + * Returns 0 on success, or a negative error returned by
>> + * crash_exclude_mem_range() on the first failure.
>> + */
>> +int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	for (i = 0; i < reserved_mem_count; i++) {
>> +		struct reserved_mem *r = &reserved_mem[i];
>> +
>> +		if (!r->no_dump || !r->size)
>> +			continue;
>> +		ret = crash_exclude_mem_range(cmem, r->base,
>> +					      r->base + r->size - 1);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +	return 0;
>> +}
>> +#endif /* CONFIG_KEXEC_FILE */


From chenwandun1 at gmail.com  Thu May  7 02:35:33 2026
From: chenwandun1 at gmail.com (Wandun)
Date: Thu, 7 May 2026 17:35:33 +0800
Subject: [PATCH 02/11] of: reserved_mem: reject reserved memory outside
 physical address range
In-Reply-To: <20260506015112.GA286568-robh@kernel.org>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-3-chenwandun@lixiang.com>
 <20260506015112.GA286568-robh@kernel.org>
Message-ID: <1a8af3cf-5edc-4ca4-b340-12ebeb2ed982@gmail.com>



On 5/6/26 09:51, Rob Herring wrote:
> On Wed, Apr 29, 2026 at 02:58:22PM +0800, Chen Wandun wrote:
>> early_init_dt_reserve_memory() does not validate whether the region
>> falls within physical memory. If a device tree incorrectly specifies a
>> reserved memory region outside the physical address range:
>>
>>   - For the non-nomap path, memblock_reserve() blindly adds the region
>>     to memblock.reserved, creating a stale entry that refers to
>>     non-existent memory.
>>
>>   - For the nomap path, memblock_mark_nomap() silently fails to match
>>     any region in memblock.memory, but still returns success.
>>
>> Add a memblock_overlaps_region() check at the entry of
>> early_init_dt_reserve_memory() to reject such regions before any
>> memblock operation takes place. This also simplifies the existing nomap
>> guard: the original "overlaps && is_reserved" condition reduces to just
>> "is_reserved", since the overlap with physical memory is already
>> guaranteed by the new check.
> While I agree, I suspect we already have cases abusing reserved-memory
> like this.
Sashiko reviewed this patch and told me:
"Historically, the reserved-memory binding is often used to describe 
hardware
SRAM, DSP memory, or IOMEM carveouts that reside outside of system RAM."
IIUC, nowdays using mmio-sram DT binding is more appropriate for SRAM or 
IOMEM carveouts.
Should I drop this patch or keep it ?
Thanks.
>
>> Signed-off-by: Chen Wandun <chenwandun at lixiang.com>
>> Tested-by: Zhao Meijing <zhaomeijing at lixiang.com>
>> ---
>>   drivers/of/of_reserved_mem.c | 15 +++++++++++----
>>   1 file changed, 11 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
>> index 9d1b0193864c..03c676052dab 100644
>> --- a/drivers/of/of_reserved_mem.c
>> +++ b/drivers/of/of_reserved_mem.c
>> @@ -112,14 +112,21 @@ static int fdt_fixup_reserved_mem_node(unsigned long node,
>>   static int __init early_init_dt_reserve_memory(phys_addr_t base,
>>   					       phys_addr_t size, bool nomap)
>>   {
>> +	if (!memblock_overlaps_region(&memblock.memory, base, size)) {
>> +		phys_addr_t end = base + size - 1;
>> +
>> +		pr_warn("Reserved memory region %pa..%pa is outside of physical memory\n",
>> +			&base, &end);
>> +		return -EINVAL;
>> +	}
>> +
>>   	if (nomap) {
>>   		/*
>>   		 * If the memory is already reserved (by another region), we
>> -		 * should not allow it to be marked nomap, but don't worry
>> -		 * if the region isn't memory as it won't be mapped.
>> +		 * should not allow it to be marked nomap. The region being
>> +		 * physical memory is guaranteed by the overlap check above.
>>   		 */
>> -		if (memblock_overlaps_region(&memblock.memory, base, size) &&
>> -		    memblock_is_region_reserved(base, size))
>> +		if (memblock_is_region_reserved(base, size))
>>   			return -EBUSY;
>>   
>>   		return memblock_mark_nomap(base, size);
>> -- 
>> 2.43.0
>>



From chenwandun1 at gmail.com  Thu May  7 02:41:49 2026
From: chenwandun1 at gmail.com (Wandun)
Date: Thu, 7 May 2026 17:41:49 +0800
Subject: [PATCH 05/11] of: reserved_mem: add linux,no-dump property
 support for reserved memory regions
In-Reply-To: <20260506144542.GA2072596-robh@kernel.org>
References: <20260429065831.1510858-1-chenwandun@lixiang.com>
 <20260429065831.1510858-6-chenwandun@lixiang.com>
 <20260506144542.GA2072596-robh@kernel.org>
Message-ID: <eece7690-634a-43c5-9b20-0afc174a89a0@gmail.com>



On 5/6/26 22:45, Rob Herring wrote:
> On Wed, Apr 29, 2026 at 02:58:25PM +0800, Chen Wandun wrote:
>> Add a 'no_dump' field to struct reserved_mem and parse the
>> 'linux,no-dump' device tree property during reserved memory node
>> initialization. This property allows device tree authors to mark
>> specific reserved memory regions that should be excluded from kdump
>> vmcore dumps.
>>
>> Reserved memory regions used by device firmware (e.g., GPU, DSP, modem)
>> typically contain data that is not useful for kernel crash analysis and
>> can significantly increase vmcore size. The 'linux,no-dump' property
>> provides a declarative way to indicate these regions should be filtered
>> out when constructing the elfcorehdr for kdump.
>>
>> The property is named with a 'linux,' prefix because kdump/vmcore is
>> Linux-specific and the property is an OS hint rather than a hardware
>> description, matching existing properties such as 'linux,cma-default'
>> and 'linux,usable-memory-range'.
>>
>> The 'linux,no-dump' property is only effective when the region:
>>   - Does not have 'no-map': these regions are already excluded from
>>     vmcore since they are removed from the linear mapping (MEMBLOCK_NOMAP).
>>   - Does not have 'reusable': CMA reusable regions are actively used by
>>     the kernel for movable page allocations, and their contents are
>>     valuable for crash analysis.
>>
>> The no-dump status is also printed in the boot log alongside the
>> existing nomap and reusable flags for diagnostic purposes.
> I think this property is the wrong way around and probably not needed.
> The default should be exclude the regions, but if Linux is using the
> regions (like CMA) then it can decide on its own to include them.
>
> With the restructuring that went into 7.1, it should be possible for the
> CMA code (and code for any other regions) to set some flag for the
> region.
Agree that the property direction is wrong. Rework in v2:
 ? - Default: exclude reserved regions from vmcore
 ? - CMA (reusable) setup path marks regions as kernel-managed general 
memory ? include
 ? - No DT binding needed; linux,no-dump proposal withdrawn

Thanks.
>
> Rob



From alhe at linux.microsoft.com  Thu May  7 14:54:17 2026
From: alhe at linux.microsoft.com (Alejandro Hernandez)
Date: Thu,  7 May 2026 21:54:17 +0000
Subject: [PATCH] kexec: restore functionality to preserve fd xattrs for uncompressed kernel images
Message-ID: <20260507215417.2674568-1-alhe@linux.microsoft.com>

Since commit 714fa115 xattrs attached to a kernel image file (such as IMA
signatures) are not being preserved, do_kexec_file_load() was modified to use
memfd_create() which now passes an anonymous file descriptor to
kexec_file_load(). This change eliminated the filesystem inode identity of the
original kernel file, hence attributes are no longer visible to the kernel IMA
appraisal handler during kexec_file_load, causing IMA policy enforcement to fail
even for validly signed kernel images.

This patch attempts to restore such behavior although, only does it for
uncompressed kernel images. To do this, we first figure out if the image file is
compressed or not and call each method accordingly.

Compressed images continue to use memfd and cannot carry forward the original fd
security attributes, since their decompressed bytes do not match the signed
artifact. Proper handling of compressed images with IMA would require either
signing the decompressed artifact or kernel-side support for decompression
within the kexec_file_load path.

Signed-off-by: Alejandro Hernandez Samaniego <alhe at linux.microsoft.com>
---
 kexec/kexec.c | 42 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/kexec/kexec.c b/kexec/kexec.c
index 08edfca..a388762 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -1328,17 +1328,41 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
 
 	kernel = argv[fileind];
 
-	/* slurp in the input kernel */
+        /* Hold original fd with its xattrs */
+	kernel_fd = open(kernel, O_RDONLY);
+	if (kernel_fd == -1) {
+		fprintf(stderr, "Failed to open file %s:%s\n", kernel,
+				strerror(errno));
+		return EFAILED;
+	}
+
+	/* Compressed vs Uncompressed */
+	struct stat kernel_stat;
+	off_t kernel_file_size = -1;
+	if (fstat(kernel_fd, &kernel_stat) == 0)
+		kernel_file_size = kernel_stat.st_size;
+
+        /* slurp in the input kernel */
 	kernel_buf = slurp_decompress_file(kernel, &kernel_size);
 	if (!kernel_buf) {
 		fprintf(stderr, "Failed to decompress file %s:%s\n", kernel,
 				strerror(errno));
+		close(kernel_fd);
 		return EFAILED;
 	}
-	kernel_fd = copybuf_memfd(kernel_buf, kernel_size);
-	if (kernel_fd < 0) {
-		fprintf(stderr, "Failed to copy decompressed buf\n");
-		return EFAILED;
+
+	if (kernel_file_size != kernel_size) {
+		close(kernel_fd);
+		kernel_fd = copybuf_memfd(kernel_buf, kernel_size);
+		if (kernel_fd < 0) {
+			fprintf(stderr, "Failed to copy decompressed buf\n");
+			return EFAILED;
+		}
+		dbgprintf("%s: compressed input, using memfd kernel fd %d\n",
+			  __func__, kernel_fd);
+	} else {
+		dbgprintf("%s: using original kernel file fd %d\n",
+			  __func__, kernel_fd);
 	}
 
 	for (i = 0; i < file_types; i++) {
@@ -1361,11 +1385,15 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
 	}
 
        /*
-	* image type specific load functioin detect the capsule kernel type
+	* image type specific load function detect the capsule kernel type
 	* and create another fd for file load. For example the zboot kernel.
 	*/
-	if (info.kernel_fd != -1)
+	if (info.kernel_fd != -1) {
+		dbgprintf("%s: using image-specific kernel fd %d\n",
+			  __func__, info.kernel_fd);
+		close(kernel_fd);
 		kernel_fd = info.kernel_fd;
+	}
 
 	/*
 	 * If there is no initramfs, set KEXEC_FILE_NO_INITRAMFS flag so that
-- 
2.43.0



From dave.hansen at intel.com  Thu May  7 15:08:08 2026
From: dave.hansen at intel.com (Dave Hansen)
Date: Thu, 7 May 2026 15:08:08 -0700
Subject: [PATCH v2] x86/kexec: Push kjump return address even for
 non-kjump kexec
In-Reply-To: <32d627134143ffd957891cb697138e839c623211.camel@infradead.org>
References: <32d627134143ffd957891cb697138e839c623211.camel@infradead.org>
Message-ID: <01a9f690-11d4-41c6-a884-18958b4566cd@intel.com>

On 4/28/26 13:59, David Woodhouse wrote:
> +	/*
> +	 * Set return address to 0 if not preserving context. The purgatory
> +	 * shipped in kexec-tools will unconditionally look for the return
> +	 * address on the stack and set a kexec_jump_back_entry= command
> +	 * line option if it's non-zero. There's no other way that it can
> +	 * tell a preserve-context (kjump) kexec from a normal one.
> +	 */
> +	pushq	$0

Thanks for the update. LGTM:

Acked-by: Dave Hansen <dave.hansen at linux.intel.com>


From coiby.xu at gmail.com  Fri May  8 05:33:14 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Fri, 8 May 2026 20:33:14 +0800
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-3-coiby.xu@gmail.com>
 <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com>
Message-ID: <af3RQa8aU_F79XOw@Rk>

On Wed, May 06, 2026 at 05:58:31PM +0530, Sourabh Jain wrote:
>Hello Coiby,

Hi Sourabh,

Thanks for reviewing the patch!

>
>On 02/05/26 05:13, Coiby Xu wrote:
>>If kexec_add_buffer somehow fails, keys_header will be freed. Depending
>>on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
>>following two problems if the kexec_file_load syscall is called again,
>>   1. Double free of keys_header if reuse=false
>>   2. UAF of keys_header if reuse=true
>>
>>To address these problems and also make it easier to reason about the
>>code, keep two invariants,
>>   1. keys_header will always be freed at the end of kexec_file_load
>>      syscall except during kdump image unloading for CPU/memory
>>      hot-plugging support
>>   2. There will always be valid keys_header if reuse=true
>>
>>Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
>>Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for CPU/memory hot-plugging")
>>Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
>>Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
>>---
>>  include/linux/kexec.h        |  6 ++++
>>  kernel/crash_dump_dm_crypt.c | 65 ++++++++++++++++++++++++++----------
>>  kernel/kexec_file.c          |  2 ++
>>  3 files changed, 56 insertions(+), 17 deletions(-)
>>
>>diff --git a/include/linux/kexec.h b/include/linux/kexec.h
>>index 8a22bc9b8c6c..91256d7ff434 100644
>>--- a/include/linux/kexec.h
>>+++ b/include/linux/kexec.h
>>@@ -552,6 +552,12 @@ void set_kexec_sig_enforced(void);
>>  static inline void set_kexec_sig_enforced(void) {}
>>  #endif
>>+#ifdef CONFIG_CRASH_DM_CRYPT
>>+void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image);
>>+#else
>>+static inline void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image) {}
>>+#endif
>>+
>>  #endif /* !defined(__ASSEBMLY__) */
>>  #endif /* LINUX_KEXEC_H */
>>diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
>>index eac4f436a8d4..4d8a3331bbe7 100644
>>--- a/kernel/crash_dump_dm_crypt.c
>>+++ b/kernel/crash_dump_dm_crypt.c
>>@@ -84,18 +84,25 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
>>  	return r;
>>  }
>>-static void get_keys_from_kdump_reserved_memory(void)
>>+static int get_keys_from_kdump_reserved_memory(void)
>>  {
>>  	struct keys_header *keys_header_loaded;
>>+	size_t keys_header_size;
>>-	arch_kexec_unprotect_crashkres();
>>+	keys_header_size = get_keys_header_size(key_count);
>>+	keys_header = kzalloc(keys_header_size, GFP_KERNEL);
>>+	if (!keys_header)
>>+		return -ENOMEM;
>>+	arch_kexec_unprotect_crashkres();
>>  	keys_header_loaded = kmap_local_page(pfn_to_page(
>>  		kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));
>
>Accessing kexec_crash_image without holding the kexec lock could lead to
>undefined behavior.
>
>I think this section of code should be called by holding kexec lock.
>
>
>>-	memcpy(keys_header, keys_header_loaded, get_keys_header_size(key_count));
>>+	memcpy(keys_header, keys_header_loaded, keys_header_size);
>>  	kunmap_local(keys_header_loaded);
>>  	arch_kexec_protect_crashkres();
>>+
>>+	return 0;
>>  }
>>  static int restore_dm_crypt_keys_to_thread_keyring(void)
>>@@ -283,17 +290,28 @@ static ssize_t config_keys_reuse_show(struct config_item *item, char *page)
>>  static ssize_t config_keys_reuse_store(struct config_item *item,
>>  					   const char *page, size_t count)
>>  {
>>+	bool val;
>>+	int r;
>>+
>>  	if (!kexec_crash_image || !kexec_crash_image->dm_crypt_keys_addr) {
>
>The above check should be performed after acquiring the kexec lock.

Good suggestion! I'll try to hold the kexec lock in next version.

>
>
>
>>  		kexec_dprintk(
>>  			"dm-crypt keys haven't be saved to crash-reserved memory\n");
>>  		return -EINVAL;
>>  	}
>>-	if (kstrtobool(page, &is_dm_key_reused))
>>+	if (kstrtobool(page, &val) || !val)
>>  		return -EINVAL;
>
>Why can?t we allow the user to set is_dm_key_reused = false and free the
>key_header? That way, the next kdump kernel load will recreate the 
>
>For example, a user may add more keys and want the new keys to be included
>in the kdump image from next kdump kernel load.

Personally, I want to limit the reuse configfs API to the case of
CPU/memory hotplug thus to make the code simpler. And for the case of a
user adding more keys, I don't think there is a need for using the reuse
API.

>
>
>>-	if (is_dm_key_reused)
>>-		get_keys_from_kdump_reserved_memory();
>>+	if (is_dm_key_reused) {
>>+		pr_info("Already got dm-crypt keys, please continue with kexec_file_load syscall\n");
>>+	} else {
>>+		r = get_keys_from_kdump_reserved_memory();
>>+		if (r) {
>>+			pr_warn("Failed to get dm-crypt keys from reserved memory\n");
>>+			return r;
>>+		}
>>+		is_dm_key_reused = true;
>>+	}
>>  	return count;
>>  }
>>@@ -366,9 +384,6 @@ static int build_keys_header(void)
>>  	struct config_key *key;
>>  	int i, r;
>>-	if (keys_header != NULL)
>>-		kvfree(keys_header);
>>-
>>  	keys_header = kzalloc(get_keys_header_size(key_count), GFP_KERNEL);
>>  	if (!keys_header)
>>  		return -ENOMEM;
>>@@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>  		.top_down = false,
>>  		.random = true,
>>  	};
>>-	int r;
>>+	int r = 0;
>>  	if (key_count <= 0) {
>>@@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>  	}
>>  	if (!is_dm_key_reused) {
>>-		image->dm_crypt_keys_addr = 0;
>>  		r = build_keys_header();
>>-		if (r) {
>>-			pr_err("Failed to build dm-crypt keys header, ret=%d\n", r);
>>-			return r;
>>-		}
>>+		if (r)
>>+			goto out;
>>  	}
>>+	/*
>>+	 * keys_header will be copied to reserver memory later and then be
>>+	 * cleaned up at the end of kexec_file_load syscall
>>+	 */
>>  	kbuf.buffer = keys_header;
>>  	kbuf.bufsz = get_keys_header_size(key_count);
>>@@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>  	r = kexec_add_buffer(&kbuf);
>>  	if (r) {
>>  		pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
>>-		kvfree((void *)kbuf.buffer);
>>-		return r;
>>+		goto out;
>>  	}
>>+
>>  	image->dm_crypt_keys_addr = kbuf.mem;
>>  	image->dm_crypt_keys_sz = kbuf.bufsz;
>>  	kexec_dprintk(
>>  		"Loaded dm crypt keys to kexec_buffer bufsz=0x%lx memsz=0x%lx\n",
>>  		kbuf.bufsz, kbuf.memsz);
>>+out:
>>+	is_dm_key_reused = false;
>>  	return r;
>>  }
>>+void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>>+{
>>+	/*
>>+	 * For CPU/memory hot-plugging, the kdump image will be reloaded. Prevent
>>+	 * keys_header from being cleaned up during unloading when
>>+	 * is_dm_key_reused=true
>>+	 */
>>+	if (!is_dm_key_reused) {
>>+		kfree_sensitive(keys_header);
>>+		keys_header = NULL;
>
>Since crash_load_dm_crypt_keys() sets is_dm_key_reused = false, 
>keys_header will
>always be released here, right? Then why is the above free under an if 
>condition?

Thanks for raising the question! This is to prevent "kexec -u" from
cleaning up keys_headers because kexec_file_post_load_cleanup_dm_crypt
will also be called during "kexec -u". Without the if condition,
keys_headers will not be available during reloading.

>
>IIUC, for the case where CONFIG_CRASH_HOTPLUG is not enabled, this is 
>how key
>restore works:
>
>After loading the kdump kernel for the first time, the state of the 
>variables is:
>
>is_dm_key_reused = false
>keys_header = NULL
>
>For example, if 2 CPUs are hot-removed and kdump is reloaded twice:
>
>Then the sequence of operations needed to ensure the loaded keys can 
>be reused is:
>
>Udev rule triggered on the 1st CPU hotplug:
>echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>Restore the key header from the reserved area
>Reload the kdump service/kernel
>
>Udev rule triggered on the 2nd CPU hotplug:
>echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>Restore the key header from the reserved area
>Reload the kdump service/kernel
>
>What I don?t understand is the need to restore the key header from 
>crashkernel
>memory for every hotplug operation.

I think there is no easy way to tell if there is another hotplug and
considering hotplug is a rare event. So I guess it's OK to restore the
key headers for every hotplug operation.


-- 
Best regards,
Coiby


From coiby.xu at gmail.com  Fri May  8 06:08:39 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Fri, 8 May 2026 21:08:39 +0800
Subject: [PATCH v2 3/9] crash_dump: Disallow writing to dm-crypt configfs
 during kexec_file_load syscall
In-Reply-To: <b6a29516-ff17-4a36-b90c-812160fa8d19@linux.ibm.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-4-coiby.xu@gmail.com>
 <b6a29516-ff17-4a36-b90c-812160fa8d19@linux.ibm.com>
Message-ID: <af3dlOG-X9geaP8w@Rk>

On Wed, May 06, 2026 at 07:26:16PM +0530, Sourabh Jain wrote:
>
>
>On 02/05/26 05:13, Coiby Xu wrote:
>>If writing to the configfs group happens concurrently during
>>kexec_file_load syscall, it may lead to the following issues,
>>   - buffer overflow if dm-crypt keys are added after allocation
>>   - stale total_keys if dm-crypt keys are removed during iteration
>>   - keys_header will not be freed if config/crash_dm_crypt_key/reuse is
>>     set true
>>
>>So hold config_keys_subsys.su_mutex for the entire sequence during the
>>kexec_file_load syscall to ensure a consistent snapshot.
>
>
>Yes, this will solve many synchronization problems when a? user tries to
>perform any operation under our configfs_subsystem while the kernel is
>executing the kexec_file_load system call.
>
>Now, regarding the third point about freeing key_header: this will work
>only if configfs takes the su_mutex lock before invoking the store callback.
>I am not sure whether it actually does.

I can confirm configfs will automatically acquire the su_mutex lock when
creating configfs item. But I forgot to try if writing to an item will
also acquire the mutux lock. I'll do an experiment and share the result
later.

>
>However, based on my previous comment on (2/9), if we keep 
>config_keys_reuse_store()
>under the kexec lock, this will be taken care of. This is because the entire
>kexec_file_load system call already runs under the kexec lock.
>
>
>>
>>Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump reserved memory")
>>Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
>>---
>>  kernel/crash_dump_dm_crypt.c | 4 ++++
>>  1 file changed, 4 insertions(+)
>>
>>diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
>>index 4d8a3331bbe7..6377ee86ec50 100644
>>--- a/kernel/crash_dump_dm_crypt.c
>>+++ b/kernel/crash_dump_dm_crypt.c
>>@@ -429,6 +429,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>  	};
>>  	int r = 0;
>>+	mutex_lock(&config_keys_subsys.su_mutex);
>>  	if (key_count <= 0) {
>>  		kexec_dprintk("No dm-crypt keys\n");
>>@@ -479,6 +480,9 @@ void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>>  		kfree_sensitive(keys_header);
>>  		keys_header = NULL;
>>  	}
>>+
>>+	if (mutex_is_locked(&config_keys_subsys.su_mutex))
>>+		mutex_unlock(&config_keys_subsys.su_mutex);
>How about release the lock in crash_load_dm_crypt_keys() itself? Given 
>that config_keys_reuse_store() is placed under kexec lock?

Thanks for proposing another solution! I'll give a try and make a
comparison.

-- 
Best regards,
Coiby


From sourabhjain at linux.ibm.com  Fri May  8 13:06:59 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Sat, 9 May 2026 01:36:59 +0530
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <af3RQa8aU_F79XOw@Rk>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-3-coiby.xu@gmail.com>
 <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com> <af3RQa8aU_F79XOw@Rk>
Message-ID: <e460f764-997e-4a63-be7d-1ceb5a503d32@linux.ibm.com>



On 08/05/26 18:03, Coiby Xu wrote:
> On Wed, May 06, 2026 at 05:58:31PM +0530, Sourabh Jain wrote:
>> Hello Coiby,
>
> Hi Sourabh,
>
> Thanks for reviewing the patch!
>
>>
>> On 02/05/26 05:13, Coiby Xu wrote:
>>> If kexec_add_buffer somehow fails, keys_header will be freed. Depending
>>> on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
>>> following two problems if the kexec_file_load syscall is called again,
>>> ? 1. Double free of keys_header if reuse=false
>>> ? 2. UAF of keys_header if reuse=true
>>>
>>> To address these problems and also make it easier to reason about the
>>> code, keep two invariants,
>>> ? 1. keys_header will always be freed at the end of kexec_file_load
>>> ???? syscall except during kdump image unloading for CPU/memory
>>> ???? hot-plugging support
>>> ? 2. There will always be valid keys_header if reuse=true
>>>
>>> Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump 
>>> reserved memory")
>>> Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for 
>>> CPU/memory hot-plugging")
>>> Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
>>> Signed-off-by: Coiby Xu <coiby.xu at gmail.com>
>>> ---
>>> ?include/linux/kexec.h??????? |? 6 ++++
>>> ?kernel/crash_dump_dm_crypt.c | 65 ++++++++++++++++++++++++++----------
>>> ?kernel/kexec_file.c????????? |? 2 ++
>>> ?3 files changed, 56 insertions(+), 17 deletions(-)
>>>
>>> diff --git a/include/linux/kexec.h b/include/linux/kexec.h
>>> index 8a22bc9b8c6c..91256d7ff434 100644
>>> --- a/include/linux/kexec.h
>>> +++ b/include/linux/kexec.h
>>> @@ -552,6 +552,12 @@ void set_kexec_sig_enforced(void);
>>> ?static inline void set_kexec_sig_enforced(void) {}
>>> ?#endif
>>> +#ifdef CONFIG_CRASH_DM_CRYPT
>>> +void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image);
>>> +#else
>>> +static inline void kexec_file_post_load_cleanup_dm_crypt(struct 
>>> kimage *image) {}
>>> +#endif
>>> +
>>> ?#endif /* !defined(__ASSEBMLY__) */
>>> ?#endif /* LINUX_KEXEC_H */
>>> diff --git a/kernel/crash_dump_dm_crypt.c 
>>> b/kernel/crash_dump_dm_crypt.c
>>> index eac4f436a8d4..4d8a3331bbe7 100644
>>> --- a/kernel/crash_dump_dm_crypt.c
>>> +++ b/kernel/crash_dump_dm_crypt.c
>>> @@ -84,18 +84,25 @@ static int add_key_to_keyring(struct 
>>> dm_crypt_key *dm_key,
>>> ???? return r;
>>> ?}
>>> -static void get_keys_from_kdump_reserved_memory(void)
>>> +static int get_keys_from_kdump_reserved_memory(void)
>>> ?{
>>> ???? struct keys_header *keys_header_loaded;
>>> +??? size_t keys_header_size;
>>> -??? arch_kexec_unprotect_crashkres();
>>> +??? keys_header_size = get_keys_header_size(key_count);
>>> +??? keys_header = kzalloc(keys_header_size, GFP_KERNEL);
>>> +??? if (!keys_header)
>>> +??????? return -ENOMEM;
>>> +??? arch_kexec_unprotect_crashkres();
>>> ???? keys_header_loaded = kmap_local_page(pfn_to_page(
>>> ???????? kexec_crash_image->dm_crypt_keys_addr >> PAGE_SHIFT));
>>
>> Accessing kexec_crash_image without holding the kexec lock could lead to
>> undefined behavior.
>>
>> I think this section of code should be called by holding kexec lock.
>>
>>
>>> -??? memcpy(keys_header, keys_header_loaded, 
>>> get_keys_header_size(key_count));
>>> +??? memcpy(keys_header, keys_header_loaded, keys_header_size);
>>> ???? kunmap_local(keys_header_loaded);
>>> ???? arch_kexec_protect_crashkres();
>>> +
>>> +??? return 0;
>>> ?}
>>> ?static int restore_dm_crypt_keys_to_thread_keyring(void)
>>> @@ -283,17 +290,28 @@ static ssize_t config_keys_reuse_show(struct 
>>> config_item *item, char *page)
>>> ?static ssize_t config_keys_reuse_store(struct config_item *item,
>>> ??????????????????????? const char *page, size_t count)
>>> ?{
>>> +??? bool val;
>>> +??? int r;
>>> +
>>> ???? if (!kexec_crash_image || 
>>> !kexec_crash_image->dm_crypt_keys_addr) {
>>
>> The above check should be performed after acquiring the kexec lock.
>
> Good suggestion! I'll try to hold the kexec lock in next version.
>
>>
>>
>>
>>> ???????? kexec_dprintk(
>>> ???????????? "dm-crypt keys haven't be saved to crash-reserved 
>>> memory\n");
>>> ???????? return -EINVAL;
>>> ???? }
>>> -??? if (kstrtobool(page, &is_dm_key_reused))
>>> +??? if (kstrtobool(page, &val) || !val)
>>> ???????? return -EINVAL;
>>
>> Why can?t we allow the user to set is_dm_key_reused = false and free the
>> key_header? That way, the next kdump kernel load will recreate the
>> For example, a user may add more keys and want the new keys to be 
>> included
>> in the kdump image from next kdump kernel load.
>
> Personally, I want to limit the reuse configfs API to the case of
> CPU/memory hotplug thus to make the code simpler. And for the case of a
> user adding more keys, I don't think there is a need for using the reuse
> API.


Yes, there is no need for it, but I think the user is forced to use
key_reuse because once it is set to true, it cannot be set back to false.

For example, the kdump kernel is loaded using the kexec_load system call
and reuse is set to true. The user may later add a couple of new keys and
want to include them in the kdump image.

I think the next kdump kernel load will reuse the key_headers even though
the user does not want it. Hence I see a problem here.

Well user can load kdump kernel a second time, and at that point the
keys will get updated. But do we really want this behavior?


>
>>
>>
>>> -??? if (is_dm_key_reused)
>>> -??????? get_keys_from_kdump_reserved_memory();
>>> +??? if (is_dm_key_reused) {
>>> +??????? pr_info("Already got dm-crypt keys, please continue with 
>>> kexec_file_load syscall\n");
>>> +??? } else {
>>> +??????? r = get_keys_from_kdump_reserved_memory();
>>> +??????? if (r) {
>>> +??????????? pr_warn("Failed to get dm-crypt keys from reserved 
>>> memory\n");
>>> +??????????? return r;
>>> +??????? }
>>> +??????? is_dm_key_reused = true;
>>> +??? }
>>> ???? return count;
>>> ?}
>>> @@ -366,9 +384,6 @@ static int build_keys_header(void)
>>> ???? struct config_key *key;
>>> ???? int i, r;
>>> -??? if (keys_header != NULL)
>>> -??????? kvfree(keys_header);
>>> -
>>> ???? keys_header = kzalloc(get_keys_header_size(key_count), 
>>> GFP_KERNEL);
>>> ???? if (!keys_header)
>>> ???????? return -ENOMEM;
>>> @@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>> ???????? .top_down = false,
>>> ???????? .random = true,
>>> ???? };
>>> -??? int r;
>>> +??? int r = 0;
>>> ???? if (key_count <= 0) {
>>> @@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage 
>>> *image)
>>> ???? }
>>> ???? if (!is_dm_key_reused) {
>>> -??????? image->dm_crypt_keys_addr = 0;
>>> ???????? r = build_keys_header();
>>> -??????? if (r) {
>>> -??????????? pr_err("Failed to build dm-crypt keys header, 
>>> ret=%d\n", r);
>>> -??????????? return r;
>>> -??????? }
>>> +??????? if (r)
>>> +??????????? goto out;
>>> ???? }
>>> +??? /*
>>> +???? * keys_header will be copied to reserver memory later and then be
>>> +???? * cleaned up at the end of kexec_file_load syscall
>>> +???? */
>>> ???? kbuf.buffer = keys_header;
>>> ???? kbuf.bufsz = get_keys_header_size(key_count);
>>> @@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage 
>>> *image)
>>> ???? r = kexec_add_buffer(&kbuf);
>>> ???? if (r) {
>>> ???????? pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
>>> -??????? kvfree((void *)kbuf.buffer);
>>> -??????? return r;
>>> +??????? goto out;
>>> ???? }
>>> +
>>> ???? image->dm_crypt_keys_addr = kbuf.mem;
>>> ???? image->dm_crypt_keys_sz = kbuf.bufsz;
>>> ???? kexec_dprintk(
>>> ???????? "Loaded dm crypt keys to kexec_buffer bufsz=0x%lx 
>>> memsz=0x%lx\n",
>>> ???????? kbuf.bufsz, kbuf.memsz);
>>> +out:
>>> +??? is_dm_key_reused = false;
>>> ???? return r;
>>> ?}
>>> +void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>>> +{
>>> +??? /*
>>> +???? * For CPU/memory hot-plugging, the kdump image will be 
>>> reloaded. Prevent
>>> +???? * keys_header from being cleaned up during unloading when
>>> +???? * is_dm_key_reused=true
>>> +???? */
>>> +??? if (!is_dm_key_reused) {
>>> +??????? kfree_sensitive(keys_header);
>>> +??????? keys_header = NULL;
>>
>> Since crash_load_dm_crypt_keys() sets is_dm_key_reused = false, 
>> keys_header will
>> always be released here, right? Then why is the above free under an 
>> if condition?
>
> Thanks for raising the question! This is to prevent "kexec -u" from
> cleaning up keys_headers because kexec_file_post_load_cleanup_dm_crypt
> will also be called during "kexec -u". Without the if condition,
> keys_headers will not be available during reloading.

Agree. But do we really run kexec -u and then kexec -p to reload the kdump
kernel on hotplug events? I am under the impression that the udev rule
simply reloads the kdump kernel using kexec -p without explicitly running
kexec -u.

And if that is the case, I think we should clean key_headers on kexec -u.

- Sourabh Jain


>
>>
>> IIUC, for the case where CONFIG_CRASH_HOTPLUG is not enabled, this is 
>> how key
>> restore works:
>>
>> After loading the kdump kernel for the first time, the state of the 
>> variables is:
>>
>> is_dm_key_reused = false
>> keys_header = NULL
>>
>> For example, if 2 CPUs are hot-removed and kdump is reloaded twice:
>>
>> Then the sequence of operations needed to ensure the loaded keys can 
>> be reused is:
>>
>> Udev rule triggered on the 1st CPU hotplug:
>> echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>> Restore the key header from the reserved area
>> Reload the kdump service/kernel
>>
>> Udev rule triggered on the 2nd CPU hotplug:
>> echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>> Restore the key header from the reserved area
>> Reload the kdump service/kernel
>>
>> What I don?t understand is the need to restore the key header from 
>> crashkernel
>> memory for every hotplug operation.
>
> I think there is no easy way to tell if there is another hotplug and
> considering hotplug is a rare event. So I guess it's OK to restore the
> key headers for every hotplug operation. 




From dongtai.guo at linux.dev  Fri May  8 19:44:15 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Sat,  9 May 2026 10:44:15 +0800
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
Message-ID: <20260509024415.33190-1-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

KHO_TREE_MAX_DEPTH is calculated as:

  DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
               KHO_TABLE_SIZE_LOG2) + 1

For systems with 16KB pages (e.g. LoongArch), this gives a depth of 4,
with the top-level shift at bit 39.  The order-0 bit sits at bit 50
(KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT = 50).  When inserting or reading
a key, the index extracted at the top level is:

  (1 << 50) >> 39 = 2048

2048 is exactly the table size (PAGE_SIZE / sizeof(phys_addr_t) = 2048
for 16KB pages), so it wraps to 0, aliasing the order bit to index 0
and losing it silently.

On the second kernel, kho_radix_decode_key() sees a key without the
order bit, calls fls64() on the wrong bit, computes a wrong order and
thus a garbage physical address. phys_to_page() of that address faults
in kho_preserved_memory_reserve(), causing a kernel panic early in boot.

Fix by adding +1 to the DIV_ROUND_UP numerator so the formula accounts
for the order bit itself, giving depth 5 for 16KB pages. The top-level
shift becomes 50, and (1 << 50) >> 50 = 1, which is nonzero and
unambiguous. For 4KB and 64KB page sizes the depth is unchanged.

Fixes: 3f2ad90060f6 ("kho: adopt radix tree for preserved memory tracking")

Tested-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 include/linux/kho/abi/kexec_handover.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 7e847a2339b0..db9bda6dd310 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -274,7 +274,7 @@ enum kho_radix_consts {
 	 * and 1 bitmap level.
 	 */
 	KHO_TREE_MAX_DEPTH =
-		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
+		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
 			     KHO_TABLE_SIZE_LOG2) + 1,
 };
 
-- 
2.25.1



From coiby.xu at gmail.com  Sat May  9 17:14:55 2026
From: coiby.xu at gmail.com (Coiby Xu)
Date: Sun, 10 May 2026 08:14:55 +0800
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <e460f764-997e-4a63-be7d-1ceb5a503d32@linux.ibm.com>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-3-coiby.xu@gmail.com>
 <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com>
 <af3RQa8aU_F79XOw@Rk>
 <e460f764-997e-4a63-be7d-1ceb5a503d32@linux.ibm.com>
Message-ID: <af_JKzEAwp9r-ZSS@Rk>

On Sat, May 09, 2026 at 01:36:59AM +0530, Sourabh Jain wrote:
>
>
>On 08/05/26 18:03, Coiby Xu wrote:
>>On Wed, May 06, 2026 at 05:58:31PM +0530, Sourabh Jain wrote:
>>>Hello Coiby,
>>
>>Hi Sourabh,
>>
>>Thanks for reviewing the patch!
>>
>>>
>>>On 02/05/26 05:13, Coiby Xu wrote:
>>>>If kexec_add_buffer somehow fails, keys_header will be freed. Depending
>>>>on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
>>>>following two problems if the kexec_file_load syscall is called again,
>>>>? 1. Double free of keys_header if reuse=false
>>>>? 2. UAF of keys_header if reuse=true
>>>>
>>>>To address these problems and also make it easier to reason about the
>>>>code, keep two invariants,
>>>>? 1. keys_header will always be freed at the end of kexec_file_load
>>>>???? syscall except during kdump image unloading for CPU/memory
>>>>???? hot-plugging support
>>>>? 2. There will always be valid keys_header if reuse=true
>>>>
>>>>Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump 
>>>>reserved memory")
>>>>Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for 
>>>>CPU/memory hot-plugging")
>>>>Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
[...]
>>
>>>
>>>
>>>
>>>>???????? kexec_dprintk(
>>>>???????????? "dm-crypt keys haven't be saved to crash-reserved 
>>>>memory\n");
>>>>???????? return -EINVAL;
>>>>???? }
>>>>-??? if (kstrtobool(page, &is_dm_key_reused))
>>>>+??? if (kstrtobool(page, &val) || !val)
>>>>???????? return -EINVAL;
>>>
>>>Why can?t we allow the user to set is_dm_key_reused = false and free the
>>>key_header? That way, the next kdump kernel load will recreate the
>>>For example, a user may add more keys and want the new keys to be 
>>>included
>>>in the kdump image from next kdump kernel load.
>>
>>Personally, I want to limit the reuse configfs API to the case of
>>CPU/memory hotplug thus to make the code simpler. And for the case of a
>>user adding more keys, I don't think there is a need for using the reuse
>>API.
>
>
>Yes, there is no need for it, but I think the user is forced to use
>key_reuse because once it is set to true, it cannot be set back to false.
>
>For example, the kdump kernel is loaded using the kexec_load system call
>and reuse is set to true. The user may later add a couple of new keys and
>want to include them in the kdump image.
>
>I think the next kdump kernel load will reuse the key_headers even though
>the user does not want it. Hence I see a problem here.
>
>Well user can load kdump kernel a second time, and at that point the
>keys will get updated. But do we really want this behavior?

key_reuse won't be set to true automatically unless an user explicitly
does so. Thus I don't think it's forcing users to use it. And by design
key_reuse is set only for the hotplug case.

For the cause of loading kdump kernel again, I don't see the need to set
key_reuse.

I also had the urge to allow writing false to the reuse API and even
implemented it. But later I deleted the code to avoid over-engineering
because I'm not sure how it is supposed to be used. Besides this reuse
API already makes it much harder to reason about the code. Shall all
architectures implement CONFIG_CRASH_HOTPLUG, I'll delete it immediately
so crash_dump_dm_crypt.c can be much easier to maintain.

>
>
>>
>>>
>>>
>>>>-??? if (is_dm_key_reused)
>>>>-??????? get_keys_from_kdump_reserved_memory();
>>>>+??? if (is_dm_key_reused) {
>>>>+??????? pr_info("Already got dm-crypt keys, please continue 
>>>>with kexec_file_load syscall\n");
>>>>+??? } else {
>>>>+??????? r = get_keys_from_kdump_reserved_memory();
>>>>+??????? if (r) {
>>>>+??????????? pr_warn("Failed to get dm-crypt keys from reserved 
>>>>memory\n");
>>>>+??????????? return r;
>>>>+??????? }
>>>>+??????? is_dm_key_reused = true;
>>>>+??? }
>>>>???? return count;
>>>>?}
>>>>@@ -366,9 +384,6 @@ static int build_keys_header(void)
>>>>???? struct config_key *key;
>>>>???? int i, r;
>>>>-??? if (keys_header != NULL)
>>>>-??????? kvfree(keys_header);
>>>>-
>>>>???? keys_header = kzalloc(get_keys_header_size(key_count), 
>>>>GFP_KERNEL);
>>>>???? if (!keys_header)
>>>>???????? return -ENOMEM;
>>>>@@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage *image)
>>>>???????? .top_down = false,
>>>>???????? .random = true,
>>>>???? };
>>>>-??? int r;
>>>>+??? int r = 0;
>>>>???? if (key_count <= 0) {
>>>>@@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage 
>>>>*image)
>>>>???? }
>>>>???? if (!is_dm_key_reused) {
>>>>-??????? image->dm_crypt_keys_addr = 0;
>>>>???????? r = build_keys_header();
>>>>-??????? if (r) {
>>>>-??????????? pr_err("Failed to build dm-crypt keys header, 
>>>>ret=%d\n", r);
>>>>-??????????? return r;
>>>>-??????? }
>>>>+??????? if (r)
>>>>+??????????? goto out;
>>>>???? }
>>>>+??? /*
>>>>+???? * keys_header will be copied to reserver memory later and then be
>>>>+???? * cleaned up at the end of kexec_file_load syscall
>>>>+???? */
>>>>???? kbuf.buffer = keys_header;
>>>>???? kbuf.bufsz = get_keys_header_size(key_count);
>>>>@@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage 
>>>>*image)
>>>>???? r = kexec_add_buffer(&kbuf);
>>>>???? if (r) {
>>>>???????? pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
>>>>-??????? kvfree((void *)kbuf.buffer);
>>>>-??????? return r;
>>>>+??????? goto out;
>>>>???? }
>>>>+
>>>>???? image->dm_crypt_keys_addr = kbuf.mem;
>>>>???? image->dm_crypt_keys_sz = kbuf.bufsz;
>>>>???? kexec_dprintk(
>>>>???????? "Loaded dm crypt keys to kexec_buffer bufsz=0x%lx 
>>>>memsz=0x%lx\n",
>>>>???????? kbuf.bufsz, kbuf.memsz);
>>>>+out:
>>>>+??? is_dm_key_reused = false;
>>>>???? return r;
>>>>?}
>>>>+void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>>>>+{
>>>>+??? /*
>>>>+???? * For CPU/memory hot-plugging, the kdump image will be 
>>>>reloaded. Prevent
>>>>+???? * keys_header from being cleaned up during unloading when
>>>>+???? * is_dm_key_reused=true
>>>>+???? */
>>>>+??? if (!is_dm_key_reused) {
>>>>+??????? kfree_sensitive(keys_header);
>>>>+??????? keys_header = NULL;
>>>
>>>Since crash_load_dm_crypt_keys() sets is_dm_key_reused = false, 
>>>keys_header will
>>>always be released here, right? Then why is the above free under 
>>>an if condition?
>>
>>Thanks for raising the question! This is to prevent "kexec -u" from
>>cleaning up keys_headers because kexec_file_post_load_cleanup_dm_crypt
>>will also be called during "kexec -u". Without the if condition,
>>keys_headers will not be available during reloading.
>
>Agree. But do we really run kexec -u and then kexec -p to reload the kdump
>kernel on hotplug events? I am under the impression that the udev rule
>simply reloads the kdump kernel using kexec -p without explicitly running
>kexec -u.

Yes, "kdumpctl reload" will be called during hotplug events 
https://github.com/rhkdump/kdump-utils/blob/main/kdump-udev-throttler#L51
So there is explicitly unloading first and then reloading.

>
>And if that is the case, I think we should clean key_headers on kexec -u.

Most of the cases is_dm_key_reused is false thus keys_header will be
cleaned on kexec -u.

>
>- Sourabh Jain
>
>
>>
>>>
>>>IIUC, for the case where CONFIG_CRASH_HOTPLUG is not enabled, this 
>>>is how key
>>>restore works:
>>>
>>>After loading the kdump kernel for the first time, the state of 
>>>the variables is:
>>>
>>>is_dm_key_reused = false
>>>keys_header = NULL
>>>
>>>For example, if 2 CPUs are hot-removed and kdump is reloaded twice:
>>>
>>>Then the sequence of operations needed to ensure the loaded keys 
>>>can be reused is:
>>>
>>>Udev rule triggered on the 1st CPU hotplug:
>>>echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>>>Restore the key header from the reserved area
>>>Reload the kdump service/kernel
>>>
>>>Udev rule triggered on the 2nd CPU hotplug:
>>>echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>>>Restore the key header from the reserved area
>>>Reload the kdump service/kernel
>>>
>>>What I don?t understand is the need to restore the key header from 
>>>crashkernel
>>>memory for every hotplug operation.
>>
>>I think there is no easy way to tell if there is another hotplug and
>>considering hotplug is a rare event. So I guess it's OK to restore the
>>key headers for every hotplug operation.
>
>

-- 
Best regards,
Coiby


From rppt at kernel.org  Sun May 10 08:26:26 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 10 May 2026 18:26:26 +0300
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <20260509024415.33190-1-dongtai.guo@linux.dev>
References: <20260509024415.33190-1-dongtai.guo@linux.dev>
Message-ID: <177842678313.259088.8928737921907121858.b4-ty@b4>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

On Sat, 09 May 2026 10:44:15 +0800, George Guo wrote:
> KHO_TREE_MAX_DEPTH is calculated as:
> 
>   DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
>                KHO_TABLE_SIZE_LOG2) + 1
> 
> For systems with 16KB pages (e.g. LoongArch), this gives a depth of 4,
> with the top-level shift at bit 39.  The order-0 bit sits at bit 50
> (KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT = 50).  When inserting or reading
> a key, the index extracted at the top level is:
> 
> [...]

Applied to fixes branch of liveupdate/linux.git tree, thanks!

[1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
      commit: eaf3933ec58da601cf0008afbcca7cf5433321e8

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: fixes

--
Sincerely yours,
Mike.



From lkp at intel.com  Sun May 10 21:58:59 2026
From: lkp at intel.com (kernel test robot)
Date: Mon, 11 May 2026 12:58:59 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 eaf3933ec58da601cf0008afbcca7cf5433321e8
Message-ID: <202605111249.6D0dS0yW-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: eaf3933ec58da601cf0008afbcca7cf5433321e8  kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes

elapsed time: 771m

configs tested: 275
configs skipped: 47

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260511    gcc-10.5.0
arc                   randconfig-002-20260511    gcc-10.5.0
arc                        vdk_hs38_defconfig    gcc-15.2.0
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                              allyesconfig    gcc-15.2.0
arm                        clps711x_defconfig    clang-23
arm                                 defconfig    gcc-15.2.0
arm                            hisi_defconfig    gcc-15.2.0
arm                   randconfig-001-20260511    gcc-10.5.0
arm                   randconfig-002-20260511    gcc-10.5.0
arm                   randconfig-003-20260511    gcc-10.5.0
arm                   randconfig-004-20260511    gcc-10.5.0
arm                         wpcm450_defconfig    gcc-15.2.0
arm64                            allmodconfig    clang-19
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260511    clang-17
arm64                 randconfig-001-20260511    gcc-15.2.0
arm64                 randconfig-002-20260511    clang-17
arm64                 randconfig-002-20260511    gcc-15.2.0
arm64                 randconfig-003-20260511    clang-17
arm64                 randconfig-004-20260511    clang-17
arm64                 randconfig-004-20260511    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260511    clang-17
csky                  randconfig-001-20260511    gcc-15.2.0
csky                  randconfig-002-20260511    clang-17
csky                  randconfig-002-20260511    gcc-15.2.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260511    gcc-8.5.0
hexagon               randconfig-002-20260511    gcc-8.5.0
i386                             allmodconfig    clang-20
i386                             allmodconfig    gcc-14
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                             allyesconfig    gcc-14
i386                 buildonly-randconfig-001    gcc-14
i386        buildonly-randconfig-001-20260511    gcc-14
i386                 buildonly-randconfig-002    gcc-14
i386        buildonly-randconfig-002-20260511    gcc-14
i386                 buildonly-randconfig-003    gcc-14
i386        buildonly-randconfig-003-20260511    gcc-14
i386                 buildonly-randconfig-004    gcc-14
i386        buildonly-randconfig-004-20260511    gcc-14
i386                 buildonly-randconfig-005    gcc-14
i386        buildonly-randconfig-005-20260511    gcc-14
i386                 buildonly-randconfig-006    gcc-14
i386        buildonly-randconfig-006-20260511    gcc-14
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260511    gcc-12
i386                  randconfig-002-20260511    gcc-12
i386                  randconfig-003-20260511    gcc-12
i386                  randconfig-004-20260511    gcc-12
i386                  randconfig-005-20260511    gcc-12
i386                  randconfig-006-20260511    gcc-12
i386                  randconfig-007-20260511    gcc-12
i386                           randconfig-011    clang-20
i386                  randconfig-011-20260511    clang-20
i386                  randconfig-011-20260511    gcc-14
i386                           randconfig-012    clang-20
i386                  randconfig-012-20260511    clang-20
i386                  randconfig-012-20260511    gcc-14
i386                           randconfig-013    clang-20
i386                  randconfig-013-20260511    clang-20
i386                           randconfig-014    clang-20
i386                  randconfig-014-20260511    clang-20
i386                           randconfig-015    clang-20
i386                  randconfig-015-20260511    clang-20
i386                  randconfig-015-20260511    gcc-13
i386                  randconfig-015-20260511    gcc-14
i386                           randconfig-016    clang-20
i386                  randconfig-016-20260511    clang-20
i386                           randconfig-017    clang-20
i386                  randconfig-017-20260511    clang-20
i386                  randconfig-017-20260511    gcc-14
loongarch                        allmodconfig    clang-19
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260511    gcc-8.5.0
loongarch             randconfig-002-20260511    gcc-8.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    clang-19
m68k                                defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
microblaze                          defconfig    gcc-15.2.0
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                malta_qemu_32r6_defconfig    gcc-15.2.0
mips                    maltaup_xpa_defconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                             allnoconfig    gcc-15.2.0
nios2                               defconfig    clang-19
nios2                               defconfig    gcc-11.5.0
nios2                 randconfig-001-20260511    gcc-8.5.0
nios2                 randconfig-002-20260511    gcc-8.5.0
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-11.5.0
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260511    clang-20
parisc                randconfig-001-20260511    gcc-8.5.0
parisc                randconfig-002-20260511    clang-20
parisc                randconfig-002-20260511    gcc-8.5.0
parisc64                            defconfig    clang-19
parisc64                            defconfig    gcc-15.2.0
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc               randconfig-001-20260511    clang-20
powerpc               randconfig-001-20260511    gcc-8.5.0
powerpc               randconfig-002-20260511    clang-20
powerpc64             randconfig-001-20260511    clang-20
powerpc64             randconfig-001-20260511    gcc-8.5.0
powerpc64             randconfig-002-20260511    clang-20
powerpc64             randconfig-002-20260511    gcc-14.3.0
powerpc64             randconfig-002-20260511    gcc-8.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    clang-23
riscv                               defconfig    gcc-15.2.0
riscv                          randconfig-001    gcc-12.5.0
riscv                 randconfig-001-20260511    gcc-10.5.0
riscv                 randconfig-001-20260511    gcc-12.5.0
riscv                          randconfig-002    gcc-12.5.0
riscv                 randconfig-002-20260511    gcc-12.5.0
riscv                 randconfig-002-20260511    gcc-8.5.0
s390                             allmodconfig    clang-18
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    clang-23
s390                                defconfig    gcc-15.2.0
s390                           randconfig-001    gcc-12.5.0
s390                  randconfig-001-20260511    gcc-12.5.0
s390                  randconfig-001-20260511    gcc-8.5.0
s390                           randconfig-002    gcc-12.5.0
s390                  randconfig-002-20260511    gcc-12.5.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sh                             randconfig-001    gcc-12.5.0
sh                    randconfig-001-20260511    gcc-12.5.0
sh                    randconfig-001-20260511    gcc-14.3.0
sh                             randconfig-002    gcc-12.5.0
sh                    randconfig-002-20260511    gcc-12.5.0
sh                           se7722_defconfig    gcc-15.2.0
sh                           se7751_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260511    gcc-8.5.0
sparc                 randconfig-002-20260511    gcc-8.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260511    gcc-8.5.0
sparc64               randconfig-002-20260511    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260511    gcc-8.5.0
um                    randconfig-002-20260511    gcc-8.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260511    gcc-14
x86_64      buildonly-randconfig-002-20260511    gcc-14
x86_64      buildonly-randconfig-003-20260511    clang-20
x86_64      buildonly-randconfig-003-20260511    gcc-14
x86_64      buildonly-randconfig-004-20260511    clang-20
x86_64      buildonly-randconfig-004-20260511    gcc-14
x86_64      buildonly-randconfig-005-20260511    gcc-14
x86_64      buildonly-randconfig-006-20260511    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260511    clang-20
x86_64                randconfig-001-20260511    gcc-14
x86_64                randconfig-002-20260511    clang-20
x86_64                randconfig-002-20260511    gcc-14
x86_64                randconfig-003-20260511    clang-20
x86_64                randconfig-004-20260511    clang-20
x86_64                randconfig-005-20260511    clang-20
x86_64                randconfig-006-20260511    clang-20
x86_64                         randconfig-011    clang-20
x86_64                randconfig-011-20260511    clang-20
x86_64                randconfig-011-20260511    gcc-14
x86_64                         randconfig-012    clang-20
x86_64                randconfig-012-20260511    clang-20
x86_64                randconfig-012-20260511    gcc-14
x86_64                         randconfig-013    clang-20
x86_64                randconfig-013-20260511    clang-20
x86_64                         randconfig-014    clang-20
x86_64                randconfig-014-20260511    clang-20
x86_64                randconfig-014-20260511    gcc-14
x86_64                         randconfig-015    clang-20
x86_64                randconfig-015-20260511    clang-20
x86_64                randconfig-015-20260511    gcc-14
x86_64                         randconfig-016    clang-20
x86_64                randconfig-016-20260511    clang-20
x86_64                randconfig-016-20260511    gcc-14
x86_64                         randconfig-071    clang-20
x86_64                randconfig-071-20260511    clang-20
x86_64                         randconfig-072    clang-20
x86_64                randconfig-072-20260511    clang-20
x86_64                randconfig-072-20260511    gcc-14
x86_64                         randconfig-073    clang-20
x86_64                randconfig-073-20260511    clang-20
x86_64                randconfig-073-20260511    gcc-14
x86_64                         randconfig-074    clang-20
x86_64                randconfig-074-20260511    clang-20
x86_64                randconfig-074-20260511    gcc-14
x86_64                         randconfig-075    clang-20
x86_64                randconfig-075-20260511    clang-20
x86_64                         randconfig-076    clang-20
x86_64                randconfig-076-20260511    clang-20
x86_64                randconfig-076-20260511    gcc-14
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                           allyesconfig    gcc-11.5.0
xtensa                randconfig-001-20260511    gcc-8.5.0
xtensa                randconfig-002-20260511    gcc-8.5.0

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


From rppt at kernel.org  Mon May 11 03:38:08 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 13:38:08 +0300
Subject: [PATCH v2 1/3] kho: skip KHO data for context-preserving kexec
In-Reply-To: <20260506182039.2623553-6-pasha.tatashin@soleen.com>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
 <20260506182039.2623553-6-pasha.tatashin@soleen.com>
Message-ID: <agGxkHPx7W7P5AUS@kernel.org>

On Wed, May 06, 2026 at 02:20:41PM -0400, Pasha Tatashin wrote:
> A preserve_context kexec returns to the current kernel, which means the
> 2nd kernel does not use KHO data (and their memory spaces don't
> overlap). Passing KHO data to the 2nd kernel via setup_data or
> devicetree is unnecessary.
> 
> Add a check in kho_fill_kimage() and return early if preserve_context is
> set, to avoid passing KHO state during context-preserving kexecs.
> 
> Fixes: 3bdecc3c93f9 ("kexec: add KHO support to kexec file loads")
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/kexec_handover.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 4fde8325c49f..2eec2a169c83 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -1702,7 +1702,8 @@ int kho_fill_kimage(struct kimage *image)
>  	int err = 0;
>  	struct kexec_buf scratch;
>  
> -	if (!kho_enable || image->type == KEXEC_TYPE_CRASH)
> +	if (!kho_enable || image->type == KEXEC_TYPE_CRASH ||
> +	    image->preserve_context)
>  		return 0;

Sashiko says it's unnecessary:

https://sashiko.dev/#/patchset/20260506182039.2623553-5-pasha.tatashin at soleen.com

I tend to agree :)
  
>  	image->kho.fdt = virt_to_phys(kho_out.fdt);
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

-- 
Sincerely yours,
Mike.


From pratyush at kernel.org  Mon May 11 03:40:01 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 12:40:01 +0200
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <20260509024415.33190-1-dongtai.guo@linux.dev> (George Guo's
	message of "Sat, 9 May 2026 10:44:15 +0800")
References: <20260509024415.33190-1-dongtai.guo@linux.dev>
Message-ID: <2vxzcxz2dybi.fsf@kernel.org>

On Sat, May 09 2026, George Guo wrote:

> From: George Guo <guodongtai at kylinos.cn>
>
> KHO_TREE_MAX_DEPTH is calculated as:
>
>   DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
>                KHO_TABLE_SIZE_LOG2) + 1
>
> For systems with 16KB pages (e.g. LoongArch), this gives a depth of 4,

As of now, we only support KHO on x86 and arm64. Support for other
architectures is not there. Are you working on supporting it for
LoongArch? What is your use case?

Without LoongArch supporting KHO, this remains a purely theoretical fix.

> with the top-level shift at bit 39.  The order-0 bit sits at bit 50
> (KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT = 50).  When inserting or reading
> a key, the index extracted at the top level is:
>
>   (1 << 50) >> 39 = 2048
>
> 2048 is exactly the table size (PAGE_SIZE / sizeof(phys_addr_t) = 2048
> for 16KB pages), so it wraps to 0, aliasing the order bit to index 0
> and losing it silently.
>
> On the second kernel, kho_radix_decode_key() sees a key without the
> order bit, calls fls64() on the wrong bit, computes a wrong order and
> thus a garbage physical address. phys_to_page() of that address faults
> in kho_preserved_memory_reserve(), causing a kernel panic early in boot.
>
> Fix by adding +1 to the DIV_ROUND_UP numerator so the formula accounts
> for the order bit itself, giving depth 5 for 16KB pages. The top-level
> shift becomes 50, and (1 << 50) >> 50 = 1, which is nonzero and
> unambiguous. For 4KB and 64KB page sizes the depth is unchanged.

Maybe I don't understand the math so well, but I can't see the problem.
Here's what I did in my calculator (the lines starting with the = are
the result of the previous statement):

First, define all the constants:

    PAGE_SHIFT = 14
    = 14

    KHO_ORDER_0_LOG2 = 64 ? PAGE_SHIFT
    = 50

    KHO_TABLE_SIZE_LOG2  = log(2; (1 << PAGE_SHIFT) / 8)
    = 11

    KHO_BITMAP_SIZE_LOG2 = PAGE_SHIFT + 3
    = 17

    KHO_TREE_MAX_DEPTH = ((KHO_ORDER_0_LOG2 ? KHO_BITMAP_SIZE_LOG2) / KHO_TABLE_SIZE_LOG2) + 1
    = 4

Then let's assume the highest possible physical address (52-bit
addressing) and order 0:

    phys = 0xffffffffff000
    = 4503599627366400

    order = 0
    = 0

This makes the key:

    key = (1 << (KHO_ORDER_0_LOG2 ? order)) | (phys >> (PAGE_SHIFT + order))
    = 1126174784749567

For higher orders the numerical value of the key will be smaller so this
is the highest possible key.

Then do what kho_radix_get_table_index() does to get the index on level
4:

    level = 4
    = 4

    s = ((level ? 1) ? KHO_TABLE_SIZE_LOG2) + KHO_BITMAP_SIZE_LOG2
    = 50

    idx = mod((key >> s); (1 << KHO_TABLE_SIZE_LOG2))
    = 1

If we do get a 5th level, the index would be:

    level = 5
    = 5

    s = ((level ? 1) ? KHO_TABLE_SIZE_LOG2) + KHO_BITMAP_SIZE_LOG2
    = 61

    idx = mod((key >> s); (1 << KHO_TABLE_SIZE_LOG2))
    = 0

So based on this, looks to me that the 5th level table won't ever go
above index 0 and the 4th level is enough to represent all possible
keys.

What am I missing?

Also, Jason, can you please help review this? You understand the math of
the radix tree the best I reckon.

>
> Fixes: 3f2ad90060f6 ("kho: adopt radix tree for preserved memory tracking")
>
> Tested-by: Kexin Liu <liukexin at kylinos.cn>
> Signed-off-by: George Guo <guodongtai at kylinos.cn>
> ---
>  include/linux/kho/abi/kexec_handover.h | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
> index 7e847a2339b0..db9bda6dd310 100644
> --- a/include/linux/kho/abi/kexec_handover.h
> +++ b/include/linux/kho/abi/kexec_handover.h
> @@ -274,7 +274,7 @@ enum kho_radix_consts {
>  	 * and 1 bitmap level.
>  	 */
>  	KHO_TREE_MAX_DEPTH =
> -		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
> +		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
>  			     KHO_TABLE_SIZE_LOG2) + 1,
>  };

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 04:00:20 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 13:00:20 +0200
Subject: [PATCH v2 1/3] kho: skip KHO data for context-preserving kexec
In-Reply-To: <20260506182039.2623553-6-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 6 May 2026 14:20:41 -0400")
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
	<20260506182039.2623553-6-pasha.tatashin@soleen.com>
Message-ID: <2vxzzf26cit7.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> A preserve_context kexec returns to the current kernel, which means the
> 2nd kernel does not use KHO data (and their memory spaces don't
> overlap). Passing KHO data to the 2nd kernel via setup_data or
> devicetree is unnecessary.
>
> Add a check in kho_fill_kimage() and return early if preserve_context is
> set, to avoid passing KHO state during context-preserving kexecs.
>
> Fixes: 3bdecc3c93f9 ("kexec: add KHO support to kexec file loads")
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 04:03:34 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 13:03:34 +0200
Subject: [PATCH v2 3/3] liveupdate: block outgoing session mutations
In-Reply-To: <20260506182039.2623553-8-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 6 May 2026 14:20:43 -0400")
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
	<20260506182039.2623553-8-pasha.tatashin@soleen.com>
Message-ID: <2vxzv7cucint.fsf@kernel.org>

On Wed, May 06 2026, Pasha Tatashin wrote:

> During the reboot() syscall, user processes may still be running
> concurrently and attempting to mutate sessions (e.g., adding files or
> closing sessions). To prevent this, hold the session header's rwsem and
> each session's mutex indefinitely on successful serialization.
>
> If serialization succeeds, these locks will never be released as the
> system transitions to the new kernel, effectively blocking any
> concurrent task attempting to modify or release a session. If
> liveupdate_reboot() fails, the locks are released in the error path,
> allowing normal operation to resume.
>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 04:05:21 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 13:05:21 +0200
Subject: [PATCH 08/12] kho: add kho_radix_init_tree()
In-Reply-To: <e5e9c15f-a030-423e-de79-757c20d084e1@linux.microsoft.com> (Jork
	Loeser's message of "Wed, 6 May 2026 03:51:07 -0700 (PDT)")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-9-pratyush@kernel.org>
	<e5e9c15f-a030-423e-de79-757c20d084e1@linux.microsoft.com>
Message-ID: <2vxzqzniciku.fsf@kernel.org>

On Wed, May 06 2026, Jork Loeser wrote:

> On Wed, 29 Apr 2026, Pratyush Yadav wrote:
>
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>
>> Move the initialization logic of the radix tree into
>> kho_radix_init_tree() instead of having users open-code it. Makes the
>> boundaries cleaner and reduces code duplication when a new user of the
>> radix tree will be added in a future commit.
>
> Consider using kho_radix_init_tree() in kho_init() as well. This will also
> remove the kzalloc() one-off use. Similar for kho_radix_destroy_tree().

Good point. Will do.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 04:16:08 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 13:16:08 +0200
Subject: [PATCH v2 1/3] kho: skip KHO data for context-preserving kexec
In-Reply-To: <agGxkHPx7W7P5AUS@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 13:38:08 +0300")
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
	<20260506182039.2623553-6-pasha.tatashin@soleen.com>
	<agGxkHPx7W7P5AUS@kernel.org>
Message-ID: <2vxzik8uci2v.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, May 06, 2026 at 02:20:41PM -0400, Pasha Tatashin wrote:
>> A preserve_context kexec returns to the current kernel, which means the
>> 2nd kernel does not use KHO data (and their memory spaces don't
>> overlap). Passing KHO data to the 2nd kernel via setup_data or
>> devicetree is unnecessary.
>> 
>> Add a check in kho_fill_kimage() and return early if preserve_context is
>> set, to avoid passing KHO state during context-preserving kexecs.
>> 
>> Fixes: 3bdecc3c93f9 ("kexec: add KHO support to kexec file loads")
>> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 3 ++-
>>  1 file changed, 2 insertions(+), 1 deletion(-)
>> 
>> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
>> index 4fde8325c49f..2eec2a169c83 100644
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -1702,7 +1702,8 @@ int kho_fill_kimage(struct kimage *image)
>>  	int err = 0;
>>  	struct kexec_buf scratch;
>>  
>> -	if (!kho_enable || image->type == KEXEC_TYPE_CRASH)
>> +	if (!kho_enable || image->type == KEXEC_TYPE_CRASH ||
>> +	    image->preserve_context)
>>  		return 0;
>
> Sashiko says it's unnecessary:
>
> https://sashiko.dev/#/patchset/20260506182039.2623553-5-pasha.tatashin at soleen.com
>
> I tend to agree :)

Hmm, good point. I take back my suggestion then.

-- 
Regards,
Pratyush Yadav


From rppt at kernel.org  Mon May 11 04:32:10 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:32:10 +0300
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <20260429133928.850721-2-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-2-pratyush@kernel.org>
Message-ID: <agG-OuOTAg5YD-CV@kernel.org>

On Wed, Apr 29, 2026 at 03:39:03PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The KHO radix tree is a data structure that can track the presence or
> absence of an arbitrary key, with nothing inherently tied to KHO memory
> preservation tracking. This was one of the design goals of the radix
> tree. This was done to enable it to be re-used by other users of KHO.
> 
> Despite that, the radix tree APIs are very closely tied to KHO memory
> preservation tracking. Adding a key is done by kho_radix_add_page(),
> which encodes it as a page tracking operation and takes in PFN and
> order. kho_radix_del_page() does the same. These functions encode the
> key internally that goes into the radix tree. kho_radix_walk_tree() does
> the same by baking the PFN and order into the callback arguments.
> 
> Generalize the APIs by taking the key directly and doing the encoding at
> the callers. Rename the functions to kho_radix_add_key() and
> kho_radix_del_key(). In practice, this removes a line each from the
> functions and moves the encoding function call to the callers.
> Similarly, update kho_radix_tree_walk_callback_t to take the key
> directly.
> 
> To keep the naming convention clearer, rename
> kho_radix_{encode,decode}_key() to kho_{encode,decode}_radix_key().
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     | 18 +++----
>  kernel/liveupdate/kexec_handover.c | 76 ++++++++++++++----------------
>  2 files changed, 42 insertions(+), 52 deletions(-)
> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index 84e918b96e53..f368f3b9f923 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -85,7 +85,7 @@ static struct kho_out kho_out = {
>  };
>  
>  /**
> - * kho_radix_encode_key - Encodes a physical address and order into a radix key.
> + * kho_encode_radix_key - Encodes a physical address and order into a radix key.

Let's keep kho_radix_ prefix for namespasing please.
This is the common practice these days.

>   * @phys: The physical address of the page.
>   * @order: The order of the page.
>   *

...

> @@ -144,24 +144,21 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
>  }
>  
>  /**
> - * kho_radix_add_page - Marks a page as preserved in the radix tree.
> + * kho_radix_add_key - Add a key to the radix tree.
>   * @tree: The KHO radix tree.
> - * @pfn: The page frame number of the page to preserve.
> - * @order: The order of the page.
> + * @key: The key to add.
>   *
> - * 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.
> + * This function traverses the radix tree based on the key provided. It sets the

Nit:                                                     @key here and in
the comments below.

> + * corresponding bit in the leaf bitmap to mark the key as present. 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.
>   */

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:43:22 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:43:22 +0300
Subject: [PATCH 02/12] kho: store incoming radix tree in kho_in
In-Reply-To: <20260429133928.850721-3-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-3-pratyush@kernel.org>
Message-ID: <agHA2imH5hdMSlId@kernel.org>

On Wed, Apr 29, 2026 at 03:39:04PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> This allows other functions to also use the radix tree. While at it,
> also use kho_get_mem_map_phys() instead of duplicating the code to get
> the radix tree root from the FDT.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  kernel/liveupdate/kexec_handover.c | 27 ++++++++-------------------
>  1 file changed, 8 insertions(+), 19 deletions(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index ba568d34c5b4..5758dc6fab5d 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -1294,6 +1294,7 @@ struct kho_in {
>  	char previous_release[__NEW_UTS_LEN + 1];
>  	u32 kexec_count;
>  	struct kho_debugfs dbg;
> +	struct kho_radix_tree radix_tree;
>  };
>  
>  static struct kho_in kho_in = {
> @@ -1373,24 +1374,10 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
>  
>  static int __init kho_mem_retrieve(const void *fdt)
>  {
> -	struct kho_radix_tree tree;
> -	const phys_addr_t *mem;
> -	int len;
> -
> -	/* Retrieve the KHO radix tree from passed-in FDT. */
> -	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
> -
> -	if (!mem || len != sizeof(*mem)) {
> -		pr_err("failed to get preserved KHO memory tree\n");
> -		return -ENOENT;
> -	}
> -
> -	if (!*mem)
> -		return -EINVAL;
> -
> -	tree.root = phys_to_virt(*mem);
> -	mutex_init(&tree.lock);
> -	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
> +	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));

Do we really want to remove an explicit check for 0 and get a WARN() in
kho_radix_walk_tree()?

> +	mutex_init(&kho_in.radix_tree.lock);
> +	return kho_radix_walk_tree(&kho_in.radix_tree,
> +				   kho_preserved_memory_reserve);
>  }
>  
>  static __init int kho_out_fdt_setup(void)
> @@ -1597,8 +1584,10 @@ void __init kho_memory_init(void)
>  	if (kho_in.scratch_phys) {
>  		kho_scratch = phys_to_virt(kho_in.scratch_phys);
>  
> -		if (kho_mem_retrieve(kho_get_fdt()))
> +		if (kho_mem_retrieve(kho_get_fdt())) {
>  			kho_in.fdt_phys = 0;
> +			kho_in.radix_tree.root = NULL;

I'd make kho_mem_retrieve() void and put these there.

> +		}
>  	} else {
>  		kho_reserve_scratch();
>  	}
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:47:56 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:47:56 +0300
Subject: [PATCH 03/12] kho: add a struct for radix callbacks
In-Reply-To: <20260429133928.850721-4-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-4-pratyush@kernel.org>
Message-ID: <agHB7HB-p_vkLS7R@kernel.org>

On Wed, Apr 29, 2026 at 03:39:05PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> A future commit will add more callbacks for the KHO radix tree. Add a
> struct for collecting the callbacks.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     | 15 ++++++++++++---
>  kernel/liveupdate/kexec_handover.c | 29 ++++++++++++++++-------------
>  2 files changed, 28 insertions(+), 16 deletions(-)
> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index f368f3b9f923..030da6399d28 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -34,14 +34,23 @@ struct kho_radix_tree {
>  	struct mutex lock; /* protects the tree's structure and root pointer */
>  };
>  
> -typedef int (*kho_radix_tree_walk_callback_t)(unsigned long key);
> +/**
> + * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
> + * @key:      Called on each present key in the radix tree.
> + *
> + * For each callback, a return value of 0 continues the walk and a non-zero
> + * return value is directly returned to the caller.
> + */
> +struct kho_radix_walk_cb {
> +	int (*key)(unsigned long key);
> +};
>  
>  #ifdef CONFIG_KEXEC_HANDOVER
>  
>  int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
>  void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
>  int kho_radix_walk_tree(struct kho_radix_tree *tree,
> -			kho_radix_tree_walk_callback_t cb);
> +			const struct kho_radix_walk_cb *cb);
>  
>  #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
>  
> @@ -54,7 +63,7 @@ static inline void kho_radix_del_key(struct kho_radix_tree *tree,
>  				     unsigned long key) { }
>  
>  static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
> -				      kho_radix_tree_walk_callback_t cb)
> +				      const struct kho_radix_walk_cb *cb)
>  {
>  	return -EOPNOTSUPP;
>  }
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 5758dc6fab5d..4a5d1b47799c 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>  }
>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
>  
> -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
> -			       unsigned long key,
> -			       kho_radix_tree_walk_callback_t cb)
> +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
> +			       const struct kho_radix_walk_cb *cb)
>  {
>  	unsigned long *bitmap = (unsigned long *)leaf;
>  	unsigned int i;
>  	int err;
>  
> +	if (!cb->key)
> +		return 0;
> +
>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
> -		err = cb(key | i);
> +		err = cb->key(key | i);

key(key) reads weird :)
Can't say I have a good name, maybe key_action()?

>  		if (err)
>  			return err;
>  	}

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:50:06 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:50:06 +0300
Subject: [PATCH 04/12] kho: add callback for table pages
In-Reply-To: <20260429133928.850721-5-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-5-pratyush@kernel.org>
Message-ID: <agHCbpn5Hn3XsEWH@kernel.org>

On Wed, Apr 29, 2026 at 03:39:06PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The KHO memory preservation radix tree does not mark the table pages
> themselves as scratch. This is done to avoid a circular dependency where
> preserving a page can lead of allocating other preserved pages. This
> means any walker looking for free ranges of memory outside of scratch
> areas will ignore the table
> 
> Add a table callback that is invoked for each table page. The callback

A sentence why a table callback helps would be nice here :)

> is given the physical address of the table page.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     |  3 +++
>  kernel/liveupdate/kexec_handover.c | 12 ++++++++++++
>  2 files changed, 15 insertions(+)
> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index 030da6399d28..fe7151d89361 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -37,12 +37,15 @@ struct kho_radix_tree {
>  /**
>   * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
>   * @key:      Called on each present key in the radix tree.
> + * @table:    Called on each table of the radix tree itself. Receives the
> + *            physical address of the page containing the table.
>   *
>   * For each callback, a return value of 0 continues the walk and a non-zero
>   * return value is directly returned to the caller.
>   */
>  struct kho_radix_walk_cb {
>  	int (*key)(unsigned long key);
> +	int (*table)(phys_addr_t phys);

Naming is hard ;-)

>  };
>  

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:53:07 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:53:07 +0300
Subject: [PATCH 05/12] kho: add data argument to radix walk callback
In-Reply-To: <20260429133928.850721-6-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-6-pratyush@kernel.org>
Message-ID: <agHDI-NdI_AhslBJ@kernel.org>

On Wed, Apr 29, 2026 at 03:39:07PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Add an opaque data pointer argument to kho_radix_walk_cb_t. This can be
> used for callers to pass extra information to the callback.

Nit:  ^by IMHO
 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     |  8 ++++----
>  kernel/liveupdate/kexec_handover.c | 24 +++++++++++++-----------
>  2 files changed, 17 insertions(+), 15 deletions(-)
> 

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:56:40 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:56:40 +0300
Subject: [PATCH 06/12] kho: allow early-boot usage of the KHO radix tree
In-Reply-To: <20260429133928.850721-7-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-7-pratyush@kernel.org>
Message-ID: <agHD-E4iDhvUEZDS@kernel.org>

On Wed, Apr 29, 2026 at 03:39:08PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The KHO radix tree allocates memory for table pages from the buddy
> allocator using get_zeroed_page(). This is not available in early boot
> when memblock is still active.
> 
> Using the radix tree in early boot is useful for KHO to track metadata
> about its memory. One such example is for tracking free blocks for
> memory allocation when scratch runs out of space. This feature will be
> added in the following commits.
> 
> Add kho_radix_{alloc,free}_node() which allocate and free the table
> pages. They use slab_is_available() to decide which allocator to use.
> While slab_is_available() indicates availability of the slab allocator,
> it gets initialized right before buddy so it serves the same practical

                           ^ after?

> purpose.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  kernel/liveupdate/kexec_handover.c | 24 ++++++++++++++++++++++--
>  1 file changed, 22 insertions(+), 2 deletions(-)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 04:57:53 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 14:57:53 +0300
Subject: [PATCH 07/12] kho: allow destroying KHO radix tree
In-Reply-To: <20260429133928.850721-8-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-8-pratyush@kernel.org>
Message-ID: <agHEQYbDmeOiH5IU@kernel.org>

On Wed, Apr 29, 2026 at 03:39:09PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Add kho_radix_destroy_tree() which allows destroying the radix tree and
> freeing all its pages.

A sentence why it Will be useful would be nice.
 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     |  3 +++
>  kernel/liveupdate/kexec_handover.c | 34 ++++++++++++++++++++++++++++++
>  2 files changed, 37 insertions(+)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 05:06:19 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 15:06:19 +0300
Subject: [PATCH 09/12] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <20260429133928.850721-10-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-10-pratyush@kernel.org>
Message-ID: <agHGOxSnP_VraH1M@kernel.org>

On Wed, Apr 29, 2026 at 03:39:11PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> In the upcoming commits, the KHO will learn how to discover free blocks
> of memory by walking the KHO radix tree. It will then mark those regions
> as scratch to allow memory allocation in case scratch runs low.
> 
> To differentiate the extended scratch areas from the main scratch areas,
> introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
> for allocations during scratch-only. Teach should_skip_region() to check
> for both flags before deciding if the region should be skipped.

Why there's a need to differentiate SCRATCH and SCRATCH_EXT?
SCRATCH (I still hate the name) means "memory memblock can safely use for
the allocations". Initially this memory comes from the reservations in the
first kernel, but if the second kernel can find more memory to extend it,
why that additional memory should be treated differently? 

> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
> 
> Notes:
>     Checkpatch complains about no space after MEMBLOCK_KHO_SCRATCH_EXT in
>     the declaration, but doing so makes it nicely align with all the other
>     numbers. Mike, if you'd like I can add some whitespace.
> 
>  include/linux/memblock.h | 10 ++++++++++
>  mm/memblock.c            | 41 ++++++++++++++++++++++++++++++++++------
>  2 files changed, 45 insertions(+), 6 deletions(-)
 
-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 05:13:00 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 11 May 2026 15:13:00 +0300
Subject: [PATCH 11/12] kho: return virtual address of mem_map
In-Reply-To: <20260429133928.850721-12-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-12-pratyush@kernel.org>
Message-ID: <agHHzJS7bLPHk1GJ@kernel.org>

On Wed, Apr 29, 2026 at 03:39:13PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> There are currently 3 callers of kho_get_mem_map_phys(). Two of them,
> kho_mem_retrieve() and kho_extend_scratch() need the virtual address.
> The third, kho_populate() doesn't care. Make things simpler by
> directly returning the virtual address. Rename kho_get_mem_map_phys() to
> kho_get_mem_map() to accurately reflect what it returns.

This should be moved earlier, before replacing open-coded get_mem_map()
with a call to kho_get_mem_map_phys().
 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  kernel/liveupdate/kexec_handover.c | 28 +++++++++++++++-------------
>  1 file changed, 15 insertions(+), 13 deletions(-)

-- 
Sincerely yours,
Mike.


From pasha.tatashin at soleen.com  Mon May 11 06:11:43 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 11 May 2026 09:11:43 -0400
Subject: [PATCH v2 1/3] kho: skip KHO data for context-preserving kexec
In-Reply-To: <agGxkHPx7W7P5AUS@kernel.org>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
 <20260506182039.2623553-6-pasha.tatashin@soleen.com>
 <agGxkHPx7W7P5AUS@kernel.org>
Message-ID: <agHU6q3N2URn8Sei@google.com>

On 05-11 13:38, Mike Rapoport wrote:
> On Wed, May 06, 2026 at 02:20:41PM -0400, Pasha Tatashin wrote:
> > A preserve_context kexec returns to the current kernel, which means the
> > 2nd kernel does not use KHO data (and their memory spaces don't
> > overlap). Passing KHO data to the 2nd kernel via setup_data or
> > devicetree is unnecessary.
> > 
> > Add a check in kho_fill_kimage() and return early if preserve_context is
> > set, to avoid passing KHO state during context-preserving kexecs.
> > 
> > Fixes: 3bdecc3c93f9 ("kexec: add KHO support to kexec file loads")
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  kernel/liveupdate/kexec_handover.c | 3 ++-
> >  1 file changed, 2 insertions(+), 1 deletion(-)
> > 
> > diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> > index 4fde8325c49f..2eec2a169c83 100644
> > --- a/kernel/liveupdate/kexec_handover.c
> > +++ b/kernel/liveupdate/kexec_handover.c
> > @@ -1702,7 +1702,8 @@ int kho_fill_kimage(struct kimage *image)
> >  	int err = 0;
> >  	struct kexec_buf scratch;
> >  
> > -	if (!kho_enable || image->type == KEXEC_TYPE_CRASH)
> > +	if (!kho_enable || image->type == KEXEC_TYPE_CRASH ||
> > +	    image->preserve_context)
> >  		return 0;
> 
> Sashiko says it's unnecessary:
> 
> https://sashiko.dev/#/patchset/20260506182039.2623553-5-pasha.tatashin at soleen.com
> 
> I tend to agree :)

Awesome catch by Sashiko, I will drop this patch.

Pasha

>   
> >  	image->kho.fdt = virt_to_phys(kho_out.fdt);
> > -- 
> > 2.54.0.545.g6539524ca2-goog
> > 
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Mon May 11 06:44:08 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 11 May 2026 09:44:08 -0400
Subject: [PATCH v2 3/3] liveupdate: block outgoing session mutations
In-Reply-To: <2vxzv7cucint.fsf@kernel.org>
References: <20260506182039.2623553-5-pasha.tatashin@soleen.com>
 <20260506182039.2623553-8-pasha.tatashin@soleen.com>
 <2vxzv7cucint.fsf@kernel.org>
Message-ID: <agHcu9_eSolgNZFT@google.com>

On 05-11 13:03, Pratyush Yadav wrote:
> On Wed, May 06 2026, Pasha Tatashin wrote:
> 
> > During the reboot() syscall, user processes may still be running
> > concurrently and attempting to mutate sessions (e.g., adding files or
> > closing sessions). To prevent this, hold the session header's rwsem and
> > each session's mutex indefinitely on successful serialization.
> >
> > If serialization succeeds, these locks will never be released as the
> > system transitions to the new kernel, effectively blocking any
> > concurrent task attempting to modify or release a session. If
> > liveupdate_reboot() fails, the locks are released in the error path,
> > allowing normal operation to resume.
> >
> > Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> > Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> 
> Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Sashiko also provided a good comment about this patch: We run over 
MAX_LOCK_DEPTH limit with this patch. I am going to refactor to not take 
every session lock simultaneously during serialization.

> 
> [...]
> 
> -- 
> Regards,
> Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:25:52 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:25:52 +0200
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <agG-OuOTAg5YD-CV@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:32:10 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-2-pratyush@kernel.org>
	<agG-OuOTAg5YD-CV@kernel.org>
Message-ID: <2vxz8q9pdib3.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:03PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> The KHO radix tree is a data structure that can track the presence or
>> absence of an arbitrary key, with nothing inherently tied to KHO memory
>> preservation tracking. This was one of the design goals of the radix
>> tree. This was done to enable it to be re-used by other users of KHO.
>> 
>> Despite that, the radix tree APIs are very closely tied to KHO memory
>> preservation tracking. Adding a key is done by kho_radix_add_page(),
>> which encodes it as a page tracking operation and takes in PFN and
>> order. kho_radix_del_page() does the same. These functions encode the
>> key internally that goes into the radix tree. kho_radix_walk_tree() does
>> the same by baking the PFN and order into the callback arguments.
>> 
>> Generalize the APIs by taking the key directly and doing the encoding at
>> the callers. Rename the functions to kho_radix_add_key() and
>> kho_radix_del_key(). In practice, this removes a line each from the
>> functions and moves the encoding function call to the callers.
>> Similarly, update kho_radix_tree_walk_callback_t to take the key
>> directly.
>> 
>> To keep the naming convention clearer, rename
>> kho_radix_{encode,decode}_key() to kho_{encode,decode}_radix_key().
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  include/linux/kho_radix_tree.h     | 18 +++----
>>  kernel/liveupdate/kexec_handover.c | 76 ++++++++++++++----------------
>>  2 files changed, 42 insertions(+), 52 deletions(-)
>> 
>> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
>> index 84e918b96e53..f368f3b9f923 100644
>> --- a/include/linux/kho_radix_tree.h
>> +++ b/include/linux/kho_radix_tree.h
>> @@ -85,7 +85,7 @@ static struct kho_out kho_out = {
>>  };
>>  
>>  /**
>> - * kho_radix_encode_key - Encodes a physical address and order into a radix key.
>> + * kho_encode_radix_key - Encodes a physical address and order into a radix key.
>
> Let's keep kho_radix_ prefix for namespasing please.
> This is the common practice these days.

I am keeping the namespacing, just moving it out of the "kho_radix"
namespace to "kho" namespace. The mental model is that this function has
nothing to do with the radix tree. It is just something KHO uses for one
of its radix tree, so it should live in the "kho" namespace not
"kho_radix".

Does that make sense? I can add this explanation to the commit message
as well.

>
>>   * @phys: The physical address of the page.
>>   * @order: The order of the page.
>>   *
>
> ...
>
>> @@ -144,24 +144,21 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
>>  }
>>  
>>  /**
>> - * kho_radix_add_page - Marks a page as preserved in the radix tree.
>> + * kho_radix_add_key - Add a key to the radix tree.
>>   * @tree: The KHO radix tree.
>> - * @pfn: The page frame number of the page to preserve.
>> - * @order: The order of the page.
>> + * @key: The key to add.
>>   *
>> - * 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.
>> + * This function traverses the radix tree based on the key provided. It sets the
>
> Nit:                                                     @key here and in
> the comments below.

ACK.

>
>> + * corresponding bit in the leaf bitmap to mark the key as present. 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.
>>   */

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:28:21 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:28:21 +0200
Subject: [PATCH 02/12] kho: store incoming radix tree in kho_in
In-Reply-To: <agHA2imH5hdMSlId@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:43:22 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-3-pratyush@kernel.org>
	<agHA2imH5hdMSlId@kernel.org>
Message-ID: <2vxz4ikddi6y.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:04PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> This allows other functions to also use the radix tree. While at it,
>> also use kho_get_mem_map_phys() instead of duplicating the code to get
>> the radix tree root from the FDT.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 27 ++++++++-------------------
>>  1 file changed, 8 insertions(+), 19 deletions(-)
>> 
>> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
>> index ba568d34c5b4..5758dc6fab5d 100644
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -1294,6 +1294,7 @@ struct kho_in {
>>  	char previous_release[__NEW_UTS_LEN + 1];
>>  	u32 kexec_count;
>>  	struct kho_debugfs dbg;
>> +	struct kho_radix_tree radix_tree;
>>  };
>>  
>>  static struct kho_in kho_in = {
>> @@ -1373,24 +1374,10 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
>>  
>>  static int __init kho_mem_retrieve(const void *fdt)
>>  {
>> -	struct kho_radix_tree tree;
>> -	const phys_addr_t *mem;
>> -	int len;
>> -
>> -	/* Retrieve the KHO radix tree from passed-in FDT. */
>> -	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
>> -
>> -	if (!mem || len != sizeof(*mem)) {
>> -		pr_err("failed to get preserved KHO memory tree\n");
>> -		return -ENOENT;
>> -	}
>> -
>> -	if (!*mem)
>> -		return -EINVAL;
>> -
>> -	tree.root = phys_to_virt(*mem);
>> -	mutex_init(&tree.lock);
>> -	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
>> +	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
>
> Do we really want to remove an explicit check for 0 and get a WARN() in
> kho_radix_walk_tree()?

If mem_map_phys is 0 then kho_populate() never sets kho_in.scratch_phys
and thus kho_mem_retrieve() never gets called. So here it will never be
0, and if it is, a WARN() is certainly warranted.

Perhaps I should add a comment since this isn't so obvious?

>
>> +	mutex_init(&kho_in.radix_tree.lock);
>> +	return kho_radix_walk_tree(&kho_in.radix_tree,
>> +				   kho_preserved_memory_reserve);
>>  }
>>  
>>  static __init int kho_out_fdt_setup(void)
>> @@ -1597,8 +1584,10 @@ void __init kho_memory_init(void)
>>  	if (kho_in.scratch_phys) {
>>  		kho_scratch = phys_to_virt(kho_in.scratch_phys);
>>  
>> -		if (kho_mem_retrieve(kho_get_fdt()))
>> +		if (kho_mem_retrieve(kho_get_fdt())) {
>>  			kho_in.fdt_phys = 0;
>> +			kho_in.radix_tree.root = NULL;
>
> I'd make kho_mem_retrieve() void and put these there.

Sure, will do.

>
>> +		}
>>  	} else {
>>  		kho_reserve_scratch();
>>  	}
>> -- 
>> 2.54.0.545.g6539524ca2-goog
>> 

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:35:54 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:35:54 +0200
Subject: [PATCH 03/12] kho: add a struct for radix callbacks
In-Reply-To: <agHB7HB-p_vkLS7R@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:47:56 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-4-pratyush@kernel.org>
	<agHB7HB-p_vkLS7R@kernel.org>
Message-ID: <2vxzzf25c39x.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:05PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> A future commit will add more callbacks for the KHO radix tree. Add a
>> struct for collecting the callbacks.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
[...]
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>>  }
>>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
>>  
>> -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
>> -			       unsigned long key,
>> -			       kho_radix_tree_walk_callback_t cb)
>> +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
>> +			       const struct kho_radix_walk_cb *cb)
>>  {
>>  	unsigned long *bitmap = (unsigned long *)leaf;
>>  	unsigned int i;
>>  	int err;
>>  
>> +	if (!cb->key)
>> +		return 0;
>> +
>>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
>> -		err = cb(key | i);
>> +		err = cb->key(key | i);
>
> key(key) reads weird :)
> Can't say I have a good name, maybe key_action()?

I thought the context of it being under a callback struct would make it
more obvious. I don't like key_action() much better TBH, but I don't
have a strong opinion. Perhaps Pasha can suggest a 3rd option and we
pick on randomly ;-)

>
>>  		if (err)
>>  			return err;
>>  	}

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:36:53 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:36:53 +0200
Subject: [PATCH 04/12] kho: add callback for table pages
In-Reply-To: <agHCbpn5Hn3XsEWH@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:50:06 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-5-pratyush@kernel.org>
	<agHCbpn5Hn3XsEWH@kernel.org>
Message-ID: <2vxzv7ctc38a.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:06PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> The KHO memory preservation radix tree does not mark the table pages
>> themselves as scratch. This is done to avoid a circular dependency where
>> preserving a page can lead of allocating other preserved pages. This
>> means any walker looking for free ranges of memory outside of scratch
>> areas will ignore the table
>> 
>> Add a table callback that is invoked for each table page. The callback
>
> A sentence why a table callback helps would be nice here :)

The main reason is that a later patch will create a radix tree for short
use and will need to destroy it afterwards.

Will add that here.

>
>> is given the physical address of the table page.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  include/linux/kho_radix_tree.h     |  3 +++
>>  kernel/liveupdate/kexec_handover.c | 12 ++++++++++++
>>  2 files changed, 15 insertions(+)
>> 
>> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
>> index 030da6399d28..fe7151d89361 100644
>> --- a/include/linux/kho_radix_tree.h
>> +++ b/include/linux/kho_radix_tree.h
>> @@ -37,12 +37,15 @@ struct kho_radix_tree {
>>  /**
>>   * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
>>   * @key:      Called on each present key in the radix tree.
>> + * @table:    Called on each table of the radix tree itself. Receives the
>> + *            physical address of the page containing the table.
>>   *
>>   * For each callback, a return value of 0 continues the walk and a non-zero
>>   * return value is directly returned to the caller.
>>   */
>>  struct kho_radix_walk_cb {
>>  	int (*key)(unsigned long key);
>> +	int (*table)(phys_addr_t phys);
>
> Naming is hard ;-)

Yes :-/

>
>>  };
>>  

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:37:20 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:37:20 +0200
Subject: [PATCH 05/12] kho: add data argument to radix walk callback
In-Reply-To: <agHDI-NdI_AhslBJ@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:53:07 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-6-pratyush@kernel.org>
	<agHDI-NdI_AhslBJ@kernel.org>
Message-ID: <2vxzqznhc37j.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:07PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> Add an opaque data pointer argument to kho_radix_walk_cb_t. This can be
>> used for callers to pass extra information to the callback.
>
> Nit:  ^by IMHO

ACK, makes sense.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:37:52 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:37:52 +0200
Subject: [PATCH 06/12] kho: allow early-boot usage of the KHO radix tree
In-Reply-To: <agHD-E4iDhvUEZDS@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 14:56:40 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-7-pratyush@kernel.org>
	<agHD-E4iDhvUEZDS@kernel.org>
Message-ID: <2vxzmry5c36n.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:08PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> The KHO radix tree allocates memory for table pages from the buddy
>> allocator using get_zeroed_page(). This is not available in early boot
>> when memblock is still active.
>> 
>> Using the radix tree in early boot is useful for KHO to track metadata
>> about its memory. One such example is for tracking free blocks for
>> memory allocation when scratch runs out of space. This feature will be
>> added in the following commits.
>> 
>> Add kho_radix_{alloc,free}_node() which allocate and free the table
>> pages. They use slab_is_available() to decide which allocator to use.
>> While slab_is_available() indicates availability of the slab allocator,
>> it gets initialized right before buddy so it serves the same practical
>
>                            ^ after?

ACK, will fix.

>
>> purpose.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 24 ++++++++++++++++++++++--
>>  1 file changed, 22 insertions(+), 2 deletions(-)

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:40:16 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:40:16 +0200
Subject: [PATCH 04/12] kho: add callback for table pages
In-Reply-To: <2vxzv7ctc38a.fsf@kernel.org> (Pratyush Yadav's message of "Mon,
	11 May 2026 18:36:53 +0200")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-5-pratyush@kernel.org>
	<agHCbpn5Hn3XsEWH@kernel.org> <2vxzv7ctc38a.fsf@kernel.org>
Message-ID: <2vxzik8tc32n.fsf@kernel.org>

On Mon, May 11 2026, Pratyush Yadav wrote:

> On Mon, May 11 2026, Mike Rapoport wrote:
>
>> On Wed, Apr 29, 2026 at 03:39:06PM +0200, Pratyush Yadav wrote:
>>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>> 
>>> The KHO memory preservation radix tree does not mark the table pages
>>> themselves as scratch. This is done to avoid a circular dependency where
>>> preserving a page can lead of allocating other preserved pages. This
>>> means any walker looking for free ranges of memory outside of scratch
>>> areas will ignore the table
>>> 
>>> Add a table callback that is invoked for each table page. The callback
>>
>> A sentence why a table callback helps would be nice here :)
>
> The main reason is that a later patch will create a radix tree for short
> use and will need to destroy it afterwards.

This was meant for patch 7.

The reason for this one is that kho_extend_scratch() needs to exclude
table pages from its free ranges. Will add it.

>
> Will add that here.
>
>>
>>> is given the physical address of the table page.
>>> 
>>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:46:31 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:46:31 +0200
Subject: [PATCH 09/12] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <agHGOxSnP_VraH1M@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 15:06:19 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-10-pratyush@kernel.org>
	<agHGOxSnP_VraH1M@kernel.org>
Message-ID: <2vxzecjhc2s8.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:11PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> In the upcoming commits, the KHO will learn how to discover free blocks
>> of memory by walking the KHO radix tree. It will then mark those regions
>> as scratch to allow memory allocation in case scratch runs low.
>> 
>> To differentiate the extended scratch areas from the main scratch areas,
>> introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
>> for allocations during scratch-only. Teach should_skip_region() to check
>> for both flags before deciding if the region should be skipped.
>
> Why there's a need to differentiate SCRATCH and SCRATCH_EXT?
> SCRATCH (I still hate the name) means "memory memblock can safely use for
> the allocations". Initially this memory comes from the reservations in the
> first kernel, but if the second kernel can find more memory to extend it,
> why that additional memory should be treated differently? 

Two reasons:

1. We mark SCRATCH as MIGRATE_CMA. We don't want to do that for
   SCRATCH_EXT since this memory can be used for non-movable
   allocations.

2. Gigantic (1G) huge pages can not be allocated from scratch. They can
   be preserved memory and thus should not be allocated from SCRATCH.
   See patch 12 that does allocations for gigantic huge pages only from
   SCRATCH_EXT.

I will add this in the commit message for the next version.

Naming is hard, so if you have any better names I'm all ears :-)

>
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>> 
>> Notes:
>>     Checkpatch complains about no space after MEMBLOCK_KHO_SCRATCH_EXT in
>>     the declaration, but doing so makes it nicely align with all the other
>>     numbers. Mike, if you'd like I can add some whitespace.
>> 
>>  include/linux/memblock.h | 10 ++++++++++
>>  mm/memblock.c            | 41 ++++++++++++++++++++++++++++++++++------
>>  2 files changed, 45 insertions(+), 6 deletions(-)

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 11 09:48:37 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 11 May 2026 18:48:37 +0200
Subject: [PATCH 11/12] kho: return virtual address of mem_map
In-Reply-To: <agHHzJS7bLPHk1GJ@kernel.org> (Mike Rapoport's message of "Mon,
	11 May 2026 15:13:00 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-12-pratyush@kernel.org>
	<agHHzJS7bLPHk1GJ@kernel.org>
Message-ID: <2vxza4u5c2oq.fsf@kernel.org>

On Mon, May 11 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:13PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> There are currently 3 callers of kho_get_mem_map_phys(). Two of them,
>> kho_mem_retrieve() and kho_extend_scratch() need the virtual address.
>> The third, kho_populate() doesn't care. Make things simpler by
>> directly returning the virtual address. Rename kho_get_mem_map_phys() to
>> kho_get_mem_map() to accurately reflect what it returns.
>
> This should be moved earlier, before replacing open-coded get_mem_map()
> with a call to kho_get_mem_map_phys().

Do you mean move it before patch 2 "kho: store incoming radix tree in
kho_in"?

I can do that. I did it this way to show a clear need for this function,
but I don't mind doing it as a preparatory step.

>  
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 28 +++++++++++++++-------------
>>  1 file changed, 15 insertions(+), 13 deletions(-)

-- 
Regards,
Pratyush Yadav


From sourabhjain at linux.ibm.com  Mon May 11 22:42:16 2026
From: sourabhjain at linux.ibm.com (Sourabh Jain)
Date: Tue, 12 May 2026 11:12:16 +0530
Subject: [PATCH v2 2/9] crash_dump: Fix potential double free and UAF of
 keys_header
In-Reply-To: <af_JKzEAwp9r-ZSS@Rk>
References: <20260501234342.2518281-1-coiby.xu@gmail.com>
 <20260501234342.2518281-3-coiby.xu@gmail.com>
 <fdb25929-2b47-4f92-b6a9-8caea1642fff@linux.ibm.com> <af3RQa8aU_F79XOw@Rk>
 <e460f764-997e-4a63-be7d-1ceb5a503d32@linux.ibm.com> <af_JKzEAwp9r-ZSS@Rk>
Message-ID: <54a27f47-a7cd-458c-9b27-7b84949aa453@linux.ibm.com>



On 10/05/26 05:44, Coiby Xu wrote:
> On Sat, May 09, 2026 at 01:36:59AM +0530, Sourabh Jain wrote:
>>
>>
>> On 08/05/26 18:03, Coiby Xu wrote:
>>> On Wed, May 06, 2026 at 05:58:31PM +0530, Sourabh Jain wrote:
>>>> Hello Coiby,
>>>
>>> Hi Sourabh,
>>>
>>> Thanks for reviewing the patch!
>>>
>>>>
>>>> On 02/05/26 05:13, Coiby Xu wrote:
>>>>> If kexec_add_buffer somehow fails, keys_header will be freed. 
>>>>> Depending
>>>>> on /sys/kernel/config/crash_dm_crypt_key/reuse, it will lead to the
>>>>> following two problems if the kexec_file_load syscall is called 
>>>>> again,
>>>>> ? 1. Double free of keys_header if reuse=false
>>>>> ? 2. UAF of keys_header if reuse=true
>>>>>
>>>>> To address these problems and also make it easier to reason about the
>>>>> code, keep two invariants,
>>>>> ? 1. keys_header will always be freed at the end of kexec_file_load
>>>>> ???? syscall except during kdump image unloading for CPU/memory
>>>>> ???? hot-plugging support
>>>>> ? 2. There will always be valid keys_header if reuse=true
>>>>>
>>>>> Fixes: 479e58549b0f ("crash_dump: store dm crypt keys in kdump 
>>>>> reserved memory")
>>>>> Fixes: 9ebfa8dcaea7 ("crash_dump: reuse saved dm crypt keys for 
>>>>> CPU/memory hot-plugging")
>>>>> Reported-by: Sourabh Jain <sourabhjain at linux.ibm.com>
> [...]
>>>
>>>>
>>>>
>>>>
>>>>> ???????? kexec_dprintk(
>>>>> ???????????? "dm-crypt keys haven't be saved to crash-reserved 
>>>>> memory\n");
>>>>> ???????? return -EINVAL;
>>>>> ???? }
>>>>> -??? if (kstrtobool(page, &is_dm_key_reused))
>>>>> +??? if (kstrtobool(page, &val) || !val)
>>>>> ???????? return -EINVAL;
>>>>
>>>> Why can?t we allow the user to set is_dm_key_reused = false and 
>>>> free the
>>>> key_header? That way, the next kdump kernel load will recreate the
>>>> For example, a user may add more keys and want the new keys to be 
>>>> included
>>>> in the kdump image from next kdump kernel load.
>>>
>>> Personally, I want to limit the reuse configfs API to the case of
>>> CPU/memory hotplug thus to make the code simpler. And for the case of a
>>> user adding more keys, I don't think there is a need for using the 
>>> reuse
>>> API.
>>
>>
>> Yes, there is no need for it, but I think the user is forced to use
>> key_reuse because once it is set to true, it cannot be set back to 
>> false.
>>
>> For example, the kdump kernel is loaded using the kexec_load system call
>> and reuse is set to true. The user may later add a couple of new keys 
>> and
>> want to include them in the kdump image.
>>
>> I think the next kdump kernel load will reuse the key_headers even 
>> though
>> the user does not want it. Hence I see a problem here.
>>
>> Well user can load kdump kernel a second time, and at that point the
>> keys will get updated. But do we really want this behavior?
>
> key_reuse won't be set to true automatically unless an user explicitly
> does so. Thus I don't think it's forcing users to use it. And by design
> key_reuse is set only for the hotplug case.

Oh, I got it now. So, on kdump kernel load, key reuse will always be
set to false.

And when the user wants to reuse the key, they can just set the key reuse to
true and reload the kdump kernel. After the reload, key reuse will be set to
false again by the kernel itself.

Thanks for clearing up my doubts.

>
> For the cause of loading kdump kernel again, I don't see the need to set
> key_reuse.
>
> I also had the urge to allow writing false to the reuse API and even
> implemented it. But later I deleted the code to avoid over-engineering
> because I'm not sure how it is supposed to be used. Besides this reuse
> API already makes it much harder to reason about the code. Shall all
> architectures implement CONFIG_CRASH_HOTPLUG, I'll delete it immediately
> so crash_dump_dm_crypt.c can be much easier to maintain.
>
>>
>>
>>>
>>>>
>>>>
>>>>> -??? if (is_dm_key_reused)
>>>>> -??????? get_keys_from_kdump_reserved_memory();
>>>>> +??? if (is_dm_key_reused) {
>>>>> +??????? pr_info("Already got dm-crypt keys, please continue with 
>>>>> kexec_file_load syscall\n");
>>>>> +??? } else {
>>>>> +??????? r = get_keys_from_kdump_reserved_memory();
>>>>> +??????? if (r) {
>>>>> +??????????? pr_warn("Failed to get dm-crypt keys from reserved 
>>>>> memory\n");
>>>>> +??????????? return r;
>>>>> +??????? }
>>>>> +??????? is_dm_key_reused = true;
>>>>> +??? }
>>>>> ???? return count;
>>>>> ?}
>>>>> @@ -366,9 +384,6 @@ static int build_keys_header(void)
>>>>> ???? struct config_key *key;
>>>>> ???? int i, r;
>>>>> -??? if (keys_header != NULL)
>>>>> -??????? kvfree(keys_header);
>>>>> -
>>>>> ???? keys_header = kzalloc(get_keys_header_size(key_count), 
>>>>> GFP_KERNEL);
>>>>> ???? if (!keys_header)
>>>>> ???????? return -ENOMEM;
>>>>> @@ -412,7 +427,7 @@ int crash_load_dm_crypt_keys(struct kimage 
>>>>> *image)
>>>>> ???????? .top_down = false,
>>>>> ???????? .random = true,
>>>>> ???? };
>>>>> -??? int r;
>>>>> +??? int r = 0;
>>>>> ???? if (key_count <= 0) {
>>>>> @@ -421,14 +436,15 @@ int crash_load_dm_crypt_keys(struct kimage 
>>>>> *image)
>>>>> ???? }
>>>>> ???? if (!is_dm_key_reused) {
>>>>> -??????? image->dm_crypt_keys_addr = 0;
>>>>> ???????? r = build_keys_header();
>>>>> -??????? if (r) {
>>>>> -??????????? pr_err("Failed to build dm-crypt keys header, 
>>>>> ret=%d\n", r);
>>>>> -??????????? return r;
>>>>> -??????? }
>>>>> +??????? if (r)
>>>>> +??????????? goto out;
>>>>> ???? }
>>>>> +??? /*
>>>>> +???? * keys_header will be copied to reserver memory later and 
>>>>> then be
>>>>> +???? * cleaned up at the end of kexec_file_load syscall
>>>>> +???? */
>>>>> ???? kbuf.buffer = keys_header;
>>>>> ???? kbuf.bufsz = get_keys_header_size(key_count);
>>>>> @@ -438,18 +454,33 @@ int crash_load_dm_crypt_keys(struct kimage 
>>>>> *image)
>>>>> ???? r = kexec_add_buffer(&kbuf);
>>>>> ???? if (r) {
>>>>> ???????? pr_err("Failed to call kexec_add_buffer, ret=%d\n", r);
>>>>> -??????? kvfree((void *)kbuf.buffer);
>>>>> -??????? return r;
>>>>> +??????? goto out;
>>>>> ???? }
>>>>> +
>>>>> ???? image->dm_crypt_keys_addr = kbuf.mem;
>>>>> ???? image->dm_crypt_keys_sz = kbuf.bufsz;
>>>>> ???? kexec_dprintk(
>>>>> ???????? "Loaded dm crypt keys to kexec_buffer bufsz=0x%lx 
>>>>> memsz=0x%lx\n",
>>>>> ???????? kbuf.bufsz, kbuf.memsz);
>>>>> +out:
>>>>> +??? is_dm_key_reused = false;
>>>>> ???? return r;
>>>>> ?}
>>>>> +void kexec_file_post_load_cleanup_dm_crypt(struct kimage *image)
>>>>> +{
>>>>> +??? /*
>>>>> +???? * For CPU/memory hot-plugging, the kdump image will be 
>>>>> reloaded. Prevent
>>>>> +???? * keys_header from being cleaned up during unloading when
>>>>> +???? * is_dm_key_reused=true
>>>>> +???? */
>>>>> +??? if (!is_dm_key_reused) {
>>>>> +??????? kfree_sensitive(keys_header);
>>>>> +??????? keys_header = NULL;
>>>>
>>>> Since crash_load_dm_crypt_keys() sets is_dm_key_reused = false, 
>>>> keys_header will
>>>> always be released here, right? Then why is the above free under an 
>>>> if condition?
>>>
>>> Thanks for raising the question! This is to prevent "kexec -u" from
>>> cleaning up keys_headers because kexec_file_post_load_cleanup_dm_crypt
>>> will also be called during "kexec -u". Without the if condition,
>>> keys_headers will not be available during reloading.
>>
>> Agree. But do we really run kexec -u and then kexec -p to reload the 
>> kdump
>> kernel on hotplug events? I am under the impression that the udev rule
>> simply reloads the kdump kernel using kexec -p without explicitly 
>> running
>> kexec -u.
>
> Yes, "kdumpctl reload" will be called during hotplug events 
> https://github.com/rhkdump/kdump-utils/blob/main/kdump-udev-throttler#L51
> So there is explicitly unloading first and then reloading.

Oh ok, then it makes sense.

BTW, I got the impression about not using kexec -u on kdump service reload
from the repo below:

https://github.com/openSUSE/kdump/blob/master/70-kdump.rules.in [Udev Rule]
https://github.com/openSUSE/kdump/blob/00979dc621ba35285b85bf55917a5b0dfd273114/init/load-once.sh#L22 
[Wrapper to load once for multiple hotplug]
https://github.com/openSUSE/kdump/blob/00979dc621ba35285b85bf55917a5b0dfd273114/init/load.sh#L312 
[load kdump script]


- Sourabh Jain
>
>>
>> And if that is the case, I think we should clean key_headers on kexec 
>> -u.
>
> Most of the cases is_dm_key_reused is false thus keys_header will be
> cleaned on kexec -u.
>
>>
>> - Sourabh Jain
>>
>>
>>>
>>>>
>>>> IIUC, for the case where CONFIG_CRASH_HOTPLUG is not enabled, this 
>>>> is how key
>>>> restore works:
>>>>
>>>> After loading the kdump kernel for the first time, the state of the 
>>>> variables is:
>>>>
>>>> is_dm_key_reused = false
>>>> keys_header = NULL
>>>>
>>>> For example, if 2 CPUs are hot-removed and kdump is reloaded twice:
>>>>
>>>> Then the sequence of operations needed to ensure the loaded keys 
>>>> can be reused is:
>>>>
>>>> Udev rule triggered on the 1st CPU hotplug:
>>>> echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>>>> Restore the key header from the reserved area
>>>> Reload the kdump service/kernel
>>>>
>>>> Udev rule triggered on the 2nd CPU hotplug:
>>>> echo true > /sys/kernel/config/crash_dm_crypt_keys/reuse
>>>> Restore the key header from the reserved area
>>>> Reload the kdump service/kernel
>>>>
>>>> What I don?t understand is the need to restore the key header from 
>>>> crashkernel
>>>> memory for every hotplug operation.
>>>
>>> I think there is no easy way to tell if there is another hotplug and
>>> considering hotplug is a rare event. So I guess it's OK to restore the
>>> key headers for every hotplug operation.
>>
>>
>



From rppt at kernel.org  Mon May 11 23:46:34 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Tue, 12 May 2026 09:46:34 +0300
Subject: [PATCH 02/12] kho: store incoming radix tree in kho_in
In-Reply-To: <2vxz4ikddi6y.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-3-pratyush@kernel.org>
 <agHA2imH5hdMSlId@kernel.org>
 <2vxz4ikddi6y.fsf@kernel.org>
Message-ID: <agLMyu2_oQpbPCdn@kernel.org>

On Mon, May 11, 2026 at 06:28:21PM +0200, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:04PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> This allows other functions to also use the radix tree. While at it,
> >> also use kho_get_mem_map_phys() instead of duplicating the code to get
> >> the radix tree root from the FDT.
> >> 
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> >>  kernel/liveupdate/kexec_handover.c | 27 ++++++++-------------------
> >>  1 file changed, 8 insertions(+), 19 deletions(-)
> >> 
> >> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> >> index ba568d34c5b4..5758dc6fab5d 100644
> >> --- a/kernel/liveupdate/kexec_handover.c
> >> +++ b/kernel/liveupdate/kexec_handover.c
> >> @@ -1294,6 +1294,7 @@ struct kho_in {
> >>  	char previous_release[__NEW_UTS_LEN + 1];
> >>  	u32 kexec_count;
> >>  	struct kho_debugfs dbg;
> >> +	struct kho_radix_tree radix_tree;
> >>  };
> >>  
> >>  static struct kho_in kho_in = {
> >> @@ -1373,24 +1374,10 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
> >>  
> >>  static int __init kho_mem_retrieve(const void *fdt)
> >>  {
> >> -	struct kho_radix_tree tree;
> >> -	const phys_addr_t *mem;
> >> -	int len;
> >> -
> >> -	/* Retrieve the KHO radix tree from passed-in FDT. */
> >> -	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
> >> -
> >> -	if (!mem || len != sizeof(*mem)) {
> >> -		pr_err("failed to get preserved KHO memory tree\n");
> >> -		return -ENOENT;
> >> -	}
> >> -
> >> -	if (!*mem)
> >> -		return -EINVAL;
> >> -
> >> -	tree.root = phys_to_virt(*mem);
> >> -	mutex_init(&tree.lock);
> >> -	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
> >> +	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
> >
> > Do we really want to remove an explicit check for 0 and get a WARN() in
> > kho_radix_walk_tree()?
> 
> If mem_map_phys is 0 then kho_populate() never sets kho_in.scratch_phys
> and thus kho_mem_retrieve() never gets called. So here it will never be
> 0, and if it is, a WARN() is certainly warranted.
> 
> Perhaps I should add a comment since this isn't so obvious?

Yes, please.
After a few weeks it will be completely obscure.
 
> >
> >> +	mutex_init(&kho_in.radix_tree.lock);
> >> +	return kho_radix_walk_tree(&kho_in.radix_tree,
> >> +				   kho_preserved_memory_reserve);
> >>  }
> >>  
> >>  static __init int kho_out_fdt_setup(void)
> >> @@ -1597,8 +1584,10 @@ void __init kho_memory_init(void)
> >>  	if (kho_in.scratch_phys) {
> >>  		kho_scratch = phys_to_virt(kho_in.scratch_phys);
> >>  
> >> -		if (kho_mem_retrieve(kho_get_fdt()))
> >> +		if (kho_mem_retrieve(kho_get_fdt())) {
> >>  			kho_in.fdt_phys = 0;
> >> +			kho_in.radix_tree.root = NULL;
> >
> > I'd make kho_mem_retrieve() void and put these there.
> 
> Sure, will do.
> 
> >
> >> +		}
> >>  	} else {
> >>  		kho_reserve_scratch();
> >>  	}
> >> -- 
> >> 2.54.0.545.g6539524ca2-goog
> >> 
> 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 23:48:48 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Tue, 12 May 2026 09:48:48 +0300
Subject: [PATCH 03/12] kho: add a struct for radix callbacks
In-Reply-To: <2vxzzf25c39x.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-4-pratyush@kernel.org>
 <agHB7HB-p_vkLS7R@kernel.org>
 <2vxzzf25c39x.fsf@kernel.org>
Message-ID: <agLNUKE5LZqcoUex@kernel.org>

On Mon, May 11, 2026 at 06:35:54PM +0200, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:05PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> A future commit will add more callbacks for the KHO radix tree. Add a
> >> struct for collecting the callbacks.
> >> 
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> [...]
> >> --- a/kernel/liveupdate/kexec_handover.c
> >> +++ b/kernel/liveupdate/kexec_handover.c
> >> @@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
> >>  }
> >>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
> >>  
> >> -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
> >> -			       unsigned long key,
> >> -			       kho_radix_tree_walk_callback_t cb)
> >> +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
> >> +			       const struct kho_radix_walk_cb *cb)
> >>  {
> >>  	unsigned long *bitmap = (unsigned long *)leaf;
> >>  	unsigned int i;
> >>  	int err;
> >>  
> >> +	if (!cb->key)
> >> +		return 0;
> >> +
> >>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
> >> -		err = cb(key | i);
> >> +		err = cb->key(key | i);
> >
> > key(key) reads weird :)
> > Can't say I have a good name, maybe key_action()?
> 
> I thought the context of it being under a callback struct would make it
> more obvious. I don't like key_action() much better TBH, but I don't
> have a strong opinion. Perhaps Pasha can suggest a 3rd option and we
> pick on randomly ;-)

Another option I thought is to call them ->leaf() and ->node().
 
> >
> >>  		if (err)
> >>  			return err;
> >>  	}
> 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 11 23:51:46 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Tue, 12 May 2026 09:51:46 +0300
Subject: [PATCH 11/12] kho: return virtual address of mem_map
In-Reply-To: <2vxza4u5c2oq.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-12-pratyush@kernel.org>
 <agHHzJS7bLPHk1GJ@kernel.org>
 <2vxza4u5c2oq.fsf@kernel.org>
Message-ID: <agLOAtdL0rXL1HJ2@kernel.org>

On Mon, May 11, 2026 at 06:48:37PM +0200, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:13PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> There are currently 3 callers of kho_get_mem_map_phys(). Two of them,
> >> kho_mem_retrieve() and kho_extend_scratch() need the virtual address.
> >> The third, kho_populate() doesn't care. Make things simpler by
> >> directly returning the virtual address. Rename kho_get_mem_map_phys() to
> >> kho_get_mem_map() to accurately reflect what it returns.
> >
> > This should be moved earlier, before replacing open-coded get_mem_map()
> > with a call to kho_get_mem_map_phys().
> 
> Do you mean move it before patch 2 "kho: store incoming radix tree in
> kho_in"?

Yes.
 
> I can do that. I did it this way to show a clear need for this function,
> but I don't mind doing it as a preparatory step.

With the usual "it will be used in the upcoming changes" text in the log
it's fine :)
 
> >  
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> >>  kernel/liveupdate/kexec_handover.c | 28 +++++++++++++++-------------
> >>  1 file changed, 15 insertions(+), 13 deletions(-)
> 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From qirui.001 at bytedance.com  Tue May 12 00:21:42 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Tue, 12 May 2026 15:21:42 +0800
Subject: [PATCH][makedumpfile] Fix riscv64 virtual-to-physical address translation
In-Reply-To: <20260506115758.2497610-1-qirui.001@bytedance.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com>
Message-ID: <20260512072142.58396-1-qirui.001@bytedance.com>

Ping.

This patchset has been out for 6 days without feedback. Could anyone
please take a look?

Link: https://lists.infradead.org/pipermail/kexec/2026-May/036059.html

Thanks,
Rui Qi


From pratyush at kernel.org  Tue May 12 02:11:19 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 11:11:19 +0200
Subject: [PATCH 03/12] kho: add a struct for radix callbacks
In-Reply-To: <agLNUKE5LZqcoUex@kernel.org> (Mike Rapoport's message of "Tue,
	12 May 2026 09:48:48 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-4-pratyush@kernel.org>
	<agHB7HB-p_vkLS7R@kernel.org> <2vxzzf25c39x.fsf@kernel.org>
	<agLNUKE5LZqcoUex@kernel.org>
Message-ID: <2vxz5x4tat6w.fsf@kernel.org>

On Tue, May 12 2026, Mike Rapoport wrote:

> On Mon, May 11, 2026 at 06:35:54PM +0200, Pratyush Yadav wrote:
>> On Mon, May 11 2026, Mike Rapoport wrote:
>> 
>> > On Wed, Apr 29, 2026 at 03:39:05PM +0200, Pratyush Yadav wrote:
>> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> >> 
>> >> A future commit will add more callbacks for the KHO radix tree. Add a
>> >> struct for collecting the callbacks.
>> >> 
>> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> >> ---
>> [...]
>> >> --- a/kernel/liveupdate/kexec_handover.c
>> >> +++ b/kernel/liveupdate/kexec_handover.c
>> >> @@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>> >>  }
>> >>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
>> >>  
>> >> -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
>> >> -			       unsigned long key,
>> >> -			       kho_radix_tree_walk_callback_t cb)
>> >> +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
>> >> +			       const struct kho_radix_walk_cb *cb)
>> >>  {
>> >>  	unsigned long *bitmap = (unsigned long *)leaf;
>> >>  	unsigned int i;
>> >>  	int err;
>> >>  
>> >> +	if (!cb->key)
>> >> +		return 0;
>> >> +
>> >>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
>> >> -		err = cb(key | i);
>> >> +		err = cb->key(key | i);
>> >
>> > key(key) reads weird :)
>> > Can't say I have a good name, maybe key_action()?
>> 
>> I thought the context of it being under a callback struct would make it
>> more obvious. I don't like key_action() much better TBH, but I don't
>> have a strong opinion. Perhaps Pasha can suggest a 3rd option and we
>> pick on randomly ;-)
>
> Another option I thought is to call them ->leaf() and ->node().

This one sounds nice. Will use it in v2.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 12 06:35:55 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 15:35:55 +0200
Subject: [PATCH 1/5] liveupdate: Remove limit on the number of sessions
In-Reply-To: <20260414200237.444170-2-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Tue, 14 Apr 2026 20:02:33 +0000")
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
	<20260414200237.444170-2-pasha.tatashin@soleen.com>
Message-ID: <2vxz1pfgbvic.fsf@kernel.org>

On Tue, Apr 14 2026, Pasha Tatashin wrote:

> Currently, the number of LUO sessions is limited by a fixed number of
> pre-allocated pages for serialization (16 pages, allowing for ~819
> sessions).
>
> This limitation is problematic if LUO is used to support things such as
> systemd file descriptor store, and would be used not just as VM memory
> but to save other states on the machine.
>
> Remove this limit by transitioning to a linked-block approach for
> session metadata serialization. Instead of a single contiguous block,
> session metadata is now stored in a chain of 16-page blocks. Each block
> starts with a header containing the physical address of the next block
> and the number of session entries in the current block.

We now have 3 variants of this linked block data structure: LUO
sessions, LUO files, and KHO vmalloc. Is it time now to unify them into
a reusable data structure? I proposed "KHO Array" some time ago. That
was a collection of pointers, but perhaps we can generalize that to a
collection of elements of arbitrary size?

[0] https://lore.kernel.org/linux-mm/20250909144426.33274-1-pratyush at kernel.org/T/#u

>
> - Bump session ABI version to v3.
> - Update struct luo_session_header_ser to include a 'next' pointer.
> - Implement dynamic block allocation in luo_session_insert().
> - Update setup, serialization, and deserialization logic to traverse
>   the block chain.
> - Remove LUO_SESSION_MAX limit.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  include/linux/kho/abi/luo.h      |  19 +--
>  kernel/liveupdate/luo_internal.h |  12 +-
>  kernel/liveupdate/luo_session.c  | 237 +++++++++++++++++++++++--------
>  3 files changed, 197 insertions(+), 71 deletions(-)
>
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 46750a0ddf88..f5732958545e 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -57,9 +57,10 @@
>   *   - compatible: "luo-session-v1"
>   *     Identifies the session ABI version.
>   *   - luo-session-header: u64
> - *     The physical address of a `struct luo_session_header_ser`. This structure
> - *     is the header for a contiguous block of memory containing an array of
> - *     `struct luo_session_ser`, one for each preserved session.
> + *     The physical address of the first `struct luo_session_header_ser`.
> + *     This structure is the header for a block of memory containing an array
> + *     of `struct luo_session_ser` entries. Multiple blocks are linked via
> + *     the `next` field in the header.
>   *
>   * File-Lifecycle-Bound Node (luo-flb):
>   *   This node describes all preserved global objects whose lifecycle is bound
> @@ -77,9 +78,9 @@
>   *   `__packed` structures. These structures contain the actual preserved state.
>   *
>   *   - struct luo_session_header_ser:
> - *     Header for the session array. Contains the total page count of the
> - *     preserved memory block and the number of `struct luo_session_ser`
> - *     entries that follow.
> + *     Header for the session data block. Contains the physical address of the
> + *     next session data block and the number of `struct luo_session_ser`
> + *     entries that follow this header in the current block.
>   *
>   *   - struct luo_session_ser:
>   *     Metadata for a single session, including its name and a physical pointer
> @@ -153,21 +154,23 @@ struct luo_file_set_ser {
>   *                          luo_session_header_ser
>   */
>  #define LUO_FDT_SESSION_NODE_NAME	"luo-session"
> -#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v2"
> +#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v3"
>  #define LUO_FDT_SESSION_HEADER		"luo-session-header"
>  
>  /**
>   * struct luo_session_header_ser - Header for the serialized session data block.
> + * @next:  Physical address of the next struct luo_session_header_ser.
>   * @count: The number of `struct luo_session_ser` entries that immediately
>   *         follow this header in the memory block.
>   *
> - * This structure is located at the beginning of a contiguous block of
> + * This structure is located at the beginning of a block of
>   * physical memory preserved across the kexec. It provides the necessary
>   * metadata to interpret the array of session entries that follow.
>   *
>   * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
>   */
>  struct luo_session_header_ser {
> +	u64 next;
>  	u64 count;
>  } __packed;
>  
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index 875844d7a41d..a73f42069301 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -11,6 +11,16 @@
>  #include <linux/liveupdate.h>
>  #include <linux/uaccess.h>
>  
> +/*
> + * Safeguard limit for the number of serialization blocks. This is used to
> + * prevent infinite loops and excessive memory allocation in case of memory
> + * corruption in the preserved state.
> + *
> + * This limit allows for ~8.1 million sessions and ~1.2 million files per
> + * session, which is more than enough for all realistic use cases.
> + */
> +#define LUO_MAX_BLOCKS 10000
> +
>  struct luo_ucmd {
>  	void __user *ubuffer;
>  	u32 user_size;
> @@ -59,7 +69,6 @@ struct luo_file_set {
>   * struct luo_session - Represents an active or incoming Live Update session.
>   * @name:       A unique name for this session, used for identification and
>   *              retrieval.
> - * @ser:        Pointer to the serialized data for this session.
>   * @list:       A list_head member used to link this session into a global list
>   *              of either outgoing (to be preserved) or incoming (restored from
>   *              previous kernel) sessions.
> @@ -70,7 +79,6 @@ struct luo_file_set {
>   */
>  struct luo_session {
>  	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> -	struct luo_session_ser *ser;

I was confused by this removal. Seeing this makes one think this got
moved to some other place. But it seems like this was never used. I
think it would be good to mention that in the commit message.

>  	struct list_head list;
>  	bool retrieved;
>  	struct luo_file_set file_set;
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 92b1af791889..007ca34eba79 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -69,30 +69,39 @@
>  #include <uapi/linux/liveupdate.h>
>  #include "luo_internal.h"
>  
> -/* 16 4K pages, give space for 744 sessions */
> +/* 16 4K pages, give space for 819 sessions per block */

It seems odd to read that we added 8 bytes to the header and the number
of sessions per block grew. But then I did the math and I think the
number was always 819 sessions per block and adding the extra 8 bytes
didn't make a difference.

>  #define LUO_SESSION_PGCNT	16ul
> -#define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
> +#define LUO_SESSION_BLOCK_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
>  		sizeof(struct luo_session_header_ser)) /		\
>  		sizeof(struct luo_session_ser))
>  
> +/**
> + * struct luo_session_block - Internal representation of a session serialization block.
> + * @list: List head for linking blocks in memory.
> + * @ser:  Pointer to the serialized header in preserved memory.
> + */
> +struct luo_session_block {
> +	struct list_head list;
> +	struct luo_session_header_ser *ser;

Nit: luo_session_header_ser reads like it is the header for the entire
list not for each block. Perhaps rename it to luo_block_header_ser?

> +};
> +
>  /**
>   * struct luo_session_header - Header struct for managing LUO sessions.
>   * @count:      The number of sessions currently tracked in the @list.
> + * @nblocks:    The number of allocated serialization blocks.
>   * @list:       The head of the linked list of `struct luo_session` instances.
>   * @rwsem:      A read-write semaphore providing synchronized access to the
>   *              session list and other fields in this structure.
> - * @header_ser: The header data of serialization array.
> - * @ser:        The serialized session data (an array of
> - *              `struct luo_session_ser`).
> + * @blocks:     The list of serialization blocks (struct luo_session_block).
>   * @active:     Set to true when first initialized. If previous kernel did not
>   *              send session data, active stays false for incoming.
>   */
>  struct luo_session_header {
>  	long count;
> +	long nblocks;
>  	struct list_head list;
>  	struct rw_semaphore rwsem;
> -	struct luo_session_header_ser *header_ser;
> -	struct luo_session_ser *ser;
> +	struct list_head blocks;
>  	bool active;
>  };
>  
> @@ -110,10 +119,12 @@ static struct luo_session_global luo_session_global = {
>  	.incoming = {
>  		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
>  		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
> +		.blocks = LIST_HEAD_INIT(luo_session_global.incoming.blocks),
>  	},
>  	.outgoing = {
>  		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
>  		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
> +		.blocks = LIST_HEAD_INIT(luo_session_global.outgoing.blocks),
>  	},
>  };
>  
> @@ -140,6 +151,70 @@ static void luo_session_free(struct luo_session *session)
>  	kfree(session);
>  }
>  
> +static int luo_session_add_block(struct luo_session_header *sh,
> +				 struct luo_session_header_ser *ser)
> +{
> +	struct luo_session_block *block;
> +
> +	if (sh->nblocks >= LUO_MAX_BLOCKS)
> +		return -ENOSPC;
> +
> +	block = kzalloc_obj(*block);
> +	if (!block)
> +		return -ENOMEM;
> +
> +	block->ser = ser;
> +	list_add_tail(&block->list, &sh->blocks);
> +	sh->nblocks++;
> +
> +	return 0;
> +}
> +
> +static int luo_session_create_ser_block(struct luo_session_header *sh)
> +{
> +	struct luo_session_block *last = NULL;
> +	struct luo_session_header_ser *ser;
> +	int err;
> +
> +	ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
> +	if (IS_ERR(ser))
> +		return PTR_ERR(ser);
> +
> +	if (!list_empty(&sh->blocks))
> +		last = list_last_entry(&sh->blocks, struct luo_session_block, list);

Nit: using list_last_entry_or_null() is a tiny bit cleaner.

> +
> +	err = luo_session_add_block(sh, ser);
> +	if (err)
> +		goto err_unpreserve;
> +
> +	if (last)
> +		last->ser->next = virt_to_phys(ser);

Nit: can you please move this to luo_session_add_block(). Logically this
operation is a part of adding a block. You add a block to the list and
then update the serialized state. So would be nice to have it done in
one place.

> +
> +	return 0;
> +
> +err_unpreserve:
> +	kho_unpreserve_free(ser);
> +	return err;
> +}
> +
> +static void luo_session_destroy_ser_blocks(struct luo_session_header *sh,
> +					   bool unpreserve)
> +{
> +	struct luo_session_block *block, *tmp;
> +
> +	list_for_each_entry_safe(block, tmp, &sh->blocks, list) {
> +		if (block->ser) {

Block always has ser. Why this check?

> +			if (unpreserve)
> +				kho_unpreserve_free(block->ser);
> +			else
> +				kho_restore_free(block->ser);

Ugh, this is ugly. But I don't see anything obviously better. Perhaps we
can check for sh == luo_session_global.outgoing but that is probably
worse.

> +		}
> +		list_del(&block->list);
> +		kfree(block);
> +		sh->nblocks--;
> +	}
> +}
> +
>  static int luo_session_insert(struct luo_session_header *sh,
>  			      struct luo_session *session)
>  {
> @@ -147,15 +222,6 @@ static int luo_session_insert(struct luo_session_header *sh,
>  
>  	guard(rwsem_write)(&sh->rwsem);
>  
> -	/*
> -	 * For outgoing we should make sure there is room in serialization array
> -	 * for new session.
> -	 */
> -	if (sh == &luo_session_global.outgoing) {
> -		if (sh->count == LUO_SESSION_MAX)
> -			return -ENOMEM;
> -	}
> -
>  	/*
>  	 * For small number of sessions this loop won't hurt performance
>  	 * but if we ever start using a lot of sessions, this might
> @@ -166,6 +232,20 @@ static int luo_session_insert(struct luo_session_header *sh,
>  		if (!strncmp(it->name, session->name, sizeof(it->name)))
>  			return -EEXIST;
>  	}
> +
> +	/*
> +	 * For outgoing we should make sure there is room in serialization array
> +	 * for new session. If not, allocate a new block.
> +	 */
> +	if (sh == &luo_session_global.outgoing) {
> +		if (sh->count == sh->nblocks * LUO_SESSION_BLOCK_MAX) {
> +			int err = luo_session_create_ser_block(sh);
> +
> +			if (err)
> +				return err;
> +		}
> +	}
> +

Since we just allocate space here and not actually fill it yet, I think
we can do the same check in luo_session_remove() to free blocks once
session count falls below (sh->nblocks - 1) * LUO_SESSION_BLOCK_MAX.
This prevents memory leak if the number of sessions goes too high at
some point and then falls back down.

Not that I think it is something likely to happen, but I don't see why
not.

Perhaps also abstract this out to a helper function for readability?

>  	list_add_tail(&session->list, &sh->list);
>  	sh->count++;
>  
> @@ -444,9 +524,12 @@ int __init luo_session_setup_outgoing(void *fdt_out)
>  	u64 header_ser_pa;
>  	int err;
>  
> -	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
> -	if (IS_ERR(header_ser))
> -		return PTR_ERR(header_ser);
> +	err = luo_session_create_ser_block(&luo_session_global.outgoing);
> +	if (err)
> +		return err;
> +
> +	header_ser = list_first_entry(&luo_session_global.outgoing.blocks,
> +				      struct luo_session_block, list)->ser;

I suppose it would be a tiny bit better to create a placeholder entry
here and then fill it up later in luo_session_serialize(). This would
result in the first block not being a special case and it can be
allocated and freed on demand list the rest of the blocks.

I won't insist on it but would be nice to have IMO if you're willing to
do the refactor.

>  	header_ser_pa = virt_to_phys(header_ser);
>  
>  	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
> @@ -459,19 +542,18 @@ int __init luo_session_setup_outgoing(void *fdt_out)
>  	if (err)
>  		goto err_unpreserve;
>  
> -	luo_session_global.outgoing.header_ser = header_ser;
> -	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
>  	luo_session_global.outgoing.active = true;
>  
>  	return 0;
>  
>  err_unpreserve:
> -	kho_unpreserve_free(header_ser);
> +	luo_session_destroy_ser_blocks(&luo_session_global.outgoing, true);
>  	return err;
>  }
>  
>  int __init luo_session_setup_incoming(void *fdt_in)
>  {
> +	struct luo_session_header *sh = &luo_session_global.incoming;
>  	struct luo_session_header_ser *header_ser;
>  	int err, header_size, offset;
>  	u64 header_ser_pa;
[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 12 06:58:23 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 15:58:23 +0200
Subject: [PATCH 2/5] liveupdate: Remove limit on the number of files per
 session
In-Reply-To: <20260414200237.444170-3-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Tue, 14 Apr 2026 20:02:34 +0000")
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
	<20260414200237.444170-3-pasha.tatashin@soleen.com>
Message-ID: <2vxzwlx8afwg.fsf@kernel.org>

On Tue, Apr 14 2026, Pasha Tatashin wrote:

> To remove the fixed limit on the number of preserved files per session,
> transition the file metadata serialization from a single contiguous memory
> block to a chain of linked blocks.

Same comment as previous patch about a general linked block data
structure here.

>
> ABI Changes:
> - Define 'struct luo_file_header_ser' to manage linked blocks in preserved
>   memory.
> - Update 'struct luo_file_set_ser' to point to the head of the block chain.
> - Bump the session ABI version to 'luo-session-v4' to reflect these breaking
>   changes.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  include/linux/kho/abi/luo.h      |  35 ++++-
>  kernel/liveupdate/luo_file.c     | 226 ++++++++++++++++++++-----------
>  kernel/liveupdate/luo_internal.h |  17 ++-
>  3 files changed, 189 insertions(+), 89 deletions(-)
>
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index f5732958545e..a15f6a0f3d93 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -84,8 +84,13 @@
>   *
>   *   - struct luo_session_ser:
>   *     Metadata for a single session, including its name and a physical pointer
> - *     to another preserved memory block containing an array of
> - *     `struct luo_file_ser` for all files in that session.
> + *     to the first `struct luo_file_header_ser` for all files in that session.
> + *     Multiple blocks are linked via the `next` field in the header.
> + *
> + *   - struct luo_file_header_ser:
> + *     Header for the file data block. Contains the physical address of the
> + *     next file data block and the number of `struct luo_file_ser` entries
> + *     that follow this header in the current block.
>   *
>   *   - struct luo_file_ser:
>   *     Metadata for a single preserved file. Contains the `compatible` string to
> @@ -134,11 +139,29 @@ struct luo_file_ser {
>  	u64 token;
>  } __packed;
>  
> +/**
> + * struct luo_file_header_ser - Header for the serialized file data block.
> + * @next:  Physical address of the next struct luo_file_header_ser.
> + * @count: The number of `struct luo_file_ser` entries that immediately
> + *         follow this header in the memory block.
> + *
> + * This structure is located at the beginning of a block of
> + * physical memory preserved across the kexec. It provides the necessary
> + * metadata to interpret the array of file entries that follow.
> + *
> + * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
> + */
> +struct luo_file_header_ser {

Nit: Same as before, can we rename it to make it clear that this is a
block header?

> +	u64 next;
> +	u64 count;
> +} __packed;
> +
>  /**
>   * struct luo_file_set_ser - Represents the serialized metadata for file set
> - * @files:   The physical address of a contiguous memory block that holds
> - *           the serialized state of files (array of luo_file_ser) in this file
> - *           set.
> + * @files:   The physical address of the first `struct luo_file_header_ser`.
> + *           This structure is the header for a block of memory containing
> + *           an array of `struct luo_file_ser` entries. Multiple blocks are
> + *           linked via the `next` field in the header.
>   * @count:   The total number of files that were part of this session during
>   *           serialization. Used for iteration and validation during
>   *           restoration.
> @@ -154,7 +177,7 @@ struct luo_file_set_ser {
>   *                          luo_session_header_ser
>   */
>  #define LUO_FDT_SESSION_NODE_NAME	"luo-session"
> -#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v3"
> +#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v4"
>  #define LUO_FDT_SESSION_HEADER		"luo-session-header"
>  
>  /**
> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> index dedbb8d02e6a..20a2d776cff4 100644
> --- a/kernel/liveupdate/luo_file.c
> +++ b/kernel/liveupdate/luo_file.c
> @@ -119,10 +119,12 @@ static LIST_HEAD(luo_file_handler_list);
>  /* Keep track of files being preserved by LUO */
>  static DEFINE_XARRAY(luo_preserved_files);
>  
> -/* 2 4K pages, give space for 128 files per file_set */
> +/* 2 4K pages, give space for 127 files per block */
>  #define LUO_FILE_PGCNT		2ul
> -#define LUO_FILE_MAX							\
> -	((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
> +#define LUO_FILE_BLOCK_MAX					\
> +	(((LUO_FILE_PGCNT << PAGE_SHIFT) -			\
> +	  sizeof(struct luo_file_header_ser)) /			\
> +	 sizeof(struct luo_file_ser))
>  
>  /**
>   * struct luo_file - Represents a single preserved file instance.
> @@ -158,7 +160,7 @@ static DEFINE_XARRAY(luo_preserved_files);
>   * and the serialized state handle returned by the handler's .preserve()
>   * operation.
>   *
> - * These instances are tracked in a per-file_set list. The @serialized_data
> + * These structures are tracked in a per-file_set list. The @serialized_data
>   * field, which holds a handle to the file's serialized state, may be updated
>   * during the .freeze() callback before being serialized for the next kernel.
>   * After reboot, these structures are recreated by luo_file_deserialize() and
> @@ -175,37 +177,68 @@ struct luo_file {
>  	u64 token;
>  };
>  
> -static int luo_alloc_files_mem(struct luo_file_set *file_set)
> +static int luo_file_add_block(struct luo_file_set *file_set,
> +			      struct luo_file_header_ser *ser)
>  {
> -	size_t size;
> -	void *mem;
> +	struct luo_file_block *block;
>  
> -	if (file_set->files)
> -		return 0;
> -
> -	WARN_ON_ONCE(file_set->count);
> +	if (file_set->nblocks >= LUO_MAX_BLOCKS)
> +		return -ENOSPC;
>  
> -	size = LUO_FILE_PGCNT << PAGE_SHIFT;
> -	mem = kho_alloc_preserve(size);
> -	if (IS_ERR(mem))
> -		return PTR_ERR(mem);
> +	block = kzalloc_obj(*block);
> +	if (!block)
> +		return -ENOMEM;
>  
> -	file_set->files = mem;
> +	block->ser = ser;
> +	list_add_tail(&block->list, &file_set->blocks);
> +	file_set->nblocks++;
>  
>  	return 0;
>  }
>  
> -static void luo_free_files_mem(struct luo_file_set *file_set)
> +static int luo_file_create_ser_block(struct luo_file_set *file_set)
>  {
> -	/* If file_set has files, no need to free preservation memory */
> -	if (file_set->count)
> -		return;
> +	struct luo_file_block *last = NULL;
> +	struct luo_file_header_ser *ser;
> +	int err;
>  
> -	if (!file_set->files)
> -		return;
> +	ser = kho_alloc_preserve(LUO_FILE_PGCNT << PAGE_SHIFT);
> +	if (IS_ERR(ser))
> +		return PTR_ERR(ser);
> +
> +	if (!list_empty(&file_set->blocks))
> +		last = list_last_entry(&file_set->blocks, struct luo_file_block, list);
> +
> +	err = luo_file_add_block(file_set, ser);
> +	if (err)
> +		goto err_unpreserve;
> +
> +	if (last)
> +		last->ser->next = virt_to_phys(ser);

Same comments as last patch about list_last_entry_or_null() and moving
this to luo_file_add_block().

> +
> +	return 0;
>  
> -	kho_unpreserve_free(file_set->files);
> -	file_set->files = NULL;
> +err_unpreserve:
> +	kho_unpreserve_free(ser);
> +	return err;
> +}
> +
> +static void luo_file_destroy_ser_blocks(struct luo_file_set *file_set,
> +					bool unpreserve)
> +{
> +	struct luo_file_block *block, *tmp;
> +
> +	list_for_each_entry_safe(block, tmp, &file_set->blocks, list) {
> +		if (block->ser) {

Same here. block is always created with its ser, so why have this check?

> +			if (unpreserve)
> +				kho_unpreserve_free(block->ser);
> +			else
> +				kho_restore_free(block->ser);

And same comment about the nastiness here, but once again I have nothing
better to offer.

> +		}
> +		list_del(&block->list);
> +		kfree(block);
> +		file_set->nblocks--;
> +	}
>  }
>  
>  static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
> @@ -247,7 +280,9 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
>   * 3. Iterates through registered handlers, calling can_preserve() to find one
>   *    compatible with the given @fd.
>   * 4. Calls the handler's .preserve() operation, which saves the file's state
> - *    and returns an opaque private data handle.
> + *    and returns an opaque u64 handle. This is typically performed while the
> + *    workload is still active to minimize the downtime during the
> + *    actual reboot transition.
>   * 5. Adds the new instance to the file_set's internal list.
>   *
>   * On success, LUO takes a reference to the 'struct file' and considers it
> @@ -277,17 +312,16 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
>  	if (luo_token_is_used(file_set, token))
>  		return -EEXIST;
>  
> -	if (file_set->count == LUO_FILE_MAX)
> -		return -ENOSPC;
> +	if (file_set->count == file_set->nblocks * LUO_FILE_BLOCK_MAX) {
> +		err = luo_file_create_ser_block(file_set);
> +		if (err)
> +			return err;
> +	}
>  
>  	file = fget(fd);
>  	if (!file)
>  		return -EBADF;
>  
> -	err = luo_alloc_files_mem(file_set);
> -	if (err)
> -		goto  err_fput;
> -
>  	err = -ENOENT;
>  	down_read(&luo_register_rwlock);
>  	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> @@ -301,7 +335,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
>  
>  	/* err is still -ENOENT if no handler was found */
>  	if (err)
> -		goto err_free_files_mem;
> +		goto err_fput;
>  
>  	/* safe to use fh because its module is pinned */
>  	err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
> @@ -345,8 +379,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
>  	xa_erase(&luo_preserved_files, luo_get_id(fh, file));
>  err_module_put:
>  	module_put(fh->ops->owner);
> -err_free_files_mem:
> -	luo_free_files_mem(file_set);
>  err_fput:
>  	fput(file);
>  
> @@ -399,7 +431,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
>  		kfree(luo_file);
>  	}
>  
> -	luo_free_files_mem(file_set);
> +	luo_file_destroy_ser_blocks(file_set, true);
>  }
>  
>  static int luo_file_freeze_one(struct luo_file_set *file_set,
> @@ -446,6 +478,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
>  				struct luo_file *failed_entry)
>  {
>  	struct list_head *files_list = &file_set->files_list;
> +	struct luo_file_block *block;
>  	struct luo_file *luo_file;
>  
>  	list_for_each_entry(luo_file, files_list, list) {
> @@ -455,7 +488,11 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
>  		luo_file_unfreeze_one(file_set, luo_file);
>  	}
>  
> -	memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
> +	list_for_each_entry(block, &file_set->blocks, list) {
> +		block->ser->count = 0;
> +		memset(block->ser + 1, 0,
> +		       (LUO_FILE_PGCNT << PAGE_SHIFT) - sizeof(*block->ser));
> +	}
>  }
>  
>  /**
> @@ -494,7 +531,8 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
>  int luo_file_freeze(struct luo_file_set *file_set,
>  		    struct luo_file_set_ser *file_set_ser)
>  {
> -	struct luo_file_ser *file_ser = file_set->files;
> +	struct luo_file_block *block;
> +	struct luo_file_ser *file_ser;
>  	struct luo_file *luo_file;
>  	int err;
>  	int i;
> @@ -502,11 +540,18 @@ int luo_file_freeze(struct luo_file_set *file_set,
>  	if (!file_set->count)
>  		return 0;
>  
> -	if (WARN_ON(!file_ser))
> -		return -EINVAL;
> +	block = list_first_entry(&file_set->blocks, struct luo_file_block, list);
> +	file_ser = (void *)(block->ser + 1);
>  
>  	i = 0;
>  	list_for_each_entry(luo_file, &file_set->files_list, list) {
> +		if (i == LUO_FILE_BLOCK_MAX) {
> +			block->ser->count = i;
> +			block = list_next_entry(block, list);
> +			file_ser = (void *)(block->ser + 1);
> +			i = 0;
> +		}
> +
>  		err = luo_file_freeze_one(file_set, luo_file);
>  		if (err < 0) {
>  			pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> @@ -521,10 +566,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
>  		file_ser[i].token = luo_file->token;
>  		i++;
>  	}
> +	block->ser->count = i;
>  
>  	file_set_ser->count = file_set->count;
> -	if (file_set->files)
> -		file_set_ser->files = virt_to_phys(file_set->files);
> +	if (!list_empty(&file_set->blocks)) {
> +		block = list_first_entry(&file_set->blocks, struct luo_file_block, list);
> +		file_set_ser->files = virt_to_phys(block->ser);
> +	}
>  
>  	return 0;
>  
> @@ -746,10 +794,7 @@ int luo_file_finish(struct luo_file_set *file_set)
>  		kfree(luo_file);
>  	}
>  
> -	if (file_set->files) {
> -		kho_restore_free(file_set->files);
> -		file_set->files = NULL;
> -	}
> +	luo_file_destroy_ser_blocks(file_set, false);
>  
>  	return 0;
>  }
> @@ -782,8 +827,10 @@ int luo_file_finish(struct luo_file_set *file_set)
>  int luo_file_deserialize(struct luo_file_set *file_set,
>  			 struct luo_file_set_ser *file_set_ser)
>  {
> -	struct luo_file_ser *file_ser;
> -	u64 i;
> +	struct luo_file_header_ser *header_ser;
> +	struct luo_file_block *block;
> +	u64 header_ser_pa;
> +	int err;
>  
>  	if (!file_set_ser->files) {
>  		WARN_ON(file_set_ser->count);
> @@ -791,7 +838,15 @@ int luo_file_deserialize(struct luo_file_set *file_set,
>  	}
>  
>  	file_set->count = file_set_ser->count;
> -	file_set->files = phys_to_virt(file_set_ser->files);
> +	header_ser_pa = file_set_ser->files;
> +
> +	while (header_ser_pa) {
> +		header_ser = phys_to_virt(header_ser_pa);
> +		err = luo_file_add_block(file_set, header_ser);
> +		if (err)
> +			return err;
> +		header_ser_pa = header_ser->next;
> +	}
>  
>  	/*
>  	 * Note on error handling:
> @@ -808,42 +863,51 @@ int luo_file_deserialize(struct luo_file_set *file_set,
>  	 * userspace to detect the failure and trigger a reboot, which will
>  	 * reliably reset devices and reclaim memory.
>  	 */
> -	file_ser = file_set->files;
> -	for (i = 0; i < file_set->count; i++) {
> -		struct liveupdate_file_handler *fh;
> -		bool handler_found = false;
> -		struct luo_file *luo_file;
> -
> -		down_read(&luo_register_rwlock);
> -		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> -			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
> -				if (try_module_get(fh->ops->owner))
> -					handler_found = true;
> -				break;
> -			}
> -		}
> -		up_read(&luo_register_rwlock);
> +	list_for_each_entry(block, &file_set->blocks, list) {
> +		struct luo_file_ser *file_ser = (void *)(block->ser + 1);
>  
> -		if (!handler_found) {
> -			pr_warn("No registered handler for compatible '%.*s'\n",
> -				(int)sizeof(file_ser[i].compatible),
> -				file_ser[i].compatible);
> -			return -ENOENT;
> +		if (block->ser->count > LUO_FILE_BLOCK_MAX) {
> +			pr_warn("File block contains too many entries: %llu\n",
> +				block->ser->count);
> +			return -EINVAL;
>  		}
>  
> -		luo_file = kzalloc_obj(*luo_file);
> -		if (!luo_file) {
> -			module_put(fh->ops->owner);
> -			return -ENOMEM;
> -		}
> +		for (int i = 0; i < block->ser->count; i++) {
> +			struct liveupdate_file_handler *fh;
> +			bool handler_found = false;
> +			struct luo_file *luo_file;
> +
> +			down_read(&luo_register_rwlock);
> +			list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> +				if (!strcmp(fh->compatible, file_ser[i].compatible)) {
> +					if (try_module_get(fh->ops->owner))
> +						handler_found = true;
> +					break;
> +				}
> +			}
> +			up_read(&luo_register_rwlock);
>  
> -		/* safe to use fh because its module is pinned */
> -		luo_file->fh = fh;
> -		luo_file->file = NULL;
> -		luo_file->serialized_data = file_ser[i].data;
> -		luo_file->token = file_ser[i].token;
> -		mutex_init(&luo_file->mutex);
> -		list_add_tail(&luo_file->list, &file_set->files_list);
> +			if (!handler_found) {
> +				pr_warn("No registered handler for compatible '%.*s'\n",
> +					(int)sizeof(file_ser[i].compatible),
> +					file_ser[i].compatible);
> +				return -ENOENT;
> +			}
> +
> +			luo_file = kzalloc_obj(*luo_file);
> +			if (!luo_file) {
> +				module_put(fh->ops->owner);
> +				return -ENOMEM;
> +			}
> +
> +			/* safe to use fh because its module is pinned */
> +			luo_file->fh = fh;
> +			luo_file->file = NULL;
> +			luo_file->serialized_data = file_ser[i].data;
> +			luo_file->token = file_ser[i].token;
> +			mutex_init(&luo_file->mutex);
> +			list_add_tail(&luo_file->list, &file_set->files_list);
> +		}

Nit: this diff is very hard to read. Perhaps splitting this into helper
functions would help?

>  	}
>  
>  	return 0;
> @@ -852,12 +916,14 @@ int luo_file_deserialize(struct luo_file_set *file_set,
>  void luo_file_set_init(struct luo_file_set *file_set)
>  {
>  	INIT_LIST_HEAD(&file_set->files_list);
> +	INIT_LIST_HEAD(&file_set->blocks);
>  }
>  
>  void luo_file_set_destroy(struct luo_file_set *file_set)
>  {
>  	WARN_ON(file_set->count);
>  	WARN_ON(!list_empty(&file_set->files_list));
> +	WARN_ON(!list_empty(&file_set->blocks));
>  }
>  
>  /**
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index a73f42069301..a92aaa9c073d 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -50,19 +50,30 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
>   */
>  #define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__)
>  
> +/**
> + * struct luo_file_block - Internal representation of a file serialization block.
> + * @list: List head for linking blocks in memory.
> + * @ser:  Pointer to the serialized header in preserved memory.
> + */
> +struct luo_file_block {
> +	struct list_head list;
> +	struct luo_file_header_ser *ser;
> +};
> +
>  /**
>   * struct luo_file_set - A set of files that belong to the same sessions.
>   * @files_list: An ordered list of files associated with this session, it is
>   *              ordered by preservation time.
> - * @files:      The physically contiguous memory block that holds the serialized
> - *              state of files.
> + * @blocks:     The list of serialization blocks (struct luo_file_block).
>   * @count:      A counter tracking the number of files currently stored in the
>   *              @files_list for this session.
> + * @nblocks:    The number of allocated serialization blocks.
>   */
>  struct luo_file_set {
>  	struct list_head files_list;
> -	struct luo_file_ser *files;
> +	struct list_head blocks;
>  	long count;
> +	long nblocks;
>  };
>  
>  /**

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 12 07:04:15 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 16:04:15 +0200
Subject: [PATCH 3/5] selftests/liveupdate: Test session and file limit
 removal
In-Reply-To: <20260414200237.444170-4-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Tue, 14 Apr 2026 20:02:35 +0000")
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
	<20260414200237.444170-4-pasha.tatashin@soleen.com>
Message-ID: <2vxzse7wafmo.fsf@kernel.org>

On Tue, Apr 14 2026, Pasha Tatashin wrote:

> With the removal of static limits on the number of sessions and files per
> session, the orchestrator now uses dynamic allocation.
>
> Add new test cases to verify that the system can handle a large number of
> sessions and files. These tests ensure that the dynamic block allocation
> and reuse logic for session metadata and outgoing files work correctly
> beyond the previous static limits.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  .../testing/selftests/liveupdate/liveupdate.c | 99 +++++++++++++++++++
>  1 file changed, 99 insertions(+)
>
> diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
> index 37c808fbe1e9..0eaf97b19267 100644
> --- a/tools/testing/selftests/liveupdate/liveupdate.c
> +++ b/tools/testing/selftests/liveupdate/liveupdate.c
> @@ -22,6 +22,7 @@
>  #include <fcntl.h>
>  #include <string.h>
>  #include <sys/ioctl.h>
> +#include <sys/resource.h>
>  #include <unistd.h>
>  
>  #include <linux/liveupdate.h>
> @@ -386,4 +387,102 @@ TEST_F(liveupdate_device, prevent_double_preservation)
>  	ASSERT_EQ(close(session_fd2), 0);
>  }
>  
> +static void ensure_nofile_limit(struct __test_metadata *_metadata,
> +				long min_limit)
> +{
> +	struct rlimit hl;
> +
> +	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
> +		ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno));
> +
> +	if (hl.rlim_cur >= min_limit)
> +		return;
> +
> +	hl.rlim_cur = min_limit;
> +	if (hl.rlim_cur > hl.rlim_max)
> +		hl.rlim_max = hl.rlim_cur;
> +
> +	if (setrlimit(RLIMIT_NOFILE, &hl) < 0) {
> +		if (errno == EPERM) {
> +			SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE to %ld",
> +			     hl.rlim_cur);
> +		}
> +		ksft_exit_fail_msg("setrlimit to %ld failed: %s\n",
> +				   hl.rlim_cur, strerror(errno));
> +	}
> +}
> +
> +/*
> + * Test Case: Manage Many Sessions
> + *
> + * Verifies that a large number of sessions can be created and then
> + * destroyed during normal system operation. This specifically tests the
> + * dynamic block allocation and reuse logic for session metadata management
> + * without preserving any files.
> + */
> +TEST_F(liveupdate_device, preserve_many_sessions)
> +{
> +#define MANY_SESSIONS 2000
> +	int session_fds[MANY_SESSIONS];
> +	int i;
> +
> +	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
> +	if (self->fd1 < 0 && errno == ENOENT)
> +		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
> +	ASSERT_GE(self->fd1, 0);
> +
> +	ensure_nofile_limit(_metadata, MANY_SESSIONS + 10);
> +	if (_metadata->exit_code == KSFT_SKIP)
> +		return;

Nit: This is strange. Why not just return errno in ensure_nofile_limit()
and handle the skipping here? Same below.

LGTM otherwise.

> +
> +	for (i = 0; i < MANY_SESSIONS; i++) {
> +		char name[64];
> +
> +		snprintf(name, sizeof(name), "many-session-%d", i);
> +		session_fds[i] = create_session(self->fd1, name);
> +		ASSERT_GE(session_fds[i], 0);
> +	}
> +
> +	for (i = 0; i < MANY_SESSIONS; i++)
> +		ASSERT_EQ(close(session_fds[i]), 0);
> +}
> +
> +/*
> + * Test Case: Preserve Many Files
> + *
> + * Verifies that a large number of files can be preserved in a single session
> + * and then destroyed during normal system operation. This tests the dynamic
> + * block allocation and management for outgoing files.
> + */
> +TEST_F(liveupdate_device, preserve_many_files)
> +{
> +#define MANY_FILES 500
> +	int mem_fds[MANY_FILES];
> +	int session_fd;
> +	int i;
> +
> +	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
> +	if (self->fd1 < 0 && errno == ENOENT)
> +		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
> +	ASSERT_GE(self->fd1, 0);
> +
> +	session_fd = create_session(self->fd1, "many-files-test");
> +	ASSERT_GE(session_fd, 0);
> +
> +	ensure_nofile_limit(_metadata, MANY_FILES + 10);
> +	if (_metadata->exit_code == KSFT_SKIP)
> +		return;
> +
> +	for (i = 0; i < MANY_FILES; i++) {
> +		mem_fds[i] = memfd_create("test-memfd", 0);
> +		ASSERT_GE(mem_fds[i], 0);
> +		ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
> +	}
> +
> +	for (i = 0; i < MANY_FILES; i++)
> +		ASSERT_EQ(close(mem_fds[i]), 0);
> +
> +	ASSERT_EQ(close(session_fd), 0);
> +}
> +
>  TEST_HARNESS_MAIN

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 12 07:09:06 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 16:09:06 +0200
Subject: [PATCH 4/5] selftests/liveupdate: Add stress-sessions kexec test
In-Reply-To: <20260414200237.444170-5-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Tue, 14 Apr 2026 20:02:36 +0000")
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
	<20260414200237.444170-5-pasha.tatashin@soleen.com>
Message-ID: <2vxzlddoafel.fsf@kernel.org>

On Tue, Apr 14 2026, Pasha Tatashin wrote:

> Add a new test that creates 2000 LUO sessions before a kexec
> reboot and verifies their presence after the reboot. This ensures
> that the linked-block serialization mechanism works correctly for
> a large number of sessions.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
> index 3c8721c505df..37c330b9bb36 100644
> --- a/tools/testing/selftests/liveupdate/luo_test_utils.c
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
> @@ -20,6 +20,7 @@
>  #include <sys/stat.h>
>  #include <errno.h>
>  #include <stdarg.h>
> +#include <sys/resource.h>
>  
>  #include "luo_test_utils.h"
>  
> @@ -28,6 +29,30 @@ int luo_open_device(void)
>  	return open(LUO_DEVICE, O_RDWR);
>  }
>  
> +void luo_ensure_nofile_limit(long min_limit)

Since you are adding this to luo_test_utils anyway, I think you should
do it in patch 3 and use it there too.

LGTM otherwise.

> +{
> +	struct rlimit hl;
> +
> +	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
> +		ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno));
> +
> +	if (hl.rlim_cur >= min_limit)
> +		return;
> +
> +	hl.rlim_cur = min_limit;
> +	if (hl.rlim_cur > hl.rlim_max)
> +		hl.rlim_max = hl.rlim_cur;
> +
> +	if (setrlimit(RLIMIT_NOFILE, &hl) < 0) {
> +		if (errno == EPERM) {
> +			ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE to %ld\n",
> +				       hl.rlim_cur);
> +		}
> +		ksft_exit_fail_msg("setrlimit to %ld failed: %s\n",
> +				   hl.rlim_cur, strerror(errno));
> +	}
> +}
> +
>  int luo_create_session(int luo_fd, const char *name)
>  {
>  	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 12 07:10:48 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 12 May 2026 16:10:48 +0200
Subject: [PATCH 5/5] selftests/liveupdate: Add stress-files kexec test
In-Reply-To: <20260414200237.444170-6-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Tue, 14 Apr 2026 20:02:37 +0000")
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
	<20260414200237.444170-6-pasha.tatashin@soleen.com>
Message-ID: <2vxzh5ocafbr.fsf@kernel.org>

On Tue, Apr 14 2026, Pasha Tatashin wrote:

> Add a new luo_stress_files kexec test that verifies preserving and
> retrieving 500 files across a kexec reboot.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pasha.tatashin at soleen.com  Tue May 12 08:55:18 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 12 May 2026 15:55:18 +0000
Subject: [PATCH 1/5] liveupdate: Remove limit on the number of sessions
In-Reply-To: <2vxz1pfgbvic.fsf@kernel.org>
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
 <20260414200237.444170-2-pasha.tatashin@soleen.com>
 <2vxz1pfgbvic.fsf@kernel.org>
Message-ID: <agMtfe19n4OlxXmS@plex>

On 05-12 15:35, Pratyush Yadav wrote:
> On Tue, Apr 14 2026, Pasha Tatashin wrote:
> 
> > Currently, the number of LUO sessions is limited by a fixed number of
> > pre-allocated pages for serialization (16 pages, allowing for ~819
> > sessions).
> >
> > This limitation is problematic if LUO is used to support things such as
> > systemd file descriptor store, and would be used not just as VM memory
> > but to save other states on the machine.
> >
> > Remove this limit by transitioning to a linked-block approach for
> > session metadata serialization. Instead of a single contiguous block,
> > session metadata is now stored in a chain of 16-page blocks. Each block
> > starts with a header containing the physical address of the next block
> > and the number of session entries in the current block.
> 
> We now have 3 variants of this linked block data structure: LUO
> sessions, LUO files, and KHO vmalloc. Is it time now to unify them into
> a reusable data structure? I proposed "KHO Array" some time ago. That
> was a collection of pointers, but perhaps we can generalize that to a
> collection of elements of arbitrary size?

I agree, especially since there are a couple more users incoming. I know 
Sami is proposing this for IOMMU as well. He and I have talked about 
creating a library that others could use. My suggestion is to get this 
in now and unify it later into something that works for all use cases, 
rather than changing it for every new one. The KHO ABI is not stable, so 
we can change it at any time, but I would still like to minimize the 
number of times we do so.

> [0] https://lore.kernel.org/linux-mm/20250909144426.33274-1-pratyush at kernel.org/T/#u
> 
> >
> > - Bump session ABI version to v3.
> > - Update struct luo_session_header_ser to include a 'next' pointer.
> > - Implement dynamic block allocation in luo_session_insert().
> > - Update setup, serialization, and deserialization logic to traverse
> >   the block chain.
> > - Remove LUO_SESSION_MAX limit.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  include/linux/kho/abi/luo.h      |  19 +--
> >  kernel/liveupdate/luo_internal.h |  12 +-
> >  kernel/liveupdate/luo_session.c  | 237 +++++++++++++++++++++++--------
> >  3 files changed, 197 insertions(+), 71 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> > index 46750a0ddf88..f5732958545e 100644
> > --- a/include/linux/kho/abi/luo.h
> > +++ b/include/linux/kho/abi/luo.h
> > @@ -57,9 +57,10 @@
> >   *   - compatible: "luo-session-v1"
> >   *     Identifies the session ABI version.
> >   *   - luo-session-header: u64
> > - *     The physical address of a `struct luo_session_header_ser`. This structure
> > - *     is the header for a contiguous block of memory containing an array of
> > - *     `struct luo_session_ser`, one for each preserved session.
> > + *     The physical address of the first `struct luo_session_header_ser`.
> > + *     This structure is the header for a block of memory containing an array
> > + *     of `struct luo_session_ser` entries. Multiple blocks are linked via
> > + *     the `next` field in the header.
> >   *
> >   * File-Lifecycle-Bound Node (luo-flb):
> >   *   This node describes all preserved global objects whose lifecycle is bound
> > @@ -77,9 +78,9 @@
> >   *   `__packed` structures. These structures contain the actual preserved state.
> >   *
> >   *   - struct luo_session_header_ser:
> > - *     Header for the session array. Contains the total page count of the
> > - *     preserved memory block and the number of `struct luo_session_ser`
> > - *     entries that follow.
> > + *     Header for the session data block. Contains the physical address of the
> > + *     next session data block and the number of `struct luo_session_ser`
> > + *     entries that follow this header in the current block.
> >   *
> >   *   - struct luo_session_ser:
> >   *     Metadata for a single session, including its name and a physical pointer
> > @@ -153,21 +154,23 @@ struct luo_file_set_ser {
> >   *                          luo_session_header_ser
> >   */
> >  #define LUO_FDT_SESSION_NODE_NAME	"luo-session"
> > -#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v2"
> > +#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v3"
> >  #define LUO_FDT_SESSION_HEADER		"luo-session-header"
> >  
> >  /**
> >   * struct luo_session_header_ser - Header for the serialized session data block.
> > + * @next:  Physical address of the next struct luo_session_header_ser.
> >   * @count: The number of `struct luo_session_ser` entries that immediately
> >   *         follow this header in the memory block.
> >   *
> > - * This structure is located at the beginning of a contiguous block of
> > + * This structure is located at the beginning of a block of
> >   * physical memory preserved across the kexec. It provides the necessary
> >   * metadata to interpret the array of session entries that follow.
> >   *
> >   * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
> >   */
> >  struct luo_session_header_ser {
> > +	u64 next;
> >  	u64 count;
> >  } __packed;
> >  
> > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> > index 875844d7a41d..a73f42069301 100644
> > --- a/kernel/liveupdate/luo_internal.h
> > +++ b/kernel/liveupdate/luo_internal.h
> > @@ -11,6 +11,16 @@
> >  #include <linux/liveupdate.h>
> >  #include <linux/uaccess.h>
> >  
> > +/*
> > + * Safeguard limit for the number of serialization blocks. This is used to
> > + * prevent infinite loops and excessive memory allocation in case of memory
> > + * corruption in the preserved state.
> > + *
> > + * This limit allows for ~8.1 million sessions and ~1.2 million files per
> > + * session, which is more than enough for all realistic use cases.
> > + */
> > +#define LUO_MAX_BLOCKS 10000
> > +
> >  struct luo_ucmd {
> >  	void __user *ubuffer;
> >  	u32 user_size;
> > @@ -59,7 +69,6 @@ struct luo_file_set {
> >   * struct luo_session - Represents an active or incoming Live Update session.
> >   * @name:       A unique name for this session, used for identification and
> >   *              retrieval.
> > - * @ser:        Pointer to the serialized data for this session.
> >   * @list:       A list_head member used to link this session into a global list
> >   *              of either outgoing (to be preserved) or incoming (restored from
> >   *              previous kernel) sessions.
> > @@ -70,7 +79,6 @@ struct luo_file_set {
> >   */
> >  struct luo_session {
> >  	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> > -	struct luo_session_ser *ser;
> 
> I was confused by this removal. Seeing this makes one think this got
> moved to some other place. But it seems like this was never used. I
> think it would be good to mention that in the commit message.

Let me move this to a separate commit, may be together with the comment 
fix below.

> 
> >  	struct list_head list;
> >  	bool retrieved;
> >  	struct luo_file_set file_set;
> > diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> > index 92b1af791889..007ca34eba79 100644
> > --- a/kernel/liveupdate/luo_session.c
> > +++ b/kernel/liveupdate/luo_session.c
> > @@ -69,30 +69,39 @@
> >  #include <uapi/linux/liveupdate.h>
> >  #include "luo_internal.h"
> >  
> > -/* 16 4K pages, give space for 744 sessions */
> > +/* 16 4K pages, give space for 819 sessions per block */
> 
> It seems odd to read that we added 8 bytes to the header and the number
> of sessions per block grew. But then I did the math and I think the
> number was always 819 sessions per block and adding the extra 8 bytes
> didn't make a difference.

Yeah, I updated the comment while at it, 744 was true in some earlier 
LUO versions, but was never updated.

> 
> >  #define LUO_SESSION_PGCNT	16ul
> > -#define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
> > +#define LUO_SESSION_BLOCK_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
> >  		sizeof(struct luo_session_header_ser)) /		\
> >  		sizeof(struct luo_session_ser))
> >  
> > +/**
> > + * struct luo_session_block - Internal representation of a session serialization block.
> > + * @list: List head for linking blocks in memory.
> > + * @ser:  Pointer to the serialized header in preserved memory.
> > + */
> > +struct luo_session_block {
> > +	struct list_head list;
> > +	struct luo_session_header_ser *ser;
> 
> Nit: luo_session_header_ser reads like it is the header for the entire
> list not for each block. Perhaps rename it to luo_block_header_ser?

Sounds good I will rename this to:
luo_session_block_header_ser and, in the next patch 
luo_file_block_header_ser accordingly.

> 
> > +};
> > +
> >  /**
> >   * struct luo_session_header - Header struct for managing LUO sessions.
> >   * @count:      The number of sessions currently tracked in the @list.
> > + * @nblocks:    The number of allocated serialization blocks.
> >   * @list:       The head of the linked list of `struct luo_session` instances.
> >   * @rwsem:      A read-write semaphore providing synchronized access to the
> >   *              session list and other fields in this structure.
> > - * @header_ser: The header data of serialization array.
> > - * @ser:        The serialized session data (an array of
> > - *              `struct luo_session_ser`).
> > + * @blocks:     The list of serialization blocks (struct luo_session_block).
> >   * @active:     Set to true when first initialized. If previous kernel did not
> >   *              send session data, active stays false for incoming.
> >   */
> >  struct luo_session_header {
> >  	long count;
> > +	long nblocks;
> >  	struct list_head list;
> >  	struct rw_semaphore rwsem;
> > -	struct luo_session_header_ser *header_ser;
> > -	struct luo_session_ser *ser;
> > +	struct list_head blocks;
> >  	bool active;
> >  };
> >  
> > @@ -110,10 +119,12 @@ static struct luo_session_global luo_session_global = {
> >  	.incoming = {
> >  		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
> >  		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
> > +		.blocks = LIST_HEAD_INIT(luo_session_global.incoming.blocks),
> >  	},
> >  	.outgoing = {
> >  		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
> >  		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
> > +		.blocks = LIST_HEAD_INIT(luo_session_global.outgoing.blocks),
> >  	},
> >  };
> >  
> > @@ -140,6 +151,70 @@ static void luo_session_free(struct luo_session *session)
> >  	kfree(session);
> >  }
> >  
> > +static int luo_session_add_block(struct luo_session_header *sh,
> > +				 struct luo_session_header_ser *ser)
> > +{
> > +	struct luo_session_block *block;
> > +
> > +	if (sh->nblocks >= LUO_MAX_BLOCKS)
> > +		return -ENOSPC;
> > +
> > +	block = kzalloc_obj(*block);
> > +	if (!block)
> > +		return -ENOMEM;
> > +
> > +	block->ser = ser;
> > +	list_add_tail(&block->list, &sh->blocks);
> > +	sh->nblocks++;
> > +
> > +	return 0;
> > +}
> > +
> > +static int luo_session_create_ser_block(struct luo_session_header *sh)
> > +{
> > +	struct luo_session_block *last = NULL;
> > +	struct luo_session_header_ser *ser;
> > +	int err;
> > +
> > +	ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
> > +	if (IS_ERR(ser))
> > +		return PTR_ERR(ser);
> > +
> > +	if (!list_empty(&sh->blocks))
> > +		last = list_last_entry(&sh->blocks, struct luo_session_block, list);
> 
> Nit: using list_last_entry_or_null() is a tiny bit cleaner.

Done.

> 
> > +
> > +	err = luo_session_add_block(sh, ser);
> > +	if (err)
> > +		goto err_unpreserve;
> > +
> > +	if (last)
> > +		last->ser->next = virt_to_phys(ser);
> 
> Nit: can you please move this to luo_session_add_block(). Logically this
> operation is a part of adding a block. You add a block to the list and
> then update the serialized state. So would be nice to have it done in
> one place.

Done.

> 
> > +
> > +	return 0;
> > +
> > +err_unpreserve:
> > +	kho_unpreserve_free(ser);
> > +	return err;
> > +}
> > +
> > +static void luo_session_destroy_ser_blocks(struct luo_session_header *sh,
> > +					   bool unpreserve)
> > +{
> > +	struct luo_session_block *block, *tmp;
> > +
> > +	list_for_each_entry_safe(block, tmp, &sh->blocks, list) {
> > +		if (block->ser) {
> 
> Block always has ser. Why this check?

Removed, thanks!

> 
> > +			if (unpreserve)
> > +				kho_unpreserve_free(block->ser);
> > +			else
> > +				kho_restore_free(block->ser);
> 
> Ugh, this is ugly. But I don't see anything obviously better. Perhaps we
> can check for sh == luo_session_global.outgoing but that is probably
> worse.
> 
> > +		}
> > +		list_del(&block->list);
> > +		kfree(block);
> > +		sh->nblocks--;
> > +	}
> > +}
> > +
> >  static int luo_session_insert(struct luo_session_header *sh,
> >  			      struct luo_session *session)
> >  {
> > @@ -147,15 +222,6 @@ static int luo_session_insert(struct luo_session_header *sh,
> >  
> >  	guard(rwsem_write)(&sh->rwsem);
> >  
> > -	/*
> > -	 * For outgoing we should make sure there is room in serialization array
> > -	 * for new session.
> > -	 */
> > -	if (sh == &luo_session_global.outgoing) {
> > -		if (sh->count == LUO_SESSION_MAX)
> > -			return -ENOMEM;
> > -	}
> > -
> >  	/*
> >  	 * For small number of sessions this loop won't hurt performance
> >  	 * but if we ever start using a lot of sessions, this might
> > @@ -166,6 +232,20 @@ static int luo_session_insert(struct luo_session_header *sh,
> >  		if (!strncmp(it->name, session->name, sizeof(it->name)))
> >  			return -EEXIST;
> >  	}
> > +
> > +	/*
> > +	 * For outgoing we should make sure there is room in serialization array
> > +	 * for new session. If not, allocate a new block.
> > +	 */
> > +	if (sh == &luo_session_global.outgoing) {
> > +		if (sh->count == sh->nblocks * LUO_SESSION_BLOCK_MAX) {
> > +			int err = luo_session_create_ser_block(sh);
> > +
> > +			if (err)
> > +				return err;
> > +		}
> > +	}
> > +
> 
> Since we just allocate space here and not actually fill it yet, I think
> we can do the same check in luo_session_remove() to free blocks once
> session count falls below (sh->nblocks - 1) * LUO_SESSION_BLOCK_MAX.
> This prevents memory leak if the number of sessions goes too high at
> some point and then falls back down.
> 
> Not that I think it is something likely to happen, but I don't see why
> not.

Done. I did not want to add this functionality just to keep the patch 
simpler, but since you brought it up, I am adding it here.

> 
> Perhaps also abstract this out to a helper function for readability?

Done

> 
> >  	list_add_tail(&session->list, &sh->list);
> >  	sh->count++;
> >  
> > @@ -444,9 +524,12 @@ int __init luo_session_setup_outgoing(void *fdt_out)
> >  	u64 header_ser_pa;
> >  	int err;
> >  
> > -	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
> > -	if (IS_ERR(header_ser))
> > -		return PTR_ERR(header_ser);
> > +	err = luo_session_create_ser_block(&luo_session_global.outgoing);
> > +	if (err)
> > +		return err;
> > +
> > +	header_ser = list_first_entry(&luo_session_global.outgoing.blocks,
> > +				      struct luo_session_block, list)->ser;
> 
> I suppose it would be a tiny bit better to create a placeholder entry
> here and then fill it up later in luo_session_serialize(). This would
> result in the first block not being a special case and it can be
> allocated and freed on demand list the rest of the blocks.
> 
> I won't insist on it but would be nice to have IMO if you're willing to
> do the refactor.

Let's keep it as is for now. Perhaps, we can return to this once we have 
a unifying library for KHO arrays.

> 
> >  	header_ser_pa = virt_to_phys(header_ser);
> >  
> >  	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
> > @@ -459,19 +542,18 @@ int __init luo_session_setup_outgoing(void *fdt_out)
> >  	if (err)
> >  		goto err_unpreserve;
> >  
> > -	luo_session_global.outgoing.header_ser = header_ser;
> > -	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
> >  	luo_session_global.outgoing.active = true;
> >  
> >  	return 0;
> >  
> >  err_unpreserve:
> > -	kho_unpreserve_free(header_ser);
> > +	luo_session_destroy_ser_blocks(&luo_session_global.outgoing, true);
> >  	return err;
> >  }
> >  
> >  int __init luo_session_setup_incoming(void *fdt_in)
> >  {
> > +	struct luo_session_header *sh = &luo_session_global.incoming;
> >  	struct luo_session_header_ser *header_ser;
> >  	int err, header_size, offset;
> >  	u64 header_ser_pa;
> [...]
> 
> -- 
> Regards,
> Pratyush Yadav


From tarunsahu at google.com  Tue May 12 09:21:44 2026
From: tarunsahu at google.com (tarunsahu at google.com)
Date: Tue, 12 May 2026 16:21:44 +0000
Subject: [PATCH v15 0/5] shut down devices asynchronously
In-Reply-To: <20260429175016.7915-1-djeffery@redhat.com>
References: <20260429175016.7915-1-djeffery@redhat.com>
Message-ID: <9huzh5ocmwdj.fsf@tarunix.c.googlers.com>

David Jeffery <djeffery at redhat.com> writes:

FWIW,
I have tested this series by opting it for NVMe devices and observed
significant performance benefit (from 9 sec to 2 sec).

Please feel free to use
Tested-by: Tarun Sahu <tarunsahu at google.com>

> This patchset allows the kernel to shutdown devices asynchronously and
> unrelated async devices to be shut down in parallel to each other.
>
> Only devices which explicitly enable it are shut down asynchronously. The
> default is for a device to be shut down from the synchronous shutdown loop.
>
> This can dramatically reduce system shutdown/reboot time on systems that
> have multiple devices that take many seconds to shut down (like certain
> NVMe drives). On one system tested, the shutdown time went from 11 minutes
> without this patch to 55 seconds with the patch. And on another system from
> 80 seconds to 11.
>
> And thank you to everyone who has spent some of their valuable time
> providing reviews, suggestions, criticisms, or tests on the various
> iterations of this patchset.
>
>
> Changes from V14:
>
> Remove unneeded use of '!!' with boolean type
>
> Changes from V13:
>
> Remove duplicate flagging of async shutdown on scsi hosts/targets/devices
>
> Changes from V12:
>
> Only acquire a parent reference if acquiring the parent's lock
> device_enable_async_shutdown should return void
> Minor comment and description cleanups
>
> Changes from V11:
>
>   * Swap the order of the first two patches
>   * Rework conditional parent locking so that lock and unlock no longer use
>     separate conditional checks
>   * Remove an used variable
>   * Comment and description text cleanups
>
> Changes from V10:
>
> Reworked to more closely match the design used for async suspend
>   * No longer uses async subsystem cookies for synchronization
>   * Minimized changes to struct device
>   * Enable async shutdown for pci and scsi devices which support async suspend
>
> Changes from V9:
>
> Address resource and timing issues when spawning a unique async thread
> for every device during shutdown:
>   * Make the asynchronous threads able to shut down multiple devices,
>     instead of spawning a unique thread for every device.
>   * Modify core kernel async code with a custom wake function so it
>     doesn't wake up a thread waiting to synchronize on a cookie until
>     the cookie has reached the desired value, instead of waking up
>     every waiting thread to check the cookie every time an async thread
>     ends.
>
> Changes from V8:
>
> Deal with shutdown hangs resulting when a parent/supplier device is
>   later in the devices_kset list than its children/consumers:
>   * Ignore sync_state_only devlinks for shutdown dependencies
>   * Ignore shutdown_after for devices that don't want async shutdown
>   * Add a sanity check to revert to sync shutdown for any device that
>     would otherwise wait for a child/consumer shutdown that hasn't
>     already been scheduled
>
> Changes from V7:
>
> Do not expose driver async_shutdown_enable in sysfs.
> Wrapped a long line.
>
> Changes from V6:
>
> Removed a sysfs attribute that allowed the async device shutdown to be
> "on" (with driver opt-out), "safe" (driver opt-in), or "off"... what was
> previously "safe" is now the only behavior, so drivers now only need to
> have the option to enable or disable async shutdown.
>
> Changes from V5:
>
> Separated into multiple patches to make review easier.
> Reworked some code to make it more readable
> Made devices wait for consumers to shut down, not just children
>   (suggested by David Jeffery)
>
> Changes from V4:
>
> Change code to use cookies for synchronization rather than async domains
> Allow async shutdown to be disabled via sysfs, and allow driver opt-in or
>   opt-out of async shutdown (when not disabled), with ability to control
>   driver opt-in/opt-out via sysfs
>   
> Changes from V3:
>
> Bug fix (used "parent" not "dev->parent" in device_shutdown)
>  
> Changes from V2:
>  
> Removed recursive functions to schedule children to be shutdown before
>   parents, since existing device_shutdown loop will already do this
>  
> Changes from V1:
>
> Rewritten using kernel async code (suggested by Lukas Wunner)
>
>
> Stuart Hayes (2):
>   driver core: separate function to shutdown one device
>   driver core: do not always lock parent in shutdown
>
> David Jeffery (3):
>   driver core: async device shutdown infrastructure
>   PCI: Enable async shutdown support
>   scsi: Enable async shutdown support
>
>  drivers/base/base.h       |   2 +
>  drivers/base/core.c       | 180 ++++++++++++++++++++++++++++++--------
>  drivers/pci/probe.c       |   2 +
>  drivers/scsi/hosts.c      |   2 +
>  drivers/scsi/scsi_sysfs.c |   3 +
>  include/linux/device.h    |  14 ++-
>  6 files changed, 166 insertions(+), 37 deletions(-)
>
> -- 
> 2.53.0


From ruanjinjie at huawei.com  Sun May 10 20:04:41 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:41 +0800
Subject: [PATCH v13 02/15] powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-3-ruanjinjie@huawei.com>

In get_crash_memory_ranges(), if crash_exclude_mem_range() failed
after realloc_mem_ranges() has successfully allocated the cmem
memory, it just returns an error but leaves cmem pointing to
the allocated memory, nor is it freed in the caller
update_crash_elfcorehdr(), which cause a memory leak, goto out
to free the cmem.

Cc: Sourabh Jain <sourabhjain at linux.ibm.com>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Fixes: 849599b702ef ("powerpc/crash: add crash memory hotplug support")
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/kexec/crash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index e6539f213b3d..a520f851c3a6 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -502,7 +502,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	ret = get_crash_memory_ranges(&cmem);
 	if (ret) {
 		pr_err("Failed to get crash mem range\n");
-		return;
+		goto out;
 	}
 
 	/*
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:39 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:39 +0800
Subject: [PATCH v13 00/15] arm64/riscv: Add support for crashkernel CMA reservation
Message-ID: <20260511030454.1730881-1-ruanjinjie@huawei.com>

The crash memory allocation, and the exclude of crashk_res, crashk_low_res
and crashk_cma memory are almost identical across different architectures,
This patch set handle them in crash core in a general way, which eliminate
a lot of duplication code.

And add support for crashkernel CMA reservation for arm64 and riscv.

Rebased on v7.1-rc1.

Basic second kernel boot test were performed on QEMU platforms for x86,
ARM64 and RISC-V architectures with the following parameters:

	"cma=256M crashkernel=4G crashkernel=64M,cma"

For first kernel, there will be such log:

	# dmesg | grep crash
	[    0.000000] crashkernel low memory reserved: 0xe8000000 - 0xf0000000 (128 MB)
	[    0.000000] crashkernel reserved: 0x000000023e600000 - 0x000000033e600000 (4096 MB)
	[    0.000000] crashkernel CMA reserved: 64 MB in 1 ranges

	# dmesg | grep cma
	[    0.000000] cma: Reserved 256 MiB at 0x00000000f0000000
	[    0.000000] cma: Reserved 64 MiB at 0x0000000100000000

For second kernel, there will be such log:

	[    0.000000] OF: fdt: Looking for usable-memory-range property...
	[    0.000000] OF: fdt: cap_mem_regions[0]: base=0x000000023e600000, size=0x0000000100000000
	[    0.000000] OF: fdt: cap_mem_regions[1]: base=0x00000000e8000000, size=0x0000000008000000
	[    0.000000] OF: fdt: cap_mem_regions[2]: base=0x0000000100000000, size=0x0000000004000000

Changes in v13:
- Rebased on v7.1-rc1.
- Update the commit message.
- Add Reviewed-by.
- Link to v12: https://lore.kernel.org/all/20260402072701.628293-1-ruanjinjie at huawei.com/

Changes in v12:
- Remove the unused "nr_mem_ranges" for x86.
- Add "Fix crashk_low_res not exclude bug" test log.
- Provide a separate patch for each architecture for using
  crash_prepare_headers(), which will make the review more convenient.
- Add Reviewed-by and Tested-by.
- Link to v11: https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie at huawei.com/

Changes in v11:
- Avoid silently drop crash memory if the crash kernel is built without
  CONFIG_CMA.
- Remove unnecessary "cmem->nr_ranges = 0" for arch_crash_populate_cmem()
  as we use kvzalloc().
- Provide a separate patch for each architecture to fix the existing
  buffer overflow issue.
- Add Acked-bys for arm64.

Changes in v10:
- Fix crashk_low_res not excluded bug in the existing
  RISC-V code.
- Fix an existing memory leak issue in the existing PowerPC code.
- Fix the ordering issue of adding CMA ranges to
  "linux,usable-memory-range".
- Fix an existing concurrency issue. A Concurrent memory hotplug may occur
  between reading memblock and attempting to fill cmem during kexec_load()
  for almost all existing architectures.
- Link to v9: https://lore.kernel.org/all/20260323072745.2481719-1-ruanjinjie at huawei.com/

Changes in v9:
- Collect Reviewed-by and Acked-by, and prepare for Sashiko AI review.
- Link to v8: https://lore.kernel.org/all/20260302035315.3892241-1-ruanjinjie at huawei.com/

Changes in v8:
- Fix the build issues reported by kernel test robot and Sourabh.
- Link to v7: https://lore.kernel.org/all/20260226130437.1867658-1-ruanjinjie at huawei.com/

Changes in v7:
- Correct the inclusion of CMA-reserved ranges for kdump kernel in of/kexec
  for arm64 and riscv.
- Add Acked-by.
- Link to v6: https://lore.kernel.org/all/20260224085342.387996-1-ruanjinjie at huawei.com/

Changes in v6:
- Update the crash core exclude code as Mike suggested.
- Rebased on v7.0-rc1.
- Add acked-by.
- Link to v5: https://lore.kernel.org/all/20260212101001.343158-1-ruanjinjie at huawei.com/

Jinjie Ruan (14):
  riscv: kexec_file: Fix crashk_low_res not exclude bug
  powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
  x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
  arm64: kexec_file: Fix potential buffer overflow in
    prepare_elf_headers()
  riscv: kexec_file: Fix potential buffer overflow in
    prepare_elf_headers()
  LoongArch: kexec: Fix potential buffer overflow in
    prepare_elf_headers()
  crash: Add crash_prepare_headers() to exclude crash kernel memory
  arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
  x86/kexec: Use crash_prepare_headers() helper to simplify code
  riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
  LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
  crash: Use crash_exclude_core_ranges() on powerpc
  arm64: kexec: Add support for crashkernel CMA reservation
  riscv: kexec: Add support for crashkernel CMA reservation

Sourabh Jain (1):
  powerpc/crash: sort crash memory ranges before preparing elfcorehdr

 .../admin-guide/kernel-parameters.txt         |  16 +--
 arch/arm64/kernel/machine_kexec_file.c        |  43 +++-----
 arch/arm64/mm/init.c                          |   5 +-
 arch/loongarch/kernel/machine_kexec_file.c    |  43 +++-----
 arch/powerpc/include/asm/kexec_ranges.h       |   1 -
 arch/powerpc/kexec/crash.c                    |   7 +-
 arch/powerpc/kexec/ranges.c                   | 101 +-----------------
 arch/riscv/kernel/machine_kexec_file.c        |  42 +++-----
 arch/riscv/mm/init.c                          |   5 +-
 arch/x86/kernel/crash.c                       |  92 +++-------------
 drivers/of/fdt.c                              |   9 +-
 drivers/of/kexec.c                            |   9 ++
 include/linux/crash_core.h                    |   9 ++
 include/linux/crash_reserve.h                 |   4 +-
 kernel/crash_core.c                           |  98 ++++++++++++++++-
 15 files changed, 202 insertions(+), 282 deletions(-)

-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:40 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:40 +0800
Subject: [PATCH v13 01/15] riscv: kexec_file: Fix crashk_low_res not exclude bug
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-2-ruanjinjie@huawei.com>

As done in commit 944a45abfabc ("arm64: kdump: Reimplement crashkernel=X")
and commit 4831be702b95 ("arm64/kexec: Fix missing extra range for
crashkres_low.") for arm64, while implementing crashkernel=X,[high,low],
riscv should have excluded the "crashk_low_res" reserved ranges from
the crash kernel memory to prevent them from being exported through
/proc/vmcore, and the exclusion would need an extra crash_mem range.

Just simply tested on qemu with crashkernel=4G with kexec in [1] mentioned
in [2]. And the second kernel can be started normally.

	# dmesg | grep crash
	[    0.000000] crashkernel low memory reserved: 0xf8000000 - 0x100000000 (128 MB)
	[    0.000000] crashkernel reserved: 0x000000017fe00000 - 0x000000027fe00000 (4096 MB)

Cc: Guo Ren <guoren at kernel.org>
Cc: Baoquan He <bhe at redhat.com>
[1]: https://github.com/chenjh005/kexec-tools/tree/build-test-riscv-v2
[2]: https://lore.kernel.org/all/20230726175000.2536220-1-chenjiahao16 at huawei.com/
Fixes: 5882e5acf18d ("riscv: kdump: Implement crashkernel=X,[high,low]")
Reviewed-by: Guo Ren <guoren at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 54e2d9552e93..3f7766057cac 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -61,7 +61,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	unsigned int nr_ranges;
 	int ret;
 
-	nr_ranges = 1; /* For exclusion of crashkernel region */
+	nr_ranges = 2; /* For exclusion of crashkernel region */
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
@@ -76,8 +76,16 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	/* Exclude crashkernel region */
 	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (!ret)
-		ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
+	if (ret)
+		goto out;
+
+	if (crashk_low_res.end) {
+		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		if (ret)
+			goto out;
+	}
+
+	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
 	kfree(cmem);
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:42 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:42 +0800
Subject: [PATCH v13 03/15] x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-4-ruanjinjie@huawei.com>

There is a race condition between the kexec_load() system call
(crash kernel loading path) and memory hotplug operations that can lead
to buffer overflow and potential kernel crash.

During prepare_elf_headers(), the following steps occur:
1. get_nr_ram_ranges_callback() queries current System RAM memory ranges
2. Allocates buffer based on queried count
3. prepare_elf64_ram_headers_callback() populates ranges from memblock

If memory hotplug occurs between step 1 and step 3, the number of ranges
can increase, causing out-of-bounds write when populating cmem->ranges[].

This happens because kexec_load() uses kexec_trylock (atomic_t) while
memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
with each other.

Since x86 supports crash hotplug, any data inconsistency caused by
a race during the initial load will be corrected by the subsequent
hotplug update. However, we must prevent a buffer overflow if the
number of memory regions increases between the two passes.

Add a boundary checking in prepare_elf64_ram_headers_callback() to ensure
that the number of populated ranges does not exceed
the allocated maximum.

Cc: Thomas Gleixner <tglx at kernel.org>
Cc: Ingo Molnar <mingo at redhat.com>
Cc: Borislav Petkov <bp at alien8.de>
Cc: "H. Peter Anvin" <hpa at zytor.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan He <bhe at redhat.com>
Cc: Mike Rapoport <rppt at kernel.org>
Cc: stable at vger.kernel.org
Fixes: 8d5f894a3108 ("x86: kexec_file: lift CRASH_MAX_RANGES limit on crash_mem buffer")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/x86/kernel/crash.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index cd796818d94d..fa6a1feb1bf1 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -226,6 +226,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
 
+	if (cmem->nr_ranges >= cmem->max_nr_ranges)
+		return -ENOMEM;
+
 	cmem->ranges[cmem->nr_ranges].start = res->start;
 	cmem->ranges[cmem->nr_ranges].end = res->end;
 	cmem->nr_ranges++;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:43 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:43 +0800
Subject: [PATCH v13 04/15] arm64: kexec_file: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-5-ruanjinjie@huawei.com>

There is a race condition between the kexec_load() system call
(crash kernel loading path) and memory hotplug operations that can
lead to buffer overflow and potential kernel crash.

During prepare_elf_headers(), the following steps occur:
1. The first for_each_mem_range() queries current System RAM memory ranges
2. Allocates buffer based on queried count
3. The 2st for_each_mem_range() populates ranges from memblock

If memory hotplug occurs between step 1 and step 3, the number of ranges
can increase, causing out-of-bounds write when populating cmem->ranges[].

This happens because kexec_load() uses kexec_trylock (atomic_t) while
memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
with each other.

Add the explicit bounds checking to prevent out-of-bounds access.

Cc: Catalin Marinas <catalin.marinas at arm.com>
Cc: Will Deacon <will.deacon at arm.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan He <bhe at redhat.com>
Cc: Breno Leitao <leitao at debian.org>
Cc: stable at vger.kernel.org
Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
Closes: https://sashiko.dev/#/patchset/20260323072745.2481719-1-ruanjinjie%40huawei.com
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/kernel/machine_kexec_file.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..a67e7b1abbab 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -59,6 +59,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	cmem->max_nr_ranges = nr_ranges;
 	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
+		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:44 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:44 +0800
Subject: [PATCH v13 05/15] riscv: kexec_file: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-6-ruanjinjie@huawei.com>

There is a race condition between the kexec_load() system call
(crash kernel loading path) and memory hotplug operations that can lead
to buffer overflow and potential kernel crash.

During prepare_elf_headers(), the following steps occur:
1. get_nr_ram_ranges_callback() queries current System RAM memory ranges
2. Allocates buffer based on queried count
3. prepare_elf64_ram_headers_callback() populates ranges from memblock

If memory hotplug occurs between step 1 and step 3, the number of ranges
can increase, causing out-of-bounds write when populating cmem->ranges[].

This happens because kexec_load() uses kexec_trylock (atomic_t) while
memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
with each other.

While this works today because RISC-V server hardware with hotplug
support is still rare and most deployments use fixed memory configurations
(e.g., QEMU virt machine), it is technically fragile. So add bounds
checking in prepare_elf64_ram_headers_callback() to prevent
out-of-bounds (OOB) access.

No functional change for current RISC-V deployments, but makes
the code robust against future hotplug-capable platforms.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Cc: songshuaishuai at tinylab.org
Cc: bjorn at rivosinc.com
Cc: leitao at debian.org
Fixes: 8acea455fafa ("RISC-V: Support for kexec_file on panic")
Reviewed-by: Guo Ren <guoren at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 3f7766057cac..773a1cba8ba0 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -48,6 +48,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
 
+	if (cmem->nr_ranges >= cmem->max_nr_ranges)
+		return -ENOMEM;
+
 	cmem->ranges[cmem->nr_ranges].start = res->start;
 	cmem->ranges[cmem->nr_ranges].end = res->end;
 	cmem->nr_ranges++;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:46 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:46 +0800
Subject: [PATCH v13 07/15] powerpc/crash: sort crash memory ranges before preparing elfcorehdr
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-8-ruanjinjie@huawei.com>

From: Sourabh Jain <sourabhjain at linux.ibm.com>

During a memory hot-remove event, the elfcorehdr is rebuilt to exclude
the removed memory. While updating the crash memory ranges for this
operation, the crash memory ranges array can become unsorted. This
happens because remove_mem_range() may split a memory range into two
parts and append the higher-address part as a separate range at the end
of the array.

So far, no issues have been observed due to the unsorted crash memory
ranges. However, this could lead to problems once crash memory range
removal is handled by generic code, as introduced in the upcoming
patches in this series.

Currently, powerpc uses a platform-specific function,
remove_mem_range(), to exclude hot-removed memory from the crash memory
ranges. This function performs the same task as the generic
crash_exclude_mem_range() in crash_core.c. The generic helper also
ensures that the crash memory ranges remain sorted. So remove the
redundant powerpc-specific implementation and instead call
crash_exclude_mem_range_guarded() (which internally calls
crash_exclude_mem_range()) to exclude the hot-removed memory ranges.

Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan he <bhe at redhat.com>
Cc: Jinjie Ruan <ruanjinjie at huawei.com>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Madhavan Srinivasan <maddy at linux.ibm.com>
Cc: Mahesh Salgaonkar <mahesh at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Cc: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Cc: Shivang Upadhyay <shivangu at linux.ibm.com>
Cc: linux-kernel at vger.kernel.org
Acked-by: Baoquan He <bhe at redhat.com>
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/include/asm/kexec_ranges.h |  4 +-
 arch/powerpc/kexec/crash.c              |  5 +-
 arch/powerpc/kexec/ranges.c             | 87 +------------------------
 3 files changed, 7 insertions(+), 89 deletions(-)

diff --git a/arch/powerpc/include/asm/kexec_ranges.h b/arch/powerpc/include/asm/kexec_ranges.h
index 14055896cbcb..ad95e3792d10 100644
--- a/arch/powerpc/include/asm/kexec_ranges.h
+++ b/arch/powerpc/include/asm/kexec_ranges.h
@@ -7,7 +7,9 @@
 void sort_memory_ranges(struct crash_mem *mrngs, bool merge);
 struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges);
 int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
-int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
+int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
+				    unsigned long long mstart,
+				    unsigned long long mend);
 int get_exclude_memory_ranges(struct crash_mem **mem_ranges);
 int get_reserved_memory_ranges(struct crash_mem **mem_ranges);
 int get_crash_memory_ranges(struct crash_mem **mem_ranges);
diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index a520f851c3a6..d634db67becc 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -493,7 +493,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	struct crash_mem *cmem = NULL;
 	struct kexec_segment *ksegment;
 	void *ptr, *mem, *elfbuf = NULL;
-	unsigned long elfsz, memsz, base_addr, size;
+	unsigned long elfsz, memsz, base_addr, size, end;
 
 	ksegment = &image->segment[image->elfcorehdr_index];
 	mem = (void *) ksegment->mem;
@@ -512,7 +512,8 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	if (image->hp_action == KEXEC_CRASH_HP_REMOVE_MEMORY) {
 		base_addr = PFN_PHYS(mn->start_pfn);
 		size = mn->nr_pages * PAGE_SIZE;
-		ret = remove_mem_range(&cmem, base_addr, size);
+		end = base_addr + size - 1;
+		ret = crash_exclude_mem_range_guarded(&cmem, base_addr, end);
 		if (ret) {
 			pr_err("Failed to remove hot-unplugged memory from crash memory ranges\n");
 			goto out;
diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c
index 867135560e5c..6c58bcc3e130 100644
--- a/arch/powerpc/kexec/ranges.c
+++ b/arch/powerpc/kexec/ranges.c
@@ -553,7 +553,7 @@ int get_usable_memory_ranges(struct crash_mem **mem_ranges)
 #endif /* CONFIG_KEXEC_FILE */
 
 #ifdef CONFIG_CRASH_DUMP
-static int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
+int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
 					   unsigned long long mstart,
 					   unsigned long long mend)
 {
@@ -641,89 +641,4 @@ int get_crash_memory_ranges(struct crash_mem **mem_ranges)
 		pr_err("Failed to setup crash memory ranges\n");
 	return ret;
 }
-
-/**
- * remove_mem_range - Removes the given memory range from the range list.
- * @mem_ranges:    Range list to remove the memory range to.
- * @base:          Base address of the range to remove.
- * @size:          Size of the memory range to remove.
- *
- * (Re)allocates memory, if needed.
- *
- * Returns 0 on success, negative errno on error.
- */
-int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
-{
-	u64 end;
-	int ret = 0;
-	unsigned int i;
-	u64 mstart, mend;
-	struct crash_mem *mem_rngs = *mem_ranges;
-
-	if (!size)
-		return 0;
-
-	/*
-	 * Memory range are stored as start and end address, use
-	 * the same format to do remove operation.
-	 */
-	end = base + size - 1;
-
-	for (i = 0; i < mem_rngs->nr_ranges; i++) {
-		mstart = mem_rngs->ranges[i].start;
-		mend = mem_rngs->ranges[i].end;
-
-		/*
-		 * Memory range to remove is not part of this range entry
-		 * in the memory range list
-		 */
-		if (!(base >= mstart && end <= mend))
-			continue;
-
-		/*
-		 * Memory range to remove is equivalent to this entry in the
-		 * memory range list. Remove the range entry from the list.
-		 */
-		if (base == mstart && end == mend) {
-			for (; i < mem_rngs->nr_ranges - 1; i++) {
-				mem_rngs->ranges[i].start = mem_rngs->ranges[i+1].start;
-				mem_rngs->ranges[i].end = mem_rngs->ranges[i+1].end;
-			}
-			mem_rngs->nr_ranges--;
-			goto out;
-		}
-		/*
-		 * Start address of the memory range to remove and the
-		 * current memory range entry in the list is same. Just
-		 * move the start address of the current memory range
-		 * entry in the list to end + 1.
-		 */
-		else if (base == mstart) {
-			mem_rngs->ranges[i].start = end + 1;
-			goto out;
-		}
-		/*
-		 * End address of the memory range to remove and the
-		 * current memory range entry in the list is same.
-		 * Just move the end address of the current memory
-		 * range entry in the list to base - 1.
-		 */
-		else if (end == mend)  {
-			mem_rngs->ranges[i].end = base - 1;
-			goto out;
-		}
-		/*
-		 * Memory range to remove is not at the edge of current
-		 * memory range entry. Split the current memory entry into
-		 * two half.
-		 */
-		else {
-			size = mem_rngs->ranges[i].end - end + 1;
-			mem_rngs->ranges[i].end = base - 1;
-			ret = add_mem_range(mem_ranges, end + 1, size);
-		}
-	}
-out:
-	return ret;
-}
 #endif /* CONFIG_CRASH_DUMP */
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:45 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:45 +0800
Subject: [PATCH v13 06/15] LoongArch: kexec: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-7-ruanjinjie@huawei.com>

There is a race condition between the kexec_load() system call
(crash kernel loading path) and memory hotplug operations that can lead
to buffer overflow and potential kernel crash.

During prepare_elf_headers(), the following steps occur:
1. The first for_each_mem_range()  queries current System RAM memory ranges
2. Allocates buffer based on queried count
3. The 2st for_each_mem_range() populates ranges from memblock

If memory hotplug occurs between step 1 and step 3, the number of ranges
can increase, causing out-of-bounds write when populating cmem->ranges[].

This happens because kexec_load() uses kexec_trylock (atomic_t) while
memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
with each other.

Just add bounds checking to prevent out-of-bounds access.

Cc: Youling Tang <tangyouling at kylinos.cn>
Cc: Huacai Chen <chenhuacai at loongson.cn>
Cc: WANG Xuerui <kernel at xen0n.name>
Cc: stable at vger.kernel.org
Fixes: 1bcca8620a91 ("LoongArch: Add crash dump support for kexec_file")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..167392c1da33 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -75,6 +75,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	cmem->max_nr_ranges = nr_ranges;
 	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
+		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:47 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:47 +0800
Subject: [PATCH v13 08/15] crash: Add crash_prepare_headers() to exclude crash kernel memory
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-9-ruanjinjie@huawei.com>

The crash memory alloc, and the exclude of crashk_res, crashk_low_res
and crashk_cma memory are almost identical across different architectures,
handling them in the crash core would eliminate a lot of duplication, so
add crash_prepare_headers() helper to handle them in the common code.

To achieve the above goal, three architecture-specific functions are
introduced:

- arch_get_system_nr_ranges(). Pre-counts the max number of memory ranges.

- arch_crash_populate_cmem(). Collects the memory ranges and fills them
  into cmem.

- arch_crash_exclude_ranges(). Architecture's additional crash memory
  ranges exclusion, defaulting to empty.

Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 include/linux/crash_core.h |  5 +++
 kernel/crash_core.c        | 91 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index c1dee3f971a9..583ffcc703d4 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -59,6 +59,8 @@ extern int crash_exclude_mem_range(struct crash_mem *mem,
 				   unsigned long long mend);
 extern int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 				       void **addr, unsigned long *sz);
+extern int crash_prepare_headers(int need_kernel_map, void **addr,
+				 unsigned long *sz, unsigned long *nr_mem_ranges);
 
 struct kimage;
 struct kexec_segment;
@@ -76,6 +78,9 @@ int kexec_should_crash(struct task_struct *p);
 int kexec_crash_loaded(void);
 void crash_save_cpu(struct pt_regs *regs, int cpu);
 extern int kimage_crash_copy_vmcoreinfo(struct kimage *image);
+extern unsigned int arch_get_system_nr_ranges(void);
+extern int arch_crash_populate_cmem(struct crash_mem *cmem);
+extern int arch_crash_exclude_ranges(struct crash_mem *cmem);
 
 #else /* !CONFIG_CRASH_DUMP*/
 struct pt_regs;
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index 4f21fc3b108b..d28be3890efb 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -16,6 +16,7 @@
 #include <linux/mm.h>
 #include <linux/cpuhotplug.h>
 #include <linux/memblock.h>
+#include <linux/memory_hotplug.h>
 #include <linux/kmemleak.h>
 #include <linux/crash_core.h>
 #include <linux/reboot.h>
@@ -168,9 +169,6 @@ static inline resource_size_t crash_resource_size(const struct resource *res)
 	return !res->end ? 0 : resource_size(res);
 }
 
-
-
-
 int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 			  void **addr, unsigned long *sz)
 {
@@ -272,6 +270,93 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 	return 0;
 }
 
+static struct crash_mem *alloc_cmem(unsigned int nr_ranges)
+{
+	struct crash_mem *cmem;
+
+	cmem = kvzalloc_flex(*cmem, ranges, nr_ranges);
+	if (!cmem)
+		return NULL;
+
+	cmem->max_nr_ranges = nr_ranges;
+	return cmem;
+}
+
+unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
+int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
+int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
+
+static int crash_exclude_core_ranges(struct crash_mem *cmem)
+{
+	int ret, i;
+
+	/* Exclude crashkernel region */
+	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	if (ret)
+		return ret;
+
+	if (crashk_low_res.end) {
+		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < crashk_cma_cnt; ++i) {
+		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
+					      crashk_cma_ranges[i].end);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
+			  unsigned long *nr_mem_ranges)
+{
+	unsigned int max_nr_ranges;
+	struct crash_mem *cmem;
+	int ret;
+
+	get_online_mems();
+	max_nr_ranges = arch_get_system_nr_ranges();
+	if (!max_nr_ranges) {
+		put_online_mems();
+		return -ENOMEM;
+	}
+
+	cmem = alloc_cmem(max_nr_ranges);
+	if (!cmem) {
+		put_online_mems();
+		return -ENOMEM;
+	}
+
+	ret = arch_crash_populate_cmem(cmem);
+	if (ret) {
+		put_online_mems();
+		goto out;
+	}
+
+	put_online_mems();
+	ret = crash_exclude_core_ranges(cmem);
+	if (ret)
+		goto out;
+
+	ret = arch_crash_exclude_ranges(cmem);
+	if (ret)
+		goto out;
+
+	/* Return the computed number of memory ranges, for hotplug usage */
+	if (nr_mem_ranges)
+		*nr_mem_ranges = cmem->nr_ranges;
+
+	ret = crash_prepare_elf64_headers(cmem, need_kernel_map, addr, sz);
+
+out:
+	kvfree(cmem);
+	return ret;
+}
+
 /**
  * crash_exclude_mem_range - exclude a mem range for existing ranges
  * @mem: mem->range contains an array of ranges sorted in ascending order
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:48 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:48 +0800
Subject: [PATCH v13 09/15] arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-10-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash
kernel memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Use for_each_mem_range() to traverse
  and pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use for_each_mem_range to traverse
  and collect the memory ranges and fills them into cmem.

Acked-by: Catalin Marinas <catalin.marinas at arm.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/kernel/machine_kexec_file.c | 46 ++++++++------------------
 1 file changed, 14 insertions(+), 32 deletions(-)

diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index a67e7b1abbab..8d72038f206d 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -40,51 +40,33 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 }
 
 #ifdef CONFIG_CRASH_DUMP
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	struct crash_mem *cmem;
-	unsigned int nr_ranges;
-	int ret;
-	u64 i;
+	unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
+	u64 i;
 
-	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
 
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
+	return nr_ranges;
+}
+
+int arch_crash_populate_cmem(struct crash_mem *cmem)
+{
+	phys_addr_t start, end;
+	u64 i;
 
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
-		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
-			ret = -ENOMEM;
-			goto out;
-		}
+		if (cmem->nr_ranges >= cmem->max_nr_ranges)
+			return -ENOMEM;
 
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return 0;
 }
 #endif
 
@@ -114,7 +96,7 @@ int load_other_segments(struct kimage *image,
 	void *headers;
 	unsigned long headers_sz;
 	if (image->type == KEXEC_TYPE_CRASH) {
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
 		if (ret) {
 			pr_err("Preparing elf core header failed\n");
 			goto out_err;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:49 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:49 +0800
Subject: [PATCH v13 10/15] x86/kexec: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-11-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following three architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Call get_nr_ram_ranges_callback()
  to pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use prepare_elf64_ram_headers_callback()
  to collect the memory ranges and fills them into cmem.

- arch_crash_exclude_ranges(). Exclude the low 1M for x86.

By the way, remove the unused "nr_mem_ranges" in
arch_crash_handle_hotplug_event().

Cc: Thomas Gleixner <tglx at kernel.org>
Cc: Ingo Molnar <mingo at redhat.com>
Cc: Borislav Petkov <bp at alien8.de>
Cc: Dave Hansen <dave.hansen at linux.intel.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Vivek Goyal <vgoyal at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/x86/kernel/crash.c | 89 +++++------------------------------------
 1 file changed, 11 insertions(+), 78 deletions(-)

diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index fa6a1feb1bf1..8cf6e115196e 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -153,16 +153,8 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-/* Gather all the required information to prepare elf headers for ram regions */
-static struct crash_mem *fill_up_crash_elf_data(void)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 0;
-	struct crash_mem *cmem;
-
-	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
-	if (!nr_ranges)
-		return NULL;
-
 	/*
 	 * Exclusion of crash region, crashk_low_res and/or crashk_cma_ranges
 	 * may cause range splits. So add extra slots here.
@@ -177,49 +169,16 @@ static struct crash_mem *fill_up_crash_elf_data(void)
 	 * But in order to lest the low 1M could be changed in the future,
 	 * (e.g. [start, 1M]), add a extra slot.
 	 */
-	nr_ranges += 3 + crashk_cma_cnt;
-	cmem = vzalloc(struct_size(cmem, ranges, nr_ranges));
-	if (!cmem)
-		return NULL;
-
-	cmem->max_nr_ranges = nr_ranges;
+	unsigned int nr_ranges = 3 + crashk_cma_cnt;
 
-	return cmem;
+	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+	return nr_ranges;
 }
 
-/*
- * Look for any unwanted ranges between mstart, mend and remove them. This
- * might lead to split and split ranges are put in cmem->ranges[] array
- */
-static int elf_header_exclude_ranges(struct crash_mem *cmem)
+int arch_crash_exclude_ranges(struct crash_mem *cmem)
 {
-	int ret = 0;
-	int i;
-
 	/* Exclude the low 1M because it is always reserved */
-	ret = crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
-	if (ret)
-		return ret;
-
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		return ret;
-
-	if (crashk_low_res.end)
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start,
-					      crashk_low_res.end);
-	if (ret)
-		return ret;
-
-	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
+	return crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
 }
 
 static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
@@ -236,35 +195,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-/* Prepare elf headers. Return addr and size */
-static int prepare_elf_headers(void **addr, unsigned long *sz,
-			       unsigned long *nr_mem_ranges)
+int arch_crash_populate_cmem(struct crash_mem *cmem)
 {
-	struct crash_mem *cmem;
-	int ret;
-
-	cmem = fill_up_crash_elf_data();
-	if (!cmem)
-		return -ENOMEM;
-
-	ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
-	if (ret)
-		goto out;
-
-	/* Exclude unwanted mem ranges */
-	ret = elf_header_exclude_ranges(cmem);
-	if (ret)
-		goto out;
-
-	/* Return the computed number of memory ranges, for hotplug usage */
-	*nr_mem_ranges = cmem->nr_ranges;
-
-	/* By default prepare 64bit headers */
-	ret = crash_prepare_elf64_headers(cmem, IS_ENABLED(CONFIG_X86_64), addr, sz);
-
-out:
-	vfree(cmem);
-	return ret;
+	return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
 }
 #endif
 
@@ -422,7 +355,8 @@ int crash_load_segments(struct kimage *image)
 				  .buf_max = ULONG_MAX, .top_down = false };
 
 	/* Prepare elf headers and add a segment */
-	ret = prepare_elf_headers(&kbuf.buffer, &kbuf.bufsz, &pnum);
+	ret = crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &kbuf.buffer,
+				    &kbuf.bufsz, &pnum);
 	if (ret)
 		return ret;
 
@@ -515,7 +449,6 @@ unsigned int arch_crash_get_elfcorehdr_size(void)
 void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
 {
 	void *elfbuf = NULL, *old_elfcorehdr;
-	unsigned long nr_mem_ranges;
 	unsigned long mem, memsz;
 	unsigned long elfsz = 0;
 
@@ -533,7 +466,7 @@ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
 	 * Create the new elfcorehdr reflecting the changes to CPU and/or
 	 * memory resources.
 	 */
-	if (prepare_elf_headers(&elfbuf, &elfsz, &nr_mem_ranges)) {
+	if (crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &elfbuf, &elfsz, NULL)) {
 		pr_err("unable to create new elfcorehdr");
 		goto out;
 	}
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:50 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:50 +0800
Subject: [PATCH v13 11/15] riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-12-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Call get_nr_ram_ranges_callback()
  to pre-counts the max number of memory ranges.

- arch_crash_populate_cmem(). Use prepare_elf64_ram_headers_callback()
  to collects the memory ranges and fills them into cmem.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Cc: Guo Ren <guoren at kernel.org>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 47 +++++++-------------------
 1 file changed, 12 insertions(+), 35 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 773a1cba8ba0..bea818f75dd6 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -44,6 +44,15 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 	return 0;
 }
 
+unsigned int arch_get_system_nr_ranges(void)
+{
+	unsigned int nr_ranges = 2; /* For exclusion of crashkernel region */
+
+	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+
+	return nr_ranges;
+}
+
 static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
@@ -58,41 +67,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+int arch_crash_populate_cmem(struct crash_mem *cmem)
 {
-	struct crash_mem *cmem;
-	unsigned int nr_ranges;
-	int ret;
-
-	nr_ranges = 2; /* For exclusion of crashkernel region */
-	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
-
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
-
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
-	ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
-	if (ret)
-		goto out;
-
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
 }
 
 static char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
@@ -284,7 +261,7 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start,
 	if (image->type == KEXEC_TYPE_CRASH) {
 		void *headers;
 		unsigned long headers_sz;
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
 		if (ret) {
 			pr_err("Preparing elf core header failed\n");
 			goto out;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:51 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:51 +0800
Subject: [PATCH v13 12/15] LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-13-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Use for_each_mem_range to traverse
  and pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use for_each_mem_range to traverse
  and collect the memory ranges and fills them into cmem.

Cc: Huacai Chen <chenhuacai at kernel.org>
Cc: WANG Xuerui <kernel at xen0n.name>
Cc: Youling Tang <tangyouling at kylinos.cn>
Cc: Baoquan He <bhe at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 46 +++++++---------------
 1 file changed, 14 insertions(+), 32 deletions(-)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 167392c1da33..3d0386ee18ef 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -56,51 +56,33 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
 }
 
 #ifdef CONFIG_CRASH_DUMP
-
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	int ret, nr_ranges;
-	uint64_t i;
+	int nr_ranges = 2; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
-	struct crash_mem *cmem;
+	uint64_t i;
 
-	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
 
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
+	return nr_ranges;
+}
+
+int arch_crash_populate_cmem(struct crash_mem *cmem)
+{
+	phys_addr_t start, end;
+	uint64_t i;
 
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
-		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
-			ret = -ENOMEM;
-			goto out;
-		}
+		if (cmem->nr_ranges >= cmem->max_nr_ranges)
+			return -ENOMEM;
 
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret < 0)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret < 0)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return 0;
 }
 
 /*
@@ -168,7 +150,7 @@ int load_other_segments(struct kimage *image,
 		void *headers;
 		unsigned long headers_sz;
 
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
 		if (ret < 0) {
 			pr_err("Preparing elf core header failed\n");
 			goto out_err;
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:52 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:52 +0800
Subject: [PATCH v13 13/15] crash: Use crash_exclude_core_ranges() on powerpc
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-14-ruanjinjie@huawei.com>

The crash memory exclude of crashk_res and crashk_cma memory on powerpc
are almost identical to the generic crash_exclude_core_ranges().

By introducing the architecture-specific arch_crash_exclude_mem_range()
function with a default implementation of crash_exclude_mem_range(),
and using crash_exclude_mem_range_guarded as powerpc's separate
implementation, the generic crash_exclude_core_ranges() helper function
can be reused.

Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Madhavan Srinivasan <maddy at linux.ibm.com>
Cc: Mahesh Salgaonkar <mahesh at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Cc: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Cc: Shivang Upadhyay <shivangu at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/include/asm/kexec_ranges.h |  3 ---
 arch/powerpc/kexec/crash.c              |  2 +-
 arch/powerpc/kexec/ranges.c             | 16 ++++------------
 include/linux/crash_core.h              |  4 ++++
 kernel/crash_core.c                     | 19 +++++++++++++------
 5 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/arch/powerpc/include/asm/kexec_ranges.h b/arch/powerpc/include/asm/kexec_ranges.h
index ad95e3792d10..8489e844b447 100644
--- a/arch/powerpc/include/asm/kexec_ranges.h
+++ b/arch/powerpc/include/asm/kexec_ranges.h
@@ -7,9 +7,6 @@
 void sort_memory_ranges(struct crash_mem *mrngs, bool merge);
 struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges);
 int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
-int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
-				    unsigned long long mstart,
-				    unsigned long long mend);
 int get_exclude_memory_ranges(struct crash_mem **mem_ranges);
 int get_reserved_memory_ranges(struct crash_mem **mem_ranges);
 int get_crash_memory_ranges(struct crash_mem **mem_ranges);
diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index d634db67becc..775895f31037 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -513,7 +513,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 		base_addr = PFN_PHYS(mn->start_pfn);
 		size = mn->nr_pages * PAGE_SIZE;
 		end = base_addr + size - 1;
-		ret = crash_exclude_mem_range_guarded(&cmem, base_addr, end);
+		ret = arch_crash_exclude_mem_range(&cmem, base_addr, end);
 		if (ret) {
 			pr_err("Failed to remove hot-unplugged memory from crash memory ranges\n");
 			goto out;
diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c
index 6c58bcc3e130..e5fea23b191b 100644
--- a/arch/powerpc/kexec/ranges.c
+++ b/arch/powerpc/kexec/ranges.c
@@ -553,9 +553,9 @@ int get_usable_memory_ranges(struct crash_mem **mem_ranges)
 #endif /* CONFIG_KEXEC_FILE */
 
 #ifdef CONFIG_CRASH_DUMP
-int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
-					   unsigned long long mstart,
-					   unsigned long long mend)
+int arch_crash_exclude_mem_range(struct crash_mem **mem_ranges,
+				 unsigned long long mstart,
+				 unsigned long long mend)
 {
 	struct crash_mem *tmem = *mem_ranges;
 
@@ -604,18 +604,10 @@ int get_crash_memory_ranges(struct crash_mem **mem_ranges)
 			sort_memory_ranges(*mem_ranges, true);
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_res.start, crashk_res.end);
+	ret = crash_exclude_core_ranges(mem_ranges);
 	if (ret)
 		goto out;
 
-	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
-		if (ret)
-			goto out;
-	}
-
 	/*
 	 * FIXME: For now, stay in parity with kexec-tools but if RTAS/OPAL
 	 *        regions are exported to save their context at the time of
diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index 583ffcc703d4..bc087124cd78 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -61,6 +61,7 @@ extern int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_ma
 				       void **addr, unsigned long *sz);
 extern int crash_prepare_headers(int need_kernel_map, void **addr,
 				 unsigned long *sz, unsigned long *nr_mem_ranges);
+extern int crash_exclude_core_ranges(struct crash_mem **cmem);
 
 struct kimage;
 struct kexec_segment;
@@ -81,6 +82,9 @@ extern int kimage_crash_copy_vmcoreinfo(struct kimage *image);
 extern unsigned int arch_get_system_nr_ranges(void);
 extern int arch_crash_populate_cmem(struct crash_mem *cmem);
 extern int arch_crash_exclude_ranges(struct crash_mem *cmem);
+extern int arch_crash_exclude_mem_range(struct crash_mem **mem,
+					unsigned long long mstart,
+					unsigned long long mend);
 
 #else /* !CONFIG_CRASH_DUMP*/
 struct pt_regs;
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index d28be3890efb..c42eeabcbdac 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -286,24 +286,31 @@ unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
 int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
 int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
 
-static int crash_exclude_core_ranges(struct crash_mem *cmem)
+int __weak arch_crash_exclude_mem_range(struct crash_mem **mem,
+					unsigned long long mstart,
+					unsigned long long mend)
+{
+	return crash_exclude_mem_range(*mem, mstart, mend);
+}
+
+int crash_exclude_core_ranges(struct crash_mem **cmem)
 {
 	int ret, i;
 
 	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	ret = arch_crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
 	if (ret)
 		return ret;
 
 	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		ret = arch_crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
 		if (ret)
 			return ret;
 	}
 
 	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
+		ret = arch_crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
+						   crashk_cma_ranges[i].end);
 		if (ret)
 			return ret;
 	}
@@ -338,7 +345,7 @@ int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
 	}
 
 	put_online_mems();
-	ret = crash_exclude_core_ranges(cmem);
+	ret = crash_exclude_core_ranges(&cmem);
 	if (ret)
 		goto out;
 
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:53 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:53 +0800
Subject: [PATCH v13 14/15] arm64: kexec: Add support for crashkernel CMA reservation
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-15-ruanjinjie@huawei.com>

Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
crashkernel= command line option") and commit ab475510e042 ("kdump:
implement reserve_crashkernel_cma") added CMA support for kdump
crashkernel reservation.

Crash kernel memory reservation wastes production resources if too
large, risks kdump failure if too small, and faces allocation difficulties
on fragmented systems due to contiguous block constraints. The new
CMA-based crashkernel reservation scheme splits the "large fixed
reservation" into a "small fixed region + large CMA dynamic region": the
CMA memory is available to userspace during normal operation to avoid
waste, and is reclaimed for kdump upon crash?saving memory while
improving reliability.

So extend crashkernel CMA reservation support to arm64. The following
changes are made to enable CMA reservation:

- Parse and obtain the CMA reservation size along with other crashkernel
  parameters.
- Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
- Include the CMA-reserved ranges for kdump kernel to use.
- Exclude the CMA-reserved ranges from the crash kernel memory to
  prevent them from being exported through /proc/vmcore, which is already
  done in the crash core.

Update kernel-parameters.txt to document CMA support for crashkernel on
arm64 architecture.

Tested-by: Breno Leitao <leitao at debian.org>
Acked-by: Catalin Marinas <catalin.marinas at arm.com>
Acked-by: Rob Herring (Arm) <robh at kernel.org>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Acked-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
v7:
- Correct the inclusion of CMA-reserved ranges for kdump
  kernel in of/kexec.
v3:
- Add Acked-by.
v2:
- Free cmem in prepare_elf_headers()
- Add the mtivation.
---
 Documentation/admin-guide/kernel-parameters.txt | 2 +-
 arch/arm64/kernel/machine_kexec_file.c          | 2 +-
 arch/arm64/mm/init.c                            | 5 +++--
 drivers/of/fdt.c                                | 9 +++++----
 drivers/of/kexec.c                              | 9 +++++++++
 include/linux/crash_reserve.h                   | 4 +++-
 6 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..52742fab49a9 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1119,7 +1119,7 @@ Kernel parameters
 			It will be ignored when crashkernel=X,high is not used
 			or memory reserved is below 4G.
 	crashkernel=size[KMG],cma
-			[KNL, X86, ppc] Reserve additional crash kernel memory from
+			[KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
 			CMA. This reservation is usable by the first system's
 			userspace memory and kernel movable allocations (memory
 			balloon, zswap). Pages allocated from this memory range
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index 8d72038f206d..0590ff9eeab4 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -42,7 +42,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 #ifdef CONFIG_CRASH_DUMP
 unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
+	unsigned int nr_ranges = 2 + crashk_cma_cnt; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
 	u64 i;
 
diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
index 97987f850a33..227f58522dad 100644
--- a/arch/arm64/mm/init.c
+++ b/arch/arm64/mm/init.c
@@ -96,8 +96,8 @@ phys_addr_t __ro_after_init arm64_dma_phys_limit;
 
 static void __init arch_reserve_crashkernel(void)
 {
+	unsigned long long crash_base, crash_size, cma_size = 0;
 	unsigned long long low_size = 0;
-	unsigned long long crash_base, crash_size;
 	bool high = false;
 	int ret;
 
@@ -106,11 +106,12 @@ static void __init arch_reserve_crashkernel(void)
 
 	ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
 				&crash_size, &crash_base,
-				&low_size, NULL, &high);
+				&low_size, &cma_size, &high);
 	if (ret)
 		return;
 
 	reserve_crashkernel_generic(crash_size, crash_base, low_size, high);
+	reserve_crashkernel_cma(cma_size);
 }
 
 static phys_addr_t __init max_zone_phys(phys_addr_t zone_limit)
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 82f7327c59ea..0470acbd1fcf 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -880,11 +880,12 @@ static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;
 /*
  * The main usage of linux,usable-memory-range is for crash dump kernel.
  * Originally, the number of usable-memory regions is one. Now there may
- * be two regions, low region and high region.
- * To make compatibility with existing user-space and older kdump, the low
- * region is always the last range of linux,usable-memory-range if exist.
+ * be 2 + CRASHK_CMA_RANGES_MAX regions, low region, high region and cma
+ * regions. To make compatibility with existing user-space and older kdump,
+ * the high and low region are always the first two ranges of
+ * linux,usable-memory-range if exist.
  */
-#define MAX_USABLE_RANGES		2
+#define MAX_USABLE_RANGES		(2 + CRASHK_CMA_RANGES_MAX)
 
 /**
  * early_init_dt_check_for_usable_mem_range - Decode usable memory range
diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c
index b6837e299e7f..029903b986cb 100644
--- a/drivers/of/kexec.c
+++ b/drivers/of/kexec.c
@@ -458,6 +458,15 @@ void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
 			if (ret)
 				goto out;
 		}
+
+		for (int i = 0; i < crashk_cma_cnt; i++) {
+			ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
+					"linux,usable-memory-range",
+					crashk_cma_ranges[i].start,
+					crashk_cma_ranges[i].end - crashk_cma_ranges[i].start + 1);
+			if (ret)
+				goto out;
+		}
 #endif
 	}
 
diff --git a/include/linux/crash_reserve.h b/include/linux/crash_reserve.h
index f0dc03d94ca2..30864d90d7f5 100644
--- a/include/linux/crash_reserve.h
+++ b/include/linux/crash_reserve.h
@@ -14,9 +14,11 @@
 extern struct resource crashk_res;
 extern struct resource crashk_low_res;
 extern struct range crashk_cma_ranges[];
+
+#define CRASHK_CMA_RANGES_MAX 4
 #if defined(CONFIG_CMA) && defined(CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION)
 #define CRASHKERNEL_CMA
-#define CRASHKERNEL_CMA_RANGES_MAX 4
+#define CRASHKERNEL_CMA_RANGES_MAX (CRASHK_CMA_RANGES_MAX)
 extern int crashk_cma_cnt;
 #else
 #define crashk_cma_cnt 0
-- 
2.34.1



From ruanjinjie at huawei.com  Sun May 10 20:04:54 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 11:04:54 +0800
Subject: [PATCH v13 15/15] riscv: kexec: Add support for crashkernel CMA reservation
In-Reply-To: <20260511030454.1730881-1-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
Message-ID: <20260511030454.1730881-16-ruanjinjie@huawei.com>

Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
crashkernel= command line option") and commit ab475510e042 ("kdump:
implement reserve_crashkernel_cma") added CMA support for kdump
crashkernel reservation. This allows the kernel to dynamically allocate
contiguous memory for crash dumping when needed, rather than permanently
reserving a fixed region at boot time.

So extend crashkernel CMA reservation support to riscv. The following
changes are made to enable CMA reservation:

- Parse and obtain the CMA reservation size along with other crashkernel
  parameters.
- Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
- Include the CMA-reserved ranges for kdump kernel to use, which was
  already done in of_kexec_alloc_and_setup_fdt().
- Exclude the CMA-reserved ranges from the crash kernel memory to
  prevent them from being exported through /proc/vmcore, which was
  already done in the crash core.

Update kernel-parameters.txt to document CMA support for crashkernel on
riscv architecture.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Acked-by: Paul Walmsley <pjw at kernel.org> # arch/riscv
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 Documentation/admin-guide/kernel-parameters.txt | 16 ++++++++--------
 arch/riscv/kernel/machine_kexec_file.c          |  2 +-
 arch/riscv/mm/init.c                            |  5 +++--
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 52742fab49a9..3ff3ddd516cf 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1119,14 +1119,14 @@ Kernel parameters
 			It will be ignored when crashkernel=X,high is not used
 			or memory reserved is below 4G.
 	crashkernel=size[KMG],cma
-			[KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
-			CMA. This reservation is usable by the first system's
-			userspace memory and kernel movable allocations (memory
-			balloon, zswap). Pages allocated from this memory range
-			will not be included in the vmcore so this should not
-			be used if dumping of userspace memory is intended and
-			it has to be expected that some movable kernel pages
-			may be missing from the dump.
+			[KNL, X86, ARM64, RISCV, PPC] Reserve additional crash
+			kernel memory from CMA. This reservation is usable by
+			the first system's userspace memory and kernel movable
+			allocations (memory balloon, zswap). Pages allocated
+			from this memory range will not be included in the vmcore
+			so this should not be used if dumping of userspace memory
+			is intended and it has to be expected that some movable
+			kernel pages may be missing from the dump.
 
 			A standard crashkernel reservation, as described above,
 			is still needed to hold the crash kernel and initrd.
diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index bea818f75dd6..c79cd86d5713 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -46,7 +46,7 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 
 unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 2; /* For exclusion of crashkernel region */
+	unsigned int nr_ranges = 2 + crashk_cma_cnt; /* For exclusion of crashkernel region */
 
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
 
diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c
index decd7df40fa4..c848454b8349 100644
--- a/arch/riscv/mm/init.c
+++ b/arch/riscv/mm/init.c
@@ -1295,7 +1295,7 @@ static inline void setup_vm_final(void)
  */
 static void __init arch_reserve_crashkernel(void)
 {
-	unsigned long long low_size = 0;
+	unsigned long long low_size = 0, cma_size = 0;
 	unsigned long long crash_base, crash_size;
 	bool high = false;
 	int ret;
@@ -1305,11 +1305,12 @@ static void __init arch_reserve_crashkernel(void)
 
 	ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
 				&crash_size, &crash_base,
-				&low_size, NULL, &high);
+				&low_size, &cma_size, &high);
 	if (ret)
 		return;
 
 	reserve_crashkernel_generic(crash_size, crash_base, low_size, high);
+	reserve_crashkernel_cma(cma_size);
 }
 
 void __init paging_init(void)
-- 
2.34.1



From leitao at debian.org  Mon May 11 02:46:43 2026
From: leitao at debian.org (Breno Leitao)
Date: Mon, 11 May 2026 02:46:43 -0700
Subject: [PATCH v13 04/15] arm64: kexec_file: Fix potential buffer
 overflow in prepare_elf_headers()
In-Reply-To: <20260511030454.1730881-5-ruanjinjie@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
 <20260511030454.1730881-5-ruanjinjie@huawei.com>
Message-ID: <agGkvrg06KNDNfDi@gmail.com>

On Mon, May 11, 2026 at 11:04:43AM +0800, Jinjie Ruan wrote:
> There is a race condition between the kexec_load() system call
> (crash kernel loading path) and memory hotplug operations that can
> lead to buffer overflow and potential kernel crash.
> 
> During prepare_elf_headers(), the following steps occur:
> 1. The first for_each_mem_range() queries current System RAM memory ranges
> 2. Allocates buffer based on queried count
> 3. The 2st for_each_mem_range() populates ranges from memblock
> 
> If memory hotplug occurs between step 1 and step 3, the number of ranges
> can increase, causing out-of-bounds write when populating cmem->ranges[].
> 
> This happens because kexec_load() uses kexec_trylock (atomic_t) while
> memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
> with each other.
> 
> Add the explicit bounds checking to prevent out-of-bounds access.

It seems you have a TOCTOU type of issue, and this seems to be shrinking
the window, but not fully solving it?

> Cc: Catalin Marinas <catalin.marinas at arm.com>
> Cc: Will Deacon <will.deacon at arm.com>
> Cc: Andrew Morton <akpm at linux-foundation.org>
> Cc: Baoquan He <bhe at redhat.com>
> Cc: Breno Leitao <leitao at debian.org>
> Cc: stable at vger.kernel.org
> Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
> Closes: https://sashiko.dev/#/patchset/20260323072745.2481719-1-ruanjinjie%40huawei.com
> Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
> ---
>  arch/arm64/kernel/machine_kexec_file.c | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> index e31fabed378a..a67e7b1abbab 100644
> --- a/arch/arm64/kernel/machine_kexec_file.c
> +++ b/arch/arm64/kernel/machine_kexec_file.c
> @@ -59,6 +59,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>  	cmem->max_nr_ranges = nr_ranges;
>  	cmem->nr_ranges = 0;
>  	for_each_mem_range(i, &start, &end) {
> +		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
> +			ret = -ENOMEM;

-ENOMEM seems to be the the wrong errno. This isn't an allocation
failure; it's a transient race. -EBUSY or -EAGAIN would be more honest


From ruanjinjie at huawei.com  Mon May 11 04:30:44 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 11 May 2026 19:30:44 +0800
Subject: [PATCH v13 04/15] arm64: kexec_file: Fix potential buffer
 overflow in prepare_elf_headers()
In-Reply-To: <agGkvrg06KNDNfDi@gmail.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
 <20260511030454.1730881-5-ruanjinjie@huawei.com> <agGkvrg06KNDNfDi@gmail.com>
Message-ID: <79c14bee-b1f5-4d70-8345-6582d6cf0128@huawei.com>



On 5/11/2026 5:46 PM, Breno Leitao wrote:
> On Mon, May 11, 2026 at 11:04:43AM +0800, Jinjie Ruan wrote:
>> There is a race condition between the kexec_load() system call
>> (crash kernel loading path) and memory hotplug operations that can
>> lead to buffer overflow and potential kernel crash.
>>
>> During prepare_elf_headers(), the following steps occur:
>> 1. The first for_each_mem_range() queries current System RAM memory ranges
>> 2. Allocates buffer based on queried count
>> 3. The 2st for_each_mem_range() populates ranges from memblock
>>
>> If memory hotplug occurs between step 1 and step 3, the number of ranges
>> can increase, causing out-of-bounds write when populating cmem->ranges[].
>>
>> This happens because kexec_load() uses kexec_trylock (atomic_t) while
>> memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
>> with each other.
>>
>> Add the explicit bounds checking to prevent out-of-bounds access.
> 
> It seems you have a TOCTOU type of issue, and this seems to be shrinking
> the window, but not fully solving it?

Hi Breno,

Thanks for your comments regarding the TOCTOU issue.

You are correct that the current bounds checking only "shrinks the
window" and prevents a kernel crash, but doesn't fully guarantee header
consistency if a race occurs.

In my local environment, this race is extremely difficult to reproduce,
but it is theoretically possible.

To address this properly for arm64, I am considering two steps:

- For this patch: I will change the return value to -EAGAIN and keep the
bounds check. This ensures that even if a race happens, the kernel
remains safe (no OOB access), and user-space is notified to retry.

- Long-term solution: A better way to solve this is to implement ARM64
CRASH_HOTPLUG support (similar to x86). With crash hotplug, the kernel
will automatically re-generate the crash headers whenever a memory
hotplug event occurs. This makes the TOCTOU during the initial
kexec_load less critical, as any transient inconsistency will be
immediately corrected by the subsequent hotplug handler.

Does it make sense to you to use this patch as a safety guard first, and
then I (or someone else) follow up with the full CRASH_HOTPLUG support
for arm64 as [1]?

[1]:
https://lore.kernel.org/all/20260402081459.635022-1-ruanjinjie at huawei.com/

Best regards,
Jinjie

> 
>> Cc: Catalin Marinas <catalin.marinas at arm.com>
>> Cc: Will Deacon <will.deacon at arm.com>
>> Cc: Andrew Morton <akpm at linux-foundation.org>
>> Cc: Baoquan He <bhe at redhat.com>
>> Cc: Breno Leitao <leitao at debian.org>
>> Cc: stable at vger.kernel.org
>> Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
>> Closes: https://sashiko.dev/#/patchset/20260323072745.2481719-1-ruanjinjie%40huawei.com
>> Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
>> ---
>>  arch/arm64/kernel/machine_kexec_file.c | 5 +++++
>>  1 file changed, 5 insertions(+)
>>
>> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
>> index e31fabed378a..a67e7b1abbab 100644
>> --- a/arch/arm64/kernel/machine_kexec_file.c
>> +++ b/arch/arm64/kernel/machine_kexec_file.c
>> @@ -59,6 +59,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>>  	cmem->max_nr_ranges = nr_ranges;
>>  	cmem->nr_ranges = 0;
>>  	for_each_mem_range(i, &start, &end) {
>> +		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
>> +			ret = -ENOMEM;
> 
> -ENOMEM seems to be the the wrong errno. This isn't an allocation
> failure; it's a transient race. -EBUSY or -EAGAIN would be more honest



From leitao at debian.org  Mon May 11 05:30:54 2026
From: leitao at debian.org (Breno Leitao)
Date: Mon, 11 May 2026 05:30:54 -0700
Subject: [PATCH v13 04/15] arm64: kexec_file: Fix potential buffer
 overflow in prepare_elf_headers()
In-Reply-To: <79c14bee-b1f5-4d70-8345-6582d6cf0128@huawei.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
 <20260511030454.1730881-5-ruanjinjie@huawei.com>
 <agGkvrg06KNDNfDi@gmail.com>
 <79c14bee-b1f5-4d70-8345-6582d6cf0128@huawei.com>
Message-ID: <agHL1zzC5bzgoCiJ@gmail.com>

On Mon, May 11, 2026 at 07:30:44PM +0800, Jinjie Ruan wrote:
> 
> 
> On 5/11/2026 5:46 PM, Breno Leitao wrote:
> > On Mon, May 11, 2026 at 11:04:43AM +0800, Jinjie Ruan wrote:
> >> There is a race condition between the kexec_load() system call
> >> (crash kernel loading path) and memory hotplug operations that can
> >> lead to buffer overflow and potential kernel crash.
> >>
> >> During prepare_elf_headers(), the following steps occur:
> >> 1. The first for_each_mem_range() queries current System RAM memory ranges
> >> 2. Allocates buffer based on queried count
> >> 3. The 2st for_each_mem_range() populates ranges from memblock
> >>
> >> If memory hotplug occurs between step 1 and step 3, the number of ranges
> >> can increase, causing out-of-bounds write when populating cmem->ranges[].
> >>
> >> This happens because kexec_load() uses kexec_trylock (atomic_t) while
> >> memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
> >> with each other.
> >>
> >> Add the explicit bounds checking to prevent out-of-bounds access.
> > 
> > It seems you have a TOCTOU type of issue, and this seems to be shrinking
> > the window, but not fully solving it?
> 
> Hi Breno,
> 
> Thanks for your comments regarding the TOCTOU issue.
> 
> You are correct that the current bounds checking only "shrinks the
> window" and prevents a kernel crash, but doesn't fully guarantee header
> consistency if a race occurs.
> 
> In my local environment, this race is extremely difficult to reproduce,
> but it is theoretically possible.
> 
> To address this properly for arm64, I am considering two steps:
> 
> - For this patch: I will change the return value to -EAGAIN and keep the
> bounds check. This ensures that even if a race happens, the kernel
> remains safe (no OOB access), and user-space is notified to retry.
> 
> - Long-term solution: A better way to solve this is to implement ARM64
> CRASH_HOTPLUG support (similar to x86). With crash hotplug, the kernel
> will automatically re-generate the crash headers whenever a memory
> hotplug event occurs. This makes the TOCTOU during the initial
> kexec_load less critical, as any transient inconsistency will be
> immediately corrected by the subsequent hotplug handler.
> 
> Does it make sense to you to use this patch as a safety guard first, and
> then I (or someone else) follow up with the full CRASH_HOTPLUG support
> for arm64 as [1]?

It would be OK for me, but, make it explict that there is a TOCTOU
issue, that depends on CRASH_HOTPLUG.


From hangej at amazon.com  Tue May 12 09:24:01 2026
From: hangej at amazon.com (hangej)
Date: Tue, 12 May 2026 18:24:01 +0200
Subject: [PATCH] pci_crash: capture PCI config space at panic time
Message-ID: <20260512162402.1929687-1-hangej@amazon.com>

From: Johannes Hange <hangej at amazon.com>

When a system crashes and kexec boots into the crash kernel, PCI
device state is lost.  AER registers are volatile and cleared by
device reset, making post-mortem root cause analysis difficult.

Add CONFIG_PCI_CRASH to capture PCI configuration space (including
AER extended capability registers) during panic, before kexec.  The
data is exported via VMCOREINFO so crash analysis tools can extract
it from the vmcore.

Hook point: crash_save_vmcoreinfo() inside __crash_kexec(), which
runs before machine_kexec() -- the only reliable capture point since
panic notifiers run after kexec by default.

Key design choices:
- No config reads at init or hotplug; buffer pre-allocated, filled
  only at crash time for fresh register state.
- capture=aer (default): quick-scan root port ROOT_STATUS, bail if
  no uncorrectable errors.  capture=always: every panic.
- devices= filter applied at rebuild time (zero crash-path cost).
- 200ms debounced workqueue rebuild for VF enumeration storms.
- kvmalloc buffer + kmalloc pagemap for physical page directory
  (vmalloc addresses need explicit page-to-phys translation).
- dcache flush (ARM64/x86) ensures data survives kexec.

Build-tested on v7.1-rc2 (x86_64).  Runtime-tested on 6.6 with
forced panics: ~2ms for 31 PCIe devices (4096B config each),
sub-microsecond AER bail on non-PCI panics.

See Documentation/PCI/pci-crash-capture.rst for full details.

Signed-off-by: Johannes Hange <hangej at amazon.com>
---
 Documentation/PCI/index.rst             |   1 +
 Documentation/PCI/pci-crash-capture.rst | 134 +++++
 MAINTAINERS                             |   8 +
 include/linux/pci_crash.h               | 110 ++++
 kernel/Kconfig.kexec                    |  17 +
 kernel/Makefile                         |   1 +
 kernel/pci_crash.c                      | 755 ++++++++++++++++++++++++
 kernel/vmcore_info.c                    |  13 +
 8 files changed, 1039 insertions(+)
 create mode 100644 Documentation/PCI/pci-crash-capture.rst
 create mode 100644 include/linux/pci_crash.h
 create mode 100644 kernel/pci_crash.c

diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e..7f499a43ddb4 100644
--- a/Documentation/PCI/index.rst
+++ b/Documentation/PCI/index.rst
@@ -19,4 +19,5 @@ PCI Bus Subsystem
    endpoint/index
    controller/index
    boot-interrupts
+   pci-crash-capture
    tph
diff --git a/Documentation/PCI/pci-crash-capture.rst b/Documentation/PCI/pci-crash-capture.rst
new file mode 100644
index 000000000000..35f676c2724b
--- /dev/null
+++ b/Documentation/PCI/pci-crash-capture.rst
@@ -0,0 +1,134 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================
+PCI Crash Capture Buffer
+========================
+
+Overview
+========
+
+The PCI crash capture module (``CONFIG_PCI_CRASH``) saves PCI configuration
+space for all (or selected) devices at panic time.  The data is written into
+a pre-allocated buffer whose physical pages are exported via VMCOREINFO,
+allowing crash analysis tools to extract device state from the vmcore.
+
+This is useful because AER (Advanced Error Reporting) registers are volatile
+and cleared by device reset during kexec into the crash kernel.  Capturing
+them before kexec preserves the error state that caused or contributed to the
+crash.
+
+Boot Parameters
+===============
+
+``pci_crash.capture=`` (default: ``aer``)
+  When to capture PCI config space.  Comma-separated tokens:
+
+  ``aer``
+    Capture only if a root port reports uncorrectable errors in its
+    AER ROOT_STATUS register.  Non-PCI panics skip capture entirely
+    (a handful of MMIO reads to root ports, sub-microsecond).
+
+  ``always``
+    Capture on every panic regardless of AER state.  Useful for
+    cascading failures where a PCI link-down causes an MCE or NMI
+    watchdog timeout before DPC/AER fires.
+
+``pci_crash.devices=`` (default: ``all``)
+  Which devices to include in the capture buffer.  Comma-separated tokens:
+
+  ``all``
+    Every PCI device in the system.
+
+  ``bridges``
+    PCI-to-PCI bridges (class 0604) and CardBus bridges (class 0607).
+
+  ``root_ports``
+    PCIe root ports only.
+
+  ``XXYY``
+    Hex PCI class code (class byte XX, subclass byte YY).
+    Up to 8 class codes may be specified.
+
+  Bridges are always implicitly included regardless of the filter value
+  because they hold AER registers needed for root cause analysis.  The
+  filter is applied at device enumeration and hotplug rebuild time, not at
+  crash time (zero overhead on the panic path).
+
+Both parameters are writable at runtime via sysfs
+(``/sys/module/pci_crash/parameters/``).
+
+Architecture
+============
+
+::
+
+  late_initcall
+      ?
+      ??? enumerate PCI devices (filtered by devices= param)
+      ??? allocate buffer via kvmalloc (may be vmalloc for >4 MiB)
+      ??? build pagemap: kmalloc'd array of per-page physical addresses
+      ??? register bus notifier for hotplug
+
+  hotplug (BUS_NOTIFY_ADD_DEVICE / BUS_NOTIFY_DEL_DEVICE)
+      ?
+      ??? schedule delayed rebuild (200 ms debounce)
+              ??? re-enumerate, re-allocate buffer + pagemap
+
+  panic (__crash_kexec ? crash_save_vmcoreinfo ? pci_crash_save)
+      ?
+      ??? quick-scan root port AER ROOT_STATUS (capture=aer)
+      ?     ??? bail if no uncorrectable errors
+      ??? read config space for each device (MMIO, no locks)
+      ??? flush dcache (buffer + pagemap) to RAM
+      ??? VMCOREINFO exports: PCI_CRASH_PAGEMAP, PCI_CRASH_BUF_SZ
+
+Buffer Format
+=============
+
+The buffer consists of a 32-byte header followed by variable-length
+device records:
+
+.. code-block:: c
+
+    struct pci_crash_buffer_header {   /* 32 bytes */
+        __le32 magic;           /* 0x50434943 "PCIC" */
+        __le32 version;         /* 1 */
+        __le32 device_count;
+        __le32 config_size;     /* 0 = variable-length records */
+        __le64 timestamp;       /* ktime_get_real_fast_ns() */
+        __le32 flags;           /* reserved */
+        __le32 reserved;
+    };
+
+    struct pci_crash_device_record {   /* 8 + cfg_size bytes */
+        __le16 domain;
+        __u8   bus;
+        __u8   devfn;
+        __le32 config_size;     /* 256 or 4096 */
+        __u8   config_data[];
+    };
+
+The pagemap (exported via ``PCI_CRASH_PAGEMAP``) allows the parser to
+locate buffer pages without walking page tables:
+
+.. code-block:: c
+
+    struct pci_crash_pagemap {
+        __le32 magic;           /* 0x5043504d "PCPM" */
+        __le32 num_pages;
+        __le64 buf_size;
+        __le64 addrs[];         /* physical address per page */
+    };
+
+Safety Considerations
+=====================
+
+- ``pci_read_config_dword()`` is direct ECAM MMIO at crash time (no locks).
+- ``ktime_get_real_fast_ns()`` is NMI-safe (lockless timekeeper snapshot).
+- ``WRITE_ONCE``/``READ_ONCE`` + memory barriers between rebuild (process
+  context) and ``pci_crash_save()`` (crash context, single CPU, interrupts
+  disabled).
+- Buffer capped at 24 MiB to prevent excessive allocation on systems with
+  thousands of VFs.
+- ``slab_is_available()`` guard in param setters prevents use-before-init
+  when parameters are set via kernel command line.
diff --git a/MAINTAINERS b/MAINTAINERS
index b2040011a386..3e4fbb3011ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20622,6 +20622,14 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git
 F:	drivers/pci/pwrctrl/*
 F:	include/linux/pci-pwrctrl.h
 
+
+PCI CRASH BUFFER
+M:	Johannes Hange <hangej at amazon.com>
+L:	linux-pci at vger.kernel.org
+S:	Maintained
+F:	include/linux/pci_crash.h
+F:	kernel/pci_crash.c
+
 PCI SUBSYSTEM
 M:	Bjorn Helgaas <bhelgaas at google.com>
 L:	linux-pci at vger.kernel.org
diff --git a/include/linux/pci_crash.h b/include/linux/pci_crash.h
new file mode 100644
index 000000000000..59a46ee26183
--- /dev/null
+++ b/include/linux/pci_crash.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * PCI Crash Buffer - Capture PCI config space at panic time
+ *
+ * This module captures PCI configuration space data (including AER
+ * extended capability registers) for all PCI devices at panic time.
+ * The data is stored in a buffer whose pages are captured in the
+ * vmcore for off-site analysis.
+ *
+ * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
+ */
+#ifndef _LINUX_PCI_CRASH_H
+#define _LINUX_PCI_CRASH_H
+
+#include <linux/types.h>
+
+#define PCI_CRASH_MAGIC         0x50434943  /* "PCIC" in ASCII */
+#define PCI_CRASH_VERSION       1
+
+/**
+ * struct pci_crash_buffer_header - Header for PCI crash buffer
+ * @magic:        Magic number (PCI_CRASH_MAGIC)
+ * @version:      Format version (PCI_CRASH_VERSION)
+ * @device_count: Number of device records following this header
+ * @config_size:  0 -- indicates variable-length records. Each device
+ *                record stores its own config_size (pdev->cfg_size:
+ *                256 for legacy PCI, 4096 for PCIe). Parsers walk
+ *                records sequentially using per-record config_size.
+ * @timestamp:    Capture timestamp from ktime_get_real_fast_ns()
+ * @flags:        Reserved for future use (0 for now)
+ * @reserved:     Padding to align to 32 bytes
+ *
+ * Total size: 32 bytes
+ */
+struct pci_crash_buffer_header {
+	__le32 magic;
+	__le32 version;
+	__le32 device_count;
+	__le32 config_size;
+	__le64 timestamp;
+	__le32 flags;
+	__le32 reserved;
+} __packed;
+
+/**
+ * struct pci_crash_device_record - Per-device record in crash buffer
+ * @domain:      PCI domain number
+ * @bus:         PCI bus number
+ * @devfn:       Device and function number (PCI_DEVFN format)
+ * @config_size: Config space size for this device (pdev->cfg_size:
+ *               256 for legacy PCI, 4096 for PCIe)
+ * @config_data: Raw PCI config space (config_size bytes)
+ *
+ * Records are variable-length: total size per record is
+ * PCI_CRASH_RECORD_META + config_size bytes.
+ */
+struct pci_crash_device_record {
+	__le16 domain;
+	__u8   bus;
+	__u8   devfn;
+	__le32 config_size;
+	__u8   config_data[];
+} __packed;
+
+#define PCI_CRASH_HEADER_SIZE  sizeof(struct pci_crash_buffer_header)
+#define PCI_CRASH_RECORD_META  sizeof(struct pci_crash_device_record)
+
+/**
+ * struct pci_crash_pagemap - Physical page directory for crash buffer
+ *
+ * The PCI crash buffer may be allocated via vmalloc (for buffers
+ * exceeding ~4 MB where the buddy allocator cannot provide contiguous
+ * pages).  virt_to_phys() returns garbage for vmalloc addresses, so
+ * we maintain this small kmalloc'd directory that maps the buffer's
+ * virtual pages to their actual physical addresses.
+ *
+ * At panic time, crash_core.c exports the pagemap's physical address
+ * via VMCOREINFO.  The parser reads the pagemap, then reads each
+ * physical page from the vmcore to reconstruct the full buffer.
+ *
+ * The pagemap itself is always kmalloc'd (direct-mapped), so
+ * virt_to_phys() works correctly on it.
+ *
+ * @magic:     0x5043504d ("PCPM") -- validates this is a pagemap
+ * @num_pages: Number of entries in the addrs[] array
+ * @buf_size:  Exact buffer size in bytes (last page may be partial)
+ * @addrs:     Physical address of each PAGE_SIZE page backing the buffer
+ */
+struct pci_crash_pagemap {
+	__le32 magic;
+	__le32 num_pages;
+	__le64 buf_size;
+	__le64 addrs[];
+} __packed;
+
+#define PCI_CRASH_PAGEMAP_MAGIC  0x5043504d  /* "PCPM" in ASCII */
+
+#ifdef CONFIG_PCI_CRASH
+void pci_crash_save(void);
+extern void *pci_crash_buffer;
+extern size_t pci_crash_buffer_size;
+extern phys_addr_t pci_crash_pagemap_phys;
+#else
+static inline void pci_crash_save(void) {}
+#define pci_crash_buffer        ((void *)NULL)
+#define pci_crash_buffer_size   ((size_t)0)
+#define pci_crash_pagemap_phys  ((phys_addr_t)0)
+#endif
+
+#endif /* _LINUX_PCI_CRASH_H */
diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
index 15632358bcf7..6eb49a934e50 100644
--- a/kernel/Kconfig.kexec
+++ b/kernel/Kconfig.kexec
@@ -179,4 +179,21 @@ config CRASH_MAX_MEMORY_RANGES
 	  the computation behind the value provided through the
 	  /sys/kernel/crash_elfcorehdr_size attribute.
 
+
+config PCI_CRASH
+	bool "Capture PCI config space at panic time"
+	depends on VMCORE_INFO && PCI && (ARM64 || X86)
+	default y
+	help
+	  Capture PCI configuration space (including AER extended capability
+	  registers) for all PCI devices at panic time.  The data is stored
+	  in a buffer whose pages are recorded in VMCOREINFO for off-site
+	  crash analysis.
+
+	  This is useful for diagnosing PCI errors that caused or contributed
+	  to the crash, especially when AER registers are volatile and cleared
+	  by device reset during kexec.
+
+	  If unsure, say Y.
+
 endmenu
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dc..6629755bc5a3 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_KEXEC_CORE) += kexec_core.o
 obj-$(CONFIG_CRASH_DUMP) += crash_core.o
 obj-$(CONFIG_CRASH_DM_CRYPT) += crash_dump_dm_crypt.o
 obj-$(CONFIG_CRASH_DUMP_KUNIT_TEST) += crash_core_test.o
+obj-$(CONFIG_PCI_CRASH) += pci_crash.o
 obj-$(CONFIG_KEXEC) += kexec.o
 obj-$(CONFIG_KEXEC_FILE) += kexec_file.o
 obj-$(CONFIG_KEXEC_ELF) += kexec_elf.o
diff --git a/kernel/pci_crash.c b/kernel/pci_crash.c
new file mode 100644
index 000000000000..8bbc9008fdc8
--- /dev/null
+++ b/kernel/pci_crash.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCI Crash Buffer - Capture PCI config space for crash analysis
+ *
+ * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
+ *
+ * Captures PCI configuration space at crash time so AER error
+ * registers reflect the crash-time state for off-site analysis.
+ *
+ * Design:
+ * - Init (late_initcall): enumerate devices, allocate buffer.
+ * - Hotplug: bus notifier queues deferred rebuild of device list
+ *   and buffer via workqueue -- no PCI reads.
+ * - Crash: crash_save_vmcoreinfo() calls pci_crash_save() which
+ *   reads config space into buffer, flushes dcache to RAM so
+ *   data survives kexec into crash kernel.
+ *
+ * Records are variable-length: each device's record is exactly
+ * 8 + pdev->cfg_size bytes (264 for legacy PCI, 4104 for PCIe).
+ * The parser walks records sequentially using per-record config_size.
+ *
+ * Buffer pages may be physically scattered (kvmalloc falls back to
+ * vmalloc for buffers exceeding ~4 MB).  A small kmalloc'd pagemap
+ * records each page's physical address so the crash parser can
+ * reconstruct the buffer without page-table walking.
+ *
+ * pci_read_config_dword() is direct MMIO (no locks) -- safe in crash.
+ * for_each_pci_dev() needs pci_bus_sem -- only used at init/hotplug.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/crash_dump.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_crash.h>
+#include <linux/slab.h>
+#include <linux/timekeeping.h>
+#include <linux/workqueue.h>
+
+#include <linux/cacheflush.h>
+#include <linux/unaligned.h>
+
+/**
+ * pci_crash_flush_dcache() - Flush a memory region from CPU cache to RAM
+ * @addr: virtual address of region to flush
+ * @size: size in bytes
+ *
+ * Used at crash time to ensure the crash kernel sees our buffer/pagemap
+ * writes after kexec.
+ */
+static inline void pci_crash_flush_dcache(void *addr, size_t size)
+{
+#ifdef CONFIG_ARM64
+	unsigned long start = (unsigned long)addr;
+	unsigned long end = start + size;
+
+	dcache_clean_inval_poc(start, end);
+#elif defined(CONFIG_X86)
+	clflush_cache_range(addr, size);
+#endif
+}
+
+/**
+ * pci_crash_buffer - Virtual address of PCI crash capture buffer
+ *
+ * Contains PCI configuration space data captured at panic time.
+ * Written by pci_crash_save(), read by crash kernel via VMCOREINFO.
+ * May be vmalloc'd -- use pci_crash_pagemap for physical addresses.
+ */
+void *pci_crash_buffer;
+EXPORT_SYMBOL_GPL(pci_crash_buffer);
+
+/**
+ * pci_crash_buffer_size - Size of pci_crash_buffer in bytes
+ */
+size_t pci_crash_buffer_size;
+EXPORT_SYMBOL_GPL(pci_crash_buffer_size);
+
+/**
+ * pci_crash_pagemap_phys - Physical address of page directory
+ *
+ * Points to a struct pci_crash_pagemap (always kmalloc'd, direct-mapped).
+ * Exported via VMCOREINFO so the crash kernel can locate buffer pages
+ * without walking page tables.
+ */
+phys_addr_t pci_crash_pagemap_phys;
+EXPORT_SYMBOL_GPL(pci_crash_pagemap_phys);
+
+static struct pci_crash_pagemap *pci_crash_pagemap;
+static size_t pci_crash_pagemap_size;
+static struct pci_dev **pci_crash_devs;
+static unsigned int pci_crash_num_devs;
+static DEFINE_MUTEX(pci_crash_lock);
+
+/*
+ * Set in pci_crash_init() after delayed_work, PCI bus and notifier are
+ * ready.  Guards parse + rebuild in param setters: at boot (level -1)
+ * the setter just stores the string; pci_crash_init() parses and does
+ * the initial rebuild once PCI is up.
+ */
+static bool pci_crash_ready;
+
+/*
+ * capture -- when to capture PCI config space.
+ * Comma-separated tokens:
+ *   aer    -- root port ROOT_STATUS has uncorrectable errors (default)
+ *   always -- every panic regardless of PCI error state
+ *
+ * Writable at runtime (0644) so operators and tests can toggle without
+ * reboot.  Writes re-parse capture_flags immediately.
+ */
+static char capture[32] = "aer";
+
+#define PCI_CRASH_CAPTURE_AER		BIT(0)
+#define PCI_CRASH_CAPTURE_ALWAYS	BIT(1)
+static unsigned long capture_flags = PCI_CRASH_CAPTURE_AER;
+
+static void pci_crash_parse_capture(void);
+
+static int capture_param_set(const char *val, const struct kernel_param *kp)
+{
+	if (strlen(val) >= sizeof(capture))
+		return -EINVAL;
+	strscpy(capture, val, sizeof(capture));
+	strim(capture);
+	if (READ_ONCE(pci_crash_ready))
+		pci_crash_parse_capture();
+	return 0;
+}
+
+static int capture_param_get(char *buf, const struct kernel_param *kp)
+{
+	return scnprintf(buf, PAGE_SIZE, "%s\n", capture);
+}
+
+static const struct kernel_param_ops capture_param_ops = {
+	.set = capture_param_set,
+	.get = capture_param_get,
+};
+module_param_cb(capture, &capture_param_ops, NULL, 0644);
+MODULE_PARM_DESC(capture, "When to capture: aer, always (default: aer)");
+
+/*
+ * devices -- which devices to capture.
+ * Comma-separated tokens:
+ *   all        -- every PCI device (default)
+ *   bridges    -- PCI bridges (class 0604, 0607)
+ *   root_ports -- PCIe root ports only
+ *   XXYY       -- hex PCI class code (class + subclass)
+ *
+ * Bridges are always implicitly included regardless of filter value
+ * because they hold the AER registers needed for root cause analysis.
+ * Applies at rebuild time only -- zero cost at crash time.  Writable
+ * at runtime (0644); writes re-parse and trigger async rebuild.
+ */
+static char devices[256] = "all";
+
+static void pci_crash_parse_devices(void);
+static struct delayed_work pci_crash_rebuild_dwork;
+
+/* Debounce period for bus notifications (ms).
+ * TRN2 liveupdate enumerates ~3000 VFs in ~1.5s -- this coalesces
+ * the storm into a single rebuild after the last event.
+ */
+#define PCI_CRASH_REBUILD_DELAY_MS	200
+
+static int devices_param_set(const char *val, const struct kernel_param *kp)
+{
+	if (strlen(val) >= sizeof(devices))
+		return -EINVAL;
+
+	mutex_lock(&pci_crash_lock);
+	strscpy(devices, val, sizeof(devices));
+	strim(devices);
+	if (READ_ONCE(pci_crash_ready)) {
+		pci_crash_parse_devices();
+		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
+				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
+	}
+	mutex_unlock(&pci_crash_lock);
+	return 0;
+}
+
+static int devices_param_get(char *buf, const struct kernel_param *kp)
+{
+	return scnprintf(buf, PAGE_SIZE, "%s\n", devices);
+}
+
+static const struct kernel_param_ops devices_param_ops = {
+	.set = devices_param_set,
+	.get = devices_param_get,
+};
+module_param_cb(devices, &devices_param_ops, NULL, 0644);
+MODULE_PARM_DESC(devices,
+	"Which devices: all, bridges, root_ports, XXYY hex class (default: all)");
+
+#define PCI_CRASH_DEVICES_ALL		BIT(0)
+#define PCI_CRASH_DEVICES_BRIDGES	BIT(1)
+#define PCI_CRASH_DEVICES_ROOT_PORTS	BIT(2)
+#define PCI_CRASH_MAX_DEVICE_CLASSES	8
+static unsigned long devices_flags = PCI_CRASH_DEVICES_ALL;
+static u16 device_classes[PCI_CRASH_MAX_DEVICE_CLASSES];
+static unsigned int device_class_count;
+
+static void pci_crash_parse_capture(void)
+{
+	char *buf, *token, *rest;
+	unsigned long flags = 0;
+
+	if (!*capture) {
+		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_AER);
+		return;
+	}
+
+	buf = kstrdup(capture, GFP_KERNEL);
+	if (!buf) {
+		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_AER);
+		return;
+	}
+
+	rest = buf;
+	while ((token = strsep(&rest, ",")) != NULL) {
+		if (strcmp(token, "aer") == 0)
+			flags |= PCI_CRASH_CAPTURE_AER;
+		else if (strcmp(token, "always") == 0)
+			flags |= PCI_CRASH_CAPTURE_ALWAYS;
+		else
+			pr_warn("unknown capture token: %s\n",
+				token);
+	}
+	kfree(buf);
+
+	if (!flags) {
+		pr_warn("no valid capture tokens, defaulting to aer\n");
+		flags = PCI_CRASH_CAPTURE_AER;
+	}
+	WRITE_ONCE(capture_flags, flags);
+}
+
+static void pci_crash_parse_devices(void)
+{
+	char *buf, *token, *rest;
+	unsigned long val;
+
+	devices_flags = 0;
+	device_class_count = 0;
+
+	if (!*devices) {
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+		return;
+	}
+
+	buf = kstrdup(devices, GFP_KERNEL);
+	if (!buf) {
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+		return;
+	}
+
+	rest = buf;
+	while ((token = strsep(&rest, ",")) != NULL) {
+		if (strcmp(token, "all") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_ALL;
+		} else if (strcmp(token, "bridges") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_BRIDGES;
+		} else if (strcmp(token, "root_ports") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_ROOT_PORTS;
+		} else if (kstrtoul(token, 16, &val) == 0 && val <= 0xFFFF) {
+			if (device_class_count < PCI_CRASH_MAX_DEVICE_CLASSES)
+				device_classes[device_class_count++] = (u16)val;
+			else
+				pr_warn("too many device classes (max %d)\n",
+					PCI_CRASH_MAX_DEVICE_CLASSES);
+		} else {
+			pr_warn("unknown devices token: %s\n",
+				token);
+		}
+	}
+	kfree(buf);
+
+	if (!devices_flags && device_class_count == 0) {
+		pr_warn("no valid devices tokens, defaulting to all\n");
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+	}
+}
+
+static bool pci_crash_device_matches(struct pci_dev *pdev)
+{
+	unsigned int i;
+	u16 dev_class = pdev->class >> 8;
+
+	if (devices_flags & PCI_CRASH_DEVICES_ALL)
+		return true;
+
+	/* Bridges always included -- they hold AER registers */
+	if (dev_class == PCI_CLASS_BRIDGE_PCI ||
+	    dev_class == PCI_CLASS_BRIDGE_CARDBUS)
+		return true;
+
+	if ((devices_flags & PCI_CRASH_DEVICES_ROOT_PORTS) &&
+	    pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT)
+		return true;
+
+	for (i = 0; i < device_class_count; i++) {
+		if (dev_class == device_classes[i])
+			return true;
+	}
+
+	return false;
+}
+
+/* Sanity limit -- prevents multi-GB allocations on systems with many VFs */
+#define PCI_CRASH_MAX_BUFFER_SIZE	(24 * 1024 * 1024)
+
+/**
+ * pci_crash_build_pagemap() - Build physical page directory for buffer
+ * @buf: buffer allocated via kvmalloc (may be vmalloc'd)
+ * @buf_size: buffer size in bytes
+ *
+ * Allocates a kmalloc'd directory containing the physical address of
+ * each page backing @buf.  The pagemap is always direct-mapped, so
+ * virt_to_phys() works on it at crash time.
+ *
+ * Returns the new pagemap, or NULL on allocation failure.
+ */
+static struct pci_crash_pagemap *pci_crash_build_pagemap(void *buf,
+							 size_t buf_size)
+{
+	unsigned int num_pages = DIV_ROUND_UP(buf_size, PAGE_SIZE);
+	struct pci_crash_pagemap *pm;
+	unsigned int i;
+
+	pm = kmalloc(struct_size(pm, addrs, num_pages), GFP_KERNEL);
+	if (!pm)
+		return NULL;
+
+	pm->magic = cpu_to_le32(PCI_CRASH_PAGEMAP_MAGIC);
+	pm->num_pages = cpu_to_le32(num_pages);
+	pm->buf_size = cpu_to_le64(buf_size);
+
+	for (i = 0; i < num_pages; i++) {
+		struct page *page;
+		phys_addr_t pa;
+
+		if (is_vmalloc_addr(buf + i * PAGE_SIZE))
+			page = vmalloc_to_page(buf + i * PAGE_SIZE);
+		else
+			page = virt_to_page(buf + i * PAGE_SIZE);
+
+		if (!page) {
+			kfree(pm);
+			return NULL;
+		}
+		pa = page_to_phys(page);
+		pm->addrs[i] = cpu_to_le64(pa);
+	}
+
+	return pm;
+}
+
+/**
+ * pci_crash_read_config_space() - Read config space for one device
+ * @pdev: PCI device to read
+ * @ptr: destination pointer within the crash buffer
+ *
+ * Reads pdev->cfg_size bytes of config space (256 for legacy PCI,
+ * 4096 for PCIe). Each 4-byte dword is read individually via MMIO.
+ * Failed reads write 0xFFFFFFFF -- standard PCI convention for
+ * absent/unreachable devices.
+ */
+static void pci_crash_read_config_space(struct pci_dev *pdev, u8 *ptr)
+{
+	struct pci_crash_device_record *record =
+		(struct pci_crash_device_record *)ptr;
+	u8 *cfg_data = ptr + PCI_CRASH_RECORD_META;
+	int offset;
+	u32 val;
+
+	record->domain = cpu_to_le16(pci_domain_nr(pdev->bus));
+	record->bus = pdev->bus->number;
+	record->devfn = pdev->devfn;
+	record->config_size = cpu_to_le32(pdev->cfg_size);
+
+	for (offset = 0; offset < pdev->cfg_size; offset += 4) {
+		if (pci_read_config_dword(pdev, offset, &val)) {
+			put_unaligned_le32(0xFFFFFFFF, &cfg_data[offset]);
+			continue;
+		}
+		put_unaligned_le32(val, &cfg_data[offset]);
+	}
+}
+
+/**
+ * pci_crash_fill_buffer() - Populate buffer with config space
+ * @buffer: destination buffer (header + variable-length records)
+ * @num_devs: number of devices to read
+ *
+ * Records are variable-length: each is PCI_CRASH_RECORD_META +
+ * pdev->cfg_size bytes. The header's config_size is 0 to indicate
+ * variable-length; parsers walk records using per-record config_size.
+ *
+ * Uses ktime_get_real_fast_ns() for the timestamp -- safe in NMI/panic
+ * context (lockless, reads the NMI-safe timekeeper snapshot).
+ */
+static void pci_crash_fill_buffer(void *buffer, unsigned int num_devs)
+{
+	struct pci_crash_buffer_header *header = buffer;
+	struct pci_dev **devs = READ_ONCE(pci_crash_devs);
+	u8 *ptr;
+	unsigned int i;
+
+	header->magic = cpu_to_le32(PCI_CRASH_MAGIC);
+	header->version = cpu_to_le32(PCI_CRASH_VERSION);
+	header->device_count = cpu_to_le32(num_devs);
+	header->config_size = 0;
+	header->timestamp = cpu_to_le64(ktime_get_real_fast_ns());
+	header->flags = 0;
+	header->reserved = 0;
+
+	ptr = (u8 *)buffer + PCI_CRASH_HEADER_SIZE;
+	for (i = 0; i < num_devs; i++) {
+		struct pci_dev *pdev = devs[i];
+
+		if (unlikely(!pdev))
+			break;
+		pci_crash_read_config_space(pdev, ptr);
+		ptr += PCI_CRASH_RECORD_META + pdev->cfg_size;
+	}
+
+	header->device_count = cpu_to_le32(i);
+}
+
+/**
+ * pci_crash_rebuild_snapshot() - Rebuild device list and allocate buffer
+ *
+ * Two-pass approach:
+ *   Pass 1: count PCI devices
+ *   Pass 2: populate device array (filtered) and compute exact buffer
+ *            size from actual pdev->cfg_size per device (no padding)
+ *
+ * The devices param controls which devices are included.  Bridges are
+ * always included regardless of devices setting (they hold AER registers).
+ * devices=all (default) includes everything.
+ *
+ * Does NOT read PCI config space -- reads happen only at crash time.
+ * This keeps rebuild fast during VF enumeration storms (~6000 ADD
+ * events on TRN2 during liveupdate).
+ *
+ * After allocation, builds the pagemap so the crash parser can
+ * locate the buffer's physical pages in the vmcore.
+ *
+ * Caller must hold pci_crash_lock.
+ */
+static void pci_crash_rebuild_snapshot(void)
+{
+	struct pci_dev *pdev = NULL;
+	unsigned int count = 0, i;
+	void *old_buf;
+	unsigned int old_num_devs = pci_crash_num_devs;
+	struct pci_dev **old_devs = pci_crash_devs;
+	struct pci_crash_pagemap *old_pm = pci_crash_pagemap;
+	struct pci_crash_pagemap *new_pm;
+	struct pci_dev **new_devs;
+	void *new_buf;
+	size_t total_size;
+
+	/* Pass 1: count devices */
+	for_each_pci_dev(pdev)
+		count++;
+
+	if (count == 0) {
+		pr_info("no PCI devices found\n");
+		WRITE_ONCE(pci_crash_num_devs, 0);
+		if (old_devs) {
+			for (i = 0; i < old_num_devs; i++)
+				pci_dev_put(old_devs[i]);
+			kvfree(old_devs);
+			WRITE_ONCE(pci_crash_devs, NULL);
+		}
+		kfree(old_pm);
+		WRITE_ONCE(pci_crash_pagemap, NULL);
+		WRITE_ONCE(pci_crash_pagemap_phys, 0);
+		WRITE_ONCE(pci_crash_pagemap_size, 0);
+		old_buf = pci_crash_buffer;
+		WRITE_ONCE(pci_crash_buffer, NULL);
+		WRITE_ONCE(pci_crash_buffer_size, 0);
+		kvfree(old_buf);
+		return;
+	}
+
+	/*
+	 * Disable capture during rebuild to prevent pci_crash_save()
+	 * from accessing stale device references or partially populated
+	 * arrays.
+	 */
+	WRITE_ONCE(pci_crash_num_devs, 0);
+
+	new_devs = kvmalloc_array(count, sizeof(struct pci_dev *),
+				  GFP_KERNEL | __GFP_ZERO);
+	if (!new_devs) {
+		pr_err("dev array alloc failed (%u)\n", count);
+		goto err_restore;
+	}
+
+	/*
+	 * Pass 2: populate device array (filtered) and compute exact
+	 * buffer size from actual pdev->cfg_size per device.
+	 * count from pass 1 is an upper bound; actual may be smaller.
+	 */
+	total_size = PCI_CRASH_HEADER_SIZE;
+	pdev = NULL;
+	i = 0;
+	for_each_pci_dev(pdev) {
+		if (i >= count) {
+			pci_dev_put(pdev);
+			break;
+		}
+		if (!pci_crash_device_matches(pdev))
+			continue;
+		new_devs[i] = pci_dev_get(pdev);
+		total_size += PCI_CRASH_RECORD_META + pdev->cfg_size;
+		i++;
+	}
+	count = i;
+
+	if (count == 0) {
+		kvfree(new_devs);
+		pr_info("no devices match devices=%s\n", devices);
+		goto err_restore;
+	}
+
+	if (total_size > PCI_CRASH_MAX_BUFFER_SIZE) {
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		pr_warn("buffer too large (%zu > %d bytes)\n",
+			total_size, PCI_CRASH_MAX_BUFFER_SIZE);
+		goto err_restore;
+	}
+
+	new_buf = kvmalloc(total_size, GFP_KERNEL | __GFP_ZERO);
+	if (!new_buf) {
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		pr_err("buffer alloc failed (%zu bytes)\n",
+		       total_size);
+		goto err_restore;
+	}
+
+	new_pm = pci_crash_build_pagemap(new_buf, total_size);
+	if (!new_pm) {
+		kvfree(new_buf);
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		pr_err("pagemap alloc failed\n");
+		goto err_restore;
+	}
+
+	/* Release old device references */
+	for (i = 0; i < old_num_devs; i++)
+		if (old_devs && old_devs[i])
+			pci_dev_put(old_devs[i]);
+
+	WRITE_ONCE(pci_crash_devs, new_devs);
+	kvfree(old_devs);
+
+	/*
+	 * Swap pagemap first (with pre-computed phys addr), then buffer,
+	 * then size and count.  If crash fires mid-swap, num_devs is
+	 * still 0 from above, so pci_crash_save() bails out safely.
+	 */
+	WRITE_ONCE(pci_crash_pagemap, new_pm);
+	WRITE_ONCE(pci_crash_pagemap_phys, virt_to_phys(new_pm));
+	WRITE_ONCE(pci_crash_pagemap_size, struct_size(new_pm, addrs,
+			le32_to_cpu(new_pm->num_pages)));
+	kfree(old_pm);
+
+	old_buf = pci_crash_buffer;
+	WRITE_ONCE(pci_crash_buffer, new_buf);
+	WRITE_ONCE(pci_crash_buffer_size, total_size);
+	/* Ensure buffer/pagemap/size are visible before num_devs enables capture */
+	smp_wmb();
+	WRITE_ONCE(pci_crash_num_devs, count);
+	kvfree(old_buf);
+
+	pr_info("rebuild: %u devices (%zu bytes, %u pages)\n",
+		count, total_size, le32_to_cpu(new_pm->num_pages));
+	return;
+
+err_restore:
+	WRITE_ONCE(pci_crash_num_devs, old_num_devs);
+}
+
+/**
+ * pci_crash_save() - Capture PCI config space at crash time
+ *
+ * Called from crash_save_vmcoreinfo() inside __crash_kexec(), which
+ * runs before machine_kexec() boots the crash kernel.  This is the
+ * only reliable capture point -- panic notifiers run AFTER kexec by
+ * default (crash_kexec_post_notifiers=0).
+ *
+ * Capture check (capture param):
+ *   always  -- capture unconditionally
+ *   aer     -- quick-scan root port AER ROOT_STATUS for uncorrectable
+ *             errors; skip if none found
+ *
+ * When capture=always, captures on every panic.
+ * This is useful for cascading failures: a PCI link-down can cause
+ * an MCE or NMI watchdog timeout before DPC/AER fires, so the crash
+ * reason is UNKNOWN but AER registers may still hold error state.
+ *
+ * Reads config space fresh -- successful reads get current register
+ * state, failed reads (offline devices) write 0xFFFFFFFF.
+ *
+ * Flushes both buffer and pagemap from CPU cache to RAM so data
+ * survives kexec into crash kernel.
+ */
+void pci_crash_save(void)
+{
+	struct pci_crash_pagemap *pm;
+	unsigned long cflags;
+	unsigned int num_devs;
+	size_t pm_size;
+	size_t buf_size;
+	void *buffer;
+
+	num_devs = READ_ONCE(pci_crash_num_devs);
+	if (num_devs == 0)
+		return;
+
+	/* Pairs with smp_wmb() in rebuild -- ensures buffer/pagemap visible */
+	smp_rmb();
+
+	buffer = READ_ONCE(pci_crash_buffer);
+	buf_size = READ_ONCE(pci_crash_buffer_size);
+	pm = READ_ONCE(pci_crash_pagemap);
+	pm_size = READ_ONCE(pci_crash_pagemap_size);
+	if (!buffer || buf_size == 0)
+		return;
+
+	cflags = READ_ONCE(capture_flags);
+	if (!(cflags & PCI_CRASH_CAPTURE_ALWAYS)) {
+		if (cflags & PCI_CRASH_CAPTURE_AER) {
+			struct pci_dev **devs = READ_ONCE(pci_crash_devs);
+			unsigned int i;
+			bool pci_error_found = false;
+
+			if (!devs)
+				return;
+
+			for (i = 0; i < num_devs; i++) {
+				struct pci_dev *pdev = devs[i];
+				u32 status;
+
+				if (!pdev || !pdev->aer_cap)
+					continue;
+
+				if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) {
+					pci_read_config_dword(pdev,
+							     pdev->aer_cap + PCI_ERR_ROOT_STATUS,
+							     &status);
+					if (status & PCI_ERR_ROOT_UNCOR_RCV) {
+						pci_error_found = true;
+						break;
+					}
+				}
+			}
+
+			if (!pci_error_found) {
+				pr_emerg("no PCI errors detected, skipping capture\n");
+				return;
+			}
+		} else {
+			return;
+		}
+	}
+
+	pci_crash_fill_buffer(buffer, num_devs);
+
+	/*
+	 * Flush buffer and pagemap from CPU cache to RAM so the
+	 * crash kernel sees our writes after kexec.
+	 */
+	pci_crash_flush_dcache(buffer, buf_size);
+
+	if (pm && pm_size > 0)
+		pci_crash_flush_dcache(pm, pm_size);
+
+	pr_emerg("CAPTURE: %u devices, %zu bytes\n",
+		 num_devs, buf_size);
+}
+EXPORT_SYMBOL_GPL(pci_crash_save);
+
+static void pci_crash_rebuild_worker(struct work_struct *work)
+{
+	mutex_lock(&pci_crash_lock);
+	pci_crash_rebuild_snapshot();
+	mutex_unlock(&pci_crash_lock);
+}
+
+static int pci_crash_bus_notifier(struct notifier_block *nb,
+				  unsigned long action, void *data)
+{
+	if (action == BUS_NOTIFY_ADD_DEVICE ||
+	    action == BUS_NOTIFY_DEL_DEVICE)
+		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
+				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block pci_crash_bus_nb = {
+	.notifier_call = pci_crash_bus_notifier,
+};
+
+static int __init pci_crash_init(void)
+{
+	/* Nothing to do in crash kernel -- the buffer from the first kernel
+	 * is already in RAM (flushed before kexec) and the parser finds it
+	 * via the pagemap in VMCOREINFO.
+	 */
+	if (is_kdump_kernel())
+		return 0;
+
+	INIT_DELAYED_WORK(&pci_crash_rebuild_dwork, pci_crash_rebuild_worker);
+
+	pci_crash_parse_capture();
+	pci_crash_parse_devices();
+
+	mutex_lock(&pci_crash_lock);
+	pci_crash_rebuild_snapshot();
+	mutex_unlock(&pci_crash_lock);
+
+	bus_register_notifier(&pci_bus_type, &pci_crash_bus_nb);
+
+	WRITE_ONCE(pci_crash_ready, true);
+
+	pr_info("ready: %u devices (%zu bytes), capture=%s devices=%s\n",
+		pci_crash_num_devs, pci_crash_buffer_size,
+		capture, devices);
+
+	return 0;
+}
+late_initcall(pci_crash_init);
+
+/* Built-in only: crash infrastructure must outlive all drivers. */
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Capture PCI config space at panic time for crash analysis");
+MODULE_AUTHOR("Amazon.com, Inc.");
diff --git a/kernel/vmcore_info.c b/kernel/vmcore_info.c
index 8614430ca212..8813f7e2e516 100644
--- a/kernel/vmcore_info.c
+++ b/kernel/vmcore_info.c
@@ -14,6 +14,7 @@
 #include <linux/cpuhotplug.h>
 #include <linux/memblock.h>
 #include <linux/kmemleak.h>
+#include <linux/pci_crash.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -91,6 +92,18 @@ void crash_save_vmcoreinfo(void)
 		vmcoreinfo_data = vmcoreinfo_data_safecopy;
 
 	vmcoreinfo_append_str("CRASHTIME=%lld\n", ktime_get_real_seconds());
+
+	/* Capture PCI config space before kexec into crash kernel */
+	pci_crash_save();
+	if (pci_crash_pagemap_phys && pci_crash_buffer_size > 0) {
+		vmcoreinfo_append_str("PCI_CRASH_PAGEMAP=0x%llx\n",
+				      (unsigned long long)pci_crash_pagemap_phys);
+		vmcoreinfo_append_str("PCI_CRASH_VERSION=%d\n",
+				      PCI_CRASH_VERSION);
+		vmcoreinfo_append_str("PCI_CRASH_BUF_SZ=%zu\n",
+				      pci_crash_buffer_size);
+	}
+
 	update_vmcoreinfo_note();
 }
 
-- 
2.47.3




Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597

From dmatlack at google.com  Tue May 12 11:48:35 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:35 +0000
Subject: [PATCH v5 00/11] PCI: liveupdate: PCI core support for Live Update
Message-ID: <20260512184846.119396-1-dmatlack@google.com>

This series can be found on GitHub:

  https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v5

This patch series introduces the initial support in the PCI core for
Live Update, enabling drivers to preserve PCI devices across a
kexec-based kernel update without interrupting the device. This
functionality is critical for minimizing downtime in environments where
PCI devices (e.g., those assigned to VMs via VFIO) must continue
operating or maintain state across a host kernel upgrade.

Specifically, this patch series allows preserved PCI devices to perform
memory transactions to/from system memory (DMA) uninterrupted across a
Live Update. The devices can be behind a bridge, but must not be a VF.
Support for P2P and preserving VFs will come in future series.

Series Overview
---------------

This series implements the following to support PCI device preservation
across Live Update:

  1. Set up a File-Lifecycle-Bound (FLB) handler to track and preserve
     PCI-specific state (struct pci_ser) across Live Update using Kexec
     Handover (KHO).

  2. Add APIs for drivers to register "outgoing" devices for
     preservation and for the PCI core to identify "incoming" preserved
     devices during enumeration.

  3. Automatically preserve all upstream bridges for any preserved
     endpoint. Use reference counting to ensure bridges remain preserved
     as long as any downstream device is preserved.

  4. Guarantee that preserved devices can be identified by the same
     RequesterID (bus, device, function) for as long as they are
     preserved by always inheriting secondary and subordinate bus
     numbers and ARI Forwarding Enable on bridges with preserved
     downstream endpoints.

  5. Guarantee the memory transactions to/from preserved devices are
     routed the same way by inheriting Access Control Services (ACS)
     flags across a Live Update.

  6. Modify the PCI shutdown path to avoid disabling bus mastering on
     preserved devices and their upstream bridges, allowing memory
     transactions to continue uninterrupted.

  7. Provide comprehensive documentation for the FLB API, device
     tracking mechanisms, and the division of responsibilities between
     the PCI core, drivers, and userspace.

Dependencies
------------

This series is built on top of the next branch of liveupdate.git tree
which has 2 commits to enable refcounting the incoming FLB:

  https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git/log/?h=next

Testing
-------

This series was tested in conjunction with v4 of the VFIO PCI driver
series:

  https://lore.kernel.org/kvm/20260511234802.2280368-1-vipinsh at google.com/

The full set of patches that I used for testing can be found on GitHub:

  https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v5-with-vfio

The full set of patches was tested using the new VFIO selftests:

  - vfio_pci_liveupdate_uapi_test
  - vfio_pci_liveupdate_kexec_test

Both tests were ran in ran in a QEMU-based VM environment, using a
single virtio-net PCIe device connected to a root port (to exercise the
bridge support in this series), and in a baremetal environment on an
Intel EMR server, using 8x Intel DSA PCIe devices (each on a host
bridge) and 1x NVMe device connected to a root port.

Future Work
-----------

After this series we expect to make further improvements to the PCI core
support for Live Update.

  - Allow P2P across Live Update by avoiding sizing or moving preserved
    device BARs and preserving all upstream bridge windows.

  - Support preserving Virtual Functions, by preserving SR-IOV
    configuration on PFs and enumerating VFs after Live Update.

Changelog
---------

v5:
 - Update PCI LIVE UPDATE entry in MAINTAINERS to use liveupdate.git,
   add kexec@ mailing list, and drop Bjorn (Pasha, Bjorn, Pratyush)
 - Create separate headers for Live Update definitions to avoid future
   patch conflicts (me)
 - Add kernel doc for public (Driver) API (me)
 - Rename reserved field to padding (Vipin)
 - Reorder checks outside of mutex where possible (Jacob)
 - Clarify refcount in struct pci_dev_ser in kernel-doc (Sami)
 - Require CONFIG_64BIT to avoid overflowing xarray key (Sashiko)
 - Various spelling and grammar fixes (Bjorn)
 - Ensure incoming and outgoing devices do not have their bus numbers
   changed during manual rescans via sysfs (Jacob)
 - Fix refcount dropping for upstream bridges during finish (Sashiko)
 - Disallow devices with PCI_DEV_FLAGS_ACS_ENABLED_QUIRK to simplify
   ACS inheritence across Live Update (Sashiko)
 - Fix ACS re-enablement via pci_restore_state() (Sashiko)
 - Drop commit that requires singleton iommu groups (me, Sami)
 - Add per-device lock to protect Live Update fields (Sami, Sashiko)

v4: https://lore.kernel.org/linux-pci/20260423212316.3431746-1-dmatlack at google.com/
v3: https://lore.kernel.org/kvm/20260323235817.1960573-1-dmatlack at google.com/
v2: https://lore.kernel.org/kvm/20260129212510.967611-1-dmatlack at google.com/
v1: https://lore.kernel.org/kvm/20251126193608.2678510-1-dmatlack at google.com/
rfc: https://lore.kernel.org/kvm/20251018000713.677779-1-vipinsh at google.com/


David Matlack (11):
  PCI: liveupdate: Set up FLB handler for the PCI core
  PCI: liveupdate: Track outgoing preserved PCI devices
  PCI: liveupdate: Track incoming preserved PCI devices
  PCI: liveupdate: Document driver binding responsibilities
  PCI: liveupdate: Keep bus numbers constant during Live Update
  PCI: liveupdate: Auto-preserve upstream bridges across Live Update
  PCI: liveupdate: Inherit ACS flags in incoming preserved devices
  PCI: liveupdate: Inherit ARI Forwarding Enable on preserved bridges
  PCI: liveupdate: Freeze preservation status during shutdown
  PCI: liveupdate: Do not disable bus mastering on preserved devices
    during kexec
  Documentation: PCI: Add documentation for Live Update

 Documentation/PCI/index.rst                   |   1 +
 Documentation/PCI/liveupdate.rst              |  29 +
 .../admin-guide/kernel-parameters.txt         |   6 +-
 Documentation/core-api/liveupdate.rst         |   1 +
 MAINTAINERS                                   |  12 +
 drivers/pci/Kconfig                           |  14 +
 drivers/pci/Makefile                          |   1 +
 drivers/pci/liveupdate.c                      | 807 ++++++++++++++++++
 drivers/pci/liveupdate.h                      |  66 ++
 drivers/pci/pci-driver.c                      |  33 +-
 drivers/pci/pci.c                             |  13 +-
 drivers/pci/probe.c                           |  29 +-
 include/linux/kho/abi/pci.h                   |  64 ++
 include/linux/pci.h                           |   4 +
 include/linux/pci_liveupdate.h                |  75 ++
 15 files changed, 1140 insertions(+), 15 deletions(-)
 create mode 100644 Documentation/PCI/liveupdate.rst
 create mode 100644 drivers/pci/liveupdate.c
 create mode 100644 drivers/pci/liveupdate.h
 create mode 100644 include/linux/kho/abi/pci.h
 create mode 100644 include/linux/pci_liveupdate.h


base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:36 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:36 +0000
Subject: [PATCH v5 01/11] PCI: liveupdate: Set up FLB handler for the PCI core
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-2-dmatlack@google.com>

Set up a File-Lifecycle-Bound (FLB) handler for the PCI core to enable
it to participate in the preservation of PCI devices across Live Update.
Essentially, this commit enables the PCI core to allocate a struct
(struct pci_ser) and preserve it across a Live Update whenever at least
one device is preserved.

Preserving PCI devices across Live Update is built on top of the Live
Update Orchestrator's (LUO) support for file preservation. Drivers are
expected to expose a file to userspace to represent a single PCI device
and support preservation of that file. This is intended primarily to
support preservation of PCI devices bound to VFIO drivers.

This commit enables drivers to register their liveupdate_file_handler
with the PCI core so that the PCI core can do its own tracking and
enforcement of which devices are preserved.

  pci_liveupdate_register_flb(driver_file_handler);
  pci_liveupdate_unregister_flb(driver_file_handler);

When the first file (with a handler registered with the PCI core) is
preserved, the PCI core will be notified to allocate its tracking struct
(pci_ser). When the last file is unpreserved (i.e. preservation
cancelled) the PCI core will be notified to free struct pci_ser.

This struct is preserved across a Live Update using KHO and can be
fetched by the PCI core during early boot (e.g. during device
enumeration) so that it knows which devices were preserved.

Note: This commit only allocates struct pci_ser and preserves it across
Live Update. A subsequent commit will add an API for drivers to tell the
PCI core exactly which devices are being preserved.

Note: There is no reason to check for kho_is_enabled() since it can be
assumed to return true. If KHO was not enabled then Live Update would
not be enabled and these routines would never run.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 MAINTAINERS                    |  10 +++
 drivers/pci/Kconfig            |  14 +++
 drivers/pci/Makefile           |   1 +
 drivers/pci/liveupdate.c       | 153 +++++++++++++++++++++++++++++++++
 include/linux/kho/abi/pci.h    |  61 +++++++++++++
 include/linux/pci.h            |   1 +
 include/linux/pci_liveupdate.h |  30 +++++++
 7 files changed, 270 insertions(+)
 create mode 100644 drivers/pci/liveupdate.c
 create mode 100644 include/linux/kho/abi/pci.h
 create mode 100644 include/linux/pci_liveupdate.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..6c618830cf61 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20530,6 +20530,16 @@ L:	linux-pci at vger.kernel.org
 S:	Supported
 F:	Documentation/PCI/pci-error-recovery.rst
 
+PCI LIVE UPDATE
+M:	David Matlack <dmatlack at google.com>
+L:	kexec at lists.infradead.org
+L:	linux-pci at vger.kernel.org
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	drivers/pci/liveupdate.c
+F:	include/linux/kho/abi/pci.h
+F:	include/linux/pci_liveupdate.h
+
 PCI MSI DRIVER FOR ALTERA MSI IP
 L:	linux-pci at vger.kernel.org
 S:	Orphan
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 33c88432b728..08398cbe970c 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -328,6 +328,20 @@ config VGA_ARB_MAX_GPUS
 	  Reserves space in the kernel to maintain resource locking for
 	  multiple GPUS.  The overhead for each GPU is very small.
 
+config PCI_LIVEUPDATE
+	bool "PCI Live Update Support (EXPERIMENTAL)"
+	depends on PCI && LIVEUPDATE
+	help
+	  Enable PCI core support for preserving PCI devices across Live
+	  Update. This, in combination with support in a device's driver,
+	  enables PCI devices to run and perform memory transactions
+	  uninterrupted during a kexec for Live Update.
+
+	  This option should only be enabled by developers working on
+	  implementing this support.
+
+	  If unsure, say N.
+
 source "drivers/pci/hotplug/Kconfig"
 source "drivers/pci/controller/Kconfig"
 source "drivers/pci/endpoint/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..e8d003cb6757 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_PROC_FS)		+= proc.o
 obj-$(CONFIG_SYSFS)		+= pci-sysfs.o slot.o
 obj-$(CONFIG_ACPI)		+= pci-acpi.o
 obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o
+obj-$(CONFIG_PCI_LIVEUPDATE)	+= liveupdate.o
 endif
 
 obj-$(CONFIG_OF)		+= of.o
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
new file mode 100644
index 000000000000..dd2449e12b6d
--- /dev/null
+++ b/drivers/pci/liveupdate.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+
+/**
+ * DOC: PCI Live Update
+ *
+ * The PCI subsystem participates in the Live Update process to enable drivers
+ * to preserve their PCI devices across kexec.
+ *
+ * .. note::
+ *    The support for preserving PCI devices across Live Update is currently
+ *    *partial* and should be considered *experimental*. It should only be
+ *    used by developers working on the implementation for the time being.
+ *
+ *    To enable the support, enable ``CONFIG_PCI_LIVEUPDATE``.
+ *
+ * File-Lifecycle-Bound (FLB) Data
+ * ===============================
+ *
+ * PCI device preservation across Live Update is built on top of the Live Update
+ * Orchestrator's (LUO) support for file preservation across kexec. Drivers
+ * are expected to expose a file to represent a single PCI device and support
+ * preservation of that file with ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)``.
+ * This allows userspace to control the preservation of devices and ensure
+ * proper lifecycle management while a device is preserved. The first intended
+ * use-case is preserving vfio-pci device files.
+ *
+ * The PCI core maintains its own state about what devices are being preserved
+ * across Live Update using a feature called File-Lifecycle-Bound (FLB) data in
+ * LUO.  Essentially, this allows the PCI core to allocate struct pci_ser when
+ * the first device (file) is preserved and free it when the last device (file)
+ * is unpreserved. After kexec, the PCI core can fetch the struct pci_ser (which
+ * was constructed by the previous kernel) from LUO at any time (e.g. during
+ * enumeration) so that it knows which devices were preserved.
+ *
+ * To enable the PCI core to be notified whenever a file representing a device
+ * is preserved, drivers must register their struct liveupdate_file_handler with
+ * the PCI core by using the following APIs:
+ *
+ *  * ``pci_liveupdate_register_flb(driver_file_handler)``
+ *  * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ */
+
+#define pr_fmt(fmt) "PCI: liveupdate: " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/pci.h>
+#include <linux/liveupdate.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
+{
+	struct pci_dev *dev = NULL;
+	u32 max_nr_devices = 0;
+	struct pci_ser *ser;
+	unsigned long size;
+
+	/*
+	 * Allocate enough space to preserve all of the devices that are
+	 * currently present on the system. Extra padding can be added to this
+	 * in the future to increase the chances that there is enough room to
+	 * preserve devices that are not yet present on the system (e.g. VFs,
+	 * hot-plugged devices).
+	 */
+	for_each_pci_dev(dev)
+		max_nr_devices++;
+
+	size = struct_size_t(struct pci_ser, devices, max_nr_devices);
+
+	ser = kho_alloc_preserve(size);
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	pr_debug("Preserved struct pci_ser with room for %u devices\n",
+		 max_nr_devices);
+
+	ser->max_nr_devices = max_nr_devices;
+	ser->nr_devices = 0;
+
+	args->obj = ser;
+	args->data = virt_to_phys(ser);
+	return 0;
+}
+
+static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
+{
+	struct pci_ser *ser = args->obj;
+
+	WARN_ON_ONCE(ser->nr_devices);
+	kho_unpreserve_free(ser);
+
+	pr_debug("Unpreserved struct pci_ser\n");
+}
+
+static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
+{
+	args->obj = phys_to_virt(args->data);
+	return 0;
+}
+
+static void pci_flb_finish(struct liveupdate_flb_op_args *args)
+{
+	kho_restore_free(args->obj);
+}
+
+static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
+	.preserve = pci_flb_preserve,
+	.unpreserve = pci_flb_unpreserve,
+	.retrieve = pci_flb_retrieve,
+	.finish = pci_flb_finish,
+	.owner = THIS_MODULE,
+};
+
+static struct liveupdate_flb pci_liveupdate_flb = {
+	.ops = &pci_liveupdate_flb_ops,
+	.compatible = PCI_LUO_FLB_COMPATIBLE,
+};
+
+/**
+ * pci_liveupdate_register_flb() - Register a file handler with the PCI core
+ * @fh: The file handler to register.
+ *
+ * Drivers should call pci_liveupdate_register_flb() to register their
+ * struct liveupdate_file_handler with the PCI core. This enables the PCI core
+ * to allocate its outgoing struct pci_ser whenever the first device is
+ * preserved, and free it when the last device is unpreserved.
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+	pr_debug("Registering file handler \"%s\"\n", fh->compatible);
+	return liveupdate_register_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
+
+/**
+ * pci_liveupdate_unregister_flb() - Unregister a file handler with the PCI core
+ * @fh: The file handler to unregister.
+ */
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+	pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
+	liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
new file mode 100644
index 000000000000..6ebcf817fff4
--- /dev/null
+++ b/include/linux/kho/abi/pci.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+
+#ifndef _LINUX_KHO_ABI_PCI_H
+#define _LINUX_KHO_ABI_PCI_H
+
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+/**
+ * DOC: PCI File-Lifecycle Bound (FLB) Live Update ABI
+ *
+ * This header defines the ABI for preserving core PCI state across kexec using
+ * Live Update File-Lifecycle Bound (FLB) data.
+ *
+ * This interface is a contract. Any modification to any of the serialization
+ * structs defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
+ */
+
+#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+
+/**
+ * struct pci_dev_ser - Serialized state about a single PCI device.
+ *
+ * @domain: The device's PCI domain number (segment).
+ * @bdf: The device's PCI bus, device, and function number.
+ * @padding: Padding to naturally align struct pci_dev_ser.
+ */
+struct pci_dev_ser {
+	u32 domain;
+	u16 bdf;
+	u16 padding;
+} __packed;
+
+/**
+ * struct pci_ser - PCI Subsystem Live Update State
+ *
+ * This struct tracks state about all devices that are being preserved across
+ * a Live Update for the next kernel.
+ *
+ * @max_nr_devices: The length of the devices[] flexible array.
+ * @nr_devices: The number of devices that were preserved.
+ * @devices: Flexible array of pci_dev_ser structs for each device.
+ */
+struct pci_ser {
+	u32 max_nr_devices;
+	u32 nr_devices;
+	struct pci_dev_ser devices[];
+} __packed;
+
+/* Ensure all elements of devices[] are naturally aligned. */
+static_assert(offsetof(struct pci_ser, devices) % sizeof(unsigned long) == 0);
+static_assert(sizeof(struct pci_dev_ser) % sizeof(unsigned long) == 0);
+
+#endif /* _LINUX_KHO_ABI_PCI_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..8cadeeab86fd 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -42,6 +42,7 @@
 #include <uapi/linux/pci.h>
 
 #include <linux/pci_ids.h>
+#include <linux/pci_liveupdate.h>
 
 #define PCI_STATUS_ERROR_BITS (PCI_STATUS_DETECTED_PARITY  | \
 			       PCI_STATUS_SIG_SYSTEM_ERROR | \
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
new file mode 100644
index 000000000000..8ec98beefcb4
--- /dev/null
+++ b/include/linux/pci_liveupdate.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (Public/Driver API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+#ifndef LINUX_PCI_LIVEUPDATE_H
+#define LINUX_PCI_LIVEUPDATE_H
+
+#include <linux/liveupdate.h>
+#include <linux/types.h>
+
+struct pci_dev;
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+#else
+static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+}
+#endif
+
+#endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:37 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:37 +0000
Subject: [PATCH v5 02/11] PCI: liveupdate: Track outgoing preserved PCI devices
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-3-dmatlack@google.com>

Add APIs to allow drivers to notify the PCI core of which devices are
being preserved across a Live Update for the next kernel, i.e.
"outgoing" devices.

Drivers must notify the PCI core when devices are preserved so that the
PCI core can update its FLB data (struct pci_ser) and track the list of
outgoing devices. pci_liveupdate_preserve() notifies the PCI core that a
device must be preserved across Live Update. pci_liveupdate_unpreserve()
reverses this (cancels the preservation of the device).

This tracking ensures the PCI core is fully aware of which devices may
need special handling during shutdown and kexec, and so that it can be
handed off to the next kernel.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 167 ++++++++++++++++++++++++++++++---
 drivers/pci/probe.c            |   3 +
 include/linux/kho/abi/pci.h    |   9 +-
 include/linux/pci.h            |   3 +
 include/linux/pci_liveupdate.h |  23 +++++
 5 files changed, 191 insertions(+), 14 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index dd2449e12b6d..9c4582ecd55c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -43,6 +43,26 @@
  *
  *  * ``pci_liveupdate_register_flb(driver_file_handler)``
  *  * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ *
+ * Device Tracking
+ * ===============
+ *
+ * Drivers must notify the PCI core when specific devices are preserved or
+ * unpreserved with the following APIs:
+ *
+ *  * ``pci_liveupdate_preserve(pci_dev)``
+ *  * ``pci_liveupdate_unpreserve(pci_dev)``
+ *
+ * This allows the PCI core to keep its FLB data (struct pci_ser) up to date
+ * with the list of **outgoing** preserved devices for the next kernel.
+ *
+ * Restrictions
+ * ============
+ *
+ * The PCI core enforces the following restrictions on which devices can be
+ * preserved. These may be relaxed in the future:
+ *
+ *  * The device cannot be a Virtual Function (VF).
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -55,13 +75,29 @@
 #include <linux/mm.h>
 #include <linux/pci.h>
 
+/**
+ * struct pci_flb_outgoing - Outgoing PCI FLB object
+ * @ser: The outgoing struct pci_ser for the next kernel.
+ * @lock: Lock used to protect against changes to @ser.
+ */
+struct pci_flb_outgoing {
+	struct pci_ser *ser;
+	struct mutex lock;
+};
+
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
+	struct pci_flb_outgoing *outgoing;
 	struct pci_dev *dev = NULL;
 	u32 max_nr_devices = 0;
-	struct pci_ser *ser;
 	unsigned long size;
 
+	outgoing = kmalloc_obj(*outgoing);
+	if (!outgoing)
+		return -ENOMEM;
+
+	mutex_init(&outgoing->lock);
+
 	/*
 	 * Allocate enough space to preserve all of the devices that are
 	 * currently present on the system. Extra padding can be added to this
@@ -74,27 +110,30 @@ static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 
 	size = struct_size_t(struct pci_ser, devices, max_nr_devices);
 
-	ser = kho_alloc_preserve(size);
-	if (IS_ERR(ser))
-		return PTR_ERR(ser);
+	outgoing->ser = kho_alloc_preserve(size);
+	if (IS_ERR(outgoing->ser)) {
+		kfree(outgoing);
+		return PTR_ERR(outgoing->ser);
+	}
 
 	pr_debug("Preserved struct pci_ser with room for %u devices\n",
 		 max_nr_devices);
 
-	ser->max_nr_devices = max_nr_devices;
-	ser->nr_devices = 0;
+	outgoing->ser->max_nr_devices = max_nr_devices;
+	outgoing->ser->nr_devices = 0;
 
-	args->obj = ser;
-	args->data = virt_to_phys(ser);
+	args->obj = outgoing;
+	args->data = virt_to_phys(outgoing->ser);
 	return 0;
 }
 
 static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
 {
-	struct pci_ser *ser = args->obj;
+	struct pci_flb_outgoing *outgoing = args->obj;
 
-	WARN_ON_ONCE(ser->nr_devices);
-	kho_unpreserve_free(ser);
+	WARN_ON_ONCE(outgoing->ser->nr_devices);
+	kho_unpreserve_free(outgoing->ser);
+	kfree(outgoing);
 
 	pr_debug("Unpreserved struct pci_ser\n");
 }
@@ -123,6 +162,112 @@ static struct liveupdate_flb pci_liveupdate_flb = {
 	.compatible = PCI_LUO_FLB_COMPATIBLE,
 };
 
+/**
+ * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
+ * @dev: The PCI device to preserve.
+ *
+ * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be
+ * preserved across the next Live Update. Drivers must call
+ * pci_liveupdate_preserve() from their struct liveupdate_file_handler
+ * preserve() callback to ensure the outgoing struct pci_ser is allocated.
+ *
+ * Returns: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+	struct pci_flb_outgoing *outgoing = NULL;
+	struct pci_ser *ser;
+	int i, ret;
+
+	if (dev->is_virtfn)
+		return -EINVAL;
+
+	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
+	if (ret)
+		return ret;
+
+	if (!outgoing)
+		return -ENOENT;
+
+	guard(mutex)(&outgoing->lock);
+	ser = outgoing->ser;
+
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	if (dev->liveupdate.outgoing)
+		return -EBUSY;
+
+	if (ser->nr_devices == ser->max_nr_devices)
+		return -ENOSPC;
+
+	for (i = 0; i < ser->max_nr_devices; i++) {
+		/*
+		 * Start searching at index ser->nr_devices. This should result
+		 * in a constant time search under expected conditions (devices
+		 * are not getting unpreserved).
+		 */
+		int index = (ser->nr_devices + i) % ser->max_nr_devices;
+		struct pci_dev_ser *dev_ser = &ser->devices[index];
+
+		if (dev_ser->refcount)
+			continue;
+
+		pci_info(dev, "Device will be preserved across next Live Update\n");
+		ser->nr_devices++;
+
+		dev_ser->domain = pci_domain_nr(dev->bus);
+		dev_ser->bdf = pci_dev_id(dev);
+		dev_ser->refcount = 1;
+
+		dev->liveupdate.outgoing = dev_ser;
+		return 0;
+	}
+
+	return -ENOSPC;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
+
+/**
+ * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device
+ * @dev: The PCI device to preserve.
+ *
+ * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no
+ * longer be preserved across the next Live Update. Drivers must call
+ * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
+ * unpreserve() callback to ensure the outgoing struct pci_ser is allocated.
+ */
+void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+	struct pci_flb_outgoing *outgoing = NULL;
+	struct pci_dev_ser *dev_ser;
+	struct pci_ser *ser;
+	int ret;
+
+	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
+
+	if (ret || !outgoing) {
+		pci_warn(dev, "Cannot unpreserve device without outgoing Live Update state\n");
+		return;
+	}
+
+	guard(mutex)(&outgoing->lock);
+	ser = outgoing->ser;
+
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	dev_ser = dev->liveupdate.outgoing;
+	if (!dev_ser) {
+		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
+		return;
+	}
+
+	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
+	ser->nr_devices--;
+	memset(dev_ser, 0, sizeof(*dev_ser));
+	dev->liveupdate.outgoing = NULL;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
+
 /**
  * pci_liveupdate_register_flb() - Register a file handler with the PCI core
  * @fh: The file handler to register.
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..54ae32cb0000 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2522,6 +2522,9 @@ struct pci_dev *pci_alloc_dev(struct pci_bus *bus)
 	spin_lock_init(&dev->pcie_cap_lock);
 #ifdef CONFIG_PCI_MSI
 	raw_spin_lock_init(&dev->msi_lock);
+#endif
+#ifdef CONFIG_PCI_LIVEUPDATE
+	rwlock_init(&dev->liveupdate.lock);
 #endif
 	return dev;
 }
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 6ebcf817fff4..807fe0e6538f 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,19 +23,22 @@
  * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
  */
 
-#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
 
 /**
  * struct pci_dev_ser - Serialized state about a single PCI device.
  *
  * @domain: The device's PCI domain number (segment).
  * @bdf: The device's PCI bus, device, and function number.
- * @padding: Padding to naturally align struct pci_dev_ser.
+ * @refcount: Reference count used by the PCI core to keep track of whether it
+ *            is done using a device's struct pci_dev_ser. The value of the
+ *            refcount is equal to the number of devices preserved at or below
+ *            this device in the PCI hierarchy.
  */
 struct pci_dev_ser {
 	u32 domain;
 	u16 bdf;
-	u16 padding;
+	u16 refcount;
 } __packed;
 
 /**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 8cadeeab86fd..a7c3722b1e77 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -594,6 +594,9 @@ struct pci_dev {
 	u8		tph_mode;	/* TPH mode */
 	u8		tph_req_type;	/* TPH requester type */
 #endif
+#ifdef CONFIG_PCI_LIVEUPDATE
+	struct pci_liveupdate liveupdate;
+#endif
 };
 
 static inline struct pci_dev *pci_physfn(struct pci_dev *dev)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 8ec98beefcb4..0803d44becd5 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -8,14 +8,28 @@
 #ifndef LINUX_PCI_LIVEUPDATE_H
 #define LINUX_PCI_LIVEUPDATE_H
 
+#include <linux/kho/abi/pci.h>
 #include <linux/liveupdate.h>
 #include <linux/types.h>
+#include <linux/spinlock_types.h>
+
+/**
+ * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
+ * @lock: Lock used to protect members of struct pci_liveupdate.
+ * @outgoing: State preserved for the next kernel.
+ */
+struct pci_liveupdate {
+	rwlock_t lock;
+	struct pci_dev_ser *outgoing;
+};
 
 struct pci_dev;
 
 #ifdef CONFIG_PCI_LIVEUPDATE
 int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
 void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+int pci_liveupdate_preserve(struct pci_dev *dev);
+void pci_liveupdate_unpreserve(struct pci_dev *dev);
 #else
 static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
 {
@@ -25,6 +39,15 @@ static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh
 static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
 {
 }
+
+static inline int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+}
 #endif
 
 #endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:38 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:38 +0000
Subject: [PATCH v5 03/11] PCI: liveupdate: Track incoming preserved PCI devices
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-4-dmatlack@google.com>

During PCI enumeration, the previous kernel might have passed state about
devices that were preserved across kexec. The PCI core needs to fetch
this state to identify which devices are "incoming" and require special
handling.

Add pci_liveupdate_setup_device() which is called during device setup
to fetch the serialized state (struct pci_ser) from the Live Update
Orchestrator. The first time this happens, pci_flb_retrieve() will run
and convert the array of pci_dev_ser structs into an xarray so that it
can be looked up efficiently.

If a device is found in the xarray, the PCI core stores a pointer to its
state in dev->liveupdate_incoming and holds a reference to the incoming
FLB until pci_liveupdate_finish() is called by the driver.

This ensures proper lifecycle management for incoming preserved devices
and allows the PCI core and drivers to apply specific Live Update
logic to them in subsequent commits.

Drivers can check if a device is an incoming preserved device (e.g.
during probe) by calling pci_liveupdate_is_incoming().

CONFIG_64BIT is now required to enable CONFIG_PCI_LIVEUPDATE so that the
domain and bdf can be guaranteed to fit in an unsigned long and be used
as the xarray key.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 MAINTAINERS                    |   1 +
 drivers/pci/Kconfig            |   2 +-
 drivers/pci/liveupdate.c       | 223 ++++++++++++++++++++++++++++++++-
 drivers/pci/liveupdate.h       |  26 ++++
 drivers/pci/probe.c            |   5 +
 include/linux/pci_liveupdate.h |  13 ++
 6 files changed, 267 insertions(+), 3 deletions(-)
 create mode 100644 drivers/pci/liveupdate.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 6c618830cf61..0e262c0ceb43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20537,6 +20537,7 @@ L:	linux-pci at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
 F:	drivers/pci/liveupdate.c
+F:	drivers/pci/liveupdate.h
 F:	include/linux/kho/abi/pci.h
 F:	include/linux/pci_liveupdate.h
 
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 08398cbe970c..eea0a6cd388a 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
 
 config PCI_LIVEUPDATE
 	bool "PCI Live Update Support (EXPERIMENTAL)"
-	depends on PCI && LIVEUPDATE
+	depends on PCI && LIVEUPDATE && 64BIT
 	help
 	  Enable PCI core support for preserving PCI devices across Live
 	  Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 9c4582ecd55c..f14396dd1477 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -56,6 +56,20 @@
  * This allows the PCI core to keep its FLB data (struct pci_ser) up to date
  * with the list of **outgoing** preserved devices for the next kernel.
  *
+ * After kexec, whenever a device is enumerated, the PCI core will check if it
+ * is an **incoming** preserved device (i.e. preserved by the previous kernel)
+ * by checking the incoming FLB data (struct pci_ser).
+ *
+ * Drivers must notify the PCI core when an **incoming** device is done
+ * participating in the incoming Live Update with the following API:
+ *
+ *  * ``pci_liveupdate_finish(pci_dev)``
+ *
+ * The PCI core does not enforce any ordering of ``pci_liveupdate_finish()`` and
+ * ``pci_liveupdate_preserve()``. i.e. A PCI device can be **outgoing**
+ * (preserved for next kernel) and **incoming** (preserved by previous kernel)
+ * at the same time.
+ *
  * Restrictions
  * ============
  *
@@ -75,6 +89,8 @@
 #include <linux/mm.h>
 #include <linux/pci.h>
 
+#include "liveupdate.h"
+
 /**
  * struct pci_flb_outgoing - Outgoing PCI FLB object
  * @ser: The outgoing struct pci_ser for the next kernel.
@@ -85,6 +101,21 @@ struct pci_flb_outgoing {
 	struct mutex lock;
 };
 
+/**
+ * struct pci_flb_incoming - Incoming PCI FLB object
+ * @ser: The incoming struct pci_ser from the previous kernel.
+ * @xa: Xarray used to quickly lookup devices in @ser.
+ */
+struct pci_flb_incoming {
+	struct pci_ser *ser;
+	struct xarray xa;
+};
+
+static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
+{
+	return domain << 16 | bdf;
+}
+
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_flb_outgoing *outgoing;
@@ -140,13 +171,44 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
 
 static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 {
-	args->obj = phys_to_virt(args->data);
+	struct pci_flb_incoming *incoming;
+	int i, ret;
+
+	incoming = kmalloc_obj(*incoming);
+	if (!incoming)
+		return -ENOMEM;
+
+	incoming->ser = phys_to_virt(args->data);
+
+	xa_init(&incoming->xa);
+
+	for (i = 0; i < incoming->ser->max_nr_devices; i++) {
+		struct pci_dev_ser *dev_ser = &incoming->ser->devices[i];
+		unsigned long key;
+
+		if (!dev_ser->refcount)
+			continue;
+
+		key = pci_ser_xa_key(dev_ser->domain, dev_ser->bdf);
+		ret = xa_err(xa_store(&incoming->xa, key, dev_ser, GFP_KERNEL));
+		if (ret) {
+			xa_destroy(&incoming->xa);
+			kfree(incoming);
+			return ret;
+		}
+	}
+
+	args->obj = incoming;
 	return 0;
 }
 
 static void pci_flb_finish(struct liveupdate_flb_op_args *args)
 {
-	kho_restore_free(args->obj);
+	struct pci_flb_incoming *incoming = args->obj;
+
+	xa_destroy(&incoming->xa);
+	kho_restore_free(incoming->ser);
+	kfree(incoming);
 }
 
 static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
@@ -268,6 +330,163 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
 
+static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
+{
+	struct pci_flb_incoming *incoming = NULL;
+	int ret;
+
+	ret = liveupdate_flb_get_incoming(&pci_liveupdate_flb, (void **)&incoming);
+
+	/* Live Update is not enabled. */
+	if (ret == -EOPNOTSUPP)
+		return NULL;
+
+	/* Live Update is enabled, but there is no incoming FLB data. */
+	if (ret == -ENODATA)
+		return NULL;
+
+	/*
+	 * Live Update is enabled and there is incoming FLB data, but none of it
+	 * matches pci_liveupdate_flb.compatible.
+	 *
+	 * This could mean that no PCI FLB data was passed by the previous
+	 * kernel, but it could also mean the previous kernel used a different
+	 * compatibility string (i.e. a different ABI).
+	 */
+	if (ret == -ENOENT) {
+		pr_info_once("No incoming FLB matched %s\n", pci_liveupdate_flb.compatible);
+		return NULL;
+	}
+
+	/*
+	 * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+	 * but it cannot be retrieved.
+	 */
+	if (ret) {
+		WARN_ONCE(ret, "Failed to retrieve incoming FLB data\n");
+		return NULL;
+	}
+
+	return incoming;
+}
+
+static void pci_liveupdate_flb_put_incoming(void)
+{
+	liveupdate_flb_put_incoming(&pci_liveupdate_flb);
+}
+
+void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+	struct pci_flb_incoming *incoming;
+	struct pci_dev_ser *dev_ser;
+	unsigned long key;
+
+	incoming = pci_liveupdate_flb_get_incoming();
+	if (!incoming)
+		return;
+
+	key = pci_ser_xa_key(pci_domain_nr(dev->bus), pci_dev_id(dev));
+	dev_ser = xa_load(&incoming->xa, key);
+
+	/* This device was not preserved across Live Update */
+	if (!dev_ser) {
+		pci_liveupdate_flb_put_incoming();
+		return;
+	}
+
+	/*
+	 * This device was preserved, but has already been probed and gone
+	 * through pci_liveupdate_finish(). This can happen if PCI core probes
+	 * the same device multiple times, e.g. due to hotplug.
+	 */
+	if (!dev_ser->refcount) {
+		pci_liveupdate_flb_put_incoming();
+		return;
+	}
+
+	pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
+	guard(write_lock)(&dev->liveupdate.lock);
+	dev->liveupdate.incoming = dev_ser;
+
+	/*
+	 * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
+	 * that dev->liveupdate.incoming does not get freed while it is in use.
+	 */
+}
+
+void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+	bool incoming;
+
+	scoped_guard(write_lock, &dev->liveupdate.lock)
+		incoming = !!xchg(&dev->liveupdate.incoming, NULL);
+
+	/*
+	 * Drop the FLB reference acquired in pci_liveupdate_setup_device() if
+	 * the device is being cleaned up before pci_liveupdate_finish(), e.g.
+	 * due to allocation failure during setup.
+	 *
+	 * Do not drop dev->liveupdate.incoming->refcount since this device has
+	 * not gone through pci_liveupdate_finish() and thus is still an
+	 * incoming preserved device.
+	 */
+	if (incoming)
+		pci_liveupdate_flb_put_incoming();
+}
+
+/**
+ * pci_liveupdate_finish() - Finish the preservation of a PCI device across Live Update
+ * @dev: The PCI device
+ *
+ * pci_liveupdate_finish() notifies the PCI core that a PCI device that was
+ * preserved across the previous Live Update has finished participating in Live
+ * Update. Drivers must call pci_liveupdate_finish() from their struct
+ * liveupdate_file_handler finish() callback to ensure the incoming struct
+ * pci_ser is allocated.
+ */
+void pci_liveupdate_finish(struct pci_dev *dev)
+{
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	if (!dev->liveupdate.incoming) {
+		pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
+		return;
+	}
+
+	pci_info(dev, "Device is finished participating in Live Update\n");
+
+	/*
+	 * Drop the refcount so this device does not get treated as an incoming
+	 * device again, e.g. in case pci_liveupdate_setup_device() gets called
+	 * again because the device is hot-plugged.
+	 */
+	dev->liveupdate.incoming->refcount = 0;
+	dev->liveupdate.incoming = NULL;
+
+	/* Drop this device's reference on the incoming FLB. */
+	pci_liveupdate_flb_put_incoming();
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
+
+/**
+ * pci_liveupdate_is_incoming() - Check if a device is incoming preserved
+ * @dev: The PCI device to check
+ *
+ * Check if a device was preserved across Live Update by the previous kernel,
+ * i.e. the device is incoming preserved. Note that a device is only considered
+ * incoming preserved prior to pci_liveupdate_finish(). It is up to drivers to
+ * synchronize usage of pci_liveupdate_is_incoming() with their own call to
+ * pci_liveupdate_finish() to avoid acting on stale data.
+ *
+ * Returns: True if the device is incoming preserved, false otherwise.
+ */
+bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+	guard(read_lock)(&dev->liveupdate.lock);
+	return dev->liveupdate.incoming;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
+
 /**
  * pci_liveupdate_register_flb() - Register a file handler with the PCI core
  * @fh: The file handler to register.
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
new file mode 100644
index 000000000000..eaaa3559fd77
--- /dev/null
+++ b/drivers/pci/liveupdate.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (core API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+#ifndef DRIVERS_PCI_LIVEUPDATE_H
+#define DRIVERS_PCI_LIVEUPDATE_H
+
+#include <linux/pci.h>
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+void pci_liveupdate_setup_device(struct pci_dev *dev);
+void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+#else
+static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+}
+
+static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+}
+#endif
+
+#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 54ae32cb0000..b5fdc5017f92 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -24,6 +24,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/bitfield.h>
 #include <trace/events/pci.h>
+#include "liveupdate.h"
 #include "pci.h"
 
 static struct resource busn_resource = {
@@ -2069,6 +2070,8 @@ int pci_setup_device(struct pci_dev *dev)
 	if (pci_early_dump)
 		early_dump_pci_device(dev);
 
+	pci_liveupdate_setup_device(dev);
+
 	/* Need to have dev->class ready */
 	dev->cfg_size = pci_cfg_space_size(dev);
 
@@ -2192,6 +2195,7 @@ int pci_setup_device(struct pci_dev *dev)
 	default:				    /* unknown header */
 		pci_err(dev, "unknown header type %02x, ignoring device\n",
 			dev->hdr_type);
+		pci_liveupdate_cleanup_device(dev);
 		pci_release_of_node(dev);
 		return -EIO;
 
@@ -2490,6 +2494,7 @@ static void pci_release_dev(struct device *dev)
 
 	pci_dev = to_pci_dev(dev);
 	pci_release_capabilities(pci_dev);
+	pci_liveupdate_cleanup_device(pci_dev);
 	pci_release_of_node(pci_dev);
 	pcibios_release_device(pci_dev);
 	pci_bus_put(pci_dev->bus);
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 0803d44becd5..1c2ee32ad058 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -17,10 +17,12 @@
  * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
  * @lock: Lock used to protect members of struct pci_liveupdate.
  * @outgoing: State preserved for the next kernel.
+ * @incoming: State preserved by the previous kernel.
  */
 struct pci_liveupdate {
 	rwlock_t lock;
 	struct pci_dev_ser *outgoing;
+	struct pci_dev_ser *incoming;
 };
 
 struct pci_dev;
@@ -30,6 +32,8 @@ int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
 void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
 int pci_liveupdate_preserve(struct pci_dev *dev);
 void pci_liveupdate_unpreserve(struct pci_dev *dev);
+void pci_liveupdate_finish(struct pci_dev *dev);
+bool pci_liveupdate_is_incoming(struct pci_dev *dev);
 #else
 static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
 {
@@ -48,6 +52,15 @@ static inline int pci_liveupdate_preserve(struct pci_dev *dev)
 static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
 {
 }
+
+static inline void pci_liveupdate_finish(struct pci_dev *dev)
+{
+}
+
+static inline bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:39 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:39 +0000
Subject: [PATCH v5 04/11] PCI: liveupdate: Document driver binding responsibilities
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-5-dmatlack@google.com>

Document how driver binding works during a Live Update and what the PCI
core expects of drivers and users. Note that this is only a description
of the current division of responsibilities. These can change in the
future if we decide.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index f14396dd1477..d77e64906a25 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -77,6 +77,22 @@
  * preserved. These may be relaxed in the future:
  *
  *  * The device cannot be a Virtual Function (VF).
+ *
+ * Driver Binding
+ * ==============
+ *
+ * In the outgoing kernel, it is the driver's responsibility to ensure that it
+ * does not release a device between pci_liveupdate_preserve() and
+ * pci_liveupdate_unpreserve().
+ *
+ * In the incoming kernel, it is the driver's responsibility to ensure that it
+ * does not release a preserved device between probe() and
+ * pci_liveupdate_finish().
+ *
+ * It is the user's responsibility to ensure that incoming preserved devices are
+ * bound to the correct driver. i.e. The PCI core does not protect against a
+ * device getting preserved by driver A in the outgoing kernel and then getting
+ * bound to driver B in the incoming kernel.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:40 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:40 +0000
Subject: [PATCH v5 05/11] PCI: liveupdate: Keep bus numbers constant during
 Live Update
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-6-dmatlack@google.com>

During a Live Update, preserved devices must be allowed to continue
performing memory transactions so the kernel cannot change the fabric
topology, including bus numbers, since that would require disabling
and flushing any memory transactions first.

To keep bus numbers constant, always inherit the secondary and
subordinate bus numbers assigned to bridges during scanning, instead of
assigning new ones, if any PCI devices are being preserved. Note that
the kernel inherits bus numbers even on bridges without any downstream
endpoints that were preserved. This avoids accidentally assigning a
bridge a new window that overlaps with a preserved device that is
downstream of a different bridge.

If a bridge is scanned with a broken topology or has no bus numbers
set during a Live Update, refuse to assign it new bus numbers and refuse
to enumerate devices below it. This is a safety measure to prevent
topology conflicts.

Require that CONFIG_CARDBUS is not enabled to enable
CONFIG_PCI_LIVEUPDATE since inheriting bus numbers on PCI-to-CardBus
bridges requires additional work but is not a priority at the moment.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 .../admin-guide/kernel-parameters.txt         |  6 +-
 drivers/pci/Kconfig                           |  2 +-
 drivers/pci/liveupdate.c                      | 60 +++++++++++++++++++
 drivers/pci/liveupdate.h                      |  6 ++
 drivers/pci/probe.c                           | 21 +++++--
 5 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..a64af71c2705 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5138,7 +5138,11 @@ Kernel parameters
 				explicitly which ones they are.
 		assign-busses	[X86] Always assign all PCI bus
 				numbers ourselves, overriding
-				whatever the firmware may have done.
+				whatever the firmware may have done. Ignored
+				during a Live Update, where the kernel must
+				inherit the PCI topology (including bus numbers)
+				to avoid interrupting ongoing memory
+				transactions of preserved devices.
 		usepirqmask	[X86] Honor the possible IRQ mask stored
 				in the BIOS $PIR table. This is needed on
 				some systems with broken BIOSes, notably
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index eea0a6cd388a..aa665231921c 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
 
 config PCI_LIVEUPDATE
 	bool "PCI Live Update Support (EXPERIMENTAL)"
-	depends on PCI && LIVEUPDATE && 64BIT
+	depends on PCI && LIVEUPDATE && 64BIT && !CARDBUS
 	help
 	  Enable PCI core support for preserving PCI devices across Live
 	  Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index d77e64906a25..558fbaec8ddd 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -93,6 +93,21 @@
  * bound to the correct driver. i.e. The PCI core does not protect against a
  * device getting preserved by driver A in the outgoing kernel and then getting
  * bound to driver B in the incoming kernel.
+ *
+ * BDF Stability
+ * =============
+ *
+ * The PCI core guarantees that preserved devices can be identified by the same
+ * bus, device, and function numbers for as long as they are preserved
+ * (including across kexec). To accomplish this, the PCI core always inherits
+ * the secondary and subordinate bus numbers assigned to bridges during scanning
+ * if any device is preserved. This is true even on architectures that always
+ * assign new bus numbers during scanning. The kernel assumes the previous
+ * kernel established a sane bus topology across kexec.
+ *
+ * If a misconfigured or unconfigured bridge is encountered during enumeration
+ * while there are preserved devices, itss secondary and subordinate bus numbers
+ * will be cleared and devices below it will not be enumerated.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -107,6 +122,20 @@
 
 #include "liveupdate.h"
 
+/*
+ * During a Live Update, preserved devices are allowed to continue performing
+ * memory transactions. The kernel must not change the fabric topology,
+ * including bus numbers, since that would require disabling and flushing any
+ * memory transactions first.
+ *
+ * To keep things simple, inherit the secondary and subordinate bus numbers on
+ * _all_ bridges if _any_ PCI devices are preserved (i.e.  even bridges without
+ * any downstream endpoints that were preserved).  This avoids accidentally
+ * assigning a bridge a new window that overlaps with a preserved device that is
+ * downstream of a different bridge.
+ */
+static atomic_t inherit_buses;
+
 /**
  * struct pci_flb_outgoing - Outgoing PCI FLB object
  * @ser: The outgoing struct pci_ser for the next kernel.
@@ -132,6 +161,29 @@ static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
 	return domain << 16 | bdf;
 }
 
+bool pci_liveupdate_inherit_buses(void)
+{
+	return atomic_read(&inherit_buses);
+}
+
+static void pci_set_liveupdate_inherit_buses(bool enable)
+{
+	/* Ensure updates to inherit_buses do not race with rescans */
+	pci_lock_rescan_remove();
+
+	/*
+	 * Increment/decrement instead of setting directly to true/false so that
+	 * pci_liveupdate_inherit_buses() returns true if any device is outgoing
+	 * preserved or incoming preserved.
+	 */
+	if (enable)
+		atomic_inc(&inherit_buses);
+	else
+		atomic_dec(&inherit_buses);
+
+	pci_unlock_rescan_remove();
+}
+
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_flb_outgoing *outgoing;
@@ -171,6 +223,8 @@ static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 
 	args->obj = outgoing;
 	args->data = virt_to_phys(outgoing->ser);
+
+	pci_set_liveupdate_inherit_buses(true);
 	return 0;
 }
 
@@ -178,6 +232,8 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_flb_outgoing *outgoing = args->obj;
 
+	pci_set_liveupdate_inherit_buses(false);
+
 	WARN_ON_ONCE(outgoing->ser->nr_devices);
 	kho_unpreserve_free(outgoing->ser);
 	kfree(outgoing);
@@ -215,6 +271,8 @@ static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 	}
 
 	args->obj = incoming;
+
+	pci_set_liveupdate_inherit_buses(true);
 	return 0;
 }
 
@@ -222,6 +280,8 @@ static void pci_flb_finish(struct liveupdate_flb_op_args *args)
 {
 	struct pci_flb_incoming *incoming = args->obj;
 
+	pci_set_liveupdate_inherit_buses(false);
+
 	xa_destroy(&incoming->xa);
 	kho_restore_free(incoming->ser);
 	kfree(incoming);
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index eaaa3559fd77..0bd3e961d5c5 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,7 @@
 #ifdef CONFIG_PCI_LIVEUPDATE
 void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+bool pci_liveupdate_inherit_buses(void);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -21,6 +22,11 @@ static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 }
+
+static inline bool pci_liveupdate_inherit_buses(void)
+{
+	return false;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b5fdc5017f92..08ea9324647b 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1375,6 +1375,14 @@ bool pci_ea_fixed_busnrs(struct pci_dev *dev, u8 *sec, u8 *sub)
 	return true;
 }
 
+static bool pci_should_assign_new_buses(void)
+{
+	if (pci_liveupdate_inherit_buses())
+		return false;
+
+	return pcibios_assign_all_busses();
+}
+
 /*
  * pci_scan_bridge_extend() - Scan buses behind a bridge
  * @bus: Parent bus the bridge is on
@@ -1402,6 +1410,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 				  int max, unsigned int available_buses,
 				  int pass)
 {
+	const bool assign_new_buses = pci_should_assign_new_buses();
 	struct pci_bus *child;
 	u32 buses;
 	u16 bctl;
@@ -1454,8 +1463,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 		goto out;
 	}
 
-	if ((secondary || subordinate) &&
-	    !pcibios_assign_all_busses() && !broken) {
+	if ((secondary || subordinate) && !assign_new_buses && !broken) {
 		unsigned int cmax, buses;
 
 		/*
@@ -1497,8 +1505,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 		 * do in the second pass.
 		 */
 		if (!pass) {
-			if (pcibios_assign_all_busses() || broken)
-
+			if (assign_new_buses || broken)
 				/*
 				 * Temporarily disable forwarding of the
 				 * configuration cycles on all bridges in
@@ -1512,6 +1519,12 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 			goto out;
 		}
 
+		if (pci_liveupdate_inherit_buses()) {
+			pci_err(dev, "Cannot reconfigure bridge during Live Update!\n");
+			pci_err(dev, "Downstream devices will not be enumerated!\n");
+			goto out;
+		}
+
 		/* Clear errors */
 		pci_write_config_word(dev, PCI_STATUS, 0xffff);
 
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:41 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:41 +0000
Subject: [PATCH v5 06/11] PCI: liveupdate: Auto-preserve upstream bridges
 across Live Update
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-7-dmatlack@google.com>

When a PCI device is preserved across a Live Update, all of its upstream
bridges up to the root port must also be preserved. This enables the PCI
core and any drivers bound to the bridges to manage bridges correctly
across a Live Update.

Notably, this will be used in subsequent commits to ensure that
preserved devices can continue performing memory transactions without a
disruption or change in routing.

To preserve bridges, the PCI core tracks the number of downstream
devices preserved under each bridge using a reference count in struct
pci_dev_ser. This allows a bridge to remain preserved until all its
downstream preserved devices are unpreserved or finish their
participation in the Live Update.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 241 ++++++++++++++++++++++++++++++---------
 1 file changed, 184 insertions(+), 57 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 558fbaec8ddd..d8e06afde2c7 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -108,6 +108,18 @@
  * If a misconfigured or unconfigured bridge is encountered during enumeration
  * while there are preserved devices, itss secondary and subordinate bus numbers
  * will be cleared and devices below it will not be enumerated.
+ *
+ * PCI-to-PCI Bridges
+ * ==================
+ *
+ * Any PCI-to-PCI bridges upstream of a preserved device are automatically
+ * preserved when the device is preserved. The PCI core keeps track of the
+ * number of downstream devices that are preserved under a bridge so that the
+ * bridge is only unpreserved once all downstream devices are unpreserved.
+ *
+ * This enables the PCI core and any drivers bound to the bridge to participate
+ * in the Live Update so that preserved endpoints can continue issuing memory
+ * transactions during the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -300,41 +312,55 @@ static struct liveupdate_flb pci_liveupdate_flb = {
 	.compatible = PCI_LUO_FLB_COMPATIBLE,
 };
 
-/**
- * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
- * @dev: The PCI device to preserve.
- *
- * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be
- * preserved across the next Live Update. Drivers must call
- * pci_liveupdate_preserve() from their struct liveupdate_file_handler
- * preserve() callback to ensure the outgoing struct pci_ser is allocated.
- *
- * Returns: 0 on success, <0 on failure.
- */
-int pci_liveupdate_preserve(struct pci_dev *dev)
+static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
 {
-	struct pci_flb_outgoing *outgoing = NULL;
-	struct pci_ser *ser;
-	int i, ret;
+	struct pci_dev_ser *dev_ser;
 
-	if (dev->is_virtfn)
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	dev_ser = dev->liveupdate.outgoing;
+	if (!dev_ser) {
+		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
 		return -EINVAL;
+	}
 
-	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
-	if (ret)
-		return ret;
+	if (!dev_ser->refcount) {
+		pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+		return -EINVAL;
+	}
 
-	if (!outgoing)
-		return -ENOENT;
+	if (--dev_ser->refcount)
+		return 0;
 
-	guard(mutex)(&outgoing->lock);
-	ser = outgoing->ser;
+	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
+	ser->nr_devices--;
+	memset(dev_ser, 0, sizeof(*dev_ser));
+	dev->liveupdate.outgoing = NULL;
+	return 0;
+}
 
-	guard(write_lock)(&dev->liveupdate.lock);
+static int pci_liveupdate_preserve_device_existing(struct pci_dev *dev)
+{
+	if (!dev->liveupdate.outgoing->refcount) {
+		pci_WARN(dev, 1, "Preserved device with 0 refcount!\n");
+		return -EINVAL;
+	}
 
-	if (dev->liveupdate.outgoing)
+	/*
+	 * Endpoint devices should not be preserved more than once. Bridges are
+	 * preserved once for every downstream device that is preserved.
+	 */
+	if (!dev->subordinate)
 		return -EBUSY;
 
+	dev->liveupdate.outgoing->refcount++;
+	return 0;
+}
+
+static int pci_liveupdate_preserve_device_new(struct pci_ser *ser, struct pci_dev *dev)
+{
+	int i;
+
 	if (ser->nr_devices == ser->max_nr_devices)
 		return -ENOSPC;
 
@@ -363,8 +389,82 @@ int pci_liveupdate_preserve(struct pci_dev *dev)
 
 	return -ENOSPC;
 }
+
+static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	if (dev->liveupdate.outgoing)
+		return pci_liveupdate_preserve_device_existing(dev);
+	else
+		return pci_liveupdate_preserve_device_new(ser, dev);
+}
+
+static int pci_liveupdate_preserve_path(struct pci_ser *ser, struct pci_dev *dev)
+{
+	int ret;
+
+	if (!dev)
+		return 0;
+
+	ret = pci_liveupdate_preserve_device(ser, dev);
+	if (ret)
+		return ret;
+
+	ret = pci_liveupdate_preserve_path(ser, dev->bus->self);
+	if (ret) {
+		pci_liveupdate_unpreserve_device(ser, dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
+ * @dev: The PCI device to preserve.
+ *
+ * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be
+ * preserved across the next Live Update. Drivers must call
+ * pci_liveupdate_preserve() from their struct liveupdate_file_handler
+ * preserve() callback to ensure the outgoing struct pci_ser is allocated.
+ *
+ * pci_liveupdate_preserve() automatically preserves all bridges upstream of
+ * @dev.
+ *
+ * Returns: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+	struct pci_flb_outgoing *outgoing = NULL;
+	int ret;
+
+	if (dev->is_virtfn)
+		return -EINVAL;
+
+	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
+	if (ret)
+		return ret;
+
+	if (!outgoing)
+		return -ENOENT;
+
+	guard(mutex)(&outgoing->lock);
+	return pci_liveupdate_preserve_path(outgoing->ser, dev);
+}
 EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
 
+static void pci_liveupdate_unpreserve_path(struct pci_ser *ser, struct pci_dev *dev)
+{
+	if (!dev)
+		return;
+
+	if (pci_liveupdate_unpreserve_device(ser, dev))
+		return;
+
+	pci_liveupdate_unpreserve_path(ser, dev->bus->self);
+}
+
 /**
  * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device
  * @dev: The PCI device to preserve.
@@ -373,12 +473,13 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
  * longer be preserved across the next Live Update. Drivers must call
  * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
  * unpreserve() callback to ensure the outgoing struct pci_ser is allocated.
+ *
+ * pci_liveupdate_unpreserve() automatically unpreserves all bridges upstream of
+ * @dev.
  */
 void pci_liveupdate_unpreserve(struct pci_dev *dev)
 {
 	struct pci_flb_outgoing *outgoing = NULL;
-	struct pci_dev_ser *dev_ser;
-	struct pci_ser *ser;
 	int ret;
 
 	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
@@ -389,20 +490,7 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
 	}
 
 	guard(mutex)(&outgoing->lock);
-	ser = outgoing->ser;
-
-	guard(write_lock)(&dev->liveupdate.lock);
-
-	dev_ser = dev->liveupdate.outgoing;
-	if (!dev_ser) {
-		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
-		return;
-	}
-
-	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
-	ser->nr_devices--;
-	memset(dev_ser, 0, sizeof(*dev_ser));
-	dev->liveupdate.outgoing = NULL;
+	pci_liveupdate_unpreserve_path(outgoing->ser, dev);
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
 
@@ -510,6 +598,55 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 		pci_liveupdate_flb_put_incoming();
 }
 
+static int __pci_liveupdate_finish_device(struct pci_dev *dev)
+{
+	guard(write_lock)(&dev->liveupdate.lock);
+
+	if (!dev->liveupdate.incoming) {
+		pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
+		return -EINVAL;
+	}
+
+	if (!dev->liveupdate.incoming->refcount) {
+		pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Decrement the refcount so this device does not get treated as an
+	 * incoming device again, e.g. in case pci_liveupdate_setup_device()
+	 * gets called again because the device is hot-plugged.
+	 */
+	if (--dev->liveupdate.incoming->refcount)
+		return -EBUSY;
+
+	pci_info(dev, "Device is finished participating in Live Update\n");
+	dev->liveupdate.incoming = NULL;
+	return 0;
+}
+
+static int pci_liveupdate_finish_device(struct pci_dev *dev)
+{
+	int ret;
+
+	/*
+	 * If ret == -EBUSY the device is still preserved due to remaining
+	 * references. Return 0 up to the caller to indicate it should proceed
+	 * to finish preserving upstream devices but do not drop the device's
+	 * reference on the incoming FLB below.
+	 */
+	ret = __pci_liveupdate_finish_device(dev);
+	if (ret)
+		return ret == -EBUSY ? 0 : ret;
+
+	/*
+	 * Once the device's refcount reaches zero drop the device's reference
+	 * on the incoming FLB so it can be freed.
+	 */
+	pci_liveupdate_flb_put_incoming();
+	return 0;
+}
+
 /**
  * pci_liveupdate_finish() - Finish the preservation of a PCI device across Live Update
  * @dev: The PCI device
@@ -519,28 +656,18 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
  * Update. Drivers must call pci_liveupdate_finish() from their struct
  * liveupdate_file_handler finish() callback to ensure the incoming struct
  * pci_ser is allocated.
+ *
+ * pci_liveupdate_finish() automatically finishes all bridges upstream of @dev.
  */
 void pci_liveupdate_finish(struct pci_dev *dev)
 {
-	guard(write_lock)(&dev->liveupdate.lock);
-
-	if (!dev->liveupdate.incoming) {
-		pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
+	if (!dev)
 		return;
-	}
-
-	pci_info(dev, "Device is finished participating in Live Update\n");
 
-	/*
-	 * Drop the refcount so this device does not get treated as an incoming
-	 * device again, e.g. in case pci_liveupdate_setup_device() gets called
-	 * again because the device is hot-plugged.
-	 */
-	dev->liveupdate.incoming->refcount = 0;
-	dev->liveupdate.incoming = NULL;
+	if (pci_liveupdate_finish_device(dev))
+		return;
 
-	/* Drop this device's reference on the incoming FLB. */
-	pci_liveupdate_flb_put_incoming();
+	pci_liveupdate_finish(dev->bus->self);
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
 
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:42 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:42 +0000
Subject: [PATCH v5 07/11] PCI: liveupdate: Inherit ACS flags in incoming
 preserved devices
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-8-dmatlack@google.com>

Inherit Access Control Services (ACS) flags on all incoming preserved
devices (endpoints and upstream bridges) during a Live Update.

Inheriting ACS flags avoids changing routing rules while memory
transactions are in flight from preserved devices. This is also strictly
necessary to ensure that IOMMU group assignments do not change across
a Live Update for preserved devices, as changing ACS configurations can
split or merge IOMMU groups.

Cache the inherited ACS controls established by the previous kernel in
struct pci_dev so that ACS controls do not change after a reset
(pci_restore_state() calls pci_enable_acs()).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 49 ++++++++++++++++++++++++++++++++++
 drivers/pci/liveupdate.h       | 11 ++++++++
 drivers/pci/pci.c              |  5 ++++
 include/linux/pci_liveupdate.h |  6 +++++
 4 files changed, 71 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index d8e06afde2c7..e3cd6d76636c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -120,6 +120,18 @@
  * This enables the PCI core and any drivers bound to the bridge to participate
  * in the Live Update so that preserved endpoints can continue issuing memory
  * transactions during the Live Update.
+ *
+ * Handling Preserved Devices
+ * ==========================
+ *
+ * The PCI core treats preserved devices differently than non-preserved devices.
+ * This section enumerates those differences.
+ *
+ *  * The PCI core inherits all ACS flags enabled on incoming preserved devices
+ *    rather than assigning new ones. This ensures that TLPs are routed the same
+ *    way after Live Update and ensures that IOMMU groups do not change. Note
+ *    that a device will use its inherited ACS flags for the lifetime of its
+ *    struct pci_dev (i.e. even after pci_liveupdate_finish()).
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -361,6 +373,16 @@ static int pci_liveupdate_preserve_device_new(struct pci_ser *ser, struct pci_de
 {
 	int i;
 
+	/*
+	 * Do not preserve a devices that rely on device-specific ACS
+	 * equivalents (for now) since that would complicate keeping ACS
+	 * flags constant across Live Update.
+	 */
+	if (dev->dev_flags & PCI_DEV_FLAGS_ACS_ENABLED_QUIRK) {
+		pci_warn(dev, "Refusing to preserve device that relies on ACS quirks\n");
+		return -EINVAL;
+	}
+
 	if (ser->nr_devices == ser->max_nr_devices)
 		return -ENOSPC;
 
@@ -571,6 +593,7 @@ void pci_liveupdate_setup_device(struct pci_dev *dev)
 	pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
 	guard(write_lock)(&dev->liveupdate.lock);
 	dev->liveupdate.incoming = dev_ser;
+	dev->liveupdate.was_preserved = true;
 
 	/*
 	 * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
@@ -671,6 +694,32 @@ void pci_liveupdate_finish(struct pci_dev *dev)
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
 
+void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+	guard(read_lock)(&dev->liveupdate.lock);
+
+	if (!dev->acs_cap || !dev->liveupdate.incoming)
+		return;
+
+	pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, &dev->liveupdate.acs_ctrl);
+}
+
+bool pci_liveupdate_inherit_acs(struct pci_dev *dev)
+{
+	guard(read_lock)(&dev->liveupdate.lock);
+
+	/*
+	 * Use liveupdate.was_preserved instead of liveupdate.incoming since the
+	 * device's ACS controls should not change even after the device is
+	 * finished participating in the Live Update.
+	 */
+	if (!dev->acs_cap || !dev->liveupdate.was_preserved)
+		return false;
+
+	pci_write_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, dev->liveupdate.acs_ctrl);
+	return true;
+}
+
 /**
  * pci_liveupdate_is_incoming() - Check if a device is incoming preserved
  * @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 0bd3e961d5c5..c0826ca717e3 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -14,6 +14,8 @@
 void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
 bool pci_liveupdate_inherit_buses(void);
+void pci_liveupdate_init_acs(struct pci_dev *dev);
+bool pci_liveupdate_inherit_acs(struct pci_dev *dev);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -27,6 +29,15 @@ static inline bool pci_liveupdate_inherit_buses(void)
 {
 	return false;
 }
+
+static inline void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+}
+
+static inline bool pci_liveupdate_inherit_acs(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 8f7cfcc00090..cd2c1f2ada92 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -33,6 +33,7 @@
 #include <asm/dma.h>
 #include <linux/aer.h>
 #include <linux/bitfield.h>
+#include "liveupdate.h"
 #include "pci.h"
 
 DEFINE_MUTEX(pci_slot_mutex);
@@ -1017,6 +1018,9 @@ void pci_enable_acs(struct pci_dev *dev)
 	bool enable_acs = false;
 	int pos;
 
+	if (pci_liveupdate_inherit_acs(dev))
+		return;
+
 	/* If an iommu is present we start with kernel default caps */
 	if (pci_acs_enable) {
 		if (pci_dev_specific_enable_acs(dev))
@@ -3657,6 +3661,7 @@ void pci_acs_init(struct pci_dev *dev)
 
 	pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities);
 	pci_disable_broken_acs_cap(dev);
+	pci_liveupdate_init_acs(dev);
 }
 
 /**
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 1c2ee32ad058..34f9900c7d29 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -18,11 +18,17 @@
  * @lock: Lock used to protect members of struct pci_liveupdate.
  * @outgoing: State preserved for the next kernel.
  * @incoming: State preserved by the previous kernel.
+ * @acs_ctrl: ACS features established by the previous kernel.
+ * @was_preserved: True if this struct pci_dev was preserved by the previous
+ *                 kernel. Unlike @incoming, this field is not cleared after
+ *                 the device is finished participating in Live Update.
  */
 struct pci_liveupdate {
 	rwlock_t lock;
 	struct pci_dev_ser *outgoing;
 	struct pci_dev_ser *incoming;
+	u16 acs_ctrl;
+	unsigned int was_preserved:1;
 };
 
 struct pci_dev;
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:43 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:43 +0000
Subject: [PATCH v5 08/11] PCI: liveupdate: Inherit ARI Forwarding Enable on
 preserved bridges
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-9-dmatlack@google.com>

Inherit the ARI Forwarding Enable on preserved bridges and update
pci_dev->ari_enabled accordingly during a Live Update. This ensures that
the preserved devices on the bridge's secondary bus can be identified
with the same expanded 8-bit function number after a Live Update.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 18 ++++++++++++++++++
 drivers/pci/liveupdate.h |  6 ++++++
 drivers/pci/pci.c        |  8 +++++++-
 3 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index e3cd6d76636c..6ab03bd548b3 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -132,6 +132,10 @@
  *    way after Live Update and ensures that IOMMU groups do not change. Note
  *    that a device will use its inherited ACS flags for the lifetime of its
  *    struct pci_dev (i.e. even after pci_liveupdate_finish()).
+ *
+ *  * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
+ *    preserved devices to ensure that all preserved devices on the bridge's
+ *    secondary bus are addressable after the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -720,6 +724,20 @@ bool pci_liveupdate_inherit_acs(struct pci_dev *dev)
 	return true;
 }
 
+bool pci_liveupdate_inherit_ari(struct pci_dev *dev)
+{
+	u16 val;
+
+	guard(read_lock)(&dev->liveupdate.lock);
+
+	if (!dev->liveupdate.incoming)
+		return false;
+
+	pcie_capability_read_word(dev, PCI_EXP_DEVCTL2, &val);
+	dev->ari_enabled = !!(val & PCI_EXP_DEVCTL2_ARI);
+	return true;
+}
+
 /**
  * pci_liveupdate_is_incoming() - Check if a device is incoming preserved
  * @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index c0826ca717e3..fd7693c7ddd2 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -16,6 +16,7 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev);
 bool pci_liveupdate_inherit_buses(void);
 void pci_liveupdate_init_acs(struct pci_dev *dev);
 bool pci_liveupdate_inherit_acs(struct pci_dev *dev);
+bool pci_liveupdate_inherit_ari(struct pci_dev *dev);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -38,6 +39,11 @@ static inline bool pci_liveupdate_inherit_acs(struct pci_dev *dev)
 {
 	return false;
 }
+
+static inline bool pci_liveupdate_inherit_ari(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index cd2c1f2ada92..7e9768dfe092 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3495,7 +3495,7 @@ void pci_configure_ari(struct pci_dev *dev)
 	u32 cap;
 	struct pci_dev *bridge;
 
-	if (pcie_ari_disabled || !pci_is_pcie(dev) || dev->devfn)
+	if (!pci_is_pcie(dev) || dev->devfn)
 		return;
 
 	bridge = dev->bus->self;
@@ -3506,6 +3506,12 @@ void pci_configure_ari(struct pci_dev *dev)
 	if (!(cap & PCI_EXP_DEVCAP2_ARI))
 		return;
 
+	if (pci_liveupdate_inherit_ari(bridge))
+		return;
+
+	if (pcie_ari_disabled)
+		return;
+
 	if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI)) {
 		pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2,
 					 PCI_EXP_DEVCTL2_ARI);
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:44 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:44 +0000
Subject: [PATCH v5 09/11] PCI: liveupdate: Freeze preservation status during shutdown
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-10-dmatlack@google.com>

Freeze a device's outgoing preservation status (preserved or not
preserved) during shutdown. This enables the PCI core and drivers to
safely make decisions based on the device's preservation status during
shutdown.

Note that pci_liveupdate_freeze() is triggered by the PCI core rather
than from drivers participating in Live Update so that all devices can
have their status frozen (i.e. prevent non-preserved devices from
getting preserved late).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 16 ++++++++++++++++
 drivers/pci/liveupdate.h       |  5 +++++
 drivers/pci/pci-driver.c       |  2 ++
 include/linux/pci_liveupdate.h |  3 +++
 4 files changed, 26 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 6ab03bd548b3..825166a57913 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -334,6 +334,11 @@ static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev
 
 	guard(write_lock)(&dev->liveupdate.lock);
 
+	if (dev->liveupdate.frozen) {
+		pci_WARN(dev, 1, "Cannot unpreserve device after it is frozen!\n");
+		return -EINVAL;
+	}
+
 	dev_ser = dev->liveupdate.outgoing;
 	if (!dev_ser) {
 		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
@@ -420,6 +425,11 @@ static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *d
 {
 	guard(write_lock)(&dev->liveupdate.lock);
 
+	if (dev->liveupdate.frozen) {
+		pci_WARN(dev, 1, "Cannot preserve device after it is frozen!\n");
+		return -EINVAL;
+	}
+
 	if (dev->liveupdate.outgoing)
 		return pci_liveupdate_preserve_device_existing(dev);
 	else
@@ -625,6 +635,12 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 		pci_liveupdate_flb_put_incoming();
 }
 
+void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+	guard(write_lock)(&dev->liveupdate.lock);
+	dev->liveupdate.frozen = 1;
+}
+
 static int __pci_liveupdate_finish_device(struct pci_dev *dev)
 {
 	guard(write_lock)(&dev->liveupdate.lock);
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index fd7693c7ddd2..30deaa673efe 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,7 @@
 #ifdef CONFIG_PCI_LIVEUPDATE
 void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+void pci_liveupdate_freeze(struct pci_dev *dev);
 bool pci_liveupdate_inherit_buses(void);
 void pci_liveupdate_init_acs(struct pci_dev *dev);
 bool pci_liveupdate_inherit_acs(struct pci_dev *dev);
@@ -26,6 +27,10 @@ static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 }
 
+static inline void pci_liveupdate_freeze(struct pci_dev *dev);
+{
+}
+
 static inline bool pci_liveupdate_inherit_buses(void)
 {
 	return false;
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index d10ece0889f0..f7a5e65a7c75 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -21,6 +21,7 @@
 #include <linux/acpi.h>
 #include <linux/dma-map-ops.h>
 #include <linux/iommu.h>
+#include "liveupdate.h"
 #include "pci.h"
 #include "pcie/portdrv.h"
 
@@ -536,6 +537,7 @@ static void pci_device_shutdown(struct device *dev)
 	struct pci_dev *pci_dev = to_pci_dev(dev);
 	struct pci_driver *drv = pci_dev->driver;
 
+	pci_liveupdate_freeze(pci_dev);
 	pm_runtime_resume(dev);
 
 	if (drv && drv->shutdown)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 34f9900c7d29..7e4ac7a0f4fc 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -22,6 +22,8 @@
  * @was_preserved: True if this struct pci_dev was preserved by the previous
  *                 kernel. Unlike @incoming, this field is not cleared after
  *                 the device is finished participating in Live Update.
+ * @frozen: True if the outgoing preservation status of this device is frozen
+ *          and thus cannot be changed.
  */
 struct pci_liveupdate {
 	rwlock_t lock;
@@ -29,6 +31,7 @@ struct pci_liveupdate {
 	struct pci_dev_ser *incoming;
 	u16 acs_ctrl;
 	unsigned int was_preserved:1;
+	unsigned int frozen:1;
 };
 
 struct pci_dev;
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:45 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:45 +0000
Subject: [PATCH v5 10/11] PCI: liveupdate: Do not disable bus mastering on
 preserved devices during kexec
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-11-dmatlack@google.com>

Do not disable bus mastering on outgoing preserved devices during
pci_device_shutdown() for kexec.

Preserved devices must be allowed to perform memory transactions during
a Live Update to minimize downtime and ensure continuous operation.
Clearing the bus mastering bit would prevent these devices from issuing
any memory requests while the new kernel boots.

Because bridges upstream of preserved endpoint devices are also
automatically preserved, this change also avoids clearing bus mastering
on them. This is critical because clearing bus mastering on an upstream
bridge prevents the bridge from forwarding memory requests upstream (i.e.
it would prevent the endpoint device from accessing system RAM and doing
peer-to-peer transactions with devices not downstream of the bridge).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c |  4 ++++
 drivers/pci/liveupdate.h | 12 ++++++++++++
 drivers/pci/pci-driver.c | 31 ++++++++++++++++++++++---------
 3 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 825166a57913..6c4b57d8f780 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -136,6 +136,10 @@
  *  * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
  *    preserved devices to ensure that all preserved devices on the bridge's
  *    secondary bus are addressable after the Live Update.
+ *
+ *  * The PCI core does not disable bus mastering on outgoing preserved devices
+ *    during kexec. This allows preserved devices to issue memory transactions
+ *    throughout the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 30deaa673efe..8ad404307a70 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -18,6 +18,13 @@ bool pci_liveupdate_inherit_buses(void);
 void pci_liveupdate_init_acs(struct pci_dev *dev);
 bool pci_liveupdate_inherit_acs(struct pci_dev *dev);
 bool pci_liveupdate_inherit_ari(struct pci_dev *dev);
+
+static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+	guard(read_lock)(&dev->liveupdate.lock);
+	pci_WARN_ONCE(dev, !dev->liveupdate.frozen, "Preservation status is unstable!\n");
+	return dev->liveupdate.outgoing;
+}
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -49,6 +56,11 @@ static inline bool pci_liveupdate_inherit_ari(struct pci_dev *dev)
 {
 	return false;
 }
+
+static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index f7a5e65a7c75..b6c931ebd3be 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -532,6 +532,27 @@ static void pci_device_remove(struct device *dev)
 	pci_dev_put(pci_dev);
 }
 
+/*
+ * Disable bus mastering on the device so that it does not perform memory
+ * transactions during kexec.
+ *
+ * Don't touch devices that are being preserved across kexec for Live
+ * Update or that are in D3cold or unknown states.
+ */
+static void pci_clear_master_for_shutdown(struct pci_dev *pci_dev)
+{
+	if (!kexec_in_progress)
+		return;
+
+	if (pci_liveupdate_is_outgoing(pci_dev))
+		return;
+
+	if (pci_dev->current_state > PCI_D3hot)
+		return;
+
+	pci_clear_master(pci_dev);
+}
+
 static void pci_device_shutdown(struct device *dev)
 {
 	struct pci_dev *pci_dev = to_pci_dev(dev);
@@ -543,15 +564,7 @@ static void pci_device_shutdown(struct device *dev)
 	if (drv && drv->shutdown)
 		drv->shutdown(pci_dev);
 
-	/*
-	 * If this is a kexec reboot, turn off Bus Master bit on the
-	 * device to tell it to not continue to do DMA. Don't touch
-	 * devices in D3cold or unknown states.
-	 * If it is not a kexec reboot, firmware will hit the PCI
-	 * devices with big hammer and stop their DMA any way.
-	 */
-	if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot))
-		pci_clear_master(pci_dev);
+	pci_clear_master_for_shutdown(pci_dev);
 }
 
 #ifdef CONFIG_PM_SLEEP
-- 
2.54.0.563.g4f69b47b94-goog



From dmatlack at google.com  Tue May 12 11:48:46 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 12 May 2026 18:48:46 +0000
Subject: [PATCH v5 11/11] Documentation: PCI: Add documentation for Live Update
In-Reply-To: <20260512184846.119396-1-dmatlack@google.com>
References: <20260512184846.119396-1-dmatlack@google.com>
Message-ID: <20260512184846.119396-12-dmatlack@google.com>

Add documentation files for the PCI subsystem's participation in Live
Update.

These documentation files are generated from the kernel-doc comments
in the PCI Live Update source code. They describe the File-Lifecycle
Bound (FLB) API, the device tracking API, and the specific policies
applied to preserved devices (such as bus number inheritance and bus
mastering preservation).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 Documentation/PCI/index.rst           |  1 +
 Documentation/PCI/liveupdate.rst      | 29 +++++++++++++++++++++++++++
 Documentation/core-api/liveupdate.rst |  1 +
 MAINTAINERS                           |  1 +
 4 files changed, 32 insertions(+)
 create mode 100644 Documentation/PCI/liveupdate.rst

diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e..23fb737ac969 100644
--- a/Documentation/PCI/index.rst
+++ b/Documentation/PCI/index.rst
@@ -20,3 +20,4 @@ PCI Bus Subsystem
    controller/index
    boot-interrupts
    tph
+   liveupdate
diff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst
new file mode 100644
index 000000000000..eba55f8a92ae
--- /dev/null
+++ b/Documentation/PCI/liveupdate.rst
@@ -0,0 +1,29 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+===========================
+PCI Support for Live Update
+===========================
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+   :doc: PCI Live Update
+
+Driver API
+==========
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+   :export:
+
+Live Update ABI
+===============
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+   :doc: PCI File-Lifecycle Bound (FLB) Live Update ABI
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+   :internal:
+
+See Also
+========
+
+ * :doc:`/core-api/liveupdate`
+ * :doc:`/core-api/kho/index`
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 5a292d0f3706..d56a7760978a 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -70,3 +70,4 @@ See Also
 
 - :doc:`Live Update uAPI </userspace-api/liveupdate>`
 - :doc:`/core-api/kho/index`
+- :doc:`PCI </PCI/liveupdate>`
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e262c0ceb43..6f0b0ebf67cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20536,6 +20536,7 @@ L:	kexec at lists.infradead.org
 L:	linux-pci at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	Documentation/PCI/liveupdate.rst
 F:	drivers/pci/liveupdate.c
 F:	drivers/pci/liveupdate.h
 F:	include/linux/kho/abi/pci.h
-- 
2.54.0.563.g4f69b47b94-goog



From skhawaja at google.com  Tue May 12 12:51:32 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 12 May 2026 19:51:32 +0000
Subject: [PATCH 0/3] kho: Add support for kunit mocking KHO restore API
Message-ID: <20260512195135.804833-1-skhawaja@google.com>

To write kunit tests for preservation and restoration of liveupdate
state in various subsystems without triggering the actual kexec, the KHO
restore API needs to be mocked by the test writer. The mocking is done
to allow testing of the individual components or functions in isolation.

The patch series adds the following to support kunit testing when using the KHO
API:

- Add static stub hooks to mock the KHO restore API so the restore path
  can be tested without triggering kexec.
- Add helper function that can be used by the test writer to check if
  memory is preserved in KHO tree.

Finally, it adds a KUnit test for the KHO API that verifies the allocation of
preserved memory, and the preservation/restoration of pages and folios.

KHO Kunit test run:

 KTAP version 1
 1..1
     KTAP version 1
     # Subtest: kho_test
     # module: kexec_handover_test
     1..3
     ok 1 kho_test_alloc_preserve
     ok 2 kho_test_preserve_pages
     ok 3 kho_test_preserve_folio
 # kho_test: pass:3 fail:0 skip:0 total:3
 # Totals: pass:3 fail:0 skip:0 total:3
 ok 1 kho_test

Samiullah Khawaja (3):
  kho: Add kunit static stubs
  kho: Add helper function to check if pages are preserved
  kho: Add kunit test to verify preserve/restore pages and folio

 include/linux/kexec_handover.h          |   5 +
 kernel/liveupdate/Kconfig               |  10 ++
 kernel/liveupdate/Makefile              |   1 +
 kernel/liveupdate/kexec_handover.c      |  61 ++++++++++-
 kernel/liveupdate/kexec_handover_test.c | 130 ++++++++++++++++++++++++
 5 files changed, 206 insertions(+), 1 deletion(-)
 create mode 100644 kernel/liveupdate/kexec_handover_test.c


base-commit: 9974969c14031a097d6b45bcb7a06bb4aa525c40
-- 
2.54.0.563.g4f69b47b94-goog



From skhawaja at google.com  Tue May 12 12:51:33 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 12 May 2026 19:51:33 +0000
Subject: [PATCH 1/3] kho: Add kunit static stubs
In-Reply-To: <20260512195135.804833-1-skhawaja@google.com>
References: <20260512195135.804833-1-skhawaja@google.com>
Message-ID: <20260512195135.804833-2-skhawaja@google.com>

Add kunit stubs in kho_restore_folio() and kho_restore_pages() so the
users can mock these functions in kunit tests.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 kernel/liveupdate/kexec_handover.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index d5718bef6d4d..1375291d9b07 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) "KHO: " fmt
 
 #include <linux/cleanup.h>
+#include <kunit/static_stub.h>
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
 #include <linux/count_zeros.h>
@@ -437,7 +438,11 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
  */
 struct folio *kho_restore_folio(phys_addr_t phys)
 {
-	struct page *page = kho_restore_page(phys, true);
+	struct page *page;
+
+	KUNIT_STATIC_STUB_REDIRECT(kho_restore_folio, phys);
+
+	page = kho_restore_page(phys, true);
 
 	return page ? page_folio(page) : NULL;
 }
@@ -459,6 +464,8 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)
 	const unsigned long end_pfn = start_pfn + nr_pages;
 	unsigned long pfn = start_pfn;
 
+	KUNIT_STATIC_STUB_REDIRECT(kho_restore_pages, phys, nr_pages);
+
 	while (pfn < end_pfn) {
 		const unsigned int order =
 			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-- 
2.54.0.563.g4f69b47b94-goog



From skhawaja at google.com  Tue May 12 12:51:34 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 12 May 2026 19:51:34 +0000
Subject: [PATCH 2/3] kho: Add helper function to check if pages are preserved
In-Reply-To: <20260512195135.804833-1-skhawaja@google.com>
References: <20260512195135.804833-1-skhawaja@google.com>
Message-ID: <20260512195135.804833-3-skhawaja@google.com>

Kunit test needs to check whether the pages are preserved after
preservation or after unpreserve. The helper function added is only
enabled when kunit is enabled.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/kexec_handover.h     |  5 +++
 kernel/liveupdate/kexec_handover.c | 52 ++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..a3afc74cf41c 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -32,6 +32,11 @@ void kho_restore_free(void *mem);
 struct folio *kho_restore_folio(phys_addr_t phys);
 struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
 void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
+
+#if IS_ENABLED(CONFIG_KUNIT)
+bool kho_test_pages_preserved(phys_addr_t phys, unsigned long nr_pages);
+#endif
+
 int kho_add_subtree(const char *name, void *blob, size_t size);
 void kho_remove_subtree(void *blob);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 1375291d9b07..f678372b3e81 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -12,6 +12,7 @@
 
 #include <linux/cleanup.h>
 #include <kunit/static_stub.h>
+#include <kunit/visibility.h>
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
 #include <linux/count_zeros.h>
@@ -1771,3 +1772,54 @@ int kho_locate_mem_hole(struct kexec_buf *kbuf,
 
 	return ret == 1 ? 0 : -EADDRNOTAVAIL;
 }
+
+#if IS_ENABLED(CONFIG_KUNIT)
+static bool _kho_is_pfn_preserved(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 (!tree->root)
+		return false;
+
+	guard(mutex)(&tree->lock);
+
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		idx = kho_radix_get_table_index(key, i);
+
+		if (!node->table[idx])
+			return false;
+
+		node = phys_to_virt(node->table[idx]);
+	}
+
+	idx = kho_radix_get_bitmap_index(key);
+	leaf = (struct kho_radix_leaf *)node;
+
+	return test_bit(idx, leaf->bitmap);
+}
+
+bool kho_test_pages_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	struct kho_radix_tree *tree = &kho_out.radix_tree;
+	unsigned long pfn = PHYS_PFN(phys);
+	unsigned long end_pfn;
+	unsigned int order;
+
+	end_pfn = pfn + nr_pages;
+	while (pfn < end_pfn) {
+		order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+
+		if (!_kho_is_pfn_preserved(tree, pfn, order))
+			return false;
+
+		pfn += 1 << order;
+	}
+
+	return true;
+}
+EXPORT_SYMBOL_IF_KUNIT(kho_test_pages_preserved);
+#endif
-- 
2.54.0.563.g4f69b47b94-goog



From skhawaja at google.com  Tue May 12 12:51:35 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 12 May 2026 19:51:35 +0000
Subject: [PATCH 3/3] kho: Add kunit test to verify preserve/restore pages and folio
In-Reply-To: <20260512195135.804833-1-skhawaja@google.com>
References: <20260512195135.804833-1-skhawaja@google.com>
Message-ID: <20260512195135.804833-4-skhawaja@google.com>

Add a kunit test to verify the preserve/unpreserve and restore of pages
and folios by mocking the kho kunit restore stubs.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 kernel/liveupdate/Kconfig               |  10 ++
 kernel/liveupdate/Makefile              |   1 +
 kernel/liveupdate/kexec_handover_test.c | 130 ++++++++++++++++++++++++
 3 files changed, 141 insertions(+)
 create mode 100644 kernel/liveupdate/kexec_handover_test.c

diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index c13af38ba23a..ffdfdc4080ef 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -49,6 +49,16 @@ config KEXEC_HANDOVER_ENABLE_DEFAULT
 	  The default behavior can still be overridden at boot time by
 	  passing 'kho=off'.
 
+config KEXEC_HANDOVER_KUNIT_TEST
+	tristate "KUnit test for Kexec Handover (KHO)"
+	depends on KEXEC_HANDOVER && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Enable Kunit tests for Kexec Handover to verify preservation and
+	  unpreservation of memory using KHO API without triggering kexec.
+
+	  If unsure, say N.
+
 config LIVEUPDATE
 	bool "Live Update Orchestrator"
 	depends on KEXEC_HANDOVER
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d2f779cbe279..ed798b2401a8 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -9,5 +9,6 @@ luo-y :=								\
 obj-$(CONFIG_KEXEC_HANDOVER)		+= kexec_handover.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)	+= kexec_handover_debug.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)	+= kexec_handover_debugfs.o
+obj-$(CONFIG_KEXEC_HANDOVER_KUNIT_TEST) += kexec_handover_test.o
 
 obj-$(CONFIG_LIVEUPDATE)		+= luo.o
diff --git a/kernel/liveupdate/kexec_handover_test.c b/kernel/liveupdate/kexec_handover_test.c
new file mode 100644
index 000000000000..1113007f9ac5
--- /dev/null
+++ b/kernel/liveupdate/kexec_handover_test.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Google LLC
+ * Author: Samiullah Khawaja <skhawaja at google.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/static_stub.h>
+#include <linux/kexec_handover.h>
+#include <linux/gfp.h>
+#include <linux/mm.h>
+
+static struct page *kho_test_restore_pages_mock(phys_addr_t phys,
+						unsigned long nr_pages)
+{
+	struct page *page = phys_to_page(phys);
+
+	if (!kho_test_pages_preserved(phys, nr_pages))
+		return NULL;
+
+	kho_unpreserve_pages(page, nr_pages);
+	return page;
+}
+
+static struct folio *kho_test_restore_folio_mock(phys_addr_t phys)
+{
+	struct folio *folio = page_folio(phys_to_page(phys));
+
+	if (!kho_test_pages_preserved(phys, (1 << folio_order(folio))))
+		return NULL;
+
+	kho_unpreserve_folio(folio);
+	return folio;
+}
+
+static int kho_test_init(struct kunit *test)
+{
+	kunit_activate_static_stub(test, kho_restore_pages,
+				   kho_test_restore_pages_mock);
+	kunit_activate_static_stub(test, kho_restore_folio,
+				   kho_test_restore_folio_mock);
+	return 0;
+}
+
+static void kho_test_alloc_preserve(struct kunit *test)
+{
+	void *mem;
+
+	mem = kho_alloc_preserve(PAGE_SIZE);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mem);
+
+	/* Verify if memory is preserved */
+	KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(__pa(mem), 1));
+
+	kho_restore_free(mem);
+
+	/* Memory should be unpreserved after restore_free */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(__pa(mem), 1));
+}
+
+static void kho_test_preserve_pages(struct kunit *test)
+{
+	struct page *restored;
+	struct page *page;
+	int err;
+
+	page = alloc_pages(GFP_KERNEL | __GFP_ZERO, 1);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+
+	/* Preserve and verify that pages are preserved */
+	err = kho_preserve_pages(page, 2);
+	KUNIT_EXPECT_EQ(test, err, 0);
+	KUNIT_EXPECT_TRUE(test,
+			  kho_test_pages_preserved(page_to_phys(page), 2));
+
+	restored = kho_restore_pages(page_to_phys(page), 2);
+	KUNIT_EXPECT_NOT_NULL(test, restored);
+	KUNIT_EXPECT_PTR_EQ(test, restored, page);
+
+	/* Verify that the pages are not preserved */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(page_to_phys(page), 2));
+
+	__free_pages(page, 1);
+}
+
+static void kho_test_preserve_folio(struct kunit *test)
+{
+	struct folio *restored;
+	unsigned long nr_pages;
+	struct folio *folio;
+	int err;
+
+	folio = folio_alloc(GFP_KERNEL | __GFP_ZERO, 1);
+	KUNIT_ASSERT_NOT_NULL(test, folio);
+
+	nr_pages = 1 << folio_order(folio);
+
+	/* Preserve and verify that folio is preserved */
+	err = kho_preserve_folio(folio);
+	KUNIT_EXPECT_EQ(test, err, 0);
+	KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(PFN_PHYS(folio_pfn(folio)), nr_pages));
+
+	restored = kho_restore_folio(PFN_PHYS(folio_pfn(folio)));
+	KUNIT_EXPECT_NOT_NULL(test, restored);
+	KUNIT_EXPECT_PTR_EQ(test, restored, folio);
+
+	/* Verify that the folio is not preserved */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(PFN_PHYS(folio_pfn(folio)), nr_pages));
+
+	folio_put(folio);
+}
+
+static struct kunit_case kho_test_cases[] = {
+	KUNIT_CASE(kho_test_alloc_preserve),
+	KUNIT_CASE(kho_test_preserve_pages),
+	KUNIT_CASE(kho_test_preserve_folio),
+	{}
+};
+
+static struct kunit_suite kho_test_suite = {
+	.name = "kho_test",
+	.init = kho_test_init,
+	.test_cases = kho_test_cases,
+};
+
+kunit_test_suite(kho_test_suite);
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_DESCRIPTION("KUnit tests for Kexec Handover");
+MODULE_LICENSE("GPL");
-- 
2.54.0.563.g4f69b47b94-goog



From pasha.tatashin at soleen.com  Tue May 12 13:06:27 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 12 May 2026 20:06:27 +0000
Subject: [PATCH 2/5] liveupdate: Remove limit on the number of files per
 session
In-Reply-To: <2vxzwlx8afwg.fsf@kernel.org>
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
 <20260414200237.444170-3-pasha.tatashin@soleen.com>
 <2vxzwlx8afwg.fsf@kernel.org>
Message-ID: <agNWbKNvpJFM4YE7@plex>

On 05-12 15:58, Pratyush Yadav wrote:
> On Tue, Apr 14 2026, Pasha Tatashin wrote:
> 
> > To remove the fixed limit on the number of preserved files per session,
> > transition the file metadata serialization from a single contiguous memory
> > block to a chain of linked blocks.
> 
> Same comment as previous patch about a general linked block data
> structure here.
> 
> >
> > ABI Changes:
> > - Define 'struct luo_file_header_ser' to manage linked blocks in preserved
> >   memory.
> > - Update 'struct luo_file_set_ser' to point to the head of the block chain.
> > - Bump the session ABI version to 'luo-session-v4' to reflect these breaking
> >   changes.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  include/linux/kho/abi/luo.h      |  35 ++++-
> >  kernel/liveupdate/luo_file.c     | 226 ++++++++++++++++++++-----------
> >  kernel/liveupdate/luo_internal.h |  17 ++-
> >  3 files changed, 189 insertions(+), 89 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> > index f5732958545e..a15f6a0f3d93 100644
> > --- a/include/linux/kho/abi/luo.h
> > +++ b/include/linux/kho/abi/luo.h
> > @@ -84,8 +84,13 @@
> >   *
> >   *   - struct luo_session_ser:
> >   *     Metadata for a single session, including its name and a physical pointer
> > - *     to another preserved memory block containing an array of
> > - *     `struct luo_file_ser` for all files in that session.
> > + *     to the first `struct luo_file_header_ser` for all files in that session.
> > + *     Multiple blocks are linked via the `next` field in the header.
> > + *
> > + *   - struct luo_file_header_ser:
> > + *     Header for the file data block. Contains the physical address of the
> > + *     next file data block and the number of `struct luo_file_ser` entries
> > + *     that follow this header in the current block.
> >   *
> >   *   - struct luo_file_ser:
> >   *     Metadata for a single preserved file. Contains the `compatible` string to
> > @@ -134,11 +139,29 @@ struct luo_file_ser {
> >  	u64 token;
> >  } __packed;
> >  
> > +/**
> > + * struct luo_file_header_ser - Header for the serialized file data block.
> > + * @next:  Physical address of the next struct luo_file_header_ser.
> > + * @count: The number of `struct luo_file_ser` entries that immediately
> > + *         follow this header in the memory block.
> > + *
> > + * This structure is located at the beginning of a block of
> > + * physical memory preserved across the kexec. It provides the necessary
> > + * metadata to interpret the array of file entries that follow.
> > + *
> > + * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
> > + */
> > +struct luo_file_header_ser {
> 
> Nit: Same as before, can we rename it to make it clear that this is a
> block header?

Done

> 
> > +	u64 next;
> > +	u64 count;
> > +} __packed;
> > +
> >  /**
> >   * struct luo_file_set_ser - Represents the serialized metadata for file set
> > - * @files:   The physical address of a contiguous memory block that holds
> > - *           the serialized state of files (array of luo_file_ser) in this file
> > - *           set.
> > + * @files:   The physical address of the first `struct luo_file_header_ser`.
> > + *           This structure is the header for a block of memory containing
> > + *           an array of `struct luo_file_ser` entries. Multiple blocks are
> > + *           linked via the `next` field in the header.
> >   * @count:   The total number of files that were part of this session during
> >   *           serialization. Used for iteration and validation during
> >   *           restoration.
> > @@ -154,7 +177,7 @@ struct luo_file_set_ser {
> >   *                          luo_session_header_ser
> >   */
> >  #define LUO_FDT_SESSION_NODE_NAME	"luo-session"
> > -#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v3"
> > +#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v4"
> >  #define LUO_FDT_SESSION_HEADER		"luo-session-header"
> >  
> >  /**
> > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> > index dedbb8d02e6a..20a2d776cff4 100644
> > --- a/kernel/liveupdate/luo_file.c
> > +++ b/kernel/liveupdate/luo_file.c
> > @@ -119,10 +119,12 @@ static LIST_HEAD(luo_file_handler_list);
> >  /* Keep track of files being preserved by LUO */
> >  static DEFINE_XARRAY(luo_preserved_files);
> >  
> > -/* 2 4K pages, give space for 128 files per file_set */
> > +/* 2 4K pages, give space for 127 files per block */
> >  #define LUO_FILE_PGCNT		2ul
> > -#define LUO_FILE_MAX							\
> > -	((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
> > +#define LUO_FILE_BLOCK_MAX					\
> > +	(((LUO_FILE_PGCNT << PAGE_SHIFT) -			\
> > +	  sizeof(struct luo_file_header_ser)) /			\
> > +	 sizeof(struct luo_file_ser))
> >  
> >  /**
> >   * struct luo_file - Represents a single preserved file instance.
> > @@ -158,7 +160,7 @@ static DEFINE_XARRAY(luo_preserved_files);
> >   * and the serialized state handle returned by the handler's .preserve()
> >   * operation.
> >   *
> > - * These instances are tracked in a per-file_set list. The @serialized_data
> > + * These structures are tracked in a per-file_set list. The @serialized_data
> >   * field, which holds a handle to the file's serialized state, may be updated
> >   * during the .freeze() callback before being serialized for the next kernel.
> >   * After reboot, these structures are recreated by luo_file_deserialize() and
> > @@ -175,37 +177,68 @@ struct luo_file {
> >  	u64 token;
> >  };
> >  
> > -static int luo_alloc_files_mem(struct luo_file_set *file_set)
> > +static int luo_file_add_block(struct luo_file_set *file_set,
> > +			      struct luo_file_header_ser *ser)
> >  {
> > -	size_t size;
> > -	void *mem;
> > +	struct luo_file_block *block;
> >  
> > -	if (file_set->files)
> > -		return 0;
> > -
> > -	WARN_ON_ONCE(file_set->count);
> > +	if (file_set->nblocks >= LUO_MAX_BLOCKS)
> > +		return -ENOSPC;
> >  
> > -	size = LUO_FILE_PGCNT << PAGE_SHIFT;
> > -	mem = kho_alloc_preserve(size);
> > -	if (IS_ERR(mem))
> > -		return PTR_ERR(mem);
> > +	block = kzalloc_obj(*block);
> > +	if (!block)
> > +		return -ENOMEM;
> >  
> > -	file_set->files = mem;
> > +	block->ser = ser;
> > +	list_add_tail(&block->list, &file_set->blocks);
> > +	file_set->nblocks++;
> >  
> >  	return 0;
> >  }
> >  
> > -static void luo_free_files_mem(struct luo_file_set *file_set)
> > +static int luo_file_create_ser_block(struct luo_file_set *file_set)
> >  {
> > -	/* If file_set has files, no need to free preservation memory */
> > -	if (file_set->count)
> > -		return;
> > +	struct luo_file_block *last = NULL;
> > +	struct luo_file_header_ser *ser;
> > +	int err;
> >  
> > -	if (!file_set->files)
> > -		return;
> > +	ser = kho_alloc_preserve(LUO_FILE_PGCNT << PAGE_SHIFT);
> > +	if (IS_ERR(ser))
> > +		return PTR_ERR(ser);
> > +
> > +	if (!list_empty(&file_set->blocks))
> > +		last = list_last_entry(&file_set->blocks, struct luo_file_block, list);
> > +
> > +	err = luo_file_add_block(file_set, ser);
> > +	if (err)
> > +		goto err_unpreserve;
> > +
> > +	if (last)
> > +		last->ser->next = virt_to_phys(ser);
> 
> Same comments as last patch about list_last_entry_or_null() and moving
> this to luo_file_add_block().

Done

> 
> > +
> > +	return 0;
> >  
> > -	kho_unpreserve_free(file_set->files);
> > -	file_set->files = NULL;
> > +err_unpreserve:
> > +	kho_unpreserve_free(ser);
> > +	return err;
> > +}
> > +
> > +static void luo_file_destroy_ser_blocks(struct luo_file_set *file_set,
> > +					bool unpreserve)
> > +{
> > +	struct luo_file_block *block, *tmp;
> > +
> > +	list_for_each_entry_safe(block, tmp, &file_set->blocks, list) {
> > +		if (block->ser) {
> 
> Same here. block is always created with its ser, so why have this check?

Removed.

> 
> > +			if (unpreserve)
> > +				kho_unpreserve_free(block->ser);
> > +			else
> > +				kho_restore_free(block->ser);
> 
> And same comment about the nastiness here, but once again I have nothing
> better to offer.
> 
> > +		}
> > +		list_del(&block->list);
> > +		kfree(block);
> > +		file_set->nblocks--;
> > +	}
> >  }
> >  
> >  static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
> > @@ -247,7 +280,9 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
> >   * 3. Iterates through registered handlers, calling can_preserve() to find one
> >   *    compatible with the given @fd.
> >   * 4. Calls the handler's .preserve() operation, which saves the file's state
> > - *    and returns an opaque private data handle.
> > + *    and returns an opaque u64 handle. This is typically performed while the
> > + *    workload is still active to minimize the downtime during the
> > + *    actual reboot transition.
> >   * 5. Adds the new instance to the file_set's internal list.
> >   *
> >   * On success, LUO takes a reference to the 'struct file' and considers it
> > @@ -277,17 +312,16 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> >  	if (luo_token_is_used(file_set, token))
> >  		return -EEXIST;
> >  
> > -	if (file_set->count == LUO_FILE_MAX)
> > -		return -ENOSPC;
> > +	if (file_set->count == file_set->nblocks * LUO_FILE_BLOCK_MAX) {
> > +		err = luo_file_create_ser_block(file_set);
> > +		if (err)
> > +			return err;
> > +	}
> >  
> >  	file = fget(fd);
> >  	if (!file)
> >  		return -EBADF;
> >  
> > -	err = luo_alloc_files_mem(file_set);
> > -	if (err)
> > -		goto  err_fput;
> > -
> >  	err = -ENOENT;
> >  	down_read(&luo_register_rwlock);
> >  	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> > @@ -301,7 +335,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> >  
> >  	/* err is still -ENOENT if no handler was found */
> >  	if (err)
> > -		goto err_free_files_mem;
> > +		goto err_fput;
> >  
> >  	/* safe to use fh because its module is pinned */
> >  	err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
> > @@ -345,8 +379,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> >  	xa_erase(&luo_preserved_files, luo_get_id(fh, file));
> >  err_module_put:
> >  	module_put(fh->ops->owner);
> > -err_free_files_mem:
> > -	luo_free_files_mem(file_set);
> >  err_fput:
> >  	fput(file);
> >  
> > @@ -399,7 +431,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
> >  		kfree(luo_file);
> >  	}
> >  
> > -	luo_free_files_mem(file_set);
> > +	luo_file_destroy_ser_blocks(file_set, true);
> >  }
> >  
> >  static int luo_file_freeze_one(struct luo_file_set *file_set,
> > @@ -446,6 +478,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> >  				struct luo_file *failed_entry)
> >  {
> >  	struct list_head *files_list = &file_set->files_list;
> > +	struct luo_file_block *block;
> >  	struct luo_file *luo_file;
> >  
> >  	list_for_each_entry(luo_file, files_list, list) {
> > @@ -455,7 +488,11 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> >  		luo_file_unfreeze_one(file_set, luo_file);
> >  	}
> >  
> > -	memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
> > +	list_for_each_entry(block, &file_set->blocks, list) {
> > +		block->ser->count = 0;
> > +		memset(block->ser + 1, 0,
> > +		       (LUO_FILE_PGCNT << PAGE_SHIFT) - sizeof(*block->ser));
> > +	}
> >  }
> >  
> >  /**
> > @@ -494,7 +531,8 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> >  int luo_file_freeze(struct luo_file_set *file_set,
> >  		    struct luo_file_set_ser *file_set_ser)
> >  {
> > -	struct luo_file_ser *file_ser = file_set->files;
> > +	struct luo_file_block *block;
> > +	struct luo_file_ser *file_ser;
> >  	struct luo_file *luo_file;
> >  	int err;
> >  	int i;
> > @@ -502,11 +540,18 @@ int luo_file_freeze(struct luo_file_set *file_set,
> >  	if (!file_set->count)
> >  		return 0;
> >  
> > -	if (WARN_ON(!file_ser))
> > -		return -EINVAL;
> > +	block = list_first_entry(&file_set->blocks, struct luo_file_block, list);
> > +	file_ser = (void *)(block->ser + 1);
> >  
> >  	i = 0;
> >  	list_for_each_entry(luo_file, &file_set->files_list, list) {
> > +		if (i == LUO_FILE_BLOCK_MAX) {
> > +			block->ser->count = i;
> > +			block = list_next_entry(block, list);
> > +			file_ser = (void *)(block->ser + 1);
> > +			i = 0;
> > +		}
> > +
> >  		err = luo_file_freeze_one(file_set, luo_file);
> >  		if (err < 0) {
> >  			pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> > @@ -521,10 +566,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
> >  		file_ser[i].token = luo_file->token;
> >  		i++;
> >  	}
> > +	block->ser->count = i;
> >  
> >  	file_set_ser->count = file_set->count;
> > -	if (file_set->files)
> > -		file_set_ser->files = virt_to_phys(file_set->files);
> > +	if (!list_empty(&file_set->blocks)) {
> > +		block = list_first_entry(&file_set->blocks, struct luo_file_block, list);
> > +		file_set_ser->files = virt_to_phys(block->ser);
> > +	}
> >  
> >  	return 0;
> >  
> > @@ -746,10 +794,7 @@ int luo_file_finish(struct luo_file_set *file_set)
> >  		kfree(luo_file);
> >  	}
> >  
> > -	if (file_set->files) {
> > -		kho_restore_free(file_set->files);
> > -		file_set->files = NULL;
> > -	}
> > +	luo_file_destroy_ser_blocks(file_set, false);
> >  
> >  	return 0;
> >  }
> > @@ -782,8 +827,10 @@ int luo_file_finish(struct luo_file_set *file_set)
> >  int luo_file_deserialize(struct luo_file_set *file_set,
> >  			 struct luo_file_set_ser *file_set_ser)
> >  {
> > -	struct luo_file_ser *file_ser;
> > -	u64 i;
> > +	struct luo_file_header_ser *header_ser;
> > +	struct luo_file_block *block;
> > +	u64 header_ser_pa;
> > +	int err;
> >  
> >  	if (!file_set_ser->files) {
> >  		WARN_ON(file_set_ser->count);
> > @@ -791,7 +838,15 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> >  	}
> >  
> >  	file_set->count = file_set_ser->count;
> > -	file_set->files = phys_to_virt(file_set_ser->files);
> > +	header_ser_pa = file_set_ser->files;
> > +
> > +	while (header_ser_pa) {
> > +		header_ser = phys_to_virt(header_ser_pa);
> > +		err = luo_file_add_block(file_set, header_ser);
> > +		if (err)
> > +			return err;
> > +		header_ser_pa = header_ser->next;
> > +	}
> >  
> >  	/*
> >  	 * Note on error handling:
> > @@ -808,42 +863,51 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> >  	 * userspace to detect the failure and trigger a reboot, which will
> >  	 * reliably reset devices and reclaim memory.
> >  	 */
> > -	file_ser = file_set->files;
> > -	for (i = 0; i < file_set->count; i++) {
> > -		struct liveupdate_file_handler *fh;
> > -		bool handler_found = false;
> > -		struct luo_file *luo_file;
> > -
> > -		down_read(&luo_register_rwlock);
> > -		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> > -			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
> > -				if (try_module_get(fh->ops->owner))
> > -					handler_found = true;
> > -				break;
> > -			}
> > -		}
> > -		up_read(&luo_register_rwlock);
> > +	list_for_each_entry(block, &file_set->blocks, list) {
> > +		struct luo_file_ser *file_ser = (void *)(block->ser + 1);
> >  
> > -		if (!handler_found) {
> > -			pr_warn("No registered handler for compatible '%.*s'\n",
> > -				(int)sizeof(file_ser[i].compatible),
> > -				file_ser[i].compatible);
> > -			return -ENOENT;
> > +		if (block->ser->count > LUO_FILE_BLOCK_MAX) {
> > +			pr_warn("File block contains too many entries: %llu\n",
> > +				block->ser->count);
> > +			return -EINVAL;
> >  		}
> >  
> > -		luo_file = kzalloc_obj(*luo_file);
> > -		if (!luo_file) {
> > -			module_put(fh->ops->owner);
> > -			return -ENOMEM;
> > -		}
> > +		for (int i = 0; i < block->ser->count; i++) {
> > +			struct liveupdate_file_handler *fh;
> > +			bool handler_found = false;
> > +			struct luo_file *luo_file;
> > +
> > +			down_read(&luo_register_rwlock);
> > +			list_private_for_each_entry(fh, &luo_file_handler_list, list) {
> > +				if (!strcmp(fh->compatible, file_ser[i].compatible)) {
> > +					if (try_module_get(fh->ops->owner))
> > +						handler_found = true;
> > +					break;
> > +				}
> > +			}
> > +			up_read(&luo_register_rwlock);
> >  
> > -		/* safe to use fh because its module is pinned */
> > -		luo_file->fh = fh;
> > -		luo_file->file = NULL;
> > -		luo_file->serialized_data = file_ser[i].data;
> > -		luo_file->token = file_ser[i].token;
> > -		mutex_init(&luo_file->mutex);
> > -		list_add_tail(&luo_file->list, &file_set->files_list);
> > +			if (!handler_found) {
> > +				pr_warn("No registered handler for compatible '%.*s'\n",
> > +					(int)sizeof(file_ser[i].compatible),
> > +					file_ser[i].compatible);
> > +				return -ENOENT;
> > +			}
> > +
> > +			luo_file = kzalloc_obj(*luo_file);
> > +			if (!luo_file) {
> > +				module_put(fh->ops->owner);
> > +				return -ENOMEM;
> > +			}
> > +
> > +			/* safe to use fh because its module is pinned */
> > +			luo_file->fh = fh;
> > +			luo_file->file = NULL;
> > +			luo_file->serialized_data = file_ser[i].data;
> > +			luo_file->token = file_ser[i].token;
> > +			mutex_init(&luo_file->mutex);
> > +			list_add_tail(&luo_file->list, &file_set->files_list);
> > +		}
> 
> Nit: this diff is very hard to read. Perhaps splitting this into helper
> functions would help?

Done, also added a patch to do the same for sessions.

> 
> >  	}
> >  
> >  	return 0;
> > @@ -852,12 +916,14 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> >  void luo_file_set_init(struct luo_file_set *file_set)
> >  {
> >  	INIT_LIST_HEAD(&file_set->files_list);
> > +	INIT_LIST_HEAD(&file_set->blocks);
> >  }
> >  
> >  void luo_file_set_destroy(struct luo_file_set *file_set)
> >  {
> >  	WARN_ON(file_set->count);
> >  	WARN_ON(!list_empty(&file_set->files_list));
> > +	WARN_ON(!list_empty(&file_set->blocks));
> >  }
> >  
> >  /**
> > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> > index a73f42069301..a92aaa9c073d 100644
> > --- a/kernel/liveupdate/luo_internal.h
> > +++ b/kernel/liveupdate/luo_internal.h
> > @@ -50,19 +50,30 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
> >   */
> >  #define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__)
> >  
> > +/**
> > + * struct luo_file_block - Internal representation of a file serialization block.
> > + * @list: List head for linking blocks in memory.
> > + * @ser:  Pointer to the serialized header in preserved memory.
> > + */
> > +struct luo_file_block {
> > +	struct list_head list;
> > +	struct luo_file_header_ser *ser;
> > +};
> > +
> >  /**
> >   * struct luo_file_set - A set of files that belong to the same sessions.
> >   * @files_list: An ordered list of files associated with this session, it is
> >   *              ordered by preservation time.
> > - * @files:      The physically contiguous memory block that holds the serialized
> > - *              state of files.
> > + * @blocks:     The list of serialization blocks (struct luo_file_block).
> >   * @count:      A counter tracking the number of files currently stored in the
> >   *              @files_list for this session.
> > + * @nblocks:    The number of allocated serialization blocks.
> >   */
> >  struct luo_file_set {
> >  	struct list_head files_list;
> > -	struct luo_file_ser *files;
> > +	struct list_head blocks;
> >  	long count;
> > +	long nblocks;
> >  };
> >  
> >  /**
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Tue May 12 13:13:02 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 12 May 2026 20:13:02 +0000
Subject: [PATCH 3/5] selftests/liveupdate: Test session and file limit
 removal
In-Reply-To: <2vxzse7wafmo.fsf@kernel.org>
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
 <20260414200237.444170-4-pasha.tatashin@soleen.com>
 <2vxzse7wafmo.fsf@kernel.org>
Message-ID: <agOJui6EOiJR3cgn@plex>

On 05-12 16:04, Pratyush Yadav wrote:
> On Tue, Apr 14 2026, Pasha Tatashin wrote:
> 
> > With the removal of static limits on the number of sessions and files per
> > session, the orchestrator now uses dynamic allocation.
> >
> > Add new test cases to verify that the system can handle a large number of
> > sessions and files. These tests ensure that the dynamic block allocation
> > and reuse logic for session metadata and outgoing files work correctly
> > beyond the previous static limits.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  .../testing/selftests/liveupdate/liveupdate.c | 99 +++++++++++++++++++
> >  1 file changed, 99 insertions(+)
> >
> > diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
> > index 37c808fbe1e9..0eaf97b19267 100644
> > --- a/tools/testing/selftests/liveupdate/liveupdate.c
> > +++ b/tools/testing/selftests/liveupdate/liveupdate.c
> > @@ -22,6 +22,7 @@
> >  #include <fcntl.h>
> >  #include <string.h>
> >  #include <sys/ioctl.h>
> > +#include <sys/resource.h>
> >  #include <unistd.h>
> >  
> >  #include <linux/liveupdate.h>
> > @@ -386,4 +387,102 @@ TEST_F(liveupdate_device, prevent_double_preservation)
> >  	ASSERT_EQ(close(session_fd2), 0);
> >  }
> >  
> > +static void ensure_nofile_limit(struct __test_metadata *_metadata,
> > +				long min_limit)
> > +{
> > +	struct rlimit hl;
> > +
> > +	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
> > +		ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno));
> > +
> > +	if (hl.rlim_cur >= min_limit)
> > +		return;
> > +
> > +	hl.rlim_cur = min_limit;
> > +	if (hl.rlim_cur > hl.rlim_max)
> > +		hl.rlim_max = hl.rlim_cur;
> > +
> > +	if (setrlimit(RLIMIT_NOFILE, &hl) < 0) {
> > +		if (errno == EPERM) {
> > +			SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE to %ld",
> > +			     hl.rlim_cur);
> > +		}
> > +		ksft_exit_fail_msg("setrlimit to %ld failed: %s\n",
> > +				   hl.rlim_cur, strerror(errno));
> > +	}
> > +}
> > +
> > +/*
> > + * Test Case: Manage Many Sessions
> > + *
> > + * Verifies that a large number of sessions can be created and then
> > + * destroyed during normal system operation. This specifically tests the
> > + * dynamic block allocation and reuse logic for session metadata management
> > + * without preserving any files.
> > + */
> > +TEST_F(liveupdate_device, preserve_many_sessions)
> > +{
> > +#define MANY_SESSIONS 2000
> > +	int session_fds[MANY_SESSIONS];
> > +	int i;
> > +
> > +	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
> > +	if (self->fd1 < 0 && errno == ENOENT)
> > +		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
> > +	ASSERT_GE(self->fd1, 0);
> > +
> > +	ensure_nofile_limit(_metadata, MANY_SESSIONS + 10);
> > +	if (_metadata->exit_code == KSFT_SKIP)
> > +		return;
> 
> Nit: This is strange. Why not just return errno in ensure_nofile_limit()
> and handle the skipping here? Same below.
> 
> LGTM otherwise.

Good suggestion. Done.



From pasha.tatashin at soleen.com  Tue May 12 13:41:21 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 12 May 2026 20:41:21 +0000
Subject: [PATCH 4/5] selftests/liveupdate: Add stress-sessions kexec test
In-Reply-To: <2vxzlddoafel.fsf@kernel.org>
References: <20260414200237.444170-1-pasha.tatashin@soleen.com>
 <20260414200237.444170-5-pasha.tatashin@soleen.com>
 <2vxzlddoafel.fsf@kernel.org>
Message-ID: <agOQa0aGZMVRlCu2@plex>

On 05-12 16:09, Pratyush Yadav wrote:
> On Tue, Apr 14 2026, Pasha Tatashin wrote:
> 
> > Add a new test that creates 2000 LUO sessions before a kexec
> > reboot and verifies their presence after the reboot. This ensures
> > that the linked-block serialization mechanism works correctly for
> > a large number of sessions.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> > diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
> > index 3c8721c505df..37c330b9bb36 100644
> > --- a/tools/testing/selftests/liveupdate/luo_test_utils.c
> > +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
> > @@ -20,6 +20,7 @@
> >  #include <sys/stat.h>
> >  #include <errno.h>
> >  #include <stdarg.h>
> > +#include <sys/resource.h>
> >  
> >  #include "luo_test_utils.h"
> >  
> > @@ -28,6 +29,30 @@ int luo_open_device(void)
> >  	return open(LUO_DEVICE, O_RDWR);
> >  }
> >  
> > +void luo_ensure_nofile_limit(long min_limit)
> 
> Since you are adding this to luo_test_utils anyway, I think you should
> do it in patch 3 and use it there too.

Done

> 
> LGTM otherwise.
> 
> > +{
> > +	struct rlimit hl;
> > +
> > +	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
> > +		ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno));
> > +
> > +	if (hl.rlim_cur >= min_limit)
> > +		return;
> > +
> > +	hl.rlim_cur = min_limit;
> > +	if (hl.rlim_cur > hl.rlim_max)
> > +		hl.rlim_max = hl.rlim_cur;
> > +
> > +	if (setrlimit(RLIMIT_NOFILE, &hl) < 0) {
> > +		if (errno == EPERM) {
> > +			ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE to %ld\n",
> > +				       hl.rlim_cur);
> > +		}
> > +		ksft_exit_fail_msg("setrlimit to %ld failed: %s\n",
> > +				   hl.rlim_cur, strerror(errno));
> > +	}
> > +}
> > +
> >  int luo_create_session(int luo_fd, const char *name)
> >  {
> >  	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
> 
> -- 
> Regards,
> Pratyush Yadav


From k-hagio-ab at nec.com  Tue May 12 16:45:58 2026
From: k-hagio-ab at nec.com (=?utf-8?B?SEFHSU8gS0FaVUhJVE8o6JCp5bC+44CA5LiA5LuBKQ==?=)
Date: Tue, 12 May 2026 23:45:58 +0000
Subject: [PATCH][makedumpfile] Fix riscv64 virtual-to-physical address
 translation
In-Reply-To: <20260512072142.58396-1-qirui.001@bytedance.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com>
 <20260512072142.58396-1-qirui.001@bytedance.com>
Message-ID: <28dced8f-a9c5-4514-b7d8-8110fd693133@nec.com>

On 2026/05/12 16:21, Rui Qi wrote:
> Ping.
> 
> This patchset has been out for 6 days without feedback. Could anyone
> please take a look?

Thank you for the patchset.  It's in my queue, please wait for a while.

Thanks,
Kazu

> 
> Link: https://lists.infradead.org/pipermail/kexec/2026-May/036059.html
> 
> Thanks,
> Rui Qi

From rppt at kernel.org  Wed May 13 00:50:40 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Wed, 13 May 2026 10:50:40 +0300
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <2vxzcxz2dybi.fsf@kernel.org>
References: <20260509024415.33190-1-dongtai.guo@linux.dev>
 <2vxzcxz2dybi.fsf@kernel.org>
Message-ID: <agQtUOImdpoYrfkV@kernel.org>

On Mon, May 11, 2026 at 12:40:01PM +0200, Pratyush Yadav wrote:
> On Sat, May 09 2026, George Guo wrote:
> 
> > From: George Guo <guodongtai at kylinos.cn>
> >
> > KHO_TREE_MAX_DEPTH is calculated as:
> >
> >   DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
> >                KHO_TABLE_SIZE_LOG2) + 1
> >
> > For systems with 16KB pages (e.g. LoongArch), this gives a depth of 4,
> 
> As of now, we only support KHO on x86 and arm64. Support for other
> architectures is not there. Are you working on supporting it for
> LoongArch? What is your use case?

arm64 can have 16k pages, but ...
 
> So based on this, looks to me that the 5th level table won't ever go
> above index 0 and the 4th level is enough to represent all possible
> keys.
 
... as it looks like your math is correct, I'm going to drop the patch for
now.

> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From hu.shengming at zte.com.cn  Wed May 13 02:11:50 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Wed, 13 May 2026 17:11:50 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIXSBSSVNDLVY6IGZpeCBjcmFzaGR1bXAgbG9hZCBtZW1vcnkgcmFuZ2VzwqA=?=
Message-ID: <20260513171150519t11GTC14QEEV4EgIurNHM@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: validate_mem_section: Can't read mem_section array.
 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: get_mm_sparsemem: Can't get the address of mem_section.
 kdump.sh[338]: The kernel version is not supported.
 kdump.sh[338]: The makedumpfile operation may be incomplete.

crash_mem_ranges only includes available memory, not all system RAM.
Inspired by the arm64 architecture, use system_mem_ranges in the
load_elfcorehdr function.

Fixes: b3fd54b (RISC-V: Add support for riscv kexec/kdump on kexec-tools)
Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 167 ++++++++++++++++++-----------
 1 file changed, 102 insertions(+), 65 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 336d7a7..1589dba 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -6,6 +6,7 @@
 #include "crashdump.h"
 #include "kexec-elf.h"
 #include "mem_regions.h"
+#include "iomem.h"

 static struct crash_elf_info elf_info = {
 #if __riscv_xlen == 64
@@ -18,6 +19,7 @@ static struct crash_elf_info elf_info = {
 };

 static struct memory_ranges crash_mem_ranges = {0};
+static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

 static unsigned long long get_page_offset(struct kexec_info *info)
@@ -34,86 +36,114 @@ static unsigned long long get_page_offset(struct kexec_info *info)
 	return vaddr_off;
 }

-int load_elfcorehdr(struct kexec_info *info)
+/*
+ * iomem_range_callback() - callback called for each iomem region
+ * @data: not used
+ * @nr: not used
+ * @str: name of the memory region
+ * @base: start address of the memory region
+ * @length: size of the memory region
+ *
+ * This function is called once for each memory region found in /proc/iomem.
+ * It locates system RAM and crashkernel reserved memory and places these to
+ * variables, respectively, system_memory_rgns and crash_mem_ranges.
+ */
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+				char *str, unsigned long long base,
+				unsigned long long length)
 {
-	struct memory_range crashkern_range = {0};
-	struct memory_range *ranges = NULL;
-	unsigned long start = 0;
-	unsigned long end = 0;
-	unsigned long buf_size = 0;
-	unsigned long elfcorehdr_addr = 0;
-	void* buf = NULL;
-	int i = 0;
-	int ret = 0;
+	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+		return mem_regions_alloc_and_add(&crash_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+		return mem_regions_alloc_and_add(&system_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+		elf_info.kern_paddr_start = base;
+	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+		elf_info.kern_size = base + length - elf_info.kern_paddr_start;

-	ret = parse_iomem_single("Kernel code\n", &start, NULL);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical base addr\n");
-		return -EINVAL;
-	}
-	elf_info.kern_paddr_start = start;
+	return 0;
+}
+
+/*
+ * crash_get_memory_ranges() - read system physical memory
+ *
+ * Function reads through system physical memory and stores found memory
+ * regions in system_memory_ranges.
+ * Regions are sorted in ascending order.
+ *
+ * Returns 0 in case of success and a negative value otherwise.
+ */
+static int crash_get_memory_ranges(void)
+{
+	int i;
+
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	ret = parse_iomem_single("Kernel bss\n", NULL, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+	if (!crash_mem_ranges.size)
 		return -EINVAL;
+
+	dbgprint_mem_range("Reserved memory range",
+			crash_mem_ranges.ranges, crash_mem_ranges.size);
+
+	for (i = 0; i < crash_mem_ranges.size; i++) {
+		if (mem_regions_alloc_and_exclude(&system_mem_ranges,
+					&crash_mem_ranges.ranges[i])) {
+			fprintf(stderr, "Cannot allocate memory for ranges\n");
+			return -ENOMEM;
+		}
 	}
-	elf_info.kern_paddr_start = start;
-	elf_info.kern_size = end - start;

+	/*
+	 * Make sure that the memory regions are sorted.
+	 */
+	mem_regions_sort(&system_mem_ranges);
+
+	dbgprint_mem_range("Coredump memory ranges",
+			   system_mem_ranges.ranges, system_mem_ranges.size);
+
+	/*
+	 * For additional kernel code/data segment.
+	 * kern_paddr_start/kern_size are determined in iomem_range_callback
+	 */
 	elf_info.kern_vaddr_start = get_kernel_sym("_text");
-	if (!elf_info.kern_vaddr_start) {
+	if (!elf_info.kern_vaddr_start)
 		elf_info.kern_vaddr_start = UINT64_MAX;
-	}

-	elf_info.page_offset = get_page_offset(info);
-	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
+	return 0;
+}

-	ret = parse_iomem_single("Crash kernel\n", &start, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
-		return -EINVAL;
-	}
-	crashkern_range.start = start;
-	crashkern_range.end = end;
-	crashkern_range.type = RANGE_RESERVED;
-
-	ranges = info->memory_range;
-	for (i = 0; i < info->memory_ranges; i++) {
-		ret = mem_regions_alloc_and_add(&crash_mem_ranges,
-						ranges[i].start,
-						ranges[i].end - ranges[i].start + 1,
-						ranges[i].type);
-		if (ret ) {
-			fprintf(stderr, "Could not create crash_mem_ranges\n");
-			return ret;
-		}
-	}
+int load_elfcorehdr(struct kexec_info *info)
+{
+	unsigned long buf_size = 0;
+	unsigned long elfcorehdr_addr = 0;
+	void* buf = NULL;

-	ret = mem_regions_alloc_and_exclude(&crash_mem_ranges,
-					    &crashkern_range);
-	if (ret) {
-		fprintf(stderr, "Could not exclude crashkern_range\n");
-		return ret;
-	}
+	if (crash_get_memory_ranges())
+		return -EFAILED;
+
+	elf_info.page_offset = get_page_offset(info);
+	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
-	crash_create_elf64_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf64_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);

 #else
-	crash_create_elf32_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf32_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);
 #endif


 	elfcorehdr_addr = add_buffer_phys_virt(info, buf, buf_size,
-					       buf_size, 0,
-					       crashkern_range.start,
-					       crashkern_range.end,
-					       -1, 0);
+					    buf_size, 0,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end,
+					    -1, 0);

 	elfcorehdr_mem.start = elfcorehdr_addr;
 	elfcorehdr_mem.end = elfcorehdr_addr + buf_size - 1;
@@ -126,15 +156,22 @@ int load_elfcorehdr(struct kexec_info *info)

 int is_crashkernel_mem_reserved(void)
 {
-	uint64_t start = 0;
-	uint64_t end = 0;
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
-	       (start != end) : 0;
+	return crash_mem_ranges.size;
 }

 int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
 {
-	return parse_iomem_single("Crash kernel\n", start, end);
-}
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+	if (!crash_mem_ranges.size)
+		return -1;
+
+	*start = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start;
+	*end = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end;

+	return 0;
+}
-- 
2.27.0


From rppt at kernel.org  Wed May 13 03:22:18 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Wed, 13 May 2026 13:22:18 +0300
Subject: [GIT PULL] liveupdate updates for v7.1-rc4
Message-ID: <agRQ2pV1vsO-MoTB@kernel.org>

Hi Linus,

The following changes since commit 254f49634ee16a731174d2ae34bc50bd5f45e731:

  Linux 7.1-rc1 (2026-04-26 14:19:00 -0700)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git tags/fixes-2026-05-13

for you to fetch changes up to 7b0b68b2b95606e65594958686833e53423f58f2:

  mm/memfd_luo: document preservation of file seals (2026-05-04 14:03:16 +0000)

----------------------------------------------------------------
liveupdate fixes for v7.1-rc4

A few fixes for kexec handover and liveupdate:

* make sure KHO is skipped for crash kernel
* fix error reporting in memfd preservation if it fails mid-loop
* don't allow preserving memfds whose page count exceeds UINT_MAX
* fix documentation of memfd seals preservation to match the code

----------------------------------------------------------------
David Carlier (3):
      mm/memfd_luo: report error when restoring a folio fails mid-loop
      mm/memfd_luo: reject memfds whose page count exceeds UINT_MAX
      mm/memfd_luo: document preservation of file seals

Evangelos Petrongonas (1):
      kho: skip KHO for crash kernel

 kernel/liveupdate/kexec_handover.c |  2 +-
 mm/memfd_luo.c                     | 25 +++++++++++++++++++------
 2 files changed, 20 insertions(+), 7 deletions(-)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Wed May 13 03:32:32 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Wed, 13 May 2026 13:32:32 +0300
Subject: [PATCH 01/12] kho: generalize radix tree APIs
In-Reply-To: <2vxz8q9pdib3.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-2-pratyush@kernel.org>
 <agG-OuOTAg5YD-CV@kernel.org>
 <2vxz8q9pdib3.fsf@kernel.org>
Message-ID: <agRTQLJv9pMEZe_7@kernel.org>

On Mon, May 11, 2026 at 06:25:52PM +0200, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:03PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> The KHO radix tree is a data structure that can track the presence or
> >> absence of an arbitrary key, with nothing inherently tied to KHO memory
> >> preservation tracking. This was one of the design goals of the radix
> >> tree. This was done to enable it to be re-used by other users of KHO.
> >> 
> >> Despite that, the radix tree APIs are very closely tied to KHO memory
> >> preservation tracking. Adding a key is done by kho_radix_add_page(),
> >> which encodes it as a page tracking operation and takes in PFN and
> >> order. kho_radix_del_page() does the same. These functions encode the
> >> key internally that goes into the radix tree. kho_radix_walk_tree() does
> >> the same by baking the PFN and order into the callback arguments.
> >> 
> >> Generalize the APIs by taking the key directly and doing the encoding at
> >> the callers. Rename the functions to kho_radix_add_key() and
> >> kho_radix_del_key(). In practice, this removes a line each from the
> >> functions and moves the encoding function call to the callers.
> >> Similarly, update kho_radix_tree_walk_callback_t to take the key
> >> directly.
> >> 
> >> To keep the naming convention clearer, rename
> >> kho_radix_{encode,decode}_key() to kho_{encode,decode}_radix_key().
> >> 
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> >>  include/linux/kho_radix_tree.h     | 18 +++----
> >>  kernel/liveupdate/kexec_handover.c | 76 ++++++++++++++----------------
> >>  2 files changed, 42 insertions(+), 52 deletions(-)
> >> 
> >> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> >> index 84e918b96e53..f368f3b9f923 100644
> >> --- a/include/linux/kho_radix_tree.h
> >> +++ b/include/linux/kho_radix_tree.h
> >> @@ -85,7 +85,7 @@ static struct kho_out kho_out = {
> >>  };
> >>  
> >>  /**
> >> - * kho_radix_encode_key - Encodes a physical address and order into a radix key.
> >> + * kho_encode_radix_key - Encodes a physical address and order into a radix key.
> >
> > Let's keep kho_radix_ prefix for namespasing please.
> > This is the common practice these days.
> 
> I am keeping the namespacing, just moving it out of the "kho_radix"
> namespace to "kho" namespace. The mental model is that this function has
> nothing to do with the radix tree. It is just something KHO uses for one
> of its radix tree, so it should live in the "kho" namespace not
> "kho_radix".
> 
> Does that make sense? I can add this explanation to the commit message
> as well.

With this explanation it makes sense, with "to keep the naming convention
clearer" it felt the opposite :)

> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From dongtai.guo at linux.dev  Wed May 13 08:07:18 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Wed, 13 May 2026 23:07:18 +0800
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <2vxzcxz2dybi.fsf@kernel.org>
References: <2vxzcxz2dybi.fsf@kernel.org>
Message-ID: <20260513150718.182224-1-dongtai.guo@linux.dev>

Sorry for the late reply.

On Mon, May 11 2026, Pratyush Yadav wrote:

> As of now, we only support KHO on x86 and arm64. Are you working on
> supporting it for LoongArch? What is your use case?

Yes, we are adding KHO support for LoongArch (16KB page size). The
LoongArch patches are being prepared separately. This fix is a
prerequisite.

> Maybe I don't understand the math so well, but I can't see the problem.
> ...
> level = 4, s = 50, idx = 1
> What am I missing?

The issue is that all three operations start the traversal at
KHO_TREE_MAX_DEPTH - 1, not KHO_TREE_MAX_DEPTH:

  kho_radix_add_page()  line 180: for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--)
  kho_radix_del_page()  line 255: for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--)
  kho_radix_walk_tree() line 356: __kho_radix_walk_tree(..., KHO_TREE_MAX_DEPTH - 1, ...)

So with depth=4 the effective top level is 3, not 4.

phys_to_page() of that address faults in kho_preserved_memory_reserve().

This is confirmed by a kernel panic on 7.1-rc3 LoongArch (16KB pages)
without the fix. With depth=5 the top level is 4 (shift=50):

  (key >> 50) % 2048 = 1   /* order bit correctly captured */

Signed-off-by: George Guo <guodongtai at kylinos.cn>

Panic log on 7.1-rc3 LoongArch (16KB pages) without the fix:

[    0.000000] CPU 0 Unable to handle kernel paging request at virtual address 00003d3ffe000028, era == 90000000c162f10c, ra == 90000000c162f0f8
[    0.000000] Oops[#1]:
...
[    0.000000] Call Trace:
[    0.000000] [<90000000c162f10c>] kho_preserved_memory_reserve+0xc4/0xe8
[    0.000000] [<90000000c0129f88>] __kho_radix_walk_tree+0xf0/0x138
[    0.000000] [<90000000c0129f10>] __kho_radix_walk_tree+0x78/0x138
[    0.000000] [<90000000c012b730>] kho_radix_walk_tree+0x88/0xe8
[    0.000000] [<90000000c162f874>] kho_memory_init+0x220/0x4e4
[    0.000000] [<90000000c1639b38>] mm_core_init+0x168/0x1a0
[    0.000000] [<90000000c1620d50>] start_kernel+0x5c4/0x778
[    0.000000] Kernel panic - not syncing: Attempted to kill the idle task!


From pr-tracker-bot at kernel.org  Wed May 13 08:28:54 2026
From: pr-tracker-bot at kernel.org (pr-tracker-bot at kernel.org)
Date: Wed, 13 May 2026 15:28:54 +0000
Subject: [GIT PULL] liveupdate updates for v7.1-rc4
In-Reply-To: <agRQ2pV1vsO-MoTB@kernel.org>
References: <agRQ2pV1vsO-MoTB@kernel.org>
Message-ID: <177868613426.3700920.13576586222009152264.pr-tracker-bot@kernel.org>

The pull request you sent on Wed, 13 May 2026 13:22:18 +0300:

> https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git tags/fixes-2026-05-13

has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/1f63dd8ca0dc05a8272bb8155f643c691d29bb11

Thank you!

-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html


From lkp at intel.com  Wed May 13 14:30:09 2026
From: lkp at intel.com (kernel test robot)
Date: Thu, 14 May 2026 05:30:09 +0800
Subject: [PATCH] pci_crash: capture PCI config space at panic time
In-Reply-To: <20260512162402.1929687-1-hangej@amazon.com>
References: <20260512162402.1929687-1-hangej@amazon.com>
Message-ID: <202605140501.1tSG7lxR-lkp@intel.com>

Hi hangej,

kernel test robot noticed the following build errors:

[auto build test ERROR on pci/next]
[also build test ERROR on pci/for-linus linus/master v7.1-rc3 next-20260508]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/hangej/pci_crash-capture-PCI-config-space-at-panic-time/20260514-000653
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git next
patch link:    https://lore.kernel.org/r/20260512162402.1929687-1-hangej%40amazon.com
patch subject: [PATCH] pci_crash: capture PCI config space at panic time
config: i386-randconfig-r071-20260514 (https://download.01.org/0day-ci/archive/20260514/202605140501.1tSG7lxR-lkp at intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
smatch: v0.5.0-9185-gbcc58b9c
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260514/202605140501.1tSG7lxR-lkp at 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 at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605140501.1tSG7lxR-lkp at intel.com/

All errors (new ones prefixed by >>):

   kernel/pci_crash.c: In function 'pci_crash_save':
>> kernel/pci_crash.c:661:53: error: 'struct pci_dev' has no member named 'aer_cap'; did you mean 'acs_cap'?
     661 |                                 if (!pdev || !pdev->aer_cap)
         |                                                     ^~~~~~~
         |                                                     acs_cap
   kernel/pci_crash.c:666:68: error: 'struct pci_dev' has no member named 'aer_cap'; did you mean 'acs_cap'?
     666 |                                                              pdev->aer_cap + PCI_ERR_ROOT_STATUS,
         |                                                                    ^~~~~~~
         |                                                                    acs_cap


vim +661 kernel/pci_crash.c

   599	
   600	/**
   601	 * pci_crash_save() - Capture PCI config space at crash time
   602	 *
   603	 * Called from crash_save_vmcoreinfo() inside __crash_kexec(), which
   604	 * runs before machine_kexec() boots the crash kernel.  This is the
   605	 * only reliable capture point -- panic notifiers run AFTER kexec by
   606	 * default (crash_kexec_post_notifiers=0).
   607	 *
   608	 * Capture check (capture param):
   609	 *   always  -- capture unconditionally
   610	 *   aer     -- quick-scan root port AER ROOT_STATUS for uncorrectable
   611	 *             errors; skip if none found
   612	 *
   613	 * When capture=always, captures on every panic.
   614	 * This is useful for cascading failures: a PCI link-down can cause
   615	 * an MCE or NMI watchdog timeout before DPC/AER fires, so the crash
   616	 * reason is UNKNOWN but AER registers may still hold error state.
   617	 *
   618	 * Reads config space fresh -- successful reads get current register
   619	 * state, failed reads (offline devices) write 0xFFFFFFFF.
   620	 *
   621	 * Flushes both buffer and pagemap from CPU cache to RAM so data
   622	 * survives kexec into crash kernel.
   623	 */
   624	void pci_crash_save(void)
   625	{
   626		struct pci_crash_pagemap *pm;
   627		unsigned long cflags;
   628		unsigned int num_devs;
   629		size_t pm_size;
   630		size_t buf_size;
   631		void *buffer;
   632	
   633		num_devs = READ_ONCE(pci_crash_num_devs);
   634		if (num_devs == 0)
   635			return;
   636	
   637		/* Pairs with smp_wmb() in rebuild -- ensures buffer/pagemap visible */
   638		smp_rmb();
   639	
   640		buffer = READ_ONCE(pci_crash_buffer);
   641		buf_size = READ_ONCE(pci_crash_buffer_size);
   642		pm = READ_ONCE(pci_crash_pagemap);
   643		pm_size = READ_ONCE(pci_crash_pagemap_size);
   644		if (!buffer || buf_size == 0)
   645			return;
   646	
   647		cflags = READ_ONCE(capture_flags);
   648		if (!(cflags & PCI_CRASH_CAPTURE_ALWAYS)) {
   649			if (cflags & PCI_CRASH_CAPTURE_AER) {
   650				struct pci_dev **devs = READ_ONCE(pci_crash_devs);
   651				unsigned int i;
   652				bool pci_error_found = false;
   653	
   654				if (!devs)
   655					return;
   656	
   657				for (i = 0; i < num_devs; i++) {
   658					struct pci_dev *pdev = devs[i];
   659					u32 status;
   660	
 > 661					if (!pdev || !pdev->aer_cap)
   662						continue;
   663	
   664					if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) {
   665						pci_read_config_dword(pdev,
   666								     pdev->aer_cap + PCI_ERR_ROOT_STATUS,
   667								     &status);
   668						if (status & PCI_ERR_ROOT_UNCOR_RCV) {
   669							pci_error_found = true;
   670							break;
   671						}
   672					}
   673				}
   674	
   675				if (!pci_error_found) {
   676					pr_emerg("no PCI errors detected, skipping capture\n");
   677					return;
   678				}
   679			} else {
   680				return;
   681			}
   682		}
   683	
   684		pci_crash_fill_buffer(buffer, num_devs);
   685	
   686		/*
   687		 * Flush buffer and pagemap from CPU cache to RAM so the
   688		 * crash kernel sees our writes after kexec.
   689		 */
   690		pci_crash_flush_dcache(buffer, buf_size);
   691	
   692		if (pm && pm_size > 0)
   693			pci_crash_flush_dcache(pm, pm_size);
   694	
   695		pr_emerg("CAPTURE: %u devices, %zu bytes\n",
   696			 num_devs, buf_size);
   697	}
   698	EXPORT_SYMBOL_GPL(pci_crash_save);
   699	

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


From lkp at intel.com  Wed May 13 15:23:54 2026
From: lkp at intel.com (kernel test robot)
Date: Thu, 14 May 2026 06:23:54 +0800
Subject: [PATCH] pci_crash: capture PCI config space at panic time
In-Reply-To: <20260512162402.1929687-1-hangej@amazon.com>
References: <20260512162402.1929687-1-hangej@amazon.com>
Message-ID: <202605140624.Tw71sECt-lkp@intel.com>

Hi hangej,

kernel test robot noticed the following build errors:

[auto build test ERROR on pci/next]
[also build test ERROR on pci/for-linus linus/master v7.1-rc3 next-20260508]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/hangej/pci_crash-capture-PCI-config-space-at-panic-time/20260514-000653
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git next
patch link:    https://lore.kernel.org/r/20260512162402.1929687-1-hangej%40amazon.com
patch subject: [PATCH] pci_crash: capture PCI config space at panic time
config: x86_64-randconfig-161-20260514 (https://download.01.org/0day-ci/archive/20260514/202605140624.Tw71sECt-lkp at intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
smatch: v0.5.0-9185-gbcc58b9c
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260514/202605140624.Tw71sECt-lkp at 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 at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605140624.Tw71sECt-lkp at intel.com/

All errors (new ones prefixed by >>):

>> kernel/pci_crash.c:661:25: error: no member named 'aer_cap' in 'struct pci_dev'
     661 |                                 if (!pdev || !pdev->aer_cap)
         |                                               ~~~~  ^
   kernel/pci_crash.c:666:19: error: no member named 'aer_cap' in 'struct pci_dev'
     666 |                                                              pdev->aer_cap + PCI_ERR_ROOT_STATUS,
         |                                                              ~~~~  ^
   2 errors generated.


vim +661 kernel/pci_crash.c

   599	
   600	/**
   601	 * pci_crash_save() - Capture PCI config space at crash time
   602	 *
   603	 * Called from crash_save_vmcoreinfo() inside __crash_kexec(), which
   604	 * runs before machine_kexec() boots the crash kernel.  This is the
   605	 * only reliable capture point -- panic notifiers run AFTER kexec by
   606	 * default (crash_kexec_post_notifiers=0).
   607	 *
   608	 * Capture check (capture param):
   609	 *   always  -- capture unconditionally
   610	 *   aer     -- quick-scan root port AER ROOT_STATUS for uncorrectable
   611	 *             errors; skip if none found
   612	 *
   613	 * When capture=always, captures on every panic.
   614	 * This is useful for cascading failures: a PCI link-down can cause
   615	 * an MCE or NMI watchdog timeout before DPC/AER fires, so the crash
   616	 * reason is UNKNOWN but AER registers may still hold error state.
   617	 *
   618	 * Reads config space fresh -- successful reads get current register
   619	 * state, failed reads (offline devices) write 0xFFFFFFFF.
   620	 *
   621	 * Flushes both buffer and pagemap from CPU cache to RAM so data
   622	 * survives kexec into crash kernel.
   623	 */
   624	void pci_crash_save(void)
   625	{
   626		struct pci_crash_pagemap *pm;
   627		unsigned long cflags;
   628		unsigned int num_devs;
   629		size_t pm_size;
   630		size_t buf_size;
   631		void *buffer;
   632	
   633		num_devs = READ_ONCE(pci_crash_num_devs);
   634		if (num_devs == 0)
   635			return;
   636	
   637		/* Pairs with smp_wmb() in rebuild -- ensures buffer/pagemap visible */
   638		smp_rmb();
   639	
   640		buffer = READ_ONCE(pci_crash_buffer);
   641		buf_size = READ_ONCE(pci_crash_buffer_size);
   642		pm = READ_ONCE(pci_crash_pagemap);
   643		pm_size = READ_ONCE(pci_crash_pagemap_size);
   644		if (!buffer || buf_size == 0)
   645			return;
   646	
   647		cflags = READ_ONCE(capture_flags);
   648		if (!(cflags & PCI_CRASH_CAPTURE_ALWAYS)) {
   649			if (cflags & PCI_CRASH_CAPTURE_AER) {
   650				struct pci_dev **devs = READ_ONCE(pci_crash_devs);
   651				unsigned int i;
   652				bool pci_error_found = false;
   653	
   654				if (!devs)
   655					return;
   656	
   657				for (i = 0; i < num_devs; i++) {
   658					struct pci_dev *pdev = devs[i];
   659					u32 status;
   660	
 > 661					if (!pdev || !pdev->aer_cap)
   662						continue;
   663	
   664					if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) {
   665						pci_read_config_dword(pdev,
   666								     pdev->aer_cap + PCI_ERR_ROOT_STATUS,
   667								     &status);
   668						if (status & PCI_ERR_ROOT_UNCOR_RCV) {
   669							pci_error_found = true;
   670							break;
   671						}
   672					}
   673				}
   674	
   675				if (!pci_error_found) {
   676					pr_emerg("no PCI errors detected, skipping capture\n");
   677					return;
   678				}
   679			} else {
   680				return;
   681			}
   682		}
   683	
   684		pci_crash_fill_buffer(buffer, num_devs);
   685	
   686		/*
   687		 * Flush buffer and pagemap from CPU cache to RAM so the
   688		 * crash kernel sees our writes after kexec.
   689		 */
   690		pci_crash_flush_dcache(buffer, buf_size);
   691	
   692		if (pm && pm_size > 0)
   693			pci_crash_flush_dcache(pm, pm_size);
   694	
   695		pr_emerg("CAPTURE: %u devices, %zu bytes\n",
   696			 num_devs, buf_size);
   697	}
   698	EXPORT_SYMBOL_GPL(pci_crash_save);
   699	

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


From lkp at intel.com  Wed May 13 16:07:16 2026
From: lkp at intel.com (kernel test robot)
Date: Thu, 14 May 2026 07:07:16 +0800
Subject: [PATCH] pci_crash: capture PCI config space at panic time
In-Reply-To: <20260512162402.1929687-1-hangej@amazon.com>
References: <20260512162402.1929687-1-hangej@amazon.com>
Message-ID: <202605140714.yFl4KATZ-lkp@intel.com>

Hi hangej,

kernel test robot noticed the following build warnings:

[auto build test WARNING on pci/next]
[also build test WARNING on pci/for-linus linus/master v7.1-rc3 next-20260508]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/hangej/pci_crash-capture-PCI-config-space-at-panic-time/20260514-000653
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git next
patch link:    https://lore.kernel.org/r/20260512162402.1929687-1-hangej%40amazon.com
patch subject: [PATCH] pci_crash: capture PCI config space at panic time
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260514/202605140714.yFl4KATZ-lkp at intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260514/202605140714.yFl4KATZ-lkp at 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 at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605140714.yFl4KATZ-lkp at intel.com/

All warnings (new ones prefixed by >>):

>> Warning: kernel/pci_crash.c:74 cannot understand function prototype: 'void *pci_crash_buffer;'
>> Warning: kernel/pci_crash.c:80 cannot understand function prototype: 'size_t pci_crash_buffer_size;'
>> Warning: kernel/pci_crash.c:90 cannot understand function prototype: 'phys_addr_t pci_crash_pagemap_phys;'

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


From k-hagio-ab at nec.com  Wed May 13 19:26:43 2026
From: k-hagio-ab at nec.com (=?utf-8?B?SEFHSU8gS0FaVUhJVE8o6JCp5bC+44CA5LiA5LuBKQ==?=)
Date: Thu, 14 May 2026 02:26:43 +0000
Subject: [PATCH][makedumpfile] 2/2 riscv64: Fix huge page translation in
 vtop_riscv64()
In-Reply-To: <20260506115758.2497610-3-qirui.001@bytedance.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com>
 <20260506115758.2497610-3-qirui.001@bytedance.com>
Message-ID: <58d40519-caa4-4ea8-8162-bb213545d6dc@nec.com>

On 2026/05/06 20:57, Rui Qi wrote:
> When a page table entry has _PAGE_LEAF set at PGD/P4D/PUD/PMD level,
> the current code incorrectly falls through to the next level instead
> of computing the correct physical address. Fix this by calculating
> the paddr with the proper level shift and returning directly.
> 
> Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
> Signed-off-by: Shuan He <heshuan at bytedance.com>
> ---
>   arch/riscv64.c | 26 ++++++++++++++++++--------
>   1 file changed, 18 insertions(+), 8 deletions(-)
> 
> diff --git a/arch/riscv64.c b/arch/riscv64.c
> index 28365fa0cc5b..67755331de24 100644
> --- a/arch/riscv64.c
> +++ b/arch/riscv64.c
> @@ -107,8 +107,12 @@ vtop_riscv64(pgd_t * pgd, unsigned long vaddr, long va_bits)
>   
>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>   
> -	if (pt_val & _PAGE_LEAF)
> -		goto out;

the following warning is observed:

cc  -g -O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D__riscv64__ -U__x86_64__ -c -o ./arch/riscv64.o arch/riscv64.c
arch/riscv64.c: In function 'vtop_riscv64':
arch/riscv64.c:203:1: warning: label 'out' defined but not used [-Wunused-label]
  out:
  ^~~

> +	if (pt_val & _PAGE_LEAF) {
> +		unsigned long shift = (va_bits == VA_BITS_SV57) ? PGD_SHIFT_L5 :
> +				      (va_bits == VA_BITS_SV48) ? PGD_SHIFT_L4 : PGD_SHIFT_L3;
> +		paddr = (pt_phys & ~((1ULL << shift) - 1)) + (vaddr & ((1ULL << shift) - 1));
> +		goto invalid;

just a style point, if this path is not invalid, please do no use the 'invalid'
label and better to return here instead.

Thanks,
Kazu

> +	}
>   
>   	if (va_bits == VA_BITS_SV57)
>   		goto p4d;
> @@ -133,8 +137,10 @@ p4d:
>   
>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>   
> -	if (pt_val & _PAGE_LEAF)
> -		goto out;
> +	if (pt_val & _PAGE_LEAF) {
> +		paddr = (pt_phys & ~((1ULL << P4D_SHIFT) - 1)) + (vaddr & ((1ULL << P4D_SHIFT) - 1));
> +		goto invalid;
> +	}
>   pud:
>   	/* PUD */
>   	puda = (pud_t *)(pt_phys) + pud_index(vaddr);
> @@ -152,8 +158,10 @@ pud:
>   
>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>   
> -	if(pt_val & _PAGE_LEAF)
> -		goto out;
> +	if (pt_val & _PAGE_LEAF) {
> +		paddr = (pt_phys & ~((1ULL << PUD_SHIFT) - 1)) + (vaddr & ((1ULL << PUD_SHIFT) - 1));
> +		goto invalid;
> +	}
>   pmd:
>   	/* PMD */
>   	pmda = (pmd_t *)(pt_phys) + pmd_index(vaddr);
> @@ -171,8 +179,10 @@ pmd:
>   
>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>   
> -	if (pt_val & _PAGE_LEAF)
> -		goto out;
> +	if (pt_val & _PAGE_LEAF) {
> +		paddr = (pt_phys & ~((1ULL << PMD_SHIFT) - 1)) + (vaddr & ((1ULL << PMD_SHIFT) - 1));
> +		goto invalid;
> +	}
>   
>   	/* PTE */
>   	ptea = (pte_t *)(pt_phys) + pte_index(vaddr);

From pasha.tatashin at soleen.com  Thu May 14 15:26:18 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:18 +0000
Subject: [PATCH v2 00/10] liveupdate: Remove limits on the number of files and sessions
Message-ID: <20260514222628.931312-1-pasha.tatashin@soleen.com>

This series removes the fixed limits on the number of files that can
be preserved within a single session, and the total number of sessions
managed by the Live Update Orchestrator.

The core of the change is a transition from single contiguous memory
blocks for metadata serialization to a chain of linked blocks. This
allows LUO to scale dynamically.

1.  ABI Evolution:
    - Introduced linked-block headers for both file and session
      serialization.
    - Bumped session ABI version to v4.

2.  Memory Management & Security:
    - Implemented a dynamic block allocation and reuse strategy. Blocks
      are allocated only when existing ones are exhausted and are reused
      during session/file removal cycles.
    - Introduced LUO_MAX_BLOCKS (10000) as a safeguard against stupid
      excessive allocations or corrupted cyclic lists during restore.

3.  Expanded Selftests:
    - Added new kexec-based tests verifying preservation of
      2000 sessions and 500 files per session.
    - Added self-tests for many sessions and many files management.

Changes v2:
   - Addressed all comments from Pratyush
   - Consolidated LUO state management from FDT into struct luo_ser.
   - Extracted luo_file_deserialize_one and luo_session_deserialize_one
     helpers to improve modularity, and later review (Pratyush).
   - Added dedicated luo_block.c for unified linked-block serialization
     and management.

Pasha Tatashin (10):
  liveupdate: centralize state management into struct luo_ser
  liveupdate: Extract luo_file_deserialize_one helper
  liveupdate: Extract luo_session_deserialize_one helper
  liveupdate: add support for linked-block serialization
  liveupdate: defer session block allocation and PA setting
  liveupdate: Remove limit on the number of sessions
  liveupdate: Remove limit on the number of files per session
  selftests/liveupdate: Test session and file limit removal
  selftests/liveupdate: Add stress-sessions kexec test
  selftests/liveupdate: Add stress-files kexec test

 Documentation/core-api/liveupdate.rst         |   8 +
 include/linux/kho/abi/luo.h                   | 140 +++----
 kernel/liveupdate/Makefile                    |   1 +
 kernel/liveupdate/luo_block.c                 | 388 ++++++++++++++++++
 kernel/liveupdate/luo_core.c                  |  57 ++-
 kernel/liveupdate/luo_file.c                  | 209 +++++-----
 kernel/liveupdate/luo_flb.c                   |  65 +--
 kernel/liveupdate/luo_internal.h              |  70 +++-
 kernel/liveupdate/luo_session.c               | 202 ++++-----
 tools/testing/selftests/liveupdate/Makefile   |   2 +
 .../testing/selftests/liveupdate/liveupdate.c |  75 ++++
 .../selftests/liveupdate/luo_stress_files.c   | 101 +++++
 .../liveupdate/luo_stress_sessions.c          | 102 +++++
 .../selftests/liveupdate/luo_test_utils.c     |  24 ++
 .../selftests/liveupdate/luo_test_utils.h     |   2 +
 15 files changed, 1068 insertions(+), 378 deletions(-)
 create mode 100644 kernel/liveupdate/luo_block.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c


base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:19 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:19 +0000
Subject: [PATCH v2 01/10] liveupdate: centralize state management into struct luo_ser
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-2-pasha.tatashin@soleen.com>

Transition the LUO to ABI v2, which centralizes state management into a
single struct luo_ser header.

Previously, LUO state was spread across multiple FDT properties and
subnodes. ABI v2 simplifies this by placing all core state, including
the liveupdate number and physical addresses for sessions and FLB
headers into a centralized struct luo_ser.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
 kernel/liveupdate/luo_core.c     | 59 ++++++++++++++-------
 kernel/liveupdate/luo_flb.c      | 65 ++++-------------------
 kernel/liveupdate/luo_internal.h |  8 +--
 kernel/liveupdate/luo_session.c  | 57 +++-----------------
 5 files changed, 93 insertions(+), 187 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 46750a0ddf88..1b2f865a771a 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -30,52 +30,25 @@
  *   .. code-block:: none
  *
  *     / {
- *         compatible = "luo-v1";
- *         liveupdate-number = <...>;
- *
- *         luo-session {
- *             compatible = "luo-session-v1";
- *             luo-session-header = <phys_addr_of_session_header_ser>;
- *         };
- *
- *         luo-flb {
- *             compatible = "luo-flb-v1";
- *             luo-flb-header = <phys_addr_of_flb_header_ser>;
- *         };
+ *         compatible = "luo-v2";
+ *         luo-abi-header = <phys_addr_of_luo_ser>;
  *     };
  *
  * Main LUO Node (/):
  *
- *   - compatible: "luo-v1"
+ *   - compatible: "luo-v2"
  *     Identifies the overall LUO ABI version.
- *   - liveupdate-number: u64
- *     A counter tracking the number of successful live updates performed.
- *
- * Session Node (luo-session):
- *   This node describes all preserved user-space sessions.
- *
- *   - compatible: "luo-session-v1"
- *     Identifies the session ABI version.
- *   - luo-session-header: u64
- *     The physical address of a `struct luo_session_header_ser`. This structure
- *     is the header for a contiguous block of memory containing an array of
- *     `struct luo_session_ser`, one for each preserved session.
- *
- * File-Lifecycle-Bound Node (luo-flb):
- *   This node describes all preserved global objects whose lifecycle is bound
- *   to that of the preserved files (e.g., shared IOMMU state).
- *
- *   - compatible: "luo-flb-v1"
- *     Identifies the FLB ABI version.
- *   - luo-flb-header: u64
- *     The physical address of a `struct luo_flb_header_ser`. This structure is
- *     the header for a contiguous block of memory containing an array of
- *     `struct luo_flb_ser`, one for each preserved global object.
+ *   - luo-abi-header: u64
+ *     The physical address of `struct luo_ser`.
  *
  * Serialization Structures:
  *   The FDT properties point to memory regions containing arrays of simple,
  *   `__packed` structures. These structures contain the actual preserved state.
  *
+ *   - struct luo_ser:
+ *     The central ABI structure that contains the overall state of the LUO.
+ *     It includes the liveupdate-number and pointers to sessions and FLBs.
+ *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
  *     preserved memory block and the number of `struct luo_session_ser`
@@ -109,13 +82,26 @@
 
 /*
  * The LUO FDT hooks all LUO state for sessions, fds, etc.
- * In the root it also carries "liveupdate-number" 64-bit property that
- * corresponds to the number of live-updates performed on this machine.
  */
 #define LUO_FDT_SIZE		PAGE_SIZE
 #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v1"
-#define LUO_FDT_LIVEUPDATE_NUM	"liveupdate-number"
+#define LUO_FDT_COMPATIBLE	"luo-v2"
+#define LUO_FDT_ABI_HEADER	"luo-abi-header"
+
+/**
+ * struct luo_ser - Centralized LUO ABI header.
+ * @liveupdate_num: A counter tracking the number of successful live updates.
+ * @sessions_pa:    Physical address of the first session block header.
+ * @flbs_pa:        Physical address of the FLB header.
+ *
+ * This structure is the root of all preserved LUO state. It is pointed to by
+ * the "luo-abi-header" property in the LUO FDT.
+ */
+struct luo_ser {
+	u64 liveupdate_num;
+	u64 sessions_pa;
+	u64 flbs_pa;
+} __packed;
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
@@ -147,15 +133,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/*
- * LUO FDT session node
- * LUO_FDT_SESSION_HEADER:  is a u64 physical address of struct
- *                          luo_session_header_ser
- */
-#define LUO_FDT_SESSION_NODE_NAME	"luo-session"
-#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v2"
-#define LUO_FDT_SESSION_HEADER		"luo-session-header"
-
 /**
  * struct luo_session_header_ser - Header for the serialized session data block.
  * @count: The number of `struct luo_session_ser` entries that immediately
@@ -165,7 +142,7 @@ struct luo_file_set_ser {
  * physical memory preserved across the kexec. It provides the necessary
  * metadata to interpret the array of session entries that follow.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_header_ser {
 	u64 count;
@@ -182,7 +159,7 @@ struct luo_session_header_ser {
  * session) is created and passed to the new kernel, allowing it to reconstruct
  * the session context.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -192,10 +169,6 @@ struct luo_session_ser {
 /* The max size is set so it can be reliably used during in serialization */
 #define LIVEUPDATE_FLB_COMPAT_LENGTH	48
 
-#define LUO_FDT_FLB_NODE_NAME	"luo-flb"
-#define LUO_FDT_FLB_COMPATIBLE	"luo-flb-v1"
-#define LUO_FDT_FLB_HEADER	"luo-flb-header"
-
 /**
  * struct luo_flb_header_ser - Header for the serialized FLB data block.
  * @pgcnt: The total number of pages occupied by the entire preserved memory
@@ -205,11 +178,9 @@ struct luo_session_ser {
  *         in the memory block.
  *
  * This structure is located at the physical address specified by the
- * `LUO_FDT_FLB_HEADER` FDT property. It provides the new kernel with the
- * necessary information to find and iterate over the array of preserved
- * File-Lifecycle-Bound objects and to manage the underlying memory.
+ * flbs_pa in luo_ser.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
@@ -231,7 +202,7 @@ struct luo_flb_header_ser {
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..9bd649b22029 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -82,9 +82,11 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	struct luo_ser *luo_ser;
+	int err, header_size;
 	phys_addr_t fdt_phys;
-	int err, ln_size;
 	const void *ptr;
+	u64 luo_ser_pa;
 
 	if (!kho_is_enabled()) {
 		if (liveupdate_enabled())
@@ -115,27 +117,29 @@ static int __init luo_early_startup(void)
 		return -EINVAL;
 	}
 
-	ln_size = 0;
-	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_LIVEUPDATE_NUM,
-			  &ln_size);
-	if (!ptr || ln_size != sizeof(luo_global.liveupdate_num)) {
-		pr_err("Unable to get live update number '%s' [%d]\n",
-		       LUO_FDT_LIVEUPDATE_NUM, ln_size);
+	header_size = 0;
+	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
+	if (!ptr || header_size != sizeof(u64)) {
+		pr_err("Unable to get ABI header '%s' [%d]\n",
+		       LUO_FDT_ABI_HEADER, header_size);
 
 		return -EINVAL;
 	}
 
-	luo_global.liveupdate_num = get_unaligned((u64 *)ptr);
+	luo_ser_pa = get_unaligned((u64 *)ptr);
+	luo_ser = phys_to_virt(luo_ser_pa);
+
+	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
 
-	err = luo_session_setup_incoming(luo_global.fdt_in);
+	err = luo_session_setup_incoming(luo_ser->sessions_pa);
 	if (err)
 		return err;
 
-	err = luo_flb_setup_incoming(luo_global.fdt_in);
+	luo_flb_setup_incoming(luo_ser->flbs_pa);
 
-	return err;
+	return 0;
 }
 
 static int __init liveupdate_early_init(void)
@@ -156,7 +160,8 @@ early_initcall(liveupdate_early_init);
 /* Called during boot to create outgoing LUO fdt tree */
 static int __init luo_fdt_setup(void)
 {
-	const u64 ln = luo_global.liveupdate_num + 1;
+	struct luo_ser *luo_ser;
+	u64 luo_ser_pa;
 	void *fdt_out;
 	int err;
 
@@ -166,27 +171,45 @@ static int __init luo_fdt_setup(void)
 		return PTR_ERR(fdt_out);
 	}
 
+	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
+	if (IS_ERR(luo_ser)) {
+		err = PTR_ERR(luo_ser);
+		goto exit_free_fdt;
+	}
+	luo_ser_pa = virt_to_phys(luo_ser);
+
 	err = fdt_create(fdt_out, LUO_FDT_SIZE);
 	err |= fdt_finish_reservemap(fdt_out);
 	err |= fdt_begin_node(fdt_out, "");
 	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln));
-	err |= luo_session_setup_outgoing(fdt_out);
-	err |= luo_flb_setup_outgoing(fdt_out);
+	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
+			    sizeof(luo_ser_pa));
 	err |= fdt_end_node(fdt_out);
 	err |= fdt_finish(fdt_out);
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
+
+	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
 			      fdt_totalsize(fdt_out));
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
 	luo_global.fdt_out = fdt_out;
 
 	return 0;
 
-exit_free:
+exit_free_luo_ser:
+	kho_unpreserve_free(luo_ser);
+exit_free_fdt:
 	kho_unpreserve_free(fdt_out);
 	pr_err("failed to prepare LUO FDT: %d\n", err);
 
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 8f5c5dd01cd0..7ccc59981297 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -159,8 +159,8 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
 
 static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 {
-	struct luo_flb_private *private = luo_flb_get_private(flb);
 	struct luo_flb_header *fh = &luo_flb_global.incoming;
+	struct luo_flb_private *private = luo_flb_get_private(flb);
 	struct liveupdate_flb_op_args args = {0};
 	bool found = false;
 	int err;
@@ -551,27 +551,15 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
 	return 0;
 }
 
-int __init luo_flb_setup_outgoing(void *fdt_out)
+int __init luo_flb_setup_outgoing(u64 *flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_FLB_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
 
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_FLB_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_FLB_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_FLB_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
-
-	if (err)
-		goto err_unpreserve;
+	*flbs_pa = virt_to_phys(header_ser);
 
 	header_ser->pgcnt = LUO_FLB_PGCNT;
 	luo_flb_global.outgoing.header_ser = header_ser;
@@ -579,53 +567,18 @@ int __init luo_flb_setup_outgoing(void *fdt_out)
 	luo_flb_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-
-	return err;
 }
 
-int __init luo_flb_setup_incoming(void *fdt_in)
+void __init luo_flb_setup_incoming(u64 flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	int err, header_size, offset;
-	const void *ptr;
-	u64 header_ser_pa;
-
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
 
-		return -ENOENT;
+	if (flbs_pa) {
+		header_ser = phys_to_virt(flbs_pa);
+		luo_flb_global.incoming.header_ser = header_ser;
+		luo_flb_global.incoming.ser = (void *)(header_ser + 1);
+		luo_flb_global.incoming.active = true;
 	}
-
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_FLB_COMPATIBLE);
-	if (err) {
-		pr_err("FLB node is incompatible with '%s' [%d]\n",
-		       LUO_FDT_FLB_COMPATIBLE, err);
-
-		return -EINVAL;
-	}
-
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_FLB_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get FLB header property '%s' [%d]\n",
-		       LUO_FDT_FLB_HEADER, header_size);
-
-		return -EINVAL;
-	}
-
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_flb_global.incoming.header_ser = header_ser;
-	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
-	luo_flb_global.incoming.active = true;
-
-	return 0;
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..4b6c18348d9b 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -81,8 +81,8 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(void *fdt);
-int __init luo_session_setup_incoming(void *fdt);
+int __init luo_session_setup_outgoing(u64 *sessions_pa);
+int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
 
@@ -104,8 +104,8 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_finish(struct liveupdate_file_handler *fh);
 void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
-int __init luo_flb_setup_outgoing(void *fdt);
-int __init luo_flb_setup_incoming(void *fdt);
+int __init luo_flb_setup_outgoing(u64 *flbs_pa);
+void __init luo_flb_setup_incoming(u64 flbs_pa);
 void luo_flb_serialize(void);
 
 #ifdef CONFIG_LIVEUPDATE_TEST
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 1caaa7886ec6..915ab9ce0f34 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -458,75 +458,34 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(void *fdt_out)
+int __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_SESSION_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_SESSION_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
 
-	if (err)
-		goto err_unpreserve;
+	*sessions_pa = virt_to_phys(header_ser);
 
 	luo_session_global.outgoing.header_ser = header_ser;
 	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
 	luo_session_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-	return err;
 }
 
-int __init luo_session_setup_incoming(void *fdt_in)
+int __init luo_session_setup_incoming(u64 sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	int err, header_size, offset;
-	u64 header_ser_pa;
-	const void *ptr;
-
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get session node: [%s]\n",
-		       LUO_FDT_SESSION_NODE_NAME);
-		return -EINVAL;
-	}
 
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_SESSION_COMPATIBLE);
-	if (err) {
-		pr_err("Session node incompatible [%s]\n",
-		       LUO_FDT_SESSION_COMPATIBLE);
-		return -EINVAL;
-	}
-
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get session header '%s' [%d]\n",
-		       LUO_FDT_SESSION_HEADER, header_size);
-		return -EINVAL;
+	if (sessions_pa) {
+		header_ser = phys_to_virt(sessions_pa);
+		luo_session_global.incoming.header_ser = header_ser;
+		luo_session_global.incoming.ser = (void *)(header_ser + 1);
+		luo_session_global.incoming.active = true;
 	}
 
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_session_global.incoming.header_ser = header_ser;
-	luo_session_global.incoming.ser = (void *)(header_ser + 1);
-	luo_session_global.incoming.active = true;
-
 	return 0;
 }
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:20 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:20 +0000
Subject: [PATCH v2 02/10] liveupdate: Extract luo_file_deserialize_one helper
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-3-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for files into
separate helper functions. In preparation to a linked-block
serialization for files.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 77 ++++++++++++++++++++----------------
 1 file changed, 44 insertions(+), 33 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..a2510563469a 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -752,6 +752,46 @@ int luo_file_finish(struct luo_file_set *file_set)
 	return 0;
 }
 
+static int luo_file_deserialize_one(struct luo_file_set *file_set,
+				    struct luo_file_ser *ser)
+{
+	struct liveupdate_file_handler *fh;
+	bool handler_found = false;
+	struct luo_file *luo_file;
+
+	down_read(&luo_register_rwlock);
+	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
+		if (!strcmp(fh->compatible, ser->compatible)) {
+			if (try_module_get(fh->ops->owner))
+				handler_found = true;
+			break;
+		}
+	}
+	up_read(&luo_register_rwlock);
+
+	if (!handler_found) {
+		pr_warn("No registered handler for compatible '%.*s'\n",
+			(int)sizeof(ser->compatible),
+			ser->compatible);
+		return -ENOENT;
+	}
+
+	luo_file = kzalloc_obj(*luo_file);
+	if (!luo_file) {
+		module_put(fh->ops->owner);
+		return -ENOMEM;
+	}
+
+	luo_file->fh = fh;
+	luo_file->file = NULL;
+	luo_file->serialized_data = ser->data;
+	luo_file->token = ser->token;
+	mutex_init(&luo_file->mutex);
+	list_add_tail(&luo_file->list, &file_set->files_list);
+
+	return 0;
+}
+
 /**
  * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
  * @file_set:     The incoming file_set to fill with deserialized data.
@@ -781,6 +821,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	int err;
 	u64 i;
 
 	if (!file_set_ser->files) {
@@ -808,39 +849,9 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 */
 	file_ser = file_set->files;
 	for (i = 0; i < file_set->count; i++) {
-		struct liveupdate_file_handler *fh;
-		bool handler_found = false;
-		struct luo_file *luo_file;
-
-		down_read(&luo_register_rwlock);
-		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
-			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
-				if (try_module_get(fh->ops->owner))
-					handler_found = true;
-				break;
-			}
-		}
-		up_read(&luo_register_rwlock);
-
-		if (!handler_found) {
-			pr_warn("No registered handler for compatible '%.*s'\n",
-				(int)sizeof(file_ser[i].compatible),
-				file_ser[i].compatible);
-			return -ENOENT;
-		}
-
-		luo_file = kzalloc_obj(*luo_file);
-		if (!luo_file) {
-			module_put(fh->ops->owner);
-			return -ENOMEM;
-		}
-
-		luo_file->fh = fh;
-		luo_file->file = NULL;
-		luo_file->serialized_data = file_ser[i].data;
-		luo_file->token = file_ser[i].token;
-		mutex_init(&luo_file->mutex);
-		list_add_tail(&luo_file->list, &file_set->files_list);
+		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+		if (err)
+			return err;
 	}
 
 	return 0;
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:21 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:21 +0000
Subject: [PATCH v2 03/10] liveupdate: Extract luo_session_deserialize_one helper
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-4-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for sessions into
separate helper functions. In preparation to a linked-block
serialization for sessions.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 62 +++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 26 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 915ab9ce0f34..c08733a328bc 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -489,6 +489,40 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 	return 0;
 }
 
+static int luo_session_deserialize_one(struct luo_session_header *sh,
+				       struct luo_session_ser *ser)
+{
+	struct luo_session *session;
+	int err;
+
+	session = luo_session_alloc(ser->name);
+	if (IS_ERR(session)) {
+		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
+			(int)sizeof(ser->name), ser->name, session);
+		return PTR_ERR(session);
+	}
+
+	err = luo_session_insert(sh, session);
+	if (err) {
+		pr_warn("Failed to insert session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		luo_session_free(session);
+		return err;
+	}
+
+	scoped_guard(mutex, &session->mutex) {
+		err = luo_file_deserialize(&session->file_set,
+					   &ser->file_set_ser);
+	}
+	if (err) {
+		pr_warn("Failed to deserialize files for session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		return err;
+	}
+
+	return 0;
+}
+
 int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
@@ -519,33 +553,9 @@ int luo_session_deserialize(void)
 	 * reliably reset devices and reclaim memory.
 	 */
 	for (int i = 0; i < sh->header_ser->count; i++) {
-		struct luo_session *session;
-
-		session = luo_session_alloc(sh->ser[i].name);
-		if (IS_ERR(session)) {
-			pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
-				(int)sizeof(sh->ser[i].name),
-				sh->ser[i].name, session);
-			return PTR_ERR(session);
-		}
-
-		err = luo_session_insert(sh, session);
-		if (err) {
-			pr_warn("Failed to insert session [%s] %pe\n",
-				session->name, ERR_PTR(err));
-			luo_session_free(session);
-			return err;
-		}
-
-		scoped_guard(mutex, &session->mutex) {
-			err = luo_file_deserialize(&session->file_set,
-						   &sh->ser[i].file_set_ser);
-		}
-		if (err) {
-			pr_warn("Failed to deserialize files for session [%s] %pe\n",
-				session->name, ERR_PTR(err));
+		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+		if (err)
 			return err;
-		}
 	}
 
 	kho_restore_free(sh->header_ser);
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:22 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:22 +0000
Subject: [PATCH v2 04/10] liveupdate: add support for linked-block serialization
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-5-pasha.tatashin@soleen.com>

Introduce a linked-block serialization mechanism for LUO state.

Previously, LUO used contiguous memory blocks for serializing sessions
and files, which imposed limits on the total number of items that could
be preserved across a live update.

This commit adds the infrastructure for a more flexible, block-based
approach where serialized data is stored in a chain of linked blocks.
This is a preparatory step to allow an unlimited number of
luo_sessions and luo_files to be preserved.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 Documentation/core-api/liveupdate.rst |   8 +
 include/linux/kho/abi/luo.h           |  22 ++
 kernel/liveupdate/Makefile            |   1 +
 kernel/liveupdate/luo_block.c         | 388 ++++++++++++++++++++++++++
 kernel/liveupdate/luo_internal.h      |  57 ++++
 5 files changed, 476 insertions(+)
 create mode 100644 kernel/liveupdate/luo_block.c

diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 5a292d0f3706..978beb02292f 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -23,6 +23,11 @@ LUO File Lifecycle Bound Global Data
 .. kernel-doc:: kernel/liveupdate/luo_flb.c
    :doc: LUO File Lifecycle Bound Global Data
 
+LUO Serialization Blocks
+========================
+.. kernel-doc:: kernel/liveupdate/luo_block.c
+   :doc: LUO Serialization Blocks
+
 Live Update Orchestrator ABI
 ============================
 .. kernel-doc:: include/linux/kho/abi/luo.h
@@ -65,6 +70,9 @@ Internal API
 .. kernel-doc:: kernel/liveupdate/luo_file.c
    :internal:
 
+.. kernel-doc:: kernel/liveupdate/luo_block.c
+   :internal:
+
 See Also
 ========
 
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 1b2f865a771a..2ba447c0c1ba 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -49,6 +49,11 @@
  *     The central ABI structure that contains the overall state of the LUO.
  *     It includes the liveupdate-number and pointers to sessions and FLBs.
  *
+ *   - struct luo_block_header_ser:
+ *     Header for the session or file data block. Contains the physical address
+ *     of the next data block and the number of entries that follow this
+ *     header in the current block.
+ *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
  *     preserved memory block and the number of `struct luo_session_ser`
@@ -105,6 +110,23 @@ struct luo_ser {
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
+/**
+ * struct luo_block_header_ser - Header for the serialized data block.
+ * @next:  Physical address of the next struct luo_block_header_ser.
+ * @count: The number of entries that immediately follow this header in the
+ *         memory block.
+ *
+ * This structure is located at the beginning of a block of
+ * physical memory preserved across the kexec. It provides the necessary
+ * metadata to interpret the array of entries that follow.
+ *
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ */
+struct luo_block_header_ser {
+	u64 next;
+	u64 count;
+} __packed;
+
 /**
  * struct luo_file_ser - Represents the serialized preserves files.
  * @compatible:  File handler compatible string.
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d2f779cbe279..9f8f6fb1b8e5 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 luo-y :=								\
+		luo_block.o						\
 		luo_core.o						\
 		luo_file.o						\
 		luo_flb.o						\
diff --git a/kernel/liveupdate/luo_block.c b/kernel/liveupdate/luo_block.c
new file mode 100644
index 000000000000..0be0669b5cac
--- /dev/null
+++ b/kernel/liveupdate/luo_block.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+/**
+ * DOC: LUO Serialization Blocks
+ *
+ * LUO provides a mechanism to preserve stateful data across a kexec-based live
+ * update by serializing it into contiguous memory blocks. This file provides
+ * the common infrastructure for managing these blocks.
+ *
+ * Each block consists of a header (struct luo_block_header_ser) followed by an
+ * array of serialized entries. Multiple blocks are linked together via a
+ * physical pointer in the header, forming a linked list that can be easily
+ * traversed in both the current and the next kernel.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/slab.h>
+#include "luo_internal.h"
+
+/* 2 4K pages, give space for 170 sessions or 127 files per block */
+#define LUO_BLOCK_PGCNT		2ul
+#define LUO_BLOCK_SIZE		(LUO_BLOCK_PGCNT << PAGE_SHIFT)
+
+/*
+ * Safeguard limit for the number of serialization blocks. This is used to
+ * prevent infinite loops and excessive memory allocation in case of memory
+ * corruption in the preserved state.
+ *
+ * This limit allows for 1.7 million sessions and 1.27 million files per
+ * session, which is more than enough for all realistic use cases.
+ */
+#define LUO_MAX_BLOCKS 10000
+
+/**
+ * luo_block_set_init - Initialize a block set.
+ * @bs:   The block set to initialize.
+ * @entry_size: The size of each entry in the blocks.
+ */
+void luo_block_set_init(struct luo_block_set *bs, size_t entry_size)
+{
+	*bs = (struct luo_block_set)LUO_BLOCK_SET_INIT(*bs, entry_size);
+}
+
+static inline u64 luo_block_count_per_block(struct luo_block_set *bs)
+{
+	if (unlikely(!bs->count_per_block)) {
+		bs->count_per_block = (LUO_BLOCK_SIZE -
+				       sizeof(struct luo_block_header_ser)) /
+				      bs->entry_size;
+		WARN_ON(!bs->count_per_block);
+	}
+	return bs->count_per_block;
+}
+
+/* Free serialzied data */
+static void luo_block_free_ser(struct luo_block_set *bs,
+			       struct luo_block_header_ser *ser)
+{
+	if (bs->incoming)
+		kho_restore_free(ser);
+	else
+		kho_unpreserve_free(ser);
+}
+
+static struct luo_block_header_ser *luo_block_alloc_ser(struct luo_block_set *bs)
+{
+	WARN_ON(bs->incoming);
+	return kho_alloc_preserve(LUO_BLOCK_SIZE);
+}
+
+static int luo_block_add(struct luo_block_set *bs,
+			 struct luo_block_header_ser *ser)
+{
+	struct luo_block *block, *last;
+
+	if (bs->nblocks >= LUO_MAX_BLOCKS)
+		return -ENOSPC;
+
+	block = kzalloc_obj(*block);
+	if (!block)
+		return -ENOMEM;
+
+	block->ser = ser;
+	last = list_last_entry_or_null(&bs->blocks, struct luo_block, list);
+	list_add_tail(&block->list, &bs->blocks);
+	bs->nblocks++;
+
+	if (last)
+		last->ser->next = virt_to_phys(ser);
+	else
+		bs->head_pa = virt_to_phys(ser);
+
+	return 0;
+}
+
+/**
+ * luo_block_grow - Create a new block if the current capacity is reached.
+ * @bs:    The block set.
+ * @count: The current number of entries.
+ *
+ * This function handles the dynamic expansion of a block set. It allocates
+ * and links a new serialization block if the provided entry count matches
+ * the current total capacity of the set.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int luo_block_grow(struct luo_block_set *bs, u64 count)
+{
+	struct luo_block_header_ser *ser;
+	int err;
+
+	if (WARN_ON(bs->incoming))
+		return -EINVAL;
+
+	if (count != bs->nblocks * luo_block_count_per_block(bs))
+		return 0;
+
+	ser = luo_block_alloc_ser(bs);
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	err = luo_block_add(bs, ser);
+	if (err) {
+		luo_block_free_ser(bs, ser);
+		return err;
+	}
+
+	return 0;
+}
+
+/**
+ * luo_block_shrink - Conditionally destroy the last block in a block set.
+ * @bs:              The block set.
+ * @count:           The current number of entries across all blocks.
+ *
+ * This function checks if the last block in the set is redundant based on the
+ * total entry count and the capacity of the preceding blocks. If the entry
+ * count can be accommodated by the blocks that come before the last one, the
+ * last block is destroyed and removed from the set.
+ */
+void luo_block_shrink(struct luo_block_set *bs, u64 count)
+{
+	struct luo_block *last, *new_last;
+
+	if (count > (bs->nblocks - 1) * luo_block_count_per_block(bs))
+		return;
+
+	if (list_empty(&bs->blocks))
+		return;
+
+	last = list_last_entry(&bs->blocks, struct luo_block, list);
+	list_del(&last->list);
+	bs->nblocks--;
+	luo_block_free_ser(bs, last->ser);
+	kfree(last);
+
+	new_last = list_last_entry_or_null(&bs->blocks, struct luo_block, list);
+	if (new_last)
+		new_last->ser->next = 0;
+	else
+		bs->head_pa = 0;
+}
+
+/*
+ * luo_cyclic_blocks_check - Check for cycles in a linked list of blocks.
+ * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
+ */
+static bool luo_cyclic_blocks_check(struct luo_block_set *bs)
+{
+	struct luo_block_header_ser *fast;
+	struct luo_block_header_ser *slow;
+	int count = 0;
+
+	fast = phys_to_virt(bs->head_pa);
+	slow = fast;
+
+	while (fast) {
+		if (count++ >= LUO_MAX_BLOCKS) {
+			pr_err("Linked list too long\n");
+			return false;
+		}
+
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		slow = phys_to_virt(slow->next);
+
+		if (slow == fast) {
+			pr_err("Cyclic list detected\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/**
+ * luo_block_restore - Restore a block set from a physical address.
+ * @bs:      The block set to restore.
+ * @head_pa: Physical address of the first block header.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int luo_block_restore(struct luo_block_set *bs, u64 head_pa)
+{
+	struct luo_block_header_ser *ser;
+	u64 next_pa = head_pa;
+	int err;
+
+	/* Restored block sets use size from the previous kernel */
+	bs->incoming = true;
+	if (!head_pa)
+		return 0;
+
+	bs->head_pa = head_pa;
+	if (!luo_cyclic_blocks_check(bs))
+		return -EINVAL;
+
+	while (next_pa) {
+		ser = phys_to_virt(next_pa);
+		if (ser->count > luo_block_count_per_block(bs)) {
+			pr_warn("Block contains too many entries: %llu\n",
+				ser->count);
+			err = -EINVAL;
+			goto err_destroy;
+		}
+		err = luo_block_add(bs, ser);
+		if (err)
+			goto err_destroy;
+		next_pa = ser->next;
+	}
+
+	return 0;
+
+err_destroy:
+	luo_block_destroy(bs);
+	return err;
+}
+
+/**
+ * luo_block_destroy - Destroy all blocks in a block set.
+ * @bs:          The block set.
+ */
+void luo_block_destroy(struct luo_block_set *bs)
+{
+	u64 head_pa = bs->head_pa;
+	struct luo_block *block;
+
+	while (!list_empty(&bs->blocks)) {
+		block = list_first_entry(&bs->blocks, struct luo_block, list);
+		list_del(&block->list);
+		kfree(block);
+	}
+	bs->nblocks = 0;
+	bs->head_pa = 0;
+
+	while (head_pa) {
+		struct luo_block_header_ser *ser = phys_to_virt(head_pa);
+
+		head_pa = ser->next;
+		luo_block_free_ser(bs, ser);
+	}
+}
+
+/**
+ * luo_block_set_clear - Clear all serialized data in a block set.
+ * @bs: The block set to clear.
+ */
+void luo_block_set_clear(struct luo_block_set *bs)
+{
+	struct luo_block *block;
+
+	list_for_each_entry(block, &bs->blocks, list) {
+		block->ser->count = 0;
+		memset(block->ser + 1, 0, LUO_BLOCK_SIZE - sizeof(*block->ser));
+	}
+}
+
+/**
+ * luo_block_it_init - Initialize a block set iterator.
+ * @it:         The iterator to initialize.
+ * @bs:         The block set to iterate over.
+ */
+void luo_block_it_init(struct luo_block_it *it, struct luo_block_set *bs)
+{
+	it->bs = bs;
+	it->block = list_first_entry_or_null(&bs->blocks, struct luo_block, list);
+	it->i = 0;
+}
+
+/**
+ * luo_block_it_next - Return the next entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current block is full, it automatically advances to the next block
+ * in the set.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more slots are
+ * available.
+ */
+void *luo_block_it_next(struct luo_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == luo_block_count_per_block(it->bs)) {
+		it->block->ser->count = it->i;
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * luo_block_it_read - Return the next entry slot for reading.
+ * @it: The block iterator.
+ *
+ * This function iterates through entries that were previously serialized,
+ * respecting the count stored in each block's header.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more entries are
+ * available.
+ */
+void *luo_block_it_read(struct luo_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	while (it->i == it->block->ser->count) {
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * luo_block_it_prev - Return the previous entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current index is at the start of a block, it automatically moves to
+ * the end of the previous block.
+ *
+ * Return: A pointer to the previous entry slot, or NULL if at the very
+ * beginning of the block set.
+ */
+void *luo_block_it_prev(struct luo_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == 0) {
+		if (list_is_first(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_prev_entry(it->block, list);
+		it->i = luo_block_count_per_block(it->bs);
+	}
+
+	return (void *)(it->block->ser + 1) + (--it->i * it->bs->entry_size);
+}
+
+/**
+ * luo_block_it_finalize - Finalize the current block by setting its entry count.
+ * @it: The block iterator.
+ */
+void luo_block_it_finalize(struct luo_block_it *it)
+{
+	if (it->block)
+		it->block->ser->count = it->i;
+}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 4b6c18348d9b..04f31319ccdf 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -40,6 +40,51 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
  */
 #define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__)
 
+/**
+ * struct luo_block - Internal representation of a serialization block.
+ * @list: List head for linking blocks in memory.
+ * @ser:  Pointer to the serialized header in preserved memory.
+ */
+struct luo_block {
+	struct list_head list;
+	struct luo_block_header_ser *ser;
+};
+
+/**
+ * struct luo_block_set - A set of blocks that belong to the same object.
+ * @blocks:          The list of serialization blocks (struct luo_block).
+ * @nblocks:         The number of allocated serialization blocks.
+ * @head_pa:         Physical address of the first block header.
+ * @entry_size:      The size of each entry in the blocks.
+ * @count_per_block: The maximum number of entries each block can hold.
+ * @incoming:        True if this block set was restored from the previous kernel.
+ */
+struct luo_block_set {
+	struct list_head blocks;
+	long nblocks;
+	u64 head_pa;
+	size_t entry_size;
+	u64 count_per_block;
+	bool incoming;
+};
+
+/**
+ * struct luo_block_it - Iterator for serializing entries into blocks.
+ * @bs:         The block set being iterated.
+ * @block:      The current block.
+ * @i:          The current entry index within @block.
+ */
+struct luo_block_it {
+	struct luo_block_set *bs;
+	struct luo_block *block;
+	u64 i;
+};
+
+#define LUO_BLOCK_SET_INIT(name, _entry_size) {				\
+	.blocks = LIST_HEAD_INIT((name).blocks),			\
+	.entry_size = _entry_size,					\
+}
+
 /**
  * struct luo_file_set - A set of files that belong to the same sessions.
  * @files_list: An ordered list of files associated with this session, it is
@@ -100,6 +145,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 void luo_file_set_init(struct luo_file_set *file_set);
 void luo_file_set_destroy(struct luo_file_set *file_set);
 
+void luo_block_set_init(struct luo_block_set *bs, size_t entry_size);
+int luo_block_grow(struct luo_block_set *bs, u64 count);
+void luo_block_shrink(struct luo_block_set *bs, u64 count);
+int luo_block_restore(struct luo_block_set *bs, u64 head_pa);
+void luo_block_destroy(struct luo_block_set *bs);
+void luo_block_set_clear(struct luo_block_set *bs);
+void luo_block_it_init(struct luo_block_it *it, struct luo_block_set *bs);
+void *luo_block_it_next(struct luo_block_it *it);
+void *luo_block_it_read(struct luo_block_it *it);
+void *luo_block_it_prev(struct luo_block_it *it);
+void luo_block_it_finalize(struct luo_block_it *it);
+
 int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_finish(struct liveupdate_file_handler *fh);
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:23 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:23 +0000
Subject: [PATCH v2 05/10] liveupdate: defer session block allocation and PA setting
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-6-pasha.tatashin@soleen.com>

Currently, luo_session_setup_outgoing() allocates the session block and
sets its physical address in the header immediately. With upcoming
dynamic block-based session management, this makes the first block
different from the rest. Move the allocation to where it is first needed.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_core.c     |  4 +-
 kernel/liveupdate/luo_internal.h |  2 +-
 kernel/liveupdate/luo_session.c  | 68 ++++++++++++++++++++------------
 3 files changed, 45 insertions(+), 29 deletions(-)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 9bd649b22029..5dd7f1e7dddb 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -189,9 +189,7 @@ static int __init luo_fdt_setup(void)
 	if (err)
 		goto exit_free_luo_ser;
 
-	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
-	if (err)
-		goto exit_free_luo_ser;
+	luo_session_setup_outgoing(&luo_ser->sessions_pa);
 
 	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
 	if (err)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 04f31319ccdf..090078ad771c 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -126,7 +126,7 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(u64 *sessions_pa);
+void __init luo_session_setup_outgoing(u64 *sessions_pa);
 int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index c08733a328bc..339514e8122c 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -77,15 +77,16 @@
 
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
- * @count:      The number of sessions currently tracked in the @list.
- * @list:       The head of the linked list of `struct luo_session` instances.
- * @rwsem:      A read-write semaphore providing synchronized access to the
- *              session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser:        The serialized session data (an array of
- *              `struct luo_session_ser`).
- * @active:     Set to true when first initialized. If previous kernel did not
- *              send session data, active stays false for incoming.
+ * @count:       The number of sessions currently tracked in the @list.
+ * @list:        The head of the linked list of `struct luo_session` instances.
+ * @rwsem:       A read-write semaphore providing synchronized access to the
+ *               session list and other fields in this structure.
+ * @header_ser:  The header data of serialization array.
+ * @ser:         The serialized session data (an array of
+ *               `struct luo_session_ser`).
+ * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
+ * @active:      Set to true when first initialized. If previous kernel did not
+ *               send session data, active stays false for incoming.
  */
 struct luo_session_header {
 	long count;
@@ -93,6 +94,7 @@ struct luo_session_header {
 	struct rw_semaphore rwsem;
 	struct luo_session_header_ser *header_ser;
 	struct luo_session_ser *ser;
+	u64 *sessions_pa;
 	bool active;
 };
 
@@ -140,10 +142,30 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
+static int luo_session_grow_ser(struct luo_session_header *sh)
+{
+	struct luo_session_header_ser *header_ser;
+
+	if (sh->count == LUO_SESSION_MAX)
+		return -ENOMEM;
+
+	if (sh->header_ser)
+		return 0;
+
+	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
+	if (IS_ERR(header_ser))
+		return PTR_ERR(header_ser);
+
+	sh->header_ser = header_ser;
+	sh->ser = (void *)(header_ser + 1);
+	return 0;
+}
+
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
 	struct luo_session *it;
+	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
 
@@ -152,8 +174,9 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		if (sh->count == LUO_SESSION_MAX)
-			return -ENOMEM;
+		err = luo_session_grow_ser(sh);
+		if (err)
+			return err;
 	}
 
 	/*
@@ -458,21 +481,10 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(u64 *sessions_pa)
+void __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	*sessions_pa = virt_to_phys(header_ser);
-
-	luo_session_global.outgoing.header_ser = header_ser;
-	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
+	luo_session_global.outgoing.sessions_pa = sessions_pa;
 	luo_session_global.outgoing.active = true;
-
-	return 0;
 }
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
@@ -573,6 +585,8 @@ int luo_session_serialize(void)
 	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
+	*sh->sessions_pa = 0;
+
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -582,7 +596,11 @@ int luo_session_serialize(void)
 			sizeof(sh->ser[i].name));
 		i++;
 	}
-	sh->header_ser->count = sh->count;
+
+	if (sh->header_ser && sh->count > 0) {
+		sh->header_ser->count = sh->count;
+		*sh->sessions_pa = virt_to_phys(sh->header_ser);
+	}
 
 	return 0;
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:24 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:24 +0000
Subject: [PATCH v2 06/10] liveupdate: Remove limit on the number of sessions
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-7-pasha.tatashin@soleen.com>

Currently, the number of LUO sessions is limited by a fixed number of
pre-allocated pages for serialization (16 pages, allowing for ~819
sessions).

This limitation is problematic if LUO is used to support things such as
systemd file descriptor store, and would be used not just as VM memory
but to save other states on the machine.

Remove this limit by transitioning to a linked-block approach for
session metadata serialization. Instead of a single contiguous block,
session metadata is now stored in a chain of 16-page blocks. Each block
starts with a header containing the physical address of the next block
and the number of session entries in the current block.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h     |  24 +-------
 kernel/liveupdate/luo_session.c | 105 +++++++++++++++-----------------
 2 files changed, 50 insertions(+), 79 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 2ba447c0c1ba..a643193a379c 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -36,7 +36,7 @@
  *
  * Main LUO Node (/):
  *
- *   - compatible: "luo-v2"
+ *   - compatible: "luo-v3"
  *     Identifies the overall LUO ABI version.
  *   - luo-abi-header: u64
  *     The physical address of `struct luo_ser`.
@@ -54,11 +54,6 @@
  *     of the next data block and the number of entries that follow this
  *     header in the current block.
  *
- *   - struct luo_session_header_ser:
- *     Header for the session array. Contains the total page count of the
- *     preserved memory block and the number of `struct luo_session_ser`
- *     entries that follow.
- *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
  *     to another preserved memory block containing an array of
@@ -90,7 +85,7 @@
  */
 #define LUO_FDT_SIZE		PAGE_SIZE
 #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v2"
+#define LUO_FDT_COMPATIBLE	"luo-v3"
 #define LUO_FDT_ABI_HEADER	"luo-abi-header"
 
 /**
@@ -155,21 +150,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @count: The number of `struct luo_session_ser` entries that immediately
- *         follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
- */
-struct luo_session_header_ser {
-	u64 count;
-} __packed;
-
 /**
  * struct luo_session_ser - Represents the serialized metadata for a LUO session.
  * @name:         The unique name of the session, provided by the userspace at
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 339514e8122c..064e83fabe62 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -69,21 +69,13 @@
 #include <uapi/linux/liveupdate.h>
 #include "luo_internal.h"
 
-/* 16 4K pages, give space for 744 sessions */
-#define LUO_SESSION_PGCNT	16ul
-#define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
-		sizeof(struct luo_session_header_ser)) /		\
-		sizeof(struct luo_session_ser))
-
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:       The number of sessions currently tracked in the @list.
  * @list:        The head of the linked list of `struct luo_session` instances.
  * @rwsem:       A read-write semaphore providing synchronized access to the
  *               session list and other fields in this structure.
- * @header_ser:  The header data of serialization array.
- * @ser:         The serialized session data (an array of
- *               `struct luo_session_ser`).
+ * @block_set:   The set of serialization blocks.
  * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
  * @active:      Set to true when first initialized. If previous kernel did not
  *               send session data, active stays false for incoming.
@@ -92,8 +84,7 @@ struct luo_session_header {
 	long count;
 	struct list_head list;
 	struct rw_semaphore rwsem;
-	struct luo_session_header_ser *header_ser;
-	struct luo_session_ser *ser;
+	struct luo_block_set block_set;
 	u64 *sessions_pa;
 	bool active;
 };
@@ -112,10 +103,14 @@ static struct luo_session_global luo_session_global = {
 	.incoming = {
 		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+		.block_set = LUO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 	.outgoing = {
 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+		.block_set = LUO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 };
 
@@ -142,25 +137,6 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
-static int luo_session_grow_ser(struct luo_session_header *sh)
-{
-	struct luo_session_header_ser *header_ser;
-
-	if (sh->count == LUO_SESSION_MAX)
-		return -ENOMEM;
-
-	if (sh->header_ser)
-		return 0;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	sh->header_ser = header_ser;
-	sh->ser = (void *)(header_ser + 1);
-	return 0;
-}
-
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
@@ -174,7 +150,7 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		err = luo_session_grow_ser(sh);
+		err = luo_block_grow(&sh->block_set, sh->count);
 		if (err)
 			return err;
 	}
@@ -201,6 +177,8 @@ static void luo_session_remove(struct luo_session_header *sh,
 	guard(rwsem_write)(&sh->rwsem);
 	list_del(&session->list);
 	sh->count--;
+	if (sh == &luo_session_global.outgoing)
+		luo_block_shrink(&sh->block_set, sh->count);
 }
 
 static int luo_session_finish_one(struct luo_session *session)
@@ -489,15 +467,17 @@ void __init luo_session_setup_outgoing(u64 *sessions_pa)
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
+	struct luo_session_header *sh = &luo_session_global.incoming;
+	int err;
 
-	if (sessions_pa) {
-		header_ser = phys_to_virt(sessions_pa);
-		luo_session_global.incoming.header_ser = header_ser;
-		luo_session_global.incoming.ser = (void *)(header_ser + 1);
-		luo_session_global.incoming.active = true;
-	}
+	if (!sessions_pa)
+		return 0;
 
+	err = luo_block_restore(&sh->block_set, sessions_pa);
+	if (err)
+		return err;
+
+	sh->active = true;
 	return 0;
 }
 
@@ -539,6 +519,8 @@ int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
+	struct luo_session_ser *ser;
+	struct luo_block_it it;
 	static int err;
 
 	/* If has been deserialized, always return the same error code */
@@ -564,51 +546,60 @@ int luo_session_deserialize(void)
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	for (int i = 0; i < sh->header_ser->count; i++) {
-		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+	luo_block_it_init(&it, &sh->block_set);
+	while ((ser = luo_block_it_read(&it))) {
+		err = luo_session_deserialize_one(sh, ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
 	}
 
-	kho_restore_free(sh->header_ser);
-	sh->header_ser = NULL;
-	sh->ser = NULL;
+err_destroy_blocks:
+	luo_block_destroy(&sh->block_set);
 
-	return 0;
+	return err;
 }
 
 int luo_session_serialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.outgoing;
 	struct luo_session *session;
-	int i = 0;
+	struct luo_block_it it;
 	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
 	*sh->sessions_pa = 0;
 
+	luo_block_it_init(&it, &sh->block_set);
+
 	list_for_each_entry(session, &sh->list, list) {
-		err = luo_session_freeze_one(session, &sh->ser[i]);
+		struct luo_session_ser *ser = luo_block_it_next(&it);
+
+		if (!ser) {
+			err = -ENOSPC;
+			goto err_undo;
+		}
+
+		err = luo_session_freeze_one(session, ser);
 		if (err)
 			goto err_undo;
 
-		strscpy(sh->ser[i].name, session->name,
-			sizeof(sh->ser[i].name));
-		i++;
+		strscpy(ser->name, session->name, sizeof(ser->name));
 	}
 
-	if (sh->header_ser && sh->count > 0) {
-		sh->header_ser->count = sh->count;
-		*sh->sessions_pa = virt_to_phys(sh->header_ser);
-	}
+	luo_block_it_finalize(&it);
+
+	if (sh->sessions_pa && sh->count > 0)
+		*sh->sessions_pa = sh->block_set.head_pa;
 
 	return 0;
 
 err_undo:
+	luo_block_it_prev(&it);
 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
-		i--;
-		luo_session_unfreeze_one(session, &sh->ser[i]);
-		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+		struct luo_session_ser *ser = luo_block_it_prev(&it);
+
+		luo_session_unfreeze_one(session, ser);
+		memset(ser->name, 0, sizeof(ser->name));
 	}
 
 	return err;
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:25 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:25 +0000
Subject: [PATCH v2 07/10] liveupdate: Remove limit on the number of files per session
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-8-pasha.tatashin@soleen.com>

To remove the fixed limit on the number of preserved files per session,
transition the file metadata serialization from a single contiguous
memory block to a chain of linked blocks.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      |  13 +--
 kernel/liveupdate/luo_file.c     | 142 +++++++++++++++----------------
 kernel/liveupdate/luo_internal.h |   5 +-
 3 files changed, 77 insertions(+), 83 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index a643193a379c..59677dc9cdd1 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -56,8 +56,8 @@
  *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
- *     to another preserved memory block containing an array of
- *     `struct luo_file_ser` for all files in that session.
+ *     to the first `struct luo_block_header_ser` for all files in that session.
+ *     Multiple blocks are linked via the `next` field in the header.
  *
  *   - struct luo_file_ser:
  *     Metadata for a single preserved file. Contains the `compatible` string to
@@ -85,7 +85,7 @@
  */
 #define LUO_FDT_SIZE		PAGE_SIZE
 #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v3"
+#define LUO_FDT_COMPATIBLE	"luo-v4"
 #define LUO_FDT_ABI_HEADER	"luo-abi-header"
 
 /**
@@ -138,9 +138,10 @@ struct luo_file_ser {
 
 /**
  * struct luo_file_set_ser - Represents the serialized metadata for file set
- * @files:   The physical address of a contiguous memory block that holds
- *           the serialized state of files (array of luo_file_ser) in this file
- *           set.
+ * @files:   The physical address of the first `struct luo_block_header_ser`.
+ *           This structure is the header for a block of memory containing
+ *           an array of `struct luo_file_ser` entries. Multiple blocks are
+ *           linked via the `next` field in the header.
  * @count:   The total number of files that were part of this session during
  *           serialization. Used for iteration and validation during
  *           restoration.
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a2510563469a..e2ab7834963d 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
 /* Keep track of files being preserved by LUO */
 static DEFINE_XARRAY(luo_preserved_files);
 
-/* 2 4K pages, give space for 128 files per file_set */
-#define LUO_FILE_PGCNT		2ul
-#define LUO_FILE_MAX							\
-	((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
-
 /**
  * struct luo_file - Represents a single preserved file instance.
  * @fh:            Pointer to the &struct liveupdate_file_handler that manages
@@ -174,39 +169,6 @@ struct luo_file {
 	u64 token;
 };
 
-static int luo_alloc_files_mem(struct luo_file_set *file_set)
-{
-	size_t size;
-	void *mem;
-
-	if (file_set->files)
-		return 0;
-
-	WARN_ON_ONCE(file_set->count);
-
-	size = LUO_FILE_PGCNT << PAGE_SHIFT;
-	mem = kho_alloc_preserve(size);
-	if (IS_ERR(mem))
-		return PTR_ERR(mem);
-
-	file_set->files = mem;
-
-	return 0;
-}
-
-static void luo_free_files_mem(struct luo_file_set *file_set)
-{
-	/* If file_set has files, no need to free preservation memory */
-	if (file_set->count)
-		return;
-
-	if (!file_set->files)
-		return;
-
-	kho_unpreserve_free(file_set->files);
-	file_set->files = NULL;
-}
-
 static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
 				struct file *file)
 {
@@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	if (luo_token_is_used(file_set, token))
 		return -EEXIST;
 
-	if (file_set->count == LUO_FILE_MAX)
-		return -ENOSPC;
+	err = luo_block_grow(&file_set->block_set, file_set->count);
+	if (err)
+		return err;
 
 	file = fget(fd);
-	if (!file)
-		return -EBADF;
-
-	err = luo_alloc_files_mem(file_set);
-	if (err)
-		goto  err_fput;
+	if (!file) {
+		err = -EBADF;
+		goto err_shrink;
+	}
 
 	err = -ENOENT;
 	down_read(&luo_register_rwlock);
@@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 
 	/* err is still -ENOENT if no handler was found */
 	if (err)
-		goto err_free_files_mem;
+		goto err_fput;
 
 	err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
 			file, GFP_KERNEL);
@@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	xa_erase(&luo_preserved_files, luo_get_id(fh, file));
 err_module_put:
 	module_put(fh->ops->owner);
-err_free_files_mem:
-	luo_free_files_mem(file_set);
 err_fput:
 	fput(file);
+err_shrink:
+	luo_block_shrink(&file_set->block_set, file_set->count);
 
 	return err;
 }
@@ -391,13 +352,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 			 luo_get_id(luo_file->fh, luo_file->file));
 		list_del(&luo_file->list);
 		file_set->count--;
+		luo_block_shrink(&file_set->block_set, file_set->count);
 
 		fput(luo_file->file);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	luo_free_files_mem(file_set);
+	luo_block_destroy(&file_set->block_set);
 }
 
 static int luo_file_freeze_one(struct luo_file_set *file_set,
@@ -453,7 +415,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 		luo_file_unfreeze_one(file_set, luo_file);
 	}
 
-	memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
+	luo_block_set_clear(&file_set->block_set);
 }
 
 /**
@@ -492,19 +454,23 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 int luo_file_freeze(struct luo_file_set *file_set,
 		    struct luo_file_set_ser *file_set_ser)
 {
-	struct luo_file_ser *file_ser = file_set->files;
 	struct luo_file *luo_file;
+	struct luo_block_it it;
 	int err;
-	int i;
 
 	if (!file_set->count)
 		return 0;
 
-	if (WARN_ON(!file_ser))
-		return -EINVAL;
+	luo_block_it_init(&it, &file_set->block_set);
 
-	i = 0;
 	list_for_each_entry(luo_file, &file_set->files_list, list) {
+		struct luo_file_ser *file_ser = luo_block_it_next(&it);
+
+		if (!file_ser) {
+			err = -ENOSPC;
+			goto err_unfreeze;
+		}
+
 		err = luo_file_freeze_one(file_set, luo_file);
 		if (err < 0) {
 			pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
@@ -513,16 +479,21 @@ int luo_file_freeze(struct luo_file_set *file_set,
 			goto err_unfreeze;
 		}
 
-		strscpy(file_ser[i].compatible, luo_file->fh->compatible,
-			sizeof(file_ser[i].compatible));
-		file_ser[i].data = luo_file->serialized_data;
-		file_ser[i].token = luo_file->token;
-		i++;
+		strscpy(file_ser->compatible, luo_file->fh->compatible,
+			sizeof(file_ser->compatible));
+		file_ser->data = luo_file->serialized_data;
+		file_ser->token = luo_file->token;
 	}
+	luo_block_it_finalize(&it);
 
 	file_set_ser->count = file_set->count;
-	if (file_set->files)
-		file_set_ser->files = virt_to_phys(file_set->files);
+	if (!list_empty(&file_set->block_set.blocks)) {
+		struct luo_block *block;
+
+		block = list_first_entry(&file_set->block_set.blocks,
+					 struct luo_block, list);
+		file_set_ser->files = virt_to_phys(block->ser);
+	}
 
 	return 0;
 
@@ -740,14 +711,12 @@ int luo_file_finish(struct luo_file_set *file_set)
 		}
 		list_del(&luo_file->list);
 		file_set->count--;
+		luo_block_shrink(&file_set->block_set, file_set->count);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	if (file_set->files) {
-		kho_restore_free(file_set->files);
-		file_set->files = NULL;
-	}
+	luo_block_destroy(&file_set->block_set);
 
 	return 0;
 }
@@ -821,16 +790,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	struct luo_block_it it;
 	int err;
-	u64 i;
 
 	if (!file_set_ser->files) {
 		WARN_ON(file_set_ser->count);
 		return 0;
 	}
 
-	file_set->count = file_set_ser->count;
-	file_set->files = phys_to_virt(file_set_ser->files);
+	file_set->count = 0;
+	err = luo_block_restore(&file_set->block_set, file_set_ser->files);
+	if (err)
+		return err;
 
 	/*
 	 * Note on error handling:
@@ -847,25 +818,48 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	file_ser = file_set->files;
-	for (i = 0; i < file_set->count; i++) {
-		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+	luo_block_it_init(&it, &file_set->block_set);
+	while ((file_ser = luo_block_it_read(&it))) {
+		err = luo_file_deserialize_one(file_set, file_ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
+		file_set->count++;
+	}
+
+	if (file_set->count != file_set_ser->count) {
+		pr_warn("File count mismatch: expected %llu, found %ld\n",
+			file_set_ser->count, file_set->count);
 	}
 
 	return 0;
+
+err_destroy_blocks:
+	while (!list_empty(&file_set->files_list)) {
+		struct luo_file *luo_file;
+
+		luo_file = list_first_entry(&file_set->files_list,
+					    struct luo_file, list);
+		list_del(&luo_file->list);
+		module_put(luo_file->fh->ops->owner);
+		mutex_destroy(&luo_file->mutex);
+		kfree(luo_file);
+	}
+	file_set->count = 0;
+	luo_block_destroy(&file_set->block_set);
+	return err;
 }
 
 void luo_file_set_init(struct luo_file_set *file_set)
 {
 	INIT_LIST_HEAD(&file_set->files_list);
+	luo_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
 }
 
 void luo_file_set_destroy(struct luo_file_set *file_set)
 {
 	WARN_ON(file_set->count);
 	WARN_ON(!list_empty(&file_set->files_list));
+	WARN_ON(!list_empty(&file_set->block_set.blocks));
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 090078ad771c..901483e6e4ad 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -89,14 +89,13 @@ struct luo_block_it {
  * struct luo_file_set - A set of files that belong to the same sessions.
  * @files_list: An ordered list of files associated with this session, it is
  *              ordered by preservation time.
- * @files:      The physically contiguous memory block that holds the serialized
- *              state of files.
+ * @block_set:  The set of serialization blocks.
  * @count:      A counter tracking the number of files currently stored in the
  *              @files_list for this session.
  */
 struct luo_file_set {
 	struct list_head files_list;
-	struct luo_file_ser *files;
+	struct luo_block_set block_set;
 	long count;
 };
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:26 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:26 +0000
Subject: [PATCH v2 08/10] selftests/liveupdate: Test session and file limit removal
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-9-pasha.tatashin@soleen.com>

With the removal of static limits on the number of sessions and files per
session, the orchestrator now uses dynamic allocation.

Add new test cases to verify that the system can handle a large number of
sessions and files. These tests ensure that the dynamic block allocation
and reuse logic for session metadata and outgoing files work correctly
beyond the previous static limits.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 .../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
 .../selftests/liveupdate/luo_test_utils.c     | 24 ++++++
 .../selftests/liveupdate/luo_test_utils.h     |  2 +
 3 files changed, 101 insertions(+)

diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index c7d94b9181e1..502fb3567e38 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -26,6 +26,7 @@
 
 #include <linux/liveupdate.h>
 
+#include "luo_test_utils.h"
 #include "../kselftest.h"
 #include "../kselftest_harness.h"
 
@@ -499,4 +500,78 @@ TEST_F(liveupdate_device, get_session_name_max_length)
 	ASSERT_EQ(close(session_fd), 0);
 }
 
+/*
+ * Test Case: Manage Many Sessions
+ *
+ * Verifies that a large number of sessions can be created and then
+ * destroyed during normal system operation. This specifically tests the
+ * dynamic block allocation and reuse logic for session metadata management
+ * without preserving any files.
+ */
+TEST_F(liveupdate_device, preserve_many_sessions)
+{
+#define MANY_SESSIONS 2000
+	int session_fds[MANY_SESSIONS];
+	int ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_SESSIONS);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_SESSIONS; i++) {
+		char name[64];
+
+		snprintf(name, sizeof(name), "many-session-%d", i);
+		session_fds[i] = create_session(self->fd1, name);
+		ASSERT_GE(session_fds[i], 0);
+	}
+
+	for (i = 0; i < MANY_SESSIONS; i++)
+		ASSERT_EQ(close(session_fds[i]), 0);
+}
+
+/*
+ * Test Case: Preserve Many Files
+ *
+ * Verifies that a large number of files can be preserved in a single session
+ * and then destroyed during normal system operation. This tests the dynamic
+ * block allocation and management for outgoing files.
+ */
+TEST_F(liveupdate_device, preserve_many_files)
+{
+#define MANY_FILES 500
+	int mem_fds[MANY_FILES];
+	int session_fd, ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	session_fd = create_session(self->fd1, "many-files-test");
+	ASSERT_GE(session_fd, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_FILES + 10);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_FILES; i++) {
+		mem_fds[i] = memfd_create("test-memfd", 0);
+		ASSERT_GE(mem_fds[i], 0);
+		ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
+	}
+
+	for (i = 0; i < MANY_FILES; i++)
+		ASSERT_EQ(close(mem_fds[i]), 0);
+
+	ASSERT_EQ(close(session_fd), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
index 3c8721c505df..333a3530051b 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.c
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -17,6 +17,7 @@
 #include <sys/syscall.h>
 #include <sys/mman.h>
 #include <sys/types.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <stdarg.h>
@@ -28,6 +29,29 @@ int luo_open_device(void)
 	return open(LUO_DEVICE, O_RDWR);
 }
 
+int luo_ensure_nofile_limit(long min_limit)
+{
+	struct rlimit hl;
+
+	/* Allow to extra files to be used by test itself */
+	min_limit += 32;
+
+	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	if (hl.rlim_cur >= min_limit)
+		return 0;
+
+	hl.rlim_cur = min_limit;
+	if (hl.rlim_cur > hl.rlim_max)
+		hl.rlim_max = hl.rlim_cur;
+
+	if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	return 0;
+}
+
 int luo_create_session(int luo_fd, const char *name)
 {
 	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
index 90099bf49577..6a0d85386613 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.h
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -26,6 +26,8 @@ int luo_create_session(int luo_fd, const char *name);
 int luo_retrieve_session(int luo_fd, const char *name);
 int luo_session_finish(int session_fd);
 
+int luo_ensure_nofile_limit(long min_limit);
+
 int create_and_preserve_memfd(int session_fd, int token, const char *data);
 int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:27 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:27 +0000
Subject: [PATCH v2 09/10] selftests/liveupdate: Add stress-sessions kexec test
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-10-pasha.tatashin@soleen.com>

Add a new test that creates 2000 LUO sessions before a kexec
reboot and verifies their presence after the reboot. This ensures
that the linked-block serialization mechanism works correctly for
a large number of sessions.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 tools/testing/selftests/liveupdate/Makefile   |   1 +
 .../liveupdate/luo_stress_sessions.c          | 102 ++++++++++++++++++
 2 files changed, 103 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 080754787ede..ed7534468386 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -6,6 +6,7 @@ TEST_GEN_PROGS += liveupdate
 
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
+TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
new file mode 100644
index 000000000000..f201b1839d1d
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of sessions across a kexec
+ * reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_SESSIONS 2000
+#define STATE_SESSION_NAME "kexec_many_state"
+#define STATE_MEMFD_TOKEN 999
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int ret, i;
+
+	ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+	ret = luo_ensure_nofile_limit(NUM_SESSIONS);
+	if (ret == -EPERM)
+		ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+	if (ret < 0)
+		ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating %d sessions...\n", NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_create_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_create_session for '%s' at index %d",
+				  name, i);
+		}
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully created %d sessions.\n",
+		       NUM_SESSIONS);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving and finishing %d sessions...\n",
+		       NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_retrieve_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_retrieve_session for '%s' at index %d",
+				  name, i);
+		}
+
+		if (luo_session_finish(s_fd) < 0) {
+			fail_exit("luo_session_finish for '%s' at index %d",
+				  name, i);
+		}
+		close(s_fd);
+	}
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-SESSIONS KEXEC TEST PASSED (%d sessions) ---\n",
+		       NUM_SESSIONS);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 15:26:28 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 14 May 2026 22:26:28 +0000
Subject: [PATCH v2 10/10] selftests/liveupdate: Add stress-files kexec test
In-Reply-To: <20260514222628.931312-1-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
Message-ID: <20260514222628.931312-11-pasha.tatashin@soleen.com>

Add a new luo_stress_files kexec test that verifies preserving and
retrieving 500 files across a kexec reboot.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---
 tools/testing/selftests/liveupdate/Makefile   |   1 +
 .../selftests/liveupdate/luo_stress_files.c   | 101 ++++++++++++++++++
 2 files changed, 102 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index ed7534468386..30689d22cb02 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -7,6 +7,7 @@ TEST_GEN_PROGS += liveupdate
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
 TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
+TEST_GEN_PROGS_EXTENDED += luo_stress_files
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c
new file mode 100644
index 000000000000..b6a0c3899890
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_files.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of files per session across
+ * a kexec reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_FILES 500
+#define STATE_SESSION_NAME "kexec_many_files_state"
+#define STATE_MEMFD_TOKEN 9999
+#define TEST_SESSION_NAME "many_files_session"
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int session_fd, ret, i;
+
+	ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+	ret = luo_ensure_nofile_limit(NUM_FILES);
+	if (ret == -EPERM)
+		ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+	if (ret < 0)
+		ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_create_session");
+
+	ksft_print_msg("[STAGE 1] Preserving %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		if (create_and_preserve_memfd(session_fd, i, data) < 0)
+			fail_exit("create_and_preserve_memfd for index %d", i);
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully preserved %d files.\n", NUM_FILES);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int session_fd;
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_retrieve_session");
+
+	ksft_print_msg("[STAGE 2] Verifying %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		if (restore_and_verify_memfd(session_fd, i, data) < 0)
+			fail_exit("restore_and_verify_memfd for index %d", i);
+	}
+
+	ksft_print_msg("[STAGE 2] Finishing test session...\n");
+	if (luo_session_finish(session_fd) < 0)
+		fail_exit("luo_session_finish for test session");
+	close(session_fd);
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-FILES KEXEC TEST PASSED (%d files) ---\n",
+		       NUM_FILES);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:17 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:17 +0000
Subject: [PATCH v3 0/5] liveupdate: serialization safety and race fixes
Message-ID: <20260515003722.938123-1-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization
between the reboot process and LUO session management.

Changes in v3:
- Refined the session mutation blocking to use a dedicated global
  rwsem (luo_session_serialize_rwsem) instead of pinning individual
  mutexes.
- Fixed a use-after-free race in luo_file_unpreserve_files() where
  a module could be released before its file handler ID was erased.
- Fixed a TOCTOU race in luo_session_retrieve() by extending the
  lock scope to overlap with session mutex acquisition.
- Removed an unused 'ser' field from struct luo_session.
- Dropped the KHO skip patch as it was not needed.

1. Skip LUO serialization for context-preserving kexec: A
preserve_context kexec returns to the current kernel, which is unrelated
to live update where state is passed to the next kernel. Skipping
serialization avoids unnecessary work and prevents sessions from being
left in a frozen state upon return.

2. Block session mutations during reboot: During the reboot() syscall,
user processes may still be running concurrently and attempting to
mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
All mutation operations (create, retrieve, release, ioctl) hold the
read lock. The serialization process holds the write lock indefinitely
on success, effectively freezing the subsystem.

3. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
to ensure the file handler module remains pinned while its operations
are being accessed during cleanup.

4. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
scope to prevent a session from being released between lookup and
mutex acquisition.

5. Remove unused ser field from struct luo_session: Clean up the
session structure by removing a field that was never utilized.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
luo-reboot-sync/v3

Pasha Tatashin (5):
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: block session mutations during reboot
  liveupdate: fix u-a-f in luo_file_unpreserve_files() and
    luo_file_finish()
  liveupdate: fix TOCTOU race in luo_session_retrieve()
  liveupdate: Remove unused ser field from struct luo_session

 kernel/kexec_core.c              |  8 +++++---
 kernel/liveupdate/luo_file.c     |  5 +++--
 kernel/liveupdate/luo_internal.h |  2 --
 kernel/liveupdate/luo_session.c  | 27 ++++++++++++++++++++-------
 4 files changed, 28 insertions(+), 14 deletions(-)


base-commit: 7b0b68b2b95606e65594958686833e53423f58f2
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:18 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:18 +0000
Subject: [PATCH v3 1/5] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <20260515003722.938123-2-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which is
unrelated to live update where the state is passed to the next kernel.
Skip liveupdate_reboot() in this case to avoid serialization and prevent
sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:19 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:19 +0000
Subject: [PATCH v3 2/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <20260515003722.938123-3-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., creating,
retrieving, or releasing sessions). To prevent this, introduce
luo_session_serialize_rwsem to synchronize mutations with the
serialization process.

All session mutation operations (create, retrieve, release, ioctl) take
the read lock. The serialization process (luo_session_serialize) takes
the write lock and holds it indefinitely on success. This effectively
freezes the LUO session subsystem during the transition to the new
kernel. If serialization fails, the lock is released to allow recovery.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..5aa0011d9643 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -75,6 +75,13 @@
 		sizeof(struct luo_session_header_ser)) /		\
 		sizeof(struct luo_session_ser))
 
+/*
+ * Protects session mutations during serialization. All session mutation
+ * operations must hold the read lock. The serialization process holds the write
+ * lock indefinitely on success to block all concurrent and future mutations.
+ */
+static DECLARE_RWSEM(luo_session_serialize_rwsem);
+
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:      The number of sessions currently tracked in the @list.
@@ -205,6 +212,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 	struct luo_session *session = filep->private_data;
 	struct luo_session_header *sh;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	/* If retrieved is set, it means this session is from incoming list */
 	if (session->retrieved) {
 		int err = luo_session_finish_one(session);
@@ -354,6 +362,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 	if (ret)
 		return ret;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	return op->execute(session, &ucmd);
 }
 
@@ -385,6 +394,7 @@ int luo_session_create(const char *name, struct file **filep)
 	struct luo_session *session;
 	int err;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	session = luo_session_alloc(name);
 	if (IS_ERR(session))
 		return PTR_ERR(session);
@@ -415,6 +425,7 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	scoped_guard(rwsem_read, &sh->rwsem) {
 		list_for_each_entry(it, &sh->list, list) {
 			if (!strncmp(it->name, name, sizeof(it->name))) {
@@ -583,7 +594,8 @@ int luo_session_serialize(void)
 	int i = 0;
 	int err;
 
-	guard(rwsem_write)(&sh->rwsem);
+	down_write(&luo_session_serialize_rwsem);
+	down_write(&sh->rwsem);
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -603,6 +615,8 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	up_write(&sh->rwsem);
+	up_write(&luo_session_serialize_rwsem);
 
 	return err;
 }
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:20 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:20 +0000
Subject: [PATCH v3 3/5] liveupdate: fix u-a-f in luo_file_unpreserve_files() and luo_file_finish()
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <20260515003722.938123-4-pasha.tatashin@soleen.com>

In luo_file_unpreserve_files() and luo_file_finish(), reorder
module_put() and xa_erase() to ensure the file handler module remains
pinned while its operations are being accessed.

Specifically, luo_get_id() dereferences fh->ops->get_id, so the module
reference must be held until after xa_erase() (which calls luo_get_id)
completes.

For luo_file_finish(), this requires moving the module_put() call out of
the luo_file_finish_one() helper and into the main loop of
luo_file_finish() itself.

Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..208987502f73 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -385,10 +385,11 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 		args.private_data = luo_file->private_data;
 		luo_file->fh->ops->unpreserve(&args);
 		luo_flb_file_unpreserve(luo_file->fh);
-		module_put(luo_file->fh->ops->owner);
 
 		xa_erase(&luo_preserved_files,
 			 luo_get_id(luo_file->fh, luo_file->file));
+		module_put(luo_file->fh->ops->owner);
+
 		list_del(&luo_file->list);
 		file_set->count--;
 
@@ -677,7 +678,6 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
 
 	luo_file->fh->ops->finish(&args);
 	luo_flb_file_finish(luo_file->fh);
-	module_put(luo_file->fh->ops->owner);
 }
 
 /**
@@ -738,6 +738,7 @@ int luo_file_finish(struct luo_file_set *file_set)
 				 luo_get_id(luo_file->fh, luo_file->file));
 			fput(luo_file->file);
 		}
+		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
 		mutex_destroy(&luo_file->mutex);
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:21 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:21 +0000
Subject: [PATCH v3 4/5] liveupdate: fix TOCTOU race in luo_session_retrieve()
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <20260515003722.938123-5-pasha.tatashin@soleen.com>

Extend the scope of the rwsem_read lock in luo_session_retrieve() to
overlap with the acquisition of the session mutex. This prevents a
concurrent thread from releasing and freeing the session between the
lookup and the mutex lock.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 5aa0011d9643..f99a82517e51 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -426,12 +426,11 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	int err;
 
 	guard(rwsem_read)(&luo_session_serialize_rwsem);
-	scoped_guard(rwsem_read, &sh->rwsem) {
-		list_for_each_entry(it, &sh->list, list) {
-			if (!strncmp(it->name, name, sizeof(it->name))) {
-				session = it;
-				break;
-			}
+	guard(rwsem_read)(&sh->rwsem);
+	list_for_each_entry(it, &sh->list, list) {
+		if (!strncmp(it->name, name, sizeof(it->name))) {
+			session = it;
+			break;
 		}
 	}
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 14 17:37:22 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 00:37:22 +0000
Subject: [PATCH v3 5/5] liveupdate: Remove unused ser field from struct luo_session
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <20260515003722.938123-6-pasha.tatashin@soleen.com>

The ser field in struct luo_session was intended to point to the
serialized data for a session, but it was never actually utilized in the
implementation. All serialization and deserialization logic consistently
uses the pointers maintained in struct luo_session_header.

Remove the dead field to clean up the structure.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..dd53d4a7277e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -59,7 +59,6 @@ struct luo_file_set {
  * struct luo_session - Represents an active or incoming Live Update session.
  * @name:       A unique name for this session, used for identification and
  *              retrieval.
- * @ser:        Pointer to the serialized data for this session.
  * @list:       A list_head member used to link this session into a global list
  *              of either outgoing (to be preserved) or incoming (restored from
  *              previous kernel) sessions.
@@ -70,7 +69,6 @@ struct luo_file_set {
  */
 struct luo_session {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	struct luo_session_ser *ser;
 	struct list_head list;
 	bool retrieved;
 	struct luo_file_set file_set;
-- 
2.53.0



From qirui.001 at bytedance.com  Thu May 14 23:48:51 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Fri, 15 May 2026 14:48:51 +0800
Subject: [PATCH][makedumpfile] 2/2 riscv64: Fix huge page translation in vtop_riscv64()
In-Reply-To: <58d40519-caa4-4ea8-8162-bb213545d6dc@nec.com>
References: <20260506115758.2497610-1-qirui.001@bytedance.com> <20260506115758.2497610-3-qirui.001@bytedance.com> <58d40519-caa4-4ea8-8162-bb213545d6dc@nec.com>
Message-ID: <81a77194-14cc-4b7d-9d58-58b52fc411da@bytedance.com>

On 5/14/26 10:26 AM, HAGIO KAZUHITO(?? ??) wrote:
> On 2026/05/06 20:57, Rui Qi wrote:
>> When a page table entry has _PAGE_LEAF set at PGD/P4D/PUD/PMD level,
>> the current code incorrectly falls through to the next level instead
>> of computing the correct physical address. Fix this by calculating
>> the paddr with the proper level shift and returning directly.
>>
>> Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
>> Signed-off-by: Shuan He <heshuan at bytedance.com>
>> ---
>>   arch/riscv64.c | 26 ++++++++++++++++++--------
>>   1 file changed, 18 insertions(+), 8 deletions(-)
>>
>> diff --git a/arch/riscv64.c b/arch/riscv64.c
>> index 28365fa0cc5b..67755331de24 100644
>> --- a/arch/riscv64.c
>> +++ b/arch/riscv64.c
>> @@ -107,8 +107,12 @@ vtop_riscv64(pgd_t * pgd, unsigned long vaddr, long va_bits)
>>   
>>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>>   
>> -	if (pt_val & _PAGE_LEAF)
>> -		goto out;
> 
> the following warning is observed:
> 
> cc  -g -O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D__riscv64__ -U__x86_64__ -c -o ./arch/riscv64.o arch/riscv64.c
> arch/riscv64.c: In function 'vtop_riscv64':
> arch/riscv64.c:203:1: warning: label 'out' defined but not used [-Wunused-label]
>   out:
>   ^~~

Good catch. The out: label is now unused since we replaced all goto out
with goto invalid. We will remove it in v2.

> 
>> +	if (pt_val & _PAGE_LEAF) {
>> +		unsigned long shift = (va_bits == VA_BITS_SV57) ? PGD_SHIFT_L5 :
>> +				      (va_bits == VA_BITS_SV48) ? PGD_SHIFT_L4 : PGD_SHIFT_L3;
>> +		paddr = (pt_phys & ~((1ULL << shift) - 1)) + (vaddr & ((1ULL << shift) - 1));
>> +		goto invalid;
> 
> just a style point, if this path is not invalid, please do no use the 'invalid'
> label and better to return here instead.
> 

Agreed, using goto invalid for a valid huge page translation path is
indeed misleading. We will replace it with a direct return paddr in v2.

Will send v2 shortly with both issues addressed.
Thanks,
Rui Qi

> Thanks,
> Kazu
> 
>> +	}
>>   
>>   	if (va_bits == VA_BITS_SV57)
>>   		goto p4d;
>> @@ -133,8 +137,10 @@ p4d:
>>   
>>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>>   
>> -	if (pt_val & _PAGE_LEAF)
>> -		goto out;
>> +	if (pt_val & _PAGE_LEAF) {
>> +		paddr = (pt_phys & ~((1ULL << P4D_SHIFT) - 1)) + (vaddr & ((1ULL << P4D_SHIFT) - 1));
>> +		goto invalid;
>> +	}
>>   pud:
>>   	/* PUD */
>>   	puda = (pud_t *)(pt_phys) + pud_index(vaddr);
>> @@ -152,8 +158,10 @@ pud:
>>   
>>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>>   
>> -	if(pt_val & _PAGE_LEAF)
>> -		goto out;
>> +	if (pt_val & _PAGE_LEAF) {
>> +		paddr = (pt_phys & ~((1ULL << PUD_SHIFT) - 1)) + (vaddr & ((1ULL << PUD_SHIFT) - 1));
>> +		goto invalid;
>> +	}
>>   pmd:
>>   	/* PMD */
>>   	pmda = (pmd_t *)(pt_phys) + pmd_index(vaddr);
>> @@ -171,8 +179,10 @@ pmd:
>>   
>>   	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
>>   
>> -	if (pt_val & _PAGE_LEAF)
>> -		goto out;
>> +	if (pt_val & _PAGE_LEAF) {
>> +		paddr = (pt_phys & ~((1ULL << PMD_SHIFT) - 1)) + (vaddr & ((1ULL << PMD_SHIFT) - 1));
>> +		goto invalid;
>> +	}
>>   
>>   	/* PTE */
>>   	ptea = (pte_t *)(pt_phys) + pte_index(vaddr);


From qirui.001 at bytedance.com  Fri May 15 01:07:42 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Fri, 15 May 2026 16:07:42 +0800
Subject: [PATCH v2 0/2] Fix riscv64 virtual-to-physical address translation
Message-ID: <20260515080745.70110-1-qirui.001@bytedance.com>

This patchset fixes two bugs in the riscv64 architecture support of
makedumpfile related to virtual-to-physical address translation.

Patch 1 fixes vaddr_to_paddr_riscv64() to exclude vmalloc and vmemmap
regions from the VTOP() fast path. These regions are not linearly mapped
and must go through page table walk.

Patch 2 fixes vtop_riscv64() to correctly handle huge pages when
_PAGE_LEAF is set at PGD/P4D/PUD/PMD level. The original code incorrectly
fell through to the next level, causing wrong physical address computation.


Changes in v2:
- Patch 2: Changed `goto invalid;` to `return paddr;` when huge page
  (_PAGE_LEAF) hits, as the calculated physical address is valid.
- Patch 2: Removed the unused `out:` label to fix a compiler warning.

Rui Qi (2):
  riscv64: Fix vaddr_to_paddr for vmalloc and vmemmap regions
  riscv64: Fix huge page translation in vtop_riscv64()

 arch/riscv64.c | 31 +++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 10 deletions(-)

-- 
2.20.1


From qirui.001 at bytedance.com  Fri May 15 01:07:43 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Fri, 15 May 2026 16:07:43 +0800
Subject: [PATCH v2 1/2] riscv64: Fix vaddr_to_paddr for vmalloc and vmemmap regions
In-Reply-To: <20260515080745.70110-1-qirui.001@bytedance.com>
References: <20260515080745.70110-1-qirui.001@bytedance.com>
Message-ID: <20260515080745.70110-2-qirui.001@bytedance.com>

The current vaddr_to_paddr_riscv64() incorrectly uses VTOP() linear
mapping for vmalloc and vmemmap addresses, which are not linearly
mapped. Add checks to exclude these regions so they fall through to
page table walk via vtop_riscv64().

Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
Signed-off-by: Shuan He <heshuan at bytedance.com>
---
 arch/riscv64.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/riscv64.c b/arch/riscv64.c
index 1b43ec4f62a1..28365fa0cc5b 100644
--- a/arch/riscv64.c
+++ b/arch/riscv64.c
@@ -202,6 +202,8 @@ vaddr_to_paddr_riscv64(unsigned long vaddr)
 	unsigned long long swapper_phys;
 
 	if (vaddr >= PAGE_OFFSET &&
+	    !(vaddr >= NUMBER(vmalloc_start) && vaddr <= NUMBER(vmalloc_end)) &&
+	    !(vaddr >= NUMBER(vmemmap_start) && vaddr <= NUMBER(vmemmap_end)) &&
 	    !(vaddr >= NUMBER(modules_vaddr) && vaddr <= NUMBER(modules_end))){
 		return VTOP(vaddr);
 	}
-- 
2.20.1


From qirui.001 at bytedance.com  Fri May 15 01:07:44 2026
From: qirui.001 at bytedance.com (Rui Qi)
Date: Fri, 15 May 2026 16:07:44 +0800
Subject: [PATCH v2 2/2] riscv64: Fix huge page translation in vtop_riscv64()
In-Reply-To: <20260515080745.70110-1-qirui.001@bytedance.com>
References: <20260515080745.70110-1-qirui.001@bytedance.com>
Message-ID: <20260515080745.70110-3-qirui.001@bytedance.com>

When a page table entry has _PAGE_LEAF set at PGD/P4D/PUD/PMD level,
the current code incorrectly falls through to the next level instead
of computing the correct physical address. Fix this by calculating
the paddr with the proper level shift and returning directly.

Co-developed-by: Rui Qi <qirui.001 at bytedance.com>
Signed-off-by: Shuan He <heshuan at bytedance.com>
---
 arch/riscv64.c | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/arch/riscv64.c b/arch/riscv64.c
index 28365fa0cc5b..7b640b6f7d29 100644
--- a/arch/riscv64.c
+++ b/arch/riscv64.c
@@ -107,8 +107,12 @@ vtop_riscv64(pgd_t * pgd, unsigned long vaddr, long va_bits)
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		unsigned long shift = (va_bits == VA_BITS_SV57) ? PGD_SHIFT_L5 :
+				      (va_bits == VA_BITS_SV48) ? PGD_SHIFT_L4 : PGD_SHIFT_L3;
+		paddr = (pt_phys & ~((1ULL << shift) - 1)) + (vaddr & ((1ULL << shift) - 1));
+		return paddr;
+	}
 
 	if (va_bits == VA_BITS_SV57)
 		goto p4d;
@@ -133,8 +137,10 @@ p4d:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << P4D_SHIFT) - 1)) + (vaddr & ((1ULL << P4D_SHIFT) - 1));
+		return paddr;
+	}
 pud:
 	/* PUD */
 	puda = (pud_t *)(pt_phys) + pud_index(vaddr);
@@ -152,8 +158,10 @@ pud:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if(pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << PUD_SHIFT) - 1)) + (vaddr & ((1ULL << PUD_SHIFT) - 1));
+		return paddr;
+	}
 pmd:
 	/* PMD */
 	pmda = (pmd_t *)(pt_phys) + pmd_index(vaddr);
@@ -171,8 +179,10 @@ pmd:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-	if (pt_val & _PAGE_LEAF)
-		goto out;
+	if (pt_val & _PAGE_LEAF) {
+		paddr = (pt_phys & ~((1ULL << PMD_SHIFT) - 1)) + (vaddr & ((1ULL << PMD_SHIFT) - 1));
+		return paddr;
+	}
 
 	/* PTE */
 	ptea = (pte_t *)(pt_phys) + pte_index(vaddr);
@@ -190,8 +200,7 @@ pmd:
 
 	pt_phys = (pt_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
 
-out:
-	paddr = pt_phys + PAGEOFFSET(vaddr);
+	return pt_phys + PAGEOFFSET(vaddr);
 invalid:
 	return paddr;
 }
-- 
2.20.1


From mclapinski at google.com  Fri May 15 05:13:29 2026
From: mclapinski at google.com (Michal Clapinski)
Date: Fri, 15 May 2026 14:13:29 +0200
Subject: [PATCH] kho: test: include more variety of allocations
Message-ID: <20260515121329.619995-1-mclapinski@google.com>

Test early (before kho_init) and late allocations.
Also test allocations on primary and secondary boot, since kho_scratch
behaves differently.

Signed-off-by: Michal Clapinski <mclapinski at google.com>
---
 lib/test_kho.c                        | 167 +++++++++++++++++++-------
 tools/testing/selftests/kho/init.c    |  36 +++++-
 tools/testing/selftests/kho/vmtest.sh |  36 +++++-
 3 files changed, 188 insertions(+), 51 deletions(-)

diff --git a/lib/test_kho.c b/lib/test_kho.c
index aa6a0956bb8b..90a49f7cc9ab 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -23,13 +23,17 @@
 
 #include <net/checksum.h>
 
-#define KHO_TEST_MAGIC	0x4b484f21	/* KHO! */
-#define KHO_TEST_FDT	"kho_test"
 #define KHO_TEST_COMPAT "kho-test-v1"
 
 static long max_mem = (PAGE_SIZE << MAX_PAGE_ORDER) * 2;
 module_param(max_mem, long, 0644);
 
+static bool second_boot;
+module_param(second_boot, bool, 0644);
+
+static bool third_boot;
+module_param(third_boot, bool, 0644);
+
 struct kho_test_state {
 	unsigned int nr_folios;
 	struct folio **folios;
@@ -40,7 +44,25 @@ struct kho_test_state {
 	__wsum csum;
 };
 
-static struct kho_test_state kho_test_state;
+struct kho_superstate {
+	struct kho_test_state kho_test_state;
+	const char *kho_test_fdt;
+	int kho_test_magic;
+};
+
+static struct kho_superstate kho_superstate[] = {
+	{{}, "kho_test0", 0x4b484f30},	/* KHO0 */
+	{{}, "kho_test1", 0x4b484f31},
+	{{}, "kho_test2", 0x4b484f32},
+	{{}, "kho_test3", 0x4b484f33},
+};
+
+enum superstate_index {
+	FIRST_BOOT_EARLY_ALLOC,
+	FIRST_BOOT_LATE_ALLOC,
+	SECOND_BOOT_EARLY_ALLOC,
+	SECOND_BOOT_LATE_ALLOC,
+};
 
 static void kho_test_unpreserve_data(struct kho_test_state *state)
 {
@@ -94,10 +116,11 @@ static int kho_test_preserve_data(struct kho_test_state *state)
 	return err;
 }
 
-static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
+static int kho_test_prepare_fdt(struct kho_superstate *superstate, ssize_t fdt_size)
 {
+	struct kho_test_state *state = &superstate->kho_test_state;
 	const char compatible[] = KHO_TEST_COMPAT;
-	unsigned int magic = KHO_TEST_MAGIC;
+	unsigned int magic = superstate->kho_test_magic;
 	void *fdt = folio_address(state->fdt);
 	int err;
 
@@ -121,10 +144,11 @@ static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
 	return err;
 }
 
-static int kho_test_preserve(struct kho_test_state *state)
+static int kho_test_preserve(struct kho_superstate *superstate)
 {
 	ssize_t fdt_size;
 	int err;
+	struct kho_test_state *state = &superstate->kho_test_state;
 
 	fdt_size = state->nr_folios * sizeof(phys_addr_t) + PAGE_SIZE;
 	state->fdt = folio_alloc(GFP_KERNEL, get_order(fdt_size));
@@ -139,11 +163,11 @@ static int kho_test_preserve(struct kho_test_state *state)
 	if (err)
 		goto err_unpreserve_fdt;
 
-	err = kho_test_prepare_fdt(state, fdt_size);
+	err = kho_test_prepare_fdt(superstate, fdt_size);
 	if (err)
 		goto err_unpreserve_data;
 
-	err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt),
+	err = kho_add_subtree(superstate->kho_test_fdt, folio_address(state->fdt),
 			      fdt_totalsize(folio_address(state->fdt)));
 	if (err)
 		goto err_unpreserve_data;
@@ -202,14 +226,12 @@ static int kho_test_generate_data(struct kho_test_state *state)
 	return -ENOMEM;
 }
 
-static int kho_test_save(void)
+static int kho_test_alloc(struct kho_test_state *state)
 {
-	struct kho_test_state *state = &kho_test_state;
 	struct folio **folios;
 	unsigned long max_nr;
 	int err;
 
-	max_mem = PAGE_ALIGN(max_mem);
 	max_nr = max_mem >> PAGE_SHIFT;
 
 	folios = kvmalloc_objs(*state->folios, max_nr);
@@ -221,10 +243,6 @@ static int kho_test_save(void)
 	if (err)
 		goto err_free_folios;
 
-	err = kho_test_preserve(state);
-	if (err)
-		goto err_free_folios;
-
 	return 0;
 
 err_free_folios:
@@ -232,6 +250,18 @@ static int kho_test_save(void)
 	return err;
 }
 
+static int kho_test_alloc_and_preserve(int nr)
+{
+	struct kho_test_state *state = &kho_superstate[nr].kho_test_state;
+	int err;
+
+	err = kho_test_alloc(state);
+	if (err)
+		return err;
+
+	return kho_test_preserve(&kho_superstate[nr]);
+}
+
 static int kho_test_restore_data(const void *fdt, int node)
 {
 	const struct kho_vmalloc *folios_info_phys;
@@ -284,12 +314,31 @@ static int kho_test_restore_data(const void *fdt, int node)
 	return 0;
 }
 
-static int kho_test_restore(phys_addr_t fdt_phys)
+static int kho_test_early_alloc(void)
+{
+	if (third_boot)
+		return 0;
+	else if (second_boot)
+		return kho_test_alloc(&kho_superstate[SECOND_BOOT_EARLY_ALLOC].kho_test_state);
+	else
+		return kho_test_alloc(&kho_superstate[FIRST_BOOT_EARLY_ALLOC].kho_test_state);
+}
+core_initcall(kho_test_early_alloc);
+
+static int kho_test_restore(int nr)
 {
-	void *fdt = phys_to_virt(fdt_phys);
-	const unsigned int *magic;
 	int node, len, err;
+	phys_addr_t fdt_phys;
+	void *fdt;
+	const unsigned int *magic;
 
+	err = kho_retrieve_subtree(kho_superstate[nr].kho_test_fdt, &fdt_phys, NULL);
+	if (err) {
+		pr_err("failed to retrieve %s FDT: %d\n", kho_superstate[nr].kho_test_fdt, err);
+		return err;
+	}
+
+	fdt = phys_to_virt(fdt_phys);
 	node = fdt_path_offset(fdt, "/");
 	if (node < 0)
 		return -EINVAL;
@@ -301,62 +350,90 @@ static int kho_test_restore(phys_addr_t fdt_phys)
 	if (!magic || len != sizeof(*magic))
 		return -EINVAL;
 
-	if (*magic != KHO_TEST_MAGIC)
+	if (*magic != kho_superstate[nr].kho_test_magic)
 		return -EINVAL;
 
 	err = kho_test_restore_data(fdt, node);
 	if (err)
-		return err;
+		pr_err("KHO restore failed\n");
+	else
+		pr_info("KHO restore succeeded\n");
+
+	return err;
+}
+
+extern struct kho_scratch *kho_scratch;
+extern unsigned int kho_scratch_cnt;
 
+static int check_cma(void)
+{
+	for (int i = 0; i < kho_scratch_cnt; i++) {
+		unsigned long base_pfn = PHYS_PFN(kho_scratch[i].addr);
+		unsigned long count = kho_scratch[i].size >> PAGE_SHIFT;
+		unsigned long pfn;
+
+		for (pfn = base_pfn; pfn < base_pfn + count;
+		     pfn += pageblock_nr_pages)
+			if (get_pageblock_migratetype(pfn_to_page(pfn)) != MIGRATE_CMA) {
+				pr_err("KHO wrong migratetype\n");
+				return 1;
+			}
+	}
 	return 0;
 }
 
 static int __init kho_test_init(void)
 {
-	phys_addr_t fdt_phys;
 	int err;
 
 	if (!kho_is_enabled())
 		return 0;
 
-	err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys, NULL);
-	if (!err) {
-		err = kho_test_restore(fdt_phys);
-		if (err)
-			pr_err("KHO restore failed\n");
-		else
-			pr_info("KHO restore succeeded\n");
-
-		return err;
-	}
+	if (check_cma())
+		return -EINVAL;
 
-	if (err != -ENOENT) {
-		pr_warn("failed to retrieve %s FDT: %d\n", KHO_TEST_FDT, err);
-		return err;
+	if (third_boot) {
+		err = kho_test_restore(SECOND_BOOT_EARLY_ALLOC);
+		err |= kho_test_restore(SECOND_BOOT_LATE_ALLOC);
+	} else if (second_boot) {
+		err = kho_test_restore(FIRST_BOOT_EARLY_ALLOC);
+		err |= kho_test_restore(FIRST_BOOT_LATE_ALLOC);
+
+		err |= kho_test_preserve(&kho_superstate[SECOND_BOOT_EARLY_ALLOC]);
+		err |= kho_test_alloc_and_preserve(SECOND_BOOT_LATE_ALLOC);
+	} else {
+		err = kho_test_preserve(&kho_superstate[FIRST_BOOT_EARLY_ALLOC]);
+		err |= kho_test_alloc_and_preserve(FIRST_BOOT_LATE_ALLOC);
 	}
-
-	return kho_test_save();
+	return err;
 }
 module_init(kho_test_init);
 
-static void kho_test_cleanup(void)
+static void kho_test_cleanup(struct kho_test_state *state)
 {
+	kho_remove_subtree(folio_address(state->fdt));
+
 	/* unpreserve and free the data stored in folios */
-	kho_test_unpreserve_data(&kho_test_state);
-	for (int i = 0; i < kho_test_state.nr_folios; i++)
-		folio_put(kho_test_state.folios[i]);
+	kho_test_unpreserve_data(state);
+	for (int i = 0; i < state->nr_folios; i++)
+		folio_put(state->folios[i]);
 
-	kvfree(kho_test_state.folios);
+	kvfree(state->folios);
 
 	/* Unpreserve and release the FDT folio */
-	kho_unpreserve_folio(kho_test_state.fdt);
-	folio_put(kho_test_state.fdt);
+	kho_unpreserve_folio(state->fdt);
+	folio_put(state->fdt);
 }
 
 static void __exit kho_test_exit(void)
 {
-	kho_remove_subtree(folio_address(kho_test_state.fdt));
-	kho_test_cleanup();
+	if (second_boot) {
+		kho_test_cleanup(&kho_superstate[SECOND_BOOT_EARLY_ALLOC].kho_test_state);
+		kho_test_cleanup(&kho_superstate[SECOND_BOOT_LATE_ALLOC].kho_test_state);
+	} else {
+		kho_test_cleanup(&kho_superstate[FIRST_BOOT_EARLY_ALLOC].kho_test_state);
+		kho_test_cleanup(&kho_superstate[FIRST_BOOT_LATE_ALLOC].kho_test_state);
+	}
 }
 module_exit(kho_test_exit);
 
diff --git a/tools/testing/selftests/kho/init.c b/tools/testing/selftests/kho/init.c
index 88a41b6eba95..18cf180c3322 100644
--- a/tools/testing/selftests/kho/init.c
+++ b/tools/testing/selftests/kho/init.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -12,6 +13,17 @@
 #define COMMAND_LINE_SIZE	2048
 
 #define KERNEL_IMAGE "/kernel"
+#define INITRD_IMAGE "/initrd"
+
+void console_log(char *log)
+{
+	static int fd;
+
+	if (!fd)
+		fd = open("/dev/console", O_WRONLY);
+
+	write(fd, log, strlen(log) + 1);
+}
 
 static int mount_filesystems(void)
 {
@@ -29,11 +41,14 @@ static long kexec_file_load(int kernel_fd, int initrd_fd,
 		       cmdline, flags);
 }
 
+#define CMDLINE_SECOND " test_kho.second_boot=1"
+#define CMDLINE_THIRD " test_kho.third_boot=1"
+
 static int kexec_load(void)
 {
 	char cmdline[COMMAND_LINE_SIZE];
 	ssize_t len;
-	int fd, err;
+	int fd, err, initfd;
 
 	fd = open("/proc/cmdline", O_RDONLY);
 	if (fd < 0)
@@ -50,6 +65,25 @@ static int kexec_load(void)
 	if (fd < 0)
 		return -1;
 
+	if (!strstr(cmdline, CMDLINE_SECOND)) {
+		console_log("init: first boot detected\n");
+
+		strcat(cmdline, CMDLINE_SECOND);
+		len = strlen(cmdline) + 1;
+		initfd = open(INITRD_IMAGE, O_RDONLY);
+		if (initfd < 0)
+			return -1;
+
+		err = kexec_file_load(fd, initfd, len, cmdline, 0);
+		close(fd);
+		close(initfd);
+		return err ? : 0;
+	}
+
+	console_log("init: second boot detected\n");
+	strcat(cmdline, CMDLINE_THIRD);
+	len = strlen(cmdline) + 1;
+
 	err = kexec_file_load(fd, -1, len, cmdline, KEXEC_FILE_NO_INITRAMFS);
 	close(fd);
 
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 0014bd76e88d..e7bd1b84bbe8 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -43,6 +43,14 @@ function fail() {
 	local msg=${1:-""}
 
 	ktap_test_fail "$msg"
+
+	local serial_log="$tmp_dir/qemu.serial"
+	if [[ -f "$serial_log" ]]; then
+		echo "=== QEMU Serial Output ===" >&2
+		cat "$serial_log" >&2
+		echo "==========================" >&2
+	fi
+
 	exit "$KSFT_FAIL"
 }
 
@@ -84,11 +92,11 @@ EOF
 
 function mkinitrd() {
 	local kernel=$1
+	local initrd_temp="$tmp_dir/initrd_temp.cpio"
 
-	"$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \
+	"$CROSS_COMPILE"gcc -s -static -Os \
 			-fno-asynchronous-unwind-tables -fno-ident \
 			-I "$headers_dir/include" \
-			-I "$kernel_dir/tools/include/nolibc" \
 			-o "$tmp_dir/init" "$test_dir/init.c"
 
 	cat > "$tmp_dir/cpio_list" <<EOF
@@ -98,6 +106,18 @@ dir /debugfs 0755 0 0
 nod /dev/console 0600 0 0 c 5 1
 file /init $tmp_dir/init 0755 0 0
 file /kernel $kernel 0644 0 0
+EOF
+
+	"$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd_temp"
+
+	cat > "$tmp_dir/cpio_list" <<EOF
+dir /dev 0755 0 0
+dir /proc 0755 0 0
+dir /debugfs 0755 0 0
+nod /dev/console 0600 0 0 c 5 1
+file /init $tmp_dir/init 0755 0 0
+file /kernel $kernel 0644 0 0
+file /initrd $initrd_temp 0755 0 0
 EOF
 
 	"$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd"
@@ -109,16 +129,22 @@ function run_qemu() {
 	local kernel=$3
 	local serial="$tmp_dir/qemu.serial"
 
-	cmdline="$cmdline kho=on panic=-1"
+	# 2GiB of preserved mem to exhaust memory initialized early
+	cmdline="$cmdline kho=on panic=-1 test_kho.max_mem=2147483648 \
+ ignore_loglevel earlyprintk=serial,ttyS0,115200 printk.time=1"
 
-	$qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
+	$qemu_cmd -m 8G -smp 2 -no-reboot -nographic -nodefaults \
 		  -accel kvm -accel hvf -accel tcg  \
 		  -serial file:"$serial" \
 		  -append "$cmdline" \
 		  -kernel "$kernel" \
 		  -initrd "$initrd"
 
-	grep "KHO restore succeeded" "$serial" &> /dev/null || fail "KHO failed"
+	count="$(grep --text "KHO restore succeeded" $serial | wc -l)"
+	echo Successful restores: "$count"
+	if [[ $count -ne 4 ]]; then
+		fail "KHO failed"
+	fi
 }
 
 function target_to_arch() {
-- 
2.54.0.563.g4f69b47b94-goog



From pasha.tatashin at soleen.com  Fri May 15 07:19:10 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 15 May 2026 14:19:10 +0000
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <20260509024415.33190-1-dongtai.guo@linux.dev>
References: <20260509024415.33190-1-dongtai.guo@linux.dev>
Message-ID: <agcrLMYHwcEi8_G1@plex>

On 05-09 10:44, George Guo wrote:
> From: George Guo <guodongtai at kylinos.cn>
> 
> KHO_TREE_MAX_DEPTH is calculated as:
> 
>   DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2,
>                KHO_TABLE_SIZE_LOG2) + 1
> 
> For systems with 16KB pages (e.g. LoongArch), this gives a depth of 4,
> with the top-level shift at bit 39.  The order-0 bit sits at bit 50
> (KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT = 50).  When inserting or reading
> a key, the index extracted at the top level is:
> 
>   (1 << 50) >> 39 = 2048
> 
> 2048 is exactly the table size (PAGE_SIZE / sizeof(phys_addr_t) = 2048
> for 16KB pages), so it wraps to 0, aliasing the order bit to index 0
> and losing it silently.
> 
> On the second kernel, kho_radix_decode_key() sees a key without the
> order bit, calls fls64() on the wrong bit, computes a wrong order and
> thus a garbage physical address. phys_to_page() of that address faults
> in kho_preserved_memory_reserve(), causing a kernel panic early in boot.
> 
> Fix by adding +1 to the DIV_ROUND_UP numerator so the formula accounts
> for the order bit itself, giving depth 5 for 16KB pages. The top-level
> shift becomes 50, and (1 << 50) >> 50 = 1, which is nonzero and
> unambiguous. For 4KB and 64KB page sizes the depth is unchanged.
> 
> Fixes: 3f2ad90060f6 ("kho: adopt radix tree for preserved memory tracking")
> 
> Tested-by: Kexin Liu <liukexin at kylinos.cn>
> Signed-off-by: George Guo <guodongtai at kylinos.cn>

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

The patch fixes 16K support on ARM64.

Pasha


From ross.philipson at gmail.com  Fri May 15 14:13:32 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:32 -0700
Subject: [PATCH v16 00/38] x86: Secure Launch support for Intel TXT
Message-ID: <20260515211410.31440-1-ross.philipson@gmail.com>

Secure Launch is a vendor-neutral approach to implementing TGC Dynamic
Root of Trust (DRTM) support in the kernel. This is complementary to
better known Static Root of Trust (SRTM) schemes such as UEFI
SecureBoot.

This series provides the common infrastructure along with Intel TXT
support, without needing the tboot exokernel. Support for AMD SKINIT is
pending the common infrastructure getting nailed down, and ARM are
looking to build on it too.

Originally, tboot were approached to see if they'd take support for
other vendors, but they elected not to. Hence this approach instead.

Work is being coordinated by the Trenchboot project,
https://trenchboot.org/, organising Secure Launch support for upstream
open source projects including Grub, iPXE and Xen. The goal of the
Trenchboot project is to make DTRM easy to use, e.g. GRUB adds "slaunch"
as a command in the boot stanza. See
https://trenchboot.org/user-docs/QUICKSTART/#linux-quick-start-guide for
more details

Patch set based on commit:
torvalds/master/028ef9c96e96197026887c0f092424679298aae8
(tag: v7.0) Linux 7.0

Finally we would like to thank everyone for their input and
assistance. It has all been very helpful in improving the quality of
our solution and in reviewing/strengthening our security posture.

Thanks
Ross Philipson and Daniel P. Smith

Changes in v16:

 - Moved Secure Launch entry point out of the x86 setup kernel into the
   mainline kernel.
 - Add EFI protocol support for DL stub callback.
 - Add SHA 384/512 PCR extend support.
 - Rewrote Secure Launch kernel documentation.
 - Updated the reorganization of TPM header and buffer related files.
 - TXT early heap parsing support.
 - Corrected commit messages and tag lists per review feedback.
 - Fix code formatting and typos.

Changes in v15:

 - Rewriting and reformatting of the cover letter, commit message and
   code comments per requests from maintainers.
 - Introduction of a early TPM driver in the x86 setup kernel to allow
   TPM extend command very early in the boot.
 - Remove previous TPM extending architecture that attempted to update
   the TPM PCRs later in the boot process.
 - Include set of split up TPM header files to allow TPM driver reuse
   in other environments (e.g. early kernel, x86).
 - Split slaunch.h into 2 files, with a new txt.h. The former contains
   platform agnostic definitions for the SL feature. The new txt.h file
   contains Intel TXT definitions from the public specs.
 - Split TPM headers up following the specifications where the
   technologies are defined.
 - Fix code formatting and typos.

Alec Brown (1):
  tpm: Remove main TPM header from TPM event log header

Ard Biesheuvel (3):
  x86/boot: Slight refactor of the 5 level paging logic
  x86/efistub: EFI stub DRTM support for Secure Launch
  x86/boot: Legacy boot DRTM support for Secure Launch

Daniel P. Smith (9):
  tpm/tpm_tis: Close all localities
  tpm/tpm_tis: Address positive localities in tpm_tis_request_locality()
  tpm/tpm_tis: Allow locality to be set to a different value
  tpm/sysfs: Show locality used by kernel
  Documentation/security: Secure Launch kernel documentation
  x86: Add early SHA-1 support for Secure Launch early measurements
  x86: Add early SHA-256 support for Secure Launch early measurements
  x86: Add early SHA-384/512 support for Secure Launch early
    measurements
  x86/slaunch: Secure Launch late initcall platform module

Jarkko Sakkinen (3):
  tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW
  tpm-buf: Remove chip parameter from tpm_buf_append_handle()
  tpm-buf: Implement managed allocations

Ross Philipson (22):
  tpm: Initial step to reorganize TPM public headers
  tpm: Move TPM1 specific definitions to the command header
  tpm: Move TPM2 specific definitions to the command header
  tpm: Move TPM common base definitions to the command header
  tpm: Move platform specific definitions to the new PTP header
  tpm-buf: Add TPM buffer support header for standalone reuse
  x86: Secure Launch Kconfig
  x86: Secure Launch Resource Table header file
  x86/efi: Secure Launch Resource Table EFI definitions header file
  x86: Secure Launch main header file
  x86/txt: Intel Trusted eXecution Technology (TXT) definitions
  lib/crypto: Add SHA1 support for pre-boot environments
  lib/crypto: Add SHA512 support for pre-boot environments
  x86: Allow WARN_trap() macro to be included in pre-boot environments
  x86/msr: Add variable MTRR base/mask and x2apic ID registers
  x86/tpm: Early startup TPM PCR extending driver
  x86/slaunch: Add MLE header and Secure Launch entrypoint to the core
    kernel
  x86/slaunch: Secure Launch kernel early boot initialization
  x86/slaunch: Secure Launch kernel late boot initialization
  x86/slaunch: Secure Launch SMP bringup support
  kexec/slaunch: Secure Launch kexec SEXIT support
  reboot/slaunch: Secure Launch SEXIT support on reboot paths

 Documentation/arch/x86/boot.rst               |   8 +
 Documentation/arch/x86/zero-page.rst          |   1 +
 Documentation/security/index.rst              |   1 +
 .../security/launch-integrity/index.rst       |   9 +
 .../launch-integrity/secure_launch.rst        | 681 ++++++++++++++
 arch/x86/Kconfig                              |  15 +
 arch/x86/boot/compressed/Makefile             |   2 +-
 arch/x86/boot/compressed/misc.c               |  55 +-
 arch/x86/boot/compressed/pgtable_64.c         |  18 +-
 arch/x86/boot/startup/Makefile                |   8 +
 arch/x86/boot/startup/exports.h               |   7 +
 arch/x86/boot/startup/lib-sha1.c              |   6 +
 arch/x86/boot/startup/lib-sha256.c            |   6 +
 arch/x86/boot/startup/lib-sha512.c            |   6 +
 arch/x86/boot/startup/sl_main.c               | 638 +++++++++++++
 arch/x86/boot/startup/tpm.h                   |  47 +
 arch/x86/boot/startup/tpm_drv.c               | 567 ++++++++++++
 arch/x86/include/asm/boot.h                   |   4 +
 arch/x86/include/asm/bug.h                    |   8 +-
 arch/x86/include/asm/msr-index.h              |   5 +
 arch/x86/include/asm/realmode.h               |   3 +
 arch/x86/include/asm/txt.h                    | 281 ++++++
 arch/x86/include/uapi/asm/bootparam.h         |   3 +-
 arch/x86/kernel/Makefile                      |   3 +
 arch/x86/kernel/asm-offsets.c                 |  22 +
 arch/x86/kernel/reboot.c                      |  14 +
 arch/x86/kernel/setup.c                       |   3 +
 arch/x86/kernel/sl_stub.S                     | 847 ++++++++++++++++++
 arch/x86/kernel/slaunch.c                     | 619 +++++++++++++
 arch/x86/kernel/slmodule.c                    | 353 ++++++++
 arch/x86/kernel/smpboot.c                     |  47 +-
 arch/x86/kernel/vmlinux.lds.S                 |   5 +
 arch/x86/realmode/init.c                      |   8 +
 arch/x86/realmode/rm/header.S                 |   3 +
 arch/x86/realmode/rm/trampoline_64.S          |  32 +
 arch/x86/tools/relocs.c                       |   1 +
 drivers/char/tpm/tpm-buf.c                    | 148 +--
 drivers/char/tpm/tpm-chip.c                   |  35 +-
 drivers/char/tpm/tpm-sysfs.c                  |  31 +-
 drivers/char/tpm/tpm.h                        | 180 ----
 drivers/char/tpm/tpm1-cmd.c                   | 193 ++--
 drivers/char/tpm/tpm2-cmd.c                   | 338 +++----
 drivers/char/tpm/tpm2-sessions.c              | 144 ++-
 drivers/char/tpm/tpm2-space.c                 |  57 +-
 drivers/char/tpm/tpm_tis_core.c               |  24 +-
 drivers/char/tpm/tpm_tis_core.h               |  64 +-
 drivers/char/tpm/tpm_vtpm_proxy.c             |  30 +-
 drivers/firmware/efi/libstub/Makefile         |   1 +
 drivers/firmware/efi/libstub/efistub.h        |  24 +
 drivers/firmware/efi/libstub/x86-slaunch.c    |  38 +
 drivers/firmware/efi/libstub/x86-stub.c       |  27 +-
 drivers/iommu/intel/dmar.c                    |   4 +
 include/keys/trusted_tpm.h                    |   1 -
 include/linux/slaunch.h                       | 262 ++++++
 include/linux/slr_efi.h                       |  40 +
 include/linux/slr_table.h                     | 339 +++++++
 include/linux/tpm.h                           | 234 +----
 include/linux/tpm_buf.h                       |  57 ++
 include/linux/tpm_command.h                   | 516 ++++++++++-
 include/linux/tpm_eventlog.h                  |   4 +-
 include/linux/tpm_ptp.h                       | 151 ++++
 kernel/kexec_core.c                           |   8 +
 lib/crypto/sha1.c                             |  10 +-
 lib/crypto/sha512.c                           |  10 +-
 security/keys/trusted-keys/trusted_tpm1.c     |  37 +-
 security/keys/trusted-keys/trusted_tpm2.c     | 171 ++--
 66 files changed, 6385 insertions(+), 1129 deletions(-)
 create mode 100644 Documentation/security/launch-integrity/index.rst
 create mode 100644 Documentation/security/launch-integrity/secure_launch.rst
 create mode 100644 arch/x86/boot/startup/lib-sha1.c
 create mode 100644 arch/x86/boot/startup/lib-sha256.c
 create mode 100644 arch/x86/boot/startup/lib-sha512.c
 create mode 100644 arch/x86/boot/startup/sl_main.c
 create mode 100644 arch/x86/boot/startup/tpm.h
 create mode 100644 arch/x86/boot/startup/tpm_drv.c
 create mode 100644 arch/x86/include/asm/txt.h
 create mode 100644 arch/x86/kernel/sl_stub.S
 create mode 100644 arch/x86/kernel/slaunch.c
 create mode 100644 arch/x86/kernel/slmodule.c
 create mode 100644 drivers/firmware/efi/libstub/x86-slaunch.c
 create mode 100644 include/linux/slaunch.h
 create mode 100644 include/linux/slr_efi.h
 create mode 100644 include/linux/slr_table.h
 create mode 100644 include/linux/tpm_buf.h
 create mode 100644 include/linux/tpm_ptp.h

-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:33 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:33 -0700
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public headers
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-2-ross.philipson@gmail.com>

Consolidate TPM1 constants in tpm_command.h and remove duplicate
constants from tpm1-cmd.c.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-buf.c                |  1 -
 drivers/char/tpm/tpm1-cmd.c               | 14 +-------
 include/keys/trusted_tpm.h                |  1 -
 include/linux/tpm.h                       |  2 ++
 include/linux/tpm_command.h               | 41 ++++++++++++++++-------
 security/keys/trusted-keys/trusted_tpm1.c |  1 -
 security/keys/trusted-keys/trusted_tpm2.c |  1 -
 7 files changed, 31 insertions(+), 30 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index dc882fc9fa9e..4c4f450630df 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -3,7 +3,6 @@
  * Handling of TPM command and other buffers.
  */
 
-#include <linux/tpm_command.h>
 #include <linux/module.h>
 #include <linux/tpm.h>
 
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index b49a790f1bd5..664ca1fff2e8 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -22,8 +22,6 @@
 
 #include "tpm.h"
 
-#define TPM_MAX_ORDINAL 243
-
 /*
  * Array with one entry per ordinal defining the maximum amount
  * of time the chip could take to return the result.  The ordinal
@@ -308,9 +306,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
 		return duration;
 }
 
-#define TPM_ORD_STARTUP 153
-#define TPM_ST_CLEAR 1
-
 /**
  * tpm1_startup() - turn on the TPM
  * @chip: TPM chip to use
@@ -459,7 +454,6 @@ int tpm1_get_timeouts(struct tpm_chip *chip)
 	return 0;
 }
 
-#define TPM_ORD_PCR_EXTEND 20
 int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
 		    const char *log_msg)
 {
@@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
 	return rc;
 }
 
-#define TPM_ORD_GET_CAP 101
 ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
 		    const char *desc, size_t min_cap_length)
 {
@@ -511,7 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
 }
 EXPORT_SYMBOL_GPL(tpm1_getcap);
 
-#define TPM_ORD_GET_RANDOM 70
 struct tpm1_get_random_out {
 	__be32 rng_data_len;
 	u8 rng_data[TPM_MAX_RNG_DATA];
@@ -580,13 +572,12 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 	return rc;
 }
 
-#define TPM_ORD_PCRREAD 21
 int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
 {
 	struct tpm_buf buf;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
+	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
 	if (rc)
 		return rc;
 
@@ -609,7 +600,6 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
 	return rc;
 }
 
-#define TPM_ORD_CONTINUE_SELFTEST 83
 /**
  * tpm1_continue_selftest() - run TPM's selftest
  * @chip: TPM chip to use
@@ -726,8 +716,6 @@ int tpm1_auto_startup(struct tpm_chip *chip)
 	return rc;
 }
 
-#define TPM_ORD_SAVESTATE 152
-
 /**
  * tpm1_pm_suspend() - pm suspend handler
  * @chip: TPM chip to use.
diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
index 0fadc6a4f166..3a0fa3bc8454 100644
--- a/include/keys/trusted_tpm.h
+++ b/include/keys/trusted_tpm.h
@@ -3,7 +3,6 @@
 #define __TRUSTED_TPM_H
 
 #include <keys/trusted-type.h>
-#include <linux/tpm_command.h>
 
 extern struct trusted_key_ops trusted_key_tpm_ops;
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 202da079d500..1846d5485a2c 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -25,6 +25,8 @@
 #include <crypto/hash_info.h>
 #include <crypto/aes.h>
 
+#include <linux/tpm_command.h>
+
 #define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
 
 #define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index f5c03e9c3913..174b043d8bbc 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -3,27 +3,42 @@
 #define __LINUX_TPM_COMMAND_H__
 
 /*
- * TPM Command constants from specifications at
- * http://www.trustedcomputinggroup.org
+ * == TPM 1 Family Chips ==
+ *
+ * TPM 1.2 Main Specification:
+ * https://trustedcomputinggroup.org/resource/tpm-main-specification/
  */
 
+#define TPM_MAX_ORDINAL	243
+
 /* Command TAGS */
-#define TPM_TAG_RQU_COMMAND             193
-#define TPM_TAG_RQU_AUTH1_COMMAND       194
-#define TPM_TAG_RQU_AUTH2_COMMAND       195
-#define TPM_TAG_RSP_COMMAND             196
-#define TPM_TAG_RSP_AUTH1_COMMAND       197
-#define TPM_TAG_RSP_AUTH2_COMMAND       198
+enum tpm_command_tags {
+	TPM_TAG_RQU_COMMAND		= 193,
+	TPM_TAG_RQU_AUTH1_COMMAND	= 194,
+	TPM_TAG_RQU_AUTH2_COMMAND	= 195,
+	TPM_TAG_RSP_COMMAND		= 196,
+	TPM_TAG_RSP_AUTH1_COMMAND	= 197,
+	TPM_TAG_RSP_AUTH2_COMMAND	= 198,
+};
 
 /* Command Ordinals */
-#define TPM_ORD_GETRANDOM               70
-#define TPM_ORD_OSAP                    11
-#define TPM_ORD_OIAP                    10
-#define TPM_ORD_SEAL                    23
-#define TPM_ORD_UNSEAL                  24
+enum tpm_command_ordinals {
+	TPM_ORD_CONTINUE_SELFTEST	= 83,
+	TPM_ORD_GET_CAP			= 101,
+	TPM_ORD_GET_RANDOM		= 70,
+	TPM_ORD_PCR_EXTEND		= 20,
+	TPM_ORD_PCR_READ		= 21,
+	TPM_ORD_OSAP			= 11,
+	TPM_ORD_OIAP			= 10,
+	TPM_ORD_SAVESTATE		= 152,
+	TPM_ORD_SEAL			= 23,
+	TPM_ORD_STARTUP			= 153,
+	TPM_ORD_UNSEAL			= 24,
+};
 
 /* Other constants */
 #define SRKHANDLE                       0x40000000
 #define TPM_NONCE_SIZE                  20
+#define TPM_ST_CLEAR			1
 
 #endif
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 6ea728f1eae6..0d3244af8de3 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -18,7 +18,6 @@
 #include <keys/trusted-type.h>
 #include <linux/key-type.h>
 #include <linux/tpm.h>
-#include <linux/tpm_command.h>
 
 #include <keys/trusted_tpm.h>
 
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 6340823f8b53..29d79c05ed6b 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -9,7 +9,6 @@
 #include <linux/string.h>
 #include <linux/err.h>
 #include <linux/tpm.h>
-#include <linux/tpm_command.h>
 
 #include <keys/trusted-type.h>
 #include <keys/trusted_tpm.h>
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:34 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:34 -0700
Subject: [PATCH v16 02/38] tpm: Move TPM1 specific definitions to the command header
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-3-ross.philipson@gmail.com>

Gather all the TPM1 definitions and structures in the internal header
file drivers/char/tpm/tpm.h into the command header. In addition, bring
in the single RNG structure from tpm-interface.c.

The definitions moved to these files correspond to the TCG specification
for TPM 1 family:

TPM 1.2 Main Specification
 -  https://trustedcomputinggroup.org/resource/tpm-main-specification/

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm.h      | 102 --------------------------------
 drivers/char/tpm/tpm1-cmd.c |   5 --
 include/linux/tpm_command.h | 115 ++++++++++++++++++++++++++++++++++++
 3 files changed, 115 insertions(+), 107 deletions(-)

diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 87d68ddf270a..043d78a9617a 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -52,105 +52,9 @@ enum tpm_addr {
 	TPM_ADDR = 0x4E,
 };
 
-#define TPM_WARN_RETRY          0x800
-#define TPM_WARN_DOING_SELFTEST 0x802
-#define TPM_ERR_DEACTIVATED     0x6
-#define TPM_ERR_DISABLED        0x7
-#define TPM_ERR_FAILEDSELFTEST  0x1C
-#define TPM_ERR_INVALID_POSTINIT 38
-
-#define TPM_TAG_RQU_COMMAND 193
-
 /* TPM2 specific constants. */
 #define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
 
-struct	stclear_flags_t {
-	__be16	tag;
-	u8	deactivated;
-	u8	disableForceClear;
-	u8	physicalPresence;
-	u8	physicalPresenceLock;
-	u8	bGlobalLock;
-} __packed;
-
-struct tpm1_version {
-	u8 major;
-	u8 minor;
-	u8 rev_major;
-	u8 rev_minor;
-} __packed;
-
-struct tpm1_version2 {
-	__be16 tag;
-	struct tpm1_version version;
-} __packed;
-
-struct	timeout_t {
-	__be32	a;
-	__be32	b;
-	__be32	c;
-	__be32	d;
-} __packed;
-
-struct duration_t {
-	__be32	tpm_short;
-	__be32	tpm_medium;
-	__be32	tpm_long;
-} __packed;
-
-struct permanent_flags_t {
-	__be16	tag;
-	u8	disable;
-	u8	ownership;
-	u8	deactivated;
-	u8	readPubek;
-	u8	disableOwnerClear;
-	u8	allowMaintenance;
-	u8	physicalPresenceLifetimeLock;
-	u8	physicalPresenceHWEnable;
-	u8	physicalPresenceCMDEnable;
-	u8	CEKPUsed;
-	u8	TPMpost;
-	u8	TPMpostLock;
-	u8	FIPS;
-	u8	operator;
-	u8	enableRevokeEK;
-	u8	nvLocked;
-	u8	readSRKPub;
-	u8	tpmEstablished;
-	u8	maintenanceDone;
-	u8	disableFullDALogicInfo;
-} __packed;
-
-typedef union {
-	struct	permanent_flags_t perm_flags;
-	struct	stclear_flags_t	stclear_flags;
-	__u8	owned;
-	__be32	num_pcrs;
-	struct tpm1_version version1;
-	struct tpm1_version2 version2;
-	__be32	manufacturer_id;
-	struct timeout_t  timeout;
-	struct duration_t duration;
-} cap_t;
-
-enum tpm_capabilities {
-	TPM_CAP_FLAG = 4,
-	TPM_CAP_PROP = 5,
-	TPM_CAP_VERSION_1_1 = 0x06,
-	TPM_CAP_VERSION_1_2 = 0x1A,
-};
-
-enum tpm_sub_capabilities {
-	TPM_CAP_PROP_PCR = 0x101,
-	TPM_CAP_PROP_MANUFACTURER = 0x103,
-	TPM_CAP_FLAG_PERM = 0x108,
-	TPM_CAP_FLAG_VOL = 0x109,
-	TPM_CAP_PROP_OWNER = 0x111,
-	TPM_CAP_PROP_TIS_TIMEOUT = 0x115,
-	TPM_CAP_PROP_TIS_DURATION = 0x120,
-};
-
 enum tpm2_pt_props {
 	TPM2_PT_NONE = 0x00000000,
 	TPM2_PT_GROUP = 0x00000100,
@@ -225,12 +129,6 @@ enum tpm2_pt_props {
 	TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20,
 };
 
-/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18
- * bytes, but 128 is still a relatively large number of random bytes and
- * anything much bigger causes users of struct tpm_cmd_t to start getting
- * compiler warnings about stack frame size. */
-#define TPM_MAX_RNG_DATA	128
-
 extern const struct class tpm_class;
 extern const struct class tpmrm_class;
 extern dev_t tpm_devt;
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index 664ca1fff2e8..96f189b5fd6f 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -504,11 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
 }
 EXPORT_SYMBOL_GPL(tpm1_getcap);
 
-struct tpm1_get_random_out {
-	__be32 rng_data_len;
-	u8 rng_data[TPM_MAX_RNG_DATA];
-} __packed;
-
 /**
  * tpm1_get_random() - get random bytes from the TPM's RNG
  * @chip:	a &struct tpm_chip instance
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index 174b043d8bbc..30d01953a6f8 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -36,6 +36,121 @@ enum tpm_command_ordinals {
 	TPM_ORD_UNSEAL			= 24,
 };
 
+enum tpm_capabilities {
+	TPM_CAP_FLAG		= 4,
+	TPM_CAP_PROP		= 5,
+	TPM_CAP_VERSION_1_1	= 0x06,
+	TPM_CAP_VERSION_1_2	= 0x1A,
+};
+
+enum tpm_sub_capabilities {
+	TPM_CAP_PROP_PCR		= 0x101,
+	TPM_CAP_PROP_MANUFACTURER	= 0x103,
+	TPM_CAP_FLAG_PERM		= 0x108,
+	TPM_CAP_FLAG_VOL		= 0x109,
+	TPM_CAP_PROP_OWNER		= 0x111,
+	TPM_CAP_PROP_TIS_TIMEOUT	= 0x115,
+	TPM_CAP_PROP_TIS_DURATION	= 0x120,
+};
+
+/* Return Codes */
+enum tpm_return_codes {
+	TPM_BASE_MASK			= 0,
+	TPM_NON_FATAL_MASK		= 0x00000800,
+	TPM_SUCCESS			= TPM_BASE_MASK + 0,
+	TPM_ERR_DEACTIVATED		= TPM_BASE_MASK + 6,
+	TPM_ERR_DISABLED		= TPM_BASE_MASK + 7,
+	TPM_ERR_FAIL			= TPM_BASE_MASK + 9,
+	TPM_ERR_FAILEDSELFTEST		= TPM_BASE_MASK + 28,
+	TPM_ERR_INVALID_POSTINIT	= TPM_BASE_MASK + 38,
+	TPM_ERR_INVALID_FAMILY		= TPM_BASE_MASK + 55,
+	TPM_WARN_RETRY			= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 0,
+	TPM_WARN_DOING_SELFTEST		= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 2,
+};
+
+struct	stclear_flags_t {
+	__be16 tag;
+	u8 deactivated;
+	u8 disableForceClear;
+	u8 physicalPresence;
+	u8 physicalPresenceLock;
+	u8 bGlobalLock;
+} __packed;
+
+struct tpm1_version {
+	u8 major;
+	u8 minor;
+	u8 rev_major;
+	u8 rev_minor;
+} __packed;
+
+struct tpm1_version2 {
+	__be16 tag;
+	struct tpm1_version version;
+} __packed;
+
+struct	timeout_t {
+	__be32 a;
+	__be32 b;
+	__be32 c;
+	__be32 d;
+} __packed;
+
+struct duration_t {
+	__be32 tpm_short;
+	__be32 tpm_medium;
+	__be32 tpm_long;
+} __packed;
+
+struct permanent_flags_t {
+	__be16 tag;
+	u8 disable;
+	u8 ownership;
+	u8 deactivated;
+	u8 readPubek;
+	u8 disableOwnerClear;
+	u8 allowMaintenance;
+	u8 physicalPresenceLifetimeLock;
+	u8 physicalPresenceHWEnable;
+	u8 physicalPresenceCMDEnable;
+	u8 CEKPUsed;
+	u8 TPMpost;
+	u8 TPMpostLock;
+	u8 FIPS;
+	u8 operator;
+	u8 enableRevokeEK;
+	u8 nvLocked;
+	u8 readSRKPub;
+	u8 tpmEstablished;
+	u8 maintenanceDone;
+	u8 disableFullDALogicInfo;
+} __packed;
+
+typedef union {
+	struct permanent_flags_t perm_flags;
+	struct stclear_flags_t stclear_flags;
+	__u8 owned;
+	__be32 num_pcrs;
+	struct tpm1_version version1;
+	struct tpm1_version2 version2;
+	__be32 manufacturer_id;
+	struct timeout_t timeout;
+	struct duration_t duration;
+} cap_t;
+
+/*
+ * 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18
+ * bytes, but 128 is still a relatively large number of random bytes and
+ * anything much bigger causes users of struct tpm_cmd_t to start getting
+ * compiler warnings about stack frame size.
+ */
+#define TPM_MAX_RNG_DATA		128
+
+struct tpm1_get_random_out {
+	__be32 rng_data_len;
+	u8 rng_data[TPM_MAX_RNG_DATA];
+} __packed;
+
 /* Other constants */
 #define SRKHANDLE                       0x40000000
 #define TPM_NONCE_SIZE                  20
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:35 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:35 -0700
Subject: [PATCH v16 03/38] tpm: Move TPM2 specific definitions to the command header
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-4-ross.philipson@gmail.com>

Gather all the TPM2 definitions and structures in the internal header
file drivers/char/tpm/tpm.h into the command header, including:
 - Command codes, return codes and definitions from the public and
internal tpm.h files.
 - Structures defined in numerous TPM driver C modules.

The definitions moved to these files correspond to the TCG specification
for TPM 2 family:

TPM 2.0 Library
 - https://trustedcomputinggroup.org/resource/tpm-library-specification/

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm.h        |  77 ----------
 drivers/char/tpm/tpm2-cmd.c   |  30 ----
 drivers/char/tpm/tpm2-space.c |  13 --
 include/linux/tpm.h           | 145 ------------------
 include/linux/tpm_command.h   | 271 ++++++++++++++++++++++++++++++++++
 5 files changed, 271 insertions(+), 265 deletions(-)

diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 043d78a9617a..680f89d9c9f9 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -52,83 +52,6 @@ enum tpm_addr {
 	TPM_ADDR = 0x4E,
 };
 
-/* TPM2 specific constants. */
-#define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
-
-enum tpm2_pt_props {
-	TPM2_PT_NONE = 0x00000000,
-	TPM2_PT_GROUP = 0x00000100,
-	TPM2_PT_FIXED = TPM2_PT_GROUP * 1,
-	TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0,
-	TPM2_PT_LEVEL = TPM2_PT_FIXED + 1,
-	TPM2_PT_REVISION = TPM2_PT_FIXED + 2,
-	TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3,
-	TPM2_PT_YEAR = TPM2_PT_FIXED + 4,
-	TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5,
-	TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6,
-	TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7,
-	TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8,
-	TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9,
-	TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10,
-	TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11,
-	TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12,
-	TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13,
-	TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14,
-	TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15,
-	TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16,
-	TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17,
-	TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18,
-	TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19,
-	TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20,
-	TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22,
-	TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23,
-	TPM2_PT_MEMORY = TPM2_PT_FIXED + 24,
-	TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25,
-	TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26,
-	TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27,
-	TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28,
-	TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29,
-	TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30,
-	TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31,
-	TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32,
-	TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33,
-	TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34,
-	TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35,
-	TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36,
-	TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37,
-	TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38,
-	TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39,
-	TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40,
-	TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41,
-	TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42,
-	TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43,
-	TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44,
-	TPM2_PT_MODES = TPM2_PT_FIXED + 45,
-	TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46,
-	TPM2_PT_VAR = TPM2_PT_GROUP * 2,
-	TPM2_PT_PERMANENT = TPM2_PT_VAR + 0,
-	TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1,
-	TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2,
-	TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3,
-	TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4,
-	TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5,
-	TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6,
-	TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7,
-	TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8,
-	TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9,
-	TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10,
-	TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11,
-	TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12,
-	TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13,
-	TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14,
-	TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15,
-	TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16,
-	TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17,
-	TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18,
-	TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19,
-	TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20,
-};
-
 extern const struct class tpm_class;
 extern const struct class tpmrm_class;
 extern dev_t tpm_devt;
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 3a77be7ebf4a..1fa3e8a43c79 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -94,17 +94,6 @@ unsigned long tpm2_calc_ordinal_duration(u32 ordinal)
 	return msecs_to_jiffies(TPM2_DURATION_DEFAULT);
 }
 
-struct tpm2_pcr_read_out {
-	__be32	update_cnt;
-	__be32	pcr_selects_cnt;
-	__be16	hash_alg;
-	u8	pcr_select_size;
-	u8	pcr_select[TPM2_PCR_SELECT_MIN];
-	__be32	digests_cnt;
-	__be16	digest_size;
-	u8	digest[];
-} __packed;
-
 /**
  * tpm2_pcr_read() - read a PCR value
  * @chip:	TPM chip to use.
@@ -238,11 +227,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 	return rc;
 }
 
-struct tpm2_get_random_out {
-	__be16 size;
-	u8 buffer[TPM_MAX_RNG_DATA];
-} __packed;
-
 /**
  * tpm2_get_random() - get random bytes from the TPM RNG
  *
@@ -366,14 +350,6 @@ void tpm2_flush_context(struct tpm_chip *chip, u32 handle)
 }
 EXPORT_SYMBOL_GPL(tpm2_flush_context);
 
-struct tpm2_get_cap_out {
-	u8 more_data;
-	__be32 subcap_id;
-	__be32 property_cnt;
-	__be32 property_id;
-	__be32 value;
-} __packed;
-
 /**
  * tpm2_get_tpm_pt() - get value of a TPM_CAP_TPM_PROPERTIES type property
  * @chip:		a &tpm_chip instance
@@ -541,12 +517,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index)
 	return tpm2_pcr_read(chip, 0, &digest, &bank->digest_size);
 }
 
-struct tpm2_pcr_selection {
-	__be16  hash_alg;
-	u8  size_of_select;
-	u8  pcr_select[3];
-} __packed;
-
 ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 {
 	struct tpm2_pcr_selection pcr_selection;
diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
index 60354cd53b5c..7c1c0a174a2b 100644
--- a/drivers/char/tpm/tpm2-space.c
+++ b/drivers/char/tpm/tpm2-space.c
@@ -15,19 +15,6 @@
 #include <linux/unaligned.h>
 #include "tpm.h"
 
-enum tpm2_handle_types {
-	TPM2_HT_HMAC_SESSION	= 0x02000000,
-	TPM2_HT_POLICY_SESSION	= 0x03000000,
-	TPM2_HT_TRANSIENT	= 0x80000000,
-};
-
-struct tpm2_context {
-	__be64 sequence;
-	__be32 saved_handle;
-	__be32 hierarchy;
-	__be16 blob_size;
-} __packed;
-
 static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space)
 {
 	int i;
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 1846d5485a2c..8551b24c2bff 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -38,12 +38,6 @@ struct trusted_key_options;
 /* opaque structure, holds auth session parameters like the session key */
 struct tpm2_auth;
 
-enum tpm2_session_types {
-	TPM2_SE_HMAC	= 0x00,
-	TPM2_SE_POLICY	= 0x01,
-	TPM2_SE_TRIAL	= 0x02,
-};
-
 /* if you add a new hash to this, increment TPM_MAX_HASHES below */
 enum tpm_algorithms {
 	TPM_ALG_ERROR		= 0x0000,
@@ -65,11 +59,6 @@ enum tpm_algorithms {
  */
 #define TPM_MAX_HASHES	5
 
-enum tpm2_curves {
-	TPM2_ECC_NONE		= 0x0000,
-	TPM2_ECC_NIST_P256	= 0x0003,
-};
-
 struct tpm_digest {
 	u16 alg_id;
 	u8 digest[TPM2_MAX_DIGEST_SIZE];
@@ -222,122 +211,11 @@ struct tpm_chip {
 
 #define TPM_HEADER_SIZE		10
 
-enum tpm2_const {
-	TPM2_PLATFORM_PCR       =     24,
-	TPM2_PCR_SELECT_MIN     = ((TPM2_PLATFORM_PCR + 7) / 8),
-};
-
-enum tpm2_timeouts {
-	TPM2_TIMEOUT_A          =    750,
-	TPM2_TIMEOUT_B          =   4000,
-	TPM2_TIMEOUT_C          =    200,
-	TPM2_TIMEOUT_D          =     30,
-};
-
-enum tpm2_durations {
-	TPM2_DURATION_SHORT     =     20,
-	TPM2_DURATION_LONG      =   2000,
-	TPM2_DURATION_DEFAULT   = 120000,
-};
-
-enum tpm2_structures {
-	TPM2_ST_NO_SESSIONS	= 0x8001,
-	TPM2_ST_SESSIONS	= 0x8002,
-	TPM2_ST_CREATION	= 0x8021,
-};
-
-/* Indicates from what layer of the software stack the error comes from */
-#define TSS2_RC_LAYER_SHIFT	 16
-#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT)
-
-enum tpm2_return_codes {
-	TPM2_RC_SUCCESS		= 0x0000,
-	TPM2_RC_HASH		= 0x0083, /* RC_FMT1 */
-	TPM2_RC_HANDLE		= 0x008B,
-	TPM2_RC_INTEGRITY	= 0x009F,
-	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
-	TPM2_RC_FAILURE		= 0x0101,
-	TPM2_RC_DISABLED	= 0x0120,
-	TPM2_RC_UPGRADE		= 0x012D,
-	TPM2_RC_COMMAND_CODE    = 0x0143,
-	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
-	TPM2_RC_REFERENCE_H0	= 0x0910,
-	TPM2_RC_RETRY		= 0x0922,
-	TPM2_RC_SESSION_MEMORY	= 0x0903,
-};
-
-enum tpm2_command_codes {
-	TPM2_CC_FIRST		        = 0x011F,
-	TPM2_CC_HIERARCHY_CONTROL       = 0x0121,
-	TPM2_CC_HIERARCHY_CHANGE_AUTH   = 0x0129,
-	TPM2_CC_CREATE_PRIMARY          = 0x0131,
-	TPM2_CC_SEQUENCE_COMPLETE       = 0x013E,
-	TPM2_CC_SELF_TEST	        = 0x0143,
-	TPM2_CC_STARTUP		        = 0x0144,
-	TPM2_CC_SHUTDOWN	        = 0x0145,
-	TPM2_CC_NV_READ                 = 0x014E,
-	TPM2_CC_CREATE		        = 0x0153,
-	TPM2_CC_LOAD		        = 0x0157,
-	TPM2_CC_SEQUENCE_UPDATE         = 0x015C,
-	TPM2_CC_UNSEAL		        = 0x015E,
-	TPM2_CC_CONTEXT_LOAD	        = 0x0161,
-	TPM2_CC_CONTEXT_SAVE	        = 0x0162,
-	TPM2_CC_FLUSH_CONTEXT	        = 0x0165,
-	TPM2_CC_READ_PUBLIC		= 0x0173,
-	TPM2_CC_START_AUTH_SESS		= 0x0176,
-	TPM2_CC_VERIFY_SIGNATURE        = 0x0177,
-	TPM2_CC_GET_CAPABILITY	        = 0x017A,
-	TPM2_CC_GET_RANDOM	        = 0x017B,
-	TPM2_CC_PCR_READ	        = 0x017E,
-	TPM2_CC_PCR_EXTEND	        = 0x0182,
-	TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185,
-	TPM2_CC_HASH_SEQUENCE_START     = 0x0186,
-	TPM2_CC_CREATE_LOADED           = 0x0191,
-	TPM2_CC_LAST		        = 0x0193, /* Spec 1.36 */
-};
-
-enum tpm2_permanent_handles {
-	TPM2_RH_NULL		= 0x40000007,
-	TPM2_RS_PW		= 0x40000009,
-};
-
-/* Most Significant Octet for key types  */
-enum tpm2_mso_type {
-	TPM2_MSO_NVRAM		= 0x01,
-	TPM2_MSO_SESSION	= 0x02,
-	TPM2_MSO_POLICY		= 0x03,
-	TPM2_MSO_PERMANENT	= 0x40,
-	TPM2_MSO_VOLATILE	= 0x80,
-	TPM2_MSO_PERSISTENT	= 0x81,
-};
-
 static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
 {
 	return handle >> 24;
 }
 
-enum tpm2_capabilities {
-	TPM2_CAP_HANDLES	= 1,
-	TPM2_CAP_COMMANDS	= 2,
-	TPM2_CAP_PCRS		= 5,
-	TPM2_CAP_TPM_PROPERTIES = 6,
-};
-
-enum tpm2_properties {
-	TPM_PT_TOTAL_COMMANDS	= 0x0129,
-};
-
-enum tpm2_startup_types {
-	TPM2_SU_CLEAR	= 0x0000,
-	TPM2_SU_STATE	= 0x0001,
-};
-
-enum tpm2_cc_attrs {
-	TPM2_CC_ATTR_CHANDLES	= 25,
-	TPM2_CC_ATTR_RHANDLE	= 28,
-	TPM2_CC_ATTR_VENDOR	= 29,
-};
-
 #define TPM_VID_INTEL    0x8086
 #define TPM_VID_WINBOND  0x1050
 #define TPM_VID_STM      0x104A
@@ -389,29 +267,6 @@ struct tpm_buf {
 	u8 handles;
 };
 
-enum tpm2_object_attributes {
-	TPM2_OA_FIXED_TPM		= BIT(1),
-	TPM2_OA_ST_CLEAR		= BIT(2),
-	TPM2_OA_FIXED_PARENT		= BIT(4),
-	TPM2_OA_SENSITIVE_DATA_ORIGIN	= BIT(5),
-	TPM2_OA_USER_WITH_AUTH		= BIT(6),
-	TPM2_OA_ADMIN_WITH_POLICY	= BIT(7),
-	TPM2_OA_NO_DA			= BIT(10),
-	TPM2_OA_ENCRYPTED_DUPLICATION	= BIT(11),
-	TPM2_OA_RESTRICTED		= BIT(16),
-	TPM2_OA_DECRYPT			= BIT(17),
-	TPM2_OA_SIGN			= BIT(18),
-};
-
-enum tpm2_session_attributes {
-	TPM2_SA_CONTINUE_SESSION	= BIT(0),
-	TPM2_SA_AUDIT_EXCLUSIVE		= BIT(1),
-	TPM2_SA_AUDIT_RESET		= BIT(3),
-	TPM2_SA_DECRYPT			= BIT(5),
-	TPM2_SA_ENCRYPT			= BIT(6),
-	TPM2_SA_AUDIT			= BIT(7),
-};
-
 struct tpm2_hash {
 	unsigned int crypto_id;
 	unsigned int tpm_id;
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index 30d01953a6f8..9dd903dd6b5c 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -156,4 +156,275 @@ struct tpm1_get_random_out {
 #define TPM_NONCE_SIZE                  20
 #define TPM_ST_CLEAR			1
 
+/*
+ * == TPM 2 Family Chips ==
+ *
+ * TPM 2.0 Library
+ * https://trustedcomputinggroup.org/resource/tpm-library-specification/
+ */
+
+/* TPM2 specific constants. */
+#define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
+
+enum tpm2_session_types {
+	TPM2_SE_HMAC	= 0x00,
+	TPM2_SE_POLICY	= 0x01,
+	TPM2_SE_TRIAL	= 0x02,
+};
+
+enum tpm2_timeouts {
+	TPM2_TIMEOUT_A		= 750,
+	TPM2_TIMEOUT_B		= 4000,
+	TPM2_TIMEOUT_C		= 200,
+	TPM2_TIMEOUT_D		= 30,
+	TPM2_DURATION_SHORT	= 20,
+	TPM2_DURATION_MEDIUM	= 750,
+	TPM2_DURATION_LONG	= 2000,
+	TPM2_DURATION_LONG_LONG	= 300000,
+	TPM2_DURATION_DEFAULT	= 120000,
+};
+
+enum tpm2_structures {
+	TPM2_ST_NO_SESSIONS	= 0x8001,
+	TPM2_ST_SESSIONS	= 0x8002,
+	TPM2_ST_CREATION	= 0x8021,
+};
+
+/* Indicates from what layer of the software stack the error comes from */
+#define TSS2_RC_LAYER_SHIFT	 16
+#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT)
+
+enum tpm2_return_codes {
+	TPM2_RC_SUCCESS		= 0x0000,
+	TPM2_RC_HASH		= 0x0083, /* RC_FMT1 */
+	TPM2_RC_HANDLE		= 0x008B,
+	TPM2_RC_INTEGRITY	= 0x009F,
+	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
+	TPM2_RC_FAILURE		= 0x0101,
+	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
+	TPM2_RC_COMMAND_CODE	= 0x0143,
+	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
+	TPM2_RC_REFERENCE_H0	= 0x0910,
+	TPM2_RC_RETRY		= 0x0922,
+	TPM2_RC_SESSION_MEMORY	= 0x0903,
+};
+
+enum tpm2_command_codes {
+	TPM2_CC_FIRST			= 0x011F,
+	TPM2_CC_HIERARCHY_CONTROL	= 0x0121,
+	TPM2_CC_HIERARCHY_CHANGE_AUTH	= 0x0129,
+	TPM2_CC_CREATE_PRIMARY		= 0x0131,
+	TPM2_CC_SEQUENCE_COMPLETE	= 0x013E,
+	TPM2_CC_SELF_TEST		= 0x0143,
+	TPM2_CC_STARTUP			= 0x0144,
+	TPM2_CC_SHUTDOWN		= 0x0145,
+	TPM2_CC_NV_READ			= 0x014E,
+	TPM2_CC_CREATE			= 0x0153,
+	TPM2_CC_LOAD			= 0x0157,
+	TPM2_CC_SEQUENCE_UPDATE		= 0x015C,
+	TPM2_CC_UNSEAL			= 0x015E,
+	TPM2_CC_CONTEXT_LOAD		= 0x0161,
+	TPM2_CC_CONTEXT_SAVE		= 0x0162,
+	TPM2_CC_FLUSH_CONTEXT		= 0x0165,
+	TPM2_CC_READ_PUBLIC		= 0x0173,
+	TPM2_CC_START_AUTH_SESS		= 0x0176,
+	TPM2_CC_VERIFY_SIGNATURE	= 0x0177,
+	TPM2_CC_GET_CAPABILITY		= 0x017A,
+	TPM2_CC_GET_RANDOM		= 0x017B,
+	TPM2_CC_PCR_READ		= 0x017E,
+	TPM2_CC_PCR_EXTEND		= 0x0182,
+	TPM2_CC_EVENT_SEQUENCE_COMPLETE	= 0x0185,
+	TPM2_CC_HASH_SEQUENCE_START	= 0x0186,
+	TPM2_CC_CREATE_LOADED		= 0x0191,
+	TPM2_CC_LAST			= 0x0193, /* Spec 1.36 */
+};
+
+enum tpm2_capabilities {
+	TPM2_CAP_HANDLES	= 1,
+	TPM2_CAP_COMMANDS	= 2,
+	TPM2_CAP_PCRS		= 5,
+	TPM2_CAP_TPM_PROPERTIES = 6,
+};
+
+enum tpm2_properties {
+	TPM_PT_TOTAL_COMMANDS	= 0x0129,
+};
+
+enum tpm2_startup_types {
+	TPM2_SU_CLEAR		= 0x0000,
+	TPM2_SU_STATE		= 0x0001,
+};
+
+enum tpm2_cc_attrs {
+	TPM2_CC_ATTR_CHANDLES	= 25,
+	TPM2_CC_ATTR_RHANDLE	= 28,
+	TPM2_CC_ATTR_VENDOR	= 29,
+};
+
+enum tpm2_permanent_handles {
+	TPM2_RH_NULL		= 0x40000007,
+	TPM2_RS_PW		= 0x40000009,
+};
+
+/* Most Significant Octet for key types  */
+enum tpm2_mso_type {
+	TPM2_MSO_NVRAM		= 0x01,
+	TPM2_MSO_SESSION	= 0x02,
+	TPM2_MSO_POLICY		= 0x03,
+	TPM2_MSO_PERMANENT	= 0x40,
+	TPM2_MSO_VOLATILE	= 0x80,
+	TPM2_MSO_PERSISTENT	= 0x81,
+};
+
+enum tpm2_curves {
+	TPM2_ECC_NONE		= 0x0000,
+	TPM2_ECC_NIST_P256	= 0x0003,
+};
+
+enum tpm2_object_attributes {
+	TPM2_OA_FIXED_TPM		= BIT(1),
+	TPM2_OA_ST_CLEAR		= BIT(2),
+	TPM2_OA_FIXED_PARENT		= BIT(4),
+	TPM2_OA_SENSITIVE_DATA_ORIGIN	= BIT(5),
+	TPM2_OA_USER_WITH_AUTH		= BIT(6),
+	TPM2_OA_ADMIN_WITH_POLICY	= BIT(7),
+	TPM2_OA_NO_DA			= BIT(10),
+	TPM2_OA_ENCRYPTED_DUPLICATION	= BIT(11),
+	TPM2_OA_RESTRICTED		= BIT(16),
+	TPM2_OA_DECRYPT			= BIT(17),
+	TPM2_OA_SIGN			= BIT(18),
+};
+
+enum tpm2_session_attributes {
+	TPM2_SA_CONTINUE_SESSION	= BIT(0),
+	TPM2_SA_AUDIT_EXCLUSIVE		= BIT(1),
+	TPM2_SA_AUDIT_RESET		= BIT(3),
+	TPM2_SA_DECRYPT			= BIT(5),
+	TPM2_SA_ENCRYPT			= BIT(6),
+	TPM2_SA_AUDIT			= BIT(7),
+};
+
+enum tpm2_pcr_select {
+	TPM2_PLATFORM_PCR	= 24,
+	TPM2_PCR_SELECT_MIN	= ((TPM2_PLATFORM_PCR + 7) / 8),
+};
+
+enum tpm2_handle_types {
+	TPM2_HT_HMAC_SESSION	= 0x02000000,
+	TPM2_HT_POLICY_SESSION	= 0x03000000,
+	TPM2_HT_TRANSIENT	= 0x80000000,
+};
+
+enum tpm2_pt_props {
+	TPM2_PT_NONE			= 0x00000000,
+	TPM2_PT_GROUP			= 0x00000100,
+	TPM2_PT_FIXED			= TPM2_PT_GROUP * 1,
+	TPM2_PT_FAMILY_INDICATOR	= TPM2_PT_FIXED + 0,
+	TPM2_PT_LEVEL		= TPM2_PT_FIXED + 1,
+	TPM2_PT_REVISION	= TPM2_PT_FIXED + 2,
+	TPM2_PT_DAY_OF_YEAR	= TPM2_PT_FIXED + 3,
+	TPM2_PT_YEAR		= TPM2_PT_FIXED + 4,
+	TPM2_PT_MANUFACTURER	= TPM2_PT_FIXED + 5,
+	TPM2_PT_VENDOR_STRING_1	= TPM2_PT_FIXED + 6,
+	TPM2_PT_VENDOR_STRING_2	= TPM2_PT_FIXED + 7,
+	TPM2_PT_VENDOR_STRING_3	= TPM2_PT_FIXED + 8,
+	TPM2_PT_VENDOR_STRING_4	= TPM2_PT_FIXED + 9,
+	TPM2_PT_VENDOR_TPM_TYPE	= TPM2_PT_FIXED + 10,
+	TPM2_PT_FIRMWARE_VERSION_1	= TPM2_PT_FIXED + 11,
+	TPM2_PT_FIRMWARE_VERSION_2	= TPM2_PT_FIXED + 12,
+	TPM2_PT_INPUT_BUFFER		= TPM2_PT_FIXED + 13,
+	TPM2_PT_HR_TRANSIENT_MIN	= TPM2_PT_FIXED + 14,
+	TPM2_PT_HR_PERSISTENT_MIN	= TPM2_PT_FIXED + 15,
+	TPM2_PT_HR_LOADED_MIN		= TPM2_PT_FIXED + 16,
+	TPM2_PT_ACTIVE_SESSIONS_MAX	= TPM2_PT_FIXED + 17,
+	TPM2_PT_PCR_COUNT	= TPM2_PT_FIXED + 18,
+	TPM2_PT_PCR_SELECT_MIN	= TPM2_PT_FIXED + 19,
+	TPM2_PT_CONTEXT_GAP_MAX	= TPM2_PT_FIXED + 20,
+	TPM2_PT_NV_COUNTERS_MAX	= TPM2_PT_FIXED + 22,
+	TPM2_PT_NV_INDEX_MAX	= TPM2_PT_FIXED + 23,
+	TPM2_PT_MEMORY		= TPM2_PT_FIXED + 24,
+	TPM2_PT_CLOCK_UPDATE	= TPM2_PT_FIXED + 25,
+	TPM2_PT_CONTEXT_HASH	= TPM2_PT_FIXED + 26,
+	TPM2_PT_CONTEXT_SYM	= TPM2_PT_FIXED + 27,
+	TPM2_PT_CONTEXT_SYM_SIZE	= TPM2_PT_FIXED + 28,
+	TPM2_PT_ORDERLY_COUNT		= TPM2_PT_FIXED + 29,
+	TPM2_PT_MAX_COMMAND_SIZE	= TPM2_PT_FIXED + 30,
+	TPM2_PT_MAX_RESPONSE_SIZE	= TPM2_PT_FIXED + 31,
+	TPM2_PT_MAX_DIGEST		= TPM2_PT_FIXED + 32,
+	TPM2_PT_MAX_OBJECT_CONTEXT	= TPM2_PT_FIXED + 33,
+	TPM2_PT_MAX_SESSION_CONTEXT	= TPM2_PT_FIXED + 34,
+	TPM2_PT_PS_FAMILY_INDICATOR	= TPM2_PT_FIXED + 35,
+	TPM2_PT_PS_LEVEL	= TPM2_PT_FIXED + 36,
+	TPM2_PT_PS_REVISION	= TPM2_PT_FIXED + 37,
+	TPM2_PT_PS_DAY_OF_YEAR	= TPM2_PT_FIXED + 38,
+	TPM2_PT_PS_YEAR		= TPM2_PT_FIXED + 39,
+	TPM2_PT_SPLIT_MAX	= TPM2_PT_FIXED + 40,
+	TPM2_PT_TOTAL_COMMANDS	= TPM2_PT_FIXED + 41,
+	TPM2_PT_LIBRARY_COMMANDS	= TPM2_PT_FIXED + 42,
+	TPM2_PT_VENDOR_COMMANDS		= TPM2_PT_FIXED + 43,
+	TPM2_PT_NV_BUFFER_MAX		= TPM2_PT_FIXED + 44,
+	TPM2_PT_MODES			= TPM2_PT_FIXED + 45,
+	TPM2_PT_MAX_CAP_BUFFER		= TPM2_PT_FIXED + 46,
+	TPM2_PT_VAR		= TPM2_PT_GROUP * 2,
+	TPM2_PT_PERMANENT	= TPM2_PT_VAR + 0,
+	TPM2_PT_STARTUP_CLEAR	= TPM2_PT_VAR + 1,
+	TPM2_PT_HR_NV_INDEX	= TPM2_PT_VAR + 2,
+	TPM2_PT_HR_LOADED	= TPM2_PT_VAR + 3,
+	TPM2_PT_HR_LOADED_AVAIL	= TPM2_PT_VAR + 4,
+	TPM2_PT_HR_ACTIVE	= TPM2_PT_VAR + 5,
+	TPM2_PT_HR_ACTIVE_AVAIL	= TPM2_PT_VAR + 6,
+	TPM2_PT_HR_TRANSIENT_AVAIL	= TPM2_PT_VAR + 7,
+	TPM2_PT_HR_PERSISTENT		= TPM2_PT_VAR + 8,
+	TPM2_PT_HR_PERSISTENT_AVAIL	= TPM2_PT_VAR + 9,
+	TPM2_PT_NV_COUNTERS		= TPM2_PT_VAR + 10,
+	TPM2_PT_NV_COUNTERS_AVAIL	= TPM2_PT_VAR + 11,
+	TPM2_PT_ALGORITHM_SET		= TPM2_PT_VAR + 12,
+	TPM2_PT_LOADED_CURVES		= TPM2_PT_VAR + 13,
+	TPM2_PT_LOCKOUT_COUNTER		= TPM2_PT_VAR + 14,
+	TPM2_PT_MAX_AUTH_FAIL		= TPM2_PT_VAR + 15,
+	TPM2_PT_LOCKOUT_INTERVAL	= TPM2_PT_VAR + 16,
+	TPM2_PT_LOCKOUT_RECOVERY	= TPM2_PT_VAR + 17,
+	TPM2_PT_NV_WRITE_RECOVERY	= TPM2_PT_VAR + 18,
+	TPM2_PT_AUDIT_COUNTER_0	= TPM2_PT_VAR + 19,
+	TPM2_PT_AUDIT_COUNTER_1	= TPM2_PT_VAR + 20,
+};
+
+struct tpm2_pcr_read_out {
+	__be32 update_cnt;
+	__be32 pcr_selects_cnt;
+	__be16 hash_alg;
+	u8 pcr_select_size;
+	u8 pcr_select[TPM2_PCR_SELECT_MIN];
+	__be32 digests_cnt;
+	__be16 digest_size;
+	u8 digest[];
+} __packed;
+
+struct tpm2_get_random_out {
+	__be16 size;
+	u8 buffer[TPM_MAX_RNG_DATA];
+} __packed;
+
+struct tpm2_get_cap_out {
+	u8 more_data;
+	__be32 subcap_id;
+	__be32 property_cnt;
+	__be32 property_id;
+	__be32 value;
+} __packed;
+
+struct tpm2_pcr_selection {
+	__be16 hash_alg;
+	u8 size_of_select;
+	u8 pcr_select[3];
+} __packed;
+
+struct tpm2_context {
+	__be64 sequence;
+	__be32 saved_handle;
+	__be32 hierarchy;
+	__be16 blob_size;
+} __packed;
+
 #endif
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:36 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:36 -0700
Subject: [PATCH v16 04/38] tpm: Move TPM common base definitions to the command header
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-5-ross.philipson@gmail.com>

These are top level definitions shared by both TPM 1 and 2
family chips. This includes core definitions like TPM localities,
common crypto algorithm IDs, and the base TPM command header.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 include/linux/tpm.h         | 50 +--------------------
 include/linux/tpm_command.h | 89 +++++++++++++++++++++++++++++++++++++
 2 files changed, 90 insertions(+), 49 deletions(-)

diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 8551b24c2bff..3630b2ea6aef 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -27,49 +27,12 @@
 
 #include <linux/tpm_command.h>
 
-#define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
-
-#define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
-#define TPM2_MAX_PCR_BANKS	8
-
 struct tpm_chip;
 struct trusted_key_payload;
 struct trusted_key_options;
 /* opaque structure, holds auth session parameters like the session key */
 struct tpm2_auth;
 
-/* if you add a new hash to this, increment TPM_MAX_HASHES below */
-enum tpm_algorithms {
-	TPM_ALG_ERROR		= 0x0000,
-	TPM_ALG_SHA1		= 0x0004,
-	TPM_ALG_AES		= 0x0006,
-	TPM_ALG_KEYEDHASH	= 0x0008,
-	TPM_ALG_SHA256		= 0x000B,
-	TPM_ALG_SHA384		= 0x000C,
-	TPM_ALG_SHA512		= 0x000D,
-	TPM_ALG_NULL		= 0x0010,
-	TPM_ALG_SM3_256		= 0x0012,
-	TPM_ALG_ECC		= 0x0023,
-	TPM_ALG_CFB		= 0x0043,
-};
-
-/*
- * maximum number of hashing algorithms a TPM can have.  This is
- * basically a count of every hash in tpm_algorithms above
- */
-#define TPM_MAX_HASHES	5
-
-struct tpm_digest {
-	u16 alg_id;
-	u8 digest[TPM2_MAX_DIGEST_SIZE];
-} __packed;
-
-struct tpm_bank_info {
-	u16 alg_id;
-	u16 digest_size;
-	u16 crypto_id;
-};
-
 enum TPM_OPS_FLAGS {
 	TPM_OPS_AUTO_STARTUP = BIT(0),
 };
@@ -127,7 +90,7 @@ struct tpm_chip_seqops {
 	const struct seq_operations *seqops;
 };
 
-/* fixed define for the curve we use which is NIST_P256 */
+/* Fixed define for the curve we use which is NIST_P256 */
 #define EC_PT_SZ	32
 
 /*
@@ -209,8 +172,6 @@ struct tpm_chip {
 #endif
 };
 
-#define TPM_HEADER_SIZE		10
-
 static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
 {
 	return handle >> 24;
@@ -239,15 +200,6 @@ enum tpm_chip_flags {
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
 
-struct tpm_header {
-	__be16 tag;
-	__be32 length;
-	union {
-		__be32 ordinal;
-		__be32 return_code;
-	};
-} __packed;
-
 enum tpm_buf_flags {
 	/* the capacity exceeded: */
 	TPM_BUF_OVERFLOW	= BIT(0),
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index 9dd903dd6b5c..96edebd9610f 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -427,4 +427,93 @@ struct tpm2_context {
 	__be16 blob_size;
 } __packed;
 
+/*
+ * == TPM Common Defs ==
+ */
+
+#define TPM_DIGEST_SIZE		20	/* Max TPM v1.2 PCR size */
+#define TPM_BUFSIZE		4096
+
+/*
+ * SHA-512 is, as of today, the largest digest in the TCG algorithm repository.
+ */
+#define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
+
+/*
+ * A TPM name digest i.e., TPMT_HA, is a concatenation of TPM_ALG_ID of the
+ * name algorithm and hash of TPMT_PUBLIC.
+ */
+#define TPM2_MAX_NAME_SIZE	(TPM2_MAX_DIGEST_SIZE + 2)
+
+/*
+ * Fixed define for the size of a name.  This is actually HASHALG size
+ * plus 2, so 32 for SHA256
+ */
+#define TPM2_NULL_NAME_SIZE	34
+
+/*
+ * The maximum number of PCR banks.
+ */
+#define TPM2_MAX_PCR_BANKS	8
+
+/* If you add a new hash to this, increment TPM_MAX_HASHES below */
+enum tpm_algorithms {
+	TPM_ALG_ERROR		= 0x0000,
+	TPM_ALG_SHA1		= 0x0004,
+	TPM_ALG_AES		= 0x0006,
+	TPM_ALG_KEYEDHASH	= 0x0008,
+	TPM_ALG_SHA256		= 0x000B,
+	TPM_ALG_SHA384		= 0x000C,
+	TPM_ALG_SHA512		= 0x000D,
+	TPM_ALG_NULL		= 0x0010,
+	TPM_ALG_SM3_256		= 0x0012,
+	TPM_ALG_ECC		= 0x0023,
+	TPM_ALG_CFB		= 0x0043,
+};
+
+/*
+ * The locality (0 - 4) for a TPM, as defined in section 3.2 of the
+ * Client Platform Profile Specification.
+ */
+enum tpm_localities {
+	TPM_LOCALITY_0		= 0, /* Static RTM */
+	TPM_LOCALITY_1		= 1, /* Dynamic OS */
+	TPM_LOCALITY_2		= 2, /* DRTM Environment */
+	TPM_LOCALITY_3		= 3, /* Aux Components */
+	TPM_LOCALITY_4		= 4, /* CPU DRTM Establishment */
+	TPM_MAX_LOCALITY	= TPM_LOCALITY_4
+};
+
+/*
+ * Structure to represent active PCR algorithm banks usable by the
+ * TPM chip.
+ */
+struct tpm_bank_info {
+	u16 alg_id;
+	u16 digest_size;
+	u16 crypto_id;
+};
+
+/*
+ * Maximum number of hashing algorithms a TPM can have.  This is
+ * basically a count of every hash in tpm_algorithms above
+ */
+#define TPM_MAX_HASHES		5
+
+struct tpm_digest {
+	u16 alg_id;
+	u8 digest[TPM2_MAX_DIGEST_SIZE];
+} __packed;
+
+#define TPM_HEADER_SIZE		10
+
+struct tpm_header {
+	__be16 tag;
+	__be32 length;
+	union {
+		__be32 ordinal;
+		__be32 return_code;
+	};
+} __packed;
+
 #endif
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:37 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:37 -0700
Subject: [PATCH v16 05/38] tpm: Move platform specific definitions to the new PTP header
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-6-ross.philipson@gmail.com>

These are definitions for TPM 2.0 interface and interactions with
the platform as defined in the TCG specification:

These definitions are located here in a separate file to avoid conflicts
with vendor specific TIS/FIFO definition (e.g. STMicroelectronics,
Infineon Technologies, etc). This allows the TCG defined TIS/FIFO
interface to be in a public header while the former chip specific
implementations contain their own definitions. TPM 1.x family chips
that adhere to the TCG specifications use the TIS/FIFO interface as
defined here.

TCG PC Client Platform TPM Profile (PTP) Specification
 - https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm_tis_core.h |  64 +-------------
 include/linux/tpm_ptp.h         | 151 ++++++++++++++++++++++++++++++++
 2 files changed, 152 insertions(+), 63 deletions(-)
 create mode 100644 include/linux/tpm_ptp.h

diff --git a/drivers/char/tpm/tpm_tis_core.h b/drivers/char/tpm/tpm_tis_core.h
index 6c3aa480396b..be68883ed399 100644
--- a/drivers/char/tpm/tpm_tis_core.h
+++ b/drivers/char/tpm/tpm_tis_core.h
@@ -19,71 +19,9 @@
 #ifndef __TPM_TIS_CORE_H__
 #define __TPM_TIS_CORE_H__
 
+#include <linux/tpm_ptp.h>
 #include "tpm.h"
 
-enum tis_access {
-	TPM_ACCESS_VALID = 0x80,
-	TPM_ACCESS_ACTIVE_LOCALITY = 0x20,
-	TPM_ACCESS_REQUEST_PENDING = 0x04,
-	TPM_ACCESS_REQUEST_USE = 0x02,
-};
-
-enum tis_status {
-	TPM_STS_VALID = 0x80,
-	TPM_STS_COMMAND_READY = 0x40,
-	TPM_STS_GO = 0x20,
-	TPM_STS_DATA_AVAIL = 0x10,
-	TPM_STS_DATA_EXPECT = 0x08,
-	TPM_STS_RESPONSE_RETRY = 0x02,
-	TPM_STS_READ_ZERO = 0x23, /* bits that must be zero on read */
-};
-
-enum tis_int_flags {
-	TPM_GLOBAL_INT_ENABLE = 0x80000000,
-	TPM_INTF_BURST_COUNT_STATIC = 0x100,
-	TPM_INTF_CMD_READY_INT = 0x080,
-	TPM_INTF_INT_EDGE_FALLING = 0x040,
-	TPM_INTF_INT_EDGE_RISING = 0x020,
-	TPM_INTF_INT_LEVEL_LOW = 0x010,
-	TPM_INTF_INT_LEVEL_HIGH = 0x008,
-	TPM_INTF_LOCALITY_CHANGE_INT = 0x004,
-	TPM_INTF_STS_VALID_INT = 0x002,
-	TPM_INTF_DATA_AVAIL_INT = 0x001,
-};
-
-enum tis_defaults {
-	TIS_MEM_LEN = 0x5000,
-	TIS_SHORT_TIMEOUT = 750,	/* ms */
-	TIS_LONG_TIMEOUT = 4000,	/* 4 secs */
-	TIS_TIMEOUT_MIN_ATML = 14700,	/* usecs */
-	TIS_TIMEOUT_MAX_ATML = 15000,	/* usecs */
-};
-
-/* Some timeout values are needed before it is known whether the chip is
- * TPM 1.0 or TPM 2.0.
- */
-#define TIS_TIMEOUT_A_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A)
-#define TIS_TIMEOUT_B_MAX	max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B)
-#define TIS_TIMEOUT_C_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C)
-#define TIS_TIMEOUT_D_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D)
-
-#define	TPM_ACCESS(l)			(0x0000 | ((l) << 12))
-#define	TPM_INT_ENABLE(l)		(0x0008 | ((l) << 12))
-#define	TPM_INT_VECTOR(l)		(0x000C | ((l) << 12))
-#define	TPM_INT_STATUS(l)		(0x0010 | ((l) << 12))
-#define	TPM_INTF_CAPS(l)		(0x0014 | ((l) << 12))
-#define	TPM_STS(l)			(0x0018 | ((l) << 12))
-#define	TPM_STS3(l)			(0x001b | ((l) << 12))
-#define	TPM_DATA_FIFO(l)		(0x0024 | ((l) << 12))
-
-#define	TPM_DID_VID(l)			(0x0F00 | ((l) << 12))
-#define	TPM_RID(l)			(0x0F04 | ((l) << 12))
-
-#define LPC_CNTRL_OFFSET		0x84
-#define LPC_CLKRUN_EN			(1 << 2)
-#define INTEL_LEGACY_BLK_BASE_ADDR	0xFED08000
-#define ILB_REMAP_SIZE			0x100
-
 enum tpm_tis_flags {
 	TPM_TIS_ITPM_WORKAROUND		= 0,
 	TPM_TIS_INVALID_STATUS		= 1,
diff --git a/include/linux/tpm_ptp.h b/include/linux/tpm_ptp.h
new file mode 100644
index 000000000000..143e887599bb
--- /dev/null
+++ b/include/linux/tpm_ptp.h
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Following copyright information was take from the original file
+ * <drivers/char/tpm/tpm_tis_core.h> where the definitions were moved
+ * from:
+ *
+ * Copyright (C) 2005, 2006 IBM Corporation
+ * Copyright (C) 2014, 2015 Intel Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert at watson.ibm.com>
+ * Kylene Hall <kjhall at us.ibm.com>
+ *
+ * Maintained by: <tpmdd-devel at lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This device driver implements the TPM interface as defined in
+ * the TCG TPM Interface Spec version 1.2, revision 1.0.
+ */
+
+#ifndef __LINUX_TPM_PTP_H__
+#define __LINUX_TPM_PTP_H__
+
+/*
+ * TCG PC Client Platform TPM Profile (PTP) Specification
+ * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
+ */
+
+/* TIS/FIFO macros and definitions */
+
+enum tis_access {
+	TPM_ACCESS_VALID		= 0x80,
+	TPM_ACCESS_ACTIVE_LOCALITY	= 0x20,	/* (R) */
+	TPM_ACCESS_RELINQUISH_LOCALITY	= 0x20, /* (W) */
+	TPM_ACCESS_REQUEST_PENDING	= 0x04,	/* (W) */
+	TPM_ACCESS_REQUEST_USE		= 0x02,	/* (W) */
+};
+
+enum tis_status {
+	TPM_STS_VALID		= 0x80, /* (R) */
+	TPM_STS_COMMAND_READY	= 0x40, /* (R) */
+	TPM_STS_DATA_AVAIL	= 0x10, /* (R) */
+	TPM_STS_DATA_EXPECT	= 0x08, /* (R) */
+	TPM_STS_GO		= 0x20, /* (W) */
+	TPM_STS_RESPONSE_RETRY	= 0x02, /* (R) */
+	TPM_STS_READ_ZERO	= 0x23, /* bits that must be zero on read */
+};
+
+enum tis_int_flags {
+	TPM_GLOBAL_INT_ENABLE		= 0x80000000,
+	TPM_INTF_BURST_COUNT_STATIC	= 0x100,
+	TPM_INTF_CMD_READY_INT		= 0x080,
+	TPM_INTF_INT_EDGE_FALLING	= 0x040,
+	TPM_INTF_INT_EDGE_RISING	= 0x020,
+	TPM_INTF_INT_LEVEL_LOW		= 0x010,
+	TPM_INTF_INT_LEVEL_HIGH		= 0x008,
+	TPM_INTF_LOCALITY_CHANGE_INT	= 0x004,
+	TPM_INTF_STS_VALID_INT		= 0x002,
+	TPM_INTF_DATA_AVAIL_INT		= 0x001,
+};
+
+enum tis_defaults {
+	TIS_MEM_LEN		= 0x5000,
+	TIS_SHORT_TIMEOUT	= 750,   /* ms */
+	TIS_LONG_TIMEOUT	= 4000,  /* 4 secs */
+	TIS_TIMEOUT_MIN_ATML	= 14700, /* usecs */
+	TIS_TIMEOUT_MAX_ATML	= 15000, /* usecs */
+};
+
+enum tis_x86_defaults {
+	TIS_MEM_X86_LPC_BASE		= 0xFED40000,
+	TIS_MEM_X86_LEN			= 0x5000,
+	INTEL_LEGACY_BLK_BASE_ADDR	= 0xFED08000,
+	ILB_REMAP_SIZE			= 0x100,
+	LPC_CNTRL_OFFSET		= 0x84,
+	LPC_CLKRUN_EN			= (1 << 2),
+};
+
+/*
+ * Some timeout values are needed before it is known whether the chip is
+ * TPM 1.0 or TPM 2.0.
+ */
+#define TIS_TIMEOUT_A_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A)
+#define TIS_TIMEOUT_B_MAX	max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B)
+#define TIS_TIMEOUT_C_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C)
+#define TIS_TIMEOUT_D_MAX	max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D)
+
+#define	TPM_ACCESS(l)			(0x0000 | ((l) << 12))
+#define	TPM_INT_ENABLE(l)		(0x0008 | ((l) << 12))
+#define	TPM_INT_VECTOR(l)		(0x000C | ((l) << 12))
+#define	TPM_INT_STATUS(l)		(0x0010 | ((l) << 12))
+#define	TPM_INTF_CAPS(l)		(0x0014 | ((l) << 12))
+#define	TPM_STS(l)			(0x0018 | ((l) << 12))
+#define	TPM_STS3(l)			(0x001b | ((l) << 12))
+#define	TPM_DATA_FIFO(l)		(0x0024 | ((l) << 12))
+#define	TPM_INTF_ID(l)			(0x0030 | ((l) << 12))
+
+#define	TPM_DID_VID(l)			(0x0F00 | ((l) << 12))
+#define	TPM_RID(l)			(0x0F04 | ((l) << 12))
+
+/* TPM HW Interface and Capabilities */
+#define TPM_TIS_INTF_ACTIVE	0x00
+#define TPM_CRB_INTF_ACTIVE	0x01
+
+struct tpm_interface_id {
+	union {
+		u32 val;
+		struct {
+			u32 interface_type:4;
+			u32 interface_version:4;
+			u32 cap_locality:1;
+			u32 reserved1:4;
+			u32 cap_tis:1;
+			u32 cap_crb:1;
+			u32 cap_if_res:2;
+			u32 interface_selector:2;
+			u32 intf_sel_lock:1;
+			u32 reserved2:4;
+			u32 reserved3:8;
+		};
+	};
+} __packed;
+
+#define TPM_TIS_INTF_12		0x00
+#define TPM_TIS_INTF_13		0x02
+#define TPM2_TIS_INTF_13	0x03
+
+struct tpm_intf_capability {
+	union {
+		u32 val;
+		struct {
+			u32 data_avail_int_support:1;
+			u32 sts_valid_int_support:1;
+			u32 locality_change_int_support:1;
+			u32 interrupt_level_high:1;
+			u32 interrupt_level_low:1;
+			u32 interrupt_edge_rising:1;
+			u32 interrupt_edge_falling:1;
+			u32 command_ready_int_support:1;
+			u32 burst_count_static:1;
+			u32 data_transfer_size_support:2;
+			u32 reserved1:17;
+			u32 interface_version:3;
+			u32 reserved2:1;
+		};
+	};
+} __packed;
+
+#endif
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:38 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:38 -0700
Subject: [PATCH v16 06/38] tpm: Remove main TPM header from TPM event log header
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-7-ross.philipson@gmail.com>

From: Alec Brown <alec.r.brown at oracle.com>

Allow the TPM event log functionality to be used without including
the main TPM driver definitions.

Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 include/linux/tpm_eventlog.h | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/include/linux/tpm_eventlog.h b/include/linux/tpm_eventlog.h
index 891368e82558..367b70ecc4b1 100644
--- a/include/linux/tpm_eventlog.h
+++ b/include/linux/tpm_eventlog.h
@@ -3,7 +3,9 @@
 #ifndef __LINUX_TPM_EVENTLOG_H__
 #define __LINUX_TPM_EVENTLOG_H__
 
-#include <linux/tpm.h>
+#include <crypto/sha1.h>
+#include <crypto/sha2.h>
+#include <linux/tpm_command.h>
 
 #define TCG_EVENT_NAME_LEN_MAX	255
 #define MAX_TEXT_EVENT		1000	/* Max event string length */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:39 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:39 -0700
Subject: [PATCH v16 07/38] tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-8-ross.philipson@gmail.com>

From: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>

Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW into TPM_BUF_INVALID,
given that they are identical (the only difference being the associated
log messages).

In addition, add a missing TPM_BUF_INVALID check to tpm_buf_append_handle()
following the pattern from other functions in tpm-buf.c.

Message-ID: <20260125192526.782202-11-jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>
Reviewed-by: Jonathan McDowell <noodles at meta.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-buf.c                | 10 ++++------
 include/linux/tpm.h                       |  8 +++-----
 security/keys/trusted-keys/trusted_tpm2.c |  6 +++---
 3 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 4c4f450630df..61833b4d81f0 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -103,13 +103,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length);
  */
 void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length)
 {
-	/* Return silently if overflow has already happened. */
-	if (buf->flags & TPM_BUF_OVERFLOW)
+	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
 	if ((buf->length + new_length) > PAGE_SIZE) {
 		WARN(1, "tpm_buf: write overflow\n");
-		buf->flags |= TPM_BUF_OVERFLOW;
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
@@ -176,14 +175,13 @@ static void tpm_buf_read(struct tpm_buf *buf, off_t *offset, size_t count, void
 {
 	off_t next_offset;
 
-	/* Return silently if overflow has already happened. */
-	if (buf->flags & TPM_BUF_BOUNDARY_ERROR)
+	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
 	next_offset = *offset + count;
 	if (next_offset > buf->length) {
 		WARN(1, "tpm_buf: read out of boundary\n");
-		buf->flags |= TPM_BUF_BOUNDARY_ERROR;
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 3630b2ea6aef..3c6a5bcc138a 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -201,12 +201,10 @@ enum tpm_chip_flags {
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
 
 enum tpm_buf_flags {
-	/* the capacity exceeded: */
-	TPM_BUF_OVERFLOW	= BIT(0),
 	/* TPM2B format: */
-	TPM_BUF_TPM2B		= BIT(1),
-	/* read out of boundary: */
-	TPM_BUF_BOUNDARY_ERROR	= BIT(2),
+	TPM_BUF_TPM2B		= BIT(0),
+	/* The buffer is in invalid and unusable state: */
+	TPM_BUF_INVALID		= BIT(1),
 };
 
 /*
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 29d79c05ed6b..779a2e66ac20 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -312,7 +312,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 	/* creation PCR */
 	tpm_buf_append_u32(&buf, 0);
 
-	if (buf.flags & TPM_BUF_OVERFLOW) {
+	if (buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		tpm2_end_auth_session(chip);
 		goto out;
@@ -328,7 +328,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		goto out;
 
 	blob_len = tpm_buf_read_u32(&buf, &offset);
-	if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) {
+	if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		goto out;
 	}
@@ -441,7 +441,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 
 	tpm_buf_append(&buf, blob, blob_len);
 
-	if (buf.flags & TPM_BUF_OVERFLOW) {
+	if (buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		tpm2_end_auth_session(chip);
 		goto out;
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:40 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:40 -0700
Subject: [PATCH v16 08/38] tpm-buf: Remove chip parameter from tpm_buf_append_handle()
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-9-ross.philipson@gmail.com>

From: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>

Remove the TPM driver chip parameter from the function
tpm_buf_append_handle(). The chip parameter is only for error logging
which can be done with other facilities like WARN().

Message-ID: <20260125192526.782202-11-jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-buf.c       | 9 ++++++---
 drivers/char/tpm/tpm2-cmd.c      | 2 +-
 drivers/char/tpm/tpm2-sessions.c | 2 +-
 include/linux/tpm.h              | 2 +-
 4 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 61833b4d81f0..99811809a72a 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -146,17 +146,20 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_u32);
 
 /**
  * tpm_buf_append_handle() - Add a handle
- * @chip:	&tpm_chip instance
  * @buf:	&tpm_buf instance
  * @handle:	a TPM object handle
  *
  * Add a handle to the buffer, and increase the count tracking the number of
  * handles in the command buffer. Works only for command buffers.
  */
-void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle)
+void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle)
 {
+	if (buf->flags & TPM_BUF_INVALID)
+		return;
+
 	if (buf->flags & TPM_BUF_TPM2B) {
-		dev_err(&chip->dev, "Invalid buffer type (TPM2B)\n");
+		WARN(1, "tpm-buf: invalid type: TPM2B\n");
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 1fa3e8a43c79..c9dc35b57687 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -198,7 +198,7 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 		}
 		tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0);
 	} else {
-		tpm_buf_append_handle(chip, &buf, pcr_idx);
+		tpm_buf_append_handle(&buf, pcr_idx);
 		tpm_buf_append_auth(chip, &buf, NULL, 0);
 	}
 
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 3b1cf1ca0420..eb67ae14ad73 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -257,7 +257,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
 #endif
 
 	if (!tpm2_chip_auth(chip)) {
-		tpm_buf_append_handle(chip, buf, handle);
+		tpm_buf_append_handle(buf, handle);
 		return 0;
 	}
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 3c6a5bcc138a..b357f8971d03 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -235,7 +235,7 @@ void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value);
 u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset);
 u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset);
 u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset);
-void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle);
+void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle);
 
 /*
  * Check if TPM device is in the firmware upgrade mode.
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:42 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:42 -0700
Subject: [PATCH v16 10/38] tpm-buf: Add TPM buffer support header for standalone reuse
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-11-ross.philipson@gmail.com>

Extract all the functions and definitions for TPM buffer handling
and separate them into their own header.

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-buf.c |  4 ++-
 include/linux/tpm.h        | 34 +----------------------
 include/linux/tpm_buf.h    | 57 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 61 insertions(+), 34 deletions(-)
 create mode 100644 include/linux/tpm_buf.h

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index e79a8071c9ee..e81ce7215b1a 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -4,7 +4,9 @@
  */
 
 #include <linux/module.h>
-#include <linux/tpm.h>
+#include <crypto/sha2.h>
+#include <linux/tpm_command.h>
+#include <linux/tpm_buf.h>
 
 static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size)
 {
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 0a56f6c1ea98..0db277af45c3 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -26,6 +26,7 @@
 #include <crypto/aes.h>
 
 #include <linux/tpm_command.h>
+#include <linux/tpm_buf.h>
 
 struct tpm_chip;
 struct trusted_key_payload;
@@ -200,44 +201,11 @@ enum tpm_chip_flags {
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
 
-enum tpm_buf_flags {
-	/* TPM2B format: */
-	TPM_BUF_TPM2B		= BIT(0),
-	/* The buffer is in invalid and unusable state: */
-	TPM_BUF_INVALID		= BIT(1),
-};
-
-/*
- * A buffer for constructing and parsing TPM commands, responses and sized
- * (TPM2B) buffers.
- */
-struct tpm_buf {
-	u8 flags;
-	u8 handles;
-	u16 length;
-	u16 capacity;
-	u8 data[];
-};
-
 struct tpm2_hash {
 	unsigned int crypto_id;
 	unsigned int tpm_id;
 };
 
-void tpm_buf_init(struct tpm_buf *buf, u16 buf_size);
-void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size);
-void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal);
-void tpm_buf_reset_sized(struct tpm_buf *buf);
-u16 tpm_buf_length(struct tpm_buf *buf);
-void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length);
-void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value);
-void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value);
-void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value);
-u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset);
-u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset);
-u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset);
-void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle);
-
 /*
  * Check if TPM device is in the firmware upgrade mode.
  */
diff --git a/include/linux/tpm_buf.h b/include/linux/tpm_buf.h
new file mode 100644
index 000000000000..7f8abb59de24
--- /dev/null
+++ b/include/linux/tpm_buf.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Following copyright information was take from the original file
+ * <include/linux/tpm.h> where the definitions were moved from:
+ *
+ * Copyright (C) 2004,2007,2008 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert at watson.ibm.com>
+ * Dave Safford <safford at watson.ibm.com>
+ * Reiner Sailer <sailer at watson.ibm.com>
+ * Kylene Hall <kjhall at us.ibm.com>
+ * Debora Velarde <dvelarde at us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel at lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ */
+
+#ifndef __LINUX_TPM_BUF_H__
+#define __LINUX_TPM_BUF_H__
+
+enum tpm_buf_flags {
+	/* TPM2B format: */
+	TPM_BUF_TPM2B		= BIT(0),
+	/* The buffer is in invalid and unusable state: */
+	TPM_BUF_INVALID		= BIT(1),
+};
+
+/*
+ * A buffer for constructing and parsing TPM commands, responses and sized
+ * (TPM2B) buffers.
+ */
+struct tpm_buf {
+	u8 flags;
+	u8 handles;
+	u16 length;
+	u16 capacity;
+	u8 data[];
+};
+
+void tpm_buf_init(struct tpm_buf *buf, u16 buf_size);
+void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size);
+void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal);
+void tpm_buf_reset_sized(struct tpm_buf *buf);
+u16 tpm_buf_length(struct tpm_buf *buf);
+void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length);
+void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value);
+void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value);
+void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value);
+u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset);
+u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset);
+u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset);
+void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle);
+
+#endif
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:43 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:43 -0700
Subject: [PATCH v16 11/38] tpm/tpm_tis: Close all localities
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-12-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Close all the localities while initializing the TPM driver. The addition
of TCG DRTM support requires this.

Co-developed-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm_tis_core.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/char/tpm/tpm_tis_core.c b/drivers/char/tpm/tpm_tis_core.c
index e2a1769081b1..1fbb74a565f4 100644
--- a/drivers/char/tpm/tpm_tis_core.c
+++ b/drivers/char/tpm/tpm_tis_core.c
@@ -1111,7 +1111,7 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
 	u32 intmask;
 	u32 clkrun_val;
 	u8 rid;
-	int rc, probe;
+	int rc, probe, i;
 	struct tpm_chip *chip;
 
 	chip = tpmm_chip_alloc(dev, &tpm_tis);
@@ -1176,6 +1176,15 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
 		goto out_err;
 	}
 
+	/*
+	 * In order to comply with the TCG DRTM specification, relinquish all
+	 * the localities.
+	 */
+	for (i = 0; i <= TPM_MAX_LOCALITY; i++) {
+		if (check_locality(chip, i))
+			tpm_tis_relinquish_locality(chip, i);
+	}
+
 	/* Take control of the TPM's interrupt hardware and shut it off */
 	rc = tpm_tis_read32(priv, TPM_INT_ENABLE(priv->locality), &intmask);
 	if (rc < 0)
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:44 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:44 -0700
Subject: [PATCH v16 12/38] tpm/tpm_tis: Address positive localities in tpm_tis_request_locality()
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-13-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Validate that the input locality is within the correct range, as specified
by TCG standards, and increase the locality count also for the positive
localities.

Co-developed-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm_tis_core.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/char/tpm/tpm_tis_core.c b/drivers/char/tpm/tpm_tis_core.c
index 1fbb74a565f4..70aba05f4ee1 100644
--- a/drivers/char/tpm/tpm_tis_core.c
+++ b/drivers/char/tpm/tpm_tis_core.c
@@ -179,7 +179,8 @@ static int tpm_tis_relinquish_locality(struct tpm_chip *chip, int l)
 	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
 
 	mutex_lock(&priv->locality_count_mutex);
-	priv->locality_count--;
+	if (priv->locality_count > 0)
+		priv->locality_count--;
 	if (priv->locality_count == 0)
 		__tpm_tis_relinquish_locality(priv, l);
 	mutex_unlock(&priv->locality_count_mutex);
@@ -233,10 +234,16 @@ static int tpm_tis_request_locality(struct tpm_chip *chip, int l)
 	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
 	int ret = 0;
 
+	if (l < 0 || l > TPM_MAX_LOCALITY) {
+		dev_warn(&chip->dev, "%s: failed to request unknown locality: %d\n",
+			 __func__, l);
+		return -EINVAL;
+	}
+
 	mutex_lock(&priv->locality_count_mutex);
 	if (priv->locality_count == 0)
 		ret = __tpm_tis_request_locality(chip, l);
-	if (!ret)
+	if (ret >= 0)
 		priv->locality_count++;
 	mutex_unlock(&priv->locality_count_mutex);
 	return ret;
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:45 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:45 -0700
Subject: [PATCH v16 13/38] tpm/tpm_tis: Allow locality to be set to a different value
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-14-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Provide a one-time function tpm_chip_set_locality() for setting the
correct locality needed by DRTM launches.

Co-developed-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-chip.c     | 35 ++++++++++++++++++++++++++++++++-
 drivers/char/tpm/tpm_tis_core.c |  2 ++
 include/linux/tpm.h             |  4 ++++
 3 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 0719577e584d..a9620b27b63d 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -44,7 +44,7 @@ static int tpm_request_locality(struct tpm_chip *chip)
 	if (!chip->ops->request_locality)
 		return 0;
 
-	rc = chip->ops->request_locality(chip, 0);
+	rc = chip->ops->request_locality(chip, chip->kernel_locality);
 	if (rc < 0)
 		return rc;
 
@@ -343,6 +343,7 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
 	}
 
 	chip->locality = -1;
+	chip->kernel_locality = 0;
 	return chip;
 
 out:
@@ -658,3 +659,35 @@ void tpm_chip_unregister(struct tpm_chip *chip)
 	tpm_del_char_device(chip);
 }
 EXPORT_SYMBOL_GPL(tpm_chip_unregister);
+
+/**
+ * tpm_chip_set_locality() - Set the TPM locality the kernel uses
+ * @chip:	&tpm_chip instance
+ * @locality:   new locality
+ *
+ * This a one time function to set the locality the kernel driver
+ * uses. Returns zero or POSIX error on failure.
+ */
+int tpm_chip_set_locality(struct tpm_chip *chip, u8 locality)
+{
+	int ret;
+
+	if (locality >= TPM_MAX_LOCALITY)
+		return -EINVAL;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		return ret;
+
+	if (!(chip->flags & TPM_CHIP_FLAG_SET_LOCALITY_ENABLED)) {
+		tpm_put_ops(chip);
+		return -EPERM;
+	}
+
+	chip->kernel_locality = locality;
+	chip->flags &= ~TPM_CHIP_FLAG_SET_LOCALITY_ENABLED;
+	tpm_put_ops(chip);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(tpm_chip_set_locality);
diff --git a/drivers/char/tpm/tpm_tis_core.c b/drivers/char/tpm/tpm_tis_core.c
index 70aba05f4ee1..cf6d929ce5aa 100644
--- a/drivers/char/tpm/tpm_tis_core.c
+++ b/drivers/char/tpm/tpm_tis_core.c
@@ -1125,6 +1125,8 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
 	if (IS_ERR(chip))
 		return PTR_ERR(chip);
 
+	chip->flags |= TPM_CHIP_FLAG_SET_LOCALITY_ENABLED;
+
 #ifdef CONFIG_ACPI
 	chip->acpi_dev_handle = acpi_dev_handle;
 #endif
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 0db277af45c3..4a26a49040b9 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -159,6 +159,8 @@ struct tpm_chip {
 
 	/* active locality */
 	int locality;
+	/* the locality used by kernel */
+	u8 kernel_locality;
 
 #ifdef CONFIG_TCG_TPM2_HMAC
 	/* details for communication security via sessions */
@@ -197,6 +199,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HWRNG_DISABLED		= BIT(9),
 	TPM_CHIP_FLAG_DISABLE			= BIT(10),
 	TPM_CHIP_FLAG_SYNC			= BIT(11),
+	TPM_CHIP_FLAG_SET_LOCALITY_ENABLED	= BIT(12),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -244,6 +247,7 @@ static inline ssize_t tpm_ret_to_err(ssize_t ret)
 extern int tpm_is_tpm2(struct tpm_chip *chip);
 extern __must_check int tpm_try_get_ops(struct tpm_chip *chip);
 extern void tpm_put_ops(struct tpm_chip *chip);
+int tpm_chip_set_locality(struct tpm_chip *chip, u8 locality);
 extern ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_buf *buf,
 				size_t min_rsp_body_length, const char *desc);
 extern int tpm_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:46 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:46 -0700
Subject: [PATCH v16 14/38] tpm/sysfs: Show locality used by kernel
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-15-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Expose the current locality used by the kernel TPM driver via
the sysfs interface.

Co-developed-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko at kernel.org>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/char/tpm/tpm-sysfs.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
index f5dcadb1ab3c..772c4ae67957 100644
--- a/drivers/char/tpm/tpm-sysfs.c
+++ b/drivers/char/tpm/tpm-sysfs.c
@@ -308,6 +308,14 @@ static ssize_t tpm_version_major_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(tpm_version_major);
 
+static ssize_t locality_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct tpm_chip *chip = to_tpm_chip(dev);
+
+	return sprintf(buf, "%u\n", chip->kernel_locality);
+}
+static DEVICE_ATTR_RO(locality);
+
 #ifdef CONFIG_TCG_TPM2_HMAC
 static ssize_t null_name_show(struct device *dev, struct device_attribute *attr,
 			      char *buf)
@@ -335,6 +343,7 @@ static struct attribute *tpm1_dev_attrs[] = {
 	&dev_attr_durations.attr,
 	&dev_attr_timeouts.attr,
 	&dev_attr_tpm_version_major.attr,
+	&dev_attr_locality.attr,
 	NULL,
 };
 
@@ -343,6 +352,7 @@ static struct attribute *tpm2_dev_attrs[] = {
 #ifdef CONFIG_TCG_TPM2_HMAC
 	&dev_attr_null_name.attr,
 #endif
+	&dev_attr_locality.attr,
 	NULL
 };
 
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:47 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:47 -0700
Subject: [PATCH v16 15/38] Documentation/security: Secure Launch kernel documentation
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-16-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Introduce documentation for the Linux Secure Launch feature.

Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 Documentation/arch/x86/boot.rst               |   8 +
 Documentation/security/index.rst              |   1 +
 .../security/launch-integrity/index.rst       |   9 +
 .../launch-integrity/secure_launch.rst        | 681 ++++++++++++++++++
 4 files changed, 699 insertions(+)
 create mode 100644 Documentation/security/launch-integrity/index.rst
 create mode 100644 Documentation/security/launch-integrity/secure_launch.rst

diff --git a/Documentation/arch/x86/boot.rst b/Documentation/arch/x86/boot.rst
index dca3875a2435..eb53da9dcbbf 100644
--- a/Documentation/arch/x86/boot.rst
+++ b/Documentation/arch/x86/boot.rst
@@ -482,6 +482,14 @@ Protocol:	2.00+
 	    - If 1, KASLR enabled.
 	    - If 0, KASLR disabled.
 
+  Bit 2 (kernel internal): SLAUNCH_FLAG
+
+	- Used internally by the setup kernel to communicate
+	  Secure Launch status to the kernel proper.
+
+	    - If 1, Secure Launch enabled.
+	    - If 0, Secure Launch disabled.
+
   Bit 5 (write): QUIET_FLAG
 
 	- If 0, print early messages.
diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
index 3e0a7114a862..f89741271ed0 100644
--- a/Documentation/security/index.rst
+++ b/Documentation/security/index.rst
@@ -20,3 +20,4 @@ Security Documentation
    landlock
    secrets/index
    ipe
+   launch-integrity/index
diff --git a/Documentation/security/launch-integrity/index.rst b/Documentation/security/launch-integrity/index.rst
new file mode 100644
index 000000000000..fb9f63f01056
--- /dev/null
+++ b/Documentation/security/launch-integrity/index.rst
@@ -0,0 +1,9 @@
+=====================================
+System Launch Integrity documentation
+=====================================
+
+.. toctree::
+   :maxdepth: 3
+
+   secure_launch
+
diff --git a/Documentation/security/launch-integrity/secure_launch.rst b/Documentation/security/launch-integrity/secure_launch.rst
new file mode 100644
index 000000000000..b4c61fdceaf0
--- /dev/null
+++ b/Documentation/security/launch-integrity/secure_launch.rst
@@ -0,0 +1,681 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright (c) 2019-2026 Daniel P. Smith <dpsmith at apertussolutions.com>
+
+======================
+Secure Launch Overview
+======================
+
+:Author: Daniel P. Smith
+:Date: February 2026
+
+Overview
+========
+
+The TrenchBoot project initiated development of the Secure Launch kernel
+feature to establish a generalized framework for dynamic root-of-trust
+measurement (DRTM). The effort addresses two core objectives: (1) exposing the
+platform-specific launch protocols (e.g., Intel TXT, AMD SKINIT, Arm DRTM, and
+potentially OpenPOWER) directly to the Linux kernel, and (2) enabling the
+kernel to act as the dynamic launch initiator. This design provides the
+necessary foundation for the Linux kernel to support a broad range of dynamic
+launch use cases without platform-specific user-space intermediaries.
+
+.. note::
+    A quick note on terminology. The larger open source project itself is
+    called TrenchBoot, which is hosted on GitHub (links below). The kernel
+    feature enabling the use of the x86 technology is referred to as "Secure
+    Launch" within the kernel code.
+
+Goals
+=====
+
+The primary use case initially targeted by the TrenchBoot project is the
+ability for the Linux kernel to be launched via a dynamic launch during the
+early boot sequence. In this scenario, the dynamic launch is initiated by a
+bootloader that has been extended with Secure Launch support. The first
+bootloader targeted was GRUB2.
+
+An essential part of establishing measurement-based launch integrity is
+ensuring that all components intended for execution (kernel image, initrd,
+etc.) and all configuration data that will affect kernel execution (command
+line, boot parameters, etc.) are measured prior to execution. These
+measurements are then stored securely using the Trusted Platform Module (TPM).
+
+Both Intel TXT and AMD dynamic launch implementations use the TPM for this
+purpose. The architecture is designed so that a dynamic launch unlocks a
+specific set of Platform Configuration Registers (PCRs) reserved for DRTM
+(Dynamic Root of Trust for Measurement) measurements. These registers are known
+as the **DRTM PCRs** (PCRs 17-22).
+
+Further details on the hardware mechanisms are documented in Intel's Trusted
+Execution Technology specification for the ``GETSEC`` instruction and AMD's
+documentation for the ``SKINIT`` instruction. See the `Resources` section for
+references.
+
+.. note::
+    Currently, only Intel TXT is supported in this first release of the Secure
+    Launch feature. AMD/Hygon SKINIT and Arm support will be added in a
+    subsequent release.
+
+To enable the Linux kernel to be launched via the Intel ``GETSEC`` instruction,
+a Secure Launch entry point is built into the mainline kernel. This entry point
+is responsible for handling the specific processor state that the dynamic
+launch process leaves the Bootstrap Processor (BSP) in.
+
+The Secure Launch entry point performs the earliest possible measurements of
+all components and data that the kernel will consume (kernel image, initrd,
+command line, boot parameters, etc.). Both this entry point code and subsequent
+kernel initialization code must also correctly handle the specific state that
+the dynamic launch leaves the Application Processors (APs) in.
+
+Design Decisions
+================
+
+Several design decisions were made during the development of the Secure Launch
+feature. The primary guiding principle was to minimize the modifications to the
+existing boot path of the kernel as little as possible.
+
+The following illustrate how the implementation followed these principle:
+
+ - All the entry point code necessary to properly configure the system post
+   launch is found in sl_stub.S in the kernel image. This code
+   validates the state of the system, restores necessary system operating
+   configurations and properly handles post launch CPU states.
+ - Support is introduced in the SMP boot code to properly wake the APs. This
+   is required due to the unique state the dynamic launch leaves the APs in
+   (i.e. they cannot be woken with the standard INIT-SIPI sequence).
+ - Final setup for the Secure Launch kernel is done in a separate Secure Launch
+   module that is loaded via a late initcall. This code is responsible for
+   setting up the securityfs interface to allow access to the TPM event log and
+   public TXT registers.
+ - On the reboot and kexec paths, calls are made to a function to finalize the
+   state of the Secure Launch kernel.
+
+Basic Boot Flow
+===============
+
+Outlined here is a summary of the boot flow for Secure Launch. A more detailed
+review of the Secure Launch process can be found in the Secure Launch
+Specification (a link is in the `Resources`_ section).
+
+Pre-launch: *Phase where the environment is prepared and configured to initiate
+the secure launch by the boot chain.*
+
+ - The SLRT is initialized, and dl_stub is placed in memory.
+ - Load the kernel, initrd and ACM [4]_ into memory.
+ - Set up the TXT heap and page tables describing the MLE [1]_ per the
+   specification.
+ - If non-UEFI platform, SLRT is registered in boot params and dl_stub is
+   called from the legacy setup kernel.
+ - If UEFI platform, SLRT registered with UEFI and kernel efi-stub support calls
+   dl_stub after executing EBS.
+ - The dl_stub will prepare the CPU and the TPM for the launch.
+ - The secure launch is then initiated with the GETSET[SENTER] instruction.
+
+Post-launch: *Phase where control is passed from the ACM to the MLE and the secure
+kernel begins execution.*
+
+ - Entry from the dynamic launch jumps to the SL stub.
+ - SL stub fixes up the world on the BSP.
+ - For TXT,
+    - SL stub wakes the APs, fixes up their worlds.
+    - APs are left in an optimized (MONITOR/MWAIT) wait state.
+ - SL main does validation of buffers and memory locations. It sets
+   the boot parameter loadflag value SLAUNCH_FLAG to inform the main
+   kernel that a Secure Launch was done.
+ - SL main locates the TPM event log and writes the measurements of
+   configuration and module information into it.
+ - The SMP bring up code is modified to wake the waiting APs via the monitor
+   address.
+ - SL platform module is registered as a late initcall module.
+ - SL platform module initializes the securityfs interface to allow
+   access to the TPM event log and TXT public registers.
+ - Kernel boot finishes booting normally.
+ - SEXIT support to leave SMX mode is present on the kexec path and
+   the various reboot paths (poweroff, reset, halt).
+
+PCR Usage
+=========
+
+The TCG DRTM architecture defines three PCRs for dynamic root of trust
+measurement:
+
+- PCR.Details (PCR 17)
+- PCR.Authorities (PCR 18)
+- PCR.DLME_Authority (PCR 19)
+
+Further details on the semantics of the Details and Authorities PCRs are
+available in the TCG DRTM Architecture specification.
+
+The Linux kernel's Secure Launch implementation adheres to the TrenchBoot
+Secure Launch Specification. It utilizes a measurement policy stored in the
+`Secure Launch Resource Table` (``SLRT``) to determine both what to measure and
+into which PCR each measurement should be extended.
+
+This policy makes it possible for the kernel to store its own DRTM measurements
+(such as an external initrd image) into **PCR.DLME_Detail** (PCR 20). When
+combined with storing user authority information into **PCR.DLME_Authority**
+(PCR 19), it enables sealing and attestation across different combinations of
+platform and user details/authorities.
+
+An example of this approach was presented in the FOSDEM 2021 talk titled
+"Secure Upgrades with DRTM".
+
+Configuration
+=============
+
+The settings to enable Secure Launch using Kconfig are under::
+
+  "Processor type and features" --> "Secure Launch support"
+
+A kernel with this option enabled can still be booted using other supported
+methods.
+
+To reduce the Trusted Computing Base (TCB) of the MLE [1]_, the build
+configuration should be pared down as narrowly as one's use case allows.
+Fewer drivers (less active hardware) and features reduce the attack surface.
+As an example in the extreme, the MLE could only have local disk access with no
+other hardware supports except optional network access for remote attestation.
+
+It is also desirable, if possible, to embed the initrd used with the MLE kernel
+image to reduce complexity.
+
+The following are important configuration necessities to always consider:
+
+IOMMU Configuration
+-------------------
+
+When doing a Secure Launch, the IOMMU should always be enabled and the drivers
+loaded. However, IOMMU passthrough mode should never be used. This leaves the
+MLE completely exposed to DMA after the PMRs [2]_ are disabled. The current
+default mode is to use IOMMU in lazy translated mode, but strict translated
+mode, is the preferred IOMMU mode and this should be selected in the build
+configuration::
+
+  "Device Drivers" -->
+      "IOMMU Hardware Support" -->
+          "IOMMU default domain type" -->
+              "(X) Translated - Strict"
+
+In addition, the Intel IOMMU should be on by default. The following sets this as the
+default in the build configuration::
+
+  "Device Drivers" -->
+      "IOMMU Hardware Support" -->
+          "Support for Intel IOMMU using DMA Remapping Devices [*]"
+
+and::
+
+  "Device Drivers" -->
+      "IOMMU Hardware Support" -->
+          "Support for Intel IOMMU using DMA Remapping Devices [*]" -->
+              "Enable Intel DMA Remapping Devices by default  [*]"
+
+It is recommended that no other command line options should be set to override
+the defaults above. If there is a desire to run an alternate configuration,
+then that configuration should be evaluated for what benefits might
+be gained against the risks for DMA attacks to which the kernel is likely
+going to be exposed.
+
+Secure Launch Resource Table
+============================
+
+The Secure Launch Resource Table (SLRT) is a platform-agnostic, standard format
+for providing information for the pre-launch environment and to pass
+information to the post-launch environment. The table is populated by one or
+more bootloaders in the boot chain and used by Secure Launch on how to set up
+the environment during post-launch. The details for the SLRT are documented
+in the TrenchBoot Secure Launch Specification [3]_.
+
+Intel TXT Interface
+===================
+
+The primary interfaces between the various components in TXT are the TXT MMIO
+registers and the TXT heap. The MMIO register banks are described in Appendix B
+of the TXT MLE [1]_ Development Guide.
+
+The TXT heap is described in Appendix C of the TXT MLE [1]_ Development
+Guide. Most of the TXT heap is predefined in the specification. The heap is
+initialized by firmware and the pre-launch environment and is subsequently used
+by the SINIT ACM. One section, called the OS to MLE Data Table, is reserved for
+software to define. This table is set up per the recommendation detailed in
+Appendix B of the TrenchBoot Secure Launch Specification::
+
+        /*
+         * Secure Launch defined OS/MLE TXT Heap table
+         */
+        struct txt_os_mle_data {
+                u32 version;
+                u32 reserved;
+                u64 slrt;
+                u64 txt_info;
+                u32 ap_wake_block;
+                u32 ap_wake_block_size;
+                u8 mle_scratch[64];
+        } __packed;
+
+Description of structure:
+
+=====================  ========================================================================
+Field                  Use
+=====================  ========================================================================
+version                Structure version, current value 1
+slrt                   Physical address of the Secure Launch Resource Table
+txt_info               Pointer into the SLRT for easily locating TXT specific table
+ap_wake_block          Physical address of the block of memory for parking APs after a launch
+ap_wake_block_size     Size of the AP wake block
+mle_scratch            Scratch area used post-launch by the MLE kernel. Fields:
+ 
+                        - SL_SCRATCH_AP_EBX area to share %ebx base pointer among CPUs
+                        - SL_SCRATCH_AP_JMP_OFFSET offset to abs. ljmp fixup location for APs
+                        - SL_SCRATCH_AP_STACKS_OFFSET offset to AP startup stacks in wake block
+=====================  ========================================================================
+
+Error Codes
+===========
+
+The TXT specification defines the layout for TXT 32 bit error code values.
+The bit encodings indicate where the error originated (e.g. with the CPU,
+in the SINIT ACM, in software). The error is written to a sticky TXT
+register that persists across resets called TXT.ERRORCODE (see the TXT
+MLE Development Guide). The errors defined by the Secure Launch feature are
+those generated in the MLE software. They have the format::
+
+  0xc0008XXX
+
+The low 12 bits are free for defining the following Secure Launch specific
+error codes.
+
+0xc0008001: SL_ERROR_GENERIC
+----------------------------
+
+Description:
+
+Generic catch all error. Currently unused.
+
+0xc0008002: SL_ERROR_TPM_INIT
+-----------------------------
+
+Description:
+
+The Secure Launch code failed to get access to the TPM hardware interface.
+This is most likely due to misconfigured hardware or kernel. Ensure the TPM
+chip is enabled, and the kernel TPM support is built in (it should not be built
+as a module).
+
+0xc0008003: SL_ERROR_TPM_INVALID_LOG20
+--------------------------------------
+
+Description:
+
+Either the Secure Launch code failed to find a valid event log descriptor for a
+version 2.0 TPM, or the event log descriptor is malformed. Usually this
+indicates incompatible versions of the pre-launch environment and the
+MLE kernel. The pre-launch environment and the kernel share a structure in the
+TXT heap and if this structure (the OS-MLE table) is mismatched, this error is
+common. This TXT heap area is set up by the pre-launch environment, so the
+issue may originate there. It could also be the sign of an attempted attack.
+
+0xc0008004: SL_ERROR_TPM_LOGGING_FAILED
+---------------------------------------
+
+Description:
+
+There was a failed attempt to write a TPM event to the event log early in the
+Secure Launch process. This is likely the result of a malformed TPM event log
+buffer. Formatting of the event log buffer information is done by the
+pre-launch environment, so the issue most likely originates there.
+
+0xc0008005: SL_ERROR_REGION_STRADDLE_4GB
+----------------------------------------
+
+Description:
+
+During early validation, a buffer or region was found to straddle the 4Gb
+boundary. Because of the way TXT provides DMA memory protection, this is an unsafe
+configuration and is flagged as an error. This is most likely a configuration
+issue in the pre-launch environment. It could also be the sign of an attempted
+attack.
+
+0xc0008006: SL_ERROR_TPM_EXTEND
+-------------------------------
+
+Description:
+
+There was a failed attempt to extend a TPM PCR in the Secure Launch platform
+module. This is most likely to due to misconfigured hardware or kernel. Ensure
+the TPM chip is enabled, and the kernel TPM support is built in (it should not
+be built as a module).
+
+0xc0008007: SL_ERROR_MTRR_INV_VCNT
+----------------------------------
+
+Description:
+
+During early Secure Launch validation, an invalid variable MTRR count was
+found. The pre-launch environment passes several MSR values to the MLE to
+restore including the MTRRs. The values are restored by the Secure Launch early
+entry point code. After measuring the values supplied by the pre-launch
+environment, a discrepancy was found, validating the values. It could be the
+sign of an attempted attack.
+
+0xc0008008: SL_ERROR_MTRR_INV_DEF_TYPE
+--------------------------------------
+
+Description:
+
+During early Secure Launch validation, an invalid default MTRR type was found.
+See SL_ERROR_MTRR_INV_VCNT for more details.
+
+0xc0008009: SL_ERROR_MTRR_INV_BASE
+----------------------------------
+
+Description:
+
+During early Secure Launch validation, an invalid variable MTRR base value was
+found. See SL_ERROR_MTRR_INV_VCNT for more details.
+
+0xc000800a: SL_ERROR_MTRR_INV_MASK
+----------------------------------
+
+Description:
+
+During early Secure Launch validation, an invalid variable MTRR mask value was
+found. See SL_ERROR_MTRR_INV_VCNT for more details.
+
+0xc000800b: SL_ERROR_MSR_INV_MISC_EN
+------------------------------------
+
+Description:
+
+During early Secure Launch validation, an invalid miscellaneous enable MSR
+value was found. See SL_ERROR_MTRR_INV_VCNT for more details.
+
+0xc000800c: SL_ERROR_INV_AP_INTERRUPT
+-------------------------------------
+
+Description:
+
+The application processors (APs) wait to be woken up by the SMP initialization
+code. The only interrupt that they expect is an NMI; all other interrupts
+should be masked. If an AP gets an interrupt other than an NMI, it will
+cause this error. This error is very unlikely to occur.
+
+0xc000800d: SL_ERROR_INTEGER_OVERFLOW
+-------------------------------------
+
+Description:
+
+A buffer base and size passed to the MLE caused an integer overflow when
+added together. This is most likely a configuration issue in the pre-launch
+environment. It could also be the sign of an attempted attack.
+
+0xc000800e: SL_ERROR_HEAP_WALK
+------------------------------
+
+Description:
+
+An error occurred in TXT heap walking code. The underlying issue is a failure to
+early_memremap() portions of the heap, most likely due to a resource shortage.
+
+0xc000800f: SL_ERROR_HEAP_MAP
+-----------------------------
+
+Description:
+
+This error is essentially the same as SL_ERROR_HEAP_WALK, but occurred during the
+actual early_memremap() operation.
+
+0xc0008010: SL_ERROR_REGION_ABOVE_4GB
+-------------------------------------
+
+Description:
+
+A memory region used by the MLE is above 4Gb. In general, this is not a problem
+because memory > 4Gb can be protected from DMA. There are certain buffers that
+should never be above 4Gb, and one of these caused the violation. This is most
+likely a configuration issue in the pre-launch environment. It could also be
+the sign of an attempted attack.
+
+0xc0008011: SL_ERROR_HEAP_INVALID_DMAR
+--------------------------------------
+
+Description:
+
+The backup copy of the ACPI DMAR table, which is expected to be in the
+TXT heap, could not be found. This is due to a bug in the platform's ACM module
+or in firmware.
+
+0xc0008012: SL_ERROR_HEAP_DMAR_SIZE
+-----------------------------------
+
+Description:
+
+The backup copy of the ACPI DMAR table in the TXT heap is too large to be stored
+for later usage. This error is very unlikely to occur since the area reserved
+for the copy is far larger than the DMAR should be.
+
+0xc0008013: SL_ERROR_HEAP_DMAR_MAP
+----------------------------------
+
+Description:
+
+The backup copy of the ACPI DMAR table in the TXT heap could not be mapped. The
+underlying issue is a failure to early_memremap() the DMAR table, most likely
+due to a resource shortage.
+
+0xc0008014: SL_ERROR_HI_PMR_BASE
+--------------------------------
+
+Description:
+
+On a system with more than 4Gb of RAM, the high PMR [2]_ base address should be
+set to 4Gb. This error is due to that not being the case. This PMR value is set
+by the pre-launch environment, so the issue most likely originates there. It
+could also be the sign of an attempted attack.
+
+0xc0008015: SL_ERROR_HI_PMR_SIZE
+--------------------------------
+
+Description:
+
+On a system with more than 4Gb of RAM, the high PMR [2]_ size should be set to
+cover all RAM > 4Gb. This error is due to that not being the case. This PMR
+value is set by the pre-launch environment, so the issue most likely originates
+there. It could also be the sign of an attempted attack.
+
+0xc0008016: SL_ERROR_LO_PMR_BASE
+--------------------------------
+
+Description:
+
+The low PMR [2]_ base should always be set to address zero. This error is due
+to that not being the case. This PMR value is set by the pre-launch environment
+so the issue most likely originates there. It could also be the sign of an
+attempted attack.
+
+0xc0008017: SL_ERROR_LO_PMR_MLE
+-------------------------------
+
+Description:
+
+This error indicates the MLE image is not covered by the low PMR [2]_ range.
+The PMR values are set by the pre-launch environment, so the issue most likely
+originates there. It could also be the sign of an attempted attack.
+
+0xc0008018: SL_ERROR_INITRD_TOO_BIG
+-----------------------------------
+
+Description:
+
+The external initrd provided is larger than 4Gb. This is not a valid
+configuration for Secure Launch due to managing DMA protection.
+
+0xc0008019: SL_ERROR_HEAP_ZERO_OFFSET
+-------------------------------------
+
+Description:
+
+During a TXT heap walk, an invalid/zero next table offset value was found. This
+indicates the TXT heap is malformed. The TXT heap is initialized by the
+pre-launch environment, so the issue most likely originates there. It could
+also be a sign of an attempted attack. In addition, ACM is also responsible for
+manipulating parts of the TXT heap, so the issue could be due to a bug in the
+platform's ACM module.
+
+0xc000801a: SL_ERROR_WAKE_BLOCK_TOO_SMALL
+-----------------------------------------
+
+Description:
+
+The AP wake block buffer passed to the MLE via the OS-MLE TXT heap table is not
+large enough. This value is set by the pre-launch environment, so the issue
+most likely originates there. It also could be the sign of an attempted attack.
+
+0xc000801b: SL_ERROR_MLE_BUFFER_OVERLAP
+---------------------------------------
+
+Description:
+
+One of the buffers passed to the MLE via the OS-MLE TXT heap table overlaps
+with the MLE image in memory. This value is set by the pre-launch environment
+so the issue most likely originates there. It could also be the sign of an
+attempted attack.
+
+0xc000801c: SL_ERROR_BUFFER_BEYOND_PMR
+--------------------------------------
+
+Description:
+
+One of the buffers passed to the MLE via the OS-MLE TXT heap table is not
+protected by a PMR. This value is set by the pre-launch environment, so the
+issue most likely originates there. It could also be the sign of an attempted
+attack.
+
+0xc000801d: SL_ERROR_OS_SINIT_BAD_VERSION
+-----------------------------------------
+
+Description:
+
+The version of the OS-SINIT TXT heap table is bad. It must be 6 or greater.
+This value is set by the pre-launch environment, so the issue most likely
+originates there. It could also be the sign of an attempted attack. It is also
+possible though very unlikely that the platform is so old that the ACM being
+used requires an unsupported version.
+
+0xc000801e: SL_ERROR_EVENTLOG_MAP
+---------------------------------
+
+Description:
+
+An error occurred in the Secure Launch module while mapping the TPM event log.
+The underlying issue is memremap() failure, most likely due to a resource
+shortage.
+
+0xc000801f: SL_ERROR_TPM_INVALID_ALGS
+-------------------------------------
+
+Description:
+
+The TPM 2.0 event log reports either no hashing algorithms, invalid algorithm ID
+or an algorithm size larger than the max size recognized by the TPM support code.
+
+0xc0008020: SL_ERROR_TPM_EVENT_COUNT
+------------------------------------
+
+Description:
+
+The TPM 2.0 event log contains an event with a digest count that is not equal
+to the algorithm count of the overall log. This is an invalid configuration
+that could indicate either a bug or a possible attack.
+
+0xc0008021: SL_ERROR_TPM_INVALID_EVENT
+--------------------------------------
+
+Description:
+
+An invalid/malformed event was found in the TPM event log while reading it.
+Since only trusted entities are supposed to be writing the event log, this
+would indicate either a bug or a possible attack.
+
+0xc0008022: SL_ERROR_INVALID_SLRT
+---------------------------------
+
+Description:
+
+The Secure Launch Resource Table is invalid or malformed and is unusable. This
+implies the pre-launch code did not properly set up the SLRT.
+
+0xc0008023: SL_ERROR_SLRT_MISSING_ENTRY
+---------------------------------------
+
+Description:
+
+The Secure Launch Resource Table is missing a required entry within it. This
+implies the pre-launch code did not properly set up the SLRT.
+
+0xc0008024: SL_ERROR_SLRT_MAP
+-----------------------------
+
+Description:
+
+An error occurred in the Secure Launch module while mapping the Secure Launch
+Resource table. The underlying issue is memremap() failure, most likely due to
+a resource shortage.
+
+
+Resources
+=========
+
+The TrenchBoot project:
+
+https://trenchboot.org
+
+Secure Launch Specification:
+
+https://trenchboot.org/specifications/Secure_Launch/
+
+Trusted Computing Group's D-RTM Architecture:
+
+https://trustedcomputinggroup.org/wp-content/uploads/TCG_D-RTM_Architecture_v1-0_Published_06172013.pdf
+
+TXT documentation in the Intel TXT MLE Development Guide:
+
+https://www.intel.com/content/dam/www/public/us/en/documents/guides/intel-txt-software-development-guide.pdf
+
+TXT instructions documentation in the Intel SDM Instruction Set volume:
+
+https://software.intel.com/en-us/articles/intel-sdm
+
+AMD SKINIT documentation in the System Programming manual:
+
+https://www.amd.com/system/files/TechDocs/24593.pdf
+
+GRUB Secure Launch support:
+
+https://github.com/TrenchBoot/grub/tree/grub-sl-fc-38-dlstub
+
+FOSDEM 2021: Secure Upgrades with DRTM
+
+https://archive.fosdem.org/2021/schedule/event/firmware_suwd/
+
+.. [1]
+    MLE: Measured Launch Environment is the binary runtime that is measured and
+    then run by the TXT SINIT ACM. The TXT MLE Development Guide describes the
+    requirements for the MLE in detail.
+
+.. [2]
+    PMR: Intel VTd has a feature in the IOMMU called Protected Memory Registers.
+    There are two of these registers and they allow all DMA to be blocked
+    to large areas of memory. The low PMR can cover all memory below 4Gb on 2Mb
+    boundaries. The high PMR can cover all RAM on the system, again on 2Mb
+    boundaries. This feature is used during a Secure Launch by TXT.
+
+.. [3]
+    Secure Launch Specification: https://trenchboot.org/specifications/Secure_Launch/
+
+.. [4]
+    ACM: Intel's Authenticated Code Module. This is the 32b bit binary blob that
+    is run securely by the GETSEC[SENTER] during a measured launch. It is described
+    in the Intel documentation on TXT and versions for various chipsets are
+    signed and distributed by Intel.
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:48 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:48 -0700
Subject: [PATCH v16 16/38] x86: Secure Launch Kconfig
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-17-ross.philipson@gmail.com>

Add an x86 Kconfig option for compiling in/out the Secure Launch feature.
Secure Launch is controlled by a single on/off boolean.

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index e2df1b147184..fd9edb0651d9 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1989,6 +1989,21 @@ config EFI_RUNTIME_MAP
 
 	  See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map.
 
+config SECURE_LAUNCH
+	bool "Secure Launch DRTM support"
+	depends on X86_64 && X86_X2APIC && TCG_TIS && TCG_CRB
+	select CRYPTO_LIB_SHA1
+	select CRYPTO_LIB_SHA256
+	select CRYPTO_LIB_SHA512
+	help
+	  The Secure Launch feature allows a kernel to be launched directly
+	  through a vendor neutral DRTM (Dynamic Root of Trust for Measurement)
+	  solution, with Intel TXT being one example. The DRTM establishes an
+	  environment where the CPU measures the kernel image, employing the TPM,
+	  before starting it. Secure Launch then continues the measurement chain
+	  over kernel configuration information and other launch artifacts (e.g.
+	  any initramfs image).
+
 source "kernel/Kconfig.hz"
 
 config ARCH_SUPPORTS_KEXEC
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:49 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:49 -0700
Subject: [PATCH v16 17/38] x86: Secure Launch Resource Table header file
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-18-ross.philipson@gmail.com>

The Secure Launch Specification is an architecture agnostic,
software neutral API/ABI maintained by the TrenchBoot project.
Its function is to allow any compliant boot loader to communicate
the pre-launch configuration to any compliant post-launch kernel.
The Secure Launch Resource Table, defined in the specification,
presents the programmatic interface for this API/ABI.

The specification can be found here:
https://github.com/TrenchBoot/documentation/blob/master/specifications/secure-launch-specification.rst

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 include/linux/slr_table.h | 339 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 339 insertions(+)
 create mode 100644 include/linux/slr_table.h

diff --git a/include/linux/slr_table.h b/include/linux/slr_table.h
new file mode 100644
index 000000000000..2cc542121414
--- /dev/null
+++ b/include/linux/slr_table.h
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TrenchBoot Secure Launch Resource Table
+ *
+ * The Secure Launch Resource Table (SLRT) is a TrenchBoot project defined
+ * specification to provide a cross-platform interface/ABI between
+ * the Secure Launch components. While most of the table is platform
+ * agnostic, platform or architecture specific entries can be added.
+ *
+ * See TrenchBoot Secure Launch kernel documentation for details.
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#ifndef _LINUX_SLR_TABLE_H
+#define _LINUX_SLR_TABLE_H
+
+/* SLR table header values */
+#define SLR_TABLE_MAGIC		0x4452544d
+#define SLR_TABLE_REVISION	1
+
+/* Current revisions for the policy and UEFI config */
+#define SLR_POLICY_REVISION		1
+#define SLR_UEFI_CONFIG_REVISION	1
+
+/* SLR defined architectures */
+#define SLR_INTEL_TXT		1
+#define SLR_AMD_SKINIT		2
+
+/* SLR defined bootloaders */
+#define SLR_BOOTLOADER_INVALID	0
+#define SLR_BOOTLOADER_GRUB	1
+
+/* Log formats */
+#define SLR_DRTM_TPM12_LOG	1
+#define SLR_DRTM_TPM20_LOG	2
+
+/* DRTM Policy Entry Flags */
+#define SLR_POLICY_FLAG_MEASURED	0x1
+#define SLR_POLICY_IMPLICIT_SIZE	0x2
+
+/* Array Lengths */
+#define TPM_EVENT_INFO_LENGTH		32
+#define TXT_VARIABLE_MTRRS_LENGTH	32
+
+/* Tags */
+#define SLR_ENTRY_INVALID	0x0000
+#define SLR_ENTRY_DL_INFO	0x0001
+#define SLR_ENTRY_LOG_INFO	0x0002
+#define SLR_ENTRY_ENTRY_POLICY	0x0003
+#define SLR_ENTRY_INTEL_INFO	0x0004
+#define SLR_ENTRY_AMD_INFO	0x0005
+#define SLR_ENTRY_ARM_INFO	0x0006
+#define SLR_ENTRY_UEFI_INFO	0x0007
+#define SLR_ENTRY_UEFI_CONFIG	0x0008
+#define SLR_ENTRY_END		0xffff
+
+/* Entity Types */
+#define SLR_ET_UNSPECIFIED	0x0000
+#define SLR_ET_SLRT		0x0001
+#define SLR_ET_BOOT_PARAMS	0x0002
+#define SLR_ET_SETUP_DATA	0x0003
+#define SLR_ET_CMDLINE		0x0004
+#define SLR_ET_UEFI_MEMMAP	0x0005
+#define SLR_ET_RAMDISK		0x0006
+#define SLR_ET_TXT_OS2MLE	0x0010
+#define SLR_ET_UNUSED		0xffff
+
+#ifndef __ASSEMBLER__
+
+/*
+ * Standard Secure Launch DLME header.
+ *
+ * All capable DRTM kernels must have an instance of this header.
+ * This header was derived from the Intel TXT Measured Launch
+ * Environment header and uses the predefined UUID:
+ *
+ * {5aac8290-6f47-a774-0f5c-55a2cb51b642}
+ */
+struct slr_dlme_header {
+	u32 uuid0; /* 9082AC5A */
+	u32 uuid1; /* 74A7476F */
+	u32 uuid2; /* A2555C0F */
+	u32 uuid3; /* 42B651CB */
+	u32 header_len;
+	u32 version;
+	u32 entry_point;
+	u32 first_valid_page;
+	u32 start;
+	u32 end;
+	u32 capabilities;
+	u32 cmdline_start;
+	u32 cmdline_end;
+} __packed;
+
+/*
+ * Primary Secure Launch Resource Table header
+ */
+struct slr_table {
+	u32 magic;
+	u16 revision;
+	u16 architecture;
+	u32 size;
+	u32 max_size;
+	/* table entries */
+} __packed;
+
+/*
+ * Common SLRT entry header
+ */
+struct slr_entry_hdr {
+	u32 tag;
+	u32 size;
+} __packed;
+
+/*
+ * Boot loader context and DLME setup
+ */
+struct slr_bl_context {
+	u16 bootloader;
+	u16 reserved[3];
+	u64 context;
+} __packed;
+
+struct slr_setup_dlme {
+	u64 dlme_base;
+	u64 dlme_header_offset;
+	u64 dlme_table;
+} __packed;
+
+/*
+ * Dynamic Launch Callback Function
+ */
+typedef void (*dl_launch_func)(struct slr_bl_context *bl_context,
+			       struct slr_setup_dlme *setup_dlme);
+
+/*
+ * DRTM Dynamic Launch Configuration
+ */
+struct slr_entry_dl_info {
+	struct slr_entry_hdr hdr;
+	u64 dce_size;
+	u64 dce_base;
+	u64 dlme_size;
+	u64 dlme_base;
+	u64 dlme_entry;
+	u64 dlme_header_offset;
+	u64 dlme_config_table;
+	struct slr_bl_context bl_context;
+	u64 dl_launch;
+} __packed;
+
+/*
+ * TPM Log Information
+ */
+struct slr_entry_log_info {
+	struct slr_entry_hdr hdr;
+	u16 format;
+	u16 reserved;
+	u32 size;
+	u64 addr;
+} __packed;
+
+/*
+ * DRTM Measurement Entry
+ */
+struct slr_policy_entry {
+	u16 pcr;
+	u16 entity_type;
+	u16 flags;
+	u16 reserved;
+	u64 size;
+	u64 entity;
+	char evt_info[TPM_EVENT_INFO_LENGTH];
+} __packed;
+
+/*
+ * DRTM Measurement Policy
+ */
+struct slr_entry_policy {
+	struct slr_entry_hdr hdr;
+	u16 reserved[2];
+	u16 revision;
+	u16 nr_entries;
+	struct slr_policy_entry policy_entries[];
+} __packed;
+
+/*
+ * Secure Launch defined MTRR saving structures
+ */
+struct slr_txt_mtrr_pair {
+	u64 mtrr_physbase;
+	u64 mtrr_physmask;
+} __packed;
+
+struct slr_txt_mtrr_state {
+	u64 default_mem_type;
+	u64 mtrr_vcnt;
+	struct slr_txt_mtrr_pair mtrr_pair[TXT_VARIABLE_MTRRS_LENGTH];
+} __packed;
+
+/*
+ * Intel TXT Info table
+ */
+struct slr_entry_intel_info {
+	struct slr_entry_hdr hdr;
+	u64 boot_params_addr;
+	u64 txt_heap;
+	u64 saved_misc_enable_msr;
+	struct slr_txt_mtrr_state saved_bsp_mtrrs;
+} __packed;
+
+/*
+ * UEFI config measurement entry
+ */
+struct slr_uefi_cfg_entry {
+	u16 pcr;
+	u16 reserved;
+	u32 size;
+	u64 cfg; /* address or value */
+	char evt_info[TPM_EVENT_INFO_LENGTH];
+} __packed;
+
+/*
+ * UEFI config measurements
+ */
+struct slr_entry_uefi_config {
+	struct slr_entry_hdr hdr;
+	u16 reserved[2];
+	u16 revision;
+	u16 nr_entries;
+	struct slr_uefi_cfg_entry uefi_cfg_entries[];
+} __packed;
+
+/*
+ * The SLRT is laid out as a Tag-Length-Value (TLV) data structure
+ * allowing a flexible number of entries in the table. An instance
+ * of the slr_table structure is present as a header at the beginning.
+ *
+ * The following functions help to manipulate the SLRT structure
+ * and contents.
+ */
+
+/*
+ * Return the address of the end of the SLRT past the final entry.
+ */
+static inline void *slr_end_of_entries(struct slr_table *table)
+{
+	return (void *)table + table->size;
+}
+
+/*
+ * Return the next entry in the SLRT given the current entry passed
+ * to the function. NULL is returned if there are no entries to return.
+ */
+static inline void *
+slr_next_entry(struct slr_table *table,
+	       struct slr_entry_hdr *curr)
+{
+	struct slr_entry_hdr *next = (struct slr_entry_hdr *)((u8 *)curr + curr->size);
+
+	if ((void *)next >= slr_end_of_entries(table))
+		return NULL;
+	if (next->tag == SLR_ENTRY_END)
+		return NULL;
+
+	return next;
+}
+
+/*
+ * Return the next entry with the given tag in the SLRT starting at the
+ * current entry. If entry is NULL, the search begins at the beginning of
+ * table.
+ */
+static inline void *
+slr_next_entry_by_tag(struct slr_table *table,
+		      struct slr_entry_hdr *entry,
+		      u16 tag)
+{
+	if (!entry) /* Start from the beginning */
+		entry = (struct slr_entry_hdr *)(((u8 *)table) + sizeof(*table));
+
+	for ( ; ; ) {
+		if (entry->tag == tag)
+			return entry;
+
+		entry = slr_next_entry(table, entry);
+		if (!entry)
+			return NULL;
+	}
+
+	return NULL;
+}
+
+/*
+ * Add an entry to the SLRT. Entries are placed at the end.
+ */
+static inline int
+slr_add_entry(struct slr_table *table,
+	      struct slr_entry_hdr *entry)
+{
+	struct slr_entry_hdr *end;
+
+	if ((table->size + entry->size) > table->max_size)
+		return -1;
+
+	memcpy((u8 *)table + table->size - sizeof(*end), entry, entry->size);
+	table->size += entry->size;
+
+	end = (struct slr_entry_hdr *)((u8 *)table + table->size - sizeof(*end));
+	end->tag = SLR_ENTRY_END;
+	end->size = sizeof(*end);
+
+	return 0;
+}
+
+/*
+ * Initialize the SLRT for use. This prepares the meta-data in the SLRT
+ * header section of the table and table end entry.
+ */
+static inline void
+slr_init_table(struct slr_table *slrt, u16 architecture, u32 max_size)
+{
+	struct slr_entry_hdr *end;
+
+	slrt->magic = SLR_TABLE_MAGIC;
+	slrt->revision = SLR_TABLE_REVISION;
+	slrt->architecture = architecture;
+	slrt->size = sizeof(*slrt) + sizeof(*end);
+	slrt->max_size = max_size;
+	end = (struct slr_entry_hdr *)((u8 *)slrt + sizeof(*slrt));
+	end->tag = SLR_ENTRY_END;
+	end->size = sizeof(*end);
+}
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* _LINUX_SLR_TABLE_H */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:50 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:50 -0700
Subject: [PATCH v16 18/38] x86/efi: Secure Launch Resource Table EFI definitions header file
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-19-ross.philipson@gmail.com>

The Secure Launch EFI definitions are contained in the Secure Launch
Specification. The definitions are split out into a separate header
file for compilation purposes for EFI vs non-EFI environments in the
kernel.

The specification can be found here:
https://github.com/TrenchBoot/documentation/blob/master/specifications/secure-launch-specification.rst

Co-developed-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 include/linux/slr_efi.h | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 include/linux/slr_efi.h

diff --git a/include/linux/slr_efi.h b/include/linux/slr_efi.h
new file mode 100644
index 000000000000..5de87a9b38aa
--- /dev/null
+++ b/include/linux/slr_efi.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * EFI Definitions for Secure Launch Resource Table
+ *
+ * See TrenchBoot Secure Launch kernel documentation for details.
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#ifndef _LINUX_SLR_EFI_H
+#define _LINUX_SLR_EFI_H
+
+#include <linux/slr_table.h>
+
+#ifndef __ASSEMBLER__
+
+/* EFI Support */
+
+/* SLR table GUID for registering as an EFI Configuration Table (put this in efi.h if it becomes a standard) */
+#define SLR_TABLE_GUID			EFI_GUID(0x877a9b2a, 0x0385, 0x45d1, 0xa0, 0x34, 0x9d, 0xac, 0x9c, 0x9e, 0x56, 0x5f)
+
+/* Secure Launch EFI runtime protocol */
+#define EFI_SLAUNCH_PROTOCOL_GUID	EFI_GUID(0x534189e0, 0x6fde, 0x413d,  0xbe, 0x91, 0xcd, 0x4e, 0x8d, 0x67, 0x2f, 0xea)
+
+struct efi_slaunch_protocol {
+	efi_status_t
+	(__efiapi *setup_dlme)(struct efi_slaunch_protocol *this,
+			       u64 dlme_base,
+			       u64 dlme_header_offset,
+			       u64 dlme_table);
+
+	efi_status_t
+	(__efiapi *launch)(struct efi_slaunch_protocol *this);
+};
+typedef struct efi_slaunch_protocol efi_slaunch_protocol_t;
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* _LINUX_SLR_EFI_H */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:51 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:51 -0700
Subject: [PATCH v16 19/38] x86: Secure Launch main header file
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-20-ross.philipson@gmail.com>

Introduce the main Secure Launch header file. This header file
contains the platform neutral feature definitions:
 - Secure Launch error codes
 - DRTM TPM event logging definitions and helper functions
 - Other feature specific structures and definitions

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 include/linux/slaunch.h | 262 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 262 insertions(+)
 create mode 100644 include/linux/slaunch.h

diff --git a/include/linux/slaunch.h b/include/linux/slaunch.h
new file mode 100644
index 000000000000..7874a49c27b5
--- /dev/null
+++ b/include/linux/slaunch.h
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Main Secure Launch header file.
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#ifndef _LINUX_SLAUNCH_H
+#define _LINUX_SLAUNCH_H
+
+#include <asm/txt.h>
+
+/*
+ * Secure Launch Defined State Flags
+ */
+#define SL_FLAG_ACTIVE		0x00000001
+#define SL_FLAG_ARCH_TXT	0x00000002
+
+/*
+ * Secure Launch CPU Type
+ */
+#define SL_CPU_INTEL	1
+
+#define __SL32_CS	0x0008
+#define __SL32_DS	0x0010
+#define __SL64_CS	0x0018
+
+/*
+ * Secure Launch Defined Error Codes used in MLE-initiated TXT resets.
+ *
+ * Intel Trusted Execution Technology (TXT) Software Development Guide
+ * Appendix I - ACM Error Codes
+ */
+#define SL_ERROR_GENERIC		0xc0008001
+#define SL_ERROR_TPM_INIT		0xc0008002
+#define SL_ERROR_TPM_INVALID_LOG20	0xc0008003
+#define SL_ERROR_TPM_LOGGING_FAILED	0xc0008004
+#define SL_ERROR_REGION_STRADDLE_4GB	0xc0008005
+#define SL_ERROR_TPM_EXTEND		0xc0008006
+#define SL_ERROR_MTRR_INV_VCNT		0xc0008007
+#define SL_ERROR_MTRR_INV_DEF_TYPE	0xc0008008
+#define SL_ERROR_MTRR_INV_BASE		0xc0008009
+#define SL_ERROR_MTRR_INV_MASK		0xc000800a
+#define SL_ERROR_MSR_INV_MISC_EN	0xc000800b
+#define SL_ERROR_INV_AP_INTERRUPT	0xc000800c
+#define SL_ERROR_INTEGER_OVERFLOW	0xc000800d
+#define SL_ERROR_HEAP_WALK		0xc000800e
+#define SL_ERROR_HEAP_MAP		0xc000800f
+#define SL_ERROR_REGION_ABOVE_4GB	0xc0008010
+#define SL_ERROR_HEAP_INVALID_DMAR	0xc0008011
+#define SL_ERROR_HEAP_DMAR_SIZE		0xc0008012
+#define SL_ERROR_HEAP_DMAR_MAP		0xc0008013
+#define SL_ERROR_HI_PMR_BASE		0xc0008014
+#define SL_ERROR_HI_PMR_SIZE		0xc0008015
+#define SL_ERROR_LO_PMR_BASE		0xc0008016
+#define SL_ERROR_LO_PMR_MLE		0xc0008017
+#define SL_ERROR_INITRD_TOO_BIG		0xc0008018
+#define SL_ERROR_HEAP_ZERO_OFFSET	0xc0008019
+#define SL_ERROR_WAKE_BLOCK_TOO_SMALL	0xc000801a
+#define SL_ERROR_MLE_BUFFER_OVERLAP	0xc000801b
+#define SL_ERROR_BUFFER_BEYOND_PMR	0xc000801c
+#define SL_ERROR_OS_SINIT_BAD_VERSION	0xc000801d
+#define SL_ERROR_EVENTLOG_MAP		0xc000801e
+#define SL_ERROR_TPM_INVALID_ALGS	0xc000801f
+#define SL_ERROR_TPM_EVENT_COUNT	0xc0008020
+#define SL_ERROR_TPM_INVALID_EVENT	0xc0008021
+#define SL_ERROR_INVALID_SLRT		0xc0008022
+#define SL_ERROR_SLRT_MISSING_ENTRY	0xc0008023
+#define SL_ERROR_SLRT_MAP		0xc0008024
+
+/*
+ * Secure Launch Defined Limits
+ */
+#define SL_MAX_CPUS		512
+#define SL_BOOT_STACK_SIZE	128
+
+/*
+ * Secure Launch event log entry type. The TXT specification defines the
+ * base event value as 0x400 for DRTM values.
+ *
+ * Intel Trusted Execution Technology (TXT) Software Development Guide
+ * Appendix F - TPM Event Log
+ */
+#define SL_EVTYPE_BASE			0x400
+#define SL_EVTYPE_SECURE_LAUNCH		(SL_EVTYPE_BASE + 0x102)
+
+/*
+ * MLE scratch area offsets within TXT OS-MLE SL defined portion of the heap.
+ */
+#define SL_SCRATCH_AP_EBX		0
+#define SL_SCRATCH_AP_JMP_OFFSET	4
+#define SL_SCRATCH_AP_STACKS_OFFSET	8
+
+#ifndef __ASSEMBLER__
+
+#include <linux/io.h>
+#include <linux/tpm_eventlog.h>
+
+/*
+ * Secure Launch AP stack and monitor block
+ */
+struct sl_ap_stack_and_monitor {
+	u32 monitor;
+	u32 cache_pad[15];
+	u32 stack_pad[15];
+	u32 apicid;
+} __packed;
+
+/*
+ * Secure Launch AP wakeup information fetched in SMP boot code.
+ */
+struct sl_ap_wake_info {
+	u32 ap_wake_block;
+	u32 ap_wake_block_size;
+	u32 ap_jmp_offset;
+	u32 ap_stacks_offset;
+};
+
+/*
+ * TXT Heap Map structure
+ */
+struct sl_txt_heap_info {
+	u64 size;
+	u64 offset;
+};
+
+/*
+ * Secure Launch defined OS/MLE TXT Heap table
+ *
+ * This table is defined at the top level by the TXT specification
+ * but the format of this structure is implementation specific.
+ *
+ * Intel Trusted Execution Technology (TXT) Software Development Guide
+ * Appendix C - Intel TXT Heap Memory
+ */
+struct txt_os_mle_data {
+	u32 version;
+	u32 reserved;
+	u64 slrt;
+	u64 txt_info;
+	u32 ap_wake_block;
+	u32 ap_wake_block_size;
+	u8 mle_scratch[64];
+} __packed;
+
+#ifdef CONFIG_SECURE_LAUNCH
+
+/*
+ * TPM event logging functions.
+ */
+
+/*
+ * Log a TPM v1 formatted event to the given DRTM event log.
+ */
+static inline int tpm_log_event(void *evtlog_base, u32 evtlog_size,
+				u32 event_size, void *event)
+{
+	struct tpm_event_log_header *evtlog =
+		(struct tpm_event_log_header *)evtlog_base;
+
+	if (memcmp(evtlog->signature, TPM_EVTLOG_SIGNATURE,
+		   sizeof(TPM_EVTLOG_SIGNATURE)))
+		return -EINVAL;
+
+	if (evtlog->container_size > evtlog_size)
+		return -EINVAL;
+
+	if (evtlog->next_event_offset + event_size > evtlog->container_size)
+		return -E2BIG;
+
+	memcpy(evtlog_base + evtlog->next_event_offset, event, event_size);
+	evtlog->next_event_offset += event_size;
+
+	return 0;
+}
+
+/*
+ * Log a TPM v2 formatted event to the given DRTM event log.
+ */
+static inline int tpm2_log_event(struct txt_heap_event_log_pointer2_1_element *elem,
+				 void *evtlog_base, u32 evtlog_size,
+				 u32 event_size, void *event)
+{
+	struct tcg_pcr_event *header =
+		(struct tcg_pcr_event *)evtlog_base;
+
+	/* Has to be at least big enough for the signature */
+	if (header->event_size < sizeof(TCG_SPECID_SIG))
+		return -EINVAL;
+
+	if (memcmp((u8 *)header + sizeof(struct tcg_pcr_event),
+		   TCG_SPECID_SIG, sizeof(TCG_SPECID_SIG)))
+		return -EINVAL;
+
+	if (elem->allocated_event_container_size > evtlog_size)
+		return -EINVAL;
+
+	if (elem->next_record_offset + event_size >
+	    elem->allocated_event_container_size)
+		return -E2BIG;
+
+	memcpy(evtlog_base + elem->next_record_offset, event, event_size);
+	elem->next_record_offset += event_size;
+
+	return 0;
+}
+
+/*
+ * External functions available in mainline kernel.
+ */
+void slaunch_setup(void);
+void slaunch_fixup_ap_wake_vector(void);
+u32 slaunch_get_flags(void);
+struct sl_txt_heap_info *slaunch_txt_get_heap_map(void);
+void *slaunch_txt_get_heap_table(void *heap, u8 index);
+struct sl_ap_wake_info *slaunch_get_ap_wake_info(void);
+struct acpi_table_header *slaunch_get_dmar_table(struct acpi_table_header *dmar);
+void __noreturn slaunch_reset(void *ctx, const char *msg, u64 error);
+void slaunch_finalize(int do_sexit);
+
+static inline bool slaunch_is_txt_launch(void)
+{
+	u32 mask = SL_FLAG_ACTIVE | SL_FLAG_ARCH_TXT;
+
+	return (slaunch_get_flags() & mask) == mask;
+}
+
+#else
+
+static inline void slaunch_setup(void)
+{
+}
+
+static inline void slaunch_fixup_ap_wake_vector(void)
+{
+}
+
+static inline u32 slaunch_get_flags(void)
+{
+	return 0;
+}
+
+static inline struct acpi_table_header *slaunch_get_dmar_table(struct acpi_table_header *dmar)
+{
+	return dmar;
+}
+
+static inline void slaunch_finalize(int do_sexit)
+{
+}
+
+static inline bool slaunch_is_txt_launch(void)
+{
+	return false;
+}
+
+#endif /* !CONFIG_SECURE_LAUNCH */
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* _LINUX_SLAUNCH_H */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:52 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:52 -0700
Subject: [PATCH v16 20/38] x86/txt: Intel Trusted eXecution Technology (TXT) definitions
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-21-ross.philipson@gmail.com>

Intel TXT architectural specific definitions. See the specification
for detailed information:

https://www.intel.com/content/dam/www/public/us/en/documents/guides/intel-txt-software-development-guide.pdf

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/include/asm/txt.h | 281 +++++++++++++++++++++++++++++++++++++
 1 file changed, 281 insertions(+)
 create mode 100644 arch/x86/include/asm/txt.h

diff --git a/arch/x86/include/asm/txt.h b/arch/x86/include/asm/txt.h
new file mode 100644
index 000000000000..eaec915d4878
--- /dev/null
+++ b/arch/x86/include/asm/txt.h
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel Trusted eXecution Technology (TXT) Definitions
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#ifndef _ASM_X86_TXT_H
+#define _ASM_X86_TXT_H
+
+/*
+ * Intel Safer Mode Extensions (SMX)
+ *
+ * Intel SMX provides a programming interface to establish a Measured Launched
+ * Environment (MLE). The measurement and protection mechanisms are supported by the
+ * capabilities of an Intel Trusted Execution Technology (TXT) platform. SMX is
+ * the processor's programming interface in an Intel TXT platform.
+ *
+ * See:
+ *   Intel SDM Volume 2 - 6.1 "Safer Mode Extensions Reference"
+ *   Intel Trusted Execution Technology - Measured Launch Environment Developer's Guide
+ */
+
+/*
+ * SMX GETSEC Leaf Functions
+ */
+#define SMX_X86_GETSEC_SEXIT	5
+#define SMX_X86_GETSEC_SMCTRL	7
+#define SMX_X86_GETSEC_WAKEUP	8
+
+/*
+ * Intel Trusted Execution Technology MMIO Registers Banks
+ */
+#define TXT_PUB_CONFIG_REGS_BASE	0xfed30000
+#define TXT_PRIV_CONFIG_REGS_BASE	0xfed20000
+#define TXT_NR_CONFIG_PAGES     ((TXT_PUB_CONFIG_REGS_BASE - \
+				  TXT_PRIV_CONFIG_REGS_BASE) >> PAGE_SHIFT)
+
+/*
+ * Intel Trusted Execution Technology (TXT) Registers
+ */
+#define TXT_CR_STS			0x0000
+#define TXT_CR_ESTS			0x0008
+#define TXT_CR_ERRORCODE		0x0030
+#define TXT_CR_CMD_RESET		0x0038
+#define TXT_CR_CMD_CLOSE_PRIVATE	0x0048
+#define TXT_CR_DIDVID			0x0110
+#define TXT_CR_VER_EMIF			0x0200
+#define TXT_CR_CMD_UNLOCK_MEM_CONFIG	0x0218
+#define TXT_CR_SINIT_BASE		0x0270
+#define TXT_CR_SINIT_SIZE		0x0278
+#define TXT_CR_MLE_JOIN			0x0290
+#define TXT_CR_HEAP_BASE		0x0300
+#define TXT_CR_HEAP_SIZE		0x0308
+#define TXT_CR_SCRATCHPAD		0x0378
+#define TXT_CR_CMD_OPEN_LOCALITY1	0x0380
+#define TXT_CR_CMD_CLOSE_LOCALITY1	0x0388
+#define TXT_CR_CMD_OPEN_LOCALITY2	0x0390
+#define TXT_CR_CMD_CLOSE_LOCALITY2	0x0398
+#define TXT_CR_CMD_SECRETS		0x08e0
+#define TXT_CR_CMD_NO_SECRETS		0x08e8
+#define TXT_CR_E2STS			0x08f0
+
+/* TXT default register value */
+#define TXT_REGVALUE_ONE		0x1ULL
+
+/* TXTCR_STS status bits */
+#define TXT_SENTER_DONE_STS		BIT(0)
+#define TXT_SEXIT_DONE_STS		BIT(1)
+
+/*
+ * SINIT/MLE Capabilities Field Bit Definitions
+ */
+#define TXT_SINIT_MLE_CAP_RLP_WAKE_GETSEC	0
+#define TXT_SINIT_MLE_CAP_RLP_WAKE_MONITOR	1
+
+/*
+ * OS/MLE Secure Launch Specific Definitions
+ */
+#define TXT_OS_MLE_STRUCT_VERSION	1
+#define TXT_OS_MLE_MAX_VARIABLE_MTRRS	32
+
+#ifndef __ASSEMBLER__
+
+/*
+ * TXT Heap extended data elements.
+ */
+struct txt_heap_ext_data_element {
+	u32 type;
+	u32 size;
+	/* Data */
+} __packed;
+
+#define TXT_HEAP_EXTDATA_TYPE_END			0
+
+struct txt_heap_end_element {
+	u32 type;
+	u32 size;
+} __packed;
+
+#define TXT_HEAP_EXTDATA_TYPE_TPM_EVENT_LOG_PTR		5
+
+struct txt_heap_event_log_element {
+	u64 event_log_phys_addr;
+} __packed;
+
+#define TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1	8
+
+struct txt_heap_event_log_pointer2_1_element {
+	u64 phys_addr;
+	u32 allocated_event_container_size;
+	u32 first_record_offset;
+	u32 next_record_offset;
+} __packed;
+
+/*
+ * TXT specification defined BIOS data TXT Heap table
+ */
+struct txt_bios_data {
+	u32 version; /* Currently 5 for TPM 1.2 and 6 for TPM 2.0 */
+	u32 bios_sinit_size;
+	u64 reserved1;
+	u64 reserved2;
+	u32 num_logical_procs;
+	u32 sinit_flags;
+	u32 mle_flags;
+	/* Versions >= 5 with updates in version 6 */
+	/* Ext Data Elements */
+} __packed;
+
+/*
+ * TXT specification defined OS/SINIT TXT Heap table
+ */
+struct txt_os_sinit_data {
+	u32 version; /* Currently 6 for TPM 1.2 and 7 for TPM 2.0 */
+	u32 flags;
+	u64 mle_ptab;
+	u64 mle_size;
+	u64 mle_hdr_base;
+	u64 vtd_pmr_lo_base;
+	u64 vtd_pmr_lo_size;
+	u64 vtd_pmr_hi_base;
+	u64 vtd_pmr_hi_size;
+	u64 lcp_po_base;
+	u64 lcp_po_size;
+	u32 capabilities;
+	/* Version = 5 */
+	u64 efi_rsdt_ptr;
+	/* Versions >= 6 */
+	/* Ext Data Elements */
+} __packed;
+
+/*
+ * TXT specification defined SINIT/MLE TXT Heap table
+ */
+struct txt_sinit_mle_data {
+	u32 version;             /* Current values are 6 through 9 */
+	/* Versions <= 8 */
+	u8 bios_acm_id[20];
+	u32 edx_senter_flags;
+	u64 mseg_valid;
+	u8 sinit_hash[20];
+	u8 mle_hash[20];
+	u8 stm_hash[20];
+	u8 lcp_policy_hash[20];
+	u32 lcp_policy_control;
+	/* Versions >= 7 */
+	u32 rlp_wakeup_addr;
+	u32 reserved;
+	u32 num_of_sinit_mdrs;
+	u32 sinit_mdrs_table_offset;
+	u32 sinit_vtd_dmar_table_size;
+	u32 sinit_vtd_dmar_table_offset;
+	/* Versions >= 8 */
+	u32 processor_scrtm_status;
+	/* Versions >= 9 */
+	/* Ext Data Elements */
+} __packed;
+
+/*
+ * TXT data reporting structure for memory types
+ */
+struct txt_sinit_memory_descriptor_record {
+	u64 address;
+	u64 length;
+	u8 type;
+	u8 reserved[7];
+} __packed;
+
+/*
+ * TXT data structure used by a responsive local processor (RLP) to start
+ * execution in response to a GETSEC[WAKEUP].
+ */
+struct smx_rlp_mle_join {
+	u32 rlp_gdt_limit;
+	u32 rlp_gdt_base;
+	u32 rlp_seg_sel;     /* cs (ds, es, ss are seg_sel+8) */
+	u32 rlp_entry_point; /* phys addr */
+} __packed;
+
+/*
+ * TPM event log structures defined in both the TXT specification and
+ * the TCG documentation.
+ */
+#define TPM_EVTLOG_SIGNATURE "TXT Event Container"
+
+struct tpm_event_log_header {
+	char signature[20];
+	char reserved[12];
+	u8 container_ver_major;
+	u8 container_ver_minor;
+	u8 pcr_event_ver_major;
+	u8 pcr_event_ver_minor;
+	u32 container_size;
+	u32 pcr_events_offset;
+	u32 next_event_offset;
+	/* PCREvents[] */
+} __packed;
+
+/*
+ * Functions to extract data from the Intel TXT Heap Memory. The layout
+ * of the heap is as follows:
+ *  +----------------------------+
+ *  | Size Bios Data table (u64) |
+ *  +----------------------------+
+ *  | Bios Data table            |
+ *  +----------------------------+
+ *  | Size OS MLE table (u64)    |
+ *  +----------------------------+
+ *  | OS MLE table               |
+ *  +--------------------------- +
+ *  | Size OS SINIT table (u64)  |
+ *  +----------------------------+
+ *  | OS SINIT table             |
+ *  +----------------------------+
+ *  | Size SINIT MLE table (u64) |
+ *  +----------------------------+
+ *  | SINIT MLE table            |
+ *  +----------------------------+
+ *
+ *  NOTE: the table size fields include the 8 byte size field itself.
+ */
+enum {
+	TXT_BIOS_DATA_TABLE,
+	TXT_OS_MLE_DATA_TABLE,
+	TXT_OS_SINIT_DATA_TABLE,
+	TXT_SINIT_MLE_DATA_TABLE,
+	TXT_SINIT_TABLE_MAX,
+};
+
+/*
+ * Find the TPM v2 event log element in the TXT heap. This element contains
+ * the information about the size and location of the DRTM event log. Note
+ * this is a TXT specific structure.
+ *
+ * See:
+ *   Intel Trusted Execution Technology -
+ *     Measured Launch Environment Developer's Guide - Appendix C.
+ */
+static inline struct txt_heap_event_log_pointer2_1_element*
+txt_find_log2_1_element(struct txt_os_sinit_data *os_sinit_data)
+{
+#define ptr_after(p)     ((void *)p + sizeof(*p))
+#define next_ext_elem(e) ((void *)e + e->size)
+	/* The extended element array is at the end of this table */
+	struct txt_heap_ext_data_element *ext_elem = ptr_after(os_sinit_data);
+
+	while (ext_elem->type != TXT_HEAP_EXTDATA_TYPE_END) {
+		if (ext_elem->type == TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1)
+			return ptr_after(ext_elem);
+
+		ext_elem = next_ext_elem(ext_elem);
+	}
+
+	return NULL;
+}
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* _ASM_X86_TXT_H */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:53 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:53 -0700
Subject: [PATCH v16 21/38] lib/crypto: Add SHA1 support for pre-boot environments
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-22-ross.philipson@gmail.com>

Allow the SHA1 library code in lib/crypto/sha1.c to be used in
pre-boot environments. Use the __DISABLE_EXPORTS macro to disable
function exports and define the proper values for that environment
as was done earlier for SHA256.

Acked-by: Eric Biggers <ebiggers at kernel.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 lib/crypto/sha1.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lib/crypto/sha1.c b/lib/crypto/sha1.c
index daf18c862fdf..f30b5ff3b31e 100644
--- a/lib/crypto/sha1.c
+++ b/lib/crypto/sha1.c
@@ -125,7 +125,7 @@ static void __maybe_unused sha1_blocks_generic(struct sha1_block_state *state,
 	memzero_explicit(workspace, sizeof(workspace));
 }
 
-#ifdef CONFIG_CRYPTO_LIB_SHA1_ARCH
+#if defined(CONFIG_CRYPTO_LIB_SHA1_ARCH) && !defined(__DISABLE_EXPORTS)
 #include "sha1.h" /* $(SRCARCH)/sha1.h */
 #else
 #define sha1_blocks sha1_blocks_generic
@@ -207,6 +207,12 @@ void sha1(const u8 *data, size_t len, u8 out[SHA1_DIGEST_SIZE])
 }
 EXPORT_SYMBOL_GPL(sha1);
 
+/*
+ * Pre-boot environments (as indicated by __DISABLE_EXPORTS being defined)
+ * don't need the SHA1 HMAC support code.
+ */
+#ifndef __DISABLE_EXPORTS
+
 static void __hmac_sha1_preparekey(struct sha1_block_state *istate,
 				   struct sha1_block_state *ostate,
 				   const u8 *raw_key, size_t raw_key_len)
@@ -302,6 +308,8 @@ void hmac_sha1_usingrawkey(const u8 *raw_key, size_t raw_key_len,
 }
 EXPORT_SYMBOL_GPL(hmac_sha1_usingrawkey);
 
+#endif /* !__DISABLE_EXPORTS */
+
 #if defined(sha1_mod_init_arch) || defined(CONFIG_CRYPTO_FIPS)
 static int __init sha1_mod_init(void)
 {
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:54 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:54 -0700
Subject: [PATCH v16 22/38] lib/crypto: Add SHA512 support for pre-boot environments
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-23-ross.philipson@gmail.com>

Allow the SHA512 library code in lib/crypto/sha512.c to be used in
pre-boot environments. Use the __DISABLE_EXPORTS macro to disable
function exports and define the proper values for that environment
as was done earlier for SHA256.

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 lib/crypto/sha512.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lib/crypto/sha512.c b/lib/crypto/sha512.c
index 605eab51aabd..160e81d86da6 100644
--- a/lib/crypto/sha512.c
+++ b/lib/crypto/sha512.c
@@ -133,7 +133,7 @@ sha512_blocks_generic(struct sha512_block_state *state,
 	} while (--nblocks);
 }
 
-#ifdef CONFIG_CRYPTO_LIB_SHA512_ARCH
+#if defined(CONFIG_CRYPTO_LIB_SHA512_ARCH) && !defined(__DISABLE_EXPORTS)
 #include "sha512.h" /* $(SRCARCH)/sha512.h */
 #else
 #define sha512_blocks sha512_blocks_generic
@@ -250,6 +250,12 @@ void sha512(const u8 *data, size_t len, u8 out[SHA512_DIGEST_SIZE])
 }
 EXPORT_SYMBOL_GPL(sha512);
 
+/*
+ * Pre-boot environments (as indicated by __DISABLE_EXPORTS being defined)
+ * don't need the SHA2 HMAC support code.
+ */
+#ifndef __DISABLE_EXPORTS
+
 static void __hmac_sha512_preparekey(struct sha512_block_state *istate,
 				     struct sha512_block_state *ostate,
 				     const u8 *raw_key, size_t raw_key_len,
@@ -406,6 +412,8 @@ void hmac_sha512_usingrawkey(const u8 *raw_key, size_t raw_key_len,
 }
 EXPORT_SYMBOL_GPL(hmac_sha512_usingrawkey);
 
+#endif /* !__DISABLE_EXPORTS */
+
 #if defined(sha512_mod_init_arch) || defined(CONFIG_CRYPTO_FIPS)
 static int __init sha512_mod_init(void)
 {
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:56 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:56 -0700
Subject: [PATCH v16 24/38] x86/msr: Add variable MTRR base/mask and x2apic ID registers
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-25-ross.philipson@gmail.com>

Add the MSR values required by Secure Launch to locate particular CPU cores
during application processor (AP) startup, and restore the MTRR state after
an Intel TXT launch.

Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/include/asm/msr-index.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h
index 6673601246b3..2a95e3389622 100644
--- a/arch/x86/include/asm/msr-index.h
+++ b/arch/x86/include/asm/msr-index.h
@@ -408,6 +408,9 @@
 #define MSR_IA32_RTIT_OUTPUT_BASE	0x00000560
 #define MSR_IA32_RTIT_OUTPUT_MASK	0x00000561
 
+#define MSR_MTRRphysBase0		0x00000200
+#define MSR_MTRRphysMask0		0x00000201
+
 #define MSR_MTRRfix64K_00000		0x00000250
 #define MSR_MTRRfix16K_80000		0x00000258
 #define MSR_MTRRfix16K_A0000		0x00000259
@@ -960,6 +963,8 @@
 #define MSR_IA32_APICBASE_ENABLE	(1<<11)
 #define MSR_IA32_APICBASE_BASE		(0xfffff<<12)
 
+#define MSR_IA32_X2APIC_APICID		0x00000802
+
 #define MSR_IA32_UCODE_WRITE		0x00000079
 
 #define MSR_IA32_MCU_ENUMERATION	0x0000007b
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:57 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:57 -0700
Subject: [PATCH v16 25/38] x86/boot: Slight refactor of the 5 level paging logic
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-26-ross.philipson@gmail.com>

From: Ard Biesheuvel <ardb at kernel.org>

In preparation for adding Secure Launch support, which will require that
the number of paging levels remains untouched, tweak the logic slightly
so that the slaunch check can be inserted easily.

No functional change intended.

Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/compressed/pgtable_64.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/arch/x86/boot/compressed/pgtable_64.c b/arch/x86/boot/compressed/pgtable_64.c
index 0e89e197e112..3e9d651da73e 100644
--- a/arch/x86/boot/compressed/pgtable_64.c
+++ b/arch/x86/boot/compressed/pgtable_64.c
@@ -102,6 +102,7 @@ static unsigned long find_trampoline_placement(void)
 
 asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable)
 {
+	bool l5_enabled = native_read_cr4() & X86_CR4_LA57;
 	void (*toggle_la57)(void *cr3);
 	bool l5_required = false;
 
@@ -118,10 +119,12 @@ asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable)
 	 *     + CPUID leaf 7 is supported
 	 *     + the leaf has the feature bit set
 	 */
-	if (!cmdline_find_option_bool("no5lvl") &&
-	    native_cpuid_eax(0) >= 7 && (native_cpuid_ecx(7) & BIT(16))) {
-		l5_required = true;
+	if (native_cpuid_eax(0) < 7 || !(native_cpuid_ecx(7) & BIT(16)))
+		return;
+
+	l5_required = !cmdline_find_option_bool("no5lvl");
 
+	if (l5_required) {
 		/* Initialize variables for 5-level paging */
 		__pgtable_l5_enabled = 1;
 		pgdir_shift = 48;
@@ -132,7 +135,7 @@ asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable)
 	 * The trampoline will not be used if the paging mode is already set to
 	 * the desired one.
 	 */
-	if (l5_required == !!(native_read_cr4() & X86_CR4_LA57))
+	if (l5_required == l5_enabled)
 		return;
 
 	trampoline_32bit = (unsigned long *)find_trampoline_placement();
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:58 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:58 -0700
Subject: [PATCH v16 26/38] x86: Add early SHA-1 support for Secure Launch early measurements
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-27-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

Secure Launch is written to be compliant with the Intel TXT Measured
Launch Developer's Guide. The MLE Guide dictates that the system can be
configured to use both the SHA-1 and SHA-2 hashing algorithms.

Regardless of the preference towards SHA-2, if the firmware elected to
start with the SHA-1 and SHA-2 banks active and the dynamic launch was
configured to include SHA-1, Secure Launch is obligated to record
measurements for all algorithms requested in the launch configuration.

The user environment or the integrity management does not desire to use
SHA-1, it is free to just ignore the SHA-1 bank in any integrity operation
with the TPM. If there is a larger concern about the SHA-1 bank being
active, it is free to deliberately cap the SHA-1 PCRs, recording the
event in the DRTM log.

Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/startup/Makefile   | 4 ++++
 arch/x86/boot/startup/lib-sha1.c | 6 ++++++
 2 files changed, 10 insertions(+)
 create mode 100644 arch/x86/boot/startup/lib-sha1.c

diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index 5e499cfb29b5..e283ee4c1f45 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -20,6 +20,10 @@ KCOV_INSTRUMENT	:= n
 
 obj-$(CONFIG_X86_64)		+= gdt_idt.o map_kernel.o
 obj-$(CONFIG_AMD_MEM_ENCRYPT)	+= sme.o sev-startup.o
+
+slaunch-objs			+= lib-sha1.o
+obj-$(CONFIG_SECURE_LAUNCH)	+= $(slaunch-objs)
+
 pi-objs				:= $(patsubst %.o,$(obj)/%.o,$(obj-y))
 
 lib-$(CONFIG_X86_64)		+= la57toggle.o
diff --git a/arch/x86/boot/startup/lib-sha1.c b/arch/x86/boot/startup/lib-sha1.c
new file mode 100644
index 000000000000..8d679d12f6bf
--- /dev/null
+++ b/arch/x86/boot/startup/lib-sha1.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ */
+
+#include "../../../../lib/crypto/sha1.c"
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:00 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:00 -0700
Subject: [PATCH v16 28/38] x86: Add early SHA-384/512 support for Secure Launch early measurements
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-29-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

On newer TPM 2 implementations, SHA 384 and 512 banks may be available
for use. If these banks are enabled in firmware, they will be used for
the Dynamic Launch. The DLME will also use these algorithms to measure
configuration information into the TPM as early as possible before using
the values. This implementation uses the established approach of #including
the SHA-512 library directly in the early boot code.

Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/startup/Makefile     | 1 +
 arch/x86/boot/startup/lib-sha512.c | 6 ++++++
 2 files changed, 7 insertions(+)
 create mode 100644 arch/x86/boot/startup/lib-sha512.c

diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index 071a90f23ae0..527cba7e4560 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_AMD_MEM_ENCRYPT)	+= sme.o sev-startup.o
 
 slaunch-objs			+= lib-sha1.o
 slaunch-objs			+= lib-sha256.o
+slaunch-objs			+= lib-sha512.o
 obj-$(CONFIG_SECURE_LAUNCH)	+= $(slaunch-objs)
 
 pi-objs				:= $(patsubst %.o,$(obj)/%.o,$(obj-y))
diff --git a/arch/x86/boot/startup/lib-sha512.c b/arch/x86/boot/startup/lib-sha512.c
new file mode 100644
index 000000000000..2afd5c5935cd
--- /dev/null
+++ b/arch/x86/boot/startup/lib-sha512.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ */
+
+#include "../../../../lib/crypto/sha512.c"
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:01 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:01 -0700
Subject: [PATCH v16 29/38] x86/tpm: Early startup TPM PCR extending driver
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-30-ross.philipson@gmail.com>

Introduce a driver that can interact minimally with the TPM. This
allows the Secure Launch startup code to extend measurement values
into the TPM's DRTM PCR banks early in the launch process.

The driver implementation only currently supports basic initialization
and PCR extend commands. An extend command can be sent to both TPM 2.0 or
1.2 chip but only the TIS/FIFO interface is currently supported. The TCG
specs for these interface can be found here:

https://trustedcomputinggroup.org/resource/pc-client-work-group-pc-client-specific-tpm-interface-specification-tis/
https://trustedcomputinggroup.org/resource/tpm-2-0-mobile-command-response-buffer-interface-specification/

Note this TPM driver implementation relies as much as possible on
existing mainline kernel TPM code.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/startup/Makefile  |   1 +
 arch/x86/boot/startup/exports.h |   7 +
 arch/x86/boot/startup/tpm.h     |  47 +++
 arch/x86/boot/startup/tpm_drv.c | 567 ++++++++++++++++++++++++++++++++
 4 files changed, 622 insertions(+)
 create mode 100644 arch/x86/boot/startup/tpm.h
 create mode 100644 arch/x86/boot/startup/tpm_drv.c

diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index 527cba7e4560..ecf86ce5ebf7 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_AMD_MEM_ENCRYPT)	+= sme.o sev-startup.o
 slaunch-objs			+= lib-sha1.o
 slaunch-objs			+= lib-sha256.o
 slaunch-objs			+= lib-sha512.o
+slaunch-objs			+= tpm_drv.o
 obj-$(CONFIG_SECURE_LAUNCH)	+= $(slaunch-objs)
 
 pi-objs				:= $(patsubst %.o,$(obj)/%.o,$(obj-y))
diff --git a/arch/x86/boot/startup/exports.h b/arch/x86/boot/startup/exports.h
index 01d2363dc445..4b82ddbd62a8 100644
--- a/arch/x86/boot/startup/exports.h
+++ b/arch/x86/boot/startup/exports.h
@@ -12,3 +12,10 @@ PROVIDE(snp_cpuid			= __pi_snp_cpuid);
 PROVIDE(snp_cpuid_get_table		= __pi_snp_cpuid_get_table);
 PROVIDE(svsm_issue_call			= __pi_svsm_issue_call);
 PROVIDE(svsm_process_result_codes	= __pi_svsm_process_result_codes);
+
+#ifdef CONFIG_SECURE_LAUNCH
+__pi_bcmp				= bcmp;
+__pi_memcmp				= memcmp;
+__pi_strlen				= strlen;
+__pi___WARN_trap			= __WARN_trap;
+#endif
diff --git a/arch/x86/boot/startup/tpm.h b/arch/x86/boot/startup/tpm.h
new file mode 100644
index 000000000000..1a11396b68c6
--- /dev/null
+++ b/arch/x86/boot/startup/tpm.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TPM early extend header file.
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#ifndef BOOT_COMPRESSED_TPM_H
+#define BOOT_COMPRESSED_TPM_H
+
+enum early_tis_defaults {
+	TPM_TIMEOUT		= 5, /* ms */
+	TIS_DURATION		= 120000, /* 120 secs in ms */
+};
+
+enum tpm_family {
+	TPM_FAMILY_INVALID	= 0,
+	TPM_FAMILY_12		= 1,
+	TPM_FAMILY_20		= 2
+};
+
+struct tpm_chip {
+	enum tpm_family family;
+	u64 baseaddr;
+	int locality;
+	int did;
+	int vid;
+
+	/* in ms */
+	ktime_t timeout_a;
+	ktime_t timeout_b;
+	ktime_t timeout_c;
+	ktime_t timeout_d;
+};
+
+bool tpm_tis_check_locality(struct tpm_chip *chip, int loc);
+void tpm_tis_release_locality(struct tpm_chip *chip);
+int tpm_tis_request_locality(struct tpm_chip *chip, int loc);
+void tpm_tis_disable_interrupts(struct tpm_chip *chip);
+int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash);
+int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
+		    struct tpm_digest *digests, u32 digest_count);
+int early_tpm_init(struct tpm_chip *chip, u64 baseaddr);
+int early_tpm_fini(struct tpm_chip *chip);
+
+#endif /* BOOT_COMPRESSED_TPM_H */
diff --git a/arch/x86/boot/startup/tpm_drv.c b/arch/x86/boot/startup/tpm_drv.c
new file mode 100644
index 000000000000..99971b06f768
--- /dev/null
+++ b/arch/x86/boot/startup/tpm_drv.c
@@ -0,0 +1,567 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Based of the original tpm_tis.c implementation as found in the
+ * Linux 2.6 code base.
+ *
+ * Copyright (C) 2005, 2006 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert at watson.ibm.com>
+ * Kylene Hall <kjhall at us.ibm.com>
+ */
+
+#include <crypto/sha2.h>
+#include <asm/io.h>
+
+#include <linux/tpm_command.h>
+#include <linux/tpm_ptp.h>
+#include <linux/tpm_buf.h>
+
+#include "tpm.h"
+
+static u8 tpm_buf_page[PAGE_SIZE];
+
+/*
+ * Single threaded environment only running on BSP. Use a single shared
+ * page for all TPM extend operations.
+ */
+static inline struct tpm_buf *tpm_buf_alloc_page(void)
+{
+	memset(tpm_buf_page, 0, PAGE_SIZE);
+	return (struct tpm_buf *)tpm_buf_page;
+}
+
+static inline void tpm_buf_free_page(void)
+{
+	memset(tpm_buf_page, 0, PAGE_SIZE);
+}
+
+/* Pull in TPM buffer management support */
+#include "../../../../drivers/char/tpm/tpm-buf.c"
+
+static u32 __init tpm_get_alg_size(u16 alg_id)
+{
+	switch (alg_id) {
+	case TPM_ALG_SHA1:
+		return TPM_DIGEST_SIZE;
+	case TPM_ALG_SHA256:
+	case TPM_ALG_SM3_256:
+		return SHA256_DIGEST_SIZE;
+	case TPM_ALG_SHA384:
+		return SHA384_DIGEST_SIZE;
+	case TPM_ALG_SHA512:
+	default:
+		return SHA512_DIGEST_SIZE;
+	};
+}
+
+static inline u8 tpm_read8(struct tpm_chip *chip, u32 field)
+{
+	return readb((void *)(chip->baseaddr | field));
+}
+
+static inline void tpm_write8(struct tpm_chip *chip, u32 field, u8 val)
+{
+	writeb(val, (void *)(chip->baseaddr | field));
+}
+
+static inline u32 tpm_read32(struct tpm_chip *chip, u32 field)
+{
+	return readl((void *)(chip->baseaddr | field));
+}
+
+static inline void tpm_write32(struct tpm_chip *chip, u32 field, u32 val)
+{
+	writel(val, (void *)(chip->baseaddr | field));
+}
+
+/*
+ * In the setup kernel environment, it is far too early to calibrate time.
+ * Assume a 5GHz processor (the upper end of the Fam19h range), allowing
+ * reasonable timeouts on slower systems.
+ */
+static unsigned long ticks_per_ms = (5UL * 1000 * 1000 /* CPU in KHz */);
+
+static inline ktime_t tpm_now_ms(void)
+{
+	return rdtsc()/ticks_per_ms;
+}
+
+static inline void tpm_mdelay(unsigned int msecs)
+{
+	unsigned long ticks = msecs * ticks_per_ms;
+	unsigned long s, e;
+
+	s = rdtsc();
+	do {
+		cpu_relax();
+		e = rdtsc();
+	} while ((e - s) < ticks);
+}
+
+static inline u8 __tis_status(struct tpm_chip *chip)
+{
+	return tpm_read8(chip, TPM_STS(chip->locality));
+}
+
+static inline void __tis_cancel(struct tpm_chip *chip)
+{
+	/* This causes the current command to be aborted */
+	tpm_write8(chip, TPM_STS(chip->locality), TPM_STS_COMMAND_READY);
+}
+
+static int __init __tis_get_burstcount(struct tpm_chip *chip)
+{
+	ktime_t stop;
+	int burstcnt;
+
+	stop = tpm_now_ms() + chip->timeout_d;
+	do {
+		burstcnt = tpm_read8(chip, (TPM_STS(chip->locality) + 1));
+		burstcnt += tpm_read8(chip, TPM_STS(chip->locality) + 2) << 8;
+
+		if (burstcnt)
+			return burstcnt;
+
+		tpm_mdelay(TPM_TIMEOUT);
+	} while (tpm_now_ms() < stop);
+
+	return -EBUSY;
+}
+
+static int __init __tis_wait_for_stat(struct tpm_chip *chip, u8 mask, ktime_t timeout)
+{
+	ktime_t stop;
+	u8 status;
+
+	if ((__tis_status(chip) & mask) == mask)
+		return 0;
+
+	stop = tpm_now_ms() + timeout;
+	do {
+		tpm_mdelay(TPM_TIMEOUT);
+
+		status = __tis_status(chip);
+		if ((status & mask) == mask)
+			return 0;
+	} while (tpm_now_ms() < stop);
+
+	return -ETIME;
+}
+
+static int __init __tis_recv_data(struct tpm_chip *chip, u8 *buf, int count)
+{
+	int size = 0;
+	int burstcnt;
+
+	while (size < count &&
+	       __tis_wait_for_stat(chip,
+				   TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+				   chip->timeout_c) == 0) {
+		burstcnt = __tis_get_burstcount(chip);
+
+		for ( ; burstcnt > 0 && size < count; --burstcnt)
+			buf[size++] = tpm_read8(chip, TPM_DATA_FIFO(chip->locality));
+	}
+
+	return size;
+}
+
+/**
+ * tpm_tis_check_locality - Check if the given locality is the active one
+ * @chip:	The TPM chip instance
+ * @loc:	The locality to check
+ *
+ * Return: true - locality active, false - not active
+ */
+bool __init tpm_tis_check_locality(struct tpm_chip *chip, int loc)
+{
+	u8 res = tpm_read8(chip, TPM_ACCESS(loc));
+
+	if ((res & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ==
+		   (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) {
+		chip->locality = loc;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * tpm_tis_release_locality - Release the active locality
+ * @chip:	The TPM chip instance
+ */
+void __init tpm_tis_release_locality(struct tpm_chip *chip)
+{
+	u8 res = tpm_read8(chip, TPM_ACCESS(chip->locality));
+
+	if ((res & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) ==
+		   (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID))
+		tpm_write8(chip, TPM_ACCESS(chip->locality), TPM_ACCESS_RELINQUISH_LOCALITY);
+
+	chip->locality = 0;
+}
+
+/**
+ * tpm_tis_request_locality - Request to make the given locality the active one
+ * @chip:	The TPM chip instance
+ * @loc:	The locality to make active/set as current
+ *
+ * Return:
+ *  >= 0 - Success, new active locality returned or locality already active
+ *  < 0  - Error occurred
+ */
+int __init tpm_tis_request_locality(struct tpm_chip *chip, int loc)
+{
+	ktime_t stop;
+
+	if (tpm_tis_check_locality(chip, loc))
+		return loc;
+
+	/* Set the new locality */
+	tpm_write8(chip, TPM_ACCESS(loc), TPM_ACCESS_REQUEST_USE);
+
+	stop = tpm_now_ms() + chip->timeout_b;
+	do {
+		if (tpm_tis_check_locality(chip, loc))
+			return loc;
+
+		tpm_mdelay(TPM_TIMEOUT);
+	} while (tpm_now_ms() < stop);
+
+	return -1;
+}
+
+/**
+ * tpm_tis_disable_interrupts - Disable interrupts for the TPM, use polling mode only
+ * @chip:	The TPM chip instance
+ */
+void __init tpm_tis_disable_interrupts(struct tpm_chip *chip)
+{
+	u32 intmask;
+
+	intmask = tpm_read32(chip, TPM_INT_ENABLE(chip->locality));
+	/* Disable everything to make sure it is in a consistent state */
+	intmask &= ~(TPM_GLOBAL_INT_ENABLE | TPM_INTF_CMD_READY_INT |
+		     TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_STS_VALID_INT |
+		     TPM_INTF_DATA_AVAIL_INT);
+	tpm_write32(chip, TPM_INT_ENABLE(chip->locality), intmask);
+}
+
+/**
+ * tpm_tis_recv - Receive response data from TPM via TIS FIFO
+ * @chip:	The TPM chip instance
+ * @buf:	The response buffer
+ * @count:	Length of the response buffer
+ *
+ * Return:
+ *  = 0 - Success, no response data
+ *  > 0 - Success, value is the response data length
+ *  < 0 - Error occurred
+ */
+static int __init tpm_tis_recv(struct tpm_chip *chip, u8 *buf, int count)
+{
+	int expected, status, size = 0, rc = -EIO;
+
+	if (count < TPM_HEADER_SIZE)
+		goto out;
+
+	/* Read first 10 bytes, including tag, paramsize, and result */
+	size = __tis_recv_data(chip, buf, TPM_HEADER_SIZE);
+	if (size < TPM_HEADER_SIZE)
+		goto out;
+
+	expected = be32_to_cpu(*((u32 *)(buf + 2)));
+	if (expected > count)
+		goto out;
+
+	size += __tis_recv_data(chip, &buf[TPM_HEADER_SIZE], expected - TPM_HEADER_SIZE);
+	if (size < expected) {
+		rc = -ETIME;
+		goto out;
+	}
+
+	__tis_wait_for_stat(chip, TPM_STS_VALID, chip->timeout_c);
+
+	status = __tis_status(chip);
+	if (status & TPM_STS_DATA_AVAIL) {
+		rc = -EIO;
+		goto out;
+	}
+
+	/* Done with receive, move to Command Ready state */
+	__tis_cancel(chip);
+
+	return size;
+out:
+	__tis_cancel(chip);
+	tpm_tis_release_locality(chip);
+	return rc;
+}
+
+/**
+ * tpm_tis_send - Send command to TPM via TIS FIFO
+ * @chip:	The TPM chip instance
+ * @buf:	The command buffer
+ * @len:	Length of the command buffer to send
+ *
+ * Return:
+ *  = len - Success, all data sent
+ *  < 0	  - Error occurred
+ */
+static int __init tpm_tis_send(struct tpm_chip *chip, u8 *buf, int len)
+{
+	int status, burstcnt = 0;
+	int count = 0;
+	int rc = 0;
+
+	status = __tis_status(chip);
+	if ((status & TPM_STS_COMMAND_READY) == 0) {
+		__tis_cancel(chip);
+		if (__tis_wait_for_stat(chip, TPM_STS_COMMAND_READY, chip->timeout_b) < 0) {
+			rc = -ETIME;
+			goto out_err;
+		}
+	}
+
+	while (count < len - 1) {
+		burstcnt = __tis_get_burstcount(chip);
+		for ( ; burstcnt > 0 && count < len - 1; --burstcnt)
+			tpm_write8(chip, TPM_DATA_FIFO(chip->locality), buf[count++]);
+
+		__tis_wait_for_stat(chip, TPM_STS_VALID, chip->timeout_c);
+		status = __tis_status(chip);
+		if ((status & TPM_STS_DATA_EXPECT) == 0) {
+			rc = -EIO;
+			goto out_err;
+		}
+	}
+
+	/* Write last byte */
+	tpm_write8(chip, TPM_DATA_FIFO(chip->locality), buf[count]);
+	__tis_wait_for_stat(chip, TPM_STS_VALID, chip->timeout_c);
+	status = __tis_status(chip);
+	if ((status & TPM_STS_DATA_EXPECT) != 0) {
+		rc = -EIO;
+		goto out_err;
+	}
+
+	/* Go and do it */
+	tpm_write8(chip, TPM_STS(chip->locality), TPM_STS_GO);
+
+	return len;
+
+out_err:
+	__tis_cancel(chip);
+	tpm_tis_release_locality(chip);
+	return rc;
+}
+
+/**
+ * tpm_tis_transmit - Transmit a TPM FIFO command
+ * @chip:	The TPM chip instance
+ * @buf:	The request and response buffer object
+ * @bufsize:	Entire size available in buffer
+ *
+ * Return:
+ *  = 0 - Success, no returned data
+ *  > 0 - Success, value is the return data length
+ *  < 0 - Error occurred
+ */
+static int __init tpm_tis_transmit(struct tpm_chip *chip, u8 *buf, u32 bufsize)
+{
+	ktime_t stop;
+	u32 count;
+	u8 status;
+	int rc;
+
+	count = be32_to_cpu(*((u32 *) (buf + 2)));
+	if (count == 0)
+		return -ENODATA;
+
+	if (count > bufsize)
+		return -E2BIG;
+
+	rc = tpm_tis_send(chip, buf, count);
+	if (rc < 0)
+		goto out;
+
+	stop = tpm_now_ms() + TIS_DURATION;
+	do {
+		status = __tis_status(chip);
+		if ((status & (TPM_STS_DATA_AVAIL | TPM_STS_VALID)) ==
+			      (TPM_STS_DATA_AVAIL | TPM_STS_VALID))
+			goto out_recv;
+
+		if (status == TPM_STS_COMMAND_READY) {
+			rc = -ECANCELED;
+			goto out;
+		}
+
+		tpm_mdelay(TPM_TIMEOUT);
+		rmb();
+	} while (tpm_now_ms() < stop);
+
+	/* Cancel the command */
+	__tis_cancel(chip);
+	rc = -ETIME;
+	goto out;
+
+out_recv:
+	rc = tpm_tis_recv(chip, buf, bufsize);
+	if (rc >= 0) {
+		if (rc > 0 && rc < TPM_HEADER_SIZE)
+			return -EFAULT;
+		return rc;
+	}
+	/* Else return was an error, nothing to receive */
+
+out:
+	return rc;
+}
+
+/**
+ * tpm_find_interface_and_family - interface FIFO/CRB, family 2.0 or 1.2
+ * @chip:	The TPM chip instance
+ *
+ * Return: TPM family ID enum
+ */
+static enum tpm_family __init tpm_find_interface_and_family(struct tpm_chip *chip)
+{
+	struct tpm_intf_capability intf_cap;
+	struct tpm_interface_id intf_id;
+
+	/* Sort out whether it is 1.x */
+	intf_cap.val = tpm_read32(chip, TPM_INTF_CAPS(0));
+	if ((intf_cap.interface_version == TPM_TIS_INTF_12) ||
+	    (intf_cap.interface_version == TPM_TIS_INTF_13))
+		return TPM_FAMILY_12; /* Always TIS */
+
+	/* Assume that it is 2.0 but check if the interface is CRB */
+	intf_id.val = tpm_read32(chip, TPM_INTF_ID(0));
+	if (intf_id.interface_type == TPM_CRB_INTF_ACTIVE)
+		return TPM_FAMILY_INVALID;
+
+	/* Else TPM 2.0 with TIS interface */
+	return TPM_FAMILY_20;
+}
+
+/**
+ * tpm1_pcr_extend - send a TPM1 extend command to the device
+ * @chip:	a TPM chip to use
+ * @pcr_idx:	the PCR index to extend for the current locality
+ * @hash:	the SHA1 hash digest to extend
+ *
+ * Return:
+ * * 0		- OK
+ * * -errno	- A system error
+ * * TPM_RC	- A TPM error
+ */
+int __init tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash)
+{
+	struct tpm_buf *buf = tpm_buf_alloc_page();
+	int rc = 0;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
+
+	tpm_buf_append_u32(buf, pcr_idx);
+	tpm_buf_append(buf, hash, TPM_DIGEST_SIZE);
+
+	rc = tpm_tis_transmit(chip, buf->data, PAGE_SIZE);
+
+	/* Ignoring output */
+	if (rc > 0)
+		rc = 0;
+
+	tpm_buf_free_page();
+
+	return rc;
+}
+
+/**
+ * tpm2_pcr_extend() - send a TPM2 extend command to the device
+ *
+ * @chip:		TPM chip to use.
+ * @pcr_idx:		index of the PCR.
+ * @digests:		list of PCR banks and corresponding digest values to extend.
+ * @digest_count:	count of digests to extend
+ *
+ * Return:
+ * * 0		- OK
+ * * -errno	- A system error
+ * * TPM_RC	- A TPM error
+ */
+int __init tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
+			   struct tpm_digest *digests, u32 digest_count)
+{
+	struct tpm_buf *buf = tpm_buf_alloc_page();
+	int rc = 0, i;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
+
+	tpm_buf_append_u32(buf, pcr_idx);
+
+	/* Setup a NULL auth session for the command */
+	tpm_buf_append_u32(buf, 9);
+	/* auth handle */
+	tpm_buf_append_u32(buf, TPM2_RS_PW);
+	/* nonce */
+	tpm_buf_append_u16(buf, 0);
+	/* attributes */
+	tpm_buf_append_u8(buf, 0);
+	/* passphrase */
+	tpm_buf_append_u16(buf, 0);
+
+	tpm_buf_append_u32(buf, digest_count);
+
+	for (i = 0; i < digest_count; i++) {
+		tpm_buf_append_u16(buf, digests[i].alg_id);
+		tpm_buf_append(buf, (const unsigned char *)&digests[i].digest,
+			       tpm_get_alg_size(digests[i].alg_id));
+	}
+
+	rc = tpm_tis_transmit(chip, buf->data, PAGE_SIZE);
+
+	/* Ignoring output */
+	if (rc > 0)
+		rc = 0;
+
+	tpm_buf_free_page();
+
+	return rc;
+}
+
+int __init early_tpm_init(struct tpm_chip *chip, u64 baseaddr)
+{
+	u32 didvid;
+
+	memset(chip, 0, sizeof(*chip));
+	chip->baseaddr = baseaddr;
+
+	chip->family = tpm_find_interface_and_family(chip);
+	if (chip->family == TPM_FAMILY_INVALID)
+		return TPM_ERR_INVALID_FAMILY;
+
+	/* Set default timeouts */
+	chip->timeout_a = TIS_SHORT_TIMEOUT;
+	chip->timeout_b = TIS_LONG_TIMEOUT;
+	chip->timeout_c = TIS_SHORT_TIMEOUT;
+	chip->timeout_d = TIS_SHORT_TIMEOUT;
+
+	/* Get the vendor and device ids */
+	didvid = tpm_read32(chip, TPM_DID_VID(0));
+	chip->did = didvid >> 16;
+	chip->vid = didvid & 0xFFFF;
+
+	return TPM_SUCCESS;
+}
+
+int __init early_tpm_fini(struct tpm_chip *chip)
+{
+	tpm_tis_release_locality(chip);
+	memset(chip, 0, sizeof(*chip));
+
+	return TPM_SUCCESS;
+}
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:02 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:02 -0700
Subject: [PATCH v16 30/38] x86/slaunch: Add MLE header and Secure Launch entrypoint to the core kernel
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-31-ross.philipson@gmail.com>

The Measured Launch Environment (MLE) header is an Intel TXT specific
structure that is used by the Intel ACM and Secure Launch implementation
to determine the location and attributes of the secure kernel being
launched.

Also introduce the sl_stub.S code to create a 32-bit Secure Launch entry
point into the core kernel and expose it via an MLE header. This is the
entry point for starting a Secure Launch kernel, handling the
post-launch CPU states and validating the environment.

Co-developed-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/compressed/Makefile     |   2 +-
 arch/x86/boot/compressed/misc.c       |   4 +
 arch/x86/boot/startup/Makefile        |   1 +
 arch/x86/boot/startup/sl_main.c       |  28 +
 arch/x86/include/asm/boot.h           |   4 +
 arch/x86/include/uapi/asm/bootparam.h |   1 +
 arch/x86/kernel/Makefile              |   1 +
 arch/x86/kernel/asm-offsets.c         |  22 +
 arch/x86/kernel/sl_stub.S             | 847 ++++++++++++++++++++++++++
 arch/x86/kernel/vmlinux.lds.S         |   5 +
 arch/x86/tools/relocs.c               |   1 +
 11 files changed, 915 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/boot/startup/sl_main.c
 create mode 100644 arch/x86/kernel/sl_stub.S

diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile
index b8b2b7bea1d3..8b2e234d18cb 100644
--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -76,7 +76,7 @@ LDFLAGS_vmlinux += -T
 hostprogs	:= mkpiggy
 HOST_EXTRACFLAGS += -I$(srctree)/tools/include
 
-sed-voffset := -e 's/^\([0-9a-fA-F]*\) [ABbCDGRSTtVW] \(_text\|__start_rodata\|_sinittext\|__inittext_end\|__bss_start\|_end\)$$/\#define VO_\2 _AC(0x\1,UL)/p'
+sed-voffset := -e 's/^\([0-9a-fA-F]*\) [ABbCDGRSTtVW] \(_text\|__start_rodata\|_sinittext\|mle_header\|__inittext_end\|__bss_start\|_end\)$$/\#define VO_\2 _AC(0x\1,UL)/p'
 
 quiet_cmd_voffset = VOFFSET $@
       cmd_voffset = $(NM) $< | sed -n $(sed-voffset) > $@
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c
index 0f41ca0e52c0..e3b5177bfa6f 100644
--- a/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -336,6 +336,10 @@ const unsigned long kernel_inittext_offset = VO__sinittext - VO__text;
 const unsigned long kernel_inittext_size = VO___inittext_end - VO__sinittext;
 const unsigned long kernel_total_size = VO__end - VO__text;
 
+#ifdef CONFIG_SECURE_LAUNCH
+const unsigned long mle_header_offset = VO_mle_header - VO__text;
+#endif
+
 static u8 boot_heap[BOOT_HEAP_SIZE] __aligned(4);
 
 extern unsigned char input_data[];
diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index ecf86ce5ebf7..c4b150a0253b 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -25,6 +25,7 @@ slaunch-objs			+= lib-sha1.o
 slaunch-objs			+= lib-sha256.o
 slaunch-objs			+= lib-sha512.o
 slaunch-objs			+= tpm_drv.o
+slaunch-objs			+= sl_main.o
 obj-$(CONFIG_SECURE_LAUNCH)	+= $(slaunch-objs)
 
 pi-objs				:= $(patsubst %.o,$(obj)/%.o,$(obj-y))
diff --git a/arch/x86/boot/startup/sl_main.c b/arch/x86/boot/startup/sl_main.c
new file mode 100644
index 000000000000..1982cfb461dd
--- /dev/null
+++ b/arch/x86/boot/startup/sl_main.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Secure Launch early measurement and validation routines.
+ *
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ */
+
+#include <asm/msr.h>
+#include <asm/mtrr.h>
+#include <asm/processor-flags.h>
+#include <asm/asm-offsets.h>
+#include <asm/bootparam.h>
+#include <asm/shared/msr.h>
+#include <linux/efi.h>
+#include <linux/slr_table.h>
+#include <linux/slaunch.h>
+
+#include "tpm.h"
+
+u32 sl_cpu_type __initdata;
+u32 sl_mle_start __initdata;
+
+void sl_main(void *bootparams);
+
+asmlinkage __visible __init void sl_main(void *bootparams)
+{
+}
diff --git a/arch/x86/include/asm/boot.h b/arch/x86/include/asm/boot.h
index f7b67cb73915..84e87e0d3d82 100644
--- a/arch/x86/include/asm/boot.h
+++ b/arch/x86/include/asm/boot.h
@@ -86,6 +86,10 @@ extern const unsigned long kernel_inittext_offset;
 extern const unsigned long kernel_inittext_size;
 extern const unsigned long kernel_total_size;
 
+#ifdef CONFIG_SECURE_LAUNCH
+extern const unsigned long mle_header_offset;
+#endif
+
 unsigned long decompress_kernel(unsigned char *outbuf, unsigned long virt_addr,
 				void (*error)(char *x));
 
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index dafbf581c515..8155fa899f50 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -12,6 +12,7 @@
 /* loadflags */
 #define LOADED_HIGH	(1<<0)
 #define KASLR_FLAG	(1<<1)
+#define SLAUNCH_FLAG	(1<<2)
 #define QUIET_FLAG	(1<<5)
 #define KEEP_SEGMENTS	(1<<6)
 #define CAN_USE_HEAP	(1<<7)
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 47a32f583930..7e247064b7d0 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -89,6 +89,7 @@ obj-y			+= resource.o
 obj-y			+= irqflags.o
 obj-y			+= static_call.o
 
+obj-$(CONFIG_SECURE_LAUNCH)	+= sl_stub.o
 obj-y				+= process.o
 obj-y				+= fpu/
 obj-y				+= ptrace.o
diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c
index 081816888f7a..684e6552d973 100644
--- a/arch/x86/kernel/asm-offsets.c
+++ b/arch/x86/kernel/asm-offsets.c
@@ -13,6 +13,9 @@
 #include <linux/hardirq.h>
 #include <linux/suspend.h>
 #include <linux/kbuild.h>
+#include <linux/efi.h>
+#include <linux/slr_table.h>
+#include <linux/slaunch.h>
 #include <asm/processor.h>
 #include <asm/thread_info.h>
 #include <asm/sigframe.h>
@@ -133,4 +136,23 @@ static void __used common(void)
 	BLANK();
 	DEFINE(ALT_INSTR_SIZE,	sizeof(struct alt_instr));
 	DEFINE(EXTABLE_SIZE,	sizeof(struct exception_table_entry));
+
+#ifdef CONFIG_SECURE_LAUNCH
+	BLANK();
+	OFFSET(SL_txt_info, txt_os_mle_data, txt_info);
+	OFFSET(SL_mle_scratch, txt_os_mle_data, mle_scratch);
+	OFFSET(SL_ap_wake_block, txt_os_mle_data, ap_wake_block);
+	OFFSET(SL_ap_wake_block_size, txt_os_mle_data, ap_wake_block_size);
+	OFFSET(SL_boot_params_addr, slr_entry_intel_info, boot_params_addr);
+	OFFSET(SL_saved_misc_enable_msr, slr_entry_intel_info, saved_misc_enable_msr);
+	OFFSET(SL_saved_bsp_mtrrs, slr_entry_intel_info, saved_bsp_mtrrs);
+	OFFSET(SL_num_logical_procs, txt_bios_data, num_logical_procs);
+	OFFSET(SL_capabilities, txt_os_sinit_data, capabilities);
+	OFFSET(SL_mle_size, txt_os_sinit_data, mle_size);
+	OFFSET(SL_vtd_pmr_lo_base, txt_os_sinit_data, vtd_pmr_lo_base);
+	OFFSET(SL_vtd_pmr_lo_size, txt_os_sinit_data, vtd_pmr_lo_size);
+	OFFSET(SL_rlp_wakeup_addr, txt_sinit_mle_data, rlp_wakeup_addr);
+	OFFSET(SL_rlp_gdt_base, smx_rlp_mle_join, rlp_gdt_base);
+	OFFSET(SL_rlp_entry_point, smx_rlp_mle_join, rlp_entry_point);
+#endif
 }
diff --git a/arch/x86/kernel/sl_stub.S b/arch/x86/kernel/sl_stub.S
new file mode 100644
index 000000000000..5121de563310
--- /dev/null
+++ b/arch/x86/kernel/sl_stub.S
@@ -0,0 +1,847 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Secure Launch protected mode entry point.
+ *
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ * Copyright (c) 2026 Assured Information Security, Inc.
+ */
+
+#include <linux/linkage.h>
+#include <linux/init.h>
+#include <asm/segment.h>
+#include <asm/msr.h>
+#include <asm/apicdef.h>
+#include <asm/trapnr.h>
+#include <asm/pgtable_types.h>
+#include <asm/processor-flags.h>
+#include <asm/asm-offsets.h>
+#include <asm/bootparam.h>
+#include <asm/page_types.h>
+#include <asm/irq_vectors.h>
+#include <asm/unwind_hints.h>
+#include <linux/slr_table.h>
+#include <linux/slaunch.h>
+
+/* CPUID: leaf 1, ECX, SMX feature bit */
+#define X86_FEATURE_BIT_SMX	(1 << 6)
+
+#define IDT_VECTOR_LO_BITS	0
+#define IDT_VECTOR_HI_BITS	6
+
+/*
+ * See the comment in head_64.S for detailed information on what this macro
+ * and others like it are used for. The comment appears right at the top of
+ * the file.
+ */
+#define rva(X) ((X) - sl_stub_entry)
+
+/*
+ * The GETSEC op code is open coded because older versions of
+ * GCC do not support the getsec mnemonic.
+ */
+.macro GETSEC leaf
+	pushl	%ebx
+	xorl	%ebx, %ebx	/* Must be zero for SMCTRL */
+	movl	\leaf, %eax	/* Leaf function */
+	.byte 	0x0f, 0x37	/* GETSEC opcode */
+	popl	%ebx
+.endm
+
+.macro TXT_RESET error
+	/*
+	 * Set a sticky error value and reset. Note the movs to %eax act as
+	 * TXT register barriers.
+	 */
+	movl	\error, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_ERRORCODE)
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_E2STS), %eax
+	movl	$1, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_CMD_NO_SECRETS)
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_E2STS), %eax
+	movl	$1, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_CMD_UNLOCK_MEM_CONFIG)
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_E2STS), %eax
+	movl	$1, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_CMD_RESET)
+1:
+	hlt
+	jmp	1b
+.endm
+
+	.code32
+	__INIT
+SYM_CODE_START(sl_stub_entry)
+	UNWIND_HINT_END_OF_STACK
+	/*
+	 * On entry, %ebx has the entry absolute offset to sl_stub_entry. The rva()
+	 * macro is used to generate relative references using %ebx as a base, as
+	 * to avoid absolute relocations, which would require fixups at runtime.
+	 * Only %cs and %ds segments are known good after a TXT launch and can be
+	 * used to establish a new GDT and segments.
+	 */
+
+	/* Load GDT, set segment regs and lret to __SL32_CS */
+	leal	rva(sl_gdt_desc)(%ebx), %eax
+	addl	%eax, 2(%eax)
+	lgdt	(%eax)
+
+	movl	$(__SL32_DS), %eax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+	movw	%ax, %ss
+
+	/*
+	 * Now that %ss is known good, take the first stack for the BSP. The
+	 * AP stacks are only used on Intel.
+	 */
+	leal	rva(sl_stacks_end)(%ebx), %esp
+
+	leal	rva(.Lsl_cs)(%ebx), %eax
+	pushl	$(__SL32_CS)
+	pushl	%eax
+	lret
+
+.Lsl_cs:
+	UNWIND_HINT_END_OF_STACK
+	/* Save our base pointer reg and page table for MLE */
+	pushl	%ebx
+	movl	%ecx, %ebp
+
+	/* See if SMX feature is supported. */
+	movl	$1, %eax
+	cpuid
+	testl	$(X86_FEATURE_BIT_SMX), %ecx
+	jz	.Ldo_unknown_cpu
+
+	popl	%ebx
+
+	/* Know it is Intel */
+	movl	$(SL_CPU_INTEL), rva(__pi_sl_cpu_type)(%ebx)
+
+	/* Locate the base of the MLE using the page tables in %ecx */
+	call	sl_find_mle_base
+
+	/* Increment CPU count for BSP */
+	incl	rva(sl_txt_cpu_count)(%ebx)
+
+	/*
+	 * On the BSP, enable SMI with GETSEC[SMCTRL] which were disabled by SENTER.
+	 * NMIs were also disabled by SENTER. Since there is no IDT for the BSP,
+	 * allow the mainline kernel to re-enable them in the normal course of
+	 * booting.
+	 */
+	GETSEC	$(SMX_X86_GETSEC_SMCTRL)
+
+	/* Clear the TXT error registers for a clean start of day */
+	movl	$0, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_ERRORCODE)
+	movl	$0xffffffff, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_ESTS)
+
+	/* Read physical base of the TXT heap into %eax */
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_HEAP_BASE), %eax
+	/* Read the size of the BIOS data into ECX (first 8 bytes) */
+	movl	(%eax), %ecx
+	/* Skip over BIOS data and size of OS to MLE data section */
+	leal	8(%eax, %ecx), %eax
+
+	/* Need to verify the values in the OS-MLE struct passed in */
+	call	sl_txt_verify_os_mle_struct
+
+	/*
+	 * Get the boot params address from the TXT info table in the SLRT.
+	 * Note %esi and %ebx MUST be preserved across calls and operations.
+	 */
+	movl	SL_txt_info(%eax), %edi
+	movl	SL_boot_params_addr(%edi), %esi
+
+	/* Save %ebx so the APs can find their way home */
+	movl	%ebx, (SL_mle_scratch + SL_SCRATCH_AP_EBX)(%eax)
+
+	/* Fetch the AP wake code block address from the heap */
+	movl	SL_ap_wake_block(%eax), %edi
+	movl	%edi, rva(sl_txt_ap_wake_block)(%ebx)
+
+	/* Store the offset in the AP wake block to the jmp address */
+	movl	$(sl_ap_jmp_offset - sl_txt_ap_wake_begin), \
+		(SL_mle_scratch + SL_SCRATCH_AP_JMP_OFFSET)(%eax)
+
+	/* Store the offset in the AP wake block to the AP stacks block */
+	movl	$(sl_stacks - sl_txt_ap_wake_begin), \
+		(SL_mle_scratch + SL_SCRATCH_AP_STACKS_OFFSET)(%eax)
+
+	/* %eax still is the base of the OS-MLE block, save it */
+	pushl	%eax
+
+	/* Relocate the AP wake code to the safe block */
+	call	sl_txt_reloc_ap_wake
+
+	/*
+	 * Wake up all APs that are blocked in the ACM and wait for them to
+	 * halt. This should be done before restoring the MTRRs so the ACM is
+	 * still properly in WB memory.
+	 */
+	call	sl_txt_wake_aps
+
+	/* Restore OS-MLE in %eax */
+	popl	%eax
+
+	/*
+	 * %edi is used by this routine to find the MTRRs which are in the SLRT
+	 * in the Intel info.
+	 */
+	movl	SL_txt_info(%eax), %edi
+	call	sl_txt_load_regs
+
+	jmp	.Lcpu_setup_done
+
+.Ldo_unknown_cpu:
+	/* Non-Intel CPUs are not yet supported */
+	ud2
+
+.Lcpu_setup_done:
+	/*
+	 * Don't enable MCE at this point. The kernel will enable
+	 * it on the BSP later when it is ready.
+	 */
+
+	/* Set up 1:1 mapping using 1G mappings in the page tables in %EBP */
+	xorl	%ecx, %ecx
+	movl	$128, %edx
+1:	leal	(,%ecx,4), %eax
+	.irpc	l, 0123
+	movl	$(\l * PUD_SIZE) | _PAGE_PRESENT | _PAGE_RW | _PAGE_PSE, (\l * 8)(%ebp,%eax,8)
+	movl	%ecx, (\l * 8 + 4)(%ebp,%eax,8)
+	.endr
+	incl	%ecx
+	cmpl	%edx, %ecx
+	jc	1b
+
+	leal	(_PAGE_PRESENT | _PAGE_RW)(%ebp), %edx
+	addl	$PAGE_SIZE, %ebp
+
+	xorl	%eax, %eax
+	movl	%ebp, %edi
+	movl	$PAGE_SIZE / 4, %ecx
+	rep	stosl
+
+	movl	%edx, (%ebp)
+	movl	%ebp, %cr3
+
+	/* Enable PAE */
+	movl	%cr4, %eax
+	btsl	$X86_CR4_PAE_BIT, %eax
+	movl	%eax, %cr4
+
+	/* Enable long mode */
+	movl	$MSR_EFER, %ecx
+	rdmsr
+	btsl	$_EFER_LME, %eax
+	wrmsr
+
+	/* Set up long return to 64-bit mode */
+	leal	rva(2f)(%ebx), %eax
+	pushl	$__SL64_CS
+	pushl	%eax
+
+	/* Enable paging */
+	movl	$CR0_STATE, %eax
+	movl	%eax, %cr0
+	lretl
+
+	.code64
+	UNWIND_HINT_END_OF_STACK
+2:	andq	$~0xf, %rsp
+	movq	%rsi, %r15
+	movq	%rsi, %rdi
+	callq	__pi_sl_main
+	movq	%r15, %rsi
+	jmp	startup_64
+SYM_CODE_END(sl_stub_entry)
+
+	.code32
+SYM_FUNC_START_LOCAL(sl_find_mle_base)
+	/* %ecx has PDPT, get first PD */
+	movl	(%ebp), %eax
+	andl	$(PAGE_MASK), %eax
+	/* Get first PT from first PDE */
+	movl	(%eax), %eax
+	andl	$(PAGE_MASK), %eax
+	/* Get MLE base from first PTE */
+	movl	(%eax), %eax
+	andl	$(PAGE_MASK), %eax
+
+	movl	%eax, rva(__pi_sl_mle_start)(%ebx)
+	RET
+SYM_FUNC_END(sl_find_mle_base)
+
+SYM_FUNC_START_LOCAL(sl_check_buffer_mle_overlap)
+	/* %ecx: buffer begin %edx: buffer end */
+	/* %ebx: MLE begin %edi: MLE end */
+	/* %eax: region may be inside MLE */
+
+	cmpl	%edi, %ecx
+	jb	.Lnext_check
+	cmpl	%edi, %edx
+	jbe	.Lnext_check
+	jmp	.Lvalid /* Buffer above MLE */
+
+.Lnext_check:
+	cmpl	%ebx, %edx
+	ja	.Linside_check
+	cmpl	%ebx, %ecx
+	jae	.Linside_check
+	jmp	.Lvalid /* Buffer below MLE */
+
+.Linside_check:
+	cmpl	$0, %eax
+	jz	.Linvalid
+	cmpl	%ebx, %ecx
+	jb	.Linvalid
+	cmpl	%edi, %edx
+	ja	.Linvalid
+	jmp	.Lvalid /* Buffer in MLE */
+
+.Linvalid:
+	TXT_RESET $(SL_ERROR_MLE_BUFFER_OVERLAP)
+
+.Lvalid:
+	RET
+SYM_FUNC_END(sl_check_buffer_mle_overlap)
+
+SYM_FUNC_START_LOCAL(sl_txt_verify_os_mle_struct)
+	pushl	%ebx
+	/*
+	 * %eax points to the base of the OS-MLE struct. Need to also
+	 * read some values from the OS-SINIT struct too.
+	 */
+	movl	-8(%eax), %ecx
+	/* Skip over OS to MLE data section and size of OS-SINIT structure */
+	leal	(%eax, %ecx), %edx
+
+	/* Load MLE image base absolute offset */
+	movl	rva(__pi_sl_mle_start)(%ebx), %ebx
+
+	/* Verify the value of the low PMR base. It should always be 0. */
+	movl	SL_vtd_pmr_lo_base(%edx), %esi
+	cmpl	$0, %esi
+	jz	.Lvalid_pmr_base
+	TXT_RESET $(SL_ERROR_LO_PMR_BASE)
+
+.Lvalid_pmr_base:
+	/* Grab some values from OS-SINIT structure */
+	movl	SL_mle_size(%edx), %edi
+	addl	%ebx, %edi
+	jc	.Loverflow_detected
+	movl	SL_vtd_pmr_lo_size(%edx), %esi
+
+	/* Check the AP wake block */
+	movl	SL_ap_wake_block(%eax), %ecx
+	movl	SL_ap_wake_block_size(%eax), %edx
+	addl	%ecx, %edx
+	jc	.Loverflow_detected
+	pushl	%eax
+	xorl	%eax, %eax
+	call	sl_check_buffer_mle_overlap
+	popl	%eax
+	cmpl	%esi, %edx
+	ja	.Lbuffer_beyond_pmr
+
+	/*
+	 * Check the boot params. Note during a UEFI boot, the boot
+	 * params will be inside the MLE image. Test for this case
+	 * in the overlap case.
+	 */
+	movl	SL_boot_params_addr(%eax), %ecx
+	movl	$(PAGE_SIZE), %edx
+	addl	%ecx, %edx
+	jc	.Loverflow_detected
+	pushl	%eax
+	movl	$1, %eax
+	call	sl_check_buffer_mle_overlap
+	popl	%eax
+	cmpl	%esi, %edx
+	ja	.Lbuffer_beyond_pmr
+
+	/* Check that the AP wake block is big enough */
+	cmpl	$(sl_txt_ap_wake_end - sl_txt_ap_wake_begin), \
+		SL_ap_wake_block_size(%eax)
+	jae	.Lwake_block_ok
+	TXT_RESET $(SL_ERROR_WAKE_BLOCK_TOO_SMALL)
+
+.Lwake_block_ok:
+	popl	%ebx
+	RET
+
+.Loverflow_detected:
+	TXT_RESET $(SL_ERROR_INTEGER_OVERFLOW)
+
+.Lbuffer_beyond_pmr:
+	TXT_RESET $(SL_ERROR_BUFFER_BEYOND_PMR)
+SYM_FUNC_END(sl_txt_verify_os_mle_struct)
+
+SYM_CODE_START_LOCAL(sl_txt_ap_entry)
+	UNWIND_HINT_END_OF_STACK
+	/*
+	 * AP entry point, first order of business is to find where we are and
+	 * save it in %ebx.
+	 */
+
+	/* Read physical base of heap into EAX */
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_HEAP_BASE), %eax
+	/* Read the size of the BIOS data into ECX (first 8 bytes) */
+	movl	(%eax), %ecx
+	/* Skip over BIOS data and size of OS to MLE data section */
+	leal	8(%eax, %ecx), %eax
+
+	/* Saved %ebx from the BSP and stash OS-MLE pointer */
+	movl	(SL_mle_scratch + SL_SCRATCH_AP_EBX)(%eax), %ebx
+
+	/* Save TXT info ptr in %edi for call to sl_txt_load_regs */
+	movl	SL_txt_info(%eax), %edi
+
+	/*
+	 * Only the %cs and %ds segments are known good after waking the AP,
+	 * as with entry on the BSP. First locate a stack to use then establish
+	 * a new GDT and segments.
+	 */
+
+	/* Lock and get our stack index */
+	movl	$1, %ecx
+.Lspin:
+	xorl	%eax, %eax
+	lock cmpxchgl	%ecx, rva(sl_txt_spin_lock)(%ebx)
+	pause
+	jnz	.Lspin
+
+	/* Increment the stack index and use the next value inside lock */
+	incl	rva(sl_txt_stack_index)(%ebx)
+	movl	rva(sl_txt_stack_index)(%ebx), %eax
+
+	/* Unlock */
+	movl	$0, rva(sl_txt_spin_lock)(%ebx)
+
+	/* Location of the relocated AP wake block */
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %ecx
+
+	/* Load reloc GDT, set segment regs and lret to __SL32_CS */
+	lgdt	(sl_ap_gdt_desc - sl_txt_ap_wake_begin)(%ecx)
+
+	movl	$(__SL32_DS), %edx
+	movw	%dx, %ds
+	movw	%dx, %es
+	movw	%dx, %fs
+	movw	%dx, %gs
+	movw	%dx, %ss
+
+	/* Load our reloc AP stack */
+	movl	$(SL_BOOT_STACK_SIZE), %edx
+	mull	%edx
+	leal	(sl_stacks_end - sl_txt_ap_wake_begin)(%ecx), %esp
+	subl	%eax, %esp
+
+	/* Switch to AP code segment */
+	leal	rva(.Lsl_ap_cs)(%ebx), %eax
+	pushl	$(__SL32_CS)
+	pushl	%eax
+	lret
+
+.Lsl_ap_cs:
+	UNWIND_HINT_END_OF_STACK
+	/* Load the relocated AP IDT */
+	lidt	(sl_ap_idt_desc - sl_txt_ap_wake_begin)(%ecx)
+
+	/* Fixup MTRRs and misc enable MSR on APs too */
+	call	sl_txt_load_regs
+
+	/* Enable SMI with GETSEC[SMCTRL] */
+	GETSEC $(SMX_X86_GETSEC_SMCTRL)
+
+	/* IRET-to-self can be used to enable NMIs which SENTER disabled */
+	leal	rva(.Lnmi_enabled_ap)(%ebx), %eax
+	pushfl
+	pushl	$(__SL32_CS)
+	pushl	%eax
+	iret
+
+.Lnmi_enabled_ap:
+	UNWIND_HINT_END_OF_STACK
+	/* Put APs in X2APIC mode like the BSP */
+	movl	$(MSR_IA32_APICBASE), %ecx
+	rdmsr
+	orl	$(XAPIC_ENABLE | X2APIC_ENABLE), %eax
+	wrmsr
+
+	/*
+	 * Basically done, increment the CPU count and jump off to the AP
+	 * wake block to wait.
+	 */
+	lock incl	rva(sl_txt_cpu_count)(%ebx)
+
+	/*
+	 * Final jump to the AP wake block (see comment below). Here the APs
+	 * will idle until the Secure Launch SMP MONITOR/MWAIT framework
+	 * releases them to mainline kernel control.
+	 */
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %eax
+	jmp	*%eax
+	int3
+SYM_CODE_END(sl_txt_ap_entry)
+
+SYM_FUNC_START_LOCAL(sl_txt_reloc_ap_wake)
+	/*
+	 * What is called the "AP wake block" is simply a chunk of protected
+	 * memory that the bootloader handed the MLE. The MLE implementation will
+	 * shuffle the AP entry point code from here in the setup kernel into this wake
+	 * block where it cannot be overwritten by kernel decompression, relocation, etc.
+	 */
+
+	/* Save boot params register */
+	pushl	%esi
+
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %edi
+
+	/* Fixup AP IDT and GDT descriptor before relocating */
+	leal	rva(sl_ap_idt_desc)(%ebx), %eax
+	addl	%edi, 2(%eax)
+	leal	rva(sl_ap_gdt_desc)(%ebx), %eax
+	addl	%edi, 2(%eax)
+
+	/*
+	 * Copy the AP wake code and AP GDT/IDT to the protected wake block
+	 * provided by the loader. Destination already in %edi.
+	 */
+	movl	$(sl_txt_ap_wake_end - sl_txt_ap_wake_begin), %ecx
+	leal	rva(sl_txt_ap_wake_begin)(%ebx), %esi
+	rep movsb
+
+	/* Setup the IDT for the APs to use in the relocation block */
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %ecx
+	addl	$(sl_ap_idt - sl_txt_ap_wake_begin), %ecx
+	xorl	%edx, %edx
+
+	/* Form the default reset vector relocation address */
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %esi
+	addl	$(sl_txt_int_reset - sl_txt_ap_wake_begin), %esi
+
+1:
+	cmpw	$(NR_VECTORS), %dx
+	jz	.Lap_idt_done
+
+	cmpw	$(X86_TRAP_NMI), %dx
+	jz	2f
+
+	/* Load all other fixed vectors with reset handler */
+	movl	%esi, %eax
+	movw	%ax, (IDT_VECTOR_LO_BITS)(%ecx)
+	shrl	$16, %eax
+	movw	%ax, (IDT_VECTOR_HI_BITS)(%ecx)
+	jmp	3f
+
+2:
+	/* Load single wake NMI IPI vector at the relocation address */
+	movl	rva(sl_txt_ap_wake_block)(%ebx), %eax
+	addl	$(sl_txt_int_nmi - sl_txt_ap_wake_begin), %eax
+	movw	%ax, (IDT_VECTOR_LO_BITS)(%ecx)
+	shrl	$16, %eax
+	movw	%ax, (IDT_VECTOR_HI_BITS)(%ecx)
+
+3:
+	incw	%dx
+	addl	$8, %ecx
+	jmp	1b
+
+.Lap_idt_done:
+	popl	%esi
+	RET
+SYM_FUNC_END(sl_txt_reloc_ap_wake)
+
+SYM_FUNC_START_LOCAL(sl_txt_load_regs)
+	/* Save base pointer register */
+	pushl	%ebx
+
+	/*
+	 * On Intel, the original variable MTRRs and Misc Enable MSR are
+	 * restored on the BSP at early boot. Each AP will also restore
+	 * its MTRRs and Misc Enable MSR.
+	 */
+	pushl	%edi
+	addl	$(SL_saved_bsp_mtrrs), %edi
+	movl	(%edi), %ebx
+	pushl	%ebx /* default_mem_type lo */
+	addl	$4, %edi
+	movl	(%edi), %ebx
+	pushl	%ebx /* default_mem_type hi */
+	addl	$4, %edi
+	movl	(%edi), %ebx /* mtrr_vcnt lo, don't care about hi part */
+	addl	$8, %edi /* now at MTRR pair array */
+	/* Write the variable MTRRs */
+	movl	$(MSR_MTRRphysBase0), %ecx
+1:
+	cmpl	$0, %ebx
+	jz	2f
+
+	movl	(%edi), %eax /* MTRRphysBaseX lo */
+	addl	$4, %edi
+	movl	(%edi), %edx /* MTRRphysBaseX hi */
+	wrmsr
+	addl	$4, %edi
+	incl	%ecx
+	movl	(%edi), %eax /* MTRRphysMaskX lo */
+	addl	$4, %edi
+	movl	(%edi), %edx /* MTRRphysMaskX hi */
+	wrmsr
+	addl	$4, %edi
+	incl	%ecx
+
+	decl	%ebx
+	jmp	1b
+2:
+	/* Write the default MTRR register */
+	popl	%edx
+	popl	%eax
+	movl	$(MSR_MTRRdefType), %ecx
+	wrmsr
+
+	/* Return to beginning and write the misc enable msr */
+	popl	%edi
+	addl	$(SL_saved_misc_enable_msr), %edi
+	movl	(%edi), %eax /* saved_misc_enable_msr lo */
+	addl	$4, %edi
+	movl	(%edi), %edx /* saved_misc_enable_msr hi */
+	movl	$(MSR_IA32_MISC_ENABLE), %ecx
+	wrmsr
+
+	popl	%ebx
+	RET
+SYM_FUNC_END(sl_txt_load_regs)
+
+SYM_FUNC_START_LOCAL(sl_txt_wake_aps)
+	/* Save boot params register */
+	pushl	%esi
+
+	/*
+	 * First setup the MLE join structure and load it into the TXT register.
+	 * This structure defines the information needed to wake the APs and
+	 * safely be joined with the DRTM.
+	 */
+	leal	rva(sl_gdt)(%ebx), %eax
+	leal	rva(sl_txt_ap_entry)(%ebx), %ecx
+	leal	rva(sl_smx_rlp_mle_join)(%ebx), %edx
+	movl	%eax, SL_rlp_gdt_base(%edx)
+	movl	%ecx, SL_rlp_entry_point(%edx)
+	movl	%edx, (TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_MLE_JOIN)
+
+	/* Another TXT heap walk to find various values needed to wake APs */
+	movl	(TXT_PRIV_CONFIG_REGS_BASE + TXT_CR_HEAP_BASE), %eax
+	/* At BIOS data size, find the number of logical processors */
+	movl	(SL_num_logical_procs + 8)(%eax), %edx
+	/* Skip over BIOS data */
+	movl	(%eax), %ecx
+	addl	%ecx, %eax
+	/* Skip over OS to MLE */
+	movl	(%eax), %ecx
+	addl	%ecx, %eax
+	/* At OS-SNIT size, get capabilities to know how to wake up the APs */
+	movl	(SL_capabilities + 8)(%eax), %esi
+	/* Skip over OS to SNIT */
+	movl	(%eax), %ecx
+	addl	%ecx, %eax
+	/* At SINIT-MLE size, get the AP wake MONITOR address */
+	movl	(SL_rlp_wakeup_addr + 8)(%eax), %edi
+
+	/* Determine how to wake up the APs */
+	testl	$(1 << TXT_SINIT_MLE_CAP_RLP_WAKE_MONITOR), %esi
+	jz	.Lwake_getsec
+
+	/* Wake using MWAIT MONITOR */
+	movl	$1, (%edi)
+	jmp	.Laps_awake
+
+.Lwake_getsec:
+	/* Wake using GETSEC(WAKEUP) */
+	GETSEC	$(SMX_X86_GETSEC_WAKEUP)
+
+.Laps_awake:
+	/*
+	 * All of the APs are woken up and rendezvous in the relocated wake
+	 * block starting at sl_txt_ap_wake_begin. Wait for all of them to
+	 * halt.
+	 */
+	pause
+	cmpl	rva(sl_txt_cpu_count)(%ebx), %edx
+	jne	.Laps_awake
+
+	popl	%esi
+	RET
+SYM_FUNC_END(sl_txt_wake_aps)
+
+	__INITDATA
+/* This is the beginning of the relocated AP wake code block */
+sl_txt_ap_wake_begin:
+	/*
+	 * Note on the stack layout for the APs. The individual 128 byte stacks
+	 * fully occupy 2 cache lines. The first is for the MONITOR address
+	 * and the second contains the APICID written to it. Note the whole
+	 * cache line is unused other than the monitor field; nothing else should
+	 * write the cache line and wake the monitor.
+	 *
+	 * esp -> +-----------+
+	 *        |  APIC ID  |
+	 *        |-----------|
+	 *        |  PAD[15]  |
+	 *        |-----------|
+	 *        |  PAD[15]  |
+	 *        |-----------|
+	 *        |  MONITOR  |
+	 *        +-----------+
+	 */
+
+	/* Get the LAPIC ID for each AP and stash it on the stack */
+	movl	$(MSR_IA32_X2APIC_APICID), %ecx
+	rdmsr
+	pushl	%eax
+
+	/*
+	 * Get a pointer to the monitor location on this APs stack to test below
+	 * after mwait returns. Currently %esp points to just past the pushed APIC
+	 * ID value.
+	 */
+	movl	%esp, %edi
+	subl	$(SL_BOOT_STACK_SIZE - 4), %edi
+	movl	$0, (%edi)
+
+1:
+	/* Load eax and clear ecx/edx so no invalid extensions or hints are passed to monitor */
+	movl	%edi, %eax
+	xorl	%ecx, %ecx
+	xorl	%edx, %edx
+
+	/*
+	 * Arm the monitor and wait for it to be triggered by the SMP bringup code. The mwait
+	 * instruction can return for a number of reasons. Test to see if it returned
+	 * because the monitor was written to.
+	 */
+	monitor
+
+	cmpl	$0, (%eax)
+	jnz	2f
+
+	/* Clear eax since there are no hints sent to mwait */
+	xorl	%eax, %eax
+
+	mwait
+	jmp	1b
+
+2:
+	/*
+	 * This is the long absolute jump to the 32b Secure Launch protected mode stub
+	 * code in sl_trampoline_start32() in the rmpiggy. The jump address will be
+	 * fixed in the SMP boot code when the first AP is brought up. This whole area
+	 * is provided and protected in the memory map by the prelaunch code.
+	 */
+	.byte	0xea
+sl_ap_jmp_offset:
+	.long	0x00000000
+	.word	__SL32_CS
+
+SYM_CODE_START_LOCAL(sl_txt_int_nmi)
+	/* NMI context, just IRET */
+	iret
+SYM_CODE_END(sl_txt_int_nmi)
+
+SYM_FUNC_START_LOCAL(sl_txt_int_reset)
+	TXT_RESET $(SL_ERROR_INV_AP_INTERRUPT)
+SYM_FUNC_END(sl_txt_int_reset)
+
+	.balign 8
+SYM_DATA_START_LOCAL(sl_ap_idt_desc)
+	.word	sl_ap_idt_end - sl_ap_idt - 1		/* Limit */
+	.long	sl_ap_idt - sl_txt_ap_wake_begin	/* Base */
+SYM_DATA_END_LABEL(sl_ap_idt_desc, SYM_L_LOCAL, sl_ap_idt_desc_end)
+
+	.balign 8
+SYM_DATA_START_LOCAL(sl_ap_idt)
+	.rept	NR_VECTORS
+	.word	0x0000		/* Offset 15 to 0 */
+	.word	__SL32_CS	/* Segment selector */
+	.word	0x8e00		/* Present, DPL=0, 32b Vector, Interrupt */
+	.word	0x0000		/* Offset 31 to 16 */
+	.endr
+SYM_DATA_END_LABEL(sl_ap_idt, SYM_L_LOCAL, sl_ap_idt_end)
+
+	.balign 8
+SYM_DATA_START_LOCAL(sl_ap_gdt_desc)
+	.word	sl_ap_gdt_end - sl_ap_gdt - 1
+	.long	sl_ap_gdt - sl_txt_ap_wake_begin
+SYM_DATA_END_LABEL(sl_ap_gdt_desc, SYM_L_LOCAL, sl_ap_gdt_desc_end)
+
+	.balign	8
+SYM_DATA_START_LOCAL(sl_ap_gdt)
+	.quad	0x0000000000000000	/* NULL */
+	.quad	0x00cf9a000000ffff	/* __SL32_CS */
+	.quad	0x00cf92000000ffff	/* __SL32_DS */
+SYM_DATA_END_LABEL(sl_ap_gdt, SYM_L_LOCAL, sl_ap_gdt_end)
+
+	/* Small stacks for BSP and APs to work with */
+	.balign 64
+SYM_DATA_START_LOCAL(sl_stacks)
+	.fill (SL_MAX_CPUS * SL_BOOT_STACK_SIZE), 1, 0
+SYM_DATA_END_LABEL(sl_stacks, SYM_L_LOCAL, sl_stacks_end)
+
+/* This is the end of the relocated AP wake code block */
+sl_txt_ap_wake_end:
+
+	.balign 8
+SYM_DATA_START_LOCAL(sl_gdt_desc)
+	.word	sl_gdt_end - sl_gdt - 1
+	.long	sl_gdt - sl_gdt_desc
+SYM_DATA_END_LABEL(sl_gdt_desc, SYM_L_LOCAL, sl_gdt_desc_end)
+
+	.balign	8
+SYM_DATA_START_LOCAL(sl_gdt)
+	.quad	0x0000000000000000	/* NULL */
+	.quad	0x00cf9a000000ffff	/* __SL32_CS */
+	.quad	0x00cf92000000ffff	/* __SL32_DS */
+	.quad	0x00af9a000000ffff	/* __SL64_CS */
+SYM_DATA_END_LABEL(sl_gdt, SYM_L_LOCAL, sl_gdt_end)
+
+	.balign 8
+SYM_DATA_START_LOCAL(sl_smx_rlp_mle_join)
+	.long	sl_gdt_end - sl_gdt - 1	/* GDT limit */
+	.long	0x00000000		/* GDT base */
+	.long	__SL32_CS	/* Seg Sel - CS (DS, ES, SS = seg_sel+8) */
+	.long	0x00000000	/* Entry point physical address */
+SYM_DATA_END(sl_smx_rlp_mle_join)
+
+SYM_DATA_LOCAL(sl_txt_spin_lock, .long 0x00000000)
+
+SYM_DATA_LOCAL(sl_txt_stack_index, .long 0x00000000)
+
+SYM_DATA_LOCAL(sl_txt_cpu_count, .long 0x00000000)
+
+SYM_DATA_LOCAL(sl_txt_ap_wake_block, .long 0x00000000)
+
+	__INITRODATA
+	.balign 8
+	/*
+	 * The MLE Header per the TXT Specification, section 2.1
+	 * MLE capabilities, see table 4. Capabilities set:
+	 * bit 0: Support for GETSEC[WAKEUP] for RLP wakeup
+	 * bit 1: Support for RLP wakeup using MONITOR address
+	 * bit 2: The ECX register will contain the pointer to the MLE page table
+	 * bit 5: TPM 1.2 family: Details/authorities PCR usage support
+	 * bit 9: Supported format of TPM 2.0 event log - TCG compliant
+	 */
+SYM_DATA_START(mle_header)
+0:	.long	0x9082ac5a			/* UUID0 */
+	.long	0x74a7476f			/* UUID1 */
+	.long	0xa2555c0f			/* UUID2 */
+	.long	0x42b651cb			/* UUID3 */
+	.long	0x00000034			/* MLE header size */
+	.long	0x00020002			/* MLE version 2.2 */
+	.long	__sl_stub_entry_offset - 0b	/* Linear entry point of MLE (virt. address) */
+	.long	0x00000000			/* First valid page of MLE */
+	.long	0x00000000			/* Offset within binary of first byte of MLE */
+	.long	__sl_mle_end_offset - 0b	/* Offset within binary of last byte + 1 of MLE */
+	.long	0x00000227			/* Bit vector of MLE-supported capabilities */
+	.long	0x00000000			/* Starting linear address of command line (unused) */
+	.long	0x00000000			/* Ending linear address of command line (unused) */
+SYM_DATA_END(mle_header)
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 4711a35e706c..fc22a2b9c7d1 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -537,4 +537,9 @@ xen_elfnote_phys32_entry_value =
 	ABSOLUTE(xen_elfnote_phys32_entry) + ABSOLUTE(pvh_start_xen - LOAD_OFFSET);
 #endif
 
+#ifdef CONFIG_SECURE_LAUNCH
+__sl_stub_entry_offset = ABSOLUTE(mle_header) + ABSOLUTE(sl_stub_entry - _text);
+__sl_mle_end_offset    = ABSOLUTE(mle_header) + ABSOLUTE(__bss_start - _text);
+#endif
+
 #include "../boot/startup/exports.h"
diff --git a/arch/x86/tools/relocs.c b/arch/x86/tools/relocs.c
index e5a2b9a912d1..d4a5fc607c54 100644
--- a/arch/x86/tools/relocs.c
+++ b/arch/x86/tools/relocs.c
@@ -88,6 +88,7 @@ static const char * const	sym_regex_kernel[S_NSYMTYPES] = {
 	"__end_rodata|"
 	"__end_rodata_aligned|"
 	"__initramfs_start|"
+	"__sl_.+offset|"
 	"(jiffies|jiffies_64)|"
 #if ELF_BITS == 64
 	"__end_rodata_hpage_align|"
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:03 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:03 -0700
Subject: [PATCH v16 31/38] x86/slaunch: Secure Launch kernel early boot initialization
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-32-ross.philipson@gmail.com>

The sl_main() routine is responsible for measuring configuration and module
information before it is used by the kernel. An example of entities
measured on Intel x86 are the boot params, the kernel command line,
the TXT heap, any external initramfs, etc. In addition this routine
does some early setup and validation of the environment like locating
the TPM event log and validating the location of various buffers to
ensure they are protected and not overlapping.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/startup/sl_main.c | 610 ++++++++++++++++++++++++++++++++
 1 file changed, 610 insertions(+)

diff --git a/arch/x86/boot/startup/sl_main.c b/arch/x86/boot/startup/sl_main.c
index 1982cfb461dd..0110392470d0 100644
--- a/arch/x86/boot/startup/sl_main.c
+++ b/arch/x86/boot/startup/sl_main.c
@@ -15,14 +15,624 @@
 #include <linux/efi.h>
 #include <linux/slr_table.h>
 #include <linux/slaunch.h>
+#include <linux/tpm_ptp.h>
 
 #include "tpm.h"
 
+#define CAPS_VARIABLE_MTRR_COUNT_MASK	0xff
+
+#define SL_TPM_LOG		1
+#define SL_TPM2_LOG		2
+
+static void *evtlog_base;
+static u32 evtlog_size;
+static struct txt_heap_event_log_pointer2_1_element *log21_elem;
+static u32 tpm_log_ver = SL_TPM_LOG;
+static u32 tpm_num_algs;
+static struct tcg_efi_specid_event_algs *tpm_algs;
+static u8 event_buf[PAGE_SIZE];
+
+/* Simple instance of a TPM chip object */
+static struct tpm_chip chip __initdata;
+
+static struct slr_table *slrt __initdata;
+
 u32 sl_cpu_type __initdata;
 u32 sl_mle_start __initdata;
 
 void sl_main(void *bootparams);
 
+static void *txt_regs = (void *)TXT_PRIV_CONFIG_REGS_BASE;
+#define sl_txt_read(r) readq(txt_regs + r)
+#define sl_txt_write(r, v) writeq(v, txt_regs + r)
+
+static void *txt_heap __initdata;
+static struct sl_txt_heap_info txt_heap_map[TXT_SINIT_TABLE_MAX] __initdata;
+
+struct sl_txt_heap_info * __init sl_txt_get_heap_map(void);
+void * __init sl_txt_get_heap_table(void *heap, u8 index);
+
+struct sl_txt_heap_info * __init sl_txt_get_heap_map(void)
+{
+	return txt_heap_map;
+}
+
+void * __init sl_txt_get_heap_table(void *heap, u8 index)
+{
+	return heap + txt_heap_map[index].offset;
+}
+
+static void __init txt_parse_heap_map(void *heap)
+{
+	void *tmp = heap;
+
+	for (u8 i = 0; i < TXT_SINIT_TABLE_MAX; i++) {
+		txt_heap_map[i].size = *((u64 *) tmp);
+		txt_heap_map[i].offset = tmp - heap + sizeof(txt_heap_map[i].size);
+		tmp += txt_heap_map[i].size;
+	}
+}
+
+void __warn_printk(const char *fmt, ...)
+{
+}
+
+static void __noreturn __init sl_txt_reset(u64 error)
+{
+	/* Reading the E2STS register acts as a barrier for TXT registers */
+	sl_txt_write(TXT_CR_ERRORCODE, error);
+	sl_txt_read(TXT_CR_E2STS);
+	sl_txt_write(TXT_CR_CMD_UNLOCK_MEM_CONFIG, 1);
+	sl_txt_read(TXT_CR_E2STS);
+	sl_txt_write(TXT_CR_CMD_RESET, 1);
+
+	for ( ; ; )
+		asm volatile ("hlt");
+
+	unreachable();
+}
+
+static inline u64 __init sl_rdmsr(u32 reg)
+{
+	struct msr m;
+
+	raw_rdmsr(reg, &m);
+
+	return m.q;
+}
+
+static struct slr_table *__init sl_locate_and_validate_slrt(void)
+{
+	struct txt_os_mle_data *os_mle_data;
+	struct slr_table *slrt;
+
+	os_mle_data = sl_txt_get_heap_table(txt_heap, TXT_OS_MLE_DATA_TABLE);
+
+	if (!os_mle_data->slrt)
+		sl_txt_reset(SL_ERROR_INVALID_SLRT);
+
+	slrt = (struct slr_table *)os_mle_data->slrt;
+
+	if (slrt->magic != SLR_TABLE_MAGIC)
+		sl_txt_reset(SL_ERROR_INVALID_SLRT);
+
+	if (slrt->architecture != SLR_INTEL_TXT)
+		sl_txt_reset(SL_ERROR_INVALID_SLRT);
+
+	return slrt;
+}
+
+/*
+ * This is a validation routine that allows checking if a block of memory
+ * is protected from external access by being in a PMR range. If allow_hi is
+ * set, ranges above 4GB are allowed.
+ */
+static void __init sl_check_pmr_coverage(void *base, u32 size, bool allow_hi)
+{
+	struct txt_os_sinit_data *os_sinit_data;
+	void *end = base + size;
+
+	if (!(sl_cpu_type & SL_CPU_INTEL))
+		return;
+
+	os_sinit_data = sl_txt_get_heap_table(txt_heap, TXT_OS_SINIT_DATA_TABLE);
+
+	if ((u64)end >= SZ_4G && (u64)base < SZ_4G)
+		sl_txt_reset(SL_ERROR_REGION_STRADDLE_4GB);
+
+	/*
+	 * Note that the late stub code validates that the hi PMR covers
+	 * all memory above 4G. At this point the code can only check that
+	 * regions are within the hi PMR but that is sufficient.
+	 */
+	if ((u64)end > SZ_4G && (u64)base >= SZ_4G) {
+		if (allow_hi) {
+			if (end >= (void *)(os_sinit_data->vtd_pmr_hi_base +
+					    os_sinit_data->vtd_pmr_hi_size))
+				sl_txt_reset(SL_ERROR_BUFFER_BEYOND_PMR);
+		} else {
+			sl_txt_reset(SL_ERROR_REGION_ABOVE_4GB);
+		}
+	}
+
+	if (end >= (void *)os_sinit_data->vtd_pmr_lo_size)
+		sl_txt_reset(SL_ERROR_BUFFER_BEYOND_PMR);
+}
+
+/*
+ * Some MSRs are modified by the pre-launch code including the MTRRs.
+ * The early MLE code has to restore these values. This code validates
+ * the values after they are measured.
+ */
+static void __init sl_txt_validate_msrs(struct txt_os_mle_data *os_mle_data)
+{
+	struct slr_txt_mtrr_state *saved_bsp_mtrrs;
+	u64 mtrr_caps, mtrr_def_type, mtrr_var;
+	struct slr_entry_intel_info *txt_info;
+	u64 misc_en_msr;
+	u32 vcnt, i;
+
+	txt_info = (struct slr_entry_intel_info *)os_mle_data->txt_info;
+	saved_bsp_mtrrs = &txt_info->saved_bsp_mtrrs;
+
+	mtrr_caps = sl_rdmsr(MSR_MTRRcap);
+	vcnt = (u32)(mtrr_caps & CAPS_VARIABLE_MTRR_COUNT_MASK);
+
+	if (saved_bsp_mtrrs->mtrr_vcnt > vcnt)
+		sl_txt_reset(SL_ERROR_MTRR_INV_VCNT);
+	if (saved_bsp_mtrrs->mtrr_vcnt > TXT_OS_MLE_MAX_VARIABLE_MTRRS)
+		sl_txt_reset(SL_ERROR_MTRR_INV_VCNT);
+
+	mtrr_def_type = sl_rdmsr(MSR_MTRRdefType);
+	if (saved_bsp_mtrrs->default_mem_type != mtrr_def_type)
+		sl_txt_reset(SL_ERROR_MTRR_INV_DEF_TYPE);
+
+	for (i = 0; i < saved_bsp_mtrrs->mtrr_vcnt; i++) {
+		mtrr_var = sl_rdmsr(MTRRphysBase_MSR(i));
+		if (saved_bsp_mtrrs->mtrr_pair[i].mtrr_physbase != mtrr_var)
+			sl_txt_reset(SL_ERROR_MTRR_INV_BASE);
+		mtrr_var = sl_rdmsr(MTRRphysMask_MSR(i));
+		if (saved_bsp_mtrrs->mtrr_pair[i].mtrr_physmask != mtrr_var)
+			sl_txt_reset(SL_ERROR_MTRR_INV_MASK);
+	}
+
+	misc_en_msr = sl_rdmsr(MSR_IA32_MISC_ENABLE);
+	if (txt_info->saved_misc_enable_msr != misc_en_msr)
+		sl_txt_reset(SL_ERROR_MSR_INV_MISC_EN);
+}
+
+static void __init sl_find_drtm_event_log(struct slr_table *slrt)
+{
+	struct txt_os_sinit_data *os_sinit_data;
+	struct slr_entry_log_info *log_info;
+
+	log_info = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_LOG_INFO);
+	if (!log_info)
+		sl_txt_reset(SL_ERROR_SLRT_MISSING_ENTRY);
+
+	evtlog_base = (void *)log_info->addr;
+	evtlog_size = log_info->size;
+
+	/*
+	 * For TPM 2.0, the TXT event log 2.1 extended data structure has to also
+	 * be located to find the actual log.
+	 */
+	os_sinit_data = sl_txt_get_heap_table(txt_heap, TXT_OS_SINIT_DATA_TABLE);
+
+	/*
+	 * Only support version 6 and later that properly handle the
+	 * list of ExtDataElements in the OS-SINIT structure.
+	 */
+	if (os_sinit_data->version < 6)
+		sl_txt_reset(SL_ERROR_OS_SINIT_BAD_VERSION);
+
+	/* Find the TPM2.0 logging extended heap element */
+	log21_elem = txt_find_log2_1_element(os_sinit_data);
+
+	/* If found, this implies TPM2 log and family */
+	if (log21_elem)
+		tpm_log_ver = SL_TPM2_LOG;
+}
+
+static void __init sl_validate_event_log_buffer(void)
+{
+	struct txt_os_sinit_data *os_sinit_data;
+	void *mle_base, *mle_end;
+	void *evtlog_end;
+	void *txt_end;
+
+	if ((u64)evtlog_size > (LLONG_MAX - (u64)evtlog_base))
+		sl_txt_reset(SL_ERROR_INTEGER_OVERFLOW);
+	evtlog_end = evtlog_base + evtlog_size;
+
+	txt_end = txt_heap + sl_txt_read(TXT_CR_HEAP_SIZE);
+	os_sinit_data = sl_txt_get_heap_table(txt_heap, TXT_OS_SINIT_DATA_TABLE);
+
+	mle_base = (void *)(u64)sl_mle_start;
+	mle_end = mle_base + os_sinit_data->mle_size;
+
+	/*
+	 * This check is to ensure the event log buffer does not overlap with
+	 * the MLE image.
+	 */
+	if (evtlog_base >= mle_end && evtlog_end > mle_end)
+		goto pmr_check; /* above */
+
+	if (evtlog_end <= mle_base && evtlog_base < mle_base)
+		goto pmr_check; /* below */
+
+	sl_txt_reset(SL_ERROR_MLE_BUFFER_OVERLAP);
+
+pmr_check:
+	/*
+	 * The TXT heap is protected by the DPR. If the TPM event log is
+	 * inside the TXT heap, there is no need for a PMR check.
+	 */
+	if (evtlog_base > txt_heap && evtlog_end < txt_end)
+		return;
+
+	sl_check_pmr_coverage(evtlog_base, evtlog_size, true);
+}
+
+static void __init sl_find_event_log_algorithms(void)
+{
+	struct tcg_efi_specid_event_head *efi_head =
+		(struct tcg_efi_specid_event_head *)(evtlog_base +
+						     sizeof(struct tcg_pcr_event));
+	u32 i;
+
+	if (efi_head->num_algs == 0)
+		sl_txt_reset(SL_ERROR_TPM_INVALID_ALGS);
+
+	tpm_algs = &efi_head->digest_sizes[0];
+	tpm_num_algs = efi_head->num_algs;
+
+	for (i = 0; i < tpm_num_algs; i++) {
+		if (tpm_algs[i].digest_size > TPM2_MAX_DIGEST_SIZE)
+			sl_txt_reset(SL_ERROR_TPM_INVALID_ALGS);
+		/* Alg ID 0 is invalid and maps to TPM_ALG_ERROR */
+		if (tpm_algs[i].alg_id == TPM_ALG_ERROR)
+			sl_txt_reset(SL_ERROR_TPM_INVALID_ALGS);
+	}
+}
+
+static void __init sl_tpm1_extend(u32 pcr, u32 event_type,
+				  const u8 *data, u32 length,
+				  const u8 *event_data, u32 event_size)
+{
+	u8 sha1_hash[SHA1_DIGEST_SIZE] = {0};
+	struct tcg_pcr_event *pcr_event;
+	u32 total_size;
+
+	/* Clear on each use */
+	memset(event_buf, 0, PAGE_SIZE);
+
+	pcr_event = (struct tcg_pcr_event *)event_buf;
+	pcr_event->pcr_idx = pcr;
+	pcr_event->event_type = event_type;
+	if (length > 0) {
+		sha1(data, length, &sha1_hash[0]);
+		memcpy(&pcr_event->digest[0], &sha1_hash[0], SHA1_DIGEST_SIZE);
+	}
+	pcr_event->event_size = event_size;
+	if (event_size > 0)
+		memcpy((u8 *)pcr_event + sizeof(*pcr_event),
+		       event_data, event_size);
+
+	total_size = sizeof(*pcr_event) + event_size;
+
+	/* Do the TPM extend then log the event */
+	if (tpm1_pcr_extend(&chip, pcr, &sha1_hash[0]))
+		sl_txt_reset(SL_ERROR_TPM_EXTEND);
+
+	if (tpm_log_event(evtlog_base, evtlog_size, total_size, pcr_event))
+		sl_txt_reset(SL_ERROR_TPM_LOGGING_FAILED);
+}
+
+static void __init sl_tpm2_extend(u32 pcr, u32 event_type,
+				  const u8 *data, u32 length,
+				  const u8 *event_data, u32 event_size)
+{
+	struct tcg_pcr_event2_head *head;
+	struct tcg_event_field *event;
+	u8 digest[TPM2_MAX_DIGEST_SIZE];
+	u32 total_size, alg_idx;
+	u16 *alg_ptr;
+	u8 *dgst_ptr;
+	int rc;
+
+	/* Clear on each use */
+	memset(event_buf, 0, PAGE_SIZE);
+
+	head = (struct tcg_pcr_event2_head *)event_buf;
+	head->pcr_idx = pcr;
+	head->event_type = event_type;
+	total_size = sizeof(*head);
+	alg_ptr = (u16 *)(event_buf + sizeof(*head));
+
+	for (alg_idx = 0; alg_idx < tpm_num_algs; alg_idx++) {
+		memset(digest, 0, TPM2_MAX_DIGEST_SIZE);
+
+		*alg_ptr = tpm_algs[alg_idx].alg_id;
+		dgst_ptr = (u8 *)alg_ptr + sizeof(u16);
+
+		if (tpm_algs[alg_idx].alg_id == TPM_ALG_SHA256) {
+			sha256(data, length, &digest[0]);
+		} else if (tpm_algs[alg_idx].alg_id == TPM_ALG_SHA384) {
+			sha384(data, length, &digest[0]);
+		} else if (tpm_algs[alg_idx].alg_id == TPM_ALG_SHA512) {
+			sha512(data, length, &digest[0]);
+		} else if (tpm_algs[alg_idx].alg_id == TPM_ALG_SHA1) {
+			sha1(data, length, &digest[0]);
+		} else {
+			/*
+			 * If there are TPM banks in use that are not supported
+			 * in software here, the PCR in that bank will be capped with
+			 * the well-known value 1 as the Intel ACM does.
+			 */
+			digest[0] = 0x01;
+		}
+
+		memcpy(dgst_ptr, &digest[0], tpm_algs[alg_idx].digest_size);
+		total_size += tpm_algs[alg_idx].digest_size + sizeof(u16);
+		alg_ptr = (u16 *)((u8 *)alg_ptr +
+			      tpm_algs[alg_idx].digest_size + sizeof(u16));
+
+		head->count++;
+	}
+
+	event = (struct tcg_event_field *)(event_buf + total_size);
+	event->event_size = event_size;
+	if (event_size > 0)
+		memcpy((u8 *)event + sizeof(*event), event_data, event_size);
+	total_size += sizeof(*event) + event_size;
+
+	/*
+	 * Do the TPM extend then log the event. Note the digest list is packed
+	 * in the event behind the event header.
+	 */
+	rc = tpm2_pcr_extend(&chip, pcr, (struct tpm_digest *)(event_buf + sizeof(*head)),
+			     head->count);
+	if (rc)
+		sl_txt_reset(SL_ERROR_TPM_EXTEND);
+
+	if (tpm2_log_event(log21_elem, evtlog_base, evtlog_size,
+			   total_size, &event_buf[0]))
+		sl_txt_reset(SL_ERROR_TPM_LOGGING_FAILED);
+}
+
+static void __init sl_tpm_extend(u32 pcr, u32 type, const u8 *data, u32 length,
+				 const char *desc)
+{
+	if (chip.family == TPM_FAMILY_20)
+		sl_tpm2_extend(pcr, type, data, length, (const u8 *)desc, strlen(desc));
+	else
+		sl_tpm1_extend(pcr, type, data, length, (const u8 *)desc, strlen(desc));
+}
+
+static void __init sl_handle_setup_data(struct setup_data *curr,
+					struct slr_policy_entry *entry)
+{
+	struct setup_indirect *ind;
+
+	/* SETUP_INDIRECT instances have to be handled differently */
+	if (curr->type == SETUP_INDIRECT) {
+		ind = (struct setup_indirect *)((u8 *)curr +
+						offsetof(struct setup_data, data));
+
+		sl_check_pmr_coverage((void *)ind->addr, ind->len, true);
+
+		sl_tpm_extend(entry->pcr, SL_EVTYPE_SECURE_LAUNCH, (void *)ind->addr,
+			      ind->len, entry->evt_info);
+	}
+
+	sl_check_pmr_coverage(((u8 *)curr) + sizeof(*curr),
+			      curr->len, true);
+
+	sl_tpm_extend(entry->pcr, SL_EVTYPE_SECURE_LAUNCH, ((u8 *)curr) + sizeof(*curr),
+		      curr->len, entry->evt_info);
+}
+
+/*
+ * The setup_data linked list in the boot_params (if present) must be
+ * processed element by element. Indirect elements need to have their
+ * pointers followed to the actual data to measure.
+ */
+static void __init sl_extend_setup_data(struct slr_policy_entry *entry)
+{
+	struct setup_data *data = (void *)(unsigned long)entry->entity;
+
+	/*
+	 * Measure any setup_data entries including e820 extended entries.
+	 * Note that the e820 fixed entries are in the boot params structure
+	 * itself and measured there.
+	 */
+	while (data) {
+		sl_handle_setup_data(data, entry);
+		data = (void *)(unsigned long)data->next;
+	}
+}
+
+static void __init sl_extend_slrt(struct slr_policy_entry *entry)
+{
+	struct slr_table *slrt = (struct slr_table *)entry->entity;
+	struct slr_entry_intel_info *intel_info;
+	struct slr_entry_intel_info intel_tmp;
+
+	/*
+	 * In revision one of the SLRT, the only table that needs to be
+	 * measured is the Intel info table. Everything else is meta-data,
+	 * addresses and sizes. Note the size of what to measure is not set.
+	 * The flag SLR_POLICY_IMPLICIT_SIZE leaves it to the measuring code
+	 * to sort out.
+	 */
+	if (slrt->revision == 1) {
+		intel_info = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_INTEL_INFO);
+		if (!intel_info)
+			sl_txt_reset(SL_ERROR_SLRT_MISSING_ENTRY);
+
+		/*
+		 * Make a temp copy and zero out address fields since they should
+		 * not be measured.
+		 */
+		intel_tmp = *intel_info;
+		intel_tmp.boot_params_addr = 0;
+		intel_tmp.txt_heap = 0;
+
+		sl_tpm_extend(entry->pcr, SL_EVTYPE_SECURE_LAUNCH, (void *)&intel_tmp,
+			      sizeof(*intel_info), entry->evt_info);
+	}
+}
+
+static void __init sl_extend_txt_os2mle(struct slr_policy_entry *entry)
+{
+	struct txt_os_mle_data *os_mle_data;
+
+	os_mle_data = sl_txt_get_heap_table(txt_heap, TXT_OS_MLE_DATA_TABLE);
+
+	/*
+	 * Version 1 of the OS-MLE heap structure has no fields to measure. It just
+	 * has addresses and sizes and a scratch buffer.
+	 */
+	if (os_mle_data->version == 1)
+		return;
+}
+
+/*
+ * Process all policy entries and extend the measurements to the evtlog. Note
+ * that some entries need special processing which is done in subroutines.
+ */
+static void __init sl_process_extend_policy(struct slr_table *slrt)
+{
+	struct slr_entry_policy *policy;
+	u16 i;
+
+	policy = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_ENTRY_POLICY);
+	if (!policy)
+		sl_txt_reset(SL_ERROR_SLRT_MISSING_ENTRY);
+
+	for (i = 0; i < policy->nr_entries; i++) {
+		switch (policy->policy_entries[i].entity_type) {
+		case SLR_ET_SETUP_DATA:
+			sl_extend_setup_data(&policy->policy_entries[i]);
+			break;
+		case SLR_ET_SLRT:
+			sl_extend_slrt(&policy->policy_entries[i]);
+			break;
+		case SLR_ET_TXT_OS2MLE:
+			sl_extend_txt_os2mle(&policy->policy_entries[i]);
+			break;
+		case SLR_ET_UNUSED:
+			continue;
+		default:
+			sl_tpm_extend(policy->policy_entries[i].pcr,
+				      SL_EVTYPE_SECURE_LAUNCH,
+				      (void *)policy->policy_entries[i].entity,
+				      policy->policy_entries[i].size,
+				      policy->policy_entries[i].evt_info);
+		}
+	}
+}
+
+/*
+ * Process all EFI config entries and extend the measurements to the evtlog
+ */
+static void __init sl_process_extend_uefi_config(struct slr_table *slrt)
+{
+	struct slr_entry_uefi_config *uefi_config;
+	u16 i;
+
+	uefi_config = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_UEFI_CONFIG);
+
+	/* Optionally here depending on how SL kernel was booted */
+	if (!uefi_config)
+		return;
+
+	for (i = 0; i < uefi_config->nr_entries; i++) {
+		sl_tpm_extend(uefi_config->uefi_cfg_entries[i].pcr,
+			      SL_EVTYPE_SECURE_LAUNCH,
+			      (void *)uefi_config->uefi_cfg_entries[i].cfg,
+			      uefi_config->uefi_cfg_entries[i].size,
+			      uefi_config->uefi_cfg_entries[i].evt_info);
+	}
+}
+
 asmlinkage __visible __init void sl_main(void *bootparams)
 {
+	struct boot_params *bp = (struct boot_params *)bootparams;
+	struct txt_os_mle_data *os_mle_data;
+
+	/*
+	 * Ensure loadflags do not indicate a secure launch was done
+	 * unless it really was.
+	 */
+	bp->hdr.loadflags &= ~SLAUNCH_FLAG;
+
+	/*
+	 * Currently only Intel TXT is supported for Secure Launch. Testing
+	 * this value also indicates that the kernel was booted successfully
+	 * through the Secure Launch entry point and is in SMX mode.
+	 */
+	if (!(sl_cpu_type & SL_CPU_INTEL))
+		return;
+
+	txt_heap = (void *)sl_txt_read(TXT_CR_HEAP_BASE);
+	txt_parse_heap_map(txt_heap);
+
+	/* Find the SLRT setup by the pre-launch stage */
+	slrt = sl_locate_and_validate_slrt();
+
+	/* Locate the TPM event log. */
+	sl_find_drtm_event_log(slrt);
+
+	/* Validate the location of the event log buffer before using it */
+	sl_validate_event_log_buffer();
+
+	/*
+	 * Find the TPM hash algorithms used by the ACM and recorded in the
+	 * event log.
+	 */
+	if (tpm_log_ver == SL_TPM2_LOG)
+		sl_find_event_log_algorithms();
+
+	/*
+	 * Prepare the early TPM driver to do PCR extends for the DRTM
+	 * measurements. On a successful DRTM launch, TPM locality 2
+	 * should be available to open/acquire.
+	 *
+	 * Note that the early TPM driver does not use interrupts but
+	 * rather polling for command completion (there is no infrastructure
+	 * setup for servicing interrupts in the setup kernel).
+	 */
+	if (early_tpm_init(&chip, TIS_MEM_X86_LPC_BASE))
+		sl_txt_reset(SL_ERROR_TPM_INIT);
+	if (tpm_tis_request_locality(&chip, TPM_LOCALITY_2) < 0)
+		sl_txt_reset(SL_ERROR_TPM_INIT);
+	if (chip.family == TPM_FAMILY_20 && tpm_log_ver != SL_TPM2_LOG)
+		sl_txt_reset(SL_ERROR_TPM_INIT);
+	tpm_tis_disable_interrupts(&chip);
+
+	/*
+	 * Set the SLAUNCH_FLAG early since if anything fails, the system will
+	 * reset anyway.
+	 */
+	bp->hdr.loadflags |= SLAUNCH_FLAG;
+
+	sl_check_pmr_coverage(bootparams, PAGE_SIZE, false);
+
+	/*
+	 * Extend measurements into the TPM for entities specified in the
+	 * SLRT policies.
+	 */
+	sl_process_extend_policy(slrt);
+	sl_process_extend_uefi_config(slrt);
+
+	/* No PMR check is needed, the TXT heap is covered by the DPR */
+	os_mle_data = sl_txt_get_heap_table(txt_heap, TXT_OS_MLE_DATA_TABLE);
+
+	/*
+	 * Now that the OS-MLE data is measured, ensure the MTRR and
+	 * misc enable MSRs are what we expect.
+	 */
+	sl_txt_validate_msrs(os_mle_data);
 }
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:04 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:04 -0700
Subject: [PATCH v16 32/38] x86/slaunch: Secure Launch kernel late boot initialization
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-33-ross.philipson@gmail.com>

The routine slaunch_setup() is called out of the x86 specific setup_arch()
routine during early kernel boot. After determining what platform is
present, various operations specific to that platform occur. This
includes finalizing setting for the platform late launch and verifying
that memory protections are in place. In addition this routine reserves
key memory regions used by Secure Launch (e.g. the TXT heap, AP startup
block etc) as well as fetching values needed later from the TXT heap
and SLRT.

Intel VT-d/IOMMU hardware provides special registers called Protected
Memory Regions (PMRs) that allow all memory to be protected from
DMA during a TXT DRTM launch. This coverage is validated during the
late setup process to ensure DMA protection is in place prior to
the IOMMUs being initialized and configured by the mainline kernel.
See the Intel Trusted Execution Technology - Measured Launch Environment
Developer's Guide for more details:

https://www.intel.com/content/dam/www/public/us/en/documents/guides/intel-txt-software-development-guide.pdf

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/kernel/Makefile   |   1 +
 arch/x86/kernel/setup.c    |   3 +
 arch/x86/kernel/slaunch.c  | 513 +++++++++++++++++++++++++++++++++++++
 drivers/iommu/intel/dmar.c |   4 +
 4 files changed, 521 insertions(+)
 create mode 100644 arch/x86/kernel/slaunch.c

diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 7e247064b7d0..bf2471701662 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_X86_32)		+= tls.o
 obj-$(CONFIG_IA32_EMULATION)	+= tls.o
 obj-y				+= step.o
 obj-$(CONFIG_INTEL_TXT)		+= tboot.o
+obj-$(CONFIG_SECURE_LAUNCH)	+= slaunch.o
 obj-$(CONFIG_ISA_DMA_API)	+= i8237.o
 obj-y				+= stacktrace.o
 obj-y				+= cpu/
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index eebcc9db1a1b..82029d7640bf 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -25,6 +25,7 @@
 #include <linux/sysfb.h>
 #include <linux/swiotlb.h>
 #include <linux/tboot.h>
+#include <linux/slaunch.h>
 #include <linux/usb/xhci-dbgp.h>
 #include <linux/vmalloc.h>
 
@@ -1027,6 +1028,8 @@ void __init setup_arch(char **cmdline_p)
 	early_gart_iommu_check();
 #endif
 
+	slaunch_setup();
+
 	/*
 	 * partially used pages are not usable - thus
 	 * we are rounding upwards:
diff --git a/arch/x86/kernel/slaunch.c b/arch/x86/kernel/slaunch.c
new file mode 100644
index 000000000000..0179ff855b62
--- /dev/null
+++ b/arch/x86/kernel/slaunch.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Secure Launch late validation/setup and finalization support.
+ *
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/linkage.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/security.h>
+#include <linux/memblock.h>
+#include <asm/segment.h>
+#include <asm/sections.h>
+#include <asm/tlbflush.h>
+#include <asm/e820/api.h>
+#include <asm/setup.h>
+#include <asm/realmode.h>
+#include <linux/efi.h>
+#include <linux/slr_table.h>
+#include <linux/slaunch.h>
+
+static u32 sl_flags __ro_after_init;
+static struct sl_ap_wake_info ap_wake_info __ro_after_init;
+static u64 evtlog_addr __ro_after_init;
+static u32 evtlog_size __ro_after_init;
+static u64 vtd_pmr_lo_size __ro_after_init;
+
+/* This should be plenty of room */
+static u8 txt_dmar[PAGE_SIZE] __aligned(16);
+
+/*
+ * Get the Secure Launch flags that indicate what kind of launch is being done.
+ * E.g. a TXT launch is in progress or no Secure Launch is happening.
+ */
+u32 slaunch_get_flags(void)
+{
+	return sl_flags;
+}
+
+/*
+ * Return the AP wakeup information used in the SMP boot code to start up
+ * the APs that are parked using MONITOR/MWAIT.
+ */
+struct sl_ap_wake_info *slaunch_get_ap_wake_info(void)
+{
+	return &ap_wake_info;
+}
+
+/* Saved TXT heap map accessors for easy heap parsing */
+struct sl_txt_heap_info *__pi_sl_txt_get_heap_map(void);
+
+struct sl_txt_heap_info *__init slaunch_txt_get_heap_map(void)
+{
+	return __pi_sl_txt_get_heap_map();
+}
+
+void *__pi_sl_txt_get_heap_table(void *heap, u8 index);
+
+void *__init slaunch_txt_get_heap_table(void *heap, u8 index)
+{
+	return __pi_sl_txt_get_heap_table(heap, index);
+}
+
+/*
+ * On Intel platforms, TXT passes a safe copy of the DMAR ACPI table to the
+ * DRTM. The DRTM is supposed to use this instead of the one found in the
+ * ACPI tables.
+ */
+struct acpi_table_header *slaunch_get_dmar_table(struct acpi_table_header *dmar)
+{
+	/* The DMAR is only stashed and provided via TXT on Intel systems */
+	if (memcmp(txt_dmar, "DMAR", 4))
+		return dmar;
+
+	return (struct acpi_table_header *)(txt_dmar);
+}
+
+/*
+ * If running within a TXT established DRTM, this is the proper way to reset
+ * the system if a failure occurs or a security issue is found.
+ */
+static void __noreturn slaunch_txt_reset(void __iomem *txt, const char *msg, u64 error)
+{
+	u64 one = 1, val;
+
+	pr_err("%s", msg);
+
+	/*
+	 * This performs a TXT reset with a sticky error code. The reads of
+	 * TXT_CR_E2STS act as barriers.
+	 */
+	memcpy_toio(txt + TXT_CR_ERRORCODE, &error, sizeof(error));
+	memcpy_fromio(&val, txt + TXT_CR_E2STS, sizeof(val));
+	memcpy_toio(txt + TXT_CR_CMD_NO_SECRETS, &one, sizeof(one));
+	memcpy_fromio(&val, txt + TXT_CR_E2STS, sizeof(val));
+	memcpy_toio(txt + TXT_CR_CMD_UNLOCK_MEM_CONFIG, &one, sizeof(one));
+	memcpy_fromio(&val, txt + TXT_CR_E2STS, sizeof(val));
+	memcpy_toio(txt + TXT_CR_CMD_RESET, &one, sizeof(one));
+
+	for ( ; ; )
+		asm volatile ("hlt");
+
+	unreachable();
+}
+
+/*
+ * Handle fatal errors during DRTM initialization.
+ */
+void __noreturn slaunch_reset(void *ctx, const char *msg, u64 error)
+{
+	if (slaunch_is_txt_launch())
+		slaunch_txt_reset(ctx, msg, error);
+
+	/* Generic handler for x86 */
+	pr_err("Secure Launch: %s - error: 0x%llx", msg, error);
+	asm volatile ("ud2");
+
+	unreachable();
+}
+
+/*
+ * The TXT heap is too big to map all at once with early_ioremap
+ * so it is done a table at a time.
+ */
+static void __init *txt_early_get_heap_table(void __iomem *txt, u32 type,
+					     u32 bytes)
+{
+	struct sl_txt_heap_info *heap_map;
+	void *heap;
+	u64 base;
+
+	if (type >= TXT_SINIT_TABLE_MAX)
+		slaunch_reset(txt, "Error invalid table type for early heap walk\n",
+			      SL_ERROR_HEAP_WALK);
+
+	memcpy_fromio(&base, txt + TXT_CR_HEAP_BASE, sizeof(base));
+
+	heap_map = slaunch_txt_get_heap_map();
+	base += heap_map[type].offset;
+
+	heap = early_memremap(base, bytes);
+	if (!heap)
+		slaunch_reset(txt, "Error early_memremap of heap section\n",
+			      SL_ERROR_HEAP_MAP);
+
+	return heap;
+}
+
+static void __init txt_early_put_heap_table(void *addr, unsigned long size)
+{
+	early_memunmap(addr, size);
+}
+
+/*
+ * TXT uses a special set of VTd registers to protect all of memory from DMA
+ * until the IOMMU can be programmed to protect memory. There is the low
+ * memory PMR that can protect all memory up to 4G. The high memory PMR can
+ * be setup to protect all memory beyond 4Gb. Validate that these values cover
+ * what is expected.
+ */
+static void __init slaunch_verify_pmrs(void __iomem *txt)
+{
+	struct txt_os_sinit_data *os_sinit_data;
+	u32 field_offset, err = 0;
+	const char *errmsg = "";
+	unsigned long last_pfn;
+
+	field_offset = offsetof(struct txt_os_sinit_data, lcp_po_base);
+	os_sinit_data = txt_early_get_heap_table(txt, TXT_OS_SINIT_DATA_TABLE,
+						 field_offset);
+
+	/* Save a copy */
+	vtd_pmr_lo_size = os_sinit_data->vtd_pmr_lo_size;
+
+	last_pfn = e820__end_of_ram_pfn();
+
+	/*
+	 * First make sure the hi PMR covers all memory above 4G. In the
+	 * unlikely case where there is < 4G on the system, the hi PMR will
+	 * not be set.
+	 */
+	if (os_sinit_data->vtd_pmr_hi_base != 0x0ULL) {
+		if (os_sinit_data->vtd_pmr_hi_base != 0x100000000ULL) {
+			err = SL_ERROR_HI_PMR_BASE;
+			errmsg =  "Error hi PMR base\n";
+			goto out;
+		}
+
+		if (PFN_PHYS(last_pfn) > os_sinit_data->vtd_pmr_hi_base +
+		    os_sinit_data->vtd_pmr_hi_size) {
+			err = SL_ERROR_HI_PMR_SIZE;
+			errmsg = "Error hi PMR size\n";
+			goto out;
+		}
+	}
+
+	/*
+	 * Lo PMR base should always be 0. This was already checked in
+	 * early stub.
+	 */
+
+	/*
+	 * Check that if the kernel was loaded below 4G, that it is protected
+	 * by the lo PMR. Note this is the decompressed kernel. The ACM would
+	 * have ensured the compressed kernel (the MLE image) was protected.
+	 */
+	if (__pa_symbol(_end) < 0x100000000ULL &&
+	    __pa_symbol(_end) > os_sinit_data->vtd_pmr_lo_size) {
+		err = SL_ERROR_LO_PMR_MLE;
+		errmsg = "Error lo PMR does not cover MLE kernel\n";
+	}
+
+	/*
+	 * Other regions of interest like boot param, AP wake block, cmdline
+	 * already checked for PMR coverage in the early stub code.
+	 */
+
+out:
+	txt_early_put_heap_table(os_sinit_data, field_offset);
+
+	if (err)
+		slaunch_reset(txt, errmsg, err);
+}
+
+static void __init slaunch_txt_reserve_range(u64 base, u64 size)
+{
+	int type;
+
+	type = e820__get_entry_type(base, base + size - 1);
+	if (type == E820_TYPE_RAM) {
+		pr_info("memblock reserve base: %llx size: %llx\n", base, size);
+		memblock_reserve(base, size);
+	}
+}
+
+/*
+ * For Intel, certain regions of memory must be marked as reserved by putting
+ * them on the memblock reserved list if they are not already e820 reserved.
+ * This includes:
+ *  - The TXT heap
+ *  - The ACM area
+ *  - The TXT private register bank
+ *  - The MDR list sent to the MLE by the ACM (see TXT specification)
+ *  (Normally the above are properly reserved by firmware but if it was not
+ *  done, reserve them now)
+ *  - The AP wake block
+ *  - TPM log external to the TXT heap
+ *
+ * Also if the low PMR doesn't cover all memory < 4G, any RAM regions above
+ * the low PMR must be reserved too.
+ */
+static void __init slaunch_txt_reserve(void __iomem *txt)
+{
+	struct txt_sinit_memory_descriptor_record *mdr;
+	struct txt_sinit_mle_data *sinit_mle_data;
+	u64 base, size, heap_base, heap_size;
+	u32 mdrnum, mdroffset, mdrslen;
+	u32 field_offset, i;
+	void *mdrs;
+
+	base = TXT_PRIV_CONFIG_REGS_BASE;
+	size = TXT_PUB_CONFIG_REGS_BASE - TXT_PRIV_CONFIG_REGS_BASE;
+	slaunch_txt_reserve_range(base, size);
+
+	memcpy_fromio(&heap_base, txt + TXT_CR_HEAP_BASE, sizeof(heap_base));
+	memcpy_fromio(&heap_size, txt + TXT_CR_HEAP_SIZE, sizeof(heap_size));
+	slaunch_txt_reserve_range(heap_base, heap_size);
+
+	memcpy_fromio(&base, txt + TXT_CR_SINIT_BASE, sizeof(base));
+	memcpy_fromio(&size, txt + TXT_CR_SINIT_SIZE, sizeof(size));
+	slaunch_txt_reserve_range(base, size);
+
+	field_offset = offsetof(struct txt_sinit_mle_data,
+				sinit_vtd_dmar_table_size);
+	sinit_mle_data = txt_early_get_heap_table(txt, TXT_SINIT_MLE_DATA_TABLE,
+						  field_offset);
+
+	mdrnum = sinit_mle_data->num_of_sinit_mdrs;
+	mdroffset = sinit_mle_data->sinit_mdrs_table_offset;
+
+	txt_early_put_heap_table(sinit_mle_data, field_offset);
+
+	if (!mdrnum)
+		goto nomdr;
+
+	mdrslen = mdrnum * sizeof(*mdr);
+
+	mdrs = txt_early_get_heap_table(txt, TXT_SINIT_MLE_DATA_TABLE,
+					mdroffset + mdrslen - 8);
+
+	mdr = mdrs + mdroffset - 8;
+
+	for (i = 0; i < mdrnum; i++, mdr++) {
+		/* Spec says some entries can have length 0, ignore them */
+		if (mdr->type > 0 && mdr->length > 0)
+			slaunch_txt_reserve_range(mdr->address, mdr->length);
+	}
+
+	txt_early_put_heap_table(mdrs, mdroffset + mdrslen - 8);
+
+nomdr:
+	slaunch_txt_reserve_range(ap_wake_info.ap_wake_block,
+				  ap_wake_info.ap_wake_block_size);
+
+	/*
+	 * Earlier checks ensured that the event log was properly situated
+	 * either inside the TXT heap or outside. This is a check to see if the
+	 * event log needs to be reserved. If it is in the TXT heap, it is
+	 * already reserved.
+	 */
+	if (evtlog_addr < heap_base || evtlog_addr > (heap_base + heap_size))
+		slaunch_txt_reserve_range(evtlog_addr, evtlog_size);
+
+	for (i = 0; i < e820_table->nr_entries; i++) {
+		base = e820_table->entries[i].addr;
+		size = e820_table->entries[i].size;
+		if (base >= vtd_pmr_lo_size && base < 0x100000000ULL)
+			slaunch_txt_reserve_range(base, size);
+		else if (base < vtd_pmr_lo_size && base + size > vtd_pmr_lo_size)
+			slaunch_txt_reserve_range(vtd_pmr_lo_size,
+						  base + size - vtd_pmr_lo_size);
+	}
+}
+
+/*
+ * TXT stashes a safe copy of the DMAR ACPI table to prevent tampering.
+ * It is stored in the TXT heap. Fetch it from there and make it available
+ * to the IOMMU driver.
+ */
+static void __init slaunch_copy_dmar_table(void __iomem *txt)
+{
+	struct txt_sinit_mle_data *sinit_mle_data;
+	u32 field_offset, dmar_size, dmar_offset;
+	void *dmar;
+
+	field_offset = offsetof(struct txt_sinit_mle_data,
+				processor_scrtm_status);
+	sinit_mle_data = txt_early_get_heap_table(txt, TXT_SINIT_MLE_DATA_TABLE,
+						  field_offset);
+
+	dmar_size = sinit_mle_data->sinit_vtd_dmar_table_size;
+	dmar_offset = sinit_mle_data->sinit_vtd_dmar_table_offset;
+
+	txt_early_put_heap_table(sinit_mle_data, field_offset);
+
+	if (!dmar_size || !dmar_offset)
+		slaunch_reset(txt, "Error DMAR table values\n", SL_ERROR_HEAP_INVALID_DMAR);
+
+	if (unlikely(dmar_size > PAGE_SIZE))
+		slaunch_reset(txt, "Error DMAR too big to store\n", SL_ERROR_HEAP_DMAR_SIZE);
+
+	dmar = txt_early_get_heap_table(txt, TXT_SINIT_MLE_DATA_TABLE,
+					dmar_offset + dmar_size - 8);
+	if (!dmar)
+		slaunch_reset(txt, "Error early_ioremap of DMAR\n", SL_ERROR_HEAP_DMAR_MAP);
+
+	memcpy(txt_dmar, dmar + dmar_offset - 8, dmar_size);
+
+	txt_early_put_heap_table(dmar, dmar_offset + dmar_size - 8);
+}
+
+/*
+ * The location of the safe AP wake code block is stored in the TXT heap.
+ * Fetch needed values here in the early init code for later use in SMP
+ * startup.
+ *
+ * Also the TPM event log values are in the SLRT and have to be fetched.
+ * They will be put on the memblock reserve list later.
+ */
+static void __init slaunch_fetch_values(void __iomem *txt)
+{
+	struct txt_os_mle_data *os_mle_data;
+	struct slr_entry_log_info *log_info;
+	u8 *jmp_offset, *stacks_offset;
+	struct slr_table *slrt;
+	u32 size;
+
+	os_mle_data = txt_early_get_heap_table(txt, TXT_OS_MLE_DATA_TABLE,
+					       sizeof(*os_mle_data));
+
+	ap_wake_info.ap_wake_block = os_mle_data->ap_wake_block;
+	ap_wake_info.ap_wake_block_size = os_mle_data->ap_wake_block_size;
+
+	jmp_offset = os_mle_data->mle_scratch + SL_SCRATCH_AP_JMP_OFFSET;
+	ap_wake_info.ap_jmp_offset = *((u32 *)jmp_offset);
+
+	stacks_offset = os_mle_data->mle_scratch + SL_SCRATCH_AP_STACKS_OFFSET;
+	ap_wake_info.ap_stacks_offset = *((u32 *)stacks_offset);
+
+	slrt = (struct slr_table *)early_memremap(os_mle_data->slrt, sizeof(*slrt));
+	if (!slrt)
+		slaunch_reset(txt, "Error early_memremap of SLRT failed\n",
+			      SL_ERROR_SLRT_MAP);
+
+	size = slrt->size;
+	early_memunmap(slrt, sizeof(*slrt));
+
+	slrt = (struct slr_table *)early_memremap(os_mle_data->slrt, size);
+	if (!slrt)
+		slaunch_reset(txt, "Error early_memremap of SLRT failed\n",
+			      SL_ERROR_SLRT_MAP);
+
+	log_info = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_LOG_INFO);
+
+	if (!log_info)
+		slaunch_reset(txt, "SLRT missing logging info entry\n",
+			      SL_ERROR_SLRT_MISSING_ENTRY);
+
+	evtlog_addr = log_info->addr;
+	evtlog_size = log_info->size;
+
+	early_memunmap(slrt, size);
+
+	txt_early_put_heap_table(os_mle_data, sizeof(*os_mle_data));
+}
+
+/*
+ * Intel TXT specific late stub setup and validation called from within
+ * x86 specific setup_arch().
+ */
+static void __init slaunch_setup_txt(void)
+{
+	u64 one = TXT_REGVALUE_ONE, val;
+	void __iomem *txt;
+
+	/*
+	 * See if SENTER was done by reading the status register in the
+	 * public space. If the public register space cannot be read, TXT may
+	 * be disabled.
+	 */
+	txt = early_ioremap(TXT_PUB_CONFIG_REGS_BASE,
+			    TXT_NR_CONFIG_PAGES * PAGE_SIZE);
+	if (!txt)
+		panic("Error early_ioremap in TXT setup failed\n");
+
+	memcpy_fromio(&val, txt + TXT_CR_STS, sizeof(val));
+	early_iounmap(txt, TXT_NR_CONFIG_PAGES * PAGE_SIZE);
+
+	/* SENTER should have been done */
+	if (!(val & TXT_SENTER_DONE_STS))
+		panic("Error TXT.STS SENTER_DONE not set\n");
+
+	/* SEXIT should have been cleared */
+	if (val & TXT_SEXIT_DONE_STS)
+		panic("Error TXT.STS SEXIT_DONE set\n");
+
+	/* Now we want to use the private register space */
+	txt = early_ioremap(TXT_PRIV_CONFIG_REGS_BASE,
+			    TXT_NR_CONFIG_PAGES * PAGE_SIZE);
+	if (!txt) {
+		/* This is really bad, no where to go from here */
+		panic("Error early_ioremap of TXT priv registers\n");
+	}
+
+	/*
+	 * Try to read the Intel VID from the TXT private registers to see if
+	 * TXT measured launch happened properly and the private space is
+	 * available.
+	 */
+	memcpy_fromio(&val, txt + TXT_CR_DIDVID, sizeof(val));
+	if ((val & 0xffff) != 0x8086) {
+		/*
+		 * Can't do a proper TXT reset since it appears something is
+		 * wrong even though SENTER happened and it should be in SMX
+		 * mode.
+		 */
+		panic("Invalid TXT vendor ID, not in SMX mode\n");
+	}
+
+	/* Set flags so subsequent code knows the status of the launch */
+	sl_flags |= (SL_FLAG_ACTIVE | SL_FLAG_ARCH_TXT);
+
+	/*
+	 * Reading the proper DIDVID from the private register space means we
+	 * are in SMX mode and private registers are open for read/write.
+	 */
+
+	/* On Intel, have to handle TPM localities via TXT */
+	memcpy_toio(txt + TXT_CR_CMD_SECRETS, &one, sizeof(one));
+	memcpy_fromio(&val, txt + TXT_CR_E2STS, sizeof(val));
+	memcpy_toio(txt + TXT_CR_CMD_OPEN_LOCALITY1, &one, sizeof(one));
+	memcpy_fromio(&val, txt + TXT_CR_E2STS, sizeof(val));
+
+	slaunch_fetch_values(txt);
+
+	slaunch_verify_pmrs(txt);
+
+	slaunch_txt_reserve(txt);
+
+	slaunch_copy_dmar_table(txt);
+
+	early_iounmap(txt, TXT_NR_CONFIG_PAGES * PAGE_SIZE);
+
+	pr_info("Intel TXT setup complete\n");
+}
+
+void __init slaunch_setup(void)
+{
+	/*
+	 * If booted through secure launch entry point, the loadflags
+	 * option will be set.
+	 */
+	if (!(boot_params.hdr.loadflags & SLAUNCH_FLAG))
+		return;
+
+	if (boot_cpu_has(X86_FEATURE_SMX))
+		slaunch_setup_txt();
+}
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index 69222dbd2af0..f41b4f702565 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -28,6 +28,7 @@
 #include <linux/iommu.h>
 #include <linux/numa.h>
 #include <linux/limits.h>
+#include <linux/slaunch.h>
 #include <asm/irq_remapping.h>
 
 #include "iommu.h"
@@ -661,6 +662,9 @@ parse_dmar_table(void)
 	 */
 	dmar_tbl = tboot_get_dmar_table(dmar_tbl);
 
+	/* If Secure Launch is active, it has similar logic */
+	dmar_tbl = slaunch_get_dmar_table(dmar_tbl);
+
 	dmar = (struct acpi_table_dmar *)dmar_tbl;
 	if (!dmar)
 		return -ENODEV;
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:05 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:05 -0700
Subject: [PATCH v16 33/38] x86/slaunch: Secure Launch SMP bringup support
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-34-ross.philipson@gmail.com>

On Intel, the APs are left in a well documented state after TXT
performs the secure launch. Specifically, they cannot have #INIT
asserted on them so a standard startup via INIT/SIPI/SIPI cannot
be performed. Instead the early SL stub code uses MONITOR and MWAIT
to park the APs. The realmode/init.c code updates the jump address
for the waiting APs with the location of the Secure Launch entry
point in the rmpiggy image.

The rmpiggy image is a payload contained in the kernel used to start
the APs (in 16b or 32b modes). It is loaded at runtime so its
location and entry point must be updated in the long jump for the
waiting APs by the running kernel.

As the APs are woken up by writing the monitor, the APs jump to the
Secure Launch entry point in the rmpiggy which mimics what the real
mode code would do then jumps to the standard rmpiggy protected mode
entry point.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/include/asm/realmode.h      |  3 ++
 arch/x86/kernel/slaunch.c            | 26 +++++++++++++++
 arch/x86/kernel/smpboot.c            | 47 ++++++++++++++++++++++++++--
 arch/x86/realmode/init.c             |  8 +++++
 arch/x86/realmode/rm/header.S        |  3 ++
 arch/x86/realmode/rm/trampoline_64.S | 32 +++++++++++++++++++
 6 files changed, 117 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/realmode.h b/arch/x86/include/asm/realmode.h
index e406a1e92c63..e3336c49d26b 100644
--- a/arch/x86/include/asm/realmode.h
+++ b/arch/x86/include/asm/realmode.h
@@ -38,6 +38,9 @@ struct real_mode_header {
 #ifdef CONFIG_X86_64
 	u32	machine_real_restart_seg;
 #endif
+#ifdef CONFIG_SECURE_LAUNCH
+	u32	sl_trampoline_start32;
+#endif
 };
 
 /* This must match data at realmode/rm/trampoline_{32,64}.S */
diff --git a/arch/x86/kernel/slaunch.c b/arch/x86/kernel/slaunch.c
index 0179ff855b62..f6e6f1b7e18c 100644
--- a/arch/x86/kernel/slaunch.c
+++ b/arch/x86/kernel/slaunch.c
@@ -511,3 +511,29 @@ void __init slaunch_setup(void)
 	if (boot_cpu_has(X86_FEATURE_SMX))
 		slaunch_setup_txt();
 }
+
+/*
+ * After a launch, the APs are woken up, enter the DRTM and are left to
+ * wait for a wakeup call on a MONITOR address. The block where they are
+ * idle has a long jump to the AP startup code in the mainline kernel.
+ * This address has to be calculated at runtime and "fixed up" to point
+ * to the SL startup location in the rmpiggy SMP startup image. This image
+ * is loaded into separate memory at kernel start time.
+ */
+void __init slaunch_fixup_ap_wake_vector(void)
+{
+	struct sl_ap_wake_info *ap_wake_info;
+	u32 *ap_jmp_ptr;
+
+	if (!slaunch_is_txt_launch())
+		return;
+
+	ap_wake_info = slaunch_get_ap_wake_info();
+
+	ap_jmp_ptr = (u32 *)__va(ap_wake_info->ap_wake_block +
+				 ap_wake_info->ap_jmp_offset);
+
+	*ap_jmp_ptr = real_mode_header->sl_trampoline_start32;
+
+	pr_info("TXT AP startup vector address updated\n");
+}
diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c
index 294a8ea60298..16a0f2718a38 100644
--- a/arch/x86/kernel/smpboot.c
+++ b/arch/x86/kernel/smpboot.c
@@ -61,6 +61,7 @@
 #include <linux/cpuhotplug.h>
 #include <linux/mc146818rtc.h>
 #include <linux/acpi.h>
+#include <linux/slaunch.h>
 
 #include <asm/acpi.h>
 #include <asm/cacheinfo.h>
@@ -989,6 +990,45 @@ int common_cpu_up(unsigned int cpu, struct task_struct *idle)
 	return 0;
 }
 
+#if (IS_ENABLED(CONFIG_SECURE_LAUNCH))
+
+/*
+ * TXT AP startup is quite different than normal. The APs cannot have #INIT
+ * asserted on them or receive SIPIs. The early Secure Launch code has parked
+ * the APs using MONITOR/MWAIT in the safe AP wake block area (details in
+ * sl_stub.S). The SMP boot will wake the APs by writing the MONITOR associated
+ * with the AP and have them jump to the protected mode code in the rmpiggy where
+ * the rest of the SMP boot of the AP will proceed normally.
+ *
+ * Intel Trusted Execution Technology (TXT) Software Development Guide
+ * Section 2.3 -  MLE Initialization
+ */
+static void slaunch_wakeup_cpu_from_txt(int cpu, int apicid)
+{
+	struct sl_ap_stack_and_monitor *stack_monitor;
+	struct sl_ap_wake_info *ap_wake_info;
+
+	ap_wake_info = slaunch_get_ap_wake_info();
+
+	stack_monitor = (struct sl_ap_stack_and_monitor *)__va(ap_wake_info->ap_wake_block +
+							       ap_wake_info->ap_stacks_offset);
+
+	for (unsigned int i = SL_MAX_CPUS - 1; i >= 0; i--) {
+		if (stack_monitor[i].apicid == apicid) {
+			stack_monitor[i].monitor = 1;
+			break;
+		}
+	}
+}
+
+#else
+
+static inline void slaunch_wakeup_cpu_from_txt(int cpu, int apicid)
+{
+}
+
+#endif  /* IS_ENABLED(CONFIG_SECURE_LAUNCH) */
+
 /*
  * NOTE - on most systems this is a PHYSICAL apic ID, but on multiquad
  * (ie clustered apic addressing mode), this is a LOGICAL apic ID.
@@ -998,7 +1038,7 @@ int common_cpu_up(unsigned int cpu, struct task_struct *idle)
 static int do_boot_cpu(u32 apicid, unsigned int cpu, struct task_struct *idle)
 {
 	unsigned long start_ip = real_mode_header->trampoline_start;
-	int ret;
+	int ret = 0;
 
 #ifdef CONFIG_X86_64
 	/* If 64-bit wakeup method exists, use the 64-bit mode trampoline IP */
@@ -1043,12 +1083,15 @@ static int do_boot_cpu(u32 apicid, unsigned int cpu, struct task_struct *idle)
 
 	/*
 	 * Wake up a CPU in difference cases:
+	 * - Intel TXT DRTM launch uses its own method to wake the APs
 	 * - Use a method from the APIC driver if one defined, with wakeup
 	 *   straight to 64-bit mode preferred over wakeup to RM.
 	 * Otherwise,
 	 * - Use an INIT boot APIC message
 	 */
-	if (apic->wakeup_secondary_cpu_64)
+	if (slaunch_is_txt_launch())
+		slaunch_wakeup_cpu_from_txt(cpu, apicid);
+	else if (apic->wakeup_secondary_cpu_64)
 		ret = apic->wakeup_secondary_cpu_64(apicid, start_ip, cpu);
 	else if (apic->wakeup_secondary_cpu)
 		ret = apic->wakeup_secondary_cpu(apicid, start_ip, cpu);
diff --git a/arch/x86/realmode/init.c b/arch/x86/realmode/init.c
index 88be32026768..ded4dafc6a0a 100644
--- a/arch/x86/realmode/init.c
+++ b/arch/x86/realmode/init.c
@@ -4,6 +4,7 @@
 #include <linux/memblock.h>
 #include <linux/cc_platform.h>
 #include <linux/pgtable.h>
+#include <linux/slaunch.h>
 
 #include <asm/set_memory.h>
 #include <asm/realmode.h>
@@ -213,6 +214,13 @@ void __init init_real_mode(void)
 
 	setup_real_mode();
 	set_real_mode_permissions();
+
+	/*
+	 * If Secure Launch is active, it will use the rmpiggy to do the TXT AP
+	 * startup. Secure Launch has its own entry stub in the rmpiggy and this prepares
+	 * it for SMP boot.
+	 */
+	slaunch_fixup_ap_wake_vector();
 }
 
 static int __init do_init_real_mode(void)
diff --git a/arch/x86/realmode/rm/header.S b/arch/x86/realmode/rm/header.S
index 2eb62be6d256..3b5cbcbbfc90 100644
--- a/arch/x86/realmode/rm/header.S
+++ b/arch/x86/realmode/rm/header.S
@@ -37,6 +37,9 @@ SYM_DATA_START(real_mode_header)
 #ifdef CONFIG_X86_64
 	.long	__KERNEL32_CS
 #endif
+#ifdef CONFIG_SECURE_LAUNCH
+	.long	pa_sl_trampoline_start32
+#endif
 SYM_DATA_END(real_mode_header)
 
 	/* End signature, used to verify integrity */
diff --git a/arch/x86/realmode/rm/trampoline_64.S b/arch/x86/realmode/rm/trampoline_64.S
index 14d9c7daf90f..b0ce6205d7ea 100644
--- a/arch/x86/realmode/rm/trampoline_64.S
+++ b/arch/x86/realmode/rm/trampoline_64.S
@@ -122,6 +122,38 @@ SYM_CODE_END(sev_es_trampoline_start)
 
 	.section ".text32","ax"
 	.code32
+#ifdef CONFIG_SECURE_LAUNCH
+	.balign 4
+SYM_CODE_START(sl_trampoline_start32)
+	/*
+	 * The early secure launch stub AP wakeup code has taken care of all
+	 * the vagaries of launching out of TXT. This bit just mimics what the
+	 * 16b entry code does and jumps off to the real startup_32.
+	 */
+	cli
+	wbinvd
+
+	/*
+	 * The %ebx provided is not terribly useful since it is the physical
+	 * address of tb_trampoline_start and not the base of the image.
+	 * Use pa_real_mode_base, which is fixed up, to get a run time
+	 * base register to use for offsets to location that do not have
+	 * pa_ symbols.
+	 */
+	movl    $pa_real_mode_base, %ebx
+
+	LOCK_AND_LOAD_REALMODE_ESP lock_pa=1
+
+	lgdt    tr_gdt(%ebx)
+	lidt    tr_idt(%ebx)
+
+	movw	$__KERNEL_DS, %dx	# Data segment descriptor
+
+	/* Jump to where the 16b code would have jumped */
+	ljmpl	$__KERNEL32_CS, $pa_startup_32
+SYM_CODE_END(sl_trampoline_start32)
+#endif
+
 	.balign 4
 SYM_CODE_START(startup_32)
 	movl	%edx, %ss
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:06 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:06 -0700
Subject: [PATCH v16 34/38] kexec/slaunch: Secure Launch kexec SEXIT support
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-35-ross.philipson@gmail.com>

Prior to running the next kernel via kexec, the Secure Launch code
closes down private SMX resources and does an SEXIT. This allows the
next kernel to start normally, effectively exiting the DRTM
environment.

The function slaunch_finalize() takes a boolean argument that controls
whether a GETSEC[SEXIT] can be issued. When true, the finalize code can
completely shutdown and exit the DRTM. This allows another kernel to
start normally and in turn can re-establish another DRTM session.

In cases where the machine has not been fully shutdown (e.g. when
machine_shutdown() was not called), the SEXIT step cannot be done
(SEXIT will fail if other processors are busy). In these cases SEXIT
is not attempted. This normally occurs on power off or reboot
operations where it doesn't really matter.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/kernel/slaunch.c | 80 +++++++++++++++++++++++++++++++++++++++
 kernel/kexec_core.c       |  8 ++++
 2 files changed, 88 insertions(+)

diff --git a/arch/x86/kernel/slaunch.c b/arch/x86/kernel/slaunch.c
index f6e6f1b7e18c..b34997a5f541 100644
--- a/arch/x86/kernel/slaunch.c
+++ b/arch/x86/kernel/slaunch.c
@@ -537,3 +537,83 @@ void __init slaunch_fixup_ap_wake_vector(void)
 
 	pr_info("TXT AP startup vector address updated\n");
 }
+
+static inline void smx_getsec_sexit(void)
+{
+	asm volatile ("getsec\n" : : "a" (SMX_X86_GETSEC_SEXIT));
+}
+
+/*
+ * Used during kexec and on reboot paths to finalize the TXT state
+ * and do an SEXIT SMX operation, exiting the DRTM and disabling SMX mode.
+ */
+void slaunch_finalize(int do_sexit)
+{
+	u64 one = TXT_REGVALUE_ONE, val;
+	void __iomem *config;
+
+	if (!slaunch_is_txt_launch())
+		return;
+
+	config = ioremap(TXT_PRIV_CONFIG_REGS_BASE, TXT_NR_CONFIG_PAGES * PAGE_SIZE);
+	if (!config) {
+		pr_emerg("TXT: SEXIT failed to ioremap TXT private registers\n");
+		return;
+	}
+
+	/* Clear secrets bit for SEXIT */
+	memcpy_toio(config + TXT_CR_CMD_NO_SECRETS, &one, sizeof(one));
+	memcpy_fromio(&val, config + TXT_CR_E2STS, sizeof(val));
+
+	/* Unlock memory configurations */
+	memcpy_toio(config + TXT_CR_CMD_UNLOCK_MEM_CONFIG, &one, sizeof(one));
+	memcpy_fromio(&val, config + TXT_CR_E2STS, sizeof(val));
+
+	/* Close the TXT private register space */
+	memcpy_toio(config + TXT_CR_CMD_CLOSE_PRIVATE, &one, sizeof(one));
+	memcpy_fromio(&val, config + TXT_CR_E2STS, sizeof(val));
+
+	/*
+	 * Calls to iounmap are skipped due to the system state this late in the
+	 * kexec process. Local IRQs are disabled and iounmap causes a TLB flush
+	 * which in turn causes a warning. Leaving these mappings is not an issue
+	 * since the next kernel is going to completely re-setup memory management.
+	 */
+
+	/* Map public registers and do a final read fence */
+	config = ioremap(TXT_PUB_CONFIG_REGS_BASE, TXT_NR_CONFIG_PAGES *
+			 PAGE_SIZE);
+	if (!config) {
+		pr_emerg("TXT: SEXIT failed to ioremap TXT public registers\n");
+		return;
+	}
+
+	memcpy_fromio(&val, config + TXT_CR_E2STS, sizeof(val));
+
+	pr_emerg("TXT clear secrets bit and unlock memory complete.\n");
+
+	/*
+	 * Mostly finalized but the system is still in SMX mode. At this point if the
+	 * system has been quiesced, the APs are halted and the current process is
+	 * running on the BSP, a final GETSEC(SEXIT) can be done exiting DRTM/SMX mode.
+	 * This cannot be done on certain boot paths where the system has not been quiesced
+	 * (e.g. where machine_shutdown() has not been called).
+	 */
+	if (!do_sexit)
+		return;
+
+	if (smp_processor_id() != 0)
+		panic("TXT: SEXIT must be called on CPU 0\n");
+
+	/*
+	 * In case SMX mode was disabled, enable it for SEXIT. Clearing the bit
+	 * anytime during DRTM operation will not have an affect until the next
+	 * GETSEC() op is performed.
+	 */
+	cr4_set_bits(X86_CR4_SMXE);
+
+	/* Do the SEXIT SMX operation */
+	smx_getsec_sexit();
+
+	pr_info("TXT SEXIT complete.\n");
+}
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index 2fea396d29b9..dd284e5043ab 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -43,6 +43,7 @@
 #include <linux/kmsg_dump.h>
 #include <linux/dma-map-ops.h>
 #include <linux/sysfs.h>
+#include <linux/slaunch.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -1201,6 +1202,13 @@ int kernel_kexec(void)
 		cpu_hotplug_enable();
 		pr_notice("Starting new kernel\n");
 		machine_shutdown();
+
+		/*
+		 * If a Secure Launch is in progress and the current kernel is
+		 * running as a DRTM with TXT, finalize the Secure Launch state
+		 * and do the GETSEC(SEXIT) returning from SMX mode to do the KEXEC.
+		 */
+		slaunch_finalize(1);
 	}
 
 	kmsg_dump(KMSG_DUMP_SHUTDOWN);
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:07 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:07 -0700
Subject: [PATCH v16 35/38] reboot/slaunch: Secure Launch SEXIT support on reboot paths
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-36-ross.philipson@gmail.com>

If the MLE kernel is being powered off, rebooted or halted,
then SEXIT must be called. Note that the GETSEC[SEXIT] leaf
can only be called after a machine_shutdown() has been done on
these paths. The machine_shutdown() is not called on a few paths
like when poweroff action does not have a poweroff callback (into
ACPI code) or when an emergency reset is done. In these cases,
just the TXT registers are finalized but SEXIT is skipped.

Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/kernel/reboot.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c
index 6032fa9ec753..87835706bb4f 100644
--- a/arch/x86/kernel/reboot.c
+++ b/arch/x86/kernel/reboot.c
@@ -14,6 +14,7 @@
 #include <linux/pgtable.h>
 #include <linux/kexec.h>
 #include <linux/kvm_types.h>
+#include <linux/slaunch.h>
 #include <acpi/reboot.h>
 #include <asm/io.h>
 #include <asm/apic.h>
@@ -779,6 +780,12 @@ static void native_machine_restart(char *__unused)
 
 	if (!reboot_force)
 		machine_shutdown();
+	/*
+	 * The comments for slaunch_finalize() provides the explanation for the
+	 * conditions required to do the SEXIT op reflected in the conditional
+	 * parameter do_sexit.
+	 */
+	slaunch_finalize(!reboot_force);
 	__machine_emergency_restart(0);
 }
 
@@ -789,6 +796,8 @@ static void native_machine_halt(void)
 
 	tboot_shutdown(TB_SHUTDOWN_HALT);
 
+	slaunch_finalize(1);
+
 	stop_this_cpu(NULL);
 }
 
@@ -797,8 +806,12 @@ static void native_machine_power_off(void)
 	if (kernel_can_power_off()) {
 		if (!reboot_force)
 			machine_shutdown();
+		slaunch_finalize(!reboot_force);
 		do_kernel_power_off();
+	} else {
+		slaunch_finalize(0);
 	}
+
 	/* A fallback in case there is no PM info available */
 	tboot_shutdown(TB_SHUTDOWN_HALT);
 }
@@ -826,6 +839,7 @@ void machine_shutdown(void)
 
 void machine_emergency_restart(void)
 {
+	slaunch_finalize(0);
 	__machine_emergency_restart(1);
 }
 
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:08 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:08 -0700
Subject: [PATCH v16 36/38] x86/slaunch: Secure Launch late initcall platform module
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-37-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

The Secure Launch platform module is a late init module. During the
init call, the TPM event log is read and measurements taken in the
early boot stub code are located. These measurements are extended
into the TPM PCRs using the mainline TPM kernel driver.

The platform module also registers the securityfs nodes to allow
fetching and writing events from/to the DRTM TPM event log. In
addition, on Intel, access to TXT register fields is made available
for reading.

Co-developed-by: garnetgrimm <grimmg at ainfosec.com>
Signed-off-by: garnetgrimm <grimmg at ainfosec.com>
Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/kernel/Makefile   |   1 +
 arch/x86/kernel/slmodule.c | 353 +++++++++++++++++++++++++++++++++++++
 2 files changed, 354 insertions(+)
 create mode 100644 arch/x86/kernel/slmodule.c

diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index bf2471701662..8b039ed0a902 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_IA32_EMULATION)	+= tls.o
 obj-y				+= step.o
 obj-$(CONFIG_INTEL_TXT)		+= tboot.o
 obj-$(CONFIG_SECURE_LAUNCH)	+= slaunch.o
+obj-$(CONFIG_SECURE_LAUNCH)	+= slmodule.o
 obj-$(CONFIG_ISA_DMA_API)	+= i8237.o
 obj-y				+= stacktrace.o
 obj-y				+= cpu/
diff --git a/arch/x86/kernel/slmodule.c b/arch/x86/kernel/slmodule.c
new file mode 100644
index 000000000000..9688249e274c
--- /dev/null
+++ b/arch/x86/kernel/slmodule.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Secure Launch late validation/setup, securityfs exposure and finalization.
+ *
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ * Copyright (c) 2026 Assured Information Security, Inc.
+ * Copyright (c) 2026, Oracle and/or its affiliates.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/linkage.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/security.h>
+#include <linux/memblock.h>
+#include <linux/tpm.h>
+#include <asm/segment.h>
+#include <asm/sections.h>
+#include <crypto/sha2.h>
+#include <linux/efi.h>
+#include <linux/slr_table.h>
+#include <linux/slaunch.h>
+
+/*
+ * The macro DECLARE_TXT_PUB_READ_U is used to read values from the TXT
+ * public registers as unsigned values.
+ */
+#define DECLARE_TXT_PUB_READ_U(size, fmt, msg_size)			\
+static ssize_t txt_pub_read_u##size(unsigned int offset,		\
+		loff_t *read_offset,					\
+		size_t read_len,					\
+		char __user *buf)					\
+{									\
+	char msg_buffer[msg_size];					\
+	u##size reg_value = 0;						\
+	void __iomem *txt;						\
+									\
+	txt = ioremap(TXT_PUB_CONFIG_REGS_BASE,				\
+			TXT_NR_CONFIG_PAGES * PAGE_SIZE);		\
+	if (!txt)							\
+		return -EFAULT;						\
+	memcpy_fromio(&reg_value, txt + offset, sizeof(u##size));	\
+	iounmap(txt);							\
+	snprintf(msg_buffer, msg_size, fmt, reg_value);			\
+	return simple_read_from_buffer(buf, read_len, read_offset,	\
+			&msg_buffer, msg_size);				\
+}
+
+DECLARE_TXT_PUB_READ_U(8, "%#04x\n", 6);
+DECLARE_TXT_PUB_READ_U(32, "%#010x\n", 12);
+DECLARE_TXT_PUB_READ_U(64, "%#018llx\n", 20);
+
+#define DECLARE_TXT_FOPS(reg_name, reg_offset, reg_size)		\
+static ssize_t txt_##reg_name##_read(struct file *flip,			\
+		char __user *buf, size_t read_len, loff_t *read_offset)	\
+{									\
+	return txt_pub_read_u##reg_size(reg_offset, read_offset,	\
+			read_len, buf);					\
+}									\
+static const struct file_operations reg_name##_ops = {			\
+	.read = txt_##reg_name##_read,					\
+}
+
+DECLARE_TXT_FOPS(sts, TXT_CR_STS, 64);
+DECLARE_TXT_FOPS(ests, TXT_CR_ESTS, 8);
+DECLARE_TXT_FOPS(errorcode, TXT_CR_ERRORCODE, 32);
+DECLARE_TXT_FOPS(didvid, TXT_CR_DIDVID, 64);
+DECLARE_TXT_FOPS(e2sts, TXT_CR_E2STS, 64);
+DECLARE_TXT_FOPS(ver_emif, TXT_CR_VER_EMIF, 32);
+DECLARE_TXT_FOPS(scratchpad, TXT_CR_SCRATCHPAD, 64);
+
+/*
+ * Securityfs exposure
+ */
+struct memfile {
+	char *name;
+	void *addr;
+	size_t size;
+};
+
+static struct memfile sl_evtlog = { "eventlog", NULL, 0 };
+static void *txt_heap;
+static struct txt_heap_event_log_pointer2_1_element *evtlog21;
+static DEFINE_MUTEX(sl_evt_log_mutex);
+static struct tcg_efi_specid_event_head *efi_head;
+
+static ssize_t sl_evtlog_read(struct file *file, char __user *buf,
+			      size_t count, loff_t *pos)
+{
+	ssize_t size;
+
+	if (!sl_evtlog.addr)
+		return 0;
+
+	mutex_lock(&sl_evt_log_mutex);
+	size = simple_read_from_buffer(buf, count, pos, sl_evtlog.addr,
+				       sl_evtlog.size);
+	mutex_unlock(&sl_evt_log_mutex);
+
+	return size;
+}
+
+static ssize_t sl_evtlog_write(struct file *file, const char __user *buf,
+			       size_t datalen, loff_t *ppos)
+{
+	ssize_t result;
+	char *data;
+
+	if (!sl_evtlog.addr)
+		return 0;
+
+	/* No partial writes. */
+	result = -EINVAL;
+	if (*ppos != 0)
+		goto out;
+
+	data = memdup_user(buf, datalen);
+	if (IS_ERR(data)) {
+		result = PTR_ERR(data);
+		goto out;
+	}
+
+	mutex_lock(&sl_evt_log_mutex);
+	if (evtlog21)
+		result = tpm2_log_event(evtlog21, sl_evtlog.addr,
+					sl_evtlog.size, datalen, data);
+	else
+		result = tpm_log_event(sl_evtlog.addr, sl_evtlog.size,
+				       datalen, data);
+	mutex_unlock(&sl_evt_log_mutex);
+
+	kfree(data);
+out:
+	return result;
+}
+
+static const struct file_operations sl_evtlog_ops = {
+	.read = sl_evtlog_read,
+	.write = sl_evtlog_write,
+	.llseek = default_llseek,
+};
+
+struct sfs_file {
+	const char *name;
+	const struct file_operations *fops;
+};
+
+#define SL_TXT_ENTRY_COUNT	7
+static const struct sfs_file sl_txt_files[] = {
+	{ "sts", &sts_ops },
+	{ "ests", &ests_ops },
+	{ "errorcode", &errorcode_ops },
+	{ "didvid", &didvid_ops },
+	{ "ver_emif", &ver_emif_ops },
+	{ "scratchpad", &scratchpad_ops },
+	{ "e2sts", &e2sts_ops }
+};
+
+/* sysfs file handles */
+static struct dentry *slaunch_dir;
+static struct dentry *event_file;
+static struct dentry *txt_dir;
+static struct dentry *txt_entries[SL_TXT_ENTRY_COUNT];
+
+static long slaunch_expose_securityfs(void)
+{
+	long ret = 0;
+	int i;
+
+	slaunch_dir = securityfs_create_dir("slaunch", NULL);
+	if (IS_ERR(slaunch_dir))
+		return PTR_ERR(slaunch_dir);
+
+	if (slaunch_get_flags() & SL_FLAG_ARCH_TXT) {
+		txt_dir = securityfs_create_dir("txt", slaunch_dir);
+		if (IS_ERR(txt_dir)) {
+			ret = PTR_ERR(txt_dir);
+			goto remove_slaunch;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(sl_txt_files); i++) {
+			txt_entries[i] =
+				securityfs_create_file(sl_txt_files[i].name, 0440, txt_dir,
+						       NULL, sl_txt_files[i].fops);
+			if (IS_ERR(txt_entries[i])) {
+				ret = PTR_ERR(txt_entries[i]);
+				goto remove_files;
+			}
+		}
+	}
+
+	if (sl_evtlog.addr) {
+		event_file = securityfs_create_file(sl_evtlog.name, 0440,
+						    slaunch_dir, NULL,
+						    &sl_evtlog_ops);
+		if (IS_ERR(event_file)) {
+			ret = PTR_ERR(event_file);
+			goto remove_files;
+		}
+	}
+
+	return 0;
+
+remove_files:
+	if (slaunch_get_flags() & SL_FLAG_ARCH_TXT) {
+		while (--i >= 0)
+			securityfs_remove(txt_entries[i]);
+		securityfs_remove(txt_dir);
+	}
+
+remove_slaunch:
+	securityfs_remove(slaunch_dir);
+
+	return ret;
+}
+
+static void slaunch_teardown_securityfs(void)
+{
+	int i;
+
+	securityfs_remove(event_file);
+	if (sl_evtlog.addr) {
+		memunmap(sl_evtlog.addr);
+		sl_evtlog.addr = NULL;
+	}
+	sl_evtlog.size = 0;
+
+	if (slaunch_get_flags() & SL_FLAG_ARCH_TXT) {
+		for (i = 0; i < ARRAY_SIZE(sl_txt_files); i++)
+			securityfs_remove(txt_entries[i]);
+
+		securityfs_remove(txt_dir);
+
+		if (txt_heap) {
+			memunmap(txt_heap);
+			txt_heap = NULL;
+		}
+	}
+
+	securityfs_remove(slaunch_dir);
+}
+
+static void __init slaunch_intel_evtlog(void __iomem *txt)
+{
+	struct slr_entry_log_info *log_info;
+	struct txt_os_mle_data *params;
+	struct slr_table *slrt;
+	void *os_sinit_data;
+	u64 base, size;
+
+	memcpy_fromio(&base, txt + TXT_CR_HEAP_BASE, sizeof(base));
+	memcpy_fromio(&size, txt + TXT_CR_HEAP_SIZE, sizeof(size));
+
+	/* now map TXT heap */
+	txt_heap = memremap(base, size, MEMREMAP_WB);
+	if (!txt_heap)
+		slaunch_reset(txt, "Error memremap TXT heap failed\n", SL_ERROR_HEAP_MAP);
+
+	params = (struct txt_os_mle_data *)slaunch_txt_get_heap_table(txt_heap,
+								      TXT_OS_MLE_DATA_TABLE);
+
+	/* Get the SLRT and remap it */
+	slrt = memremap(params->slrt, sizeof(*slrt), MEMREMAP_WB);
+	if (!slrt)
+		slaunch_reset(txt, "Error memremap SLR Table failed\n", SL_ERROR_SLRT_MAP);
+	size = slrt->size;
+	memunmap(slrt);
+
+	slrt = memremap(params->slrt, size, MEMREMAP_WB);
+	if (!slrt)
+		slaunch_reset(txt, "Error memremap SLR Table failed\n", SL_ERROR_SLRT_MAP);
+
+	log_info = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_LOG_INFO);
+	if (!log_info)
+		slaunch_reset(txt, "Error SLR Table missing entry\n", SL_ERROR_SLRT_MISSING_ENTRY);
+
+	sl_evtlog.size = log_info->size;
+	sl_evtlog.addr = memremap(log_info->addr, log_info->size, MEMREMAP_WB);
+	if (!sl_evtlog.addr)
+		slaunch_reset(txt, "Error memremap TPM event log failed\n", SL_ERROR_EVENTLOG_MAP);
+
+	memunmap(slrt);
+
+	/* Determine if this is TPM 1.2 or 2.0 event log */
+	if (memcmp(sl_evtlog.addr + sizeof(struct tcg_pcr_event),
+		   TCG_SPECID_SIG, sizeof(TCG_SPECID_SIG)))
+		return; /* looks like it is not 2.0 */
+
+	/* For TPM 2.0 logs, the extended heap element must be located */
+	os_sinit_data = slaunch_txt_get_heap_table(txt_heap, TXT_OS_SINIT_DATA_TABLE);
+
+	evtlog21 = txt_find_log2_1_element(os_sinit_data);
+
+	/*
+	 * If this fails, things are in really bad shape. Any attempt to write
+	 * events to the log will fail.
+	 */
+	if (!evtlog21)
+		slaunch_reset(txt, "Error locate TPM20 event log element failed\n",
+			      SL_ERROR_TPM_INVALID_LOG20);
+
+	/* Save pointer to the EFI SpecID log header */
+	efi_head = (struct tcg_efi_specid_event_head *)(sl_evtlog.addr +
+							sizeof(struct tcg_pcr_event));
+}
+
+static void __init slaunch_tpm_open_locality2(void __iomem *txt)
+{
+	struct tpm_chip *tpm;
+	int rc;
+
+	tpm = tpm_default_chip();
+	if (!tpm)
+		slaunch_reset(txt, "Could not get default TPM chip\n", SL_ERROR_TPM_INIT);
+
+	rc = tpm_chip_set_locality(tpm, 2);
+	if (rc)
+		slaunch_reset(txt, "Could not set TPM chip locality 2\n", SL_ERROR_TPM_INIT);
+}
+
+static int __init slaunch_module_init(void)
+{
+	void __iomem *txt;
+
+	/* Check to see if Secure Launch happened */
+	if ((slaunch_get_flags() & (SL_FLAG_ACTIVE|SL_FLAG_ARCH_TXT)) !=
+	    (SL_FLAG_ACTIVE | SL_FLAG_ARCH_TXT))
+		return 0;
+
+	txt = ioremap(TXT_PRIV_CONFIG_REGS_BASE, TXT_NR_CONFIG_PAGES *
+		      PAGE_SIZE);
+	if (!txt)
+		panic("Error ioremap of TXT priv registers\n");
+
+	/* Only Intel TXT is supported at this point */
+	slaunch_intel_evtlog(txt);
+	slaunch_tpm_open_locality2(txt);
+	iounmap(txt);
+
+	return slaunch_expose_securityfs();
+}
+
+static void __exit slaunch_module_exit(void)
+{
+	slaunch_teardown_securityfs();
+}
+
+late_initcall(slaunch_module_init);
+__exitcall(slaunch_module_exit);
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:09 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:09 -0700
Subject: [PATCH v16 37/38] x86/efistub: EFI stub DRTM support for Secure Launch
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-38-ross.philipson@gmail.com>

From: Ard Biesheuvel <ardb at kernel.org>

Invoke the Secure Launch protocol exposed by the boot loader at the
appropriate time to perform a measured launch of the decompressed
kernel after ExitBootServices().

Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 drivers/firmware/efi/libstub/Makefile      |  1 +
 drivers/firmware/efi/libstub/efistub.h     | 24 ++++++++++++++
 drivers/firmware/efi/libstub/x86-slaunch.c | 38 ++++++++++++++++++++++
 drivers/firmware/efi/libstub/x86-stub.c    | 27 ++++++++++++---
 4 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 drivers/firmware/efi/libstub/x86-slaunch.c

diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index e386ffd009b7..fd5eaf3142b2 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -86,6 +86,7 @@ lib-$(CONFIG_ARM)		+= arm32-stub.o
 lib-$(CONFIG_ARM64)		+= kaslr.o arm64.o arm64-stub.o smbios.o
 lib-$(CONFIG_X86)		+= x86-stub.o smbios.o
 lib-$(CONFIG_X86_64)		+= x86-5lvl.o
+lib-$(CONFIG_SECURE_LAUNCH)	+= x86-slaunch.o
 lib-$(CONFIG_RISCV)		+= kaslr.o riscv.o riscv-stub.o
 lib-$(CONFIG_LOONGARCH)		+= loongarch.o loongarch-stub.o
 
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 979a21818cc1..18301ba3ae0f 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1267,4 +1267,28 @@ void arch_accept_memory(phys_addr_t start, phys_addr_t end);
 efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size);
 efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen);
 
+#ifdef CONFIG_SECURE_LAUNCH
+efi_status_t efi_secure_launch_init(efi_handle_t image_handle);
+efi_status_t efi_secure_launch_prepare(struct boot_params *boot_params,
+				       phys_addr_t base);
+void efi_secure_launch(void);
+#else
+static inline
+efi_status_t efi_secure_launch_init(efi_handle_t image_handle)
+{
+	return EFI_UNSUPPORTED;
+}
+
+static inline
+efi_status_t efi_secure_launch_prepare(struct boot_params *boot_params,
+				       phys_addr_t base)
+{
+	return EFI_SUCCESS;
+}
+
+static inline void efi_secure_launch(void)
+{
+}
+#endif
+
 #endif
diff --git a/drivers/firmware/efi/libstub/x86-slaunch.c b/drivers/firmware/efi/libstub/x86-slaunch.c
new file mode 100644
index 000000000000..98ff15f94996
--- /dev/null
+++ b/drivers/firmware/efi/libstub/x86-slaunch.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/efi.h>
+#include <linux/pci.h>
+#include <linux/stddef.h>
+#include <linux/slr_efi.h>
+#include <linux/slaunch.h>
+
+#include <asm/boot.h>
+#include <asm/bootparam.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+static struct efi_slaunch_protocol *slaunch;
+
+efi_status_t efi_secure_launch_init(efi_handle_t image_handle)
+{
+	return efi_bs_call(handle_protocol, image_handle,
+			   &EFI_SLAUNCH_PROTOCOL_GUID, (void **)&slaunch);
+}
+
+efi_status_t efi_secure_launch_prepare(struct boot_params *boot_params,
+				       phys_addr_t base)
+{
+	if (!slaunch)
+		return EFI_SUCCESS;
+
+	return slaunch->setup_dlme(slaunch, base, mle_header_offset, (u64)boot_params);
+}
+
+void efi_secure_launch(void)
+{
+	if (!slaunch)
+		return;
+
+	slaunch->launch(slaunch);
+}
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index cef32e2c82d8..339e63ae84ef 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -833,7 +833,8 @@ static efi_status_t parse_options(const char *cmdline)
 }
 
 static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry,
-					  struct boot_params *boot_params)
+					  struct boot_params *boot_params,
+					  unsigned long alloc_limit)
 {
 	unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
 	unsigned long addr, alloc_size, entry;
@@ -877,8 +878,7 @@ static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry,
 
 	status = efi_random_alloc(alloc_size, CONFIG_PHYSICAL_ALIGN, &addr,
 				  seed[0], EFI_LOADER_CODE,
-				  LOAD_PHYSICAL_ADDR,
-				  EFI_X86_KERNEL_ALLOC_LIMIT);
+				  LOAD_PHYSICAL_ADDR, alloc_limit);
 	if (status != EFI_SUCCESS)
 		return status;
 
@@ -890,6 +890,10 @@ static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry,
 
 	*kernel_entry = addr + entry;
 
+	status = efi_secure_launch_prepare(boot_params, addr);
+	if (status != EFI_SUCCESS)
+		return status;
+
 	return efi_adjust_memory_range_protection(addr, kernel_text_size) ?:
 	       efi_adjust_memory_range_protection(addr + kernel_inittext_offset,
 						  kernel_inittext_size);
@@ -914,6 +918,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 			       struct boot_params *boot_params)
 
 {
+	unsigned long alloc_limit = EFI_X86_KERNEL_ALLOC_LIMIT;
 	efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID;
 	const struct linux_efi_initrd *initrd = NULL;
 	unsigned long kernel_entry;
@@ -925,6 +930,17 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 	if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
 		efi_exit(handle, EFI_INVALID_PARAMETER);
 
+	status = efi_secure_launch_init(handle);
+	switch (status) {
+	case EFI_SUCCESS:
+		alloc_limit = U32_MAX;
+		break;
+	case EFI_UNSUPPORTED:
+		break;
+	default:
+		efi_exit(handle, status);
+	}
+
 	if (!IS_ENABLED(CONFIG_EFI_HANDOVER_PROTOCOL) || !boot_params) {
 		status = efi_allocate_bootparams(handle, &boot_params);
 		if (status != EFI_SUCCESS)
@@ -974,7 +990,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 	if (efi_mem_encrypt > 0)
 		hdr->xloadflags |= XLF_MEM_ENCRYPTION;
 
-	status = efi_decompress_kernel(&kernel_entry, boot_params);
+	status = efi_decompress_kernel(&kernel_entry, boot_params, alloc_limit);
 	if (status != EFI_SUCCESS) {
 		efi_err("Failed to decompress kernel\n");
 		goto fail;
@@ -1029,6 +1045,9 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 		goto fail;
 	}
 
+	/* If a Secure Launch is in progress, this never returns */
+	efi_secure_launch();
+
 	/*
 	 * Call the SEV init code while still running with the firmware's
 	 * GDT/IDT, so #VC exceptions will be handled by EFI.
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:14:10 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:14:10 -0700
Subject: [PATCH v16 38/38] x86/boot: Legacy boot DRTM support for Secure Launch
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-39-ross.philipson@gmail.com>

From: Ard Biesheuvel <ardb at kernel.org>

Implement Secure Launch D-RTM of the decompressed kernel via a
callback interface exposed by the Secure Launch Resource Table (SLRT), a
reference to which is added to struct boot_params.

This permits a boot loader to set up the Secure Launch, allow the
decompressor to execute up to the point where it would otherwise boot the
core kernel, and at that point, perform the Dynamic Launch Event in a
architecture/vendor specific manner. This is similar to how EFI boot
achieves this, using a EFI protocol exposed by the boot loader.

This requires that the decompressor unpacks the kernel into the buffer that
it was started from itself, and so physical KASLR needs to be omitted
(although the boot loader is free to place the decompressor at any
suitably aligned locations in system memory, and so it can perform the
physical randomization itself).

It also relies on the demand paging logic in the decompressor, to ensure
that the SLRT and the entry point it describes are callable, at least to
the extent that allows the callback code to re-establish its own
execution environment.

Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 Documentation/arch/x86/zero-page.rst  |  1 +
 arch/x86/boot/compressed/misc.c       | 51 ++++++++++++++++++++++++---
 arch/x86/boot/compressed/pgtable_64.c |  7 ++++
 arch/x86/include/uapi/asm/bootparam.h |  2 +-
 4 files changed, 56 insertions(+), 5 deletions(-)

diff --git a/Documentation/arch/x86/zero-page.rst b/Documentation/arch/x86/zero-page.rst
index 45aa9cceb4f1..dd98b467929c 100644
--- a/Documentation/arch/x86/zero-page.rst
+++ b/Documentation/arch/x86/zero-page.rst
@@ -20,6 +20,7 @@ Offset/Size	Proto	Name			Meaning
 060/010		ALL	ist_info		Intel SpeedStep (IST) BIOS support information
 						(struct ist_info)
 070/008		ALL	acpi_rsdp_addr		Physical address of ACPI RSDP table
+078/008		ALL	slr_table_addr		Physical address of Secure Launch Resource Table
 080/010		ALL	hd0_info		hd0 disk parameter, OBSOLETE!!
 090/010		ALL	hd1_info		hd1 disk parameter, OBSOLETE!!
 0A0/010		ALL	sys_desc_table		System description table (struct sys_desc_table),
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c
index e3b5177bfa6f..eaaface4cd7d 100644
--- a/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -17,6 +17,7 @@
 #include "../string.h"
 #include "../voffset.h"
 #include <asm/bootparam_utils.h>
+#include <linux/slr_table.h>
 
 /*
  * WARNING!!
@@ -391,6 +392,36 @@ static void early_sev_detect(void)
 		lines = cols = 0;
 }
 
+#ifdef CONFIG_SECURE_LAUNCH
+static void sl_initiate_launch(unsigned long table, unsigned long base)
+{
+	struct slr_table *slrt = (void *)table;
+	struct slr_entry_dl_info *dl_info;
+	struct slr_setup_dlme dlme;
+	dl_launch_func launch_fn;
+
+	dlme.dlme_base = base;
+	dlme.dlme_header_offset = mle_header_offset;
+	dlme.dlme_table = 0;
+
+	if (!slrt)
+		return;
+
+	dl_info = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_DL_INFO);
+	if (!dl_info)
+		return;
+
+	launch_fn = (void *)dl_info->dl_launch;
+
+	/* Do the Dynamic Launch Event */
+	launch_fn(&dl_info->bl_context, &dlme);
+}
+#else
+static inline void sl_initiate_launch(unsigned long table, unsigned long base)
+{
+}
+#endif
+
 /*
  * The compressed kernel image (ZO), has been moved so that its position
  * is against the end of the buffer used to hold the uncompressed kernel
@@ -491,10 +522,15 @@ asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)
 	debug_putaddr(trampoline_32bit);
 #endif
 
-	choose_random_location((unsigned long)input_data, input_len,
-				(unsigned long *)&output,
-				needed_size,
-				&virt_addr);
+	/*
+	 * When doing a secure launch, the actual launch will be initiated by
+	 * jumping back to the bootloader. Omit physical KASLR in that case, to
+	 * avoid trampling on its code or data inadvertently.
+	 */
+	if (!boot_params_ptr->slr_table_addr)
+		choose_random_location((unsigned long)input_data, input_len,
+				       (unsigned long *)&output,
+				       needed_size, &virt_addr);
 
 	/* Validate memory location choices. */
 	if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
@@ -528,6 +564,13 @@ asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)
 	debug_puthex(entry_offset);
 	debug_putstr(").\n");
 
+	/*
+	 * Secure Launch involves calling back into the bootloader, so this
+	 * needs to happen before disabling exception handling, to ensure that
+	 * the entry point will be mapped on demand if needed.
+	 */
+	sl_initiate_launch(boot_params_ptr->slr_table_addr, (unsigned long)output);
+
 	/* Disable exception handling before booting the kernel */
 	cleanup_exception_handling();
 
diff --git a/arch/x86/boot/compressed/pgtable_64.c b/arch/x86/boot/compressed/pgtable_64.c
index 3e9d651da73e..f82094669ac0 100644
--- a/arch/x86/boot/compressed/pgtable_64.c
+++ b/arch/x86/boot/compressed/pgtable_64.c
@@ -124,6 +124,13 @@ asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable)
 
 	l5_required = !cmdline_find_option_bool("no5lvl");
 
+	/*
+	 * Don't change the number of levels when doing a Secure Launch. The
+	 * Secure Launch stub will take care of that if needed.
+	 */
+	if (bp->slr_table_addr)
+		l5_required = l5_enabled;
+
 	if (l5_required) {
 		/* Initialize variables for 5-level paging */
 		__pgtable_l5_enabled = 1;
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index 8155fa899f50..bc2ef37096af 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -121,7 +121,7 @@ struct boot_params {
 	__u64  tboot_addr;				/* 0x058 */
 	struct ist_info ist_info;			/* 0x060 */
 	__u64 acpi_rsdp_addr;				/* 0x070 */
-	__u8  _pad3[8];					/* 0x078 */
+	__u64 slr_table_addr;				/* 0x078 */
 	__u8  hd0_info[16];	/* obsolete! */		/* 0x080 */
 	__u8  hd1_info[16];	/* obsolete! */		/* 0x090 */
 	struct sys_desc_table sys_desc_table; /* obsolete! */	/* 0x0a0 */
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:55 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:55 -0700
Subject: [PATCH v16 23/38] x86: Allow WARN_trap() macro to be included in pre-boot environments
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-24-ross.philipson@gmail.com>

For pre-boot environments, do not use the static call definition of the
WARN_trap() macro.

Suggested-by: Peter Zijlstra <peterz at infradead.org>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/include/asm/bug.h | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 80c1696d8d59..7899768ae644 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -138,8 +138,14 @@ do {									\
 #ifdef HAVE_ARCH_BUG_FORMAT_ARGS
 
 #ifndef __ASSEMBLER__
+
+#ifndef __DISABLE_EXPORTS
 #include <linux/static_call_types.h>
 DECLARE_STATIC_CALL(WARN_trap, __WARN_trap);
+#define WARN_trap(...)	static_call_mod(WARN_trap)(__VA_ARGS__)
+#else /* __DISABLE_EXPORTS */
+#define WARN_trap(...)	__WARN_trap(__VA_ARGS__)
+#endif /* __DISABLE_EXPORTS */
 
 struct pt_regs;
 struct sysv_va_list { /* from AMD64 System V ABI */
@@ -172,7 +178,7 @@ extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs);
 #define __WARN_print_arg(flags, format, arg...)				\
 do {									\
 	int __flags = (flags) | BUGFLAG_WARNING | BUGFLAG_ARGS ;	\
-	static_call_mod(WARN_trap)(__WARN_bug_entry(__flags, format), ## arg); \
+	WARN_trap(__WARN_bug_entry(__flags, format), ## arg);		\
 	asm (""); /* inhibit tail-call optimization */			\
 } while (0)
 
-- 
2.47.3



From ross.philipson at gmail.com  Fri May 15 14:13:59 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:59 -0700
Subject: [PATCH v16 27/38] x86: Add early SHA-256 support for Secure Launch early measurements
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-28-ross.philipson@gmail.com>

From: "Daniel P. Smith" <dpsmith at apertussolutions.com>

The SHA-256 algorithm is necessary to measure configuration information
into the TPM as early as possible before using the values. This
implementation uses the established approach of #including the SHA-256
library directly in the early boot code.

Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
---
 arch/x86/boot/startup/Makefile     | 1 +
 arch/x86/boot/startup/lib-sha256.c | 6 ++++++
 2 files changed, 7 insertions(+)
 create mode 100644 arch/x86/boot/startup/lib-sha256.c

diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index e283ee4c1f45..071a90f23ae0 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_X86_64)		+= gdt_idt.o map_kernel.o
 obj-$(CONFIG_AMD_MEM_ENCRYPT)	+= sme.o sev-startup.o
 
 slaunch-objs			+= lib-sha1.o
+slaunch-objs			+= lib-sha256.o
 obj-$(CONFIG_SECURE_LAUNCH)	+= $(slaunch-objs)
 
 pi-objs				:= $(patsubst %.o,$(obj)/%.o,$(obj-y))
diff --git a/arch/x86/boot/startup/lib-sha256.c b/arch/x86/boot/startup/lib-sha256.c
new file mode 100644
index 000000000000..f60df97f9244
--- /dev/null
+++ b/arch/x86/boot/startup/lib-sha256.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Apertus Solutions, LLC
+ */
+
+#include "../../../../lib/crypto/sha256.c"
-- 
2.47.3



From rdunlap at infradead.org  Fri May 15 15:02:16 2026
From: rdunlap at infradead.org (Randy Dunlap)
Date: Fri, 15 May 2026 15:02:16 -0700
Subject: [PATCH v16 15/38] Documentation/security: Secure Launch kernel
 documentation
In-Reply-To: <20260515211410.31440-16-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-16-ross.philipson@gmail.com>
Message-ID: <130c4cfa-7388-462f-9b0f-7939680a90ed@infradead.org>



On 5/15/26 2:13 PM, Ross Philipson wrote:
> From: "Daniel P. Smith" <dpsmith at apertussolutions.com>
> 
> Introduce documentation for the Linux Secure Launch feature.
> 
> Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
> Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
> ---
>  Documentation/arch/x86/boot.rst               |   8 +
>  Documentation/security/index.rst              |   1 +
>  .../security/launch-integrity/index.rst       |   9 +
>  .../launch-integrity/secure_launch.rst        | 681 ++++++++++++++++++
>  4 files changed, 699 insertions(+)
>  create mode 100644 Documentation/security/launch-integrity/index.rst
>  create mode 100644 Documentation/security/launch-integrity/secure_launch.rst
> 


> diff --git a/Documentation/security/launch-integrity/secure_launch.rst b/Documentation/security/launch-integrity/secure_launch.rst
> new file mode 100644
> index 000000000000..b4c61fdceaf0
> --- /dev/null
> +++ b/Documentation/security/launch-integrity/secure_launch.rst
> @@ -0,0 +1,681 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +.. Copyright (c) 2019-2026 Daniel P. Smith <dpsmith at apertussolutions.com>
> +
> +======================
> +Secure Launch Overview
> +======================
> +
> +:Author: Daniel P. Smith
> +:Date: February 2026

maybe update?

> +
> +Overview
> +========

[snip]

> +Error Codes
> +===========
> +
> +The TXT specification defines the layout for TXT 32 bit error code values.

                                                    32-bit

> +The bit encodings indicate where the error originated (e.g. with the CPU,
> +in the SINIT ACM, in software). The error is written to a sticky TXT
> +register that persists across resets called TXT.ERRORCODE (see the TXT
> +MLE Development Guide). The errors defined by the Secure Launch feature are
> +those generated in the MLE software. They have the format::
> +
> +  0xc0008XXX
> +
> +The low 12 bits are free for defining the following Secure Launch specific
> +error codes.

[snip]

> +Resources
> +=========
> +
> +The TrenchBoot project:
> +
> +https://trenchboot.org
> +
> +Secure Launch Specification:
> +
> +https://trenchboot.org/specifications/Secure_Launch/
> +
> +Trusted Computing Group's D-RTM Architecture:
> +
> +https://trustedcomputinggroup.org/wp-content/uploads/TCG_D-RTM_Architecture_v1-0_Published_06172013.pdf
> +
> +TXT documentation in the Intel TXT MLE Development Guide:
> +
> +https://www.intel.com/content/dam/www/public/us/en/documents/guides/intel-txt-software-development-guide.pdf
> +
> +TXT instructions documentation in the Intel SDM Instruction Set volume:
> +
> +https://software.intel.com/en-us/articles/intel-sdm
> +
> +AMD SKINIT documentation in the System Programming manual:
> +
> +https://www.amd.com/system/files/TechDocs/24593.pdf
> +
> +GRUB Secure Launch support:
> +
> +https://github.com/TrenchBoot/grub/tree/grub-sl-fc-38-dlstub
> +
> +FOSDEM 2021: Secure Upgrades with DRTM
> +
> +https://archive.fosdem.org/2021/schedule/event/firmware_suwd/
> +
> +.. [1]
> +    MLE: Measured Launch Environment is the binary runtime that is measured and
> +    then run by the TXT SINIT ACM. The TXT MLE Development Guide describes the
> +    requirements for the MLE in detail.
> +
> +.. [2]
> +    PMR: Intel VTd has a feature in the IOMMU called Protected Memory Registers.
> +    There are two of these registers and they allow all DMA to be blocked
> +    to large areas of memory. The low PMR can cover all memory below 4Gb on 2Mb
> +    boundaries. The high PMR can cover all RAM on the system, again on 2Mb
> +    boundaries. This feature is used during a Secure Launch by TXT.
> +
> +.. [3]
> +    Secure Launch Specification: https://trenchboot.org/specifications/Secure_Launch/
> +
> +.. [4]
> +    ACM: Intel's Authenticated Code Module. This is the 32b bit binary blob that

Does "32b" mean something or should that be 32-bit?

> +    is run securely by the GETSEC[SENTER] during a measured launch. It is described
> +    in the Intel documentation on TXT and versions for various chipsets are
> +    signed and distributed by Intel.

-- 
~Randy



From dave.hansen at intel.com  Fri May 15 15:32:13 2026
From: dave.hansen at intel.com (Dave Hansen)
Date: Fri, 15 May 2026 15:32:13 -0700
Subject: [PATCH v16 29/38] x86/tpm: Early startup TPM PCR extending driver
In-Reply-To: <20260515211410.31440-30-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-30-ross.philipson@gmail.com>
Message-ID: <7648b5f3-2810-4bae-99ab-21c93ea36f25@intel.com>

On 5/15/26 14:14, Ross Philipson wrote:
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Based of the original tpm_tis.c implementation as found in the
> + * Linux 2.6 code base.
> + *
> + * Copyright (C) 2005, 2006 IBM Corporation
> + *
> + * Authors:
> + * Leendert van Doorn <leendert at watson.ibm.com>
> + * Kylene Hall <kjhall at us.ibm.com>
> + */

Hey guys,

Last time:

> https://lore.kernel.org/lkml/56929e8b-a7cf-4390-b4ec-0b4c2c32b311 at intel.com/

So, fast forward another version and six months ... the "fishy" bits
ripped out of it, license changed and ... crickets in the changelogs and
cover letter from what I can see.

Second, the cadence on these is rather slow. I don't know what the delta
from the last one is, but it doesn't _look_ like a six month project.
It's really hard for reviewers to page stuff back in after that long.

Also, new since the last post, you got Sashiko'd:

> https://sashiko.dev/#/patchset/20260515211410.31440-1-ross.philipson%40gmail.com

I'd suggest working through those first before the maintainers take
another crack at this. There are a *LOT* of comments in there.

I'd appreciate if you'd go back through the v15 comments and make sure
you got all of them. I certainly felt like you didn't address at least
on of my questions (the licensing stuff).


From jarkko at kernel.org  Fri May 15 16:03:59 2026
From: jarkko at kernel.org (Jarkko Sakkinen)
Date: Sat, 16 May 2026 02:03:59 +0300
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public
 headers
In-Reply-To: <20260515211410.31440-2-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-2-ross.philipson@gmail.com>
Message-ID: <agemXwxVb9jvAbYM@kernel.org>

On Fri, May 15, 2026 at 02:13:33PM -0700, Ross Philipson wrote:
> Consolidate TPM1 constants in tpm_command.h and remove duplicate
> constants from tpm1-cmd.c.
> 
> Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
> ---
>  drivers/char/tpm/tpm-buf.c                |  1 -
>  drivers/char/tpm/tpm1-cmd.c               | 14 +-------
>  include/keys/trusted_tpm.h                |  1 -
>  include/linux/tpm.h                       |  2 ++
>  include/linux/tpm_command.h               | 41 ++++++++++++++++-------
>  security/keys/trusted-keys/trusted_tpm1.c |  1 -
>  security/keys/trusted-keys/trusted_tpm2.c |  1 -
>  7 files changed, 31 insertions(+), 30 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
> index dc882fc9fa9e..4c4f450630df 100644
> --- a/drivers/char/tpm/tpm-buf.c
> +++ b/drivers/char/tpm/tpm-buf.c
> @@ -3,7 +3,6 @@
>   * Handling of TPM command and other buffers.
>   */
>  
> -#include <linux/tpm_command.h>
>  #include <linux/module.h>
>  #include <linux/tpm.h>
>  
> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> index b49a790f1bd5..664ca1fff2e8 100644
> --- a/drivers/char/tpm/tpm1-cmd.c
> +++ b/drivers/char/tpm/tpm1-cmd.c
> @@ -22,8 +22,6 @@
>  
>  #include "tpm.h"
>  
> -#define TPM_MAX_ORDINAL 243
> -
>  /*
>   * Array with one entry per ordinal defining the maximum amount
>   * of time the chip could take to return the result.  The ordinal
> @@ -308,9 +306,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
>  		return duration;
>  }
>  
> -#define TPM_ORD_STARTUP 153
> -#define TPM_ST_CLEAR 1
> -
>  /**
>   * tpm1_startup() - turn on the TPM
>   * @chip: TPM chip to use
> @@ -459,7 +454,6 @@ int tpm1_get_timeouts(struct tpm_chip *chip)
>  	return 0;
>  }
>  
> -#define TPM_ORD_PCR_EXTEND 20
>  int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
>  		    const char *log_msg)
>  {
> @@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
>  	return rc;
>  }
>  
> -#define TPM_ORD_GET_CAP 101
>  ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>  		    const char *desc, size_t min_cap_length)
>  {
> @@ -511,7 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>  }
>  EXPORT_SYMBOL_GPL(tpm1_getcap);
>  
> -#define TPM_ORD_GET_RANDOM 70
>  struct tpm1_get_random_out {
>  	__be32 rng_data_len;
>  	u8 rng_data[TPM_MAX_RNG_DATA];
> @@ -580,13 +572,12 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
>  	return rc;
>  }
>  
> -#define TPM_ORD_PCRREAD 21
>  int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>  {
>  	struct tpm_buf buf;
>  	int rc;
>  
> -	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
> +	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
>  	if (rc)
>  		return rc;
>  
> @@ -609,7 +600,6 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
>  	return rc;
>  }
>  
> -#define TPM_ORD_CONTINUE_SELFTEST 83
>  /**
>   * tpm1_continue_selftest() - run TPM's selftest
>   * @chip: TPM chip to use
> @@ -726,8 +716,6 @@ int tpm1_auto_startup(struct tpm_chip *chip)
>  	return rc;
>  }
>  
> -#define TPM_ORD_SAVESTATE 152
> -
>  /**
>   * tpm1_pm_suspend() - pm suspend handler
>   * @chip: TPM chip to use.
> diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
> index 0fadc6a4f166..3a0fa3bc8454 100644
> --- a/include/keys/trusted_tpm.h
> +++ b/include/keys/trusted_tpm.h
> @@ -3,7 +3,6 @@
>  #define __TRUSTED_TPM_H
>  
>  #include <keys/trusted-type.h>
> -#include <linux/tpm_command.h>
>  
>  extern struct trusted_key_ops trusted_key_tpm_ops;
>  
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 202da079d500..1846d5485a2c 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -25,6 +25,8 @@
>  #include <crypto/hash_info.h>
>  #include <crypto/aes.h>
>  
> +#include <linux/tpm_command.h>
> +
>  #define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
>  
>  #define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
> index f5c03e9c3913..174b043d8bbc 100644
> --- a/include/linux/tpm_command.h
> +++ b/include/linux/tpm_command.h
> @@ -3,27 +3,42 @@
>  #define __LINUX_TPM_COMMAND_H__
>  
>  /*
> - * TPM Command constants from specifications at
> - * http://www.trustedcomputinggroup.org
> + * == TPM 1 Family Chips ==
> + *
> + * TPM 1.2 Main Specification:
> + * https://trustedcomputinggroup.org/resource/tpm-main-specification/
>   */
>  
> +#define TPM_MAX_ORDINAL	243
> +
>  /* Command TAGS */
> -#define TPM_TAG_RQU_COMMAND             193
> -#define TPM_TAG_RQU_AUTH1_COMMAND       194
> -#define TPM_TAG_RQU_AUTH2_COMMAND       195
> -#define TPM_TAG_RSP_COMMAND             196
> -#define TPM_TAG_RSP_AUTH1_COMMAND       197
> -#define TPM_TAG_RSP_AUTH2_COMMAND       198
> +enum tpm_command_tags {
> +	TPM_TAG_RQU_COMMAND		= 193,
> +	TPM_TAG_RQU_AUTH1_COMMAND	= 194,
> +	TPM_TAG_RQU_AUTH2_COMMAND	= 195,
> +	TPM_TAG_RSP_COMMAND		= 196,
> +	TPM_TAG_RSP_AUTH1_COMMAND	= 197,
> +	TPM_TAG_RSP_AUTH2_COMMAND	= 198,
> +};
>  
>  /* Command Ordinals */
> -#define TPM_ORD_GETRANDOM               70
> -#define TPM_ORD_OSAP                    11
> -#define TPM_ORD_OIAP                    10
> -#define TPM_ORD_SEAL                    23
> -#define TPM_ORD_UNSEAL                  24
> +enum tpm_command_ordinals {
> +	TPM_ORD_CONTINUE_SELFTEST	= 83,
> +	TPM_ORD_GET_CAP			= 101,
> +	TPM_ORD_GET_RANDOM		= 70,
> +	TPM_ORD_PCR_EXTEND		= 20,
> +	TPM_ORD_PCR_READ		= 21,
> +	TPM_ORD_OSAP			= 11,
> +	TPM_ORD_OIAP			= 10,
> +	TPM_ORD_SAVESTATE		= 152,
> +	TPM_ORD_SEAL			= 23,
> +	TPM_ORD_STARTUP			= 153,
> +	TPM_ORD_UNSEAL			= 24,
> +};
>  
>  /* Other constants */
>  #define SRKHANDLE                       0x40000000
>  #define TPM_NONCE_SIZE                  20
> +#define TPM_ST_CLEAR			1
>  
>  #endif
> diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
> index 6ea728f1eae6..0d3244af8de3 100644
> --- a/security/keys/trusted-keys/trusted_tpm1.c
> +++ b/security/keys/trusted-keys/trusted_tpm1.c
> @@ -18,7 +18,6 @@
>  #include <keys/trusted-type.h>
>  #include <linux/key-type.h>
>  #include <linux/tpm.h>
> -#include <linux/tpm_command.h>
>  
>  #include <keys/trusted_tpm.h>
>  
> diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
> index 6340823f8b53..29d79c05ed6b 100644
> --- a/security/keys/trusted-keys/trusted_tpm2.c
> +++ b/security/keys/trusted-keys/trusted_tpm2.c
> @@ -9,7 +9,6 @@
>  #include <linux/string.h>
>  #include <linux/err.h>
>  #include <linux/tpm.h>
> -#include <linux/tpm_command.h>
>  
>  #include <keys/trusted-type.h>
>  #include <keys/trusted_tpm.h>
> -- 
> 2.47.3
> 

LGTM

I'll hold on from actual tags up until there is some consensus with the
patch set.

BR, Jarkko


From jgg at ziepe.ca  Fri May 15 16:05:53 2026
From: jgg at ziepe.ca (Jason Gunthorpe)
Date: Fri, 15 May 2026 20:05:53 -0300
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public
 headers
In-Reply-To: <agemXwxVb9jvAbYM@kernel.org>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-2-ross.philipson@gmail.com>
 <agemXwxVb9jvAbYM@kernel.org>
Message-ID: <20260515230553.GO7702@ziepe.ca>

On Sat, May 16, 2026 at 02:03:59AM +0300, Jarkko Sakkinen wrote:

> LGTM
> 
> I'll hold on from actual tags up until there is some consensus with the
> patch set.

This patch set is huge, and I know there is alot of interest now in
DRTM.

Can we please split out and progress the TPM reorg mini-series at the
front?

Jason


From dave.hansen at intel.com  Fri May 15 16:10:41 2026
From: dave.hansen at intel.com (Dave Hansen)
Date: Fri, 15 May 2026 16:10:41 -0700
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public
 headers
In-Reply-To: <20260515230553.GO7702@ziepe.ca>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-2-ross.philipson@gmail.com>
 <agemXwxVb9jvAbYM@kernel.org> <20260515230553.GO7702@ziepe.ca>
Message-ID: <8f8328d3-a7db-4a64-82f8-1e0e3eff93cf@intel.com>

On 5/15/26 16:05, Jason Gunthorpe wrote:
> Can we please split out and progress the TPM reorg mini-series at the
> front?

Yes, please.

Any way to break this down and merge in more bite-size pieces would be
better for everyone involved.


From jarkko at kernel.org  Fri May 15 16:14:11 2026
From: jarkko at kernel.org (Jarkko Sakkinen)
Date: Sat, 16 May 2026 02:14:11 +0300
Subject: [PATCH v16 02/38] tpm: Move TPM1 specific definitions to the
 command header
In-Reply-To: <20260515211410.31440-3-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-3-ross.philipson@gmail.com>
Message-ID: <ageow-IuSpY8O6bq@kernel.org>

On Fri, May 15, 2026 at 02:13:34PM -0700, Ross Philipson wrote:
> Gather all the TPM1 definitions and structures in the internal header
> file drivers/char/tpm/tpm.h into the command header. In addition, bring
> in the single RNG structure from tpm-interface.c.
> 
> The definitions moved to these files correspond to the TCG specification
> for TPM 1 family:
> 
> TPM 1.2 Main Specification
>  -  https://trustedcomputinggroup.org/resource/tpm-main-specification/
> 
> Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
> ---
>  drivers/char/tpm/tpm.h      | 102 --------------------------------
>  drivers/char/tpm/tpm1-cmd.c |   5 --
>  include/linux/tpm_command.h | 115 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 115 insertions(+), 107 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index 87d68ddf270a..043d78a9617a 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -52,105 +52,9 @@ enum tpm_addr {
>  	TPM_ADDR = 0x4E,
>  };
>  
> -#define TPM_WARN_RETRY          0x800
> -#define TPM_WARN_DOING_SELFTEST 0x802
> -#define TPM_ERR_DEACTIVATED     0x6
> -#define TPM_ERR_DISABLED        0x7
> -#define TPM_ERR_FAILEDSELFTEST  0x1C
> -#define TPM_ERR_INVALID_POSTINIT 38
> -
> -#define TPM_TAG_RQU_COMMAND 193
> -
>  /* TPM2 specific constants. */
>  #define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
>  
> -struct	stclear_flags_t {
> -	__be16	tag;
> -	u8	deactivated;
> -	u8	disableForceClear;
> -	u8	physicalPresence;
> -	u8	physicalPresenceLock;
> -	u8	bGlobalLock;
> -} __packed;
> -
> -struct tpm1_version {
> -	u8 major;
> -	u8 minor;
> -	u8 rev_major;
> -	u8 rev_minor;
> -} __packed;
> -
> -struct tpm1_version2 {
> -	__be16 tag;
> -	struct tpm1_version version;
> -} __packed;
> -
> -struct	timeout_t {
> -	__be32	a;
> -	__be32	b;
> -	__be32	c;
> -	__be32	d;
> -} __packed;
> -
> -struct duration_t {
> -	__be32	tpm_short;
> -	__be32	tpm_medium;
> -	__be32	tpm_long;
> -} __packed;
> -
> -struct permanent_flags_t {
> -	__be16	tag;
> -	u8	disable;
> -	u8	ownership;
> -	u8	deactivated;
> -	u8	readPubek;
> -	u8	disableOwnerClear;
> -	u8	allowMaintenance;
> -	u8	physicalPresenceLifetimeLock;
> -	u8	physicalPresenceHWEnable;
> -	u8	physicalPresenceCMDEnable;
> -	u8	CEKPUsed;
> -	u8	TPMpost;
> -	u8	TPMpostLock;
> -	u8	FIPS;
> -	u8	operator;
> -	u8	enableRevokeEK;
> -	u8	nvLocked;
> -	u8	readSRKPub;
> -	u8	tpmEstablished;
> -	u8	maintenanceDone;
> -	u8	disableFullDALogicInfo;
> -} __packed;
> -
> -typedef union {
> -	struct	permanent_flags_t perm_flags;
> -	struct	stclear_flags_t	stclear_flags;
> -	__u8	owned;
> -	__be32	num_pcrs;
> -	struct tpm1_version version1;
> -	struct tpm1_version2 version2;
> -	__be32	manufacturer_id;
> -	struct timeout_t  timeout;
> -	struct duration_t duration;
> -} cap_t;
> -
> -enum tpm_capabilities {
> -	TPM_CAP_FLAG = 4,
> -	TPM_CAP_PROP = 5,
> -	TPM_CAP_VERSION_1_1 = 0x06,
> -	TPM_CAP_VERSION_1_2 = 0x1A,
> -};
> -
> -enum tpm_sub_capabilities {
> -	TPM_CAP_PROP_PCR = 0x101,
> -	TPM_CAP_PROP_MANUFACTURER = 0x103,
> -	TPM_CAP_FLAG_PERM = 0x108,
> -	TPM_CAP_FLAG_VOL = 0x109,
> -	TPM_CAP_PROP_OWNER = 0x111,
> -	TPM_CAP_PROP_TIS_TIMEOUT = 0x115,
> -	TPM_CAP_PROP_TIS_DURATION = 0x120,
> -};
> -
>  enum tpm2_pt_props {
>  	TPM2_PT_NONE = 0x00000000,
>  	TPM2_PT_GROUP = 0x00000100,
> @@ -225,12 +129,6 @@ enum tpm2_pt_props {
>  	TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20,
>  };
>  
> -/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18
> - * bytes, but 128 is still a relatively large number of random bytes and
> - * anything much bigger causes users of struct tpm_cmd_t to start getting
> - * compiler warnings about stack frame size. */
> -#define TPM_MAX_RNG_DATA	128
> -
>  extern const struct class tpm_class;
>  extern const struct class tpmrm_class;
>  extern dev_t tpm_devt;
> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> index 664ca1fff2e8..96f189b5fd6f 100644
> --- a/drivers/char/tpm/tpm1-cmd.c
> +++ b/drivers/char/tpm/tpm1-cmd.c
> @@ -504,11 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
>  }
>  EXPORT_SYMBOL_GPL(tpm1_getcap);
>  
> -struct tpm1_get_random_out {
> -	__be32 rng_data_len;
> -	u8 rng_data[TPM_MAX_RNG_DATA];
> -} __packed;
> -
>  /**
>   * tpm1_get_random() - get random bytes from the TPM's RNG
>   * @chip:	a &struct tpm_chip instance
> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
> index 174b043d8bbc..30d01953a6f8 100644
> --- a/include/linux/tpm_command.h
> +++ b/include/linux/tpm_command.h
> @@ -36,6 +36,121 @@ enum tpm_command_ordinals {
>  	TPM_ORD_UNSEAL			= 24,
>  };
>  
> +enum tpm_capabilities {
> +	TPM_CAP_FLAG		= 4,
> +	TPM_CAP_PROP		= 5,
> +	TPM_CAP_VERSION_1_1	= 0x06,
> +	TPM_CAP_VERSION_1_2	= 0x1A,
> +};
> +
> +enum tpm_sub_capabilities {
> +	TPM_CAP_PROP_PCR		= 0x101,
> +	TPM_CAP_PROP_MANUFACTURER	= 0x103,
> +	TPM_CAP_FLAG_PERM		= 0x108,
> +	TPM_CAP_FLAG_VOL		= 0x109,
> +	TPM_CAP_PROP_OWNER		= 0x111,
> +	TPM_CAP_PROP_TIS_TIMEOUT	= 0x115,
> +	TPM_CAP_PROP_TIS_DURATION	= 0x120,
> +};
> +
> +/* Return Codes */
> +enum tpm_return_codes {
> +	TPM_BASE_MASK			= 0,
> +	TPM_NON_FATAL_MASK		= 0x00000800,
> +	TPM_SUCCESS			= TPM_BASE_MASK + 0,
> +	TPM_ERR_DEACTIVATED		= TPM_BASE_MASK + 6,
> +	TPM_ERR_DISABLED		= TPM_BASE_MASK + 7,
> +	TPM_ERR_FAIL			= TPM_BASE_MASK + 9,
> +	TPM_ERR_FAILEDSELFTEST		= TPM_BASE_MASK + 28,
> +	TPM_ERR_INVALID_POSTINIT	= TPM_BASE_MASK + 38,
> +	TPM_ERR_INVALID_FAMILY		= TPM_BASE_MASK + 55,
> +	TPM_WARN_RETRY			= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 0,
> +	TPM_WARN_DOING_SELFTEST		= TPM_BASE_MASK + TPM_NON_FATAL_MASK + 2,
> +};
> +
> +struct	stclear_flags_t {
> +	__be16 tag;
> +	u8 deactivated;
> +	u8 disableForceClear;
> +	u8 physicalPresence;
> +	u8 physicalPresenceLock;
> +	u8 bGlobalLock;
> +} __packed;
> +
> +struct tpm1_version {
> +	u8 major;
> +	u8 minor;
> +	u8 rev_major;
> +	u8 rev_minor;
> +} __packed;
> +
> +struct tpm1_version2 {
> +	__be16 tag;
> +	struct tpm1_version version;
> +} __packed;
> +
> +struct	timeout_t {
> +	__be32 a;
> +	__be32 b;
> +	__be32 c;
> +	__be32 d;
> +} __packed;
> +
> +struct duration_t {
> +	__be32 tpm_short;
> +	__be32 tpm_medium;
> +	__be32 tpm_long;
> +} __packed;
> +
> +struct permanent_flags_t {
> +	__be16 tag;
> +	u8 disable;
> +	u8 ownership;
> +	u8 deactivated;
> +	u8 readPubek;
> +	u8 disableOwnerClear;
> +	u8 allowMaintenance;
> +	u8 physicalPresenceLifetimeLock;
> +	u8 physicalPresenceHWEnable;
> +	u8 physicalPresenceCMDEnable;
> +	u8 CEKPUsed;
> +	u8 TPMpost;
> +	u8 TPMpostLock;
> +	u8 FIPS;
> +	u8 operator;
> +	u8 enableRevokeEK;
> +	u8 nvLocked;
> +	u8 readSRKPub;
> +	u8 tpmEstablished;
> +	u8 maintenanceDone;
> +	u8 disableFullDALogicInfo;
> +} __packed;
> +
> +typedef union {
> +	struct permanent_flags_t perm_flags;
> +	struct stclear_flags_t stclear_flags;
> +	__u8 owned;
> +	__be32 num_pcrs;
> +	struct tpm1_version version1;
> +	struct tpm1_version2 version2;
> +	__be32 manufacturer_id;
> +	struct timeout_t timeout;
> +	struct duration_t duration;
> +} cap_t;
> +
> +/*
> + * 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18
> + * bytes, but 128 is still a relatively large number of random bytes and
> + * anything much bigger causes users of struct tpm_cmd_t to start getting
> + * compiler warnings about stack frame size.
> + */
> +#define TPM_MAX_RNG_DATA		128
> +
> +struct tpm1_get_random_out {
> +	__be32 rng_data_len;
> +	u8 rng_data[TPM_MAX_RNG_DATA];
> +} __packed;
> +
>  /* Other constants */
>  #define SRKHANDLE                       0x40000000
>  #define TPM_NONCE_SIZE                  20
> -- 
> 2.47.3
> 

LGTM

BR, Jarkko


From jarkko at kernel.org  Fri May 15 16:15:56 2026
From: jarkko at kernel.org (Jarkko Sakkinen)
Date: Sat, 16 May 2026 02:15:56 +0300
Subject: [PATCH v16 03/38] tpm: Move TPM2 specific definitions to the
 command header
In-Reply-To: <20260515211410.31440-4-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-4-ross.philipson@gmail.com>
Message-ID: <agepLKST0x6Pq_U7@kernel.org>

On Fri, May 15, 2026 at 02:13:35PM -0700, Ross Philipson wrote:
> Gather all the TPM2 definitions and structures in the internal header
> file drivers/char/tpm/tpm.h into the command header, including:
>  - Command codes, return codes and definitions from the public and
> internal tpm.h files.
>  - Structures defined in numerous TPM driver C modules.
> 
> The definitions moved to these files correspond to the TCG specification
> for TPM 2 family:
> 
> TPM 2.0 Library
>  - https://trustedcomputinggroup.org/resource/tpm-library-specification/
> 
> Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
> ---
>  drivers/char/tpm/tpm.h        |  77 ----------
>  drivers/char/tpm/tpm2-cmd.c   |  30 ----
>  drivers/char/tpm/tpm2-space.c |  13 --
>  include/linux/tpm.h           | 145 ------------------
>  include/linux/tpm_command.h   | 271 ++++++++++++++++++++++++++++++++++
>  5 files changed, 271 insertions(+), 265 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index 043d78a9617a..680f89d9c9f9 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -52,83 +52,6 @@ enum tpm_addr {
>  	TPM_ADDR = 0x4E,
>  };
>  
> -/* TPM2 specific constants. */
> -#define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
> -
> -enum tpm2_pt_props {
> -	TPM2_PT_NONE = 0x00000000,
> -	TPM2_PT_GROUP = 0x00000100,
> -	TPM2_PT_FIXED = TPM2_PT_GROUP * 1,
> -	TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0,
> -	TPM2_PT_LEVEL = TPM2_PT_FIXED + 1,
> -	TPM2_PT_REVISION = TPM2_PT_FIXED + 2,
> -	TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3,
> -	TPM2_PT_YEAR = TPM2_PT_FIXED + 4,
> -	TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5,
> -	TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6,
> -	TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7,
> -	TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8,
> -	TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9,
> -	TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10,
> -	TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11,
> -	TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12,
> -	TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13,
> -	TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14,
> -	TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15,
> -	TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16,
> -	TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17,
> -	TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18,
> -	TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19,
> -	TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20,
> -	TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22,
> -	TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23,
> -	TPM2_PT_MEMORY = TPM2_PT_FIXED + 24,
> -	TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25,
> -	TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26,
> -	TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27,
> -	TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28,
> -	TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29,
> -	TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30,
> -	TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31,
> -	TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32,
> -	TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33,
> -	TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34,
> -	TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35,
> -	TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36,
> -	TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37,
> -	TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38,
> -	TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39,
> -	TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40,
> -	TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41,
> -	TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42,
> -	TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43,
> -	TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44,
> -	TPM2_PT_MODES = TPM2_PT_FIXED + 45,
> -	TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46,
> -	TPM2_PT_VAR = TPM2_PT_GROUP * 2,
> -	TPM2_PT_PERMANENT = TPM2_PT_VAR + 0,
> -	TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1,
> -	TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2,
> -	TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3,
> -	TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4,
> -	TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5,
> -	TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6,
> -	TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7,
> -	TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8,
> -	TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9,
> -	TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10,
> -	TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11,
> -	TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12,
> -	TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13,
> -	TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14,
> -	TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15,
> -	TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16,
> -	TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17,
> -	TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18,
> -	TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19,
> -	TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20,
> -};
> -
>  extern const struct class tpm_class;
>  extern const struct class tpmrm_class;
>  extern dev_t tpm_devt;
> diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
> index 3a77be7ebf4a..1fa3e8a43c79 100644
> --- a/drivers/char/tpm/tpm2-cmd.c
> +++ b/drivers/char/tpm/tpm2-cmd.c
> @@ -94,17 +94,6 @@ unsigned long tpm2_calc_ordinal_duration(u32 ordinal)
>  	return msecs_to_jiffies(TPM2_DURATION_DEFAULT);
>  }
>  
> -struct tpm2_pcr_read_out {
> -	__be32	update_cnt;
> -	__be32	pcr_selects_cnt;
> -	__be16	hash_alg;
> -	u8	pcr_select_size;
> -	u8	pcr_select[TPM2_PCR_SELECT_MIN];
> -	__be32	digests_cnt;
> -	__be16	digest_size;
> -	u8	digest[];
> -} __packed;
> -
>  /**
>   * tpm2_pcr_read() - read a PCR value
>   * @chip:	TPM chip to use.
> @@ -238,11 +227,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
>  	return rc;
>  }
>  
> -struct tpm2_get_random_out {
> -	__be16 size;
> -	u8 buffer[TPM_MAX_RNG_DATA];
> -} __packed;
> -
>  /**
>   * tpm2_get_random() - get random bytes from the TPM RNG
>   *
> @@ -366,14 +350,6 @@ void tpm2_flush_context(struct tpm_chip *chip, u32 handle)
>  }
>  EXPORT_SYMBOL_GPL(tpm2_flush_context);
>  
> -struct tpm2_get_cap_out {
> -	u8 more_data;
> -	__be32 subcap_id;
> -	__be32 property_cnt;
> -	__be32 property_id;
> -	__be32 value;
> -} __packed;
> -
>  /**
>   * tpm2_get_tpm_pt() - get value of a TPM_CAP_TPM_PROPERTIES type property
>   * @chip:		a &tpm_chip instance
> @@ -541,12 +517,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index)
>  	return tpm2_pcr_read(chip, 0, &digest, &bank->digest_size);
>  }
>  
> -struct tpm2_pcr_selection {
> -	__be16  hash_alg;
> -	u8  size_of_select;
> -	u8  pcr_select[3];
> -} __packed;
> -
>  ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
>  {
>  	struct tpm2_pcr_selection pcr_selection;
> diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> index 60354cd53b5c..7c1c0a174a2b 100644
> --- a/drivers/char/tpm/tpm2-space.c
> +++ b/drivers/char/tpm/tpm2-space.c
> @@ -15,19 +15,6 @@
>  #include <linux/unaligned.h>
>  #include "tpm.h"
>  
> -enum tpm2_handle_types {
> -	TPM2_HT_HMAC_SESSION	= 0x02000000,
> -	TPM2_HT_POLICY_SESSION	= 0x03000000,
> -	TPM2_HT_TRANSIENT	= 0x80000000,
> -};
> -
> -struct tpm2_context {
> -	__be64 sequence;
> -	__be32 saved_handle;
> -	__be32 hierarchy;
> -	__be16 blob_size;
> -} __packed;
> -
>  static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space)
>  {
>  	int i;
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 1846d5485a2c..8551b24c2bff 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -38,12 +38,6 @@ struct trusted_key_options;
>  /* opaque structure, holds auth session parameters like the session key */
>  struct tpm2_auth;
>  
> -enum tpm2_session_types {
> -	TPM2_SE_HMAC	= 0x00,
> -	TPM2_SE_POLICY	= 0x01,
> -	TPM2_SE_TRIAL	= 0x02,
> -};
> -
>  /* if you add a new hash to this, increment TPM_MAX_HASHES below */
>  enum tpm_algorithms {
>  	TPM_ALG_ERROR		= 0x0000,
> @@ -65,11 +59,6 @@ enum tpm_algorithms {
>   */
>  #define TPM_MAX_HASHES	5
>  
> -enum tpm2_curves {
> -	TPM2_ECC_NONE		= 0x0000,
> -	TPM2_ECC_NIST_P256	= 0x0003,
> -};
> -
>  struct tpm_digest {
>  	u16 alg_id;
>  	u8 digest[TPM2_MAX_DIGEST_SIZE];
> @@ -222,122 +211,11 @@ struct tpm_chip {
>  
>  #define TPM_HEADER_SIZE		10
>  
> -enum tpm2_const {
> -	TPM2_PLATFORM_PCR       =     24,
> -	TPM2_PCR_SELECT_MIN     = ((TPM2_PLATFORM_PCR + 7) / 8),
> -};
> -
> -enum tpm2_timeouts {
> -	TPM2_TIMEOUT_A          =    750,
> -	TPM2_TIMEOUT_B          =   4000,
> -	TPM2_TIMEOUT_C          =    200,
> -	TPM2_TIMEOUT_D          =     30,
> -};
> -
> -enum tpm2_durations {
> -	TPM2_DURATION_SHORT     =     20,
> -	TPM2_DURATION_LONG      =   2000,
> -	TPM2_DURATION_DEFAULT   = 120000,
> -};
> -
> -enum tpm2_structures {
> -	TPM2_ST_NO_SESSIONS	= 0x8001,
> -	TPM2_ST_SESSIONS	= 0x8002,
> -	TPM2_ST_CREATION	= 0x8021,
> -};
> -
> -/* Indicates from what layer of the software stack the error comes from */
> -#define TSS2_RC_LAYER_SHIFT	 16
> -#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT)
> -
> -enum tpm2_return_codes {
> -	TPM2_RC_SUCCESS		= 0x0000,
> -	TPM2_RC_HASH		= 0x0083, /* RC_FMT1 */
> -	TPM2_RC_HANDLE		= 0x008B,
> -	TPM2_RC_INTEGRITY	= 0x009F,
> -	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
> -	TPM2_RC_FAILURE		= 0x0101,
> -	TPM2_RC_DISABLED	= 0x0120,
> -	TPM2_RC_UPGRADE		= 0x012D,
> -	TPM2_RC_COMMAND_CODE    = 0x0143,
> -	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
> -	TPM2_RC_REFERENCE_H0	= 0x0910,
> -	TPM2_RC_RETRY		= 0x0922,
> -	TPM2_RC_SESSION_MEMORY	= 0x0903,
> -};
> -
> -enum tpm2_command_codes {
> -	TPM2_CC_FIRST		        = 0x011F,
> -	TPM2_CC_HIERARCHY_CONTROL       = 0x0121,
> -	TPM2_CC_HIERARCHY_CHANGE_AUTH   = 0x0129,
> -	TPM2_CC_CREATE_PRIMARY          = 0x0131,
> -	TPM2_CC_SEQUENCE_COMPLETE       = 0x013E,
> -	TPM2_CC_SELF_TEST	        = 0x0143,
> -	TPM2_CC_STARTUP		        = 0x0144,
> -	TPM2_CC_SHUTDOWN	        = 0x0145,
> -	TPM2_CC_NV_READ                 = 0x014E,
> -	TPM2_CC_CREATE		        = 0x0153,
> -	TPM2_CC_LOAD		        = 0x0157,
> -	TPM2_CC_SEQUENCE_UPDATE         = 0x015C,
> -	TPM2_CC_UNSEAL		        = 0x015E,
> -	TPM2_CC_CONTEXT_LOAD	        = 0x0161,
> -	TPM2_CC_CONTEXT_SAVE	        = 0x0162,
> -	TPM2_CC_FLUSH_CONTEXT	        = 0x0165,
> -	TPM2_CC_READ_PUBLIC		= 0x0173,
> -	TPM2_CC_START_AUTH_SESS		= 0x0176,
> -	TPM2_CC_VERIFY_SIGNATURE        = 0x0177,
> -	TPM2_CC_GET_CAPABILITY	        = 0x017A,
> -	TPM2_CC_GET_RANDOM	        = 0x017B,
> -	TPM2_CC_PCR_READ	        = 0x017E,
> -	TPM2_CC_PCR_EXTEND	        = 0x0182,
> -	TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185,
> -	TPM2_CC_HASH_SEQUENCE_START     = 0x0186,
> -	TPM2_CC_CREATE_LOADED           = 0x0191,
> -	TPM2_CC_LAST		        = 0x0193, /* Spec 1.36 */
> -};
> -
> -enum tpm2_permanent_handles {
> -	TPM2_RH_NULL		= 0x40000007,
> -	TPM2_RS_PW		= 0x40000009,
> -};
> -
> -/* Most Significant Octet for key types  */
> -enum tpm2_mso_type {
> -	TPM2_MSO_NVRAM		= 0x01,
> -	TPM2_MSO_SESSION	= 0x02,
> -	TPM2_MSO_POLICY		= 0x03,
> -	TPM2_MSO_PERMANENT	= 0x40,
> -	TPM2_MSO_VOLATILE	= 0x80,
> -	TPM2_MSO_PERSISTENT	= 0x81,
> -};
> -
>  static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
>  {
>  	return handle >> 24;
>  }
>  
> -enum tpm2_capabilities {
> -	TPM2_CAP_HANDLES	= 1,
> -	TPM2_CAP_COMMANDS	= 2,
> -	TPM2_CAP_PCRS		= 5,
> -	TPM2_CAP_TPM_PROPERTIES = 6,
> -};
> -
> -enum tpm2_properties {
> -	TPM_PT_TOTAL_COMMANDS	= 0x0129,
> -};
> -
> -enum tpm2_startup_types {
> -	TPM2_SU_CLEAR	= 0x0000,
> -	TPM2_SU_STATE	= 0x0001,
> -};
> -
> -enum tpm2_cc_attrs {
> -	TPM2_CC_ATTR_CHANDLES	= 25,
> -	TPM2_CC_ATTR_RHANDLE	= 28,
> -	TPM2_CC_ATTR_VENDOR	= 29,
> -};
> -
>  #define TPM_VID_INTEL    0x8086
>  #define TPM_VID_WINBOND  0x1050
>  #define TPM_VID_STM      0x104A
> @@ -389,29 +267,6 @@ struct tpm_buf {
>  	u8 handles;
>  };
>  
> -enum tpm2_object_attributes {
> -	TPM2_OA_FIXED_TPM		= BIT(1),
> -	TPM2_OA_ST_CLEAR		= BIT(2),
> -	TPM2_OA_FIXED_PARENT		= BIT(4),
> -	TPM2_OA_SENSITIVE_DATA_ORIGIN	= BIT(5),
> -	TPM2_OA_USER_WITH_AUTH		= BIT(6),
> -	TPM2_OA_ADMIN_WITH_POLICY	= BIT(7),
> -	TPM2_OA_NO_DA			= BIT(10),
> -	TPM2_OA_ENCRYPTED_DUPLICATION	= BIT(11),
> -	TPM2_OA_RESTRICTED		= BIT(16),
> -	TPM2_OA_DECRYPT			= BIT(17),
> -	TPM2_OA_SIGN			= BIT(18),
> -};
> -
> -enum tpm2_session_attributes {
> -	TPM2_SA_CONTINUE_SESSION	= BIT(0),
> -	TPM2_SA_AUDIT_EXCLUSIVE		= BIT(1),
> -	TPM2_SA_AUDIT_RESET		= BIT(3),
> -	TPM2_SA_DECRYPT			= BIT(5),
> -	TPM2_SA_ENCRYPT			= BIT(6),
> -	TPM2_SA_AUDIT			= BIT(7),
> -};
> -
>  struct tpm2_hash {
>  	unsigned int crypto_id;
>  	unsigned int tpm_id;
> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
> index 30d01953a6f8..9dd903dd6b5c 100644
> --- a/include/linux/tpm_command.h
> +++ b/include/linux/tpm_command.h
> @@ -156,4 +156,275 @@ struct tpm1_get_random_out {
>  #define TPM_NONCE_SIZE                  20
>  #define TPM_ST_CLEAR			1
>  
> +/*
> + * == TPM 2 Family Chips ==
> + *
> + * TPM 2.0 Library
> + * https://trustedcomputinggroup.org/resource/tpm-library-specification/
> + */
> +
> +/* TPM2 specific constants. */
> +#define TPM2_SPACE_BUFFER_SIZE		16384 /* 16 kB */
> +
> +enum tpm2_session_types {
> +	TPM2_SE_HMAC	= 0x00,
> +	TPM2_SE_POLICY	= 0x01,
> +	TPM2_SE_TRIAL	= 0x02,
> +};
> +
> +enum tpm2_timeouts {
> +	TPM2_TIMEOUT_A		= 750,
> +	TPM2_TIMEOUT_B		= 4000,
> +	TPM2_TIMEOUT_C		= 200,
> +	TPM2_TIMEOUT_D		= 30,
> +	TPM2_DURATION_SHORT	= 20,
> +	TPM2_DURATION_MEDIUM	= 750,
> +	TPM2_DURATION_LONG	= 2000,
> +	TPM2_DURATION_LONG_LONG	= 300000,
> +	TPM2_DURATION_DEFAULT	= 120000,
> +};
> +
> +enum tpm2_structures {
> +	TPM2_ST_NO_SESSIONS	= 0x8001,
> +	TPM2_ST_SESSIONS	= 0x8002,
> +	TPM2_ST_CREATION	= 0x8021,
> +};
> +
> +/* Indicates from what layer of the software stack the error comes from */
> +#define TSS2_RC_LAYER_SHIFT	 16
> +#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT)
> +
> +enum tpm2_return_codes {
> +	TPM2_RC_SUCCESS		= 0x0000,
> +	TPM2_RC_HASH		= 0x0083, /* RC_FMT1 */
> +	TPM2_RC_HANDLE		= 0x008B,
> +	TPM2_RC_INTEGRITY	= 0x009F,
> +	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
> +	TPM2_RC_FAILURE		= 0x0101,
> +	TPM2_RC_DISABLED	= 0x0120,
> +	TPM2_RC_UPGRADE		= 0x012D,
> +	TPM2_RC_COMMAND_CODE	= 0x0143,
> +	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
> +	TPM2_RC_REFERENCE_H0	= 0x0910,
> +	TPM2_RC_RETRY		= 0x0922,
> +	TPM2_RC_SESSION_MEMORY	= 0x0903,
> +};
> +
> +enum tpm2_command_codes {
> +	TPM2_CC_FIRST			= 0x011F,
> +	TPM2_CC_HIERARCHY_CONTROL	= 0x0121,
> +	TPM2_CC_HIERARCHY_CHANGE_AUTH	= 0x0129,
> +	TPM2_CC_CREATE_PRIMARY		= 0x0131,
> +	TPM2_CC_SEQUENCE_COMPLETE	= 0x013E,
> +	TPM2_CC_SELF_TEST		= 0x0143,
> +	TPM2_CC_STARTUP			= 0x0144,
> +	TPM2_CC_SHUTDOWN		= 0x0145,
> +	TPM2_CC_NV_READ			= 0x014E,
> +	TPM2_CC_CREATE			= 0x0153,
> +	TPM2_CC_LOAD			= 0x0157,
> +	TPM2_CC_SEQUENCE_UPDATE		= 0x015C,
> +	TPM2_CC_UNSEAL			= 0x015E,
> +	TPM2_CC_CONTEXT_LOAD		= 0x0161,
> +	TPM2_CC_CONTEXT_SAVE		= 0x0162,
> +	TPM2_CC_FLUSH_CONTEXT		= 0x0165,
> +	TPM2_CC_READ_PUBLIC		= 0x0173,
> +	TPM2_CC_START_AUTH_SESS		= 0x0176,
> +	TPM2_CC_VERIFY_SIGNATURE	= 0x0177,
> +	TPM2_CC_GET_CAPABILITY		= 0x017A,
> +	TPM2_CC_GET_RANDOM		= 0x017B,
> +	TPM2_CC_PCR_READ		= 0x017E,
> +	TPM2_CC_PCR_EXTEND		= 0x0182,
> +	TPM2_CC_EVENT_SEQUENCE_COMPLETE	= 0x0185,
> +	TPM2_CC_HASH_SEQUENCE_START	= 0x0186,
> +	TPM2_CC_CREATE_LOADED		= 0x0191,
> +	TPM2_CC_LAST			= 0x0193, /* Spec 1.36 */
> +};
> +
> +enum tpm2_capabilities {
> +	TPM2_CAP_HANDLES	= 1,
> +	TPM2_CAP_COMMANDS	= 2,
> +	TPM2_CAP_PCRS		= 5,
> +	TPM2_CAP_TPM_PROPERTIES = 6,
> +};
> +
> +enum tpm2_properties {
> +	TPM_PT_TOTAL_COMMANDS	= 0x0129,
> +};
> +
> +enum tpm2_startup_types {
> +	TPM2_SU_CLEAR		= 0x0000,
> +	TPM2_SU_STATE		= 0x0001,
> +};
> +
> +enum tpm2_cc_attrs {
> +	TPM2_CC_ATTR_CHANDLES	= 25,
> +	TPM2_CC_ATTR_RHANDLE	= 28,
> +	TPM2_CC_ATTR_VENDOR	= 29,
> +};
> +
> +enum tpm2_permanent_handles {
> +	TPM2_RH_NULL		= 0x40000007,
> +	TPM2_RS_PW		= 0x40000009,
> +};
> +
> +/* Most Significant Octet for key types  */
> +enum tpm2_mso_type {
> +	TPM2_MSO_NVRAM		= 0x01,
> +	TPM2_MSO_SESSION	= 0x02,
> +	TPM2_MSO_POLICY		= 0x03,
> +	TPM2_MSO_PERMANENT	= 0x40,
> +	TPM2_MSO_VOLATILE	= 0x80,
> +	TPM2_MSO_PERSISTENT	= 0x81,
> +};
> +
> +enum tpm2_curves {
> +	TPM2_ECC_NONE		= 0x0000,
> +	TPM2_ECC_NIST_P256	= 0x0003,
> +};
> +
> +enum tpm2_object_attributes {
> +	TPM2_OA_FIXED_TPM		= BIT(1),
> +	TPM2_OA_ST_CLEAR		= BIT(2),
> +	TPM2_OA_FIXED_PARENT		= BIT(4),
> +	TPM2_OA_SENSITIVE_DATA_ORIGIN	= BIT(5),
> +	TPM2_OA_USER_WITH_AUTH		= BIT(6),
> +	TPM2_OA_ADMIN_WITH_POLICY	= BIT(7),
> +	TPM2_OA_NO_DA			= BIT(10),
> +	TPM2_OA_ENCRYPTED_DUPLICATION	= BIT(11),
> +	TPM2_OA_RESTRICTED		= BIT(16),
> +	TPM2_OA_DECRYPT			= BIT(17),
> +	TPM2_OA_SIGN			= BIT(18),
> +};
> +
> +enum tpm2_session_attributes {
> +	TPM2_SA_CONTINUE_SESSION	= BIT(0),
> +	TPM2_SA_AUDIT_EXCLUSIVE		= BIT(1),
> +	TPM2_SA_AUDIT_RESET		= BIT(3),
> +	TPM2_SA_DECRYPT			= BIT(5),
> +	TPM2_SA_ENCRYPT			= BIT(6),
> +	TPM2_SA_AUDIT			= BIT(7),
> +};
> +
> +enum tpm2_pcr_select {
> +	TPM2_PLATFORM_PCR	= 24,
> +	TPM2_PCR_SELECT_MIN	= ((TPM2_PLATFORM_PCR + 7) / 8),
> +};
> +
> +enum tpm2_handle_types {
> +	TPM2_HT_HMAC_SESSION	= 0x02000000,
> +	TPM2_HT_POLICY_SESSION	= 0x03000000,
> +	TPM2_HT_TRANSIENT	= 0x80000000,
> +};
> +
> +enum tpm2_pt_props {
> +	TPM2_PT_NONE			= 0x00000000,
> +	TPM2_PT_GROUP			= 0x00000100,
> +	TPM2_PT_FIXED			= TPM2_PT_GROUP * 1,
> +	TPM2_PT_FAMILY_INDICATOR	= TPM2_PT_FIXED + 0,
> +	TPM2_PT_LEVEL		= TPM2_PT_FIXED + 1,
> +	TPM2_PT_REVISION	= TPM2_PT_FIXED + 2,
> +	TPM2_PT_DAY_OF_YEAR	= TPM2_PT_FIXED + 3,
> +	TPM2_PT_YEAR		= TPM2_PT_FIXED + 4,
> +	TPM2_PT_MANUFACTURER	= TPM2_PT_FIXED + 5,
> +	TPM2_PT_VENDOR_STRING_1	= TPM2_PT_FIXED + 6,
> +	TPM2_PT_VENDOR_STRING_2	= TPM2_PT_FIXED + 7,
> +	TPM2_PT_VENDOR_STRING_3	= TPM2_PT_FIXED + 8,
> +	TPM2_PT_VENDOR_STRING_4	= TPM2_PT_FIXED + 9,
> +	TPM2_PT_VENDOR_TPM_TYPE	= TPM2_PT_FIXED + 10,
> +	TPM2_PT_FIRMWARE_VERSION_1	= TPM2_PT_FIXED + 11,
> +	TPM2_PT_FIRMWARE_VERSION_2	= TPM2_PT_FIXED + 12,
> +	TPM2_PT_INPUT_BUFFER		= TPM2_PT_FIXED + 13,
> +	TPM2_PT_HR_TRANSIENT_MIN	= TPM2_PT_FIXED + 14,
> +	TPM2_PT_HR_PERSISTENT_MIN	= TPM2_PT_FIXED + 15,
> +	TPM2_PT_HR_LOADED_MIN		= TPM2_PT_FIXED + 16,
> +	TPM2_PT_ACTIVE_SESSIONS_MAX	= TPM2_PT_FIXED + 17,
> +	TPM2_PT_PCR_COUNT	= TPM2_PT_FIXED + 18,
> +	TPM2_PT_PCR_SELECT_MIN	= TPM2_PT_FIXED + 19,
> +	TPM2_PT_CONTEXT_GAP_MAX	= TPM2_PT_FIXED + 20,
> +	TPM2_PT_NV_COUNTERS_MAX	= TPM2_PT_FIXED + 22,
> +	TPM2_PT_NV_INDEX_MAX	= TPM2_PT_FIXED + 23,
> +	TPM2_PT_MEMORY		= TPM2_PT_FIXED + 24,
> +	TPM2_PT_CLOCK_UPDATE	= TPM2_PT_FIXED + 25,
> +	TPM2_PT_CONTEXT_HASH	= TPM2_PT_FIXED + 26,
> +	TPM2_PT_CONTEXT_SYM	= TPM2_PT_FIXED + 27,
> +	TPM2_PT_CONTEXT_SYM_SIZE	= TPM2_PT_FIXED + 28,
> +	TPM2_PT_ORDERLY_COUNT		= TPM2_PT_FIXED + 29,
> +	TPM2_PT_MAX_COMMAND_SIZE	= TPM2_PT_FIXED + 30,
> +	TPM2_PT_MAX_RESPONSE_SIZE	= TPM2_PT_FIXED + 31,
> +	TPM2_PT_MAX_DIGEST		= TPM2_PT_FIXED + 32,
> +	TPM2_PT_MAX_OBJECT_CONTEXT	= TPM2_PT_FIXED + 33,
> +	TPM2_PT_MAX_SESSION_CONTEXT	= TPM2_PT_FIXED + 34,
> +	TPM2_PT_PS_FAMILY_INDICATOR	= TPM2_PT_FIXED + 35,
> +	TPM2_PT_PS_LEVEL	= TPM2_PT_FIXED + 36,
> +	TPM2_PT_PS_REVISION	= TPM2_PT_FIXED + 37,
> +	TPM2_PT_PS_DAY_OF_YEAR	= TPM2_PT_FIXED + 38,
> +	TPM2_PT_PS_YEAR		= TPM2_PT_FIXED + 39,
> +	TPM2_PT_SPLIT_MAX	= TPM2_PT_FIXED + 40,
> +	TPM2_PT_TOTAL_COMMANDS	= TPM2_PT_FIXED + 41,
> +	TPM2_PT_LIBRARY_COMMANDS	= TPM2_PT_FIXED + 42,
> +	TPM2_PT_VENDOR_COMMANDS		= TPM2_PT_FIXED + 43,
> +	TPM2_PT_NV_BUFFER_MAX		= TPM2_PT_FIXED + 44,
> +	TPM2_PT_MODES			= TPM2_PT_FIXED + 45,
> +	TPM2_PT_MAX_CAP_BUFFER		= TPM2_PT_FIXED + 46,
> +	TPM2_PT_VAR		= TPM2_PT_GROUP * 2,
> +	TPM2_PT_PERMANENT	= TPM2_PT_VAR + 0,
> +	TPM2_PT_STARTUP_CLEAR	= TPM2_PT_VAR + 1,
> +	TPM2_PT_HR_NV_INDEX	= TPM2_PT_VAR + 2,
> +	TPM2_PT_HR_LOADED	= TPM2_PT_VAR + 3,
> +	TPM2_PT_HR_LOADED_AVAIL	= TPM2_PT_VAR + 4,
> +	TPM2_PT_HR_ACTIVE	= TPM2_PT_VAR + 5,
> +	TPM2_PT_HR_ACTIVE_AVAIL	= TPM2_PT_VAR + 6,
> +	TPM2_PT_HR_TRANSIENT_AVAIL	= TPM2_PT_VAR + 7,
> +	TPM2_PT_HR_PERSISTENT		= TPM2_PT_VAR + 8,
> +	TPM2_PT_HR_PERSISTENT_AVAIL	= TPM2_PT_VAR + 9,
> +	TPM2_PT_NV_COUNTERS		= TPM2_PT_VAR + 10,
> +	TPM2_PT_NV_COUNTERS_AVAIL	= TPM2_PT_VAR + 11,
> +	TPM2_PT_ALGORITHM_SET		= TPM2_PT_VAR + 12,
> +	TPM2_PT_LOADED_CURVES		= TPM2_PT_VAR + 13,
> +	TPM2_PT_LOCKOUT_COUNTER		= TPM2_PT_VAR + 14,
> +	TPM2_PT_MAX_AUTH_FAIL		= TPM2_PT_VAR + 15,
> +	TPM2_PT_LOCKOUT_INTERVAL	= TPM2_PT_VAR + 16,
> +	TPM2_PT_LOCKOUT_RECOVERY	= TPM2_PT_VAR + 17,
> +	TPM2_PT_NV_WRITE_RECOVERY	= TPM2_PT_VAR + 18,
> +	TPM2_PT_AUDIT_COUNTER_0	= TPM2_PT_VAR + 19,
> +	TPM2_PT_AUDIT_COUNTER_1	= TPM2_PT_VAR + 20,
> +};
> +
> +struct tpm2_pcr_read_out {
> +	__be32 update_cnt;
> +	__be32 pcr_selects_cnt;
> +	__be16 hash_alg;
> +	u8 pcr_select_size;
> +	u8 pcr_select[TPM2_PCR_SELECT_MIN];
> +	__be32 digests_cnt;
> +	__be16 digest_size;
> +	u8 digest[];
> +} __packed;
> +
> +struct tpm2_get_random_out {
> +	__be16 size;
> +	u8 buffer[TPM_MAX_RNG_DATA];
> +} __packed;
> +
> +struct tpm2_get_cap_out {
> +	u8 more_data;
> +	__be32 subcap_id;
> +	__be32 property_cnt;
> +	__be32 property_id;
> +	__be32 value;
> +} __packed;
> +
> +struct tpm2_pcr_selection {
> +	__be16 hash_alg;
> +	u8 size_of_select;
> +	u8 pcr_select[3];
> +} __packed;
> +
> +struct tpm2_context {
> +	__be64 sequence;
> +	__be32 saved_handle;
> +	__be32 hierarchy;
> +	__be16 blob_size;
> +} __packed;
> +
>  #endif
> -- 
> 2.47.3
> 

LGTM

BR, Jarkko


From jarkko at kernel.org  Fri May 15 16:22:36 2026
From: jarkko at kernel.org (Jarkko Sakkinen)
Date: Sat, 16 May 2026 02:22:36 +0300
Subject: [PATCH v16 04/38] tpm: Move TPM common base definitions to the
 command header
In-Reply-To: <20260515211410.31440-5-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-5-ross.philipson@gmail.com>
Message-ID: <ageqvDfgVk1nh2-w@kernel.org>

On Fri, May 15, 2026 at 02:13:36PM -0700, Ross Philipson wrote:
> These are top level definitions shared by both TPM 1 and 2
> family chips. This includes core definitions like TPM localities,
> common crypto algorithm IDs, and the base TPM command header.
> 
> Co-developed-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Signed-off-by: Daniel P. Smith <dpsmith at apertussolutions.com>
> Co-developed-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Alec Brown <alec.r.brown at oracle.com>
> Signed-off-by: Ross Philipson <ross.philipson at gmail.com>
> ---
>  include/linux/tpm.h         | 50 +--------------------
>  include/linux/tpm_command.h | 89 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 90 insertions(+), 49 deletions(-)
> 
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 8551b24c2bff..3630b2ea6aef 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -27,49 +27,12 @@
>  
>  #include <linux/tpm_command.h>
>  
> -#define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
> -
> -#define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
> -#define TPM2_MAX_PCR_BANKS	8
> -
>  struct tpm_chip;
>  struct trusted_key_payload;
>  struct trusted_key_options;
>  /* opaque structure, holds auth session parameters like the session key */
>  struct tpm2_auth;
>  
> -/* if you add a new hash to this, increment TPM_MAX_HASHES below */
> -enum tpm_algorithms {
> -	TPM_ALG_ERROR		= 0x0000,
> -	TPM_ALG_SHA1		= 0x0004,
> -	TPM_ALG_AES		= 0x0006,
> -	TPM_ALG_KEYEDHASH	= 0x0008,
> -	TPM_ALG_SHA256		= 0x000B,
> -	TPM_ALG_SHA384		= 0x000C,
> -	TPM_ALG_SHA512		= 0x000D,
> -	TPM_ALG_NULL		= 0x0010,
> -	TPM_ALG_SM3_256		= 0x0012,
> -	TPM_ALG_ECC		= 0x0023,
> -	TPM_ALG_CFB		= 0x0043,
> -};
> -
> -/*
> - * maximum number of hashing algorithms a TPM can have.  This is
> - * basically a count of every hash in tpm_algorithms above
> - */
> -#define TPM_MAX_HASHES	5
> -
> -struct tpm_digest {
> -	u16 alg_id;
> -	u8 digest[TPM2_MAX_DIGEST_SIZE];
> -} __packed;
> -
> -struct tpm_bank_info {
> -	u16 alg_id;
> -	u16 digest_size;
> -	u16 crypto_id;
> -};
> -
>  enum TPM_OPS_FLAGS {
>  	TPM_OPS_AUTO_STARTUP = BIT(0),
>  };
> @@ -127,7 +90,7 @@ struct tpm_chip_seqops {
>  	const struct seq_operations *seqops;
>  };
>  
> -/* fixed define for the curve we use which is NIST_P256 */
> +/* Fixed define for the curve we use which is NIST_P256 */
>  #define EC_PT_SZ	32
>  
>  /*
> @@ -209,8 +172,6 @@ struct tpm_chip {
>  #endif
>  };
>  
> -#define TPM_HEADER_SIZE		10
> -
>  static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
>  {
>  	return handle >> 24;
> @@ -239,15 +200,6 @@ enum tpm_chip_flags {
>  
>  #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
>  
> -struct tpm_header {
> -	__be16 tag;
> -	__be32 length;
> -	union {
> -		__be32 ordinal;
> -		__be32 return_code;
> -	};
> -} __packed;
> -
>  enum tpm_buf_flags {
>  	/* the capacity exceeded: */
>  	TPM_BUF_OVERFLOW	= BIT(0),
> diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
> index 9dd903dd6b5c..96edebd9610f 100644
> --- a/include/linux/tpm_command.h
> +++ b/include/linux/tpm_command.h
> @@ -427,4 +427,93 @@ struct tpm2_context {
>  	__be16 blob_size;
>  } __packed;
>  
> +/*
> + * == TPM Common Defs ==
> + */
> +
> +#define TPM_DIGEST_SIZE		20	/* Max TPM v1.2 PCR size */
> +#define TPM_BUFSIZE		4096
> +
> +/*
> + * SHA-512 is, as of today, the largest digest in the TCG algorithm repository.
> + */
> +#define TPM2_MAX_DIGEST_SIZE	SHA512_DIGEST_SIZE
> +
> +/*
> + * A TPM name digest i.e., TPMT_HA, is a concatenation of TPM_ALG_ID of the
> + * name algorithm and hash of TPMT_PUBLIC.
> + */
> +#define TPM2_MAX_NAME_SIZE	(TPM2_MAX_DIGEST_SIZE + 2)
> +
> +/*
> + * Fixed define for the size of a name.  This is actually HASHALG size
> + * plus 2, so 32 for SHA256
> + */
> +#define TPM2_NULL_NAME_SIZE	34
> +
> +/*
> + * The maximum number of PCR banks.
> + */
> +#define TPM2_MAX_PCR_BANKS	8
> +
> +/* If you add a new hash to this, increment TPM_MAX_HASHES below */
> +enum tpm_algorithms {
> +	TPM_ALG_ERROR		= 0x0000,
> +	TPM_ALG_SHA1		= 0x0004,
> +	TPM_ALG_AES		= 0x0006,
> +	TPM_ALG_KEYEDHASH	= 0x0008,
> +	TPM_ALG_SHA256		= 0x000B,
> +	TPM_ALG_SHA384		= 0x000C,
> +	TPM_ALG_SHA512		= 0x000D,
> +	TPM_ALG_NULL		= 0x0010,
> +	TPM_ALG_SM3_256		= 0x0012,
> +	TPM_ALG_ECC		= 0x0023,
> +	TPM_ALG_CFB		= 0x0043,
> +};
> +
> +/*
> + * The locality (0 - 4) for a TPM, as defined in section 3.2 of the
> + * Client Platform Profile Specification.
> + */
> +enum tpm_localities {
> +	TPM_LOCALITY_0		= 0, /* Static RTM */
> +	TPM_LOCALITY_1		= 1, /* Dynamic OS */
> +	TPM_LOCALITY_2		= 2, /* DRTM Environment */
> +	TPM_LOCALITY_3		= 3, /* Aux Components */
> +	TPM_LOCALITY_4		= 4, /* CPU DRTM Establishment */
> +	TPM_MAX_LOCALITY	= TPM_LOCALITY_4
> +};
> +
> +/*
> + * Structure to represent active PCR algorithm banks usable by the
> + * TPM chip.
> + */
> +struct tpm_bank_info {
> +	u16 alg_id;
> +	u16 digest_size;
> +	u16 crypto_id;
> +};
> +
> +/*
> + * Maximum number of hashing algorithms a TPM can have.  This is
> + * basically a count of every hash in tpm_algorithms above
> + */
> +#define TPM_MAX_HASHES		5
> +
> +struct tpm_digest {
> +	u16 alg_id;
> +	u8 digest[TPM2_MAX_DIGEST_SIZE];
> +} __packed;
> +
> +#define TPM_HEADER_SIZE		10
> +
> +struct tpm_header {
> +	__be16 tag;
> +	__be32 length;
> +	union {
> +		__be32 ordinal;
> +		__be32 return_code;
> +	};
> +} __packed;
> +
>  #endif
> -- 
> 2.47.3
> 

LGTM

BR, Jarkko


From jarkko at kernel.org  Fri May 15 16:51:22 2026
From: jarkko at kernel.org (Jarkko Sakkinen)
Date: Sat, 16 May 2026 02:51:22 +0300
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public
 headers
In-Reply-To: <20260515230553.GO7702@ziepe.ca>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-2-ross.philipson@gmail.com>
 <agemXwxVb9jvAbYM@kernel.org>
 <20260515230553.GO7702@ziepe.ca>
Message-ID: <agexemLOag0eAiJB@kernel.org>

On Fri, May 15, 2026 at 08:05:53PM -0300, Jason Gunthorpe wrote:
> On Sat, May 16, 2026 at 02:03:59AM +0300, Jarkko Sakkinen wrote:
> 
> > LGTM
> > 
> > I'll hold on from actual tags up until there is some consensus with the
> > patch set.
> 
> This patch set is huge, and I know there is alot of interest now in
> DRTM.
> 
> Can we please split out and progress the TPM reorg mini-series at the
> front?

I'm not usually for "non-functional" series but in the case of D-RTM
that could stil bel a good idea.

I.e. let's iterate it through faster than the main series and get it to
the mainline before next merge window,  and then switch gears back to
the "main series".

> 
> Jason

BR, Jarkko


From rppt at kernel.org  Fri May 15 23:29:10 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sat, 16 May 2026 09:29:10 +0300
Subject: [PATCH 1/1] kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes
In-Reply-To: <20260513150718.182224-1-dongtai.guo@linux.dev>
References: <2vxzcxz2dybi.fsf@kernel.org>
 <20260513150718.182224-1-dongtai.guo@linux.dev>
Message-ID: <aggOtl4HUcph3VSF@kernel.org>

On Wed, May 13, 2026 at 11:07:18PM +0800, George Guo wrote:
> Sorry for the late reply.
> 
> On Mon, May 11 2026, Pratyush Yadav wrote:
> 
> > As of now, we only support KHO on x86 and arm64. Are you working on
> > supporting it for LoongArch? What is your use case?
> 
> Yes, we are adding KHO support for LoongArch (16KB page size). The
> LoongArch patches are being prepared separately. This fix is a
> prerequisite.

I massaged the changelog and reapplied this to the liveupdate tree:

https://git.kernel.org/liveupdate/c/d13dfb48dfa3

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sat May 16 00:39:43 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sat, 16 May 2026 10:39:43 +0300
Subject: [PATCH 1/3] kho: Add kunit static stubs
In-Reply-To: <20260512195135.804833-2-skhawaja@google.com>
References: <20260512195135.804833-1-skhawaja@google.com>
 <20260512195135.804833-2-skhawaja@google.com>
Message-ID: <aggfP-0A-i-bYlP7@kernel.org>

Hi Sami,

On Tue, May 12, 2026 at 07:51:33PM +0000, Samiullah Khawaja wrote:
> Add kunit stubs in kho_restore_folio() and kho_restore_pages() so the
> users can mock these functions in kunit tests.
> 
> Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
> ---
>  kernel/liveupdate/kexec_handover.c | 9 ++++++++-
>  1 file changed, 8 insertions(+), 1 deletion(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index d5718bef6d4d..1375291d9b07 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -11,6 +11,7 @@
>  #define pr_fmt(fmt) "KHO: " fmt
>  
>  #include <linux/cleanup.h>
> +#include <kunit/static_stub.h>

Can we include kunit headers before linux headers?

>  #include <linux/cma.h>
>  #include <linux/kmemleak.h>
>  #include <linux/count_zeros.h>
> @@ -437,7 +438,11 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
>   */
>  struct folio *kho_restore_folio(phys_addr_t phys)
>  {
> -	struct page *page = kho_restore_page(phys, true);
> +	struct page *page;
> +
> +	KUNIT_STATIC_STUB_REDIRECT(kho_restore_folio, phys);
> +
> +	page = kho_restore_page(phys, true);
>  
>  	return page ? page_folio(page) : NULL;
>  }
> @@ -459,6 +464,8 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)
>  	const unsigned long end_pfn = start_pfn + nr_pages;
>  	unsigned long pfn = start_pfn;
>  
> +	KUNIT_STATIC_STUB_REDIRECT(kho_restore_pages, phys, nr_pages);
> +
>  	while (pfn < end_pfn) {
>  		const unsigned int order =
>  			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
> -- 
> 2.54.0.563.g4f69b47b94-goog
> 

-- 
Sincerely yours,
Mike.


From longwei27 at huawei.com  Sat May 16 01:56:53 2026
From: longwei27 at huawei.com (longwei)
Date: Sat, 16 May 2026 16:56:53 +0800
Subject: [PATCH] kho: docs: fix typo in ABI documentation
Message-ID: <20260516085653.2193872-1-longwei27@huawei.com>

From: LongWei <longwei27 at huawei.com>

Replace "Indentifies" with "Identifies".

Signed-off-by: Long Wei <longwei27 at huawei.com>
---
 include/linux/kho/abi/kexec_handover.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 7e847a2339b0..7682a94871f5 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -64,7 +64,7 @@
  *   Root KHO Node (/):
  *     - compatible: "kho-v3"
  *
- *       Indentifies the overall KHO ABI version.
+ *       Identifies the overall KHO ABI version.
  *
  *     - preserved-memory-map: u64
  *
-- 
2.43.0



From lkp at intel.com  Sat May 16 15:35:32 2026
From: lkp at intel.com (kernel test robot)
Date: Sun, 17 May 2026 06:35:32 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 d13dfb48dfa369fb2745116c0bc6f215517a351f
Message-ID: <202605170621.Ktc3oHd5-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: d13dfb48dfa369fb2745116c0bc6f215517a351f  kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes

elapsed time: 813m

configs tested: 325
configs skipped: 6

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                        nsimosci_defconfig    gcc-15.2.0
arc                   randconfig-001-20260516    gcc-8.5.0
arc                   randconfig-001-20260517    gcc-13.4.0
arc                   randconfig-002-20260516    gcc-8.5.0
arc                   randconfig-002-20260517    gcc-13.4.0
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    gcc-15.2.0
arm                          pxa910_defconfig    gcc-15.2.0
arm                   randconfig-001-20260516    gcc-8.5.0
arm                   randconfig-001-20260517    gcc-13.4.0
arm                   randconfig-002-20260516    gcc-8.5.0
arm                   randconfig-002-20260517    gcc-13.4.0
arm                   randconfig-003-20260516    gcc-8.5.0
arm                   randconfig-003-20260517    gcc-13.4.0
arm                   randconfig-004-20260516    gcc-8.5.0
arm                   randconfig-004-20260517    gcc-13.4.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                          randconfig-001    clang-23
arm64                 randconfig-001-20260516    clang-23
arm64                 randconfig-001-20260516    gcc-9.5.0
arm64                 randconfig-001-20260517    clang-23
arm64                          randconfig-002    clang-23
arm64                 randconfig-002-20260516    clang-23
arm64                 randconfig-002-20260516    gcc-9.5.0
arm64                 randconfig-002-20260517    clang-23
arm64                          randconfig-003    clang-23
arm64                 randconfig-003-20260516    clang-23
arm64                 randconfig-003-20260516    gcc-9.5.0
arm64                 randconfig-003-20260517    clang-23
arm64                          randconfig-004    clang-23
arm64                 randconfig-004-20260516    clang-23
arm64                 randconfig-004-20260516    gcc-9.5.0
arm64                 randconfig-004-20260517    clang-23
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                           randconfig-001    clang-23
csky                  randconfig-001-20260516    clang-23
csky                  randconfig-001-20260516    gcc-9.5.0
csky                  randconfig-001-20260517    clang-23
csky                           randconfig-002    clang-23
csky                  randconfig-002-20260516    clang-23
csky                  randconfig-002-20260516    gcc-9.5.0
csky                  randconfig-002-20260517    clang-23
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260516    gcc-11.5.0
hexagon               randconfig-001-20260517    gcc-9.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260516    gcc-11.5.0
hexagon               randconfig-002-20260517    gcc-9.5.0
i386                             alldefconfig    gcc-14
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                 buildonly-randconfig-001    clang-20
i386        buildonly-randconfig-001-20260516    clang-20
i386        buildonly-randconfig-001-20260517    gcc-14
i386                 buildonly-randconfig-002    clang-20
i386        buildonly-randconfig-002-20260516    clang-20
i386        buildonly-randconfig-002-20260517    gcc-14
i386                 buildonly-randconfig-003    clang-20
i386        buildonly-randconfig-003-20260516    clang-20
i386        buildonly-randconfig-003-20260517    gcc-14
i386                 buildonly-randconfig-004    clang-20
i386        buildonly-randconfig-004-20260516    clang-20
i386        buildonly-randconfig-004-20260517    gcc-14
i386                 buildonly-randconfig-005    clang-20
i386        buildonly-randconfig-005-20260516    clang-20
i386        buildonly-randconfig-005-20260517    gcc-14
i386                 buildonly-randconfig-006    clang-20
i386        buildonly-randconfig-006-20260516    clang-20
i386        buildonly-randconfig-006-20260517    gcc-14
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260516    clang-20
i386                  randconfig-001-20260517    gcc-14
i386                  randconfig-002-20260516    clang-20
i386                  randconfig-002-20260517    gcc-14
i386                  randconfig-003-20260516    clang-20
i386                  randconfig-003-20260517    gcc-14
i386                  randconfig-004-20260516    clang-20
i386                  randconfig-004-20260517    gcc-14
i386                  randconfig-005-20260516    clang-20
i386                  randconfig-005-20260517    gcc-14
i386                  randconfig-006-20260516    clang-20
i386                  randconfig-006-20260517    gcc-14
i386                  randconfig-007-20260516    clang-20
i386                  randconfig-007-20260517    gcc-14
i386                           randconfig-011    gcc-14
i386                  randconfig-011-20260516    gcc-14
i386                           randconfig-012    gcc-14
i386                  randconfig-012-20260516    gcc-14
i386                           randconfig-013    gcc-14
i386                  randconfig-013-20260516    gcc-14
i386                           randconfig-014    gcc-14
i386                  randconfig-014-20260516    gcc-14
i386                           randconfig-015    gcc-14
i386                  randconfig-015-20260516    gcc-14
i386                           randconfig-016    gcc-14
i386                  randconfig-016-20260516    gcc-14
i386                           randconfig-017    gcc-14
i386                  randconfig-017-20260516    gcc-14
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260516    gcc-11.5.0
loongarch             randconfig-001-20260517    gcc-9.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260516    gcc-11.5.0
loongarch             randconfig-002-20260517    gcc-9.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260516    gcc-11.5.0
nios2                 randconfig-001-20260517    gcc-9.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260516    gcc-11.5.0
nios2                 randconfig-002-20260517    gcc-9.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-12.5.0
parisc                randconfig-001-20260516    gcc-12.5.0
parisc                randconfig-001-20260517    gcc-10.5.0
parisc                         randconfig-002    gcc-12.5.0
parisc                randconfig-002-20260516    gcc-12.5.0
parisc                randconfig-002-20260517    gcc-10.5.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                      ppc44x_defconfig    clang-23
powerpc                        randconfig-001    gcc-12.5.0
powerpc               randconfig-001-20260516    gcc-12.5.0
powerpc               randconfig-001-20260517    gcc-10.5.0
powerpc                        randconfig-002    gcc-12.5.0
powerpc               randconfig-002-20260516    gcc-12.5.0
powerpc               randconfig-002-20260517    gcc-10.5.0
powerpc64                      randconfig-001    gcc-12.5.0
powerpc64             randconfig-001-20260516    gcc-12.5.0
powerpc64             randconfig-001-20260517    gcc-10.5.0
powerpc64                      randconfig-002    gcc-12.5.0
powerpc64             randconfig-002-20260516    gcc-12.5.0
powerpc64             randconfig-002-20260517    gcc-10.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                    nommu_k210_defconfig    clang-23
riscv                          randconfig-001    gcc-15.2.0
riscv                 randconfig-001-20260516    gcc-15.2.0
riscv                 randconfig-001-20260517    gcc-10.5.0
riscv                          randconfig-002    gcc-15.2.0
riscv                 randconfig-002-20260516    gcc-15.2.0
riscv                 randconfig-002-20260517    gcc-10.5.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                           randconfig-001    gcc-15.2.0
s390                  randconfig-001-20260516    gcc-15.2.0
s390                  randconfig-001-20260517    gcc-10.5.0
s390                           randconfig-002    gcc-15.2.0
s390                  randconfig-002-20260516    gcc-15.2.0
s390                  randconfig-002-20260517    gcc-10.5.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                             randconfig-001    gcc-15.2.0
sh                    randconfig-001-20260516    gcc-15.2.0
sh                    randconfig-001-20260517    gcc-10.5.0
sh                             randconfig-002    gcc-15.2.0
sh                    randconfig-002-20260516    gcc-15.2.0
sh                    randconfig-002-20260517    gcc-10.5.0
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-8.5.0
sparc                 randconfig-001-20260516    gcc-8.5.0
sparc                 randconfig-001-20260517    gcc-8.5.0
sparc                          randconfig-002    gcc-8.5.0
sparc                 randconfig-002-20260516    gcc-8.5.0
sparc                 randconfig-002-20260517    gcc-8.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-8.5.0
sparc64               randconfig-001-20260516    gcc-8.5.0
sparc64               randconfig-001-20260517    gcc-8.5.0
sparc64                        randconfig-002    gcc-8.5.0
sparc64               randconfig-002-20260516    gcc-8.5.0
sparc64               randconfig-002-20260517    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-8.5.0
um                    randconfig-001-20260516    gcc-8.5.0
um                    randconfig-001-20260517    gcc-8.5.0
um                             randconfig-002    gcc-8.5.0
um                    randconfig-002-20260516    gcc-8.5.0
um                    randconfig-002-20260517    gcc-8.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64               buildonly-randconfig-001    gcc-14
x86_64      buildonly-randconfig-001-20260516    gcc-14
x86_64      buildonly-randconfig-001-20260517    gcc-14
x86_64               buildonly-randconfig-002    gcc-14
x86_64      buildonly-randconfig-002-20260516    gcc-14
x86_64      buildonly-randconfig-002-20260517    gcc-14
x86_64               buildonly-randconfig-003    gcc-14
x86_64      buildonly-randconfig-003-20260516    gcc-14
x86_64      buildonly-randconfig-003-20260517    gcc-14
x86_64               buildonly-randconfig-004    gcc-14
x86_64      buildonly-randconfig-004-20260516    gcc-14
x86_64      buildonly-randconfig-004-20260517    gcc-14
x86_64               buildonly-randconfig-005    gcc-14
x86_64      buildonly-randconfig-005-20260516    gcc-14
x86_64      buildonly-randconfig-005-20260517    gcc-14
x86_64               buildonly-randconfig-006    gcc-14
x86_64      buildonly-randconfig-006-20260516    gcc-14
x86_64      buildonly-randconfig-006-20260517    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                         randconfig-001    gcc-14
x86_64                randconfig-001-20260516    gcc-14
x86_64                randconfig-001-20260517    clang-20
x86_64                         randconfig-002    gcc-14
x86_64                randconfig-002-20260516    gcc-14
x86_64                randconfig-002-20260517    clang-20
x86_64                         randconfig-003    gcc-14
x86_64                randconfig-003-20260516    gcc-14
x86_64                randconfig-003-20260517    clang-20
x86_64                         randconfig-004    gcc-14
x86_64                randconfig-004-20260516    gcc-14
x86_64                randconfig-004-20260517    clang-20
x86_64                         randconfig-005    gcc-14
x86_64                randconfig-005-20260516    gcc-14
x86_64                randconfig-005-20260517    clang-20
x86_64                         randconfig-006    gcc-14
x86_64                randconfig-006-20260516    gcc-14
x86_64                randconfig-006-20260517    clang-20
x86_64                         randconfig-011    clang-20
x86_64                randconfig-011-20260516    clang-20
x86_64                randconfig-011-20260517    gcc-14
x86_64                         randconfig-012    clang-20
x86_64                randconfig-012-20260516    clang-20
x86_64                randconfig-012-20260517    gcc-14
x86_64                         randconfig-013    clang-20
x86_64                randconfig-013-20260516    clang-20
x86_64                randconfig-013-20260517    gcc-14
x86_64                         randconfig-014    clang-20
x86_64                randconfig-014-20260516    clang-20
x86_64                randconfig-014-20260517    gcc-14
x86_64                         randconfig-015    clang-20
x86_64                randconfig-015-20260516    clang-20
x86_64                randconfig-015-20260517    gcc-14
x86_64                         randconfig-016    clang-20
x86_64                randconfig-016-20260516    clang-20
x86_64                randconfig-016-20260517    gcc-14
x86_64                         randconfig-071    gcc-14
x86_64                randconfig-071-20260516    gcc-14
x86_64                randconfig-071-20260517    clang-20
x86_64                         randconfig-072    gcc-14
x86_64                randconfig-072-20260516    gcc-14
x86_64                randconfig-072-20260517    clang-20
x86_64                         randconfig-073    gcc-14
x86_64                randconfig-073-20260516    gcc-14
x86_64                randconfig-073-20260517    clang-20
x86_64                         randconfig-074    gcc-14
x86_64                randconfig-074-20260516    gcc-14
x86_64                randconfig-074-20260517    clang-20
x86_64                         randconfig-075    gcc-14
x86_64                randconfig-075-20260516    gcc-14
x86_64                randconfig-075-20260517    clang-20
x86_64                         randconfig-076    gcc-14
x86_64                randconfig-076-20260516    gcc-14
x86_64                randconfig-076-20260517    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                         randconfig-001    gcc-8.5.0
xtensa                randconfig-001-20260516    gcc-8.5.0
xtensa                randconfig-001-20260517    gcc-8.5.0
xtensa                         randconfig-002    gcc-8.5.0
xtensa                randconfig-002-20260516    gcc-8.5.0
xtensa                randconfig-002-20260517    gcc-8.5.0

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


From rppt at kernel.org  Sun May 17 00:25:29 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 10:25:29 +0300
Subject: [PATCH] kho: docs: fix typo in ABI documentation
In-Reply-To: <20260516085653.2193872-1-longwei27@huawei.com>
References: <20260516085653.2193872-1-longwei27@huawei.com>
Message-ID: <177900272376.2854906.3562157499024061729.b4-ty@b4>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

On Sat, 16 May 2026 16:56:53 +0800, longwei wrote:
> Replace "Indentifies" with "Identifies".

Applied to fixes branch of liveupdate/linux.git tree, thanks!

[1/1] kho: docs: fix typo in ABI documentation
      commit: b1378127003b61930ce30064328640503ad3ef6d

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: fixes

--
Sincerely yours,
Mike.



From rppt at kernel.org  Sun May 17 03:05:27 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 13:05:27 +0300
Subject: [PATCH 12/12] mm/hugetlb: make bootmem allocation work with KHO
In-Reply-To: <20260429133928.850721-13-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-13-pratyush@kernel.org>
Message-ID: <agmS5yZRflzN1M8U@kernel.org>

Hi Pratyush,

On Wed, Apr 29, 2026 at 03:39:14PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Gigantic page allocation is somewhat broken currently when KHO is used.
> 
> Firstly, they break KHO scratch size accounting. RSRV_KERN is used to
> track how much memory is reserved for use by the kernel. Since
> alloc_bootmem() calls the memblock_alloc*() APIs, the hugepages
> allocated also get marked as RSRV_KERN.

The intended semantics of RSRV_KERN was to distinguish memory reserved
because it's in use by firmware from the memory reserved or allocated by
the kernel. It does not have to be directly used by the kernel, some of
PG_Reserved memory is mapped to user-space and it's not only hugetlb. There
are zero pages, VDSO and maybe something else, I don't remember.

> Allocations marked RSRV_KERN are used by KHO to calculate how much
> scratch space it should reserve to make sure the next kernel has enough
> memory to boot when it is in scratch-only phase. Counting hugepages in

The size of RSRV_KERN allocations is also used in
memblock_estimated_nr_free_pages() that's currently used by
set_max_threads() to set several rlimits and by s390 to allocate colored
zero pages. Excluding hugetlb from that will skew the calculations there.

> that blows up scratch size, and can lead to the scratch allocation
> failing, making KHO unusable. This will show up when huge pages make up
> more than 50% of the system, which is a fairly common use case.
> 
> Secondly, while not supported right now, huge pages are user memory and
> can be preserved via KHO. The scratch spaces should not have any
> preserved memory. Allocating hugepages from scratch (on a KHO boot) can
> lead to them being un-preservable.
> 
> Introduce memblock_alloc_nid_user(). This does two things: first, it
> instructs __memblock_alloc_range_nid() to not use scratch areas to
> fulfill allocation. If KHO is in scratch-only mode, allocations will
> only be made from extended scratch areas. Second, it removes RSRV_KERN
> from the allocation to make sure it doesn't mess up scratch size
> accounting.
> 
> To reduce duplication, introduce __memblock_alloc_range_nid() which does
> exactly what memblock_alloc_range_nid() used to do, but takes the flags
> from its caller. Then make memblock_alloc_range_nid() a wrapper to it.
> This lets memblock_alloc_nid_user() re-use most of the logic without
> causing churn to update all callers of memblock_alloc_range_nid() and
> adding yet another argument to it.

That's neat :)

But I'm not too fond of the memblock_alloc_nid_user() as a concept. That
early at boot everything is still kernel, even though hugetlb pages might
become user afterwards if they are actually consumed.
Another thing, is that adding such global API to memblock could be abused
and suddenly some early code will clear RSRV_KERN for a random pieces of
memory.

If we'd still need a special memblock function for gigantic pages
allocation I'd rather make it explicit that it's for hugetlb and keep it in
mm/internal.h

I was thinking about possible alternative solutions and here's what I came
up with

1. SCRATCH_EXT nicely increases the memory pool available to memblock, but
   the decision which memory can be used for which allocations becomes more
   complex. It should make sure that SCRATCH is not used for hugetlb, but
   OTOH it's preferred for other early allocations. With implicit
   interaction between choose_memblock_flags() and should_skip_region()
   this seems to me quite a headache.
   memblock_reserved_clear_kern() should be changed to something else to
   keep set_max_threads() working. And, IMHO, memblock_alloc_nid_user()
   should be turned into memblock_alloc_gigantic_hugetlb() and only exposed
   to MM code. It's even possible that it will duplicate some of
   memblock_alloc_range_nid) rather than use it because hugetlb is always a
   special case.

2. Split memblock_reserve() part from kho_mem_retrieve() to run before
   hugetlb allocations. With this we won't need new types and APIs, we can
   ensure that hugetlb allocations do not use SCRATCH by reserving scratch
   areas before hugetlb allocations and releasing them afterwards.
   Obviously this is the slowest option as it will slow down all memblock
   allocations from the point we memblock_reserve() preserved memory. Still
   realistically I wouldn't expect large impact on performance because the
   heaviest part there is reserving of the preserved memory that has to
   happen anyway. Also, I don't thing that a system that uses a lot of
   gigantic pages will have a lot of preserved chunks scattered around.

3. Move the complexity into hugetlb and make it preserve all the gigantic
   pages with KHO. This means, though, that we won't be able to increase
   the number of gigantic pages after the first boot (although decreasing
   it seems easy) and that we need to let scratch auto scaling understand
   what were the "normal" memblock allocations and what were the
   allocations of the gigantic pages.

4. Invert SCRATCH_EXT logic and instead of freeing large chunks around the
   preserved memory to SCRATCH_EXT, reserve memory surrounding the
   preserved areas and release scratch_only before hugetlb allocations.
   We'd still need to somehow prevent hugetlb allocation spilling into
   scratch and there's a nasty piece of releasing the memory around the
   preserved chunks. On the bright side, I think it's feasible to defer the
   release of those regions and free them when we are already
   multithreaded. This probably the most involved alternative but it also
   could help with the bottleneck of kho_mem_retrieve() creating too many
   memblock.reserved regions.

Thoughts?

> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
> 
> Notes:
>     Checkpatch complains here about the alignment of arguments of
>     memblock_alloc_range_nid() with open parentheses. That can be ignored
>     since the code already was mis-aligned, and for good reason.
> 
>  include/linux/memblock.h |   4 ++
>  mm/hugetlb.c             |  19 ++----
>  mm/memblock.c            | 138 ++++++++++++++++++++++++++++++---------
>  3 files changed, 116 insertions(+), 45 deletions(-)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 03:17:50 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 13:17:50 +0300
Subject: [PATCH 10/12] kho: extended scratch
In-Reply-To: <20260429133928.850721-11-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-11-pratyush@kernel.org>
Message-ID: <agmVzhvF8cxFu1PK@kernel.org>

On Wed, Apr 29, 2026 at 03:39:12PM +0200, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
> Methodology
> ===========
> 
> Introduce extended scratch areas. These areas are discovered at boot by
> walking the preserved memory radix tree and looking for free blocks of
> memory. They then marked as scratch to allow allocations from them. This
> makes KHO more resilient to memory pressure and allows supporting huge
> page preservation.
> 
> Since the preserved memory radix tree mixes both physical address and
> order into a single key, and does not track table pages, it is difficult
> to identify free areas from it directly. Walk the tree and digest it
> down into another radix tree. The latter tracks blocks of
> KHO_EXT_SHIFT (1 GiB as of now) granularity. Then walk the digested tree
> and mark the areas between the present keys as scratch.
>
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kexec_handover.h     |   1 +
>  kernel/liveupdate/kexec_handover.c | 148 +++++++++++++++++++++++++----
>  mm/mm_init.c                       |   1 +
>  3 files changed, 133 insertions(+), 17 deletions(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 1a04e089f779..c2b843a5fb28 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -840,6 +857,120 @@ static void __init kho_reserve_scratch(void)
>  	kho_enable = false;
>  }
>  
> +#define KHO_EXT_SHIFT 30 /* 1 GiB */

arm64 does not necessarily use 1G gigantic pages and worse, it can have 2
gigantic hstates.

I think this should take into account what actual gigantic page sizes are
in use for the general case.

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:20:15 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:20:15 +0300
Subject: [PATCH v2 01/10] liveupdate: centralize state management into
 struct luo_ser
In-Reply-To: <20260514222628.931312-2-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-2-pasha.tatashin@soleen.com>
Message-ID: <agn4z5ZrFBi9AIGw@kernel.org>

On Thu, May 14, 2026 at 10:26:19PM +0000, Pasha Tatashin wrote:
> Transition the LUO to ABI v2, which centralizes state management into a
> single struct luo_ser header.
> 
> Previously, LUO state was spread across multiple FDT properties and
> subnodes. ABI v2 simplifies this by placing all core state, including
> the liveupdate number and physical addresses for sessions and FLB
> headers into a centralized struct luo_ser.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
>  kernel/liveupdate/luo_core.c     | 59 ++++++++++++++-------
>  kernel/liveupdate/luo_flb.c      | 65 ++++-------------------
>  kernel/liveupdate/luo_internal.h |  8 +--
>  kernel/liveupdate/luo_session.c  | 57 +++-----------------
>  5 files changed, 93 insertions(+), 187 deletions(-)

This looks lovely :)

> @@ -115,27 +117,29 @@ static int __init luo_early_startup(void)
>  		return -EINVAL;
>  	}
>  
> -	ln_size = 0;
> -	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_LIVEUPDATE_NUM,
> -			  &ln_size);
> -	if (!ptr || ln_size != sizeof(luo_global.liveupdate_num)) {
> -		pr_err("Unable to get live update number '%s' [%d]\n",
> -		       LUO_FDT_LIVEUPDATE_NUM, ln_size);
> +	header_size = 0;
> +	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
> +	if (!ptr || header_size != sizeof(u64)) {
> +		pr_err("Unable to get ABI header '%s' [%d]\n",
> +		       LUO_FDT_ABI_HEADER, header_size);
>  
>  		return -EINVAL;
>  	}
>  
> -	luo_global.liveupdate_num = get_unaligned((u64 *)ptr);
> +	luo_ser_pa = get_unaligned((u64 *)ptr);
> +	luo_ser = phys_to_virt(luo_ser_pa);
> +
> +	luo_global.liveupdate_num = luo_ser->liveupdate_num;
>  	pr_info("Retrieved live update data, liveupdate number: %lld\n",
>  		luo_global.liveupdate_num);
>  
> -	err = luo_session_setup_incoming(luo_global.fdt_in);
> +	err = luo_session_setup_incoming(luo_ser->sessions_pa);
>  	if (err)
>  		return err;
>  
> -	err = luo_flb_setup_incoming(luo_global.fdt_in);
> +	luo_flb_setup_incoming(luo_ser->flbs_pa);

Sashiko asks: 

  Is there a leak of the preserved luo_ser memory here?

  The outgoing kernel allocates the header using kho_alloc_preserve(), but
  luo_early_startup() returns without calling kho_restore_free() on the
  luo_ser pointer once the fields have been processed. This results in an
  unreleased memory reservation on every successful live update.

This seems preexisting, and we probably don't care enough, but still...

There were more comments by sashiko, didn't check them yet.
  
> -	return err;
> +	return 0;
>  }
>  
>  static int __init liveupdate_early_init(void)
> @@ -156,7 +160,8 @@ early_initcall(liveupdate_early_init);
>  /* Called during boot to create outgoing LUO fdt tree */
>  static int __init luo_fdt_setup(void)
>  {
> -	const u64 ln = luo_global.liveupdate_num + 1;
> +	struct luo_ser *luo_ser;
> +	u64 luo_ser_pa;
>  	void *fdt_out;
>  	int err;
>  
> @@ -166,27 +171,45 @@ static int __init luo_fdt_setup(void)
>  		return PTR_ERR(fdt_out);
>  	}
>  
> +	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
> +	if (IS_ERR(luo_ser)) {
> +		err = PTR_ERR(luo_ser);
> +		goto exit_free_fdt;
> +	}
> +	luo_ser_pa = virt_to_phys(luo_ser);
> +
>  	err = fdt_create(fdt_out, LUO_FDT_SIZE);
>  	err |= fdt_finish_reservemap(fdt_out);
>  	err |= fdt_begin_node(fdt_out, "");
>  	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
> -	err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln));
> -	err |= luo_session_setup_outgoing(fdt_out);
> -	err |= luo_flb_setup_outgoing(fdt_out);
> +	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
> +			    sizeof(luo_ser_pa));
>  	err |= fdt_end_node(fdt_out);
>  	err |= fdt_finish(fdt_out);
>  	if (err)
> -		goto exit_free;
> +		goto exit_free_luo_ser;
> +
> +	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
> +	if (err)
> +		goto exit_free_luo_ser;
> +
> +	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
> +	if (err)
> +		goto exit_free_luo_ser;
> +
> +	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
>  
>  	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
>  			      fdt_totalsize(fdt_out));
>  	if (err)
> -		goto exit_free;
> +		goto exit_free_luo_ser;

And here we also seem to leak memory allocated by sesttion/flb setup.

>  	luo_global.fdt_out = fdt_out;
>  
>  	return 0;
>  
> -exit_free:
> +exit_free_luo_ser:
> +	kho_unpreserve_free(luo_ser);
> +exit_free_fdt:
>  	kho_unpreserve_free(fdt_out);
>  	pr_err("failed to prepare LUO FDT: %d\n", err);

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:24:33 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:24:33 +0300
Subject: [PATCH v2 02/10] liveupdate: Extract luo_file_deserialize_one
 helper
In-Reply-To: <20260514222628.931312-3-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-3-pasha.tatashin@soleen.com>
Message-ID: <agn50TRvhDpGm_9v@kernel.org>

On Thu, May 14, 2026 at 10:26:20PM +0000, Pasha Tatashin wrote:
> Extract the logic for deserializing single entries for files into
> separate helper functions. In preparation to a linked-block
> serialization for files.

It would be nice to mention that this is a pure code movement without
indented changes.
 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_file.c | 77 ++++++++++++++++++++----------------
>  1 file changed, 44 insertions(+), 33 deletions(-)

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:25:05 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:25:05 +0300
Subject: [PATCH v2 03/10] liveupdate: Extract luo_session_deserialize_one
 helper
In-Reply-To: <20260514222628.931312-4-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-4-pasha.tatashin@soleen.com>
Message-ID: <agn58YPpCkkKFfWA@kernel.org>

On Thu, May 14, 2026 at 10:26:21PM +0000, Pasha Tatashin wrote:
> Extract the logic for deserializing single entries for sessions into
> separate helper functions. In preparation to a linked-block
> serialization for sessions.

It would be nice to mention that this is a pure code movement without
indented changes.
 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_session.c | 62 +++++++++++++++++++--------------
>  1 file changed, 36 insertions(+), 26 deletions(-)

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:26:25 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:26:25 +0300
Subject: [PATCH v2 04/10] liveupdate: add support for linked-block
 serialization
In-Reply-To: <20260514222628.931312-5-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-5-pasha.tatashin@soleen.com>
Message-ID: <agn6QeIamoeMkesv@kernel.org>

On Thu, May 14, 2026 at 10:26:22PM +0000, Pasha Tatashin wrote:
> Introduce a linked-block serialization mechanism for LUO state.
> 
> Previously, LUO used contiguous memory blocks for serializing sessions
> and files, which imposed limits on the total number of items that could
> be preserved across a live update.
> 
> This commit adds the infrastructure for a more flexible, block-based
> approach where serialized data is stored in a chain of linked blocks.
> This is a preparatory step to allow an unlimited number of
> luo_sessions and luo_files to be preserved.

Shouldn't it be a part of KHO?
 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  Documentation/core-api/liveupdate.rst |   8 +
>  include/linux/kho/abi/luo.h           |  22 ++
>  kernel/liveupdate/Makefile            |   1 +
>  kernel/liveupdate/luo_block.c         | 388 ++++++++++++++++++++++++++
>  kernel/liveupdate/luo_internal.h      |  57 ++++
>  5 files changed, 476 insertions(+)
>  create mode 100644 kernel/liveupdate/luo_block.c

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:31:10 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:31:10 +0300
Subject: [PATCH v2 05/10] liveupdate: defer session block allocation and
 PA setting
In-Reply-To: <20260514222628.931312-6-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-6-pasha.tatashin@soleen.com>
Message-ID: <agn7XnuO7VU4eK52@kernel.org>

On Thu, May 14, 2026 at 10:26:23PM +0000, Pasha Tatashin wrote:
> Currently, luo_session_setup_outgoing() allocates the session block and
> sets its physical address in the header immediately. With upcoming
> dynamic block-based session management, this makes the first block
> different from the rest. Move the allocation to where it is first needed.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
> @@ -77,15 +77,16 @@
>  
>  /**
>   * struct luo_session_header - Header struct for managing LUO sessions.
> - * @count:      The number of sessions currently tracked in the @list.
> - * @list:       The head of the linked list of `struct luo_session` instances.
> - * @rwsem:      A read-write semaphore providing synchronized access to the
> - *              session list and other fields in this structure.
> - * @header_ser: The header data of serialization array.
> - * @ser:        The serialized session data (an array of
> - *              `struct luo_session_ser`).
> - * @active:     Set to true when first initialized. If previous kernel did not
> - *              send session data, active stays false for incoming.
> + * @count:       The number of sessions currently tracked in the @list.
> + * @list:        The head of the linked list of `struct luo_session` instances.
> + * @rwsem:       A read-write semaphore providing synchronized access to the
> + *               session list and other fields in this structure.
> + * @header_ser:  The header data of serialization array.
> + * @ser:         The serialized session data (an array of
> + *               `struct luo_session_ser`).
> + * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
> + * @active:      Set to true when first initialized. If previous kernel did not
> + *               send session data, active stays false for incoming.

Hmm, why addition of a single field changed the entire block? :/

>   */
>  struct luo_session_header {
>  	long count;

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:33:05 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:33:05 +0300
Subject: [PATCH v2 06/10] liveupdate: Remove limit on the number of
 sessions
In-Reply-To: <20260514222628.931312-7-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-7-pasha.tatashin@soleen.com>
Message-ID: <agn70Rol6l0Okp9B@kernel.org>

On Thu, May 14, 2026 at 10:26:24PM +0000, Pasha Tatashin wrote:
> Currently, the number of LUO sessions is limited by a fixed number of
> pre-allocated pages for serialization (16 pages, allowing for ~819
> sessions).
> 
> This limitation is problematic if LUO is used to support things such as
> systemd file descriptor store, and would be used not just as VM memory
> but to save other states on the machine.
> 
> Remove this limit by transitioning to a linked-block approach for
> session metadata serialization. Instead of a single contiguous block,
> session metadata is now stored in a chain of 16-page blocks. Each block
> starts with a header containing the physical address of the next block
> and the number of session entries in the current block.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
>  include/linux/kho/abi/luo.h     |  24 +-------
>  kernel/liveupdate/luo_session.c | 105 +++++++++++++++-----------------
>  2 files changed, 50 insertions(+), 79 deletions(-)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:33:59 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:33:59 +0300
Subject: [PATCH v2 07/10] liveupdate: Remove limit on the number of files
 per session
In-Reply-To: <20260514222628.931312-8-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-8-pasha.tatashin@soleen.com>
Message-ID: <agn8B_OIH1fQjwa5@kernel.org>

On Thu, May 14, 2026 at 10:26:25PM +0000, Pasha Tatashin wrote:
> To remove the fixed limit on the number of preserved files per session,
> transition the file metadata serialization from a single contiguous
> memory block to a chain of linked blocks.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
>  include/linux/kho/abi/luo.h      |  13 +--
>  kernel/liveupdate/luo_file.c     | 142 +++++++++++++++----------------
>  kernel/liveupdate/luo_internal.h |   5 +-
>  3 files changed, 77 insertions(+), 83 deletions(-)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:35:01 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:35:01 +0300
Subject: [PATCH v2 08/10] selftests/liveupdate: Test session and file
 limit removal
In-Reply-To: <20260514222628.931312-9-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-9-pasha.tatashin@soleen.com>
Message-ID: <agn8RUGJ5RcySkUY@kernel.org>

On Thu, May 14, 2026 at 10:26:26PM +0000, Pasha Tatashin wrote:
> With the removal of static limits on the number of sessions and files per
> session, the orchestrator now uses dynamic allocation.
> 
> Add new test cases to verify that the system can handle a large number of
> sessions and files. These tests ensure that the dynamic block allocation
> and reuse logic for session metadata and outgoing files work correctly
> beyond the previous static limits.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
>  .../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
>  .../selftests/liveupdate/luo_test_utils.c     | 24 ++++++
>  .../selftests/liveupdate/luo_test_utils.h     |  2 +
>  3 files changed, 101 insertions(+)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:37:12 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:37:12 +0300
Subject: [PATCH v2 09/10] selftests/liveupdate: Add stress-sessions kexec
 test
In-Reply-To: <20260514222628.931312-10-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-10-pasha.tatashin@soleen.com>
Message-ID: <agn8yJVcdvPw8ZCQ@kernel.org>

On Thu, May 14, 2026 at 10:26:27PM +0000, Pasha Tatashin wrote:
> Add a new test that creates 2000 LUO sessions before a kexec
> reboot and verifies their presence after the reboot. This ensures
> that the linked-block serialization mechanism works correctly for
> a large number of sessions.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
>  tools/testing/selftests/liveupdate/Makefile   |   1 +
>  .../liveupdate/luo_stress_sessions.c          | 102 ++++++++++++++++++
>  2 files changed, 103 insertions(+)
>  create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:38:04 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:38:04 +0300
Subject: [PATCH v2 10/10] selftests/liveupdate: Add stress-files kexec
 test
In-Reply-To: <20260514222628.931312-11-pasha.tatashin@soleen.com>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-11-pasha.tatashin@soleen.com>
Message-ID: <agn8_FAtMdc8B5c9@kernel.org>

On Thu, May 14, 2026 at 10:26:28PM +0000, Pasha Tatashin wrote:
> Add a new luo_stress_files kexec test that verifies preserving and
> retrieving 500 files across a kexec reboot.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

> ---
>  tools/testing/selftests/liveupdate/Makefile   |   1 +
>  .../selftests/liveupdate/luo_stress_files.c   | 101 ++++++++++++++++++
>  2 files changed, 102 insertions(+)
>  create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 17 10:43:35 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 20:43:35 +0300
Subject: [PATCH v3 0/5] liveupdate: serialization safety and race fixes
In-Reply-To: <20260515003722.938123-1-pasha.tatashin@soleen.com>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
Message-ID: <agn-R5TPNmUSeU3y@kernel.org>

On Fri, May 15, 2026 at 12:37:17AM +0000, Pasha Tatashin wrote:
> This series addresses several issues related to the synchronization
> between the reboot process and LUO session management.
> 
> Changes in v3:
> - Refined the session mutation blocking to use a dedicated global
>   rwsem (luo_session_serialize_rwsem) instead of pinning individual
>   mutexes.
> - Fixed a use-after-free race in luo_file_unpreserve_files() where
>   a module could be released before its file handler ID was erased.
> - Fixed a TOCTOU race in luo_session_retrieve() by extending the
>   lock scope to overlap with session mutex acquisition.
> - Removed an unused 'ser' field from struct luo_session.
> - Dropped the KHO skip patch as it was not needed.
> 
> 1. Skip LUO serialization for context-preserving kexec: A
> preserve_context kexec returns to the current kernel, which is unrelated
> to live update where state is passed to the next kernel. Skipping
> serialization avoids unnecessary work and prevents sessions from being
> left in a frozen state upon return.
> 
> 2. Block session mutations during reboot: During the reboot() syscall,
> user processes may still be running concurrently and attempting to
> mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
> All mutation operations (create, retrieve, release, ioctl) hold the
> read lock. The serialization process holds the write lock indefinitely
> on success, effectively freezing the subsystem.
> 
> 3. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
> to ensure the file handler module remains pinned while its operations
> are being accessed during cleanup.
> 
> 4. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
> scope to prevent a session from being released between lookup and
> mutex acquisition.
> 
> 5. Remove unused ser field from struct luo_session: Clean up the
> session structure by removing a field that was never utilized.

Sashiko is still unhappy:
https://sashiko.dev/#/patchset/20260515003722.938123-1-pasha.tatashin at soleen.com

Didn't verify it's actually right, but its complaints seem legit.

Among other things sashiko noted a TOCTOU issue and then found it's fixed
by a later patch, maybe move the TOCTOU fix earlier in the series?
 
> Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
> luo-reboot-sync/v3
> 
> Pasha Tatashin (5):
>   liveupdate: skip serialization for context-preserving kexec
>   liveupdate: block session mutations during reboot
>   liveupdate: fix u-a-f in luo_file_unpreserve_files() and
>     luo_file_finish()
>   liveupdate: fix TOCTOU race in luo_session_retrieve()
>   liveupdate: Remove unused ser field from struct luo_session
> 
>  kernel/kexec_core.c              |  8 +++++---
>  kernel/liveupdate/luo_file.c     |  5 +++--
>  kernel/liveupdate/luo_internal.h |  2 --
>  kernel/liveupdate/luo_session.c  | 27 ++++++++++++++++++++-------
>  4 files changed, 28 insertions(+), 14 deletions(-)
> 
> 
> base-commit: 7b0b68b2b95606e65594958686833e53423f58f2
> -- 
> 2.53.0
> 

-- 
Sincerely yours,
Mike.


From pasha.tatashin at soleen.com  Sun May 17 11:36:36 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 18:36:36 +0000
Subject: [PATCH v2 01/10] liveupdate: centralize state management into
 struct luo_ser
In-Reply-To: <agn4z5ZrFBi9AIGw@kernel.org>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-2-pasha.tatashin@soleen.com>
 <agn4z5ZrFBi9AIGw@kernel.org>
Message-ID: <agoKAw9CQIVIhp2b@plex>

On 05-17 20:20, Mike Rapoport wrote:
> On Thu, May 14, 2026 at 10:26:19PM +0000, Pasha Tatashin wrote:
> > Transition the LUO to ABI v2, which centralizes state management into a
> > single struct luo_ser header.
> > 
> > Previously, LUO state was spread across multiple FDT properties and
> > subnodes. ABI v2 simplifies this by placing all core state, including
> > the liveupdate number and physical addresses for sessions and FLB
> > headers into a centralized struct luo_ser.
> > 
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
> >  kernel/liveupdate/luo_core.c     | 59 ++++++++++++++-------
> >  kernel/liveupdate/luo_flb.c      | 65 ++++-------------------
> >  kernel/liveupdate/luo_internal.h |  8 +--
> >  kernel/liveupdate/luo_session.c  | 57 +++-----------------
> >  5 files changed, 93 insertions(+), 187 deletions(-)
> 
> This looks lovely :)
> 
> > @@ -115,27 +117,29 @@ static int __init luo_early_startup(void)
> >  		return -EINVAL;
> >  	}
> >  
> > -	ln_size = 0;
> > -	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_LIVEUPDATE_NUM,
> > -			  &ln_size);
> > -	if (!ptr || ln_size != sizeof(luo_global.liveupdate_num)) {
> > -		pr_err("Unable to get live update number '%s' [%d]\n",
> > -		       LUO_FDT_LIVEUPDATE_NUM, ln_size);
> > +	header_size = 0;
> > +	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
> > +	if (!ptr || header_size != sizeof(u64)) {
> > +		pr_err("Unable to get ABI header '%s' [%d]\n",
> > +		       LUO_FDT_ABI_HEADER, header_size);
> >  
> >  		return -EINVAL;
> >  	}
> >  
> > -	luo_global.liveupdate_num = get_unaligned((u64 *)ptr);
> > +	luo_ser_pa = get_unaligned((u64 *)ptr);
> > +	luo_ser = phys_to_virt(luo_ser_pa);
> > +
> > +	luo_global.liveupdate_num = luo_ser->liveupdate_num;
> >  	pr_info("Retrieved live update data, liveupdate number: %lld\n",
> >  		luo_global.liveupdate_num);
> >  
> > -	err = luo_session_setup_incoming(luo_global.fdt_in);
> > +	err = luo_session_setup_incoming(luo_ser->sessions_pa);
> >  	if (err)
> >  		return err;
> >  
> > -	err = luo_flb_setup_incoming(luo_global.fdt_in);
> > +	luo_flb_setup_incoming(luo_ser->flbs_pa);
> 
> Sashiko asks: 
> 
>   Is there a leak of the preserved luo_ser memory here?
> 
>   The outgoing kernel allocates the header using kho_alloc_preserve(), but
>   luo_early_startup() returns without calling kho_restore_free() on the
>   luo_ser pointer once the fields have been processed. This results in an
>   unreleased memory reservation on every successful live update.
> 
> This seems preexisting, and we probably don't care enough, but still...

The freeing happen during finish, that was by design.

> 
> There were more comments by sashiko, didn't check them yet.

The other comment is stupid: yes, changing ABI prevents updating from 
older kernel.

>   
> > -	return err;
> > +	return 0;
> >  }
> >  
> >  static int __init liveupdate_early_init(void)
> > @@ -156,7 +160,8 @@ early_initcall(liveupdate_early_init);
> >  /* Called during boot to create outgoing LUO fdt tree */
> >  static int __init luo_fdt_setup(void)
> >  {
> > -	const u64 ln = luo_global.liveupdate_num + 1;
> > +	struct luo_ser *luo_ser;
> > +	u64 luo_ser_pa;
> >  	void *fdt_out;
> >  	int err;
> >  
> > @@ -166,27 +171,45 @@ static int __init luo_fdt_setup(void)
> >  		return PTR_ERR(fdt_out);
> >  	}
> >  
> > +	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
> > +	if (IS_ERR(luo_ser)) {
> > +		err = PTR_ERR(luo_ser);
> > +		goto exit_free_fdt;
> > +	}
> > +	luo_ser_pa = virt_to_phys(luo_ser);
> > +
> >  	err = fdt_create(fdt_out, LUO_FDT_SIZE);
> >  	err |= fdt_finish_reservemap(fdt_out);
> >  	err |= fdt_begin_node(fdt_out, "");
> >  	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
> > -	err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln));
> > -	err |= luo_session_setup_outgoing(fdt_out);
> > -	err |= luo_flb_setup_outgoing(fdt_out);
> > +	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
> > +			    sizeof(luo_ser_pa));
> >  	err |= fdt_end_node(fdt_out);
> >  	err |= fdt_finish(fdt_out);
> >  	if (err)
> > -		goto exit_free;
> > +		goto exit_free_luo_ser;
> > +
> > +	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
> > +	if (err)
> > +		goto exit_free_luo_ser;
> > +
> > +	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
> > +	if (err)
> > +		goto exit_free_luo_ser;
> > +
> > +	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
> >  
> >  	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
> >  			      fdt_totalsize(fdt_out));
> >  	if (err)
> > -		goto exit_free;
> > +		goto exit_free_luo_ser;
> 
> And here we also seem to leak memory allocated by sesttion/flb setup.
> 
> >  	luo_global.fdt_out = fdt_out;
> >  
> >  	return 0;
> >  
> > -exit_free:
> > +exit_free_luo_ser:
> > +	kho_unpreserve_free(luo_ser);
> > +exit_free_fdt:
> >  	kho_unpreserve_free(fdt_out);
> >  	pr_err("failed to prepare LUO FDT: %d\n", err);
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Sun May 17 11:37:18 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 18:37:18 +0000
Subject: [PATCH v2 02/10] liveupdate: Extract luo_file_deserialize_one
 helper
In-Reply-To: <agn50TRvhDpGm_9v@kernel.org>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-3-pasha.tatashin@soleen.com>
 <agn50TRvhDpGm_9v@kernel.org>
Message-ID: <agoKzw8mR21K7cCJ@plex>

On 05-17 20:24, Mike Rapoport wrote:
> On Thu, May 14, 2026 at 10:26:20PM +0000, Pasha Tatashin wrote:
> > Extract the logic for deserializing single entries for files into
> > separate helper functions. In preparation to a linked-block
> > serialization for files.
> 
> It would be nice to mention that this is a pure code movement without
> indented changes.

OK, will do that if there is another spin.

>  
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  kernel/liveupdate/luo_file.c | 77 ++++++++++++++++++++----------------
> >  1 file changed, 44 insertions(+), 33 deletions(-)
> 
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Sun May 17 11:40:50 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 18:40:50 +0000
Subject: [PATCH v2 04/10] liveupdate: add support for linked-block
 serialization
In-Reply-To: <agn6QeIamoeMkesv@kernel.org>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-5-pasha.tatashin@soleen.com>
 <agn6QeIamoeMkesv@kernel.org>
Message-ID: <agoK7TRpT1LErW8H@plex>

On 05-17 20:26, Mike Rapoport wrote:
> On Thu, May 14, 2026 at 10:26:22PM +0000, Pasha Tatashin wrote:
> > Introduce a linked-block serialization mechanism for LUO state.
> > 
> > Previously, LUO used contiguous memory blocks for serializing sessions
> > and files, which imposed limits on the total number of items that could
> > be preserved across a live update.
> > 
> > This commit adds the infrastructure for a more flexible, block-based
> > approach where serialized data is stored in a chain of linked blocks.
> > This is a preparatory step to allow an unlimited number of
> > luo_sessions and luo_files to be preserved.
> 
> Shouldn't it be a part of KHO?

It can be. However, at the moment, it is only used by LUO, so I kept it 
there. Sami is also planning to use it for IOMMU; if so, he will adopt 
the interface for his usage as well, and move it to a separate KHO 
library.

>  
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  Documentation/core-api/liveupdate.rst |   8 +
> >  include/linux/kho/abi/luo.h           |  22 ++
> >  kernel/liveupdate/Makefile            |   1 +
> >  kernel/liveupdate/luo_block.c         | 388 ++++++++++++++++++++++++++
> >  kernel/liveupdate/luo_internal.h      |  57 ++++
> >  5 files changed, 476 insertions(+)
> >  create mode 100644 kernel/liveupdate/luo_block.c
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Sun May 17 11:52:02 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 18:52:02 +0000
Subject: [PATCH v2 05/10] liveupdate: defer session block allocation and
 PA setting
In-Reply-To: <agn7XnuO7VU4eK52@kernel.org>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-6-pasha.tatashin@soleen.com>
 <agn7XnuO7VU4eK52@kernel.org>
Message-ID: <agoL1Afx2cHUNmE9@plex>

On 05-17 20:31, Mike Rapoport wrote:
> On Thu, May 14, 2026 at 10:26:23PM +0000, Pasha Tatashin wrote:
> > Currently, luo_session_setup_outgoing() allocates the session block and
> > sets its physical address in the header immediately. With upcoming
> > dynamic block-based session management, this makes the first block
> > different from the rest. Move the allocation to where it is first needed.
> > 
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> 
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> 
> > ---
> > @@ -77,15 +77,16 @@
> >  
> >  /**
> >   * struct luo_session_header - Header struct for managing LUO sessions.
> > - * @count:      The number of sessions currently tracked in the @list.
> > - * @list:       The head of the linked list of `struct luo_session` instances.
> > - * @rwsem:      A read-write semaphore providing synchronized access to the
> > - *              session list and other fields in this structure.
> > - * @header_ser: The header data of serialization array.
> > - * @ser:        The serialized session data (an array of
> > - *              `struct luo_session_ser`).
> > - * @active:     Set to true when first initialized. If previous kernel did not
> > - *              send session data, active stays false for incoming.
> > + * @count:       The number of sessions currently tracked in the @list.
> > + * @list:        The head of the linked list of `struct luo_session` instances.
> > + * @rwsem:       A read-write semaphore providing synchronized access to the
> > + *               session list and other fields in this structure.
> > + * @header_ser:  The header data of serialization array.
> > + * @ser:         The serialized session data (an array of
> > + *               `struct luo_session_ser`).
> > + * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
> > + * @active:      Set to true when first initialized. If previous kernel did not
> > + *               send session data, active stays false for incoming.
> 
> Hmm, why addition of a single field changed the entire block? :/

Yes, I had to decide whether to shorten the field name or change the 
entire block. I opted for the latter. :-)

> 
> >   */
> >  struct luo_session_header {
> >  	long count;
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Sun May 17 12:01:27 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:01:27 +0000
Subject: [PATCH v3 0/5] liveupdate: serialization safety and race fixes
In-Reply-To: <agn-R5TPNmUSeU3y@kernel.org>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
 <agn-R5TPNmUSeU3y@kernel.org>
Message-ID: <agoOe_BjM4IKJM4Z@plex>

On 05-17 20:43, Mike Rapoport wrote:
> On Fri, May 15, 2026 at 12:37:17AM +0000, Pasha Tatashin wrote:
> > This series addresses several issues related to the synchronization
> > between the reboot process and LUO session management.
> > 
> > Changes in v3:
> > - Refined the session mutation blocking to use a dedicated global
> >   rwsem (luo_session_serialize_rwsem) instead of pinning individual
> >   mutexes.
> > - Fixed a use-after-free race in luo_file_unpreserve_files() where
> >   a module could be released before its file handler ID was erased.
> > - Fixed a TOCTOU race in luo_session_retrieve() by extending the
> >   lock scope to overlap with session mutex acquisition.
> > - Removed an unused 'ser' field from struct luo_session.
> > - Dropped the KHO skip patch as it was not needed.
> > 
> > 1. Skip LUO serialization for context-preserving kexec: A
> > preserve_context kexec returns to the current kernel, which is unrelated
> > to live update where state is passed to the next kernel. Skipping
> > serialization avoids unnecessary work and prevents sessions from being
> > left in a frozen state upon return.
> > 
> > 2. Block session mutations during reboot: During the reboot() syscall,
> > user processes may still be running concurrently and attempting to
> > mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
> > All mutation operations (create, retrieve, release, ioctl) hold the
> > read lock. The serialization process holds the write lock indefinitely
> > on success, effectively freezing the subsystem.
> > 
> > 3. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
> > to ensure the file handler module remains pinned while its operations
> > are being accessed during cleanup.
> > 
> > 4. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
> > scope to prevent a session from being released between lookup and
> > mutex acquisition.
> > 
> > 5. Remove unused ser field from struct luo_session: Clean up the
> > session structure by removing a field that was never utilized.
> 
> Sashiko is still unhappy:
> https://sashiko.dev/#/patchset/20260515003722.938123-1-pasha.tatashin at soleen.com
> 
> Didn't verify it's actually right, but its complaints seem legit.

Reviewed the complaints, a couple things are legit, I will address 
them and respin.

> Among other things sashiko noted a TOCTOU issue and then found it's fixed
> by a later patch, maybe move the TOCTOU fix earlier in the series?

Sure, will move it earlier, while I think, as long as it is fixed in 
ther series it does not matter where it is :-)

Pasha


From pasha.tatashin at soleen.com  Sun May 17 12:26:45 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:45 +0000
Subject: [PATCH v4 0/5] liveupdate: serialization safety and race fixes
Message-ID: <20260517192650.1019314-1-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization
between the reboot process and LUO session management.

Changes in v4:
- Addressed some comments from Sashiko:
- Reordered patches: "fix TOCTOU race" moved to #2.
- In "block session mutations during reboot" (#3):
  - Updated to use manual down_read()/up_read() in luo_session_create()
    to accommodate existing error labels and ensure consistent unlocking.
  - Added missing unlock of sh->rwsem in the success path of
    luo_session_serialize().

1. Skip LUO serialization for context-preserving kexec: A
preserve_context kexec returns to the current kernel, which is unrelated
to live update where state is passed to the next kernel. Skipping
serialization avoids unnecessary work and prevents sessions from being
left in a frozen state upon return.

2. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
scope to prevent a session from being released between lookup and
mutex acquisition.

3. Block session mutations during reboot: During the reboot() syscall,
user processes may still be running concurrently and attempting to
mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
All mutation operations (create, retrieve, release, ioctl) hold the
read lock. The serialization process holds the write lock indefinitely
on success, effectively freezing the subsystem.

4. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
to ensure the file handler module remains pinned while its operations
are being accessed during cleanup.

5. Remove unused ser field from struct luo_session: Clean up the
session structure by removing a field that was never utilized.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
luo-reboot-sync/v4

Pasha Tatashin (5):
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: fix TOCTOU race in luo_session_retrieve()
  liveupdate: block session mutations during reboot
  liveupdate: fix u-a-f in luo_file_unpreserve_files() and
    luo_file_finish()
  liveupdate: Remove unused ser field from struct luo_session

 kernel/kexec_core.c              |  8 ++++---
 kernel/liveupdate/luo_file.c     |  5 +++--
 kernel/liveupdate/luo_internal.h |  2 --
 kernel/liveupdate/luo_session.c  | 38 ++++++++++++++++++++++++--------
 4 files changed, 37 insertions(+), 16 deletions(-)


base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.53.0



From pasha.tatashin at soleen.com  Sun May 17 12:26:46 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:46 +0000
Subject: [PATCH v4 1/5] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <20260517192650.1019314-2-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which is
unrelated to live update where the state is passed to the next kernel.
Skip liveupdate_reboot() in this case to avoid serialization and prevent
sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.53.0



From pasha.tatashin at soleen.com  Sun May 17 12:26:47 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:47 +0000
Subject: [PATCH v4 2/5] liveupdate: fix TOCTOU race in luo_session_retrieve()
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <20260517192650.1019314-3-pasha.tatashin@soleen.com>

Extend the scope of the rwsem_read lock in luo_session_retrieve() to
overlap with the acquisition of the session mutex. This prevents a
concurrent thread from releasing and freeing the session between the
lookup and the mutex lock.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..59b37d17db6b 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -415,12 +415,11 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
-	scoped_guard(rwsem_read, &sh->rwsem) {
-		list_for_each_entry(it, &sh->list, list) {
-			if (!strncmp(it->name, name, sizeof(it->name))) {
-				session = it;
-				break;
-			}
+	guard(rwsem_read)(&sh->rwsem);
+	list_for_each_entry(it, &sh->list, list) {
+		if (!strncmp(it->name, name, sizeof(it->name))) {
+			session = it;
+			break;
 		}
 	}
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Sun May 17 12:26:48 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:48 +0000
Subject: [PATCH v4 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <20260517192650.1019314-4-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., creating,
retrieving, or releasing sessions). To prevent this, introduce
luo_session_serialize_rwsem to synchronize mutations with the
serialization process.

All session mutation operations (create, retrieve, release, ioctl) take
the read lock. The serialization process (luo_session_serialize) takes
the write lock and holds it indefinitely on success. This effectively
freezes the LUO session subsystem during the transition to the new
kernel. If serialization fails, the lock is released to allow recovery.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 27 ++++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 59b37d17db6b..6099213275d8 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -75,6 +75,13 @@
 		sizeof(struct luo_session_header_ser)) /		\
 		sizeof(struct luo_session_ser))
 
+/*
+ * Protects session mutations during serialization. All session mutation
+ * operations must hold the read lock. The serialization process holds the write
+ * lock indefinitely on success to block all concurrent and future mutations.
+ */
+static DECLARE_RWSEM(luo_session_serialize_rwsem);
+
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:      The number of sessions currently tracked in the @list.
@@ -205,6 +212,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 	struct luo_session *session = filep->private_data;
 	struct luo_session_header *sh;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	/* If retrieved is set, it means this session is from incoming list */
 	if (session->retrieved) {
 		int err = luo_session_finish_one(session);
@@ -354,6 +362,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 	if (ret)
 		return ret;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	return op->execute(session, &ucmd);
 }
 
@@ -385,9 +394,12 @@ int luo_session_create(const char *name, struct file **filep)
 	struct luo_session *session;
 	int err;
 
+	down_read(&luo_session_serialize_rwsem);
 	session = luo_session_alloc(name);
-	if (IS_ERR(session))
-		return PTR_ERR(session);
+	if (IS_ERR(session)) {
+		err = PTR_ERR(session);
+		goto err_unlock;
+	}
 
 	err = luo_session_insert(&luo_session_global.outgoing, session);
 	if (err)
@@ -398,12 +410,16 @@ int luo_session_create(const char *name, struct file **filep)
 	if (err)
 		goto err_remove;
 
+	up_read(&luo_session_serialize_rwsem);
+
 	return 0;
 
 err_remove:
 	luo_session_remove(&luo_session_global.outgoing, session);
 err_free:
 	luo_session_free(session);
+err_unlock:
+	up_read(&luo_session_serialize_rwsem);
 
 	return err;
 }
@@ -415,6 +431,7 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	guard(rwsem_read)(&sh->rwsem);
 	list_for_each_entry(it, &sh->list, list) {
 		if (!strncmp(it->name, name, sizeof(it->name))) {
@@ -582,7 +599,8 @@ int luo_session_serialize(void)
 	int i = 0;
 	int err;
 
-	guard(rwsem_write)(&sh->rwsem);
+	down_write(&luo_session_serialize_rwsem);
+	down_write(&sh->rwsem);
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -593,6 +611,7 @@ int luo_session_serialize(void)
 		i++;
 	}
 	sh->header_ser->count = sh->count;
+	up_write(&sh->rwsem);
 
 	return 0;
 
@@ -602,6 +621,8 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	up_write(&sh->rwsem);
+	up_write(&luo_session_serialize_rwsem);
 
 	return err;
 }
-- 
2.53.0



From pasha.tatashin at soleen.com  Sun May 17 12:26:49 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:49 +0000
Subject: [PATCH v4 4/5] liveupdate: fix u-a-f in luo_file_unpreserve_files() and luo_file_finish()
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <20260517192650.1019314-5-pasha.tatashin@soleen.com>

In luo_file_unpreserve_files() and luo_file_finish(), reorder
module_put() and xa_erase() to ensure the file handler module remains
pinned while its operations are being accessed.

Specifically, luo_get_id() dereferences fh->ops->get_id, so the module
reference must be held until after xa_erase() (which calls luo_get_id)
completes.

For luo_file_finish(), this requires moving the module_put() call out of
the luo_file_finish_one() helper and into the main loop of
luo_file_finish() itself.

Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..208987502f73 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -385,10 +385,11 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 		args.private_data = luo_file->private_data;
 		luo_file->fh->ops->unpreserve(&args);
 		luo_flb_file_unpreserve(luo_file->fh);
-		module_put(luo_file->fh->ops->owner);
 
 		xa_erase(&luo_preserved_files,
 			 luo_get_id(luo_file->fh, luo_file->file));
+		module_put(luo_file->fh->ops->owner);
+
 		list_del(&luo_file->list);
 		file_set->count--;
 
@@ -677,7 +678,6 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
 
 	luo_file->fh->ops->finish(&args);
 	luo_flb_file_finish(luo_file->fh);
-	module_put(luo_file->fh->ops->owner);
 }
 
 /**
@@ -738,6 +738,7 @@ int luo_file_finish(struct luo_file_set *file_set)
 				 luo_get_id(luo_file->fh, luo_file->file));
 			fput(luo_file->file);
 		}
+		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
 		mutex_destroy(&luo_file->mutex);
-- 
2.53.0



From pasha.tatashin at soleen.com  Sun May 17 12:26:50 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 17 May 2026 19:26:50 +0000
Subject: [PATCH v4 5/5] liveupdate: Remove unused ser field from struct luo_session
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <20260517192650.1019314-6-pasha.tatashin@soleen.com>

The ser field in struct luo_session was intended to point to the
serialized data for a session, but it was never actually utilized in the
implementation. All serialization and deserialization logic consistently
uses the pointers maintained in struct luo_session_header.

Remove the dead field to clean up the structure.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..dd53d4a7277e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -59,7 +59,6 @@ struct luo_file_set {
  * struct luo_session - Represents an active or incoming Live Update session.
  * @name:       A unique name for this session, used for identification and
  *              retrieval.
- * @ser:        Pointer to the serialized data for this session.
  * @list:       A list_head member used to link this session into a global list
  *              of either outgoing (to be preserved) or incoming (restored from
  *              previous kernel) sessions.
@@ -70,7 +69,6 @@ struct luo_file_set {
  */
 struct luo_session {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	struct luo_session_ser *ser;
 	struct list_head list;
 	bool retrieved;
 	struct luo_file_set file_set;
-- 
2.53.0



From rppt at kernel.org  Sun May 17 13:11:28 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 17 May 2026 23:11:28 +0300
Subject: [PATCH v3 0/5] liveupdate: serialization safety and race fixes
In-Reply-To: <agoOe_BjM4IKJM4Z@plex>
References: <20260515003722.938123-1-pasha.tatashin@soleen.com>
 <agn-R5TPNmUSeU3y@kernel.org>
 <agoOe_BjM4IKJM4Z@plex>
Message-ID: <agog8LftVATbhl_c@kernel.org>

On Sun, May 17, 2026 at 07:01:27PM +0000, Pasha Tatashin wrote:
> On 05-17 20:43, Mike Rapoport wrote:
> 
> > Among other things sashiko noted a TOCTOU issue and then found it's fixed
> > by a later patch, maybe move the TOCTOU fix earlier in the series?
> 
> Sure, will move it earlier, while I think, as long as it is fixed in 
> ther series it does not matter where it is :-)

Less sashiko comments to read ;-)
 
> Pasha

-- 
Sincerely yours,
Mike.


From lkp at intel.com  Sun May 17 15:28:09 2026
From: lkp at intel.com (kernel test robot)
Date: Mon, 18 May 2026 06:28:09 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 b1378127003b61930ce30064328640503ad3ef6d
Message-ID: <202605180658.Z1cvyCQu-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: b1378127003b61930ce30064328640503ad3ef6d  kho: docs: fix typo in ABI documentation

elapsed time: 805m

configs tested: 374
configs skipped: 5

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                            randconfig-001    gcc-8.5.0
arc                   randconfig-001-20260517    gcc-13.4.0
arc                   randconfig-001-20260517    gcc-8.5.0
arc                   randconfig-001-20260518    gcc-8.5.0
arc                            randconfig-002    gcc-8.5.0
arc                   randconfig-002-20260517    gcc-13.4.0
arc                   randconfig-002-20260517    gcc-8.5.0
arc                   randconfig-002-20260518    gcc-8.5.0
arm                              alldefconfig    gcc-15.2.0
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    gcc-15.2.0
arm                           h3600_defconfig    gcc-15.2.0
arm                        neponset_defconfig    gcc-15.2.0
arm                         nhk8815_defconfig    clang-23
arm                             pxa_defconfig    gcc-15.2.0
arm                            randconfig-001    gcc-8.5.0
arm                   randconfig-001-20260517    gcc-13.4.0
arm                   randconfig-001-20260517    gcc-8.5.0
arm                   randconfig-001-20260518    gcc-8.5.0
arm                            randconfig-002    gcc-8.5.0
arm                   randconfig-002-20260517    gcc-13.4.0
arm                   randconfig-002-20260517    gcc-8.5.0
arm                   randconfig-002-20260518    gcc-8.5.0
arm                            randconfig-003    gcc-8.5.0
arm                   randconfig-003-20260517    gcc-13.4.0
arm                   randconfig-003-20260517    gcc-8.5.0
arm                   randconfig-003-20260518    gcc-8.5.0
arm                            randconfig-004    gcc-8.5.0
arm                   randconfig-004-20260517    gcc-13.4.0
arm                   randconfig-004-20260517    gcc-8.5.0
arm                   randconfig-004-20260518    gcc-8.5.0
arm64                            allmodconfig    clang-19
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                          randconfig-001    clang-23
arm64                 randconfig-001-20260517    clang-23
arm64                 randconfig-001-20260518    gcc-15.2.0
arm64                          randconfig-002    clang-23
arm64                 randconfig-002-20260517    clang-23
arm64                 randconfig-002-20260518    gcc-15.2.0
arm64                          randconfig-003    clang-23
arm64                 randconfig-003-20260517    clang-23
arm64                 randconfig-003-20260518    gcc-15.2.0
arm64                          randconfig-004    clang-23
arm64                 randconfig-004-20260517    clang-23
arm64                 randconfig-004-20260518    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                           randconfig-001    clang-23
csky                  randconfig-001-20260517    clang-23
csky                  randconfig-001-20260518    gcc-15.2.0
csky                           randconfig-002    clang-23
csky                  randconfig-002-20260517    clang-23
csky                  randconfig-002-20260518    gcc-15.2.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260517    gcc-11.5.0
hexagon               randconfig-001-20260517    gcc-9.5.0
hexagon               randconfig-001-20260518    gcc-11.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260517    gcc-11.5.0
hexagon               randconfig-002-20260517    gcc-9.5.0
hexagon               randconfig-002-20260518    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                             allmodconfig    gcc-14
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                 buildonly-randconfig-001    gcc-14
i386        buildonly-randconfig-001-20260517    gcc-14
i386        buildonly-randconfig-001-20260518    clang-20
i386                 buildonly-randconfig-002    gcc-14
i386        buildonly-randconfig-002-20260517    gcc-14
i386        buildonly-randconfig-002-20260518    clang-20
i386                 buildonly-randconfig-003    gcc-14
i386        buildonly-randconfig-003-20260517    gcc-14
i386        buildonly-randconfig-003-20260518    clang-20
i386                 buildonly-randconfig-004    gcc-14
i386        buildonly-randconfig-004-20260517    gcc-14
i386        buildonly-randconfig-004-20260518    clang-20
i386                 buildonly-randconfig-005    gcc-14
i386        buildonly-randconfig-005-20260517    gcc-14
i386        buildonly-randconfig-005-20260518    clang-20
i386                 buildonly-randconfig-006    gcc-14
i386        buildonly-randconfig-006-20260517    gcc-14
i386        buildonly-randconfig-006-20260518    clang-20
i386                                defconfig    gcc-15.2.0
i386                           randconfig-001    gcc-14
i386                  randconfig-001-20260517    gcc-14
i386                  randconfig-001-20260518    gcc-14
i386                           randconfig-002    gcc-14
i386                  randconfig-002-20260517    gcc-14
i386                  randconfig-002-20260518    gcc-14
i386                           randconfig-003    gcc-14
i386                  randconfig-003-20260517    gcc-14
i386                  randconfig-003-20260518    gcc-14
i386                           randconfig-004    gcc-14
i386                  randconfig-004-20260517    gcc-14
i386                  randconfig-004-20260518    gcc-14
i386                           randconfig-005    gcc-14
i386                  randconfig-005-20260517    gcc-14
i386                  randconfig-005-20260518    gcc-14
i386                           randconfig-006    gcc-14
i386                  randconfig-006-20260517    gcc-14
i386                  randconfig-006-20260518    gcc-14
i386                           randconfig-007    gcc-14
i386                  randconfig-007-20260517    gcc-14
i386                  randconfig-007-20260518    gcc-14
i386                           randconfig-011    gcc-14
i386                  randconfig-011-20260517    gcc-14
i386                  randconfig-011-20260518    gcc-14
i386                           randconfig-012    gcc-14
i386                  randconfig-012-20260517    gcc-14
i386                  randconfig-012-20260518    gcc-14
i386                           randconfig-013    gcc-14
i386                  randconfig-013-20260517    gcc-14
i386                  randconfig-013-20260518    gcc-14
i386                           randconfig-014    gcc-14
i386                  randconfig-014-20260517    gcc-14
i386                  randconfig-014-20260518    gcc-14
i386                           randconfig-015    gcc-14
i386                  randconfig-015-20260517    gcc-14
i386                  randconfig-015-20260518    gcc-14
i386                           randconfig-016    gcc-14
i386                  randconfig-016-20260517    gcc-14
i386                  randconfig-016-20260518    gcc-14
i386                           randconfig-017    gcc-14
i386                  randconfig-017-20260517    gcc-14
i386                  randconfig-017-20260518    gcc-14
loongarch                        allmodconfig    clang-19
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260517    gcc-11.5.0
loongarch             randconfig-001-20260517    gcc-9.5.0
loongarch             randconfig-001-20260518    gcc-11.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260517    gcc-11.5.0
loongarch             randconfig-002-20260517    gcc-9.5.0
loongarch             randconfig-002-20260518    gcc-11.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    clang-19
m68k                                defconfig    gcc-15.2.0
m68k                       m5275evb_defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
microblaze                          defconfig    gcc-15.2.0
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                               defconfig    gcc-11.5.0
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260517    gcc-11.5.0
nios2                 randconfig-001-20260517    gcc-9.5.0
nios2                 randconfig-001-20260518    gcc-11.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260517    gcc-11.5.0
nios2                 randconfig-002-20260517    gcc-9.5.0
nios2                 randconfig-002-20260518    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-10.5.0
parisc                randconfig-001-20260517    gcc-10.5.0
parisc                randconfig-001-20260518    clang-23
parisc                         randconfig-002    gcc-10.5.0
parisc                randconfig-002-20260517    gcc-10.5.0
parisc                randconfig-002-20260518    clang-23
parisc64                            defconfig    clang-19
parisc64                            defconfig    gcc-15.2.0
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                     powernv_defconfig    gcc-15.2.0
powerpc                        randconfig-001    gcc-10.5.0
powerpc               randconfig-001-20260517    gcc-10.5.0
powerpc               randconfig-001-20260518    clang-23
powerpc                        randconfig-002    gcc-10.5.0
powerpc               randconfig-002-20260517    gcc-10.5.0
powerpc               randconfig-002-20260518    clang-23
powerpc64                      randconfig-001    gcc-10.5.0
powerpc64             randconfig-001-20260517    gcc-10.5.0
powerpc64             randconfig-001-20260518    clang-23
powerpc64                      randconfig-002    gcc-10.5.0
powerpc64             randconfig-002-20260517    gcc-10.5.0
powerpc64             randconfig-002-20260518    clang-23
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                          randconfig-001    gcc-10.5.0
riscv                 randconfig-001-20260517    gcc-10.5.0
riscv                 randconfig-001-20260518    gcc-11.5.0
riscv                          randconfig-002    gcc-10.5.0
riscv                 randconfig-002-20260517    gcc-10.5.0
riscv                 randconfig-002-20260518    gcc-11.5.0
s390                             allmodconfig    clang-18
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                           randconfig-001    gcc-10.5.0
s390                  randconfig-001-20260517    gcc-10.5.0
s390                  randconfig-001-20260518    gcc-11.5.0
s390                           randconfig-002    gcc-10.5.0
s390                  randconfig-002-20260517    gcc-10.5.0
s390                  randconfig-002-20260518    gcc-11.5.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sh                          landisk_defconfig    gcc-15.2.0
sh                             randconfig-001    gcc-10.5.0
sh                    randconfig-001-20260517    gcc-10.5.0
sh                    randconfig-001-20260518    gcc-11.5.0
sh                             randconfig-002    gcc-10.5.0
sh                    randconfig-002-20260517    gcc-10.5.0
sh                    randconfig-002-20260518    gcc-11.5.0
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-8.5.0
sparc                 randconfig-001-20260517    gcc-8.5.0
sparc                 randconfig-001-20260518    gcc-15.2.0
sparc                          randconfig-002    gcc-8.5.0
sparc                 randconfig-002-20260517    gcc-8.5.0
sparc                 randconfig-002-20260518    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-8.5.0
sparc64               randconfig-001-20260517    gcc-8.5.0
sparc64               randconfig-001-20260518    gcc-15.2.0
sparc64                        randconfig-002    gcc-8.5.0
sparc64               randconfig-002-20260517    gcc-8.5.0
sparc64               randconfig-002-20260518    gcc-15.2.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-8.5.0
um                    randconfig-001-20260517    gcc-8.5.0
um                    randconfig-001-20260518    gcc-15.2.0
um                             randconfig-002    gcc-8.5.0
um                    randconfig-002-20260517    gcc-8.5.0
um                    randconfig-002-20260518    gcc-15.2.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64               buildonly-randconfig-001    gcc-14
x86_64      buildonly-randconfig-001-20260517    gcc-14
x86_64      buildonly-randconfig-001-20260518    gcc-14
x86_64               buildonly-randconfig-002    gcc-14
x86_64      buildonly-randconfig-002-20260517    gcc-14
x86_64      buildonly-randconfig-002-20260518    gcc-14
x86_64               buildonly-randconfig-003    gcc-14
x86_64      buildonly-randconfig-003-20260517    gcc-14
x86_64      buildonly-randconfig-003-20260518    gcc-14
x86_64               buildonly-randconfig-004    gcc-14
x86_64      buildonly-randconfig-004-20260517    gcc-14
x86_64      buildonly-randconfig-004-20260518    gcc-14
x86_64               buildonly-randconfig-005    gcc-14
x86_64      buildonly-randconfig-005-20260517    gcc-14
x86_64      buildonly-randconfig-005-20260518    gcc-14
x86_64               buildonly-randconfig-006    gcc-14
x86_64      buildonly-randconfig-006-20260517    gcc-14
x86_64      buildonly-randconfig-006-20260518    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                         randconfig-001    clang-20
x86_64                randconfig-001-20260517    clang-20
x86_64                         randconfig-002    clang-20
x86_64                randconfig-002-20260517    clang-20
x86_64                         randconfig-003    clang-20
x86_64                randconfig-003-20260517    clang-20
x86_64                         randconfig-004    clang-20
x86_64                randconfig-004-20260517    clang-20
x86_64                         randconfig-005    clang-20
x86_64                randconfig-005-20260517    clang-20
x86_64                         randconfig-006    clang-20
x86_64                randconfig-006-20260517    clang-20
x86_64                         randconfig-011    gcc-14
x86_64                randconfig-011-20260517    gcc-14
x86_64                randconfig-011-20260518    gcc-14
x86_64                         randconfig-012    gcc-14
x86_64                randconfig-012-20260517    gcc-14
x86_64                randconfig-012-20260518    gcc-14
x86_64                         randconfig-013    gcc-14
x86_64                randconfig-013-20260517    gcc-14
x86_64                randconfig-013-20260518    gcc-14
x86_64                         randconfig-014    gcc-14
x86_64                randconfig-014-20260517    gcc-14
x86_64                randconfig-014-20260518    gcc-14
x86_64                         randconfig-015    gcc-14
x86_64                randconfig-015-20260517    gcc-14
x86_64                randconfig-015-20260518    gcc-14
x86_64                         randconfig-016    gcc-14
x86_64                randconfig-016-20260517    gcc-14
x86_64                randconfig-016-20260518    gcc-14
x86_64                         randconfig-071    clang-20
x86_64                randconfig-071-20260517    clang-20
x86_64                randconfig-071-20260518    clang-20
x86_64                         randconfig-072    clang-20
x86_64                randconfig-072-20260517    clang-20
x86_64                randconfig-072-20260518    clang-20
x86_64                         randconfig-073    clang-20
x86_64                randconfig-073-20260517    clang-20
x86_64                randconfig-073-20260518    clang-20
x86_64                         randconfig-074    clang-20
x86_64                randconfig-074-20260517    clang-20
x86_64                randconfig-074-20260518    clang-20
x86_64                         randconfig-075    clang-20
x86_64                randconfig-075-20260517    clang-20
x86_64                randconfig-075-20260518    clang-20
x86_64                         randconfig-076    clang-20
x86_64                randconfig-076-20260517    clang-20
x86_64                randconfig-076-20260518    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                         randconfig-001    gcc-8.5.0
xtensa                randconfig-001-20260517    gcc-8.5.0
xtensa                randconfig-001-20260518    gcc-15.2.0
xtensa                         randconfig-002    gcc-8.5.0
xtensa                randconfig-002-20260517    gcc-8.5.0
xtensa                randconfig-002-20260518    gcc-15.2.0

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


From hu.shengming at zte.com.cn  Sun May 17 23:57:42 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Mon, 18 May 2026 14:57:42 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYyIDAvM10ga2V4ZWMtdG9vbO+8mmJ1ZyBmaXggZm9yIGNyYXNoZHVtcCBsb2FkIG9uIHJpc2N2?=
Message-ID: <20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
         so add bss segment to the kernel text elf.

Changes from v1:
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u
- Add patch 2 and patch 3 to fix other bug

Luo Haiyang (3):
  RISC-V: fix crashdump load memory ranges
  RISCV: Fix incorrect virtual address translation in crashdump load
  RISC-V: add bss section to kernel text elf header

 kexec/arch/riscv/Makefile          |   2 +
 kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
 kexec/arch/riscv/iomem.h           |   1 +
 util_lib/elf_info.c                |  41 ++++--
 util_lib/include/elf_info.h        |   1 +
 5 files changed, 181 insertions(+), 82 deletions(-)

-- 
2.27.0


From hu.shengming at zte.com.cn  Sun May 17 23:59:00 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Mon, 18 May 2026 14:59:00 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIDEvM10gUklTQy1WOiBmaXggY3Jhc2hkdW1wIGxvYWQgbWVtb3J5IHJhbmdlcw==?=
In-Reply-To: <20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn>
References: 20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn
Message-ID: <20260518145900121tCLnAGYor_GFRCPfLajz5@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: validate_mem_section: Can't read mem_section array.
 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: get_mm_sparsemem: Can't get the address of mem_section.
 kdump.sh[338]: The kernel version is not supported.
 kdump.sh[338]: The makedumpfile operation may be incomplete.

crash_mem_ranges only includes available memory, not all system RAM.
Inspired by the arm64 architecture, use system_mem_ranges in the
load_elfcorehdr function.

Fixes: b3fd54b (RISC-V: Add support for riscv kexec/kdump on kexec-tools)
Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 167 ++++++++++++++++++-----------
 1 file changed, 102 insertions(+), 65 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 336d7a7..e923a71 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -6,6 +6,7 @@
 #include "crashdump.h"
 #include "kexec-elf.h"
 #include "mem_regions.h"
+#include "iomem.h"

 static struct crash_elf_info elf_info = {
 #if __riscv_xlen == 64
@@ -18,6 +19,7 @@ static struct crash_elf_info elf_info = {
 };

 static struct memory_ranges crash_mem_ranges = {0};
+static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

 static unsigned long long get_page_offset(struct kexec_info *info)
@@ -34,86 +36,114 @@ static unsigned long long get_page_offset(struct kexec_info *info)
 	return vaddr_off;
 }

-int load_elfcorehdr(struct kexec_info *info)
+/*
+ * iomem_range_callback() - callback called for each iomem region
+ * @data: not used
+ * @nr: not used
+ * @str: name of the memory region
+ * @base: start address of the memory region
+ * @length: size of the memory region
+ *
+ * This function is called once for each memory region found in /proc/iomem.
+ * It locates system RAM and crashkernel reserved memory and places these to
+ * variables, respectively, system_memory_rgns and crash_mem_ranges.
+ */
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+				char *str, unsigned long long base,
+				unsigned long long length)
 {
-	struct memory_range crashkern_range = {0};
-	struct memory_range *ranges = NULL;
-	unsigned long start = 0;
-	unsigned long end = 0;
-	unsigned long buf_size = 0;
-	unsigned long elfcorehdr_addr = 0;
-	void* buf = NULL;
-	int i = 0;
-	int ret = 0;
+	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+		return mem_regions_alloc_and_add(&crash_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+		return mem_regions_alloc_and_add(&system_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+		elf_info.kern_paddr_start = base;
+	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+		elf_info.kern_size = base + length - elf_info.kern_paddr_start;

-	ret = parse_iomem_single("Kernel code\n", &start, NULL);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical base addr\n");
-		return -EINVAL;
-	}
-	elf_info.kern_paddr_start = start;
+	return 0;
+}
+
+/*
+ * crash_get_memory_ranges() - read system physical memory
+ *
+ * Function reads through system physical memory and stores found memory
+ * regions in system_memory_ranges.
+ * Regions are sorted in ascending order.
+ *
+ * Returns 0 in case of success and a negative value otherwise.
+ */
+static int crash_get_memory_ranges(void)
+{
+	int i;
+
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	ret = parse_iomem_single("Kernel bss\n", NULL, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+	if (!crash_mem_ranges.size)
 		return -EINVAL;
+
+	dbgprint_mem_range("Reserved memory range",
+			crash_mem_ranges.ranges, crash_mem_ranges.size);
+
+	for (i = 0; i < crash_mem_ranges.size; i++) {
+		if (mem_regions_alloc_and_exclude(&system_mem_ranges,
+					&crash_mem_ranges.ranges[i])) {
+			fprintf(stderr, "Cannot allocate memory for ranges\n");
+			return -ENOMEM;
+		}
 	}
-	elf_info.kern_paddr_start = start;
-	elf_info.kern_size = end - start;

+	/*
+	 * Make sure that the memory regions are sorted.
+	 */
+	mem_regions_sort(&system_mem_ranges);
+
+	dbgprint_mem_range("Coredump memory ranges",
+			   system_mem_ranges.ranges, system_mem_ranges.size);
+
+	/*
+	 * For additional kernel code/data segment.
+	 * kern_paddr_start/kern_size are determined in iomem_range_callback
+	 */
 	elf_info.kern_vaddr_start = get_kernel_sym("_text");
-	if (!elf_info.kern_vaddr_start) {
+	if (!elf_info.kern_vaddr_start)
 		elf_info.kern_vaddr_start = UINT64_MAX;
-	}

-	elf_info.page_offset = get_page_offset(info);
-	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
+	return 0;
+}

-	ret = parse_iomem_single("Crash kernel\n", &start, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
-		return -EINVAL;
-	}
-	crashkern_range.start = start;
-	crashkern_range.end = end;
-	crashkern_range.type = RANGE_RESERVED;
-
-	ranges = info->memory_range;
-	for (i = 0; i < info->memory_ranges; i++) {
-		ret = mem_regions_alloc_and_add(&crash_mem_ranges,
-						ranges[i].start,
-						ranges[i].end - ranges[i].start + 1,
-						ranges[i].type);
-		if (ret ) {
-			fprintf(stderr, "Could not create crash_mem_ranges\n");
-			return ret;
-		}
-	}
+int load_elfcorehdr(struct kexec_info *info)
+{
+	unsigned long buf_size = 0;
+	unsigned long elfcorehdr_addr = 0;
+	void* buf = NULL;

-	ret = mem_regions_alloc_and_exclude(&crash_mem_ranges,
-					    &crashkern_range);
-	if (ret) {
-		fprintf(stderr, "Could not exclude crashkern_range\n");
-		return ret;
-	}
+	if (crash_get_memory_ranges())
+		return EFAILED;
+
+	elf_info.page_offset = get_page_offset(info);
+	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
-	crash_create_elf64_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf64_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);

 #else
-	crash_create_elf32_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf32_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);
 #endif


 	elfcorehdr_addr = add_buffer_phys_virt(info, buf, buf_size,
-					       buf_size, 0,
-					       crashkern_range.start,
-					       crashkern_range.end,
-					       -1, 0);
+					    buf_size, 0,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end,
+					    -1, 0);

 	elfcorehdr_mem.start = elfcorehdr_addr;
 	elfcorehdr_mem.end = elfcorehdr_addr + buf_size - 1;
@@ -126,15 +156,22 @@ int load_elfcorehdr(struct kexec_info *info)

 int is_crashkernel_mem_reserved(void)
 {
-	uint64_t start = 0;
-	uint64_t end = 0;
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
-	       (start != end) : 0;
+	return crash_mem_ranges.size;
 }

 int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
 {
-	return parse_iomem_single("Crash kernel\n", start, end);
-}
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+	if (!crash_mem_ranges.size)
+		return -1;
+
+	*start = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start;
+	*end = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end;

+	return 0;
+}
-- 
2.27.0


From hu.shengming at zte.com.cn  Mon May 18 00:00:16 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Mon, 18 May 2026 15:00:16 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIDIvM10gUklTQ1Y6IEZpeCBpbmNvcnJlY3QgdmlydHVhbCBhZGRyZXNzIHRyYW5zbGF0aW9uIGluIGNyYXNoZHVtcCBsb2Fk?=
In-Reply-To: <20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn>
References: 20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn
Message-ID: <202605181500164354t1onASFGicXcpK39tlQv@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kexec shows the following crashdump log:

  Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
  Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
  Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...

Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
the linear address mapping is defined as:
  va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/Makefile          |  2 ++
 kexec/arch/riscv/crashdump-riscv.c | 55 ++++++++++++++++++++++++------
 util_lib/elf_info.c                | 41 +++++++++++++++++-----
 util_lib/include/elf_info.h        |  1 +
 4 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
index 18a997b..e886770 100644
--- a/kexec/arch/riscv/Makefile
+++ b/kexec/arch/riscv/Makefile
@@ -14,6 +14,8 @@ riscv_ARCH_REUSE_INITRD =

 riscv_CPPFLAGS += -I $(srcdir)/kexec/

+riscv_PHYS_TO_VIRT =
+
 dist += $(riscv_KEXEC_SRCS)				\
 	kexec/arch/riscv/image-header.h			\
 	kexec/arch/riscv/include/arch/options.h		\
diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index e923a71..6875249 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <elf.h>
+#include <elf_info.h>
 #include <unistd.h>

 #include "kexec.h"
@@ -22,18 +23,45 @@ static struct memory_ranges crash_mem_ranges = {0};
 static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

-static unsigned long long get_page_offset(struct kexec_info *info)
+static unsigned long long phys_offset;
+
+static int get_page_offset(unsigned long long *page_offset)
 {
-	unsigned long long vaddr_off = 0;
-	unsigned long long page_size = sysconf(_SC_PAGESIZE);
-	unsigned long long init_start = get_kernel_sym("_sinittext");
+	int fd, ret = 0;

-	/*
-	 * Begining of init section is aligned to page size
-	 */
-	vaddr_off = init_start - page_size;
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_page_offset_elf_kcore(fd, (long *)page_offset);
+	if (ret)
+		fprintf(stderr, "Can't get page_offset from /proc/kcore\n");
+
+	close(fd);
+	return ret;
+}
+
+static int get_phys_offset(unsigned long long *phys_offset)
+{
+	int fd, ret = 0;
+
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_phys_offset_elf_kcore(fd, (long *)phys_offset);
+	if (ret)
+		fprintf(stderr, "Can't get phys_offset from /proc/kcore\n");

-	return vaddr_off;
+	close(fd);
+	return ret;
+}
+
+unsigned long phys_to_virt(struct crash_elf_info *elf_info, unsigned long long p)
+{
+	return elf_info->page_offset - phys_offset + p;
 }

 /*
@@ -124,7 +152,14 @@ int load_elfcorehdr(struct kexec_info *info)
 	if (crash_get_memory_ranges())
 		return EFAILED;

-	elf_info.page_offset = get_page_offset(info);
+	if (get_phys_offset(&phys_offset))
+		return EFAILED;
+
+	dbgprintf("phys_offset:   %016llx\n", phys_offset);
+
+	if (get_page_offset(&elf_info.page_offset))
+		return EFAILED;
+
 	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 589bc1a..36eb895 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -1308,16 +1308,39 @@ int read_phys_offset_elf_kcore(int fd, long *phys_off)

 	*phys_off = ULONG_MAX;

+	if (phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	/* If we have a valid 'PHYS_OFFSET' by now,
+	 * return it to the caller now.
+	 */
 	ret = read_elf(fd);
-	if (!ret) {
-		/* If we have a valid 'PHYS_OFFSET' by now,
-		 * return it to the caller now.
-		 */
-		if (phys_offset != UINT64_MAX) {
-			*phys_off = phys_offset;
-			return ret;
-		}
+	if (!ret && phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	return -EFAULT;
+}
+
+int read_page_offset_elf_kcore(int fd, long *page_off)
+{
+	int ret;
+
+	*page_off = ULONG_MAX;
+
+	if (page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
+	}
+
+	ret = read_elf(fd);
+	if (!ret && page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
 	}

-	return 2;
+	return -EFAULT;
 }
diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
index fdf4c3d..3ccc950 100644
--- a/util_lib/include/elf_info.h
+++ b/util_lib/include/elf_info.h
@@ -29,6 +29,7 @@ int get_pt_load(int idx,
 	unsigned long long *virt_start,
 	unsigned long long *virt_end);
 int read_phys_offset_elf_kcore(int fd, long *phys_off);
+int read_page_offset_elf_kcore(int fd, long *page_off);
 int read_elf(int fd);
 void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
 extern void (*arch_scan_vmcoreinfo)(char *pos);
-- 
2.27.0


From hu.shengming at zte.com.cn  Mon May 18 00:01:40 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Mon, 18 May 2026 15:01:40 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIDMvM10gUklTQy1WOiBhZGQgYnNzIHNlY3Rpb24gdG8ga2VybmVsIHRleHQgZWxmIGhlYWRlcg==?=
In-Reply-To: <20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn>
References: 20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn
Message-ID: <20260518150140578xGhdr-ct94HGGS4gmP_it@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

  No program header covering vaddr 0xffffffff819fa200 found kexec bug?
  saving vmcore-dmesg.txt failed

prb stores the address of printk_rb_dynamic. Since printk_rb_dynamic
resides in the bss segment and the PHDR (Program Header) does not include
the bss segment address. Therefore,  add the bss segment to the Kernel text.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 2 +-
 kexec/arch/riscv/iomem.h           | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 6875249..a892225 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -88,7 +88,7 @@ static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
 						base, length, RANGE_RAM);
 	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
 		elf_info.kern_paddr_start = base;
-	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+	else if (strncmp(str, KERNEL_BSS, strlen(KERNEL_BSS)) == 0)
 		elf_info.kern_size = base + length - elf_info.kern_paddr_start;

 	return 0;
diff --git a/kexec/arch/riscv/iomem.h b/kexec/arch/riscv/iomem.h
index 7671e26..fcc0d0a 100644
--- a/kexec/arch/riscv/iomem.h
+++ b/kexec/arch/riscv/iomem.h
@@ -4,6 +4,7 @@
 #define SYSTEM_RAM		"System RAM\n"
 #define KERNEL_CODE		"Kernel code\n"
 #define KERNEL_DATA		"Kernel data\n"
+#define KERNEL_BSS		"Kernel bss\n"
 #define CRASH_KERNEL		"Crash kernel\n"
 #define IOMEM_RESERVED		"Reserved\n"

-- 
2.27.0


From k-hagio-ab at nec.com  Mon May 18 01:33:11 2026
From: k-hagio-ab at nec.com (=?utf-8?B?SEFHSU8gS0FaVUhJVE8o6JCp5bC+44CA5LiA5LuBKQ==?=)
Date: Mon, 18 May 2026 08:33:11 +0000
Subject: [PATCH v2 0/2] Fix riscv64 virtual-to-physical address
 translation
In-Reply-To: <20260515080745.70110-1-qirui.001@bytedance.com>
References: <20260515080745.70110-1-qirui.001@bytedance.com>
Message-ID: <fe6d8428-0dde-4005-ae06-7f9acddb314c@nec.com>

On 2026/05/15 17:07, Rui Qi wrote:
> This patchset fixes two bugs in the riscv64 architecture support of
> makedumpfile related to virtual-to-physical address translation.
> 
> Patch 1 fixes vaddr_to_paddr_riscv64() to exclude vmalloc and vmemmap
> regions from the VTOP() fast path. These regions are not linearly mapped
> and must go through page table walk.
> 
> Patch 2 fixes vtop_riscv64() to correctly handle huge pages when
> _PAGE_LEAF is set at PGD/P4D/PUD/PMD level. The original code incorrectly
> fell through to the next level, causing wrong physical address computation.
> 
> 
> Changes in v2:
> - Patch 2: Changed `goto invalid;` to `return paddr;` when huge page
>    (_PAGE_LEAF) hits, as the calculated physical address is valid.
> - Patch 2: Removed the unused `out:` label to fix a compiler warning.

Thank you for the update, applied.

Thanks,
Kazu

> 
> Rui Qi (2):
>    riscv64: Fix vaddr_to_paddr for vmalloc and vmemmap regions
>    riscv64: Fix huge page translation in vtop_riscv64()
> 
>   arch/riscv64.c | 31 +++++++++++++++++++++----------
>   1 file changed, 21 insertions(+), 10 deletions(-)
> 

From rppt at kernel.org  Mon May 18 01:40:08 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 18 May 2026 11:40:08 +0300
Subject: [PATCH v4 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260517192650.1019314-4-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
 <20260517192650.1019314-4-pasha.tatashin@soleen.com>
Message-ID: <agrQaP22pgxOkR_j@kernel.org>

On Sun, May 17, 2026 at 07:26:48PM +0000, Pasha Tatashin wrote:
> During the reboot() syscall, user processes may still be running
> concurrently and attempting to mutate sessions (e.g., creating,
> retrieving, or releasing sessions). To prevent this, introduce
> luo_session_serialize_rwsem to synchronize mutations with the
> serialization process.
> 
> All session mutation operations (create, retrieve, release, ioctl) take
> the read lock. The serialization process (luo_session_serialize) takes
> the write lock and holds it indefinitely on success. This effectively
> freezes the LUO session subsystem during the transition to the new
> kernel. If serialization fails, the lock is released to allow recovery.
> 
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_session.c | 27 ++++++++++++++++++++++++---
>  1 file changed, 24 insertions(+), 3 deletions(-)
> 
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 59b37d17db6b..6099213275d8 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -385,9 +394,12 @@ int luo_session_create(const char *name, struct file **filep)
>  	struct luo_session *session;
>  	int err;
>  
> +	down_read(&luo_session_serialize_rwsem);
>  	session = luo_session_alloc(name);

We can alloc outside the lock, will make error handling simpler.

sashiko complains about mixing down_read() with scoped_guard(), for this
function it would make sense to use plain mutex_{lock,unlock} around
luo_session_getfile()

> -	if (IS_ERR(session))
> -		return PTR_ERR(session);
> +	if (IS_ERR(session)) {
> +		err = PTR_ERR(session);
> +		goto err_unlock;
> +	}
>  
>  	err = luo_session_insert(&luo_session_global.outgoing, session);
>  	if (err)
> @@ -398,12 +410,16 @@ int luo_session_create(const char *name, struct file **filep)
>  	if (err)
>  		goto err_remove;
>  
> +	up_read(&luo_session_serialize_rwsem);
> +
>  	return 0;
>  
>  err_remove:
>  	luo_session_remove(&luo_session_global.outgoing, session);
>  err_free:
>  	luo_session_free(session);
> +err_unlock:
> +	up_read(&luo_session_serialize_rwsem);
>  
>  	return err;
>  }

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Mon May 18 01:42:44 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 18 May 2026 11:42:44 +0300
Subject: [PATCH v4 0/5] liveupdate: serialization safety and race fixes
In-Reply-To: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
Message-ID: <agrRBAY6cvQgDmIb@kernel.org>

On Sun, May 17, 2026 at 07:26:45PM +0000, Pasha Tatashin wrote:
> This series addresses several issues related to the synchronization
> between the reboot process and LUO session management.
> 
> Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
> luo-reboot-sync/v4
> 
> Pasha Tatashin (5):
>   liveupdate: skip serialization for context-preserving kexec
>   liveupdate: fix TOCTOU race in luo_session_retrieve()
>   liveupdate: block session mutations during reboot
>   liveupdate: fix u-a-f in luo_file_unpreserve_files() and
>     luo_file_finish()
>   liveupdate: Remove unused ser field from struct luo_session

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>

for the series, with a small comment in patch 3/5

>  kernel/kexec_core.c              |  8 ++++---
>  kernel/liveupdate/luo_file.c     |  5 +++--
>  kernel/liveupdate/luo_internal.h |  2 --
>  kernel/liveupdate/luo_session.c  | 38 ++++++++++++++++++++++++--------
>  4 files changed, 37 insertions(+), 16 deletions(-)
> 
> 
> base-commit: b1378127003b61930ce30064328640503ad3ef6d
> -- 
> 2.53.0

-- 
Sincerely yours,
Mike.


From tarunsahu at google.com  Mon May 18 02:36:30 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:30 +0000
Subject: [RFC PATCH v1 0/8] liveupdate: kvm: Guest_memfd preservation
Message-ID: <cover.1779080766.git.tarunsahu@google.com>

Hello,

I am proposing this series as RFC, to initiate the discussion for
supporting the guest_memfd preservation. This will setup basic arhitecture
for VM preservation during liveupdate. This Cover letter has three
sections (please feel free to skip the on you already know):

A. Guest_memfd introduction:
To make the audience familiar with guest_memfd
B. Liveupdate introduction:
To make the audience familiar with liveupdate
C. Actual Implementation Design and questions.

**GUEST MEMFD INTRODUCTION**

Initially, guest_memfd was created to support guest private memory in
confidential computing VMs (CoCo VMs). It was designed so that whenever
a guest wants to grant the host access to private memory, a series of
calls occurs: from the guest to KVM, KVM to the host userspace, host
userspace back to KVM, and finally a new page fault maps the memory into
a separate shared address space. Conversely, if the guest transitions the
memory back to private, the subsequent fault is handled by guest_memfd.
(Dual Mapping Architecture). In such a VM, all guest memory is initially
shared. On the fly, the guest may request to change pages to private; the
metadata indicating which parts of memory are private is stored in an
xarray inside struct kvm (mem_attr_array). This array serves as the source
of truth for the fault mechanism, determining whether a mapping should be
created from host-userspace-mapped pages or directly from the guest_memfd
file. For private memory, Fault also calls architecture-specific function
to set up private hardware access (e.g., on SEV-SNP or TDX). This type of
guest_memfd is fully-private where shared mapping comes from userspace
mapped address space.

Subsequently, support was added to allow the entire guest memory to be
backed by guest_memfd. This led to the implementation of the MMAP and
INIT_SHARED flags for the guest_memfd inode. When KVM_CREATE_GUEST_MEMFD
is called with these flags, the guest_memfd becomes mmap-able by host
userspace. The INIT_SHARED flag is used to make the guest_memfd completely
shared between the host and the guest. Consequently, page faults from both
host userspace and the guest resolve to the same guest_memfd page cache.
However, under this configuration, marking a portion of this memory as
private is not possible. This type of guest_memfd is fully-shared.

If guest_memfd is created with INIT_SHARED without MMAP, the host
can never access the guest_memfd. But the memory is still considered
shared.

Hence, At this point, Only use-case of guest_memfd is either fully-shared
or fully-private.

There is ongoing work to make shared and private mapping in-place backed
by guest_memfd. [1] There is also ongoing work to back guest_memfd by
hugetlb pages. [2]

**LIVEUPDATE INTRODUCTION (LIVEUPDATE ORCHESTRATOR - LUO)**

Livepdate support was added in kernel to update the host kernel by
minimizing the downtime to minimal. This is generally achieved by
preserving the current state of the system and retrieve after boot to
resume from where we left it.

Any subsystem that wants to preserve themselves, register their handler
with liveupdate system. This handler includes calls to the following

*can_preserve (file)*:
This tells the luo system about the eligibility of the file. When
preserve ioctl is called, it first loop through all the file handlers
and call can_preserve, the one which return true, luo uses this file
handler fh->preserve call to preserve the file.

*preserve(file)*:
This actually preserves the file.

*unpreserve(file)*:
This unpreserve the file incase userspace want to go back.

*retrieve(file)*:
On new kernel boot, this function retrieves the file.

*finish(file)*:
When userspace decides that all the files in the liveupdate session has
been retrieved, it can trigger this to do final work of cleaning up.

LUO preserve its memory using KHO (kexec-handover). All these APIs will
be implemented using KHO calls.

**GUEST MEMFD PRESERVATION**

This patch sets up the basic infrastructure to preserve the guest_memfd.
Currently this supports only fully-shared, pre-faulted guest_memfd
(INIT_SHARED) backed by PAGE_SIZE pages.

It registers a new LUO file handler for guest_memfd file to serialize
and deserialize guest memory. This allows preserving guest memory backed
by guest_memfd across updates, ensuring that guest instances can be
resumed seamlessly without losing their memory contents.

The preservation call is straight forward. It walks through the page
cache, serialize the folios and preserve them.

On the retrieval path:
Currently, creating a guest_memfd requires an associated struct kvm
(derived from vm_file / vm_fd). Since there is no direct way to pass a
VM file descriptor via the LUO API, we considered two main approaches:

Approach (1)
Split the KVM_CREATE_GUEST_MEMFD ioctl into two separate ioctl: one
to create the guest_memfd without a VM file (without struct kvm)
descriptor, and another to attach a newly created VM file descriptor to
a retrieved guest_memfd.

Introducing a new ioctl is in itself a problem (UAPI). Currently, a
guest_memfd file belongs to a single VM. Decoupling creation and
attachment could allow a guest_memfd to be attached to any VM, or shared
among multiple VMs when passed at different offsets. Fully supporting
this feature would require extensive work, and it is unclear if there
are any non-LUO use cases that justify this complexity.
There is related work going on here [4], but not exactly same. It still
does not allow guest_memfd to be created without vm_fd. But there be
other ways to use it, I would like to discuss the idea.

Approach (2)
Leverage a companion patch [3] (Also added as part of this series
PATCH[1]) that allows one file to retrieve another file from the same LUO
session. This enables the guest_memfd retrieval path to obtain the
preserved KVM file, use it during guest_memfd file creation, and
subsequently populate its preserved memory.

Preserving the KVM file allows us to preserve additional VM-specific
metadata, which will be crucial in the future for cleanly resuming the
VM. Currently, it preserves only the VM type and kvm->mem_attr_array.

Though the ongoing in-place sharing series [1] transfers attributes to
the guest_memfd file, But preserving the kvm file opens the opportunity
to preserve other VM state in future like registers state, vCPU etc.

Having the extensive usecases for preserving the kvm file, I went
ahead with Approach (2). In future, if approach (1) become possible, it
can easily be integrated with approach (2).

Following the first approach (preserving vm_fd along with guest_memfd),

** VM FILE LIVEUPDATE ** PATCH[3] && [4]

*PATCH[3]* has refactored few functions to support kvm preservation.
During retrieval, vm_file needs to be recreated which will require kvm
api. This patch exports those APIs. There is a new addition to struct
kvm, vm_file. Which will be used by guest_memfd. I will discuss about
this later.

*PATCH[4]*
The preservation of the vm file is straightforward.

On the retrieval path:
KVM normally requires a unique identifier (fdname) upon creation,
which KVM typically assigns based on the newly created file descriptor
number. However, in the LUO retrieval path, the retrieve call restores
the underlying file structure and delegates actual file descriptor
allocation to LUO (check luo_session_retrieve_fd). Currently, I used an
atomically incremented sequence number as the fdname. I would like to
discuss whether userspace services rely on specific naming conventions
here. Or if we can change underlying the retrieve call
(luo_retrieve_file) to pass fd?

**GUEST_MEMFD FILE LIVEUPDATE** PATCH[5], [6] & [7]

*PATCH[5]*
During retrieval of guest_memfd file, for its creation, this patch has
exported APIs from guest_memfd.c to be used for guest_memfd_luo.c

*PATCH[6]*
This patch implements the API for gmem inode freeze, which freeze the
fallocate operation on this inode. Freeze check can be extended in
future to prevent new page faults as well, when liveupdate support
for non-pre-faulted guest_memfd will be implemented.

*PATCH[7]*
Preservation Path:
We have discussed about this before,
I would like to add to that and discuss here a major design decision:
"Preservation order in between VM File and guest_memfd file"

Preservation Ordering is required because guest_memfd needs to store
vm file token as one of its data, which it can use during retrieval to
get the vm file and use (file->private_data: struct kvm ) for its
creation using [3]. So KVM file must be preserved before guest_memfd
file, so that guest_memfd preserve call can find vm file token from the
same luo session.

Currently My preservation implementation does not require any strict
ordering, they can be preserved in any sequence from userspace. I
achieved this by implementing the freeze call for guest_memfd which
gets run at the end just before kexec. This call freeze the luo session
and no further changes can be done to the session. Inside guest_memfd
luo_freeze handler, I update the token for vm_file. Which enable us to
preserve the vm file and guest_memfd file in any order.

The drawback is, incase vm_file is not preserved, freeze will fail. And
in enforcing the preserving order fails the guest_memfd preservation
from the start. As with VM preservation will evolve in future, it will
keep getting complicated so avoiding the preservation order should be
the better choice to make the userspace simpler. I would be happy to
disucss on this further.

To get the token, we need the vm_file and there is no way to get the
vm_file from the struct kvm, as guest_memfd file only store the
struct kvm. I have introduced a new member in struct kvm, vm_file.
But with weak circular dependency as it is just to get the pointer
for the file. we don't want to keep the reference of the file as vm_file
takes for the kvm to keep itself (vm_file) alive. So whenever there is a
need to use of kvm->vm_file, we take the reference and drop it suddenly.

Retrieval Path:
During retrieval path, we just retrieve the data from kho and populate
into the newly created guest_memfd.
To create guest_memfd itself, it needs struct kvm, as we discussed
above, which will come from vm_file, hence retrieval order is needed
here. VM file needs to be retrieved first before guest_memfd.

To handle this situation, I had three approaches in mind with their own
pros and cons:

Approach (1):
Use [3], retrieve internally using liveupdate_get_file_incoming which
inherently retrieves the file incase it was not retrieved by the
userspace already. But this creates an scenerio, that userspace might
call luo_finish which will drop all the references of vm_file (and
userspace not holding any as it has not retrieved it yet explicitly).
And vm_file will get released. But this is a valid situation as when vm
is going to be put down. Userspace can close the vm_fd and have
guest_memfd yet opened and so other user of struct kvm like vCPUs etc.
Only thing, this makes retrieved guest_memfd unusable unless, there is
a mechanism to link to another VM (Nope).
This leaves us with following situation:
	(A): As it is a valid situation, We can leave it as it, No
	retrieval order enforcement.
	(B): We can implement can_finish to check if userspace has
	retrieved the vm_file, otherwise can stop luo_finish from
	succeeding, but I did not find a way to implement such check.
Approach (2):
Enforce the strict order, by implementing a new call which will first
check whether the vm_file is retrieved or not, if not, it will not
retrieve it internally and retrurn err to the caller which is
guest_memfd retrieve function in this case. So guest_memfd can report
the userspace about this error.

I have implemented Approach (1)(A), as it is a valid case, and does not
enforce any retrieve order on userspace, which relieves the burden from
the userspace when vm_file preservation will evolve. But userspace is
now expected to retrieve the vm_file before calling luo_finish or
guest_memfd will become unusable. As per LUO philosphy, It is userspace
error.

**KERNEL SELFTEST FOR POC** PATCH[8] & [9]

*PATCH[8]* refactor kvm selftest framework to expose some raw apis to
setup the VM.
*PATCH[9]* implements the basic test, where it spawn a VM with guest_memfd
or 16MB and fault it completely and write data to its 5MB portion. After
LUO preserve call, and kexec, On retrieve, a new VM is spawn with the
restored vm_file and restored guest_memfd and the data is verified.

I will update this test in the next version to use the liveupdate
selftests library [5].

Future Work:
1. To support preservation for non-prefaulted guest_memfd to save memory
in KHO. (Already working on this, will post another series soon)
2. Support private guest_memfd preservation.
3. Extend the support for guest_memfd with in-place conversion of
shared/private.

[1] https://lore.kernel.org/all/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4 at google.com/
[2] https://lore.kernel.org/all/cover.1747264138.git.ackerleytng at google.com/
[3] https://lore.kernel.org/all/20260427175633.1978233-2-skhawaja at google.com/
[4] https://lore.kernel.org/all/cover.1691446946.git.ackerleytng at google.com/
[5] https://lore.kernel.org/all/20260511201155.1488670-1-vipinsh at google.com/

Pasha Tatashin (1):
  liveupdate: luo_file: Add internal APIs for file preservation

Tarun Sahu (8):
  liveupdate: Add LIVEUPDATE_GUEST_MEMFD config option
  kvm: Prepare core VM structs and helpers for LUO support
  kvm: kvm_luo: Allow kvm preservation with LUO
  kvm: guest_memfd: Move internal definitions and helper to new header
  kvm: guest_memfd: Add support for freezing and unfreezing mappings
  kvm: guest_memfd_luo: add support for guest_memfd preservation
  selftests: kvm: Split ____vm_create() to expose init helpers
  selftests: kvm: Add guest_memfd_preservation_test

 MAINTAINERS                                   |  13 +
 include/linux/kho/abi/kvm.h                   | 121 +++++
 include/linux/kvm_host.h                      |  14 +
 include/linux/liveupdate.h                    |  21 +
 kernel/liveupdate/Kconfig                     |  15 +
 kernel/liveupdate/luo_file.c                  |  69 +++
 kernel/liveupdate/luo_internal.h              |  17 +
 tools/testing/selftests/kvm/Makefile.kvm      |   2 +
 .../kvm/guest_memfd_preservation_test.c       | 285 ++++++++++
 .../testing/selftests/kvm/include/kvm_util.h  |   2 +
 tools/testing/selftests/kvm/lib/kvm_util.c    |  26 +-
 virt/kvm/Makefile.kvm                         |   1 +
 virt/kvm/guest_memfd.c                        | 180 +++++--
 virt/kvm/guest_memfd.h                        |  44 ++
 virt/kvm/guest_memfd_luo.c                    | 495 ++++++++++++++++++
 virt/kvm/kvm_luo.c                            | 346 ++++++++++++
 virt/kvm/kvm_main.c                           |  79 ++-
 virt/kvm/kvm_mm.h                             |   3 +
 18 files changed, 1653 insertions(+), 80 deletions(-)
 create mode 100644 include/linux/kho/abi/kvm.h
 create mode 100644 tools/testing/selftests/kvm/guest_memfd_preservation_test.c
 create mode 100644 virt/kvm/guest_memfd.h
 create mode 100644 virt/kvm/guest_memfd_luo.c
 create mode 100644 virt/kvm/kvm_luo.c


base-commit: 5200f5f493f79f14bbdc349e402a40dfb32f23c8
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:31 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:31 +0000
Subject: [RFC PATCH v1 1/9] liveupdate: luo_file: Add internal APIs for file preservation
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <9ff4237649ace8b7eb78f3c3ec9ffaf5ae49ae6a.1779080766.git.tarunsahu@google.com>

From: Pasha Tatashin <pasha.tatashin at soleen.com>

The core liveupdate mechanism allows userspace to preserve file
descriptors. However, kernel subsystems often manage struct file
objects directly and need to participate in the preservation process
programmatically without relying solely on userspace interaction.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 include/linux/liveupdate.h       | 21 ++++++++++
 kernel/liveupdate/luo_file.c     | 69 ++++++++++++++++++++++++++++++++
 kernel/liveupdate/luo_internal.h | 17 ++++++++
 3 files changed, 107 insertions(+)

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 30c5a39ff9e9..de052438eaac 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -24,6 +24,7 @@ struct file;
 /**
  * struct liveupdate_file_op_args - Arguments for file operation callbacks.
  * @handler:          The file handler being called.
+ * @session:          The session this file belongs to.
  * @retrieve_status:  The retrieve status for the 'can_finish / finish'
  *                    operation. A value of 0 means the retrieve has not been
  *                    attempted, a positive value means the retrieve was
@@ -44,6 +45,7 @@ struct file;
  */
 struct liveupdate_file_op_args {
 	struct liveupdate_file_handler *handler;
+	struct liveupdate_session *session;
 	int retrieve_status;
 	struct file *file;
 	u64 serialized_data;
@@ -240,6 +242,13 @@ void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
 
 int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp);
 int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp);
+/* kernel can internally retrieve files */
+int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token,
+				 struct file **filep);
+
+/* Get a token for an outgoing file, or -ENOENT if file is not preserved */
+int liveupdate_get_token_outgoing(struct liveupdate_session *s,
+				  struct file *file, u64 *tokenp);
 
 #else /* CONFIG_LIVEUPDATE */
 
@@ -285,5 +294,17 @@ static inline int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb,
 	return -EOPNOTSUPP;
 }
 
+static inline int liveupdate_get_file_incoming(struct liveupdate_session *s,
+					       u64 token, struct file **filep)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int liveupdate_get_token_outgoing(struct liveupdate_session *s,
+						struct file *file, u64 *tokenp)
+{
+	return -EOPNOTSUPP;
+}
+
 #endif /* CONFIG_LIVEUPDATE */
 #endif /* _LINUX_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..0aa0b4e5339f 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -323,6 +323,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	mutex_init(&luo_file->mutex);
 
 	args.handler = fh;
+	args.session = luo_session_from_file_set(file_set);
 	args.file = file;
 	err = fh->ops->preserve(&args);
 	if (err)
@@ -380,6 +381,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 					   struct luo_file, list);
 
 		args.handler = luo_file->fh;
+		args.session = luo_session_from_file_set(file_set);
 		args.file = luo_file->file;
 		args.serialized_data = luo_file->serialized_data;
 		args.private_data = luo_file->private_data;
@@ -411,6 +413,7 @@ static int luo_file_freeze_one(struct luo_file_set *file_set,
 		struct liveupdate_file_op_args args = {0};
 
 		args.handler = luo_file->fh;
+		args.session = luo_session_from_file_set(file_set);
 		args.file = luo_file->file;
 		args.serialized_data = luo_file->serialized_data;
 		args.private_data = luo_file->private_data;
@@ -432,6 +435,7 @@ static void luo_file_unfreeze_one(struct luo_file_set *file_set,
 		struct liveupdate_file_op_args args = {0};
 
 		args.handler = luo_file->fh;
+		args.session = luo_session_from_file_set(file_set);
 		args.file = luo_file->file;
 		args.serialized_data = luo_file->serialized_data;
 		args.private_data = luo_file->private_data;
@@ -621,6 +625,7 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
 	}
 
 	args.handler = luo_file->fh;
+	args.session = luo_session_from_file_set(file_set);
 	args.serialized_data = luo_file->serialized_data;
 	err = luo_file->fh->ops->retrieve(&args);
 	if (err) {
@@ -654,6 +659,7 @@ static int luo_file_can_finish_one(struct luo_file_set *file_set,
 		struct liveupdate_file_op_args args = {0};
 
 		args.handler = luo_file->fh;
+		args.session = luo_session_from_file_set(file_set);
 		args.file = luo_file->file;
 		args.serialized_data = luo_file->serialized_data;
 		args.retrieve_status = luo_file->retrieve_status;
@@ -671,6 +677,7 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
 	guard(mutex)(&luo_file->mutex);
 
 	args.handler = luo_file->fh;
+	args.session = luo_session_from_file_set(file_set);
 	args.file = luo_file->file;
 	args.serialized_data = luo_file->serialized_data;
 	args.retrieve_status = luo_file->retrieve_status;
@@ -924,3 +931,65 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
 	luo_flb_unregister_all(fh);
 	list_del(&ACCESS_PRIVATE(fh, list));
 }
+EXPORT_SYMBOL_GPL(liveupdate_unregister_file_handler);
+
+/**
+ * liveupdate_get_token_outgoing - Get the token for a preserved file.
+ * @s:      The outgoing liveupdate session.
+ * @file:   The file object to search for.
+ * @tokenp: Output parameter for the found token.
+ *
+ * Searches the list of preserved files in an outgoing session for a matching
+ * file object. If found, the corresponding user-provided token is returned.
+ *
+ * This function is intended for in-kernel callers that need to correlate a
+ * file with its liveupdate token.
+ *
+ * Context: It must be called with session mutex acquired.
+ * Return: 0 on success, -ENOENT if the file is not preserved in this session.
+ */
+int liveupdate_get_token_outgoing(struct liveupdate_session *s,
+				  struct file *file, u64 *tokenp)
+{
+	struct luo_file_set *file_set = luo_file_set_from_session_locked(s);
+	struct luo_file *luo_file;
+	int err = -ENOENT;
+
+	list_for_each_entry(luo_file, &file_set->files_list, list) {
+		if (luo_file->file == file) {
+			if (tokenp)
+				*tokenp = luo_file->token;
+			err = 0;
+			break;
+		}
+	}
+
+	return err;
+}
+
+/**
+ * liveupdate_get_file_incoming - Retrieves a preserved file for in-kernel use.
+ * @s:      The incoming liveupdate session (restored from the previous kernel).
+ * @token:  The unique token identifying the file to retrieve.
+ * @filep:  On success, this will be populated with a pointer to the retrieved
+ *          'struct file'.
+ *
+ * Provides a kernel-internal API for other subsystems to retrieve their
+ * preserved files after a live update. This function is a simple wrapper
+ * around luo_retrieve_file(), allowing callers to find a file by its token.
+ *
+ * The caller receives a new reference to the file and must call fput() when it
+ * is no longer needed. The file's lifetime is managed by LUO and any userspace
+ * file descriptors. If the caller needs to hold a reference to the file beyond
+ * the immediate scope, it must call get_file() itself.
+ *
+ * Context: It must be called with session mutex acquired of a restored session.
+ * Return: 0 on success. Returns -ENOENT if no file with the matching token is
+ *         found, or any other negative errno on failure.
+ */
+int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token,
+				 struct file **filep)
+{
+	return luo_retrieve_file(luo_file_set_from_session_locked(s),
+				 token, filep);
+}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..08b198802e7f 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,6 +79,23 @@ struct luo_session {
 
 extern struct rw_semaphore luo_register_rwlock;
 
+static inline struct liveupdate_session *luo_session_from_file_set(struct luo_file_set *file_set)
+{
+	struct luo_session *session;
+
+	session = container_of(file_set, struct luo_session, file_set);
+
+	return (struct liveupdate_session *)session;
+}
+
+static inline struct luo_file_set *luo_file_set_from_session_locked(struct liveupdate_session *s)
+{
+	struct luo_session *session = (struct luo_session *)s;
+
+	lockdep_assert_held(&session->mutex);
+	return &session->file_set;
+}
+
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
 int __init luo_session_setup_outgoing(void *fdt);
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:32 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:32 +0000
Subject: [RFC PATCH v1 2/9] liveupdate: Add LIVEUPDATE_GUEST_MEMFD config option
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <63776195da42f3307cfa0ad9ee7aa5504e4c2cb1.1779080766.git.tarunsahu@google.com>

Introduce the LIVEUPDATE_GUEST_MEMFD Kconfig option. This option
enables live update support for KVM guest_memfd files, enabling
guest_memfd-backed memory preservation across kernel upgrades.

Currently this support only guest_memfd files that are full-shared
and pre-faulted.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 kernel/liveupdate/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index 1a8513f16ef7..0bbc4037192e 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -88,4 +88,19 @@ config LIVEUPDATE_MEMFD
 
 	  If unsure, say N.
 
+config LIVEUPDATE_GUEST_MEMFD
+	bool "Live update support for guest_memfd"
+	depends on LIVEUPDATE
+	depends on KVM_GUEST_MEMFD
+	default LIVEUPDATE
+	help
+	  Enable live update support for KVM guest_memfd files. This allows
+	  preserving VM Memory backed by guest_memfd file across kernel live
+	  updates.
+
+	  This can only be used for the guest_memfd that are fully-shared
+	  and pre-faulted.
+
+	  If unsure, say N.
+
 endmenu
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:34 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:34 +0000
Subject: [RFC PATCH v1 4/9] kvm: kvm_luo: Allow kvm preservation with LUO
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <b489ff0ab6edef9723778bd0018e209fe410e05d.1779080766.git.tarunsahu@google.com>

Introduce KVM VM preservation support for Live Update Orchestrator.

Register an LUO file handler for KVM files to serialize and
deserialize necessary VM state across live updates. Currently, this
preserves the VM type and generic memory attributes. This
implementation provides the necessary infrastructure and dependencies
for the upcoming guest_memfd preservation support. And it can be
extended to preserve more vm state in future.

To preserve the kvm file it is necessary that the attributes that we
are preserving must not change while or after preservation. The memory
attribute change request is triggered by Guest to KVM and exit to VMM.
VMM is aware that liveupdate is in progress and is expected to cancel
this request Or pause the VM. This ensures that no change in memory
attributes from guest are introduced while/after preservation of kvm.

Retrieve is simply creating the kvm and populate the retrieved data.
Only catch here is there is no way to know which fd is going to be
assigned to this kvm file hence I am using atomically incremented id
for the fdname.

This change also updates the MAINTAINERS list for kvm_luo.c.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>

---
My only worry is if userspace strictly depends on the fdname, that it
needs to be consistent with vm_fd. Discussed more details in the
cover letter. Would really appreciates the alternatives/other approaches.
---
 MAINTAINERS                 |  11 ++
 include/linux/kho/abi/kvm.h |  54 ++++++
 virt/kvm/Makefile.kvm       |   1 +
 virt/kvm/kvm_luo.c          | 346 ++++++++++++++++++++++++++++++++++++
 4 files changed, 412 insertions(+)
 create mode 100644 include/linux/kho/abi/kvm.h
 create mode 100644 virt/kvm/kvm_luo.c

diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d79275c6..2c26eb17bc0a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14404,6 +14404,17 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/leds/backlight/kinetic,ktz8866.yaml
 F:	drivers/video/backlight/ktz8866.c
 
+KVM LIVE UPDATE
+M:	Pasha Tatashin <pasha.tatashin at soleen.com>
+M:	Mike Rapoport <rppt at kernel.org>
+M:	Pratyush Yadav <pratyush at kernel.org>
+R:	Tarun Sahu <tarunsahu at google.com>
+L:	kexec at lists.infradead.org
+L:	kvm at vger.kernel.org
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	virt/kvm/kvm_luo.c
+
 KVM PARAVIRT (KVM/paravirt)
 M:	Paolo Bonzini <pbonzini at redhat.com>
 R:	Vitaly Kuznetsov <vkuznets at redhat.com>
diff --git a/include/linux/kho/abi/kvm.h b/include/linux/kho/abi/kvm.h
new file mode 100644
index 000000000000..31bd39588bdd
--- /dev/null
+++ b/include/linux/kho/abi/kvm.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Tarun Sahu <tarunsahu at google.com>
+ *
+ * KVM Preservation ABI for Live Update Orchestrator (LUO)
+ */
+#ifndef _LINUX_KHO_ABI_KVM_H
+#define _LINUX_KHO_ABI_KVM_H
+
+#include <linux/types.h>
+#include <linux/kho/abi/kexec_handover.h>
+
+/**
+ * DOC: KVM Live Update ABI
+ *
+ * KVM uses the ABI defined below for preserving its state
+ * across a kexec reboot using the LUO.
+ *
+ * The state is serialized into a packed structure `struct kvm_luo_ser`
+ * which is handed over to the next kernel via the KHO mechanism.
+ *
+ * This interface is a contract. Any modification to the structure layout
+ * constitutes a breaking change. Such changes require incrementing the
+ * version number in the KVM_LUO_FH_COMPATIBLE compatibility string.
+ */
+
+/**
+ * struct kvm_luo_mem_attr - GFN memory attribute serialization.
+ * @gfn:        Guest Frame Number.
+ * @attributes: Memory attributes associated with this GFN.
+ */
+struct kvm_luo_mem_attr {
+	u64 gfn;
+	u64 attributes;
+} __packed;
+
+/**
+ * struct kvm_luo_ser - Main serialization structure for a KVM VM.
+ * @type:         The type of VM.
+ * @nr_mem_attrs: The number of memory attributes in the array.
+ * @mem_attrs:    KHO vmalloc descriptor pointing to the array of
+ *                struct kvm_luo_mem_attr.
+ */
+struct kvm_luo_ser {
+	u64 type;
+	u64 nr_mem_attrs;
+	struct kho_vmalloc mem_attrs;
+} __packed;
+
+/* The compatibility string for KVM VM file handler */
+#define KVM_LUO_FH_COMPATIBLE	"kvm_vm_luo_v1"
+
+#endif /* _LINUX_KHO_ABI_KVM_H */
diff --git a/virt/kvm/Makefile.kvm b/virt/kvm/Makefile.kvm
index d047d4cf58c9..c1a962159264 100644
--- a/virt/kvm/Makefile.kvm
+++ b/virt/kvm/Makefile.kvm
@@ -13,3 +13,4 @@ kvm-$(CONFIG_HAVE_KVM_IRQ_ROUTING) += $(KVM)/irqchip.o
 kvm-$(CONFIG_HAVE_KVM_DIRTY_RING) += $(KVM)/dirty_ring.o
 kvm-$(CONFIG_HAVE_KVM_PFNCACHE) += $(KVM)/pfncache.o
 kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o
+kvm-$(CONFIG_LIVEUPDATE_GUEST_MEMFD) += $(KVM)/kvm_luo.o
diff --git a/virt/kvm/kvm_luo.c b/virt/kvm/kvm_luo.c
new file mode 100644
index 000000000000..1cf3941c16b7
--- /dev/null
+++ b/virt/kvm/kvm_luo.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Tarun Sahu <tarunsahu at google.com>
+ *
+ * KVM VM Preservation for Live Update Orchestrator (LUO)
+ */
+
+/**
+ * DOC: KVM VM Preservation via LUO
+ *
+ * Overview
+ * ========
+ *
+ * KVM virtual machines (VMs) can be preserved over a kexec reboot using the
+ * Live Update Orchestrator (LUO) file preservation. This allows userspace
+ * to preserve KVM VM state across kexec reboots.
+ *
+ * The preservation is not intended to be fully transparent. Only specific
+ * VM configuration and state are preserved, while other aspects of the VM
+ * must be re-established or re-configured by userspace after retrieval.
+ *
+ * Preserved Properties
+ * ====================
+ *
+ * The following properties of the KVM VM are preserved across kexec:
+ *
+ * VM Type
+ *   The VM type (e.g., on x86 architecture, the vm_type parameter) is
+ *   preserved.
+ *
+ * Memory Attributes
+ *   All entries in the memory attributes array are preserved.
+ *
+ * Non-Preserved Properties
+ * ========================
+ *
+ * The preservation does not cover:
+ *
+ * - vCPUs and vCPU states
+ * - Memspots / Memory slot layout (memslots)
+ * - Interrupt controllers and IRQ routings
+ * - Coalesced MMIO zones
+ * - Device bindings (VFIO/Eventfds)
+ * - Active paging or guest registers state
+ * - etc
+ */
+#include <linux/liveupdate.h>
+#include <linux/kvm_host.h>
+#include <linux/pagemap.h>
+#include <linux/file.h>
+#include <linux/err.h>
+#include <linux/anon_inodes.h>
+#include <linux/magic.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/kvm.h>
+#include "kvm_mm.h"
+
+static bool kvm_luo_can_preserve(struct liveupdate_file_handler *handler,
+				 struct file *file)
+{
+	return file_is_kvm(file);
+}
+
+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+static int kvm_luo_preserve_mem_attrs(struct kvm *kvm, struct kvm_luo_ser *ser,
+				struct kvm_luo_mem_attr **mem_attrs_ptr)
+{
+	struct kvm_luo_mem_attr *mem_attrs = NULL;
+	unsigned long index;
+	void *attributes;
+	u64 count = 0;
+	int err;
+
+	mutex_lock(&kvm->slots_lock);
+
+	xa_for_each(&kvm->mem_attr_array, index, attributes) {
+		count++;
+	}
+
+	if (count == 0) {
+		mutex_unlock(&kvm->slots_lock);
+		ser->nr_mem_attrs = 0;
+		*mem_attrs_ptr = NULL;
+		return 0;
+	}
+
+	mem_attrs = vcalloc(count, sizeof(*mem_attrs));
+	if (!mem_attrs) {
+		mutex_unlock(&kvm->slots_lock);
+		return -ENOMEM;
+	}
+
+	count = 0;
+	xa_for_each(&kvm->mem_attr_array, index, attributes) {
+		mem_attrs[count].gfn = index;
+		mem_attrs[count].attributes = xa_to_value(attributes);
+		count++;
+	}
+
+	mutex_unlock(&kvm->slots_lock);
+
+	ser->nr_mem_attrs = count;
+	err = kho_preserve_vmalloc(mem_attrs, &ser->mem_attrs);
+	if (err) {
+		vfree(mem_attrs);
+		return err;
+	}
+
+	*mem_attrs_ptr = mem_attrs;
+	return 0;
+}
+
+static int kvm_luo_retrieve_mem_attrs(struct kvm *kvm, struct kvm_luo_ser *ser,
+				bool *mem_attrs_restored_ptr)
+{
+	struct kvm_luo_mem_attr *mem_attrs;
+	u64 i;
+	int err = 0;
+
+	if (!ser->nr_mem_attrs)
+		return 0;
+
+	mem_attrs = kho_restore_vmalloc(&ser->mem_attrs);
+	*mem_attrs_restored_ptr = true;
+	if (!mem_attrs)
+		return -EINVAL;
+
+	for (i = 0; i < ser->nr_mem_attrs; i++) {
+		err = xa_err(xa_store(&kvm->mem_attr_array, mem_attrs[i].gfn,
+				      xa_mk_value(mem_attrs[i].attributes),
+				      GFP_KERNEL_ACCOUNT));
+		if (err)
+			break;
+	}
+	vfree(mem_attrs);
+	return err;
+}
+
+static void kvm_luo_retrieve_mem_attrs_cleanup(struct kvm_luo_ser *ser,
+					bool mem_attrs_restored)
+{
+	struct kvm_luo_mem_attr *mem_attrs = NULL;
+
+	if (ser->nr_mem_attrs && !mem_attrs_restored)
+		mem_attrs = kho_restore_vmalloc(&ser->mem_attrs);
+	vfree(mem_attrs);
+}
+
+static void kvm_luo_unpreserve_mem_attrs(struct kvm_luo_ser *ser)
+{
+	if (ser && ser->nr_mem_attrs)
+		kho_unpreserve_vmalloc(&ser->mem_attrs);
+}
+
+static void kvm_luo_finish_mem_attrs(struct kvm_luo_ser *ser)
+{
+	struct kvm_luo_mem_attr *mem_attrs;
+
+	if (ser && ser->nr_mem_attrs) {
+		mem_attrs = kho_restore_vmalloc(&ser->mem_attrs);
+		if (mem_attrs)
+			vfree(mem_attrs);
+	}
+}
+#else
+static inline int kvm_luo_preserve_mem_attrs(struct kvm *kvm,
+					struct kvm_luo_ser *ser,
+					struct kvm_luo_mem_attr **mem_attrs_ptr)
+{
+	ser->nr_mem_attrs = 0;
+	*mem_attrs_ptr = NULL;
+	return 0;
+}
+
+static inline int kvm_luo_retrieve_mem_attrs(struct kvm *kvm,
+					struct kvm_luo_ser *ser,
+					bool *mem_attrs_restored_ptr)
+{
+	if (ser->nr_mem_attrs)
+		return -EOPNOTSUPP;
+	return 0;
+}
+
+static inline void kvm_luo_retrieve_mem_attrs_cleanup(struct kvm_luo_ser *ser,
+						bool mem_attrs_restored)
+{
+}
+
+static inline void kvm_luo_unpreserve_mem_attrs(struct kvm_luo_ser *ser)
+{
+}
+
+static inline void kvm_luo_finish_mem_attrs(struct kvm_luo_ser *ser)
+{
+}
+#endif
+
+static int kvm_luo_preserve(struct liveupdate_file_op_args *args)
+{
+	struct kvm *kvm = args->file->private_data;
+	struct kvm_luo_mem_attr *mem_attrs = NULL;
+	struct kvm_luo_ser *ser;
+	int err = 0;
+
+	if (kvm->vm_dead || kvm->vm_bugged)
+		return -EINVAL;
+
+	ser = kho_alloc_preserve(sizeof(*ser));
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	err = kvm_luo_preserve_mem_attrs(kvm, ser, &mem_attrs);
+	if (err)
+		goto err_free_ser;
+
+#ifdef CONFIG_X86
+	ser->type = kvm->arch.vm_type;
+#else
+	ser->type = 0;
+#endif
+
+	args->serialized_data = virt_to_phys(ser);
+	args->private_data = mem_attrs;
+
+	return 0;
+
+err_free_ser:
+	kho_unpreserve_free(ser);
+	return err;
+}
+
+static atomic_t restored_vm_id = ATOMIC_INIT(0);
+
+static int kvm_luo_retrieve(struct liveupdate_file_op_args *args)
+{
+	struct kvm_luo_mem_attr *mem_attrs = NULL;
+	bool mem_attrs_restored = false;
+	char fdname[ITOA_MAX_LEN + 1];
+	struct kvm_luo_ser *ser;
+	struct file *file;
+	struct kvm *kvm;
+	int err = 0;
+
+	if (!args->serialized_data)
+		return -EINVAL;
+
+	ser = phys_to_virt(args->serialized_data);
+
+	snprintf(fdname, sizeof(fdname), "%d",
+		 atomic_inc_return(&restored_vm_id));
+
+	file = kvm_create_vm_file(ser->type, fdname);
+	if (IS_ERR(file)) {
+		err = PTR_ERR(file);
+		goto err_free_ser;
+	}
+
+	kvm = file->private_data;
+
+	err = kvm_luo_retrieve_mem_attrs(kvm, ser, &mem_attrs_restored);
+	if (err)
+		goto err_destroy_file;
+
+	args->file = file;
+	kho_restore_free(ser);
+
+	kvm_uevent_notify_vm_create(kvm);
+	return 0;
+
+err_destroy_file:
+	fput(file);
+err_free_ser:
+	kvm_luo_retrieve_mem_attrs_cleanup(ser, mem_attrs_restored);
+	kho_restore_free(ser);
+	return err;
+}
+
+static void kvm_luo_unpreserve(struct liveupdate_file_op_args *args)
+{
+	struct kvm_luo_mem_attr *mem_attrs = args->private_data;
+	struct kvm_luo_ser *ser;
+
+	/*
+	 * in case preservation failed, args->serialized_data will
+	 * be NULL and kvm_luo_preserve takes care of cleaning up.
+	 * If preserve succeeds, this condition fails and unpreserve
+	 * function takes care of cleaning up.
+	 */
+	if (WARN_ON_ONCE(!args->serialized_data))
+		return;
+
+	ser = phys_to_virt(args->serialized_data);
+
+	kvm_luo_unpreserve_mem_attrs(ser);
+	kho_unpreserve_free(ser);
+	vfree(mem_attrs);
+}
+
+static void kvm_luo_finish(struct liveupdate_file_op_args *args)
+{
+	struct kvm_luo_ser *ser;
+
+	/*
+	 * If retrieve_status is true or set to error, nothing to do here.
+	 * Already cleaned up in kvm_luo_retrieve().
+	 */
+	if (args->retrieve_status)
+		return;
+
+	if (!args->serialized_data)
+		return;
+
+	ser = phys_to_virt(args->serialized_data);
+	kvm_luo_finish_mem_attrs(ser);
+	kho_restore_free(ser);
+}
+
+static const struct liveupdate_file_ops kvm_luo_file_ops = {
+	.can_preserve = kvm_luo_can_preserve,
+	.preserve = kvm_luo_preserve,
+	.retrieve = kvm_luo_retrieve,
+	.unpreserve = kvm_luo_unpreserve,
+	.finish = kvm_luo_finish,
+	.owner = THIS_MODULE,
+};
+
+static struct liveupdate_file_handler kvm_luo_handler = {
+	.ops = &kvm_luo_file_ops,
+	.compatible = KVM_LUO_FH_COMPATIBLE,
+};
+
+static int __init kvm_luo_init(void)
+{
+	int err = liveupdate_register_file_handler(&kvm_luo_handler);
+
+	if (err && err != -EOPNOTSUPP) {
+		pr_err("Could not register kvm_vm_luo handler: %pe\n", ERR_PTR(err));
+		return err;
+	}
+
+	return 0;
+}
+late_initcall(kvm_luo_init);
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:35 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:35 +0000
Subject: [RFC PATCH v1 5/9] kvm: guest_memfd: Move internal definitions and
 helper to new header
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <7ffc1fb8962fd27428ac3e2f3d8950492597e03b.1779080766.git.tarunsahu@google.com>

To support guest_memfd memory preservation with LUO, guest_memfd luo
code needs to access guest_memfd internals and reconstruct guest_memfd
file instances from a preserved state.

Extract gmem_file, gmem_inode, and the GMEM_I() helper from guest_memfd.c
into a new internal header virt/kvm/guest_memfd.h.

Additionally, split __kvm_gmem_create() to expose a non-static
__kvm_gmem_create_file() helper. This helper returns a struct file
instead of a file descriptor, enabling file creation and initialization
without installing it into a file descriptor table.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 virt/kvm/guest_memfd.c | 68 +++++++++++++++++-------------------------
 virt/kvm/guest_memfd.h | 39 ++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 40 deletions(-)
 create mode 100644 virt/kvm/guest_memfd.h

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 69c9d6d546b2..6740ae2bf948 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -7,38 +7,12 @@
 #include <linux/mempolicy.h>
 #include <linux/pseudo_fs.h>
 #include <linux/pagemap.h>
+#include "guest_memfd.h"
 
 #include "kvm_mm.h"
 
 static struct vfsmount *kvm_gmem_mnt;
 
-/*
- * A guest_memfd instance can be associated multiple VMs, each with its own
- * "view" of the underlying physical memory.
- *
- * The gmem's inode is effectively the raw underlying physical storage, and is
- * used to track properties of the physical memory, while each gmem file is
- * effectively a single VM's view of that storage, and is used to track assets
- * specific to its associated VM, e.g. memslots=>gmem bindings.
- */
-struct gmem_file {
-	struct kvm *kvm;
-	struct xarray bindings;
-	struct list_head entry;
-};
-
-struct gmem_inode {
-	struct shared_policy policy;
-	struct inode vfs_inode;
-	struct list_head gmem_file_list;
-
-	u64 flags;
-};
-
-static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
-{
-	return container_of(inode, struct gmem_inode, vfs_inode);
-}
 
 #define kvm_gmem_for_each_file(f, inode) \
 	list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
@@ -556,23 +530,17 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
 	return true;
 }
 
-static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
+struct file *__kvm_gmem_create_file(struct kvm *kvm, loff_t size, u64 flags)
 {
 	static const char *name = "[kvm-gmem]";
 	struct gmem_file *f;
 	struct inode *inode;
 	struct file *file;
-	int fd, err;
-
-	fd = get_unused_fd_flags(0);
-	if (fd < 0)
-		return fd;
+	int err;
 
 	f = kzalloc_obj(*f);
-	if (!f) {
-		err = -ENOMEM;
-		goto err_fd;
-	}
+	if (!f)
+		return ERR_PTR(-ENOMEM);
 
 	/* __fput() will take care of fops_put(). */
 	if (!fops_get(&kvm_gmem_fops)) {
@@ -611,8 +579,7 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 	xa_init(&f->bindings);
 	list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
 
-	fd_install(fd, file);
-	return fd;
+	return file;
 
 err_inode:
 	iput(inode);
@@ -620,7 +587,28 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
 	fops_put(&kvm_gmem_fops);
 err_gmem:
 	kfree(f);
-err_fd:
+	return ERR_PTR(err);
+}
+
+static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
+{
+	struct file *file;
+	int fd, err;
+
+	fd = get_unused_fd_flags(0);
+	if (fd < 0)
+		return fd;
+
+	file = __kvm_gmem_create_file(kvm, size, flags);
+	if (IS_ERR(file)) {
+		err = PTR_ERR(file);
+		goto err_put_fd;
+	}
+
+	fd_install(fd, file);
+	return fd;
+
+err_put_fd:
 	put_unused_fd(fd);
 	return err;
 }
diff --git a/virt/kvm/guest_memfd.h b/virt/kvm/guest_memfd.h
new file mode 100644
index 000000000000..c528b046dd69
--- /dev/null
+++ b/virt/kvm/guest_memfd.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __KVM_GUEST_MEMFD_H__
+#define __KVM_GUEST_MEMFD_H__ 1
+
+#include <linux/kvm_host.h>
+#include <linux/fs.h>
+#include <linux/mempolicy.h>
+
+/*
+ * A guest_memfd instance can be associated multiple VMs, each with its own
+ * "view" of the underlying physical memory.
+ *
+ * The gmem's inode is effectively the raw underlying physical storage, and is
+ * used to track properties of the physical memory, while each gmem file is
+ * effectively a single VM's view of that storage, and is used to track assets
+ * specific to its associated VM, e.g. memslots=>gmem bindings.
+ */
+struct gmem_file {
+	struct kvm *kvm;
+	struct xarray bindings;
+	struct list_head entry;
+};
+
+struct gmem_inode {
+	struct shared_policy policy;
+	struct inode vfs_inode;
+	struct list_head gmem_file_list;
+
+	u64 flags;
+};
+
+static inline struct gmem_inode *GMEM_I(struct inode *inode)
+{
+	return container_of(inode, struct gmem_inode, vfs_inode);
+}
+
+struct file *__kvm_gmem_create_file(struct kvm *kvm, loff_t size, u64 flags);
+
+#endif /* __KVM_GUEST_MEMFD_H__ */
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:33 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:33 +0000
Subject: [RFC PATCH v1 3/9] kvm: Prepare core VM structs and helpers for LUO support
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <24e13c0679157302f0d8385c341169b0ddd4ea40.1779080766.git.tarunsahu@google.com>

Introduce core infrastructure to support VM preservation with LUO.

First two changes are just refactoring, no functional change, third
change introduces a new member in struct kvm.
- Move ITOA_MAX_LEN to kvm_mm.h for reuse by upcoming kvm_luo code.
- Add a public kvm_create_vm_file() helper wrapping kvm_create_vm()
  and anon_inode_getfile() to provide a unified VM file creation API.
- Track a weak reference to the backing file in struct kvm under
  CONFIG_LIVEUPDATE_GUEST_MEMFD to enable reverse file resolution
  without circular lifetime dependencies.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 include/linux/kvm_host.h | 14 +++++++
 virt/kvm/kvm_main.c      | 79 +++++++++++++++++++++++++++++-----------
 virt/kvm/kvm_mm.h        |  3 ++
 3 files changed, 75 insertions(+), 21 deletions(-)

diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 4c14aee1fb06..9111a28637af 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -874,6 +874,18 @@ struct kvm {
 #ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
 	/* Protected by slots_lock (for writes) and RCU (for reads) */
 	struct xarray mem_attr_array;
+#endif
+#ifdef CONFIG_LIVEUPDATE_GUEST_MEMFD
+	/*
+	 * Weak reference to the VFS file backing this KVM instance. Stored
+	 * without incrementing the file refcount to prevent a circular lifetime
+	 * dependency (since file->private_data already pins this struct kvm).
+	 * Used exclusively to resolve the file pointer back from struct kvm.
+	 *
+	 * Written/cleared via rcu_assign_pointer() and read locklessly under
+	 * RCU (e.g. via get_file_active() to prevent ABA races).
+	 */
+	struct file *vm_file;
 #endif
 	char stats_id[KVM_STATS_NAME_SIZE];
 };
@@ -1074,7 +1086,9 @@ void kvm_get_kvm(struct kvm *kvm);
 bool kvm_get_kvm_safe(struct kvm *kvm);
 void kvm_put_kvm(struct kvm *kvm);
 bool file_is_kvm(struct file *file);
+struct file *kvm_create_vm_file(unsigned long type, const char *fdname);
 void kvm_put_kvm_no_destroy(struct kvm *kvm);
+void kvm_uevent_notify_vm_create(struct kvm *kvm);
 
 static inline struct kvm_memslots *__kvm_memslots(struct kvm *kvm, int as_id)
 {
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 89489996fbc1..65f0c5fb353e 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -67,9 +67,6 @@
 #include <linux/kvm_dirty_ring.h>
 
 
-/* Worst case buffer size needed for holding an integer. */
-#define ITOA_MAX_LEN 12
-
 MODULE_AUTHOR("Qumranet");
 MODULE_DESCRIPTION("Kernel-based Virtual Machine (KVM) Hypervisor");
 MODULE_LICENSE("GPL");
@@ -1349,6 +1346,19 @@ static int kvm_vm_release(struct inode *inode, struct file *filp)
 {
 	struct kvm *kvm = filp->private_data;
 
+#ifdef CONFIG_LIVEUPDATE_GUEST_MEMFD
+	/*
+	 * Clear the weak reference of the vm file.
+	 * In case vm file is closed by userspace, but kvm still has
+	 * other users like vCPUs, clearing this pointer ensures
+	 * that we don't have a dangling pointer to a closed file.
+	 *
+	 * Cleared via rcu_assign_pointer() to ensure proper memory visibility
+	 * for concurrent lockless readers under RCU.
+	 */
+	rcu_assign_pointer(kvm->vm_file, NULL);
+#endif
+
 	kvm_irqfd_release(kvm);
 
 	kvm_put_kvm(kvm);
@@ -5476,11 +5486,47 @@ bool file_is_kvm(struct file *file)
 }
 EXPORT_SYMBOL_FOR_KVM_INTERNAL(file_is_kvm);
 
+struct file *kvm_create_vm_file(unsigned long type, const char *fdname)
+{
+	struct kvm *kvm = kvm_create_vm(type, fdname);
+	struct file *file;
+
+	if (IS_ERR(kvm))
+		return ERR_CAST(kvm);
+
+	file = anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);
+	if (IS_ERR(file)) {
+		kvm_put_kvm(kvm);
+		return file;
+	}
+
+#ifdef CONFIG_LIVEUPDATE_GUEST_MEMFD
+	/*
+	 * Weak reference to the file (without get_file()) to prevent a circular
+	 * dependency. Safe because the file's release path clears this pointer
+	 * and drops its reference to the VM.
+	 *
+	 * Written via rcu_assign_pointer() because the pointer can be read
+	 * locklessly under RCU (e.g., in kvm_gmem_luo_preserve() via
+	 * get_file_active() to prevent lockless ABA races).
+	 */
+	rcu_assign_pointer(kvm->vm_file, file);
+#endif
+
+	/*
+	 * Don't call kvm_put_kvm anymore at this point; file->f_op is
+	 * already set, with ->release() being kvm_vm_release().  In error
+	 * cases it will be called by the final fput(file) and will take
+	 * care of doing kvm_put_kvm(kvm).
+	 */
+
+	return file;
+}
+
 static int kvm_dev_ioctl_create_vm(unsigned long type)
 {
 	char fdname[ITOA_MAX_LEN + 1];
 	int r, fd;
-	struct kvm *kvm;
 	struct file *file;
 
 	fd = get_unused_fd_flags(O_CLOEXEC);
@@ -5489,31 +5535,17 @@ static int kvm_dev_ioctl_create_vm(unsigned long type)
 
 	snprintf(fdname, sizeof(fdname), "%d", fd);
 
-	kvm = kvm_create_vm(type, fdname);
-	if (IS_ERR(kvm)) {
-		r = PTR_ERR(kvm);
-		goto put_fd;
-	}
-
-	file = anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);
+	file = kvm_create_vm_file(type, fdname);
 	if (IS_ERR(file)) {
 		r = PTR_ERR(file);
-		goto put_kvm;
+		goto put_fd;
 	}
 
-	/*
-	 * Don't call kvm_put_kvm anymore at this point; file->f_op is
-	 * already set, with ->release() being kvm_vm_release().  In error
-	 * cases it will be called by the final fput(file) and will take
-	 * care of doing kvm_put_kvm(kvm).
-	 */
-	kvm_uevent_notify_change(KVM_EVENT_CREATE_VM, kvm);
+	kvm_uevent_notify_change(KVM_EVENT_CREATE_VM, file->private_data);
 
 	fd_install(fd, file);
 	return fd;
 
-put_kvm:
-	kvm_put_kvm(kvm);
 put_fd:
 	put_unused_fd(fd);
 	return r;
@@ -6341,6 +6373,11 @@ static void kvm_uevent_notify_change(unsigned int type, struct kvm *kvm)
 	kfree(env);
 }
 
+void kvm_uevent_notify_vm_create(struct kvm *kvm)
+{
+	kvm_uevent_notify_change(KVM_EVENT_CREATE_VM, kvm);
+}
+
 static void kvm_init_debug(void)
 {
 	const struct file_operations *fops;
diff --git a/virt/kvm/kvm_mm.h b/virt/kvm/kvm_mm.h
index 9fcc5d5b7f8d..7aa1d65c3d46 100644
--- a/virt/kvm/kvm_mm.h
+++ b/virt/kvm/kvm_mm.h
@@ -3,6 +3,9 @@
 #ifndef __KVM_MM_H__
 #define __KVM_MM_H__ 1
 
+/* Worst case buffer size needed for holding an integer as a string. */
+#define ITOA_MAX_LEN 12
+
 /*
  * Architectures can choose whether to use an rwlock or spinlock
  * for the mmu_lock.  These macros, for use in common code
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:36 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:36 +0000
Subject: [RFC PATCH v1 6/9] kvm: guest_memfd: Add support for freezing and
 unfreezing mappings
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <823a5f2af1d794c61560d0bad96863c4c851f29b.1779080766.git.tarunsahu@google.com>

This patch introduces the freeze on gmem_inode which prevents
the fallocate call. This will avoid gmem file modification when it is
being preserved

Used srcu lock to synchronise the freeze call, where write blocks
until all the reads are free. And reads are re-entrant.

This can be extended to freeze the fault path as well. But currently
the fault failure due to sudden freeze might be fatal to the running
guest.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 virt/kvm/guest_memfd.c | 112 +++++++++++++++++++++++++++++++++++++----
 virt/kvm/guest_memfd.h |   5 ++
 2 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 6740ae2bf948..91e42f717286 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -7,11 +7,13 @@
 #include <linux/mempolicy.h>
 #include <linux/pseudo_fs.h>
 #include <linux/pagemap.h>
+#include <linux/srcu.h>
 #include "guest_memfd.h"
 
 #include "kvm_mm.h"
 
 static struct vfsmount *kvm_gmem_mnt;
+static struct srcu_struct kvm_gmem_freeze_srcu;
 
 
 #define kvm_gmem_for_each_file(f, inode) \
@@ -96,6 +98,7 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 	/* TODO: Support huge pages. */
 	struct mempolicy *policy;
 	struct folio *folio;
+	int idx;
 
 	/*
 	 * Fast-path: See if folio is already present in mapping to avoid
@@ -273,16 +276,30 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
 static long kvm_gmem_fallocate(struct file *file, int mode, loff_t offset,
 			       loff_t len)
 {
+	struct inode *inode = file_inode(file);
 	int ret;
+	int idx;
 
-	if (!(mode & FALLOC_FL_KEEP_SIZE))
-		return -EOPNOTSUPP;
+	idx = srcu_read_lock(&kvm_gmem_freeze_srcu);
+	if (kvm_gmem_is_frozen(inode)) {
+		srcu_read_unlock(&kvm_gmem_freeze_srcu, idx);
+		return -EPERM;
+	}
 
-	if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE))
-		return -EOPNOTSUPP;
+	if (!(mode & FALLOC_FL_KEEP_SIZE)) {
+		ret = -EOPNOTSUPP;
+		goto out;
+	}
 
-	if (!PAGE_ALIGNED(offset) || !PAGE_ALIGNED(len))
-		return -EINVAL;
+	if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) {
+		ret = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (!PAGE_ALIGNED(offset) || !PAGE_ALIGNED(len)) {
+		ret = -EINVAL;
+		goto out;
+	}
 
 	if (mode & FALLOC_FL_PUNCH_HOLE)
 		ret = kvm_gmem_punch_hole(file_inode(file), offset, len);
@@ -291,6 +308,9 @@ static long kvm_gmem_fallocate(struct file *file, int mode, loff_t offset,
 
 	if (!ret)
 		file_modified(file);
+
+out:
+	srcu_read_unlock(&kvm_gmem_freeze_srcu, idx);
 	return ret;
 }
 
@@ -944,7 +964,9 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
 
 static void kvm_gmem_free_inode(struct inode *inode)
 {
-	kmem_cache_free(kvm_gmem_inode_cachep, GMEM_I(inode));
+	struct gmem_inode *gi = GMEM_I(inode);
+
+	kmem_cache_free(kvm_gmem_inode_cachep, gi);
 }
 
 static const struct super_operations kvm_gmem_super_operations = {
@@ -1001,12 +1023,21 @@ int kvm_gmem_init(struct module *module)
 	if (!kvm_gmem_inode_cachep)
 		return -ENOMEM;
 
+	ret = init_srcu_struct(&kvm_gmem_freeze_srcu);
+	if (ret)
+		goto err_cache;
+
 	ret = kvm_gmem_init_mount();
-	if (ret) {
-		kmem_cache_destroy(kvm_gmem_inode_cachep);
-		return ret;
-	}
+	if (ret)
+		goto err_srcu;
+
 	return 0;
+
+err_srcu:
+	cleanup_srcu_struct(&kvm_gmem_freeze_srcu);
+err_cache:
+	kmem_cache_destroy(kvm_gmem_inode_cachep);
+	return ret;
 }
 
 void kvm_gmem_exit(void)
@@ -1014,5 +1045,64 @@ void kvm_gmem_exit(void)
 	kern_unmount(kvm_gmem_mnt);
 	kvm_gmem_mnt = NULL;
 	rcu_barrier();
+	cleanup_srcu_struct(&kvm_gmem_freeze_srcu);
 	kmem_cache_destroy(kvm_gmem_inode_cachep);
 }
+
+/**
+ * kvm_gmem_freeze - Freeze or unfreeze a guest_memfd inode mapping.
+ * @inode: The guest_memfd inode.
+ * @freeze: True to freeze, false to unfreeze.
+ *
+ * This API is used strictly during the live update / preservation transition
+ * window to prevent host userspace and guest-side faults from making any
+ * mapping modifications (such as fallocate or page fault allocation)
+ * to the guest_memfd page cache.
+ *
+ * NOTE: Currently It is only checked at fallocate path. Page fault path NOT
+ * touched.
+ *
+ * Synchronization Strategy (Sleepable RCU):
+ * To avoid high-contention VFS locks (like inode_lock or filemap_invalidate_lock)
+ * on the vCPU page fault hot paths, this subsystem implements a lightweight,
+ * system-wide Sleepable RCU (SRCU) mechanism (`kvm_gmem_freeze_srcu`):
+ *
+ * Though currently, the freeze is checked only in fallocate, but it might be needed
+ * to the fault path as well in future to completely freeze the inode.
+ *
+ * Global vs. Per-Inode SRCU:
+ * A single system-wide global static `srcu_struct` is used instead of a per-inode
+ * SRCU structure to completely prevent unprivileged users from exhausting the
+ * host's per-CPU memory allocator. Because `init_srcu_struct()` allocates per-CPU
+ * memory via `alloc_percpu()`, which is not accounted by memory cgroups (memcg),
+ * a per-inode SRCU structure would allow a tenant to bypass cgroup limits and
+ * trigger a system-wide Out-of-Memory (OOM) crash simply by spawning a large
+ * number of guest_memfd file descriptors (bounded only by RLIMIT_NOFILE).
+ *
+ * Flag Modification Note:
+ * Since `GUEST_MEMFD_F_MAPPING_FROZEN` is the ONLY flag in `GMEM_I(inode)->flags`
+ * that is mutated dynamically at runtime (all other flags are creation-time flags
+ * which remain strictly read-only), there is no possibility of concurrent bit-
+ * modification races. Therefore, a standard `WRITE_ONCE` is fully safe and
+ * does not require complex `cmpxchg` synchronization loops.
+ *
+ */
+void kvm_gmem_freeze(struct inode *inode, bool freeze)
+{
+	u64 flags = READ_ONCE(GMEM_I(inode)->flags);
+
+	if (freeze)
+		flags |= GUEST_MEMFD_F_MAPPING_FROZEN;
+	else
+		flags &= ~GUEST_MEMFD_F_MAPPING_FROZEN;
+
+	WRITE_ONCE(GMEM_I(inode)->flags, flags);
+
+	if (freeze)
+		synchronize_srcu(&kvm_gmem_freeze_srcu);
+}
+
+bool kvm_gmem_is_frozen(struct inode *inode)
+{
+	return READ_ONCE(GMEM_I(inode)->flags) & GUEST_MEMFD_F_MAPPING_FROZEN;
+}
diff --git a/virt/kvm/guest_memfd.h b/virt/kvm/guest_memfd.h
index c528b046dd69..028c348a1023 100644
--- a/virt/kvm/guest_memfd.h
+++ b/virt/kvm/guest_memfd.h
@@ -29,11 +29,16 @@ struct gmem_inode {
 	u64 flags;
 };
 
+/* Internal kernel-only flags (must not overlap with UAPI flags) */
+#define GUEST_MEMFD_F_MAPPING_FROZEN	(1ULL << 63)
+
 static inline struct gmem_inode *GMEM_I(struct inode *inode)
 {
 	return container_of(inode, struct gmem_inode, vfs_inode);
 }
 
 struct file *__kvm_gmem_create_file(struct kvm *kvm, loff_t size, u64 flags);
+void kvm_gmem_freeze(struct inode *inode, bool freeze);
+bool kvm_gmem_is_frozen(struct inode *inode);
 
 #endif /* __KVM_GUEST_MEMFD_H__ */
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:37 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:37 +0000
Subject: [RFC PATCH v1 7/9] kvm: guest_memfd_luo: add support for guest_memfd preservation
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <4d444f77f973f610386b78c5689b9726dc5f4c73.1779080766.git.tarunsahu@google.com>

This patch sets up the basic infrastructure to preserve the guest_memfd.
Currently this supports only fully shared guest_memfd (INIT_SHARED),
pre-faulted and backed by PAGE_SIZE pages.

It registers a new LUO file handler for guest_memfd files to serialize
and deserialize guest memory. This allows preserving guest memory backed
by guest_memfd across updates, ensuring that guest instances can be
resumed seamlessly without losing their memory contents.

Preservation is straight forward. It walks through the folios and
serialize them.

There is kvm_gmem_freeze call on preserve which freeze the guest_memfd
inode. It avoids any changes to inode mapping with fallocate calls on
or after preservation. No need to check this during the page fault as
preservation is only supported for pre-faulted/pre-allocated guest_memfd.

While retrieving the guest_memfd, it requires the struct kvm to create
new guest_memfd. So it first get the vm_file from the same session using
the token passed during the preservation. And use it to get
vm_file->kvm.

This change also update the MAINTAINERS list.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>

---
Also, I wanted to use the luo file handler compatible string
for guest_memfd_luo same as kvm_luo (KVM_LUO_FH_COMPATIBLE), but
unfortnately LUO design does not permit this, every handler needs to be
registered with the separate string.
---
 MAINTAINERS                 |   1 +
 include/linux/kho/abi/kvm.h |  79 +++++-
 virt/kvm/Makefile.kvm       |   2 +-
 virt/kvm/guest_memfd_luo.c  | 495 ++++++++++++++++++++++++++++++++++++
 4 files changed, 570 insertions(+), 7 deletions(-)
 create mode 100644 virt/kvm/guest_memfd_luo.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2c26eb17bc0a..e5402a56ab98 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14413,6 +14413,7 @@ L:	kexec at lists.infradead.org
 L:	kvm at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	virt/kvm/guest_memfd_luo.c
 F:	virt/kvm/kvm_luo.c
 
 KVM PARAVIRT (KVM/paravirt)
diff --git a/include/linux/kho/abi/kvm.h b/include/linux/kho/abi/kvm.h
index 31bd39588bdd..fcdec609a41e 100644
--- a/include/linux/kho/abi/kvm.h
+++ b/include/linux/kho/abi/kvm.h
@@ -9,20 +9,23 @@
 #define _LINUX_KHO_ABI_KVM_H
 
 #include <linux/types.h>
+#include <linux/bits.h>
 #include <linux/kho/abi/kexec_handover.h>
 
 /**
- * DOC: KVM Live Update ABI
+ * DOC: KVM and guest_memfd Live Update ABI
  *
- * KVM uses the ABI defined below for preserving its state
+ * KVM and guest_memfd use the ABI defined below for preserving their states
  * across a kexec reboot using the LUO.
  *
- * The state is serialized into a packed structure `struct kvm_luo_ser`
- * which is handed over to the next kernel via the KHO mechanism.
+ * The state is serialized into packed structures (struct kvm_luo_ser and
+ * struct guest_memfd_luo_ser) which are handed over to the next kernel via
+ * the KHO mechanism.
  *
- * This interface is a contract. Any modification to the structure layout
+ * This interface is a contract. Any modification to the structure layouts
  * constitutes a breaking change. Such changes require incrementing the
- * version number in the KVM_LUO_FH_COMPATIBLE compatibility string.
+ * version number in the KVM_LUO_FH_COMPATIBLE or
+ * GUEST_MEMFD_LUO_FH_COMPATIBLE compatibility strings.
  */
 
 /**
@@ -51,4 +54,68 @@ struct kvm_luo_ser {
 /* The compatibility string for KVM VM file handler */
 #define KVM_LUO_FH_COMPATIBLE	"kvm_vm_luo_v1"
 
+/**
+ * struct guest_memfd_luo_folio_ser - Serialization layout for a single folio in guest_memfd.
+ * @pfn:   Page Frame Number of the folio.
+ * @index: Page offset of the folio within the file.
+ * @flags: State flags associated with the folio.
+ */
+struct guest_memfd_luo_folio_ser {
+	u64 pfn:52;
+	u64 flags:12;
+	u64 index;
+} __packed;
+
+/**
+ * GUEST_MEMFD_LUO_FOLIO_UPTODATE - The folio is up-to-date.
+ *
+ * This flag is per folio to check if the folio is uptodate.
+ */
+#define GUEST_MEMFD_LUO_FOLIO_UPTODATE	BIT(0)
+
+
+/**
+ * GUEST_MEMFD_LUO_FLAG_MMAP - The guest_memfd supports mmap.
+ *
+ * This flag indicates that the guest_memfd supports host-side mmap.
+ */
+#define GUEST_MEMFD_LUO_FLAG_MMAP		BIT(0)
+
+/**
+ * GUEST_MEMFD_LUO_FLAG_INIT_SHARED - Initialize memory as shared.
+ *
+ * This flag indicates that the guest_memfd has been initialized as shared
+ * memory.
+ */
+#define GUEST_MEMFD_LUO_FLAG_INIT_SHARED	BIT(1)
+
+/**
+ * GUEST_MEMFD_LUO_SUPPORTED_FLAGS - Supported guest_memfd LUO flags mask.
+ *
+ * A mask of all guest_memfd preservation flags supported by this version
+ * of the KVM LUO ABI.
+ */
+#define GUEST_MEMFD_LUO_SUPPORTED_FLAGS	(GUEST_MEMFD_LUO_FLAG_MMAP | \
+						 GUEST_MEMFD_LUO_FLAG_INIT_SHARED)
+
+/**
+ * struct guest_memfd_luo_ser - Main serialization structure for guest_memfd.
+ * @size:      The size of the file in bytes.
+ * @flags:     File-level flags.
+ * @nr_folios: Number of folios in the folios array.
+ * @vm_token:  Token of the associated KVM VM instance.
+ * @folios:    KHO vmalloc descriptor pointing to the array of
+ *             struct guest_memfd_luo_folio_ser.
+ */
+struct guest_memfd_luo_ser {
+	u64 size;
+	u64 flags;
+	u64 nr_folios;
+	u64 vm_token;
+	struct kho_vmalloc folios;
+} __packed;
+
+/* The compatibility string for GUEST_MEMFD file handler */
+#define GUEST_MEMFD_LUO_FH_COMPATIBLE	"guest_memfd_luo_v1"
+
 #endif /* _LINUX_KHO_ABI_KVM_H */
diff --git a/virt/kvm/Makefile.kvm b/virt/kvm/Makefile.kvm
index c1a962159264..d30fca094c42 100644
--- a/virt/kvm/Makefile.kvm
+++ b/virt/kvm/Makefile.kvm
@@ -13,4 +13,4 @@ kvm-$(CONFIG_HAVE_KVM_IRQ_ROUTING) += $(KVM)/irqchip.o
 kvm-$(CONFIG_HAVE_KVM_DIRTY_RING) += $(KVM)/dirty_ring.o
 kvm-$(CONFIG_HAVE_KVM_PFNCACHE) += $(KVM)/pfncache.o
 kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o
-kvm-$(CONFIG_LIVEUPDATE_GUEST_MEMFD) += $(KVM)/kvm_luo.o
+kvm-$(CONFIG_LIVEUPDATE_GUEST_MEMFD) += $(KVM)/guest_memfd_luo.o $(KVM)/kvm_luo.o
diff --git a/virt/kvm/guest_memfd_luo.c b/virt/kvm/guest_memfd_luo.c
new file mode 100644
index 000000000000..66b931eafc82
--- /dev/null
+++ b/virt/kvm/guest_memfd_luo.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Tarun Sahu <tarunsahu at google.com>
+ *
+ * Guestmemfd Preservation for Live Update Orchestrator (LUO)
+ */
+
+/**
+ * DOC: Guestmemfd Preservation via LUO
+ *
+ * Overview
+ * ========
+ *
+ * Guest memory file descriptors (guest_memfd) can be preserved over a kexec
+ * reboot using the Live Update Orchestrator (LUO) file preservation. This
+ * allows userspace to preserve VM memory across kexec reboots.
+ *
+ * The preservation is not intended to be transparent. Only select properties
+ * of the guest_memfd are preserved, while others are reset to default.
+ *
+ * .. note::
+ *    Currently, only guest_memfd backed by standard system page size
+ *    (PAGE_SIZE) is supported. Huge pages are not supported.
+ *
+ * Preserved Properties
+ * ====================
+ *
+ * The following properties of guest_memfd are preserved across kexec:
+ *
+ * File Size
+ *   The size of the file is preserved.
+ *
+ * File Contents
+ *   All folios present in the page cache are preserved.
+ *
+ * File-level Flags
+ *   The file-level flags (such as MMAP support and INIT_SHARED default mapping)
+ *   are preserved.
+ *
+ * Non-Preserved Properties
+ * ========================
+ *
+ * NUMA Memory Policy
+ *   NUMA memory policies associated with the guest_memfd are not preserved.
+ */
+#include <linux/liveupdate.h>
+#include <linux/kvm_host.h>
+#include <linux/pagemap.h>
+#include <linux/file.h>
+#include <linux/err.h>
+#include <linux/anon_inodes.h>
+#include <linux/magic.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/kvm.h>
+#include "guest_memfd.h"
+
+static int kvm_gmem_luo_walk_folios(struct address_space *mapping,
+		pgoff_t end_index, struct guest_memfd_luo_folio_ser *folios_ser,
+		u64 *out_count)
+{
+	struct folio_batch fbatch;
+	pgoff_t index = 0;
+	u64 count = 0;
+	int err = 0;
+
+	folio_batch_init(&fbatch);
+	while (index < end_index) {
+		unsigned int nr, i;
+
+		nr = filemap_get_folios(mapping, &index, end_index - 1, &fbatch);
+		if (nr == 0)
+			break;
+
+		for (i = 0; i < nr; i++) {
+			struct folio *folio = fbatch.folios[i];
+
+			if (folios_ser) {
+				if (folio_test_hwpoison(folio)) {
+					err = -EHWPOISON;
+					folio_batch_release(&fbatch);
+					goto out;
+				}
+				err = kho_preserve_folio(folio);
+				if (err) {
+					folio_batch_release(&fbatch);
+					goto out;
+				}
+
+				folios_ser[count].pfn = folio_pfn(folio);
+				folios_ser[count].index = folio->index;
+				folios_ser[count].flags = folio_test_uptodate(folio) ?
+							  GUEST_MEMFD_LUO_FOLIO_UPTODATE : 0;
+			}
+			count++;
+		}
+		folio_batch_release(&fbatch);
+		cond_resched();
+	}
+
+out:
+	*out_count = count;
+	return err;
+}
+
+static bool kvm_gmem_luo_can_preserve(struct liveupdate_file_handler *handler, struct file *file)
+{
+	struct inode *inode = file_inode(file);
+	u64 count = 0;
+	pgoff_t end_index;
+	long size;
+
+	if (inode->i_sb->s_magic != GUEST_MEMFD_MAGIC)
+		return 0;
+
+	if (!(GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_INIT_SHARED))
+		return 0;
+
+	if (mapping_large_folio_support(inode->i_mapping))
+		return 0;
+
+	size = i_size_read(inode);
+	if (!size)
+		return 0;
+
+	if (size & (PAGE_SIZE - 1))
+		return 0;
+
+	end_index = size >> PAGE_SHIFT;
+
+	if (kvm_gmem_luo_walk_folios(inode->i_mapping, end_index, NULL, &count))
+		return 0;
+
+	if (count != end_index)
+		return 0;
+
+	return 1;
+}
+
+static int kvm_gmem_luo_preserve(struct liveupdate_file_op_args *args)
+{
+	struct guest_memfd_luo_folio_ser *folios_ser;
+	u64 count, gmem_flags, abi_flags = 0;
+	struct guest_memfd_luo_ser *ser;
+	struct address_space *mapping;
+	struct gmem_file *gmem_file;
+	struct inode *inode;
+	pgoff_t end_index;
+	struct kvm *kvm;
+	int err = 0;
+	long size;
+
+	inode = file_inode(args->file);
+	kvm_gmem_freeze(inode, true);
+
+	mapping = inode->i_mapping;
+	size = i_size_read(inode);
+	if (!size) {
+		err = 0;
+		goto err_unfreeze_inode;
+	}
+
+	if (WARN_ON_ONCE(size & (PAGE_SIZE - 1))) {
+		err = -EINVAL;
+		goto err_unfreeze_inode;
+	}
+
+	gmem_file = args->file->private_data;
+	kvm = gmem_file->kvm;
+
+	gmem_flags = READ_ONCE(GMEM_I(inode)->flags);
+	if (gmem_flags & ~(GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED
+				| GUEST_MEMFD_F_MAPPING_FROZEN)) {
+		err = -EOPNOTSUPP;
+		goto err_unfreeze_inode;
+	}
+
+	if (gmem_flags & GUEST_MEMFD_FLAG_MMAP)
+		abi_flags |= GUEST_MEMFD_LUO_FLAG_MMAP;
+	if (gmem_flags & GUEST_MEMFD_FLAG_INIT_SHARED)
+		abi_flags |= GUEST_MEMFD_LUO_FLAG_INIT_SHARED;
+
+	end_index = size >> PAGE_SHIFT;
+
+	ser = kho_alloc_preserve(sizeof(*ser));
+	if (IS_ERR(ser)) {
+		err = PTR_ERR(ser);
+		goto err_unfreeze_inode;
+	}
+
+	folios_ser = vcalloc(end_index, sizeof(*folios_ser));
+	if (!folios_ser) {
+		err = -ENOMEM;
+		goto err_free_ser;
+	}
+
+	/* Walk: Fill the metadata array and preserve folios */
+	err = kvm_gmem_luo_walk_folios(mapping, end_index, folios_ser, &count);
+	if (err)
+		goto err_unpreserve_unlocked;
+
+	if (WARN_ON_ONCE(count != end_index)) {
+		err = -EINVAL;
+		goto err_unpreserve_unlocked;
+	}
+
+	ser->size = size;
+	ser->flags = abi_flags;
+	ser->nr_folios = count;
+	ser->vm_token = 0; // It will be set during the kvm_gmem_luo_freeze()
+
+	err = kho_preserve_vmalloc(folios_ser, &ser->folios);
+	if (err)
+		goto err_unpreserve_unlocked;
+
+	args->serialized_data = virt_to_phys(ser);
+	args->private_data = folios_ser;
+
+	return 0;
+
+err_unpreserve_unlocked:
+	for (long i = count - 1; i >= 0; i--) {
+		struct folio *folio = pfn_folio(folios_ser[i].pfn);
+
+		kho_unpreserve_folio(folio);
+	}
+	vfree(folios_ser);
+err_free_ser:
+	kho_unpreserve_free(ser);
+err_unfreeze_inode:
+	kvm_gmem_freeze(inode, false);
+	return err;
+}
+
+static int kvm_gmem_luo_freeze(struct liveupdate_file_op_args *args)
+{
+	struct guest_memfd_luo_ser *ser;
+	struct gmem_file *gmem_file;
+	struct kvm *kvm;
+	struct file *kvm_file;
+	u64 vm_token;
+	int err;
+
+	if (WARN_ON_ONCE(!args->serialized_data))
+		return -EINVAL;
+
+	ser = phys_to_virt(args->serialized_data);
+	if (!ser)
+		return -EINVAL;
+
+	gmem_file = args->file->private_data;
+	kvm = gmem_file->kvm;
+
+	/*
+	 * Obtain a strong reference to kvm->vm_file to prevent the SLAB_TYPESAFE_BY_RCU
+	 * file memory from being reallocated while it is being processed.
+	 */
+	kvm_file = get_file_active(&kvm->vm_file);
+	if (!kvm_file)
+		return -ENOENT;
+
+	err = liveupdate_get_token_outgoing(args->session, kvm_file, &vm_token);
+	fput(kvm_file);
+	if (err)
+		return err;
+
+	ser->vm_token = vm_token;
+	return 0;
+}
+
+static void kvm_gmem_luo_discard_folios(
+	const struct guest_memfd_luo_folio_ser *folios_ser,
+	u64 nr_folios, u64 start_idx)
+{
+	long i;
+
+	for (i = start_idx; i < nr_folios; i++) {
+		struct folio *folio;
+		phys_addr_t phys;
+
+		if (!folios_ser[i].pfn)
+			continue;
+
+		phys = PFN_PHYS(folios_ser[i].pfn);
+		folio = kho_restore_folio(phys);
+		if (folio)
+			folio_put(folio);
+	}
+}
+
+static void kvm_gmem_luo_unpreserve(struct liveupdate_file_op_args *args)
+{
+	struct guest_memfd_luo_folio_ser *folios_ser = args->private_data;
+	struct guest_memfd_luo_ser *ser;
+	long i;
+
+	if (WARN_ON_ONCE(!args->serialized_data))
+		return;
+
+	ser = phys_to_virt(args->serialized_data);
+	if (!ser)
+		return;
+
+	if (ser->nr_folios > 0)
+		kho_unpreserve_vmalloc(&ser->folios);
+	for (i = ser->nr_folios - 1; i >= 0; i--) {
+		struct folio *folio;
+
+		if (!folios_ser[i].pfn)
+			continue;
+
+		folio = pfn_folio(folios_ser[i].pfn);
+		kho_unpreserve_folio(folio);
+	}
+	vfree(folios_ser);
+
+	kho_unpreserve_free(ser);
+	kvm_gmem_freeze(file_inode(args->file), false);
+}
+
+static int kvm_gmem_luo_retrieve(struct liveupdate_file_op_args *args)
+{
+	struct guest_memfd_luo_folio_ser *folios_ser = NULL;
+	struct guest_memfd_luo_ser *ser;
+	struct kvm *kvm = NULL;
+	struct file *vm_file;
+	struct inode *inode;
+	struct file *file;
+	u64 gmem_flags = 0;
+	int err = 0;
+	long i = 0;
+
+	if (!args->serialized_data)
+		return -EINVAL;
+
+	ser = phys_to_virt(args->serialized_data);
+	if (!ser)
+		return -EINVAL;
+
+	if (ser->flags & ~GUEST_MEMFD_LUO_SUPPORTED_FLAGS) {
+		err = -EOPNOTSUPP;
+		goto err_free_ser;
+	}
+
+	if (ser->flags & GUEST_MEMFD_LUO_FLAG_MMAP)
+		gmem_flags |= GUEST_MEMFD_FLAG_MMAP;
+	if (ser->flags & GUEST_MEMFD_LUO_FLAG_INIT_SHARED)
+		gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
+
+	err = liveupdate_get_file_incoming(args->session, ser->vm_token, &vm_file);
+	if (err) {
+		pr_warn("gmem: provided VM FD token (%llx) on preserve is incorrect\n",
+						ser->vm_token);
+		goto err_free_ser;
+	}
+
+	if (file_is_kvm(vm_file))
+		kvm = vm_file->private_data;
+
+	/*
+	 * Release the temporary reference taken by the liveupdate_get_file_incoming
+	 * call. LUO still holds a reference.
+	 */
+	fput(vm_file);
+
+	if (!kvm) {
+		err = -EINVAL;
+		goto err_free_ser;
+	}
+
+	file = __kvm_gmem_create_file(kvm, ser->size, gmem_flags);
+	if (IS_ERR(file)) {
+		err = PTR_ERR(file);
+		goto err_free_ser;
+	}
+
+	inode = file_inode(file);
+
+	if (ser->nr_folios) {
+		folios_ser = kho_restore_vmalloc(&ser->folios);
+		if (!folios_ser) {
+			err = -EINVAL;
+			goto err_destroy_file;
+		}
+
+		for (i = 0; i < ser->nr_folios; i++) {
+			struct folio *folio;
+			phys_addr_t phys;
+
+			if (!folios_ser[i].pfn)
+				continue;
+
+			phys = PFN_PHYS(folios_ser[i].pfn);
+			folio = kho_restore_folio(phys);
+			if (!folio) {
+				pr_err("gmem: failed to restore folio at %llx\n", phys);
+				err = -EIO;
+				goto err_put_remaining_folios;
+			}
+
+			err = filemap_add_folio(inode->i_mapping, folio, folios_ser[i].index,
+						GFP_KERNEL);
+			if (err) {
+				pr_err("gmem: failed to add folio to page cache\n");
+				folio_put(folio);
+				goto err_put_remaining_folios;
+			}
+
+			if (folios_ser[i].flags & GUEST_MEMFD_LUO_FOLIO_UPTODATE)
+				folio_mark_uptodate(folio);
+			folio_unlock(folio);
+			folio_put(folio);
+		}
+		vfree(folios_ser);
+	}
+
+	args->file = file;
+	kho_restore_free(ser);
+	return 0;
+
+err_put_remaining_folios:
+	i++;
+err_destroy_file:
+	fput(file);
+err_free_ser:
+	if (ser->nr_folios) {
+		if (!folios_ser)
+			folios_ser = kho_restore_vmalloc(&ser->folios);
+		if (folios_ser) {
+			kvm_gmem_luo_discard_folios(folios_ser, ser->nr_folios, i);
+			vfree(folios_ser);
+		}
+	}
+	kho_restore_free(ser);
+	return err;
+}
+
+static void kvm_gmem_luo_finish(struct liveupdate_file_op_args *args)
+{
+	struct guest_memfd_luo_ser *ser;
+	struct guest_memfd_luo_folio_ser *folios_ser;
+
+	/* Nothing to be done here, if retrieve_status was successful or errored,
+	 * Cleanup is taken care of in retrieval call.
+	 */
+	if (args->retrieve_status)
+		return;
+
+	if (!args->serialized_data)
+		return;
+
+	ser = phys_to_virt(args->serialized_data);
+	if (!ser)
+		return;
+
+	if (ser->nr_folios) {
+		folios_ser = kho_restore_vmalloc(&ser->folios);
+		if (folios_ser) {
+			kvm_gmem_luo_discard_folios(folios_ser, ser->nr_folios, 0);
+			vfree(folios_ser);
+		}
+	}
+
+	kho_restore_free(ser);
+}
+
+static const struct liveupdate_file_ops kvm_gmem_luo_file_ops = {
+	.can_preserve = kvm_gmem_luo_can_preserve,
+	.preserve = kvm_gmem_luo_preserve,
+	.freeze = kvm_gmem_luo_freeze,
+	.retrieve = kvm_gmem_luo_retrieve,
+	.unpreserve = kvm_gmem_luo_unpreserve,
+	.finish = kvm_gmem_luo_finish,
+	.owner = THIS_MODULE,
+};
+
+static struct liveupdate_file_handler kvm_gmem_luo_handler = {
+	.ops = &kvm_gmem_luo_file_ops,
+	.compatible = GUEST_MEMFD_LUO_FH_COMPATIBLE,
+};
+
+static int __init kvm_gmem_luo_init(void)
+{
+	int err = liveupdate_register_file_handler(&kvm_gmem_luo_handler);
+
+	if (err && err != -EOPNOTSUPP) {
+		pr_err("Could not register luo filesystem handler: %pe\n", ERR_PTR(err));
+		return err;
+	}
+
+	return 0;
+}
+late_initcall(kvm_gmem_luo_init);
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:38 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:38 +0000
Subject: [RFC PATCH v1 8/9] selftests: kvm: Split ____vm_create() to expose
 init helpers
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <8bf92f738d3456ea71ffe2a8406714e0d2afa114.1779080766.git.tarunsahu@google.com>

Refactor `____vm_create()` in the KVM selftest library to extract its
initialization steps into separate, reusable internal helpers.

Introduce `vm_init_fields()` and `vm_init_memory_properties()`. This
allows advanced test setups to perform targeted VM fields or memory
property initializations independently, which is required by upcoming
test cases that restore preserved VMs. No functional changes are
introduced for the existing tests.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 .../testing/selftests/kvm/include/kvm_util.h  |  2 ++
 tools/testing/selftests/kvm/lib/kvm_util.c    | 26 +++++++++++++------
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 2ecaaa0e9965..d10cd25d0658 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -471,6 +471,8 @@ const char *vm_guest_mode_string(u32 i);
 
 void kvm_vm_free(struct kvm_vm *vmp);
 void kvm_vm_restart(struct kvm_vm *vmp);
+void vm_init_fields(struct kvm_vm *vm, struct vm_shape shape);
+void vm_init_memory_properties(struct kvm_vm *vm);
 void kvm_vm_release(struct kvm_vm *vmp);
 void kvm_vm_elf_load(struct kvm_vm *vm, const char *filename);
 int kvm_memfd_alloc(size_t size, bool hugepages);
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 2a76eca7029d..f4cd06d34ce9 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -276,13 +276,8 @@ __weak void vm_populate_gva_bitmap(struct kvm_vm *vm)
 		(1ULL << (vm->va_bits - 1)) >> vm->page_shift);
 }
 
-struct kvm_vm *____vm_create(struct vm_shape shape)
+void vm_init_fields(struct kvm_vm *vm, struct vm_shape shape)
 {
-	struct kvm_vm *vm;
-
-	vm = calloc(1, sizeof(*vm));
-	TEST_ASSERT(vm != NULL, "Insufficient Memory");
-
 	INIT_LIST_HEAD(&vm->vcpus);
 	vm->regions.gpa_tree = RB_ROOT;
 	vm->regions.hva_tree = RB_ROOT;
@@ -380,9 +375,10 @@ struct kvm_vm *____vm_create(struct vm_shape shape)
 	if (vm->pa_bits != 40)
 		vm->type = KVM_VM_TYPE_ARM_IPA_SIZE(vm->pa_bits);
 #endif
+}
 
-	vm_open(vm);
-
+void vm_init_memory_properties(struct kvm_vm *vm)
+{
 	/* Limit to VA-bit canonical virtual addresses. */
 	vm->vpages_valid = sparsebit_alloc();
 	vm_populate_gva_bitmap(vm);
@@ -392,6 +388,20 @@ struct kvm_vm *____vm_create(struct vm_shape shape)
 
 	/* Allocate and setup memory for guest. */
 	vm->vpages_mapped = sparsebit_alloc();
+}
+
+struct kvm_vm *____vm_create(struct vm_shape shape)
+{
+	struct kvm_vm *vm;
+
+	vm = calloc(1, sizeof(*vm));
+	TEST_ASSERT(vm != NULL, "Insufficient Memory");
+
+	vm_init_fields(vm, shape);
+
+	vm_open(vm);
+
+	vm_init_memory_properties(vm);
 
 	return vm;
 }
-- 
2.54.0.563.g4f69b47b94-goog



From tarunsahu at google.com  Mon May 18 02:36:39 2026
From: tarunsahu at google.com (Tarun Sahu)
Date: Mon, 18 May 2026 09:36:39 +0000
Subject: [RFC PATCH v1 9/9] selftests: kvm: Add guest_memfd_preservation_test
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <a1bc78e4f43b5b6b079012954af9af1cf864b3f8.1779080766.git.tarunsahu@google.com>

Add a new KVM selftest `guest_memfd_preservation_test` to verify that
guest memory backed by guest_memfd is preserved properly.

The test leverages the Live Update Orchestrator (LUO) infrastructure
to validate that memory folios and configuration layouts are
successfully saved and then restored during kernel live updates,
preventing any memory loss for the guest.

Here, I have used the kvm selftests framework by creating a new
vm and mapping two memory slots to it. One is the code that is executed
inside the vm and other is the guest_memfd whose memory is being
written by the guest code.

In Phase 1: Once data is written the vm exits and wait for the user
to trigger the kexec.

In Phase 2: A new vm is created with retrieved kvm and again two
memory slots are assigned. Once for guest code, and another is for
retrieved guest_memfd where guest_memfd memory is verified by the
executed guest code. If verification succeeds, The test passes.

Signed-off-by: Tarun Sahu <tarunsahu at google.com>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/kvm/Makefile.kvm      |   2 +
 .../kvm/guest_memfd_preservation_test.c       | 285 ++++++++++++++++++
 3 files changed, 288 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/guest_memfd_preservation_test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index e5402a56ab98..647d60f6a1e2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14413,6 +14413,7 @@ L:	kexec at lists.infradead.org
 L:	kvm at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	tools/testing/selftests/kvm/guest_memfd_preservation_test.c
 F:	virt/kvm/guest_memfd_luo.c
 F:	virt/kvm/kvm_luo.c
 
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89..4ea6cb7bf001 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -161,6 +161,8 @@ TEST_GEN_PROGS_x86 += pre_fault_memory_test
 
 # Compiled outputs used by test targets
 TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
+# Manual test that forks a persistent background daemon; skip auto CI run
+TEST_GEN_PROGS_EXTENDED_x86 += guest_memfd_preservation_test
 
 TEST_GEN_PROGS_arm64 = $(TEST_GEN_PROGS_COMMON)
 TEST_GEN_PROGS_arm64 += arm64/aarch32_id_regs
diff --git a/tools/testing/selftests/kvm/guest_memfd_preservation_test.c b/tools/testing/selftests/kvm/guest_memfd_preservation_test.c
new file mode 100644
index 000000000000..ad7b305b48c3
--- /dev/null
+++ b/tools/testing/selftests/kvm/guest_memfd_preservation_test.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026, Google LLC.
+ *
+ * Author: Tarun Sahu <tarunsahu at google.com>
+ *
+ * Test for VM and guest_memfd preservation across kexec (Live Update) via LUO.
+ *
+ * NOTE: This is a MANUAL test and is excluded from automated CI/testing
+ * frameworks because Phase 1 daemonizes into the background to pin resources
+ * and requires a human operator to manually trigger kexec before Phase 2
+ * is executed. Running Phase 1 automatically would leak the background daemon
+ * and cause CI runners to falsely interpret it as a passed test.
+ *
+ * Usage:
+ * Phase 1: ./guest_memfd_preservation_test
+ * Phase 2: ./guest_memfd_preservation_test --phase2
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/sizes.h>
+#include <linux/falloc.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall_common.h"
+#include "../kselftest.h"
+#include "../kselftest_harness.h"
+
+#include "../../../../include/uapi/linux/liveupdate.h"
+
+#define SESSION_NAME "gmem_vm_preservation_session"
+#define VM_TOKEN 0x1001
+#define GMEM_TOKEN 0x1002
+
+#define GMEM_SIZE (16ULL * 1024 * 1024)
+#define DATA_SIZE (5ULL * 1024 * 1024)
+
+static size_t page_size;
+
+/* Deterministic byte pattern generation based on offset */
+static inline uint8_t get_pattern_byte(size_t offset)
+{
+	return (uint8_t)(offset ^ 0x5A);
+}
+
+static void guest_code_phase1(uint64_t gpa, uint64_t size, uint64_t data_size)
+{
+	uint8_t *mem = (uint8_t *)gpa;
+	size_t i;
+
+	for (i = 0; i < data_size; i++)
+		mem[i] = get_pattern_byte(i);
+
+	GUEST_DONE();
+}
+
+static void guest_code_phase2(uint64_t gpa, uint64_t size, uint64_t data_size)
+{
+	uint8_t *mem = (uint8_t *)gpa;
+	size_t i;
+
+	for (i = 0; i < data_size; i++) {
+		uint8_t val = get_pattern_byte(i);
+
+		__GUEST_ASSERT(mem[i] == val,
+			       "Data mismatch at offset %lu! Expected 0x%x, got 0x%x",
+			       i, val, mem[i]);
+	}
+
+	GUEST_DONE();
+}
+
+static void do_phase1(void)
+{
+	uint64_t flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED;
+	int gmem_fd, dev_luo_fd, ret;
+	const uint64_t gpa = SZ_4G;
+	struct kvm_vcpu *vcpu;
+	const int slot = 1;
+	struct kvm_vm *vm;
+	struct liveupdate_ioctl_create_session create_sess = {
+		.size = sizeof(create_sess),
+		.name = SESSION_NAME,
+	};
+	struct liveupdate_session_preserve_fd preserve_vm = {
+		.size = sizeof(preserve_vm),
+		.token = VM_TOKEN,
+	};
+	struct liveupdate_session_preserve_fd preserve_gmem = {
+		.size = sizeof(preserve_gmem),
+		.token = GMEM_TOKEN,
+	};
+
+	vm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, &vcpu, 1,
+					guest_code_phase1);
+	gmem_fd = vm_create_guest_memfd(vm, GMEM_SIZE, flags);
+	vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, GMEM_SIZE, NULL,
+				 gmem_fd, 0);
+	ret = fallocate(gmem_fd, FALLOC_FL_KEEP_SIZE, 0, GMEM_SIZE);
+	TEST_ASSERT(!ret, "fallocate failed, errno = %d (%s)", errno, strerror(errno));
+
+	for (size_t i = 0; i < GMEM_SIZE; i += page_size)
+		virt_pg_map(vm, gpa + i, gpa + i);
+
+	vcpu_args_set(vcpu, 3, gpa, GMEM_SIZE, DATA_SIZE);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+	dev_luo_fd = open("/dev/liveupdate", O_RDWR);
+	TEST_ASSERT(dev_luo_fd >= 0, "Failed to open /dev/liveupdate");
+
+	TEST_ASSERT(ioctl(dev_luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION,
+			  &create_sess) == 0,
+			  "Failed to create LUO session");
+	TEST_ASSERT(create_sess.fd >= 0, "Invalid session fd");
+
+	preserve_vm.fd = vm->fd;
+	TEST_ASSERT(ioctl(create_sess.fd, LIVEUPDATE_SESSION_PRESERVE_FD,
+			  &preserve_vm) == 0,
+			  "Failed to preserve VM file descriptor");
+
+	preserve_gmem.fd = gmem_fd;
+	TEST_ASSERT(ioctl(create_sess.fd, LIVEUPDATE_SESSION_PRESERVE_FD,
+		    &preserve_gmem) == 0,
+		    "Failed to preserve guest_memfd file descriptor");
+
+	printf("\n============================================================\n");
+	printf("Phase 1 Complete Successfully!\n");
+	printf("VM file and guest_memfd file have been preserved via LUO.\n");
+	printf("Tokens: VM_TOKEN=0x%x, GMEM_TOKEN=0x%x\n", VM_TOKEN, GMEM_TOKEN);
+	printf("Machine Size: %llu MB, Data Size: %llu MB\n", GMEM_SIZE / SZ_1M,
+				 DATA_SIZE / SZ_1M);
+	printf("------------------------------------------------------------\n");
+
+	pid_t pid;
+
+	printf("Forking background process to hold sessions open...\n");
+	pid = fork();
+	TEST_ASSERT(pid >= 0, "fork failed");
+
+	if (pid > 0) {
+		printf("Background child process PID: %d. Resources are pinned.\n", pid);
+		printf("ACTION REQUIRED: Trigger kexec now to boot into Phase 2 kernel.\n");
+		exit(EXIT_SUCCESS);
+	}
+
+	/* Child process: detach from terminal and hold resources */
+	if (setsid() < 0)
+		exit(EXIT_FAILURE);
+
+	close(STDIN_FILENO);
+	close(STDOUT_FILENO);
+	close(STDERR_FILENO);
+
+	while (1)
+		sleep(60);
+}
+
+static struct kvm_vm *vm_create_from_fd(int resurrected_vm_fd,
+					struct vm_shape shape)
+{
+	struct kvm_vm *vm;
+
+	vm = calloc(1, sizeof(*vm));
+	TEST_ASSERT(vm != NULL, "Insufficient Memory");
+
+	vm_init_fields(vm, shape);
+
+	vm->kvm_fd = open_path_or_exit(KVM_DEV_PATH, O_RDWR);
+	vm->fd = resurrected_vm_fd;
+
+	if (kvm_has_cap(KVM_CAP_BINARY_STATS_FD))
+		vm->stats.fd = vm_get_stats_fd(vm);
+	else
+		vm->stats.fd = -1;
+
+	vm_init_memory_properties(vm);
+
+	return vm;
+}
+
+static void do_phase2(void)
+{
+	int retrieved_vm_fd, retrieved_gmem_fd, dev_luo_fd;
+	struct vm_shape shape = VM_SHAPE_DEFAULT;
+	const uint64_t gpa = SZ_4G;
+	struct kvm_vcpu *vcpu;
+	const int slot = 1;
+	struct kvm_vm *vm;
+	struct liveupdate_ioctl_retrieve_session retrieve_sess = {
+		.size = sizeof(retrieve_sess),
+		.name = SESSION_NAME,
+	};
+	struct liveupdate_session_retrieve_fd retrieve_vm = {
+		.size = sizeof(retrieve_vm),
+		.token = VM_TOKEN,
+	};
+	struct liveupdate_session_retrieve_fd retrieve_gmem = {
+		.size = sizeof(retrieve_gmem),
+		.token = GMEM_TOKEN,
+	};
+
+	dev_luo_fd = open("/dev/liveupdate", O_RDWR);
+	TEST_ASSERT(dev_luo_fd >= 0, "Failed to open /dev/liveupdate");
+
+	TEST_ASSERT(ioctl(dev_luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &retrieve_sess) == 0,
+		    "Failed to retrieve LUO session");
+	TEST_ASSERT(retrieve_sess.fd >= 0, "Invalid retrieved session fd");
+
+	TEST_ASSERT(ioctl(retrieve_sess.fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &retrieve_vm) == 0,
+		    "Failed to retrieve VM file descriptor");
+	retrieved_vm_fd = retrieve_vm.fd;
+
+	TEST_ASSERT(ioctl(retrieve_sess.fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &retrieve_gmem) == 0,
+		    "Failed to retrieve guest_memfd file descriptor");
+	retrieved_gmem_fd = retrieve_gmem.fd;
+
+	vm = vm_create_from_fd(retrieved_vm_fd, shape);
+
+	u64 nr_pages = 2048; /* 8MB is plenty for slot0 pages */
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, 0);
+	kvm_vm_elf_load(vm, program_invocation_name);
+
+	for (int i = 0; i < NR_MEM_REGIONS; i++)
+		vm->memslots[i] = 0;
+
+	struct userspace_mem_region *slot0 = memslot2region(vm, 0);
+
+	ucall_init(vm, slot0->region.guest_phys_addr + slot0->region.memory_size);
+
+	vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, GMEM_SIZE, NULL,
+				   retrieved_gmem_fd, 0);
+
+	for (size_t i = 0; i < GMEM_SIZE; i += page_size)
+		virt_pg_map(vm, gpa + i, gpa + i);
+
+	vcpu = vm_vcpu_add(vm, 0, guest_code_phase2);
+	kvm_arch_vm_finalize_vcpus(vm);
+
+	vcpu_args_set(vcpu, 3, gpa, GMEM_SIZE, DATA_SIZE);
+
+	printf("Resuming / Running VM in Phase 2...\n");
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+	printf("\nSUCCESS: Phase 2 Complete! All 5MB complex data verified intact!\n");
+
+	close(retrieve_sess.fd);
+	close(dev_luo_fd);
+	/* This will also close the vm_fd */
+	kvm_vm_free(vm);
+	close(retrieved_gmem_fd);
+}
+
+int main(int argc, char *argv[])
+{
+	bool phase2 = false;
+
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_GUEST_MEMFD));
+	page_size = getpagesize();
+
+	for (int i = 1; i < argc; i++) {
+		if (strcmp(argv[i], "--phase2") == 0)
+			phase2 = true;
+	}
+
+	if (phase2)
+		do_phase2();
+	else
+		do_phase1();
+
+	return 0;
+}
-- 
2.54.0.563.g4f69b47b94-goog



From pasha.tatashin at soleen.com  Mon May 18 05:40:33 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:40:33 +0000
Subject: [PATCH v4 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <agrQaP22pgxOkR_j@kernel.org>
References: <20260517192650.1019314-1-pasha.tatashin@soleen.com>
 <20260517192650.1019314-4-pasha.tatashin@soleen.com>
 <agrQaP22pgxOkR_j@kernel.org>
Message-ID: <agsIsQLQR8i1sF_A@plex>

On 05-18 11:40, Mike Rapoport wrote:
> On Sun, May 17, 2026 at 07:26:48PM +0000, Pasha Tatashin wrote:
> > During the reboot() syscall, user processes may still be running
> > concurrently and attempting to mutate sessions (e.g., creating,
> > retrieving, or releasing sessions). To prevent this, introduce
> > luo_session_serialize_rwsem to synchronize mutations with the
> > serialization process.
> > 
> > All session mutation operations (create, retrieve, release, ioctl) take
> > the read lock. The serialization process (luo_session_serialize) takes
> > the write lock and holds it indefinitely on success. This effectively
> > freezes the LUO session subsystem during the transition to the new
> > kernel. If serialization fails, the lock is released to allow recovery.
> > 
> > Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> > Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  kernel/liveupdate/luo_session.c | 27 ++++++++++++++++++++++++---
> >  1 file changed, 24 insertions(+), 3 deletions(-)
> > 
> > diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> > index 59b37d17db6b..6099213275d8 100644
> > --- a/kernel/liveupdate/luo_session.c
> > +++ b/kernel/liveupdate/luo_session.c
> > @@ -385,9 +394,12 @@ int luo_session_create(const char *name, struct file **filep)
> >  	struct luo_session *session;
> >  	int err;
> >  
> > +	down_read(&luo_session_serialize_rwsem);
> >  	session = luo_session_alloc(name);
> 
> We can alloc outside the lock, will make error handling simpler.
> 
> sashiko complains about mixing down_read() with scoped_guard(), for this
> function it would make sense to use plain mutex_{lock,unlock} around
> luo_session_getfile()

Done. Thank  you for review.

Pasha

> 
> > -	if (IS_ERR(session))
> > -		return PTR_ERR(session);
> > +	if (IS_ERR(session)) {
> > +		err = PTR_ERR(session);
> > +		goto err_unlock;
> > +	}
> >  
> >  	err = luo_session_insert(&luo_session_global.outgoing, session);
> >  	if (err)
> > @@ -398,12 +410,16 @@ int luo_session_create(const char *name, struct file **filep)
> >  	if (err)
> >  		goto err_remove;
> >  
> > +	up_read(&luo_session_serialize_rwsem);
> > +
> >  	return 0;
> >  
> >  err_remove:
> >  	luo_session_remove(&luo_session_global.outgoing, session);
> >  err_free:
> >  	luo_session_free(session);
> > +err_unlock:
> > +	up_read(&luo_session_serialize_rwsem);
> >  
> >  	return err;
> >  }
> 
> -- 
> Sincerely yours,
> Mike.


From pasha.tatashin at soleen.com  Mon May 18 05:54:54 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:54 +0000
Subject: [PATCH v5 0/5] liveupdate: serialization safety and race fixes
Message-ID: <20260518125459.1092373-1-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization
between the reboot process and LUO session management.

Changes in v5:
- Collected Acked-by from Mike Rapoport.
- In "block session mutations during reboot" (#3):
  - Moved down_read(&luo_session_serialize_rwsem) after luo_session_alloc()
    to minimize the critical section, and simplify cleanup.
  - Replaced scoped_guard() with explicit mutex_lock/unlock in
    luo_session_create() for consistency.

1. Skip LUO serialization for context-preserving kexec: A
preserve_context kexec returns to the current kernel, which is unrelated
to live update where state is passed to the next kernel. Skipping
serialization avoids unnecessary work and prevents sessions from being
left in a frozen state upon return.

2. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
scope to prevent a session from being released between lookup and
mutex acquisition.

3. Block session mutations during reboot: During the reboot() syscall,
user processes may still be running concurrently and attempting to
mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
All mutation operations (create, retrieve, release, ioctl) hold the
read lock. The serialization process holds the write lock indefinitely
on success, effectively freezing the subsystem.

4. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
to ensure the file handler module remains pinned while its operations
are being accessed during cleanup.

5. Remove unused ser field from struct luo_session: Clean up the
session structure by removing a field that was never utilized.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
luo-reboot-sync/v5

Pasha Tatashin (5):
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: fix TOCTOU race in luo_session_retrieve()
  liveupdate: block session mutations during reboot
  liveupdate: fix u-a-f in luo_file_unpreserve_files() and
    luo_file_finish()
  liveupdate: Remove unused ser field from struct luo_session

 kernel/kexec_core.c              |  8 +++++---
 kernel/liveupdate/luo_file.c     |  5 +++--
 kernel/liveupdate/luo_internal.h |  2 --
 kernel/liveupdate/luo_session.c  | 35 ++++++++++++++++++++++++--------
 4 files changed, 34 insertions(+), 16 deletions(-)


base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 05:54:55 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:55 +0000
Subject: [PATCH v5 1/5] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
Message-ID: <20260518125459.1092373-2-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which is
unrelated to live update where the state is passed to the next kernel.
Skip liveupdate_reboot() in this case to avoid serialization and prevent
sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 05:54:56 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:56 +0000
Subject: [PATCH v5 2/5] liveupdate: fix TOCTOU race in luo_session_retrieve()
In-Reply-To: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
Message-ID: <20260518125459.1092373-3-pasha.tatashin@soleen.com>

Extend the scope of the rwsem_read lock in luo_session_retrieve() to
overlap with the acquisition of the session mutex. This prevents a
concurrent thread from releasing and freeing the session between the
lookup and the mutex lock.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..59b37d17db6b 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -415,12 +415,11 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
-	scoped_guard(rwsem_read, &sh->rwsem) {
-		list_for_each_entry(it, &sh->list, list) {
-			if (!strncmp(it->name, name, sizeof(it->name))) {
-				session = it;
-				break;
-			}
+	guard(rwsem_read)(&sh->rwsem);
+	list_for_each_entry(it, &sh->list, list) {
+		if (!strncmp(it->name, name, sizeof(it->name))) {
+			session = it;
+			break;
 		}
 	}
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 05:54:57 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:57 +0000
Subject: [PATCH v5 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
Message-ID: <20260518125459.1092373-4-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., creating,
retrieving, or releasing sessions). To prevent this, introduce
luo_session_serialize_rwsem to synchronize mutations with the
serialization process.

All session mutation operations (create, retrieve, release, ioctl) take
the read lock. The serialization process (luo_session_serialize) takes
the write lock and holds it indefinitely on success. This effectively
freezes the LUO session subsystem during the transition to the new
kernel. If serialization fails, the lock is released to allow recovery.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 59b37d17db6b..812f09152ec1 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -75,6 +75,13 @@
 		sizeof(struct luo_session_header_ser)) /		\
 		sizeof(struct luo_session_ser))
 
+/*
+ * Protects session mutations during serialization. All session mutation
+ * operations must hold the read lock. The serialization process holds the write
+ * lock indefinitely on success to block all concurrent and future mutations.
+ */
+static DECLARE_RWSEM(luo_session_serialize_rwsem);
+
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:      The number of sessions currently tracked in the @list.
@@ -205,6 +212,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 	struct luo_session *session = filep->private_data;
 	struct luo_session_header *sh;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	/* If retrieved is set, it means this session is from incoming list */
 	if (session->retrieved) {
 		int err = luo_session_finish_one(session);
@@ -354,6 +362,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 	if (ret)
 		return ret;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	return op->execute(session, &ucmd);
 }
 
@@ -389,14 +398,17 @@ int luo_session_create(const char *name, struct file **filep)
 	if (IS_ERR(session))
 		return PTR_ERR(session);
 
+	down_read(&luo_session_serialize_rwsem);
 	err = luo_session_insert(&luo_session_global.outgoing, session);
 	if (err)
 		goto err_free;
 
-	scoped_guard(mutex, &session->mutex)
-		err = luo_session_getfile(session, filep);
+	mutex_lock(&session->mutex);
+	err = luo_session_getfile(session, filep);
+	mutex_unlock(&session->mutex);
 	if (err)
 		goto err_remove;
+	up_read(&luo_session_serialize_rwsem);
 
 	return 0;
 
@@ -404,6 +416,7 @@ int luo_session_create(const char *name, struct file **filep)
 	luo_session_remove(&luo_session_global.outgoing, session);
 err_free:
 	luo_session_free(session);
+	up_read(&luo_session_serialize_rwsem);
 
 	return err;
 }
@@ -415,6 +428,7 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	guard(rwsem_read)(&sh->rwsem);
 	list_for_each_entry(it, &sh->list, list) {
 		if (!strncmp(it->name, name, sizeof(it->name))) {
@@ -582,7 +596,8 @@ int luo_session_serialize(void)
 	int i = 0;
 	int err;
 
-	guard(rwsem_write)(&sh->rwsem);
+	down_write(&luo_session_serialize_rwsem);
+	down_write(&sh->rwsem);
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -593,6 +608,7 @@ int luo_session_serialize(void)
 		i++;
 	}
 	sh->header_ser->count = sh->count;
+	up_write(&sh->rwsem);
 
 	return 0;
 
@@ -602,6 +618,8 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	up_write(&sh->rwsem);
+	up_write(&luo_session_serialize_rwsem);
 
 	return err;
 }
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 05:54:58 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:58 +0000
Subject: [PATCH v5 4/5] liveupdate: fix u-a-f in luo_file_unpreserve_files() and luo_file_finish()
In-Reply-To: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
Message-ID: <20260518125459.1092373-5-pasha.tatashin@soleen.com>

In luo_file_unpreserve_files() and luo_file_finish(), reorder
module_put() and xa_erase() to ensure the file handler module remains
pinned while its operations are being accessed.

Specifically, luo_get_id() dereferences fh->ops->get_id, so the module
reference must be held until after xa_erase() (which calls luo_get_id)
completes.

For luo_file_finish(), this requires moving the module_put() call out of
the luo_file_finish_one() helper and into the main loop of
luo_file_finish() itself.

Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..208987502f73 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -385,10 +385,11 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 		args.private_data = luo_file->private_data;
 		luo_file->fh->ops->unpreserve(&args);
 		luo_flb_file_unpreserve(luo_file->fh);
-		module_put(luo_file->fh->ops->owner);
 
 		xa_erase(&luo_preserved_files,
 			 luo_get_id(luo_file->fh, luo_file->file));
+		module_put(luo_file->fh->ops->owner);
+
 		list_del(&luo_file->list);
 		file_set->count--;
 
@@ -677,7 +678,6 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
 
 	luo_file->fh->ops->finish(&args);
 	luo_flb_file_finish(luo_file->fh);
-	module_put(luo_file->fh->ops->owner);
 }
 
 /**
@@ -738,6 +738,7 @@ int luo_file_finish(struct luo_file_set *file_set)
 				 luo_get_id(luo_file->fh, luo_file->file));
 			fput(luo_file->file);
 		}
+		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
 		mutex_destroy(&luo_file->mutex);
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 05:54:59 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 12:54:59 +0000
Subject: [PATCH v5 5/5] liveupdate: Remove unused ser field from struct luo_session
In-Reply-To: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
Message-ID: <20260518125459.1092373-6-pasha.tatashin@soleen.com>

The ser field in struct luo_session was intended to point to the
serialized data for a session, but it was never actually utilized in the
implementation. All serialization and deserialization logic consistently
uses the pointers maintained in struct luo_session_header.

Remove the dead field to clean up the structure.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..dd53d4a7277e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -59,7 +59,6 @@ struct luo_file_set {
  * struct luo_session - Represents an active or incoming Live Update session.
  * @name:       A unique name for this session, used for identification and
  *              retrieval.
- * @ser:        Pointer to the serialized data for this session.
  * @list:       A list_head member used to link this session into a global list
  *              of either outgoing (to be preserved) or incoming (restored from
  *              previous kernel) sessions.
@@ -70,7 +69,6 @@ struct luo_file_set {
  */
 struct luo_session {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	struct luo_session_ser *ser;
 	struct list_head list;
 	bool retrieved;
 	struct luo_file_set file_set;
-- 
2.53.0



From pratyush at kernel.org  Mon May 18 09:13:02 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 18 May 2026 18:13:02 +0200
Subject: [PATCH v5 2/5] liveupdate: fix TOCTOU race in
 luo_session_retrieve()
In-Reply-To: <20260518125459.1092373-3-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Mon, 18 May 2026 12:54:56 +0000")
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
	<20260518125459.1092373-3-pasha.tatashin@soleen.com>
Message-ID: <2vxz1pf8ae7l.fsf@kernel.org>

On Mon, May 18 2026, Pasha Tatashin wrote:

> Extend the scope of the rwsem_read lock in luo_session_retrieve() to
> overlap with the acquisition of the session mutex. This prevents a
> concurrent thread from releasing and freeing the session between the
> lookup and the mutex lock.
>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

I was about to comment that perhaps we should drop session header lock
after getting session lock, but I think the session retrieval is pretty
fast so this should not matter.

So,

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 18 09:24:14 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 18 May 2026 18:24:14 +0200
Subject: [PATCH v5 4/5] liveupdate: fix u-a-f in
 luo_file_unpreserve_files() and luo_file_finish()
In-Reply-To: <20260518125459.1092373-5-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Mon, 18 May 2026 12:54:58 +0000")
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
	<20260518125459.1092373-5-pasha.tatashin@soleen.com>
Message-ID: <2vxzwlx08z4h.fsf@kernel.org>

On Mon, May 18 2026, Pasha Tatashin wrote:

> In luo_file_unpreserve_files() and luo_file_finish(), reorder
> module_put() and xa_erase() to ensure the file handler module remains
> pinned while its operations are being accessed.
>
> Specifically, luo_get_id() dereferences fh->ops->get_id, so the module
> reference must be held until after xa_erase() (which calls luo_get_id)
> completes.
>
> For luo_file_finish(), this requires moving the module_put() call out of
> the luo_file_finish_one() helper and into the main loop of
> luo_file_finish() itself.
>
> Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 18 09:24:35 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 18 May 2026 18:24:35 +0200
Subject: [PATCH v5 5/5] liveupdate: Remove unused ser field from struct
 luo_session
In-Reply-To: <20260518125459.1092373-6-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Mon, 18 May 2026 12:54:59 +0000")
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
	<20260518125459.1092373-6-pasha.tatashin@soleen.com>
Message-ID: <2vxzse7o8z3w.fsf@kernel.org>

On Mon, May 18 2026, Pasha Tatashin wrote:

> The ser field in struct luo_session was intended to point to the
> serialized data for a session, but it was never actually utilized in the
> implementation. All serialization and deserialization logic consistently
> uses the pointers maintained in struct luo_session_header.
>
> Remove the dead field to clean up the structure.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 18 09:31:20 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 18 May 2026 18:31:20 +0200
Subject: [PATCH v5 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260518125459.1092373-4-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Mon, 18 May 2026 12:54:57 +0000")
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
	<20260518125459.1092373-4-pasha.tatashin@soleen.com>
Message-ID: <2vxzo6ic8ysn.fsf@kernel.org>

On Mon, May 18 2026, Pasha Tatashin wrote:

> During the reboot() syscall, user processes may still be running
> concurrently and attempting to mutate sessions (e.g., creating,
> retrieving, or releasing sessions). To prevent this, introduce
> luo_session_serialize_rwsem to synchronize mutations with the
> serialization process.
>
> All session mutation operations (create, retrieve, release, ioctl) take
> the read lock. The serialization process (luo_session_serialize) takes
> the write lock and holds it indefinitely on success. This effectively
> freezes the LUO session subsystem during the transition to the new
> kernel. If serialization fails, the lock is released to allow recovery.

Good idea I think.

But, do we need a new mutex? Can't we use luo_session_header->rwsem?
Session creation and release take the header rwsem at one point anyway,
so perhaps we can just reuse that?

Also, do we need to block incoming sessions? They won't participate in
serialization, so perhaps we can leave those alone, and all the outgoing
sessions get protected by the outgoing session header rwsem?

>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 18 10:04:53 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 18 May 2026 19:04:53 +0200
Subject: [PATCH 10/12] kho: extended scratch
In-Reply-To: <agmVzhvF8cxFu1PK@kernel.org> (Mike Rapoport's message of "Sun,
	17 May 2026 13:17:50 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-11-pratyush@kernel.org>
	<agmVzhvF8cxFu1PK@kernel.org>
Message-ID: <2vxzjyt08x8q.fsf@kernel.org>

On Sun, May 17 2026, Mike Rapoport wrote:

> On Wed, Apr 29, 2026 at 03:39:12PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>>
>> Methodology
>> ===========
>> 
>> Introduce extended scratch areas. These areas are discovered at boot by
>> walking the preserved memory radix tree and looking for free blocks of
>> memory. They then marked as scratch to allow allocations from them. This
>> makes KHO more resilient to memory pressure and allows supporting huge
>> page preservation.
>> 
>> Since the preserved memory radix tree mixes both physical address and
>> order into a single key, and does not track table pages, it is difficult
>> to identify free areas from it directly. Walk the tree and digest it
>> down into another radix tree. The latter tracks blocks of
>> KHO_EXT_SHIFT (1 GiB as of now) granularity. Then walk the digested tree
>> and mark the areas between the present keys as scratch.
>>
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  include/linux/kexec_handover.h     |   1 +
>>  kernel/liveupdate/kexec_handover.c | 148 +++++++++++++++++++++++++----
>>  mm/mm_init.c                       |   1 +
>>  3 files changed, 133 insertions(+), 17 deletions(-)
>> 
>> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
>> index 1a04e089f779..c2b843a5fb28 100644
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -840,6 +857,120 @@ static void __init kho_reserve_scratch(void)
>>  	kho_enable = false;
>>  }
>>  
>> +#define KHO_EXT_SHIFT 30 /* 1 GiB */
>
> arm64 does not necessarily use 1G gigantic pages and worse, it can have 2
> gigantic hstates.
>
> I think this should take into account what actual gigantic page sizes are
> in use for the general case.

This has nothing to with the gigantic page sizes. This is simply the
granularity at which KHO looks for free blocks. Making this larger means
less memory usage and better performance at the cost of amount of memory
recovered. Making this smaller does the opposite.

I picked 1G because it "feels" the right balance. Mostly gut feeling
without real science behind the number. I can make it smaller or larger
if you'd like.

-- 
Regards,
Pratyush Yadav


From skhawaja at google.com  Mon May 18 12:12:05 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Mon, 18 May 2026 19:12:05 +0000
Subject: [PATCH 1/3] kho: Add kunit static stubs
In-Reply-To: <aggfP-0A-i-bYlP7@kernel.org>
References: <20260512195135.804833-1-skhawaja@google.com>
 <20260512195135.804833-2-skhawaja@google.com>
 <aggfP-0A-i-bYlP7@kernel.org>
Message-ID: <agtkMYr4-boyOQtK@google.com>

Hi Mike,

On Sat, May 16, 2026 at 10:39:43AM +0300, Mike Rapoport wrote:
>Hi Sami,
>
>On Tue, May 12, 2026 at 07:51:33PM +0000, Samiullah Khawaja wrote:
>> Add kunit stubs in kho_restore_folio() and kho_restore_pages() so the
>> users can mock these functions in kunit tests.
>>
>> Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 9 ++++++++-
>>  1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
>> index d5718bef6d4d..1375291d9b07 100644
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -11,6 +11,7 @@
>>  #define pr_fmt(fmt) "KHO: " fmt
>>
>>  #include <linux/cleanup.h>
>> +#include <kunit/static_stub.h>
>
>Can we include kunit headers before linux headers?

Agreed. I will update this in next revision.
>
>>  #include <linux/cma.h>
>>  #include <linux/kmemleak.h>
>>  #include <linux/count_zeros.h>
>> @@ -437,7 +438,11 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
>>   */
>>  struct folio *kho_restore_folio(phys_addr_t phys)
>>  {
>> -	struct page *page = kho_restore_page(phys, true);
>> +	struct page *page;
>> +
>> +	KUNIT_STATIC_STUB_REDIRECT(kho_restore_folio, phys);
>> +
>> +	page = kho_restore_page(phys, true);
>>
>>  	return page ? page_folio(page) : NULL;
>>  }
>> @@ -459,6 +464,8 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)
>>  	const unsigned long end_pfn = start_pfn + nr_pages;
>>  	unsigned long pfn = start_pfn;
>>
>> +	KUNIT_STATIC_STUB_REDIRECT(kho_restore_pages, phys, nr_pages);
>> +
>>  	while (pfn < end_pfn) {
>>  		const unsigned int order =
>>  			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
>> --
>> 2.54.0.563.g4f69b47b94-goog
>>
>
>-- 
>Sincerely yours,
>Mike.

Thanks,
Sami


From djeffery at redhat.com  Mon May 18 12:31:59 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:31:59 -0400
Subject: [PATCH v16 0/5] shut down devices asynchronously
Message-ID: <20260518193204.14273-1-djeffery@redhat.com>

These patches are now rebased against the driver-core tree's driver-core-next
branch. Please let me know if there is a different tree/branch which would be
better to use for possible inclusion of these patches.

This patchset allows the kernel to shutdown devices asynchronously and
unrelated async devices to be shut down in parallel to each other.

Only devices which explicitly enable it are shut down asynchronously. The
default is for a device to be shut down from the synchronous shutdown loop.

This can dramatically reduce system shutdown/reboot time on systems that
have multiple devices that take many seconds to shut down (like certain
NVMe drives). On one system tested, the shutdown time went from 11 minutes
without this patch to 55 seconds with the patch. And on another system from
80 seconds to 11.

And thank you to everyone who has spent some of their valuable time
providing reviews, suggestions, criticisms, or tests on the various
iterations of this patchset.

Changes from V15:

The async_shutdown bit field is converted to a device flags bit
Convert all patches to use the flag bit accessor macros to set or check if
  async shutdown should be used
Added documentation on the kernel parameter to control use of async shutdown

Changes from V14:

Remove unneeded use of '!!' with boolean type

Changes from V13:

Remove duplicate flagging of async shutdown on scsi hosts/targets/devices

Changes from V12:

Only acquire a parent reference if acquiring the parent's lock
device_enable_async_shutdown should return void
Minor comment and description cleanups

Changes from V11:

  * Swap the order of the first two patches
  * Rework conditional parent locking so that lock and unlock no longer use
    separate conditional checks
  * Remove an used variable
  * Comment and description text cleanups

Changes from V10:

Reworked to more closely match the design used for async suspend
  * No longer uses async subsystem cookies for synchronization
  * Minimized changes to struct device
  * Enable async shutdown for pci and scsi devices which support async suspend

Changes from V9:

Address resource and timing issues when spawning a unique async thread
for every device during shutdown:
  * Make the asynchronous threads able to shut down multiple devices,
    instead of spawning a unique thread for every device.
  * Modify core kernel async code with a custom wake function so it
    doesn't wake up a thread waiting to synchronize on a cookie until
    the cookie has reached the desired value, instead of waking up
    every waiting thread to check the cookie every time an async thread
    ends.

Changes from V8:

Deal with shutdown hangs resulting when a parent/supplier device is
  later in the devices_kset list than its children/consumers:
  * Ignore sync_state_only devlinks for shutdown dependencies
  * Ignore shutdown_after for devices that don't want async shutdown
  * Add a sanity check to revert to sync shutdown for any device that
    would otherwise wait for a child/consumer shutdown that hasn't
    already been scheduled

Changes from V7:

Do not expose driver async_shutdown_enable in sysfs.
Wrapped a long line.

Changes from V6:

Removed a sysfs attribute that allowed the async device shutdown to be
"on" (with driver opt-out), "safe" (driver opt-in), or "off"... what was
previously "safe" is now the only behavior, so drivers now only need to
have the option to enable or disable async shutdown.

Changes from V5:

Separated into multiple patches to make review easier.
Reworked some code to make it more readable
Made devices wait for consumers to shut down, not just children
  (suggested by David Jeffery)

Changes from V4:

Change code to use cookies for synchronization rather than async domains
Allow async shutdown to be disabled via sysfs, and allow driver opt-in or
  opt-out of async shutdown (when not disabled), with ability to control
  driver opt-in/opt-out via sysfs
  
Changes from V3:

Bug fix (used "parent" not "dev->parent" in device_shutdown)
 
Changes from V2:
 
Removed recursive functions to schedule children to be shutdown before
  parents, since existing device_shutdown loop will already do this
 
Changes from V1:

Rewritten using kernel async code (suggested by Lukas Wunner)


Stuart Hayes (2):
  driver core: separate function to shutdown one device
  driver core: do not always lock parent in shutdown

David Jeffery (3):
  driver core: async device shutdown infrastructure
  PCI: Enable async shutdown support
  scsi: Enable async shutdown support

 .../admin-guide/kernel-parameters.txt         |  10 +
 drivers/base/base.h                           |   2 +
 drivers/base/core.c                           | 180 ++++++++++++++----
 drivers/pci/probe.c                           |   2 +
 drivers/scsi/hosts.c                          |   2 +
 drivers/scsi/scsi_sysfs.c                     |   3 +
 include/linux/device.h                        |   2 +
 7 files changed, 165 insertions(+), 36 deletions(-)

-- 
2.53.0



From djeffery at redhat.com  Mon May 18 12:32:00 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:32:00 -0400
Subject: [PATCH 1/5] driver core: separate function to shutdown one device
In-Reply-To: <20260518193204.14273-1-djeffery@redhat.com>
References: <20260518193204.14273-1-djeffery@redhat.com>
Message-ID: <20260518193204.14273-2-djeffery@redhat.com>

Make a separate function for the part of device_shutdown() that does the
shutown for a single device.  This is in preparation for making device
shutdown asynchronous.

Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
Signed-off-by: David Jeffery <djeffery at redhat.com>
Tested-by: Laurence Oberman <loberman at redhat.com>
Tested-by: Tarun Sahu <tarunsahu at google.com>
Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 drivers/base/core.c | 71 +++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 32 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index d49420e066de..127432248165 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4795,12 +4795,48 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static void shutdown_one_device(struct device *dev)
+{
+	struct device *parent = dev->parent;
+
+	/* hold lock to avoid race with probe/release */
+	if (parent)
+		device_lock(parent);
+	device_lock(dev);
+
+	/* Don't allow any more runtime suspends */
+	pm_runtime_get_noresume(dev);
+	pm_runtime_barrier(dev);
+
+	if (dev->class && dev->class->shutdown_pre) {
+		if (initcall_debug)
+			dev_info(dev, "shutdown_pre\n");
+		dev->class->shutdown_pre(dev);
+	}
+	if (dev->bus && dev->bus->shutdown) {
+		if (initcall_debug)
+			dev_info(dev, "shutdown\n");
+		dev->bus->shutdown(dev);
+	} else if (dev->driver && dev->driver->shutdown) {
+		if (initcall_debug)
+			dev_info(dev, "shutdown\n");
+		dev->driver->shutdown(dev);
+	}
+
+	device_unlock(dev);
+	if (parent)
+		device_unlock(parent);
+
+	put_device(parent);
+	put_device(dev);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
 void device_shutdown(void)
 {
-	struct device *dev, *parent;
+	struct device *dev;
 
 	wait_for_device_probe();
 	device_block_probing();
@@ -4822,7 +4858,7 @@ void device_shutdown(void)
 		 * prevent it from being freed because parent's
 		 * lock is to be held
 		 */
-		parent = get_device(dev->parent);
+		get_device(dev->parent);
 		get_device(dev);
 		/*
 		 * Make sure the device is off the kset list, in the
@@ -4831,36 +4867,7 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		/* hold lock to avoid race with probe/release */
-		if (parent)
-			device_lock(parent);
-		device_lock(dev);
-
-		/* Don't allow any more runtime suspends */
-		pm_runtime_get_noresume(dev);
-		pm_runtime_barrier(dev);
-
-		if (dev->class && dev->class->shutdown_pre) {
-			if (initcall_debug)
-				dev_info(dev, "shutdown_pre\n");
-			dev->class->shutdown_pre(dev);
-		}
-		if (dev->bus && dev->bus->shutdown) {
-			if (initcall_debug)
-				dev_info(dev, "shutdown\n");
-			dev->bus->shutdown(dev);
-		} else if (dev->driver && dev->driver->shutdown) {
-			if (initcall_debug)
-				dev_info(dev, "shutdown\n");
-			dev->driver->shutdown(dev);
-		}
-
-		device_unlock(dev);
-		if (parent)
-			device_unlock(parent);
-
-		put_device(dev);
-		put_device(parent);
+		shutdown_one_device(dev);
 
 		spin_lock(&devices_kset->list_lock);
 	}
-- 
2.53.0



From djeffery at redhat.com  Mon May 18 12:32:01 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:32:01 -0400
Subject: [PATCH 2/5] driver core: do not always lock parent in shutdown
In-Reply-To: <20260518193204.14273-1-djeffery@redhat.com>
References: <20260518193204.14273-1-djeffery@redhat.com>
Message-ID: <20260518193204.14273-3-djeffery@redhat.com>

Don't lock a parent device unless it is needed in device_shutdown. This
is in preparation for making device shutdown asynchronous, when it will
be needed to allow children of a common parent to shut down
simultaneously.

And only acquire a reference to the parent device if the parent is to be
locked.

Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
Signed-off-by: David Jeffery <djeffery at redhat.com>
Tested-by: Laurence Oberman <loberman at redhat.com>
Tested-by: Tarun Sahu <tarunsahu at google.com>
Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 drivers/base/core.c | 30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 127432248165..6e2c37115bc1 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4795,13 +4795,8 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
-static void shutdown_one_device(struct device *dev)
+static void __shutdown_one_device(struct device *dev)
 {
-	struct device *parent = dev->parent;
-
-	/* hold lock to avoid race with probe/release */
-	if (parent)
-		device_lock(parent);
 	device_lock(dev);
 
 	/* Don't allow any more runtime suspends */
@@ -4824,10 +4819,23 @@ static void shutdown_one_device(struct device *dev)
 	}
 
 	device_unlock(dev);
-	if (parent)
+}
+
+static void shutdown_one_device(struct device *dev)
+{
+	struct device *parent;
+
+	/* hold lock if needed to avoid race with probe/release */
+	if (dev->bus && dev->bus->need_parent_lock &&
+	    (parent = get_device(dev->parent))) {
+		device_lock(parent);
+		__shutdown_one_device(dev);
 		device_unlock(parent);
+		put_device(parent);
+	} else {
+		__shutdown_one_device(dev);
+	}
 
-	put_device(parent);
 	put_device(dev);
 }
 
@@ -4853,12 +4861,6 @@ void device_shutdown(void)
 		dev = list_entry(devices_kset->list.prev, struct device,
 				kobj.entry);
 
-		/*
-		 * hold reference count of device's parent to
-		 * prevent it from being freed because parent's
-		 * lock is to be held
-		 */
-		get_device(dev->parent);
 		get_device(dev);
 		/*
 		 * Make sure the device is off the kset list, in the
-- 
2.53.0



From djeffery at redhat.com  Mon May 18 12:32:02 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:32:02 -0400
Subject: [PATCH 3/5] driver core: async device shutdown infrastructure
In-Reply-To: <20260518193204.14273-1-djeffery@redhat.com>
References: <20260518193204.14273-1-djeffery@redhat.com>
Message-ID: <20260518193204.14273-4-djeffery@redhat.com>

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Enabled by default, async shutdown can be explicitly enabled or disabled
by using the kernel parameter "core.async_shutdown=<bool>"

Signed-off-by: David Jeffery <djeffery at redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
Tested-by: Laurence Oberman <loberman at redhat.com>
Tested-by: Tarun Sahu <tarunsahu at google.com>
Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 .../admin-guide/kernel-parameters.txt         |  10 ++
 drivers/base/base.h                           |   2 +
 drivers/base/core.c                           | 101 +++++++++++++++++-
 include/linux/device.h                        |   2 +
 4 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5a51a36a048..dd912f47ace4 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1019,6 +1019,16 @@ Kernel parameters
 			seconds. A value of 0 disables the blank timer.
 			Defaults to 0.
 
+	core.async_shutdown=
+			[KNL]
+			Format: <bool>
+			Enable or disable asynchronous shutdown support. When
+			enabled, on system shutdown unrelated devices flagged
+			as async shutdown compatible may be shut down in
+			parallel and asynchronously. When disabled, device
+			shutdown is performed in a serially and synchronously.
+			Enabled by default.
+
 	coredump_filter=
 			[KNL] Change the default value for
 			/proc/<pid>/coredump_filter.
diff --git a/drivers/base/base.h b/drivers/base/base.h
index 483b99b4fa3d..aefa4f256d59 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -103,6 +103,7 @@ struct driver_private {
  *			   dev_err_probe() for later retrieval via debugfs
  * @device: pointer back to the struct device that this structure is
  *	    associated with.
+ * @complete: completion for device shutdown ordering
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
@@ -119,6 +120,7 @@ struct device_private {
 	const struct device_driver *async_driver;
 	char *deferred_probe_reason;
 	struct device *device;
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 6e2c37115bc1..cab10b0e70db 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -9,6 +9,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/async.h>
 #include <linux/blkdev.h>
 #include <linux/cleanup.h>
 #include <linux/cpufreq.h>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3536,6 +3541,7 @@ static int device_private_init(struct device *dev)
 	klist_init(&dev->p->klist_children, klist_children_get,
 		   klist_children_put);
 	INIT_LIST_HEAD(&dev->p->deferred_probe);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4795,6 +4801,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev_async_shutdown(dev);
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4818,6 +4855,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4839,6 +4878,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4851,6 +4939,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4869,11 +4963,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index d54c86d77764..0f2aeba34483 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -546,6 +546,7 @@ enum struct_device_flags {
 	DEV_FLAG_OF_NODE_REUSED = 7,
 	DEV_FLAG_OFFLINE_DISABLED = 8,
 	DEV_FLAG_OFFLINE = 9,
+	DEV_FLAG_ASYNC_SHUTDOWN = 10,
 
 	DEV_FLAG_COUNT
 };
@@ -763,6 +764,7 @@ __create_dev_flag_accessors(dma_coherent, DEV_FLAG_DMA_COHERENT);
 __create_dev_flag_accessors(of_node_reused, DEV_FLAG_OF_NODE_REUSED);
 __create_dev_flag_accessors(offline_disabled, DEV_FLAG_OFFLINE_DISABLED);
 __create_dev_flag_accessors(offline, DEV_FLAG_OFFLINE);
+__create_dev_flag_accessors(async_shutdown, DEV_FLAG_ASYNC_SHUTDOWN);
 
 #undef __create_dev_flag_accessors
 
-- 
2.53.0



From djeffery at redhat.com  Mon May 18 12:32:03 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:32:03 -0400
Subject: [PATCH 4/5] PCI: Enable async shutdown support
In-Reply-To: <20260518193204.14273-1-djeffery@redhat.com>
References: <20260518193204.14273-1-djeffery@redhat.com>
Message-ID: <20260518193204.14273-5-djeffery@redhat.com>

Like its async suspend support, allow PCI device shutdown to be performed
asynchronously to reduce shutdown time.

Signed-off-by: David Jeffery <djeffery at redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
Tested-by: Laurence Oberman <loberman at redhat.com>
Tested-by: Tarun Sahu <tarunsahu at google.com>
Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
Reviewed-by: Bjorn Helgaas <bhelgaas at google.com>
---
 drivers/pci/probe.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..7cf3dabc885e 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1045,6 +1045,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 	bus->bridge = get_device(&bridge->dev);
 	device_enable_async_suspend(bus->bridge);
+	dev_set_async_shutdown(bus->bridge);
 	pci_set_bus_of_node(bus);
 	pci_set_bus_msi_domain(bus);
 	if (bridge->msi_domain && !dev_get_msi_domain(&bus->dev) &&
@@ -2753,6 +2754,7 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 	pci_reassigndev_resource_alignment(dev);
 
 	pci_init_capabilities(dev);
+	dev_set_async_shutdown(&dev->dev);
 
 	/*
 	 * Add the device to our list of discovered devices
-- 
2.53.0



From djeffery at redhat.com  Mon May 18 12:32:04 2026
From: djeffery at redhat.com (David Jeffery)
Date: Mon, 18 May 2026 15:32:04 -0400
Subject: [PATCH 5/5] scsi: Enable async shutdown support
In-Reply-To: <20260518193204.14273-1-djeffery@redhat.com>
References: <20260518193204.14273-1-djeffery@redhat.com>
Message-ID: <20260518193204.14273-6-djeffery@redhat.com>

Like scsi's async suspend support, allow scsi devices to be shut down
asynchronously to reduce system shutdown time.

Signed-off-by: David Jeffery <djeffery at redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes at gmail.com>
Tested-by: Laurence Oberman <loberman at redhat.com>
Reviewed-by: Martin K. Petersen <martin.petersen at oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
Reviewed-by: John Garry <john.g.garry at oracle.com>
---
 drivers/scsi/hosts.c      | 2 ++
 drivers/scsi/scsi_sysfs.c | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index e047747d4ecf..bf691acc7a67 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -273,6 +273,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
 	pm_runtime_set_active(&shost->shost_gendev);
 	pm_runtime_enable(&shost->shost_gendev);
 	device_enable_async_suspend(&shost->shost_gendev);
+	dev_set_async_shutdown(&shost->shost_gendev);
 
 	error = device_add(&shost->shost_gendev);
 	if (error)
@@ -282,6 +283,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
 	get_device(shost->shost_gendev.parent);
 
 	device_enable_async_suspend(&shost->shost_dev);
+	dev_set_async_shutdown(&shost->shost_dev);
 
 	get_device(&shost->shost_gendev);
 	error = device_add(&shost->shost_dev);
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index dfc3559e7e04..8fd317aef37b 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1386,6 +1386,7 @@ static int scsi_target_add(struct scsi_target *starget)
 	pm_runtime_set_active(&starget->dev);
 	pm_runtime_enable(&starget->dev);
 	device_enable_async_suspend(&starget->dev);
+	dev_set_async_shutdown(&starget->dev);
 
 	return 0;
 }
@@ -1412,6 +1413,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 	transport_configure_device(&starget->dev);
 
 	device_enable_async_suspend(&sdev->sdev_gendev);
+	dev_set_async_shutdown(&sdev->sdev_gendev);
 	scsi_autopm_get_target(starget);
 	pm_runtime_set_active(&sdev->sdev_gendev);
 	if (!sdev->rpm_autosuspend)
@@ -1431,6 +1433,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 	}
 
 	device_enable_async_suspend(&sdev->sdev_dev);
+	dev_set_async_shutdown(&sdev->sdev_dev);
 	error = device_add(&sdev->sdev_dev);
 	if (error) {
 		sdev_printk(KERN_INFO, sdev,
-- 
2.53.0



From pasha.tatashin at soleen.com  Mon May 18 16:15:17 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Mon, 18 May 2026 23:15:17 +0000
Subject: [PATCH v5 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <2vxzo6ic8ysn.fsf@kernel.org>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
 <20260518125459.1092373-4-pasha.tatashin@soleen.com>
 <2vxzo6ic8ysn.fsf@kernel.org>
Message-ID: <aguXhxrjhvmxAyb2@plex>

On 05-18 18:31, Pratyush Yadav wrote:
> On Mon, May 18 2026, Pasha Tatashin wrote:
> 
> > During the reboot() syscall, user processes may still be running
> > concurrently and attempting to mutate sessions (e.g., creating,
> > retrieving, or releasing sessions). To prevent this, introduce
> > luo_session_serialize_rwsem to synchronize mutations with the
> > serialization process.
> >
> > All session mutation operations (create, retrieve, release, ioctl) take
> > the read lock. The serialization process (luo_session_serialize) takes
> > the write lock and holds it indefinitely on success. This effectively
> > freezes the LUO session subsystem during the transition to the new
> > kernel. If serialization fails, the lock is released to allow recovery.
> 
> Good idea I think.

Hi Pratyush,

> 
> But, do we need a new mutex? Can't we use luo_session_header->rwsem?
> Session creation and release take the header rwsem at one point anyway,
> so perhaps we can just reuse that?

The sh->rwsem is for protecting the the session list. We only take it as 
a writer when modifying the list (insert/remove) and as a reader when 
traversing it. Also, we drop sh->rwsem as soon as we've acquired the 
per-session mutex to allow other list operations to proceed while a 
session is being modified.

Because of this, many session mutation operations (specifically ioctl 
calls) don't touch sh->rwsem at all?they jump straight to the  session 
state via the file's private_data. To use sh->rwsem to block
these mutations, we would be forced to add down_read(&sh->rwsem) to 
every ioctl path. This would be a layering violation, coupling list 
management to per-session data mutations, and would introduce a global
bottleneck for operations that are otherwise independent.

The only other way to prevent mutations without a new global lock would 
be for the reboot process to acquire every individual session mutex. 
However, since LUO_SESSION_MAX can be large, this would exceed lockdep's 
maximum lock tracking limit and trigger failures. The 
luo_session_serialize_rwsem provides a dedicated signal to freeze the 
entire subsystem without messing with the existing fine-grained locking 
logic.

> 
> Also, do we need to block incoming sessions? They won't participate in
> serialization, so perhaps we can leave those alone, and all the outgoing
> sessions get protected by the outgoing session header rwsem?

Incoming sessions don't participate in serialization, but blocking them 
makes the code more robust. This provides a level of future proofing if 
new ioctls or operations are added later, we won't accidentally miss a 
path that should have been frozen during reboot. It's safer to treat the 
subsystem as a single unit that freezes entirely once the transition 
begins.

Pasha


From xiaolei.wang at windriver.com  Mon May 18 20:17:06 2026
From: xiaolei.wang at windriver.com (Xiaolei Wang)
Date: Tue, 19 May 2026 11:17:06 +0800
Subject: [PATCH] arm64: Add 5-level page table support for 4K pages with 52-bit VA
Message-ID: <20260519031706.3511081-1-xiaolei.wang@windriver.com>

From: Xiaolei wang <xiaolei.wang at windriver.com>

makedumpfile fails with "PAGE SIZE 0x1000 and VA Bits 52 not supported"
on ARM64 systems configured with CONFIG_ARM64_VA_BITS_52=y and 4KB page
size. This combination requires 5-level page tables, which makedumpfile
does not currently support.

Add support for 5-level page tables on ARM64 by:
- Adding the P4D page table level definitions and helpers
- Updating pud_t to contain p4d_t (reflecting the correct hierarchy)
- Extending calculate_plat_config() to recognize 4K + 52-bit VA as
  pgtable_level=5, and 16K + 52-bit VA as pgtable_level=4
- Adding p4d_offset() to handle the additional translation level
- Updating vaddr_to_paddr_arm64() to walk through PGD->P4D->PUD->PMD->PTE
- Using vabits_actual instead of va_bits in calculate_plat_config() and
  PTRS_PER_PGD to correctly handle the case where the kernel is compiled
  with VA_BITS=52 but the hardware only supports 48-bit VA (runtime
  fallback to 4-level)

The pgtable_level assignments follow arch/arm64/Kconfig:
- 4K  + 52-bit VA ? 5-level (the only 5-level configuration)
- 16K + 52-bit VA ? 4-level
- 4K  + 48-bit VA ? 4-level
- 16K + 48-bit VA ? 4-level

Fixes: https://github.com/makedumpfile/makedumpfile/issues/18
Signed-off-by: Xiaolei Wang <xiaolei.wang at windriver.com>
---
 arch/arm64.c | 89 +++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 74 insertions(+), 15 deletions(-)

diff --git a/arch/arm64.c b/arch/arm64.c
index 1072178..f3f33e4 100644
--- a/arch/arm64.c
+++ b/arch/arm64.c
@@ -29,6 +29,10 @@ typedef struct {
 
 typedef struct {
 	pgd_t pgd;
+} p4d_t;
+
+typedef struct {
+	p4d_t p4d;
 } pud_t;
 
 typedef struct {
@@ -42,6 +46,7 @@ typedef struct {
 #define __pte(x)	((pte_t) { (x) } )
 #define __pmd(x)	((pmd_t) { (x) } )
 #define __pud(x)	((pud_t) { (x) } )
+#define __p4d(x)	((p4d_t) { (x) } )
 #define __pgd(x)	((pgd_t) { (x) } )
 
 static int lpa_52_bit_support_available;
@@ -62,7 +67,8 @@ static unsigned long kimage_voffset;
 #define PAGE_OFFSET_48		((0xffffffffffffffffUL) << 48)
 
 #define pgd_val(x)		((x).pgd)
-#define pud_val(x)		(pgd_val((x).pgd))
+#define p4d_val(x)		(pgd_val((x).pgd))
+#define pud_val(x)		(p4d_val((x).p4d))
 #define pmd_val(x)		(pud_val((x).pud))
 #define pte_val(x)		((x).pte)
 
@@ -75,6 +81,7 @@ static unsigned long kimage_voffset;
 typedef unsigned long pteval_t;
 typedef unsigned long pmdval_t;
 typedef unsigned long pudval_t;
+typedef unsigned long p4dval_t;
 typedef unsigned long pgdval_t;
 
 #define PAGE_SHIFT	PAGESHIFT()
@@ -101,6 +108,14 @@ typedef unsigned long pgdval_t;
 #define PUD_MASK		(~(PUD_SIZE-1))
 #define PTRS_PER_PUD		PTRS_PER_PTE
 
+/*
+ * P4D_SHIFT determines the size a level 0 page table entry can map.
+ */
+#define P4D_SHIFT		ARM64_HW_PGTABLE_LEVEL_SHIFT(0)
+#define P4D_SIZE		(_AC(1, UL) << P4D_SHIFT)
+#define P4D_MASK		(~(P4D_SIZE-1))
+#define PTRS_PER_P4D		PTRS_PER_PTE
+
 /*
  * PGDIR_SHIFT determines the size a top-level page table entry can map
  * (depending on the configuration, this level can be 0, 1 or 2).
@@ -108,7 +123,7 @@ typedef unsigned long pgdval_t;
 #define PGDIR_SHIFT		ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - (pgtable_level))
 #define PGDIR_SIZE		(_AC(1, UL) << PGDIR_SHIFT)
 #define PGDIR_MASK		(~(PGDIR_SIZE-1))
-#define PTRS_PER_PGD		(1 << ((va_bits) - PGDIR_SHIFT))
+#define PTRS_PER_PGD		(1 << ((vabits_actual) - PGDIR_SHIFT))
 
 /*
  * Section address mask and size definitions.
@@ -178,6 +193,22 @@ pgd_pte(pgd_t pgd)
 #define __pgd_to_phys(pgd)		__pte_to_phys(pgd_pte(pgd))
 #define pgd_offset(pgd, vaddr)		((pgd_t *)(pgd) + pgd_index(vaddr))
 
+/* P4D */
+#define p4d_index(vaddr)		(((vaddr) >> P4D_SHIFT) & (PTRS_PER_P4D - 1))
+
+static inline pte_t p4d_pte(p4d_t p4d)
+{
+	return __pte(p4d_val(p4d));
+}
+
+#define __p4d_to_phys(p4d)		__pte_to_phys(p4d_pte(p4d))
+
+static inline unsigned long
+p4d_page_paddr(p4d_t p4d)
+{
+	return __p4d_to_phys(p4d);
+}
+
 static inline pte_t pud_pte(pud_t pud)
 {
 	return __pte(pud_val(pud));
@@ -237,13 +268,22 @@ __pa(unsigned long vaddr)
 		return (vaddr - kimage_voffset);
 }
 
+static p4d_t *
+p4d_offset(pgd_t *pgda, pgd_t *pgdv, unsigned long vaddr)
+{
+	if (pgtable_level > 4)
+		return (p4d_t *)(pgd_page_paddr(*pgdv) + p4d_index(vaddr) * sizeof(p4d_t));
+	else
+		return (p4d_t *)(pgda);
+}
+
 static pud_t *
-pud_offset(pgd_t *pgda, pgd_t *pgdv, unsigned long vaddr)
+pud_offset(p4d_t *p4da, p4d_t *p4dv, unsigned long vaddr)
 {
 	if (pgtable_level > 3)
-		return (pud_t *)(pgd_page_paddr(*pgdv) + pud_index(vaddr) * sizeof(pud_t));
+		return (pud_t *)(p4d_page_paddr(*p4dv) + pud_index(vaddr) * sizeof(pud_t));
 	else
-		return (pud_t *)(pgda);
+		return (pud_t *)(p4da);
 }
 
 static pmd_t *
@@ -257,20 +297,32 @@ pmd_offset(pud_t *puda, pud_t *pudv, unsigned long vaddr)
 
 static int calculate_plat_config(void)
 {
-	/* derive pgtable_level as per arch/arm64/Kconfig */
-	if ((PAGESIZE() == SZ_16K && va_bits == 36) ||
-			(PAGESIZE() == SZ_64K && va_bits == 42)) {
+	/*
+	 * Derive pgtable_level as per arch/arm64/Kconfig.
+	 * Use vabits_actual (runtime VA size) rather than va_bits (compile-time)
+	 * because the kernel may reduce the VA space at boot if the hardware
+	 * does not support 52-bit VA (LVA). In that case va_bits=52 but
+	 * vabits_actual=48, and the page tables are 4-level, not 5-level.
+	 */
+	int va = vabits_actual;
+
+	if ((PAGESIZE() == SZ_16K && va == 36) ||
+			(PAGESIZE() == SZ_64K && va == 42)) {
 		pgtable_level = 2;
-	} else if ((PAGESIZE() == SZ_64K && va_bits == 48) ||
-			(PAGESIZE() == SZ_64K && va_bits == 52) ||
-			(PAGESIZE() == SZ_4K && va_bits == 39) ||
-			(PAGESIZE() == SZ_16K && va_bits == 47)) {
+	} else if ((PAGESIZE() == SZ_64K && va == 48) ||
+			(PAGESIZE() == SZ_64K && va == 52) ||
+			(PAGESIZE() == SZ_4K && va == 39) ||
+			(PAGESIZE() == SZ_16K && va == 47)) {
 		pgtable_level = 3;
-	} else if ((PAGESIZE() != SZ_64K && va_bits == 48)) {
+	} else if ((PAGESIZE() == SZ_4K && va == 48) ||
+			(PAGESIZE() == SZ_16K && va == 48) ||
+			(PAGESIZE() == SZ_16K && va == 52)) {
 		pgtable_level = 4;
+	} else if (PAGESIZE() == SZ_4K && va == 52) {
+		pgtable_level = 5;
 	} else {
 		ERRMSG("PAGE SIZE %#lx and VA Bits %d not supported\n",
-				PAGESIZE(), va_bits);
+				PAGESIZE(), va);
 		return FALSE;
 	}
 	DEBUG_MSG("pgtable_level: %d\n", pgtable_level);
@@ -548,6 +600,7 @@ vaddr_to_paddr_arm64(unsigned long vaddr)
 	unsigned long long paddr = NOT_PADDR;
 	unsigned long long swapper_phys;
 	pgd_t	*pgda, pgdv;
+	p4d_t	*p4da, p4dv;
 	pud_t	*puda, pudv;
 	pmd_t	*pmda, pmdv;
 	pte_t 	*ptea, ptev;
@@ -565,7 +618,13 @@ vaddr_to_paddr_arm64(unsigned long vaddr)
 		return NOT_PADDR;
 	}
 
-	puda = pud_offset(pgda, &pgdv, vaddr);
+	p4da = p4d_offset(pgda, &pgdv, vaddr);
+	if (!readmem(PADDR, (unsigned long long)p4da, &p4dv, sizeof(p4dv))) {
+		ERRMSG("Can't read p4d\n");
+		return NOT_PADDR;
+	}
+
+	puda = pud_offset(p4da, &p4dv, vaddr);
 	if (!readmem(PADDR, (unsigned long long)puda, &pudv, sizeof(pudv))) {
 		ERRMSG("Can't read pud\n");
 		return NOT_PADDR;
-- 
2.43.0



From k-hagio-ab at nec.com  Mon May 18 21:56:37 2026
From: k-hagio-ab at nec.com (=?iso-2022-jp?B?SEFHSU8gS0FaVUhJVE8oGyRCR2tIeCEhMGw/ThsoQik=?=)
Date: Tue, 19 May 2026 04:56:37 +0000
Subject: [PATCH makedumpfile] Fix wrong exclusion of compound tail pages on
 Linux 7.1 and later
Message-ID: <1779166595-32596-1-git-send-email-k-hagio-ab@nec.com>

From: Kazuhito Hagio <k-hagio-ab at nec.com>

* Required for kernel 7.1

Kernel commit d50569612c29 ("mm: rename the 'compound_head' field in the
'struct page' to 'compound_info'), which is contained in Linux 7.1 and
later kernels, renamed its vmcoreinfo entry too.

Without the patch, makedumpfile cannot get the offset of it from the
vmcoreinfo, and cannot determine whether a page is a tail page or not.
As a result, makedumpfile can exclude required tail pages wrongly.

Signed-off-by: Kazuhito Hagio <k-hagio-ab at nec.com>
---
 makedumpfile.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/makedumpfile.c b/makedumpfile.c
index 12fb0d816726..80637a7233f3 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -1751,6 +1751,9 @@ get_structure_info(void)
 	OFFSET_INIT(page.compound_dtor, "page", "compound_dtor");
 	OFFSET_INIT(page.compound_order, "page", "compound_order");
 	OFFSET_INIT(page.compound_head, "page", "compound_head");
+	/* Linux 7.1 and later */
+	if (OFFSET(page.compound_head) == NOT_FOUND_STRUCTURE)
+		OFFSET_INIT(page.compound_head, "page", "compound_info");
 	/* Linux 6.3 and later */
 	OFFSET_INIT(folio._folio_dtor, "folio", "_folio_dtor");
 	OFFSET_INIT(folio._folio_order, "folio", "_folio_order");
@@ -2426,6 +2429,10 @@ write_vmcoreinfo_data(void)
 	WRITE_MEMBER_OFFSET("page.private", page.private);
 	WRITE_MEMBER_OFFSET("page.compound_dtor", page.compound_dtor);
 	WRITE_MEMBER_OFFSET("page.compound_order", page.compound_order);
+	/*
+	 * on Linux 7.1 and later, this has page.compound_info's value, but
+	 * it's OK to write it as 'page.compound_head'. See read_vmcoreinfo().
+	 */
 	WRITE_MEMBER_OFFSET("page.compound_head", page.compound_head);
 	/* Linux 6.3 and later */
 	WRITE_MEMBER_OFFSET("folio._folio_dtor", folio._folio_dtor);
@@ -2876,6 +2883,8 @@ read_vmcoreinfo(void)
 	READ_MEMBER_OFFSET("page.compound_dtor", page.compound_dtor);
 	READ_MEMBER_OFFSET("page.compound_order", page.compound_order);
 	READ_MEMBER_OFFSET("page.compound_head", page.compound_head);
+	/* Linux 7.1 and later */
+	READ_MEMBER_OFFSET("page.compound_info", page.compound_head);
 	/* Linux 6.3 and later */
 	READ_MEMBER_OFFSET("folio._folio_dtor", folio._folio_dtor);
 	READ_MEMBER_OFFSET("folio._folio_order", folio._folio_order);
-- 
2.31.1


From horms at kernel.org  Tue May 19 02:41:53 2026
From: horms at kernel.org (Simon Horman)
Date: Tue, 19 May 2026 10:41:53 +0100
Subject: [PATCH] kexec: restore functionality to preserve fd xattrs for
 uncompressed kernel images
In-Reply-To: <20260507215417.2674568-1-alhe@linux.microsoft.com>
References: <20260507215417.2674568-1-alhe@linux.microsoft.com>
Message-ID: <20260519094153.GA227450@horms.kernel.org>

On Thu, May 07, 2026 at 09:54:17PM +0000, Alejandro Hernandez wrote:
> Since commit 714fa115 xattrs attached to a kernel image file (such as IMA
> signatures) are not being preserved, do_kexec_file_load() was modified to use
> memfd_create() which now passes an anonymous file descriptor to
> kexec_file_load(). This change eliminated the filesystem inode identity of the
> original kernel file, hence attributes are no longer visible to the kernel IMA
> appraisal handler during kexec_file_load, causing IMA policy enforcement to fail
> even for validly signed kernel images.
> 
> This patch attempts to restore such behavior although, only does it for
> uncompressed kernel images. To do this, we first figure out if the image file is
> compressed or not and call each method accordingly.
> 
> Compressed images continue to use memfd and cannot carry forward the original fd
> security attributes, since their decompressed bytes do not match the signed
> artifact. Proper handling of compressed images with IMA would require either
> signing the decompressed artifact or kernel-side support for decompression
> within the kexec_file_load path.
> 
> Signed-off-by: Alejandro Hernandez Samaniego <alhe at linux.microsoft.com>

Hi Alejandro,

Thanks for the patch.

Overall this looks good to me. But I'm wondering if you could spin a v2
to address a minor code-style issue I have noted inline.

> ---
>  kexec/kexec.c | 42 +++++++++++++++++++++++++++++++++++-------
>  1 file changed, 35 insertions(+), 7 deletions(-)
> 
> diff --git a/kexec/kexec.c b/kexec/kexec.c
> index 08edfca..a388762 100644
> --- a/kexec/kexec.c
> +++ b/kexec/kexec.c
> @@ -1328,17 +1328,41 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
>  
>  	kernel = argv[fileind];
>  
> -	/* slurp in the input kernel */
> +        /* Hold original fd with its xattrs */
> +	kernel_fd = open(kernel, O_RDONLY);
> +	if (kernel_fd == -1) {
> +		fprintf(stderr, "Failed to open file %s:%s\n", kernel,
> +				strerror(errno));
> +		return EFAILED;
> +	}
> +
> +	/* Compressed vs Uncompressed */
> +	struct stat kernel_stat;
> +	off_t kernel_file_size = -1;

I would prefer if we stuck to declaring local variables
at the top of this function.

> +	if (fstat(kernel_fd, &kernel_stat) == 0)
> +		kernel_file_size = kernel_stat.st_size;
> +
> +        /* slurp in the input kernel */
>  	kernel_buf = slurp_decompress_file(kernel, &kernel_size);
>  	if (!kernel_buf) {
>  		fprintf(stderr, "Failed to decompress file %s:%s\n", kernel,
>  				strerror(errno));
> +		close(kernel_fd);
>  		return EFAILED;
>  	}

...


From pratyush at kernel.org  Tue May 19 05:24:26 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 14:24:26 +0200
Subject: [PATCH] liveupdate: validate session type before performing operation
Message-ID: <20260519122428.2378446-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

The sessions ioctls are not applicable to all session types. PRESERVE_FD
is only applicable to outgoing sessions. RETRIEVE_FD and FINISH are only
valid for incoming session. Calling a incoming ioctl on an outgoing
session is invalid and can cause file handlers to run into unexpected
errors.

For example, a user can create a (outgoing) session, preserve a memfd,
and then immediately do a retrieve without doing a kexec in between.
This would result in memfd's retrieve handler to run. The handlers
expects to be called from a post-kexec context, and will try to do a
kho_restore_vmalloc() or kho_restore_folio() to try and restore memory.

KHO catches this (thanks to KHO_PAGE_MAGIC) and returns an error, but
since this is considered an internal error and KHO throws out a bunch of
WARN()s.

Associate a type with each ioctl op and validate the type in
luo_session_ioctl() before dispatching the ioctl handler to make sure
the op is being called for the right session type.

Fixes: 16cec0d26521 ("liveupdate: luo_session: add ioctls for file preservation")
Cc: stable at vger.kernel.org
Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---

Notes:
    I added LUO_IOCTL_ALL but there is no user in this patch. The type is
    for LIVEUPDATE_SESSION_GET_NAME which is supported for both session
    types. The support for GET_NAME is in next and this patch should go
    through fixes.
    
    Alternatively, we can remove LUO_IOCTL_ALL from this patch and add it to
    the LIVEUPDATE_SESSION_GET_NAME patch in next. But that would need us to
    rebase to an rc that contains this fix.

 kernel/liveupdate/luo_session.c | 36 +++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..e84218e3cacb 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -295,32 +295,58 @@ union ucmd_buffer {
 	struct liveupdate_session_retrieve_fd retrieve;
 };
 
+/* Type of sessions the ioctl applies to. */
+enum luo_ioctl_type {
+	LUO_IOCTL_INCOMING,
+	LUO_IOCTL_OUTGOING,
+	LUO_IOCTL_ALL,
+};
+
 struct luo_ioctl_op {
 	unsigned int size;
 	unsigned int min_size;
 	unsigned int ioctl_num;
+	enum luo_ioctl_type type;
 	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
 };
 
-#define IOCTL_OP(_ioctl, _fn, _struct, _last)                                  \
+#define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
 	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
 		.size = sizeof(_struct) +                                      \
 			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
 					  sizeof(_struct)),                    \
 		.min_size = offsetofend(_struct, _last),                       \
 		.ioctl_num = _ioctl,                                           \
+		.type = _type,                                                 \
 		.execute = _fn,                                                \
 	}
 
 static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
 	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
-		 struct liveupdate_session_finish, reserved),
+		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
 	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
-		 struct liveupdate_session_preserve_fd, token),
+		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
 	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
-		 struct liveupdate_session_retrieve_fd, token),
+		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
 };
 
+static bool luo_ioctl_type_valid(struct luo_session *session,
+				 const struct luo_ioctl_op *op)
+{
+	switch (op->type) {
+	case LUO_IOCTL_INCOMING:
+		/* Retrieved is only set on incoming sessions */
+		return session->retrieved;
+	case LUO_IOCTL_OUTGOING:
+		return !session->retrieved;
+	case LUO_IOCTL_ALL:
+		return true;
+	}
+
+	/* Catch-all. */
+	return false;
+}
+
 static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 			      unsigned long arg)
 {
@@ -345,6 +371,8 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
 	if (op->ioctl_num != cmd)
 		return -ENOIOCTLCMD;
+	if (!luo_ioctl_type_valid(session, op))
+		return -EINVAL;
 	if (ucmd.user_size < op->min_size)
 		return -EINVAL;
 

base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.54.0.563.g4f69b47b94-goog



From horms at kernel.org  Tue May 19 05:44:12 2026
From: horms at kernel.org (Simon Horman)
Date: Tue, 19 May 2026 13:44:12 +0100
Subject: [PATCH 3/3] RISC-V: add bss section to kernel text elf header
In-Reply-To: <20260518150140578xGhdr-ct94HGGS4gmP_it@zte.com.cn>
References: <20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn>
 <20260518150140578xGhdr-ct94HGGS4gmP_it@zte.com.cn>
Message-ID: <20260519124412.GB227450@horms.kernel.org>

On Mon, May 18, 2026 at 03:01:40PM +0800, hu.shengming at zte.com.cn wrote:
> From: Luo Haiyang <luo.haiyang at zte.com.cn>
> 
> The kdump service reported the following error:
> 
>   No program header covering vaddr 0xffffffff819fa200 found kexec bug?
>   saving vmcore-dmesg.txt failed
> 
> prb stores the address of printk_rb_dynamic. Since printk_rb_dynamic
> resides in the bss segment and the PHDR (Program Header) does not include
> the bss segment address. Therefore,  add the bss segment to the Kernel text.
> 
> Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>

Hi,

I am wondering if to_be_excluded() also needs to be updated.
And, if so, should that change also be made for ARM64.

Review assisted by gemini-3-pro-preview


From pratyush at kernel.org  Tue May 19 05:57:06 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 14:57:06 +0200
Subject: [PATCH] liveupdate: document liveupdate=on
Message-ID: <20260519125714.2435640-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

While the liveupdate= parameter is documented in kernel-parameters.txt,
it is not listed in LUO's user facing documentation. This can make it
hard for users to figure out how to enable the subsystem, since enabling
just the config isn't enough.

Note the need for the kernel parameter in LUO core documentation, which
gets exported to Documentation/core-api/liveupdate.rst.

Suggested-by: Luca Boccassi <luca.boccassi at gmail.com>
Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---

Notes:
    I think we should take this patch through the liveupdate/fixes branch.

 kernel/liveupdate/luo_core.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..5d5827ced73c 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -36,6 +36,10 @@
  *
  * LUO uses Kexec Handover to transfer memory state from the current kernel to
  * the next kernel. For more details see Documentation/core-api/kho/index.rst.
+ *
+ * .. note::
+ *     To enable LUO, boot the kernel with the ``liveupdate=on`` command line
+ *     parameter.
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.54.0.563.g4f69b47b94-goog



From luca.boccassi at gmail.com  Tue May 19 05:58:53 2026
From: luca.boccassi at gmail.com (Luca Boccassi)
Date: Tue, 19 May 2026 13:58:53 +0100
Subject: [PATCH] liveupdate: document liveupdate=on
In-Reply-To: <20260519125714.2435640-1-pratyush@kernel.org>
References: <20260519125714.2435640-1-pratyush@kernel.org>
Message-ID: <CAMw=ZnTotDOXfh=9zHtdRpyt9QRSQ+y=c1N+U1=f1wdzOtJzig@mail.gmail.com>

On Tue, 19 May 2026 at 13:57, Pratyush Yadav <pratyush at kernel.org> wrote:
>
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
> While the liveupdate= parameter is documented in kernel-parameters.txt,
> it is not listed in LUO's user facing documentation. This can make it
> hard for users to figure out how to enable the subsystem, since enabling
> just the config isn't enough.
>
> Note the need for the kernel parameter in LUO core documentation, which
> gets exported to Documentation/core-api/liveupdate.rst.
>
> Suggested-by: Luca Boccassi <luca.boccassi at gmail.com>
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>
> Notes:
>     I think we should take this patch through the liveupdate/fixes branch.
>
>  kernel/liveupdate/luo_core.c | 4 ++++
>  1 file changed, 4 insertions(+)

Acked-by: Luca Boccassi <luca.boccassi at gmail.com>


From pratyush at kernel.org  Tue May 19 06:33:30 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 15:33:30 +0200
Subject: [PATCH] kho: fix order calculation for kho_unpreserve_pages()
Message-ID: <20260519133332.2498092-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Commit 91e74fa8b1bc ("kho: make sure preservations do not span multiple
NUMA nodes") made sure preservations from kho_preserve_pages() do not
span multiple NUMA nodes. If they do, the order is reduced and tried
again.

The same logic was not implemented for kho_unpreserve_pages(). This can
result in unpreserve calculating a different order than preserve, and
thus not actually unpreserving the pages.

Fix this by moving the order calculation logic to
__kho_preserve_pages_order() and use it from both preserve and
unpreserve paths.

Move __kho_unpreserve() down to avoid having a forward declaration. Its
users are further down in the file anyway. Also, it results in grouping
for all the page-level preservation and unpreservation functions. This
unfortunately makes the diff hard to read, but the main change in
__kho_unpreserve() is to call __kho_preserve_pages_order() instead of
open-coding the order calculation.

Fixes: 91e74fa8b1bc ("kho: make sure preservations do not span multiple NUMA nodes")
Cc: stable at vger.kernel.org
Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---
 kernel/liveupdate/kexec_handover.c | 56 +++++++++++++++++-------------
 1 file changed, 32 insertions(+), 24 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4fde8325c49f..11855e275397 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -357,20 +357,6 @@ int kho_radix_walk_tree(struct kho_radix_tree *tree,
 }
 EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
 
-static void __kho_unpreserve(struct kho_radix_tree *tree,
-			     unsigned long pfn, unsigned long end_pfn)
-{
-	unsigned int order;
-
-	while (pfn < end_pfn) {
-		order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-
-		kho_radix_del_page(tree, pfn, order);
-
-		pfn += 1 << order;
-	}
-}
-
 /* For physically contiguous 0-order pages. */
 static void kho_init_pages(struct page *page, unsigned long nr_pages)
 {
@@ -855,6 +841,37 @@ void kho_unpreserve_folio(struct folio *folio)
 }
 EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
 
+static unsigned int __kho_preserve_pages_order(unsigned long start_pfn,
+					       unsigned long end_pfn)
+{
+	unsigned int order = min(count_trailing_zeros(start_pfn),
+				 ilog2(end_pfn - start_pfn));
+
+	/*
+	 * Make sure all the pages in a single preservation are in the same NUMA
+	 * node. The restore machinery can not cope with a preservation spanning
+	 * multiple NUMA nodes.
+	 */
+	while (pfn_to_nid(start_pfn) != pfn_to_nid(start_pfn + (1UL << order) - 1))
+		order--;
+
+	return order;
+}
+
+static void __kho_unpreserve(struct kho_radix_tree *tree,
+			     unsigned long pfn, unsigned long end_pfn)
+{
+	unsigned int order;
+
+	while (pfn < end_pfn) {
+		order = __kho_preserve_pages_order(pfn, end_pfn);
+
+		kho_radix_del_page(tree, pfn, order);
+
+		pfn += 1 << order;
+	}
+}
+
 /**
  * kho_preserve_pages - preserve contiguous pages across kexec
  * @page: first page in the list.
@@ -880,16 +897,7 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
 	}
 
 	while (pfn < end_pfn) {
-		unsigned int order =
-			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-
-		/*
-		 * Make sure all the pages in a single preservation are in the
-		 * same NUMA node. The restore machinery can not cope with a
-		 * preservation spanning multiple NUMA nodes.
-		 */
-		while (pfn_to_nid(pfn) != pfn_to_nid(pfn + (1UL << order) - 1))
-			order--;
+		unsigned int order = __kho_preserve_pages_order(pfn, end_pfn);
 
 		err = kho_radix_add_page(tree, pfn, order);
 		if (err) {

base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.54.0.563.g4f69b47b94-goog



From pasha.tatashin at soleen.com  Tue May 19 07:03:31 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 19 May 2026 14:03:31 +0000
Subject: [PATCH] liveupdate: document liveupdate=on
In-Reply-To: <20260519125714.2435640-1-pratyush@kernel.org>
References: <20260519125714.2435640-1-pratyush@kernel.org>
Message-ID: <177919941198.1116313.3518745939102714040.b4-ty@soleen.com>


On Tue, 19 May 2026 14:57:06 +0200, Pratyush Yadav wrote:
> While the liveupdate= parameter is documented in kernel-parameters.txt,
> it is not listed in LUO's user facing documentation. This can make it
> hard for users to figure out how to enable the subsystem, since enabling
> just the config isn't enough.
> 
> Note the need for the kernel parameter in LUO core documentation, which
> gets exported to Documentation/core-api/liveupdate.rst.
> 
> [...]

Applied, thanks!

[1/1] liveupdate: document liveupdate=on
      commit: d65d7e28888a6f3a1999908cacadd99f4c14ddd9

Best regards,
-- 
Pasha Tatashin <pasha.tatashin at soleen.com>



From pasha.tatashin at soleen.com  Tue May 19 07:11:26 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 19 May 2026 14:11:26 +0000
Subject: [PATCH] liveupdate: validate session type before performing
 operation
In-Reply-To: <20260519122428.2378446-1-pratyush@kernel.org>
References: <20260519122428.2378446-1-pratyush@kernel.org>
Message-ID: <agxuNavjtB8T_xRO@plex>

On 05-19 14:24, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The sessions ioctls are not applicable to all session types. PRESERVE_FD
> is only applicable to outgoing sessions. RETRIEVE_FD and FINISH are only
> valid for incoming session. Calling a incoming ioctl on an outgoing
> session is invalid and can cause file handlers to run into unexpected
> errors.
> 
> For example, a user can create a (outgoing) session, preserve a memfd,
> and then immediately do a retrieve without doing a kexec in between.

Please add a self-test tools/testing/selftests/liveupdate/liveupdate.c
to verify that outgoing sessions do not accept retrieve_fd ioctl.
Option, you could also add to luo_multi_session.c a test to verifying 
that incoming does not accept preserve_fd

> This would result in memfd's retrieve handler to run. The handlers
> expects to be called from a post-kexec context, and will try to do a
> kho_restore_vmalloc() or kho_restore_folio() to try and restore memory.
> 
> KHO catches this (thanks to KHO_PAGE_MAGIC) and returns an error, but
> since this is considered an internal error and KHO throws out a bunch of
> WARN()s.
> 
> Associate a type with each ioctl op and validate the type in
> luo_session_ioctl() before dispatching the ioctl handler to make sure
> the op is being called for the right session type.
> 
> Fixes: 16cec0d26521 ("liveupdate: luo_session: add ioctls for file preservation")
> Cc: stable at vger.kernel.org
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
> 
> Notes:
>     I added LUO_IOCTL_ALL but there is no user in this patch. The type is
>     for LIVEUPDATE_SESSION_GET_NAME which is supported for both session
>     types. The support for GET_NAME is in next and this patch should go
>     through fixes.
>     
>     Alternatively, we can remove LUO_IOCTL_ALL from this patch and add it to
>     the LIVEUPDATE_SESSION_GET_NAME patch in next. But that would need us to
>     rebase to an rc that contains this fix.

Let's keep LUO_IOCTL_ALL change in this patch.

Please add tests, otherwise LGTM.

Pasha

> 
>  kernel/liveupdate/luo_session.c | 36 +++++++++++++++++++++++++++++----
>  1 file changed, 32 insertions(+), 4 deletions(-)
> 
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index a3327a28fc1f..e84218e3cacb 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -295,32 +295,58 @@ union ucmd_buffer {
>  	struct liveupdate_session_retrieve_fd retrieve;
>  };
>  
> +/* Type of sessions the ioctl applies to. */
> +enum luo_ioctl_type {
> +	LUO_IOCTL_INCOMING,
> +	LUO_IOCTL_OUTGOING,
> +	LUO_IOCTL_ALL,
> +};
> +
>  struct luo_ioctl_op {
>  	unsigned int size;
>  	unsigned int min_size;
>  	unsigned int ioctl_num;
> +	enum luo_ioctl_type type;
>  	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
>  };
>  
> -#define IOCTL_OP(_ioctl, _fn, _struct, _last)                                  \
> +#define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
>  	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
>  		.size = sizeof(_struct) +                                      \
>  			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
>  					  sizeof(_struct)),                    \
>  		.min_size = offsetofend(_struct, _last),                       \
>  		.ioctl_num = _ioctl,                                           \
> +		.type = _type,                                                 \
>  		.execute = _fn,                                                \
>  	}
>  
>  static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
>  	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
> -		 struct liveupdate_session_finish, reserved),
> +		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
>  	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
> -		 struct liveupdate_session_preserve_fd, token),
> +		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
>  	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
> -		 struct liveupdate_session_retrieve_fd, token),
> +		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
>  };
>  
> +static bool luo_ioctl_type_valid(struct luo_session *session,
> +				 const struct luo_ioctl_op *op)
> +{
> +	switch (op->type) {
> +	case LUO_IOCTL_INCOMING:
> +		/* Retrieved is only set on incoming sessions */
> +		return session->retrieved;
> +	case LUO_IOCTL_OUTGOING:
> +		return !session->retrieved;
> +	case LUO_IOCTL_ALL:
> +		return true;
> +	}
> +
> +	/* Catch-all. */
> +	return false;
> +}
> +
>  static long luo_session_ioctl(struct file *filep, unsigned int cmd,
>  			      unsigned long arg)
>  {
> @@ -345,6 +371,8 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
>  	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
>  	if (op->ioctl_num != cmd)
>  		return -ENOIOCTLCMD;
> +	if (!luo_ioctl_type_valid(session, op))
> +		return -EINVAL;
>  	if (ucmd.user_size < op->min_size)
>  		return -EINVAL;
>  
> 
> base-commit: b1378127003b61930ce30064328640503ad3ef6d
> -- 
> 2.54.0.563.g4f69b47b94-goog
> 


From pasha.tatashin at soleen.com  Tue May 19 07:13:05 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 19 May 2026 14:13:05 +0000
Subject: [PATCH] kho: fix order calculation for kho_unpreserve_pages()
In-Reply-To: <20260519133332.2498092-1-pratyush@kernel.org>
References: <20260519133332.2498092-1-pratyush@kernel.org>
Message-ID: <agxv3w--GPrc85De@plex>

On 05-19 15:33, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Commit 91e74fa8b1bc ("kho: make sure preservations do not span multiple
> NUMA nodes") made sure preservations from kho_preserve_pages() do not
> span multiple NUMA nodes. If they do, the order is reduced and tried
> again.
> 
> The same logic was not implemented for kho_unpreserve_pages(). This can
> result in unpreserve calculating a different order than preserve, and
> thus not actually unpreserving the pages.
> 
> Fix this by moving the order calculation logic to
> __kho_preserve_pages_order() and use it from both preserve and
> unpreserve paths.
> 
> Move __kho_unpreserve() down to avoid having a forward declaration. Its
> users are further down in the file anyway. Also, it results in grouping
> for all the page-level preservation and unpreservation functions. This
> unfortunately makes the diff hard to read, but the main change in
> __kho_unpreserve() is to call __kho_preserve_pages_order() instead of
> open-coding the order calculation.
> 
> Fixes: 91e74fa8b1bc ("kho: make sure preservations do not span multiple NUMA nodes")
> Cc: stable at vger.kernel.org
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

Pasha


From pratyush at kernel.org  Tue May 19 07:29:48 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 16:29:48 +0200
Subject: [PATCH] liveupdate: validate session type before performing
 operation
In-Reply-To: <agxuNavjtB8T_xRO@plex> (Pasha Tatashin's message of "Tue, 19 May
	2026 14:11:26 +0000")
References: <20260519122428.2378446-1-pratyush@kernel.org>
	<agxuNavjtB8T_xRO@plex>
Message-ID: <2vxzfr3n8obn.fsf@kernel.org>

On Tue, May 19 2026, Pasha Tatashin wrote:

> On 05-19 14:24, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> The sessions ioctls are not applicable to all session types. PRESERVE_FD
>> is only applicable to outgoing sessions. RETRIEVE_FD and FINISH are only
>> valid for incoming session. Calling a incoming ioctl on an outgoing
>> session is invalid and can cause file handlers to run into unexpected
>> errors.
>> 
>> For example, a user can create a (outgoing) session, preserve a memfd,
>> and then immediately do a retrieve without doing a kexec in between.
>
> Please add a self-test tools/testing/selftests/liveupdate/liveupdate.c
> to verify that outgoing sessions do not accept retrieve_fd ioctl.
> Option, you could also add to luo_multi_session.c a test to verifying 
> that incoming does not accept preserve_fd

Right, forgot about that. Will do.

>
>> This would result in memfd's retrieve handler to run. The handlers
>> expects to be called from a post-kexec context, and will try to do a
>> kho_restore_vmalloc() or kho_restore_folio() to try and restore memory.
>> 
>> KHO catches this (thanks to KHO_PAGE_MAGIC) and returns an error, but
>> since this is considered an internal error and KHO throws out a bunch of
>> WARN()s.
>> 
>> Associate a type with each ioctl op and validate the type in
>> luo_session_ioctl() before dispatching the ioctl handler to make sure
>> the op is being called for the right session type.
>> 
>> Fixes: 16cec0d26521 ("liveupdate: luo_session: add ioctls for file preservation")
>> Cc: stable at vger.kernel.org
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From luca.boccassi at gmail.com  Tue May 19 08:28:39 2026
From: luca.boccassi at gmail.com (luca.boccassi at gmail.com)
Date: Tue, 19 May 2026 16:28:39 +0100
Subject: [PATCH] liveupdate: document systemd support
Message-ID: <20260519152957.548119-1-luca.boccassi@gmail.com>

From: Luca Boccassi <luca.boccassi at gmail.com>

systemd v261 will add native support for LUO via the file descriptor
store mechanism. Add a brief paragraph in the LUO userspace documentation
page to inform users.

Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>
---
NOTE: the external links will mention LUO after the next release is
      published, which should happen within ~4 weeks

 Documentation/userspace-api/liveupdate.rst | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
index 41c0473e4f16..f6efc32fb40b 100644
--- a/Documentation/userspace-api/liveupdate.rst
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -14,6 +14,29 @@ ioctl uAPI
 ===========
 .. kernel-doc:: include/uapi/linux/liveupdate.h
 
+Userspace Integration
+=====================
+
+systemd (since version v261) uses LUO to preserve its per-service file
+descriptor store across a kexec-based live update. Services opt in by setting
+``FileDescriptorStoreMax=`` and ``FileDescriptorStorePreserve=`` in their unit,
+and push file descriptors with a name into the store via
+``sd_pid_notify_with_fds(... "FDSTORE=1\nFDNAME=foo")``.
+
+Services may also create their own LUO sessions (via ``/dev/liveupdate``) and
+push the resulting session fds into their file descriptor store like any other
+fd. systemd detects such session fds and handles them accordingly, and
+hands the re-retrieved session fd back to the service after kexec, using the
+existing file descriptor store service interface.
+
+For details, see:
+
+- `File Descriptor Store <https://systemd.io/FILE_DESCRIPTOR_STORE/>`_
+- `systemd.service(5) FileDescriptorStorePreserve=
+  <https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#FileDescriptorStorePreserve=>`_
+- `sd_pid_notify_with_fds(3)
+  <https://www.freedesktop.org/software/systemd/man/latest/sd_pid_notify_with_fds.html>`_
+
 See Also
 ========
 
-- 
2.47.3



From pratyush at kernel.org  Tue May 19 08:53:21 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 17:53:21 +0200
Subject: [PATCH] kho: always print scratch sizes
Message-ID: <20260519155324.2692594-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Currently, KHO only prints scratch sizes when the size is specified
by the command line. It prints nothing when the size is calculated
at runtime by counting RSRV_KERN.

These prints are completely useless. When the user specified the size on
the command line, they already know how large the scratch areas are. And
that information is readily available by looking at the command line. It
is a lot more useful to know the sizes that are calculated at runtime
since they can vary by kernel version. Plus, if KHO fails to allocate
the scratch areas, the calculated scratch sizes are not available via
debugfs.

Print the scratch sizes in kho_reserve_scratch(). Do it before making
the allocations. The size can provide hints for the failure reason.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---
 kernel/liveupdate/kexec_handover.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 33fcf848ef95..b030f53f2235 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -621,11 +621,6 @@ static int __init kho_parse_scratch_size(char *p)
 	scratch_size_pernode = sizes[2];
 	scratch_scale = 0;
 
-	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB pernode: %lldMiB\n",
-		  (u64)(scratch_size_lowmem >> 20),
-		  (u64)(scratch_size_global >> 20),
-		  (u64)(scratch_size_pernode >> 20));
-
 	return 0;
 }
 early_param("kho_scratch", kho_parse_scratch_size);
@@ -691,6 +686,10 @@ static void __init kho_reserve_scratch(void)
 		goto err_disable_kho;
 	}
 
+	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB\n",
+		  (u64)(scratch_size_lowmem >> 20),
+		  (u64)(scratch_size_global >> 20));
+
 	/*
 	 * reserve scratch area in low memory for lowmem allocations in the
 	 * next kernel
@@ -725,6 +724,9 @@ static void __init kho_reserve_scratch(void)
 	 */
 	for_each_node_state(nid, N_MEMORY) {
 		size = scratch_size_node(nid);
+
+		pr_notice("scratch_areas: nid %d: %lluMiB\n", nid, size >> 20);
+
 		addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES,
 						0, MEMBLOCK_ALLOC_ACCESSIBLE,
 						nid, true);

base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
-- 
2.54.0.563.g4f69b47b94-goog



From pratyush at kernel.org  Tue May 19 09:05:49 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 19 May 2026 18:05:49 +0200
Subject: [PATCH] kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES
Message-ID: <20260519160554.2713361-1-pratyush@kernel.org>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

When using scratch_scale, the scratch sizes are rounded up to
CMA_MIN_ALIGNMENT_BYTES since they will be released as MIGRATE_CMA. This
is not done when using fixed scratch sizes via command line. This can
result in user specifying a size which is not aligned, and thus kernel
releasing a pageblock that is only partially scratch.

Do the rounding up for both cases in scratch_size_update().

Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
---
 kernel/liveupdate/kexec_handover.c | 32 ++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4fde8325c49f..c26e4df37479 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -607,20 +607,30 @@ early_param("kho_scratch", kho_parse_scratch_size);
 
 static void __init scratch_size_update(void)
 {
-	phys_addr_t size;
+	/*
+	 * If fixed sizes are not provided via command line, calculate them
+	 * now.
+	 */
+	if (scratch_scale) {
+		phys_addr_t size;
 
-	if (!scratch_scale)
-		return;
+		size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
+						   NUMA_NO_NODE);
+		size = size * scratch_scale / 100;
+		scratch_size_lowmem = size;
 
-	size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
-					   NUMA_NO_NODE);
-	size = size * scratch_scale / 100;
-	scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+		size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
+						   NUMA_NO_NODE);
+		size = size * scratch_scale / 100 - scratch_size_lowmem;
+		scratch_size_global = size;
+	}
 
-	size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
-					   NUMA_NO_NODE);
-	size = size * scratch_scale / 100 - scratch_size_lowmem;
-	scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+	/*
+	 * Scratch areas are released as MIGRATE_CMA. Round them up to the right
+	 * size.
+	 */
+	scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
+	scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
 }
 
 static phys_addr_t __init scratch_size_node(int nid)

base-commit: b1378127003b61930ce30064328640503ad3ef6d
-- 
2.54.0.563.g4f69b47b94-goog



From skhawaja at google.com  Tue May 19 11:47:32 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Tue, 19 May 2026 18:47:32 +0000
Subject: [PATCH] kho: fix order calculation for kho_unpreserve_pages()
In-Reply-To: <20260519133332.2498092-1-pratyush@kernel.org>
References: <20260519133332.2498092-1-pratyush@kernel.org>
Message-ID: <agyvXfrf6aX8LE19@google.com>

On Tue, May 19, 2026 at 03:33:30PM +0200, Pratyush Yadav wrote:
>From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>
>Commit 91e74fa8b1bc ("kho: make sure preservations do not span multiple
>NUMA nodes") made sure preservations from kho_preserve_pages() do not
>span multiple NUMA nodes. If they do, the order is reduced and tried
>again.
>
>The same logic was not implemented for kho_unpreserve_pages(). This can
>result in unpreserve calculating a different order than preserve, and
>thus not actually unpreserving the pages.
>
>Fix this by moving the order calculation logic to
>__kho_preserve_pages_order() and use it from both preserve and
>unpreserve paths.
>
>Move __kho_unpreserve() down to avoid having a forward declaration. Its
>users are further down in the file anyway. Also, it results in grouping
>for all the page-level preservation and unpreservation functions. This
>unfortunately makes the diff hard to read, but the main change in
>__kho_unpreserve() is to call __kho_preserve_pages_order() instead of
>open-coding the order calculation.
>
>Fixes: 91e74fa8b1bc ("kho: make sure preservations do not span multiple NUMA nodes")
>Cc: stable at vger.kernel.org
>Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Nice find, sashiko was also complaining about same on my kunit series:
https://sashiko.dev/#/patchset/20260512195135.804833-1-skhawaja%40google.com

Reviewed-by: Samiullah Khawaja <skhawaja at google.com>


From ltao at redhat.com  Tue May 19 21:55:11 2026
From: ltao at redhat.com (Tao Liu)
Date: Wed, 20 May 2026 16:55:11 +1200
Subject: [PATCH v5][makedumpfile 0/9] btf/kallsyms based makedumpfile
 extension for mm page filtering
In-Reply-To: <20260414102656.55200-1-ltao@redhat.com>
References: <20260414102656.55200-1-ltao@redhat.com>
Message-ID: <CAO7dBbXOpsFmUOO6CR+bEudHcL162neVdMTUmPK_w8tBLHNJng@mail.gmail.com>

Kindly ping, Any comments for this patchset?

Thanks,
Tao Liu

On Tue, Apr 14, 2026 at 10:27?PM Tao Liu <ltao at redhat.com> wrote:
>
> A) This patchset will introduce the following features to makedumpfile:
>
>   1) Add .so extension support to makedumpfile
>   2) Enable btf and kallsyms for symbol type and address resolving.
>
> B) The purpose of the features are:
>
>   1) Currently makedumpfile filters mm pages based on page flags, because flags
>      can help to determine one page's usage. But this page-flag-checking method
>      lacks of flexibility in certain cases, e.g. if we want to filter those mm
>      pages occupied by GPU during vmcore dumping due to:
>
>      a) GPU may be taking a large memory and contains sensitive data;
>      b) GPU mm pages have no relations to kernel crash and useless for vmcore
>         analysis.
>
>      But there is no GPU mm page specific flags, and apparently we don't need
>      to create one just for kdump use. A programmable filtering tool is more
>      suitable for such cases. In addition, different GPU vendors may use
>      different ways for mm pages allocating, programmable filtering is better
>      than hard coding these GPU specific logics into makedumpfile in this case.
>
>   2) Currently makedumpfile already contains a programmable filtering tool, aka
>      eppic script, which allows user to write customized code for data erasing.
>      However it has the following drawbacks:
>
>      a) cannot do mm page filtering.
>      b) need to access to debuginfo of both kernel and modules, which is not
>         applicable in the 2nd kernel.
>      c) eppic library has memory leaks which are not all resolved [1]. This
>         is not acceptable in 2nd kernel.
>
>      makedumpfile need to resolve the dwarf data from debuginfo, to get symbols
>      types and addresses. In recent kernel there are dwarf alternatives such
>      as btf/kallsyms which can be used for this purpose. And btf/kallsyms info
>      are already packed within vmcore, so we can use it directly.
>
>   With these, this patchset introduces makedumpfile extensions, which is based
>   on btf/kallsyms symbol resolving, and is programmable for mm page filtering.
>   The following section shows its usage and performance, please note the tests
>   are performed in 1st kernel.
>
>   3) Compile and run makedumpfile extensions:
>
>   $ make LINKTYPE=dynamic USELZO=on USESNAPPY=on USEZSTD=on EXTENSION=on
>   $ make extensions
>
>   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
>     /tmp/extension.out --extension amdgpu_filter.so
>     Loaded extension: ./extensions/amdgpu_filter.so
>     makedumpfile Completed.
>         User time (seconds): 5.08
>         System time (seconds): 0.84
>         Percent of CPU this job got: 99%
>         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:05.95
>         Maximum resident set size (kbytes): 17360
>         ...
>
>      To contrast with eppic script of v2 [2]:
>
>   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
>     /tmp/eppic.out --eppic eppic_scripts/filter_amdgpu_mm_pages.c
>     makedumpfile Completed.
>         User time (seconds): 8.23
>         System time (seconds): 0.88
>         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:09.16
>         Maximum resident set size (kbytes): 57128
>         ...
>
>   -rw------- 1 root root 367475074 Jan 19 19:01 /tmp/extension.out
>   -rw------- 1 root root 367475074 Jan 19 19:48 /tmp/eppic.out
>   -rw------- 1 root root 387181418 Jun 10 18:03 /var/crash/127.0.0.1-2025-06-10-18:03:12/vmcore
>
> C) Discussion:
>
>   1) GPU types: Currently only tested with amdgpu's mm page filtering, others
>      are not tested.
>   2) OS: The code can work on rhel-10+/rhel9.5+ on x86_64/arm64/s390/ppc64.
>      rhel8.x is not supported, others are not tested.
>
> D) Testing:
>
>      If you don't want to create your vmcore, you can find a vmcore which I
>      created with amdgpu mm pages unfiltered [3], the amdgpu mm pages are
>      allocated by program [4]. You can use the vmcore in 1st kernel to filter
>      the amdgpu mm pages by the previous performance testing cmdline. To
>      verify the pages are filtered in crash:
>
>      Unfiltered:
>      crash> search -c "!QAZXSW@#EDC"
>      ffff96b7fa800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>      ffff96b87c800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>      crash> rd ffff96b7fa800000
>      ffff96b7fa800000:  405753585a415121                    !QAZXSW@
>      crash> rd ffff96b87c800000
>      ffff96b87c800000:  405753585a415121                    !QAZXSW@
>
>      Filtered:
>      crash> search -c "!QAZXSW@#EDC"
>      crash> rd ffff96b7fa800000
>      rd: page excluded: kernel virtual address: ffff96b7fa800000  type: "64-bit KVADDR"
>      crash> rd ffff96b87c800000
>      rd: page excluded: kernel virtual address: ffff96b87c800000  type: "64-bit KVADDR"
>
> [1]: https://github.com/lucchouina/eppic/pull/32
> [2]: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> [3]: https://people.redhat.com/~ltao/core/vmcore
> [4]: https://gist.github.com/liutgnu/a8cbce1c666452f1530e1410d1f352df
>
> v5 -> v4:
>
> 1) Add "make EXTENSION=on" switch to customize the extension feature.
> 2) Clean up macros within btf_info.h.
> 3) Updated doc and a sample extension to demo the extension usage.
> 4) Use MSG()/ERRMSG() rather than fprintf().
> 5) Add return value check for readmem().
> 6) Allow "makedumpfile -d 1 --extension ext.so" to enter extension.
> 7) The patches are organized as follows:
>
>     --- <customization specific> ---
>     9. Add amdgpu mm pages filtering extension
>
>     --- <code should be merged> ---
>     8. Doc: Add --extension option to makedumpfile manual
>     7. Add sample extension as an example reference
>     6. Add makedumpfile extensions support
>     5. Implement kernel module's btf resolving
>     4. Implement kernel module's kallsyms resolving
>     3. Implement kernel btf resolving
>     2. Implement kernel kallsyms resolving
>     1. Reserve sections for makedumpfile and extenions
>
>     Patch 9 is customization specific, merging depends on the strategy of
>     maintenance.
>     Patch 1 ~ 8 are common code which should be merged with makedumpfile.
>
> Link to v4: https://lore.kernel.org/kexec/20260317150743.69590-1-ltao at redhat.com/
> Link to v3: https://lore.kernel.org/kexec/20260120025500.25095-1-ltao at redhat.com/
> Link to v2: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> Link to v1: https://lore.kernel.org/kexec/20250610095743.18073-1-ltao at redhat.com/
>
> Tao Liu (9):
>   Reserve sections for makedumpfile and extenions
>   Implement kernel kallsyms resolving
>   Implement kernel btf resolving
>   Implement kernel module's kallsyms resolving
>   Implement kernel module's btf resolving
>   Add makedumpfile extensions support
>   Add sample extension as an example reference
>   Doc: Add --extension option to makedumpfile manual
>   Add amdgpu mm pages filtering extension
>
>  Makefile                   |  15 +-
>  README                     |   6 +
>  btf_info.c                 | 375 +++++++++++++++++++++++++
>  btf_info.h                 |  77 ++++++
>  extension.c                | 338 ++++++++++++++++++++++
>  extension.h                |  16 ++
>  extensions/Makefile        |  13 +
>  extensions/amdgpu_filter.c | 221 +++++++++++++++
>  extensions/maple_tree.c    | 328 ++++++++++++++++++++++
>  extensions/maple_tree.h    |   7 +
>  extensions/sample.c        |  69 +++++
>  kallsyms.c                 | 554 +++++++++++++++++++++++++++++++++++++
>  kallsyms.h                 |  87 ++++++
>  makedumpfile.8.in          |  11 +-
>  makedumpfile.c             |  44 ++-
>  makedumpfile.h             |  12 +
>  makedumpfile.ld            |  16 ++
>  17 files changed, 2180 insertions(+), 9 deletions(-)
>  create mode 100644 btf_info.c
>  create mode 100644 btf_info.h
>  create mode 100644 extension.c
>  create mode 100644 extension.h
>  create mode 100644 extensions/Makefile
>  create mode 100644 extensions/amdgpu_filter.c
>  create mode 100644 extensions/maple_tree.c
>  create mode 100644 extensions/maple_tree.h
>  create mode 100644 extensions/sample.c
>  create mode 100644 kallsyms.c
>  create mode 100644 kallsyms.h
>  create mode 100644 makedumpfile.ld
>
> --
> 2.47.0
>



From rppt at kernel.org  Tue May 19 22:09:26 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Wed, 20 May 2026 08:09:26 +0300
Subject: [PATCH] kho: fix order calculation for kho_unpreserve_pages()
In-Reply-To: <20260519133332.2498092-1-pratyush@kernel.org>
References: <20260519133332.2498092-1-pratyush@kernel.org>
Message-ID: <177925371880.3535040.4659421700056655477.b4-ty@b4>

On Tue, 19 May 2026 15:33:30 +0200, Pratyush Yadav wrote:
> Commit 91e74fa8b1bc ("kho: make sure preservations do not span multiple
> NUMA nodes") made sure preservations from kho_preserve_pages() do not
> span multiple NUMA nodes. If they do, the order is reduced and tried
> again.
> 
> The same logic was not implemented for kho_unpreserve_pages(). This can
> result in unpreserve calculating a different order than preserve, and
> thus not actually unpreserving the pages.
> 
> [...]

Applied to fixes branch of liveupdate/linux.git tree, thanks!

[1/1] kho: fix order calculation for kho_unpreserve_pages()
      commit: ec4084bc445027a52f600e30a976928be1ba1950

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: fixes

--
Sincerely yours,
Mike.



From lkp at intel.com  Tue May 19 22:35:30 2026
From: lkp at intel.com (kernel test robot)
Date: Wed, 20 May 2026 13:35:30 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 d65d7e28888a6f3a1999908cacadd99f4c14ddd9
Message-ID: <202605201321.CDiyGadg-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: d65d7e28888a6f3a1999908cacadd99f4c14ddd9  liveupdate: document liveupdate=on

elapsed time: 736m

configs tested: 179
configs skipped: 2

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260520    gcc-8.5.0
arc                   randconfig-002-20260520    gcc-8.5.0
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                            dove_defconfig    gcc-15.2.0
arm                           omap1_defconfig    gcc-15.2.0
arm                   randconfig-001-20260520    gcc-8.5.0
arm                   randconfig-002-20260520    gcc-8.5.0
arm                   randconfig-003-20260520    gcc-8.5.0
arm                   randconfig-004-20260520    gcc-8.5.0
arm                          sp7021_defconfig    gcc-15.2.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260520    gcc-10.5.0
arm64                 randconfig-002-20260520    gcc-10.5.0
arm64                 randconfig-003-20260520    gcc-10.5.0
arm64                 randconfig-004-20260520    gcc-10.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260520    gcc-10.5.0
csky                  randconfig-002-20260520    gcc-10.5.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260520    gcc-11.5.0
hexagon               randconfig-002-20260520    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260520    clang-20
i386        buildonly-randconfig-002-20260520    clang-20
i386        buildonly-randconfig-003-20260520    clang-20
i386        buildonly-randconfig-004-20260520    clang-20
i386        buildonly-randconfig-005-20260520    clang-20
i386        buildonly-randconfig-006-20260520    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260520    clang-20
i386                  randconfig-002-20260520    clang-20
i386                  randconfig-003-20260520    clang-20
i386                  randconfig-004-20260520    clang-20
i386                  randconfig-005-20260520    clang-20
i386                  randconfig-006-20260520    clang-20
i386                  randconfig-007-20260520    clang-20
i386                           randconfig-011    gcc-14
i386                  randconfig-011-20260520    gcc-14
i386                           randconfig-012    gcc-14
i386                  randconfig-012-20260520    gcc-14
i386                           randconfig-013    gcc-14
i386                  randconfig-013-20260520    gcc-14
i386                           randconfig-014    gcc-14
i386                  randconfig-014-20260520    gcc-14
i386                           randconfig-015    gcc-14
i386                  randconfig-015-20260520    gcc-14
i386                           randconfig-016    gcc-14
i386                  randconfig-016-20260520    gcc-14
i386                           randconfig-017    gcc-14
i386                  randconfig-017-20260520    gcc-14
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260520    gcc-11.5.0
loongarch             randconfig-002-20260520    gcc-11.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                         rt305x_defconfig    clang-23
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260520    gcc-11.5.0
nios2                 randconfig-002-20260520    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260520    clang-23
parisc                randconfig-002-20260520    clang-23
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc               randconfig-001-20260520    clang-23
powerpc               randconfig-002-20260520    clang-23
powerpc64             randconfig-001-20260520    clang-23
powerpc64             randconfig-002-20260520    clang-23
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv             nommu_k210_sdcard_defconfig    gcc-15.2.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260520    gcc-8.5.0
sparc                 randconfig-002-20260520    gcc-8.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260520    gcc-8.5.0
sparc64               randconfig-002-20260520    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260520    gcc-8.5.0
um                    randconfig-002-20260520    gcc-8.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64               buildonly-randconfig-001    gcc-14
x86_64      buildonly-randconfig-001-20260520    gcc-14
x86_64               buildonly-randconfig-002    gcc-14
x86_64      buildonly-randconfig-002-20260520    gcc-14
x86_64               buildonly-randconfig-003    gcc-14
x86_64      buildonly-randconfig-003-20260520    gcc-14
x86_64               buildonly-randconfig-004    gcc-14
x86_64      buildonly-randconfig-004-20260520    gcc-14
x86_64               buildonly-randconfig-005    gcc-14
x86_64      buildonly-randconfig-005-20260520    gcc-14
x86_64               buildonly-randconfig-006    gcc-14
x86_64      buildonly-randconfig-006-20260520    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260520    gcc-14
x86_64                randconfig-002-20260520    gcc-14
x86_64                randconfig-003-20260520    gcc-14
x86_64                randconfig-004-20260520    gcc-14
x86_64                randconfig-005-20260520    gcc-14
x86_64                randconfig-006-20260520    gcc-14
x86_64                randconfig-011-20260520    gcc-14
x86_64                randconfig-012-20260520    gcc-14
x86_64                randconfig-013-20260520    gcc-14
x86_64                randconfig-014-20260520    gcc-14
x86_64                randconfig-015-20260520    gcc-14
x86_64                randconfig-016-20260520    gcc-14
x86_64                randconfig-071-20260520    gcc-14
x86_64                randconfig-072-20260520    gcc-14
x86_64                randconfig-073-20260520    gcc-14
x86_64                randconfig-074-20260520    gcc-14
x86_64                randconfig-075-20260520    gcc-14
x86_64                randconfig-076-20260520    gcc-14
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                randconfig-001-20260520    gcc-8.5.0
xtensa                randconfig-002-20260520    gcc-8.5.0

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


From hu.shengming at zte.com.cn  Wed May 20 00:35:20 2026
From: hu.shengming at zte.com.cn (hu.shengming at zte.com.cn)
Date: Wed, 20 May 2026 15:35:20 +0800 (CST)
Subject: =?UTF-8?B?UmU6IFtQQVRDSCAzLzNdIFJJU0MtVjogYWRkIGJzcyBzZWN0aW9uIHRvIGtlcm5lbCB0ZXh0IGVsZiBoZWFkZXI=?=
In-Reply-To: <20260519124412.GB227450@horms.kernel.org>
References: 20260518145742805S2A5Zwk9KwoEtGDh4p3Pl@zte.com.cn,20260518150140578xGhdr-ct94HGGS4gmP_it@zte.com.cn,20260519124412.GB227450@horms.kernel.org
Message-ID: <20260520153520587dSDah6BTNL9aPVMV_nsAo@zte.com.cn>

horms at kernel.org wrote:
> On Mon, May 18, 2026 at 03:01:40PM +0800, hu.shengming at zte.com.cn wrote:
> > From: Luo Haiyang <luo.haiyang at zte.com.cn>
> > 
> > The kdump service reported the following error:
> > 
> >   No program header covering vaddr 0xffffffff819fa200 found kexec bug?
> >   saving vmcore-dmesg.txt failed
> > 
> > prb stores the address of printk_rb_dynamic. Since printk_rb_dynamic
> > resides in the bss segment and the PHDR (Program Header) does not include
> > the bss segment address. Therefore,  add the bss segment to the Kernel text.
> > 
> > Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
> 
> Hi,
> 
> I am wondering if to_be_excluded() also needs to be updated.
> And, if so, should that change also be made for ARM64.
> 
> Review assisted by gemini-3-pro-preview

Thanks for taking a look.

On arm64, the kernel code contains text and rodata section, and the kernel data 
contains data and bss section. Therefore, the arm64 architecture does not have
a similar issue.

Yes, on RISC-V, the to_be_excluded function does not properly handle the kernel
memory range, it is still being excluded.

  cat /proc/iomem | grep Kernel
    878a02000-87a8426e7 : Kernel image
    878a02000-87944d309 : Kernel code
    879c00000-87a1fffff : Kernel rodata
    87a400000-87a7741ff : Kernel data
    87a775000-87a8426e7 : Kernel bss

Maybe we can directly use the kernel image in iomem_range_callback, and only the
reserved memory needs to be excluded, like this:

  static int iomem_range_callback(...)
  {
  	....
  	else if (!strncmp(str, KERNEL_IMAGE, strlen(KERNEL_IMAGE))) {
  			elf_info.kern_paddr_start = base;
  			elf_info.kern_size = length;
  	}
  
  	return 0;
  }

  static bool to_be_excluded(...)
  {
   ...
  	  if (!strncmp(str, IOMEM_RESERVED, strlen(IOMEM_RESERVED)))
  		  return true;
  
  	  return false;
  }

If you think this change is acceptable, I will resubmit patch v3 later.
---
Thanks
Luo Haiyang


From chenwandun1 at gmail.com  Wed May 20 02:18:36 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:36 +0800
Subject: [PATCH v2 0/8] kdump: reduce vmcore size and capture time
Message-ID: <20260520091844.592753-1-chenwandun@lixiang.com>

This is v2 of the vmcore size optimization series.

The original v1 [1] contains two parts of work:
 - Bug fixes and small cleanups about reserved memory.
 - A vmcore size optimization that excludes reserved memory out of
   vmcore.

For the convenience of review, I have split it into two independent
patchsets. This patchset focuses on the vmcore size optimization.

Motivation
==========

On SoCs that carve out large firmware-owned reserved memory (GPU
firmware, DSP, modem, camera ISP, NPU, ...), kdump currently dumps
those carveouts as part of system RAM even though their contents are
firmware state that is not useful for kernel crash analysis.

This series excludes /reserved-memory regions from vmcore by default,
and also for /memreserve/ firmware regions. The corresponding kdump
time has decreased, and the vmcore size has become smaller.

v1 --> v2:
1. v1 added an opt-out DT property ('linux,no-dump'). Per Rob's
   feedback [2], v2 drop that property and exclude reserve memory
   by default.
2. Split some prepared patches from the original patches.
3. Address coding-style comments on patch 5 from Rob.

[1] https://lore.kernel.org/lkml/20260429065831.1510858-1-chenwandun at lixiang.com/
[2] https://lore.kernel.org/lkml/20260506144542.GA2072596-robh at kernel.org/

Wandun Chen (8):
  of: reserved_mem: handle NULL name in of_reserved_mem_lookup()
  kexec/crash: provide crash_exclude_mem_range() stub when
    CONFIG_CRASH_DUMP=n
  of: reserved_mem: add dumpable flag to opt-in vmcore
  of: reserved_mem: save /memreserve/ entries into the reserved_mem
    array
  of: reserved_mem: add kdump helpers to exclude non-dumpable regions
  arm64: kdump: exclude non-dumpable reserved memory regions from vmcore
  riscv: kdump: exclude non-dumpable reserved memory regions from vmcore
  loongarch: kdump: exclude non-dumpable reserved memory regions from
    vmcore

 arch/arm64/kernel/machine_kexec_file.c     |  6 ++
 arch/loongarch/kernel/machine_kexec_file.c |  6 ++
 arch/riscv/kernel/machine_kexec_file.c     |  4 +
 drivers/of/fdt.c                           |  5 ++
 drivers/of/of_private.h                    |  2 +
 drivers/of/of_reserved_mem.c               | 92 +++++++++++++++++++++-
 include/linux/crash_core.h                 |  6 ++
 include/linux/of_reserved_mem.h            | 15 ++++
 kernel/dma/contiguous.c                    |  1 +
 9 files changed, 136 insertions(+), 1 deletion(-)

-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:37 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:37 +0800
Subject: [PATCH v2 1/8] of: reserved_mem: handle NULL name in of_reserved_mem_lookup()
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-2-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for an upcoming change that appends /memreserve/ entries to
reserved_mem[]; such entries have no name.

No functional change.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 drivers/of/of_reserved_mem.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 8d5777cb5d1b..313cbc57aa45 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -788,7 +788,8 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
 
 	name = kbasename(np->full_name);
 	for (i = 0; i < reserved_mem_count; i++)
-		if (!strcmp(reserved_mem[i].name, name))
+		if (reserved_mem[i].name &&
+		    !strcmp(reserved_mem[i].name, name))
 			return &reserved_mem[i];
 
 	return NULL;
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:38 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:38 +0800
Subject: [PATCH v2 2/8] kexec/crash: provide crash_exclude_mem_range() stub when CONFIG_CRASH_DUMP=n
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-3-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for an upcoming change that excludes non-dumpable reserved
regions from the kdump vmcore and will call crash_exclude_mem_range()
from generic, non-arch code.

No functional change.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 include/linux/crash_core.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index c1dee3f971a9..0033d4777648 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -87,6 +87,12 @@ static inline int kexec_should_crash(struct task_struct *p) { return 0; }
 static inline int kexec_crash_loaded(void) { return 0; }
 static inline void crash_save_cpu(struct pt_regs *regs, int cpu) {};
 static inline int kimage_crash_copy_vmcoreinfo(struct kimage *image) { return 0; };
+static inline int crash_exclude_mem_range(struct crash_mem *mem,
+					  unsigned long long mstart,
+					  unsigned long long mend)
+{
+	return 0;
+}
 #endif /* CONFIG_CRASH_DUMP*/
 
 #ifdef CONFIG_CRASH_DM_CRYPT
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:39 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:39 +0800
Subject: [PATCH v2 3/8] of: reserved_mem: add dumpable flag to opt-in vmcore
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-4-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Add a 'dumpable' flag to struct reserved_mem so the kernel can decide
whether a reserved area should be included in the kdump vmcore. Most
reserved regions are owned by devices and do not contain data useful
for kernel crash analysis, so excluding them by default is the right
behaviour.

Reusable CMA regions are different: pages in a CMA region are handed
back to the buddy allocator and may contain key data for crash
analysis, so set dumpable to true in rmem_cma_setup().

Suggested-by: Rob Herring <robh at kernel.org>
Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
Link: https://lore.kernel.org/all/20260506144542.GA2072596-robh at kernel.org/
---
 include/linux/of_reserved_mem.h | 1 +
 kernel/dma/contiguous.c         | 1 +
 2 files changed, 2 insertions(+)

diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index e8b20b29fa68..55a67cee41ea 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -15,6 +15,7 @@ struct reserved_mem {
 	phys_addr_t			base;
 	phys_addr_t			size;
 	void				*priv;
+	bool				dumpable;
 };
 
 struct reserved_mem_ops {
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index 03f52bd17120..eddec89eb414 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -579,6 +579,7 @@ static int __init rmem_cma_setup(unsigned long node, struct reserved_mem *rmem)
 		dma_contiguous_default_area = cma;
 
 	rmem->priv = cma;
+	rmem->dumpable = true;
 
 	pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
 		&rmem->base, (unsigned long)rmem->size / SZ_1M);
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:40 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:40 +0800
Subject: [PATCH v2 4/8] of: reserved_mem: save /memreserve/ entries into the reserved_mem array
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-5-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

/memreserve/ is used by firmware or bootloaders, such regions hold no
useful data for crash analysis, they should be excluded from the
kdump vmcore, so save /memreserve/ entries into the reserved_mem array
for later exclusion.

If a /memreserve/ entry overlaps any dumpable reserved region, mark
the whole memreserve entry dumpable as well. This may keep slightly
more memory in vmcore than strictly necessary, but avoids splitting
entries and never drops data that may be useful for crash analysis.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 drivers/of/fdt.c             |  5 ++++
 drivers/of/of_private.h      |  2 ++
 drivers/of/of_reserved_mem.c | 55 ++++++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+)

diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 82f7327c59ea..d2bcaf149fe8 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -499,6 +499,7 @@ void __init early_init_fdt_scan_reserved_mem(void)
 	int n;
 	int res;
 	u64 base, size;
+	int nr_memreserve = 0;
 
 	if (!initial_boot_params)
 		return;
@@ -516,7 +517,9 @@ void __init early_init_fdt_scan_reserved_mem(void)
 		if (!size)
 			break;
 		memblock_reserve(base, size);
+		nr_memreserve++;
 	}
+	fdt_reserved_mem_account_memreserve(nr_memreserve);
 }
 
 /**
@@ -1287,6 +1290,8 @@ void __init unflatten_device_tree(void)
 	/* Save the statically-placed regions in the reserved_mem array */
 	fdt_scan_reserved_mem_late();
 
+	fdt_reserved_mem_save_memreserve_entries();
+
 	/* Populate an empty root node when bootloader doesn't provide one */
 	if (!fdt) {
 		fdt = (void *) __dtb_empty_root_begin;
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 0ae16da066e2..646b5f43ad47 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -187,6 +187,8 @@ static inline struct device_node *__of_get_dma_parent(const struct device_node *
 
 int fdt_scan_reserved_mem(void);
 void __init fdt_scan_reserved_mem_late(void);
+void __init fdt_reserved_mem_account_memreserve(int n);
+void __init fdt_reserved_mem_save_memreserve_entries(void);
 
 bool of_fdt_device_is_available(const void *blob, unsigned long node);
 
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 313cbc57aa45..6dfe9e03c535 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -241,6 +241,43 @@ static void __init __rmem_check_for_overlap(void)
 	}
 }
 
+static void __init fdt_reserved_mem_add_memreserve(phys_addr_t base,
+						   phys_addr_t size)
+{
+	struct reserved_mem *rmem;
+	bool dumpable = false;
+	int i;
+
+	if (reserved_mem_count == total_reserved_mem_cnt) {
+		pr_err("not enough space for memreserve regions.\n");
+		return;
+	}
+
+	for (i = 0; i < reserved_mem_count; i++) {
+		rmem = &reserved_mem[i];
+
+		if (!rmem->dumpable)
+			continue;
+
+		if (base < rmem->base + rmem->size && rmem->base < base + size) {
+			dumpable = true;
+			break;
+		}
+	}
+
+	rmem = &reserved_mem[reserved_mem_count];
+	rmem->base = base;
+	rmem->size = size;
+	rmem->dumpable = dumpable;
+
+	reserved_mem_count++;
+}
+
+void __init fdt_reserved_mem_account_memreserve(int n)
+{
+	total_reserved_mem_cnt += n;
+}
+
 /**
  * fdt_scan_reserved_mem_late() - Scan FDT and initialize remaining reserved
  * memory regions.
@@ -301,6 +338,24 @@ void __init fdt_scan_reserved_mem_late(void)
 	__rmem_check_for_overlap();
 }
 
+void __init fdt_reserved_mem_save_memreserve_entries(void)
+{
+	const void *fdt = initial_boot_params;
+	u64 base, size;
+	int n;
+
+	if (!fdt)
+		return;
+
+	for (n = 0; ; n++) {
+		if (fdt_get_mem_rsv(fdt, n, &base, &size))
+			break;
+		if (!size)
+			break;
+		fdt_reserved_mem_add_memreserve(base, size);
+	}
+}
+
 static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname);
 
 /*
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:41 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:41 +0800
Subject: [PATCH v2 5/8] of: reserved_mem: add kdump helpers to exclude non-dumpable regions
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-6-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Add two helpers to exclude non-dumpable regions for arch-specific
code.

 - of_reserved_mem_kdump_nr_ranges() returns the count of regions
   that are not dumpable. Each excluded region may split an existing
   crash_mem range into two, so callers use this to calculate
   crash_mem allocation size.

 - of_reserved_mem_kdump_exclude() walks reserved_mem[] and calls
   crash_exclude_mem_range() for every non-dumpable region.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 drivers/of/of_reserved_mem.c    | 34 +++++++++++++++++++++++++++++++++
 include/linux/of_reserved_mem.h | 14 ++++++++++++++
 2 files changed, 48 insertions(+)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 6dfe9e03c535..ef9732865783 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -24,6 +24,7 @@
 #include <linux/slab.h>
 #include <linux/memblock.h>
 #include <linux/kmemleak.h>
+#include <linux/crash_core.h>
 
 #include "of_private.h"
 
@@ -851,6 +852,39 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
 }
 EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
 
+/*
+ * Count non-dumpable reserved regions. Excluding each one may split a
+ * crash_mem range in two, callers use this to size the allocation.
+ */
+unsigned int of_reserved_mem_kdump_nr_ranges(void)
+{
+	unsigned int i, n = 0;
+
+	for (i = 0; i < reserved_mem_count; i++)
+		if (reserved_mem[i].size && !reserved_mem[i].dumpable)
+			n++;
+	return n;
+}
+
+/* Exclude non-dumpable reserved regions from @cmem. */
+int of_reserved_mem_kdump_exclude(struct crash_mem *cmem)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < reserved_mem_count; i++) {
+		struct reserved_mem *r = &reserved_mem[i];
+
+		if (!r->size || r->dumpable)
+			continue;
+		ret = crash_exclude_mem_range(cmem, r->base,
+					      r->base + r->size - 1);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 /**
  * of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
  * @np:		node containing 'memory-region' property
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index 55a67cee41ea..70db99f1fbff 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -8,6 +8,7 @@
 struct of_phandle_args;
 struct reserved_mem_ops;
 struct resource;
+struct crash_mem;
 
 struct reserved_mem {
 	const char			*name;
@@ -48,6 +49,9 @@ int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
 					      const char *name, struct resource *res);
 int of_reserved_mem_region_count(const struct device_node *np);
 
+unsigned int of_reserved_mem_kdump_nr_ranges(void);
+int of_reserved_mem_kdump_exclude(struct crash_mem *cmem);
+
 #else
 
 #define RESERVEDMEM_OF_DECLARE(name, compat, ops)			\
@@ -92,6 +96,16 @@ static inline int of_reserved_mem_region_count(const struct device_node *np)
 {
 	return 0;
 }
+
+static inline unsigned int of_reserved_mem_kdump_nr_ranges(void)
+{
+	return 0;
+}
+
+static inline int of_reserved_mem_kdump_exclude(struct crash_mem *cmem)
+{
+	return 0;
+}
 #endif
 
 /**
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:42 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:42 +0800
Subject: [PATCH v2 6/8] arm64: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-7-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Reserved memory regions are excluded from vmcore by default unless
marked dumpable. Honor the dumpable flag to filter out device firmware
regions (e.g., GPU, DSP, modem) reserved via device tree, since they
typically contain data not useful for kernel crash analysis and can
significantly increase vmcore size.

Use of_reserved_mem_kdump_exclude() to perform the exclusion, and
pre-size the crash_mem array via of_reserved_mem_kdump_nr_ranges().

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..1d65320c6ba4 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -17,6 +17,7 @@
 #include <linux/memblock.h>
 #include <linux/of.h>
 #include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 			goto out;
 	}
 
+	ret = of_reserved_mem_kdump_exclude(cmem);
+	if (ret)
+		goto out;
+
 	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:44 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:44 +0800
Subject: [PATCH v2 8/8] loongarch: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-9-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Apply the same non-dumpable reserved memory filtering to LoongArch
kdump as was done for arm64. Use of_reserved_mem_kdump_exclude() to
drop flagged regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_kdump_nr_ranges() to pre-size the crash_mem array.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..c5cead362d2d 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -14,6 +14,7 @@
 #include <linux/kernel.h>
 #include <linux/kexec.h>
 #include <linux/memblock.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -67,6 +68,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -91,6 +93,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 			goto out;
 	}
 
+	ret = of_reserved_mem_kdump_exclude(cmem);
+	if (ret < 0)
+		goto out;
+
 	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
-- 
2.43.0



From chenwandun1 at gmail.com  Wed May 20 02:18:43 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 20 May 2026 17:18:43 +0800
Subject: [PATCH v2 7/8] riscv: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260520091844.592753-1-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
Message-ID: <20260520091844.592753-8-chenwandun@lixiang.com>

From: Wandun Chen <chenwandun1 at gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Apply the same non-dumpable reserved memory filtering to RISC-V kdump
as was done for arm64. Use of_reserved_mem_kdump_exclude() to drop
flagged regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_kdump_nr_ranges() to pre-size the crash_mem array.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 54e2d9552e93..c359cf714c79 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -10,6 +10,7 @@
 #include <linux/elf.h>
 #include <linux/slab.h>
 #include <linux/of.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/libfdt.h>
 #include <linux/types.h>
 #include <linux/memblock.h>
@@ -63,6 +64,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	nr_ranges = 1; /* For exclusion of crashkernel region */
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -76,6 +78,8 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	/* Exclude crashkernel region */
 	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	if (!ret)
+		ret = of_reserved_mem_kdump_exclude(cmem);
 	if (!ret)
 		ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
-- 
2.43.0



From horms at kernel.org  Wed May 20 02:30:10 2026
From: horms at kernel.org (Simon Horman)
Date: Wed, 20 May 2026 10:30:10 +0100
Subject: [PATCH 3/3] RISC-V: add bss section to kernel text elf header
In-Reply-To: <20260520153520587dSDah6BTNL9aPVMV_nsAo@zte.com.cn>
References: <20260519124412.GB227450@horms.kernel.org>
 <20260520153520587dSDah6BTNL9aPVMV_nsAo@zte.com.cn>
Message-ID: <20260520093010.GA296581@horms.kernel.org>

On Wed, May 20, 2026 at 03:35:20PM +0800, hu.shengming at zte.com.cn wrote:
> horms at kernel.org wrote:
> > On Mon, May 18, 2026 at 03:01:40PM +0800, hu.shengming at zte.com.cn wrote:
> > > From: Luo Haiyang <luo.haiyang at zte.com.cn>
> > > 
> > > The kdump service reported the following error:
> > > 
> > >   No program header covering vaddr 0xffffffff819fa200 found kexec bug?
> > >   saving vmcore-dmesg.txt failed
> > > 
> > > prb stores the address of printk_rb_dynamic. Since printk_rb_dynamic
> > > resides in the bss segment and the PHDR (Program Header) does not include
> > > the bss segment address. Therefore,  add the bss segment to the Kernel text.
> > > 
> > > Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
> > 
> > Hi,
> > 
> > I am wondering if to_be_excluded() also needs to be updated.
> > And, if so, should that change also be made for ARM64.
> > 
> > Review assisted by gemini-3-pro-preview
> 
> Thanks for taking a look.
> 
> On arm64, the kernel code contains text and rodata section, and the kernel data 
> contains data and bss section. Therefore, the arm64 architecture does not have
> a similar issue.
> 
> Yes, on RISC-V, the to_be_excluded function does not properly handle the kernel
> memory range, it is still being excluded.
> 
>   cat /proc/iomem | grep Kernel
>     878a02000-87a8426e7 : Kernel image
>     878a02000-87944d309 : Kernel code
>     879c00000-87a1fffff : Kernel rodata
>     87a400000-87a7741ff : Kernel data
>     87a775000-87a8426e7 : Kernel bss
> 
> Maybe we can directly use the kernel image in iomem_range_callback, and only the
> reserved memory needs to be excluded, like this:
> 
>   static int iomem_range_callback(...)
>   {
>   	....
>   	else if (!strncmp(str, KERNEL_IMAGE, strlen(KERNEL_IMAGE))) {
>   			elf_info.kern_paddr_start = base;
>   			elf_info.kern_size = length;
>   	}
>   
>   	return 0;
>   }
> 
>   static bool to_be_excluded(...)
>   {
>    ...
>   	  if (!strncmp(str, IOMEM_RESERVED, strlen(IOMEM_RESERVED)))
>   		  return true;
>   
>   	  return false;
>   }
> 
> If you think this change is acceptable, I will resubmit patch v3 later.

Thanks, sounds good to me.


From m.szyprowski at samsung.com  Wed May 20 02:53:56 2026
From: m.szyprowski at samsung.com (Marek Szyprowski)
Date: Wed, 20 May 2026 11:53:56 +0200
Subject: [PATCH v2 3/8] of: reserved_mem: add dumpable flag to opt-in
 vmcore
In-Reply-To: <20260520091844.592753-4-chenwandun@lixiang.com>
References: <20260520091844.592753-1-chenwandun@lixiang.com>
	<CGME20260520091935eucas1p1e3bd0cd7a5d2229acf937de3439571c7@eucas1p1.samsung.com>
	<20260520091844.592753-4-chenwandun@lixiang.com>
Message-ID: <e5dbe092-1c28-4799-b7c6-a78f84065dd2@samsung.com>

On 20.05.2026 11:18, Wandun Chen wrote:
> From: Wandun Chen <chenwandun1 at gmail.com>
>
> From: Wandun Chen <chenwandun at lixiang.com>
>
> Add a 'dumpable' flag to struct reserved_mem so the kernel can decide
> whether a reserved area should be included in the kdump vmcore. Most
> reserved regions are owned by devices and do not contain data useful
> for kernel crash analysis, so excluding them by default is the right
> behaviour.
>
> Reusable CMA regions are different: pages in a CMA region are handed
> back to the buddy allocator and may contain key data for crash
> analysis, so set dumpable to true in rmem_cma_setup().
>
> Suggested-by: Rob Herring <robh at kernel.org>
> Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
> Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
> Link: https://lore.kernel.org/all/20260506144542.GA2072596-robh at kernel.org/
Acked-by: Marek Szyprowski <m.szyprowski at samsung.com>
> ---
>  include/linux/of_reserved_mem.h | 1 +
>  kernel/dma/contiguous.c         | 1 +
>  2 files changed, 2 insertions(+)
>
> diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
> index e8b20b29fa68..55a67cee41ea 100644
> --- a/include/linux/of_reserved_mem.h
> +++ b/include/linux/of_reserved_mem.h
> @@ -15,6 +15,7 @@ struct reserved_mem {
>  	phys_addr_t			base;
>  	phys_addr_t			size;
>  	void				*priv;
> +	bool				dumpable;
>  };
>  
>  struct reserved_mem_ops {
> diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
> index 03f52bd17120..eddec89eb414 100644
> --- a/kernel/dma/contiguous.c
> +++ b/kernel/dma/contiguous.c
> @@ -579,6 +579,7 @@ static int __init rmem_cma_setup(unsigned long node, struct reserved_mem *rmem)
>  		dma_contiguous_default_area = cma;
>  
>  	rmem->priv = cma;
> +	rmem->dumpable = true;
>  
>  	pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
>  		&rmem->base, (unsigned long)rmem->size / SZ_1M);

Best regards
-- 
Marek Szyprowski, PhD
Samsung R&D Institute Poland



From mclapinski at google.com  Wed May 20 10:43:32 2026
From: mclapinski at google.com (Michal Clapinski)
Date: Wed, 20 May 2026 19:43:32 +0200
Subject: [PATCH] pstore: add a KHO backend
Message-ID: <20260520174332.921186-1-mclapinski@google.com>

Up to this point to preserve late shutdown logs in memory, users had to
predefine a memory region using ramoops. This commit changes this by
preserving a buffer using kexec-handover.

For now it supports preserving only 1 dmesg buffer.
It gets replaced with the new buffer on every kexec, so the user has to
copy the file out of pstore after every kexec.
There is no erase() support.

Signed-off-by: Michal Clapinski <mclapinski at google.com>
---
 fs/pstore/Kconfig      |   9 ++
 fs/pstore/Makefile     |   2 +
 fs/pstore/pstore_kho.c | 267 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 278 insertions(+)
 create mode 100644 fs/pstore/pstore_kho.c

diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig
index 3acc38600cd1..70853799e211 100644
--- a/fs/pstore/Kconfig
+++ b/fs/pstore/Kconfig
@@ -81,6 +81,15 @@ config PSTORE_RAM
 
 	  For more information, see Documentation/admin-guide/ramoops.rst.
 
+config PSTORE_KHO
+	tristate "Preserve logs over kexec"
+	depends on PSTORE
+	depends on KEXEC_HANDOVER
+	help
+	  A pstore backend for preserving dmesg over KHO (kexec handover).
+
+	  It supports preservation of only 1 dmesg file.
+
 config PSTORE_ZONE
 	tristate
 	depends on PSTORE
diff --git a/fs/pstore/Makefile b/fs/pstore/Makefile
index c270467aeece..518cd408bf8e 100644
--- a/fs/pstore/Makefile
+++ b/fs/pstore/Makefile
@@ -13,6 +13,8 @@ pstore-$(CONFIG_PSTORE_PMSG)	+= pmsg.o
 ramoops-objs += ram.o ram_core.o
 obj-$(CONFIG_PSTORE_RAM)	+= ramoops.o
 
+obj-$(CONFIG_PSTORE_KHO)	+= pstore_kho.o
+
 pstore_zone-objs += zone.o
 obj-$(CONFIG_PSTORE_ZONE)	+= pstore_zone.o
 
diff --git a/fs/pstore/pstore_kho.c b/fs/pstore/pstore_kho.c
new file mode 100644
index 000000000000..3bc679273c8d
--- /dev/null
+++ b/fs/pstore/pstore_kho.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KHO (Kexec Handover) backend for pstore
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/libfdt.h>
+#include <linux/module.h>
+#include <linux/pstore.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#define KHO_PSTORE_FDT_NAME	"pstore-kho"
+#define KHO_PSTORE_COMPAT	"pstore-kho-v1"
+#define KHO_PSTORE_DATA		"pstore_kho_record"
+
+static const size_t record_max_size = 1 << CONFIG_LOG_BUF_SHIFT;
+
+struct pstore_kho_record {
+	ssize_t			size;
+	struct timespec64	time;
+	int			count;
+	enum kmsg_dump_reason	reason;
+	unsigned int		part;
+	bool			compressed;
+	char			buf[];
+};
+
+struct pstore_kho_context {
+	struct pstore_info pstore;
+	bool read_done;
+};
+
+static struct pstore_kho_record *kho_data_in;
+static struct pstore_kho_record *kho_data_out;
+static void *fdt_out;
+
+static int pstore_kho_open(struct pstore_info *psi)
+{
+	struct pstore_kho_context *cxt = psi->data;
+
+	cxt->read_done = false;
+	return 0;
+}
+
+static ssize_t pstore_kho_read(struct pstore_record *record)
+{
+	struct pstore_kho_context *cxt = record->psi->data;
+
+	if (cxt->read_done)
+		return 0;
+
+	cxt->read_done = true;
+
+	if (!kho_data_in || !kho_data_in->size)
+		return 0;
+
+	record->buf = kmemdup(kho_data_in->buf, kho_data_in->size, GFP_KERNEL);
+	if (!record->buf)
+		return -ENOMEM;
+
+	record->type = PSTORE_TYPE_DMESG;
+	record->id = 0;
+	record->size = kho_data_in->size;
+	record->time = kho_data_in->time;
+	record->count = kho_data_in->count;
+	record->reason = kho_data_in->reason;
+	record->part = kho_data_in->part;
+	record->compressed = kho_data_in->compressed;
+
+	return record->size;
+}
+
+static int pstore_kho_write(struct pstore_record *record)
+{
+	if (record->type != PSTORE_TYPE_DMESG)
+		return -EINVAL;
+
+	if (kho_data_out->size != 0) {
+		pr_err("pstore kho already contains a record\n");
+		return -ENOSPC;
+	}
+
+	if (record->size > record_max_size) {
+		pr_err("dmesg record too big, record size: %lu, available space: %lu\n",
+		       record->size, record_max_size);
+		return -ENOSPC;
+	}
+
+	memcpy(kho_data_out->buf, record->buf, record->size);
+	kho_data_out->size = record->size;
+	kho_data_out->time = record->time;
+	kho_data_out->count = record->count;
+	kho_data_out->reason = record->reason;
+	kho_data_out->part = record->part;
+	kho_data_out->compressed = record->compressed;
+
+	return 0;
+}
+
+static struct pstore_kho_context pstore_kho_cxt = {
+	.pstore = {
+		.owner		= THIS_MODULE,
+		.name		= "kho",
+		.bufsize	= record_max_size,
+		.flags		= PSTORE_FLAGS_DMESG,
+		.max_reason	= KMSG_DUMP_SHUTDOWN,
+		.open		= pstore_kho_open,
+		.read		= pstore_kho_read,
+		.write		= pstore_kho_write,
+	},
+};
+
+static void __init kho_setup_incoming(void)
+{
+	phys_addr_t fdt_phys;
+	void *fdt_in;
+	const phys_addr_t *kho_data_phys;
+	int err, len;
+
+	err = kho_retrieve_subtree(KHO_PSTORE_FDT_NAME, &fdt_phys);
+	if (err) {
+		if (err != -ENOENT)
+			pr_err("failed to retrieve FDT %s from KHO: %d\n",
+			       KHO_PSTORE_FDT_NAME, err);
+		return;
+	}
+
+	fdt_in = phys_to_virt(fdt_phys);
+	err = fdt_node_check_compatible(fdt_in, 0, KHO_PSTORE_COMPAT);
+	if (err) {
+		pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
+		       KHO_PSTORE_FDT_NAME, KHO_PSTORE_COMPAT, err);
+		return;
+	}
+
+	kho_data_phys = fdt_getprop(fdt_in, 0, KHO_PSTORE_DATA, &len);
+	if (!kho_data_phys || len != sizeof(*kho_data_phys)) {
+		pr_err("failed to find kho property %s\n", KHO_PSTORE_DATA);
+		return;
+	}
+
+	kho_data_in = phys_to_virt(get_unaligned(kho_data_phys));
+
+	pr_info("successfully restored preserved data\n");
+}
+
+static int __init kho_setup_outgoing(void)
+{
+	phys_addr_t header_ser_phys;
+	int err;
+	size_t total_size = sizeof(struct pstore_kho_record) + record_max_size;
+
+	kho_data_out = kho_alloc_preserve(total_size);
+	if (IS_ERR(kho_data_out)) {
+		pr_err("failed to allocate pstore kho data\n");
+		return PTR_ERR(kho_data_out);
+	}
+
+	fdt_out = kho_alloc_preserve(PAGE_SIZE);
+	if (IS_ERR(fdt_out)) {
+		pr_err("failed to allocate/preserve FDT memory\n");
+		err = PTR_ERR(fdt_out);
+		goto err_free_header;
+	}
+
+	err = fdt_create(fdt_out, PAGE_SIZE);
+	err |= fdt_finish_reservemap(fdt_out);
+	err |= fdt_begin_node(fdt_out, "");
+	err |= fdt_property_string(fdt_out, "compatible", KHO_PSTORE_COMPAT);
+
+	header_ser_phys = virt_to_phys(kho_data_out);
+	err |= fdt_property(fdt_out, KHO_PSTORE_DATA, &header_ser_phys,
+			    sizeof(header_ser_phys));
+	err |= fdt_end_node(fdt_out);
+	err |= fdt_finish(fdt_out);
+	if (err) {
+		pr_err("failed to configure fdt tree\n");
+		goto err_free_fdt;
+	}
+
+	err = kho_add_subtree(KHO_PSTORE_FDT_NAME, fdt_out);
+	if (err) {
+		pr_err("failed to add subtree\n");
+		goto err_free_fdt;
+	}
+
+	return 0;
+
+err_free_fdt:
+	kho_unpreserve_free(fdt_out);
+
+err_free_header:
+	kho_unpreserve_free(kho_data_out);
+	return err;
+}
+
+static int __init pstore_kho_init(void)
+{
+	int err;
+	struct pstore_kho_context *cxt = &pstore_kho_cxt;
+
+	if (!kho_is_enabled()) {
+		pr_info("KHO is disabled, pstore_kho cannot start\n");
+		return -ENODEV;
+	}
+
+	kho_setup_incoming();
+	err = kho_setup_outgoing();
+	if (err) {
+		pr_err("failed to setup outgoing KHO\n");
+		goto err_free_incoming;
+	}
+
+	cxt->pstore.data = cxt;
+	cxt->pstore.buf = kmalloc(cxt->pstore.bufsize, GFP_KERNEL);
+	if (!cxt->pstore.buf) {
+		err = -ENOMEM;
+		goto err_free_outgoing;
+	}
+
+	err = pstore_register(&cxt->pstore);
+	if (err) {
+		pr_err("failed to register with pstore\n");
+		goto err_free_pstore_buf;
+	}
+
+	return 0;
+
+err_free_pstore_buf:
+	kfree(cxt->pstore.buf);
+
+err_free_outgoing:
+	kho_remove_subtree(fdt_out);
+	kho_unpreserve_free(fdt_out);
+
+	kho_unpreserve_free(kho_data_out);
+
+err_free_incoming:
+	if (kho_data_in)
+		kho_restore_free(kho_data_in);
+
+	return err;
+}
+module_init(pstore_kho_init);
+
+static void __exit pstore_kho_exit(void)
+{
+	pstore_unregister(&pstore_kho_cxt.pstore);
+	kfree(pstore_kho_cxt.pstore.buf);
+
+	kho_remove_subtree(fdt_out);
+	kho_unpreserve_free(fdt_out);
+
+	kho_unpreserve_free(kho_data_out);
+
+	if (kho_data_in)
+		kho_restore_free(kho_data_in);
+}
+module_exit(pstore_kho_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Pstore backend for dmesg preservation over kexec");
-- 
2.54.0.669.g59709faab0-goog



From dpsmith at apertussolutions.com  Wed May 20 13:12:28 2026
From: dpsmith at apertussolutions.com (Daniel P. Smith)
Date: Wed, 20 May 2026 16:12:28 -0400
Subject: [PATCH v16 01/38] tpm: Initial step to reorganize TPM public
 headers
In-Reply-To: <8f8328d3-a7db-4a64-82f8-1e0e3eff93cf@intel.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
 <20260515211410.31440-2-ross.philipson@gmail.com>
 <agemXwxVb9jvAbYM@kernel.org> <20260515230553.GO7702@ziepe.ca>
 <8f8328d3-a7db-4a64-82f8-1e0e3eff93cf@intel.com>
Message-ID: <a5d70928-5d86-4439-a177-e5182552b3e2@apertussolutions.com>

On 5/15/26 7:10 PM, Dave Hansen wrote:
> On 5/15/26 16:05, Jason Gunthorpe wrote:
>> Can we please split out and progress the TPM reorg mini-series at the
>> front?
> 
> Yes, please.
> 

Yes, we will split this out, address the Sashiko comments, and work on 
getting it out next week.

> Any way to break this down and merge in more bite-size pieces would be
> better for everyone involved.

I went through the TPM only part of the series and only one commit was 
of substantial length, note I am not considering those that were only 
moving headers around. That's the new buffer allocation patch from 
Jarkko. We can see if there is a way to split it up without resulting in 
either unused code or breaking the TPM driver.

V/r,
Daniel P. Smith


From lkp at intel.com  Wed May 20 19:51:29 2026
From: lkp at intel.com (kernel test robot)
Date: Thu, 21 May 2026 10:51:29 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 ec4084bc445027a52f600e30a976928be1ba1950
Message-ID: <202605211020.JkKIPpKs-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: ec4084bc445027a52f600e30a976928be1ba1950  kho: fix order calculation for kho_unpreserve_pages()

elapsed time: 729m

configs tested: 206
configs skipped: 2

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260521    gcc-8.5.0
arc                   randconfig-002-20260521    gcc-8.5.0
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                   randconfig-001-20260521    gcc-8.5.0
arm                   randconfig-002-20260521    gcc-8.5.0
arm                   randconfig-003-20260521    gcc-8.5.0
arm                   randconfig-004-20260521    gcc-8.5.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260521    gcc-8.5.0
arm64                 randconfig-002-20260521    gcc-8.5.0
arm64                 randconfig-003-20260521    gcc-8.5.0
arm64                 randconfig-004-20260521    gcc-8.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260521    gcc-8.5.0
csky                  randconfig-002-20260521    gcc-8.5.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260521    gcc-11.5.0
hexagon               randconfig-002-20260521    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260521    clang-20
i386        buildonly-randconfig-002-20260521    clang-20
i386        buildonly-randconfig-003-20260521    clang-20
i386        buildonly-randconfig-004-20260521    clang-20
i386        buildonly-randconfig-005-20260521    clang-20
i386        buildonly-randconfig-006-20260521    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260521    clang-20
i386                  randconfig-002-20260521    clang-20
i386                  randconfig-003-20260521    clang-20
i386                  randconfig-004-20260521    clang-20
i386                  randconfig-005-20260521    clang-20
i386                  randconfig-006-20260521    clang-20
i386                  randconfig-007-20260521    clang-20
i386                  randconfig-011-20260521    gcc-14
i386                  randconfig-012-20260521    gcc-14
i386                  randconfig-013-20260521    gcc-14
i386                  randconfig-014-20260521    gcc-14
i386                  randconfig-015-20260521    gcc-14
i386                  randconfig-016-20260521    gcc-14
i386                  randconfig-017-20260521    gcc-14
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260521    gcc-11.5.0
loongarch             randconfig-002-20260521    gcc-11.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                  cavium_octeon_defconfig    gcc-15.2.0
mips                         rt305x_defconfig    clang-23
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260521    gcc-11.5.0
nios2                 randconfig-002-20260521    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260521    gcc-12.5.0
parisc                randconfig-002-20260521    gcc-12.5.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                      mgcoge_defconfig    clang-23
powerpc               randconfig-001-20260521    gcc-12.5.0
powerpc               randconfig-002-20260521    gcc-12.5.0
powerpc                     stx_gp3_defconfig    gcc-15.2.0
powerpc64             randconfig-001-20260521    gcc-12.5.0
powerpc64             randconfig-002-20260521    gcc-12.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                          randconfig-001    gcc-15.2.0
riscv                 randconfig-001-20260521    gcc-15.2.0
riscv                          randconfig-002    gcc-15.2.0
riscv                 randconfig-002-20260521    gcc-15.2.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                          debug_defconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                           randconfig-001    gcc-15.2.0
s390                  randconfig-001-20260521    gcc-15.2.0
s390                           randconfig-002    gcc-15.2.0
s390                  randconfig-002-20260521    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                             randconfig-001    gcc-15.2.0
sh                    randconfig-001-20260521    gcc-15.2.0
sh                             randconfig-002    gcc-15.2.0
sh                    randconfig-002-20260521    gcc-15.2.0
sh                     sh7710voipgw_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260521    gcc-8.5.0
sparc                 randconfig-002-20260521    gcc-8.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260521    gcc-8.5.0
sparc64               randconfig-002-20260521    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260521    gcc-8.5.0
um                    randconfig-002-20260521    gcc-8.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260520    gcc-14
x86_64      buildonly-randconfig-001-20260521    clang-20
x86_64      buildonly-randconfig-002-20260520    gcc-14
x86_64      buildonly-randconfig-002-20260521    clang-20
x86_64      buildonly-randconfig-003-20260520    gcc-14
x86_64      buildonly-randconfig-003-20260521    clang-20
x86_64      buildonly-randconfig-004-20260520    gcc-14
x86_64      buildonly-randconfig-004-20260521    clang-20
x86_64      buildonly-randconfig-005-20260520    gcc-14
x86_64      buildonly-randconfig-005-20260521    clang-20
x86_64      buildonly-randconfig-006-20260520    gcc-14
x86_64      buildonly-randconfig-006-20260521    clang-20
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260520    gcc-14
x86_64                randconfig-001-20260521    clang-20
x86_64                randconfig-002-20260520    gcc-14
x86_64                randconfig-002-20260521    clang-20
x86_64                randconfig-003-20260520    gcc-14
x86_64                randconfig-003-20260521    clang-20
x86_64                randconfig-004-20260520    gcc-14
x86_64                randconfig-004-20260521    clang-20
x86_64                randconfig-005-20260520    gcc-14
x86_64                randconfig-005-20260521    clang-20
x86_64                randconfig-006-20260520    gcc-14
x86_64                randconfig-006-20260521    clang-20
x86_64                randconfig-011-20260521    gcc-14
x86_64                randconfig-012-20260521    gcc-14
x86_64                randconfig-013-20260521    gcc-14
x86_64                randconfig-014-20260521    gcc-14
x86_64                randconfig-015-20260521    gcc-14
x86_64                randconfig-016-20260521    gcc-14
x86_64                randconfig-071-20260520    gcc-14
x86_64                randconfig-071-20260521    clang-20
x86_64                randconfig-072-20260520    gcc-14
x86_64                randconfig-072-20260521    clang-20
x86_64                randconfig-073-20260520    gcc-14
x86_64                randconfig-073-20260521    clang-20
x86_64                randconfig-074-20260520    gcc-14
x86_64                randconfig-074-20260521    clang-20
x86_64                randconfig-075-20260520    gcc-14
x86_64                randconfig-075-20260521    clang-20
x86_64                randconfig-076-20260520    gcc-14
x86_64                randconfig-076-20260521    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                randconfig-001-20260521    gcc-8.5.0
xtensa                randconfig-002-20260521    gcc-8.5.0

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


From luo.haiyang at zte.com.cn  Wed May 20 20:44:46 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:44:46 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYzIDAvNF0ga2V4ZWMtdG9vbO+8mmJ1ZyBmaXggZm9yIGNyYXNoZHVtcCBsb2FkIG9uIHJpc2N2?=
Message-ID: <20260521114446317oQrRN5l2oZcTCKtgKgYrH@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
         use kernel image to creat kernel text elf header.
patch 4: due to the kernel image being additionally exported in /proc/iomem,
         the kernel memory range is not correctly handled by to_be_excluded.

Changes from v2:
- use kernel image to creat kernel text elf header
- do not exclude kernel memory range
- https://lore.kernel.org/kexec/20260518145742805S2A5Zwk9KwoEtGDh4p3Pl at zte.com.cn/T/#md89827a089395745d3bd361889e4f3c490641732

Changes from v1:
- Add patch 2 and patch 3 to fix other bug
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u

Luo Haiyang (4):
  RISC-V: fix crashdump load memory ranges
  RISCV: Fix incorrect virtual address translation in crashdump load
  RISC-V: use kernel image to creat kernel text elf header
  RISC-V: kernel image memory rang should not be excluded

 kexec/arch/riscv/Makefile          |   2 +
 kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
 kexec/arch/riscv/iomem.h           |   3 +-
 kexec/arch/riscv/kexec-riscv.c     |   8 +-
 util_lib/elf_info.c                |  41 ++++--
 util_lib/include/elf_info.h        |   1 +
 6 files changed, 184 insertions(+), 89 deletions(-)

-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 20:47:33 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:47:33 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYzIDAvNF0ga2V4ZWMtdG9vbO+8mmJ1ZyBmaXggZm9yIGNyYXNoZHVtcCBsb2FkIG9uIHJpc2N2?=
Message-ID: <202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
         use kernel image to creat kernel text elf header.
patch 4: due to the kernel image being additionally exported in /proc/iomem,
         the kernel memory range is not correctly handled by to_be_excluded.

Changes from v2:
- use kernel image to creat kernel text elf header
- do not exclude kernel memory range
- https://lore.kernel.org/kexec/20260518145742805S2A5Zwk9KwoEtGDh4p3Pl at zte.com.cn/T/#md89827a089395745d3bd361889e4f3c490641732

Changes from v1:
- Add patch 2 and patch 3 to fix other bug
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u

Luo Haiyang (4):
  RISC-V: fix crashdump load memory ranges
  RISCV: Fix incorrect virtual address translation in crashdump load
  RISC-V: use kernel image to creat kernel text elf header
  RISC-V: kernel image memory rang should not be excluded

 kexec/arch/riscv/Makefile          |   2 +
 kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
 kexec/arch/riscv/iomem.h           |   3 +-
 kexec/arch/riscv/kexec-riscv.c     |   8 +-
 util_lib/elf_info.c                |  41 ++++--
 util_lib/include/elf_info.h        |   1 +
 6 files changed, 184 insertions(+), 89 deletions(-)

-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 20:49:34 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:49:34 +0800 (CST)
Subject: =?UTF-8?B?wqBbUEFUQ0ggdjMgMS80XcKgUklTQy1WOiBmaXggY3Jhc2hkdW1wIGxvYWQgbWVtb3J5IHJhbmdlcw==?=
In-Reply-To: <202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn>
References: 202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn
Message-ID: <20260521114934073qTN5cXZIphEw6vqcHRtyS@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
kdump.sh[338]: validate_mem_section: Can't read mem_section array.
kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
kdump.sh[338]: get_mm_sparsemem: Can't get the address of mem_section.
kdump.sh[338]: The kernel version is not supported.
kdump.sh[338]: The makedumpfile operation may be incomplete.

crash_mem_ranges only includes available memory, not all system RAM.
Inspired by the arm64 architecture, use system_mem_ranges in the
load_elfcorehdr function.

Fixes: b3fd54b (RISC-V: Add support for riscv kexec/kdump on kexec-tools)
Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
kexec/arch/riscv/crashdump-riscv.c | 167 ++++++++++++++++++-----------
1 file changed, 102 insertions(+), 65 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 336d7a7..e923a71 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -6,6 +6,7 @@
#include "crashdump.h"
#include "kexec-elf.h"
#include "mem_regions.h"
+#include "iomem.h"
static struct crash_elf_info elf_info = {
#if __riscv_xlen == 64
@@ -18,6 +19,7 @@ static struct crash_elf_info elf_info = {
};
static struct memory_ranges crash_mem_ranges = {0};
+static struct memory_ranges system_mem_ranges = {0};
struct memory_range elfcorehdr_mem = {0};
static unsigned long long get_page_offset(struct kexec_info *info)
@@ -34,86 +36,114 @@ static unsigned long long get_page_offset(struct kexec_info *info)
return vaddr_off;
}
-int load_elfcorehdr(struct kexec_info *info)
+/*
+ * iomem_range_callback() - callback called for each iomem region
+ * @data: not used
+ * @nr: not used
+ * @str: name of the memory region
+ * @base: start address of the memory region
+ * @length: size of the memory region
+ *
+ * This function is called once for each memory region found in /proc/iomem.
+ * It locates system RAM and crashkernel reserved memory and places these to
+ * variables, respectively, system_memory_rgns and crash_mem_ranges.
+ */
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+               char *str, unsigned long long base,
+               unsigned long long length)
{
-   struct memory_range crashkern_range = {0};
-   struct memory_range *ranges = NULL;
-   unsigned long start = 0;
-   unsigned long end = 0;
-   unsigned long buf_size = 0;
-   unsigned long elfcorehdr_addr = 0;
-   void* buf = NULL;
-   int i = 0;
-   int ret = 0;
+   if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+       return mem_regions_alloc_and_add(&crash_mem_ranges,
+                       base, length, RANGE_RAM);
+   else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+       return mem_regions_alloc_and_add(&system_mem_ranges,
+                       base, length, RANGE_RAM);
+   else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+       elf_info.kern_paddr_start = base;
+   else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+       elf_info.kern_size = base + length - elf_info.kern_paddr_start;
-   ret = parse_iomem_single("Kernel code\n", &start, NULL);
-   if (ret) {
-       fprintf(stderr, "Cannot determine kernel physical base addr\n");
-       return -EINVAL;
-   }
-   elf_info.kern_paddr_start = start;
+   return 0;
+}
+
+/*
+ * crash_get_memory_ranges() - read system physical memory
+ *
+ * Function reads through system physical memory and stores found memory
+ * regions in system_memory_ranges.
+ * Regions are sorted in ascending order.
+ *
+ * Returns 0 in case of success and a negative value otherwise.
+ */
+static int crash_get_memory_ranges(void)
+{
+   int i;
+
+   if (!crash_mem_ranges.size)
+       kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
-   ret = parse_iomem_single("Kernel bss\n", NULL, &end);
-   if (ret) {
-       fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+   if (!crash_mem_ranges.size)
return -EINVAL;
+
+   dbgprint_mem_range("Reserved memory range",
+           crash_mem_ranges.ranges, crash_mem_ranges.size);
+
+   for (i = 0; i < crash_mem_ranges.size; i++) {
+       if (mem_regions_alloc_and_exclude(&system_mem_ranges,
+                   &crash_mem_ranges.ranges[i])) {
+           fprintf(stderr, "Cannot allocate memory for ranges\n");
+           return -ENOMEM;
+       }
}
-   elf_info.kern_paddr_start = start;
-   elf_info.kern_size = end - start;
+   /*
+    * Make sure that the memory regions are sorted.
+    */
+   mem_regions_sort(&system_mem_ranges);
+
+   dbgprint_mem_range("Coredump memory ranges",
+              system_mem_ranges.ranges, system_mem_ranges.size);
+
+   /*
+    * For additional kernel code/data segment.
+    * kern_paddr_start/kern_size are determined in iomem_range_callback
+    */
elf_info.kern_vaddr_start = get_kernel_sym("_text");
-   if (!elf_info.kern_vaddr_start) {
+   if (!elf_info.kern_vaddr_start)
elf_info.kern_vaddr_start = UINT64_MAX;
-   }
-   elf_info.page_offset = get_page_offset(info);
-   dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
+   return 0;
+}
-   ret = parse_iomem_single("Crash kernel\n", &start, &end);
-   if (ret) {
-       fprintf(stderr, "Cannot determine kernel physical bss addr\n");
-       return -EINVAL;
-   }
-   crashkern_range.start = start;
-   crashkern_range.end = end;
-   crashkern_range.type = RANGE_RESERVED;
-
-   ranges = info->memory_range;
-   for (i = 0; i < info->memory_ranges; i++) {
-       ret = mem_regions_alloc_and_add(&crash_mem_ranges,
-                       ranges[i].start,
-                       ranges[i].end - ranges[i].start + 1,
-                       ranges[i].type);
-       if (ret ) {
-           fprintf(stderr, "Could not create crash_mem_ranges\n");
-           return ret;
-       }
-   }
+int load_elfcorehdr(struct kexec_info *info)
+{
+   unsigned long buf_size = 0;
+   unsigned long elfcorehdr_addr = 0;
+   void* buf = NULL;
-   ret = mem_regions_alloc_and_exclude(&crash_mem_ranges,
-                       &crashkern_range);
-   if (ret) {
-       fprintf(stderr, "Could not exclude crashkern_range\n");
-       return ret;
-   }
+   if (crash_get_memory_ranges())
+       return EFAILED;
+
+   elf_info.page_offset = get_page_offset(info);
+   dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
#if __riscv_xlen == 64
-   crash_create_elf64_headers(info, &elf_info, crash_mem_ranges.ranges,
-                  crash_mem_ranges.size, &buf, &buf_size,
+   crash_create_elf64_headers(info, &elf_info, system_mem_ranges.ranges,
+                  system_mem_ranges.size, &buf, &buf_size,
ELF_CORE_HEADER_ALIGN);
#else
-   crash_create_elf32_headers(info, &elf_info, crash_mem_ranges.ranges,
-                  crash_mem_ranges.size, &buf, &buf_size,
+   crash_create_elf32_headers(info, &elf_info, system_mem_ranges.ranges,
+                  system_mem_ranges.size, &buf, &buf_size,
ELF_CORE_HEADER_ALIGN);
#endif
elfcorehdr_addr = add_buffer_phys_virt(info, buf, buf_size,
-                          buf_size, 0,
-                          crashkern_range.start,
-                          crashkern_range.end,
-                          -1, 0);
+                       buf_size, 0,
+                       crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start,
+                       crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end,
+                       -1, 0);
elfcorehdr_mem.start = elfcorehdr_addr;
elfcorehdr_mem.end = elfcorehdr_addr + buf_size - 1;
@@ -126,15 +156,22 @@ int load_elfcorehdr(struct kexec_info *info)
int is_crashkernel_mem_reserved(void)
{
-   uint64_t start = 0;
-   uint64_t end = 0;
+   if (!crash_mem_ranges.size)
+       kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
-   return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
-          (start != end) : 0;
+   return crash_mem_ranges.size;
}
int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
{
-   return parse_iomem_single("Crash kernel\n", start, end);
-}
+   if (!crash_mem_ranges.size)
+       kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+   if (!crash_mem_ranges.size)
+       return -1;
+
+   *start = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start;
+   *end = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end;
+   return 0;
+}
--
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 20:53:36 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:53:36 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYzIDIvNF0gUklTQ1Y6IEZpeCBpbmNvcnJlY3QgdmlydHVhbCBhZGRyZXNzIHRyYW5zbGF0aW9uIGluCiBjcmFzaGR1bXAgbG9hZA==?=
In-Reply-To: <202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn>
References: 202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn
Message-ID: <20260521115336553PqpEkTjTdtzwo2Jnk_1Tn@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kexec shows the following crashdump log:

  Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
  Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
  Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...

Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
the linear address mapping is defined as:
  va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/Makefile          |  2 ++
 kexec/arch/riscv/crashdump-riscv.c | 55 ++++++++++++++++++++++++------
 util_lib/elf_info.c                | 41 +++++++++++++++++-----
 util_lib/include/elf_info.h        |  1 +
 4 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
index 18a997b..e886770 100644
--- a/kexec/arch/riscv/Makefile
+++ b/kexec/arch/riscv/Makefile
@@ -14,6 +14,8 @@ riscv_ARCH_REUSE_INITRD =

 riscv_CPPFLAGS += -I $(srcdir)/kexec/

+riscv_PHYS_TO_VIRT =
+
 dist += $(riscv_KEXEC_SRCS)				\
 	kexec/arch/riscv/image-header.h			\
 	kexec/arch/riscv/include/arch/options.h		\
diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index e923a71..6875249 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <elf.h>
+#include <elf_info.h>
 #include <unistd.h>

 #include "kexec.h"
@@ -22,18 +23,45 @@ static struct memory_ranges crash_mem_ranges = {0};
 static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

-static unsigned long long get_page_offset(struct kexec_info *info)
+static unsigned long long phys_offset;
+
+static int get_page_offset(unsigned long long *page_offset)
 {
-	unsigned long long vaddr_off = 0;
-	unsigned long long page_size = sysconf(_SC_PAGESIZE);
-	unsigned long long init_start = get_kernel_sym("_sinittext");
+	int fd, ret = 0;

-	/*
-	 * Begining of init section is aligned to page size
-	 */
-	vaddr_off = init_start - page_size;
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_page_offset_elf_kcore(fd, (long *)page_offset);
+	if (ret)
+		fprintf(stderr, "Can't get page_offset from /proc/kcore\n");
+
+	close(fd);
+	return ret;
+}
+
+static int get_phys_offset(unsigned long long *phys_offset)
+{
+	int fd, ret = 0;
+
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_phys_offset_elf_kcore(fd, (long *)phys_offset);
+	if (ret)
+		fprintf(stderr, "Can't get phys_offset from /proc/kcore\n");

-	return vaddr_off;
+	close(fd);
+	return ret;
+}
+
+unsigned long phys_to_virt(struct crash_elf_info *elf_info, unsigned long long p)
+{
+	return elf_info->page_offset - phys_offset + p;
 }

 /*
@@ -124,7 +152,14 @@ int load_elfcorehdr(struct kexec_info *info)
 	if (crash_get_memory_ranges())
 		return EFAILED;

-	elf_info.page_offset = get_page_offset(info);
+	if (get_phys_offset(&phys_offset))
+		return EFAILED;
+
+	dbgprintf("phys_offset:   %016llx\n", phys_offset);
+
+	if (get_page_offset(&elf_info.page_offset))
+		return EFAILED;
+
 	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 589bc1a..36eb895 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -1308,16 +1308,39 @@ int read_phys_offset_elf_kcore(int fd, long *phys_off)

 	*phys_off = ULONG_MAX;

+	if (phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	/* If we have a valid 'PHYS_OFFSET' by now,
+	 * return it to the caller now.
+	 */
 	ret = read_elf(fd);
-	if (!ret) {
-		/* If we have a valid 'PHYS_OFFSET' by now,
-		 * return it to the caller now.
-		 */
-		if (phys_offset != UINT64_MAX) {
-			*phys_off = phys_offset;
-			return ret;
-		}
+	if (!ret && phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	return -EFAULT;
+}
+
+int read_page_offset_elf_kcore(int fd, long *page_off)
+{
+	int ret;
+
+	*page_off = ULONG_MAX;
+
+	if (page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
+	}
+
+	ret = read_elf(fd);
+	if (!ret && page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
 	}

-	return 2;
+	return -EFAULT;
 }
diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
index fdf4c3d..3ccc950 100644
--- a/util_lib/include/elf_info.h
+++ b/util_lib/include/elf_info.h
@@ -29,6 +29,7 @@ int get_pt_load(int idx,
 	unsigned long long *virt_start,
 	unsigned long long *virt_end);
 int read_phys_offset_elf_kcore(int fd, long *phys_off);
+int read_page_offset_elf_kcore(int fd, long *page_off);
 int read_elf(int fd);
 void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
 extern void (*arch_scan_vmcoreinfo)(char *pos);
-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 20:56:40 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:56:40 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYzIDMvNF0gUklTQy1WOiB1c2Uga2VybmVsIGltYWdlIHRvIGNyZWF0IGtlcm5lbCB0ZXh0IGVsZiBoZWFkZXI=?=
In-Reply-To: <202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn>
References: 202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn
Message-ID: <20260521115640238hMJ3vfjHjUAucs3zqox9U@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

  No program header covering vaddr 0xffffffff819fa200 found kexec bug?
  saving vmcore-dmesg.txt failed

prb stores the address of printk_rb_dynamic, which resides in the bss segment.
But the PHDR (Program Header) does not contains it. The correct approach is to
use the entire kernel memory range (includs text, rodata, data, bss) to create
the Kernel text Elf header. On RISC-V, we can use the "kernel image" directly,
which can be read from /proc/iomem.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 10 +++++-----
 kexec/arch/riscv/iomem.h           |  1 +
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 6875249..deb9a49 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -80,16 +80,16 @@ static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
 				char *str, unsigned long long base,
 				unsigned long long length)
 {
-	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0) {
 		return mem_regions_alloc_and_add(&crash_mem_ranges,
 						base, length, RANGE_RAM);
-	else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+	} else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0) {
 		return mem_regions_alloc_and_add(&system_mem_ranges,
 						base, length, RANGE_RAM);
-	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+	} else if (strncmp(str, KERNEL_IMAGE, strlen(KERNEL_IMAGE)) == 0) {
 		elf_info.kern_paddr_start = base;
-	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
-		elf_info.kern_size = base + length - elf_info.kern_paddr_start;
+		elf_info.kern_size = length;
+	}

 	return 0;
 }
diff --git a/kexec/arch/riscv/iomem.h b/kexec/arch/riscv/iomem.h
index 7671e26..86da971 100644
--- a/kexec/arch/riscv/iomem.h
+++ b/kexec/arch/riscv/iomem.h
@@ -4,6 +4,7 @@
 #define SYSTEM_RAM		"System RAM\n"
 #define KERNEL_CODE		"Kernel code\n"
 #define KERNEL_DATA		"Kernel data\n"
+#define KERNEL_IMAGE		"Kernel image\n"
 #define CRASH_KERNEL		"Crash kernel\n"
 #define IOMEM_RESERVED		"Reserved\n"

-- 
2.27.0


???
???????/?????/????_???????
??????????
?????????800?????A?
M: +86 18215606036
E: luo.haiyang at zte.com.cn
www.zte.com.cn
------------------Original------------------
From: ???10243395
To: kexec at lists.infradead.org <kexec at lists.infradead.org>;simonhorman <horms at kernel.org>;
Cc: ??10190954;??10034260;
Date: 2026?05?21? 11:47
Subject: [PATCH v3 0/4] kexec-tool?bug fix for crashdump load on riscv
From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
use kernel image to creat kernel text elf header.
patch 4: due to the kernel image being additionally exported in /proc/iomem,
the kernel memory range is not correctly handled by to_be_excluded.

Changes from v2:
- use kernel image to creat kernel text elf header
- do not exclude kernel memory range
- https://lore.kernel.org/kexec/20260518145742805S2A5Zwk9KwoEtGDh4p3Pl at zte.com.cn/T/#md89827a089395745d3bd361889e4f3c490641732

Changes from v1:
- Add patch 2 and patch 3 to fix other bug
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u

Luo Haiyang (4):
RISC-V: fix crashdump load memory ranges
RISCV: Fix incorrect virtual address translation in crashdump load
RISC-V: use kernel image to creat kernel text elf header
RISC-V: kernel image memory rang should not be excluded

kexec/arch/riscv/Makefile          |   2 +
kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
kexec/arch/riscv/iomem.h           |   3 +-
kexec/arch/riscv/kexec-riscv.c     |   8 +-
util_lib/elf_info.c                |  41 ++++--
util_lib/include/elf_info.h        |   1 +
6 files changed, 184 insertions(+), 89 deletions(-)

--
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 20:58:06 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 11:58:06 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHYzIDQvNF0gUklTQy1WOiBrZXJuZWwgaW1hZ2UgbWVtb3J5IHJhbmcgc2hvdWxkIG5vdCBiZSBleGNsdWRlZA==?=
In-Reply-To: <202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn>
References: 202605211147336599soXOrzLshSGck0QWydIB@zte.com.cn
Message-ID: <20260521115806840hgVlL9lw3qYibIzTZgFE-@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

On RISC-V, due to the kernel image being additionally exported
in /proc/iomem, the kernel memory range is not correctly handled
by to_be_excluded.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/iomem.h       | 2 --
 kexec/arch/riscv/kexec-riscv.c | 8 +++-----
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/kexec/arch/riscv/iomem.h b/kexec/arch/riscv/iomem.h
index 86da971..1ab704c 100644
--- a/kexec/arch/riscv/iomem.h
+++ b/kexec/arch/riscv/iomem.h
@@ -2,8 +2,6 @@
 #define IOMEM_H

 #define SYSTEM_RAM		"System RAM\n"
-#define KERNEL_CODE		"Kernel code\n"
-#define KERNEL_DATA		"Kernel data\n"
 #define KERNEL_IMAGE		"Kernel image\n"
 #define CRASH_KERNEL		"Crash kernel\n"
 #define IOMEM_RESERVED		"Reserved\n"
diff --git a/kexec/arch/riscv/kexec-riscv.c b/kexec/arch/riscv/kexec-riscv.c
index a5a12a0..72fde08 100644
--- a/kexec/arch/riscv/kexec-riscv.c
+++ b/kexec/arch/riscv/kexec-riscv.c
@@ -417,12 +417,10 @@ static bool to_be_excluded(char *str, unsigned long long start, unsigned long lo
 		return true;
 	}

-	if (!strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) ||
-	    !strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) ||
-	    !strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)))
-		return false;
-	else
+	if (!strncmp(str, IOMEM_RESERVED, strlen(IOMEM_RESERVED)))
 		return true;
+
+	return false;
 }

 int get_memory_ranges(struct memory_range **range, int *num_ranges,
-- 
2.27.0






???
???????/?????/????_???????
??????????
?????????800?????A?
M: +86 18215606036
E: luo.haiyang at zte.com.cn
www.zte.com.cn
------------------Original------------------
From: ???10243395
To: kexec at lists.infradead.org <kexec at lists.infradead.org>;simonhorman <horms at kernel.org>;
Cc: ??10190954;??10034260;
Date: 2026?05?21? 11:47
Subject: [PATCH v3 0/4] kexec-tool?bug fix for crashdump load on riscv
From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
use kernel image to creat kernel text elf header.
patch 4: due to the kernel image being additionally exported in /proc/iomem,
the kernel memory range is not correctly handled by to_be_excluded.

Changes from v2:
- use kernel image to creat kernel text elf header
- do not exclude kernel memory range
- https://lore.kernel.org/kexec/20260518145742805S2A5Zwk9KwoEtGDh4p3Pl at zte.com.cn/T/#md89827a089395745d3bd361889e4f3c490641732

Changes from v1:
- Add patch 2 and patch 3 to fix other bug
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u

Luo Haiyang (4):
RISC-V: fix crashdump load memory ranges
RISCV: Fix incorrect virtual address translation in crashdump load
RISC-V: use kernel image to creat kernel text elf header
RISC-V: kernel image memory rang should not be excluded

kexec/arch/riscv/Makefile          |   2 +
kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
kexec/arch/riscv/iomem.h           |   3 +-
kexec/arch/riscv/kexec-riscv.c     |   8 +-
util_lib/elf_info.c                |  41 ++++--
util_lib/include/elf_info.h        |   1 +
6 files changed, 184 insertions(+), 89 deletions(-)

--
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 21:06:43 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 12:06:43 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHY0IDAvNF0ga2V4ZWMtdG9vbO+8mmJ1ZyBmaXggZm9yIGNyYXNoZHVtcCBsb2FkIG9uIHJpc2N2?=
Message-ID: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

This patchset fixes kexec-tool related to crashdump load on riscv

patch 1: crashload should use all system RAM but crash reserved memory.
patch 2: make the phys_to_virt function return the correct value.
patch 3: vmcore-dmesg accesses the address located in the bss segment,
use kernel image to creat kernel text elf header.
patch 4: due to the kernel image being additionally exported in /proc/iomem,
the kernel memory range is not correctly handled by to_be_excluded.

Changes from v3:
- sorry for the incorrect email format in the previous submission.

Changes from v2:
- use kernel image to creat kernel text elf header
- do not exclude kernel memory range
- https://lore.kernel.org/kexec/20260518145742805S2A5Zwk9KwoEtGDh4p3Pl at zte.com.cn/T/#md89827a089395745d3bd361889e4f3c490641732

Changes from v1:
- Add patch 2 and patch 3 to fix other bug
- https://lore.kernel.org/kexec/20260513171150519t11GTC14QEEV4EgIurNHM at zte.com.cn/T/#u

Luo Haiyang (4):
  RISC-V: fix crashdump load memory ranges
  RISCV: Fix incorrect virtual address translation in crashdump load
  RISC-V: use kernel image to creat kernel text elf header
  RISC-V: kernel image memory rang should not be excluded

 kexec/arch/riscv/Makefile          |   2 +
 kexec/arch/riscv/crashdump-riscv.c | 218 +++++++++++++++++++----------
 kexec/arch/riscv/iomem.h           |   3 +-
 kexec/arch/riscv/kexec-riscv.c     |   8 +-
 util_lib/elf_info.c                |  41 ++++--
 util_lib/include/elf_info.h        |   1 +
 6 files changed, 184 insertions(+), 89 deletions(-)

-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 21:08:24 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 12:08:24 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHY0IDEvNF0gUklTQy1WOiBmaXggY3Jhc2hkdW1wIGxvYWQgbWVtb3J5IHJhbmdlcw==?=
In-Reply-To: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn
Message-ID: <202605211208244654zOqELddaxmS3YhekusNL@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: validate_mem_section: Can't read mem_section array.
 kdump.sh[338]: readpage_elf: Attempt to read non-existent page at 0x17ee12000.
 kdump.sh[338]: readmem: type_addr: 0, addr:ffffffd6fee12b00, size:8
 kdump.sh[338]: get_mm_sparsemem: Can't get the address of mem_section.
 kdump.sh[338]: The kernel version is not supported.
 kdump.sh[338]: The makedumpfile operation may be incomplete.

crash_mem_ranges only includes available memory, not all system RAM.
Inspired by the arm64 architecture, use system_mem_ranges in the
load_elfcorehdr function.

Fixes: b3fd54b (RISC-V: Add support for riscv kexec/kdump on kexec-tools)
Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 167 ++++++++++++++++++-----------
 1 file changed, 102 insertions(+), 65 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 336d7a7..e923a71 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -6,6 +6,7 @@
 #include "crashdump.h"
 #include "kexec-elf.h"
 #include "mem_regions.h"
+#include "iomem.h"

 static struct crash_elf_info elf_info = {
 #if __riscv_xlen == 64
@@ -18,6 +19,7 @@ static struct crash_elf_info elf_info = {
 };

 static struct memory_ranges crash_mem_ranges = {0};
+static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

 static unsigned long long get_page_offset(struct kexec_info *info)
@@ -34,86 +36,114 @@ static unsigned long long get_page_offset(struct kexec_info *info)
 	return vaddr_off;
 }

-int load_elfcorehdr(struct kexec_info *info)
+/*
+ * iomem_range_callback() - callback called for each iomem region
+ * @data: not used
+ * @nr: not used
+ * @str: name of the memory region
+ * @base: start address of the memory region
+ * @length: size of the memory region
+ *
+ * This function is called once for each memory region found in /proc/iomem.
+ * It locates system RAM and crashkernel reserved memory and places these to
+ * variables, respectively, system_memory_rgns and crash_mem_ranges.
+ */
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+				char *str, unsigned long long base,
+				unsigned long long length)
 {
-	struct memory_range crashkern_range = {0};
-	struct memory_range *ranges = NULL;
-	unsigned long start = 0;
-	unsigned long end = 0;
-	unsigned long buf_size = 0;
-	unsigned long elfcorehdr_addr = 0;
-	void* buf = NULL;
-	int i = 0;
-	int ret = 0;
+	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+		return mem_regions_alloc_and_add(&crash_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+		return mem_regions_alloc_and_add(&system_mem_ranges,
+						base, length, RANGE_RAM);
+	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+		elf_info.kern_paddr_start = base;
+	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+		elf_info.kern_size = base + length - elf_info.kern_paddr_start;

-	ret = parse_iomem_single("Kernel code\n", &start, NULL);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical base addr\n");
-		return -EINVAL;
-	}
-	elf_info.kern_paddr_start = start;
+	return 0;
+}
+
+/*
+ * crash_get_memory_ranges() - read system physical memory
+ *
+ * Function reads through system physical memory and stores found memory
+ * regions in system_memory_ranges.
+ * Regions are sorted in ascending order.
+ *
+ * Returns 0 in case of success and a negative value otherwise.
+ */
+static int crash_get_memory_ranges(void)
+{
+	int i;
+
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	ret = parse_iomem_single("Kernel bss\n", NULL, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+	if (!crash_mem_ranges.size)
 		return -EINVAL;
+
+	dbgprint_mem_range("Reserved memory range",
+			crash_mem_ranges.ranges, crash_mem_ranges.size);
+
+	for (i = 0; i < crash_mem_ranges.size; i++) {
+		if (mem_regions_alloc_and_exclude(&system_mem_ranges,
+					&crash_mem_ranges.ranges[i])) {
+			fprintf(stderr, "Cannot allocate memory for ranges\n");
+			return -ENOMEM;
+		}
 	}
-	elf_info.kern_paddr_start = start;
-	elf_info.kern_size = end - start;

+	/*
+	 * Make sure that the memory regions are sorted.
+	 */
+	mem_regions_sort(&system_mem_ranges);
+
+	dbgprint_mem_range("Coredump memory ranges",
+			   system_mem_ranges.ranges, system_mem_ranges.size);
+
+	/*
+	 * For additional kernel code/data segment.
+	 * kern_paddr_start/kern_size are determined in iomem_range_callback
+	 */
 	elf_info.kern_vaddr_start = get_kernel_sym("_text");
-	if (!elf_info.kern_vaddr_start) {
+	if (!elf_info.kern_vaddr_start)
 		elf_info.kern_vaddr_start = UINT64_MAX;
-	}

-	elf_info.page_offset = get_page_offset(info);
-	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
+	return 0;
+}

-	ret = parse_iomem_single("Crash kernel\n", &start, &end);
-	if (ret) {
-		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
-		return -EINVAL;
-	}
-	crashkern_range.start = start;
-	crashkern_range.end = end;
-	crashkern_range.type = RANGE_RESERVED;
-
-	ranges = info->memory_range;
-	for (i = 0; i < info->memory_ranges; i++) {
-		ret = mem_regions_alloc_and_add(&crash_mem_ranges,
-						ranges[i].start,
-						ranges[i].end - ranges[i].start + 1,
-						ranges[i].type);
-		if (ret ) {
-			fprintf(stderr, "Could not create crash_mem_ranges\n");
-			return ret;
-		}
-	}
+int load_elfcorehdr(struct kexec_info *info)
+{
+	unsigned long buf_size = 0;
+	unsigned long elfcorehdr_addr = 0;
+	void* buf = NULL;

-	ret = mem_regions_alloc_and_exclude(&crash_mem_ranges,
-					    &crashkern_range);
-	if (ret) {
-		fprintf(stderr, "Could not exclude crashkern_range\n");
-		return ret;
-	}
+	if (crash_get_memory_ranges())
+		return EFAILED;
+
+	elf_info.page_offset = get_page_offset(info);
+	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
-	crash_create_elf64_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf64_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);

 #else
-	crash_create_elf32_headers(info, &elf_info, crash_mem_ranges.ranges,
-				   crash_mem_ranges.size, &buf, &buf_size,
+	crash_create_elf32_headers(info, &elf_info, system_mem_ranges.ranges,
+				   system_mem_ranges.size, &buf, &buf_size,
 				   ELF_CORE_HEADER_ALIGN);
 #endif


 	elfcorehdr_addr = add_buffer_phys_virt(info, buf, buf_size,
-					       buf_size, 0,
-					       crashkern_range.start,
-					       crashkern_range.end,
-					       -1, 0);
+					    buf_size, 0,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start,
+					    crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end,
+					    -1, 0);

 	elfcorehdr_mem.start = elfcorehdr_addr;
 	elfcorehdr_mem.end = elfcorehdr_addr + buf_size - 1;
@@ -126,15 +156,22 @@ int load_elfcorehdr(struct kexec_info *info)

 int is_crashkernel_mem_reserved(void)
 {
-	uint64_t start = 0;
-	uint64_t end = 0;
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);

-	return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
-	       (start != end) : 0;
+	return crash_mem_ranges.size;
 }

 int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
 {
-	return parse_iomem_single("Crash kernel\n", start, end);
-}
+	if (!crash_mem_ranges.size)
+		kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+	if (!crash_mem_ranges.size)
+		return -1;
+
+	*start = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].start;
+	*end = crash_mem_ranges.ranges[crash_mem_ranges.size - 1].end;

+	return 0;
+}
-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 21:09:46 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 12:09:46 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHY0IDIvNF0gUklTQ1Y6IEZpeCBpbmNvcnJlY3QgdmlydHVhbCBhZGRyZXNzIHRyYW5zbGF0aW9uIGluCiBjcmFzaGR1bXAgbG9hZA==?=
In-Reply-To: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn
Message-ID: <20260521120946097C8qpZkVC2mBT_ozYEHlhp@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kexec shows the following crashdump log:

Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...

Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
the linear address mapping is defined as:
va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
kexec/arch/riscv/Makefile          |  2 ++
kexec/arch/riscv/crashdump-riscv.c | 55 ++++++++++++++++++++++++------
util_lib/elf_info.c                | 41 +++++++++++++++++-----
util_lib/include/elf_info.h        |  1 +
4 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
index 18a997b..e886770 100644
--- a/kexec/arch/riscv/Makefile
+++ b/kexec/arch/riscv/Makefile
@@ -14,6 +14,8 @@ riscv_ARCH_REUSE_INITRD =
riscv_CPPFLAGS += -I $(srcdir)/kexec/
+riscv_PHYS_TO_VIRT =
+
dist += $(riscv_KEXEC_SRCS)                \
kexec/arch/riscv/image-header.h         \
kexec/arch/riscv/include/arch/options.h     \
diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index e923a71..6875249 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -1,5 +1,6 @@
#include <errno.h>
#include <elf.h>
+#include <elf_info.h>
#include <unistd.h>
#include "kexec.h"
@@ -22,18 +23,45 @@ static struct memory_ranges crash_mem_ranges = {0};
static struct memory_ranges system_mem_ranges = {0};
struct memory_range elfcorehdr_mem = {0};
-static unsigned long long get_page_offset(struct kexec_info *info)
+static unsigned long long phys_offset;
+
+static int get_page_offset(unsigned long long *page_offset)
{
-   unsigned long long vaddr_off = 0;
-   unsigned long long page_size = sysconf(_SC_PAGESIZE);
-   unsigned long long init_start = get_kernel_sym("_sinittext");
+   int fd, ret = 0;
-   /*
-    * Begining of init section is aligned to page size
-    */
-   vaddr_off = init_start - page_size;
+   if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+       fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+       return EFAILED;
+   }
+
+   ret = read_page_offset_elf_kcore(fd, (long *)page_offset);
+   if (ret)
+       fprintf(stderr, "Can't get page_offset from /proc/kcore\n");
+
+   close(fd);
+   return ret;
+}
+
+static int get_phys_offset(unsigned long long *phys_offset)
+{
+   int fd, ret = 0;
+
+   if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+       fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+       return EFAILED;
+   }
+
+   ret = read_phys_offset_elf_kcore(fd, (long *)phys_offset);
+   if (ret)
+       fprintf(stderr, "Can't get phys_offset from /proc/kcore\n");
-   return vaddr_off;
+   close(fd);
+   return ret;
+}
+
+unsigned long phys_to_virt(struct crash_elf_info *elf_info, unsigned long long p)
+{
+   return elf_info->page_offset - phys_offset + p;
}
/*
@@ -124,7 +152,14 @@ int load_elfcorehdr(struct kexec_info *info)
if (crash_get_memory_ranges())
return EFAILED;
-   elf_info.page_offset = get_page_offset(info);
+   if (get_phys_offset(&phys_offset))
+       return EFAILED;
+
+   dbgprintf("phys_offset:   %016llx\n", phys_offset);
+
+   if (get_page_offset(&elf_info.page_offset))
+       return EFAILED;
+
dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
#if __riscv_xlen == 64
diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 589bc1a..36eb895 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -1308,16 +1308,39 @@ int read_phys_offset_elf_kcore(int fd, long *phys_off)
*phys_off = ULONG_MAX;
+   if (phys_offset != UINT64_MAX) {
+       *phys_off = phys_offset;
+       return 0;
+   }
+
+   /* If we have a valid 'PHYS_OFFSET' by now,
+    * return it to the caller now.
+    */
ret = read_elf(fd);
-   if (!ret) {
-       /* If we have a valid 'PHYS_OFFSET' by now,
-        * return it to the caller now.
-        */
-       if (phys_offset != UINT64_MAX) {
-           *phys_off = phys_offset;
-           return ret;
-       }
+   if (!ret && phys_offset != UINT64_MAX) {
+       *phys_off = phys_offset;
+       return 0;
+   }
+
+   return -EFAULT;
+}
+
+int read_page_offset_elf_kcore(int fd, long *page_off)
+{
+   int ret;
+
+   *page_off = ULONG_MAX;
+
+   if (page_offset != UINT64_MAX) {
+       *page_off = page_offset;
+       return 0;
+   }
+
+   ret = read_elf(fd);
+   if (!ret && page_offset != UINT64_MAX) {
+       *page_off = page_offset;
+       return 0;
}
-   return 2;
+   return -EFAULT;
}
diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
index fdf4c3d..3ccc950 100644
--- a/util_lib/include/elf_info.h
+++ b/util_lib/include/elf_info.h
@@ -29,6 +29,7 @@ int get_pt_load(int idx,
unsigned long long *virt_start,
unsigned long long *virt_end);
int read_phys_offset_elf_kcore(int fd, long *phys_off);
+int read_page_offset_elf_kcore(int fd, long *page_off);
int read_elf(int fd);
void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
extern void (*arch_scan_vmcoreinfo)(char *pos);
--
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 21:12:20 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 12:12:20 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHY0IDMvNF0gUklTQy1WOiB1c2Uga2VybmVsIGltYWdlIHRvIGNyZWF0IGtlcm5lbCB0ZXh0IGVsZiBoZWFkZXI=?=
In-Reply-To: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn
Message-ID: <20260521121220522mLvWcpvupL3cN6oQAAUZ5@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kdump service reported the following error:

  No program header covering vaddr 0xffffffff819fa200 found kexec bug?
  saving vmcore-dmesg.txt failed

prb stores the address of printk_rb_dynamic, which resides in the bss segment.
But the PHDR (Program Header) does not contains it. The correct approach is to
use the entire kernel memory range (includs text, rodata, data, bss) to create
the Kernel text Elf header. On RISC-V, we can use the "kernel image" directly,
which can be read from /proc/iomem.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/crashdump-riscv.c | 10 +++++-----
 kexec/arch/riscv/iomem.h           |  1 +
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index 6875249..deb9a49 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -80,16 +80,16 @@ static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
 				char *str, unsigned long long base,
 				unsigned long long length)
 {
-	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+	if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0) {
 		return mem_regions_alloc_and_add(&crash_mem_ranges,
 						base, length, RANGE_RAM);
-	else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+	} else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0) {
 		return mem_regions_alloc_and_add(&system_mem_ranges,
 						base, length, RANGE_RAM);
-	else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0)
+	} else if (strncmp(str, KERNEL_IMAGE, strlen(KERNEL_IMAGE)) == 0) {
 		elf_info.kern_paddr_start = base;
-	else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
-		elf_info.kern_size = base + length - elf_info.kern_paddr_start;
+		elf_info.kern_size = length;
+	}

 	return 0;
 }
diff --git a/kexec/arch/riscv/iomem.h b/kexec/arch/riscv/iomem.h
index 7671e26..86da971 100644
--- a/kexec/arch/riscv/iomem.h
+++ b/kexec/arch/riscv/iomem.h
@@ -4,6 +4,7 @@
 #define SYSTEM_RAM		"System RAM\n"
 #define KERNEL_CODE		"Kernel code\n"
 #define KERNEL_DATA		"Kernel data\n"
+#define KERNEL_IMAGE		"Kernel image\n"
 #define CRASH_KERNEL		"Crash kernel\n"
 #define IOMEM_RESERVED		"Reserved\n"

-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 20 21:13:36 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Thu, 21 May 2026 12:13:36 +0800 (CST)
Subject: =?UTF-8?B?W1BBVENIIHY0IDQvNF0gUklTQy1WOiBrZXJuZWwgaW1hZ2UgbWVtb3J5IHJhbmcgc2hvdWxkIG5vdCBiZSBleGNsdWRlZA==?=
In-Reply-To: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn
Message-ID: <20260521121336256QPCc5q-hUOhqOY00pLpR_@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

On RISC-V, due to the kernel image being additionally exported
in /proc/iomem, the kernel memory range is not correctly handled
by to_be_excluded.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/iomem.h       | 2 --
 kexec/arch/riscv/kexec-riscv.c | 8 +++-----
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/kexec/arch/riscv/iomem.h b/kexec/arch/riscv/iomem.h
index 86da971..1ab704c 100644
--- a/kexec/arch/riscv/iomem.h
+++ b/kexec/arch/riscv/iomem.h
@@ -2,8 +2,6 @@
 #define IOMEM_H

 #define SYSTEM_RAM		"System RAM\n"
-#define KERNEL_CODE		"Kernel code\n"
-#define KERNEL_DATA		"Kernel data\n"
 #define KERNEL_IMAGE		"Kernel image\n"
 #define CRASH_KERNEL		"Crash kernel\n"
 #define IOMEM_RESERVED		"Reserved\n"
diff --git a/kexec/arch/riscv/kexec-riscv.c b/kexec/arch/riscv/kexec-riscv.c
index a5a12a0..72fde08 100644
--- a/kexec/arch/riscv/kexec-riscv.c
+++ b/kexec/arch/riscv/kexec-riscv.c
@@ -417,12 +417,10 @@ static bool to_be_excluded(char *str, unsigned long long start, unsigned long lo
 		return true;
 	}

-	if (!strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) ||
-	    !strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) ||
-	    !strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)))
-		return false;
-	else
+	if (!strncmp(str, IOMEM_RESERVED, strlen(IOMEM_RESERVED)))
 		return true;
+
+	return false;
 }

 int get_memory_ranges(struct memory_range **range, int *num_ranges,
-- 
2.27.0


From pratyush at kernel.org  Thu May 21 08:25:00 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Thu, 21 May 2026 17:25:00 +0200
Subject: [PATCH] liveupdate: document systemd support
In-Reply-To: <20260519152957.548119-1-luca.boccassi@gmail.com> (luca
	boccassi's message of "Tue, 19 May 2026 16:28:39 +0100")
References: <20260519152957.548119-1-luca.boccassi@gmail.com>
Message-ID: <2vxzbje8944z.fsf@kernel.org>

On Tue, May 19 2026, luca.boccassi at gmail.com wrote:

> From: Luca Boccassi <luca.boccassi at gmail.com>
>
> systemd v261 will add native support for LUO via the file descriptor
> store mechanism. Add a brief paragraph in the LUO userspace documentation
> page to inform users.
>
> Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>

Acked-by: Pratyush Yadav <pratyush at kernel.org>

But of course, we should only apply this once systemd v261 is released.

> ---
> NOTE: the external links will mention LUO after the next release is
>       published, which should happen within ~4 weeks
>

-- 
Regards,
Pratyush Yadav


From pasha.tatashin at soleen.com  Thu May 21 10:57:47 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 17:57:47 +0000
Subject: [PATCH] liveupdate: document systemd support
In-Reply-To: <20260519152957.548119-1-luca.boccassi@gmail.com>
References: <20260519152957.548119-1-luca.boccassi@gmail.com>
Message-ID: <ag9Hj5j4OFRn1G8c@plex>

On 05-19 16:28, luca.boccassi at gmail.com wrote:
> From: Luca Boccassi <luca.boccassi at gmail.com>
> 
> systemd v261 will add native support for LUO via the file descriptor
> store mechanism. Add a brief paragraph in the LUO userspace documentation
> page to inform users.
> 
> Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>
> ---
> NOTE: the external links will mention LUO after the next release is
>       published, which should happen within ~4 weeks
> 
>  Documentation/userspace-api/liveupdate.rst | 23 ++++++++++++++++++++++
>  1 file changed, 23 insertions(+)
> 
> diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
> index 41c0473e4f16..f6efc32fb40b 100644
> --- a/Documentation/userspace-api/liveupdate.rst
> +++ b/Documentation/userspace-api/liveupdate.rst
> @@ -14,6 +14,29 @@ ioctl uAPI
>  ===========
>  .. kernel-doc:: include/uapi/linux/liveupdate.h
>  
> +Userspace Integration
> +=====================
> +
> +systemd (since version v261) uses LUO to preserve its per-service file
> +descriptor store across a kexec-based live update. Services opt in by setting
> +``FileDescriptorStoreMax=`` and ``FileDescriptorStorePreserve=`` in their unit,
> +and push file descriptors with a name into the store via
> +``sd_pid_notify_with_fds(... "FDSTORE=1\nFDNAME=foo")``.
> +
> +Services may also create their own LUO sessions (via ``/dev/liveupdate``) and
> +push the resulting session fds into their file descriptor store like any other
> +fd. systemd detects such session fds and handles them accordingly, and
> +hands the re-retrieved session fd back to the service after kexec, using the
> +existing file descriptor store service interface.
> +
> +For details, see:
> +
> +- `File Descriptor Store <https://systemd.io/FILE_DESCRIPTOR_STORE/>`_
> +- `systemd.service(5) FileDescriptorStorePreserve=
> +  <https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#FileDescriptorStorePreserve=>`_
> +- `sd_pid_notify_with_fds(3)
> +  <https://www.freedesktop.org/software/systemd/man/latest/sd_pid_notify_with_fds.html>`_
> +

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

>  See Also
>  ========
>  
> -- 
> 2.47.3
> 


From pasha.tatashin at soleen.com  Thu May 21 11:00:30 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 18:00:30 +0000
Subject: [PATCH] liveupdate: document systemd support
In-Reply-To: <2vxzbje8944z.fsf@kernel.org>
References: <20260519152957.548119-1-luca.boccassi@gmail.com>
 <2vxzbje8944z.fsf@kernel.org>
Message-ID: <ag9HpKdDuu7LvL3N@plex>

On 05-21 17:25, Pratyush Yadav wrote:
> On Tue, May 19 2026, luca.boccassi at gmail.com wrote:
> 
> > From: Luca Boccassi <luca.boccassi at gmail.com>
> >
> > systemd v261 will add native support for LUO via the file descriptor
> > store mechanism. Add a brief paragraph in the LUO userspace documentation
> > page to inform users.
> >
> > Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>
> 
> Acked-by: Pratyush Yadav <pratyush at kernel.org>
> 
> But of course, we should only apply this once systemd v261 is released.

I think, time wise it is fair just to add it to liveupdate/next branch 
instead of micro-mange the exact time when this lands.

Pasha

> 
> > ---
> > NOTE: the external links will mention LUO after the next release is
> >       published, which should happen within ~4 weeks
> >
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Thu May 21 11:03:38 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 18:03:38 +0000
Subject: [PATCH] liveupdate: document systemd support
In-Reply-To: <20260519152957.548119-1-luca.boccassi@gmail.com>
References: <20260519152957.548119-1-luca.boccassi@gmail.com>
Message-ID: <177938661809.1151612.9365895540077391794.b4-ty@soleen.com>


On Tue, 19 May 2026 16:28:39 +0100, luca.boccassi at gmail.com wrote:
> systemd v261 will add native support for LUO via the file descriptor
> store mechanism. Add a brief paragraph in the LUO userspace documentation
> page to inform users.
> 
> 

Applied, thanks!

[1/1] liveupdate: document systemd support
      commit: 1b7ce86f23a430ac91a2ed6cd88046e16751b285

Best regards,
-- 
Pasha Tatashin <pasha.tatashin at soleen.com>



From pasha.tatashin at soleen.com  Thu May 21 11:38:20 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 18:38:20 +0000
Subject: [PATCH] pstore: add a KHO backend
In-Reply-To: <20260520174332.921186-1-mclapinski@google.com>
References: <20260520174332.921186-1-mclapinski@google.com>
Message-ID: <ag9LehR5HdX14M9s@plex>

On 05-20 19:43, Michal Clapinski wrote:
> Up to this point to preserve late shutdown logs in memory, users had to
> predefine a memory region using ramoops. This commit changes this by
> preserving a buffer using kexec-handover.
> 
> For now it supports preserving only 1 dmesg buffer.

"only" sounds like a limitation, but I do not think it is. If userspace 
is configured properly after every boot, it can back up the dmesg from 
the previous kernel instance if needed.

> It gets replaced with the new buffer on every kexec, so the user has to
> copy the file out of pstore after every kexec.
> There is no erase() support.
> 
> Signed-off-by: Michal Clapinski <mclapinski at google.com>
> ---
>  fs/pstore/Kconfig      |   9 ++
>  fs/pstore/Makefile     |   2 +
>  fs/pstore/pstore_kho.c | 267 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 278 insertions(+)
>  create mode 100644 fs/pstore/pstore_kho.c
> 
> diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig
> index 3acc38600cd1..70853799e211 100644
> --- a/fs/pstore/Kconfig
> +++ b/fs/pstore/Kconfig
> @@ -81,6 +81,15 @@ config PSTORE_RAM
>  
>  	  For more information, see Documentation/admin-guide/ramoops.rst.
>  
> +config PSTORE_KHO
> +	tristate "Preserve logs over kexec"
> +	depends on PSTORE
> +	depends on KEXEC_HANDOVER
> +	help
> +	  A pstore backend for preserving dmesg over KHO (kexec handover).
> +
> +	  It supports preservation of only 1 dmesg file.
> +
>  config PSTORE_ZONE
>  	tristate
>  	depends on PSTORE
> diff --git a/fs/pstore/Makefile b/fs/pstore/Makefile
> index c270467aeece..518cd408bf8e 100644
> --- a/fs/pstore/Makefile
> +++ b/fs/pstore/Makefile
> @@ -13,6 +13,8 @@ pstore-$(CONFIG_PSTORE_PMSG)	+= pmsg.o
>  ramoops-objs += ram.o ram_core.o
>  obj-$(CONFIG_PSTORE_RAM)	+= ramoops.o
>  
> +obj-$(CONFIG_PSTORE_KHO)	+= pstore_kho.o
> +
>  pstore_zone-objs += zone.o
>  obj-$(CONFIG_PSTORE_ZONE)	+= pstore_zone.o
>  
> diff --git a/fs/pstore/pstore_kho.c b/fs/pstore/pstore_kho.c
> new file mode 100644
> index 000000000000..3bc679273c8d
> --- /dev/null
> +++ b/fs/pstore/pstore_kho.c
> @@ -0,0 +1,267 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * KHO (Kexec Handover) backend for pstore
> + */

We need a proper Doc comment here explaining the benefits of using a 
KHO-based pstore. It has some very significant advantages, such as 
removing the need to manage a hardcoded memmap or rely on firmware 
support. Also, you can mention that pstore also provides the ability to 
preserve every single dmesg even after filesystem unmounts, without 
being limited by the console baud rate.

> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/kexec_handover.h>
> +#include <linux/libfdt.h>
> +#include <linux/module.h>
> +#include <linux/pstore.h>
> +#include <linux/slab.h>
> +#include <linux/unaligned.h>
> +
> +#define KHO_PSTORE_FDT_NAME	"pstore-kho"
> +#define KHO_PSTORE_COMPAT	"pstore-kho-v1"
> +#define KHO_PSTORE_DATA		"pstore_kho_record"
> +
> +static const size_t record_max_size = 1 << CONFIG_LOG_BUF_SHIFT;

The above should be added to include/linux/kho/abi/pstore.h

Also, there is no need to add an FDT subtree; it is much cleaner to add 
a sub-blob and use a C-struct directly. Let's add something like struct 
pstore_ser to the pstore ABI header to act as an anchor for everything 
this code references.

Additionally, what happens if CONFIG_LOG_BUF_SHIFT changes between two 
kexec'd kernels? Will this still work? If not, then the buffer size 
needs to be explicitly defined as part of the ABI.

[...]

Pleae review 
https://sashiko.dev/#/patchset/20260520174332.921186-1-mclapinski%40google.com 
review comments.

Pasha


From pasha.tatashin at soleen.com  Thu May 21 11:54:23 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 18:54:23 +0000
Subject: [PATCH] kho: make sure scratch size is always aligned by
 CMA_MIN_ALIGNMENT_BYTES
In-Reply-To: <20260519160554.2713361-1-pratyush@kernel.org>
References: <20260519160554.2713361-1-pratyush@kernel.org>
Message-ID: <177938966317.1212271.16034923918291092372.b4-ty@soleen.com>


On Tue, 19 May 2026 18:05:49 +0200, Pratyush Yadav wrote:
> When using scratch_scale, the scratch sizes are rounded up to
> CMA_MIN_ALIGNMENT_BYTES since they will be released as MIGRATE_CMA. This
> is not done when using fixed scratch sizes via command line. This can
> result in user specifying a size which is not aligned, and thus kernel
> releasing a pageblock that is only partially scratch.
> 
> Do the rounding up for both cases in scratch_size_update().
> 
> [...]

Applied, thanks!

[1/1] kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES
      commit: 4129715f7ac5b71183350f1983023caf0464cb9b

Best regards,
-- 
Pasha Tatashin <pasha.tatashin at soleen.com>



From pasha.tatashin at soleen.com  Thu May 21 12:07:47 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 19:07:47 +0000
Subject: [PATCH] kho: always print scratch sizes
In-Reply-To: <20260519155324.2692594-1-pratyush@kernel.org>
References: <20260519155324.2692594-1-pratyush@kernel.org>
Message-ID: <ag9VrVW3E28UOsAt@plex>

On 05-19 17:53, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Currently, KHO only prints scratch sizes when the size is specified
> by the command line. It prints nothing when the size is calculated
> at runtime by counting RSRV_KERN.
> 
> These prints are completely useless. When the user specified the size on
> the command line, they already know how large the scratch areas are. And
> that information is readily available by looking at the command line. It
> is a lot more useful to know the sizes that are calculated at runtime
> since they can vary by kernel version. Plus, if KHO fails to allocate
> the scratch areas, the calculated scratch sizes are not available via
> debugfs.
> 
> Print the scratch sizes in kho_reserve_scratch(). Do it before making
> the allocations. The size can provide hints for the failure reason.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  kernel/liveupdate/kexec_handover.c | 12 +++++++-----
>  1 file changed, 7 insertions(+), 5 deletions(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 33fcf848ef95..b030f53f2235 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -621,11 +621,6 @@ static int __init kho_parse_scratch_size(char *p)
>  	scratch_size_pernode = sizes[2];
>  	scratch_scale = 0;
>  
> -	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB pernode: %lldMiB\n",
> -		  (u64)(scratch_size_lowmem >> 20),
> -		  (u64)(scratch_size_global >> 20),
> -		  (u64)(scratch_size_pernode >> 20));
> -
>  	return 0;
>  }
>  early_param("kho_scratch", kho_parse_scratch_size);
> @@ -691,6 +686,10 @@ static void __init kho_reserve_scratch(void)
>  		goto err_disable_kho;
>  	}
>  
> +	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB\n",
> +		  (u64)(scratch_size_lowmem >> 20),
> +		  (u64)(scratch_size_global >> 20));
> +
>  	/*
>  	 * reserve scratch area in low memory for lowmem allocations in the
>  	 * next kernel
> @@ -725,6 +724,9 @@ static void __init kho_reserve_scratch(void)
>  	 */
>  	for_each_node_state(nid, N_MEMORY) {
>  		size = scratch_size_node(nid);
> +
> +		pr_notice("scratch_areas: nid %d: %lluMiB\n", nid, size >> 20);

Please use a space instead of an underscore to match the other string: 
"scratch areas". Keeping the prefix identical makes log parsing and 
filtering much cleaner.

Unrelated to this patch?this is something I brought up to Pratyush off-list, 
but I really dislike the 'KHO scratch' terminology.

First, it isn't actually 'scratch' space, as the kernel relies on it to 
allocate crucial early boot memory, which it then uses permanently. 
Second, we cannot preserve anything within this specific region. 

Down the line, I'd like to rename this to something more descriptive that clearly 
conveys both its importance to early boot and its non-preservable nature. For 
now, the print adjustments themselves look good.

I do not know it should be, but something like kho_bootstrap area sounds 
better than kho_sratch area.

> +
>  		addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES,
>  						0, MEMBLOCK_ALLOC_ACCESSIBLE,
>  						nid, true);
> 
> base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
> -- 
> 2.54.0.563.g4f69b47b94-goog
> 


From skhawaja at google.com  Thu May 21 12:31:59 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Thu, 21 May 2026 19:31:59 +0000
Subject: [PATCH v2 0/3] kho: Add support for kunit mocking KHO restore API
Message-ID: <20260521193202.746810-1-skhawaja@google.com>

To write kunit tests for preservation and restoration of liveupdate
state in various subsystems without triggering the actual kexec, the KHO
restore API needs to be mocked by the test writer. The mocking is done
to allow testing of the individual components or functions in isolation.

The patch series adds the following to support kunit testing when using the KHO
API:

- Add static stub hooks to mock the KHO restore API so the restore path
  can be tested without triggering kexec.
- Add helper function that can be used by the test writer to check if
  memory is preserved in KHO tree.

Finally, it adds a KUnit test for the KHO API that verifies the allocation of
preserved memory, and the preservation/restoration of pages and folios.

KHO Kunit test run:

 KTAP version 1
 1..1
     KTAP version 1
     # Subtest: kho_test
     # module: kexec_handover_test
     1..3
     ok 1 kho_test_alloc_preserve
     ok 2 kho_test_preserve_pages
     ok 3 kho_test_preserve_folio
 # kho_test: pass:3 fail:0 skip:0 total:3
 # Totals: pass:3 fail:0 skip:0 total:3
 ok 1 kho_test

v2:
 - Move kunit header includes above linux header includes.
 - Use the __kho_preserve_pages_order() to get the order of preserved
   pages instead of open order calculation math.

Samiullah Khawaja (3):
  kho: Add kunit static stubs
  kho: Add helper function to check if pages are preserved
  kho: Add kunit test to verify preserve/restore pages and folio

 include/linux/kexec_handover.h          |   5 +
 kernel/liveupdate/Kconfig               |  10 ++
 kernel/liveupdate/Makefile              |   1 +
 kernel/liveupdate/kexec_handover.c      |  63 +++++++++++-
 kernel/liveupdate/kexec_handover_test.c | 131 ++++++++++++++++++++++++
 5 files changed, 209 insertions(+), 1 deletion(-)
 create mode 100644 kernel/liveupdate/kexec_handover_test.c


base-commit: ec4084bc445027a52f600e30a976928be1ba1950
-- 
2.54.0.746.g67dd491aae-goog



From skhawaja at google.com  Thu May 21 12:32:00 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Thu, 21 May 2026 19:32:00 +0000
Subject: [PATCH v2 1/3] kho: Add kunit static stubs
In-Reply-To: <20260521193202.746810-1-skhawaja@google.com>
References: <20260521193202.746810-1-skhawaja@google.com>
Message-ID: <20260521193202.746810-2-skhawaja@google.com>

Add kunit stubs in kho_restore_folio() and kho_restore_pages() so the
users can mock these functions in kunit tests.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 kernel/liveupdate/kexec_handover.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 11855e275397..902019f36c24 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -10,6 +10,8 @@
 
 #define pr_fmt(fmt) "KHO: " fmt
 
+#include <kunit/static_stub.h>
+
 #include <linux/cleanup.h>
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
@@ -423,7 +425,11 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio)
  */
 struct folio *kho_restore_folio(phys_addr_t phys)
 {
-	struct page *page = kho_restore_page(phys, true);
+	struct page *page;
+
+	KUNIT_STATIC_STUB_REDIRECT(kho_restore_folio, phys);
+
+	page = kho_restore_page(phys, true);
 
 	return page ? page_folio(page) : NULL;
 }
@@ -445,6 +451,8 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)
 	const unsigned long end_pfn = start_pfn + nr_pages;
 	unsigned long pfn = start_pfn;
 
+	KUNIT_STATIC_STUB_REDIRECT(kho_restore_pages, phys, nr_pages);
+
 	while (pfn < end_pfn) {
 		const unsigned int order =
 			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
-- 
2.54.0.746.g67dd491aae-goog



From skhawaja at google.com  Thu May 21 12:32:01 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Thu, 21 May 2026 19:32:01 +0000
Subject: [PATCH v2 2/3] kho: Add helper function to check if pages are preserved
In-Reply-To: <20260521193202.746810-1-skhawaja@google.com>
References: <20260521193202.746810-1-skhawaja@google.com>
Message-ID: <20260521193202.746810-3-skhawaja@google.com>

Kunit test needs to check whether the pages are preserved after
preservation or after unpreserve. The helper function added is only
enabled when kunit is enabled.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 include/linux/kexec_handover.h     |  5 +++
 kernel/liveupdate/kexec_handover.c | 53 ++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..a3afc74cf41c 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -32,6 +32,11 @@ void kho_restore_free(void *mem);
 struct folio *kho_restore_folio(phys_addr_t phys);
 struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
 void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
+
+#if IS_ENABLED(CONFIG_KUNIT)
+bool kho_test_pages_preserved(phys_addr_t phys, unsigned long nr_pages);
+#endif
+
 int kho_add_subtree(const char *name, void *blob, size_t size);
 void kho_remove_subtree(void *blob);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 902019f36c24..abe201825e83 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) "KHO: " fmt
 
 #include <kunit/static_stub.h>
+#include <kunit/visibility.h>
 
 #include <linux/cleanup.h>
 #include <linux/cma.h>
@@ -1775,3 +1776,55 @@ int kho_locate_mem_hole(struct kexec_buf *kbuf,
 
 	return ret == 1 ? 0 : -EADDRNOTAVAIL;
 }
+
+#if IS_ENABLED(CONFIG_KUNIT)
+static bool _kho_is_pfn_preserved(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;
+	struct kho_radix_leaf *leaf;
+	unsigned int i, idx;
+
+	if (!tree->root)
+		return false;
+
+	guard(mutex)(&tree->lock);
+
+	node = tree->root;
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		idx = kho_radix_get_table_index(key, i);
+
+		if (!node->table[idx])
+			return false;
+
+		node = phys_to_virt(node->table[idx]);
+	}
+
+	idx = kho_radix_get_bitmap_index(key);
+	leaf = (struct kho_radix_leaf *)node;
+
+	return test_bit(idx, leaf->bitmap);
+}
+
+bool kho_test_pages_preserved(phys_addr_t phys, unsigned long nr_pages)
+{
+	struct kho_radix_tree *tree = &kho_out.radix_tree;
+	unsigned long pfn = PHYS_PFN(phys);
+	unsigned long end_pfn;
+	unsigned int order;
+
+	end_pfn = pfn + nr_pages;
+	while (pfn < end_pfn) {
+		order = __kho_preserve_pages_order(pfn, end_pfn);
+
+		if (!_kho_is_pfn_preserved(tree, pfn, order))
+			return false;
+
+		pfn += 1 << order;
+	}
+
+	return true;
+}
+EXPORT_SYMBOL_IF_KUNIT(kho_test_pages_preserved);
+#endif
-- 
2.54.0.746.g67dd491aae-goog



From skhawaja at google.com  Thu May 21 12:32:02 2026
From: skhawaja at google.com (Samiullah Khawaja)
Date: Thu, 21 May 2026 19:32:02 +0000
Subject: [PATCH v2 3/3] kho: Add kunit test to verify preserve/restore pages
 and folio
In-Reply-To: <20260521193202.746810-1-skhawaja@google.com>
References: <20260521193202.746810-1-skhawaja@google.com>
Message-ID: <20260521193202.746810-4-skhawaja@google.com>

Add a kunit test to verify the preserve/unpreserve and restore of pages
and folios by mocking the kho kunit restore stubs.

Signed-off-by: Samiullah Khawaja <skhawaja at google.com>
---
 kernel/liveupdate/Kconfig               |  10 ++
 kernel/liveupdate/Makefile              |   1 +
 kernel/liveupdate/kexec_handover_test.c | 131 ++++++++++++++++++++++++
 3 files changed, 142 insertions(+)
 create mode 100644 kernel/liveupdate/kexec_handover_test.c

diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index 1a8513f16ef7..d2623f60c0dd 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -51,6 +51,16 @@ config KEXEC_HANDOVER_ENABLE_DEFAULT
 	  The default behavior can still be overridden at boot time by
 	  passing 'kho=off'.
 
+config KEXEC_HANDOVER_KUNIT_TEST
+	tristate "KUnit test for Kexec Handover (KHO)"
+	depends on KEXEC_HANDOVER && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Enable Kunit tests for Kexec Handover to verify preservation and
+	  unpreservation of memory using KHO API without triggering kexec.
+
+	  If unsure, say N.
+
 config LIVEUPDATE
 	bool "Live Update Orchestrator"
 	depends on KEXEC_HANDOVER
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d2f779cbe279..ed798b2401a8 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -9,5 +9,6 @@ luo-y :=								\
 obj-$(CONFIG_KEXEC_HANDOVER)		+= kexec_handover.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)	+= kexec_handover_debug.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)	+= kexec_handover_debugfs.o
+obj-$(CONFIG_KEXEC_HANDOVER_KUNIT_TEST) += kexec_handover_test.o
 
 obj-$(CONFIG_LIVEUPDATE)		+= luo.o
diff --git a/kernel/liveupdate/kexec_handover_test.c b/kernel/liveupdate/kexec_handover_test.c
new file mode 100644
index 000000000000..9e6dca45c3f3
--- /dev/null
+++ b/kernel/liveupdate/kexec_handover_test.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Google LLC
+ * Author: Samiullah Khawaja <skhawaja at google.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/static_stub.h>
+
+#include <linux/gfp.h>
+#include <linux/kexec_handover.h>
+#include <linux/mm.h>
+
+static struct page *kho_test_restore_pages_mock(phys_addr_t phys,
+						unsigned long nr_pages)
+{
+	struct page *page = phys_to_page(phys);
+
+	if (!kho_test_pages_preserved(phys, nr_pages))
+		return NULL;
+
+	kho_unpreserve_pages(page, nr_pages);
+	return page;
+}
+
+static struct folio *kho_test_restore_folio_mock(phys_addr_t phys)
+{
+	struct folio *folio = page_folio(phys_to_page(phys));
+
+	if (!kho_test_pages_preserved(phys, (1 << folio_order(folio))))
+		return NULL;
+
+	kho_unpreserve_folio(folio);
+	return folio;
+}
+
+static int kho_test_init(struct kunit *test)
+{
+	kunit_activate_static_stub(test, kho_restore_pages,
+				   kho_test_restore_pages_mock);
+	kunit_activate_static_stub(test, kho_restore_folio,
+				   kho_test_restore_folio_mock);
+	return 0;
+}
+
+static void kho_test_alloc_preserve(struct kunit *test)
+{
+	void *mem;
+
+	mem = kho_alloc_preserve(PAGE_SIZE);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mem);
+
+	/* Verify if memory is preserved */
+	KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(__pa(mem), 1));
+
+	kho_restore_free(mem);
+
+	/* Memory should be unpreserved after restore_free */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(__pa(mem), 1));
+}
+
+static void kho_test_preserve_pages(struct kunit *test)
+{
+	struct page *restored;
+	struct page *page;
+	int err;
+
+	page = alloc_pages(GFP_KERNEL | __GFP_ZERO, 1);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+
+	/* Preserve and verify that pages are preserved */
+	err = kho_preserve_pages(page, 2);
+	KUNIT_EXPECT_EQ(test, err, 0);
+	KUNIT_EXPECT_TRUE(test,
+			  kho_test_pages_preserved(page_to_phys(page), 2));
+
+	restored = kho_restore_pages(page_to_phys(page), 2);
+	KUNIT_EXPECT_NOT_NULL(test, restored);
+	KUNIT_EXPECT_PTR_EQ(test, restored, page);
+
+	/* Verify that the pages are not preserved */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(page_to_phys(page), 2));
+
+	__free_pages(page, 1);
+}
+
+static void kho_test_preserve_folio(struct kunit *test)
+{
+	struct folio *restored;
+	unsigned long nr_pages;
+	struct folio *folio;
+	int err;
+
+	folio = folio_alloc(GFP_KERNEL | __GFP_ZERO, 1);
+	KUNIT_ASSERT_NOT_NULL(test, folio);
+
+	nr_pages = 1 << folio_order(folio);
+
+	/* Preserve and verify that folio is preserved */
+	err = kho_preserve_folio(folio);
+	KUNIT_EXPECT_EQ(test, err, 0);
+	KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(PFN_PHYS(folio_pfn(folio)), nr_pages));
+
+	restored = kho_restore_folio(PFN_PHYS(folio_pfn(folio)));
+	KUNIT_EXPECT_NOT_NULL(test, restored);
+	KUNIT_EXPECT_PTR_EQ(test, restored, folio);
+
+	/* Verify that the folio is not preserved */
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(PFN_PHYS(folio_pfn(folio)), nr_pages));
+
+	folio_put(folio);
+}
+
+static struct kunit_case kho_test_cases[] = {
+	KUNIT_CASE(kho_test_alloc_preserve),
+	KUNIT_CASE(kho_test_preserve_pages),
+	KUNIT_CASE(kho_test_preserve_folio),
+	{}
+};
+
+static struct kunit_suite kho_test_suite = {
+	.name = "kho_test",
+	.init = kho_test_init,
+	.test_cases = kho_test_cases,
+};
+
+kunit_test_suite(kho_test_suite);
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_DESCRIPTION("KUnit tests for Kexec Handover");
+MODULE_LICENSE("GPL");
-- 
2.54.0.746.g67dd491aae-goog



From pasha.tatashin at soleen.com  Thu May 21 16:27:01 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 23:27:01 +0000
Subject: [PATCH 02/12] kho: store incoming radix tree in kho_in
In-Reply-To: <2vxz4ikddi6y.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-3-pratyush@kernel.org>
 <agHA2imH5hdMSlId@kernel.org>
 <2vxz4ikddi6y.fsf@kernel.org>
Message-ID: <ag-UnPWdmYzqn_BV@plex>

On 05-11 18:28, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:04PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> This allows other functions to also use the radix tree. While at it,
> >> also use kho_get_mem_map_phys() instead of duplicating the code to get
> >> the radix tree root from the FDT.
> >> 
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> >>  kernel/liveupdate/kexec_handover.c | 27 ++++++++-------------------
> >>  1 file changed, 8 insertions(+), 19 deletions(-)
> >> 
> >> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> >> index ba568d34c5b4..5758dc6fab5d 100644
> >> --- a/kernel/liveupdate/kexec_handover.c
> >> +++ b/kernel/liveupdate/kexec_handover.c
> >> @@ -1294,6 +1294,7 @@ struct kho_in {
> >>  	char previous_release[__NEW_UTS_LEN + 1];
> >>  	u32 kexec_count;
> >>  	struct kho_debugfs dbg;
> >> +	struct kho_radix_tree radix_tree;
> >>  };
> >>  
> >>  static struct kho_in kho_in = {
> >> @@ -1373,24 +1374,10 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
> >>  
> >>  static int __init kho_mem_retrieve(const void *fdt)
> >>  {
> >> -	struct kho_radix_tree tree;
> >> -	const phys_addr_t *mem;
> >> -	int len;
> >> -
> >> -	/* Retrieve the KHO radix tree from passed-in FDT. */
> >> -	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
> >> -
> >> -	if (!mem || len != sizeof(*mem)) {
> >> -		pr_err("failed to get preserved KHO memory tree\n");
> >> -		return -ENOENT;
> >> -	}
> >> -
> >> -	if (!*mem)
> >> -		return -EINVAL;
> >> -
> >> -	tree.root = phys_to_virt(*mem);
> >> -	mutex_init(&tree.lock);
> >> -	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
> >> +	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
> >
> > Do we really want to remove an explicit check for 0 and get a WARN() in
> > kho_radix_walk_tree()?
> 
> If mem_map_phys is 0 then kho_populate() never sets kho_in.scratch_phys
> and thus kho_mem_retrieve() never gets called. So here it will never be
> 0, and if it is, a WARN() is certainly warranted.
> 
> Perhaps I should add a comment since this isn't so obvious?
> 
> >
> >> +	mutex_init(&kho_in.radix_tree.lock);
> >> +	return kho_radix_walk_tree(&kho_in.radix_tree,
> >> +				   kho_preserved_memory_reserve);
> >>  }
> >>  
> >>  static __init int kho_out_fdt_setup(void)
> >> @@ -1597,8 +1584,10 @@ void __init kho_memory_init(void)
> >>  	if (kho_in.scratch_phys) {
> >>  		kho_scratch = phys_to_virt(kho_in.scratch_phys);
> >>  
> >> -		if (kho_mem_retrieve(kho_get_fdt()))
> >> +		if (kho_mem_retrieve(kho_get_fdt())) {
> >>  			kho_in.fdt_phys = 0;
> >> +			kho_in.radix_tree.root = NULL;
> >
> > I'd make kho_mem_retrieve() void and put these there.
> 
> Sure, will do.

+1 on this one, otherwise LGTM.

Pasha

> 
> >
> >> +		}
> >>  	} else {
> >>  		kho_reserve_scratch();
> >>  	}
> >> -- 
> >> 2.54.0.545.g6539524ca2-goog
> >> 
> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Thu May 21 16:31:09 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 23:31:09 +0000
Subject: [PATCH 03/12] kho: add a struct for radix callbacks
In-Reply-To: <2vxz5x4tat6w.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-4-pratyush@kernel.org>
 <agHB7HB-p_vkLS7R@kernel.org>
 <2vxzzf25c39x.fsf@kernel.org>
 <agLNUKE5LZqcoUex@kernel.org>
 <2vxz5x4tat6w.fsf@kernel.org>
Message-ID: <ag-VfUr_XuxDuHS8@plex>

On 05-12 11:11, Pratyush Yadav wrote:
> On Tue, May 12 2026, Mike Rapoport wrote:
> 
> > On Mon, May 11, 2026 at 06:35:54PM +0200, Pratyush Yadav wrote:
> >> On Mon, May 11 2026, Mike Rapoport wrote:
> >> 
> >> > On Wed, Apr 29, 2026 at 03:39:05PM +0200, Pratyush Yadav wrote:
> >> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> >> 
> >> >> A future commit will add more callbacks for the KHO radix tree. Add a
> >> >> struct for collecting the callbacks.
> >> >> 
> >> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> >> ---
> >> [...]
> >> >> --- a/kernel/liveupdate/kexec_handover.c
> >> >> +++ b/kernel/liveupdate/kexec_handover.c
> >> >> @@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
> >> >>  }
> >> >>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
> >> >>  
> >> >> -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
> >> >> -			       unsigned long key,
> >> >> -			       kho_radix_tree_walk_callback_t cb)
> >> >> +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
> >> >> +			       const struct kho_radix_walk_cb *cb)
> >> >>  {
> >> >>  	unsigned long *bitmap = (unsigned long *)leaf;
> >> >>  	unsigned int i;
> >> >>  	int err;
> >> >>  
> >> >> +	if (!cb->key)
> >> >> +		return 0;
> >> >> +
> >> >>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
> >> >> -		err = cb(key | i);
> >> >> +		err = cb->key(key | i);
> >> >
> >> > key(key) reads weird :)
> >> > Can't say I have a good name, maybe key_action()?
> >> 
> >> I thought the context of it being under a callback struct would make it
> >> more obvious. I don't like key_action() much better TBH, but I don't
> >> have a strong opinion. Perhaps Pasha can suggest a 3rd option and we
> >> pick on randomly ;-)
> >
> > Another option I thought is to call them ->leaf() and ->node().
> 
> This one sounds nice. Will use it in v2.

Thank you. I also prefer ->leaf() and ->node(); they are simple and 
applicable to any tree.

> 
> -- 
> Regards,
> Pratyush Yadav


From pasha.tatashin at soleen.com  Thu May 21 16:34:25 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 23:34:25 +0000
Subject: [PATCH 05/12] kho: add data argument to radix walk callback
In-Reply-To: <20260429133928.850721-6-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-6-pratyush@kernel.org>
Message-ID: <ag-WdmAZXjb905-X@plex>

On 04-29 15:39, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Add an opaque data pointer argument to kho_radix_walk_cb_t. This can be
> used for callers to pass extra information to the callback.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     |  8 ++++----
>  kernel/liveupdate/kexec_handover.c | 24 +++++++++++++-----------
>  2 files changed, 17 insertions(+), 15 deletions(-)

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index fe7151d89361..6c0f7d82716b 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -44,8 +44,8 @@ struct kho_radix_tree {
>   * return value is directly returned to the caller.
>   */
>  struct kho_radix_walk_cb {
> -	int (*key)(unsigned long key);
> -	int (*table)(phys_addr_t phys);
> +	int (*key)(unsigned long key, void *data);
> +	int (*table)(phys_addr_t phys, void *data);
>  };
>  
>  #ifdef CONFIG_KEXEC_HANDOVER
> @@ -53,7 +53,7 @@ struct kho_radix_walk_cb {
>  int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
>  void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
>  int kho_radix_walk_tree(struct kho_radix_tree *tree,
> -			const struct kho_radix_walk_cb *cb);
> +			const struct kho_radix_walk_cb *cb, void *data);
>  
>  #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
>  
> @@ -66,7 +66,7 @@ static inline void kho_radix_del_key(struct kho_radix_tree *tree,
>  				     unsigned long key) { }
>  
>  static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
> -				      const struct kho_radix_walk_cb *cb)
> +				      const struct kho_radix_walk_cb *cb, void *data)
>  {
>  	return -EOPNOTSUPP;
>  }
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 94ca831b41c9..d0a4f78eccfe 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -267,14 +267,14 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
>  
>  static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
> -			       const struct kho_radix_walk_cb *cb)
> +			       const struct kho_radix_walk_cb *cb, void *data)
>  {
>  	unsigned long *bitmap = (unsigned long *)leaf;
>  	unsigned int i;
>  	int err;
>  
>  	if (cb->table) {
> -		err = cb->table(virt_to_phys(leaf));
> +		err = cb->table(virt_to_phys(leaf), data);
>  		if (err)
>  			return err;
>  	}
> @@ -283,7 +283,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
>  		return 0;
>  
>  	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
> -		err = cb->key(key | i);
> +		err = cb->key(key | i, data);
>  		if (err)
>  			return err;
>  	}
> @@ -293,7 +293,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
>  
>  static int __kho_radix_walk_tree(struct kho_radix_node *root,
>  				 unsigned int level, unsigned long start,
> -				 const struct kho_radix_walk_cb *cb)
> +				 const struct kho_radix_walk_cb *cb, void *data)
>  {
>  	struct kho_radix_node *node;
>  	struct kho_radix_leaf *leaf;
> @@ -302,7 +302,7 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
>  	int err;
>  
>  	if (cb->table) {
> -		err = cb->table(virt_to_phys(root));
> +		err = cb->table(virt_to_phys(root), data);
>  		if (err)
>  			return err;
>  	}
> @@ -323,10 +323,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
>  			 * node is pointing to the level 0 bitmap.
>  			 */
>  			leaf = (struct kho_radix_leaf *)node;
> -			err = kho_radix_walk_leaf(leaf, key, cb);
> +			err = kho_radix_walk_leaf(leaf, key, cb, data);
>  		} else {
>  			err  = __kho_radix_walk_tree(node, level - 1,
> -						     key, cb);
> +						     key, cb, data);
>  		}
>  
>  		if (err)
> @@ -340,6 +340,7 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
>   * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
>   * @tree: A pointer to the KHO radix tree to walk.
>   * @cb:   Set of callbacks to be invoked during the tree walk.
> + * @data: Opaque data pointer passed to each callback in @cb.
>   *
>   * This function walks the radix tree, searching from the top level down to the
>   * lowest level (level 0), invoking the appropriate callbacks.
> @@ -348,14 +349,15 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
>   *         value from the callback that stopped the walk.
>   */
>  int kho_radix_walk_tree(struct kho_radix_tree *tree,
> -			const struct kho_radix_walk_cb *cb)
> +			const struct kho_radix_walk_cb *cb, void *data)
>  {
>  	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);
> +	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb,
> +				     data);
>  }
>  EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
>  
> @@ -501,7 +503,7 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
>  	return pfn_to_page(pfn);
>  }
>  
> -static int __init kho_preserved_memory_reserve(unsigned long key)
> +static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
>  {
>  	union kho_page_info info;
>  	struct page *page;
> @@ -1392,7 +1394,7 @@ static int __init kho_mem_retrieve(const void *fdt)
>  
>  	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
>  	mutex_init(&kho_in.radix_tree.lock);
> -	return kho_radix_walk_tree(&kho_in.radix_tree, &cb);
> +	return kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL);
>  }
>  
>  static __init int kho_out_fdt_setup(void)
> -- 
> 2.54.0.545.g6539524ca2-goog
> 


From pasha.tatashin at soleen.com  Thu May 21 16:37:03 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 23:37:03 +0000
Subject: [PATCH 06/12] kho: allow early-boot usage of the KHO radix tree
In-Reply-To: <20260429133928.850721-7-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-7-pratyush@kernel.org>
Message-ID: <ag-W3t7CSFHw4Wej@plex>

On 04-29 15:39, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> The KHO radix tree allocates memory for table pages from the buddy
> allocator using get_zeroed_page(). This is not available in early boot
> when memblock is still active.
> 
> Using the radix tree in early boot is useful for KHO to track metadata
> about its memory. One such example is for tracking free blocks for
> memory allocation when scratch runs out of space. This feature will be
> added in the following commits.
> 
> Add kho_radix_{alloc,free}_node() which allocate and free the table
> pages. They use slab_is_available() to decide which allocator to use.
> While slab_is_available() indicates availability of the slab allocator,
> it gets initialized right before buddy so it serves the same practical
> purpose.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

> ---
>  kernel/liveupdate/kexec_handover.c | 24 ++++++++++++++++++++++--
>  1 file changed, 22 insertions(+), 2 deletions(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index d0a4f78eccfe..47f7c4a2865e 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -143,6 +143,26 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
>  	return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
>  }
>  
> +static void __ref *kho_radix_alloc_node(void)
> +{
> +	struct kho_radix_node *node;
> +
> +	if (slab_is_available())
> +		node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
> +	else
> +		node = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
> +
> +	return node;
> +}
> +
> +static void __ref kho_radix_free_node(struct kho_radix_node *node)
> +{
> +	if (slab_is_available())
> +		free_page((unsigned long)node);
> +	else
> +		memblock_free(node, PAGE_SIZE);
> +}
> +
>  /**
>   * kho_radix_add_key - Add a key to the radix tree.
>   * @tree: The KHO radix tree.
> @@ -183,7 +203,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
>  		}
>  
>  		/* Next node is empty, create a new node for it */
> -		new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
> +		new_node = kho_radix_alloc_node();
>  		if (!new_node) {
>  			err = -ENOMEM;
>  			goto err_free_nodes;
> @@ -214,7 +234,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
>  err_free_nodes:
>  	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
>  		if (intermediate_nodes[i])
> -			free_page((unsigned long)intermediate_nodes[i]);
> +			kho_radix_free_node(intermediate_nodes[i]);
>  	}
>  	if (anchor_node)
>  		anchor_node->table[anchor_idx] = 0;
> -- 
> 2.54.0.545.g6539524ca2-goog
> 


From pasha.tatashin at soleen.com  Thu May 21 16:46:23 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 21 May 2026 23:46:23 +0000
Subject: [PATCH 07/12] kho: allow destroying KHO radix tree
In-Reply-To: <20260429133928.850721-8-pratyush@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-8-pratyush@kernel.org>
Message-ID: <ag-Xa-bDOouXAFXb@plex>

On 04-29 15:39, Pratyush Yadav wrote:
> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> Add kho_radix_destroy_tree() which allows destroying the radix tree and
> freeing all its pages.
> 
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> ---
>  include/linux/kho_radix_tree.h     |  3 +++
>  kernel/liveupdate/kexec_handover.c | 34 ++++++++++++++++++++++++++++++
>  2 files changed, 37 insertions(+)

Generally, in Linux, recursion is avoided due to statically sized kernel 
stacks. However, here we are bound by a relatively shallow KHO tree, and 
kho_radix_walk_tree() is already recursive, so I think it is alright. We 
just need to be mindful of local variable usage (frame sizes) within these 
recursive functions.

Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

> 
> diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
> index 6c0f7d82716b..617395a6647a 100644
> --- a/include/linux/kho_radix_tree.h
> +++ b/include/linux/kho_radix_tree.h
> @@ -54,6 +54,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
>  void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
>  int kho_radix_walk_tree(struct kho_radix_tree *tree,
>  			const struct kho_radix_walk_cb *cb, void *data);
> +void kho_radix_destroy_tree(struct kho_radix_tree *tree);
>  
>  #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
>  
> @@ -71,6 +72,8 @@ static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
>  	return -EOPNOTSUPP;
>  }
>  
> +static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
> +
>  #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
>  
>  #endif	/* _LINUX_KHO_RADIX_TREE_H */
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 47f7c4a2865e..29479534f65d 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -286,6 +286,40 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
>  }
>  EXPORT_SYMBOL_GPL(kho_radix_del_key);
>  
> +static void __kho_radix_destroy_tree(struct kho_radix_node *root,
> +				     unsigned int level)
> +{
> +	unsigned long i;
> +
> +	if (level == 0) {
> +		kho_radix_free_node(root);
> +		return;
> +	}
> +
> +	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
> +		if (root->table[i])
> +			__kho_radix_destroy_tree(phys_to_virt(root->table[i]),
> +						 level - 1);
> +	}
> +
> +	kho_radix_free_node(root);
> +}
> +
> +/**
> + * kho_radix_destroy_tree - Destroy the radix tree
> + * @tree: The radix tree to destroy
> + *
> + * Walk @tree and free all its nodes.
> + */
> +void kho_radix_destroy_tree(struct kho_radix_tree *tree)
> +{
> +	if (!tree->root)
> +		return;
> +
> +	__kho_radix_destroy_tree(tree->root, KHO_TREE_MAX_DEPTH - 1);
> +}
> +EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
> +
>  static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
>  			       const struct kho_radix_walk_cb *cb, void *data)
>  {
> -- 
> 2.54.0.545.g6539524ca2-goog
> 


From pasha.tatashin at soleen.com  Thu May 21 17:48:35 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 22 May 2026 00:48:35 +0000
Subject: [PATCH 09/12] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <2vxzecjhc2s8.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-10-pratyush@kernel.org>
 <agHGOxSnP_VraH1M@kernel.org>
 <2vxzecjhc2s8.fsf@kernel.org>
Message-ID: <ag-a4ru9bMkDMthO@plex>

On 05-11 18:46, Pratyush Yadav wrote:
> On Mon, May 11 2026, Mike Rapoport wrote:
> 
> > On Wed, Apr 29, 2026 at 03:39:11PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> 
> >> In the upcoming commits, the KHO will learn how to discover free blocks
> >> of memory by walking the KHO radix tree. It will then mark those regions
> >> as scratch to allow memory allocation in case scratch runs low.
> >> 
> >> To differentiate the extended scratch areas from the main scratch areas,
> >> introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
> >> for allocations during scratch-only. Teach should_skip_region() to check
> >> for both flags before deciding if the region should be skipped.
> >
> > Why there's a need to differentiate SCRATCH and SCRATCH_EXT?
> > SCRATCH (I still hate the name) means "memory memblock can safely use for

 +1000

I also strongly dislike this name and mentioned it in another thread
earlier today.

If we ever decide to s/scratch/something-else/ globally, that should be a
separate cleanup effort. However, since we are introducing a brand new flag
here, we can discuss a better name for the _ext portion to avoid overloading
the "scratch" concept.

> > the allocations". Initially this memory comes from the reservations in the
> > first kernel, but if the second kernel can find more memory to extend it,
> > why that additional memory should be treated differently? 
> 
> Two reasons:
> 
> 1. We mark SCRATCH as MIGRATE_CMA. We don't want to do that for
>    SCRATCH_EXT since this memory can be used for non-movable
>    allocations.
> 
> 2. Gigantic (1G) huge pages can not be allocated from scratch. They can
>    be preserved memory and thus should not be allocated from SCRATCH.
>    See patch 12 that does allocations for gigantic huge pages only from
>    SCRATCH_EXT.
> 
> I will add this in the commit message for the next version.
> 
> Naming is hard, so if you have any better names I'm all ears :-)

IMO, this scratch_ext is not "scratch" in the traditional KHO sense at all.
The traditional KHO scratch is what is passed from kernel to kernel and is
guaranteed to contain zero preserved memory. This new memory is not passed
from kernel to kernel and can contain preserved memory at runtime. It's
essentially just memory that we identify as currently unpreserved and release
early to the system.

If we want to keep the naming aligned with the existing codebase for now:
MEMBLOCK_KHO_SCRATCH      -> original scratch
MEMBLOCK_KHO_UNPRESERVED  -> for the new memory (instead of SCRATCH_EXT)

Alternatively, if we do want to tackle the global rename of "scratch" later:
MEMBLOCK_KHO_BOOTSTRAP    -> for the original scratch
MEMBLOCK_KHO_UNPRESERVED  -> for this new dynamic memory

What do you think?

Pasha

> 
> >
> >> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> >> ---
> >> 
> >> Notes:
> >>     Checkpatch complains about no space after MEMBLOCK_KHO_SCRATCH_EXT in
> >>     the declaration, but doing so makes it nicely align with all the other
> >>     numbers. Mike, if you'd like I can add some whitespace.
> >> 
> >>  include/linux/memblock.h | 10 ++++++++++
> >>  mm/memblock.c            | 41 ++++++++++++++++++++++++++++++++++------
> >>  2 files changed, 45 insertions(+), 6 deletions(-)
> 
> -- 
> Regards,
> Pratyush Yadav


From ross.philipson at gmail.com  Fri May 15 14:13:41 2026
From: ross.philipson at gmail.com (Ross Philipson)
Date: Fri, 15 May 2026 14:13:41 -0700
Subject: [PATCH v16 09/38] tpm-buf: Implement managed allocations
In-Reply-To: <20260515211410.31440-1-ross.philipson@gmail.com>
References: <20260515211410.31440-1-ross.philipson@gmail.com>
Message-ID: <20260515211410.31440-10-ross.philipson@gmail.com>

From: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>

Decouple kzalloc from buffer creation, so that a managed allocation can be
used:

        struct tpm_buf *buf __free(kfree) buf = kzalloc(TPM_BUFSIZE,
                                                        GFP_KERNEL);
        if (!buf)
                return -ENOMEM;

        tpm_buf_init(buf, TPM_BUFSIZE);

Alternatively, stack allocations are also possible:

        u8 buf_data[512];
        struct tpm_buf *buf = (struct tpm_buf *)buf_data;
        tpm_buf_init(buf, sizeof(buf_data));

This is achieved by embedding buffer's header inside the allocated blob,
instead of having an outer wrapper.

Message-ID: <20260125192526.782202-12-jarkko at kernel.org>
Co-developed-by: Ross Philipson <ross.philipson at gmail.com>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen at opinsys.com>
Signed-off-by: Ross Philipson <ross.philipson at gmail.com>

---

Original version was reviewed but significant changes were made during porting:
Reviewed-by: Stefan Berger <stefanb at linux.ibm.com>
---
 drivers/char/tpm/tpm-buf.c                | 124 +++++----
 drivers/char/tpm/tpm-sysfs.c              |  21 +-
 drivers/char/tpm/tpm.h                    |   1 -
 drivers/char/tpm/tpm1-cmd.c               | 176 ++++++-------
 drivers/char/tpm/tpm2-cmd.c               | 308 ++++++++++------------
 drivers/char/tpm/tpm2-sessions.c          | 142 +++++-----
 drivers/char/tpm/tpm2-space.c             |  44 ++--
 drivers/char/tpm/tpm_vtpm_proxy.c         |  30 +--
 include/linux/tpm.h                       |  17 +-
 security/keys/trusted-keys/trusted_tpm1.c |  36 +--
 security/keys/trusted-keys/trusted_tpm2.c | 170 ++++++------
 11 files changed, 513 insertions(+), 556 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 99811809a72a..e79a8071c9ee 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -6,82 +6,109 @@
 #include <linux/module.h>
 #include <linux/tpm.h>
 
-/**
- * tpm_buf_init() - Allocate and initialize a TPM command
- * @buf:	A &tpm_buf
- * @tag:	TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
- * @ordinal:	A command ordinal
- *
- * Return: 0 or -ENOMEM
- */
-int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal)
+static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size)
 {
-	buf->data = (u8 *)__get_free_page(GFP_KERNEL);
-	if (!buf->data)
-		return -ENOMEM;
-
-	tpm_buf_reset(buf, tag, ordinal);
-	return 0;
+	u32 buf_size_2 = (u32)buf->capacity + (u32)sizeof(*buf);
+
+	if (!buf->capacity) {
+		if (buf_size > TPM_BUFSIZE) {
+			WARN(1, "%s: size overflow: %u\n", __func__, buf_size);
+			buf->flags |= TPM_BUF_INVALID;
+		}
+	} else {
+		if (buf_size != buf_size_2) {
+			WARN(1, "%s: size mismatch: %u != %u\n", __func__, buf_size,
+			     buf_size_2);
+			buf->flags |= TPM_BUF_INVALID;
+		}
+	}
 }
-EXPORT_SYMBOL_GPL(tpm_buf_init);
 
-/**
- * tpm_buf_reset() - Initialize a TPM command
- * @buf:	A &tpm_buf
- * @tag:	TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
- * @ordinal:	A command ordinal
- */
-void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal)
+static void __tpm_buf_reset(struct tpm_buf *buf, u16 buf_size, u16 tag, u32 ordinal)
 {
 	struct tpm_header *head = (struct tpm_header *)buf->data;
 
+	__tpm_buf_size_invariant(buf, buf_size);
+
+	if (buf->flags & TPM_BUF_INVALID)
+		return;
+
 	WARN_ON(tag != TPM_TAG_RQU_COMMAND && tag != TPM2_ST_NO_SESSIONS &&
 		tag != TPM2_ST_SESSIONS && tag != 0);
 
 	buf->flags = 0;
 	buf->length = sizeof(*head);
+	buf->capacity = buf_size - sizeof(*buf);
+	buf->handles = 0;
 	head->tag = cpu_to_be16(tag);
 	head->length = cpu_to_be32(sizeof(*head));
 	head->ordinal = cpu_to_be32(ordinal);
+}
+
+static void __tpm_buf_reset_sized(struct tpm_buf *buf, u16 buf_size)
+{
+	__tpm_buf_size_invariant(buf, buf_size);
+
+	if (buf->flags & TPM_BUF_INVALID)
+		return;
+
+	buf->flags = TPM_BUF_TPM2B;
+	buf->length = 2;
+	buf->capacity = buf_size - sizeof(*buf);
 	buf->handles = 0;
+	buf->data[0] = 0;
+	buf->data[1] = 0;
 }
-EXPORT_SYMBOL_GPL(tpm_buf_reset);
 
 /**
- * tpm_buf_init_sized() - Allocate and initialize a sized (TPM2B) buffer
- * @buf:	A @tpm_buf
- *
- * Return: 0 or -ENOMEM
+ * tpm_buf_init() - Initialize a TPM command
+ * @buf:	A &tpm_buf
+ * @buf_size:	Size of the buffer.
  */
-int tpm_buf_init_sized(struct tpm_buf *buf)
+void tpm_buf_init(struct tpm_buf *buf, u16 buf_size)
 {
-	buf->data = (u8 *)__get_free_page(GFP_KERNEL);
-	if (!buf->data)
-		return -ENOMEM;
+	memset(buf, 0, buf_size);
+	__tpm_buf_reset(buf, buf_size, TPM_TAG_RQU_COMMAND, 0);
+}
+EXPORT_SYMBOL_GPL(tpm_buf_init);
 
-	tpm_buf_reset_sized(buf);
-	return 0;
+/**
+ * tpm_buf_init_sized() - Initialize a sized buffer
+ * @buf:	A &tpm_buf
+ * @buf_size:	Size of the buffer.
+ */
+void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size)
+{
+	memset(buf, 0, buf_size);
+	__tpm_buf_reset_sized(buf, buf_size);
 }
 EXPORT_SYMBOL_GPL(tpm_buf_init_sized);
 
 /**
- * tpm_buf_reset_sized() - Initialize a sized buffer
+ * tpm_buf_reset() - Re-initialize a TPM command
  * @buf:	A &tpm_buf
+ * @tag:	TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
+ * @ordinal:	A command ordinal
  */
-void tpm_buf_reset_sized(struct tpm_buf *buf)
+void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal)
 {
-	buf->flags = TPM_BUF_TPM2B;
-	buf->length = 2;
-	buf->data[0] = 0;
-	buf->data[1] = 0;
+	u16 buf_size = buf->capacity + sizeof(*buf);
+
+	__tpm_buf_reset(buf, buf_size, tag, ordinal);
 }
-EXPORT_SYMBOL_GPL(tpm_buf_reset_sized);
+EXPORT_SYMBOL_GPL(tpm_buf_reset);
 
-void tpm_buf_destroy(struct tpm_buf *buf)
+/**
+ * tpm_buf_reset_sized() - Re-initialize a sized buffer
+ * @buf:	A &tpm_buf
+ */
+void tpm_buf_reset_sized(struct tpm_buf *buf)
 {
-	free_page((unsigned long)buf->data);
+	u16 buf_size = buf->capacity + sizeof(*buf);
+
+	__tpm_buf_reset_sized(buf, buf_size);
 }
-EXPORT_SYMBOL_GPL(tpm_buf_destroy);
+EXPORT_SYMBOL_GPL(tpm_buf_reset_sized);
 
 /**
  * tpm_buf_length() - Return the number of bytes consumed by the data
@@ -89,8 +116,11 @@ EXPORT_SYMBOL_GPL(tpm_buf_destroy);
  *
  * Return: The number of bytes consumed by the buffer
  */
-u32 tpm_buf_length(struct tpm_buf *buf)
+u16 tpm_buf_length(struct tpm_buf *buf)
 {
+	if (buf->flags & TPM_BUF_INVALID)
+		return 0;
+
 	return buf->length;
 }
 EXPORT_SYMBOL_GPL(tpm_buf_length);
@@ -103,10 +133,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length);
  */
 void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length)
 {
+	u32 total_length = (u32)buf->length + (u32)new_length;
+
 	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
-	if ((buf->length + new_length) > PAGE_SIZE) {
+	if (total_length > (u32)buf->capacity) {
 		WARN(1, "tpm_buf: write overflow\n");
 		buf->flags |= TPM_BUF_INVALID;
 		return;
diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
index 94231f052ea7..f5dcadb1ab3c 100644
--- a/drivers/char/tpm/tpm-sysfs.c
+++ b/drivers/char/tpm/tpm-sysfs.c
@@ -32,28 +32,29 @@ struct tpm_readpubek_out {
 static ssize_t pubek_show(struct device *dev, struct device_attribute *attr,
 			  char *buf)
 {
-	struct tpm_buf tpm_buf;
 	struct tpm_readpubek_out *out;
 	int i;
 	char *str = buf;
 	struct tpm_chip *chip = to_tpm_chip(dev);
 	char anti_replay[20];
 
+	struct tpm_buf *tpm_buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tpm_buf)
+		return -ENOMEM;
+
 	memset(&anti_replay, 0, sizeof(anti_replay));
 
 	if (tpm_try_get_ops(chip))
 		return 0;
 
-	if (tpm_buf_init(&tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK))
-		goto out_ops;
-
-	tpm_buf_append(&tpm_buf, anti_replay, sizeof(anti_replay));
+	tpm_buf_init(tpm_buf, TPM_BUFSIZE);
+	tpm_buf_reset(tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK);
+	tpm_buf_append(tpm_buf, anti_replay, sizeof(anti_replay));
 
-	if (tpm_transmit_cmd(chip, &tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE,
-			     "attempting to read the PUBEK"))
-		goto out_buf;
+	if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, "TPM_ReadPubek"))
+		goto out_ops;
 
-	out = (struct tpm_readpubek_out *)&tpm_buf.data[10];
+	out = (struct tpm_readpubek_out *)&tpm_buf->data[10];
 	str +=
 	    sprintf(str,
 		    "Algorithm: %4ph\n"
@@ -71,8 +72,6 @@ static ssize_t pubek_show(struct device *dev, struct device_attribute *attr,
 	for (i = 0; i < 256; i += 16)
 		str += sprintf(str, "%16ph\n", &out->modulus[i]);
 
-out_buf:
-	tpm_buf_destroy(&tpm_buf);
 out_ops:
 	tpm_put_ops(chip);
 	return str - buf;
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 680f89d9c9f9..fa554c5ad80b 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -33,7 +33,6 @@
 #endif
 
 #define TPM_MINOR		224	/* officially assigned */
-#define TPM_BUFSIZE		4096
 #define TPM_NUM_DEVICES		65536
 #define TPM_RETRY		50
 
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index 96f189b5fd6f..ee420d73927d 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -318,20 +318,14 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
  */
 static int tpm1_startup(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	dev_info(&chip->dev, "starting up the TPM manually\n");
-
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP);
-	if (rc < 0)
-		return rc;
-
-	tpm_buf_append_u16(&buf, TPM_ST_CLEAR);
-
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
-	tpm_buf_destroy(&buf);
-	return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP);
+	tpm_buf_append_u16(buf, TPM_ST_CLEAR);
+	return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM");
 }
 
 int tpm1_get_timeouts(struct tpm_chip *chip)
@@ -457,49 +451,46 @@ int tpm1_get_timeouts(struct tpm_chip *chip)
 int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
 		    const char *log_msg)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
-	if (rc)
-		return rc;
-
-	tpm_buf_append_u32(&buf, pcr_idx);
-	tpm_buf_append(&buf, hash, TPM_DIGEST_SIZE);
-
-	rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, log_msg);
-	tpm_buf_destroy(&buf);
-	return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
+	tpm_buf_append_u32(buf, pcr_idx);
+	tpm_buf_append(buf, hash, TPM_DIGEST_SIZE);
+	return tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, log_msg);
 }
 
 ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
 		    const char *desc, size_t min_cap_length)
 {
-	struct tpm_buf buf;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP);
 
 	if (subcap_id == TPM_CAP_VERSION_1_1 ||
 	    subcap_id == TPM_CAP_VERSION_1_2) {
-		tpm_buf_append_u32(&buf, subcap_id);
-		tpm_buf_append_u32(&buf, 0);
+		tpm_buf_append_u32(buf, subcap_id);
+		tpm_buf_append_u32(buf, 0);
 	} else {
 		if (subcap_id == TPM_CAP_FLAG_PERM ||
 		    subcap_id == TPM_CAP_FLAG_VOL)
-			tpm_buf_append_u32(&buf, TPM_CAP_FLAG);
+			tpm_buf_append_u32(buf, TPM_CAP_FLAG);
 		else
-			tpm_buf_append_u32(&buf, TPM_CAP_PROP);
+			tpm_buf_append_u32(buf, TPM_CAP_PROP);
 
-		tpm_buf_append_u32(&buf, 4);
-		tpm_buf_append_u32(&buf, subcap_id);
+		tpm_buf_append_u32(buf, 4);
+		tpm_buf_append_u32(buf, subcap_id);
 	}
-	rc = tpm_transmit_cmd(chip, &buf, min_cap_length, desc);
+	rc = tpm_transmit_cmd(chip, buf, min_cap_length, desc);
 	if (!rc)
-		*cap = *(cap_t *)&buf.data[TPM_HEADER_SIZE + 4];
-	tpm_buf_destroy(&buf);
+		*cap = *(cap_t *)&buf->data[TPM_HEADER_SIZE + 4];
 	return rc;
 }
 EXPORT_SYMBOL_GPL(tpm1_getcap);
@@ -518,80 +509,74 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 {
 	struct tpm1_get_random_out *out;
 	u32 num_bytes =  min_t(u32, max, TPM_MAX_RNG_DATA);
-	struct tpm_buf buf;
 	u32 total = 0;
 	int retries = 5;
 	u32 recd;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
-	if (rc)
-		return rc;
+	if (!dest || !max || max > TPM_MAX_RNG_DATA)
+		return -EINVAL;
+
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
 
 	do {
-		tpm_buf_append_u32(&buf, num_bytes);
+		tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
+		tpm_buf_append_u32(buf, num_bytes);
 
-		rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len),
+		rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len),
 				      "attempting get random");
 		if (rc) {
 			if (rc > 0)
 				rc = -EIO;
-			goto out;
+			return rc;
 		}
 
-		out = (struct tpm1_get_random_out *)&buf.data[TPM_HEADER_SIZE];
+		out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
 
 		recd = be32_to_cpu(out->rng_data_len);
-		if (recd > num_bytes) {
-			rc = -EFAULT;
-			goto out;
-		}
+		if (recd > num_bytes)
+			return -EFAULT;
+
+		if (tpm_buf_length(buf) <
+		    TPM_HEADER_SIZE + sizeof(out->rng_data_len) + recd)
+			return -EFAULT;
 
-		if (tpm_buf_length(&buf) < TPM_HEADER_SIZE +
-					   sizeof(out->rng_data_len) + recd) {
-			rc = -EFAULT;
-			goto out;
-		}
 		memcpy(dest, out->rng_data, recd);
 
 		dest += recd;
 		total += recd;
 		num_bytes -= recd;
-
-		tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
 	} while (retries-- && total < max);
 
-	rc = total ? (int)total : -EIO;
-out:
-	tpm_buf_destroy(&buf);
-	return rc;
+	return total ? (int)total : -EIO;
 }
 
 int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
 {
-	struct tpm_buf buf;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	tpm_buf_append_u32(&buf, pcr_idx);
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ);
+	tpm_buf_append_u32(buf, pcr_idx);
 
-	rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE,
-			      "attempting to read a pcr value");
+	rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE,
+			      "attempting to read a PCR value");
 	if (rc)
-		goto out;
+		return rc;
 
-	if (tpm_buf_length(&buf) < TPM_DIGEST_SIZE) {
-		rc = -EFAULT;
-		goto out;
-	}
+	if (tpm_buf_length(buf) < TPM_DIGEST_SIZE)
+		return -EFAULT;
 
-	memcpy(res_buf, &buf.data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE);
+	memcpy(res_buf, &buf->data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE);
 
-out:
-	tpm_buf_destroy(&buf);
 	return rc;
 }
 
@@ -604,16 +589,13 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
  */
 static int tpm1_continue_selftest(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	rc = tpm_transmit_cmd(chip, &buf, 0, "continue selftest");
-	tpm_buf_destroy(&buf);
-	return rc;
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
+	return tpm_transmit_cmd(chip, buf, 0, "continue selftest");
 }
 
 /**
@@ -725,22 +707,24 @@ int tpm1_auto_startup(struct tpm_chip *chip)
 int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
 {
 	u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 };
-	struct tpm_buf buf;
 	unsigned int try;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	/* for buggy tpm, flush pcrs with extend to selected dummy */
+	/* for buggy tpm, flush PCRs with extend to selected dummy */
 	if (tpm_suspend_pcr)
 		rc = tpm1_pcr_extend(chip, tpm_suspend_pcr, dummy_hash,
-				     "extending dummy pcr before suspend");
+				     "extending dummy PCR before suspend");
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
-	if (rc)
-		return rc;
 	/* now do the actual savestate */
 	for (try = 0; try < TPM_RETRY; try++) {
-		rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
+		rc = tpm_transmit_cmd(chip, buf, 0, NULL);
 		/*
 		 * If the TPM indicates that it is too busy to respond to
 		 * this command then retry before giving up.  It can take
@@ -755,7 +739,7 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
 			break;
 		tpm_msleep(TPM_TIMEOUT_RETRY);
 
-		tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
+		tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
 	}
 
 	if (rc)
@@ -765,8 +749,6 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
 		dev_warn(&chip->dev, "TPM savestate took %dms\n",
 			 try * TPM_TIMEOUT_RETRY);
 
-	tpm_buf_destroy(&buf);
-
 	return rc;
 }
 
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index c9dc35b57687..95c1b57d5096 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -108,12 +108,15 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
 {
 	int i;
 	int rc;
-	struct tpm_buf buf;
 	struct tpm2_pcr_read_out *out;
 	u8 pcr_select[TPM2_PCR_SELECT_MIN] = {0};
 	u16 digest_size;
 	u16 expected_digest_size = 0;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	if (pcr_idx >= TPM2_PLATFORM_PCR)
 		return -EINVAL;
 
@@ -128,36 +131,32 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
 		expected_digest_size = chip->allocated_banks[i].digest_size;
 	}
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ);
-	if (rc)
-		return rc;
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ);
 
 	pcr_select[pcr_idx >> 3] = 1 << (pcr_idx & 0x7);
 
-	tpm_buf_append_u32(&buf, 1);
-	tpm_buf_append_u16(&buf, digest->alg_id);
-	tpm_buf_append_u8(&buf, TPM2_PCR_SELECT_MIN);
-	tpm_buf_append(&buf, (const unsigned char *)pcr_select,
+	tpm_buf_append_u32(buf, 1);
+	tpm_buf_append_u16(buf, digest->alg_id);
+	tpm_buf_append_u8(buf, TPM2_PCR_SELECT_MIN);
+	tpm_buf_append(buf, (const unsigned char *)pcr_select,
 		       sizeof(pcr_select));
 
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to read a pcr value");
+	rc = tpm_transmit_cmd(chip, buf, 0, "attempting to read a pcr value");
 	if (rc)
-		goto out;
+		return rc;
 
-	out = (struct tpm2_pcr_read_out *)&buf.data[TPM_HEADER_SIZE];
+	out = (struct tpm2_pcr_read_out *)&buf->data[TPM_HEADER_SIZE];
 	digest_size = be16_to_cpu(out->digest_size);
 	if (digest_size > sizeof(digest->digest) ||
-	    (!digest_size_ptr && digest_size != expected_digest_size)) {
-		rc = -EINVAL;
-		goto out;
-	}
+	    (!digest_size_ptr && digest_size != expected_digest_size))
+		return -EINVAL;
 
 	if (digest_size_ptr)
 		*digest_size_ptr = digest_size;
 
 	memcpy(digest->digest, out->digest, digest_size);
-out:
-	tpm_buf_destroy(&buf);
+
 	return rc;
 }
 
@@ -173,56 +172,53 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
 int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 		    struct tpm_digest *digests)
 {
-	struct tpm_buf buf;
 	int rc;
 	int i;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	if (!disable_pcr_integrity) {
 		rc = tpm2_start_auth_session(chip);
 		if (rc)
 			return rc;
 	}
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
-	if (rc) {
-		if (!disable_pcr_integrity)
-			tpm2_end_auth_session(chip);
-		return rc;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
 
 	if (!disable_pcr_integrity) {
-		rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL);
-		if (rc) {
-			tpm_buf_destroy(&buf);
+		rc = tpm_buf_append_name(chip, buf, pcr_idx, NULL);
+		if (rc)
 			return rc;
-		}
-		tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0);
+
+		tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0);
 	} else {
-		tpm_buf_append_handle(&buf, pcr_idx);
-		tpm_buf_append_auth(chip, &buf, NULL, 0);
+		tpm_buf_append_handle(buf, pcr_idx);
+		tpm_buf_append_auth(chip, buf, NULL, 0);
 	}
 
-	tpm_buf_append_u32(&buf, chip->nr_allocated_banks);
+	tpm_buf_append_u32(buf, chip->nr_allocated_banks);
 
 	for (i = 0; i < chip->nr_allocated_banks; i++) {
-		tpm_buf_append_u16(&buf, digests[i].alg_id);
-		tpm_buf_append(&buf, (const unsigned char *)&digests[i].digest,
+		tpm_buf_append_u16(buf, digests[i].alg_id);
+		tpm_buf_append(buf, (const unsigned char *)&digests[i].digest,
 			       chip->allocated_banks[i].digest_size);
 	}
 
+	if (buf->flags & TPM_BUF_INVALID)
+		return -EINVAL;
+
 	if (!disable_pcr_integrity) {
-		rc = tpm_buf_fill_hmac_session(chip, &buf);
-		if (rc) {
-			tpm_buf_destroy(&buf);
+		rc = tpm_buf_fill_hmac_session(chip, buf);
+		if (rc)
 			return rc;
-		}
 	}
 
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting extend a PCR value");
+	rc = tpm_transmit_cmd(chip, buf, 0, "attempting extend a PCR value");
 	if (!disable_pcr_integrity)
-		rc = tpm_buf_check_hmac_response(chip, &buf, rc);
-
-	tpm_buf_destroy(&buf);
+		rc = tpm_buf_check_hmac_response(chip, buf, rc);
 
 	return rc;
 }
@@ -242,7 +238,6 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 {
 	struct tpm2_get_random_out *out;
 	struct tpm_header *head;
-	struct tpm_buf buf;
 	u32 recd;
 	u32 num_bytes = max;
 	int err;
@@ -258,52 +253,53 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 	if (err)
 		return err;
 
-	err = tpm_buf_init(&buf, 0, 0);
-	if (err) {
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf) {
 		tpm2_end_auth_session(chip);
-		return err;
+		return -ENOMEM;
 	}
 
+	tpm_buf_init(buf, TPM_BUFSIZE);
+
 	do {
-		tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
+		tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
 		if (tpm2_chip_auth(chip)) {
-			tpm_buf_append_hmac_session(chip, &buf,
+			tpm_buf_append_hmac_session(chip, buf,
 						    TPM2_SA_ENCRYPT |
 						    TPM2_SA_CONTINUE_SESSION,
 						    NULL, 0);
 		} else  {
-			offset = buf.handles * 4 + TPM_HEADER_SIZE;
-			head = (struct tpm_header *)buf.data;
-			if (tpm_buf_length(&buf) == offset)
+			offset = buf->handles * 4 + TPM_HEADER_SIZE;
+			head = (struct tpm_header *)buf->data;
+			if (tpm_buf_length(buf) == offset)
 				head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
 		}
-		tpm_buf_append_u16(&buf, num_bytes);
-		err = tpm_buf_fill_hmac_session(chip, &buf);
-		if (err) {
-			tpm_buf_destroy(&buf);
+		tpm_buf_append_u16(buf, num_bytes);
+		err = tpm_buf_fill_hmac_session(chip, buf);
+		if (err)
 			return err;
-		}
 
-		err = tpm_transmit_cmd(chip, &buf,
+		err = tpm_transmit_cmd(chip, buf,
 				       offsetof(struct tpm2_get_random_out,
 						buffer),
 				       "attempting get random");
-		err = tpm_buf_check_hmac_response(chip, &buf, err);
+		err = tpm_buf_check_hmac_response(chip, buf, err);
 		if (err) {
 			if (err > 0)
 				err = -EIO;
 			goto out;
 		}
 
-		head = (struct tpm_header *)buf.data;
+		head = (struct tpm_header *)buf->data;
 		offset = TPM_HEADER_SIZE;
+
 		/* Skip the parameter size field: */
 		if (be16_to_cpu(head->tag) == TPM2_ST_SESSIONS)
 			offset += 4;
 
-		out = (struct tpm2_get_random_out *)&buf.data[offset];
+		out = (struct tpm2_get_random_out *)&buf->data[offset];
 		recd = min_t(u32, be16_to_cpu(out->size), num_bytes);
-		if (tpm_buf_length(&buf) <
+		if (tpm_buf_length(buf) <
 		    TPM_HEADER_SIZE +
 		    offsetof(struct tpm2_get_random_out, buffer) +
 		    recd) {
@@ -317,11 +313,9 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 		num_bytes -= recd;
 	} while (retries-- && total < max);
 
-	tpm_buf_destroy(&buf);
-
 	return total ? total : -EIO;
+
 out:
-	tpm_buf_destroy(&buf);
 	tpm2_end_auth_session(chip);
 	return err;
 }
@@ -333,20 +327,18 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
  */
 void tpm2_flush_context(struct tpm_chip *chip, u32 handle)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT);
-	if (rc) {
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf) {
 		dev_warn(&chip->dev, "0x%08x was not flushed, out of memory\n",
 			 handle);
 		return;
 	}
 
-	tpm_buf_append_u32(&buf, handle);
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT);
+	tpm_buf_append_u32(buf, handle);
 
-	tpm_transmit_cmd(chip, &buf, 0, "flushing context");
-	tpm_buf_destroy(&buf);
+	tpm_transmit_cmd(chip, buf, 0, "flushing context");
 }
 EXPORT_SYMBOL_GPL(tpm2_flush_context);
 
@@ -365,19 +357,22 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,  u32 *value,
 			const char *desc)
 {
 	struct tpm2_get_cap_out *out;
-	struct tpm_buf buf;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
-	if (rc)
-		return rc;
-	tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES);
-	tpm_buf_append_u32(&buf, property_id);
-	tpm_buf_append_u32(&buf, 1);
-	rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
+	tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES);
+	tpm_buf_append_u32(buf, property_id);
+	tpm_buf_append_u32(buf, 1);
+
+	rc = tpm_transmit_cmd(chip, buf, 0, NULL);
 	if (!rc) {
 		out = (struct tpm2_get_cap_out *)
-			&buf.data[TPM_HEADER_SIZE];
+			&buf->data[TPM_HEADER_SIZE];
 		/*
 		 * To prevent failing boot up of some systems, Infineon TPM2.0
 		 * returns SUCCESS on TPM2_Startup in field upgrade mode. Also
@@ -389,7 +384,7 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,  u32 *value,
 		else
 			rc = -ENODATA;
 	}
-	tpm_buf_destroy(&buf);
+
 	return rc;
 }
 EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt);
@@ -406,15 +401,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt);
  */
 void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN);
-	if (rc)
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
 		return;
-	tpm_buf_append_u16(&buf, shutdown_type);
-	tpm_transmit_cmd(chip, &buf, 0, "stopping the TPM");
-	tpm_buf_destroy(&buf);
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN);
+	tpm_buf_append_u16(buf, shutdown_type);
+	tpm_transmit_cmd(chip, buf, 0, "stopping the TPM");
 }
 
 /**
@@ -432,20 +426,21 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type)
  */
 static int tpm2_do_selftest(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
 	int full;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+
 	for (full = 0; full < 2; full++) {
-		rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST);
-		if (rc)
-			return rc;
+		tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST);
+		tpm_buf_append_u8(buf, full);
 
-		tpm_buf_append_u8(&buf, full);
-		rc = tpm_transmit_cmd(chip, &buf, 0,
+		rc = tpm_transmit_cmd(chip, buf, 0,
 				      "attempting the self test");
-		tpm_buf_destroy(&buf);
-
 		if (rc == TPM2_RC_TESTING)
 			rc = TPM2_RC_SUCCESS;
 		if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS)
@@ -470,23 +465,26 @@ static int tpm2_do_selftest(struct tpm_chip *chip)
 int tpm2_probe(struct tpm_chip *chip)
 {
 	struct tpm_header *out;
-	struct tpm_buf buf;
 	int rc;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
-	if (rc)
-		return rc;
-	tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES);
-	tpm_buf_append_u32(&buf, TPM_PT_TOTAL_COMMANDS);
-	tpm_buf_append_u32(&buf, 1);
-	rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
+	tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES);
+	tpm_buf_append_u32(buf, TPM_PT_TOTAL_COMMANDS);
+	tpm_buf_append_u32(buf, 1);
+
+	rc = tpm_transmit_cmd(chip, buf, 0, NULL);
 	/* We ignore TPM return codes on purpose. */
 	if (rc >=  0) {
-		out = (struct tpm_header *)buf.data;
+		out = (struct tpm_header *)buf->data;
 		if (be16_to_cpu(out->tag) == TPM2_ST_NO_SESSIONS)
 			chip->flags |= TPM_CHIP_FLAG_TPM2;
 	}
-	tpm_buf_destroy(&buf);
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(tpm2_probe);
@@ -520,7 +518,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index)
 ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 {
 	struct tpm2_pcr_selection pcr_selection;
-	struct tpm_buf buf;
 	void *marker;
 	void *end;
 	void *pcr_select_offset;
@@ -532,39 +529,38 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 	int rc;
 	int i = 0;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	tpm_buf_append_u32(&buf, TPM2_CAP_PCRS);
-	tpm_buf_append_u32(&buf, 0);
-	tpm_buf_append_u32(&buf, 1);
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
+	tpm_buf_append_u32(buf, TPM2_CAP_PCRS);
+	tpm_buf_append_u32(buf, 0);
+	tpm_buf_append_u32(buf, 1);
 
-	rc = tpm_transmit_cmd(chip, &buf, 9, "get tpm pcr allocation");
+	rc = tpm_transmit_cmd(chip, buf, 9, "get TPM PCR allocation");
 	if (rc)
-		goto out;
+		return rc;
 
 	nr_possible_banks = be32_to_cpup(
-		(__be32 *)&buf.data[TPM_HEADER_SIZE + 5]);
+		(__be32 *)&buf->data[TPM_HEADER_SIZE + 5]);
 	if (nr_possible_banks > TPM2_MAX_PCR_BANKS) {
 		pr_err("tpm: out of bank capacity: %u > %u\n",
 		       nr_possible_banks, TPM2_MAX_PCR_BANKS);
-		rc = -ENOMEM;
-		goto out;
+		return -ENOMEM;
 	}
 
-	marker = &buf.data[TPM_HEADER_SIZE + 9];
+	marker = &buf->data[TPM_HEADER_SIZE + 9];
 
-	rsp_len = be32_to_cpup((__be32 *)&buf.data[2]);
-	end = &buf.data[rsp_len];
+	rsp_len = be32_to_cpup((__be32 *)&buf->data[2]);
+	end = &buf->data[rsp_len];
 
 	for (i = 0; i < nr_possible_banks; i++) {
 		pcr_select_offset = marker +
 			offsetof(struct tpm2_pcr_selection, size_of_select);
-		if (pcr_select_offset >= end) {
-			rc = -EFAULT;
-			break;
-		}
+		if (pcr_select_offset >= end)
+			return -EFAULT;
 
 		memcpy(&pcr_selection, marker, sizeof(pcr_selection));
 		hash_alg = be16_to_cpu(pcr_selection.hash_alg);
@@ -576,7 +572,7 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 
 			rc = tpm2_init_bank_info(chip, nr_alloc_banks);
 			if (rc < 0)
-				break;
+				return rc;
 
 			nr_alloc_banks++;
 		}
@@ -588,21 +584,22 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 	}
 
 	chip->nr_allocated_banks = nr_alloc_banks;
-out:
-	tpm_buf_destroy(&buf);
 
-	return rc;
+	return 0;
 }
 
 int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
 	u32 nr_commands;
 	__be32 *attrs;
 	u32 cc;
 	int i;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL);
 	if (rc)
 		goto out;
@@ -619,30 +616,25 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
 		goto out;
 	}
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
-	if (rc)
-		goto out;
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
+	tpm_buf_append_u32(buf, TPM2_CAP_COMMANDS);
+	tpm_buf_append_u32(buf, TPM2_CC_FIRST);
+	tpm_buf_append_u32(buf, nr_commands);
 
-	tpm_buf_append_u32(&buf, TPM2_CAP_COMMANDS);
-	tpm_buf_append_u32(&buf, TPM2_CC_FIRST);
-	tpm_buf_append_u32(&buf, nr_commands);
-
-	rc = tpm_transmit_cmd(chip, &buf, 9 + 4 * nr_commands, NULL);
-	if (rc) {
-		tpm_buf_destroy(&buf);
+	rc = tpm_transmit_cmd(chip, buf, 9 + 4 * nr_commands, NULL);
+	if (rc)
 		goto out;
-	}
 
 	if (nr_commands !=
-	    be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE + 5])) {
+	    be32_to_cpup((__be32 *)&buf->data[TPM_HEADER_SIZE + 5])) {
 		rc = -EFAULT;
-		tpm_buf_destroy(&buf);
 		goto out;
 	}
 
 	chip->nr_commands = nr_commands;
 
-	attrs = (__be32 *)&buf.data[TPM_HEADER_SIZE + 9];
+	attrs = (__be32 *)&buf->data[TPM_HEADER_SIZE + 9];
 	for (i = 0; i < nr_commands; i++, attrs++) {
 		chip->cc_attrs_tbl[i] = be32_to_cpup(attrs);
 		cc = chip->cc_attrs_tbl[i] & 0xFFFF;
@@ -654,8 +646,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
 		}
 	}
 
-	tpm_buf_destroy(&buf);
-
 out:
 	if (rc > 0)
 		rc = -ENODEV;
@@ -676,20 +666,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl);
 
 static int tpm2_startup(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	dev_info(&chip->dev, "starting up the TPM manually\n");
-
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
-	if (rc < 0)
-		return rc;
-
-	tpm_buf_append_u16(&buf, TPM2_SU_CLEAR);
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
-	tpm_buf_destroy(&buf);
-
-	return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
+	tpm_buf_append_u16(buf, TPM2_SU_CLEAR);
+	return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM");
 }
 
 /**
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index eb67ae14ad73..b50844740f90 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -168,7 +168,6 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
 	u32 mso = tpm2_handle_mso(handle);
 	off_t offset = TPM_HEADER_SIZE;
 	int rc, name_size_alg;
-	struct tpm_buf buf;
 
 	if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
 	    mso != TPM2_MSO_NVRAM) {
@@ -176,47 +175,42 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
 		return sizeof(u32);
 	}
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	tpm_buf_append_u32(&buf, handle);
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
+	tpm_buf_append_u32(buf, handle);
 
-	rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic");
-	if (rc) {
-		tpm_buf_destroy(&buf);
+	rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_ReadPublic");
+	if (rc)
 		return tpm_ret_to_err(rc);
-	}
 
 	/* Skip TPMT_PUBLIC: */
-	offset += tpm_buf_read_u16(&buf, &offset);
+	offset += tpm_buf_read_u16(buf, &offset);
 
 	/*
 	 * Ensure space for the length field of TPM2B_NAME and hashAlg field of
 	 * TPMT_HA (the extra four bytes).
 	 */
-	if (offset + 4 > tpm_buf_length(&buf)) {
-		tpm_buf_destroy(&buf);
+	if (offset + 4 > tpm_buf_length(buf))
 		return -EIO;
-	}
 
-	rc = tpm_buf_read_u16(&buf, &offset);
-	name_size_alg = name_size(&buf.data[offset]);
+	rc = tpm_buf_read_u16(buf, &offset);
+	name_size_alg = name_size(&buf->data[offset]);
 
 	if (name_size_alg < 0)
 		return name_size_alg;
 
-	if (rc != name_size_alg) {
-		tpm_buf_destroy(&buf);
+	if (rc != name_size_alg)
 		return -EIO;
-	}
 
-	if (offset + rc > tpm_buf_length(&buf)) {
-		tpm_buf_destroy(&buf);
+	if (offset + rc > tpm_buf_length(buf))
 		return -EIO;
-	}
 
-	memcpy(name, &buf.data[offset], rc);
+	memcpy(name, &buf->data[offset], rc);
+
 	return name_size_alg;
 }
 #endif /* CONFIG_TCG_TPM2_HMAC */
@@ -982,7 +976,6 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key)
 int tpm2_start_auth_session(struct tpm_chip *chip)
 {
 	struct tpm2_auth *auth;
-	struct tpm_buf buf;
 	u32 null_key;
 	int rc;
 
@@ -991,6 +984,10 @@ int tpm2_start_auth_session(struct tpm_chip *chip)
 		return 0;
 	}
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	auth = kzalloc_obj(*auth);
 	if (!auth)
 		return -ENOMEM;
@@ -1001,41 +998,37 @@ int tpm2_start_auth_session(struct tpm_chip *chip)
 
 	auth->session = TPM_HEADER_SIZE;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS);
-	if (rc)
-		goto out;
-
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS);
 	/* salt key handle */
-	tpm_buf_append_u32(&buf, null_key);
+	tpm_buf_append_u32(buf, null_key);
 	/* bind key handle */
-	tpm_buf_append_u32(&buf, TPM2_RH_NULL);
+	tpm_buf_append_u32(buf, TPM2_RH_NULL);
 	/* nonce caller */
 	get_random_bytes(auth->our_nonce, sizeof(auth->our_nonce));
-	tpm_buf_append_u16(&buf, sizeof(auth->our_nonce));
-	tpm_buf_append(&buf, auth->our_nonce, sizeof(auth->our_nonce));
+	tpm_buf_append_u16(buf, sizeof(auth->our_nonce));
+	tpm_buf_append(buf, auth->our_nonce, sizeof(auth->our_nonce));
 
 	/* append encrypted salt and squirrel away unencrypted in auth */
-	tpm_buf_append_salt(&buf, chip, auth);
+	tpm_buf_append_salt(buf, chip, auth);
 	/* session type (HMAC, audit or policy) */
-	tpm_buf_append_u8(&buf, TPM2_SE_HMAC);
+	tpm_buf_append_u8(buf, TPM2_SE_HMAC);
 
 	/* symmetric encryption parameters */
 	/* symmetric algorithm */
-	tpm_buf_append_u16(&buf, TPM_ALG_AES);
+	tpm_buf_append_u16(buf, TPM_ALG_AES);
 	/* bits for symmetric algorithm */
-	tpm_buf_append_u16(&buf, AES_KEY_BITS);
+	tpm_buf_append_u16(buf, AES_KEY_BITS);
 	/* symmetric algorithm mode (must be CFB) */
-	tpm_buf_append_u16(&buf, TPM_ALG_CFB);
+	tpm_buf_append_u16(buf, TPM_ALG_CFB);
 	/* hash algorithm for session */
-	tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+	tpm_buf_append_u16(buf, TPM_ALG_SHA256);
 
-	rc = tpm_ret_to_err(tpm_transmit_cmd(chip, &buf, 0, "StartAuthSession"));
+	rc = tpm_ret_to_err(tpm_transmit_cmd(chip, buf, 0, "StartAuthSession"));
 	tpm2_flush_context(chip, null_key);
 
 	if (rc == TPM2_RC_SUCCESS)
-		rc = tpm2_parse_start_auth_session(auth, &buf);
-
-	tpm_buf_destroy(&buf);
+		rc = tpm2_parse_start_auth_session(auth, buf);
 
 	if (rc == TPM2_RC_SUCCESS) {
 		chip->auth = auth;
@@ -1257,18 +1250,18 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
 			       u32 *handle, u8 *name)
 {
 	int rc;
-	struct tpm_buf buf;
-	struct tpm_buf template;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	rc = tpm_buf_init_sized(&template);
-	if (rc) {
-		tpm_buf_destroy(&buf);
-		return rc;
-	}
+	struct tpm_buf *template __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!template)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
+	tpm_buf_init_sized(template, TPM_BUFSIZE);
 
 	/*
 	 * create the template.  Note: in order for userspace to
@@ -1280,75 +1273,72 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
 	 */
 
 	/* key type */
-	tpm_buf_append_u16(&template, TPM_ALG_ECC);
+	tpm_buf_append_u16(template, TPM_ALG_ECC);
 
 	/* name algorithm */
-	tpm_buf_append_u16(&template, TPM_ALG_SHA256);
+	tpm_buf_append_u16(template, TPM_ALG_SHA256);
 
 	/* object properties */
-	tpm_buf_append_u32(&template, TPM2_OA_NULL_KEY);
+	tpm_buf_append_u32(template, TPM2_OA_NULL_KEY);
 
 	/* sauth policy (empty) */
-	tpm_buf_append_u16(&template, 0);
+	tpm_buf_append_u16(template, 0);
 
 	/* BEGIN parameters: key specific; for ECC*/
 
 	/* symmetric algorithm */
-	tpm_buf_append_u16(&template, TPM_ALG_AES);
+	tpm_buf_append_u16(template, TPM_ALG_AES);
 
 	/* bits for symmetric algorithm */
-	tpm_buf_append_u16(&template, AES_KEY_BITS);
+	tpm_buf_append_u16(template, AES_KEY_BITS);
 
 	/* algorithm mode (must be CFB) */
-	tpm_buf_append_u16(&template, TPM_ALG_CFB);
+	tpm_buf_append_u16(template, TPM_ALG_CFB);
 
 	/* scheme (NULL means any scheme) */
-	tpm_buf_append_u16(&template, TPM_ALG_NULL);
+	tpm_buf_append_u16(template, TPM_ALG_NULL);
 
 	/* ECC Curve ID */
-	tpm_buf_append_u16(&template, TPM2_ECC_NIST_P256);
+	tpm_buf_append_u16(template, TPM2_ECC_NIST_P256);
 
 	/* KDF Scheme */
-	tpm_buf_append_u16(&template, TPM_ALG_NULL);
+	tpm_buf_append_u16(template, TPM_ALG_NULL);
 
 	/* unique: key specific; for ECC it is two zero size points */
-	tpm_buf_append_u16(&template, 0);
-	tpm_buf_append_u16(&template, 0);
+	tpm_buf_append_u16(template, 0);
+	tpm_buf_append_u16(template, 0);
 
 	/* END parameters */
 
 	/* primary handle */
-	tpm_buf_append_u32(&buf, hierarchy);
-	tpm_buf_append_empty_auth(&buf, TPM2_RS_PW);
+	tpm_buf_append_u32(buf, hierarchy);
+	tpm_buf_append_empty_auth(buf, TPM2_RS_PW);
 
 	/* sensitive create size is 4 for two empty buffers */
-	tpm_buf_append_u16(&buf, 4);
+	tpm_buf_append_u16(buf, 4);
 
 	/* sensitive create auth data (empty) */
-	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u16(buf, 0);
 
 	/* sensitive create sensitive data (empty) */
-	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u16(buf, 0);
 
 	/* the public template */
-	tpm_buf_append(&buf, template.data, template.length);
-	tpm_buf_destroy(&template);
+	tpm_buf_append(buf, template->data, template->length);
 
 	/* outside info (empty) */
-	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u16(buf, 0);
 
 	/* creation PCR (none) */
-	tpm_buf_append_u32(&buf, 0);
+	tpm_buf_append_u32(buf, 0);
 
-	rc = tpm_transmit_cmd(chip, &buf, 0,
+	rc = tpm_transmit_cmd(chip, buf, 0,
 			      "attempting to create NULL primary");
 
 	if (rc == TPM2_RC_SUCCESS)
-		rc = tpm2_parse_create_primary(chip, &buf, handle, hierarchy,
+		rc = tpm2_parse_create_primary(chip, buf, handle, hierarchy,
 					       name);
 
-	tpm_buf_destroy(&buf);
-
 	return rc;
 }
 
diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
index 7c1c0a174a2b..5b0f233db898 100644
--- a/drivers/char/tpm/tpm2-space.c
+++ b/drivers/char/tpm/tpm2-space.c
@@ -58,24 +58,25 @@ void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
 int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
 		      unsigned int *offset, u32 *handle)
 {
-	struct tpm_buf tbuf;
 	struct tpm2_context *ctx;
 	unsigned int body_size;
 	int rc;
 
-	rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD);
-	if (rc)
-		return rc;
+	struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tbuf)
+		return -ENOMEM;
+
+	tpm_buf_init(tbuf, TPM_BUFSIZE);
+	tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD);
 
 	ctx = (struct tpm2_context *)&buf[*offset];
 	body_size = sizeof(*ctx) + be16_to_cpu(ctx->blob_size);
-	tpm_buf_append(&tbuf, &buf[*offset], body_size);
+	tpm_buf_append(tbuf, &buf[*offset], body_size);
 
-	rc = tpm_transmit_cmd(chip, &tbuf, 4, NULL);
+	rc = tpm_transmit_cmd(chip, tbuf, 4, NULL);
 	if (rc < 0) {
 		dev_warn(&chip->dev, "%s: failed with a system error %d\n",
 			 __func__, rc);
-		tpm_buf_destroy(&tbuf);
 		return -EFAULT;
 	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
 		   rc == TPM2_RC_REFERENCE_H0) {
@@ -90,64 +91,55 @@ int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
 		 * flushed outside the space
 		 */
 		*handle = 0;
-		tpm_buf_destroy(&tbuf);
 		return -ENOENT;
 	} else if (tpm2_rc_value(rc) == TPM2_RC_INTEGRITY) {
-		tpm_buf_destroy(&tbuf);
 		return -EINVAL;
 	} else if (rc > 0) {
 		dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
 			 __func__, rc);
-		tpm_buf_destroy(&tbuf);
 		return -EFAULT;
 	}
 
-	*handle = be32_to_cpup((__be32 *)&tbuf.data[TPM_HEADER_SIZE]);
+	*handle = be32_to_cpup((__be32 *)&tbuf->data[TPM_HEADER_SIZE]);
 	*offset += body_size;
-
-	tpm_buf_destroy(&tbuf);
 	return 0;
 }
 
 int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
 		      unsigned int buf_size, unsigned int *offset)
 {
-	struct tpm_buf tbuf;
 	unsigned int body_size;
 	int rc;
 
-	rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE);
-	if (rc)
-		return rc;
+	struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tbuf)
+		return -ENOMEM;
 
-	tpm_buf_append_u32(&tbuf, handle);
+	tpm_buf_init(tbuf, TPM_BUFSIZE);
+	tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE);
+	tpm_buf_append_u32(tbuf, handle);
 
-	rc = tpm_transmit_cmd(chip, &tbuf, 0, NULL);
+	rc = tpm_transmit_cmd(chip, tbuf, 0, NULL);
 	if (rc < 0) {
 		dev_warn(&chip->dev, "%s: failed with a system error %d\n",
 			 __func__, rc);
-		tpm_buf_destroy(&tbuf);
 		return -EFAULT;
 	} else if (tpm2_rc_value(rc) == TPM2_RC_REFERENCE_H0) {
-		tpm_buf_destroy(&tbuf);
 		return -ENOENT;
 	} else if (rc) {
 		dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
 			 __func__, rc);
-		tpm_buf_destroy(&tbuf);
 		return -EFAULT;
 	}
 
-	body_size = tpm_buf_length(&tbuf) - TPM_HEADER_SIZE;
+	body_size = tpm_buf_length(tbuf) - TPM_HEADER_SIZE;
 	if ((*offset + body_size) > buf_size) {
 		dev_warn(&chip->dev, "%s: out of backing storage\n", __func__);
-		tpm_buf_destroy(&tbuf);
 		return -ENOMEM;
 	}
 
-	memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
+	memcpy(&buf[*offset], &tbuf->data[TPM_HEADER_SIZE], body_size);
 	*offset += body_size;
-	tpm_buf_destroy(&tbuf);
 	return 0;
 }
 
diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c
index 7bb0f4d4a2ed..b81fd2a537df 100644
--- a/drivers/char/tpm/tpm_vtpm_proxy.c
+++ b/drivers/char/tpm/tpm_vtpm_proxy.c
@@ -395,40 +395,36 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip  *chip, u8 status)
 
 static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality)
 {
-	struct tpm_buf buf;
 	int rc;
 	const struct tpm_header *header;
 	struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev);
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
-		rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS,
-				  TPM2_CC_SET_LOCALITY);
+		tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_SET_LOCALITY);
 	else
-		rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND,
-				  TPM_ORD_SET_LOCALITY);
-	if (rc)
-		return rc;
-	tpm_buf_append_u8(&buf, locality);
+		tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SET_LOCALITY);
+
+	tpm_buf_append_u8(buf, locality);
 
 	proxy_dev->state |= STATE_DRIVER_COMMAND;
 
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to set locality");
+	rc = tpm_transmit_cmd(chip, buf, 0, "attempting to set locality");
 
 	proxy_dev->state &= ~STATE_DRIVER_COMMAND;
 
-	if (rc < 0) {
-		locality = rc;
-		goto out;
-	}
+	if (rc < 0)
+		return rc;
 
-	header = (const struct tpm_header *)buf.data;
+	header = (const struct tpm_header *)buf->data;
 	rc = be32_to_cpu(header->return_code);
 	if (rc)
 		locality = -1;
 
-out:
-	tpm_buf_destroy(&buf);
-
 	return locality;
 }
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index b357f8971d03..0a56f6c1ea98 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -208,13 +208,15 @@ enum tpm_buf_flags {
 };
 
 /*
- * A string buffer type for constructing TPM commands.
+ * A buffer for constructing and parsing TPM commands, responses and sized
+ * (TPM2B) buffers.
  */
 struct tpm_buf {
-	u32 flags;
-	u32 length;
-	u8 *data;
+	u8 flags;
 	u8 handles;
+	u16 length;
+	u16 capacity;
+	u8 data[];
 };
 
 struct tpm2_hash {
@@ -222,12 +224,11 @@ struct tpm2_hash {
 	unsigned int tpm_id;
 };
 
-int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal);
+void tpm_buf_init(struct tpm_buf *buf, u16 buf_size);
+void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size);
 void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal);
-int tpm_buf_init_sized(struct tpm_buf *buf);
 void tpm_buf_reset_sized(struct tpm_buf *buf);
-void tpm_buf_destroy(struct tpm_buf *buf);
-u32 tpm_buf_length(struct tpm_buf *buf);
+u16 tpm_buf_length(struct tpm_buf *buf);
 void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length);
 void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value);
 void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value);
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 0d3244af8de3..592366572641 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -310,9 +310,10 @@ static int TSS_checkhmac2(unsigned char *buffer,
  * For key specific tpm requests, we will generate and send our
  * own TPM command packets using the drivers send function.
  */
-static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
+static int trusted_tpm_send(void *cmd, size_t cmd_len)
 {
-	struct tpm_buf buf;
+	u8 buf_data[512];
+	struct tpm_buf *buf = (struct tpm_buf *)buf_data;
 	int rc;
 
 	if (!chip)
@@ -322,11 +323,10 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
 	if (rc)
 		return rc;
 
-	buf.flags = 0;
-	buf.length = buflen;
-	buf.data = cmd;
+	tpm_buf_init(buf, sizeof(buf_data));
+	tpm_buf_append(buf, cmd, cmd_len);
 	dump_tpm_buf(cmd);
-	rc = tpm_transmit_cmd(chip, &buf, 4, "sending data");
+	rc = tpm_transmit_cmd(chip, buf, 4, "sending data");
 	dump_tpm_buf(cmd);
 
 	if (rc > 0)
@@ -624,23 +624,23 @@ static int tpm_unseal(struct tpm_buf *tb,
 static int key_seal(struct trusted_key_payload *p,
 		    struct trusted_key_options *o)
 {
-	struct tpm_buf tb;
 	int ret;
 
-	ret = tpm_buf_init(&tb, 0, 0);
-	if (ret)
-		return ret;
+	struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tb)
+		return -ENOMEM;
+
+	tpm_buf_init(tb, TPM_BUFSIZE);
 
 	/* include migratable flag at end of sealed key */
 	p->key[p->key_len] = p->migratable;
 
-	ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth,
+	ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth,
 		       p->key, p->key_len + 1, p->blob, &p->blob_len,
 		       o->blobauth, o->pcrinfo, o->pcrinfo_len);
 	if (ret < 0)
 		pr_info("srkseal failed (%d)\n", ret);
 
-	tpm_buf_destroy(&tb);
 	return ret;
 }
 
@@ -650,14 +650,15 @@ static int key_seal(struct trusted_key_payload *p,
 static int key_unseal(struct trusted_key_payload *p,
 		      struct trusted_key_options *o)
 {
-	struct tpm_buf tb;
 	int ret;
 
-	ret = tpm_buf_init(&tb, 0, 0);
-	if (ret)
-		return ret;
+	struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tb)
+		return -ENOMEM;
+
+	tpm_buf_init(tb, TPM_BUFSIZE);
 
-	ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
+	ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
 			 o->blobauth, p->key, &p->key_len);
 	if (ret < 0)
 		pr_info("srkunseal failed (%d)\n", ret);
@@ -665,7 +666,6 @@ static int key_unseal(struct trusted_key_payload *p,
 		/* pull migratable flag out of sealed key */
 		p->migratable = p->key[--p->key_len];
 
-	tpm_buf_destroy(&tb);
 	return ret;
 }
 
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 779a2e66ac20..8e77afbb50d9 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -233,12 +233,16 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		      struct trusted_key_options *options)
 {
 	off_t offset = TPM_HEADER_SIZE;
-	struct tpm_buf buf, sized;
 	int blob_len = 0;
 	int hash;
 	u32 flags;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	struct tpm_buf *sized __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf || !sized)
+		return -ENOMEM;
+
 	hash = tpm2_find_hash_alg(options->hash);
 	if (hash < 0)
 		return hash;
@@ -254,97 +258,85 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 	if (rc)
 		goto out_put;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
-	if (rc) {
-		tpm2_end_auth_session(chip);
-		goto out_put;
-	}
-
-	rc = tpm_buf_init_sized(&sized);
-	if (rc) {
-		tpm_buf_destroy(&buf);
-		tpm2_end_auth_session(chip);
-		goto out_put;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
 	if (rc)
-		goto out;
+		goto out_put;
 
-	tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT,
+	tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT,
 				    options->keyauth, TPM_DIGEST_SIZE);
 
 	/* sensitive */
-	tpm_buf_append_u16(&sized, options->blobauth_len);
+	tpm_buf_init_sized(sized, TPM_BUFSIZE);
+	tpm_buf_append_u16(sized, options->blobauth_len);
 
 	if (options->blobauth_len)
-		tpm_buf_append(&sized, options->blobauth, options->blobauth_len);
+		tpm_buf_append(sized, options->blobauth, options->blobauth_len);
 
-	tpm_buf_append_u16(&sized, payload->key_len);
-	tpm_buf_append(&sized, payload->key, payload->key_len);
-	tpm_buf_append(&buf, sized.data, sized.length);
+	tpm_buf_append_u16(sized, payload->key_len);
+	tpm_buf_append(sized, payload->key, payload->key_len);
+	tpm_buf_append(buf, sized->data, sized->length);
 
 	/* public */
-	tpm_buf_reset_sized(&sized);
-	tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH);
-	tpm_buf_append_u16(&sized, hash);
+	tpm_buf_init_sized(sized, TPM_BUFSIZE);
+	tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH);
+	tpm_buf_append_u16(sized, hash);
 
 	/* key properties */
 	flags = 0;
 	flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
 	flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
-	tpm_buf_append_u32(&sized, flags);
+	tpm_buf_append_u32(sized, flags);
 
 	/* policy */
-	tpm_buf_append_u16(&sized, options->policydigest_len);
+	tpm_buf_append_u16(sized, options->policydigest_len);
 	if (options->policydigest_len)
-		tpm_buf_append(&sized, options->policydigest, options->policydigest_len);
+		tpm_buf_append(sized, options->policydigest, options->policydigest_len);
 
 	/* public parameters */
-	tpm_buf_append_u16(&sized, TPM_ALG_NULL);
-	tpm_buf_append_u16(&sized, 0);
+	tpm_buf_append_u16(sized, TPM_ALG_NULL);
+	tpm_buf_append_u16(sized, 0);
 
-	tpm_buf_append(&buf, sized.data, sized.length);
+	tpm_buf_append(buf, sized->data, sized->length);
 
 	/* outside info */
-	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u16(buf, 0);
 
 	/* creation PCR */
-	tpm_buf_append_u32(&buf, 0);
+	tpm_buf_append_u32(buf, 0);
 
-	if (buf.flags & TPM_BUF_INVALID) {
+	if (buf->flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		tpm2_end_auth_session(chip);
 		goto out;
 	}
 
-	rc = tpm_buf_fill_hmac_session(chip, &buf);
+	rc = tpm_buf_fill_hmac_session(chip, buf);
 	if (rc)
 		goto out;
 
-	rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data");
-	rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+	rc = tpm_transmit_cmd(chip, buf, 4, "sealing data");
+	rc = tpm_buf_check_hmac_response(chip, buf, rc);
 	if (rc)
 		goto out;
 
-	blob_len = tpm_buf_read_u32(&buf, &offset);
-	if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) {
+	blob_len = tpm_buf_read_u32(buf, &offset);
+	if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		goto out;
 	}
-	if (buf.length - offset < blob_len) {
+	if (buf->length - offset < blob_len) {
 		rc = -EFAULT;
 		goto out;
 	}
 
-	blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len);
+	blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len);
 	if (blob_len < 0)
 		rc = blob_len;
 
 out:
-	tpm_buf_destroy(&sized);
-	tpm_buf_destroy(&buf);
-
 	if (!rc)
 		payload->blob_len = blob_len;
 
@@ -372,7 +364,6 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 			 u32 *blob_handle)
 {
 	u8 *blob_ref __free(kfree) = NULL;
-	struct tpm_buf buf;
 	unsigned int private_len;
 	unsigned int public_len;
 	unsigned int blob_len;
@@ -380,6 +371,10 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 	int rc;
 	u32 attrs;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	rc = tpm2_key_decode(payload, options, &blob);
 	if (rc) {
 		/* old form */
@@ -426,39 +421,32 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 	if (rc)
 		return rc;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
-	if (rc) {
-		tpm2_end_auth_session(chip);
-		return rc;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
 	if (rc)
-		goto out;
+		return rc;
 
-	tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth,
+	tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth,
 				    TPM_DIGEST_SIZE);
 
-	tpm_buf_append(&buf, blob, blob_len);
+	tpm_buf_append(buf, blob, blob_len);
 
-	if (buf.flags & TPM_BUF_INVALID) {
-		rc = -E2BIG;
+	if (buf->flags & TPM_BUF_INVALID) {
 		tpm2_end_auth_session(chip);
-		goto out;
+		return -E2BIG;
 	}
 
-	rc = tpm_buf_fill_hmac_session(chip, &buf);
+	rc = tpm_buf_fill_hmac_session(chip, buf);
 	if (rc)
-		goto out;
+		return -EFAULT;
 
-	rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob");
-	rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+	rc = tpm_transmit_cmd(chip, buf, 4, "loading blob");
+	rc = tpm_buf_check_hmac_response(chip, buf, rc);
 	if (!rc)
 		*blob_handle = be32_to_cpup(
-			(__be32 *) &buf.data[TPM_HEADER_SIZE]);
-
-out:
-	tpm_buf_destroy(&buf);
+			(__be32 *) &buf->data[TPM_HEADER_SIZE]);
 
 	return tpm_ret_to_err(rc);
 }
@@ -481,28 +469,28 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 			   u32 blob_handle)
 {
 	struct tpm_header *head;
-	struct tpm_buf buf;
 	u16 data_len;
 	int offset;
 	u8 *data;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	rc = tpm2_start_auth_session(chip);
 	if (rc)
 		return rc;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
-	if (rc) {
-		tpm2_end_auth_session(chip);
-		return rc;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
 
-	rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL);
+	rc = tpm_buf_append_name(chip, buf, blob_handle, NULL);
 	if (rc)
-		goto out;
+		return rc;
 
 	if (!options->policyhandle) {
-		tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT,
+		tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
 					    options->blobauth,
 					    options->blobauth_len);
 	} else {
@@ -517,39 +505,35 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 		 * could repeat our actions with the exfiltrated
 		 * password.
 		 */
-		tpm2_buf_append_auth(&buf, options->policyhandle,
+		tpm2_buf_append_auth(buf, options->policyhandle,
 				     NULL /* nonce */, 0, 0,
 				     options->blobauth, options->blobauth_len);
 		if (tpm2_chip_auth(chip)) {
-			tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0);
+			tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, NULL, 0);
 		} else  {
-			offset = buf.handles * 4 + TPM_HEADER_SIZE;
-			head = (struct tpm_header *)buf.data;
-			if (tpm_buf_length(&buf) == offset)
+			offset = buf->handles * 4 + TPM_HEADER_SIZE;
+			head = (struct tpm_header *)buf->data;
+			if (tpm_buf_length(buf) == offset)
 				head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
 		}
 	}
 
-	rc = tpm_buf_fill_hmac_session(chip, &buf);
+	rc = tpm_buf_fill_hmac_session(chip, buf);
 	if (rc)
-		goto out;
+		return rc;
 
-	rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing");
-	rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+	rc = tpm_transmit_cmd(chip, buf, 6, "unsealing");
+	rc = tpm_buf_check_hmac_response(chip, buf, rc);
 
 	if (!rc) {
 		data_len = be16_to_cpup(
-			(__be16 *) &buf.data[TPM_HEADER_SIZE + 4]);
-		if (data_len < MIN_KEY_SIZE ||  data_len > MAX_KEY_SIZE) {
-			rc = -EFAULT;
-			goto out;
-		}
+			(__be16 *) &buf->data[TPM_HEADER_SIZE + 4]);
+		if (data_len < MIN_KEY_SIZE ||  data_len > MAX_KEY_SIZE)
+			return -EFAULT;
 
-		if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) {
-			rc = -EFAULT;
-			goto out;
-		}
-		data = &buf.data[TPM_HEADER_SIZE + 6];
+		if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len)
+			return -EFAULT;
+		data = &buf->data[TPM_HEADER_SIZE + 6];
 
 		if (payload->old_format) {
 			/* migratable flag is at the end of the key */
@@ -566,8 +550,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 		}
 	}
 
-out:
-	tpm_buf_destroy(&buf);
 	return tpm_ret_to_err(rc);
 }
 
-- 
2.47.3



From ruanjinjie at huawei.com  Tue May 19 05:42:04 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Tue, 19 May 2026 20:42:04 +0800
Subject: [PATCH v13 04/15] arm64: kexec_file: Fix potential buffer
 overflow in prepare_elf_headers()
In-Reply-To: <agGkvrg06KNDNfDi@gmail.com>
References: <20260511030454.1730881-1-ruanjinjie@huawei.com>
 <20260511030454.1730881-5-ruanjinjie@huawei.com> <agGkvrg06KNDNfDi@gmail.com>
Message-ID: <3ca700ed-e081-4c62-8289-5bbd4248e630@huawei.com>



On 5/11/2026 5:46 PM, Breno Leitao wrote:
> On Mon, May 11, 2026 at 11:04:43AM +0800, Jinjie Ruan wrote:
>> There is a race condition between the kexec_load() system call
>> (crash kernel loading path) and memory hotplug operations that can
>> lead to buffer overflow and potential kernel crash.
>>
>> During prepare_elf_headers(), the following steps occur:
>> 1. The first for_each_mem_range() queries current System RAM memory ranges
>> 2. Allocates buffer based on queried count
>> 3. The 2st for_each_mem_range() populates ranges from memblock
>>
>> If memory hotplug occurs between step 1 and step 3, the number of ranges
>> can increase, causing out-of-bounds write when populating cmem->ranges[].
>>
>> This happens because kexec_load() uses kexec_trylock (atomic_t) while
>> memory hotplug uses device_hotplug_lock (mutex), so they don't serialize
>> with each other.
>>
>> Add the explicit bounds checking to prevent out-of-bounds access.
> 
> It seems you have a TOCTOU type of issue, and this seems to be shrinking
> the window, but not fully solving it?

I plan to fix this issue as follows, and would appreciate your feedback
on whether this is reasonable.

Sashiko AI code review pointed out there is a TOCTOU (Time-of-Check to
Time-of-Use) race condition in prepare_elf_headers() between the initial
pass that counts System RAM ranges and the second pass that populates them.
If a memory hotplug event occurs between these two steps, the number of
memory regions may increase, causing an out-of-bounds write to
the cmem->ranges[] array.

To resolve this and ensure data consistency, this patch:

1. Wraps the counting and population passes with get_online_mems() and
   crash_hotplug_lock(). This serializes the kexec_file_load() path
   with concurrent memory hotplug operations, ensuring the memory
   map remains consistent throughout the header preparation.

2. Adds an explicit boundary check in prepare_elf64_ram_headers_callback().
   If the number of ranges exceeds the allocated maximum, it now returns
   -EAGAIN, which indicates a transient race, signaling userspace
   kexec-tools to retry the syscall instead of leaving the system
without a loaded crash kernel.

index daf81a873bbd..546be6261177 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -15,6 +15,7 @@
 #include <linux/kexec.h>
 #include <linux/libfdt.h>
 #include <linux/memblock.h>
+#include <linux/memory_hotplug.h>
 #include <linux/of.h>
 #include <linux/of_fdt.h>
 #include <linux/slab.h>
@@ -40,7 +41,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage
*image)
 }

 #ifdef CONFIG_CRASH_DUMP
-int prepare_elf_headers(void **addr, unsigned long *sz)
+static int __prepare_elf_headers(void **addr, unsigned long *sz)
 {
 	struct crash_mem *cmem;
 	unsigned int nr_ranges;
@@ -59,6 +60,11 @@ int prepare_elf_headers(void **addr, unsigned long *sz)
 	cmem->max_nr_ranges = nr_ranges;
 	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
+		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
+			ret = -EAGAIN;
+			goto out;
+		}
+
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
@@ -81,6 +87,21 @@ int prepare_elf_headers(void **addr, unsigned long *sz)
 	kfree(cmem);
 	return ret;
 }
+
+int prepare_elf_headers(void **addr, unsigned long *sz)
+{
+	int ret;
+
+	crash_hotplug_lock();
+	get_online_mems();
+
+	ret = __prepare_elf_headers(addr, sz);
+
+	put_online_mems();
+	crash_hotplug_unlock();
+
+	return ret;
+}
 #endif

> 
>> Cc: Catalin Marinas <catalin.marinas at arm.com>
>> Cc: Will Deacon <will.deacon at arm.com>
>> Cc: Andrew Morton <akpm at linux-foundation.org>
>> Cc: Baoquan He <bhe at redhat.com>
>> Cc: Breno Leitao <leitao at debian.org>
>> Cc: stable at vger.kernel.org
>> Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
>> Closes: https://sashiko.dev/#/patchset/20260323072745.2481719-1-ruanjinjie%40huawei.com
>> Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
>> ---
>>  arch/arm64/kernel/machine_kexec_file.c | 5 +++++
>>  1 file changed, 5 insertions(+)
>>
>> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
>> index e31fabed378a..a67e7b1abbab 100644
>> --- a/arch/arm64/kernel/machine_kexec_file.c
>> +++ b/arch/arm64/kernel/machine_kexec_file.c
>> @@ -59,6 +59,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>>  	cmem->max_nr_ranges = nr_ranges;
>>  	cmem->nr_ranges = 0;
>>  	for_each_mem_range(i, &start, &end) {
>> +		if (cmem->nr_ranges >= cmem->max_nr_ranges) {
>> +			ret = -ENOMEM;
> 
> -ENOMEM seems to be the the wrong errno. This isn't an allocation
> failure; it's a transient race. -EBUSY or -EAGAIN would be more honest



From hangej at amazon.com  Thu May 21 15:30:08 2026
From: hangej at amazon.com (hangej)
Date: Fri, 22 May 2026 00:30:08 +0200
Subject: [PATCH v2] pci_crash: capture PCI config space at panic time
Message-ID: <20260521223010.2681515-1-hangej@amazon.com>

From: Johannes Hange <hangej at amazon.com>

When a system crashes and kexec boots into the crash kernel, PCI
device state is lost.  AER registers are volatile and cleared by
device reset, making post-mortem root cause analysis difficult.

Add CONFIG_PCI_CRASH to capture PCI configuration space (including
AER extended capability registers) during panic, before kexec.  The
data is exported via VMCOREINFO so crash analysis tools can extract
it from the vmcore.

Hook point: crash_save_vmcoreinfo() inside __crash_kexec(), which
runs before machine_kexec() -- the only reliable capture point since
panic notifiers run after kexec by default.

Key design choices:
- No config reads at init or hotplug; buffer pre-allocated, filled
  only at crash time for fresh register state.
- capture=aer (default): quick-scan root port ROOT_STATUS, bail if
  no uncorrectable errors.  capture=always: every panic.
- devices= filter applied at rebuild time (zero crash-path cost).
- 200ms debounced workqueue rebuild for VF enumeration storms.
- kvmalloc buffer + kmalloc pagemap for physical page directory
  (vmalloc addresses need explicit page-to-phys translation).
- dcache flush (ARM64/x86) ensures data survives kexec.

Build-tested on v7.1-rc2 (x86_64).  Runtime-tested on 6.6 with
forced panics: ~2ms for 31 PCIe devices (4096B config each),
sub-microsecond AER bail on non-PCI panics.

See Documentation/PCI/pci-crash-capture.rst for full details.

Reported-by: kernel test robot <lkp at intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202605140501.1tSG7lxR-lkp at intel.com/
Signed-off-by: Johannes Hange <hangej at amazon.com>
---

v2:
- Kconfig: remove default y (opt-in, not auto-enabled)
- Default changed to capture=always (better for general consumers;
  capture=aer available as opt-in optimization)
- Fix build with CONFIG_PCIEAER=n: guard pdev->aer_cap access (kernel test robot)
- Warn at init when capture=aer but CONFIG_PCIEAER=n (only if ALWAYS not also set)
- Fix MAINTAINERS alphabetical order (PCI CRASH before PCI DRIVER)
- Drop redundant OOM messages (kvmalloc/kmalloc already warn)
- Add Documentation/admin-guide/kernel-parameters.txt entries
- Fix kernel-doc warnings on exported variable declarations
- Initialize status variable in AER scan
- Fix documentation: pci_crash_ready replaces slab_is_available()
- Correct pci_lock comment (raw spinlock, not lockless)
- pr_emerg ? pr_info for non-emergency messages in crash path

 Documentation/PCI/index.rst                   |   1 +
 Documentation/PCI/pci-crash-capture.rst       | 137 ++++
 .../admin-guide/kernel-parameters.txt         |  16 +
 MAINTAINERS                                   |   8 +
 include/linux/pci_crash.h                     | 110 +++
 kernel/Kconfig.kexec                          |  16 +
 kernel/Makefile                               |   1 +
 kernel/pci_crash.c                            | 750 ++++++++++++++++++
 kernel/vmcore_info.c                          |  13 +
 9 files changed, 1052 insertions(+)
 create mode 100644 Documentation/PCI/pci-crash-capture.rst
 create mode 100644 include/linux/pci_crash.h
 create mode 100644 kernel/pci_crash.c

diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e..7f499a43ddb4 100644
--- a/Documentation/PCI/index.rst
+++ b/Documentation/PCI/index.rst
@@ -19,4 +19,5 @@ PCI Bus Subsystem
    endpoint/index
    controller/index
    boot-interrupts
+   pci-crash-capture
    tph
diff --git a/Documentation/PCI/pci-crash-capture.rst b/Documentation/PCI/pci-crash-capture.rst
new file mode 100644
index 000000000000..d7d4bd37a73b
--- /dev/null
+++ b/Documentation/PCI/pci-crash-capture.rst
@@ -0,0 +1,137 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================
+PCI Crash Capture Buffer
+========================
+
+Overview
+========
+
+The PCI crash capture module (``CONFIG_PCI_CRASH``) saves PCI configuration
+space for all (or selected) devices at panic time.  The data is written into
+a pre-allocated buffer whose physical pages are exported via VMCOREINFO,
+allowing crash analysis tools to extract device state from the vmcore.
+
+This is useful because AER (Advanced Error Reporting) registers are volatile
+and cleared by device reset during kexec into the crash kernel.  Capturing
+them before kexec preserves the error state that caused or contributed to the
+crash.
+
+Boot Parameters
+===============
+
+``pci_crash.capture=`` (default: ``always``)
+  When to capture PCI config space.  Comma-separated tokens:
+
+  ``aer``
+    Capture only if a root port reports uncorrectable errors in its
+    AER ROOT_STATUS register.  Non-PCI panics skip capture entirely
+    (a handful of MMIO reads to root ports, sub-microsecond).
+
+  ``always``
+    Capture on every panic regardless of AER state.  Useful for
+    cascading failures where a PCI link-down causes an MCE or NMI
+    watchdog timeout before DPC/AER fires.
+
+``pci_crash.devices=`` (default: ``all``)
+  Which devices to include in the capture buffer.  Comma-separated tokens:
+
+  ``all``
+    Every PCI device in the system.
+
+  ``bridges``
+    PCI-to-PCI bridges (class 0604) and CardBus bridges (class 0607).
+
+  ``root_ports``
+    PCIe root ports only.
+
+  ``XXYY``
+    Hex PCI class code (class byte XX, subclass byte YY).
+    Up to 8 class codes may be specified.
+
+  Bridges are always implicitly included regardless of the filter value
+  because they hold AER registers needed for root cause analysis.  The
+  filter is applied at device enumeration and hotplug rebuild time, not at
+  crash time (zero overhead on the panic path).
+
+Both parameters are writable at runtime via sysfs
+(``/sys/module/pci_crash/parameters/``).
+
+Architecture
+============
+
+::
+
+  late_initcall
+      ?
+      ??? enumerate PCI devices (filtered by devices= param)
+      ??? allocate buffer via kvmalloc (may be vmalloc for >4 MiB)
+      ??? build pagemap: kmalloc'd array of per-page physical addresses
+      ??? register bus notifier for hotplug
+
+  hotplug (BUS_NOTIFY_ADD_DEVICE / BUS_NOTIFY_DEL_DEVICE)
+      ?
+      ??? schedule delayed rebuild (200 ms debounce)
+              ??? re-enumerate, re-allocate buffer + pagemap
+
+  panic (__crash_kexec ? crash_save_vmcoreinfo ? pci_crash_save)
+      ?
+      ??? quick-scan root port AER ROOT_STATUS (capture=aer)
+      ?     ??? bail if no uncorrectable errors
+      ??? read config space for each device (MMIO, no locks)
+      ??? flush dcache (buffer + pagemap) to RAM
+      ??? VMCOREINFO exports: PCI_CRASH_PAGEMAP, PCI_CRASH_BUF_SZ
+
+Buffer Format
+=============
+
+The buffer consists of a 32-byte header followed by variable-length
+device records:
+
+.. code-block:: c
+
+    struct pci_crash_buffer_header {   /* 32 bytes */
+        __le32 magic;           /* 0x50434943 "PCIC" */
+        __le32 version;         /* 1 */
+        __le32 device_count;
+        __le32 config_size;     /* 0 = variable-length records */
+        __le64 timestamp;       /* ktime_get_real_fast_ns() */
+        __le32 flags;           /* reserved */
+        __le32 reserved;
+    };
+
+    struct pci_crash_device_record {   /* 8 + cfg_size bytes */
+        __le16 domain;
+        __u8   bus;
+        __u8   devfn;
+        __le32 config_size;     /* 256 or 4096 */
+        __u8   config_data[];
+    };
+
+The pagemap (exported via ``PCI_CRASH_PAGEMAP``) allows the parser to
+locate buffer pages without walking page tables:
+
+.. code-block:: c
+
+    struct pci_crash_pagemap {
+        __le32 magic;           /* 0x5043504d "PCPM" */
+        __le32 num_pages;
+        __le64 buf_size;
+        __le64 addrs[];         /* physical address per page */
+    };
+
+Safety Considerations
+=====================
+
+- ``pci_read_config_dword()`` takes ``pci_lock`` (raw spinlock); acceptable
+  in crash context since other CPUs are halted and the panicking CPU is
+  unlikely to have held the lock at the time of the crash.
+- ``ktime_get_real_fast_ns()`` is NMI-safe (lockless timekeeper snapshot).
+- ``WRITE_ONCE``/``READ_ONCE`` + memory barriers between rebuild (process
+  context) and ``pci_crash_save()`` (crash context, single CPU, interrupts
+  disabled).
+- Buffer capped at 24 MiB to prevent excessive allocation on systems with
+  thousands of VFs.
+- ``pci_crash_ready`` flag in param setters defers parsing and rebuild
+  until ``late_initcall`` completes; kernel command line values are stored
+  and take effect once PCI subsystem is initialized.
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..138d4bff7f2c 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5331,6 +5331,22 @@ Kernel parameters
 		nomsi	Do not use MSI for native PCIe PME signaling (this makes
 			all PCIe root ports use INTx for all services).
 
+	pci_crash.capture=
+			[PCI] When to capture PCI config space at panic time.
+			always (default): capture on every panic.
+			aer: capture only if root port AER reports
+				uncorrectable errors.
+			Requires CONFIG_PCI_CRASH=y.
+
+	pci_crash.devices=
+			[PCI] Which devices to include in crash capture.
+			all (default): every PCI device.
+			bridges: PCI bridges only.
+			root_ports: PCIe root ports only.
+			XXYY: hex class code (up to 8).
+			Bridges always implicitly included.
+			Requires CONFIG_PCI_CRASH=y.
+
 	pcmv=		[HW,PCMCIA] BadgePAD 4
 
 	pd_ignore_unused
diff --git a/MAINTAINERS b/MAINTAINERS
index b2040011a386..f7d2fc1fb066 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20304,6 +20304,14 @@ S:	Maintained
 F:	drivers/leds/leds-pca9532.c
 F:	include/linux/leds-pca9532.h
 
+PCI CRASH BUFFER
+M:	Johannes Hange <hangej at amazon.com>
+L:	linux-pci at vger.kernel.org
+S:	Maintained
+F:	Documentation/PCI/pci-crash-capture.rst
+F:	include/linux/pci_crash.h
+F:	kernel/pci_crash.c
+
 PCI DRIVER FOR AARDVARK (Marvell Armada 3700)
 M:	Thomas Petazzoni <thomas.petazzoni at bootlin.com>
 M:	Pali Roh?r <pali at kernel.org>
diff --git a/include/linux/pci_crash.h b/include/linux/pci_crash.h
new file mode 100644
index 000000000000..59a46ee26183
--- /dev/null
+++ b/include/linux/pci_crash.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * PCI Crash Buffer - Capture PCI config space at panic time
+ *
+ * This module captures PCI configuration space data (including AER
+ * extended capability registers) for all PCI devices at panic time.
+ * The data is stored in a buffer whose pages are captured in the
+ * vmcore for off-site analysis.
+ *
+ * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
+ */
+#ifndef _LINUX_PCI_CRASH_H
+#define _LINUX_PCI_CRASH_H
+
+#include <linux/types.h>
+
+#define PCI_CRASH_MAGIC         0x50434943  /* "PCIC" in ASCII */
+#define PCI_CRASH_VERSION       1
+
+/**
+ * struct pci_crash_buffer_header - Header for PCI crash buffer
+ * @magic:        Magic number (PCI_CRASH_MAGIC)
+ * @version:      Format version (PCI_CRASH_VERSION)
+ * @device_count: Number of device records following this header
+ * @config_size:  0 -- indicates variable-length records. Each device
+ *                record stores its own config_size (pdev->cfg_size:
+ *                256 for legacy PCI, 4096 for PCIe). Parsers walk
+ *                records sequentially using per-record config_size.
+ * @timestamp:    Capture timestamp from ktime_get_real_fast_ns()
+ * @flags:        Reserved for future use (0 for now)
+ * @reserved:     Padding to align to 32 bytes
+ *
+ * Total size: 32 bytes
+ */
+struct pci_crash_buffer_header {
+	__le32 magic;
+	__le32 version;
+	__le32 device_count;
+	__le32 config_size;
+	__le64 timestamp;
+	__le32 flags;
+	__le32 reserved;
+} __packed;
+
+/**
+ * struct pci_crash_device_record - Per-device record in crash buffer
+ * @domain:      PCI domain number
+ * @bus:         PCI bus number
+ * @devfn:       Device and function number (PCI_DEVFN format)
+ * @config_size: Config space size for this device (pdev->cfg_size:
+ *               256 for legacy PCI, 4096 for PCIe)
+ * @config_data: Raw PCI config space (config_size bytes)
+ *
+ * Records are variable-length: total size per record is
+ * PCI_CRASH_RECORD_META + config_size bytes.
+ */
+struct pci_crash_device_record {
+	__le16 domain;
+	__u8   bus;
+	__u8   devfn;
+	__le32 config_size;
+	__u8   config_data[];
+} __packed;
+
+#define PCI_CRASH_HEADER_SIZE  sizeof(struct pci_crash_buffer_header)
+#define PCI_CRASH_RECORD_META  sizeof(struct pci_crash_device_record)
+
+/**
+ * struct pci_crash_pagemap - Physical page directory for crash buffer
+ *
+ * The PCI crash buffer may be allocated via vmalloc (for buffers
+ * exceeding ~4 MB where the buddy allocator cannot provide contiguous
+ * pages).  virt_to_phys() returns garbage for vmalloc addresses, so
+ * we maintain this small kmalloc'd directory that maps the buffer's
+ * virtual pages to their actual physical addresses.
+ *
+ * At panic time, crash_core.c exports the pagemap's physical address
+ * via VMCOREINFO.  The parser reads the pagemap, then reads each
+ * physical page from the vmcore to reconstruct the full buffer.
+ *
+ * The pagemap itself is always kmalloc'd (direct-mapped), so
+ * virt_to_phys() works correctly on it.
+ *
+ * @magic:     0x5043504d ("PCPM") -- validates this is a pagemap
+ * @num_pages: Number of entries in the addrs[] array
+ * @buf_size:  Exact buffer size in bytes (last page may be partial)
+ * @addrs:     Physical address of each PAGE_SIZE page backing the buffer
+ */
+struct pci_crash_pagemap {
+	__le32 magic;
+	__le32 num_pages;
+	__le64 buf_size;
+	__le64 addrs[];
+} __packed;
+
+#define PCI_CRASH_PAGEMAP_MAGIC  0x5043504d  /* "PCPM" in ASCII */
+
+#ifdef CONFIG_PCI_CRASH
+void pci_crash_save(void);
+extern void *pci_crash_buffer;
+extern size_t pci_crash_buffer_size;
+extern phys_addr_t pci_crash_pagemap_phys;
+#else
+static inline void pci_crash_save(void) {}
+#define pci_crash_buffer        ((void *)NULL)
+#define pci_crash_buffer_size   ((size_t)0)
+#define pci_crash_pagemap_phys  ((phys_addr_t)0)
+#endif
+
+#endif /* _LINUX_PCI_CRASH_H */
diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
index 15632358bcf7..056767c8ea8d 100644
--- a/kernel/Kconfig.kexec
+++ b/kernel/Kconfig.kexec
@@ -179,4 +179,20 @@ config CRASH_MAX_MEMORY_RANGES
 	  the computation behind the value provided through the
 	  /sys/kernel/crash_elfcorehdr_size attribute.
 
+
+config PCI_CRASH
+	bool "Capture PCI config space at panic time"
+	depends on VMCORE_INFO && PCI && (ARM64 || X86)
+	help
+	  Capture PCI configuration space (including AER extended capability
+	  registers) for all PCI devices at panic time.  The data is stored
+	  in a buffer whose pages are recorded in VMCOREINFO for off-site
+	  crash analysis.
+
+	  This is useful for diagnosing PCI errors that caused or contributed
+	  to the crash, especially when AER registers are volatile and cleared
+	  by device reset during kexec.
+
+	  If unsure, say Y.
+
 endmenu
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dc..6629755bc5a3 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_KEXEC_CORE) += kexec_core.o
 obj-$(CONFIG_CRASH_DUMP) += crash_core.o
 obj-$(CONFIG_CRASH_DM_CRYPT) += crash_dump_dm_crypt.o
 obj-$(CONFIG_CRASH_DUMP_KUNIT_TEST) += crash_core_test.o
+obj-$(CONFIG_PCI_CRASH) += pci_crash.o
 obj-$(CONFIG_KEXEC) += kexec.o
 obj-$(CONFIG_KEXEC_FILE) += kexec_file.o
 obj-$(CONFIG_KEXEC_ELF) += kexec_elf.o
diff --git a/kernel/pci_crash.c b/kernel/pci_crash.c
new file mode 100644
index 000000000000..7c1311432f51
--- /dev/null
+++ b/kernel/pci_crash.c
@@ -0,0 +1,750 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCI Crash Buffer - Capture PCI config space for crash analysis
+ *
+ * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
+ *
+ * Captures PCI configuration space at crash time so AER error
+ * registers reflect the crash-time state for off-site analysis.
+ *
+ * Design:
+ * - Init (late_initcall): enumerate devices, allocate buffer.
+ * - Hotplug: bus notifier queues deferred rebuild of device list
+ *   and buffer via workqueue -- no PCI reads.
+ * - Crash: crash_save_vmcoreinfo() calls pci_crash_save() which
+ *   reads config space into buffer, flushes dcache to RAM so
+ *   data survives kexec into crash kernel.
+ *
+ * Records are variable-length: each device's record is exactly
+ * 8 + pdev->cfg_size bytes (264 for legacy PCI, 4104 for PCIe).
+ * The parser walks records sequentially using per-record config_size.
+ *
+ * Buffer pages may be physically scattered (kvmalloc falls back to
+ * vmalloc for buffers exceeding ~4 MB).  A small kmalloc'd pagemap
+ * records each page's physical address so the crash parser can
+ * reconstruct the buffer without page-table walking.
+ *
+ * pci_read_config_dword() takes pci_lock (raw spinlock) -- acceptable
+ * in crash context since other CPUs are halted and the panicking CPU
+ * is unlikely to have held pci_lock at the time of the crash.
+ * for_each_pci_dev() needs pci_bus_sem -- only used at init/hotplug.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/crash_dump.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_crash.h>
+#include <linux/slab.h>
+#include <linux/timekeeping.h>
+#include <linux/workqueue.h>
+
+#include <linux/cacheflush.h>
+#include <linux/unaligned.h>
+
+/**
+ * pci_crash_flush_dcache() - Flush a memory region from CPU cache to RAM
+ * @addr: virtual address of region to flush
+ * @size: size in bytes
+ *
+ * Used at crash time to ensure the crash kernel sees our buffer/pagemap
+ * writes after kexec.
+ */
+static inline void pci_crash_flush_dcache(void *addr, size_t size)
+{
+	/* Only ARM64 and x86 implemented; Kconfig enforces depends on (ARM64 || X86). */
+#ifdef CONFIG_ARM64
+	unsigned long start = (unsigned long)addr;
+	unsigned long end = start + size;
+
+	dcache_clean_inval_poc(start, end);
+#elif defined(CONFIG_X86)
+	clflush_cache_range(addr, size);
+#endif
+}
+
+/* Virtual address of PCI crash capture buffer.  May be vmalloc'd. */
+void *pci_crash_buffer;
+EXPORT_SYMBOL_GPL(pci_crash_buffer);
+
+/* Size of pci_crash_buffer in bytes */
+size_t pci_crash_buffer_size;
+EXPORT_SYMBOL_GPL(pci_crash_buffer_size);
+
+/* Physical address of the kmalloc'd pagemap (page directory for buffer) */
+phys_addr_t pci_crash_pagemap_phys;
+EXPORT_SYMBOL_GPL(pci_crash_pagemap_phys);
+
+static struct pci_crash_pagemap *pci_crash_pagemap;
+static size_t pci_crash_pagemap_size;
+static struct pci_dev **pci_crash_devs;
+static unsigned int pci_crash_num_devs;
+static DEFINE_MUTEX(pci_crash_lock);
+
+/*
+ * Set in pci_crash_init() after delayed_work, PCI bus and notifier are
+ * ready.  Guards parse + rebuild in param setters: at boot (level -1)
+ * the setter just stores the string; pci_crash_init() parses and does
+ * the initial rebuild once PCI is up.
+ */
+static bool pci_crash_ready;
+
+/*
+ * capture -- when to capture PCI config space.
+ * Comma-separated tokens:
+ *   aer    -- root port ROOT_STATUS has uncorrectable errors (default)
+ *   always -- every panic regardless of PCI error state
+ *
+ * Writable at runtime (0644) so operators and tests can toggle without
+ * reboot.  Writes re-parse capture_flags immediately.
+ */
+static char capture[32] = "always";
+
+#define PCI_CRASH_CAPTURE_AER		BIT(0)
+#define PCI_CRASH_CAPTURE_ALWAYS	BIT(1)
+static unsigned long capture_flags = PCI_CRASH_CAPTURE_ALWAYS;
+
+static void pci_crash_parse_capture(void);
+
+static int capture_param_set(const char *val, const struct kernel_param *kp)
+{
+	if (strlen(val) >= sizeof(capture))
+		return -EINVAL;
+	strscpy(capture, val, sizeof(capture));
+	strim(capture);
+	if (READ_ONCE(pci_crash_ready))
+		pci_crash_parse_capture();
+	return 0;
+}
+
+static int capture_param_get(char *buf, const struct kernel_param *kp)
+{
+	return scnprintf(buf, PAGE_SIZE, "%s\n", capture);
+}
+
+static const struct kernel_param_ops capture_param_ops = {
+	.set = capture_param_set,
+	.get = capture_param_get,
+};
+module_param_cb(capture, &capture_param_ops, NULL, 0644);
+MODULE_PARM_DESC(capture, "When to capture: aer, always (default: always)");
+
+/*
+ * devices -- which devices to capture.
+ * Comma-separated tokens:
+ *   all        -- every PCI device (default)
+ *   bridges    -- PCI bridges (class 0604, 0607)
+ *   root_ports -- PCIe root ports only
+ *   XXYY       -- hex PCI class code (class + subclass)
+ *
+ * Bridges are always implicitly included regardless of filter value
+ * because they hold the AER registers needed for root cause analysis.
+ * Applies at rebuild time only -- zero cost at crash time.  Writable
+ * at runtime (0644); writes re-parse and trigger async rebuild.
+ */
+static char devices[256] = "all";
+
+static void pci_crash_parse_devices(void);
+static struct delayed_work pci_crash_rebuild_dwork;
+
+/* Debounce period for bus notifications (ms).
+ * TRN2 liveupdate enumerates ~3000 VFs in ~1.5s -- this coalesces
+ * the storm into a single rebuild after the last event.
+ */
+#define PCI_CRASH_REBUILD_DELAY_MS	200
+
+static int devices_param_set(const char *val, const struct kernel_param *kp)
+{
+	if (strlen(val) >= sizeof(devices))
+		return -EINVAL;
+
+	mutex_lock(&pci_crash_lock);
+	strscpy(devices, val, sizeof(devices));
+	strim(devices);
+	if (READ_ONCE(pci_crash_ready)) {
+		pci_crash_parse_devices();
+		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
+				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
+	}
+	mutex_unlock(&pci_crash_lock);
+	return 0;
+}
+
+static int devices_param_get(char *buf, const struct kernel_param *kp)
+{
+	return scnprintf(buf, PAGE_SIZE, "%s\n", devices);
+}
+
+static const struct kernel_param_ops devices_param_ops = {
+	.set = devices_param_set,
+	.get = devices_param_get,
+};
+module_param_cb(devices, &devices_param_ops, NULL, 0644);
+MODULE_PARM_DESC(devices,
+	"Which devices: all, bridges, root_ports, XXYY hex class (default: all)");
+
+#define PCI_CRASH_DEVICES_ALL		BIT(0)
+#define PCI_CRASH_DEVICES_BRIDGES	BIT(1)
+#define PCI_CRASH_DEVICES_ROOT_PORTS	BIT(2)
+#define PCI_CRASH_MAX_DEVICE_CLASSES	8
+static unsigned long devices_flags = PCI_CRASH_DEVICES_ALL;
+static u16 device_classes[PCI_CRASH_MAX_DEVICE_CLASSES];
+static unsigned int device_class_count;
+
+static void pci_crash_parse_capture(void)
+{
+	char *buf, *token, *rest;
+	unsigned long flags = 0;
+
+	if (!*capture) {
+		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_ALWAYS);
+		return;
+	}
+
+	buf = kstrdup(capture, GFP_KERNEL);
+	if (!buf) {
+		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_ALWAYS);
+		return;
+	}
+
+	rest = buf;
+	while ((token = strsep(&rest, ",")) != NULL) {
+		if (strcmp(token, "aer") == 0)
+			flags |= PCI_CRASH_CAPTURE_AER;
+		else if (strcmp(token, "always") == 0)
+			flags |= PCI_CRASH_CAPTURE_ALWAYS;
+		else
+			pr_warn("unknown capture token: %s\n",
+				token);
+	}
+	kfree(buf);
+
+	if (!flags) {
+		pr_warn("no valid capture tokens, defaulting to always\n");
+		flags = PCI_CRASH_CAPTURE_ALWAYS;
+	}
+	WRITE_ONCE(capture_flags, flags);
+}
+
+static void pci_crash_parse_devices(void)
+{
+	char *buf, *token, *rest;
+	unsigned long val;
+
+	devices_flags = 0;
+	device_class_count = 0;
+
+	if (!*devices) {
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+		return;
+	}
+
+	buf = kstrdup(devices, GFP_KERNEL);
+	if (!buf) {
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+		return;
+	}
+
+	rest = buf;
+	while ((token = strsep(&rest, ",")) != NULL) {
+		if (strcmp(token, "all") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_ALL;
+		} else if (strcmp(token, "bridges") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_BRIDGES;
+		} else if (strcmp(token, "root_ports") == 0) {
+			devices_flags |= PCI_CRASH_DEVICES_ROOT_PORTS;
+		} else if (kstrtoul(token, 16, &val) == 0 && val <= 0xFFFF) {
+			if (device_class_count < PCI_CRASH_MAX_DEVICE_CLASSES)
+				device_classes[device_class_count++] = (u16)val;
+			else
+				pr_warn("too many device classes (max %d)\n",
+					PCI_CRASH_MAX_DEVICE_CLASSES);
+		} else {
+			pr_warn("unknown devices token: %s\n",
+				token);
+		}
+	}
+	kfree(buf);
+
+	if (!devices_flags && device_class_count == 0) {
+		pr_warn("no valid devices tokens, defaulting to all\n");
+		devices_flags = PCI_CRASH_DEVICES_ALL;
+	}
+}
+
+static bool pci_crash_device_matches(struct pci_dev *pdev)
+{
+	unsigned int i;
+	u16 dev_class = pdev->class >> 8;
+
+	if (devices_flags & PCI_CRASH_DEVICES_ALL)
+		return true;
+
+	/* Bridges always included -- they hold AER registers */
+	if (dev_class == PCI_CLASS_BRIDGE_PCI ||
+	    dev_class == PCI_CLASS_BRIDGE_CARDBUS)
+		return true;
+
+	if ((devices_flags & PCI_CRASH_DEVICES_ROOT_PORTS) &&
+	    pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT)
+		return true;
+
+	for (i = 0; i < device_class_count; i++) {
+		if (dev_class == device_classes[i])
+			return true;
+	}
+
+	return false;
+}
+
+/* Sanity limit -- prevents multi-GB allocations on systems with many VFs */
+#define PCI_CRASH_MAX_BUFFER_SIZE	(24 * 1024 * 1024)
+
+/**
+ * pci_crash_build_pagemap() - Build physical page directory for buffer
+ * @buf: buffer allocated via kvmalloc (may be vmalloc'd)
+ * @buf_size: buffer size in bytes
+ *
+ * Allocates a kmalloc'd directory containing the physical address of
+ * each page backing @buf.  The pagemap is always direct-mapped, so
+ * virt_to_phys() works on it at crash time.
+ *
+ * Returns the new pagemap, or NULL on allocation failure.
+ */
+static struct pci_crash_pagemap *pci_crash_build_pagemap(void *buf,
+							 size_t buf_size)
+{
+	unsigned int num_pages = DIV_ROUND_UP(buf_size, PAGE_SIZE);
+	struct pci_crash_pagemap *pm;
+	unsigned int i;
+
+	pm = kmalloc(struct_size(pm, addrs, num_pages), GFP_KERNEL);
+	if (!pm)
+		return NULL;
+
+	pm->magic = cpu_to_le32(PCI_CRASH_PAGEMAP_MAGIC);
+	pm->num_pages = cpu_to_le32(num_pages);
+	pm->buf_size = cpu_to_le64(buf_size);
+
+	for (i = 0; i < num_pages; i++) {
+		struct page *page;
+		phys_addr_t pa;
+
+		if (is_vmalloc_addr(buf + i * PAGE_SIZE))
+			page = vmalloc_to_page(buf + i * PAGE_SIZE);
+		else
+			page = virt_to_page(buf + i * PAGE_SIZE);
+
+		if (!page) {
+			kfree(pm);
+			return NULL;
+		}
+		pa = page_to_phys(page);
+		pm->addrs[i] = cpu_to_le64(pa);
+	}
+
+	return pm;
+}
+
+/**
+ * pci_crash_read_config_space() - Read config space for one device
+ * @pdev: PCI device to read
+ * @ptr: destination pointer within the crash buffer
+ *
+ * Reads pdev->cfg_size bytes of config space (256 for legacy PCI,
+ * 4096 for PCIe). Each 4-byte dword is read individually via MMIO.
+ * Failed reads write 0xFFFFFFFF -- standard PCI convention for
+ * absent/unreachable devices.
+ */
+static void pci_crash_read_config_space(struct pci_dev *pdev, u8 *ptr)
+{
+	struct pci_crash_device_record *record =
+		(struct pci_crash_device_record *)ptr;
+	u8 *cfg_data = ptr + PCI_CRASH_RECORD_META;
+	int offset;
+	u32 val;
+
+	record->domain = cpu_to_le16(pci_domain_nr(pdev->bus));
+	record->bus = pdev->bus->number;
+	record->devfn = pdev->devfn;
+	record->config_size = cpu_to_le32(pdev->cfg_size);
+
+	for (offset = 0; offset < pdev->cfg_size; offset += 4) {
+		if (pci_read_config_dword(pdev, offset, &val)) {
+			put_unaligned_le32(0xFFFFFFFF, &cfg_data[offset]);
+			continue;
+		}
+		put_unaligned_le32(val, &cfg_data[offset]);
+	}
+}
+
+/**
+ * pci_crash_fill_buffer() - Populate buffer with config space
+ * @buffer: destination buffer (header + variable-length records)
+ * @num_devs: number of devices to read
+ *
+ * Records are variable-length: each is PCI_CRASH_RECORD_META +
+ * pdev->cfg_size bytes. The header's config_size is 0 to indicate
+ * variable-length; parsers walk records using per-record config_size.
+ *
+ * Uses ktime_get_real_fast_ns() for the timestamp -- safe in NMI/panic
+ * context (lockless, reads the NMI-safe timekeeper snapshot).
+ */
+static void pci_crash_fill_buffer(void *buffer, unsigned int num_devs)
+{
+	struct pci_crash_buffer_header *header = buffer;
+	struct pci_dev **devs = READ_ONCE(pci_crash_devs);
+	u8 *ptr;
+	unsigned int i;
+
+	header->magic = cpu_to_le32(PCI_CRASH_MAGIC);
+	header->version = cpu_to_le32(PCI_CRASH_VERSION);
+	header->device_count = cpu_to_le32(num_devs);
+	header->config_size = 0;
+	header->timestamp = cpu_to_le64(ktime_get_real_fast_ns());
+	header->flags = 0;
+	header->reserved = 0;
+
+	ptr = (u8 *)buffer + PCI_CRASH_HEADER_SIZE;
+	for (i = 0; i < num_devs; i++) {
+		struct pci_dev *pdev = devs[i];
+
+		if (unlikely(!pdev))
+			break;
+		pci_crash_read_config_space(pdev, ptr);
+		ptr += PCI_CRASH_RECORD_META + pdev->cfg_size;
+	}
+
+	header->device_count = cpu_to_le32(i);
+}
+
+/**
+ * pci_crash_rebuild_snapshot() - Rebuild device list and allocate buffer
+ *
+ * Two-pass approach:
+ *   Pass 1: count PCI devices
+ *   Pass 2: populate device array (filtered) and compute exact buffer
+ *            size from actual pdev->cfg_size per device (no padding)
+ *
+ * The devices param controls which devices are included.  Bridges are
+ * always included regardless of devices setting (they hold AER registers).
+ * devices=all (default) includes everything.
+ *
+ * Does NOT read PCI config space -- reads happen only at crash time.
+ * This keeps rebuild fast during VF enumeration storms (~6000 ADD
+ * events on TRN2 during liveupdate).
+ *
+ * After allocation, builds the pagemap so the crash parser can
+ * locate the buffer's physical pages in the vmcore.
+ *
+ * Caller must hold pci_crash_lock.
+ */
+static void pci_crash_rebuild_snapshot(void)
+{
+	struct pci_dev *pdev = NULL;
+	unsigned int count = 0, i;
+	void *old_buf;
+	unsigned int old_num_devs = pci_crash_num_devs;
+	struct pci_dev **old_devs = pci_crash_devs;
+	struct pci_crash_pagemap *old_pm = pci_crash_pagemap;
+	struct pci_crash_pagemap *new_pm;
+	struct pci_dev **new_devs;
+	void *new_buf;
+	size_t total_size;
+
+	/* Pass 1: count devices */
+	for_each_pci_dev(pdev)
+		count++;
+
+	if (count == 0) {
+		pr_info("no PCI devices found\n");
+		WRITE_ONCE(pci_crash_num_devs, 0);
+		if (old_devs) {
+			for (i = 0; i < old_num_devs; i++)
+				pci_dev_put(old_devs[i]);
+			kvfree(old_devs);
+			WRITE_ONCE(pci_crash_devs, NULL);
+		}
+		kfree(old_pm);
+		WRITE_ONCE(pci_crash_pagemap, NULL);
+		WRITE_ONCE(pci_crash_pagemap_phys, 0);
+		WRITE_ONCE(pci_crash_pagemap_size, 0);
+		old_buf = pci_crash_buffer;
+		WRITE_ONCE(pci_crash_buffer, NULL);
+		WRITE_ONCE(pci_crash_buffer_size, 0);
+		kvfree(old_buf);
+		return;
+	}
+
+	/*
+	 * Disable capture during rebuild to prevent pci_crash_save()
+	 * from accessing stale device references or partially populated
+	 * arrays.
+	 */
+	WRITE_ONCE(pci_crash_num_devs, 0);
+
+	new_devs = kvmalloc_array(count, sizeof(struct pci_dev *),
+				  GFP_KERNEL | __GFP_ZERO);
+	if (!new_devs)
+		goto err_restore;
+
+	/*
+	 * Pass 2: populate device array (filtered) and compute exact
+	 * buffer size from actual pdev->cfg_size per device.
+	 * count from pass 1 is an upper bound; actual may be smaller.
+	 */
+	total_size = PCI_CRASH_HEADER_SIZE;
+	pdev = NULL;
+	i = 0;
+	for_each_pci_dev(pdev) {
+		if (i >= count) {
+			pci_dev_put(pdev);
+			break;
+		}
+		if (!pci_crash_device_matches(pdev))
+			continue;
+		new_devs[i] = pci_dev_get(pdev);
+		total_size += PCI_CRASH_RECORD_META + pdev->cfg_size;
+		i++;
+	}
+	count = i;
+
+	if (count == 0) {
+		kvfree(new_devs);
+		pr_info("no devices match devices=%s\n", devices);
+		goto err_restore;
+	}
+
+	if (total_size > PCI_CRASH_MAX_BUFFER_SIZE) {
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		pr_warn("buffer too large (%zu > %d bytes)\n",
+			total_size, PCI_CRASH_MAX_BUFFER_SIZE);
+		goto err_restore;
+	}
+
+	new_buf = kvmalloc(total_size, GFP_KERNEL | __GFP_ZERO);
+	if (!new_buf) {
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		goto err_restore;
+	}
+
+	new_pm = pci_crash_build_pagemap(new_buf, total_size);
+	if (!new_pm) {
+		kvfree(new_buf);
+		for (i = 0; i < count; i++)
+			pci_dev_put(new_devs[i]);
+		kvfree(new_devs);
+		goto err_restore;
+	}
+
+	/* Release old device references */
+	for (i = 0; i < old_num_devs; i++)
+		if (old_devs && old_devs[i])
+			pci_dev_put(old_devs[i]);
+
+	WRITE_ONCE(pci_crash_devs, new_devs);
+	kvfree(old_devs);
+
+	/*
+	 * Swap pagemap first (with pre-computed phys addr), then buffer,
+	 * then size and count.  If crash fires mid-swap, num_devs is
+	 * still 0 from above, so pci_crash_save() bails out safely.
+	 */
+	WRITE_ONCE(pci_crash_pagemap, new_pm);
+	WRITE_ONCE(pci_crash_pagemap_phys, virt_to_phys(new_pm));
+	WRITE_ONCE(pci_crash_pagemap_size, struct_size(new_pm, addrs,
+			le32_to_cpu(new_pm->num_pages)));
+	kfree(old_pm);
+
+	old_buf = pci_crash_buffer;
+	WRITE_ONCE(pci_crash_buffer, new_buf);
+	WRITE_ONCE(pci_crash_buffer_size, total_size);
+	/* Ensure buffer/pagemap/size are visible before num_devs enables capture */
+	smp_wmb();
+	WRITE_ONCE(pci_crash_num_devs, count);
+	kvfree(old_buf);
+
+	pr_info("rebuild: %u devices (%zu bytes, %u pages)\n",
+		count, total_size, le32_to_cpu(new_pm->num_pages));
+	return;
+
+err_restore:
+	WRITE_ONCE(pci_crash_num_devs, old_num_devs);
+}
+
+/**
+ * pci_crash_save() - Capture PCI config space at crash time
+ *
+ * Called from crash_save_vmcoreinfo() inside __crash_kexec(), which
+ * runs before machine_kexec() boots the crash kernel.  This is the
+ * only reliable capture point -- panic notifiers run AFTER kexec by
+ * default (crash_kexec_post_notifiers=0).
+ *
+ * Capture check (capture param):
+ *   always  -- capture unconditionally
+ *   aer     -- quick-scan root port AER ROOT_STATUS for uncorrectable
+ *             errors; skip if none found
+ *
+ * When capture=always, captures on every panic.
+ * This is useful for cascading failures: a PCI link-down can cause
+ * an MCE or NMI watchdog timeout before DPC/AER fires, so the crash
+ * reason is UNKNOWN but AER registers may still hold error state.
+ *
+ * Reads config space fresh -- successful reads get current register
+ * state, failed reads (offline devices) write 0xFFFFFFFF.
+ *
+ * Flushes both buffer and pagemap from CPU cache to RAM so data
+ * survives kexec into crash kernel.
+ */
+void pci_crash_save(void)
+{
+	struct pci_crash_pagemap *pm;
+	unsigned long cflags;
+	unsigned int num_devs;
+	size_t pm_size;
+	size_t buf_size;
+	void *buffer;
+
+	num_devs = READ_ONCE(pci_crash_num_devs);
+	if (num_devs == 0)
+		return;
+
+	/* Pairs with smp_wmb() in rebuild -- ensures buffer/pagemap visible */
+	smp_rmb();
+
+	buffer = READ_ONCE(pci_crash_buffer);
+	buf_size = READ_ONCE(pci_crash_buffer_size);
+	pm = READ_ONCE(pci_crash_pagemap);
+	pm_size = READ_ONCE(pci_crash_pagemap_size);
+	if (!buffer || buf_size == 0)
+		return;
+
+	cflags = READ_ONCE(capture_flags);
+	if (!(cflags & PCI_CRASH_CAPTURE_ALWAYS)) {
+#ifdef CONFIG_PCIEAER
+		if (cflags & PCI_CRASH_CAPTURE_AER) {
+			struct pci_dev **devs = READ_ONCE(pci_crash_devs);
+			unsigned int i;
+			bool pci_error_found = false;
+
+			if (!devs)
+				return;
+
+			for (i = 0; i < num_devs; i++) {
+				struct pci_dev *pdev = devs[i];
+				u32 status = 0;
+
+				if (!pdev || !pdev->aer_cap)
+					continue;
+
+				if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) {
+					pci_read_config_dword(pdev,
+							     pdev->aer_cap + PCI_ERR_ROOT_STATUS,
+							     &status);
+					if (status & PCI_ERR_ROOT_UNCOR_RCV) {
+						pci_error_found = true;
+						break;
+					}
+				}
+			}
+
+			if (!pci_error_found) {
+				pr_info("no PCI errors detected, skipping capture\n");
+				return;
+			}
+		} else {
+			return;
+		}
+#else
+		/* Without AER support, cannot detect PCI errors -- skip */
+		return;
+#endif
+	}
+
+	pci_crash_fill_buffer(buffer, num_devs);
+
+	/*
+	 * Flush buffer and pagemap from CPU cache to RAM so the
+	 * crash kernel sees our writes after kexec.
+	 */
+	pci_crash_flush_dcache(buffer, buf_size);
+
+	if (pm && pm_size > 0)
+		pci_crash_flush_dcache(pm, pm_size);
+
+	pr_info("CAPTURE: %u devices, %zu bytes\n",
+		 num_devs, buf_size);
+}
+EXPORT_SYMBOL_GPL(pci_crash_save);
+
+static void pci_crash_rebuild_worker(struct work_struct *work)
+{
+	mutex_lock(&pci_crash_lock);
+	pci_crash_rebuild_snapshot();
+	mutex_unlock(&pci_crash_lock);
+}
+
+static int pci_crash_bus_notifier(struct notifier_block *nb,
+				  unsigned long action, void *data)
+{
+	if (action == BUS_NOTIFY_ADD_DEVICE ||
+	    action == BUS_NOTIFY_DEL_DEVICE)
+		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
+				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block pci_crash_bus_nb = {
+	.notifier_call = pci_crash_bus_notifier,
+};
+
+static int __init pci_crash_init(void)
+{
+	/* Nothing to do in crash kernel -- the buffer from the first kernel
+	 * is already in RAM (flushed before kexec) and the parser finds it
+	 * via the pagemap in VMCOREINFO.
+	 */
+	if (is_kdump_kernel())
+		return 0;
+
+	INIT_DELAYED_WORK(&pci_crash_rebuild_dwork, pci_crash_rebuild_worker);
+
+	pci_crash_parse_capture();
+	pci_crash_parse_devices();
+
+	mutex_lock(&pci_crash_lock);
+	pci_crash_rebuild_snapshot();
+	mutex_unlock(&pci_crash_lock);
+
+	bus_register_notifier(&pci_bus_type, &pci_crash_bus_nb);
+
+	WRITE_ONCE(pci_crash_ready, true);
+
+#ifndef CONFIG_PCIEAER
+	if ((capture_flags & PCI_CRASH_CAPTURE_AER) &&
+	    !(capture_flags & PCI_CRASH_CAPTURE_ALWAYS))
+		pr_warn("capture=aer but CONFIG_PCIEAER=n; capture will not trigger unless set to 'always'\n");
+#endif
+
+	pr_info("ready: %u devices (%zu bytes), capture=%s devices=%s\n",
+		pci_crash_num_devs, pci_crash_buffer_size,
+		capture, devices);
+
+	return 0;
+}
+late_initcall(pci_crash_init);
+
+/* Built-in only: crash infrastructure must outlive all drivers. */
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Capture PCI config space at panic time for crash analysis");
+MODULE_AUTHOR("Amazon.com, Inc.");
diff --git a/kernel/vmcore_info.c b/kernel/vmcore_info.c
index 8614430ca212..8813f7e2e516 100644
--- a/kernel/vmcore_info.c
+++ b/kernel/vmcore_info.c
@@ -14,6 +14,7 @@
 #include <linux/cpuhotplug.h>
 #include <linux/memblock.h>
 #include <linux/kmemleak.h>
+#include <linux/pci_crash.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -91,6 +92,18 @@ void crash_save_vmcoreinfo(void)
 		vmcoreinfo_data = vmcoreinfo_data_safecopy;
 
 	vmcoreinfo_append_str("CRASHTIME=%lld\n", ktime_get_real_seconds());
+
+	/* Capture PCI config space before kexec into crash kernel */
+	pci_crash_save();
+	if (pci_crash_pagemap_phys && pci_crash_buffer_size > 0) {
+		vmcoreinfo_append_str("PCI_CRASH_PAGEMAP=0x%llx\n",
+				      (unsigned long long)pci_crash_pagemap_phys);
+		vmcoreinfo_append_str("PCI_CRASH_VERSION=%d\n",
+				      PCI_CRASH_VERSION);
+		vmcoreinfo_append_str("PCI_CRASH_BUF_SZ=%zu\n",
+				      pci_crash_buffer_size);
+	}
+
 	update_vmcoreinfo_note();
 }
 

base-commit: 50897c955902c93ae71c38698abb910525ebdc89
-- 
2.47.3




Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597

From lkp at intel.com  Thu May 21 17:33:24 2026
From: lkp at intel.com (kernel test robot)
Date: Fri, 22 May 2026 08:33:24 +0800
Subject: [linux-next:master] BUILD REGRESSION
 550604d6c9b9efc8d068aff94dc301694a7afdee
Message-ID: <202605220843.g8Ikcqqz-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
branch HEAD: 550604d6c9b9efc8d068aff94dc301694a7afdee  Add linux-next specific files for 20260521

Error/Warning (recently discovered and may have been fixed):

    https://lore.kernel.org/oe-kbuild-all/202605212104.SAIMqegX-lkp at intel.com
    https://lore.kernel.org/oe-kbuild-all/202605220247.dQAHslyv-lkp at intel.com
    https://lore.kernel.org/oe-kbuild-all/202605220312.Pu7UO05u-lkp at intel.com
    https://lore.kernel.org/oe-kbuild-all/202605220631.ugDr2VPb-lkp at intel.com

    /usr/bin/ld: sound/soc/codecs/es9356.o:(.rodata+0x2c30): undefined reference to `sdca_asoc_q78_get_volsw'
    /usr/bin/ld: sound/soc/codecs/es9356.o:(.rodata+0x2c38): undefined reference to `sdca_asoc_q78_put_volsw'
    arch/hexagon/kernel/syscalltab.c:19:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/pci_64.c:226:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/rtas.c:1850:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/signal_32.c:990:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/signal_64.c:657:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/sys_ppc32.c:70:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/powerpc/kernel/syscalls.c:52:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/riscv/kernel/sys_hwprobe.c:606:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/riscv/kernel/sys_riscv.c:43:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/sparc/kernel/sys_sparc32.c:54:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/sparc/kernel/sys_sparc_64.c:351:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    arch/x86/um/syscalls_64.c:43:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    block/ioprio.c:65:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_crat.c:1830:26: error: cannot take the address of an rvalue of type 'int'
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_crat.c:1830:27: error: call to undeclared function 'cpu_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_crat.c:1833:23: error: use of undeclared identifier 'X86_VENDOR_AMD'
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_crat.c:1833:7: error: incomplete definition of type 'struct cpuinfo_x86'
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_topology.c:2353:41: error: member reference base type 'int' is not a structure or union
    drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_topology.c:2353:9: error: call to undeclared function 'cpu_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    drivers/gpu/drm/amd/amdgpu/jpeg_v2_5.c:152:68: warning: '%d' directive output may be truncated writing between 1 and 11 bytes into a region of size 7 [-Wformat-truncation=]
    drivers/gpu/drm/scheduler/tests/tests_scheduler.c:675:10: error: initializer element is not a compile-time constant
    drivers/pinctrl/pinctrl-generic.c:130:5: error: redefinition of 'pinctrl_generic_pins_function_dt_node_to_map'
    drivers/pinctrl/pinctrl-generic.c:20:5: error: conflicting types for 'pinctrl_generic_to_map'; have 'int(struct pinctrl_dev *, struct device_node *, struct device_node *, struct pinctrl_map **, unsigned int *, unsigned int *, const char **, unsigned int,  const char **, unsigned int *, unsigned int)'
    fs/aio.c:1436:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/d_path.c:413:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/eventfd.c:414:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/eventpoll.c:2200:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/eventpoll.c:2483:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/exec.c:1924:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/exec.c:1925:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/fcntl.c:587:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/fhandle.c:129:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/file.c:818:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/file.c:819:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/file_attr.c:374:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/fsopen.c:120:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/ioctl.c:583:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/locks.c:2214:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/locks.c:2280:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/namei.c:5186:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/namei.c:5197:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/namespace.c:2068:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/namespace.c:2073:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/notify/inotify/inotify_user.c:720:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/open.c:152:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/pipe.c:1054:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/pipe.c:1055:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/read_write.c:412:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/readdir.c:215:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/readdir.c:304:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/select.c:722:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/select.c:733:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/signalfd.c:299:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/splice.c:1578:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/stat.c:420:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/stat.c:505:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/statfs.c:191:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/sync.c:148:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/timerfd.c:394:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/timerfd.c:424:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/utimes.c:142:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/xattr.c:732:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    fs/xattr.c:735:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    ipc/msg.c:315:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    ipc/sem.c:624:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    ipc/shm.c:847:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    ipc/shm.c:849:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/capability.c:137:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/events/core.c:13844:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/events/core.c:13853:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/exec_domain.c:38:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/exit.c:1082:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/exit.c:1112:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/fork.c:1785:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/fork.c:1808:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/futex/syscalls.c:28:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/groups.c:161:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/kcmp.c:135:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/kexec.c:242:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/module/main.c:804:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/nsproxy.c:569:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/nstree.c:763:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/pid.c:695:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/printk/printk.c:1853:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/ptrace.c:1388:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/ptrace.c:1415:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/reboot.c:728:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/rseq.c:547:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/sched/membarrier.c:634:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/sched/membarrier.c:636:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/sched/syscalls.c:132:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/seccomp.c:2126:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/signal.c:3319:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/sys.c:259:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/hrtimer.c:2381:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/hrtimer.c:2466:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/hrtimer.c:2487:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/itimer.c:113:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/posix-stubs.c:26:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/posix-timers.c:566:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/posix-timers.c:574:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/time.c:105:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/time.c:140:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/time/time.c:62:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    kernel/uid16.c:23:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    ld.lld: error: undefined symbol: sdca_asoc_q78_get_volsw
    ld.lld: error: undefined symbol: sdca_asoc_q78_put_volsw
    mm/fadvise.c:200:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/filemap.c:4713:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/filemap.c:4718:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/madvise.c:2013:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/madvise.c:2029:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/memfd.c:505:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/mincore.c:292:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/mlock.c:665:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/mmap.c:116:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/mprotect.c:985:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/mremap.c:2023:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/msync.c:32:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/oom_kill.c:1195:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/process_vm_access.c:292:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/readahead.c:736:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/readahead.c:760:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/swapfile.c:2903:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    mm/swapfile.c:3124:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    net/socket.c:1818:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    net/socket.c:1833:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    security/keys/compat.c:17:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    security/keys/keyctl.c:74:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    security/landlock/syscalls.c:203:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]
    security/lsm_syscalls.c:57:1: error: unknown warning group '-Wattribute-alias', ignored [-Werror,-Wunknown-warning-option]

Unverified Error/Warning (likely false positive, kindly check if interested):

    Warning: block/blk-map.c:366 Excess function parameter 'op' description in 'bio_copy_kern'
    csky-linux-ld: sound/soc/codecs/es9356.o:(.rodata+0x16c8): undefined reference to `sdca_asoc_q78_get_volsw'
    csky-linux-ld: sound/soc/codecs/es9356.o:(.rodata+0x16cc): undefined reference to `sdca_asoc_q78_put_volsw'
    drivers/android/binder_alloc.c:389:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/auxdisplay/panel.c:1508:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/base/regmap/regmap-debugfs.c:180:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/char/xillybus/xillybus_core.c:425:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/dma/qcom/hidma.c:389:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/gpu/drm/amd/amdgpu/dce_v10_0.c:2853:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/gpu/drm/nouveau/nvkm/subdev/acr/base.c:357:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/gpu/drm/nouveau/nvkm/subdev/iccsense/base.c:295:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/gpu/drm/udl/udl_main.c:260:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/gpu/drm/xe/xe_sched_job.c:162:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/greybus/es2.c:1428:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/hwmon/ibmpex.c:433:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/media/i2c/ds90ub960.c:4790:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/mtd/parsers/redboot.c:304:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/mtd/spi-nor/core.c:1645:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/net/ethernet/mellanox/mlx5/core/cmd.c:1508:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/scsi/aacraid/commsup.c:1928:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/scsi/aacraid/dpcsup.c:216:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/thunderbolt/stream.c:1388:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/tty/serial/max310x.c:1727:24: error: incompatible pointer types passing 'struct i2c_driver *' to parameter of type 'struct spi_driver *' [-Wincompatible-pointer-types]
    drivers/tty/serial/max310x.c:1727:25: error: use of undeclared identifier 'max310x_spi_driver'; did you mean 'max310x_i2c_driver'?
    drivers/tty/serial/max310x.c:1727:32: error: 'max310x_spi_driver' undeclared (first use in this function); did you mean 'max310x_i2c_driver'?
    drivers/tty/serial/max310x.c:1745:25: error: 'max310x_spi_driver' undeclared (first use in this function); did you mean 'max310x_i2c_driver'?
    drivers/tty/serial/max310x.c:1745:32: error: 'max310x_spi_driver' undeclared (first use in this function); did you mean 'max310x_i2c_driver'?
    drivers/usb/host/xhci-mem.c:866:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/usb/misc/usbtest.c:1416:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    drivers/usb/serial/usb_wwan.c:501:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    fs/btrfs/file.c:3288:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    fs/btrfs/inode.c:8975:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    fs/isofs/inode.c:1267:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    fs/nilfs2/recovery.c:397:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    fs/xfs/scrub/bitmap.c:116:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    kernel/bpf/syscall.c:3797:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    kernel/events/core.c:12131:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    kernel/trace/ring_buffer.c:2348:1: internal compiler error: in final_scan_insn_1, at final.cc:2813
    ld.lld: error: call to __compiletime_assert_537 marked "dontcall-error": BUILD_BUG_ON failed: 21 - 1 != HWEIGHT32( (VALID_OPENAT2_FLAGS & ~(O_NONBLOCK | O_NDELAY)) | __FMODE_EXEC)
    lib/raid/raid6/powerpc/altivec1.c:37:16: sparse: sparse: Trying to use reserved word 'signed' as identifier
    lib/raid/raid6/powerpc/altivec1.c:37:16: sparse: sparse: two or more data types in declaration specifiers
    lib/raid/raid6/powerpc/altivec2.c:37:16: sparse: sparse: Trying to use reserved word 'signed' as identifier
    lib/raid/raid6/powerpc/altivec2.c:37:16: sparse: sparse: two or more data types in declaration specifiers
    lib/raid/raid6/powerpc/altivec4.c:37:16: sparse: sparse: Trying to use reserved word 'signed' as identifier
    lib/raid/raid6/powerpc/altivec4.c:37:16: sparse: sparse: two or more data types in declaration specifiers
    lib/raid/raid6/powerpc/altivec8.c:37:16: sparse: sparse: Trying to use reserved word 'signed' as identifier
    lib/raid/raid6/powerpc/altivec8.c:37:16: sparse: sparse: two or more data types in declaration specifiers
    net/bluetooth/mgmt.c:4400:1: internal compiler error: in final_scan_insn_1, at final.cc:2813

Error/Warning ids grouped by kconfigs:

recent_errors
|-- alpha-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- alpha-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- alpha-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- alpha-randconfig-r052-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arc-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arc-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arc-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm-defconfig
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-aio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fhandle.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-locks.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-notify-inotify-inotify_user.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-signalfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-timerfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-msg.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-sem.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-shm.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-capability.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-events-core.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-futex-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-groups.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-kcmp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-kexec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-module-main.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-rseq.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-membarrier.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-seccomp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-itimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-timers.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-uid16.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-fadvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-filemap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-madvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-memfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-swapfile.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- net-socket.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- arm-randconfig-001-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- arm-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm-randconfig-004-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm-randconfig-r062-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-notify-inotify-inotify_user.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-signalfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-msg.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-sem.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-shm.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-capability.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-events-core.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-futex-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-groups.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-module-main.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-rseq.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-membarrier.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-itimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-timers.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-uid16.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-fadvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-filemap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-madvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-memfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- net-socket.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- arm64-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm64-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm64-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm64-randconfig-003-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm64-randconfig-004
|   |-- ld.lld:error:undefined-symbol:sdca_asoc_q78_get_volsw
|   `-- ld.lld:error:undefined-symbol:sdca_asoc_q78_put_volsw
|-- arm64-randconfig-004-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- arm64-randconfig-r072-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- csky-allmodconfig
|   `-- drivers-thunderbolt-stream.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|-- csky-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- csky-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- csky-randconfig-001-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- drivers-base-regmap-regmap-debugfs.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-gpu-drm-nouveau-nvkm-subdev-acr-base.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-gpu-drm-nouveau-nvkm-subdev-iccsense-base.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-gpu-drm-udl-udl_main.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-gpu-drm-xe-xe_sched_job.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-greybus-es2.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-mtd-parsers-redboot.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-scsi-aacraid-commsup.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-scsi-aacraid-dpcsup.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-usb-host-xhci-mem.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-usb-misc-usbtest.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-usb-serial-usb_wwan.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- fs-btrfs-file.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- fs-btrfs-inode.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- fs-isofs-inode.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- fs-nilfs2-recovery.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   `-- fs-xfs-scrub-bitmap.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|-- csky-randconfig-002-20260521
|   |-- csky-linux-ld:sound-soc-codecs-es9356.o:(.rodata):undefined-reference-to-sdca_asoc_q78_get_volsw
|   |-- csky-linux-ld:sound-soc-codecs-es9356.o:(.rodata):undefined-reference-to-sdca_asoc_q78_put_volsw
|   |-- drivers-android-binder_alloc.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-auxdisplay-panel.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-char-xillybus-xillybus_core.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-hwmon-ibmpex.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-media-i2c-ds90ub960.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-mtd-spi-nor-core.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- kernel-events-core.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   `-- kernel-trace-ring_buffer.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|-- csky-randconfig-r062-20260521
|   |-- drivers-dma-qcom-hidma.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-gpu-drm-amd-amdgpu-dce_v10_0.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- drivers-net-ethernet-mellanox-mlx5-core-cmd.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   |-- kernel-bpf-syscall.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|   `-- net-bluetooth-mgmt.c:internal-compiler-error:in-final_scan_insn_1-at-final.cc
|-- hexagon-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- hexagon-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- hexagon-defconfig
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- arch-hexagon-kernel-syscalltab.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- hexagon-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- hexagon-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-003
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-003-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-tty-serial-max31.c:error:use-of-undeclared-identifier-max31_spi_driver
|-- i386-buildonly-randconfig-004
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-005-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-buildonly-randconfig-006-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-003-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-004
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-005
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-006-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-007
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-051-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-052-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-054-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|-- i386-randconfig-062-20260522
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- i386-randconfig-063-20260522
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- loongarch-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- loongarch-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- loongarch-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- loongarch-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- m68k-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- m68k-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- m68k-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- m68k-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- microblaze-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- microblaze-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- microblaze-randconfig-r061-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- mips-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- mips-randconfig-r071-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- kernel-time-posix-stubs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- nios2-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- nios2-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- nios2-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- nios2-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- nios2-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- nios2-randconfig-r054-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- openrisc-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- openrisc-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- parisc-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- parisc-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- parisc-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- parisc-randconfig-r073-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- parisc64-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- powerpc-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- powerpc-randconfig-r063-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- powerpc-randconfig-r064-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- powerpc-sam440ep_defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- powerpc64-randconfig-r061-20260522
|   |-- arch-powerpc-kernel-pci_64.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-powerpc-kernel-rtas.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-powerpc-kernel-signal_32.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-powerpc-kernel-signal_64.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-powerpc-kernel-sys_ppc32.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- arch-powerpc-kernel-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- powerpc64-randconfig-r131-20260521
|   |-- lib-raid-raid6-powerpc-altivec1.c:sparse:sparse:Trying-to-use-reserved-word-signed-as-identifier
|   |-- lib-raid-raid6-powerpc-altivec1.c:sparse:sparse:two-or-more-data-types-in-declaration-specifiers
|   |-- lib-raid-raid6-powerpc-altivec2.c:sparse:sparse:Trying-to-use-reserved-word-signed-as-identifier
|   |-- lib-raid-raid6-powerpc-altivec2.c:sparse:sparse:two-or-more-data-types-in-declaration-specifiers
|   |-- lib-raid-raid6-powerpc-altivec4.c:sparse:sparse:Trying-to-use-reserved-word-signed-as-identifier
|   |-- lib-raid-raid6-powerpc-altivec4.c:sparse:sparse:two-or-more-data-types-in-declaration-specifiers
|   |-- lib-raid-raid6-powerpc-altivec8.c:sparse:sparse:Trying-to-use-reserved-word-signed-as-identifier
|   `-- lib-raid-raid6-powerpc-altivec8.c:sparse:sparse:two-or-more-data-types-in-declaration-specifiers
|-- riscv-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- riscv-allyesconfig
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-gpu-drm-scheduler-tests-tests_scheduler.c:error:initializer-element-is-not-a-compile-time-constant
|-- riscv-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- riscv-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- riscv-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- riscv-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- riscv-randconfig-r131-20260521
|   |-- arch-riscv-kernel-sys_hwprobe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-riscv-kernel-sys_riscv.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- ld.lld:error:call-to-__compiletime_assert_NNN-marked-dontcall-error:BUILD_BUG_ON-failed:HWEIGHT32(-(VALID_OPENAT2_FLAGS-(O_NONBLOCK-O_NDELAY))-__FMODE_EXEC)
|-- s390-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- s390-randconfig-r064-20260522
|   `-- drivers-tty-serial-max31.c:error:incompatible-pointer-types-passing-struct-i2c_driver-to-parameter-of-type-struct-spi_driver
|-- sh-allmodconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sh-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc64-allmodconfig
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- arch-sparc-kernel-sys_sparc32.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-sparc-kernel-sys_sparc_64.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-aio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fhandle.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-locks.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-notify-inotify-inotify_user.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-signalfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-timerfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-msg.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-sem.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- ipc-shm.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-capability.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-events-core.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-futex-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-groups.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-kcmp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-module-main.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-membarrier.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-seccomp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-itimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-timers.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-uid16.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-fadvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-filemap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-madvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-memfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-swapfile.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- net-socket.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- security-keys-compat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- security-landlock-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-lsm_syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- sparc64-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc64-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- sparc64-randconfig-001-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|-- sparc64-randconfig-002
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- arch-sparc-kernel-sys_sparc32.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- arch-sparc-kernel-sys_sparc_64.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-aio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fhandle.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-locks.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-notify-inotify-inotify_user.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-signalfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-timerfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-capability.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-events-core.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-futex-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-groups.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-kcmp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-membarrier.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-seccomp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-itimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-timers.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-uid16.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-fadvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-filemap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-madvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- net-socket.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- security-keys-compat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-lsm_syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- sparc64-randconfig-002-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|-- um-allmodconfig
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_crat.c:error:call-to-undeclared-function-cpu_data-ISO-C99-and-later-do-not-support-implicit-function-declarations
|   |-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_crat.c:error:cannot-take-the-address-of-an-rvalue-of-type-int
|   |-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_crat.c:error:incomplete-definition-of-type-struct-cpuinfo_x86
|   |-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_crat.c:error:use-of-undeclared-identifier-X86_VENDOR_AMD
|   |-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_topology.c:error:call-to-undeclared-function-cpu_data-ISO-C99-and-later-do-not-support-implicit-function-declarations
|   `-- drivers-gpu-drm-amd-amdgpu-..-amdkfd-kfd_topology.c:error:member-reference-base-type-int-is-not-a-structure-or-union
|-- um-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-i386_defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-randconfig-001
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fhandle.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-timerfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-kcmp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-stubs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- net-socket.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- um-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-randconfig-002
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- arch-x86-um-syscalls_64.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- block-ioprio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-aio.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-d_path.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-eventpoll.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-exec.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fcntl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fhandle.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-file_attr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-fsopen.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-ioctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-locks.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namei.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-namespace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-notify-inotify-inotify_user.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-open.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-pipe.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-read_write.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-readdir.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-select.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-signalfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-splice.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-stat.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-statfs.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-sync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-timerfd.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-utimes.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- fs-xattr.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-capability.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exec_domain.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-exit.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-fork.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-futex-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-groups.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-module-main.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nsproxy.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-nstree.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-pid.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-printk-printk.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-ptrace.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-reboot.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-membarrier.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sched-syscalls.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-seccomp.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-signal.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-sys.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-hrtimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-itimer.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-posix-timers.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-time-time.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- kernel-uid16.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-fadvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-filemap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-madvise.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mincore.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mlock.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mmap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mprotect.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-mremap.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-msync.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-oom_kill.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-process_vm_access.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-readahead.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   |-- mm-swapfile.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|   `-- security-keys-keyctl.c:error:unknown-warning-group-Wattribute-alias-ignored-Werror-Wunknown-warning-option
|-- um-randconfig-002-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|   |-- usr-bin-ld:sound-soc-codecs-es9356.o:(.rodata):undefined-reference-to-sdca_asoc_q78_get_volsw
|   `-- usr-bin-ld:sound-soc-codecs-es9356.o:(.rodata):undefined-reference-to-sdca_asoc_q78_put_volsw
|-- um-randconfig-r053-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- um-x86_64_defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-buildonly-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-buildonly-randconfig-003-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-buildonly-randconfig-004-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   |-- drivers-gpu-drm-amd-amdgpu-jpeg_v2_5.c:warning:d-directive-output-may-be-truncated-writing-between-and-bytes-into-a-region-of-size
|   |-- drivers-pinctrl-pinctrl-generic.c:error:conflicting-types-for-pinctrl_generic_to_map-have-int(struct-pinctrl_dev-struct-device_node-struct-device_node-struct-pinctrl_map-unsigned-int-unsigned-int-cons
|   |-- drivers-pinctrl-pinctrl-generic.c:error:redefinition-of-pinctrl_generic_pins_function_dt_node_to_map
|   `-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|-- x86_64-buildonly-randconfig-005-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-defconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-kexec
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-001-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-002-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-004-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-006-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-011-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-012-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-013-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-014-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-015-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-016-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-071
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-071-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-pinctrl-pinctrl-generic.c:error:redefinition-of-pinctrl_generic_pins_function_dt_node_to_map
|-- x86_64-randconfig-072
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-072-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-073-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-074
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-074-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-075
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-076
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-076-20260521
|   `-- drivers-pinctrl-pinctrl-generic.c:error:redefinition-of-pinctrl_generic_pins_function_dt_node_to_map
|-- x86_64-randconfig-101
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-101-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-102
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-102-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-103
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-104
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-104-20260521
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-121-20260522
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-122-20260522
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-randconfig-123-20260522
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-pinctrl-pinctrl-generic.c:error:redefinition-of-pinctrl_generic_pins_function_dt_node_to_map
|-- x86_64-randconfig-161
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4-bpf
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4-func
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4-kselftests
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4-kunit
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- x86_64-rhel-9.4-ltp
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- xtensa-allnoconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- xtensa-allyesconfig
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- xtensa-randconfig-001
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|-- xtensa-randconfig-001-20260521
|   |-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
|   `-- drivers-tty-serial-max31.c:error:max31_spi_driver-undeclared-(first-use-in-this-function)
|-- xtensa-randconfig-002
|   `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern
`-- xtensa-randconfig-002-20260521
    `-- Warning:block-blk-map.c-Excess-function-parameter-op-description-in-bio_copy_kern

elapsed time: 724m

configs tested: 282
configs skipped: 11

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                            randconfig-001    gcc-8.5.0
arc                   randconfig-001-20260521    gcc-13.4.0
arc                   randconfig-001-20260521    gcc-8.5.0
arc                            randconfig-002    gcc-8.5.0
arc                   randconfig-002-20260521    gcc-12.5.0
arc                   randconfig-002-20260521    gcc-8.5.0
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    clang-23
arm                                 defconfig    gcc-15.2.0
arm                            randconfig-001    gcc-8.5.0
arm                   randconfig-001-20260521    clang-23
arm                   randconfig-001-20260521    gcc-8.5.0
arm                            randconfig-002    gcc-11.5.0
arm                   randconfig-002-20260521    gcc-12.5.0
arm                   randconfig-002-20260521    gcc-8.5.0
arm                            randconfig-003    clang-20
arm                   randconfig-003-20260521    gcc-13.4.0
arm                   randconfig-003-20260521    gcc-8.5.0
arm                            randconfig-004    gcc-14.3.0
arm                   randconfig-004-20260521    gcc-8.5.0
arm64                            allmodconfig    clang-19
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260521    clang-23
arm64                 randconfig-001-20260521    gcc-8.5.0
arm64                 randconfig-002-20260521    gcc-8.5.0
arm64                 randconfig-003-20260521    gcc-8.5.0
arm64                 randconfig-004-20260521    gcc-8.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260521    gcc-15.2.0
csky                  randconfig-001-20260521    gcc-8.5.0
csky                  randconfig-002-20260521    gcc-15.2.0
csky                  randconfig-002-20260521    gcc-8.5.0
hexagon                          allmodconfig    clang-17
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    clang-23
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260521    clang-23
hexagon               randconfig-001-20260521    gcc-11.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260521    clang-23
hexagon               randconfig-002-20260521    gcc-11.5.0
i386                             allmodconfig    clang-20
i386                             allmodconfig    gcc-14
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                             allyesconfig    gcc-14
i386                 buildonly-randconfig-001    clang-20
i386        buildonly-randconfig-001-20260521    clang-20
i386        buildonly-randconfig-001-20260522    clang-20
i386                 buildonly-randconfig-002    clang-20
i386        buildonly-randconfig-002-20260521    clang-20
i386        buildonly-randconfig-002-20260522    gcc-14
i386                 buildonly-randconfig-003    clang-20
i386        buildonly-randconfig-003-20260521    clang-20
i386        buildonly-randconfig-003-20260522    clang-20
i386                 buildonly-randconfig-004    clang-20
i386        buildonly-randconfig-004-20260521    clang-20
i386        buildonly-randconfig-004-20260522    gcc-14
i386                 buildonly-randconfig-005    clang-20
i386        buildonly-randconfig-005-20260521    clang-20
i386        buildonly-randconfig-005-20260522    clang-20
i386                 buildonly-randconfig-006    clang-20
i386        buildonly-randconfig-006-20260521    clang-20
i386        buildonly-randconfig-006-20260522    gcc-13
i386                                defconfig    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260521    clang-20
i386                  randconfig-002-20260521    gcc-14
i386                  randconfig-003-20260521    clang-20
i386                  randconfig-004-20260521    gcc-14
i386                  randconfig-005-20260521    clang-20
i386                  randconfig-006-20260521    clang-20
i386                  randconfig-007-20260521    clang-20
i386                  randconfig-011-20260521    gcc-14
i386                  randconfig-012-20260521    clang-20
i386                  randconfig-013-20260521    gcc-13
i386                  randconfig-014-20260521    gcc-14
i386                  randconfig-015-20260521    gcc-14
i386                  randconfig-016-20260521    clang-20
i386                  randconfig-017-20260521    clang-20
loongarch                        allmodconfig    clang-19
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260521    gcc-11.5.0
loongarch             randconfig-001-20260521    gcc-12.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260521    gcc-11.5.0
loongarch             randconfig-002-20260521    gcc-15.2.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    clang-19
m68k                                defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
microblaze                          defconfig    gcc-15.2.0
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                               defconfig    gcc-11.5.0
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260521    gcc-11.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260521    gcc-11.5.0
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260521    gcc-12.5.0
parisc                randconfig-002-20260521    gcc-15.2.0
parisc64                            defconfig    clang-19
parisc64                            defconfig    gcc-15.2.0
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc               randconfig-001-20260521    clang-23
powerpc               randconfig-002-20260521    clang-17
powerpc                    sam440ep_defconfig    gcc-15.2.0
powerpc64             randconfig-001-20260521    clang-23
powerpc64             randconfig-002-20260521    gcc-12.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                          randconfig-001    gcc-15.2.0
riscv                 randconfig-001-20260521    clang-23
riscv                 randconfig-001-20260521    gcc-15.2.0
riscv                          randconfig-002    gcc-15.2.0
riscv                 randconfig-002-20260521    gcc-13.4.0
riscv                 randconfig-002-20260521    gcc-15.2.0
s390                             allmodconfig    clang-18
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                           randconfig-001    gcc-15.2.0
s390                  randconfig-001-20260521    gcc-15.2.0
s390                  randconfig-001-20260521    gcc-8.5.0
s390                           randconfig-002    gcc-15.2.0
s390                  randconfig-002-20260521    clang-17
s390                  randconfig-002-20260521    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sh                                  defconfig    gcc-15.2.0
sh                             randconfig-001    gcc-15.2.0
sh                    randconfig-001-20260521    gcc-15.2.0
sh                             randconfig-002    gcc-15.2.0
sh                    randconfig-002-20260521    gcc-12.5.0
sh                    randconfig-002-20260521    gcc-15.2.0
sh                          rsk7264_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-8.5.0
sparc                 randconfig-001-20260521    gcc-8.5.0
sparc                          randconfig-002    gcc-8.5.0
sparc                 randconfig-002-20260521    gcc-8.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    clang-20
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-8.5.0
sparc64               randconfig-001-20260521    gcc-8.5.0
sparc64                        randconfig-002    gcc-8.5.0
sparc64               randconfig-002-20260521    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    clang-23
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-8.5.0
um                    randconfig-001-20260521    gcc-14
um                    randconfig-001-20260521    gcc-8.5.0
um                             randconfig-002    gcc-8.5.0
um                    randconfig-002-20260521    gcc-14
um                    randconfig-002-20260521    gcc-8.5.0
um                           x86_64_defconfig    clang-23
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260521    clang-20
x86_64      buildonly-randconfig-001-20260521    gcc-12
x86_64      buildonly-randconfig-002-20260521    clang-20
x86_64      buildonly-randconfig-002-20260521    gcc-14
x86_64      buildonly-randconfig-003-20260521    clang-20
x86_64      buildonly-randconfig-004-20260521    clang-20
x86_64      buildonly-randconfig-004-20260521    gcc-14
x86_64      buildonly-randconfig-005-20260521    clang-20
x86_64      buildonly-randconfig-006-20260521    clang-20
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260521    clang-20
x86_64                randconfig-002-20260521    clang-20
x86_64                randconfig-003-20260521    clang-20
x86_64                randconfig-003-20260521    gcc-14
x86_64                randconfig-004-20260521    clang-20
x86_64                randconfig-005-20260521    clang-20
x86_64                randconfig-006-20260521    clang-20
x86_64                randconfig-011-20260521    clang-20
x86_64                randconfig-011-20260521    gcc-14
x86_64                randconfig-012-20260521    clang-20
x86_64                randconfig-012-20260521    gcc-14
x86_64                randconfig-013-20260521    clang-20
x86_64                randconfig-013-20260521    gcc-14
x86_64                randconfig-014-20260521    clang-20
x86_64                randconfig-014-20260521    gcc-14
x86_64                randconfig-015-20260521    clang-20
x86_64                randconfig-015-20260521    gcc-14
x86_64                randconfig-016-20260521    gcc-14
x86_64                         randconfig-071    gcc-14
x86_64                randconfig-071-20260521    clang-20
x86_64                         randconfig-072    gcc-14
x86_64                randconfig-072-20260521    clang-20
x86_64                         randconfig-073    clang-20
x86_64                randconfig-073-20260521    clang-20
x86_64                         randconfig-074    gcc-14
x86_64                randconfig-074-20260521    gcc-14
x86_64                         randconfig-075    gcc-13
x86_64                randconfig-075-20260521    clang-20
x86_64                         randconfig-076    clang-20
x86_64                randconfig-076-20260521    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                           allyesconfig    gcc-15.2.0
xtensa                         randconfig-001    gcc-8.5.0
xtensa                randconfig-001-20260521    gcc-8.5.0
xtensa                         randconfig-002    gcc-8.5.0
xtensa                randconfig-002-20260521    gcc-11.5.0
xtensa                randconfig-002-20260521    gcc-8.5.0

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


From lkp at intel.com  Fri May 22 01:50:10 2026
From: lkp at intel.com (kernel test robot)
Date: Fri, 22 May 2026 16:50:10 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 4129715f7ac5b71183350f1983023caf0464cb9b
Message-ID: <202605221601.ltZeGYwq-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: 4129715f7ac5b71183350f1983023caf0464cb9b  kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES

elapsed time: 827m

configs tested: 154
configs skipped: 3

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260522    gcc-9.5.0
arc                   randconfig-002-20260522    gcc-8.5.0
arm                               allnoconfig    clang-23
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    clang-23
arm                   randconfig-001-20260522    gcc-8.5.0
arm                   randconfig-002-20260522    gcc-10.5.0
arm                   randconfig-003-20260522    gcc-11.5.0
arm                   randconfig-004-20260522    clang-23
arm64                            allmodconfig    clang-19
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260522    clang-20
arm64                 randconfig-002-20260522    gcc-14.3.0
arm64                 randconfig-003-20260522    clang-23
arm64                 randconfig-004-20260522    gcc-10.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260522    gcc-15.2.0
csky                  randconfig-002-20260522    gcc-15.2.0
hexagon                          allmodconfig    clang-17
hexagon                           allnoconfig    clang-23
hexagon                             defconfig    clang-23
hexagon               randconfig-001-20260522    clang-23
hexagon               randconfig-002-20260522    clang-23
i386                             allmodconfig    gcc-14
i386                              allnoconfig    gcc-14
i386                             allyesconfig    gcc-14
i386        buildonly-randconfig-001-20260522    clang-20
i386        buildonly-randconfig-002-20260522    gcc-14
i386        buildonly-randconfig-003-20260522    clang-20
i386        buildonly-randconfig-004-20260522    gcc-14
i386        buildonly-randconfig-005-20260522    clang-20
i386        buildonly-randconfig-006-20260522    gcc-13
i386                                defconfig    clang-20
i386                  randconfig-001-20260522    gcc-14
i386                  randconfig-002-20260522    clang-20
i386                  randconfig-003-20260522    gcc-13
i386                  randconfig-004-20260522    gcc-14
i386                  randconfig-005-20260522    gcc-14
i386                  randconfig-006-20260522    clang-20
i386                  randconfig-007-20260522    gcc-13
i386                  randconfig-011-20260522    gcc-14
i386                  randconfig-012-20260522    clang-20
i386                  randconfig-013-20260522    clang-20
i386                  randconfig-014-20260522    gcc-14
i386                  randconfig-015-20260522    clang-20
i386                  randconfig-016-20260522    clang-20
i386                  randconfig-017-20260522    gcc-14
loongarch                        allmodconfig    clang-19
loongarch                         allnoconfig    clang-23
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260522    clang-23
loongarch             randconfig-002-20260522    gcc-15.2.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    gcc-15.2.0
m68k                            q40_defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    gcc-15.2.0
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    gcc-11.5.0
nios2                 randconfig-001-20260522    gcc-8.5.0
nios2                 randconfig-002-20260522    gcc-8.5.0
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260522    gcc-14.3.0
parisc                randconfig-002-20260522    gcc-8.5.0
parisc64                            defconfig    gcc-15.2.0
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    gcc-15.2.0
powerpc               randconfig-001-20260522    clang-20
powerpc               randconfig-002-20260522    clang-18
powerpc64             randconfig-001-20260522    gcc-10.5.0
powerpc64             randconfig-002-20260522    gcc-12.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    clang-23
riscv                 randconfig-001-20260522    clang-17
riscv                 randconfig-002-20260522    clang-23
s390                             allmodconfig    clang-18
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    clang-23
s390                  randconfig-001-20260522    clang-23
s390                  randconfig-002-20260522    clang-23
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-15.2.0
sh                    randconfig-001-20260522    gcc-10.5.0
sh                    randconfig-002-20260522    gcc-14.3.0
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260522    gcc-15.2.0
sparc                 randconfig-002-20260522    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    clang-20
sparc64               randconfig-001-20260522    gcc-14.3.0
sparc64               randconfig-002-20260522    gcc-12.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                                  defconfig    clang-23
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260522    clang-23
um                    randconfig-002-20260522    gcc-14
um                           x86_64_defconfig    clang-23
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260522    clang-20
x86_64      buildonly-randconfig-002-20260522    gcc-13
x86_64      buildonly-randconfig-003-20260522    gcc-14
x86_64      buildonly-randconfig-004-20260522    clang-20
x86_64      buildonly-randconfig-005-20260522    clang-20
x86_64      buildonly-randconfig-006-20260522    gcc-14
x86_64                              defconfig    gcc-14
x86_64                randconfig-001-20260522    clang-20
x86_64                randconfig-002-20260522    clang-20
x86_64                randconfig-003-20260522    gcc-14
x86_64                randconfig-004-20260522    gcc-14
x86_64                randconfig-005-20260522    gcc-14
x86_64                randconfig-006-20260522    gcc-14
x86_64                randconfig-071-20260522    gcc-14
x86_64                randconfig-072-20260522    gcc-14
x86_64                randconfig-073-20260522    clang-20
x86_64                randconfig-074-20260522    gcc-14
x86_64                randconfig-075-20260522    clang-20
x86_64                randconfig-076-20260522    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    gcc-15.2.0
xtensa                randconfig-001-20260522    gcc-9.5.0
xtensa                randconfig-002-20260522    gcc-11.5.0

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


From lkp at intel.com  Fri May 22 02:44:12 2026
From: lkp at intel.com (kernel test robot)
Date: Fri, 22 May 2026 17:44:12 +0800
Subject: [liveupdate:next] BUILD SUCCESS
 1b7ce86f23a430ac91a2ed6cd88046e16751b285
Message-ID: <202605221705.CBvQP3Im-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git next
branch HEAD: 1b7ce86f23a430ac91a2ed6cd88046e16751b285  liveupdate: document systemd support

elapsed time: 881m

configs tested: 173
configs skipped: 9

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                   randconfig-001-20260522    gcc-9.5.0
arc                   randconfig-002-20260522    gcc-8.5.0
arm                               allnoconfig    clang-23
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    clang-23
arm                   randconfig-001-20260522    gcc-8.5.0
arm                   randconfig-002-20260522    gcc-10.5.0
arm                   randconfig-003-20260522    gcc-11.5.0
arm                   randconfig-004-20260522    clang-23
arm64                            allmodconfig    clang-19
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                          randconfig-001    gcc-8.5.0
arm64                 randconfig-001-20260522    clang-20
arm64                          randconfig-002    gcc-14.3.0
arm64                 randconfig-002-20260522    gcc-14.3.0
arm64                          randconfig-003    clang-23
arm64                 randconfig-003-20260522    clang-23
arm64                          randconfig-004    clang-23
arm64                 randconfig-004-20260522    gcc-10.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                           randconfig-001    gcc-10.5.0
csky                  randconfig-001-20260522    gcc-15.2.0
csky                           randconfig-002    gcc-10.5.0
csky                  randconfig-002-20260522    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                             defconfig    clang-23
hexagon               randconfig-001-20260522    clang-23
hexagon               randconfig-002-20260522    clang-23
i386                              allnoconfig    gcc-14
i386        buildonly-randconfig-001-20260522    clang-20
i386        buildonly-randconfig-002-20260522    gcc-14
i386        buildonly-randconfig-003-20260522    clang-20
i386        buildonly-randconfig-004-20260522    gcc-14
i386        buildonly-randconfig-005-20260522    clang-20
i386        buildonly-randconfig-006-20260522    gcc-13
i386                                defconfig    clang-20
i386                  randconfig-011-20260522    gcc-14
i386                  randconfig-012-20260522    clang-20
i386                  randconfig-013-20260522    clang-20
i386                  randconfig-014-20260522    gcc-14
i386                  randconfig-015-20260522    clang-20
i386                  randconfig-016-20260522    clang-20
i386                  randconfig-017-20260522    gcc-14
loongarch                        allmodconfig    clang-19
loongarch                         allnoconfig    clang-23
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260522    clang-23
loongarch             randconfig-002-20260522    gcc-15.2.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    gcc-15.2.0
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    gcc-11.5.0
nios2                 randconfig-001-20260522    gcc-8.5.0
nios2                 randconfig-002-20260522    gcc-8.5.0
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-13.4.0
parisc                randconfig-001-20260522    gcc-14.3.0
parisc                         randconfig-002    gcc-8.5.0
parisc                randconfig-002-20260522    gcc-8.5.0
parisc64                            defconfig    gcc-15.2.0
powerpc                           allnoconfig    gcc-15.2.0
powerpc                        randconfig-001    gcc-13.4.0
powerpc               randconfig-001-20260522    clang-20
powerpc                        randconfig-002    gcc-10.5.0
powerpc               randconfig-002-20260522    clang-18
powerpc64                      randconfig-001    clang-17
powerpc64             randconfig-001-20260522    gcc-10.5.0
powerpc64                      randconfig-002    clang-23
powerpc64             randconfig-002-20260522    gcc-12.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    clang-23
riscv                          randconfig-001    gcc-8.5.0
riscv                 randconfig-001-20260522    clang-17
riscv                          randconfig-002    clang-23
riscv                 randconfig-002-20260522    clang-23
s390                             allmodconfig    clang-18
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    clang-23
s390                           randconfig-001    gcc-11.5.0
s390                  randconfig-001-20260522    clang-23
s390                           randconfig-002    clang-23
s390                  randconfig-002-20260522    clang-23
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-15.2.0
sh                             randconfig-001    gcc-15.2.0
sh                    randconfig-001-20260522    gcc-10.5.0
sh                             randconfig-002    gcc-14.3.0
sh                    randconfig-002-20260522    gcc-14.3.0
sh                           se7712_defconfig    gcc-15.2.0
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-8.5.0
sparc                 randconfig-001-20260522    gcc-15.2.0
sparc                          randconfig-002    gcc-15.2.0
sparc                 randconfig-002-20260522    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    clang-20
sparc64                        randconfig-001    gcc-8.5.0
sparc64               randconfig-001-20260522    gcc-14.3.0
sparc64                        randconfig-002    clang-23
sparc64               randconfig-002-20260522    gcc-12.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                                  defconfig    clang-23
um                             i386_defconfig    gcc-14
um                             randconfig-001    clang-23
um                    randconfig-001-20260522    clang-23
um                             randconfig-002    clang-23
um                    randconfig-002-20260522    gcc-14
um                           x86_64_defconfig    clang-23
x86_64                            allnoconfig    clang-20
x86_64      buildonly-randconfig-001-20260522    clang-20
x86_64      buildonly-randconfig-002-20260522    gcc-13
x86_64      buildonly-randconfig-003-20260522    gcc-14
x86_64      buildonly-randconfig-004-20260522    clang-20
x86_64      buildonly-randconfig-005-20260522    clang-20
x86_64      buildonly-randconfig-006-20260522    gcc-14
x86_64                              defconfig    gcc-14
x86_64                randconfig-001-20260522    clang-20
x86_64                randconfig-002-20260522    clang-20
x86_64                randconfig-003-20260522    gcc-14
x86_64                randconfig-004-20260522    gcc-14
x86_64                randconfig-005-20260522    gcc-14
x86_64                randconfig-006-20260522    gcc-14
x86_64                randconfig-011-20260522    gcc-14
x86_64                randconfig-012-20260522    clang-20
x86_64                randconfig-013-20260522    gcc-13
x86_64                randconfig-014-20260522    clang-20
x86_64                randconfig-015-20260522    clang-20
x86_64                randconfig-016-20260522    gcc-14
x86_64                randconfig-071-20260522    gcc-14
x86_64                randconfig-072-20260522    gcc-14
x86_64                randconfig-073-20260522    clang-20
x86_64                randconfig-074-20260522    gcc-14
x86_64                randconfig-075-20260522    clang-20
x86_64                randconfig-076-20260522    gcc-14
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    gcc-15.2.0
xtensa                         randconfig-001    gcc-8.5.0
xtensa                randconfig-001-20260522    gcc-9.5.0
xtensa                         randconfig-002    gcc-8.5.0
xtensa                randconfig-002-20260522    gcc-11.5.0

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


From pratyush at kernel.org  Fri May 22 05:52:07 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Fri, 22 May 2026 14:52:07 +0200
Subject: [PATCH v5 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <aguXhxrjhvmxAyb2@plex> (Pasha Tatashin's message of "Mon, 18 May
	2026 23:15:17 +0000")
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
	<20260518125459.1092373-4-pasha.tatashin@soleen.com>
	<2vxzo6ic8ysn.fsf@kernel.org> <aguXhxrjhvmxAyb2@plex>
Message-ID: <2vxz4ijz8v48.fsf@kernel.org>

On Mon, May 18 2026, Pasha Tatashin wrote:

> On 05-18 18:31, Pratyush Yadav wrote:
>> On Mon, May 18 2026, Pasha Tatashin wrote:
>> 
>> > During the reboot() syscall, user processes may still be running
>> > concurrently and attempting to mutate sessions (e.g., creating,
>> > retrieving, or releasing sessions). To prevent this, introduce
>> > luo_session_serialize_rwsem to synchronize mutations with the
>> > serialization process.
>> >
>> > All session mutation operations (create, retrieve, release, ioctl) take
>> > the read lock. The serialization process (luo_session_serialize) takes
>> > the write lock and holds it indefinitely on success. This effectively
>> > freezes the LUO session subsystem during the transition to the new
>> > kernel. If serialization fails, the lock is released to allow recovery.
>> 
>> Good idea I think.
>
> Hi Pratyush,
>
>> 
>> But, do we need a new mutex? Can't we use luo_session_header->rwsem?
>> Session creation and release take the header rwsem at one point anyway,
>> so perhaps we can just reuse that?
>
> The sh->rwsem is for protecting the the session list. We only take it as 
> a writer when modifying the list (insert/remove) and as a reader when 
> traversing it. Also, we drop sh->rwsem as soon as we've acquired the 
> per-session mutex to allow other list operations to proceed while a 
> session is being modified.
>
> Because of this, many session mutation operations (specifically ioctl 
> calls) don't touch sh->rwsem at all?they jump straight to the  session 
> state via the file's private_data. To use sh->rwsem to block
> these mutations, we would be forced to add down_read(&sh->rwsem) to 
> every ioctl path. This would be a layering violation, coupling list 
> management to per-session data mutations, and would introduce a global
> bottleneck for operations that are otherwise independent.

As for the layering violation, I think we would need to change the
semantics of the lock -- it no longer protects only the list, but other
session operations as well.

But yeah, if we do this then operations like session creation would have
to wait for ongoing session operations like PRESERVE_FD. My argument was
based around the fact that session creation or removal should not be
very frequent (and don't happen in the hot path anyway) so the added
latency should not affect them as much. By doing this tradeoff we get
slightly simpler code (and simpler locking scheme).

But I see your point as well. In practice session creation and
PRESERVE_FD are independent and one should not block the other. Maybe we
get VMMs creating sessions while another VMM is preserving stuff, and
this slowing down the live update preparation? Dunno...

I suppose let's go with this patch. But, can you please document the
lock hierarchy where you explain what this lock is for?

>
> The only other way to prevent mutations without a new global lock would 
> be for the reboot process to acquire every individual session mutex. 
> However, since LUO_SESSION_MAX can be large, this would exceed lockdep's 
> maximum lock tracking limit and trigger failures. The 
> luo_session_serialize_rwsem provides a dedicated signal to freeze the 
> entire subsystem without messing with the existing fine-grained locking 
> logic.

Yeah, that would have been nice, but we shouldn't break lockdep I reckon.

>
>> 
>> Also, do we need to block incoming sessions? They won't participate in
>> serialization, so perhaps we can leave those alone, and all the outgoing
>> sessions get protected by the outgoing session header rwsem?
>
> Incoming sessions don't participate in serialization, but blocking them 
> makes the code more robust. This provides a level of future proofing if 
> new ioctls or operations are added later, we won't accidentally miss a 
> path that should have been frozen during reboot. It's safer to treat the 
> subsystem as a single unit that freezes entirely once the transition 
> begins.

Sure, makes sense.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Fri May 22 06:24:09 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Fri, 22 May 2026 15:24:09 +0200
Subject: [PATCH 07/12] kho: allow destroying KHO radix tree
In-Reply-To: <ag-Xa-bDOouXAFXb@plex> (Pasha Tatashin's message of "Thu, 21 May
	2026 23:46:23 +0000")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-8-pratyush@kernel.org> <ag-Xa-bDOouXAFXb@plex>
Message-ID: <2vxzwlwv7f2e.fsf@kernel.org>

On Thu, May 21 2026, Pasha Tatashin wrote:

> On 04-29 15:39, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> Add kho_radix_destroy_tree() which allows destroying the radix tree and
>> freeing all its pages.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  include/linux/kho_radix_tree.h     |  3 +++
>>  kernel/liveupdate/kexec_handover.c | 34 ++++++++++++++++++++++++++++++
>>  2 files changed, 37 insertions(+)
>
> Generally, in Linux, recursion is avoided due to statically sized kernel 
> stacks. However, here we are bound by a relatively shallow KHO tree, and 
> kho_radix_walk_tree() is already recursive, so I think it is alright. We 
> just need to be mindful of local variable usage (frame sizes) within these 
> recursive functions.

Yep, recursion is strictly bounded and the depth is no worse than any
page table walkers for example.

>
> Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com> 

Thanks!

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Fri May 22 08:02:38 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Fri, 22 May 2026 17:02:38 +0200
Subject: [PATCH 09/12] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <ag-a4ru9bMkDMthO@plex> (Pasha Tatashin's message of "Fri, 22 May
	2026 00:48:35 +0000")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-10-pratyush@kernel.org>
	<agHGOxSnP_VraH1M@kernel.org> <2vxzecjhc2s8.fsf@kernel.org>
	<ag-a4ru9bMkDMthO@plex>
Message-ID: <2vxzse7j7ai9.fsf@kernel.org>

On Fri, May 22 2026, Pasha Tatashin wrote:

> On 05-11 18:46, Pratyush Yadav wrote:
>> On Mon, May 11 2026, Mike Rapoport wrote:
>> 
>> > On Wed, Apr 29, 2026 at 03:39:11PM +0200, Pratyush Yadav wrote:
>> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> >> 
>> >> In the upcoming commits, the KHO will learn how to discover free blocks
>> >> of memory by walking the KHO radix tree. It will then mark those regions
>> >> as scratch to allow memory allocation in case scratch runs low.
>> >> 
>> >> To differentiate the extended scratch areas from the main scratch areas,
>> >> introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
>> >> for allocations during scratch-only. Teach should_skip_region() to check
>> >> for both flags before deciding if the region should be skipped.
>> >
>> > Why there's a need to differentiate SCRATCH and SCRATCH_EXT?
>> > SCRATCH (I still hate the name) means "memory memblock can safely use for
>
>  +1000
>
> I also strongly dislike this name and mentioned it in another thread
> earlier today.
>
> If we ever decide to s/scratch/something-else/ globally, that should be a
> separate cleanup effort. However, since we are introducing a brand new flag
> here, we can discuss a better name for the _ext portion to avoid overloading
> the "scratch" concept.
>
>> > the allocations". Initially this memory comes from the reservations in the
>> > first kernel, but if the second kernel can find more memory to extend it,
>> > why that additional memory should be treated differently? 
>> 
>> Two reasons:
>> 
>> 1. We mark SCRATCH as MIGRATE_CMA. We don't want to do that for
>>    SCRATCH_EXT since this memory can be used for non-movable
>>    allocations.
>> 
>> 2. Gigantic (1G) huge pages can not be allocated from scratch. They can
>>    be preserved memory and thus should not be allocated from SCRATCH.
>>    See patch 12 that does allocations for gigantic huge pages only from
>>    SCRATCH_EXT.
>> 
>> I will add this in the commit message for the next version.
>> 
>> Naming is hard, so if you have any better names I'm all ears :-)
>
> IMO, this scratch_ext is not "scratch" in the traditional KHO sense at all.
> The traditional KHO scratch is what is passed from kernel to kernel and is
> guaranteed to contain zero preserved memory. This new memory is not passed
> from kernel to kernel and can contain preserved memory at runtime. It's
> essentially just memory that we identify as currently unpreserved and release
> early to the system.
>
> If we want to keep the naming aligned with the existing codebase for now:
> MEMBLOCK_KHO_SCRATCH      -> original scratch
> MEMBLOCK_KHO_UNPRESERVED  -> for the new memory (instead of SCRATCH_EXT)

UNPRESERVED sounds good to me. I will use that for the next revision
unless Mike objects.

>
> Alternatively, if we do want to tackle the global rename of "scratch" later:
> MEMBLOCK_KHO_BOOTSTRAP    -> for the original scratch
> MEMBLOCK_KHO_UNPRESERVED  -> for this new dynamic memory

Or perhaps BOOTMEM? I suppose either of the two are somewhat better than
scratch.

Anyway, can we please do the SCRATCH rename as a separate series? I
would like this series to not get muddled in the naming discussion. I
will use UNPRESERVED for the new concept in v2 though.

>
> What do you think?
>
> Pasha

-- 
Regards,
Pratyush Yadav


From alhe at linux.microsoft.com  Fri May 22 10:09:31 2026
From: alhe at linux.microsoft.com (Alejandro Hernandez)
Date: Fri, 22 May 2026 11:09:31 -0600
Subject: [PATCH] kexec: restore functionality to preserve fd xattrs for
 uncompressed kernel images
In-Reply-To: <20260519094153.GA227450@horms.kernel.org>
References: <20260507215417.2674568-1-alhe@linux.microsoft.com>
 <20260519094153.GA227450@horms.kernel.org>
Message-ID: <577e2cc5-490b-42dd-855a-8ca6780df14f@linux.microsoft.com>


On 5/19/2026 3:41 AM, Simon Horman wrote:
> On Thu, May 07, 2026 at 09:54:17PM +0000, Alejandro Hernandez wrote:
>> Since commit 714fa115 xattrs attached to a kernel image file (such as IMA
>> signatures) are not being preserved, do_kexec_file_load() was modified to use
>> memfd_create() which now passes an anonymous file descriptor to
>> kexec_file_load(). This change eliminated the filesystem inode identity of the
>> original kernel file, hence attributes are no longer visible to the kernel IMA
>> appraisal handler during kexec_file_load, causing IMA policy enforcement to fail
>> even for validly signed kernel images.
>>
>> This patch attempts to restore such behavior although, only does it for
>> uncompressed kernel images. To do this, we first figure out if the image file is
>> compressed or not and call each method accordingly.
>>
>> Compressed images continue to use memfd and cannot carry forward the original fd
>> security attributes, since their decompressed bytes do not match the signed
>> artifact. Proper handling of compressed images with IMA would require either
>> signing the decompressed artifact or kernel-side support for decompression
>> within the kexec_file_load path.
>>
>> Signed-off-by: Alejandro Hernandez Samaniego <alhe at linux.microsoft.com>
> Hi Alejandro,
>
> Thanks for the patch.
>
> Overall this looks good to me. But I'm wondering if you could spin a v2
> to address a minor code-style issue I have noted inline.

Hey Simon,

Thanks for the review, sure thing, I will send a v2


Alejandro


>
>> ---
>>   kexec/kexec.c | 42 +++++++++++++++++++++++++++++++++++-------
>>   1 file changed, 35 insertions(+), 7 deletions(-)
>>
>> diff --git a/kexec/kexec.c b/kexec/kexec.c
>> index 08edfca..a388762 100644
>> --- a/kexec/kexec.c
>> +++ b/kexec/kexec.c
>> @@ -1328,17 +1328,41 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
>>   
>>   	kernel = argv[fileind];
>>   
>> -	/* slurp in the input kernel */
>> +        /* Hold original fd with its xattrs */
>> +	kernel_fd = open(kernel, O_RDONLY);
>> +	if (kernel_fd == -1) {
>> +		fprintf(stderr, "Failed to open file %s:%s\n", kernel,
>> +				strerror(errno));
>> +		return EFAILED;
>> +	}
>> +
>> +	/* Compressed vs Uncompressed */
>> +	struct stat kernel_stat;
>> +	off_t kernel_file_size = -1;
> I would prefer if we stuck to declaring local variables
> at the top of this function.
>
>> +	if (fstat(kernel_fd, &kernel_stat) == 0)
>> +		kernel_file_size = kernel_stat.st_size;
>> +
>> +        /* slurp in the input kernel */
>>   	kernel_buf = slurp_decompress_file(kernel, &kernel_size);
>>   	if (!kernel_buf) {
>>   		fprintf(stderr, "Failed to decompress file %s:%s\n", kernel,
>>   				strerror(errno));
>> +		close(kernel_fd);
>>   		return EFAILED;
>>   	}
> ...


From helgaas at kernel.org  Fri May 22 10:59:05 2026
From: helgaas at kernel.org (Bjorn Helgaas)
Date: Fri, 22 May 2026 12:59:05 -0500
Subject: [PATCH v5 05/11] PCI: liveupdate: Keep bus numbers constant
 during Live Update
In-Reply-To: <20260512184846.119396-6-dmatlack@google.com>
Message-ID: <20260522175905.GA232400@bhelgaas>

On Tue, May 12, 2026 at 06:48:40PM +0000, David Matlack wrote:
> ...

> + * The PCI core guarantees that preserved devices can be identified by the same
> + * bus, device, and function numbers for as long as they are preserved
> + * (including across kexec). To accomplish this, the PCI core always inherits
> + * the secondary and subordinate bus numbers assigned to bridges during scanning
> + * if any device is preserved. This is true even on architectures that always
> + * assign new bus numbers during scanning. The kernel assumes the previous
> + * kernel established a sane bus topology across kexec.
> + *
> + * If a misconfigured or unconfigured bridge is encountered during enumeration
> + * while there are preserved devices, itss secondary and subordinate bus numbers
> + * will be cleared and devices below it will not be enumerated.

s/itss/its/

> + * To keep things simple, inherit the secondary and subordinate bus numbers on
> + * _all_ bridges if _any_ PCI devices are preserved (i.e.  even bridges without
> + * any downstream endpoints that were preserved).  This avoids accidentally
> + * assigning a bridge a new window that overlaps with a preserved device that is
> + * downstream of a different bridge.

s/i.e.  even/i.e. even/ (remove extra space)


From helgaas at kernel.org  Fri May 22 11:01:59 2026
From: helgaas at kernel.org (Bjorn Helgaas)
Date: Fri, 22 May 2026 13:01:59 -0500
Subject: [PATCH v5 10/11] PCI: liveupdate: Do not disable bus mastering
 on preserved devices during kexec
In-Reply-To: <20260512184846.119396-11-dmatlack@google.com>
Message-ID: <20260522180159.GA231364@bhelgaas>

On Tue, May 12, 2026 at 06:48:45PM +0000, David Matlack wrote:
> Do not disable bus mastering on outgoing preserved devices during
> pci_device_shutdown() for kexec.
> 
> Preserved devices must be allowed to perform memory transactions during
> a Live Update to minimize downtime and ensure continuous operation.
> Clearing the bus mastering bit would prevent these devices from issuing
> any memory requests while the new kernel boots.

s/minimize downtime and//

Ensuring continuous operation is the critical piece here.  Minimizing
downtime is always good but IMO it's not actually a reason for this
change.

I guess what this is probably saying is that the continuous operation
and Live Update are what minimizes downtime, and preserving bus
mastering is a piece of that.

> Because bridges upstream of preserved endpoint devices are also
> automatically preserved, this change also avoids clearing bus mastering
> on them. This is critical because clearing bus mastering on an upstream
> bridge prevents the bridge from forwarding memory requests upstream (i.e.
> it would prevent the endpoint device from accessing system RAM and doing
> peer-to-peer transactions with devices not downstream of the bridge).


From dmatlack at google.com  Fri May 22 13:23:58 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:23:58 +0000
Subject: [PATCH v6 00/12] PCI: liveupdate: PCI core support for Live Update
Message-ID: <20260522202410.3104264-1-dmatlack@google.com>

This series can be found on GitHub:

  https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v6

This series introduces initial support in the PCI core for Live Update,
enabling drivers to preserve PCI devices across a kexec-based kernel
update without interrupting the device. This functionality is critical
for minimizing downtime in environments where PCI devices (e.g., those
assigned to VMs via VFIO) must continue operating or maintain state
across a host kernel upgrade.

Specifically, this series allows preserved PCI devices to perform
uninterrupted memory transactions (DMA) to/from system memory across a
Live Update. These devices can be behind a bridge but must not be VFs.
Support for P2P and VF preservation will be addressed in future series.

Series Overview
---------------

This series implements the following to support PCI device preservation
across Live Update:

  1. Set up a File-Lifecycle-Bound (FLB) handler to track and preserve
     PCI-specific state (struct pci_ser) across Live Update using Kexec
     Handover (KHO).

  2. Add APIs for drivers to register outgoing-preserved devices for
     preservation and for the PCI core to identify incoming-preserved
     devices during enumeration.

  3. Automatically preserve all upstream bridges for any preserved
     endpoint. Use reference counting to ensure bridges remain preserved
     as long as any downstream device is preserved.

  4. Guarantee that preserved devices retain the same RequesterID (bus,
     device, function) for the duration of their preservation by
     inheriting secondary bus numbers, subordinate bus numbers, and ARI
     Forwarding Enable on preserved bridges.

  5. Guarantee that memory transactions to/from preserved devices are
     routed consistently by inheriting Access Control Services (ACS)
     flags across a Live Update, from the endpoint up to the root port.

  6. Modify the PCI shutdown path to avoid disabling bus mastering on
     preserved devices, thereby allowing preserved devices to perform
     uninterrupted during kexec for Live Update.

  7. Provide comprehensive documentation for the FLB API, device
     tracking mechanisms, and the division of responsibilities between
     the PCI core, drivers, and userspace.

Dependencies
------------

This series is built on top of the next branch of the liveupdate.git
tree, which includes two commits to enable refcounting the incoming FLB:

  https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git/log/?h=next

Testing
-------

This series was tested in conjunction with v4 of the VFIO PCI driver
series:

  https://lore.kernel.org/kvm/20260511234802.2280368-1-vipinsh at google.com/

The full set of patches used for testing can be found on GitHub:

  https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v6-with-vfio

Testing was performed using the new VFIO selftests:

  - vfio_pci_liveupdate_uapi_test
  - vfio_pci_liveupdate_kexec_test

Both tests were run in a QEMU-based VM environment (using a single
virtio-net PCIe device connected to a root port to exercise bridge
support) and on bare metal using an Intel EMR server with 8x Intel DSA
PCIe devices and 1x NVMe device.

Future Work
-----------

Following this series, we expect to make further improvements to the PCI
core support for Live Update:

  - Allow P2P across Live Update by avoiding resizing or moving
    preserved device BARs and preserving all upstream bridge windows.

  - Support preserving Virtual Functions by preserving SR-IOV
    configuration on PFs and enumerating VFs after Live Update.

Changelog
---------

v6:
 - Fix truncated domain during bit shift in pci_ser_xa_key() (Sashiko)
 - Replace complex and buggy per-device locks and outgoing mutex with a
   single rwsem (me)
 - Use a loop instead of recursion to walk upstream bridges (Sashiko)
 - Correctly account for all devices that need quirks to enable ACS
   instead of relying on PCI_DEV_FLAGS_ACS_ENABLED_QUIRK. This required
   adding a patch to refactor pci_dev_acs_ops in quirks.c (Sashiko)
 - Fix circular locking dependency between pci_rescan_remove_lock and
   pci_liveupdate_flb.private->incoming.lock (me)
 - Convert several functions to return 0/error instead of true/false.
 - Bump PCI_LUO_FLB_COMPATIBLE in the patch that changes the semantics
   of pci_dev_ser.refcount (me)
 - Convert was_prepared and frozen from bitfields to bools to avoid
   KCSAN warnings (Sashiko)
 - Drop experimental verbiage from Kconfig and Documentation (me)
 - Ensure that bridges inherit bus numbers during scanning if their
   parent bridge also inherits bus numbers. Otherwise, a race between a
   scan and pci_liveupdate_finish() could result in a bridge assigning a
   bus window larger than its parent while its parent is forced to
   inherit bus numbers (i.e., cannot expand) (me)
 - Free struct pci_ser if an error occurs during pci_flb_retrieve()
   (Sashiko)
 - Detect duplicate entries in incoming FLB when constructing xarray and
   return an error instead of silently ignoring (me)
 - Add a wrapper function for fetching from the outgoing FLB (me)
 - Grammatical and spelling fixes (Bjorn)
 - Don't bother forcing bus inheritance when only outgoing devices are
   preserved. If a scan causes a bus number conflict, that is a general
   PCI core bug; it is not specific to Live Update (me)
 - Convert bitfields in struct pci_liveupdate to bools to avoid data
   races (me)

v5: https://lore.kernel.org/linux-pci/20260512184846.119396-1-dmatlack at google.com/
v4: https://lore.kernel.org/linux-pci/20260423212316.3431746-1-dmatlack at google.com/
v3: https://lore.kernel.org/kvm/20260323235817.1960573-1-dmatlack at google.com/
v2: https://lore.kernel.org/kvm/20260129212510.967611-1-dmatlack at google.com/
v1: https://lore.kernel.org/kvm/20251126193608.2678510-1-dmatlack at google.com/
rfc: https://lore.kernel.org/kvm/20251018000713.677779-1-vipinsh at google.com/


David Matlack (12):
  PCI: liveupdate: Set up FLB handler for the PCI core
  PCI: liveupdate: Track outgoing preserved PCI devices
  PCI: liveupdate: Track incoming preserved PCI devices
  PCI: liveupdate: Document driver binding responsibilities
  PCI: liveupdate: Keep bus numbers constant during Live Update
  PCI: liveupdate: Auto-preserve upstream bridges across Live Update
  PCI: Refactor matching logic for pci_dev_acs_ops
  PCI: liveupdate: Inherit ACS flags in incoming preserved devices
  PCI: liveupdate: Inherit ARI Forwarding Enable on preserved bridges
  PCI: liveupdate: Freeze preservation status during shutdown
  PCI: liveupdate: Do not disable bus mastering on preserved devices
    during kexec
  Documentation: PCI: Add documentation for Live Update

 Documentation/PCI/index.rst                   |   1 +
 Documentation/PCI/liveupdate.rst              |  29 +
 .../admin-guide/kernel-parameters.txt         |   6 +-
 Documentation/core-api/liveupdate.rst         |   1 +
 MAINTAINERS                                   |  12 +
 drivers/pci/Kconfig                           |  15 +
 drivers/pci/Makefile                          |   1 +
 drivers/pci/liveupdate.c                      | 850 ++++++++++++++++++
 drivers/pci/liveupdate.h                      |  68 ++
 drivers/pci/pci-driver.c                      |   9 +-
 drivers/pci/pci.c                             |  13 +-
 drivers/pci/pci.h                             |   5 +
 drivers/pci/probe.c                           |  22 +-
 drivers/pci/quirks.c                          |  57 +-
 include/linux/kho/abi/pci.h                   |  65 ++
 include/linux/pci.h                           |   4 +
 include/linux/pci_liveupdate.h                |  77 ++
 17 files changed, 1197 insertions(+), 38 deletions(-)
 create mode 100644 Documentation/PCI/liveupdate.rst
 create mode 100644 drivers/pci/liveupdate.c
 create mode 100644 drivers/pci/liveupdate.h
 create mode 100644 include/linux/kho/abi/pci.h
 create mode 100644 include/linux/pci_liveupdate.h


base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:23:59 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:23:59 +0000
Subject: [PATCH v6 01/12] PCI: liveupdate: Set up FLB handler for the PCI core
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-2-dmatlack@google.com>

Set up a File-Lifecycle-Bound (FLB) handler for the PCI core to enable
it to participate in the preservation of PCI devices across Live Update.
Essentially, this commit enables the PCI core to allocate a struct
(struct pci_ser) and preserve it across a Live Update whenever at least
one device is preserved.

Preserving PCI devices across Live Update is built on top of the Live
Update Orchestrator's (LUO) support for file preservation. Drivers are
expected to expose a file to userspace to represent a single PCI device
and support preservation of that file. This is intended primarily to
support preservation of PCI devices bound to VFIO drivers.

This commit enables drivers to register their liveupdate_file_handler
with the PCI core so that the PCI core can do its own tracking and
enforcement of which devices are preserved.

  pci_liveupdate_register_flb(driver_file_handler);
  pci_liveupdate_unregister_flb(driver_file_handler);

When the first file (with a handler registered with the PCI core) is
preserved, the PCI core will be notified to allocate its tracking struct
(pci_ser). When the last file is unpreserved (i.e. preservation
cancelled) the PCI core will be notified to free struct pci_ser.

This struct is preserved across a Live Update using KHO and can be
fetched by the PCI core during early boot (e.g. during device
enumeration) so that it knows which devices were preserved.

Note: This commit only allocates struct pci_ser and preserves it across
Live Update. A subsequent commit will add an API for drivers to tell the
PCI core exactly which devices are being preserved.

Note: There is no reason to check for kho_is_enabled() since it can be
assumed to return true. If KHO was not enabled then Live Update would
not be enabled and these routines would never run.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 MAINTAINERS                    |  10 +++
 drivers/pci/Kconfig            |  15 ++++
 drivers/pci/Makefile           |   1 +
 drivers/pci/liveupdate.c       | 145 +++++++++++++++++++++++++++++++++
 include/linux/kho/abi/pci.h    |  61 ++++++++++++++
 include/linux/pci.h            |   1 +
 include/linux/pci_liveupdate.h |  30 +++++++
 7 files changed, 263 insertions(+)
 create mode 100644 drivers/pci/liveupdate.c
 create mode 100644 include/linux/kho/abi/pci.h
 create mode 100644 include/linux/pci_liveupdate.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..6c618830cf61 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20530,6 +20530,16 @@ L:	linux-pci at vger.kernel.org
 S:	Supported
 F:	Documentation/PCI/pci-error-recovery.rst
 
+PCI LIVE UPDATE
+M:	David Matlack <dmatlack at google.com>
+L:	kexec at lists.infradead.org
+L:	linux-pci at vger.kernel.org
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	drivers/pci/liveupdate.c
+F:	include/linux/kho/abi/pci.h
+F:	include/linux/pci_liveupdate.h
+
 PCI MSI DRIVER FOR ALTERA MSI IP
 L:	linux-pci at vger.kernel.org
 S:	Orphan
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 33c88432b728..10c9b65aa242 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -328,6 +328,21 @@ config VGA_ARB_MAX_GPUS
 	  Reserves space in the kernel to maintain resource locking for
 	  multiple GPUS.  The overhead for each GPU is very small.
 
+config PCI_LIVEUPDATE
+	bool "PCI Live Update Support"
+	depends on PCI && LIVEUPDATE
+	help
+	  Enable PCI core support for preserving PCI devices across Live
+	  Update. This, in combination with support in a device's driver,
+	  enables PCI devices to run and perform memory transactions
+	  uninterrupted during a kexec for Live Update.
+
+	  This option should only be enabled by users who plan to use Live
+	  Update for kernel upgrades and require preserving PCI devices during
+	  those upgrades.
+
+	  If unsure, say N.
+
 source "drivers/pci/hotplug/Kconfig"
 source "drivers/pci/controller/Kconfig"
 source "drivers/pci/endpoint/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..e8d003cb6757 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_PROC_FS)		+= proc.o
 obj-$(CONFIG_SYSFS)		+= pci-sysfs.o slot.o
 obj-$(CONFIG_ACPI)		+= pci-acpi.o
 obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o
+obj-$(CONFIG_PCI_LIVEUPDATE)	+= liveupdate.o
 endif
 
 obj-$(CONFIG_OF)		+= of.o
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
new file mode 100644
index 000000000000..737e7b9366db
--- /dev/null
+++ b/drivers/pci/liveupdate.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+
+/**
+ * DOC: PCI Live Update
+ *
+ * The PCI subsystem participates in the Live Update process to enable drivers
+ * to preserve their PCI devices across kexec.
+ *
+ * File-Lifecycle-Bound (FLB) Data
+ * ===============================
+ *
+ * PCI device preservation across Live Update is built on top of the Live Update
+ * Orchestrator's (LUO) support for file preservation across kexec. Drivers
+ * are expected to expose a file to represent a single PCI device and support
+ * preservation of that file with ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)``.
+ * This allows userspace to control the preservation of devices and ensure
+ * proper lifecycle management while a device is preserved. The first intended
+ * use-case is preserving vfio-pci device files.
+ *
+ * The PCI core maintains its own state about what devices are being preserved
+ * across Live Update using a feature called File-Lifecycle-Bound (FLB) data in
+ * LUO.  Essentially, this allows the PCI core to allocate struct pci_ser when
+ * the first device (file) is preserved and free it when the last device (file)
+ * is unpreserved. After kexec, the PCI core can fetch the struct pci_ser (which
+ * was constructed by the previous kernel) from LUO at any time (e.g. during
+ * enumeration) so that it knows which devices were preserved.
+ *
+ * To enable the PCI core to be notified whenever a file representing a device
+ * is preserved, drivers must register their struct liveupdate_file_handler with
+ * the PCI core by using the following APIs:
+ *
+ *  * ``pci_liveupdate_register_flb(driver_file_handler)``
+ *  * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ */
+
+#define pr_fmt(fmt) "PCI: liveupdate: " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/pci.h>
+#include <linux/liveupdate.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
+{
+	struct pci_dev *dev = NULL;
+	u32 max_nr_devices = 0;
+	struct pci_ser *ser;
+	unsigned long size;
+
+	/*
+	 * Allocate enough space to preserve all devices that are currently
+	 * present on the system. Extra padding can be added to this in the
+	 * future to increase the chances that there is enough room to preserve
+	 * devices that are not yet present on the system (e.g. VFs, hot-plugged
+	 * devices).
+	 */
+	for_each_pci_dev(dev)
+		max_nr_devices++;
+
+	size = struct_size_t(struct pci_ser, devices, max_nr_devices);
+
+	ser = kho_alloc_preserve(size);
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	pr_debug("Preserved struct pci_ser with room for %u devices\n",
+		 max_nr_devices);
+
+	ser->max_nr_devices = max_nr_devices;
+	ser->nr_devices = 0;
+
+	args->obj = ser;
+	args->data = virt_to_phys(ser);
+	return 0;
+}
+
+static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
+{
+	struct pci_ser *ser = args->obj;
+
+	WARN_ON(ser->nr_devices);
+	kho_unpreserve_free(ser);
+	pr_debug("Unpreserved struct pci_ser\n");
+}
+
+static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
+{
+	args->obj = phys_to_virt(args->data);
+	return 0;
+}
+
+static void pci_flb_finish(struct liveupdate_flb_op_args *args)
+{
+	kho_restore_free(args->obj);
+}
+
+static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
+	.preserve = pci_flb_preserve,
+	.unpreserve = pci_flb_unpreserve,
+	.retrieve = pci_flb_retrieve,
+	.finish = pci_flb_finish,
+	.owner = THIS_MODULE,
+};
+
+static struct liveupdate_flb pci_liveupdate_flb = {
+	.ops = &pci_liveupdate_flb_ops,
+	.compatible = PCI_LUO_FLB_COMPATIBLE,
+};
+
+/**
+ * pci_liveupdate_register_flb() - Register a file handler with the PCI core
+ * @fh: The file handler to register.
+ *
+ * Drivers should call pci_liveupdate_register_flb() to register their
+ * struct liveupdate_file_handler with the PCI core. This enables the PCI core
+ * to allocate its outgoing struct pci_ser whenever the first device is
+ * preserved, and free it when the last device is unpreserved.
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+	pr_debug("Registering file handler \"%s\"\n", fh->compatible);
+	return liveupdate_register_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
+
+/**
+ * pci_liveupdate_unregister_flb() - Unregister a file handler with the PCI core
+ * @fh: The file handler to unregister.
+ */
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+	pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
+	liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
new file mode 100644
index 000000000000..6ebcf817fff4
--- /dev/null
+++ b/include/linux/kho/abi/pci.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+
+#ifndef _LINUX_KHO_ABI_PCI_H
+#define _LINUX_KHO_ABI_PCI_H
+
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+/**
+ * DOC: PCI File-Lifecycle Bound (FLB) Live Update ABI
+ *
+ * This header defines the ABI for preserving core PCI state across kexec using
+ * Live Update File-Lifecycle Bound (FLB) data.
+ *
+ * This interface is a contract. Any modification to any of the serialization
+ * structs defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
+ */
+
+#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+
+/**
+ * struct pci_dev_ser - Serialized state about a single PCI device.
+ *
+ * @domain: The device's PCI domain number (segment).
+ * @bdf: The device's PCI bus, device, and function number.
+ * @padding: Padding to naturally align struct pci_dev_ser.
+ */
+struct pci_dev_ser {
+	u32 domain;
+	u16 bdf;
+	u16 padding;
+} __packed;
+
+/**
+ * struct pci_ser - PCI Subsystem Live Update State
+ *
+ * This struct tracks state about all devices that are being preserved across
+ * a Live Update for the next kernel.
+ *
+ * @max_nr_devices: The length of the devices[] flexible array.
+ * @nr_devices: The number of devices that were preserved.
+ * @devices: Flexible array of pci_dev_ser structs for each device.
+ */
+struct pci_ser {
+	u32 max_nr_devices;
+	u32 nr_devices;
+	struct pci_dev_ser devices[];
+} __packed;
+
+/* Ensure all elements of devices[] are naturally aligned. */
+static_assert(offsetof(struct pci_ser, devices) % sizeof(unsigned long) == 0);
+static_assert(sizeof(struct pci_dev_ser) % sizeof(unsigned long) == 0);
+
+#endif /* _LINUX_KHO_ABI_PCI_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..8cadeeab86fd 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -42,6 +42,7 @@
 #include <uapi/linux/pci.h>
 
 #include <linux/pci_ids.h>
+#include <linux/pci_liveupdate.h>
 
 #define PCI_STATUS_ERROR_BITS (PCI_STATUS_DETECTED_PARITY  | \
 			       PCI_STATUS_SIG_SYSTEM_ERROR | \
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
new file mode 100644
index 000000000000..8ec98beefcb4
--- /dev/null
+++ b/include/linux/pci_liveupdate.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (Public/Driver API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+#ifndef LINUX_PCI_LIVEUPDATE_H
+#define LINUX_PCI_LIVEUPDATE_H
+
+#include <linux/liveupdate.h>
+#include <linux/types.h>
+
+struct pci_dev;
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+#else
+static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+}
+#endif
+
+#endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:00 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:00 +0000
Subject: [PATCH v6 02/12] PCI: liveupdate: Track outgoing preserved PCI devices
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-3-dmatlack@google.com>

Add APIs to allow drivers to notify the PCI core of which devices are
being preserved across a Live Update for the next kernel, i.e.
"outgoing" devices.

Drivers must notify the PCI core when devices are preserved so that the
PCI core can update its FLB data (struct pci_ser) and track the list of
outgoing devices. pci_liveupdate_preserve() notifies the PCI core that a
device must be preserved across Live Update. pci_liveupdate_unpreserve()
reverses this (cancels the preservation of the device).

This tracking ensures the PCI core is fully aware of which devices may
need special handling during shutdown and kexec, and so that it can be
handed off to the next kernel.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 167 +++++++++++++++++++++++++++++++++
 drivers/pci/liveupdate.h       |  21 +++++
 drivers/pci/probe.c            |   2 +
 include/linux/kho/abi/pci.h    |   9 +-
 include/linux/pci.h            |   3 +
 include/linux/pci_liveupdate.h |  21 +++++
 6 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 drivers/pci/liveupdate.h

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 737e7b9366db..065d5af822f7 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -36,6 +36,26 @@
  *
  *  * ``pci_liveupdate_register_flb(driver_file_handler)``
  *  * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ *
+ * Device Tracking
+ * ===============
+ *
+ * Drivers must notify the PCI core when specific devices are preserved or
+ * unpreserved with the following APIs:
+ *
+ *  * ``pci_liveupdate_preserve(pci_dev)``
+ *  * ``pci_liveupdate_unpreserve(pci_dev)``
+ *
+ * This allows the PCI core to keep its FLB data (struct pci_ser) up to date
+ * with the list of **outgoing** preserved devices for the next kernel.
+ *
+ * Restrictions
+ * ============
+ *
+ * The PCI core enforces the following restrictions on which devices can be
+ * preserved. These may be relaxed in the future:
+ *
+ *  * The device cannot be a Virtual Function (VF).
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -48,6 +68,21 @@
 #include <linux/mm.h>
 #include <linux/pci.h>
 
+#include "liveupdate.h"
+
+/**
+ * struct pci_liveupdate_global - Global state for PCI Live Update support
+ * @rwsem: Reader/writer semaphore used to protect the incoming and outgoing
+ *         FLBs, and the references to them in struct pci_dev.
+ */
+struct pci_liveupdate_global {
+	struct rw_semaphore rwsem;
+};
+
+static struct pci_liveupdate_global pci_liveupdate = {
+	.rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem),
+};
+
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_dev *dev = NULL;
@@ -115,6 +150,138 @@ static struct liveupdate_flb pci_liveupdate_flb = {
 	.compatible = PCI_LUO_FLB_COMPATIBLE,
 };
 
+static struct pci_ser *pci_liveupdate_flb_get_outgoing(void)
+{
+	struct pci_ser *ser = NULL;
+	int ret;
+
+	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&ser);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (!ser)
+		return ERR_PTR(-ENOENT);
+
+	return ser;
+}
+
+static void pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
+
+	if (!dev_ser) {
+		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
+		return;
+	}
+
+	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
+	ser->nr_devices--;
+	memset(dev_ser, 0, sizeof(*dev_ser));
+	dev->liveupdate.outgoing = NULL;
+}
+
+static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	int i;
+
+	if (dev->liveupdate.outgoing)
+		return -EBUSY;
+
+	if (ser->nr_devices == ser->max_nr_devices)
+		return -ENOSPC;
+
+	for (i = 0; i < ser->max_nr_devices; i++) {
+		/*
+		 * Start searching at index ser->nr_devices. This should result
+		 * in a constant time search under expected conditions (devices
+		 * are not getting unpreserved).
+		 */
+		int index = (ser->nr_devices + i) % ser->max_nr_devices;
+		struct pci_dev_ser *dev_ser = &ser->devices[index];
+
+		if (dev_ser->refcount)
+			continue;
+
+		pci_info(dev, "Device will be preserved across next Live Update\n");
+		ser->nr_devices++;
+
+		dev_ser->domain = pci_domain_nr(dev->bus);
+		dev_ser->bdf = pci_dev_id(dev);
+		dev_ser->refcount = 1;
+
+		dev->liveupdate.outgoing = dev_ser;
+		return 0;
+	}
+
+	return -ENOSPC;
+}
+
+/**
+ * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
+ * @dev: The PCI device to preserve.
+ *
+ * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be
+ * preserved across the next Live Update. Drivers are expected to call
+ * pci_liveupdate_preserve() from their struct liveupdate_file_handler
+ * preserve() callback to ensure the outgoing struct pci_ser is already set up.
+ *
+ * Returns: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+	struct pci_ser *ser = NULL;
+
+	if (dev->is_virtfn)
+		return -EINVAL;
+
+	guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+	ser = pci_liveupdate_flb_get_outgoing();
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	return pci_liveupdate_preserve_device(ser, dev);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
+
+/**
+ * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device
+ * @dev: The PCI device to unpreserve.
+ *
+ * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no
+ * longer be preserved across the next Live Update. Drivers are expected to call
+ * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
+ * unpreserve() callback to ensure the outgoing struct pci_ser is already set
+ * up.
+ */
+void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+	struct pci_ser *ser = NULL;
+
+	guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+	ser = pci_liveupdate_flb_get_outgoing();
+	if (IS_ERR(ser)) {
+		pci_warn(dev, "Cannot unpreserve device without outgoing Live Update state\n");
+		return;
+	}
+
+	pci_liveupdate_unpreserve_device(ser, dev);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
+
+void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+	/*
+	 * It should be safe to READ_ONCE() outside of the rwsem during cleanup
+	 * since there should no longer be any references to @dev on the system.
+	 */
+	if (READ_ONCE(dev->liveupdate.outgoing)) {
+		pci_WARN(dev, 1, "Destroying outgoing-preserved device!\n");
+		pci_liveupdate_unpreserve(dev);
+	}
+}
+
 /**
  * pci_liveupdate_register_flb() - Register a file handler with the PCI core
  * @fh: The file handler to register.
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
new file mode 100644
index 000000000000..b2335581f8d0
--- /dev/null
+++ b/drivers/pci/liveupdate.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (core API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack at google.com>
+ */
+#ifndef DRIVERS_PCI_LIVEUPDATE_H
+#define DRIVERS_PCI_LIVEUPDATE_H
+
+#include <linux/pci.h>
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+#else
+static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+}
+#endif
+
+#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..b88109a8dfe4 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -24,6 +24,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/bitfield.h>
 #include <trace/events/pci.h>
+#include "liveupdate.h"
 #include "pci.h"
 
 static struct resource busn_resource = {
@@ -2490,6 +2491,7 @@ static void pci_release_dev(struct device *dev)
 
 	pci_dev = to_pci_dev(dev);
 	pci_release_capabilities(pci_dev);
+	pci_liveupdate_cleanup_device(pci_dev);
 	pci_release_of_node(pci_dev);
 	pcibios_release_device(pci_dev);
 	pci_bus_put(pci_dev->bus);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 6ebcf817fff4..85def616703d 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,19 +23,22 @@
  * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
  */
 
-#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
 
 /**
  * struct pci_dev_ser - Serialized state about a single PCI device.
  *
  * @domain: The device's PCI domain number (segment).
  * @bdf: The device's PCI bus, device, and function number.
- * @padding: Padding to naturally align struct pci_dev_ser.
+ * @refcount: Reference count used by the PCI core to keep track of whether it
+ *            is done using a device's struct pci_dev_ser. The value of the
+ *            refcount is equal to 1 when the struct pci_dev_ser is in use, and
+ *            0 otherwise.
  */
 struct pci_dev_ser {
 	u32 domain;
 	u16 bdf;
-	u16 padding;
+	u16 refcount;
 } __packed;
 
 /**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 8cadeeab86fd..a7c3722b1e77 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -594,6 +594,9 @@ struct pci_dev {
 	u8		tph_mode;	/* TPH mode */
 	u8		tph_req_type;	/* TPH requester type */
 #endif
+#ifdef CONFIG_PCI_LIVEUPDATE
+	struct pci_liveupdate liveupdate;
+#endif
 };
 
 static inline struct pci_dev *pci_physfn(struct pci_dev *dev)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 8ec98beefcb4..cfcfbfa73af7 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -8,14 +8,26 @@
 #ifndef LINUX_PCI_LIVEUPDATE_H
 #define LINUX_PCI_LIVEUPDATE_H
 
+#include <linux/kho/abi/pci.h>
 #include <linux/liveupdate.h>
 #include <linux/types.h>
+#include <linux/spinlock_types.h>
+
+/**
+ * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
+ * @outgoing: State preserved for the next kernel.
+ */
+struct pci_liveupdate {
+	struct pci_dev_ser *outgoing;
+};
 
 struct pci_dev;
 
 #ifdef CONFIG_PCI_LIVEUPDATE
 int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
 void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+int pci_liveupdate_preserve(struct pci_dev *dev);
+void pci_liveupdate_unpreserve(struct pci_dev *dev);
 #else
 static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
 {
@@ -25,6 +37,15 @@ static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh
 static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
 {
 }
+
+static inline int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+}
 #endif
 
 #endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:01 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:01 +0000
Subject: [PATCH v6 03/12] PCI: liveupdate: Track incoming preserved PCI devices
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-4-dmatlack@google.com>

During PCI enumeration, the previous kernel might have passed state about
devices that were preserved across kexec. The PCI core needs to fetch
this state to identify which devices are "incoming" and require special
handling.

Add pci_liveupdate_setup_device() which is called during device setup
to fetch the serialized state (struct pci_ser) from the Live Update
Orchestrator. The first time this happens, pci_flb_retrieve() will run
and convert the array of pci_dev_ser structs into an xarray so that it
can be looked up efficiently.

If a device is found in the xarray, the PCI core stores a pointer to its
state in dev->liveupdate_incoming and holds a reference to the incoming
FLB until pci_liveupdate_finish() is called by the driver.

This ensures proper lifecycle management for incoming preserved devices
and allows the PCI core and drivers to apply specific Live Update
logic to them in subsequent commits.

Drivers can check if a device is an incoming preserved device (e.g.
during probe) by calling pci_liveupdate_is_incoming().

CONFIG_64BIT is now required to enable CONFIG_PCI_LIVEUPDATE so that the
domain and bdf can be guaranteed to fit in an unsigned long and be used
as the xarray key.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 MAINTAINERS                    |   1 +
 drivers/pci/Kconfig            |   2 +-
 drivers/pci/liveupdate.c       | 230 ++++++++++++++++++++++++++++++++-
 drivers/pci/liveupdate.h       |   5 +
 drivers/pci/probe.c            |   3 +
 include/linux/pci_liveupdate.h |  13 ++
 6 files changed, 251 insertions(+), 3 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6c618830cf61..0e262c0ceb43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20537,6 +20537,7 @@ L:	linux-pci at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
 F:	drivers/pci/liveupdate.c
+F:	drivers/pci/liveupdate.h
 F:	include/linux/kho/abi/pci.h
 F:	include/linux/pci_liveupdate.h
 
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 10c9b65aa242..e68ae5c172d4 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
 
 config PCI_LIVEUPDATE
 	bool "PCI Live Update Support"
-	depends on PCI && LIVEUPDATE
+	depends on PCI && LIVEUPDATE && 64BIT
 	help
 	  Enable PCI core support for preserving PCI devices across Live
 	  Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 065d5af822f7..96c43b84532c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -49,6 +49,20 @@
  * This allows the PCI core to keep its FLB data (struct pci_ser) up to date
  * with the list of **outgoing** preserved devices for the next kernel.
  *
+ * After kexec, whenever a device is enumerated, the PCI core will check if it
+ * is an **incoming** preserved device (i.e. preserved by the previous kernel)
+ * by checking the incoming FLB data (struct pci_ser).
+ *
+ * Drivers must notify the PCI core when an **incoming** device is done
+ * participating in the incoming Live Update with the following API:
+ *
+ *  * ``pci_liveupdate_finish(pci_dev)``
+ *
+ * The PCI core does not enforce any ordering of ``pci_liveupdate_finish()`` and
+ * ``pci_liveupdate_preserve()``. i.e. A PCI device can be **outgoing**
+ * (preserved for next kernel) and **incoming** (preserved by previous kernel)
+ * at the same time.
+ *
  * Restrictions
  * ============
  *
@@ -83,6 +97,21 @@ static struct pci_liveupdate_global pci_liveupdate = {
 	.rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem),
 };
 
+/**
+ * struct pci_flb_incoming - Incoming PCI FLB object
+ * @ser: The incoming struct pci_ser from the previous kernel.
+ * @xa: Xarray used to quickly lookup devices in @ser.
+ */
+struct pci_flb_incoming {
+	struct pci_ser *ser;
+	struct xarray xa;
+};
+
+static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
+{
+	return (unsigned long)domain << 16 | bdf;
+}
+
 static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
 {
 	struct pci_dev *dev = NULL;
@@ -128,13 +157,49 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
 
 static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
 {
-	args->obj = phys_to_virt(args->data);
+	struct pci_ser *ser = phys_to_virt(args->data);
+	struct pci_flb_incoming *incoming;
+	int ret = -ENOMEM;
+	u32 i;
+
+	incoming = kmalloc_obj(*incoming);
+	if (!incoming)
+		goto err_restore_free;
+
+	incoming->ser = ser;
+	xa_init(&incoming->xa);
+
+	for (i = 0; i < incoming->ser->max_nr_devices; i++) {
+		struct pci_dev_ser *dev_ser = &incoming->ser->devices[i];
+		unsigned long key;
+
+		if (!dev_ser->refcount)
+			continue;
+
+		key = pci_ser_xa_key(dev_ser->domain, dev_ser->bdf);
+		ret = xa_insert(&incoming->xa, key, dev_ser, GFP_KERNEL);
+		if (ret)
+			goto err_xa_destroy;
+	}
+
+	args->obj = incoming;
 	return 0;
+
+err_xa_destroy:
+	xa_destroy(&incoming->xa);
+	kfree(incoming);
+err_restore_free:
+	kho_restore_free(ser);
+	return ret;
 }
 
 static void pci_flb_finish(struct liveupdate_flb_op_args *args)
 {
-	kho_restore_free(args->obj);
+	struct pci_flb_incoming *incoming = args->obj;
+
+	xa_destroy(&incoming->xa);
+	kho_restore_free(incoming->ser);
+	kfree(incoming);
 }
 
 static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
@@ -270,6 +335,91 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
 
+static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
+{
+	struct pci_flb_incoming *incoming = NULL;
+	int ret;
+
+	ret = liveupdate_flb_get_incoming(&pci_liveupdate_flb, (void **)&incoming);
+
+	/* Live Update is not enabled. */
+	if (ret == -EOPNOTSUPP)
+		return NULL;
+
+	/* Live Update is enabled, but there is no incoming FLB data. */
+	if (ret == -ENODATA)
+		return NULL;
+
+	/*
+	 * Live Update is enabled and there is incoming FLB data, but none of it
+	 * matches pci_liveupdate_flb.compatible.
+	 *
+	 * This could mean that no PCI FLB data was passed by the previous
+	 * kernel, but it could also mean the previous kernel used a different
+	 * compatibility string (i.e. a different ABI).
+	 */
+	if (ret == -ENOENT) {
+		pr_info_once("No incoming FLB matched %s\n", pci_liveupdate_flb.compatible);
+		return NULL;
+	}
+
+	/*
+	 * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+	 * but it cannot be retrieved.
+	 */
+	if (ret) {
+		WARN_ONCE(ret, "Failed to retrieve incoming FLB data\n");
+		return NULL;
+	}
+
+	return incoming;
+}
+
+static void pci_liveupdate_flb_put_incoming(void)
+{
+	liveupdate_flb_put_incoming(&pci_liveupdate_flb);
+}
+
+void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+	struct pci_flb_incoming *incoming;
+	struct pci_dev_ser *dev_ser;
+	unsigned long key;
+
+	guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+	incoming = pci_liveupdate_flb_get_incoming();
+	if (!incoming)
+		return;
+
+	key = pci_ser_xa_key(pci_domain_nr(dev->bus), pci_dev_id(dev));
+	dev_ser = xa_load(&incoming->xa, key);
+
+	/* This device was not preserved across Live Update */
+	if (!dev_ser) {
+		pci_liveupdate_flb_put_incoming();
+		return;
+	}
+
+	/*
+	 * This device was preserved, but has already been probed and gone
+	 * through pci_liveupdate_finish(). This can happen if PCI core probes
+	 * the same device multiple times, e.g. due to hotplug.
+	 */
+	if (!dev_ser->refcount) {
+		pci_liveupdate_flb_put_incoming();
+		return;
+	}
+
+	pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
+	dev->liveupdate.incoming = dev_ser;
+
+	/*
+	 * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
+	 * that dev->liveupdate.incoming does not get freed while it is in use.
+	 */
+}
+
 void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 	/*
@@ -280,7 +430,83 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 		pci_WARN(dev, 1, "Destroying outgoing-preserved device!\n");
 		pci_liveupdate_unpreserve(dev);
 	}
+
+	if (READ_ONCE(dev->liveupdate.incoming)) {
+		pci_WARN(dev, 1, "Destroying incoming-preserved device!\n");
+		pci_liveupdate_finish(dev);
+	}
+}
+
+static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	if (!dev->liveupdate.incoming) {
+		pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
+		return;
+	}
+
+	if (dev->liveupdate.incoming->refcount != 1) {
+		pci_WARN(dev, 1, "Preserved device has a corrupted refcount!\n");
+		return;
+	}
+
+	/*
+	 * Drop the refcount so this device does not get treated as an incoming
+	 * device again, e.g. in case pci_liveupdate_setup_device() gets called
+	 * again because the device is hot-plugged.
+	 */
+	dev->liveupdate.incoming->refcount = 0;
+
+	pci_info(dev, "Device is finished participating in Live Update\n");
+	dev->liveupdate.incoming = NULL;
+	ser->nr_devices--;
+	pci_liveupdate_flb_put_incoming();
+}
+
+/**
+ * pci_liveupdate_finish() - Finish the preservation of a PCI device
+ * @dev: The PCI device
+ *
+ * pci_liveupdate_finish() notifies the PCI core that a PCI device that was
+ * preserved across the previous Live Update has finished participating in Live
+ * Update. Drivers must call pci_liveupdate_finish() from their struct
+ * liveupdate_file_handler finish() callback to ensure the incoming struct
+ * pci_ser is allocated.
+ */
+void pci_liveupdate_finish(struct pci_dev *dev)
+{
+	struct pci_flb_incoming *incoming;
+
+	guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+	incoming = pci_liveupdate_flb_get_incoming();
+	if (!incoming) {
+		pci_warn(dev, "Cannot finish preserving device without incoming FLB\n");
+		return;
+	}
+
+	pci_liveupdate_finish_device(incoming->ser, dev);
+	pci_liveupdate_flb_put_incoming();
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
+
+/**
+ * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
+ * @dev: The PCI device to check
+ *
+ * Check if a device was preserved across Live Update by the previous kernel,
+ * i.e. the device is incoming-preserved. Note that a device is only considered
+ * incoming-preserved prior to pci_liveupdate_finish(). It is up to drivers to
+ * synchronize usage of pci_liveupdate_is_incoming() with their own call to
+ * pci_liveupdate_finish() to avoid acting on stale data.
+ *
+ * Returns: True if the device is incoming-preserved, false otherwise.
+ */
+bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+	guard(rwsem_read)(&pci_liveupdate.rwsem);
+	return dev->liveupdate.incoming;
 }
+EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
 
 /**
  * pci_liveupdate_register_flb() - Register a file handler with the PCI core
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index b2335581f8d0..eaaa3559fd77 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -11,8 +11,13 @@
 #include <linux/pci.h>
 
 #ifdef CONFIG_PCI_LIVEUPDATE
+void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
 #else
+static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+}
+
 static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 }
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b88109a8dfe4..2e2be8af6976 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2070,6 +2070,8 @@ int pci_setup_device(struct pci_dev *dev)
 	if (pci_early_dump)
 		early_dump_pci_device(dev);
 
+	pci_liveupdate_setup_device(dev);
+
 	/* Need to have dev->class ready */
 	dev->cfg_size = pci_cfg_space_size(dev);
 
@@ -2193,6 +2195,7 @@ int pci_setup_device(struct pci_dev *dev)
 	default:				    /* unknown header */
 		pci_err(dev, "unknown header type %02x, ignoring device\n",
 			dev->hdr_type);
+		pci_liveupdate_cleanup_device(dev);
 		pci_release_of_node(dev);
 		return -EIO;
 
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index cfcfbfa73af7..cfdc3d62ec02 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -16,9 +16,11 @@
 /**
  * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
  * @outgoing: State preserved for the next kernel.
+ * @incoming: State preserved by the previous kernel.
  */
 struct pci_liveupdate {
 	struct pci_dev_ser *outgoing;
+	struct pci_dev_ser *incoming;
 };
 
 struct pci_dev;
@@ -28,6 +30,8 @@ int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
 void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
 int pci_liveupdate_preserve(struct pci_dev *dev);
 void pci_liveupdate_unpreserve(struct pci_dev *dev);
+void pci_liveupdate_finish(struct pci_dev *dev);
+bool pci_liveupdate_is_incoming(struct pci_dev *dev);
 #else
 static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
 {
@@ -46,6 +50,15 @@ static inline int pci_liveupdate_preserve(struct pci_dev *dev)
 static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
 {
 }
+
+static inline void pci_liveupdate_finish(struct pci_dev *dev)
+{
+}
+
+static inline bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* LINUX_PCI_LIVEUPDATE_H */
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:02 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:02 +0000
Subject: [PATCH v6 04/12] PCI: liveupdate: Document driver binding responsibilities
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-5-dmatlack@google.com>

Document how driver binding works during a Live Update and what the PCI
core expects of drivers and users. Note that this is only a description
of the current division of responsibilities. These can change in the
future if we decide.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 96c43b84532c..4f2ec6ffdd16 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -70,6 +70,22 @@
  * preserved. These may be relaxed in the future:
  *
  *  * The device cannot be a Virtual Function (VF).
+ *
+ * Driver Binding
+ * ==============
+ *
+ * In the outgoing kernel, it is the driver's responsibility to ensure that it
+ * does not release a device between pci_liveupdate_preserve() and
+ * pci_liveupdate_unpreserve().
+ *
+ * In the incoming kernel, it is the driver's responsibility to ensure that it
+ * does not release a preserved device between probe() and
+ * pci_liveupdate_finish().
+ *
+ * It is the user's responsibility to ensure that incoming preserved devices are
+ * bound to the correct driver. i.e. The PCI core does not protect against a
+ * device getting preserved by driver A in the outgoing kernel and then getting
+ * bound to driver B in the incoming kernel.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:03 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:03 +0000
Subject: [PATCH v6 05/12] PCI: liveupdate: Keep bus numbers constant during
 Live Update
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-6-dmatlack@google.com>

During a Live Update, preserved devices must be allowed to continue
performing memory transactions so the kernel cannot change the fabric
topology, including bus numbers, since that would require disabling
and flushing any memory transactions first.

To keep bus numbers constant, always inherit the secondary and
subordinate bus numbers assigned to bridges during scanning, instead of
assigning new ones, if any PCI devices are being preserved. Note that
the kernel inherits bus numbers even on bridges without any downstream
endpoints that were preserved. This avoids accidentally assigning a
bridge a new window that overlaps with a preserved device that is
downstream of a different bridge.

If a bridge is scanned with a broken topology or has no bus numbers
set during a Live Update, refuse to assign it new bus numbers and refuse
to enumerate devices below it until the Live Update is finished. This is
a safety measure to prevent topology conflicts.

Require that CONFIG_CARDBUS is not enabled to enable
CONFIG_PCI_LIVEUPDATE since inheriting bus numbers on PCI-to-CardBus
bridges requires additional work but is not a priority at the moment.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 .../admin-guide/kernel-parameters.txt         |  6 +-
 drivers/pci/Kconfig                           |  2 +-
 drivers/pci/liveupdate.c                      | 83 ++++++++++++++++++-
 drivers/pci/liveupdate.h                      | 14 ++++
 drivers/pci/probe.c                           | 17 +++-
 include/linux/pci_liveupdate.h                |  4 +
 6 files changed, 119 insertions(+), 7 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..a64af71c2705 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5138,7 +5138,11 @@ Kernel parameters
 				explicitly which ones they are.
 		assign-busses	[X86] Always assign all PCI bus
 				numbers ourselves, overriding
-				whatever the firmware may have done.
+				whatever the firmware may have done. Ignored
+				during a Live Update, where the kernel must
+				inherit the PCI topology (including bus numbers)
+				to avoid interrupting ongoing memory
+				transactions of preserved devices.
 		usepirqmask	[X86] Honor the possible IRQ mask stored
 				in the BIOS $PIR table. This is needed on
 				some systems with broken BIOSes, notably
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index e68ae5c172d4..a597fede1b3b 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
 
 config PCI_LIVEUPDATE
 	bool "PCI Live Update Support"
-	depends on PCI && LIVEUPDATE && 64BIT
+	depends on PCI && LIVEUPDATE && 64BIT && !CARDBUS
 	help
 	  Enable PCI core support for preserving PCI devices across Live
 	  Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 4f2ec6ffdd16..2421bc218916 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -86,6 +86,21 @@
  * bound to the correct driver. i.e. The PCI core does not protect against a
  * device getting preserved by driver A in the outgoing kernel and then getting
  * bound to driver B in the incoming kernel.
+ *
+ * BDF Stability
+ * =============
+ *
+ * The PCI core guarantees that preserved devices can be identified by the same
+ * bus, device, and function numbers for as long as they are preserved
+ * (including across kexec). To accomplish this, the PCI core always inherits
+ * the secondary and subordinate bus numbers assigned to bridges during scanning
+ * if any device is preserved. This is true even on architectures that always
+ * assign new bus numbers during scanning. The kernel assumes the previous
+ * kernel established a sane bus topology across kexec.
+ *
+ * If a misconfigured or unconfigured bridge is encountered during enumeration
+ * while there are preserved devices, its secondary and subordinate bus numbers
+ * will be cleared and devices below it will not be enumerated.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -103,7 +118,7 @@
 /**
  * struct pci_liveupdate_global - Global state for PCI Live Update support
  * @rwsem: Reader/writer semaphore used to protect the incoming and outgoing
- *         FLBs, and the references to them in struct pci_dev.
+ *         FLBs and references to them in struct pci_dev.
  */
 struct pci_liveupdate_global {
 	struct rw_semaphore rwsem;
@@ -396,6 +411,72 @@ static void pci_liveupdate_flb_put_incoming(void)
 	liveupdate_flb_put_incoming(&pci_liveupdate_flb);
 }
 
+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+				      int pass)
+{
+	struct pci_dev *parent = bus->self;
+
+	/*
+	 * On the second pass, reuse the value that was set on the first pass
+	 * so that the passes are consistent with one another.
+	 */
+	if (pass)
+		return dev->liveupdate.inherit_buses;
+
+	/*
+	 * If the parent bridge is being forced to inherit its bus numbers
+	 * during this scan then this bridge must as well, otherwise the PCI
+	 * core could expand this bridge's reservation beyond its parent (which
+	 * cannot expand).
+	 */
+	if (parent && parent->liveupdate.inherit_buses) {
+		dev->liveupdate.inherit_buses = true;
+		goto out;
+	}
+
+	/*
+	 * Otherwise, if there are any incoming preserved devices, force the
+	 * bus numbers to be inherited to avoid changing the bus numbers
+	 * assigned to those devices during enumeration.
+	 *
+	 * To keep things simple, inherit bus numbers on all bridges if any PCI
+	 * devices are incoming, to ensure that no bridge's reservation is
+	 * expanded to overlap with a preserved device downstream of a different
+	 * bridge.
+	 */
+	scoped_guard(rwsem_read, &pci_liveupdate.rwsem) {
+		struct pci_flb_incoming *incoming;
+
+		incoming = pci_liveupdate_flb_get_incoming();
+		if (!incoming) {
+			dev->liveupdate.inherit_buses = false;
+			goto out;
+		}
+
+		/*
+		 * It is safe to sample incoming->ser->nr_devices and then
+		 * drop the rwsem since nr_devices will only decrease. Thus the
+		 * only "race" is that the current scan will be overly
+		 * conservative and force bus inheritance.
+		 */
+		dev->liveupdate.inherit_buses = incoming->ser->nr_devices;
+		pci_liveupdate_flb_put_incoming();
+	}
+
+out:
+	return dev->liveupdate.inherit_buses;
+}
+
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+	/*
+	 * Clear inherit_buses after the second pass so it can be re-evaluated
+	 * on future scans.
+	 */
+	if (pass)
+		dev->liveupdate.inherit_buses = false;
+}
+
 void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
 	struct pci_flb_incoming *incoming;
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index eaaa3559fd77..c763255a8de4 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,9 @@
 #ifdef CONFIG_PCI_LIVEUPDATE
 void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+				      int pass);
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -21,6 +24,17 @@ static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 }
+
+static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
+						    struct pci_dev *dev,
+						    int pass)
+{
+	return false;
+}
+
+static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 2e2be8af6976..19965bfd347d 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1402,6 +1402,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 				  int max, unsigned int available_buses,
 				  int pass)
 {
+	bool liveupdate, assign_new_buses = pcibios_assign_all_busses();
 	struct pci_bus *child;
 	u32 buses;
 	u16 bctl;
@@ -1411,6 +1412,10 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 	u8 fixed_sec, fixed_sub;
 	int next_busnr;
 
+	liveupdate = pci_liveupdate_scan_bridge_begin(bus, dev, pass);
+	if (liveupdate)
+		assign_new_buses = false;
+
 	/*
 	 * Make sure the bridge is powered on to be able to access config
 	 * space of devices below it.
@@ -1454,8 +1459,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 		goto out;
 	}
 
-	if ((secondary || subordinate) &&
-	    !pcibios_assign_all_busses() && !broken) {
+	if ((secondary || subordinate) && !assign_new_buses && !broken) {
 		unsigned int cmax, buses;
 
 		/*
@@ -1497,8 +1501,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 		 * do in the second pass.
 		 */
 		if (!pass) {
-			if (pcibios_assign_all_busses() || broken)
-
+			if (assign_new_buses || broken)
 				/*
 				 * Temporarily disable forwarding of the
 				 * configuration cycles on all bridges in
@@ -1512,6 +1515,11 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 			goto out;
 		}
 
+		if (liveupdate) {
+			pci_err(dev, "Cannot reconfigure bridge during Live Update, skipping\n");
+			goto out;
+		}
+
 		/* Clear errors */
 		pci_write_config_word(dev, PCI_STATUS, 0xffff);
 
@@ -1572,6 +1580,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 	pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);
 
 	pm_runtime_put(&dev->dev);
+	pci_liveupdate_scan_bridge_end(dev, pass);
 
 	return max;
 }
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index cfdc3d62ec02..2be98819e313 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -17,10 +17,14 @@
  * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
  * @outgoing: State preserved for the next kernel.
  * @incoming: State preserved by the previous kernel.
+ * @inherit_buses: True if the PCI core should inherit the secondary and
+ *                 subordinate bus numbers assigned to this device due to
+ *                 an ongoing Live Update.
  */
 struct pci_liveupdate {
 	struct pci_dev_ser *outgoing;
 	struct pci_dev_ser *incoming;
+	bool inherit_buses;
 };
 
 struct pci_dev;
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:04 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:04 +0000
Subject: [PATCH v6 06/12] PCI: liveupdate: Auto-preserve upstream bridges
 across Live Update
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-7-dmatlack@google.com>

When a PCI device is preserved across a Live Update, all of its upstream
bridges up to the root port must also be preserved. This enables the PCI
core and any drivers bound to the bridges to manage bridges correctly
across a Live Update.

Notably, this will be used in subsequent commits to ensure that
preserved devices can continue performing memory transactions without a
disruption or change in routing.

To preserve bridges, the PCI core tracks the number of downstream
devices preserved under each bridge using a reference count in struct
pci_dev_ser. This allows a bridge to remain preserved until all its
downstream preserved devices are unpreserved or finish their
participation in the Live Update.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c    | 136 +++++++++++++++++++++++++++++++-----
 include/linux/kho/abi/pci.h |   5 +-
 2 files changed, 122 insertions(+), 19 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 2421bc218916..4c79e19b7f98 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -101,6 +101,18 @@
  * If a misconfigured or unconfigured bridge is encountered during enumeration
  * while there are preserved devices, its secondary and subordinate bus numbers
  * will be cleared and devices below it will not be enumerated.
+ *
+ * PCI-to-PCI Bridges
+ * ==================
+ *
+ * Any PCI-to-PCI bridges upstream of a preserved device are automatically
+ * preserved when the device is preserved. The PCI core keeps track of the
+ * number of downstream devices that are preserved under a bridge so that the
+ * bridge is only unpreserved once all downstream devices are unpreserved.
+ *
+ * This enables the PCI core and any drivers bound to the bridge to participate
+ * in the Live Update so that preserved endpoints can continue issuing memory
+ * transactions during the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -261,28 +273,52 @@ static struct pci_ser *pci_liveupdate_flb_get_outgoing(void)
 	return ser;
 }
 
-static void pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
 {
 	struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
 
 	if (!dev_ser) {
 		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
-		return;
+		return -EINVAL;
+	}
+
+	if (!dev_ser->refcount) {
+		pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+		return -EINVAL;
 	}
 
+	if (--dev_ser->refcount)
+		return 0;
+
 	pci_info(dev, "Device will no longer be preserved across next Live Update\n");
 	ser->nr_devices--;
 	memset(dev_ser, 0, sizeof(*dev_ser));
 	dev->liveupdate.outgoing = NULL;
+	return 0;
 }
 
-static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_preserve_device_again(struct pci_dev *dev)
 {
-	int i;
+	if (!dev->liveupdate.outgoing->refcount) {
+		pci_WARN(dev, 1, "Preserved device with 0 refcount!\n");
+		return -EINVAL;
+	}
 
-	if (dev->liveupdate.outgoing)
+	/*
+	 * Endpoint devices should not be preserved more than once. Bridges are
+	 * preserved once for every downstream device that is preserved.
+	 */
+	if (!dev->subordinate)
 		return -EBUSY;
 
+	dev->liveupdate.outgoing->refcount++;
+	return 0;
+}
+
+static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	int i;
+
 	if (ser->nr_devices == ser->max_nr_devices)
 		return -ENOSPC;
 
@@ -312,6 +348,52 @@ static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *d
 	return -ENOSPC;
 }
 
+static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+	if (dev->liveupdate.outgoing)
+		return pci_liveupdate_preserve_device_again(dev);
+
+	return __pci_liveupdate_preserve_device(ser, dev);
+}
+
+#define for_each_pci_dev_in_path(_d, _start, _end) \
+	for ((_d) = (_start); (_d) != (_end); (_d) = (_d)->bus->self)
+
+static void __pci_liveupdate_unpreserve_path(struct pci_ser *ser,
+					     struct pci_dev *start,
+					     struct pci_dev *end)
+{
+	struct pci_dev *dev;
+
+	for_each_pci_dev_in_path(dev, start, end) {
+		if (pci_liveupdate_unpreserve_device(ser, dev))
+			return;
+	}
+}
+
+static void pci_liveupdate_unpreserve_path(struct pci_ser *ser,
+					   struct pci_dev *start)
+{
+	__pci_liveupdate_unpreserve_path(ser, start, /*end=*/NULL);
+}
+
+static int pci_liveupdate_preserve_path(struct pci_ser *ser,
+					struct pci_dev *start)
+{
+	struct pci_dev *dev;
+	int ret;
+
+	for_each_pci_dev_in_path(dev, start, NULL) {
+		ret = pci_liveupdate_preserve_device(ser, dev);
+		if (ret) {
+			__pci_liveupdate_unpreserve_path(ser, start, dev);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
 /**
  * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
  * @dev: The PCI device to preserve.
@@ -321,6 +403,9 @@ static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *d
  * pci_liveupdate_preserve() from their struct liveupdate_file_handler
  * preserve() callback to ensure the outgoing struct pci_ser is already set up.
  *
+ * pci_liveupdate_preserve() automatically preserves all bridges upstream of
+ * @dev.
+ *
  * Returns: 0 on success, <0 on failure.
  */
 int pci_liveupdate_preserve(struct pci_dev *dev)
@@ -336,7 +421,7 @@ int pci_liveupdate_preserve(struct pci_dev *dev)
 	if (IS_ERR(ser))
 		return PTR_ERR(ser);
 
-	return pci_liveupdate_preserve_device(ser, dev);
+	return pci_liveupdate_preserve_path(ser, dev);
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
 
@@ -349,6 +434,9 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
  * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
  * unpreserve() callback to ensure the outgoing struct pci_ser is already set
  * up.
+ *
+ * pci_liveupdate_unpreserve() automatically unpreserves all bridges upstream of
+ * @dev.
  */
 void pci_liveupdate_unpreserve(struct pci_dev *dev)
 {
@@ -362,7 +450,7 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
 		return;
 	}
 
-	pci_liveupdate_unpreserve_device(ser, dev);
+	pci_liveupdate_unpreserve_path(ser, dev);
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
 
@@ -534,29 +622,41 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 	}
 }
 
-static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
 {
 	if (!dev->liveupdate.incoming) {
 		pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
-		return;
+		return -EINVAL;
 	}
 
-	if (dev->liveupdate.incoming->refcount != 1) {
-		pci_WARN(dev, 1, "Preserved device has a corrupted refcount!\n");
-		return;
+	if (!dev->liveupdate.incoming->refcount) {
+		pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+		return -EINVAL;
 	}
 
 	/*
-	 * Drop the refcount so this device does not get treated as an incoming
-	 * device again, e.g. in case pci_liveupdate_setup_device() gets called
-	 * again because the device is hot-plugged.
+	 * Decrement the refcount so this device does not get treated as an
+	 * incoming device again, e.g. in case pci_liveupdate_setup_device()
+	 * gets called again because the device is hot-plugged.
 	 */
-	dev->liveupdate.incoming->refcount = 0;
+	if (--dev->liveupdate.incoming->refcount)
+		return 0;
 
 	pci_info(dev, "Device is finished participating in Live Update\n");
 	dev->liveupdate.incoming = NULL;
 	ser->nr_devices--;
 	pci_liveupdate_flb_put_incoming();
+	return 0;
+}
+
+static void pci_liveupdate_finish_path(struct pci_ser *ser, struct pci_dev *start)
+{
+	struct pci_dev *dev;
+
+	for_each_pci_dev_in_path(dev, start, NULL) {
+		if (pci_liveupdate_finish_device(ser, dev))
+			return;
+	}
 }
 
 /**
@@ -568,6 +668,8 @@ static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *de
  * Update. Drivers must call pci_liveupdate_finish() from their struct
  * liveupdate_file_handler finish() callback to ensure the incoming struct
  * pci_ser is allocated.
+ *
+ * pci_liveupdate_finish() automatically finishes all bridges upstream of @dev.
  */
 void pci_liveupdate_finish(struct pci_dev *dev)
 {
@@ -581,7 +683,7 @@ void pci_liveupdate_finish(struct pci_dev *dev)
 		return;
 	}
 
-	pci_liveupdate_finish_device(incoming->ser, dev);
+	pci_liveupdate_finish_path(incoming->ser, dev);
 	pci_liveupdate_flb_put_incoming();
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 85def616703d..c86518be4ce7 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,7 +23,7 @@
  * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
  */
 
-#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v3"
 
 /**
  * struct pci_dev_ser - Serialized state about a single PCI device.
@@ -32,7 +32,8 @@
  * @bdf: The device's PCI bus, device, and function number.
  * @refcount: Reference count used by the PCI core to keep track of whether it
  *            is done using a device's struct pci_dev_ser. The value of the
- *            refcount is equal to 1 when the struct pci_dev_ser is in use, and
+ *            refcount is equal to the number of preserved devices at or below
+ *            it in the PCI hierarchy when the struct pci_dev_ser is in use, and
  *            0 otherwise.
  */
 struct pci_dev_ser {
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:05 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:05 +0000
Subject: [PATCH v6 07/12] PCI: Refactor matching logic for pci_dev_acs_ops
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-8-dmatlack@google.com>

Refactor the logic to match devices to pci_dev_acs_ops by factoring out
the loop and device matching into its own routine. This eliminates some
duplicate code between pci_dev_specific_enable_acs() and
pci_dev_specific_disable_acs_redir(), and will also be used in a
subsequent commit to check if a device requires device-specific
enable_acs() during a Live Update.

No functional change intended.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/quirks.c | 50 ++++++++++++++++++--------------------------
 1 file changed, 20 insertions(+), 30 deletions(-)

diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index caaed1a01dc0..171caec2bc47 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5384,9 +5384,6 @@ static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev)
  */
 static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev)
 {
-	if (!pci_quirk_intel_pch_acs_match(dev))
-		return -ENOTTY;
-
 	if (pci_quirk_enable_intel_lpc_acs(dev)) {
 		pci_warn(dev, "Failed to enable Intel PCH ACS quirk\n");
 		return 0;
@@ -5406,9 +5403,6 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev)
 	int pos;
 	u32 cap, ctrl;
 
-	if (!pci_quirk_intel_spt_pch_acs_match(dev))
-		return -ENOTTY;
-
 	pos = dev->acs_cap;
 	if (!pos)
 		return -ENOTTY;
@@ -5436,9 +5430,6 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)
 	int pos;
 	u32 cap, ctrl;
 
-	if (!pci_quirk_intel_spt_pch_acs_match(dev))
-		return -ENOTTY;
-
 	pos = dev->acs_cap;
 	if (!pos)
 		return -ENOTTY;
@@ -5458,22 +5449,25 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)
 static const struct pci_dev_acs_ops {
 	u16 vendor;
 	u16 device;
+	bool (*match)(struct pci_dev *dev);
 	int (*enable_acs)(struct pci_dev *dev);
 	int (*disable_acs_redir)(struct pci_dev *dev);
 } pci_dev_acs_ops[] = {
 	{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
+	    .match = pci_quirk_intel_pch_acs_match,
 	    .enable_acs = pci_quirk_enable_intel_pch_acs,
 	},
 	{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
+	    .match = pci_quirk_intel_spt_pch_acs_match,
 	    .enable_acs = pci_quirk_enable_intel_spt_pch_acs,
 	    .disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir,
 	},
 };
 
-int pci_dev_specific_enable_acs(struct pci_dev *dev)
+static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev)
 {
 	const struct pci_dev_acs_ops *p;
-	int i, ret;
+	int i;
 
 	for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
 		p = &pci_dev_acs_ops[i];
@@ -5481,33 +5475,29 @@ int pci_dev_specific_enable_acs(struct pci_dev *dev)
 		     p->vendor == (u16)PCI_ANY_ID) &&
 		    (p->device == dev->device ||
 		     p->device == (u16)PCI_ANY_ID) &&
-		    p->enable_acs) {
-			ret = p->enable_acs(dev);
-			if (ret >= 0)
-				return ret;
-		}
+		    p->match(dev))
+			return p;
 	}
 
+	return NULL;
+}
+
+int pci_dev_specific_enable_acs(struct pci_dev *dev)
+{
+	const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
+
+	if (p && p->enable_acs)
+		return p->enable_acs(dev);
+
 	return -ENOTTY;
 }
 
 int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
 {
-	const struct pci_dev_acs_ops *p;
-	int i, ret;
+	const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
 
-	for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
-		p = &pci_dev_acs_ops[i];
-		if ((p->vendor == dev->vendor ||
-		     p->vendor == (u16)PCI_ANY_ID) &&
-		    (p->device == dev->device ||
-		     p->device == (u16)PCI_ANY_ID) &&
-		    p->disable_acs_redir) {
-			ret = p->disable_acs_redir(dev);
-			if (ret >= 0)
-				return ret;
-		}
-	}
+	if (p && p->disable_acs_redir)
+		return p->disable_acs_redir(dev);
 
 	return -ENOTTY;
 }
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:06 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:06 +0000
Subject: [PATCH v6 08/12] PCI: liveupdate: Inherit ACS flags in incoming
 preserved devices
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-9-dmatlack@google.com>

Inherit Access Control Services (ACS) flags on all incoming preserved
devices (endpoints and upstream bridges) during a Live Update.

Inheriting ACS flags avoids changing routing rules while memory
transactions are in flight from preserved devices. This is also strictly
necessary to ensure that IOMMU group assignments do not change across
a Live Update for preserved devices, as changing ACS configurations can
split or merge IOMMU groups.

Cache the inherited ACS controls established by the previous kernel in
struct pci_dev so that ACS controls do not change after a reset
(pci_restore_state() calls pci_enable_acs()).

To simplify ACS inheritance, reject preserving any devices that require
quirks to enable ACS as those quirks would also have to take Live Update
into account.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 68 ++++++++++++++++++++++++++++++++++
 drivers/pci/liveupdate.h       | 11 ++++++
 drivers/pci/pci.c              |  5 +++
 drivers/pci/pci.h              |  5 +++
 drivers/pci/quirks.c           |  7 ++++
 include/linux/pci_liveupdate.h |  6 +++
 6 files changed, 102 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 4c79e19b7f98..a93b7ef065f2 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -71,6 +71,9 @@
  *
  *  * The device cannot be a Virtual Function (VF).
  *
+ *  * The device cannot require device-specific quirks to enable Access
+ *    Control Services (ACS).
+ *
  * Driver Binding
  * ==============
  *
@@ -113,6 +116,18 @@
  * This enables the PCI core and any drivers bound to the bridge to participate
  * in the Live Update so that preserved endpoints can continue issuing memory
  * transactions during the Live Update.
+ *
+ * Handling Preserved Devices
+ * ==========================
+ *
+ * The PCI core treats preserved devices differently than non-preserved devices.
+ * This section enumerates those differences.
+ *
+ *  * The PCI core inherits all ACS flags enabled on incoming preserved devices
+ *    rather than assigning new ones. This ensures that TLPs are routed the same
+ *    way after Live Update and ensures that IOMMU groups do not change. Note
+ *    that a device will use its inherited ACS flags for the lifetime of its
+ *    struct pci_dev (i.e. even after pci_liveupdate_finish()).
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -126,6 +141,7 @@
 #include <linux/pci.h>
 
 #include "liveupdate.h"
+#include "pci.h"
 
 /**
  * struct pci_liveupdate_global - Global state for PCI Live Update support
@@ -319,6 +335,16 @@ static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev
 {
 	int i;
 
+	/*
+	 * Do not preserve devices that rely on device-specific ACS equivalents
+	 * (for now) since that would complicate keeping ACS constant across
+	 * Live Update.
+	 */
+	if (pci_need_dev_specific_enable_acs(dev)) {
+		pci_warn(dev, "Refusing to preserve device that relies on ACS quirks\n");
+		return -EINVAL;
+	}
+
 	if (ser->nr_devices == ser->max_nr_devices)
 		return -ENOSPC;
 
@@ -598,6 +624,7 @@ void pci_liveupdate_setup_device(struct pci_dev *dev)
 
 	pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
 	dev->liveupdate.incoming = dev_ser;
+	dev->liveupdate.was_preserved = true;
 
 	/*
 	 * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
@@ -688,6 +715,47 @@ void pci_liveupdate_finish(struct pci_dev *dev)
 }
 EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
 
+void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+	guard(rwsem_read)(&pci_liveupdate.rwsem);
+
+	if (!dev->acs_cap || !dev->liveupdate.incoming)
+		return;
+
+	pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, &dev->liveupdate.acs_ctrl);
+}
+
+int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+	u16 acs_ctrl = dev->liveupdate.acs_ctrl;
+	u16 acs_cap = dev->acs_cap;
+
+	/*
+	 * Use liveupdate.was_preserved instead of liveupdate.incoming since the
+	 * device's ACS controls should not change even after the device is
+	 * finished participating in the Live Update.
+	 */
+	if (!dev->liveupdate.was_preserved)
+		return -EINVAL;
+
+	/*
+	 * The previous kernel should not have preserved any devices that
+	 * require device-specific quirks to enable ACS, but if such a device is
+	 * detected, log a big warning and fall back to the normal enable ACS
+	 * path.
+	 */
+	if (pci_need_dev_specific_enable_acs(dev)) {
+		pci_warn(dev, "Device-specific quirk required to enable ACS!\n");
+		WARN_ON_ONCE(true);
+		return -EINVAL;
+	}
+
+	if (acs_cap)
+		pci_write_config_word(dev, acs_cap + PCI_ACS_CTRL, acs_ctrl);
+
+	return 0;
+}
+
 /**
  * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
  * @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index c763255a8de4..4e8a01bcb4bb 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -16,6 +16,8 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev);
 bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
 				      int pass);
 void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
+void pci_liveupdate_init_acs(struct pci_dev *dev);
+int pci_liveupdate_enable_acs(struct pci_dev *dev);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -35,6 +37,15 @@ static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
 static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
 {
 }
+
+static inline void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+}
+
+static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+	return -EINVAL;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 8f7cfcc00090..211df4618164 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -33,6 +33,7 @@
 #include <asm/dma.h>
 #include <linux/aer.h>
 #include <linux/bitfield.h>
+#include "liveupdate.h"
 #include "pci.h"
 
 DEFINE_MUTEX(pci_slot_mutex);
@@ -1017,6 +1018,9 @@ void pci_enable_acs(struct pci_dev *dev)
 	bool enable_acs = false;
 	int pos;
 
+	if (!pci_liveupdate_enable_acs(dev))
+		return;
+
 	/* If an iommu is present we start with kernel default caps */
 	if (pci_acs_enable) {
 		if (pci_dev_specific_enable_acs(dev))
@@ -3657,6 +3661,7 @@ void pci_acs_init(struct pci_dev *dev)
 
 	pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities);
 	pci_disable_broken_acs_cap(dev);
+	pci_liveupdate_init_acs(dev);
 }
 
 /**
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4a14f88e543a..b55f3deddd57 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1062,6 +1062,7 @@ void pci_acs_init(struct pci_dev *dev);
 void pci_enable_acs(struct pci_dev *dev);
 #ifdef CONFIG_PCI_QUIRKS
 int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);
+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev);
 int pci_dev_specific_enable_acs(struct pci_dev *dev);
 int pci_dev_specific_disable_acs_redir(struct pci_dev *dev);
 void pci_disable_broken_acs_cap(struct pci_dev *pdev);
@@ -1072,6 +1073,10 @@ static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev,
 {
 	return -ENOTTY;
 }
+static inline bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)
+{
+	return false;
+}
 static inline int pci_dev_specific_enable_acs(struct pci_dev *dev)
 {
 	return -ENOTTY;
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 171caec2bc47..59b0b19c3783 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5482,6 +5482,13 @@ static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev)
 	return NULL;
 }
 
+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)
+{
+	const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
+
+	return p && p->enable_acs;
+}
+
 int pci_dev_specific_enable_acs(struct pci_dev *dev)
 {
 	const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 2be98819e313..2446c6d237ca 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -17,14 +17,20 @@
  * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
  * @outgoing: State preserved for the next kernel.
  * @incoming: State preserved by the previous kernel.
+ * @acs_ctrl: ACS features established by the previous kernel.
  * @inherit_buses: True if the PCI core should inherit the secondary and
  *                 subordinate bus numbers assigned to this device due to
  *                 an ongoing Live Update.
+ * @was_preserved: True if this struct pci_dev was preserved by the previous
+ *                 kernel. Unlike @incoming, this field is not cleared after
+ *                 the device is finished participating in Live Update.
  */
 struct pci_liveupdate {
 	struct pci_dev_ser *outgoing;
 	struct pci_dev_ser *incoming;
+	u16 acs_ctrl;
 	bool inherit_buses;
+	bool was_preserved;
 };
 
 struct pci_dev;
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:07 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:07 +0000
Subject: [PATCH v6 09/12] PCI: liveupdate: Inherit ARI Forwarding Enable on
 preserved bridges
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-10-dmatlack@google.com>

Inherit the ARI Forwarding Enable on preserved bridges and update
pci_dev->ari_enabled accordingly during a Live Update. This ensures that
the preserved devices on the bridge's secondary bus can be identified
with the same expanded 8-bit function number after a Live Update.

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 18 ++++++++++++++++++
 drivers/pci/liveupdate.h |  6 ++++++
 drivers/pci/pci.c        |  8 +++++++-
 3 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index a93b7ef065f2..701276ef6cfb 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -128,6 +128,10 @@
  *    way after Live Update and ensures that IOMMU groups do not change. Note
  *    that a device will use its inherited ACS flags for the lifetime of its
  *    struct pci_dev (i.e. even after pci_liveupdate_finish()).
+ *
+ *  * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
+ *    preserved devices to ensure that all preserved devices on the bridge's
+ *    secondary bus are addressable after the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -756,6 +760,20 @@ int pci_liveupdate_enable_acs(struct pci_dev *dev)
 	return 0;
 }
 
+int pci_liveupdate_configure_ari(struct pci_dev *dev)
+{
+	u16 val;
+
+	guard(rwsem_read)(&pci_liveupdate.rwsem);
+
+	if (!dev->liveupdate.incoming)
+		return -EINVAL;
+
+	pcie_capability_read_word(dev, PCI_EXP_DEVCTL2, &val);
+	dev->ari_enabled = !!(val & PCI_EXP_DEVCTL2_ARI);
+	return 0;
+}
+
 /**
  * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
  * @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 4e8a01bcb4bb..6f21ec50927b 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -18,6 +18,7 @@ bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
 void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
 void pci_liveupdate_init_acs(struct pci_dev *dev);
 int pci_liveupdate_enable_acs(struct pci_dev *dev);
+int pci_liveupdate_configure_ari(struct pci_dev *dev);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -46,6 +47,11 @@ static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)
 {
 	return -EINVAL;
 }
+
+static inline int pci_liveupdate_configure_ari(struct pci_dev *dev)
+{
+	return -EINVAL;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 211df4618164..271da55af270 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3495,7 +3495,7 @@ void pci_configure_ari(struct pci_dev *dev)
 	u32 cap;
 	struct pci_dev *bridge;
 
-	if (pcie_ari_disabled || !pci_is_pcie(dev) || dev->devfn)
+	if (!pci_is_pcie(dev) || dev->devfn)
 		return;
 
 	bridge = dev->bus->self;
@@ -3506,6 +3506,12 @@ void pci_configure_ari(struct pci_dev *dev)
 	if (!(cap & PCI_EXP_DEVCAP2_ARI))
 		return;
 
+	if (!pci_liveupdate_configure_ari(bridge))
+		return;
+
+	if (pcie_ari_disabled)
+		return;
+
 	if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI)) {
 		pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2,
 					 PCI_EXP_DEVCTL2_ARI);
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:08 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:08 +0000
Subject: [PATCH v6 10/12] PCI: liveupdate: Freeze preservation status during shutdown
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-11-dmatlack@google.com>

Freeze a device's outgoing preservation status (preserved or not
preserved) during shutdown. This enables the PCI core and drivers to
safely make decisions based on the device's preservation status during
shutdown.

Note that pci_liveupdate_freeze() is triggered by the PCI core rather
than from drivers participating in Live Update so that all devices can
have their status frozen (i.e. prevent non-preserved devices from
getting preserved late).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c       | 16 ++++++++++++++++
 drivers/pci/liveupdate.h       |  5 +++++
 drivers/pci/pci-driver.c       |  2 ++
 include/linux/pci_liveupdate.h |  3 +++
 4 files changed, 26 insertions(+)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 701276ef6cfb..d404e64a4e55 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -297,6 +297,11 @@ static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev
 {
 	struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
 
+	if (dev->liveupdate.frozen) {
+		pci_warn(dev, "Cannot unpreserve device after it is frozen!\n");
+		return -EINVAL;
+	}
+
 	if (!dev_ser) {
 		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
 		return -EINVAL;
@@ -380,6 +385,11 @@ static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev
 
 static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
 {
+	if (dev->liveupdate.frozen) {
+		pci_warn(dev, "Cannot preserve device after it is frozen!\n");
+		return -EINVAL;
+	}
+
 	if (dev->liveupdate.outgoing)
 		return pci_liveupdate_preserve_device_again(dev);
 
@@ -653,6 +663,12 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 	}
 }
 
+void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+	guard(rwsem_write)(&pci_liveupdate.rwsem);
+	dev->liveupdate.frozen = 1;
+}
+
 static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
 {
 	if (!dev->liveupdate.incoming) {
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 6f21ec50927b..bcb0bc73d684 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,7 @@
 #ifdef CONFIG_PCI_LIVEUPDATE
 void pci_liveupdate_setup_device(struct pci_dev *dev);
 void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+void pci_liveupdate_freeze(struct pci_dev *dev);
 bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
 				      int pass);
 void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
@@ -28,6 +29,10 @@ static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
 {
 }
 
+static inline void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+}
+
 static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
 						    struct pci_dev *dev,
 						    int pass)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index d10ece0889f0..f7a5e65a7c75 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -21,6 +21,7 @@
 #include <linux/acpi.h>
 #include <linux/dma-map-ops.h>
 #include <linux/iommu.h>
+#include "liveupdate.h"
 #include "pci.h"
 #include "pcie/portdrv.h"
 
@@ -536,6 +537,7 @@ static void pci_device_shutdown(struct device *dev)
 	struct pci_dev *pci_dev = to_pci_dev(dev);
 	struct pci_driver *drv = pci_dev->driver;
 
+	pci_liveupdate_freeze(pci_dev);
 	pm_runtime_resume(dev);
 
 	if (drv && drv->shutdown)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 2446c6d237ca..150993405754 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -24,6 +24,8 @@
  * @was_preserved: True if this struct pci_dev was preserved by the previous
  *                 kernel. Unlike @incoming, this field is not cleared after
  *                 the device is finished participating in Live Update.
+ * @frozen: True if the outgoing preservation status of this device is frozen
+ *          and thus cannot be changed.
  */
 struct pci_liveupdate {
 	struct pci_dev_ser *outgoing;
@@ -31,6 +33,7 @@ struct pci_liveupdate {
 	u16 acs_ctrl;
 	bool inherit_buses;
 	bool was_preserved;
+	bool frozen;
 };
 
 struct pci_dev;
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:09 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:09 +0000
Subject: [PATCH v6 11/12] PCI: liveupdate: Do not disable bus mastering on
 preserved devices during kexec
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-12-dmatlack@google.com>

Do not disable bus mastering on outgoing preserved devices during
pci_device_shutdown() for kexec.

Preserved devices must be allowed to perform memory transactions during
a Live Update to ensure continuous operation.  Clearing the bus
mastering bit would prevent these devices from issuing any memory
requests while the new kernel boots.

Because bridges upstream of preserved endpoint devices are also
automatically preserved, this change also avoids clearing bus mastering
on them. This is critical because clearing bus mastering on an upstream
bridge prevents the bridge from forwarding memory requests upstream (i.e.
it would prevent the endpoint device from accessing system RAM and doing
peer-to-peer transactions with devices not downstream of the bridge).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 drivers/pci/liveupdate.c | 11 +++++++++++
 drivers/pci/liveupdate.h |  6 ++++++
 drivers/pci/pci-driver.c |  7 +++++--
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index d404e64a4e55..a6f2790bc1bf 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -132,6 +132,10 @@
  *  * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
  *    preserved devices to ensure that all preserved devices on the bridge's
  *    secondary bus are addressable after the Live Update.
+ *
+ *  * The PCI core does not disable bus mastering on outgoing preserved devices
+ *    during kexec. This allows preserved devices to issue memory transactions
+ *    throughout the Live Update.
  */
 
 #define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -790,6 +794,13 @@ int pci_liveupdate_configure_ari(struct pci_dev *dev)
 	return 0;
 }
 
+bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+	guard(rwsem_read)(&pci_liveupdate.rwsem);
+	pci_WARN_ONCE(dev, !dev->liveupdate.frozen, "Preservation status is unstable!\n");
+	return dev->liveupdate.outgoing;
+}
+
 /**
  * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
  * @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index bcb0bc73d684..b266406aaac8 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -20,6 +20,7 @@ void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
 void pci_liveupdate_init_acs(struct pci_dev *dev);
 int pci_liveupdate_enable_acs(struct pci_dev *dev);
 int pci_liveupdate_configure_ari(struct pci_dev *dev);
+bool pci_liveupdate_is_outgoing(struct pci_dev *dev);
 #else
 static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
 {
@@ -57,6 +58,11 @@ static inline int pci_liveupdate_configure_ari(struct pci_dev *dev)
 {
 	return -EINVAL;
 }
+
+static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+	return false;
+}
 #endif
 
 #endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index f7a5e65a7c75..0b1f8d01d7a5 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -546,11 +546,14 @@ static void pci_device_shutdown(struct device *dev)
 	/*
 	 * If this is a kexec reboot, turn off Bus Master bit on the
 	 * device to tell it to not continue to do DMA. Don't touch
-	 * devices in D3cold or unknown states.
+	 * devices being preserved for Live Update or in D3cold or
+	 * unknown states.
+	 *
 	 * If it is not a kexec reboot, firmware will hit the PCI
 	 * devices with big hammer and stop their DMA any way.
 	 */
-	if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot))
+	if (kexec_in_progress && !pci_liveupdate_is_outgoing(pci_dev) &&
+	    pci_dev->current_state <= PCI_D3hot)
 		pci_clear_master(pci_dev);
 }
 
-- 
2.54.0.746.g67dd491aae-goog



From dmatlack at google.com  Fri May 22 13:24:10 2026
From: dmatlack at google.com (David Matlack)
Date: Fri, 22 May 2026 20:24:10 +0000
Subject: [PATCH v6 12/12] Documentation: PCI: Add documentation for Live Update
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
References: <20260522202410.3104264-1-dmatlack@google.com>
Message-ID: <20260522202410.3104264-13-dmatlack@google.com>

Add documentation files for the PCI subsystem's participation in Live
Update.

These documentation files are generated from the kernel-doc comments
in the PCI Live Update source code. They describe the File-Lifecycle
Bound (FLB) API, the device tracking API, and the specific policies
applied to preserved devices (such as bus number inheritance and bus
mastering preservation).

Signed-off-by: David Matlack <dmatlack at google.com>
---
 Documentation/PCI/index.rst           |  1 +
 Documentation/PCI/liveupdate.rst      | 29 +++++++++++++++++++++++++++
 Documentation/core-api/liveupdate.rst |  1 +
 MAINTAINERS                           |  1 +
 4 files changed, 32 insertions(+)
 create mode 100644 Documentation/PCI/liveupdate.rst

diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e..23fb737ac969 100644
--- a/Documentation/PCI/index.rst
+++ b/Documentation/PCI/index.rst
@@ -20,3 +20,4 @@ PCI Bus Subsystem
    controller/index
    boot-interrupts
    tph
+   liveupdate
diff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst
new file mode 100644
index 000000000000..eba55f8a92ae
--- /dev/null
+++ b/Documentation/PCI/liveupdate.rst
@@ -0,0 +1,29 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+===========================
+PCI Support for Live Update
+===========================
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+   :doc: PCI Live Update
+
+Driver API
+==========
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+   :export:
+
+Live Update ABI
+===============
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+   :doc: PCI File-Lifecycle Bound (FLB) Live Update ABI
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+   :internal:
+
+See Also
+========
+
+ * :doc:`/core-api/liveupdate`
+ * :doc:`/core-api/kho/index`
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 5a292d0f3706..d56a7760978a 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -70,3 +70,4 @@ See Also
 
 - :doc:`Live Update uAPI </userspace-api/liveupdate>`
 - :doc:`/core-api/kho/index`
+- :doc:`PCI </PCI/liveupdate>`
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e262c0ceb43..6f0b0ebf67cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20536,6 +20536,7 @@ L:	kexec at lists.infradead.org
 L:	linux-pci at vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F:	Documentation/PCI/liveupdate.rst
 F:	drivers/pci/liveupdate.c
 F:	drivers/pci/liveupdate.h
 F:	include/linux/kho/abi/pci.h
-- 
2.54.0.746.g67dd491aae-goog



From dongtai.guo at linux.dev  Sun May 24 23:28:05 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:05 +0800
Subject: [PATCH 2/7] LoongArch: kho: strip stale kho_handover= from reused cmdline
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-3-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

When kexec is invoked with --reuse-cmdline after a liveupdate boot, the
running kernel's command line already contains a kho_handover= parameter
from the previous kexec load.  load_other_segments() prepends a fresh
kho_handover= for the new handover FDT but then appends the original
cmdline verbatim, resulting in two kho_handover= entries:

  kho_handover=0x4000@<new_fdt>,... kho_handover=0x4000@<stale_fdt>,...

early_param() calls early_parse_kho() for each occurrence in order, so
kho_populate() is invoked twice and the second call overwrites the first
with the stale FDT address.  The stale address no longer holds a valid
KHO FDT, causing __kho_radix_walk_tree() to dereference a garbage
pointer and panic early in mm_core_init().

Fix this by adding the new kho_handover= to the cmdline prefix first,
then stripping any stale kho_handover= tokens from the appended original
cmdline portion, so only the freshly generated entry survives.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/kernel/machine_kexec_file.c | 35 ++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index ddf4d0e0e7fd..ffaedd055e62 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -71,6 +71,37 @@ static void cmdline_add_kho(struct kimage *image, unsigned long *cmdline_tmplen,
 	    image->kho.scratch->bufsz,    (u64)image->kho.scratch->mem);
 	*cmdline_tmplen += n;
 }
+
+/*
+ * Remove all "kho_handover=..." tokens from cmdline.  Needed when
+ * --reuse-cmdline is used: the running kernel's cmdline already carries a
+ * stale kho_handover= from the previous kexec load; without removal the new
+ * kernel sees two entries and kho_populate() ends up using the wrong (stale)
+ * FDT address.
+ */
+static void cmdline_remove_kho(char *cmdline)
+{
+	const char *key = "kho_handover=";
+	size_t key_len = strlen(key);
+	char *p = cmdline;
+
+	while ((p = strstr(p, key)) != NULL) {
+		char *start = p;
+		char *end;
+
+		/* Only match at a token boundary */
+		if (start != cmdline && *(start - 1) != ' ') {
+			p += key_len;
+			continue;
+		}
+		end = start + key_len;
+		while (*end && *end != ' ')
+			end++;
+		while (*end == ' ')
+			end++;
+		memmove(start, end, strlen(end) + 1);
+	}
+}
 #endif
 
 #ifdef CONFIG_CRASH_DUMP
@@ -249,6 +280,10 @@ int load_other_segments(struct kimage *image,
 	}
 
 	memcpy(modified_cmdline + cmdline_tmplen, cmdline, cmdline_len);
+#ifdef CONFIG_KEXEC_HANDOVER
+	/* Strip stale kho_handover= that --reuse-cmdline may have carried over */
+	cmdline_remove_kho(modified_cmdline + cmdline_tmplen);
+#endif
 	cmdline = modified_cmdline;
 	image->arch.cmdline_ptr = (unsigned long)cmdline;
 
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:06 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:06 +0800
Subject: [PATCH 3/7] LoongArch: Add missing linux/mm.h include in asm/io.h
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-4-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

When CONFIG_KFENCE is enabled, the virt_to_phys() and phys_to_virt()
macros in arch/loongarch/include/asm/io.h expand to use offset_in_page()
and page_address(), both of which are declared in <linux/mm.h>.

Without this include, any translation unit that includes <asm/io.h> and
expands these macros with CONFIG_KFENCE=y will fail to build. This was
observed when building kernel/liveupdate/luo_session.c with
CONFIG_KFENCE=y:

  asm/io.h: error: implicit declaration of function 'offset_in_page'
  asm/io.h: error: implicit declaration of function 'page_address'

Add the missing include to fix these build errors.

Fixes: 0ca84aeaee15 ("LoongArch: Make {virt, phys, page, pfn} translation work with KFENCE")
Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/include/asm/io.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/loongarch/include/asm/io.h b/arch/loongarch/include/asm/io.h
index 0130185e0349..7ff4aa72a34a 100644
--- a/arch/loongarch/include/asm/io.h
+++ b/arch/loongarch/include/asm/io.h
@@ -13,6 +13,7 @@
 #include <asm/page.h>
 #include <asm/pgtable-bits.h>
 #include <asm/string.h>
+#include <linux/mm.h>
 
 extern void __init __iomem *early_ioremap(phys_addr_t phys_addr, unsigned long size);
 extern void __init early_iounmap(void __iomem *addr, unsigned long size);
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:07 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:07 +0800
Subject: [PATCH 4/7] LoongArch: kexec: avoid overwriting QEMU's machine FDT at 0x100000
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-5-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

QEMU places its machine FDT at physical address 0x100000 when booting
with '-kernel'.  KEXEC_CONTROL_CODE was defined at the same address, so
machine_kexec_prepare() overwrites the FDT with the relocation trampoline
before jumping to the new kernel.

The kexec'd kernel's fdt_setup() reads the FDT pointer from the EFI
config table (FDTPTR = 0x100000) and finds trampoline code instead of a
valid FDT, so earlycon auto-detection fails and the second kernel boots
silently with no console output.

Move KEXEC_CONTROL_CODE to 0x180000, which is still within the first 2MB
reserved by memblock_init() and does not conflict with the QEMU FDT.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/kernel/machine_kexec.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c
index d7fafda1d541..44df7dbd3de5 100644
--- a/arch/loongarch/kernel/machine_kexec.c
+++ b/arch/loongarch/kernel/machine_kexec.c
@@ -21,8 +21,13 @@
 #include <asm/cacheflush.h>
 #include <asm/page.h>
 
-/* 0x100000 ~ 0x200000 is safe */
-#define KEXEC_CONTROL_CODE	TO_CACHE(0x100000UL)
+/*
+ * Both addresses are within the first 2MB which is always reserved by
+ * memblock_init().  Avoid 0x100000 because QEMU places its machine FDT
+ * there when using '-kernel'; overwriting it silences earlycon in the
+ * kexec'd kernel.
+ */
+#define KEXEC_CONTROL_CODE	TO_CACHE(0x180000UL)
 #define KEXEC_CMDLINE_ADDR	TO_CACHE(0x108000UL)
 
 static unsigned long reboot_code_buffer;
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:03 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:03 +0800
Subject: [PATCH 0/7] LoongArch: add KHO support and selftests
Message-ID: <20260525062810.103367-1-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>>

This series adds Kexec Handover (KHO) support for LoongArch64 and
extends the KHO selftest infrastructure to run on LoongArch under QEMU.

KHO support (patches 1-2)
--------------------------
Patch 1 wires up the three pieces needed for KHO on LoongArch:
 - Kconfig: select ARCH_SUPPORTS_KEXEC_HANDOVER for 64BIT
 - machine_kexec_file: call cmdline_add_kho() to pass the KHO FDT and
   scratch buffer addresses via "kho_handover=" on the kernel cmdline
 - setup: parse "kho_handover=" early and call kho_populate()

Patch 2 fixes a duplicate-parameter bug when kexec is invoked with
--reuse-cmdline: the running kernel's cmdline already contains a stale
kho_handover= from the previous load.  load_other_segments() prepends a
fresh entry but appends the original cmdline verbatim, so early_param()
calls kho_populate() twice and the second call overwrites the first with
a stale FDT pointer, panicking early in mm_core_init().

Bug fixes (patches 3-4)
------------------------
Patch 3 fixes a build failure with CONFIG_KFENCE=y: virt_to_phys() and
phys_to_virt() in asm/io.h expand to use offset_in_page() and
page_address(), which require <linux/mm.h> that was not included.

Patch 4 moves KEXEC_CONTROL_CODE from 0x100000 to 0x180000.  QEMU
places its machine FDT at 0x100000 when booting with '-kernel'; the
previous placement silently overwrote the FDT with the relocation
trampoline, breaking earlycon in the kexec'd kernel.

Selftest support (patches 5-7)
-------------------------------
Patch 5 adds loongarch.conf and extends vmtest.sh to recognise
loongarch64 as a build target.  QEMU's LoongArch virt machine is
FDT-only (no ACPI), so 'earlycon' must appear on the kernel cmdline or
the console UART is never discovered and the test log stays empty.

Patch 6 disables SERIO_I8042 and its dependents in the QEMU_KCONFIG
fragment.  QEMU's LoongArch virt machine has no i8042 controller; the
fallback port probe hits a page fault on the memory-mapped I/O range and
panics before reaching userspace.

Patch 7 polls the serial output for the test verdict and kills QEMU once
it appears.  QEMU provides no EFI runtime services on LoongArch, so
machine_restart() falls through to an infinite idle loop and QEMU never
exits after kexec.


George Guo (7):
  LoongArch: Add KHO basic support
  LoongArch: kho: strip stale kho_handover= from reused cmdline
  LoongArch: Add missing linux/mm.h include in asm/io.h
  LoongArch: kexec: avoid overwriting QEMU's machine FDT at 0x100000
  selftests/kho: add LoongArch vmtest support
  selftests/kho: LoongArch: disable PS/2 input devices for QEMU virt
  selftests/kho: handle QEMU not exiting after kexec on LoongArch

 arch/loongarch/Kconfig                     |  3 ++
 arch/loongarch/include/asm/io.h            |  1 +
 arch/loongarch/kernel/machine_kexec.c      |  9 +++-
 arch/loongarch/kernel/machine_kexec_file.c | 57 ++++++++++++++++++++++
 arch/loongarch/kernel/setup.c              | 27 ++++++++++
 tools/testing/selftests/kho/loongarch.conf | 10 ++++
 tools/testing/selftests/kho/vmtest.sh      | 38 ++++++++++++---
 7 files changed, 135 insertions(+), 10 deletions(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf

-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:04 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:04 +0800
Subject: [PATCH 1/7] LoongArch: Add KHO basic support
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-2-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

Enable Kexec Handover on LoongArch64:

- Kconfig: select ARCH_SUPPORTS_KEXEC_HANDOVER for 64BIT
- machine_kexec_file: add cmdline_add_kho() to pass the KHO FDT and
  scratch buffer addresses to the next kernel via the "kho_handover="
  command-line parameter
- setup: parse "kho_handover=" early and call kho_populate() to hand
  memory regions to the KHO core

Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/Kconfig                     |  3 +++
 arch/loongarch/kernel/machine_kexec_file.c | 22 ++++++++++++++++++
 arch/loongarch/kernel/setup.c              | 27 ++++++++++++++++++++++
 3 files changed, 52 insertions(+)

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 606597da46b8..d494418545f5 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -684,6 +684,9 @@ config ARCH_SUPPORTS_KEXEC
 config ARCH_SUPPORTS_KEXEC_FILE
 	def_bool 64BIT
 
+config ARCH_SUPPORTS_KEXEC_HANDOVER
+	def_bool 64BIT
+
 config ARCH_SELECTS_KEXEC_FILE
 	def_bool 64BIT
 	depends on KEXEC_FILE
diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..ddf4d0e0e7fd 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -55,6 +55,24 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
 	*cmdline_tmplen += initrd_strlen;
 }
 
+#ifdef CONFIG_KEXEC_HANDOVER
+/* Add "kho_handover=<fdt_size>@<fdt_addr>,<scratch_size>@<scratch_addr>" to cmdline. */
+static void cmdline_add_kho(struct kimage *image, unsigned long *cmdline_tmplen,
+			    char *modified_cmdline)
+{
+	int n;
+
+	if (!image->kho.fdt || !image->kho.scratch)
+		return;
+
+	n = sprintf(modified_cmdline + *cmdline_tmplen,
+	    "kho_handover=0x%llx at 0x%llx,0x%lx at 0x%llx ",
+	    (u64)PAGE_SIZE,               image->kho.fdt,
+	    image->kho.scratch->bufsz,    (u64)image->kho.scratch->mem);
+	*cmdline_tmplen += n;
+}
+#endif
+
 #ifdef CONFIG_CRASH_DUMP
 
 static int prepare_elf_headers(void **addr, unsigned long *sz)
@@ -220,6 +238,10 @@ int load_other_segments(struct kimage *image,
 		cmdline_add_initrd(image, &cmdline_tmplen, modified_cmdline, initrd_load_addr);
 	}
 
+#ifdef CONFIG_KEXEC_HANDOVER
+	cmdline_add_kho(image, &cmdline_tmplen, modified_cmdline);
+#endif
+
 	if (cmdline_len + cmdline_tmplen > COMMAND_LINE_SIZE) {
 		pr_err("Appending command line exceeds COMMAND_LINE_SIZE\n");
 		ret = -EINVAL;
diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
index 839b23edee87..5934ba6f13e3 100644
--- a/arch/loongarch/kernel/setup.c
+++ b/arch/loongarch/kernel/setup.c
@@ -48,6 +48,7 @@
 #include <asm/setup.h>
 #include <asm/time.h>
 #include <asm/unwind.h>
+#include <linux/kexec_handover.h>
 
 #define SMBIOS_BIOSSIZE_OFFSET		0x09
 #define SMBIOS_BIOSEXTERN_OFFSET	0x13
@@ -227,6 +228,32 @@ static int __init early_parse_mem(char *p)
 }
 early_param("mem", early_parse_mem);
 
+#ifdef CONFIG_KEXEC_HANDOVER
+static int __init early_parse_kho(char *p)
+{
+	phys_addr_t fdt_addr, scratch_addr;
+	u64 fdt_size, scratch_size;
+
+	if (!p)
+		return -EINVAL;
+
+	fdt_size = memparse(p, &p);
+	if (*p++ != '@')
+		return -EINVAL;
+	fdt_addr = memparse(p, &p);
+	if (*p++ != ',')
+		return -EINVAL;
+	scratch_size = memparse(p, &p);
+	if (*p++ != '@')
+		return -EINVAL;
+	scratch_addr = memparse(p, &p);
+
+	kho_populate(fdt_addr, fdt_size, scratch_addr, scratch_size);
+	return 0;
+}
+early_param("kho_handover", early_parse_kho);
+#endif
+
 static void __init arch_reserve_vmcore(void)
 {
 #ifdef CONFIG_PROC_VMCORE
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:08 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:08 +0800
Subject: [PATCH 5/7] selftests/kho: add LoongArch vmtest support
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-6-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

Add loongarch.conf to configure QEMU's LoongArch virt machine with a
la464 CPU, enable the 8250 serial console, and set the kernel image
to vmlinux.efi. Extend vmtest.sh to recognize loongarch64 as a
supported target and map it to the 'loongarch' kernel arch name.

QEMU's LoongArch virt machine provides no ACPI tables and relies on
FDT to describe hardware. Without 'earlycon' on the kernel command
line, the FDT is not scanned for a console UART, no output reaches
the console, and vmtest.sh's console log stays empty causing the test
to always fail. Add 'earlycon' to KERNEL_CMDLINE in loongarch.conf
to fix this.

Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 tools/testing/selftests/kho/loongarch.conf | 7 +++++++
 tools/testing/selftests/kho/vmtest.sh      | 3 ++-
 2 files changed, 9 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf

diff --git a/tools/testing/selftests/kho/loongarch.conf b/tools/testing/selftests/kho/loongarch.conf
new file mode 100644
index 000000000000..b0551e0c2d77
--- /dev/null
+++ b/tools/testing/selftests/kho/loongarch.conf
@@ -0,0 +1,7 @@
+QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464"
+QEMU_KCONFIG="
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+"
+KERNEL_IMAGE="vmlinux.efi"
+KERNEL_CMDLINE="console=ttyS0 earlycon"
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 49fdac8e8b15..a6ae9ac09595 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -21,7 +21,7 @@ Options:
 	-d)	path to the kernel build directory
 	-j)	number of jobs for compilation, similar to -j in make
 	-t)	run test for target_arch, requires CROSS_COMPILE set
-		supported targets: aarch64, x86_64
+		supported targets: aarch64, x86_64, loongarch64
 	-h)	display this help
 EOF
 }
@@ -123,6 +123,7 @@ function target_to_arch() {
 	case $target in
 	     aarch64) echo "arm64" ;;
 	     x86_64) echo "x86" ;;
+	     loongarch64) echo "loongarch" ;;
 	     *) skip "architecture $target is not supported"
 	esac
 }
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:09 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:09 +0800
Subject: [PATCH 6/7] selftests/kho: LoongArch: disable PS/2 input devices for QEMU virt
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-7-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

QEMU's LoongArch virt machine has no i8042 PS/2 controller.  When PNP
detection finds nothing, i8042_init() falls back to probing the ports
directly.  On LoongArch the I/O ports are memory-mapped, and the i8042
port addresses are not backed by any device on the virt machine, so
i8042_flush() takes a page fault and the kernel panics:

  i8042: PNP: No PS/2 controller found.
  i8042: Probing ports directly.
  CPU 0 Unable to handle kernel paging request at virtual address ffff800000008064
  ERA: i8042_flush+0x50/0x198
   RA: i8042_init+0x2a8/0x35c
  Kernel panic - not syncing: Attempted to kill init!

Disable SERIO_I8042 and its dependents (KEYBOARD_ATKBD, MOUSE_PS2) in
the QEMU_KCONFIG fragment to prevent the driver from being built.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 tools/testing/selftests/kho/loongarch.conf | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tools/testing/selftests/kho/loongarch.conf b/tools/testing/selftests/kho/loongarch.conf
index b0551e0c2d77..0145cb49e5b2 100644
--- a/tools/testing/selftests/kho/loongarch.conf
+++ b/tools/testing/selftests/kho/loongarch.conf
@@ -2,6 +2,9 @@ QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464"
 QEMU_KCONFIG="
 CONFIG_SERIAL_8250=y
 CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_MOUSE_PS2 is not set
+# CONFIG_SERIO_I8042 is not set
 "
 KERNEL_IMAGE="vmlinux.efi"
 KERNEL_CMDLINE="console=ttyS0 earlycon"
-- 
2.25.1



From dongtai.guo at linux.dev  Sun May 24 23:28:10 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Mon, 25 May 2026 14:28:10 +0800
Subject: [PATCH 7/7] selftests/kho: handle QEMU not exiting after kexec on LoongArch
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <20260525062810.103367-8-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

On LoongArch, QEMU provides only a minimal EFI stub with no runtime
services and no ACPI tables, so machine_restart() falls through to
its infinite idle loop and QEMU never exits after kexec.

Poll the serial output for the test verdict and kill QEMU once
it appears, rather than waiting for QEMU to exit naturally.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 tools/testing/selftests/kho/vmtest.sh | 35 +++++++++++++++++++++------
 1 file changed, 28 insertions(+), 7 deletions(-)

diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index a6ae9ac09595..3cb9674e4475 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -103,16 +103,37 @@ function run_qemu() {
 	local qemu_cmd=$1
 	local cmdline=$2
 	local kernel=$3
+	local arch=$4
 	local serial="$tmp_dir/qemu.serial"
 
 	cmdline="$cmdline kho=on panic=-1"
 
-	$qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
-		  -accel kvm -accel hvf -accel tcg  \
-		  -serial file:"$serial" \
-		  -append "$cmdline" \
-		  -kernel "$kernel" \
-		  -initrd "$initrd"
+	local qemu_args=(
+		-m 1G -smp 2 -no-reboot -nographic -nodefaults
+		-accel kvm -accel hvf -accel tcg
+		-serial file:"$serial"
+		-append "$cmdline"
+		-kernel "$kernel"
+		-initrd "$initrd"
+	)
+
+	# On LoongArch, EFI runtime services are unavailable after kexec so
+	# machine_restart() hangs and QEMU never exits on its own.  Run QEMU
+	# in the background, poll for the test result, then kill it.
+	if [[ "$arch" == "loongarch" ]]; then
+		$qemu_cmd "${qemu_args[@]}" &
+		local qemu_pid=$!
+		local timeout=100
+		while ((timeout-- > 0)); do
+			grep -q "KHO restore succeeded\|KHO restore failed" \
+				"$serial" 2>/dev/null && break
+			sleep 1
+		done
+		kill "$qemu_pid" 2>/dev/null
+		wait "$qemu_pid" 2>/dev/null || true
+	else
+		$qemu_cmd "${qemu_args[@]}"
+	fi
 
 	grep "KHO restore succeeded" "$serial" &> /dev/null || fail "KHO failed"
 }
@@ -179,7 +200,7 @@ function main() {
 	local kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE"
 	mkinitrd "$kernel"
 
-	run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel"
+	run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$kernel" "$arch"
 
 	ktap_test_pass "KHO succeeded"
 }
-- 
2.25.1



From rppt at kernel.org  Mon May 25 00:48:14 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 25 May 2026 10:48:14 +0300
Subject: [PATCH 0/7] LoongArch: add KHO support and selftests
In-Reply-To: <20260525062810.103367-1-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
Message-ID: <177969529480.4047269.2120994507098781563.b4-review@b4>

On Mon, 25 May 2026 14:28:03 +0800, George Guo <dongtai.guo at linux.dev> wrote:

Hi,

> [...]
> 
> Bug fixes (patches 3-4)
> ------------------------
> Patch 3 fixes a build failure with CONFIG_KFENCE=y: virt_to_phys() and
> phys_to_virt() in asm/io.h expand to use offset_in_page() and
> page_address(), which require <linux/mm.h> that was not included.

This fix looks unrelated to KHO, it makes sense to send it as a separate
patch.

> 
> Patch 4 moves KEXEC_CONTROL_CODE from 0x100000 to 0x180000.  QEMU
> places its machine FDT at 0x100000 when booting with '-kernel'; the
> previous placement silently overwrote the FDT with the relocation

And I believe that patch 2 and patch 4 can be folded into patch 1 so
that addition of KHO support will be a single patch.

> trampoline, breaking earlycon in the kexec'd kernel.
> 
> Selftest support (patches 5-7)
> -------------------------------
> Patch 5 adds loongarch.conf and extends vmtest.sh to recognise
> loongarch64 as a build target.  QEMU's LoongArch virt machine is
> FDT-only (no ACPI), so 'earlycon' must appear on the kernel cmdline or
> the console UART is never discovered and the test log stays empty.
> 
> Patch 6 disables SERIO_I8042 and its dependents in the QEMU_KCONFIG
> fragment.  QEMU's LoongArch virt machine has no i8042 controller; the
> fallback port probe hits a page fault on the memory-mapped I/O range and
> panics before reaching userspace.

This should be folded into patch 5 for sure.

> 
> Patch 7 polls the serial output for the test verdict and kills QEMU once
> it appears.  QEMU provides no EFI runtime services on LoongArch, so
> machine_restart() falls through to an infinite idle loop and QEMU never
> exits after kexec.

And since without this selftest would hang on loongarch, the timeout
handing should be folded into patch 5.

In general, the rationale when adding new features is to make sure that
after each patch there are no regressions and the new functionality
either works or cannot be enabled until completely implemented.

-- 
Sincerely yours,
Mike.



From rppt at kernel.org  Mon May 25 00:48:14 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 25 May 2026 10:48:14 +0300
Subject: [PATCH 7/7] selftests/kho: handle QEMU not exiting after kexec
 on LoongArch
In-Reply-To: <20260525062810.103367-8-dongtai.guo@linux.dev>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
 <20260525062810.103367-8-dongtai.guo@linux.dev>
Message-ID: <177969529482.4047269.8837009779600841681.b4-review@b4>

On Mon, 25 May 2026 14:28:10 +0800, George Guo <dongtai.guo at linux.dev> wrote:
> diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
> index a6ae9ac09595..3cb9674e4475 100755
> --- a/tools/testing/selftests/kho/vmtest.sh
> +++ b/tools/testing/selftests/kho/vmtest.sh
> @@ -103,16 +103,37 @@ function run_qemu() {
>  	local qemu_cmd=$1
>  	local cmdline=$2
>  	local kernel=$3
> +	local arch=$4
>  	local serial="$tmp_dir/qemu.serial"
>  
>  	cmdline="$cmdline kho=on panic=-1"
>  
> -	$qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \

Just make it 

	timeout 100 $qemu_cmd ...

for all architectures.

-- 
Sincerely yours,
Mike.



From chenhuacai at kernel.org  Mon May 25 03:25:08 2026
From: chenhuacai at kernel.org (Huacai Chen)
Date: Mon, 25 May 2026 18:25:08 +0800
Subject: [PATCH 0/7] LoongArch: add KHO support and selftests
In-Reply-To: <177969529480.4047269.2120994507098781563.b4-review@b4>
References: <20260525062810.103367-1-dongtai.guo@linux.dev> <177969529480.4047269.2120994507098781563.b4-review@b4>
Message-ID: <CAAhV-H4p8xe_Hfx81kjxNrp5g7RKvi4f1QM7avLvnf_r+VazFA@mail.gmail.com>

Hi, Mike,

On Mon, May 25, 2026 at 3:48?PM Mike Rapoport <rppt at kernel.org> wrote:
>
> On Mon, 25 May 2026 14:28:03 +0800, George Guo <dongtai.guo at linux.dev> wrote:
>
> Hi,
>
> > [...]
> >
> > Bug fixes (patches 3-4)
> > ------------------------
> > Patch 3 fixes a build failure with CONFIG_KFENCE=y: virt_to_phys() and
> > phys_to_virt() in asm/io.h expand to use offset_in_page() and
> > page_address(), which require <linux/mm.h> that was not included.
>
> This fix looks unrelated to KHO, it makes sense to send it as a separate
> patch.
He tried, but it causes new problems, but I agree this patch has
nothing to do with KHO.
https://lore.kernel.org/loongarch/202605211925.DbDvQ6ii-lkp at intel.com/T/#t

>
> >
> > Patch 4 moves KEXEC_CONTROL_CODE from 0x100000 to 0x180000.  QEMU
> > places its machine FDT at 0x100000 when booting with '-kernel'; the
> > previous placement silently overwrote the FDT with the relocation
>
> And I believe that patch 2 and patch 4 can be folded into patch 1 so
> that addition of KHO support will be a single patch.
Patch 2 should be folded, but Patch 4 can be separate, because it
should be backported.

>
> > trampoline, breaking earlycon in the kexec'd kernel.
> >
> > Selftest support (patches 5-7)
> > -------------------------------
> > Patch 5 adds loongarch.conf and extends vmtest.sh to recognise
> > loongarch64 as a build target.  QEMU's LoongArch virt machine is
> > FDT-only (no ACPI), so 'earlycon' must appear on the kernel cmdline or
> > the console UART is never discovered and the test log stays empty.
> >
> > Patch 6 disables SERIO_I8042 and its dependents in the QEMU_KCONFIG
> > fragment.  QEMU's LoongArch virt machine has no i8042 controller; the
> > fallback port probe hits a page fault on the memory-mapped I/O range and
> > panics before reaching userspace.
>
> This should be folded into patch 5 for sure.
I completely don't know why Patch 6 is needed, for normal use I8042
doesn't cause problems in virtual machines.

Huacai

>
> >
> > Patch 7 polls the serial output for the test verdict and kills QEMU once
> > it appears.  QEMU provides no EFI runtime services on LoongArch, so
> > machine_restart() falls through to an infinite idle loop and QEMU never
> > exits after kexec.
>
> And since without this selftest would hang on loongarch, the timeout
> handing should be folded into patch 5.
>
> In general, the rationale when adding new features is to make sure that
> after each patch there are no regressions and the new functionality
> either works or cannot be enabled until completely implemented.
>
> --
> Sincerely yours,
> Mike.
>


From rppt at kernel.org  Mon May 25 04:24:18 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 25 May 2026 14:24:18 +0300
Subject: [PATCH 0/7] LoongArch: add KHO support and selftests
In-Reply-To: <CAAhV-H4p8xe_Hfx81kjxNrp5g7RKvi4f1QM7avLvnf_r+VazFA@mail.gmail.com>
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
 <177969529480.4047269.2120994507098781563.b4-review@b4>
 <CAAhV-H4p8xe_Hfx81kjxNrp5g7RKvi4f1QM7avLvnf_r+VazFA@mail.gmail.com>
Message-ID: <177970825876.4071219.16831083000796070841.b4-reply@b4>

Hi,

On 2026-05-25 18:25:08+08:00, Huacai Chen wrote:
> Hi, Mike,
> 
> On Mon, May 25, 2026 at 3:48?PM Mike Rapoport <rppt at kernel.org> wrote:
> 
> > On Mon, 25 May 2026 14:28:03 +0800, George Guo <dongtai.guo at linux.dev> wrote:
> >
> > This fix looks unrelated to KHO, it makes sense to send it as a separate
> > patch.
> 
> He tried, but it causes new problems, but I agree this patch has
> nothing to do with KHO.
> https://lore.kernel.org/loongarch/202605211925.DbDvQ6ii-lkp at intel.com/T/#t
> 
> > And I believe that patch 2 and patch 4 can be folded into patch 1 so
> > that addition of KHO support will be a single patch.
> 
> Patch 2 should be folded, but Patch 4 can be separate, because it
> should be backported.

Fair enough. 
I'd suggest to move the fixes to be the first patches in the series, but
presuming this is going via loongarch tree it's up to you :)

> > This should be folded into patch 5 for sure.
> 
> I completely don't know why Patch 6 is needed, for normal use I8042
> doesn't cause problems in virtual machines.
> 
> Huacai




From pratyush at kernel.org  Mon May 25 08:24:09 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 17:24:09 +0200
Subject: [PATCH 12/12] mm/hugetlb: make bootmem allocation work with KHO
In-Reply-To: <agmS5yZRflzN1M8U@kernel.org> (Mike Rapoport's message of "Sun,
	17 May 2026 13:05:27 +0300")
References: <20260429133928.850721-1-pratyush@kernel.org>
	<20260429133928.850721-13-pratyush@kernel.org>
	<agmS5yZRflzN1M8U@kernel.org>
Message-ID: <2vxzo6i37bs6.fsf@kernel.org>

Hi Mike,

On Sun, May 17 2026, Mike Rapoport wrote:

> Hi Pratyush,
>
> On Wed, Apr 29, 2026 at 03:39:14PM +0200, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> Gigantic page allocation is somewhat broken currently when KHO is used.
>> 
>> Firstly, they break KHO scratch size accounting. RSRV_KERN is used to
>> track how much memory is reserved for use by the kernel. Since
>> alloc_bootmem() calls the memblock_alloc*() APIs, the hugepages
>> allocated also get marked as RSRV_KERN.
>
> The intended semantics of RSRV_KERN was to distinguish memory reserved
> because it's in use by firmware from the memory reserved or allocated by
> the kernel. It does not have to be directly used by the kernel, some of
> PG_Reserved memory is mapped to user-space and it's not only hugetlb. There
> are zero pages, VDSO and maybe something else, I don't remember.
>
>> Allocations marked RSRV_KERN are used by KHO to calculate how much
>> scratch space it should reserve to make sure the next kernel has enough
>> memory to boot when it is in scratch-only phase. Counting hugepages in
>
> The size of RSRV_KERN allocations is also used in
> memblock_estimated_nr_free_pages() that's currently used by
> set_max_threads() to set several rlimits and by s390 to allocate colored
> zero pages. Excluding hugetlb from that will skew the calculations there.

Oh, I didn't know that. I thought it was only for KHO.

>
>> that blows up scratch size, and can lead to the scratch allocation
>> failing, making KHO unusable. This will show up when huge pages make up
>> more than 50% of the system, which is a fairly common use case.
>> 
>> Secondly, while not supported right now, huge pages are user memory and
>> can be preserved via KHO. The scratch spaces should not have any
>> preserved memory. Allocating hugepages from scratch (on a KHO boot) can
>> lead to them being un-preservable.
>> 
>> Introduce memblock_alloc_nid_user(). This does two things: first, it
>> instructs __memblock_alloc_range_nid() to not use scratch areas to
>> fulfill allocation. If KHO is in scratch-only mode, allocations will
>> only be made from extended scratch areas. Second, it removes RSRV_KERN
>> from the allocation to make sure it doesn't mess up scratch size
>> accounting.
>> 
>> To reduce duplication, introduce __memblock_alloc_range_nid() which does
>> exactly what memblock_alloc_range_nid() used to do, but takes the flags
>> from its caller. Then make memblock_alloc_range_nid() a wrapper to it.
>> This lets memblock_alloc_nid_user() re-use most of the logic without
>> causing churn to update all callers of memblock_alloc_range_nid() and
>> adding yet another argument to it.
>
> That's neat :)
>
> But I'm not too fond of the memblock_alloc_nid_user() as a concept. That
> early at boot everything is still kernel, even though hugetlb pages might
> become user afterwards if they are actually consumed.
> Another thing, is that adding such global API to memblock could be abused
> and suddenly some early code will clear RSRV_KERN for a random pieces of
> memory.
>
> If we'd still need a special memblock function for gigantic pages
> allocation I'd rather make it explicit that it's for hugetlb and keep it in
> mm/internal.h

Sure, that would work I think.

>
> I was thinking about possible alternative solutions and here's what I came
> up with

Thanks for the ideas, much appreciated!

>
> 1. SCRATCH_EXT nicely increases the memory pool available to memblock, but
>    the decision which memory can be used for which allocations becomes more
>    complex. It should make sure that SCRATCH is not used for hugetlb, but
>    OTOH it's preferred for other early allocations. With implicit
>    interaction between choose_memblock_flags() and should_skip_region()
>    this seems to me quite a headache.

Yep, I agree. I can try to refactor some of the logic to make this
easier to understand, but I am not sure how successful I would be.

>    memblock_reserved_clear_kern() should be changed to something else to
>    keep set_max_threads() working. And, IMHO, memblock_alloc_nid_user()
>    should be turned into memblock_alloc_gigantic_hugetlb() and only exposed
>    to MM code. It's even possible that it will duplicate some of
>    memblock_alloc_range_nid) rather than use it because hugetlb is always a
>    special case.

That's fine by me I think. Perhaps we can also hide the complexity of
the allocation strategy here? For example, we won't have to expose the
semantic of SCRATCH_EXT only allocations to the rest of the memblock
allocation machinery.

>
> 2. Split memblock_reserve() part from kho_mem_retrieve() to run before
>    hugetlb allocations. With this we won't need new types and APIs, we can
>    ensure that hugetlb allocations do not use SCRATCH by reserving scratch
>    areas before hugetlb allocations and releasing them afterwards.
>    Obviously this is the slowest option as it will slow down all memblock
>    allocations from the point we memblock_reserve() preserved memory. Still
>    realistically I wouldn't expect large impact on performance because the
>    heaviest part there is reserving of the preserved memory that has to
>    happen anyway. Also, I don't thing that a system that uses a lot of
>    gigantic pages will have a lot of preserved chunks scattered around.

This option would make my life easier (in the short term) because I
think it is the easiest to implement. But I don't think this is good for
KHO long term.

First, I think the memblock_reserve() calls are band-aid and we should
get rid of them entirely. They scale terribly. With even a few gigabytes
of memory they go in the order of seconds. I ran a quick experiment, and
with around 4 GiB of 4k preservations, it takes around 650 ms on my test
machine.

And this isn't a theoretical use case. We are looking to preserve
similar sized memfds on our machines. This would be on top of having a
few terabytes in 1G huge pages. I think we should integrate KHO more
deeply into the memblock -> buddy handover sequence.

Second, while you pay the same cost on doing the memblock_reserve(), the
reservations need to happen much sooner. Right now, the reservations
happen right before memblock_free_all(). With this we would need to move
the reservations to mm_core_init_early() which happens right after
setup_arch(). So every allocation after mm_core_init_early() will take a
hit. While I haven't measured the performance impact with this, I
imagine it won't be trivial.

>
> 3. Move the complexity into hugetlb and make it preserve all the gigantic
>    pages with KHO. This means, though, that we won't be able to increase
>    the number of gigantic pages after the first boot (although decreasing
>    it seems easy) and that we need to let scratch auto scaling understand
>    what were the "normal" memblock allocations and what were the
>    allocations of the gigantic pages.

This is a viable option. There is of course the limitation of not being
able to add more gigantic pages but I suppose most workloads should be
able to live with that. The obvious downside is that you don't have a
fallback if scratch runs out so KHO will be a little less resilient.

The complexity with this comes with correctly preserving all the
gigantic pages. Hugepages can be removed at runtime, so we would need to
plug into the removal mechanism. There's also demotion and other things
that need to be taken care of. The reason we need to track this is
because what if the freed hugepage now contains preserved memory from
something else.

>
> 4. Invert SCRATCH_EXT logic and instead of freeing large chunks around the
>    preserved memory to SCRATCH_EXT, reserve memory surrounding the
>    preserved areas and release scratch_only before hugetlb allocations.
>    We'd still need to somehow prevent hugetlb allocation spilling into
>    scratch and there's a nasty piece of releasing the memory around the
>    preserved chunks. On the bright side, I think it's feasible to defer the
>    release of those regions and free them when we are already
>    multithreaded. This probably the most involved alternative but it also
>    could help with the bottleneck of kho_mem_retrieve() creating too many
>    memblock.reserved regions.

I think we should fix kho_mem_retrieve() by making memblock_free_all()
and deferred_free_pages() aware of KHO. I feel that splitting the
freeing of pages into two phases is going to make the early boot more
complicated than it needs to. And on top of all this we still need to do
the special case thing for hugetlb anyway. So I am not sure if this is a
good idea...

>
> Thoughts?

I personally like option 1 the most (maybe because it is very similar to
my patchset). My point is, if you think the "extended scratch" is fine
as a concept and we only need to clean up the neighbouring stuff like
memblock allocation strategy, then I would like to pursue this since I
think this has the most potential.

I think option 3 is also viable. While I personally am not a huge fan, I
don't have it either. So if you really dislike 1 then I can try to make
3 work.

Option 4 can kind of work, but it just gives me a bad feeling that it
will make early boot memory management more complex and might result in
flaky boot failures.

Option 2 is the worst of the lot I think. It is the easiest to
implement, but I think it just adds to the tech debt that
memblock_reserve() is.

So, in summary, I would like to pursue option 1 and try to make it more
appetizing. But I would like to at least know if you hate the "extended
scratch" (ignore the name) as a concept or only the code it results in.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 08:26:51 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 17:26:51 +0200
Subject: [BUG] liveupdate: incoming/outgoing session ioctls accepted in
 wrong phase can panic
In-Reply-To: <CAPJnbgLtEUQ4Kv1ENM4ku0S0JFS8T49MEHFa4MpoT3WRuuVfFA@mail.gmail.com>
	(Yifei Chu's message of "Sun, 24 May 2026 10:44:31 -0400")
References: <CAPJnbgLtEUQ4Kv1ENM4ku0S0JFS8T49MEHFa4MpoT3WRuuVfFA@mail.gmail.com>
Message-ID: <2vxzjysr7bno.fsf@kernel.org>

On Sun, May 24 2026, Yifei Chu wrote:

> Hello,
>
> Short version: I found that liveupdate session ioctls can be accepted on the wrong kind of session. In my tests, that
> lets userspace drive memfd LUO into KHO restore paths for current-kernel preserved memory, which hits kho_restore_page()
> warnings and panics with panic_on_warn=1.

I already sent a fix for this:
https://lore.kernel.org/kexec/20260519122428.2378446-1-pratyush at kernel.org/

I need to do a refresh of this patch with some tests.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 08:30:34 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 17:30:34 +0200
Subject: [BUG] liveupdate/memfd: duplicate memfd preserve restores same
 KHO folio twice
In-Reply-To: <CAPJnbg+nrOfyYGaX7rPP1pQunzuLb9QXVbi_cnBhj8NoiXjM5g@mail.gmail.com>
	(Yifei Chu's message of "Sun, 24 May 2026 10:44:40 -0400")
References: <CAPJnbg+nrOfyYGaX7rPP1pQunzuLb9QXVbi_cnBhj8NoiXjM5g@mail.gmail.com>
Message-ID: <2vxzfr3f7bhh.fsf@kernel.org>

On Sun, May 24 2026, Yifei Chu wrote:

> Hello,
>
> Short version: I found that the same memfd can be preserved more than once in a single liveupdate session under different
> tokens. After kexec, those tokens can both refer to the same preserved KHO folio, and the second restore path hits
> kho_restore_page(). With panic_on_warn=1, this is a reproducible panic.

Pasha fixed this with 00d0b372374f ("liveupdate: prevent double
management of files"). It landed in v7.1-rc1.

Since you seem to be using 7.0.9, please port this patch to the 7.0.y
series and try again. If this fixes the problem, please send it to
stable at vger.kernel.org so it can be included in the 7.0.y series.

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 08:35:16 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 17:35:16 +0200
Subject: [PATCH v6 04/12] PCI: liveupdate: Document driver binding
 responsibilities
In-Reply-To: <20260522202410.3104264-5-dmatlack@google.com> (David Matlack's
	message of "Fri, 22 May 2026 20:24:02 +0000")
References: <20260522202410.3104264-1-dmatlack@google.com>
	<20260522202410.3104264-5-dmatlack@google.com>
Message-ID: <2vxzbje37b9n.fsf@kernel.org>

On Fri, May 22 2026, David Matlack wrote:

> Document how driver binding works during a Live Update and what the PCI
> core expects of drivers and users. Note that this is only a description
> of the current division of responsibilities. These can change in the
> future if we decide.

Nit: Should you also note this in the documentation that this can change
in the future?

>
> Signed-off-by: David Matlack <dmatlack at google.com>
> ---
>  drivers/pci/liveupdate.c | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
>
> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> index 96c43b84532c..4f2ec6ffdd16 100644
> --- a/drivers/pci/liveupdate.c
> +++ b/drivers/pci/liveupdate.c
> @@ -70,6 +70,22 @@
>   * preserved. These may be relaxed in the future:
>   *
>   *  * The device cannot be a Virtual Function (VF).
> + *
> + * Driver Binding
> + * ==============
> + *
> + * In the outgoing kernel, it is the driver's responsibility to ensure that it
> + * does not release a device between pci_liveupdate_preserve() and
> + * pci_liveupdate_unpreserve().
> + *
> + * In the incoming kernel, it is the driver's responsibility to ensure that it
> + * does not release a preserved device between probe() and
> + * pci_liveupdate_finish().
> + *
> + * It is the user's responsibility to ensure that incoming preserved devices are
> + * bound to the correct driver. i.e. The PCI core does not protect against a
> + * device getting preserved by driver A in the outgoing kernel and then getting
> + * bound to driver B in the incoming kernel.
>   */
>  
>  #define pr_fmt(fmt) "PCI: liveupdate: " fmt

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 09:24:19 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 18:24:19 +0200
Subject: [PATCH v2 01/10] liveupdate: centralize state management into
 struct luo_ser
In-Reply-To: <20260514222628.931312-2-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Thu, 14 May 2026 22:26:19 +0000")
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
	<20260514222628.931312-2-pasha.tatashin@soleen.com>
Message-ID: <2vxz7bor78zw.fsf@kernel.org>

On Thu, May 14 2026, Pasha Tatashin wrote:

> Transition the LUO to ABI v2, which centralizes state management into a
> single struct luo_ser header.
>
> Previously, LUO state was spread across multiple FDT properties and
> subnodes. ABI v2 simplifies this by placing all core state, including
> the liveupdate number and physical addresses for sessions and FLB
> headers into a centralized struct luo_ser.

Makes a lot of sense I think.

But nit: this brings about a semantic difference: now sessions and FLB
serialization format is no longer independent of LUO. I think that makes
sense but can you please spell that out in the commit message.

>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
>  kernel/liveupdate/luo_core.c     | 59 ++++++++++++++-------
>  kernel/liveupdate/luo_flb.c      | 65 ++++-------------------
>  kernel/liveupdate/luo_internal.h |  8 +--
>  kernel/liveupdate/luo_session.c  | 57 +++-----------------
>  5 files changed, 93 insertions(+), 187 deletions(-)
>
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 46750a0ddf88..1b2f865a771a 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -30,52 +30,25 @@
>   *   .. code-block:: none
>   *
>   *     / {
> - *         compatible = "luo-v1";
> - *         liveupdate-number = <...>;
> - *
> - *         luo-session {
> - *             compatible = "luo-session-v1";
> - *             luo-session-header = <phys_addr_of_session_header_ser>;
> - *         };
> - *
> - *         luo-flb {
> - *             compatible = "luo-flb-v1";
> - *             luo-flb-header = <phys_addr_of_flb_header_ser>;
> - *         };
> + *         compatible = "luo-v2";
> + *         luo-abi-header = <phys_addr_of_luo_ser>;

While you are at it, perhaps also get rid of the LUO FDT entirely? With
this change it only holds the compatible and a pointer to a blob. Might
as well move the compatible string to the blob and use it directly. Do
you see any benefits of sticking with the FDT here? We have already
removed it from memfd for example (and AFAIK the upcoming PCI and VFIO
patches also do not use it).

>   *     };
>   *
>   * Main LUO Node (/):
>   *
> - *   - compatible: "luo-v1"
> + *   - compatible: "luo-v2"
>   *     Identifies the overall LUO ABI version.
> - *   - liveupdate-number: u64
> - *     A counter tracking the number of successful live updates performed.
> @@ -109,13 +82,26 @@
>  
>  /*
>   * The LUO FDT hooks all LUO state for sessions, fds, etc.
> - * In the root it also carries "liveupdate-number" 64-bit property that
> - * corresponds to the number of live-updates performed on this machine.
>   */
>  #define LUO_FDT_SIZE		PAGE_SIZE
>  #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
> -#define LUO_FDT_COMPATIBLE	"luo-v1"
> -#define LUO_FDT_LIVEUPDATE_NUM	"liveupdate-number"
> +#define LUO_FDT_COMPATIBLE	"luo-v2"
> +#define LUO_FDT_ABI_HEADER	"luo-abi-header"

Nit: If we do go with FDT, I suppose "luo-ser-pa" (or "luo-ser"?) would
be more descriptive?

> +
> +/**
> + * struct luo_ser - Centralized LUO ABI header.
> diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
> index 8f5c5dd01cd0..7ccc59981297 100644
> --- a/kernel/liveupdate/luo_flb.c
> +++ b/kernel/liveupdate/luo_flb.c
> @@ -159,8 +159,8 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
>  
>  static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
>  {
> -	struct luo_flb_private *private = luo_flb_get_private(flb);
>  	struct luo_flb_header *fh = &luo_flb_global.incoming;
> +	struct luo_flb_private *private = luo_flb_get_private(flb);

Nit: this doesn't seem to do anything. Is this a stray change?

>  	struct liveupdate_flb_op_args args = {0};
>  	bool found = false;
>  	int err;

> -int __init luo_flb_setup_incoming(void *fdt_in)
> +void __init luo_flb_setup_incoming(u64 flbs_pa)
>  {
>  	struct luo_flb_header_ser *header_ser;
> -	int err, header_size, offset;
> -	const void *ptr;
> -	u64 header_ser_pa;
> -
> -	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
> -	if (offset < 0) {
> -		pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
>  
> -		return -ENOENT;
> +	if (flbs_pa) {
> +		header_ser = phys_to_virt(flbs_pa);
> +		luo_flb_global.incoming.header_ser = header_ser;
> +		luo_flb_global.incoming.ser = (void *)(header_ser + 1);
> +		luo_flb_global.incoming.active = true;

Nit: Not getting FLB data resulted in a error print. I suppose we should
keep that here as well.

>  	}
> -
> -	err = fdt_node_check_compatible(fdt_in, offset,
> -					LUO_FDT_FLB_COMPATIBLE);
> -	if (err) {
> -		pr_err("FLB node is incompatible with '%s' [%d]\n",
> -		       LUO_FDT_FLB_COMPATIBLE, err);
> -
> -		return -EINVAL;
> -	}
> -
> -	header_size = 0;
> -	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_FLB_HEADER, &header_size);
> -	if (!ptr || header_size != sizeof(u64)) {
> -		pr_err("Unable to get FLB header property '%s' [%d]\n",
> -		       LUO_FDT_FLB_HEADER, header_size);
> -
> -		return -EINVAL;
> -	}
> -
> -	header_ser_pa = get_unaligned((u64 *)ptr);
> -	header_ser = phys_to_virt(header_ser_pa);
> -
> -	luo_flb_global.incoming.header_ser = header_ser;
> -	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
> -	luo_flb_global.incoming.active = true;
> -
> -	return 0;
>  }
>  
>  /**

> -int __init luo_session_setup_incoming(void *fdt_in)
> +int __init luo_session_setup_incoming(u64 sessions_pa)
>  {
>  	struct luo_session_header_ser *header_ser;
> -	int err, header_size, offset;
> -	u64 header_ser_pa;
> -	const void *ptr;
> -
> -	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
> -	if (offset < 0) {
> -		pr_err("Unable to get session node: [%s]\n",
> -		       LUO_FDT_SESSION_NODE_NAME);
> -		return -EINVAL;
> -	}
>  
> -	err = fdt_node_check_compatible(fdt_in, offset,
> -					LUO_FDT_SESSION_COMPATIBLE);
> -	if (err) {
> -		pr_err("Session node incompatible [%s]\n",
> -		       LUO_FDT_SESSION_COMPATIBLE);
> -		return -EINVAL;
> -	}
> -
> -	header_size = 0;
> -	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
> -	if (!ptr || header_size != sizeof(u64)) {
> -		pr_err("Unable to get session header '%s' [%d]\n",
> -		       LUO_FDT_SESSION_HEADER, header_size);
> -		return -EINVAL;
> +	if (sessions_pa) {

Nit: same thing about the error print here.

> +		header_ser = phys_to_virt(sessions_pa);
> +		luo_session_global.incoming.header_ser = header_ser;
> +		luo_session_global.incoming.ser = (void *)(header_ser + 1);
> +		luo_session_global.incoming.active = true;
>  	}
>  
> -	header_ser_pa = get_unaligned((u64 *)ptr);
> -	header_ser = phys_to_virt(header_ser_pa);
> -
> -	luo_session_global.incoming.header_ser = header_ser;
> -	luo_session_global.incoming.ser = (void *)(header_ser + 1);
> -	luo_session_global.incoming.active = true;
> -
>  	return 0;
>  }

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 09:26:10 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 18:26:10 +0200
Subject: [PATCH v2 02/10] liveupdate: Extract luo_file_deserialize_one
 helper
In-Reply-To: <20260514222628.931312-3-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Thu, 14 May 2026 22:26:20 +0000")
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
	<20260514222628.931312-3-pasha.tatashin@soleen.com>
Message-ID: <2vxz33zf78wt.fsf@kernel.org>

On Thu, May 14 2026, Pasha Tatashin wrote:

> Extract the logic for deserializing single entries for files into
> separate helper functions. In preparation to a linked-block
> serialization for files.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 09:27:39 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 18:27:39 +0200
Subject: [PATCH v2 03/10] liveupdate: Extract
 luo_session_deserialize_one helper
In-Reply-To: <20260514222628.931312-4-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Thu, 14 May 2026 22:26:21 +0000")
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
	<20260514222628.931312-4-pasha.tatashin@soleen.com>
Message-ID: <2vxzy0h75u9w.fsf@kernel.org>

On Thu, May 14 2026, Pasha Tatashin wrote:

> Extract the logic for deserializing single entries for sessions into
> separate helper functions. In preparation to a linked-block
> serialization for sessions.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 10:34:13 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 19:34:13 +0200
Subject: [PATCH 1/7] LoongArch: Add KHO basic support
In-Reply-To: <20260525062810.103367-2-dongtai.guo@linux.dev> (George Guo's
	message of "Mon, 25 May 2026 14:28:04 +0800")
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
	<20260525062810.103367-2-dongtai.guo@linux.dev>
Message-ID: <2vxztsrv5r6y.fsf@kernel.org>

On Mon, May 25 2026, George Guo wrote:

> From: George Guo <guodongtai at kylinos.cn>
>
> Enable Kexec Handover on LoongArch64:
>
> - Kconfig: select ARCH_SUPPORTS_KEXEC_HANDOVER for 64BIT
> - machine_kexec_file: add cmdline_add_kho() to pass the KHO FDT and
>   scratch buffer addresses to the next kernel via the "kho_handover="
>   command-line parameter
> - setup: parse "kho_handover=" early and call kho_populate() to hand
>   memory regions to the KHO core
>
> Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
> Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
> Signed-off-by: George Guo <guodongtai at kylinos.cn>
> ---
>  arch/loongarch/Kconfig                     |  3 +++
>  arch/loongarch/kernel/machine_kexec_file.c | 22 ++++++++++++++++++
>  arch/loongarch/kernel/setup.c              | 27 ++++++++++++++++++++++
>  3 files changed, 52 insertions(+)
>
[...]
> diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
> index 5584b798ba46..ddf4d0e0e7fd 100644
> --- a/arch/loongarch/kernel/machine_kexec_file.c
> +++ b/arch/loongarch/kernel/machine_kexec_file.c
> @@ -55,6 +55,24 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
>  	*cmdline_tmplen += initrd_strlen;
>  }
>  
> +#ifdef CONFIG_KEXEC_HANDOVER
> +/* Add "kho_handover=<fdt_size>@<fdt_addr>,<scratch_size>@<scratch_addr>" to cmdline. */
> +static void cmdline_add_kho(struct kimage *image, unsigned long *cmdline_tmplen,
> +			    char *modified_cmdline)
> +{
> +	int n;
> +
> +	if (!image->kho.fdt || !image->kho.scratch)
> +		return;
> +
> +	n = sprintf(modified_cmdline + *cmdline_tmplen,
> +	    "kho_handover=0x%llx at 0x%llx,0x%lx at 0x%llx ",
> +	    (u64)PAGE_SIZE,               image->kho.fdt,
> +	    image->kho.scratch->bufsz,    (u64)image->kho.scratch->mem);
> +	*cmdline_tmplen += n;

Do you have nothing else for a bootloader to pass data to the kernel?
Passing this via the command line seems crazy... All these addresses are
now available directly to unprivileged userspace. Can't you use the
device tree like we do on ARM64?

Also, this commandline parameter isn't documented anywhere.

> +}
> +#endif
> +
>  #ifdef CONFIG_CRASH_DUMP
>  
>  static int prepare_elf_headers(void **addr, unsigned long *sz)
> @@ -220,6 +238,10 @@ int load_other_segments(struct kimage *image,
>  		cmdline_add_initrd(image, &cmdline_tmplen, modified_cmdline, initrd_load_addr);
>  	}
>  
> +#ifdef CONFIG_KEXEC_HANDOVER
> +	cmdline_add_kho(image, &cmdline_tmplen, modified_cmdline);
> +#endif
> +
>  	if (cmdline_len + cmdline_tmplen > COMMAND_LINE_SIZE) {
>  		pr_err("Appending command line exceeds COMMAND_LINE_SIZE\n");
>  		ret = -EINVAL;
> diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
> index 839b23edee87..5934ba6f13e3 100644
> --- a/arch/loongarch/kernel/setup.c
> +++ b/arch/loongarch/kernel/setup.c
> @@ -48,6 +48,7 @@
>  #include <asm/setup.h>
>  #include <asm/time.h>
>  #include <asm/unwind.h>
> +#include <linux/kexec_handover.h>
>  
>  #define SMBIOS_BIOSSIZE_OFFSET		0x09
>  #define SMBIOS_BIOSEXTERN_OFFSET	0x13
> @@ -227,6 +228,32 @@ static int __init early_parse_mem(char *p)
>  }
>  early_param("mem", early_parse_mem);
>  
> +#ifdef CONFIG_KEXEC_HANDOVER
> +static int __init early_parse_kho(char *p)
> +{
> +	phys_addr_t fdt_addr, scratch_addr;
> +	u64 fdt_size, scratch_size;
> +
> +	if (!p)
> +		return -EINVAL;
> +
> +	fdt_size = memparse(p, &p);
> +	if (*p++ != '@')

Can you please wrap these in parenthesis? I am too dumb to remember all
the precedence rules and this immediately makes me question whether we
are doing (*p)++ or *(p++).

> +		return -EINVAL;
> +	fdt_addr = memparse(p, &p);
> +	if (*p++ != ',')
> +		return -EINVAL;
> +	scratch_size = memparse(p, &p);
> +	if (*p++ != '@')
> +		return -EINVAL;
> +	scratch_addr = memparse(p, &p);
> +
> +	kho_populate(fdt_addr, fdt_size, scratch_addr, scratch_size);
> +	return 0;
> +}
> +early_param("kho_handover", early_parse_kho);
> +#endif
> +
>  static void __init arch_reserve_vmcore(void)
>  {
>  #ifdef CONFIG_PROC_VMCORE

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Mon May 25 10:49:28 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Mon, 25 May 2026 19:49:28 +0200
Subject: [PATCH 2/7] LoongArch: kho: strip stale kho_handover= from
 reused cmdline
In-Reply-To: <20260525062810.103367-3-dongtai.guo@linux.dev> (George Guo's
	message of "Mon, 25 May 2026 14:28:05 +0800")
References: <20260525062810.103367-1-dongtai.guo@linux.dev>
	<20260525062810.103367-3-dongtai.guo@linux.dev>
Message-ID: <2vxzpl2j5qhj.fsf@kernel.org>

On Mon, May 25 2026, George Guo wrote:

> From: George Guo <guodongtai at kylinos.cn>
>
> When kexec is invoked with --reuse-cmdline after a liveupdate boot, the
> running kernel's command line already contains a kho_handover= parameter
> from the previous kexec load.  load_other_segments() prepends a fresh
> kho_handover= for the new handover FDT but then appends the original
> cmdline verbatim, resulting in two kho_handover= entries:
>
>   kho_handover=0x4000@<new_fdt>,... kho_handover=0x4000@<stale_fdt>,...
>
> early_param() calls early_parse_kho() for each occurrence in order, so
> kho_populate() is invoked twice and the second call overwrites the first
> with the stale FDT address.  The stale address no longer holds a valid
> KHO FDT, causing __kho_radix_walk_tree() to dereference a garbage
> pointer and panic early in mm_core_init().
>
> Fix this by adding the new kho_handover= to the cmdline prefix first,
> then stripping any stale kho_handover= tokens from the appended original
> cmdline portion, so only the freshly generated entry survives.
>
> Signed-off-by: George Guo <guodongtai at kylinos.cn>
> ---
>  arch/loongarch/kernel/machine_kexec_file.c | 35 ++++++++++++++++++++++
>  1 file changed, 35 insertions(+)
>
> diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
> index ddf4d0e0e7fd..ffaedd055e62 100644
> --- a/arch/loongarch/kernel/machine_kexec_file.c
> +++ b/arch/loongarch/kernel/machine_kexec_file.c
> @@ -71,6 +71,37 @@ static void cmdline_add_kho(struct kimage *image, unsigned long *cmdline_tmplen,
>  	    image->kho.scratch->bufsz,    (u64)image->kho.scratch->mem);
>  	*cmdline_tmplen += n;
>  }
> +
> +/*
> + * Remove all "kho_handover=..." tokens from cmdline.  Needed when
> + * --reuse-cmdline is used: the running kernel's cmdline already carries a
> + * stale kho_handover= from the previous kexec load; without removal the new
> + * kernel sees two entries and kho_populate() ends up using the wrong (stale)
> + * FDT address.
> + */
> +static void cmdline_remove_kho(char *cmdline)
> +{
> +	const char *key = "kho_handover=";
> +	size_t key_len = strlen(key);
> +	char *p = cmdline;
> +
> +	while ((p = strstr(p, key)) != NULL) {
> +		char *start = p;
> +		char *end;
> +
> +		/* Only match at a token boundary */
> +		if (start != cmdline && *(start - 1) != ' ') {
> +			p += key_len;
> +			continue;
> +		}
> +		end = start + key_len;
> +		while (*end && *end != ' ')
> +			end++;
> +		while (*end == ' ')
> +			end++;
> +		memmove(start, end, strlen(end) + 1);
> +	}
> +}

Ugh, modifying the commandline supplied by userspace feels odd... Can
you at all do this via device tree?

If not, would it make more sense to reject kexec_load if command line
has kho_handover= and teach kexec-tools to strip it on its side?

>  #endif
>  
>  #ifdef CONFIG_CRASH_DUMP
> @@ -249,6 +280,10 @@ int load_other_segments(struct kimage *image,
>  	}
>  
>  	memcpy(modified_cmdline + cmdline_tmplen, cmdline, cmdline_len);
> +#ifdef CONFIG_KEXEC_HANDOVER
> +	/* Strip stale kho_handover= that --reuse-cmdline may have carried over */
> +	cmdline_remove_kho(modified_cmdline + cmdline_tmplen);
> +#endif
>  	cmdline = modified_cmdline;
>  	image->arch.cmdline_ptr = (unsigned long)cmdline;

-- 
Regards,
Pratyush Yadav


From ltao at redhat.com  Mon May 25 21:58:07 2026
From: ltao at redhat.com (Tao Liu)
Date: Tue, 26 May 2026 16:58:07 +1200
Subject: [kexec-tools][PATCH] RISC-V: add kexec_load support for vmlinuz
Message-ID: <20260526045806.23933-2-ltao@redhat.com>

Copy arm64 code and change for riscv so that the kexec -c can load
a vmlinuz image.

Signed-off-by: Tao Liu <ltao at redhat.com>
---
 kexec/arch/riscv/Makefile              |   1 +
 kexec/arch/riscv/image-header.h        |  12 +++
 kexec/arch/riscv/kexec-riscv.c         |   1 +
 kexec/arch/riscv/kexec-riscv.h         |   6 ++
 kexec/arch/riscv/kexec-vmlinuz-riscv.c | 112 +++++++++++++++++++++++++
 5 files changed, 132 insertions(+)
 create mode 100644 kexec/arch/riscv/kexec-vmlinuz-riscv.c

diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
index 18a997b..6ba8976 100644
--- a/kexec/arch/riscv/Makefile
+++ b/kexec/arch/riscv/Makefile
@@ -4,6 +4,7 @@
 riscv_KEXEC_SRCS = kexec/arch/riscv/crashdump-riscv.c
 riscv_KEXEC_SRCS += kexec/arch/riscv/kexec-elf-riscv.c
 riscv_KEXEC_SRCS += kexec/arch/riscv/kexec-image-riscv.c
+riscv_KEXEC_SRCS += kexec/arch/riscv/kexec-vmlinuz-riscv.c
 riscv_KEXEC_SRCS += kexec/arch/riscv/kexec-riscv.c
 
 riscv_DT_OPS += kexec/dt-ops.c
diff --git a/kexec/arch/riscv/image-header.h b/kexec/arch/riscv/image-header.h
index a677546..7e950ee 100644
--- a/kexec/arch/riscv/image-header.h
+++ b/kexec/arch/riscv/image-header.h
@@ -85,4 +85,16 @@ static inline uint64_t riscv_header_image_size(const struct riscv_image_header *
 	return le64toh(h->image_size);
 }
 
+static const uint8_t riscv_image_pe_sig[2] = {'M', 'Z'};
+static const uint8_t riscv_pe_machtype[6] = {'P','E', 0x0, 0x0, 0x64, 0x50};
+
+static inline int riscv_header_check_pe_sig(const struct riscv_image_header *h)
+{
+	if (!h)
+		return 0;
+
+	return (*((char *)&(h->code0)) == riscv_image_pe_sig[0] &&
+		*((char *)&(h->code0) + 1) == riscv_image_pe_sig[1]);
+}
+
 #endif
diff --git a/kexec/arch/riscv/kexec-riscv.c b/kexec/arch/riscv/kexec-riscv.c
index a5a12a0..a7188ac 100644
--- a/kexec/arch/riscv/kexec-riscv.c
+++ b/kexec/arch/riscv/kexec-riscv.c
@@ -34,6 +34,7 @@ const struct arch_map_entry arches[] = {
 struct file_type file_type[] = {
 	{"elf-riscv", elf_riscv_probe, elf_riscv_load, elf_riscv_usage},
 	{"image-riscv", image_riscv_probe, image_riscv_load, image_riscv_usage},
+	{"vmlinuz-riscv", pez_riscv_probe, pez_riscv_load, pez_riscv_usage},
 };
 int file_types = sizeof(file_type) / sizeof(file_type[0]);
 
diff --git a/kexec/arch/riscv/kexec-riscv.h b/kexec/arch/riscv/kexec-riscv.h
index cfb0377..f487b27 100644
--- a/kexec/arch/riscv/kexec-riscv.h
+++ b/kexec/arch/riscv/kexec-riscv.h
@@ -51,3 +51,9 @@ int image_riscv_probe(const char *buf, off_t len);
 void image_riscv_usage(void);
 int image_riscv_load(int argc, char **argv, const char *buf, off_t len,
 		   struct kexec_info *info);
+
+/* kexec-vmlinuz-riscv.c */
+int pez_riscv_probe(const char *buf, off_t len);
+void pez_riscv_usage(void);
+int pez_riscv_load(int argc, char **argv, const char *buf, off_t len,
+		   struct kexec_info *info);
diff --git a/kexec/arch/riscv/kexec-vmlinuz-riscv.c b/kexec/arch/riscv/kexec-vmlinuz-riscv.c
new file mode 100644
index 0000000..6430e37
--- /dev/null
+++ b/kexec/arch/riscv/kexec-vmlinuz-riscv.c
@@ -0,0 +1,112 @@
+/*
+ * RISC-V PE compressed Image (vmlinuz, ZBOOT) support.
+ * Based on arm64 code
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "kexec.h"
+#include "kexec-riscv.h"
+#include "image-header.h"
+#include <kexec-pe-zboot.h>
+#include "arch/options.h"
+
+static int kernel_fd = -1;
+static off_t decompressed_size;
+
+/* Returns:
+ * -1 : in case of error/invalid format (not a valid PE+compressed ZBOOT format.
+ */
+int pez_riscv_probe(const char *kernel_buf, off_t kernel_size)
+{
+	int ret = -1;
+	const struct riscv_image_header *h;
+	char *buf;
+	off_t buf_sz;
+
+	buf = (char *)kernel_buf;
+	buf_sz = kernel_size;
+	if (!buf)
+		return -1;
+	h = (const struct riscv_image_header *)buf;
+
+	dbgprintf("%s: PROBE.\n", __func__);
+	if (buf_sz < sizeof(struct riscv_image_header)) {
+		dbgprintf("%s: Not large enough to be a PE image.\n", __func__);
+		return -1;
+	}
+	if (!riscv_header_check_pe_sig(h)) {
+		dbgprintf("%s: Not an PE image.\n", __func__);
+		return -1;
+	}
+
+	if (buf_sz < sizeof(struct riscv_image_header) + h->res3) {
+		dbgprintf("%s: PE image offset larger than image.\n", __func__);
+		return -1;
+	}
+
+	if (memcmp(&buf[h->res3],
+		   riscv_pe_machtype, sizeof(riscv_pe_machtype))) {
+		dbgprintf("%s: PE header doesn't match machine type.\n", __func__);
+		return -1;
+	}
+
+	ret = pez_prepare(buf, buf_sz, &kernel_fd, &decompressed_size);
+
+	if (!ret) {
+	    /* validate the riscv specific header */
+	    struct riscv_image_header hdr_check;
+	    if (read(kernel_fd, &hdr_check, sizeof(hdr_check)) != sizeof(hdr_check))
+		goto bad_header;
+
+	    lseek(kernel_fd, 0, SEEK_SET);
+
+	    if (!riscv_header_check_magic(&hdr_check)) {
+		dbgprintf("%s: Bad riscv image header.\n", __func__);
+		goto bad_header;
+	    }
+	}
+
+	return ret;
+bad_header:
+	close(kernel_fd);
+	free(buf);
+	return -1;
+}
+
+int pez_riscv_load(int argc, char **argv, const char *buf, off_t len,
+			struct kexec_info *info)
+{
+	if (kernel_fd > 0 && decompressed_size > 0) {
+		char *kbuf;
+		off_t nread;
+		int fd;
+
+		if (info->kernel_fd > 0)
+			close(info->kernel_fd);
+		info->kernel_fd = kernel_fd;
+		fd = dup(kernel_fd);
+		if (fd < 0) {
+			dbgprintf("%s: dup fd failed.\n", __func__);
+			return -1;
+		}
+		kbuf = slurp_fd(fd, NULL, decompressed_size, &nread);
+		if (!kbuf || nread != decompressed_size) {
+			dbgprintf("%s: slurp_fd failed.\n", __func__);
+			return -1;
+		}
+		return image_riscv_load(argc, argv, kbuf, decompressed_size, info);
+	}
+
+	dbgprintf("%s: wrong kernel file descriptor.\n", __func__);
+	return -1;
+}
+
+void pez_riscv_usage(void)
+{
+	printf(
+"     An RISC-V vmlinuz, PE image of a compressed, little endian.\n"
+"     kernel, built with ZBOOT enabled.\n\n");
+}
-- 
2.47.0



From rppt at kernel.org  Tue May 26 01:09:24 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Tue, 26 May 2026 11:09:24 +0300
Subject: [PATCH] liveupdate: validate session type before performing operation
In-Reply-To: <20260519122428.2378446-1-pratyush@kernel.org>
References: <20260519122428.2378446-1-pratyush@kernel.org>
Message-ID: <177978294141.4088010.16047486364171482805.b4-ty@b4>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

On Tue, 19 May 2026 14:24:26 +0200, Pratyush Yadav wrote:
> The sessions ioctls are not applicable to all session types. PRESERVE_FD
> is only applicable to outgoing sessions. RETRIEVE_FD and FINISH are only
> valid for incoming session. Calling a incoming ioctl on an outgoing
> session is invalid and can cause file handlers to run into unexpected
> errors.
> 
> For example, a user can create a (outgoing) session, preserve a memfd,
> and then immediately do a retrieve without doing a kexec in between.
> This would result in memfd's retrieve handler to run. The handlers
> expects to be called from a post-kexec context, and will try to do a
> kho_restore_vmalloc() or kho_restore_folio() to try and restore memory.
> 
> [...]

Applied to fixes branch of liveupdate/linux.git tree, thanks!

[1/1] liveupdate: validate session type before performing operation
      commit: da7f658ccc8da60d836051a7af1c53e643f4bd11

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: fixes

--
Sincerely yours,
Mike.



From pratyush at kernel.org  Tue May 26 01:13:26 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 26 May 2026 10:13:26 +0200
Subject: [PATCH] liveupdate: validate session type before performing
 operation
In-Reply-To: <177978294141.4088010.16047486364171482805.b4-ty@b4> (Mike
	Rapoport's message of "Tue, 26 May 2026 11:09:24 +0300")
References: <20260519122428.2378446-1-pratyush@kernel.org>
	<177978294141.4088010.16047486364171482805.b4-ty@b4>
Message-ID: <2vxzldd66121.fsf@kernel.org>

On Tue, May 26 2026, Mike Rapoport wrote:

> From: Mike Rapoport (Microsoft) <rppt at kernel.org>
>
> On Tue, 19 May 2026 14:24:26 +0200, Pratyush Yadav wrote:
>> The sessions ioctls are not applicable to all session types. PRESERVE_FD
>> is only applicable to outgoing sessions. RETRIEVE_FD and FINISH are only
>> valid for incoming session. Calling a incoming ioctl on an outgoing
>> session is invalid and can cause file handlers to run into unexpected
>> errors.
>> 
>> For example, a user can create a (outgoing) session, preserve a memfd,
>> and then immediately do a retrieve without doing a kexec in between.
>> This would result in memfd's retrieve handler to run. The handlers
>> expects to be called from a post-kexec context, and will try to do a
>> kho_restore_vmalloc() or kho_restore_folio() to try and restore memory.
>> 
>> [...]
>
> Applied to fixes branch of liveupdate/linux.git tree, thanks!

Thanks! I will test the selftest update that Pasha suggested as a follow
up, since that will be content for liveupdate/next.

>
> [1/1] liveupdate: validate session type before performing operation
>       commit: da7f658ccc8da60d836051a7af1c53e643f4bd11
>
> tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
> branch: fixes
>
> --
> Sincerely yours,
> Mike.
>

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Tue May 26 04:04:06 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Tue, 26 May 2026 13:04:06 +0200
Subject: [PATCH] kho: always print scratch sizes
In-Reply-To: <ag9VrVW3E28UOsAt@plex> (Pasha Tatashin's message of "Thu, 21 May
	2026 19:07:47 +0000")
References: <20260519155324.2692594-1-pratyush@kernel.org>
	<ag9VrVW3E28UOsAt@plex>
Message-ID: <2vxzqzmy4el5.fsf@kernel.org>

On Thu, May 21 2026, Pasha Tatashin wrote:

> On 05-19 17:53, Pratyush Yadav wrote:
>> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
>> 
>> Currently, KHO only prints scratch sizes when the size is specified
>> by the command line. It prints nothing when the size is calculated
>> at runtime by counting RSRV_KERN.
>> 
>> These prints are completely useless. When the user specified the size on
>> the command line, they already know how large the scratch areas are. And
>> that information is readily available by looking at the command line. It
>> is a lot more useful to know the sizes that are calculated at runtime
>> since they can vary by kernel version. Plus, if KHO fails to allocate
>> the scratch areas, the calculated scratch sizes are not available via
>> debugfs.
>> 
>> Print the scratch sizes in kho_reserve_scratch(). Do it before making
>> the allocations. The size can provide hints for the failure reason.
>> 
>> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>> ---
>>  kernel/liveupdate/kexec_handover.c | 12 +++++++-----
>>  1 file changed, 7 insertions(+), 5 deletions(-)
>> 
>> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
>> index 33fcf848ef95..b030f53f2235 100644
>> --- a/kernel/liveupdate/kexec_handover.c
>> +++ b/kernel/liveupdate/kexec_handover.c
>> @@ -621,11 +621,6 @@ static int __init kho_parse_scratch_size(char *p)
>>  	scratch_size_pernode = sizes[2];
>>  	scratch_scale = 0;
>>  
>> -	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB pernode: %lldMiB\n",
>> -		  (u64)(scratch_size_lowmem >> 20),
>> -		  (u64)(scratch_size_global >> 20),
>> -		  (u64)(scratch_size_pernode >> 20));
>> -
>>  	return 0;
>>  }
>>  early_param("kho_scratch", kho_parse_scratch_size);
>> @@ -691,6 +686,10 @@ static void __init kho_reserve_scratch(void)
>>  		goto err_disable_kho;
>>  	}
>>  
>> +	pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB\n",
>> +		  (u64)(scratch_size_lowmem >> 20),
>> +		  (u64)(scratch_size_global >> 20));
>> +
>>  	/*
>>  	 * reserve scratch area in low memory for lowmem allocations in the
>>  	 * next kernel
>> @@ -725,6 +724,9 @@ static void __init kho_reserve_scratch(void)
>>  	 */
>>  	for_each_node_state(nid, N_MEMORY) {
>>  		size = scratch_size_node(nid);
>> +
>> +		pr_notice("scratch_areas: nid %d: %lluMiB\n", nid, size >> 20);
>
> Please use a space instead of an underscore to match the other string: 
> "scratch areas". Keeping the prefix identical makes log parsing and 
> filtering much cleaner.

Sure, will do.

>
> Unrelated to this patch?this is something I brought up to Pratyush off-list, 
> but I really dislike the 'KHO scratch' terminology.
>
> First, it isn't actually 'scratch' space, as the kernel relies on it to 
> allocate crucial early boot memory, which it then uses permanently. 
> Second, we cannot preserve anything within this specific region. 
>
> Down the line, I'd like to rename this to something more descriptive that clearly 
> conveys both its importance to early boot and its non-preservable nature. For 
> now, the print adjustments themselves look good.
>
> I do not know it should be, but something like kho_bootstrap area sounds 
> better than kho_sratch area.

Sure. I think we can land this patch now, and then update the string
when we agree on a better name. But I won't mind waiting either if you
want to reduce churn.

>
>> +
>>  		addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES,
>>  						0, MEMBLOCK_ALLOC_ACCESSIBLE,
>>  						nid, true);
>> 
>> base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
>> -- 
>> 2.54.0.563.g4f69b47b94-goog
>> 

-- 
Regards,
Pratyush Yadav


From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:04 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:04 +0800
Subject: [PATCH v2 2/7] riscv: kexec: Place norelocate trampoline into .kexec.tramp.text
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-3-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

Move riscv_kexec_norelocate out of the generic .text section and into
a dedicated executable trampoline section, .kexec.tramp.text.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/include/asm/kexec.h     | 4 ++++
 arch/riscv/kernel/kexec_relocate.S | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/arch/riscv/include/asm/kexec.h b/arch/riscv/include/asm/kexec.h
index b9ee8346cc8c..6466c1f00d41 100644
--- a/arch/riscv/include/asm/kexec.h
+++ b/arch/riscv/include/asm/kexec.h
@@ -75,4 +75,8 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start,
 			unsigned long cmdline_len);
 #endif
 
+#ifndef __ASSEMBLY__
+extern char __kexec_tramp_text_start[];
+#endif
+
 #endif
diff --git a/arch/riscv/kernel/kexec_relocate.S b/arch/riscv/kernel/kexec_relocate.S
index de0a4b35d01e..af6b99f5b0fd 100644
--- a/arch/riscv/kernel/kexec_relocate.S
+++ b/arch/riscv/kernel/kexec_relocate.S
@@ -147,7 +147,7 @@ riscv_kexec_relocate_end:
 
 
 /* Used for jumping to crashkernel */
-.section ".text"
+.section ".kexec.tramp.text", "ax"
 SYM_CODE_START(riscv_kexec_norelocate)
 	/*
 	 * s0: (const) Phys address to jump to
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:02 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:02 +0800
Subject: [PATCH v2 0/7] riscv: kexec: Fix VCPU crash on kexec/kdump under KVM
Message-ID: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

In a RISC-V kernel, both kexec and crashdump need to hand off execution
to the next kernel after tearing down the current kernel address space.
However, under virtualization the guest uses two-stage address
translation, and pc does not jump to stvec after setting satp to zero,
so the legacy single-step "csrw satp,0 + stvec redirect" sequence
traps with "kvm run failed Operation not supported" and the VCPU dies.

v1 (Link below) addressed the crash path only. v2 extends the same
mechanism to the normal kexec path (kexec -l/-e) and reworks the
trampoline as a shared piece of infrastructure between the two paths.

This patch set introduces a dedicated kexec trampoline text section and
builds a minimal trampoline page table for it. Both handoffs are then
reworked into a two-pass trampoline:

    1. First enter via the kernel VA, install the trampoline page table,
       and jump to the trampoline VA(=PA) of the entry stub;
    2. Continue execution with PC already on a PA, drop SATP with
       csrw satp,0 (now safe because PC re-anchoring is moot), and
       jump directly to the target -- either the crash kernel entry
       (crash path) or the per-image control_code_buffer that runs
       the relocate body with SATP=0 throughout (normal path).

With this, both kexec and crashdump in RISC-V guests become robust
against the two-stage translation.

Tested on QEMU virt under two configurations:

  * HS-mode bare (QEMU TCG) -- regression check
      - normal kexec: kexec -l/-e succeeds, second kernel boots and
                      prints the userspace SECOND BOOT marker.
      - crash kdump: panic triggers crash kernel boot, /proc/vmcore
                     opens cleanly in crash and shows the panic
                     backtrace.

  * VS-mode (L0 x86 + QEMU TCG -> L1 riscv64 + KVM -> L2)
      Before this series, both paths die with
          kvm run failed Operation not supported
      and an all-zero M-mode register dump on the SATP transition.
      After this series, both paths succeed end-to-end and the
      vmcore opens cleanly in crash.

---
Changes in v2:
    - Extend the trampoline mechanism to the normal kexec path; the
      legacy stvec trick in riscv_kexec_relocate (csrw satp,0 followed
      by jr s6 to land at PA) is replaced by a small wrapper
      riscv_kexec_relocate_entry in .kexec.tramp.text that performs
      the same two-step transition used by the crash path and then
      hands off to the PA of control_code_buffer.   
    - Page-align both ends of the .kexec.tramp.text section so no
      .rodata neighbour can leak into the identity-mapped executable
      trampoline page, and assert the section fits within one page.
    - In machine_kexec() pin t3 = 0 and the call arguments (a0..a4)
      to their ABI registers via local register asm variables and
      perform the final jr inside the inline asm block, so the
      compiler can never insert a scratch use of t3 between the
      argument setup and the trampoline entry. The bare
      "asm volatile ("li t3, 0" ::: "t3")" placebo used in v1 is
      not actually a guarantee.
    - Link to v1:
      https://lore.kernel.org/linux-riscv/20260324114527.91494-1-fangyu.yu at linux.alibaba.com/

Fangyu Yu (7):
  riscv: Add kexec trampoline text section to vmlinux.lds.S
  riscv: kexec: Place norelocate trampoline into .kexec.tramp.text
  riscv: kexec: Build trampoline page tables for crash kernel entry
  riscv: kexec: Switch to trampoline page table before norelocate
  riscv: kexec: Always build the trampoline page table
  riscv: kexec: Add the relocate-trampoline wrapper
  riscv: kexec: Route normal kexec through the trampoline page table

 arch/riscv/include/asm/kexec.h     |   5 ++
 arch/riscv/kernel/image-vars.h     |  14 ++++
 arch/riscv/kernel/kexec_relocate.S |  97 +++++++++++++++++------
 arch/riscv/kernel/machine_kexec.c  | 123 +++++++++++++++++++++++++++--
 arch/riscv/kernel/vmlinux.lds.S    |   1 +
 5 files changed, 209 insertions(+), 31 deletions(-)

-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:05 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:05 +0800
Subject: [PATCH v2 3/7] riscv: kexec: Build trampoline page tables for crash kernel entry
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-4-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

Crash kexec uses riscv_kexec_norelocate as a trampoline to jump into
the crashkernel. Add a small helper to build dedicated 4KB page tables
that map the trampoline page as executable.

Two mappings are installed:
  - VA(__kexec_tramp_text_start) -> PA(__kexec_tramp_text_start)
  - PA(__kexec_tramp_text_start) -> PA(__kexec_tramp_text_start)

This allows the trampoline to run regardless of whether it is entered
via its linked virtual address or its physical address.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/kernel/machine_kexec.c | 67 +++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c
index 2306ce3e5f22..9b4b495e7c15 100644
--- a/arch/riscv/kernel/machine_kexec.c
+++ b/arch/riscv/kernel/machine_kexec.c
@@ -18,6 +18,65 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 
+static pgd_t kexec_tramp_pgd[PTRS_PER_PGD] __aligned(PAGE_SIZE);
+static p4d_t kexec_tramp_p4d[PTRS_PER_P4D] __aligned(PAGE_SIZE);
+static pud_t kexec_tramp_pud[PTRS_PER_PUD] __aligned(PAGE_SIZE);
+static pmd_t kexec_tramp_pmd[PTRS_PER_PMD] __aligned(PAGE_SIZE);
+static pte_t kexec_tramp_pte[PTRS_PER_PTE] __aligned(PAGE_SIZE);
+static p4d_t kexec_tramp_p4d2[PTRS_PER_P4D] __aligned(PAGE_SIZE);
+static pud_t kexec_tramp_pud2[PTRS_PER_PUD] __aligned(PAGE_SIZE);
+static pmd_t kexec_tramp_pmd2[PTRS_PER_PMD] __aligned(PAGE_SIZE);
+static pte_t kexec_tramp_pte2[PTRS_PER_PTE] __aligned(PAGE_SIZE);
+
+static void map_tramp_page(p4d_t *p4ds, pud_t *puds, pmd_t *pmds, pte_t *ptes,
+			   unsigned long va, unsigned long pa)
+{
+	pgd_t *pgd = (pgd_t *)kexec_tramp_pgd + pgd_index(va);
+	pmd_t *pmd;
+
+	if (pgtable_l5_enabled) {
+		p4d_t *p4d;
+
+		set_pgd(pgd, pfn_pgd(PFN_DOWN(__pa_symbol(p4ds)), PAGE_TABLE));
+		p4d = (p4d_t *)p4ds + p4d_index(va);
+		if (pgtable_l4_enabled)
+			set_p4d(p4d, pfn_p4d(PFN_DOWN(__pa_symbol(puds)),
+				PAGE_TABLE));
+		else
+			set_p4d(p4d, pfn_p4d(PFN_DOWN(__pa_symbol(pmds)),
+				PAGE_TABLE));
+	} else {
+		set_pgd(pgd, pfn_pgd(PFN_DOWN(__pa_symbol(puds)), PAGE_TABLE));
+	}
+
+	if (pgtable_l4_enabled) {
+		pud_t *pud = (pud_t *)puds + pud_index(va);
+
+		set_pud(pud, pfn_pud(PFN_DOWN(__pa_symbol(pmds)), PAGE_TABLE));
+		pmd = (pmd_t *)pmds + pmd_index(va);
+	} else {
+		pmd = (pmd_t *)puds + pmd_index(va);
+	}
+	set_pmd(pmd, pfn_pmd(PFN_DOWN(__pa_symbol(ptes)), PAGE_TABLE));
+
+	set_pte((pte_t *)ptes + pte_index(va),
+		pfn_pte(PFN_DOWN(pa), PAGE_KERNEL_EXEC));
+}
+
+static void riscv_kexec_build_tramp(unsigned long va, unsigned long pa)
+{
+	/* VA -> PA: map the trampoline page via its kernel VA. */
+	map_tramp_page(kexec_tramp_p4d,  kexec_tramp_pud,
+		       kexec_tramp_pmd,  kexec_tramp_pte,  va, pa);
+
+	/*
+	 * PA -> PA: identity-map the same page so the second-pass code
+	 * can keep executing after the kernel VA mapping is dropped.
+	 */
+	map_tramp_page(kexec_tramp_p4d2, kexec_tramp_pud2,
+		       kexec_tramp_pmd2, kexec_tramp_pte2, pa, pa);
+}
+
 /*
  * machine_kexec_prepare - Initialize kexec
  *
@@ -73,6 +132,14 @@ machine_kexec_prepare(struct kimage *image)
 
 		/* Mark the control page executable */
 		set_memory_x((unsigned long) control_code_buffer, 1);
+	} else {
+		/*
+		 * Crash kexec uses riscv_kexec_norelocate as a trampoline.
+		 * Pre-build the trampoline page tables here so the panic
+		 * path only has to switch satp and jump.
+		 */
+		riscv_kexec_build_tramp((unsigned long)__kexec_tramp_text_start,
+					__pa_symbol(__kexec_tramp_text_start));
 	}
 
 	return 0;
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:06 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:06 +0800
Subject: [PATCH v2 4/7] riscv: kexec: Switch to trampoline page table before norelocate
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-5-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

Make riscv_kexec_norelocate a two-pass trampoline so it can
drop the kernel page tables while still executing from a
mapped address.

On the first entry, t3 is initialized to 0 by machine_kexec().
Loads the physical address of riscv_kexec_norelocate and the
trampoline SATP value, switches to the trampoline page table,
and jumps to the trampoline VA(=PA).

On the second entry, t3 contains the physical address of
riscv_kexec_norelocate, so the PC comparison matches and
execution continues under trampoline VA(=PA).

Since the trampoline page table is already active, replace the
previous stvec-based handoff with a direct jump to the target
entry (jr a2).

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/kernel/kexec_relocate.S | 32 ++++++++++++++++++----
 arch/riscv/kernel/machine_kexec.c  | 44 +++++++++++++++++++++++++++---
 2 files changed, 67 insertions(+), 9 deletions(-)

diff --git a/arch/riscv/kernel/kexec_relocate.S b/arch/riscv/kernel/kexec_relocate.S
index af6b99f5b0fd..2b9892bf04f2 100644
--- a/arch/riscv/kernel/kexec_relocate.S
+++ b/arch/riscv/kernel/kexec_relocate.S
@@ -147,13 +147,35 @@ riscv_kexec_relocate_end:
 
 
 /* Used for jumping to crashkernel */
+.extern kexec_tramp_satp
+.extern riscv_kexec_norelocate_pa
 .section ".kexec.tramp.text", "ax"
 SYM_CODE_START(riscv_kexec_norelocate)
+	/*
+	 * Two-pass entry:
+	 *   - 1st entry: t3 == 0 (initialized by machine_kexec()).
+	 *
+	 *   - 2nd entry: t3 holds the physical address of
+	 *     riscv_kexec_norelocate, so auipc matches t3 and we fall through
+	 *     to label 1 to continue execution under trampoline VA(=PA).
+	 */
+	auipc   t0, 0
+	beq     t0, t3, 1f
+
+	la      t0, riscv_kexec_norelocate_pa
+	REG_L   t3, 0(t0)
+	la      t0, kexec_tramp_satp
+	REG_L   t1, 0(t0)
+	csrw	CSR_SATP, t1
+	sfence.vma x0, x0
+
+	jr      t3
 	/*
 	 * s0: (const) Phys address to jump to
 	 * s1: (const) Phys address of the FDT image
 	 * s2: (const) The hartid of the current hart
 	 */
+1:
 	mv	s0, a1
 	mv	s1, a2
 	mv	s2, a3
@@ -199,13 +221,13 @@ SYM_CODE_START(riscv_kexec_norelocate)
 	csrw	CSR_SSCRATCH, zero
 
 	/*
-	 * Switch to physical addressing
-	 * This will also trigger a jump to CSR_STVEC
-	 * which in this case is the address of the new
-	 * kernel.
+	 * We are already executing from the trampoline VA with the trampoline
+	 * page table installed, so there is no need to rely on the old flow
+	 * of programming stvec and taking the implicit trap on SATP switch.
+	 * Jump directly to the target entry instead.
 	 */
-	csrw	CSR_STVEC, a2
 	csrw	CSR_SATP, zero
+	jr	a2
 
 SYM_CODE_END(riscv_kexec_norelocate)
 
diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c
index 9b4b495e7c15..00fdc42d650d 100644
--- a/arch/riscv/kernel/machine_kexec.c
+++ b/arch/riscv/kernel/machine_kexec.c
@@ -18,6 +18,8 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 
+unsigned long kexec_tramp_satp;
+unsigned long riscv_kexec_norelocate_pa;
 static pgd_t kexec_tramp_pgd[PTRS_PER_PGD] __aligned(PAGE_SIZE);
 static p4d_t kexec_tramp_p4d[PTRS_PER_P4D] __aligned(PAGE_SIZE);
 static pud_t kexec_tramp_pud[PTRS_PER_PUD] __aligned(PAGE_SIZE);
@@ -135,11 +137,17 @@ machine_kexec_prepare(struct kimage *image)
 	} else {
 		/*
 		 * Crash kexec uses riscv_kexec_norelocate as a trampoline.
-		 * Pre-build the trampoline page tables here so the panic
-		 * path only has to switch satp and jump.
+		 * Pre-build the trampoline page tables and capture the
+		 * trampoline SATP value plus the physical address of
+		 * riscv_kexec_norelocate so that the panic path only has
+		 * to switch satp and jump.
 		 */
 		riscv_kexec_build_tramp((unsigned long)__kexec_tramp_text_start,
 					__pa_symbol(__kexec_tramp_text_start));
+		WRITE_ONCE(riscv_kexec_norelocate_pa,
+			   __pa_symbol(&riscv_kexec_norelocate));
+		WRITE_ONCE(kexec_tramp_satp,
+			   PFN_DOWN(__pa_symbol(kexec_tramp_pgd)) | satp_mode);
 	}
 
 	return 0;
@@ -243,7 +251,35 @@ machine_kexec(struct kimage *image)
 
 	/* Jump to the relocation code */
 	pr_notice("Bye...\n");
-	kexec_method(first_ind_entry, jump_addr, fdt_addr,
-		     this_hart_id, kernel_map.va_pa_offset);
+	/*
+	 * Hand off to the trampoline. For KEXEC_TYPE_CRASH we go into
+	 * riscv_kexec_norelocate, which uses t3 as the 1st/2nd-pass
+	 * discriminator (must be 0 on first entry). A bare
+	 *   asm volatile ("li t3, 0" ::: "t3")
+	 * before the C call only declares t3 *modified*; the compiler is
+	 * free to use t3 as scratch when materialising args. Pin t3 = 0
+	 * (and the args) via local register variables and perform the
+	 * indirect jump inside the same inline asm so t3 == 0 is
+	 * guaranteed at the moment control leaves machine_kexec().
+	 */
+	{
+		register unsigned long a0_val asm("a0") = first_ind_entry;
+		register unsigned long a1_val asm("a1") = jump_addr;
+		register unsigned long a2_val asm("a2") = fdt_addr;
+		register unsigned long a3_val asm("a3") = this_hart_id;
+		register unsigned long a4_val asm("a4") = kernel_map.va_pa_offset;
+		register unsigned long t3_zero asm("t3") = 0;
+		register riscv_kexec_method m asm("t6") = kexec_method;
+
+		asm volatile (
+			"jr %[m]"
+			:
+			: "r" (a0_val), "r" (a1_val), "r" (a2_val),
+			  "r" (a3_val), "r" (a4_val),
+			  "r" (t3_zero),
+			  [m] "r" (m)
+			: "memory"
+		);
+	}
 	unreachable();
 }
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:08 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:08 +0800
Subject: [PATCH v2 6/7] riscv: kexec: Add the relocate-trampoline wrapper
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-7-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

Add riscv_kexec_relocate_entry to .kexec.tramp.text and the two
asm-visible globals (riscv_kexec_relocate_entry_pa and
riscv_kexec_cc_buffer_pa) that the wrapper consumes.

The wrapper performs the same two-step transition used by the crash
path: switch to the trampoline pgd, jump to the PA of self, then drop
the MMU with PC already on a PA. It finally jumps to the PA of
control_code_buffer.

machine_kexec_prepare() publishes the wrapper PA and the
control_code_buffer PA via WRITE_ONCE for non-crash images.
Nothing routes to the wrapper yet; the switchover happens in the
follow-up patch.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/include/asm/kexec.h     |  1 +
 arch/riscv/kernel/kexec_relocate.S | 37 ++++++++++++++++++++++++++++++
 arch/riscv/kernel/machine_kexec.c  |  7 ++++++
 3 files changed, 45 insertions(+)

diff --git a/arch/riscv/include/asm/kexec.h b/arch/riscv/include/asm/kexec.h
index 6466c1f00d41..b75cab959e53 100644
--- a/arch/riscv/include/asm/kexec.h
+++ b/arch/riscv/include/asm/kexec.h
@@ -53,6 +53,7 @@ typedef void (*riscv_kexec_method)(unsigned long first_ind_entry,
 				   unsigned long va_pa_off);
 
 extern riscv_kexec_method riscv_kexec_norelocate;
+extern riscv_kexec_method riscv_kexec_relocate_entry;
 
 #ifdef CONFIG_KEXEC_FILE
 extern const struct kexec_file_ops elf_kexec_ops;
diff --git a/arch/riscv/kernel/kexec_relocate.S b/arch/riscv/kernel/kexec_relocate.S
index 2b9892bf04f2..1baadad1b546 100644
--- a/arch/riscv/kernel/kexec_relocate.S
+++ b/arch/riscv/kernel/kexec_relocate.S
@@ -231,6 +231,43 @@ SYM_CODE_START(riscv_kexec_norelocate)
 
 SYM_CODE_END(riscv_kexec_norelocate)
 
+.extern riscv_kexec_relocate_entry_pa
+.extern riscv_kexec_cc_buffer_pa
+.section ".kexec.tramp.text", "ax"
+SYM_CODE_START(riscv_kexec_relocate_entry)
+	/*
+	 * Two-pass entry, identical in shape to riscv_kexec_norelocate:
+	 *   - 1st entry: t3 == 0 (initialized by machine_kexec()).
+	 *   - 2nd entry: t3 == PA of riscv_kexec_relocate_entry, so auipc
+	 *                matches t3 and we fall through to label 1.
+	 * Args a0..a4 are passed through unchanged to riscv_kexec_relocate.
+	 */
+	auipc	t0, 0
+	beq	t0, t3, 1f
+
+	la	t0, riscv_kexec_relocate_entry_pa
+	REG_L	t3, 0(t0)
+	la	t0, kexec_tramp_satp
+	REG_L	t1, 0(t0)
+	csrw	CSR_SATP, t1
+	sfence.vma x0, x0
+
+	jr	t3
+1:
+	/*
+	 * Now executing at the PA of this wrapper with the trampoline pgd
+	 * installed (identity-mapped). Drop the MMU; PC stays valid because
+	 * it is already a PA.
+	 */
+	csrw	CSR_SATP, zero
+	sfence.vma x0, x0
+
+	/* Jump to the PA of control_code_buffer to run the relocate body. */
+	la	t0, riscv_kexec_cc_buffer_pa
+	REG_L	t0, 0(t0)
+	jr	t0
+SYM_CODE_END(riscv_kexec_relocate_entry)
+
 .section ".rodata"
 SYM_DATA(riscv_kexec_relocate_size,
 	.long riscv_kexec_relocate_end - riscv_kexec_relocate)
diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c
index 556b76bcf96e..e3eb1e71920a 100644
--- a/arch/riscv/kernel/machine_kexec.c
+++ b/arch/riscv/kernel/machine_kexec.c
@@ -20,6 +20,8 @@
 
 unsigned long kexec_tramp_satp;
 unsigned long riscv_kexec_norelocate_pa;
+unsigned long riscv_kexec_relocate_entry_pa;
+unsigned long riscv_kexec_cc_buffer_pa;
 static pgd_t kexec_tramp_pgd[PTRS_PER_PGD] __aligned(PAGE_SIZE);
 static p4d_t kexec_tramp_p4d[PTRS_PER_P4D] __aligned(PAGE_SIZE);
 static pud_t kexec_tramp_pud[PTRS_PER_PUD] __aligned(PAGE_SIZE);
@@ -144,6 +146,11 @@ machine_kexec_prepare(struct kimage *image)
 
 		/* Mark the control page executable */
 		set_memory_x((unsigned long) control_code_buffer, 1);
+
+		WRITE_ONCE(riscv_kexec_relocate_entry_pa,
+			   __pa_symbol(&riscv_kexec_relocate_entry));
+		WRITE_ONCE(riscv_kexec_cc_buffer_pa,
+			   __pa(control_code_buffer));
 	} else {
 		WRITE_ONCE(riscv_kexec_norelocate_pa,
 			   __pa_symbol(&riscv_kexec_norelocate));
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:03 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:03 +0800
Subject: [PATCH v2 1/7] riscv: Add kexec trampoline text section to vmlinux.lds.S
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-2-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

When CONFIG_KEXEC_CORE is enabled, add a dedicated .kexec.tramp.text
area to the RISC-V kernel linker script.

This introduces a KEXEC_TRAMP_TEXT linker snippet in image-vars.h and
uses it from vmlinux.lds.S to:
  - align both the start and the end to PAGE_SIZE
  - define __kexec_tramp_text_start/__kexec_tramp_text_end
  - KEEP all .kexec.tramp.text* input sections
  - ASSERT the trampoline text fits within one page

The end-of-section page alignment guarantees that the trampoline page,
which is later identity-mapped as PAGE_KERNEL_EXEC, contains nothing but
the trampoline code and padding (no shared neighbour data).

When kexec is disabled, KEXEC_TRAMP_TEXT expands to nothing.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/kernel/image-vars.h  | 14 ++++++++++++++
 arch/riscv/kernel/vmlinux.lds.S |  1 +
 2 files changed, 15 insertions(+)

diff --git a/arch/riscv/kernel/image-vars.h b/arch/riscv/kernel/image-vars.h
index 3bd9d06a8b8f..35ee3f059afa 100644
--- a/arch/riscv/kernel/image-vars.h
+++ b/arch/riscv/kernel/image-vars.h
@@ -34,4 +34,18 @@ __efistub_sysfb_primary_display	= sysfb_primary_display;
 
 #endif
 
+#ifdef CONFIG_KEXEC_CORE
+#define KEXEC_TRAMP_TEXT						\
+		. = ALIGN(PAGE_SIZE);					\
+		__kexec_tramp_text_start = .;				\
+		KEEP(*(.kexec.tramp.text))				\
+		KEEP(*(.kexec.tramp.text.*))				\
+		__kexec_tramp_text_end = .;				\
+		ASSERT((__kexec_tramp_text_end - __kexec_tramp_text_start) <= PAGE_SIZE,  \
+			".kexec.tramp.text exceeds 4K");				\
+		. = ALIGN(PAGE_SIZE);
+#else
+#define KEXEC_TRAMP_TEXT /* nothing */
+#endif
+
 #endif /* __RISCV_KERNEL_IMAGE_VARS_H */
diff --git a/arch/riscv/kernel/vmlinux.lds.S b/arch/riscv/kernel/vmlinux.lds.S
index 1f4f8496941a..fc7758e5b819 100644
--- a/arch/riscv/kernel/vmlinux.lds.S
+++ b/arch/riscv/kernel/vmlinux.lds.S
@@ -41,6 +41,7 @@ SECTIONS
 		ENTRY_TEXT
 		IRQENTRY_TEXT
 		SOFTIRQENTRY_TEXT
+		KEXEC_TRAMP_TEXT
 		_etext = .;
 	}
 
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:09 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:09 +0800
Subject: [PATCH v2 7/7] riscv: kexec: Route normal kexec through the trampoline page table
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-8-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

riscv_kexec_relocate (copied into control_code_buffer) uses an stvec
trick to drop the MMU and land on the PA of the next loop label.
Under VS-mode KVM cannot emulate this single-step transition and the
VCPU dies with "kvm run failed Operation not supported".

Route normal kexec through riscv_kexec_relocate_entry, the trampoline
wrapper added in the previous patch. It drops SATP with PC already on
a PA, then hands off to control_code_buffer where the relocate body
runs with SATP=0.

Drop the stvec trick from the relocate body and pass first_ind_entry
as a physical address since the body now starts with SATP=0. The
".align 2" plus filler "nop" that ensured the PA of the loop top was
4-byte aligned -- required because the legacy stvec trick wrote that
PA into stvec.BASE, whose low two bits are MODE and are discarded by
the hardware -- is no longer load-bearing and is removed as well.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/kernel/kexec_relocate.S | 26 ++++++--------------------
 arch/riscv/kernel/machine_kexec.c  | 10 +++++++---
 2 files changed, 13 insertions(+), 23 deletions(-)

diff --git a/arch/riscv/kernel/kexec_relocate.S b/arch/riscv/kernel/kexec_relocate.S
index 1baadad1b546..29392f457f42 100644
--- a/arch/riscv/kernel/kexec_relocate.S
+++ b/arch/riscv/kernel/kexec_relocate.S
@@ -34,27 +34,13 @@ SYM_CODE_START(riscv_kexec_relocate)
 	csrw	CSR_SIP, zero
 
 	/*
-	 * When we switch SATP.MODE to "Bare" we'll only
-	 * play with physical addresses. However the first time
-	 * we try to jump somewhere, the offset on the jump
-	 * will be relative to pc which will still be on VA. To
-	 * deal with this we set stvec to the physical address at
-	 * the start of the loop below so that we jump there in
-	 * any case.
+	 * The trampoline wrapper (riscv_kexec_relocate_entry) has already
+	 * dropped the MMU and handed control to us at this PA copy of the
+	 * relocate code. From here on the entire loop runs with SATP=0 and
+	 * every address (s0, s5, source/dest pointers) is a physical one.
 	 */
-	la	s6, 1f
-	sub	s6, s6, s4
-	csrw	CSR_STVEC, s6
-
-	/*
-	 * With C-extension, here we get 42 Bytes and the next
-	 * .align directive would pad zeros here up to 44 Bytes.
-	 * So manually put a nop here to avoid zeros padding.
-	*/
-	nop
 
 	/* Process entries in a loop */
-.align 2
 1:
 	REG_L	t0, 0(s0)		/* t0 = *image->entry */
 	addi	s0, s0, RISCV_SZPTR	/* image->entry++ */
@@ -70,8 +56,8 @@ SYM_CODE_START(riscv_kexec_relocate)
 	andi	t1, t0, 0x2
 	beqz	t1, 2f
 	andi	s0, t0, ~0x2
-	csrw	CSR_SATP, zero
-	jr	s6
+	/* MMU is already off; the entry wrapper handled the transition. */
+	j	1b
 
 2:
 	/* IND_DONE entry ? -> jump to done label */
diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c
index e3eb1e71920a..99cc251f971c 100644
--- a/arch/riscv/kernel/machine_kexec.c
+++ b/arch/riscv/kernel/machine_kexec.c
@@ -231,11 +231,15 @@ machine_kexec(struct kimage *image)
 {
 	struct kimage_arch *internal = &image->arch;
 	unsigned long jump_addr = (unsigned long) image->start;
-	unsigned long first_ind_entry = (unsigned long) &image->head;
+	/*
+	 * The relocate body runs entirely with the MMU off (the wrapper
+	 * drops SATP before jumping into control_code_buffer), so the very
+	 * first entry must be a physical address.
+	 */
+	unsigned long first_ind_entry = __pa(&image->head);
 	unsigned long this_cpu_id = __smp_processor_id();
 	unsigned long this_hart_id = cpuid_to_hartid_map(this_cpu_id);
 	unsigned long fdt_addr = internal->fdt_addr;
-	void *control_code_buffer = page_address(image->control_code_page);
 	riscv_kexec_method kexec_method = NULL;
 
 #ifdef CONFIG_SMP
@@ -244,7 +248,7 @@ machine_kexec(struct kimage *image)
 #endif
 
 	if (image->type != KEXEC_TYPE_CRASH)
-		kexec_method = control_code_buffer;
+		kexec_method = (riscv_kexec_method) &riscv_kexec_relocate_entry;
 	else
 		kexec_method = (riscv_kexec_method) &riscv_kexec_norelocate;
 
-- 
2.50.1



From fangyu.yu at linux.alibaba.com  Tue May 26 05:50:07 2026
From: fangyu.yu at linux.alibaba.com (fangyu.yu at linux.alibaba.com)
Date: Tue, 26 May 2026 20:50:07 +0800
Subject: [PATCH v2 5/7] riscv: kexec: Always build the trampoline page table
In-Reply-To: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
References: <20260526125009.2404-1-fangyu.yu@linux.alibaba.com>
Message-ID: <20260526125009.2404-6-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu at linux.alibaba.com>

The trampoline page table and the kexec_tramp_satp value are
currently built only on the crash path. A follow-up patch needs
the same infrastructure for the normal kexec path.

Pull the trampoline build and the WRITE_ONCE() that publishes the
SATP value out of the crash-only else branch in
machine_kexec_prepare(). The crash path keeps recording its own
riscv_kexec_norelocate_pa; the normal path keeps its existing
control_code_buffer copy.

No functional change.

Signed-off-by: Fangyu Yu <fangyu.yu at linux.alibaba.com>
---
 arch/riscv/kernel/machine_kexec.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec.c b/arch/riscv/kernel/machine_kexec.c
index 00fdc42d650d..556b76bcf96e 100644
--- a/arch/riscv/kernel/machine_kexec.c
+++ b/arch/riscv/kernel/machine_kexec.c
@@ -119,6 +119,16 @@ machine_kexec_prepare(struct kimage *image)
 		return -EINVAL;
 	}
 
+	/*
+	 * Build the trampoline page table and capture its SATP value.
+	 * The crash path consumes it today; the non-crash kexec path
+	 * will use the same setup as well.
+	 */
+	riscv_kexec_build_tramp((unsigned long)__kexec_tramp_text_start,
+				__pa_symbol(__kexec_tramp_text_start));
+	WRITE_ONCE(kexec_tramp_satp,
+		   PFN_DOWN(__pa_symbol(kexec_tramp_pgd)) | satp_mode);
+
 	/* Copy the assembler code for relocation to the control page */
 	if (image->type != KEXEC_TYPE_CRASH) {
 		control_code_buffer = page_address(image->control_code_page);
@@ -135,19 +145,8 @@ machine_kexec_prepare(struct kimage *image)
 		/* Mark the control page executable */
 		set_memory_x((unsigned long) control_code_buffer, 1);
 	} else {
-		/*
-		 * Crash kexec uses riscv_kexec_norelocate as a trampoline.
-		 * Pre-build the trampoline page tables and capture the
-		 * trampoline SATP value plus the physical address of
-		 * riscv_kexec_norelocate so that the panic path only has
-		 * to switch satp and jump.
-		 */
-		riscv_kexec_build_tramp((unsigned long)__kexec_tramp_text_start,
-					__pa_symbol(__kexec_tramp_text_start));
 		WRITE_ONCE(riscv_kexec_norelocate_pa,
 			   __pa_symbol(&riscv_kexec_norelocate));
-		WRITE_ONCE(kexec_tramp_satp,
-			   PFN_DOWN(__pa_symbol(kexec_tramp_pgd)) | satp_mode);
 	}
 
 	return 0;
-- 
2.50.1



From alhe at linux.microsoft.com  Tue May 26 08:17:14 2026
From: alhe at linux.microsoft.com (Alejandro Hernandez)
Date: Tue, 26 May 2026 15:17:14 +0000
Subject: [PATCH v2] kexec: restore functionality to preserve fd xattrs for uncompressed kernel images
Message-ID: <20260526151714.4181388-1-alhe@linux.microsoft.com>

Since commit 714fa115 xattrs attached to a kernel image file (such as IMA
signatures) are not being preserved, do_kexec_file_load() was modified to use
memfd_create() which now passes an anonymous file descriptor to
kexec_file_load(). This change eliminated the filesystem inode identity of the
original kernel file, hence attributes are no longer visible to the kernel IMA
appraisal handler during kexec_file_load, causing IMA policy enforcement to fail
even for validly signed kernel images.

This patch attempts to restore such behavior although, only does it for
uncompressed kernel images. To do this, we first figure out if the image file is
compressed or not and call each method accordingly.

Compressed images continue to use memfd and cannot carry forward the original fd
security attributes, since their decompressed bytes do not match the signed
artifact. Proper handling of compressed images with IMA would require either
signing the decompressed artifact or kernel-side support for decompression
within the kexec_file_load path.

Signed-off-by: Alejandro Hernandez Samaniego <alhe at linux.microsoft.com>
---
 kexec/kexec.c | 42 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/kexec/kexec.c b/kexec/kexec.c
index 08edfca..68f23dc 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -1305,6 +1305,8 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
 	int ret = 0;
 	char *kernel_buf;
 	off_t kernel_size;
+	struct stat kernel_stat;
+	off_t kernel_file_size = -1;
 
 	memset(&info, 0, sizeof(info));
 	info.segment = NULL;
@@ -1328,17 +1330,39 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
 
 	kernel = argv[fileind];
 
-	/* slurp in the input kernel */
+       /* Hold original fd with its xattrs */
+	kernel_fd = open(kernel, O_RDONLY);
+	if (kernel_fd == -1) {
+		fprintf(stderr, "Failed to open file %s:%s\n", kernel,
+				strerror(errno));
+		return EFAILED;
+	}
+
+	/* Compressed vs Uncompressed */
+	if (fstat(kernel_fd, &kernel_stat) == 0)
+		kernel_file_size = kernel_stat.st_size;
+
+        /* slurp in the input kernel */
 	kernel_buf = slurp_decompress_file(kernel, &kernel_size);
 	if (!kernel_buf) {
 		fprintf(stderr, "Failed to decompress file %s:%s\n", kernel,
 				strerror(errno));
+		close(kernel_fd);
 		return EFAILED;
 	}
-	kernel_fd = copybuf_memfd(kernel_buf, kernel_size);
-	if (kernel_fd < 0) {
-		fprintf(stderr, "Failed to copy decompressed buf\n");
-		return EFAILED;
+
+	if (kernel_file_size != kernel_size) {
+		close(kernel_fd);
+		kernel_fd = copybuf_memfd(kernel_buf, kernel_size);
+		if (kernel_fd < 0) {
+			fprintf(stderr, "Failed to copy decompressed buf\n");
+			return EFAILED;
+		}
+		dbgprintf("%s: compressed input, using memfd kernel fd %d\n",
+			  __func__, kernel_fd);
+	} else {
+		dbgprintf("%s: using original kernel file fd %d\n",
+			  __func__, kernel_fd);
 	}
 
 	for (i = 0; i < file_types; i++) {
@@ -1361,11 +1385,15 @@ static int do_kexec_file_load(int fileind, int argc, char **argv,
 	}
 
        /*
-	* image type specific load functioin detect the capsule kernel type
+	* image type specific load function detect the capsule kernel type
 	* and create another fd for file load. For example the zboot kernel.
 	*/
-	if (info.kernel_fd != -1)
+	if (info.kernel_fd != -1) {
+		dbgprintf("%s: using image-specific kernel fd %d\n",
+			  __func__, info.kernel_fd);
+		close(kernel_fd);
 		kernel_fd = info.kernel_fd;
+	}
 
 	/*
 	 * If there is no initramfs, set KEXEC_FILE_NO_INITRAMFS flag so that
-- 
2.43.0



From pasha.tatashin at soleen.com  Tue May 26 11:37:07 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Tue, 26 May 2026 14:37:07 -0400
Subject: [Hypervisor Live Update] Notes from May 18, 2026
Message-ID: <ahXiYGo-IWI94IVP@google.com>

Hi everybody,

Here are the notes from the last Hypervisor Live Update call that 
happened on Monday, May 18. Thanks to everybody who was involved!

These notes are intended to bring people up to speed who could not 
attend the call as well as keep the conversation going in between 
meetings.

----->o-----

Administrative Updates & LPC 2026

To kick off the meeting, it was announced that I will be taking
over the management of the Hypervisor Live Update meetings going
forward, with David Matlack serving as a backup for scheduling
conflicts. Thank you to David Rientjes for running these up to this
point!

Additionally, the Live Update micro-conference has been officially
accepted for LPC 2026. Attendees are highly encouraged to submit topics
and proposals as the micro-conference will be listed among the available
tracks.

----->o-----
Systemd LUO Integration

Systemd: Luca's patch has been successfully merged into systemd. This
allows for the use of the fdstore to pass file descriptors across a live
update, which is a major milestone for adoption.

----->o-----
MSHV + KHO

Pratyush and I discussed using the Radix Tree as a
sparse bitmap for MSHV. To improve code modularity, we agreed to explore
establishing a new lib directory to house common data structures (e.g.,
blocked linked lists, memory management structures) that can be easily
shared by the other KHO users.

----->o-----
KHO, Boot Time & HugeTLB Preservation

LSF/MM/BPF Debrief: Pratyush provided an update on the HugeTLB
preservation talk. The consensus is that VFS hooks must be made aware of
the freezing process. Generic fallocate and setattr functions will be
updated to return errors up the stack immediately, rather than requiring
individual filesystems to handle freezing logic.

Boot Time Performance: The struct page initialization patch is ready for
the upcoming merge window. Broader boot time optimizations (like
refactoring the buddy memory allocator) remain a future project.

HugeTLB Preservation: Progress is currently gated by the I/O freezing
work and the fallback allocation strategy (which helps the kernel
discover free memory ranges lacking preserved pages for HugeTLB
allocation). Pratyush has requested reviews to unblock this.

----->o-----
VFIO, PCI Core, & IOMMU Persistence

PCI Core: David Matlack sent v5 last week and is planning to send v6 this
week after incorporating recent feedback. He is currently awaiting
reviews from Bjorn on the refactored code.

IOMMU Persistence: Samiullah Khawaja noted that v2 of the series
received positive feedback from Intel. He is preparing v3 and has also
submitted related series for VFIO self-tests and DMA lock preservation.

IOMMU Development Roadmap:
Samiullah outlined the following milestones, noting that
architecture-specific streams can run in parallel once foundational hooks
land:

Milestone 1 (Foundations): Intel IOMMU state persistence.

Milestone 2 (Multi-Arch): ARM SMMU persistence; Intel 2nd stage
hitless domain replacement.

Milestone 3 (PASID): PASID support across core and driver components.

Milestone 4 (Virtualization): vIOMMU and nested IOMMU virtualization
(Intel VT-d, then ARM/AMD).

----->o-----
guest_memfd & Confidential VMs

guest_memfd: The team discussed the guest_memfd HugeTLB enlightenment
RFC, highlighting challenges with handling shared vs. private mappings
(since LU currently only supports fully shared guest memory). There is an
ongoing debate regarding flags and whether one-time creation flags need
dynamic conversions.

CVMs: We acknowledged that supporting Confidential VMs will act as a
massive stress test for KVM due to the high volume of 4K page table
preservations. This topic was tabled for a future meeting.

----->o-----
Process Memory Persistence (CRIU & Kexec)

Maximilian Heyne presented a PoC using CRIU to preserve process memory
across a kexec reboot without writing image files to disk, successfully
keeping 10GB of memory in a memfd.

----->o-----
Next meeting will be on Monday, June 1 at 8am PDT (UTC-7), everybody is
welcome: https://meet.google.com/rjn-dmzu-hgq

Topics for the next meeting:
- Vipin and David Matlack roadmap for PCI and VFIO changes to solicit
  upstream feedback
- Status of I/O freezing implementations and HugeTLB fallback allocation
  patches
- PCI core changes v6 review and next steps
- IOMMU persistence v3 and roadmap progress
- Updates on vm_splice investigations for CRIU memory preservation
- later: KHO support for Confidential VMs including page table
  preservation and pinning
- later: versioning support for luo to negotiate
- later: KHO enlightenment for ASI
- later: testing methodology to allow downstream consumers to qualify
  that live update works from one version to another
- later: reducing blackout window during live update


From dmatlack at google.com  Tue May 26 11:53:20 2026
From: dmatlack at google.com (David Matlack)
Date: Tue, 26 May 2026 11:53:20 -0700
Subject: [Hypervisor Live Update] Notes from May 18, 2026
In-Reply-To: <ahXiYGo-IWI94IVP@google.com>
References: <ahXiYGo-IWI94IVP@google.com>
Message-ID: <CALzav=dkVWYmbWCO6wDuQrxetAcqYkitJiRF_BhDDGqn2_-RKA@mail.gmail.com>

On Tue, May 26, 2026 at 11:37?AM Pasha Tatashin
<pasha.tatashin at soleen.com> wrote:

> Next meeting will be on Monday, June 1 at 8am PDT (UTC-7), everybody is
> welcome: https://meet.google.com/rjn-dmzu-hgq
>
> Topics for the next meeting:
> - Vipin and David Matlack roadmap for PCI and VFIO changes to solicit
>   upstream feedback

Hey Pasha,

We'll need to push this back by one meeting (June 15th) due to the US
holiday and some out-of-office leave we didn't account for.

> - Status of I/O freezing implementations and HugeTLB fallback allocation
>   patches
> - PCI core changes v6 review and next steps
> - IOMMU persistence v3 and roadmap progress
> - Updates on vm_splice investigations for CRIU memory preservation
> - later: KHO support for Confidential VMs including page table
>   preservation and pinning
> - later: versioning support for luo to negotiate
> - later: KHO enlightenment for ASI
> - later: testing methodology to allow downstream consumers to qualify
>   that live update works from one version to another
> - later: reducing blackout window during live update


From chenwandun1 at gmail.com  Tue May 26 20:29:06 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:06 +0800
Subject: [PATCH v3 00/11] kdump: reduce vmcore size and capture time
Message-ID: <20260527032917.3385849-1-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

On SoCs that carve out large firmware-owned reserved memory (GPU
firmware, DSP, modem, camera ISP, NPU, ...), kdump currently dumps
those carveouts as part of system RAM even though their contents are
firmware state that is not useful for kernel crash analysis.

This series introduces an opt-in 'dumpable' flag [1] on struct
reserved_mem and uses it to filter the elfcorehdr PT_LOAD ranges on
DT-based architectures (arm64, riscv, loongarch). By default reserved
regions are treated as non-dumpable; CMA regions are explicitly opted
in because their pages are returned to the buddy allocator and may
carry key crash-analysis data.

The series is organized as follows:
Patches 1-3: Pre-existing fixes and a small prep change.
Patches 4-5: Restructure to allow appending /memreserve/ entries.
Patches 6-7: Add a dumpable flag and append /memreserve/ entries.
Patch 8: Add generic kdump helpers.
Patches 9-11: Wire the helpers into arm64, riscv and loongarch kdump
              elfcorehdr preparation.

v2 --> v3:
1. Fix out-of-bounds issue if device tree lacks /reserved-memory node.[2]
2. Fix UAF issue when alloc_reserved_mem_array() fails.
3. Add some prepare patches.

v1 --> v2:
1. v1 added an opt-out DT property ('linux,no-dump'). Per Rob's
   feedback [1], v2 drop that property and exclude reserve memory
   by default.
2. Split some prepared patches from the original patches.
3. Address coding-style comments on patch 5 from Rob.

[1] https://lore.kernel.org/lkml/20260506144542.GA2072596-robh at kernel.org/
[2] https://sashiko.dev/#/patchset/20260520091844.592753-1-chenwandun%40lixiang.com?part=4

Wandun Chen (11):
  of: reserved_mem: handle NULL name in of_reserved_mem_lookup()
  kexec/crash: provide crash_exclude_mem_range() stub when
    CONFIG_CRASH_DUMP=n
  of: reserved_mem: avoid post-init UAF when alloc_reserved_mem_array()
    fails
  of: reserved_mem: zero total_reserved_mem_cnt if no valid
    /reserved-memory entry
  of: reserved_mem: split alloc_reserved_mem_array() from
    fdt_scan_reserved_mem_late()
  of: reserved_mem: add dumpable flag to opt-in vmcore
  of: reserved_mem: save /memreserve/ entries into the reserved_mem
    array
  of: reserved_mem: add kdump helpers to exclude non-dumpable regions
  arm64: kdump: exclude non-dumpable reserved memory regions from vmcore
  riscv: kdump: exclude non-dumpable reserved memory regions from vmcore
  loongarch: kdump: exclude non-dumpable reserved memory regions from
    vmcore

 arch/arm64/kernel/machine_kexec_file.c     |   6 ++
 arch/loongarch/kernel/machine_kexec_file.c |   6 ++
 arch/riscv/kernel/machine_kexec_file.c     |   4 +
 drivers/of/fdt.c                           |  11 +-
 drivers/of/of_private.h                    |   3 +
 drivers/of/of_reserved_mem.c               | 117 +++++++++++++++++++--
 include/linux/crash_core.h                 |   6 ++
 include/linux/of_reserved_mem.h            |  15 +++
 kernel/dma/contiguous.c                    |   1 +
 9 files changed, 157 insertions(+), 12 deletions(-)

-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:07 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:07 +0800
Subject: [PATCH v3 01/11] of: reserved_mem: handle NULL name in of_reserved_mem_lookup()
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-2-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for an upcoming change that appends /memreserve/ entries to
reserved_mem[]; such entries have no name.

No functional change.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 drivers/of/of_reserved_mem.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 8d5777cb5d1b..313cbc57aa45 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -788,7 +788,8 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
 
 	name = kbasename(np->full_name);
 	for (i = 0; i < reserved_mem_count; i++)
-		if (!strcmp(reserved_mem[i].name, name))
+		if (reserved_mem[i].name &&
+		    !strcmp(reserved_mem[i].name, name))
 			return &reserved_mem[i];
 
 	return NULL;
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:08 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:08 +0800
Subject: [PATCH v3 02/11] kexec/crash: provide crash_exclude_mem_range() stub when CONFIG_CRASH_DUMP=n
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-3-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for an upcoming change that excludes non-dumpable reserved
regions from the kdump vmcore and will call crash_exclude_mem_range()
from generic, non-arch code.

No functional change.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 include/linux/crash_core.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index c1dee3f971a9..0033d4777648 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -87,6 +87,12 @@ static inline int kexec_should_crash(struct task_struct *p) { return 0; }
 static inline int kexec_crash_loaded(void) { return 0; }
 static inline void crash_save_cpu(struct pt_regs *regs, int cpu) {};
 static inline int kimage_crash_copy_vmcoreinfo(struct kimage *image) { return 0; };
+static inline int crash_exclude_mem_range(struct crash_mem *mem,
+					  unsigned long long mstart,
+					  unsigned long long mend)
+{
+	return 0;
+}
 #endif /* CONFIG_CRASH_DUMP*/
 
 #ifdef CONFIG_CRASH_DM_CRYPT
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:09 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:09 +0800
Subject: [PATCH v3 03/11] of: reserved_mem: avoid post-init UAF when alloc_reserved_mem_array() fails
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-4-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

The global pointer 'reserved_mem' continues to reference the
reserved_mem_array which lives in __initdata if
alloc_reserved_mem_array() fails. of_reserved_mem_lookup() is
exported for post-init use, that would dereference freed memory
and trigger a use-after-free.

So reset reserved_mem_count to 0 when alloc_reserved_mem_array()
fails.

Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array")
Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 drivers/of/of_reserved_mem.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 313cbc57aa45..6d479381ff1f 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -69,29 +69,31 @@ static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
  * the initial static array is copied over to this new array and
  * the new array is used from this point on.
  */
-static void __init alloc_reserved_mem_array(void)
+static bool __init alloc_reserved_mem_array(void)
 {
 	struct reserved_mem *new_array;
 	size_t alloc_size, copy_size, memset_size;
 
+	if (!total_reserved_mem_cnt)
+		return true;
+
 	alloc_size = array_size(total_reserved_mem_cnt, sizeof(*new_array));
 	if (alloc_size == SIZE_MAX) {
 		pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
-		return;
+		goto fail;
 	}
 
 	new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES);
 	if (!new_array) {
 		pr_err("Failed to allocate memory for reserved_mem array with err: %d", -ENOMEM);
-		return;
+		goto fail;
 	}
 
 	copy_size = array_size(reserved_mem_count, sizeof(*new_array));
 	if (copy_size == SIZE_MAX) {
 		memblock_free(new_array, alloc_size);
-		total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
 		pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
-		return;
+		goto fail;
 	}
 
 	memset_size = alloc_size - copy_size;
@@ -100,6 +102,11 @@ static void __init alloc_reserved_mem_array(void)
 	memset(new_array + reserved_mem_count, 0, memset_size);
 
 	reserved_mem = new_array;
+	return true;
+
+fail:
+	reserved_mem_count = 0;
+	return false;
 }
 
 static void fdt_init_reserved_mem_node(unsigned long node, const char *uname,
@@ -266,7 +273,8 @@ void __init fdt_scan_reserved_mem_late(void)
 	}
 
 	/* Attempt dynamic allocation of a new reserved_mem array */
-	alloc_reserved_mem_array();
+	if (!alloc_reserved_mem_array())
+		return;
 
 	if (__reserved_mem_check_root(node)) {
 		pr_err("Reserved memory: unsupported node format, ignoring\n");
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:10 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:10 +0800
Subject: [PATCH v3 04/11] of: reserved_mem: zero total_reserved_mem_cnt if no valid /reserved-memory entry
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-5-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for storing /memreserve/ entries in the reserved_mem array.
Zero total_reserved_mem_cnt if no valid /reserved-memory entry,
instead of keeping it's initial value of MAX_RESERVED_REGIONS, this
allows accounting /memreserve entries based on total_reserved_mem_cnt
in a follow-up patch.

No functional change.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 drivers/of/of_reserved_mem.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 6d479381ff1f..05defc91e901 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -329,11 +329,14 @@ int __init fdt_scan_reserved_mem(void)
 	const void *fdt = initial_boot_params;
 
 	node = fdt_path_offset(fdt, "/reserved-memory");
-	if (node < 0)
+	if (node < 0) {
+		total_reserved_mem_cnt = 0;
 		return -ENODEV;
+	}
 
 	if (__reserved_mem_check_root(node) != 0) {
 		pr_err("Reserved memory: unsupported node format, ignoring\n");
+		total_reserved_mem_cnt = 0;
 		return -EINVAL;
 	}
 
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:11 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:11 +0800
Subject: [PATCH v3 05/11] of: reserved_mem: split alloc_reserved_mem_array() from fdt_scan_reserved_mem_late()
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-6-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Prepare for storing /memreserve/ entries in the reserved_mem array.
alloc_reserved_mem_array is skipped if the device tree lacks a
/reserved-memory node, pointer 'reserved_mem' continues to reference
the reserved_mem_array which lives in __initdata, storing
/memreserve/ entries into reserved_mem_array would result in metadata
loss, and an out-of-bounds memory access will occur if the device
tree contains more than MAX_RESERVED_REGIONS /memreserve/ entries.

So split alloc_reserved_mem_array() from fdt_scan_reserved_mem_late(),
and call alloc_reserved_mem_array() whether or not there is a
/reserved-memory node.

No functional change.
The actual /memreserve/ population is added in a follow-up patch.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 drivers/of/fdt.c             | 7 +++++--
 drivers/of/of_private.h      | 1 +
 drivers/of/of_reserved_mem.c | 6 +-----
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 82f7327c59ea..83a2a474831e 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -1284,8 +1284,11 @@ void __init unflatten_device_tree(void)
 {
 	void *fdt = initial_boot_params;
 
-	/* Save the statically-placed regions in the reserved_mem array */
-	fdt_scan_reserved_mem_late();
+	/* Attempt dynamic allocation of a new reserved_mem array */
+	if (fdt && alloc_reserved_mem_array()) {
+		/* Save the statically-placed regions in the reserved_mem array */
+		fdt_scan_reserved_mem_late();
+	}
 
 	/* Populate an empty root node when bootloader doesn't provide one */
 	if (!fdt) {
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 0ae16da066e2..50e5a533e059 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -187,6 +187,7 @@ static inline struct device_node *__of_get_dma_parent(const struct device_node *
 
 int fdt_scan_reserved_mem(void);
 void __init fdt_scan_reserved_mem_late(void);
+bool __init alloc_reserved_mem_array(void);
 
 bool of_fdt_device_is_available(const void *blob, unsigned long node);
 
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 05defc91e901..888dcb6bdce5 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -69,7 +69,7 @@ static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
  * the initial static array is copied over to this new array and
  * the new array is used from this point on.
  */
-static bool __init alloc_reserved_mem_array(void)
+bool __init alloc_reserved_mem_array(void)
 {
 	struct reserved_mem *new_array;
 	size_t alloc_size, copy_size, memset_size;
@@ -272,10 +272,6 @@ void __init fdt_scan_reserved_mem_late(void)
 		return;
 	}
 
-	/* Attempt dynamic allocation of a new reserved_mem array */
-	if (!alloc_reserved_mem_array())
-		return;
-
 	if (__reserved_mem_check_root(node)) {
 		pr_err("Reserved memory: unsupported node format, ignoring\n");
 		return;
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:12 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:12 +0800
Subject: [PATCH v3 06/11] of: reserved_mem: add dumpable flag to opt-in vmcore
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-7-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Add a 'dumpable' flag to struct reserved_mem so the kernel can decide
whether a reserved area should be included in the kdump vmcore. Most
reserved regions are owned by devices and do not contain data useful
for kernel crash analysis, so excluding them by default is the right
behaviour.

Reusable CMA regions are different: pages in a CMA region are handed
back to the buddy allocator and may contain key data for crash
analysis, so set dumpable to true in rmem_cma_setup().

Suggested-by: Rob Herring <robh at kernel.org>
Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
Acked-by: Marek Szyprowski <m.szyprowski at samsung.com>
Link: https://lore.kernel.org/all/20260506144542.GA2072596-robh at kernel.org/
---
 include/linux/of_reserved_mem.h | 1 +
 kernel/dma/contiguous.c         | 1 +
 2 files changed, 2 insertions(+)

diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index e8b20b29fa68..55a67cee41ea 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -15,6 +15,7 @@ struct reserved_mem {
 	phys_addr_t			base;
 	phys_addr_t			size;
 	void				*priv;
+	bool				dumpable;
 };
 
 struct reserved_mem_ops {
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index 03f52bd17120..eddec89eb414 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -579,6 +579,7 @@ static int __init rmem_cma_setup(unsigned long node, struct reserved_mem *rmem)
 		dma_contiguous_default_area = cma;
 
 	rmem->priv = cma;
+	rmem->dumpable = true;
 
 	pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
 		&rmem->base, (unsigned long)rmem->size / SZ_1M);
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:13 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:13 +0800
Subject: [PATCH v3 07/11] of: reserved_mem: save /memreserve/ entries into the reserved_mem array
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-8-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

/memreserve/ is used by firmware or bootloaders, such regions hold no
useful data for crash analysis, they should be excluded from the
kdump vmcore, so save /memreserve/ entries into the reserved_mem array
for later exclusion.

If a /memreserve/ entry overlaps any dumpable reserved region, mark
the whole memreserve entry dumpable as well. This may keep slightly
more memory in vmcore than strictly necessary, but avoids splitting
entries and never drops data that may be useful for crash analysis.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 drivers/of/fdt.c             |  4 +++
 drivers/of/of_private.h      |  2 ++
 drivers/of/of_reserved_mem.c | 55 ++++++++++++++++++++++++++++++++++++
 3 files changed, 61 insertions(+)

diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 83a2a474831e..745e53b1c564 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -499,6 +499,7 @@ void __init early_init_fdt_scan_reserved_mem(void)
 	int n;
 	int res;
 	u64 base, size;
+	int nr_memreserve = 0;
 
 	if (!initial_boot_params)
 		return;
@@ -516,7 +517,9 @@ void __init early_init_fdt_scan_reserved_mem(void)
 		if (!size)
 			break;
 		memblock_reserve(base, size);
+		nr_memreserve++;
 	}
+	fdt_reserved_mem_account_memreserve(nr_memreserve);
 }
 
 /**
@@ -1288,6 +1291,7 @@ void __init unflatten_device_tree(void)
 	if (fdt && alloc_reserved_mem_array()) {
 		/* Save the statically-placed regions in the reserved_mem array */
 		fdt_scan_reserved_mem_late();
+		fdt_reserved_mem_save_memreserve_entries();
 	}
 
 	/* Populate an empty root node when bootloader doesn't provide one */
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 50e5a533e059..30954e859689 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -188,6 +188,8 @@ static inline struct device_node *__of_get_dma_parent(const struct device_node *
 int fdt_scan_reserved_mem(void);
 void __init fdt_scan_reserved_mem_late(void);
 bool __init alloc_reserved_mem_array(void);
+void __init fdt_reserved_mem_account_memreserve(int n);
+void __init fdt_reserved_mem_save_memreserve_entries(void);
 
 bool of_fdt_device_is_available(const void *blob, unsigned long node);
 
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 888dcb6bdce5..5e27f9403786 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -248,6 +248,43 @@ static void __init __rmem_check_for_overlap(void)
 	}
 }
 
+static void __init fdt_reserved_mem_add_memreserve(phys_addr_t base,
+						   phys_addr_t size)
+{
+	struct reserved_mem *rmem;
+	bool dumpable = false;
+	int i;
+
+	if (reserved_mem_count == total_reserved_mem_cnt) {
+		pr_err("not enough space for memreserve regions.\n");
+		return;
+	}
+
+	for (i = 0; i < reserved_mem_count; i++) {
+		rmem = &reserved_mem[i];
+
+		if (!rmem->dumpable)
+			continue;
+
+		if (base < rmem->base + rmem->size && rmem->base < base + size) {
+			dumpable = true;
+			break;
+		}
+	}
+
+	rmem = &reserved_mem[reserved_mem_count];
+	rmem->base = base;
+	rmem->size = size;
+	rmem->dumpable = dumpable;
+
+	reserved_mem_count++;
+}
+
+void __init fdt_reserved_mem_account_memreserve(int n)
+{
+	total_reserved_mem_cnt += n;
+}
+
 /**
  * fdt_scan_reserved_mem_late() - Scan FDT and initialize remaining reserved
  * memory regions.
@@ -305,6 +342,24 @@ void __init fdt_scan_reserved_mem_late(void)
 	__rmem_check_for_overlap();
 }
 
+void __init fdt_reserved_mem_save_memreserve_entries(void)
+{
+	const void *fdt = initial_boot_params;
+	u64 base, size;
+	int n;
+
+	if (!fdt)
+		return;
+
+	for (n = 0; ; n++) {
+		if (fdt_get_mem_rsv(fdt, n, &base, &size))
+			break;
+		if (!size)
+			break;
+		fdt_reserved_mem_add_memreserve(base, size);
+	}
+}
+
 static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname);
 
 /*
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:14 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:14 +0800
Subject: [PATCH v3 08/11] of: reserved_mem: add kdump helpers to exclude non-dumpable regions
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-9-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Add two helpers to exclude non-dumpable regions for arch-specific
code.

 - of_reserved_mem_kdump_nr_ranges() returns the count of regions
   that are not dumpable. Each excluded region may split an existing
   crash_mem range into two, so callers use this to calculate
   crash_mem allocation size.

 - of_reserved_mem_kdump_exclude() walks reserved_mem[] and calls
   crash_exclude_mem_range() for every non-dumpable region.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 drivers/of/of_reserved_mem.c    | 34 +++++++++++++++++++++++++++++++++
 include/linux/of_reserved_mem.h | 14 ++++++++++++++
 2 files changed, 48 insertions(+)

diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 5e27f9403786..1d9aa332325b 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -24,6 +24,7 @@
 #include <linux/slab.h>
 #include <linux/memblock.h>
 #include <linux/kmemleak.h>
+#include <linux/crash_core.h>
 
 #include "of_private.h"
 
@@ -858,6 +859,39 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
 }
 EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
 
+/*
+ * Count non-dumpable reserved regions. Excluding each one may split a
+ * crash_mem range in two, callers use this to size the allocation.
+ */
+unsigned int of_reserved_mem_kdump_nr_ranges(void)
+{
+	unsigned int i, n = 0;
+
+	for (i = 0; i < reserved_mem_count; i++)
+		if (reserved_mem[i].size && !reserved_mem[i].dumpable)
+			n++;
+	return n;
+}
+
+/* Exclude non-dumpable reserved regions from @cmem. */
+int of_reserved_mem_kdump_exclude(struct crash_mem *cmem)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < reserved_mem_count; i++) {
+		struct reserved_mem *r = &reserved_mem[i];
+
+		if (!r->size || r->dumpable)
+			continue;
+		ret = crash_exclude_mem_range(cmem, r->base,
+					      r->base + r->size - 1);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 /**
  * of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
  * @np:		node containing 'memory-region' property
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index 55a67cee41ea..70db99f1fbff 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -8,6 +8,7 @@
 struct of_phandle_args;
 struct reserved_mem_ops;
 struct resource;
+struct crash_mem;
 
 struct reserved_mem {
 	const char			*name;
@@ -48,6 +49,9 @@ int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
 					      const char *name, struct resource *res);
 int of_reserved_mem_region_count(const struct device_node *np);
 
+unsigned int of_reserved_mem_kdump_nr_ranges(void);
+int of_reserved_mem_kdump_exclude(struct crash_mem *cmem);
+
 #else
 
 #define RESERVEDMEM_OF_DECLARE(name, compat, ops)			\
@@ -92,6 +96,16 @@ static inline int of_reserved_mem_region_count(const struct device_node *np)
 {
 	return 0;
 }
+
+static inline unsigned int of_reserved_mem_kdump_nr_ranges(void)
+{
+	return 0;
+}
+
+static inline int of_reserved_mem_kdump_exclude(struct crash_mem *cmem)
+{
+	return 0;
+}
 #endif
 
 /**
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:15 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:15 +0800
Subject: [PATCH v3 09/11] arm64: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-10-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Reserved memory regions are excluded from vmcore by default unless
marked dumpable. Honor the dumpable flag to filter out device firmware
regions (e.g., GPU, DSP, modem) reserved via device tree, since they
typically contain data not useful for kernel crash analysis and can
significantly increase vmcore size.

Use of_reserved_mem_kdump_exclude() to perform the exclusion, and
pre-size the crash_mem array via of_reserved_mem_kdump_nr_ranges().

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
---
 arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..1d65320c6ba4 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -17,6 +17,7 @@
 #include <linux/memblock.h>
 #include <linux/of.h>
 #include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 			goto out;
 	}
 
+	ret = of_reserved_mem_kdump_exclude(cmem);
+	if (ret)
+		goto out;
+
 	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:16 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:16 +0800
Subject: [PATCH v3 10/11] riscv: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-11-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Apply the same non-dumpable reserved memory filtering to RISC-V kdump
as was done for arm64. Use of_reserved_mem_kdump_exclude() to drop
flagged regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_kdump_nr_ranges() to pre-size the crash_mem array.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 54e2d9552e93..c359cf714c79 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -10,6 +10,7 @@
 #include <linux/elf.h>
 #include <linux/slab.h>
 #include <linux/of.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/libfdt.h>
 #include <linux/types.h>
 #include <linux/memblock.h>
@@ -63,6 +64,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	nr_ranges = 1; /* For exclusion of crashkernel region */
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -76,6 +78,8 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	/* Exclude crashkernel region */
 	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	if (!ret)
+		ret = of_reserved_mem_kdump_exclude(cmem);
 	if (!ret)
 		ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
-- 
2.43.0



From chenwandun1 at gmail.com  Tue May 26 20:29:17 2026
From: chenwandun1 at gmail.com (Wandun Chen)
Date: Wed, 27 May 2026 11:29:17 +0800
Subject: [PATCH v3 11/11] loongarch: kdump: exclude non-dumpable reserved memory regions from vmcore
In-Reply-To: <20260527032917.3385849-1-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
Message-ID: <20260527032917.3385849-12-chenwandun1@gmail.com>

From: Wandun Chen <chenwandun at lixiang.com>

Apply the same non-dumpable reserved memory filtering to LoongArch
kdump as was done for arm64. Use of_reserved_mem_kdump_exclude() to
drop flagged regions from the elfcorehdr PT_LOAD segments, and
of_reserved_mem_kdump_nr_ranges() to pre-size the crash_mem array.

Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..4b918c3d4a28 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -14,6 +14,7 @@
 #include <linux/kernel.h>
 #include <linux/kexec.h>
 #include <linux/memblock.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -67,6 +68,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
+	nr_ranges += of_reserved_mem_kdump_nr_ranges();
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
 	if (!cmem)
@@ -91,6 +93,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 			goto out;
 	}
 
+	ret = of_reserved_mem_kdump_exclude(cmem);
+	if (ret)
+		goto out;
+
 	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
-- 
2.43.0



From horms at kernel.org  Wed May 27 00:27:09 2026
From: horms at kernel.org (Simon Horman)
Date: Wed, 27 May 2026 08:27:09 +0100
Subject: [PATCH v4 2/4] RISCV: Fix incorrect virtual address translation
 in? crashdump load
In-Reply-To: <20260521120946097C8qpZkVC2mBT_ozYEHlhp@zte.com.cn>
References: <20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn>
 <20260521120946097C8qpZkVC2mBT_ozYEHlhp@zte.com.cn>
Message-ID: <20260527072709.GA51310@horms.kernel.org>

On Thu, May 21, 2026 at 12:09:46PM +0800, luo.haiyang at zte.com.cn wrote:
> From: Luo Haiyang <luo.haiyang at zte.com.cn>
> 
> The kexec shows the following crashdump log:
> 
> Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
> Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
> Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...
> 
> Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
> the linear address mapping is defined as:
> va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
> Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.
> 
> Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>

Hi,

Unfortunately this patch seems to be somewhat mangled
and does not apply. Could you look into that?




From horms at kernel.org  Wed May 27 00:41:12 2026
From: horms at kernel.org (Simon Horman)
Date: Wed, 27 May 2026 08:41:12 +0100
Subject: [kexec-tools][PATCH] RISC-V: add kexec_load support for vmlinuz
In-Reply-To: <20260526045806.23933-2-ltao@redhat.com>
References: <20260526045806.23933-2-ltao@redhat.com>
Message-ID: <20260527074112.GB51310@horms.kernel.org>

On Tue, May 26, 2026 at 04:58:07PM +1200, Tao Liu wrote:
> Copy arm64 code and change for riscv so that the kexec -c can load
> a vmlinuz image.
> 
> Signed-off-by: Tao Liu <ltao at redhat.com>

Thanks, applied.


From horms at kernel.org  Wed May 27 00:42:04 2026
From: horms at kernel.org (Simon Horman)
Date: Wed, 27 May 2026 08:42:04 +0100
Subject: [PATCH v2] kexec: restore functionality to preserve fd xattrs
 for uncompressed kernel images
In-Reply-To: <20260526151714.4181388-1-alhe@linux.microsoft.com>
References: <20260526151714.4181388-1-alhe@linux.microsoft.com>
Message-ID: <20260527074204.GC51310@horms.kernel.org>

On Tue, May 26, 2026 at 03:17:14PM +0000, Alejandro Hernandez wrote:
> Since commit 714fa115 xattrs attached to a kernel image file (such as IMA
> signatures) are not being preserved, do_kexec_file_load() was modified to use
> memfd_create() which now passes an anonymous file descriptor to
> kexec_file_load(). This change eliminated the filesystem inode identity of the
> original kernel file, hence attributes are no longer visible to the kernel IMA
> appraisal handler during kexec_file_load, causing IMA policy enforcement to fail
> even for validly signed kernel images.
> 
> This patch attempts to restore such behavior although, only does it for
> uncompressed kernel images. To do this, we first figure out if the image file is
> compressed or not and call each method accordingly.
> 
> Compressed images continue to use memfd and cannot carry forward the original fd
> security attributes, since their decompressed bytes do not match the signed
> artifact. Proper handling of compressed images with IMA would require either
> signing the decompressed artifact or kernel-side support for decompression
> within the kexec_file_load path.
> 
> Signed-off-by: Alejandro Hernandez Samaniego <alhe at linux.microsoft.com>

Thanks, applied.

- kexec: restore functionality to preserve fd xattrs for uncompressed kernel images
  https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/commit/?id=f75be9241acc


From horms at kernel.org  Wed May 27 00:42:39 2026
From: horms at kernel.org (Simon Horman)
Date: Wed, 27 May 2026 08:42:39 +0100
Subject: [kexec-tools][PATCH] RISC-V: add kexec_load support for vmlinuz
In-Reply-To: <20260527074112.GB51310@horms.kernel.org>
References: <20260526045806.23933-2-ltao@redhat.com>
 <20260527074112.GB51310@horms.kernel.org>
Message-ID: <20260527074239.GD51310@horms.kernel.org>

On Wed, May 27, 2026 at 08:41:12AM +0100, Simon Horman wrote:
> On Tue, May 26, 2026 at 04:58:07PM +1200, Tao Liu wrote:
> > Copy arm64 code and change for riscv so that the kexec -c can load
> > a vmlinuz image.
> > 
> > Signed-off-by: Tao Liu <ltao at redhat.com>
> 
> Thanks, applied.

... here:

- RISC-V: add kexec_load support for vmlinuz
  https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/commit/?id=771e89dc104e



From ltao at redhat.com  Wed May 27 00:52:08 2026
From: ltao at redhat.com (Tao Liu)
Date: Wed, 27 May 2026 19:52:08 +1200
Subject: [kexec-tools][PATCH] RISC-V: add kexec_load support for vmlinuz
In-Reply-To: <20260527074239.GD51310@horms.kernel.org>
References: <20260526045806.23933-2-ltao@redhat.com> <20260527074112.GB51310@horms.kernel.org>
 <20260527074239.GD51310@horms.kernel.org>
Message-ID: <CAO7dBbW2ECfKHt18-B7LUYaJgTe8wvun3ccoSSbMkG8LynZ4bw@mail.gmail.com>

Hi Simon,

On Wed, May 27, 2026 at 7:42?PM Simon Horman <horms at kernel.org> wrote:
>
> On Wed, May 27, 2026 at 08:41:12AM +0100, Simon Horman wrote:
> > On Tue, May 26, 2026 at 04:58:07PM +1200, Tao Liu wrote:
> > > Copy arm64 code and change for riscv so that the kexec -c can load
> > > a vmlinuz image.
> > >
> > > Signed-off-by: Tao Liu <ltao at redhat.com>
> >
> > Thanks, applied.
>
> ... here:
>
> - RISC-V: add kexec_load support for vmlinuz
>   https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/commit/?id=771e89dc104e

Thanks a lot for that! :)

Thanks,
Tao Liu

>



From luo.haiyang at zte.com.cn  Wed May 27 01:18:16 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Wed, 27 May 2026 16:18:16 +0800 (CST)
Subject: =?UTF-8?B?UmU6IFtQQVRDSCB2NCAyLzRdIFJJU0NWOiBGaXggaW5jb3JyZWN0IHZpcnR1YWwgYWRkcmVzcyB0cmFuc2xhdGlvbiBpbiBjcmFzaGR1bXAgbG9hZA==?=
In-Reply-To: <20260527072709.GA51310@horms.kernel.org>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn,20260521120946097C8qpZkVC2mBT_ozYEHlhp@zte.com.cn,20260527072709.GA51310@horms.kernel.org
Message-ID: <20260527161816860TEMFvictPcT54b-t7QAFz@zte.com.cn>

From: Luo Haiyang <luo.haiyang at zte.com.cn>

The kexec shows the following crashdump log:

  Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
  Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
  Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...

Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
the linear address mapping is defined as:
  va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.

Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
---
 kexec/arch/riscv/Makefile          |  2 ++
 kexec/arch/riscv/crashdump-riscv.c | 55 ++++++++++++++++++++++++------
 util_lib/elf_info.c                | 41 +++++++++++++++++-----
 util_lib/include/elf_info.h        |  1 +
 4 files changed, 80 insertions(+), 19 deletions(-)

diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
index 18a997b..e886770 100644
--- a/kexec/arch/riscv/Makefile
+++ b/kexec/arch/riscv/Makefile
@@ -14,6 +14,8 @@ riscv_ARCH_REUSE_INITRD =

 riscv_CPPFLAGS += -I $(srcdir)/kexec/

+riscv_PHYS_TO_VIRT =
+
 dist += $(riscv_KEXEC_SRCS)				\
 	kexec/arch/riscv/image-header.h			\
 	kexec/arch/riscv/include/arch/options.h		\
diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
index e923a71..6875249 100644
--- a/kexec/arch/riscv/crashdump-riscv.c
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <elf.h>
+#include <elf_info.h>
 #include <unistd.h>

 #include "kexec.h"
@@ -22,18 +23,45 @@ static struct memory_ranges crash_mem_ranges = {0};
 static struct memory_ranges system_mem_ranges = {0};
 struct memory_range elfcorehdr_mem = {0};

-static unsigned long long get_page_offset(struct kexec_info *info)
+static unsigned long long phys_offset;
+
+static int get_page_offset(unsigned long long *page_offset)
 {
-	unsigned long long vaddr_off = 0;
-	unsigned long long page_size = sysconf(_SC_PAGESIZE);
-	unsigned long long init_start = get_kernel_sym("_sinittext");
+	int fd, ret = 0;

-	/*
-	 * Begining of init section is aligned to page size
-	 */
-	vaddr_off = init_start - page_size;
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_page_offset_elf_kcore(fd, (long *)page_offset);
+	if (ret)
+		fprintf(stderr, "Can't get page_offset from /proc/kcore\n");
+
+	close(fd);
+	return ret;
+}
+
+static int get_phys_offset(unsigned long long *phys_offset)
+{
+	int fd, ret = 0;
+
+	if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+		fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+		return EFAILED;
+	}
+
+	ret = read_phys_offset_elf_kcore(fd, (long *)phys_offset);
+	if (ret)
+		fprintf(stderr, "Can't get phys_offset from /proc/kcore\n");

-	return vaddr_off;
+	close(fd);
+	return ret;
+}
+
+unsigned long phys_to_virt(struct crash_elf_info *elf_info, unsigned long long p)
+{
+	return elf_info->page_offset - phys_offset + p;
 }

 /*
@@ -124,7 +152,14 @@ int load_elfcorehdr(struct kexec_info *info)
 	if (crash_get_memory_ranges())
 		return EFAILED;

-	elf_info.page_offset = get_page_offset(info);
+	if (get_phys_offset(&phys_offset))
+		return EFAILED;
+
+	dbgprintf("phys_offset:   %016llx\n", phys_offset);
+
+	if (get_page_offset(&elf_info.page_offset))
+		return EFAILED;
+
 	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);

 #if __riscv_xlen == 64
diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 589bc1a..36eb895 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -1308,16 +1308,39 @@ int read_phys_offset_elf_kcore(int fd, long *phys_off)

 	*phys_off = ULONG_MAX;

+	if (phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	/* If we have a valid 'PHYS_OFFSET' by now,
+	 * return it to the caller now.
+	 */
 	ret = read_elf(fd);
-	if (!ret) {
-		/* If we have a valid 'PHYS_OFFSET' by now,
-		 * return it to the caller now.
-		 */
-		if (phys_offset != UINT64_MAX) {
-			*phys_off = phys_offset;
-			return ret;
-		}
+	if (!ret && phys_offset != UINT64_MAX) {
+		*phys_off = phys_offset;
+		return 0;
+	}
+
+	return -EFAULT;
+}
+
+int read_page_offset_elf_kcore(int fd, long *page_off)
+{
+	int ret;
+
+	*page_off = ULONG_MAX;
+
+	if (page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
+	}
+
+	ret = read_elf(fd);
+	if (!ret && page_offset != UINT64_MAX) {
+		*page_off = page_offset;
+		return 0;
 	}

-	return 2;
+	return -EFAULT;
 }
diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
index fdf4c3d..3ccc950 100644
--- a/util_lib/include/elf_info.h
+++ b/util_lib/include/elf_info.h
@@ -29,6 +29,7 @@ int get_pt_load(int idx,
 	unsigned long long *virt_start,
 	unsigned long long *virt_end);
 int read_phys_offset_elf_kcore(int fd, long *phys_off);
+int read_page_offset_elf_kcore(int fd, long *page_off);
 int read_elf(int fd);
 void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
 extern void (*arch_scan_vmcoreinfo)(char *pos);
-- 
2.27.0


From luo.haiyang at zte.com.cn  Wed May 27 01:30:50 2026
From: luo.haiyang at zte.com.cn (luo.haiyang at zte.com.cn)
Date: Wed, 27 May 2026 16:30:50 +0800 (CST)
Subject: =?UTF-8?B?UmU6IFtQQVRDSCB2NCAyLzRdIFJJU0NWOiBGaXggaW5jb3JyZWN0IHZpcnR1YWwgYWRkcmVzcyB0cmFuc2xhdGlvbiBpbj8gY3Jhc2hkdW1wIGxvYWQ=?=
In-Reply-To: <20260527072709.GA51310@horms.kernel.org>
References: 20260521120643491yZJMsKq2fLbFcvtV5ukN1@zte.com.cn,20260521120946097C8qpZkVC2mBT_ozYEHlhp@zte.com.cn,20260527072709.GA51310@horms.kernel.org
Message-ID: <20260527163050630HR4Ri1nkGfW93wsEmfqOO@zte.com.cn>

SimonHorman <horms at kernel.org> wrote:
>On Thu, May 21, 2026 at 12:09:46PM +0800, luo.haiyang at zte.com.cn wrote:
>> From: Luo Haiyang <luo.haiyang at zte.com.cn>
>> 
>> The kexec shows the following crashdump log:
>> 
>> Elf header: p_type = 1, p_offset = 0x80100000 p_paddr = 0x80100000 p_vaddr = 0xcff000 ...
>> Elf header: p_type = 1, p_offset = 0x100000000 p_paddr = 0x100000000 p_vaddr = 0x80bff000 ...
>> Elf header: p_type = 1, p_offset = 0x87ffff000 p_paddr = 0x87ffff000 p_vaddr = 0x800bfe000 ...
>> 
>> Obviously, phys_to_virt returns an incorrect virtual address (vaddr). On the RISC-V architecture,
>> the linear address mapping is defined as:
>> va = pa + kernel_map.va_pa_offset (kernel_map.va_pa_offset = PAGE_OFFSET - phys_ram_base)
>> Both PAGE_OFFSET and phys_ram_base can be read from /proc/kcore.
>> 
>> Signed-off-by: Luo Haiyang <luo.haiyang at zte.com.cn>
>
>Hi,
>
>Unfortunately this patch seems to be somewhat mangled
>and does not apply. Could you look into that?

Seems something got broken, I've resent the patch. Could you please see if it works?

Thanks
Luo haiyang


From pasha.tatashin at soleen.com  Wed May 27 07:41:37 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 14:41:37 +0000
Subject: [Hypervisor Live Update] Notes from May 18, 2026
In-Reply-To: <CALzav=dkVWYmbWCO6wDuQrxetAcqYkitJiRF_BhDDGqn2_-RKA@mail.gmail.com>
References: <ahXiYGo-IWI94IVP@google.com>
 <CALzav=dkVWYmbWCO6wDuQrxetAcqYkitJiRF_BhDDGqn2_-RKA@mail.gmail.com>
Message-ID: <ahcCXlbbnsxUAfeb@plex>

On 05-26 11:53, David Matlack wrote:
> On Tue, May 26, 2026 at 11:37?AM Pasha Tatashin
> <pasha.tatashin at soleen.com> wrote:
> 
> > Next meeting will be on Monday, June 1 at 8am PDT (UTC-7), everybody is
> > welcome: https://meet.google.com/rjn-dmzu-hgq
> >
> > Topics for the next meeting:
> > - Vipin and David Matlack roadmap for PCI and VFIO changes to solicit
> >   upstream feedback
> 
> Hey Pasha,
> 
> We'll need to push this back by one meeting (June 15th) due to the US
> holiday and some out-of-office leave we didn't account for.

Understood. We will address this on June 15th.

Thank you,
Pasha

> 
> > - Status of I/O freezing implementations and HugeTLB fallback allocation
> >   patches
> > - PCI core changes v6 review and next steps
> > - IOMMU persistence v3 and roadmap progress
> > - Updates on vm_splice investigations for CRIU memory preservation
> > - later: KHO support for Confidential VMs including page table
> >   preservation and pinning
> > - later: versioning support for luo to negotiate
> > - later: KHO enlightenment for ASI
> > - later: testing methodology to allow downstream consumers to qualify
> >   that live update works from one version to another
> > - later: reducing blackout window during live update


From luca.boccassi at gmail.com  Wed May 27 08:37:12 2026
From: luca.boccassi at gmail.com (luca.boccassi at gmail.com)
Date: Wed, 27 May 2026 16:37:12 +0100
Subject: [PATCH] liveupdate: document current compatibility status
Message-ID: <20260527153725.1770394-1-luca.boccassi@gmail.com>

From: Luca Boccassi <luca.boccassi at gmail.com>

We currently do not guarantee compatibility due to ongoing
development, so mention this explicitly in the user-facing
documentation.

Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>
---
 Documentation/userspace-api/liveupdate.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Documentation/userspace-api/liveupdate.rst b/Documentation/userspace-api/liveupdate.rst
index f6efc32fb40b..cdd74627ab75 100644
--- a/Documentation/userspace-api/liveupdate.rst
+++ b/Documentation/userspace-api/liveupdate.rst
@@ -14,6 +14,14 @@ ioctl uAPI
 ===========
 .. kernel-doc:: include/uapi/linux/liveupdate.h
 
+Note on Compatibility
+=====================
+
+Note that the Live Update feature is still under development and subject to
+change, so compatibility is not guaranteed when kexec rebooting between
+different kernel versions. This is expected to change and stabilize in a future
+version.
+
 Userspace Integration
 =====================
 
-- 
2.47.3



From pasha.tatashin at soleen.com  Wed May 27 10:05:23 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 17:05:23 +0000
Subject: [ANNOUNCE/CFP] LPC 2026 Live Update Microconference
Message-ID: <ahcc3Qyuy7Oy03Iq@plex>

Hi,

The Call for Proposals is now open for the Live Update Microconference 
[1], taking place at the Linux Plumbers Conference this October 5-7 in 
Prague [2].

As infrastructure scales, minimizing workload disruption during host 
updates has become a critical operational requirement. This 
microconference focuses on the evolving engineering challenges behind 
seamless software handovers, state preservation, and warm reboots that 
bypass traditional hardware reset cycles.

We are seeking technical presentations, and problem statements spanning 
the entire stack?including kexec optimizations, hypervisor integration, 
user-space checkpoint/restore, firmware coordination, and device state 
handovers.

---
A few focus areas we hope to cover during this year's sessions include:
---
 * Orphaned Virtual Machine for zer-downtime upgrades.
 * Live Update Orchestrator (LUO) enhancements and architecture
 * Kexec HandOver (KHO) framework advancements
 * Kexec reboot latencies to minimize guest/workload downtime
 * Preserving guest states across live updates (guest_memfd, hugetlb)
 * In-flight DMA, IOMMU tracking, and preservation
 * VFIO device state preservation and live update migration mechanics
 * PCI/PCIe device handovers, reset avoidance, and state restoration
 * Live Update in Confidential Computing (TDX/SEV-SNP)
 * Building robust testing, fuzzing, and validation frameworks for 
   upgrade paths

---
Submission Details & Deadlines:
---
 * CFP Portal: https://lpc.events/event/20/abstracts/
 * Track Selection: "Live Update MC"
 * Deadline: July 24th, 2026

Session Format Reminder:
LPC sessions are highly interactive working groups, not static lecture 
slots. Proposals should be designed to introduce a concrete technical 
challenge quickly and leave substantial time open for real-time debate, 
consensus-building, and problem-solving with the room. Keep slides 
focused on architecture and open issues rather than heavy text blocks.

We hope to see you in Prague to help push the live update ecosystem 
forward.

(Note: LPC attendance caps fill up exceptionally fast. We strongly 
advise booking your passes early, as track organizers cannot always 
guarantee speaker registration overrides once the event sells out.)

[1] https://lpc.events/event/20/contributions/2321/
[2] https://lpc.events/

Best regards,
Pasha


From lkp at intel.com  Wed May 27 10:19:51 2026
From: lkp at intel.com (kernel test robot)
Date: Thu, 28 May 2026 01:19:51 +0800
Subject: [liveupdate:fixes] BUILD SUCCESS
 3a29a9841f4bfb79840f7d1f8115cc7b25e744e3
Message-ID: <202605280143.Rnjk00YT-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git fixes
branch HEAD: 3a29a9841f4bfb79840f7d1f8115cc7b25e744e3  kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES

elapsed time: 1514m

configs tested: 63
configs skipped: 10

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha            allnoconfig    gcc-15.2.0
alpha           allyesconfig    gcc-15.2.0
arc             allmodconfig    gcc-15.2.0
arc              allnoconfig    gcc-15.2.0
arc             allyesconfig    gcc-15.2.0
arm              allnoconfig    clang-23
arm             allyesconfig    gcc-15.2.0
arm64           allmodconfig    clang-19
arm64            allnoconfig    gcc-15.2.0
csky            allmodconfig    gcc-15.2.0
csky             allnoconfig    gcc-15.2.0
hexagon         allmodconfig    clang-17
hexagon          allnoconfig    clang-23
i386            allmodconfig    gcc-14
i386             allnoconfig    gcc-14
i386            allyesconfig    gcc-14
loongarch       allmodconfig    clang-19
loongarch        allnoconfig    clang-23
m68k            allmodconfig    gcc-15.2.0
m68k             allnoconfig    gcc-15.2.0
m68k            allyesconfig    gcc-15.2.0
microblaze       allnoconfig    gcc-15.2.0
microblaze      allyesconfig    gcc-15.2.0
mips            allmodconfig    gcc-15.2.0
mips             allnoconfig    gcc-15.2.0
mips            allyesconfig    gcc-15.2.0
nios2           allmodconfig    gcc-11.5.0
nios2            allnoconfig    gcc-11.5.0
openrisc        allmodconfig    gcc-15.2.0
openrisc         allnoconfig    gcc-15.2.0
openrisc           defconfig    gcc-15.2.0
parisc          allmodconfig    gcc-15.2.0
parisc           allnoconfig    gcc-15.2.0
parisc          allyesconfig    gcc-15.2.0
powerpc         allmodconfig    gcc-15.2.0
powerpc          allnoconfig    gcc-15.2.0
powerpc     arches_defconfig    gcc-15.2.0
riscv           allmodconfig    clang-23
riscv            allnoconfig    gcc-15.2.0
riscv           allyesconfig    clang-16
s390            allmodconfig    clang-18
s390             allnoconfig    clang-23
s390            allyesconfig    gcc-15.2.0
sh              allmodconfig    gcc-15.2.0
sh               allnoconfig    gcc-15.2.0
sh              allyesconfig    gcc-15.2.0
sh                 defconfig    gcc-15.2.0
sparc            allnoconfig    gcc-15.2.0
sparc              defconfig    gcc-15.2.0
sparc64         allmodconfig    clang-23
sparc64            defconfig    clang-20
um              allmodconfig    clang-19
um               allnoconfig    clang-23
um              allyesconfig    gcc-14
um                 defconfig    clang-23
um            i386_defconfig    gcc-14
um          x86_64_defconfig    clang-23
x86_64          allmodconfig    clang-20
x86_64           allnoconfig    clang-20
x86_64          allyesconfig    clang-20
x86_64             defconfig    gcc-14
x86_64         rhel-9.4-rust    clang-20
xtensa           allnoconfig    gcc-15.2.0

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


From pasha.tatashin at soleen.com  Wed May 27 13:06:39 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:06:39 +0000
Subject: [PATCH v5 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <2vxz4ijz8v48.fsf@kernel.org>
References: <20260518125459.1092373-1-pasha.tatashin@soleen.com>
 <20260518125459.1092373-4-pasha.tatashin@soleen.com>
 <2vxzo6ic8ysn.fsf@kernel.org>
 <aguXhxrjhvmxAyb2@plex>
 <2vxz4ijz8v48.fsf@kernel.org>
Message-ID: <ahdOql2hT3dwQk9e@plex>

On 05-22 14:52, Pratyush Yadav wrote:
> On Mon, May 18 2026, Pasha Tatashin wrote:
> 
> > On 05-18 18:31, Pratyush Yadav wrote:
> >> On Mon, May 18 2026, Pasha Tatashin wrote:
> >> 
> >> > During the reboot() syscall, user processes may still be running
> >> > concurrently and attempting to mutate sessions (e.g., creating,
> >> > retrieving, or releasing sessions). To prevent this, introduce
> >> > luo_session_serialize_rwsem to synchronize mutations with the
> >> > serialization process.
> >> >
> >> > All session mutation operations (create, retrieve, release, ioctl) take
> >> > the read lock. The serialization process (luo_session_serialize) takes
> >> > the write lock and holds it indefinitely on success. This effectively
> >> > freezes the LUO session subsystem during the transition to the new
> >> > kernel. If serialization fails, the lock is released to allow recovery.
> >> 
> >> Good idea I think.
> >
> > Hi Pratyush,
> >
> >> 
> >> But, do we need a new mutex? Can't we use luo_session_header->rwsem?
> >> Session creation and release take the header rwsem at one point anyway,
> >> so perhaps we can just reuse that?
> >
> > The sh->rwsem is for protecting the the session list. We only take it as 
> > a writer when modifying the list (insert/remove) and as a reader when 
> > traversing it. Also, we drop sh->rwsem as soon as we've acquired the 
> > per-session mutex to allow other list operations to proceed while a 
> > session is being modified.
> >
> > Because of this, many session mutation operations (specifically ioctl 
> > calls) don't touch sh->rwsem at all?they jump straight to the  session 
> > state via the file's private_data. To use sh->rwsem to block
> > these mutations, we would be forced to add down_read(&sh->rwsem) to 
> > every ioctl path. This would be a layering violation, coupling list 
> > management to per-session data mutations, and would introduce a global
> > bottleneck for operations that are otherwise independent.
> 
> As for the layering violation, I think we would need to change the
> semantics of the lock -- it no longer protects only the list, but other
> session operations as well.
> 
> But yeah, if we do this then operations like session creation would have
> to wait for ongoing session operations like PRESERVE_FD. My argument was
> based around the fact that session creation or removal should not be
> very frequent (and don't happen in the hot path anyway) so the added
> latency should not affect them as much. By doing this tradeoff we get
> slightly simpler code (and simpler locking scheme).
> 
> But I see your point as well. In practice session creation and
> PRESERVE_FD are independent and one should not block the other. Maybe we
> get VMMs creating sessions while another VMM is preserving stuff, and
> this slowing down the live update preparation? Dunno...
> 
> I suppose let's go with this patch. But, can you please document the
> lock hierarchy where you explain what this lock is for?

SGTM, Added a documentation about locking.

Pasha


From pasha.tatashin at soleen.com  Wed May 27 13:27:32 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:32 +0000
Subject: [PATCH v6 0/5] liveupdate: serialization safety and race fixes
Message-ID: <20260527202737.1345192-1-pasha.tatashin@soleen.com>

This series addresses several issues related to the synchronization
between the reboot process and LUO session management.

1. Skip LUO serialization for context-preserving kexec: A
preserve_context kexec returns to the current kernel, which is unrelated
to live update where state is passed to the next kernel. Skipping
serialization avoids unnecessary work and prevents sessions from being
left in a frozen state upon return.

2. Fix TOCTOU race in luo_session_retrieve(): Extend the rwsem lock
scope to prevent a session from being released between lookup and
mutex acquisition.

3. Block session mutations during reboot: During the reboot() syscall,
user processes may still be running concurrently and attempting to
mutate sessions. To prevent this, we introduce luo_session_serialize_rwsem.
All mutation operations (create, retrieve, release, ioctl) hold the
read lock. The serialization process holds the write lock indefinitely
on success, effectively freezing the subsystem.

4. Fix use-after-free in luo_file_unpreserve_files(): Reorder module_put()
to ensure the file handler module remains pinned while its operations
are being accessed during cleanup.

5. Remove unused ser field from struct luo_session: Clean up the
session structure by removing a field that was never utilized.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git Branch:
luo-reboot-sync/v6

Changes in v6:
- Added lock hierarchy documentation (Pratyush)
- Collected Reviewed-by from Pratyush Yadav for patches #2, #4, and #5.

Pasha Tatashin (5):
  liveupdate: skip serialization for context-preserving kexec
  liveupdate: fix TOCTOU race in luo_session_retrieve()
  liveupdate: block session mutations during reboot
  liveupdate: fix u-a-f in luo_file_unpreserve_files() and
    luo_file_finish()
  liveupdate: Remove unused ser field from struct luo_session

 kernel/kexec_core.c              |  8 ++--
 kernel/liveupdate/luo_file.c     |  5 ++-
 kernel/liveupdate/luo_internal.h |  2 -
 kernel/liveupdate/luo_session.c  | 67 +++++++++++++++++++++++++++-----
 4 files changed, 66 insertions(+), 16 deletions(-)


base-commit: 3a29a9841f4bfb79840f7d1f8115cc7b25e744e3
-- 
2.53.0



From pasha.tatashin at soleen.com  Wed May 27 13:27:33 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:33 +0000
Subject: [PATCH v6 1/5] liveupdate: skip serialization for context-preserving kexec
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <20260527202737.1345192-2-pasha.tatashin@soleen.com>

A preserve_context kexec returns to the current kernel, which is
unrelated to live update where the state is passed to the next kernel.
Skip liveupdate_reboot() in this case to avoid serialization and prevent
sessions from being left in a frozen state upon return.

Fixes: db8bed8082dc ("kexec: call liveupdate_reboot() before kexec")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/kexec_core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..dc770b9a6d05 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1146,9 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
-	error = liveupdate_reboot();
-	if (error)
-		goto Unlock;
+	if (!kexec_image->preserve_context) {
+		error = liveupdate_reboot();
+		if (error)
+			goto Unlock;
+	}
 
 #ifdef CONFIG_KEXEC_JUMP
 	if (kexec_image->preserve_context) {
-- 
2.53.0



From pasha.tatashin at soleen.com  Wed May 27 13:27:34 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:34 +0000
Subject: [PATCH v6 2/5] liveupdate: fix TOCTOU race in luo_session_retrieve()
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <20260527202737.1345192-3-pasha.tatashin@soleen.com>

Extend the scope of the rwsem_read lock in luo_session_retrieve() to
overlap with the acquisition of the session mutex. This prevents a
concurrent thread from releasing and freeing the session between the
lookup and the mutex lock.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 74c39d93d45a..169131642939 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -443,12 +443,11 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
-	scoped_guard(rwsem_read, &sh->rwsem) {
-		list_for_each_entry(it, &sh->list, list) {
-			if (!strncmp(it->name, name, sizeof(it->name))) {
-				session = it;
-				break;
-			}
+	guard(rwsem_read)(&sh->rwsem);
+	list_for_each_entry(it, &sh->list, list) {
+		if (!strncmp(it->name, name, sizeof(it->name))) {
+			session = it;
+			break;
 		}
 	}
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Wed May 27 13:27:35 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:35 +0000
Subject: [PATCH v6 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <20260527202737.1345192-4-pasha.tatashin@soleen.com>

During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., creating,
retrieving, or releasing sessions). To prevent this, introduce
luo_session_serialize_rwsem to synchronize mutations with the
serialization process.

All session mutation operations (create, retrieve, release, ioctl) take
the read lock. The serialization process (luo_session_serialize) takes
the write lock and holds it indefinitely on success. This effectively
freezes the LUO session subsystem during the transition to the new
kernel. If serialization fails, the lock is released to allow recovery.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 56 +++++++++++++++++++++++++++++++--
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 169131642939..c9bdad2e5ae7 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -46,6 +46,38 @@
  * 4.  Retrieval: A userspace agent in the new kernel can then call
  *     `luo_session_retrieve()` with a session name to get a new file
  *     descriptor and access the preserved state.
+ *
+ * Locking:
+ *
+ * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
+ * safety and prevent deadlocks during concurrent session mutations and kexec
+ * serialization:
+ *
+ * 1. `luo_session_serialize_rwsem` (global rwsem):
+ *    Protects session mutations (creation, retrieval, release, and ioctls)
+ *    against the serialization process during reboot.
+ *
+ *    - Readers: Taken by any path modifying or accessing session state (e.g.,
+ *      `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
+ *      and `luo_session_ioctl()`).
+ *    - Writer: Taken by the serialization process (`luo_session_serialize()`)
+ *      during reboot. On success, the write lock is held indefinitely to freeze
+ *      the subsystem. On failure, it is released to allow recovery.
+ *
+ * 2. `luo_session_header->rwsem` (per-list rwsem):
+ *    Synchronizes list-level operations for the incoming and outgoing session headers.
+ *
+ *    - Writer: Taken during list mutation operations (inserting or removing a
+ *      session from the list).
+ *    - Reader: Taken when traversing the list (e.g., retrieving a session by name).
+ *
+ * 3. `luo_session->mutex` (per-session mutex):
+ *    Protects the internal state and file sets of an individual session. It is
+ *    acquired during per-session operations such as preserving, retrieving,
+ *    or freezing files.
+ *
+ * Lock Hierarchy:
+ *   `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -75,6 +107,13 @@
 		sizeof(struct luo_session_header_ser)) /		\
 		sizeof(struct luo_session_ser))
 
+/*
+ * Protects session mutations during serialization. All session mutation
+ * operations must hold the read lock. The serialization process holds the write
+ * lock indefinitely on success to block all concurrent and future mutations.
+ */
+static DECLARE_RWSEM(luo_session_serialize_rwsem);
+
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:      The number of sessions currently tracked in the @list.
@@ -205,6 +244,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 	struct luo_session *session = filep->private_data;
 	struct luo_session_header *sh;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	/* If retrieved is set, it means this session is from incoming list */
 	if (session->retrieved) {
 		int err = luo_session_finish_one(session);
@@ -382,6 +422,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
 	if (ret)
 		return ret;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	return op->execute(session, &ucmd);
 }
 
@@ -417,14 +458,17 @@ int luo_session_create(const char *name, struct file **filep)
 	if (IS_ERR(session))
 		return PTR_ERR(session);
 
+	down_read(&luo_session_serialize_rwsem);
 	err = luo_session_insert(&luo_session_global.outgoing, session);
 	if (err)
 		goto err_free;
 
-	scoped_guard(mutex, &session->mutex)
-		err = luo_session_getfile(session, filep);
+	mutex_lock(&session->mutex);
+	err = luo_session_getfile(session, filep);
+	mutex_unlock(&session->mutex);
 	if (err)
 		goto err_remove;
+	up_read(&luo_session_serialize_rwsem);
 
 	return 0;
 
@@ -432,6 +476,7 @@ int luo_session_create(const char *name, struct file **filep)
 	luo_session_remove(&luo_session_global.outgoing, session);
 err_free:
 	luo_session_free(session);
+	up_read(&luo_session_serialize_rwsem);
 
 	return err;
 }
@@ -443,6 +488,7 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	struct luo_session *it;
 	int err;
 
+	guard(rwsem_read)(&luo_session_serialize_rwsem);
 	guard(rwsem_read)(&sh->rwsem);
 	list_for_each_entry(it, &sh->list, list) {
 		if (!strncmp(it->name, name, sizeof(it->name))) {
@@ -615,7 +661,8 @@ int luo_session_serialize(void)
 	int i = 0;
 	int err;
 
-	guard(rwsem_write)(&sh->rwsem);
+	down_write(&luo_session_serialize_rwsem);
+	down_write(&sh->rwsem);
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -626,6 +673,7 @@ int luo_session_serialize(void)
 		i++;
 	}
 	sh->header_ser->count = sh->count;
+	up_write(&sh->rwsem);
 
 	return 0;
 
@@ -635,6 +683,8 @@ int luo_session_serialize(void)
 		luo_session_unfreeze_one(session, &sh->ser[i]);
 		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
 	}
+	up_write(&sh->rwsem);
+	up_write(&luo_session_serialize_rwsem);
 
 	return err;
 }
-- 
2.53.0



From pasha.tatashin at soleen.com  Wed May 27 13:27:36 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:36 +0000
Subject: [PATCH v6 4/5] liveupdate: fix u-a-f in luo_file_unpreserve_files() and luo_file_finish()
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <20260527202737.1345192-5-pasha.tatashin@soleen.com>

In luo_file_unpreserve_files() and luo_file_finish(), reorder
module_put() and xa_erase() to ensure the file handler module remains
pinned while its operations are being accessed.

Specifically, luo_get_id() dereferences fh->ops->get_id, so the module
reference must be held until after xa_erase() (which calls luo_get_id)
completes.

For luo_file_finish(), this requires moving the module_put() call out of
the luo_file_finish_one() helper and into the main loop of
luo_file_finish() itself.

Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a0a419085e28..208987502f73 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -385,10 +385,11 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 		args.private_data = luo_file->private_data;
 		luo_file->fh->ops->unpreserve(&args);
 		luo_flb_file_unpreserve(luo_file->fh);
-		module_put(luo_file->fh->ops->owner);
 
 		xa_erase(&luo_preserved_files,
 			 luo_get_id(luo_file->fh, luo_file->file));
+		module_put(luo_file->fh->ops->owner);
+
 		list_del(&luo_file->list);
 		file_set->count--;
 
@@ -677,7 +678,6 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
 
 	luo_file->fh->ops->finish(&args);
 	luo_flb_file_finish(luo_file->fh);
-	module_put(luo_file->fh->ops->owner);
 }
 
 /**
@@ -738,6 +738,7 @@ int luo_file_finish(struct luo_file_set *file_set)
 				 luo_get_id(luo_file->fh, luo_file->file));
 			fput(luo_file->file);
 		}
+		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
 		mutex_destroy(&luo_file->mutex);
-- 
2.53.0



From pasha.tatashin at soleen.com  Wed May 27 13:27:37 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Wed, 27 May 2026 20:27:37 +0000
Subject: [PATCH v6 5/5] liveupdate: Remove unused ser field from struct luo_session
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <20260527202737.1345192-6-pasha.tatashin@soleen.com>

The ser field in struct luo_session was intended to point to the
serialized data for a session, but it was never actually utilized in the
implementation. All serialization and deserialization logic consistently
uses the pointers maintained in struct luo_session_header.

Remove the dead field to clean up the structure.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..dd53d4a7277e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -59,7 +59,6 @@ struct luo_file_set {
  * struct luo_session - Represents an active or incoming Live Update session.
  * @name:       A unique name for this session, used for identification and
  *              retrieval.
- * @ser:        Pointer to the serialized data for this session.
  * @list:       A list_head member used to link this session into a global list
  *              of either outgoing (to be preserved) or incoming (restored from
  *              previous kernel) sessions.
@@ -70,7 +69,6 @@ struct luo_file_set {
  */
 struct luo_session {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
-	struct luo_session_ser *ser;
 	struct list_head list;
 	bool retrieved;
 	struct luo_file_set file_set;
-- 
2.53.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:42 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:42 -0700
Subject: [RFC PATCH 00/20] mshv: enable kexec with Hyper-V donated pages and partitions
Message-ID: <20260528004204.1484584-1-jloeser@linux.microsoft.com>

When Linux runs as an L1 Virtual Host (L1VH) under Hyper-V, the MSHV
root partition driver deposits pages to the hypervisor and creates
partitions for guest VMs. Prior patches enabled kexec for L1VH, but
only when no partitions had been created and no memory had been donated.

This series lifts that limitation. It uses KHO (Kexec Handover) to:

 - Track all pages deposited to the hypervisor in a KHO radix tree
   and preserve them across kexec so the new kernel knows which pages
   are owned by the hypervisor.

 - Freeze running partitions before kexec, record their IDs in the
   KHO FDT, and vacuum (tear down + reclaim memory) stale partitions
   after kexec.

 - In case of a crash, exclude hypervisor-owned pages from crash
   dump collection by passing the radix tree root PA via Hyper-V
   crash MSR P2 to the crash kernel.

Dependency on Pratyush's KHO series
===================================

Patches 1-12 are cherry-picked from Pratyush Yadav's v1 series
"kho: make boot time huge page allocation work nicely with KHO" [1],
which is still under discussion. This series uses functionality from
those patches -- specifically the meta-data page enumeration via table
callbacks and the restructured radix tree API. It also extends the
KHO radix tree with:

 - A freeze mechanism to lock the tree before serializing for kexec
   (patch 13).

 - A crash-kernel-safe variant that memremaps radix nodes for use
   outside the direct map (patch 14).

Patch overview
==============

Patches 1-12:  KHO radix tree and memblock changes (from [1])
Patch 13:      Radix tree freeze and del_key() error reporting
Patch 14:      Crash-kernel-safe radix tree presence check
Patch 15:      Page tracker using KHO radix tree for deposited pages
Patch 16:      Debugfs interface for page tracker
Patches 17-18: Crash MSR reshuffling + crash dump page exclusion
Patch 19:      Export kexec_in_progress for modules
Patch 20:      Freeze and vacuum partitions across kexec

Feedback
========

This is an RFC. I am looking for feedback on the overall approach as
well as the KHO changes (patches 13-14).

[1] https://lore.kernel.org/linux-mm/20260429133928.850721-1-pratyush at kernel.org/

Based-on: linux-next/master (next-20260527)

Jork Loeser (8):
  kho: add radix tree freeze and del_key() error reporting
  kho: Add crash-kernel-safe radix tree presence check
  mshv: Use page tracker to manage MSHV-owned pages and preserve with
    KHO
  mshv: Add debugfs interface to page tracker
  hyperv: Reserve crash MSR P2 for page preservation root PA
  mshv: Exclude Hyper-V donated pages from crash dump collection
  kexec: export kexec_in_progress for modules
  mshv: freeze and vacuum partitions across kexec

Pratyush Yadav (Google) (12):
  kho: generalize radix tree APIs
  kho: store incoming radix tree in kho_in
  kho: add a struct for radix callbacks
  kho: add callback for table pages
  kho: add data argument to radix walk callback
  kho: allow early-boot usage of the KHO radix tree
  kho: allow destroying KHO radix tree
  kho: add kho_radix_init_tree()
  memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
  kho: extended scratch
  kho: return virtual address of mem_map
  mm/hugetlb: make bootmem allocation work with KHO

 arch/arm64/hyperv/hv_core.c        |   6 +-
 arch/x86/hyperv/hv_init.c          |   4 +-
 drivers/hv/Kconfig                 |   3 +
 drivers/hv/Makefile                |   2 +-
 drivers/hv/hv_common.c             |   5 +-
 drivers/hv/hv_proc.c               |  32 +-
 drivers/hv/mshv_debugfs.c          |  99 +++++
 drivers/hv/mshv_page_preserve.c    | 557 ++++++++++++++++++++++++++
 drivers/hv/mshv_page_preserve.h    |  21 +
 drivers/hv/mshv_root.h             |   5 +
 drivers/hv/mshv_root_hv_call.c     |  12 +-
 drivers/hv/mshv_root_main.c        | 341 ++++++++++++++--
 include/linux/kexec_handover.h     |   1 +
 include/linux/kho_radix_tree.h     |  90 ++++-
 include/linux/memblock.h           |  14 +
 kernel/kexec_core.c                |   1 +
 kernel/liveupdate/kexec_handover.c | 605 +++++++++++++++++++++++------
 mm/hugetlb.c                       |  19 +-
 mm/memblock.c                      | 177 +++++++--
 mm/mm_init.c                       |   1 +
 20 files changed, 1767 insertions(+), 228 deletions(-)
 create mode 100644 drivers/hv/mshv_page_preserve.c
 create mode 100644 drivers/hv/mshv_page_preserve.h

--
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:43 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:43 -0700
Subject: [RFC PATCH 01/20] kho: generalize radix tree APIs
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-2-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

The KHO radix tree is a data structure that can track the presence or
absence of an arbitrary key, with nothing inherently tied to KHO memory
preservation tracking. This was one of the design goals of the radix
tree. This was done to enable it to be re-used by other users of KHO.

Despite that, the radix tree APIs are very closely tied to KHO memory
preservation tracking. Adding a key is done by kho_radix_add_page(),
which encodes it as a page tracking operation and takes in PFN and
order. kho_radix_del_page() does the same. These functions encode the
key internally that goes into the radix tree. kho_radix_walk_tree() does
the same by baking the PFN and order into the callback arguments.

Generalize the APIs by taking the key directly and doing the encoding at
the callers. Rename the functions to kho_radix_add_key() and
kho_radix_del_key(). In practice, this removes a line each from the
functions and moves the encoding function call to the callers.
Similarly, update kho_radix_tree_walk_callback_t to take the key
directly.

To keep the naming convention clearer, rename
kho_radix_{encode,decode}_key() to kho_{encode,decode}_radix_key().

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  18 ++---
 kernel/liveupdate/kexec_handover.c | 119 ++++++++++++++---------------
 2 files changed, 63 insertions(+), 74 deletions(-)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 84e918b96e53..f368f3b9f923 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -34,30 +34,24 @@ struct kho_radix_tree {
 	struct mutex lock; /* protects the tree's structure and root pointer */
 };
 
-typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,
-					      unsigned int order);
+typedef int (*kho_radix_tree_walk_callback_t)(unsigned long key);
 
 #ifdef CONFIG_KEXEC_HANDOVER
 
-int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,
-		       unsigned int order);
-
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
-			unsigned int order);
-
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
 			kho_radix_tree_walk_callback_t cb);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
-static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,
-				     unsigned int order)
+static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 {
 	return -EOPNOTSUPP;
 }
 
-static inline void kho_radix_del_page(struct kho_radix_tree *tree,
-				      unsigned long pfn, unsigned int order) { }
+static inline void kho_radix_del_key(struct kho_radix_tree *tree,
+				     unsigned long key) { }
 
 static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
 				      kho_radix_tree_walk_callback_t cb)
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4834a809985a..05a6eb56e176 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -85,7 +85,7 @@ static struct kho_out kho_out = {
 };
 
 /**
- * kho_radix_encode_key - Encodes a physical address and order into a radix key.
+ * kho_encode_radix_key - Encodes a physical address and order into a radix key.
  * @phys: The physical address of the page.
  * @order: The order of the page.
  *
@@ -95,7 +95,7 @@ static struct kho_out kho_out = {
  *
  * Return: The encoded unsigned long radix key.
  */
-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
+static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)
 {
 	/* Order bits part */
 	unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
@@ -106,17 +106,17 @@ static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
 }
 
 /**
- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
+ * kho_decode_radix_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(),
+ * This function reverses the encoding performed by kho_encode_radix_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)
+static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)
 {
 	unsigned int order_bit = fls64(key);
 	phys_addr_t phys;
@@ -144,24 +144,21 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
 }
 
 /**
- * kho_radix_add_page - Marks a page as preserved in the radix tree.
+ * kho_radix_add_key - Add a key to the radix tree.
  * @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to preserve.
- * @order: The order of the page.
+ * @key: The key to add.
  *
- * 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.
+ * This function traverses the radix tree based on the key provided. It sets the
+ * corresponding bit in the leaf bitmap to mark the key as present. 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)
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 {
 	/* 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;
@@ -224,22 +221,19 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
 
 	return err;
 }
-EXPORT_SYMBOL_GPL(kho_radix_add_page);
+EXPORT_SYMBOL_GPL(kho_radix_add_key);
 
 /**
- * kho_radix_del_page - Removes a page's preservation status from the radix tree.
+ * kho_radix_del_key - Removes the key 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.
+ * @key: The key to remove.
  *
  * 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.
+ * the key, effectively removing it from the tree. 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)
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 {
-	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;
@@ -270,21 +264,18 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
 	idx = kho_radix_get_bitmap_index(key);
 	__clear_bit(idx, leaf->bitmap);
 }
-EXPORT_SYMBOL_GPL(kho_radix_del_page);
+EXPORT_SYMBOL_GPL(kho_radix_del_key);
 
 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);
+		err = cb(key | i);
 		if (err)
 			return err;
 	}
@@ -332,15 +323,14 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 }
 
 /**
- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
  * @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.
+ *      invoked for each key in the tree.
  *
  * 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.
+ * down to the lowest level (level 0). For each key found, it invokes the
+ * provided callback.
  *
  * Return: 0 if the walk completed the specified tree, or the non-zero return
  *         value from the callback that stopped the walk.
@@ -484,13 +474,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
 	return pfn_to_page(pfn);
 }
 
-static int __init kho_preserved_memory_reserve(phys_addr_t phys,
-					       unsigned int order)
+static int __init kho_preserved_memory_reserve(unsigned long key)
 {
 	union kho_page_info info;
 	struct page *page;
+	unsigned int order;
+	phys_addr_t phys;
 	u64 sz;
 
+	phys = kho_decode_radix_key(key, &order);
+
 	sz = 1 << (order + PAGE_SHIFT);
 	page = kho_get_preserved_page(phys, order);
 
@@ -618,30 +611,20 @@ early_param("kho_scratch", kho_parse_scratch_size);
 
 static void __init scratch_size_update(void)
 {
-	/*
-	 * If fixed sizes are not provided via command line, calculate them
-	 * now.
-	 */
-	if (scratch_scale) {
-		phys_addr_t size;
+	phys_addr_t size;
 
-		size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
-						   NUMA_NO_NODE);
-		size = size * scratch_scale / 100;
-		scratch_size_lowmem = size;
+	if (!scratch_scale)
+		return;
 
-		size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
-						   NUMA_NO_NODE);
-		size = size * scratch_scale / 100 - scratch_size_lowmem;
-		scratch_size_global = size;
-	}
+	size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
+					   NUMA_NO_NODE);
+	size = size * scratch_scale / 100;
+	scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
 
-	/*
-	 * Scratch areas are released as MIGRATE_CMA. Round them up to the right
-	 * size.
-	 */
-	scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
-	scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
+	size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
+					   NUMA_NO_NODE);
+	size = size * scratch_scale / 100 - scratch_size_lowmem;
+	scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
 }
 
 static phys_addr_t __init scratch_size_node(int nid)
@@ -859,7 +842,8 @@ int kho_preserve_folio(struct folio *folio)
 	if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
 		return -EINVAL;
 
-	return kho_radix_add_page(tree, pfn, order);
+	return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+							    order));
 }
 EXPORT_SYMBOL_GPL(kho_preserve_folio);
 
@@ -877,7 +861,7 @@ void kho_unpreserve_folio(struct folio *folio)
 	const unsigned long pfn = folio_pfn(folio);
 	const unsigned int order = folio_order(folio);
 
-	kho_radix_del_page(tree, pfn, order);
+	kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));
 }
 EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
 
@@ -906,7 +890,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,
 	while (pfn < end_pfn) {
 		order = __kho_preserve_pages_order(pfn, end_pfn);
 
-		kho_radix_del_page(tree, pfn, order);
+		kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+							     order));
 
 		pfn += 1 << order;
 	}
@@ -937,9 +922,19 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
 	}
 
 	while (pfn < end_pfn) {
-		unsigned int order = __kho_preserve_pages_order(pfn, end_pfn);
+		unsigned int order =
+			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+
+		/*
+		 * Make sure all the pages in a single preservation are in the
+		 * same NUMA node. The restore machinery can not cope with a
+		 * preservation spanning multiple NUMA nodes.
+		 */
+		while (pfn_to_nid(pfn) != pfn_to_nid(pfn + (1UL << order) - 1))
+			order--;
 
-		err = kho_radix_add_page(tree, pfn, order);
+		err = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+								   order));
 		if (err) {
 			failed_pfn = pfn;
 			break;
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:44 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:44 -0700
Subject: [RFC PATCH 02/20] kho: store incoming radix tree in kho_in
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-3-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

This allows other functions to also use the radix tree. While at it,
also use kho_get_mem_map_phys() instead of duplicating the code to get
the radix tree root from the FDT.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 kernel/liveupdate/kexec_handover.c | 27 ++++++++-------------------
 1 file changed, 8 insertions(+), 19 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 05a6eb56e176..afc986845839 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1316,6 +1316,7 @@ struct kho_in {
 	char previous_release[__NEW_UTS_LEN + 1];
 	u32 kexec_count;
 	struct kho_debugfs dbg;
+	struct kho_radix_tree radix_tree;
 };
 
 static struct kho_in kho_in = {
@@ -1395,24 +1396,10 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
 
 static int __init kho_mem_retrieve(const void *fdt)
 {
-	struct kho_radix_tree tree;
-	const phys_addr_t *mem;
-	int len;
-
-	/* Retrieve the KHO radix tree from passed-in FDT. */
-	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
-
-	if (!mem || len != sizeof(*mem)) {
-		pr_err("failed to get preserved KHO memory tree\n");
-		return -ENOENT;
-	}
-
-	if (!*mem)
-		return -EINVAL;
-
-	tree.root = phys_to_virt(*mem);
-	mutex_init(&tree.lock);
-	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
+	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
+	mutex_init(&kho_in.radix_tree.lock);
+	return kho_radix_walk_tree(&kho_in.radix_tree,
+				   kho_preserved_memory_reserve);
 }
 
 static __init int kho_out_fdt_setup(void)
@@ -1619,8 +1606,10 @@ void __init kho_memory_init(void)
 	if (kho_in.scratch_phys) {
 		kho_scratch = phys_to_virt(kho_in.scratch_phys);
 
-		if (kho_mem_retrieve(kho_get_fdt()))
+		if (kho_mem_retrieve(kho_get_fdt())) {
 			kho_in.fdt_phys = 0;
+			kho_in.radix_tree.root = NULL;
+		}
 	} else {
 		kho_reserve_scratch();
 	}
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:45 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:45 -0700
Subject: [RFC PATCH 03/20] kho: add a struct for radix callbacks
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-4-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

A future commit will add more callbacks for the KHO radix tree. Add a
struct for collecting the callbacks.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     | 15 ++++++++++++---
 kernel/liveupdate/kexec_handover.c | 29 ++++++++++++++++-------------
 2 files changed, 28 insertions(+), 16 deletions(-)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index f368f3b9f923..030da6399d28 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -34,14 +34,23 @@ struct kho_radix_tree {
 	struct mutex lock; /* protects the tree's structure and root pointer */
 };
 
-typedef int (*kho_radix_tree_walk_callback_t)(unsigned long key);
+/**
+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
+ * @key:      Called on each present key in the radix tree.
+ *
+ * For each callback, a return value of 0 continues the walk and a non-zero
+ * return value is directly returned to the caller.
+ */
+struct kho_radix_walk_cb {
+	int (*key)(unsigned long key);
+};
 
 #ifdef CONFIG_KEXEC_HANDOVER
 
 int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
 void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			kho_radix_tree_walk_callback_t cb);
+			const struct kho_radix_walk_cb *cb);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
@@ -54,7 +63,7 @@ static inline void kho_radix_del_key(struct kho_radix_tree *tree,
 				     unsigned long key) { }
 
 static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
-				      kho_radix_tree_walk_callback_t cb)
+				      const struct kho_radix_walk_cb *cb)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index afc986845839..b22b3cec251e 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -266,16 +266,18 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 }
 EXPORT_SYMBOL_GPL(kho_radix_del_key);
 
-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
-			       unsigned long key,
-			       kho_radix_tree_walk_callback_t cb)
+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
+			       const struct kho_radix_walk_cb *cb)
 {
 	unsigned long *bitmap = (unsigned long *)leaf;
 	unsigned int i;
 	int err;
 
+	if (!cb->key)
+		return 0;
+
 	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
-		err = cb(key | i);
+		err = cb->key(key | i);
 		if (err)
 			return err;
 	}
@@ -285,7 +287,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
 
 static int __kho_radix_walk_tree(struct kho_radix_node *root,
 				 unsigned int level, unsigned long start,
-				 kho_radix_tree_walk_callback_t cb)
+				 const struct kho_radix_walk_cb *cb)
 {
 	struct kho_radix_node *node;
 	struct kho_radix_leaf *leaf;
@@ -325,18 +327,16 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 /**
  * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
  * @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 key in the tree.
+ * @cb:   Set of callbacks to be invoked during the tree walk.
  *
- * This function walks the radix tree, searching from the specified top level
- * down to the lowest level (level 0). For each key found, it invokes the
- * provided callback.
+ * This function walks the radix tree, searching from the top level down to the
+ * lowest level (level 0), invoking the appropriate callbacks.
  *
  * 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)
+			const struct kho_radix_walk_cb *cb)
 {
 	if (WARN_ON_ONCE(!tree->root))
 		return -EINVAL;
@@ -1396,10 +1396,13 @@ EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
 
 static int __init kho_mem_retrieve(const void *fdt)
 {
+	const struct kho_radix_walk_cb cb = {
+		.key = kho_preserved_memory_reserve,
+	};
+
 	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
 	mutex_init(&kho_in.radix_tree.lock);
-	return kho_radix_walk_tree(&kho_in.radix_tree,
-				   kho_preserved_memory_reserve);
+	return kho_radix_walk_tree(&kho_in.radix_tree, &cb);
 }
 
 static __init int kho_out_fdt_setup(void)
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:46 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:46 -0700
Subject: [RFC PATCH 04/20] kho: add callback for table pages
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-5-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

The KHO memory preservation radix tree does not mark the table pages
themselves as scratch. This is done to avoid a circular dependency where
preserving a page can lead of allocating other preserved pages. This
means any walker looking for free ranges of memory outside of scratch
areas will ignore the table

Add a table callback that is invoked for each table page. The callback
is given the physical address of the table page.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  3 +++
 kernel/liveupdate/kexec_handover.c | 12 ++++++++++++
 2 files changed, 15 insertions(+)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 030da6399d28..fe7151d89361 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -37,12 +37,15 @@ struct kho_radix_tree {
 /**
  * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
  * @key:      Called on each present key in the radix tree.
+ * @table:    Called on each table of the radix tree itself. Receives the
+ *            physical address of the page containing the table.
  *
  * For each callback, a return value of 0 continues the walk and a non-zero
  * return value is directly returned to the caller.
  */
 struct kho_radix_walk_cb {
 	int (*key)(unsigned long key);
+	int (*table)(phys_addr_t phys);
 };
 
 #ifdef CONFIG_KEXEC_HANDOVER
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index b22b3cec251e..0f8d058f1a27 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -273,6 +273,12 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
 	unsigned int i;
 	int err;
 
+	if (cb->table) {
+		err = cb->table(virt_to_phys(leaf));
+		if (err)
+			return err;
+	}
+
 	if (!cb->key)
 		return 0;
 
@@ -295,6 +301,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 	unsigned int shift;
 	int err;
 
+	if (cb->table) {
+		err = cb->table(virt_to_phys(root));
+		if (err)
+			return err;
+	}
+
 	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
 		if (!root->table[i])
 			continue;
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:47 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:47 -0700
Subject: [RFC PATCH 05/20] kho: add data argument to radix walk callback
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-6-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Add an opaque data pointer argument to kho_radix_walk_cb_t. This can be
used for callers to pass extra information to the callback.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  8 ++++----
 kernel/liveupdate/kexec_handover.c | 24 +++++++++++++-----------
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index fe7151d89361..6c0f7d82716b 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -44,8 +44,8 @@ struct kho_radix_tree {
  * return value is directly returned to the caller.
  */
 struct kho_radix_walk_cb {
-	int (*key)(unsigned long key);
-	int (*table)(phys_addr_t phys);
+	int (*key)(unsigned long key, void *data);
+	int (*table)(phys_addr_t phys, void *data);
 };
 
 #ifdef CONFIG_KEXEC_HANDOVER
@@ -53,7 +53,7 @@ struct kho_radix_walk_cb {
 int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
 void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			const struct kho_radix_walk_cb *cb);
+			const struct kho_radix_walk_cb *cb, void *data);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
@@ -66,7 +66,7 @@ static inline void kho_radix_del_key(struct kho_radix_tree *tree,
 				     unsigned long key) { }
 
 static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
-				      const struct kho_radix_walk_cb *cb)
+				      const struct kho_radix_walk_cb *cb, void *data)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 0f8d058f1a27..f6de6bf63226 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -267,14 +267,14 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 EXPORT_SYMBOL_GPL(kho_radix_del_key);
 
 static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
-			       const struct kho_radix_walk_cb *cb)
+			       const struct kho_radix_walk_cb *cb, void *data)
 {
 	unsigned long *bitmap = (unsigned long *)leaf;
 	unsigned int i;
 	int err;
 
 	if (cb->table) {
-		err = cb->table(virt_to_phys(leaf));
+		err = cb->table(virt_to_phys(leaf), data);
 		if (err)
 			return err;
 	}
@@ -283,7 +283,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
 		return 0;
 
 	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
-		err = cb->key(key | i);
+		err = cb->key(key | i, data);
 		if (err)
 			return err;
 	}
@@ -293,7 +293,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
 
 static int __kho_radix_walk_tree(struct kho_radix_node *root,
 				 unsigned int level, unsigned long start,
-				 const struct kho_radix_walk_cb *cb)
+				 const struct kho_radix_walk_cb *cb, void *data)
 {
 	struct kho_radix_node *node;
 	struct kho_radix_leaf *leaf;
@@ -302,7 +302,7 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 	int err;
 
 	if (cb->table) {
-		err = cb->table(virt_to_phys(root));
+		err = cb->table(virt_to_phys(root), data);
 		if (err)
 			return err;
 	}
@@ -323,10 +323,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 			 * node is pointing to the level 0 bitmap.
 			 */
 			leaf = (struct kho_radix_leaf *)node;
-			err = kho_radix_walk_leaf(leaf, key, cb);
+			err = kho_radix_walk_leaf(leaf, key, cb, data);
 		} else {
 			err  = __kho_radix_walk_tree(node, level - 1,
-						     key, cb);
+						     key, cb, data);
 		}
 
 		if (err)
@@ -340,6 +340,7 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
  * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
  * @tree: A pointer to the KHO radix tree to walk.
  * @cb:   Set of callbacks to be invoked during the tree walk.
+ * @data: Opaque data pointer passed to each callback in @cb.
  *
  * This function walks the radix tree, searching from the top level down to the
  * lowest level (level 0), invoking the appropriate callbacks.
@@ -348,14 +349,15 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
  *         value from the callback that stopped the walk.
  */
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			const struct kho_radix_walk_cb *cb)
+			const struct kho_radix_walk_cb *cb, void *data)
 {
 	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);
+	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb,
+				     data);
 }
 EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
 
@@ -486,7 +488,7 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
 	return pfn_to_page(pfn);
 }
 
-static int __init kho_preserved_memory_reserve(unsigned long key)
+static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
 {
 	union kho_page_info info;
 	struct page *page;
@@ -1414,7 +1416,7 @@ static int __init kho_mem_retrieve(const void *fdt)
 
 	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
 	mutex_init(&kho_in.radix_tree.lock);
-	return kho_radix_walk_tree(&kho_in.radix_tree, &cb);
+	return kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL);
 }
 
 static __init int kho_out_fdt_setup(void)
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:48 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:48 -0700
Subject: [RFC PATCH 06/20] kho: allow early-boot usage of the KHO radix tree
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-7-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

The KHO radix tree allocates memory for table pages from the buddy
allocator using get_zeroed_page(). This is not available in early boot
when memblock is still active.

Using the radix tree in early boot is useful for KHO to track metadata
about its memory. One such example is for tracking free blocks for
memory allocation when scratch runs out of space. This feature will be
added in the following commits.

Add kho_radix_{alloc,free}_node() which allocate and free the table
pages. They use slab_is_available() to decide which allocator to use.
While slab_is_available() indicates availability of the slab allocator,
it gets initialized right before buddy so it serves the same practical
purpose.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 kernel/liveupdate/kexec_handover.c | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index f6de6bf63226..5c201e605b96 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -143,6 +143,26 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
 	return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
 }
 
+static void __ref *kho_radix_alloc_node(void)
+{
+	struct kho_radix_node *node;
+
+	if (slab_is_available())
+		node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+	else
+		node = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
+
+	return node;
+}
+
+static void __ref kho_radix_free_node(struct kho_radix_node *node)
+{
+	if (slab_is_available())
+		free_page((unsigned long)node);
+	else
+		memblock_free(node, PAGE_SIZE);
+}
+
 /**
  * kho_radix_add_key - Add a key to the radix tree.
  * @tree: The KHO radix tree.
@@ -183,7 +203,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 		}
 
 		/* Next node is empty, create a new node for it */
-		new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+		new_node = kho_radix_alloc_node();
 		if (!new_node) {
 			err = -ENOMEM;
 			goto err_free_nodes;
@@ -214,7 +234,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 err_free_nodes:
 	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
 		if (intermediate_nodes[i])
-			free_page((unsigned long)intermediate_nodes[i]);
+			kho_radix_free_node(intermediate_nodes[i]);
 	}
 	if (anchor_node)
 		anchor_node->table[anchor_idx] = 0;
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:49 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:49 -0700
Subject: [RFC PATCH 07/20] kho: allow destroying KHO radix tree
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-8-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Add kho_radix_destroy_tree() which allows destroying the radix tree and
freeing all its pages.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  3 +++
 kernel/liveupdate/kexec_handover.c | 34 ++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 6c0f7d82716b..617395a6647a 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -54,6 +54,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
 void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
 			const struct kho_radix_walk_cb *cb, void *data);
+void kho_radix_destroy_tree(struct kho_radix_tree *tree);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
@@ -71,6 +72,8 @@ static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
 	return -EOPNOTSUPP;
 }
 
+static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
+
 #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
 
 #endif	/* _LINUX_KHO_RADIX_TREE_H */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 5c201e605b96..3f3ea71baa1a 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -286,6 +286,40 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 }
 EXPORT_SYMBOL_GPL(kho_radix_del_key);
 
+static void __kho_radix_destroy_tree(struct kho_radix_node *root,
+				     unsigned int level)
+{
+	unsigned long i;
+
+	if (level == 0) {
+		kho_radix_free_node(root);
+		return;
+	}
+
+	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
+		if (root->table[i])
+			__kho_radix_destroy_tree(phys_to_virt(root->table[i]),
+						 level - 1);
+	}
+
+	kho_radix_free_node(root);
+}
+
+/**
+ * kho_radix_destroy_tree - Destroy the radix tree
+ * @tree: The radix tree to destroy
+ *
+ * Walk @tree and free all its nodes.
+ */
+void kho_radix_destroy_tree(struct kho_radix_tree *tree)
+{
+	if (!tree->root)
+		return;
+
+	__kho_radix_destroy_tree(tree->root, KHO_TREE_MAX_DEPTH - 1);
+}
+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
+
 static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
 			       const struct kho_radix_walk_cb *cb, void *data)
 {
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:50 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:50 -0700
Subject: [RFC PATCH 08/20] kho: add kho_radix_init_tree()
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-9-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Move the initialization logic of the radix tree into
kho_radix_init_tree() instead of having users open-code it. Makes the
boundaries cleaner and reduces code duplication when a new user of the
radix tree will be added in a future commit.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  7 ++++++
 kernel/liveupdate/kexec_handover.c | 37 ++++++++++++++++++++++++++++--
 2 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 617395a6647a..c0840ecb230c 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -54,6 +54,7 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
 void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
 			const struct kho_radix_walk_cb *cb, void *data);
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);
 void kho_radix_destroy_tree(struct kho_radix_tree *tree);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
@@ -72,6 +73,12 @@ static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_radix_init_tree(struct kho_radix_tree *tree,
+				      struct kho_radix_node *root)
+{
+	return 0;
+}
+
 static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
 
 #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 3f3ea71baa1a..b2d1572808eb 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -305,6 +305,34 @@ static void __kho_radix_destroy_tree(struct kho_radix_node *root,
 	kho_radix_free_node(root);
 }
 
+/**
+ * kho_radix_init_tree - initialize the radix tree.
+ * @tree:   the tree to initialize.
+ * @root:   root table of the radix tree.
+ *
+ * Initialize the radix tree with the given root node. If root is %NULL, an
+ * empty root table is allocated. If root is not %NULL, it is the caller's
+ * responsibility to make sure the root is valid and in the correct format.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)
+{
+	/* Already initialized. */
+	if (tree->root)
+		return 0;
+
+	if (!root)
+		root = kho_radix_alloc_node();
+	if (!root)
+		return -ENOMEM;
+
+	tree->root = root;
+	mutex_init(&tree->lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kho_radix_init_tree);
+
 /**
  * kho_radix_destroy_tree - Destroy the radix tree
  * @tree: The radix tree to destroy
@@ -1467,9 +1495,14 @@ static int __init kho_mem_retrieve(const void *fdt)
 	const struct kho_radix_walk_cb cb = {
 		.key = kho_preserved_memory_reserve,
 	};
+	phys_addr_t mem_map_phys;
+	int err;
+
+	mem_map_phys = kho_get_mem_map_phys(fdt);
+	err = kho_radix_init_tree(&kho_in.radix_tree, phys_to_virt(mem_map_phys));
+	if (err)
+		return err;
 
-	kho_in.radix_tree.root = phys_to_virt(kho_get_mem_map_phys(fdt));
-	mutex_init(&kho_in.radix_tree.lock);
 	return kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL);
 }
 
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:51 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:51 -0700
Subject: [RFC PATCH 09/20] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-10-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

In the upcoming commits, the KHO will learn how to discover free blocks
of memory by walking the KHO radix tree. It will then mark those regions
as scratch to allow memory allocation in case scratch runs low.

To differentiate the extended scratch areas from the main scratch areas,
introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
for allocations during scratch-only. Teach should_skip_region() to check
for both flags before deciding if the region should be skipped.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/memblock.h | 10 ++++++++++
 mm/memblock.c            | 41 ++++++++++++++++++++++++++++++++++------
 2 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index 5afcd99aa8c1..4f535ca4947a 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -51,6 +51,9 @@ extern unsigned long long max_possible_pfn;
  * memory reservations yet, so we get scratch memory from the previous
  * kernel that we know is good to use. It is the only memory that
  * allocations may happen from in this phase.
+ * @MEMBLOCK_KHO_SCRATCH_EXT: same as MEMBLOCK_KHO_SCRATCH but was discovered at
+ * boot time by finding gaps in preserved memory instead of being passed from
+ * previous kernel. Does not get passed to the next kernel.
  */
 enum memblock_flags {
 	MEMBLOCK_NONE		= 0x0,	/* No special request */
@@ -61,6 +64,7 @@ enum memblock_flags {
 	MEMBLOCK_RSRV_NOINIT	= 0x10,	/* don't initialize struct pages */
 	MEMBLOCK_RSRV_KERN	= 0x20,	/* memory reserved for kernel use */
 	MEMBLOCK_KHO_SCRATCH	= 0x40,	/* scratch memory for kexec handover */
+	MEMBLOCK_KHO_SCRATCH_EXT= 0x80, /* extended scratch memory for KHO */
 };
 
 /**
@@ -157,6 +161,7 @@ int memblock_clear_nomap(phys_addr_t base, phys_addr_t size);
 int memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size);
 int memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t size);
 int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size);
+int memblock_mark_kho_scratch_ext(phys_addr_t base, phys_addr_t size);
 int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size);
 
 void memblock_free(void *ptr, size_t size);
@@ -304,6 +309,11 @@ static inline bool memblock_is_kho_scratch(struct memblock_region *m)
 	return m->flags & MEMBLOCK_KHO_SCRATCH;
 }
 
+static inline bool memblock_is_kho_scratch_ext(struct memblock_region *m)
+{
+	return m->flags & MEMBLOCK_KHO_SCRATCH_EXT;
+}
+
 int memblock_search_pfn_nid(unsigned long pfn, unsigned long *start_pfn,
 			    unsigned long  *end_pfn);
 void __next_mem_pfn_range(int *idx, int nid, unsigned long *out_start_pfn,
diff --git a/mm/memblock.c b/mm/memblock.c
index 6349c48154f4..6f76a6bb96d6 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -182,7 +182,7 @@ static enum memblock_flags __init_memblock choose_memblock_flags(void)
 {
 	/* skip non-scratch memory for kho early boot allocations */
 	if (kho_scratch_only)
-		return MEMBLOCK_KHO_SCRATCH;
+		return MEMBLOCK_KHO_SCRATCH | MEMBLOCK_KHO_SCRATCH_EXT;
 
 	return system_has_some_mirror ? MEMBLOCK_MIRROR : MEMBLOCK_NONE;
 }
@@ -1180,8 +1180,9 @@ int __init_memblock memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t si
  * @base: the base phys addr of the region
  * @size: the size of the region
  *
- * Only memory regions marked with %MEMBLOCK_KHO_SCRATCH will be considered
- * for allocations during early boot with kexec handover.
+ * Only memory regions marked with %MEMBLOCK_KHO_SCRATCH or
+ * %MEMBLOCK_KHO_SCRATCH_EXT will be considered for allocations during early
+ * boot with kexec handover.
  *
  * Return: 0 on success, -errno on failure.
  */
@@ -1205,6 +1206,23 @@ __init int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size)
 				    MEMBLOCK_KHO_SCRATCH);
 }
 
+/**
+ * memblock_mark_kho_scratch_ext - Mark a memory region as MEMBLOCK_KHO_SCRATCH_EXT.
+ * @base: the base phys addr of the region
+ * @size: the size of the region
+ *
+ * Only memory regions marked with %MEMBLOCK_KHO_SCRATCH or
+ * %MEMBLOCK_KHO_SCRATCH_EXT will be considered for allocations during early
+ * boot with kexec handover.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+__init int memblock_mark_kho_scratch_ext(phys_addr_t base, phys_addr_t size)
+{
+	return memblock_setclr_flag(&memblock.memory, base, size, 1,
+				    MEMBLOCK_KHO_SCRATCH_EXT);
+}
+
 static bool should_skip_region(struct memblock_type *type,
 			       struct memblock_region *m,
 			       int nid, int flags)
@@ -1238,10 +1256,20 @@ static bool should_skip_region(struct memblock_type *type,
 
 	/*
 	 * In early alloc during kexec handover, we can only consider
-	 * MEMBLOCK_KHO_SCRATCH regions for the allocations
+	 * MEMBLOCK_KHO_SCRATCH or MEMBLOCK_KHO_SCRATCH_EXT regions for the
+	 * allocations.
 	 */
-	if ((flags & MEMBLOCK_KHO_SCRATCH) && !memblock_is_kho_scratch(m))
-		return true;
+	if (flags & (MEMBLOCK_KHO_SCRATCH | MEMBLOCK_KHO_SCRATCH_EXT)) {
+		bool skip = true;
+
+		if ((flags & MEMBLOCK_KHO_SCRATCH) && memblock_is_kho_scratch(m))
+			skip = false;
+
+		if ((flags & MEMBLOCK_KHO_SCRATCH_EXT) && memblock_is_kho_scratch_ext(m))
+			skip = false;
+
+		return skip;
+	}
 
 	return false;
 }
@@ -2801,6 +2829,7 @@ static const char * const flagname[] = {
 	[ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT",
 	[ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN",
 	[ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH",
+	[ilog2(MEMBLOCK_KHO_SCRATCH_EXT)] = "KHO_SCRATCH_EXT",
 };
 
 static int memblock_debug_show(struct seq_file *m, void *private)
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:52 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:52 -0700
Subject: [RFC PATCH 10/20] kho: extended scratch
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-11-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Motivation
==========

The scratch space is allocated by the first kernel in the KHO chain, and
is reused by all subsequent kernels. The size of the space is either set
via the commandline by the system administrator or by calculating the
amount of memory used by the kernel and adding a multiplier. In either
case, the scratch space is a heuristic and is liable to fill up and fail
allocation if a kernel uses more memory than expected.

In addition, gigantic huge pages (usually 1 GiB) are allocated via
memblock, and in a KHO boot that memory comes from the scratch space. In
hypervisors it is common to dedicate a major part of the system's memory
to gigantic hugepages for VM memory.

If this memory needs to come from scratch space, then scratch needs to
be greater than the memory needed for huge pages, which is impractical.
In addition, hugepages can be preserved memory. Allocating them from
scratch violates the assumption that scratch contains no preserved
memory.

Methodology
===========

Introduce extended scratch areas. These areas are discovered at boot by
walking the preserved memory radix tree and looking for free blocks of
memory. They then marked as scratch to allow allocations from them. This
makes KHO more resilient to memory pressure and allows supporting huge
page preservation.

Since the preserved memory radix tree mixes both physical address and
order into a single key, and does not track table pages, it is difficult
to identify free areas from it directly. Walk the tree and digest it
down into another radix tree. The latter tracks blocks of
KHO_EXT_SHIFT (1 GiB as of now) granularity. Then walk the digested tree
and mark the areas between the present keys as scratch.

Performance
===========

The discovery algorithm traverses the preserved memory radix tree
exactly once. While it does use memory for the digested radix tree,
since the blocks are split by 1 GiB, a single bitmap with 4k pages can
track up to 32 TiB of memory. So there are likely to be very few radix
tree pages used in this tracking. For systems with all physical memory
below 32 TiB, this should result in a total of 6 pages being
used (KHO_TREE_MAX_DEPTH == 6).

An alternate way of achieving this would be to call kho_mem_retrieve()
earlier in boot and mark all the KHO preservations as reserved. But that
can blow up memblock.reserved with a bunch of 4K pages scattered
everywhere, which will reduce performance of subsequent allocations.
Since the free blocks are tracked in chunks of 1 GiB, this won't blow up
memblock.memory as much.

Practical evaluation
====================

The testing is done on a x86_64 qemu VM running under KVM with 64G
memory and 12 CPUs. The machine pre-allocates 50 1G pages.

Since the performance scales with how busy the radix tree is, tests are
done with 2 preservation patterns: first with two 1M memfds, second with
two 1G memfds, both using 4k pages.

Test case 1 - 1M memfd
~~~~~~~~~~~~~~~~~~~~~~

This test case has two memfds with 1M memory each in 4k pages, plus
other preservations from LUO core and other KHO users.

This is how the radix tree stats look like:

    radix_nodes:       0x2f
    nr_preservations:  0x22d
    mem_preserved:     0xa2b000

    per order preservations:
    order  0:  0x215
    order  1:  0x9
    order  2:  0x1
    order  3:  0x2
    order  4:  0x5
    order  5:  0x1
    order  6:  0x2
    order  7:  0x2
    order  9:  0x1
    order 10:  0x1

and this is how long it takes to extend the scratch after KHO boot:

    kho_extend_scratch(): time taken: 88 us
    kho_extend_scratch(): total memory recovered: 0xf7ff7b000 (~62G)

Test case 2 - 1G memfd
~~~~~~~~~~~~~~~~~~~~~~

This test case has two memfds with 1G memory each in 4k pages, plus
other preservations from LUO core and other KHO users.

This is how the radix tree stats look like:

    radix_nodes:       0x45
    nr_preservations:  0x80832
    mem_preserved:     0x8102d000

    per order preservations:
    order  0:  0x80817
    order  1:  0x7
    order  2:  0x2
    order  3:  0x4
    order  4:  0x2
    order  5:  0x2
    order  6:  0x4
    order  7:  0x3
    order  8:  0x1
    order  9:  0x2

and this is how long it takes to extend the scratch after KHO boot:

    kho_extend_scratch(): time taken: 21769 us
    kho_extend_scratch(): total memory recovered: 0xe40000000 (57G)

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kexec_handover.h     |   1 +
 kernel/liveupdate/kexec_handover.c | 148 +++++++++++++++++++++++++----
 mm/mm_init.c                       |   1 +
 3 files changed, 133 insertions(+), 17 deletions(-)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..6ce46f36ed99 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -37,6 +37,7 @@ void kho_remove_subtree(void *blob);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
 
 void kho_memory_init(void);
+void kho_extend_scratch(void);
 
 void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,
 		  u64 scratch_len);
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index b2d1572808eb..a006a883ee94 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -84,6 +84,23 @@ static struct kho_out kho_out = {
 	},
 };
 
+struct kho_in {
+	phys_addr_t fdt_phys;
+	phys_addr_t scratch_phys;
+	char previous_release[__NEW_UTS_LEN + 1];
+	u32 kexec_count;
+	struct kho_debugfs dbg;
+	struct kho_radix_tree radix_tree;
+};
+
+static struct kho_in kho_in = {
+};
+
+static const void *kho_get_fdt(void)
+{
+	return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
+}
+
 /**
  * kho_encode_radix_key - Encodes a physical address and order into a radix key.
  * @phys: The physical address of the page.
@@ -825,6 +842,120 @@ static void __init kho_reserve_scratch(void)
 	kho_enable = false;
 }
 
+#define KHO_EXT_SHIFT 30 /* 1 GiB */
+
+static int __init kho_ext_walk_key(unsigned long key, void *data)
+{
+	struct kho_radix_tree *tree = data;
+	phys_addr_t start, end;
+	unsigned int order;
+	int err;
+
+	start = kho_decode_radix_key(key, &order);
+	end = start + (1UL << (order + PAGE_SHIFT));
+
+	while (start < end) {
+		err = kho_radix_add_key(tree, start >> KHO_EXT_SHIFT);
+		if (err)
+			return err;
+
+		start += (1UL << KHO_EXT_SHIFT);
+	}
+
+	return 0;
+}
+
+static int __init kho_ext_walk_table(phys_addr_t phys, void *data)
+{
+	struct kho_radix_tree *tree = data;
+
+	return kho_radix_add_key(tree, phys >> KHO_EXT_SHIFT);
+}
+
+static int __init kho_ext_mark_scratch(unsigned long key, void *data)
+{
+	phys_addr_t *prev_end = data;
+	phys_addr_t start = key << KHO_EXT_SHIFT;
+	int err;
+
+	if (start > *prev_end) {
+		err = memblock_mark_kho_scratch_ext(*prev_end, start - *prev_end);
+		if (err)
+			return err;
+	}
+
+	*prev_end = start + (1UL << KHO_EXT_SHIFT);
+	return 0;
+}
+
+/**
+ * kho_extend_scratch - Extend the scratch regions
+ *
+ * The KHO radix tree mixes both physical address and order into a single key.
+ * This makes it hard to look for free ranges directly. This function first
+ * walks the radix tree and digests it down into another radix tree, whose keys
+ * identify blocks of KHO_EXT_SHIFT which contain preserved memory.
+ *
+ * Then it walks the digested radix tree and marks everything that doesn't have
+ * preserved memory as scratch.
+ *
+ * NOTE: This function allocates memory so it should be called when scratch has
+ * available space.
+ *
+ * NOTE: The pages of the KHO radix tree tables are not marked as preserved in
+ * the KHO tree. But they are expected to remain untouched until the tree is
+ * fully parsed. So this function also considers them to be "preserved memory"
+ * and marks their blocks as busy.
+ */
+void __init kho_extend_scratch(void)
+{
+	const struct kho_radix_walk_cb kho_cb = {
+		.key = kho_ext_walk_key,
+		.table = kho_ext_walk_table,
+	};
+	const struct kho_radix_walk_cb ext_cb = {
+		.key = kho_ext_mark_scratch,
+	};
+	struct kho_radix_tree radix;
+	phys_addr_t prev_end = 0, mem_map_phys;
+	int err = 0;
+
+	if (!is_kho_boot())
+		return;
+
+	/* Make sure the KHO radix tree is initialized. */
+	mem_map_phys = kho_get_mem_map_phys(kho_get_fdt());
+	err = kho_radix_init_tree(&kho_in.radix_tree, phys_to_virt(mem_map_phys));
+	if (err)
+		goto print;
+
+	err = kho_radix_init_tree(&radix, NULL);
+	if (err)
+		goto print;
+
+	/* Walk the KHO radix tree to find busy blocks. */
+	err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &radix);
+	if (err)
+		goto out;
+
+	/* Walk the blocks and mark everything between keys as scratch. */
+	err = kho_radix_walk_tree(&radix, &ext_cb, &prev_end);
+	if (err)
+		goto out;
+
+	/* Mark everything from last busy block to end of DRAM. */
+	if (prev_end < memblock_end_of_DRAM())
+		err = memblock_mark_kho_scratch_ext(prev_end,
+						    memblock_end_of_DRAM() - prev_end);
+
+	/* fallthrough */
+out:
+	kho_radix_destroy_tree(&radix);
+print:
+	if (err)
+		pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err));
+}
+
 /**
  * kho_add_subtree - record the physical address of a sub blob in KHO root tree.
  * @name: name of the sub tree.
@@ -1406,23 +1537,6 @@ void kho_restore_free(void *mem)
 }
 EXPORT_SYMBOL_GPL(kho_restore_free);
 
-struct kho_in {
-	phys_addr_t fdt_phys;
-	phys_addr_t scratch_phys;
-	char previous_release[__NEW_UTS_LEN + 1];
-	u32 kexec_count;
-	struct kho_debugfs dbg;
-	struct kho_radix_tree radix_tree;
-};
-
-static struct kho_in kho_in = {
-};
-
-static const void *kho_get_fdt(void)
-{
-	return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
-}
-
 /**
  * is_kho_boot - check if current kernel was booted via KHO-enabled
  * kexec
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 6de3a77eb9ae..bbca4cc9b912 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -2702,6 +2702,7 @@ void __init __weak mem_init(void)
 
 void __init mm_core_init_early(void)
 {
+	kho_extend_scratch();
 	hugetlb_cma_reserve();
 	hugetlb_bootmem_alloc();
 
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:53 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:53 -0700
Subject: [RFC PATCH 11/20] kho: return virtual address of mem_map
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-12-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

There are currently 3 callers of kho_get_mem_map_phys(). Two of them,
kho_mem_retrieve() and kho_extend_scratch() need the virtual address.
The third, kho_populate() doesn't care. Make things simpler by
directly returning the virtual address. Rename kho_get_mem_map_phys() to
kho_get_mem_map() to accurately reflect what it returns.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 kernel/liveupdate/kexec_handover.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index a006a883ee94..797ec285b698 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -610,10 +610,11 @@ static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
 	return 0;
 }
 
-/* Returns physical address of the preserved memory map from FDT */
-static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
+/* Returns virtual address of the preserved memory map from FDT */
+static __init void *kho_get_mem_map(const void *fdt)
 {
 	const void *mem_ptr;
+	phys_addr_t mem_map_phys;
 	int len;
 
 	mem_ptr = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
@@ -622,7 +623,11 @@ static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
 		return 0;
 	}
 
-	return get_unaligned((const u64 *)mem_ptr);
+	mem_map_phys = get_unaligned((const u64 *)mem_ptr);
+	if (!mem_map_phys)
+		return NULL;
+
+	return phys_to_virt(mem_map_phys);
 }
 
 /*
@@ -917,15 +922,15 @@ void __init kho_extend_scratch(void)
 		.key = kho_ext_mark_scratch,
 	};
 	struct kho_radix_tree radix;
-	phys_addr_t prev_end = 0, mem_map_phys;
+	phys_addr_t prev_end = 0;
 	int err = 0;
 
 	if (!is_kho_boot())
 		return;
 
 	/* Make sure the KHO radix tree is initialized. */
-	mem_map_phys = kho_get_mem_map_phys(kho_get_fdt());
-	err = kho_radix_init_tree(&kho_in.radix_tree, phys_to_virt(mem_map_phys));
+	err = kho_radix_init_tree(&kho_in.radix_tree,
+				  kho_get_mem_map(kho_get_fdt()));
 	if (err)
 		goto print;
 
@@ -1609,11 +1614,9 @@ static int __init kho_mem_retrieve(const void *fdt)
 	const struct kho_radix_walk_cb cb = {
 		.key = kho_preserved_memory_reserve,
 	};
-	phys_addr_t mem_map_phys;
 	int err;
 
-	mem_map_phys = kho_get_mem_map_phys(fdt);
-	err = kho_radix_init_tree(&kho_in.radix_tree, phys_to_virt(mem_map_phys));
+	err = kho_radix_init_tree(&kho_in.radix_tree, kho_get_mem_map(fdt));
 	if (err)
 		return err;
 
@@ -1838,8 +1841,7 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 {
 	unsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch);
 	struct kho_scratch *scratch = NULL;
-	phys_addr_t mem_map_phys;
-	void *fdt = NULL;
+	void *fdt = NULL, *mem_map;
 	bool populated = false;
 	int err;
 
@@ -1862,8 +1864,8 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 		goto unmap_fdt;
 	}
 
-	mem_map_phys = kho_get_mem_map_phys(fdt);
-	if (!mem_map_phys)
+	mem_map = kho_get_mem_map(fdt);
+	if (!mem_map)
 		goto unmap_fdt;
 
 	scratch = early_memremap(scratch_phys, scratch_len);
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:54 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:54 -0700
Subject: [RFC PATCH 12/20] mm/hugetlb: make bootmem allocation work with KHO
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-13-jloeser@linux.microsoft.com>

From: "Pratyush Yadav (Google)" <pratyush at kernel.org>

Gigantic page allocation is somewhat broken currently when KHO is used.

Firstly, they break KHO scratch size accounting. RSRV_KERN is used to
track how much memory is reserved for use by the kernel. Since
alloc_bootmem() calls the memblock_alloc*() APIs, the hugepages
allocated also get marked as RSRV_KERN.

Allocations marked RSRV_KERN are used by KHO to calculate how much
scratch space it should reserve to make sure the next kernel has enough
memory to boot when it is in scratch-only phase. Counting hugepages in
that blows up scratch size, and can lead to the scratch allocation
failing, making KHO unusable. This will show up when huge pages make up
more than 50% of the system, which is a fairly common use case.

Secondly, while not supported right now, huge pages are user memory and
can be preserved via KHO. The scratch spaces should not have any
preserved memory. Allocating hugepages from scratch (on a KHO boot) can
lead to them being un-preservable.

Introduce memblock_alloc_nid_user(). This does two things: first, it
instructs __memblock_alloc_range_nid() to not use scratch areas to
fulfill allocation. If KHO is in scratch-only mode, allocations will
only be made from extended scratch areas. Second, it removes RSRV_KERN
from the allocation to make sure it doesn't mess up scratch size
accounting.

To reduce duplication, introduce __memblock_alloc_range_nid() which does
exactly what memblock_alloc_range_nid() used to do, but takes the flags
from its caller. Then make memblock_alloc_range_nid() a wrapper to it.
This lets memblock_alloc_nid_user() re-use most of the logic without
causing churn to update all callers of memblock_alloc_range_nid() and
adding yet another argument to it.

Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/memblock.h |   4 ++
 mm/hugetlb.c             |  19 ++----
 mm/memblock.c            | 138 ++++++++++++++++++++++++++++++---------
 3 files changed, 116 insertions(+), 45 deletions(-)

diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index 4f535ca4947a..c7056cf3f0f2 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -160,6 +160,7 @@ int memblock_mark_nomap(phys_addr_t base, phys_addr_t size);
 int memblock_clear_nomap(phys_addr_t base, phys_addr_t size);
 int memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size);
 int memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t size);
+int memblock_reserved_clear_kern(phys_addr_t base, phys_addr_t size);
 int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size);
 int memblock_mark_kho_scratch_ext(phys_addr_t base, phys_addr_t size);
 int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size);
@@ -431,6 +432,9 @@ void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,
 			     phys_addr_t min_addr, phys_addr_t max_addr,
 			     int nid);
 
+void *memblock_alloc_nid_user(phys_addr_t size, phys_addr_t align, int nid,
+			      bool exact_nid);
+
 static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
 {
 	return memblock_alloc_try_nid(size, align, MEMBLOCK_LOW_LIMIT,
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835..46f2b1bd5abe 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3033,26 +3033,19 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)
 	if (hugetlb_early_cma(h))
 		m = hugetlb_cma_alloc_bootmem(h, &listnode, node_exact);
 	else {
-		if (node_exact)
-			m = memblock_alloc_exact_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
-		else {
-			m = memblock_alloc_try_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
+		m = memblock_alloc_nid_user(huge_page_size(h), huge_page_size(h),
+					    nid, node_exact);
+		if (m) {
 			/*
 			 * For pre-HVO to work correctly, pages need to be on
 			 * the list for the node they were actually allocated
 			 * from. That node may be different in the case of
-			 * fallback by memblock_alloc_try_nid_raw. So,
-			 * extract the actual node first.
+			 * fallback by memblock_alloc_try_nid_raw. So, extract
+			 * the actual node first.
 			 */
-			if (m)
+			if (node_exact)
 				listnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));
-		}
 
-		if (m) {
 			m->flags = 0;
 			m->cma = NULL;
 		}
diff --git a/mm/memblock.c b/mm/memblock.c
index 6f76a6bb96d6..8cd52d34ad6e 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -178,11 +178,21 @@ bool __init_memblock memblock_has_mirror(void)
 	return system_has_some_mirror;
 }
 
-static enum memblock_flags __init_memblock choose_memblock_flags(void)
+static enum memblock_flags __init_memblock choose_memblock_flags(bool user)
 {
 	/* skip non-scratch memory for kho early boot allocations */
-	if (kho_scratch_only)
-		return MEMBLOCK_KHO_SCRATCH | MEMBLOCK_KHO_SCRATCH_EXT;
+	if (kho_scratch_only) {
+		enum memblock_flags flags = MEMBLOCK_KHO_SCRATCH_EXT;
+
+		/*
+		 * Scratch can only be used for kernel memory, since user memory
+		 * might be preserved and thus can not be in scratch.
+		 */
+		if (!user)
+			flags |= MEMBLOCK_KHO_SCRATCH;
+
+		return flags;
+	}
 
 	return system_has_some_mirror ? MEMBLOCK_MIRROR : MEMBLOCK_NONE;
 }
@@ -346,7 +356,7 @@ static phys_addr_t __init_memblock memblock_find_in_range(phys_addr_t start,
 					phys_addr_t align)
 {
 	phys_addr_t ret;
-	enum memblock_flags flags = choose_memblock_flags();
+	enum memblock_flags flags = choose_memblock_flags(false);
 
 again:
 	ret = memblock_find_in_range_node(size, align, start, end,
@@ -1175,6 +1185,20 @@ int __init_memblock memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t si
 				    MEMBLOCK_RSRV_KERN);
 }
 
+/**
+ * memblock_reserved_clear_kern - Clear MEMBLOCK_RSRV_KERN flag for region
+ *
+ * @base: the base phys addr of the region
+ * @size: the size of the region
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int __init_memblock memblock_reserved_clear_kern(phys_addr_t base, phys_addr_t size)
+{
+	return memblock_setclr_flag(&memblock.reserved, base, size, 0,
+				    MEMBLOCK_RSRV_KERN);
+}
+
 /**
  * memblock_mark_kho_scratch - Mark a memory region as MEMBLOCK_KHO_SCRATCH.
  * @base: the base phys addr of the region
@@ -1534,37 +1558,11 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,
 	return 0;
 }
 
-/**
- * memblock_alloc_range_nid - allocate boot memory block
- * @size: size of memory block to be allocated in bytes
- * @align: alignment of the region and block's size
- * @start: the lower bound of the memory region to allocate (phys address)
- * @end: the upper bound of the memory region to allocate (phys address)
- * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
- * @exact_nid: control the allocation fall back to other nodes
- *
- * The allocation is performed from memory region limited by
- * memblock.current_limit if @end == %MEMBLOCK_ALLOC_ACCESSIBLE.
- *
- * If the specified node can not hold the requested memory and @exact_nid
- * is false, the allocation falls back to any node in the system.
- *
- * For systems with memory mirroring, the allocation is attempted first
- * from the regions with mirroring enabled and then retried from any
- * memory region.
- *
- * In addition, function using kmemleak_alloc_phys for allocated boot
- * memory block, it is never reported as leaks.
- *
- * Return:
- * Physical address of allocated memory block on success, %0 on failure.
- */
-phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
+static phys_addr_t __init __memblock_alloc_range_nid(phys_addr_t size,
 					phys_addr_t align, phys_addr_t start,
 					phys_addr_t end, int nid,
-					bool exact_nid)
+					bool exact_nid, enum memblock_flags flags)
 {
-	enum memblock_flags flags = choose_memblock_flags();
 	phys_addr_t found;
 
 	/*
@@ -1633,6 +1631,41 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
 	return found;
 }
 
+/**
+ * memblock_alloc_range_nid - allocate boot memory block
+ * @size: size of memory block to be allocated in bytes
+ * @align: alignment of the region and block's size
+ * @start: the lower bound of the memory region to allocate (phys address)
+ * @end: the upper bound of the memory region to allocate (phys address)
+ * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
+ * @exact_nid: control the allocation fall back to other nodes
+ *
+ * The allocation is performed from memory region limited by
+ * memblock.current_limit if @end == %MEMBLOCK_ALLOC_ACCESSIBLE.
+ *
+ * If the specified node can not hold the requested memory and @exact_nid
+ * is false, the allocation falls back to any node in the system.
+ *
+ * For systems with memory mirroring, the allocation is attempted first
+ * from the regions with mirroring enabled and then retried from any
+ * memory region.
+ *
+ * In addition, function using kmemleak_alloc_phys for allocated boot
+ * memory block, it is never reported as leaks.
+ *
+ * Return:
+ * Physical address of allocated memory block on success, %0 on failure.
+ */
+phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
+					phys_addr_t align, phys_addr_t start,
+					phys_addr_t end, int nid,
+					bool exact_nid)
+{
+	enum memblock_flags flags = choose_memblock_flags(false);
+
+	return __memblock_alloc_range_nid(size, align, start, end, nid, exact_nid, flags);
+}
+
 /**
  * memblock_phys_alloc_range - allocate a memory block inside specified range
  * @size: size of memory block to be allocated in bytes
@@ -1784,6 +1817,47 @@ void * __init memblock_alloc_try_nid_raw(
 				       false);
 }
 
+/**
+ * memblock_alloc_nid_user - allocate boot memory for use by userspace
+ * @size: size of the memory block to be allocated in bytes
+ * @align: alignment of the region and block's size
+ * @exact_nid: control the allocation fall back to other nodes
+ *
+ * Public function, provides additional debug information (including caller
+ * info), if enabled. Does not zero allocated memory, does not panic if request
+ * cannot be satisfied.
+ *
+ * If the specified node can not hold the requested memory and @exact_nid is
+ * false, the allocation falls back to any node in the system. The allocated
+ * memory has no restrictions on minimum or maximum address, and does not count
+ * towards %MEMBLOCK_RSRV_KERN.
+ *
+ * Return:
+ * Virtual address of allocated memory block on success, %NULL on failure.
+ */
+void * __init memblock_alloc_nid_user(phys_addr_t size, phys_addr_t align,
+				      int nid, bool exact_nid)
+{
+	enum memblock_flags flags = choose_memblock_flags(true);
+	phys_addr_t alloc;
+
+	memblock_dbg("%s: %llu bytes align=0x%llx nid=%d %pS\n",
+		     __func__, (u64)size, (u64)align, nid, (void *)_RET_IP_);
+
+	alloc = __memblock_alloc_range_nid(size, align, 0, MEMBLOCK_ALLOC_ACCESSIBLE,
+					   nid, exact_nid, flags);
+	if (!alloc)
+		return NULL;
+
+	/* User memory should not be marked with RSRV_KERN. */
+	if (memblock_reserved_clear_kern(alloc, size)) {
+		memblock_phys_free(alloc, size);
+		return NULL;
+	}
+
+	return phys_to_virt(alloc);
+}
+
 /**
  * memblock_alloc_try_nid - allocate boot memory block
  * @size: size of memory block to be allocated in bytes
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:55 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:55 -0700
Subject: [RFC PATCH 13/20] kho: add radix tree freeze and del_key() error reporting
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-14-jloeser@linux.microsoft.com>

Add kho_radix_tree_freeze() to prevent further modifications to a
KHO radix tree. After freezing, kho_radix_add_key() and
kho_radix_del_key() return -EBUSY. This is used by the MSHV page
preservation code to lock the tree before serializing it for kexec.

Also change kho_radix_del_key() from void to int so it can report
-EBUSY (frozen) and -ENOENT (key not present).

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     | 24 ++++++++++----
 kernel/liveupdate/kexec_handover.c | 51 +++++++++++++++++++++++-------
 2 files changed, 57 insertions(+), 18 deletions(-)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index c0840ecb230c..4fe2238e1e30 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -21,10 +21,10 @@
  * scheme. Each key is an unsigned long that combines a page's physical
  * address and its order.
  *
- * 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`.
+ * Client code must initialize the tree using kho_radix_tree_init(). Pass
+ * a physical address to restore a tree preserved across kexec, or 0 to
+ * allocate a fresh empty tree. The tree uses data structures defined in
+ * the KHO ABI, `include/linux/kho/abi/kexec_handover.h`.
  */
 
 struct kho_radix_node;
@@ -32,6 +32,7 @@ struct kho_radix_node;
 struct kho_radix_tree {
 	struct kho_radix_node *root;
 	struct mutex lock; /* protects the tree's structure and root pointer */
+	bool frozen;
 };
 
 /**
@@ -51,11 +52,12 @@ struct kho_radix_walk_cb {
 #ifdef CONFIG_KEXEC_HANDOVER
 
 int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
-void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
+int kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
 			const struct kho_radix_walk_cb *cb, void *data);
 int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);
 void kho_radix_destroy_tree(struct kho_radix_tree *tree);
+int kho_radix_tree_freeze(struct kho_radix_tree *tree);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
@@ -64,8 +66,11 @@ static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long k
 	return -EOPNOTSUPP;
 }
 
-static inline void kho_radix_del_key(struct kho_radix_tree *tree,
-				     unsigned long key) { }
+static inline int kho_radix_del_key(struct kho_radix_tree *tree,
+				     unsigned long key)
+{
+	return -EOPNOTSUPP;
+}
 
 static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
 				      const struct kho_radix_walk_cb *cb, void *data)
@@ -81,6 +86,11 @@ static inline int kho_radix_init_tree(struct kho_radix_tree *tree,
 
 static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
 
+static inline int kho_radix_tree_freeze(struct kho_radix_tree *tree)
+{
+	return -EOPNOTSUPP;
+}
+
 #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
 
 #endif	/* _LINUX_KHO_RADIX_TREE_H */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 797ec285b698..2e2b4e73f00d 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -79,9 +79,6 @@ struct kho_out {
 
 static struct kho_out kho_out = {
 	.lock = __MUTEX_INITIALIZER(kho_out.lock),
-	.radix_tree = {
-		.lock = __MUTEX_INITIALIZER(kho_out.radix_tree.lock),
-	},
 };
 
 struct kho_in {
@@ -180,6 +177,28 @@ static void __ref kho_radix_free_node(struct kho_radix_node *node)
 		memblock_free(node, PAGE_SIZE);
 }
 
+/**
+ * kho_radix_tree_freeze - Freeze the tree, preventing further modifications.
+ * @tree: The KHO radix tree to freeze.
+ *
+ * After freezing, kho_radix_add_key() and kho_radix_del_key() will return
+ * -EBUSY. The check is performed under the tree's mutex, so there is no
+ * race between a concurrent add/del and the freeze.
+ *
+ * Return: 0 on success, -EBUSY if the tree is already frozen.
+ */
+int kho_radix_tree_freeze(struct kho_radix_tree *tree)
+{
+	guard(mutex)(&tree->lock);
+
+	if (tree->frozen)
+		return -EBUSY;
+
+	tree->frozen = true;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kho_radix_tree_freeze);
+
 /**
  * kho_radix_add_key - Add a key to the radix tree.
  * @tree: The KHO radix tree.
@@ -210,6 +229,9 @@ int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 
 	guard(mutex)(&tree->lock);
 
+	if (tree->frozen)
+		return -EBUSY;
+
 	/* 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);
@@ -268,20 +290,26 @@ EXPORT_SYMBOL_GPL(kho_radix_add_key);
  * This function traverses the radix tree and clears the bit corresponding to
  * the key, effectively removing it from the tree. It does not free the tree's
  * intermediate nodes, even if they become empty.
+ *
+ * Return: 0 on success, -EINVAL if the tree is uninitialized, -EBUSY if
+ *         frozen, -ENOENT if the key was not present.
  */
-void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
+int kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 {
 	struct kho_radix_node *node = tree->root;
 	struct kho_radix_leaf *leaf;
 	unsigned int i, idx;
 
 	if (WARN_ON_ONCE(!tree->root))
-		return;
+		return -EINVAL;
 
 	might_sleep();
 
 	guard(mutex)(&tree->lock);
 
+	if (WARN_ON_ONCE(tree->frozen))
+		return -EBUSY;
+
 	/* 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);
@@ -291,7 +319,7 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 		 * return with a warning.
 		 */
 		if (WARN_ON(!node->table[idx]))
-			return;
+			return -ENOENT;
 
 		node = phys_to_virt(node->table[idx]);
 	}
@@ -300,6 +328,8 @@ void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 	leaf = (struct kho_radix_leaf *)node;
 	idx = kho_radix_get_bitmap_index(key);
 	__clear_bit(idx, leaf->bitmap);
+
+	return 0;
 }
 EXPORT_SYMBOL_GPL(kho_radix_del_key);
 
@@ -346,6 +376,7 @@ int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root
 
 	tree->root = root;
 	mutex_init(&tree->lock);
+	tree->frozen = false;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(kho_radix_init_tree);
@@ -1746,11 +1777,9 @@ static __init int kho_init(void)
 	if (!kho_enable)
 		return 0;
 
-	tree->root = kzalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!tree->root) {
-		err = -ENOMEM;
+	err = kho_radix_init_tree(tree, NULL);
+	if (err)
 		goto err_free_scratch;
-	}
 
 	kho_out.fdt = kho_alloc_preserve(PAGE_SIZE);
 	if (IS_ERR(kho_out.fdt)) {
@@ -1807,7 +1836,7 @@ static __init int kho_init(void)
 err_free_fdt:
 	kho_unpreserve_free(kho_out.fdt);
 err_free_kho_radix_tree_root:
-	kfree(tree->root);
+	free_page((unsigned long)tree->root);
 	tree->root = NULL;
 err_free_scratch:
 	kho_out.fdt = NULL;
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:56 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:56 -0700
Subject: [RFC PATCH 14/20] kho: Add crash-kernel-safe radix tree presence check
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-15-jloeser@linux.microsoft.com>

In the crash kernel, the old kernel's memory is outside the direct
map. Add a read-only radix tree variant that memremaps nodes during
init so that subsequent page presence checks can traverse the tree
with plain pointer dereferencing.

This will be used by the MSHV driver to exclude hypervisor-owned pages
from /proc/vmcore via a pfn_is_ram() callback.

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 include/linux/kho_radix_tree.h     |  30 +++++++
 kernel/liveupdate/kexec_handover.c | 124 +++++++++++++++++++++++++++++
 2 files changed, 154 insertions(+)

diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 4fe2238e1e30..e906a874e612 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -49,6 +49,19 @@ struct kho_radix_walk_cb {
 	int (*table)(phys_addr_t phys, void *data);
 };
 
+/**
+ * struct kho_radix_crash_tree - Read-only radix tree for crash kernel use.
+ * @root: pointer to the remapped root node
+ *
+ * In the crash kernel, the old kernel's memory is not in the direct map.
+ * This variant uses memremap() during init to map the tree nodes and
+ * converts the physical address table entries to virtual addresses in-place,
+ * enabling efficient pointer-based traversal without per-lookup remapping.
+ */
+struct kho_radix_crash_tree {
+	struct kho_radix_node *root;
+};
+
 #ifdef CONFIG_KEXEC_HANDOVER
 
 int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
@@ -59,6 +72,11 @@ int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root
 void kho_radix_destroy_tree(struct kho_radix_tree *tree);
 int kho_radix_tree_freeze(struct kho_radix_tree *tree);
 
+int kho_radix_crash_init(struct kho_radix_crash_tree *tree, phys_addr_t root_pa);
+
+bool kho_radix_crash_contains_page(struct kho_radix_crash_tree *tree,
+				   unsigned long pfn, unsigned int order);
+
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
 static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
@@ -91,6 +109,18 @@ static inline int kho_radix_tree_freeze(struct kho_radix_tree *tree)
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_radix_crash_init(struct kho_radix_crash_tree *tree,
+				       phys_addr_t root_pa)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline bool kho_radix_crash_contains_page(
+					struct kho_radix_crash_tree *tree,
+					unsigned long pfn, unsigned int order)
+{
+	return false;
+}
 #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
 
 #endif	/* _LINUX_KHO_RADIX_TREE_H */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 2e2b4e73f00d..0dfdf0f9781e 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -15,6 +15,7 @@
 #include <linux/kmemleak.h>
 #include <linux/count_zeros.h>
 #include <linux/kasan.h>
+#include <linux/io.h>
 #include <linux/kexec.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho_radix_tree.h>
@@ -396,6 +397,129 @@ void kho_radix_destroy_tree(struct kho_radix_tree *tree)
 }
 EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
 
+/*
+ * Convert a crash tree node's children from PA to VA in-place via memremap().
+ * On failure, already-remapped pages are not cleaned up -- the crash kernel
+ * is short-lived and will reboot after dump collection, so the leak is
+ * inconsequential.
+ */
+static int kho_radix_crash_convert_node(struct kho_radix_node *node,
+					unsigned int level)
+{
+	struct kho_radix_node *child;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < (1 << KHO_TABLE_SIZE_LOG2); i++) {
+		if (!node->table[i])
+			continue;
+
+		/* Validate: PA must have bit 63 clear and be page-aligned */
+		if ((node->table[i] & BIT_ULL(63)) ||
+		    (node->table[i] & (PAGE_SIZE - 1)))
+			return -EINVAL;
+
+		child = memremap(node->table[i], PAGE_SIZE, MEMREMAP_WB);
+		if (!child)
+			return -ENOMEM;
+
+		/* Overwrite PA with VA in-place */
+		node->table[i] = (u64)(uintptr_t)child;
+
+		/* Recurse for intermediate levels; level 1 children are leaves */
+		if (level > 1) {
+			err = kho_radix_crash_convert_node(child, level - 1);
+			if (err)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * kho_radix_crash_init - Initialize a crash-kernel view of a KHO radix tree.
+ * @tree: The crash tree to initialize.
+ * @root_pa: Physical address of the radix tree root from the old kernel.
+ *
+ * Maps the old kernel's radix tree into the crash kernel's address space
+ * by memremapping each node and converting table entries from physical to
+ * virtual addresses in-place. After successful initialization, the tree
+ * can be traversed with kho_radix_crash_contains_page() using direct
+ * pointer dereferencing.
+ *
+ * This function is intended for use in the crash kernel where the old
+ * kernel's memory is not in the direct map. No locking is used as the
+ * crash kernel is effectively single-threaded during dump collection.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int kho_radix_crash_init(struct kho_radix_crash_tree *tree, phys_addr_t root_pa)
+{
+	struct kho_radix_node *root;
+	int err;
+
+	tree->root = NULL;
+
+	if (!root_pa || (root_pa & (PAGE_SIZE - 1)))
+		return -EINVAL;
+
+	root = memremap(root_pa, PAGE_SIZE, MEMREMAP_WB);
+	if (!root)
+		return -ENOMEM;
+
+	err = kho_radix_crash_convert_node(root, KHO_TREE_MAX_DEPTH - 1);
+	if (err)
+		return err;
+
+	tree->root = root;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kho_radix_crash_init);
+
+/**
+ * kho_radix_crash_contains_page - Check if a page is in a crash-kernel radix tree.
+ * @tree: The crash tree, previously initialized with kho_radix_crash_init().
+ * @pfn: The page frame number to check.
+ * @order: The order of the page.
+ *
+ * Traverses the radix tree using direct pointer dereferencing (the table
+ * entries were converted from PA to VA during init). No locking is used as the
+ * crash kernel is effectively single-threaded during dump collection.
+ *
+ * Note: This function checks specifically for the presence of the page at the
+ * given order. If a larger order page that encompasses this page is preserved,
+ * this function will return false.
+ *
+ * Return: true if the page is present in the tree, false otherwise.
+ */
+bool kho_radix_crash_contains_page(struct kho_radix_crash_tree *tree,
+				   unsigned long pfn, unsigned int order)
+{
+	unsigned long key = kho_encode_radix_key(PFN_PHYS(pfn), order);
+	struct kho_radix_node *node = tree->root;
+	struct kho_radix_leaf *leaf;
+	unsigned int i, idx;
+
+	if (!tree->root)
+		return false;
+
+	/* Traverse using VA pointers stored in table[] */
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		idx = kho_radix_get_table_index(key, i);
+
+		if (!node->table[idx])
+			return false;
+
+		node = (struct kho_radix_node *)(uintptr_t)node->table[idx];
+	}
+
+	leaf = (struct kho_radix_leaf *)node;
+	idx = kho_radix_get_bitmap_index(key);
+	return test_bit(idx, leaf->bitmap);
+}
+EXPORT_SYMBOL_GPL(kho_radix_crash_contains_page);
+
 static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
 			       const struct kho_radix_walk_cb *cb, void *data)
 {
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:58 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:58 -0700
Subject: [RFC PATCH 16/20] mshv: Add debugfs interface to page tracker
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-17-jloeser@linux.microsoft.com>

- expose stats (page-counts for data & metadata)
- expose tracked pages
- expose scheduler type
- add mshv_iterate_preserved() API for walking the radix tree

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 drivers/hv/mshv_debugfs.c       | 99 +++++++++++++++++++++++++++++++++
 drivers/hv/mshv_page_preserve.c | 13 +++++
 drivers/hv/mshv_page_preserve.h |  3 +
 drivers/hv/mshv_root.h          |  2 +
 drivers/hv/mshv_root_main.c     |  2 +-
 5 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/hv/mshv_debugfs.c b/drivers/hv/mshv_debugfs.c
index 3c3e02237ae9..d79898e21b36 100644
--- a/drivers/hv/mshv_debugfs.c
+++ b/drivers/hv/mshv_debugfs.c
@@ -33,11 +33,18 @@ static struct dentry *mshv_debugfs;
 static struct dentry *mshv_debugfs_partition;
 static struct dentry *mshv_debugfs_lp;
 static struct dentry **parent_vp_stats;
+
 static struct dentry *parent_partition_stats;
 
 static u64 mshv_lps_count;
 static struct hv_stats_page **mshv_lps_stats;
 
+struct mshv_pt_stats {
+	unsigned long count_data;
+	unsigned long count_meta;
+	struct seq_file *stat_file;
+};
+
 static int lp_stats_show(struct seq_file *m, void *v)
 {
 	const struct hv_stats_page *stats = m->private;
@@ -668,8 +675,89 @@ void mshv_debugfs_partition_remove(struct mshv_partition *partition)
 				 partition->pt_stats_dentry);
 }
 
+static int pt_count_data_cb(unsigned long key __maybe_unused, void *stats)
+{
+	((struct mshv_pt_stats *)stats)->count_data++;
+	return 0;
+}
+
+static int pt_count_meta_cb(phys_addr_t phys __maybe_unused, void *stats)
+{
+	((struct mshv_pt_stats *)stats)->count_meta++;
+	return 0;
+}
+
+static int pt_stats_show(struct seq_file *m, void *v)
+{
+	const struct kho_radix_walk_cb cb = {
+		.key = pt_count_data_cb,
+		.table = pt_count_meta_cb,
+	};
+
+	struct mshv_pt_stats pt_stats = {0};
+
+	mshv_iterate_preserved(&cb, &pt_stats);
+	seq_printf(m, "Data pages: %lu\n", pt_stats.count_data);
+	seq_printf(m, "Meta pages: %lu\n", pt_stats.count_meta);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pt_stats);
+
+static int pt_tree_data_cb(unsigned long key, void *stats)
+{
+	seq_printf(((struct mshv_pt_stats *)stats)->stat_file,
+		   "data pfn: %#lx\n", key);
+	return 0;
+}
+
+static int pt_tree_meta_cb(phys_addr_t phys, void *stats)
+{
+	seq_printf(((struct mshv_pt_stats *)stats)->stat_file,
+		   "meta pfn: %#llx\n",
+		   (unsigned long long)(phys >> PAGE_SHIFT));
+	return 0;
+}
+
+static int pt_tree_show(struct seq_file *m, void *v)
+{
+	const struct kho_radix_walk_cb cb = {
+		.key = pt_tree_data_cb,
+		.table = pt_tree_meta_cb,
+	};
+
+	struct mshv_pt_stats pt_stats = {.stat_file = m};
+
+	mshv_iterate_preserved(&cb, &pt_stats);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pt_tree);
+
+static int __init mshv_debugfs_pt_create(struct dentry *parent)
+{
+	struct dentry *d;
+
+	d = debugfs_create_file("pt_stats", 0400, parent, NULL, &pt_stats_fops);
+	if (IS_ERR(d))
+		return PTR_ERR(d);
+
+	d = debugfs_create_file("pt_tree", 0400, parent, NULL, &pt_tree_fops);
+	if (IS_ERR(d))
+		return PTR_ERR(d);
+
+	return 0;
+}
+
+static int scheduler_info_show(struct seq_file *m, void *v)
+{
+	seq_printf(m, "Scheduler type: %s (%d)\n",
+		   scheduler_type_to_string(hv_scheduler_type), hv_scheduler_type);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(scheduler_info);
+
 int __init mshv_debugfs_init(void)
 {
+	struct dentry *d;
 	int err;
 
 	mshv_debugfs = debugfs_create_dir("mshv", NULL);
@@ -694,6 +782,17 @@ int __init mshv_debugfs_init(void)
 	if (err)
 		goto unmap_lp_stats;
 
+	err = mshv_debugfs_pt_create(mshv_debugfs);
+	if (err)
+		goto unmap_lp_stats;
+
+	d = debugfs_create_file("scheduler_info", 0400, mshv_debugfs, NULL,
+				&scheduler_info_fops);
+	if (IS_ERR(d)) {
+		err = PTR_ERR(d);
+		goto unmap_lp_stats;
+	}
+
 	return 0;
 
 unmap_lp_stats:
diff --git a/drivers/hv/mshv_page_preserve.c b/drivers/hv/mshv_page_preserve.c
index a79725a74663..bc3a3a688f5b 100644
--- a/drivers/hv/mshv_page_preserve.c
+++ b/drivers/hv/mshv_page_preserve.c
@@ -52,6 +52,19 @@ int mshv_unregister_preserve_page(struct page *pg)
 }
 EXPORT_SYMBOL_GPL(mshv_unregister_preserve_page);
 
+/**
+ * mshv_iterate_preserved() - Walk all preserved pages
+ * @cb: callbacks invoked for each key/table entry
+ * @data: opaque data passed to callbacks
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int mshv_iterate_preserved(const struct kho_radix_walk_cb *cb, void *data)
+{
+	return kho_radix_walk_tree(&preserved_pages_tree, cb, data);
+}
+EXPORT_SYMBOL_GPL(mshv_iterate_preserved);
+
 /* Preserve a single page identified by its PFN key with KHO */
 static int preserve_key_cb(unsigned long key, void *data)
 {
diff --git a/drivers/hv/mshv_page_preserve.h b/drivers/hv/mshv_page_preserve.h
index 0609002e5f1d..ac99b4e33285 100644
--- a/drivers/hv/mshv_page_preserve.h
+++ b/drivers/hv/mshv_page_preserve.h
@@ -6,10 +6,13 @@
 #ifndef _MSHV_PAGE_PRESERVE_H
 #define _MSHV_PAGE_PRESERVE_H
 
+#include <linux/kho_radix_tree.h>
+
 struct page;
 
 int mshv_preserve_init(void);
 int mshv_register_preserve_page(struct page *pg);
 int mshv_unregister_preserve_page(struct page *pg);
+int mshv_iterate_preserved(const struct kho_radix_walk_cb *cb, void *data);
 
 #endif /* _MSHV_PAGE_PRESERVE_H */
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 362768786c17..216053f8e0ab 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -379,4 +379,6 @@ bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn);
 void mshv_region_movable_fini(struct mshv_mem_region *region);
 bool mshv_region_movable_init(struct mshv_mem_region *region);
 
+const char *scheduler_type_to_string(enum hv_scheduler_type type);
+
 #endif /* _MSHV_ROOT_H_ */
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index bd1359eb58dd..5fbd01c12ab8 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2134,7 +2134,7 @@ mshv_dev_release(struct inode *inode, struct file *filp)
 
 static int mshv_root_sched_online;
 
-static const char *scheduler_type_to_string(enum hv_scheduler_type type)
+const char *scheduler_type_to_string(enum hv_scheduler_type type)
 {
 	switch (type) {
 	case HV_SCHEDULER_TYPE_LP:
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:57 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:57 -0700
Subject: [RFC PATCH 15/20] mshv: Use page tracker to manage MSHV-owned pages and preserve with KHO
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-16-jloeser@linux.microsoft.com>

The MSHV driver passes pages to MSHV for its exclusive use. A
subsequently kexec'd-to kernel must not use these pages, so
we need to register these pages with KHO.

- adapt hv_call_deposit_pages() and hv_call_withdraw_memory() to
  use tracker
- Use KHO to preserve MSHV-owned pages across kexec

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 drivers/hv/Kconfig              |   3 +
 drivers/hv/Makefile             |   2 +-
 drivers/hv/hv_common.c          |   3 +
 drivers/hv/hv_proc.c            |  32 ++-
 drivers/hv/mshv_page_preserve.c | 374 ++++++++++++++++++++++++++++++++
 drivers/hv/mshv_page_preserve.h |  15 ++
 drivers/hv/mshv_root.h          |   1 +
 drivers/hv/mshv_root_hv_call.c  |  12 +-
 8 files changed, 434 insertions(+), 8 deletions(-)
 create mode 100644 drivers/hv/mshv_page_preserve.c
 create mode 100644 drivers/hv/mshv_page_preserve.h

diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig
index 2d0b3fcb0ff8..0c4ffc1c701b 100644
--- a/drivers/hv/Kconfig
+++ b/drivers/hv/Kconfig
@@ -74,6 +74,9 @@ config MSHV_ROOT
 	# e.g. When withdrawing memory, the hypervisor gives back 4k pages in
 	# no particular order, making it impossible to reassemble larger pages
 	depends on PAGE_SIZE_4KB
+	# Pages deposited to the hypervisor must be tracked and preserved
+	# across kexec to avoid memory corruption.
+	depends on KEXEC_HANDOVER
 	select EVENTFD
 	select VIRT_XFER_TO_GUEST_WORK
 	select HMM_MIRROR
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 888a748cc7cb..49526ae704f9 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -21,7 +21,7 @@ mshv_vtl-y := mshv_vtl_main.o
 
 # Code that must be built-in
 obj-$(CONFIG_HYPERV) += hv_common.o
-obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o
+obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o mshv_page_preserve.o
 ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
 	obj-y += mshv_common.o
 endif
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 6b67ac616789..8a593117e9b8 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -30,6 +30,7 @@
 #include <linux/set_memory.h>
 #include <hyperv/hvhdk.h>
 #include <asm/mshyperv.h>
+#include "mshv_root.h"
 
 u64 hv_current_partition_id = HV_PARTITION_ID_SELF;
 EXPORT_SYMBOL_GPL(hv_current_partition_id);
@@ -382,6 +383,8 @@ int __init hv_common_init(void)
 	if (hv_parent_partition()) {
 		hv_synic_eventring_tail = alloc_percpu(u8 *);
 		BUG_ON(!hv_synic_eventring_tail);
+
+		mshv_preserve_init();
 	}
 
 	hv_vp_index = kmalloc_array(nr_cpu_ids, sizeof(*hv_vp_index),
diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c
index 57b2c64197cb..0392ea1f3cc5 100644
--- a/drivers/hv/hv_proc.c
+++ b/drivers/hv/hv_proc.c
@@ -8,6 +8,7 @@
 #include <linux/minmax.h>
 #include <linux/export.h>
 #include <asm/mshyperv.h>
+#include "mshv_root.h"
 
 /*
  * See struct hv_deposit_memory. The first u64 is partition ID, the rest
@@ -22,6 +23,7 @@ int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages)
 	int *counts;
 	int num_allocations;
 	int i, j, page_count;
+	int reg_i = 0, reg_j = 0;
 	int order;
 	u64 status;
 	int ret;
@@ -72,6 +74,18 @@ int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages)
 	}
 	num_allocations = i;
 
+	/* Register the pages for preservation across kexec */
+	for (i = 0; i < num_allocations; ++i) {
+		for (j = 0; j < counts[i]; ++j) {
+			ret = mshv_register_preserve_page(pages[i] + j);
+			if (ret) {
+				reg_i = i;
+				reg_j = j;
+				goto err_unregister;
+			}
+		}
+	}
+
 	local_irq_save(flags);
 
 	input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
@@ -90,19 +104,27 @@ int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages)
 	if (!hv_result_success(status)) {
 		hv_status_err(status, "\n");
 		ret = hv_result_to_errno(status);
-		goto err_free_allocations;
+		reg_i = num_allocations;
+		goto err_unregister;
 	}
 
 	ret = 0;
 	goto free_buf;
 
-err_free_allocations:
+err_unregister:
 	for (i = 0; i < num_allocations; ++i) {
-		base_pfn = page_to_pfn(pages[i]);
-		for (j = 0; j < counts[i]; ++j)
-			__free_page(pfn_to_page(base_pfn + j));
+		for (j = 0; j < counts[i]; ++j) {
+			if (i == reg_i && j == reg_j)
+				goto err_free_allocations;
+			mshv_unregister_preserve_page(pages[i] + j);
+		}
 	}
 
+err_free_allocations:
+	for (i = 0; i < num_allocations; ++i)
+		for (j = 0; j < counts[i]; ++j)
+			__free_page(pages[i] + j);
+
 free_buf:
 	free_page((unsigned long)pages);
 	kfree(counts);
diff --git a/drivers/hv/mshv_page_preserve.c b/drivers/hv/mshv_page_preserve.c
new file mode 100644
index 000000000000..a79725a74663
--- /dev/null
+++ b/drivers/hv/mshv_page_preserve.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Preserve pages owned by Microsoft Hypervisor
+ *
+ * When handing pages to MSHV and kexec'ing, the next kernel needs to know which
+ * pages not to touch. Handles this preservation here.
+ *
+ * Copyright (C) 2026 Microsoft Corporation, Jork Loeser <jloeser at microsoft.com>
+ */
+
+#define pr_fmt(fmt) "mshv: " fmt
+
+#include <asm/mshyperv.h>
+#include <linux/kexec.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho_radix_tree.h>
+#include <linux/libfdt.h>
+#include <linux/reboot.h>
+#include "mshv_page_preserve.h"
+
+#define FDT_SUBTREE_MSHV "mshv_prsv_pt"
+#define MSHV_KHO_COMPAT_STR "mshv_kho-v1"
+
+static void *fdt_page;
+static struct kho_radix_tree preserved_pages_tree;
+
+/**
+ * mshv_register_preserve_page() - Register a page to be preserved by KHO
+ * @pg: pointer to the page to preserve
+ *
+ * Registers a single page to be preserved by KHO across kexec.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int mshv_register_preserve_page(struct page *pg)
+{
+	return kho_radix_add_key(&preserved_pages_tree, page_to_pfn(pg));
+}
+EXPORT_SYMBOL_GPL(mshv_register_preserve_page);
+
+/**
+ * mshv_unregister_preserve_page() - Unregister a page from KHO preservation
+ * @pg: pointer to the page to unpreserve
+ *
+ * Unregisters a page that was previously registered to be preserved by KHO.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int mshv_unregister_preserve_page(struct page *pg)
+{
+	return kho_radix_del_key(&preserved_pages_tree, page_to_pfn(pg));
+}
+EXPORT_SYMBOL_GPL(mshv_unregister_preserve_page);
+
+/* Preserve a single page identified by its PFN key with KHO */
+static int preserve_key_cb(unsigned long key, void *data)
+{
+	return kho_preserve_pages(pfn_to_page(key), 1);
+}
+
+/* Preserve a radix tree metadata page with KHO */
+static int preserve_table_cb(phys_addr_t phys, void *data)
+{
+	return kho_preserve_pages(phys_to_page(phys), 1);
+}
+
+static int create_fdt(void)
+{
+	int err;
+	void *fdt;
+	phys_addr_t root_table;
+
+	if (!fdt_page)
+		return -EINVAL;
+
+	fdt = fdt_page;
+
+	err = fdt_create(fdt, PAGE_SIZE);
+	if (err)
+		return err;
+	err = fdt_finish_reservemap(fdt);
+	if (err)
+		return err;
+	err = fdt_begin_node(fdt, "");
+	if (err)
+		return err;
+	err = fdt_property(fdt, "compatible", MSHV_KHO_COMPAT_STR,
+			   strlen(MSHV_KHO_COMPAT_STR) + 1);
+	if (err)
+		return err;
+	root_table = virt_to_phys(preserved_pages_tree.root);
+	err = fdt_property(fdt, "root_table", &root_table, sizeof(root_table));
+	if (err)
+		return err;
+	err = fdt_end_node(fdt);
+	if (err)
+		return err;
+	err = fdt_finish(fdt);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/**
+ * preserve_tree() - Preserve pages owned by Microsoft Hypervisor
+ *
+ * This gets called prior to kexec and is our signal to finally preserve the
+ * pages with KHO, and create & register the named FDT. We also need to freeze
+ * the tree, since we cannot communicate any later changes.
+ *
+ * Return: 0 on success, -errno on error.
+ */
+static int preserve_tree(void)
+{
+	const struct kho_radix_walk_cb preserve_cb = {
+		.key = preserve_key_cb,
+		.table = preserve_table_cb,
+	};
+	int err;
+
+	err = kho_radix_tree_freeze(&preserved_pages_tree);
+	if (err) {
+		pr_warn("%s() - kho_radix_tree_freeze() failed: %d\n",
+			__func__, err);
+		return err;
+	}
+
+	/* Populate the pre-allocated FDT page with current tree state */
+	err = create_fdt();
+	if (err) {
+		pr_warn("%s() - create_fdt() failed: %d\n", __func__, err);
+		return err;
+	}
+
+	/* Preserve both data- and meta-pages */
+	err = kho_radix_walk_tree(&preserved_pages_tree, &preserve_cb, NULL);
+	if (err) {
+		/* We could not preserve all pages and cannot kexec. */
+		pr_warn("%s() - kho_radix_walk_tree() failed: %d\n", __func__,
+			err);
+		return err;
+	}
+
+	err = kho_preserve_pages(virt_to_page(fdt_page), 1);
+	if (err) {
+		pr_warn("%s() - kho_preserve_pages(fdt) failed: %d\n", __func__,
+			err);
+		return err;
+	}
+
+	err = kho_add_subtree(FDT_SUBTREE_MSHV, fdt_page, PAGE_SIZE);
+	if (err) {
+		/* KHO will abort and undo all preservations. We cannot kexec. */
+		pr_warn("%s() - kho_add_subtree() failed: %d\n", __func__, err);
+		return err;
+	}
+
+	pr_debug("%s() - success\n", __func__);
+	return 0;
+}
+
+/*
+ * Reboot-callback triggering page preservation prior to kexec. Other reboots
+ * need no KHO preservation.
+ */
+static int reboot_cb(struct notifier_block *nb, unsigned long action,
+		     void *data)
+{
+	/* codes such as SYS_RESTART, SYS_HALT do not convey kexec specifically */
+	if (kexec_in_progress) {
+		int err;
+
+		/* Finalize handover: write KHO descriptors, flush metadata */
+		pr_debug("%s() - KHO-preserving page tree\n", __func__);
+		err = preserve_tree();
+		if (err)
+			panic("preserve_tree() failed - must not kexec: %d\n",
+			      err);
+	}
+	return NOTIFY_OK;
+}
+
+/**
+ * restore_tree() - Restore the page-tree state from KHO.
+ *
+ * Return: 0 on success, -ENOENT if no KHO subtree was found (i.e. this is
+ *         not a KHO boot), -EINVAL if the preserved FDT is malformed or
+ *         incompatible.
+ */
+static int __init restore_tree(void)
+{
+	void *fdt;
+	phys_addr_t fdt_pa;
+	int len;
+	int node;
+	const phys_addr_t *root_table_fdt_ptr;
+	int err;
+
+	err = kho_retrieve_subtree(FDT_SUBTREE_MSHV, &fdt_pa, NULL);
+	if (err)
+		return err;
+
+	fdt = phys_to_virt(fdt_pa);
+	node = fdt_path_offset(fdt, "/");
+	if (node < 0) {
+		pr_err("Could not find root node in KHO-preserved FDT.\n");
+		return -EINVAL;
+	}
+
+	if (fdt_node_check_compatible(fdt, node, MSHV_KHO_COMPAT_STR)) {
+		/*
+		 * This is unfortunate. We kexec'd into a kernel that isn't
+		 * compatible with prior preservations. Pages this kernel
+		 * considers available might actually be held by MSHV. The only
+		 * recourse is to reboot.
+		 */
+		const char *s = fdt_getprop(fdt, node, "compatible", &len);
+
+		if (s && len >= 0)
+			pr_err("Incompatible kernel: Current is %s, preserved is %.*s\n",
+			       MSHV_KHO_COMPAT_STR, len, s);
+		else
+			pr_err("Incompatible kernel: preserved misses 'compatible' mark.\n");
+		return -EINVAL;
+	}
+
+	root_table_fdt_ptr = fdt_getprop(fdt, node, "root_table", &len);
+	if (!root_table_fdt_ptr || len != sizeof(*root_table_fdt_ptr)) {
+		pr_err("Could not obtain root_table property from KHO-preserved FDT.\n");
+		return -EINVAL;
+	}
+
+	/* Restore struct page so it could be freed if needed */
+	if (!kho_restore_pages(fdt_pa, 1))
+		return -EINVAL;
+
+	fdt_page = phys_to_virt(fdt_pa);
+
+	err = kho_radix_init_tree(&preserved_pages_tree,
+				  phys_to_virt(*root_table_fdt_ptr));
+	if (err)
+		return -EINVAL;
+
+	pr_debug("Restored tracking from KHO.\n");
+	return 0;
+}
+
+/*
+ * Restore individual pages using KHO's helper during boot.
+ *
+ * Pages must be restored one at a time because they were deposited to
+ * the hypervisor individually and will be withdrawn individually later.
+ * Restoring them as a higher-order group would create compound pages
+ * that cannot be freed with __free_page().
+ */
+static int __init restore_key_cb(unsigned long key, void *data)
+{
+	if (!kho_restore_pages(PFN_PHYS(key), 1))
+		return -EINVAL;
+	return 0;
+}
+
+static int __init restore_table_cb(phys_addr_t phys, void *data)
+{
+	if (!kho_restore_pages(phys, 1))
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * restore_page_structs() - Restore page-structs so they can be __free_page()'d
+ *
+ * This is necessary because KHO-preserved pages are in a "weird" state
+ * post-kexec. While doing so here in bulk adds to boot time, there is no vetted
+ * alternative that would allow doing this later, when we cannot say which pages
+ * had been freshly added, and which came into the tree through KHO.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int __init restore_page_structs(void)
+{
+	const struct kho_radix_walk_cb cb = {
+		.key = restore_key_cb,
+		.table = restore_table_cb,
+	};
+
+	return kho_radix_walk_tree(&preserved_pages_tree, &cb, NULL);
+}
+
+/**
+ * alloc_tree() - Allocate a fresh page tree and FDT page.
+ *
+ * Called on fresh boot (no KHO data). Allocates an empty radix tree and
+ * the FDT page used to serialize state before kexec.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int __init alloc_tree(void)
+{
+	int err;
+
+	fdt_page = (void *)get_zeroed_page(GFP_KERNEL);
+	if (!fdt_page)
+		return -ENOMEM;
+
+	err = kho_radix_init_tree(&preserved_pages_tree, NULL);
+	if (err) {
+		free_page((unsigned long)fdt_page);
+		fdt_page = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+static struct notifier_block reboot_notifier = {
+	.notifier_call = reboot_cb,
+	.priority = 0,
+};
+
+/**
+ * mshv_preserve_init() - Initialize the page preservation
+ *
+ * Upon return:
+ * - the tracker will be ready for use (restored post-kexec, or empty
+ *   post-reboot),
+ * - restored pages will be in a state that can be __free_page()'d,
+ * - KHO notification for preservation will be registered.
+ *
+ * Return: 0 on success, -errno on error.
+ */
+int __init mshv_preserve_init(void)
+{
+	int err;
+
+	if (!kho_is_enabled()) {
+		pr_err("KHO is disabled; page deposits will fail.\n");
+		return 0;
+	}
+
+	err = restore_tree();
+	if (!err) {
+		/* Restore struct pages so they can be __free_page()'d */
+		if (restore_page_structs())
+			/*
+			 * Unrestored struct pages would BUG when freed
+			 * at withdraw time.
+			 */
+			panic("Failed to restore MSHV page structs\n");
+	} else if (err == -ENOENT) {
+		pr_debug("Nothing to restore from KHO.\n");
+		if (alloc_tree()) {
+			pr_err("Could not allocate page tree; page deposits will fail.\n");
+			return 0;
+		}
+	} else {
+		/*
+		 * Pages from the prior kernel are held by MSHV but we
+		 * lost track of them -- memory corruption is inevitable.
+		 */
+		panic("Could not restore page tree from KHO: %d\n", err);
+	}
+
+	err = register_reboot_notifier(&reboot_notifier);
+	if (err)
+		/*
+		 * Deposits would succeed but pages would not be preserved
+		 * across kexec, causing memory corruption post-kexec.
+		 */
+		panic("Could not register reboot notification: %d\n", err);
+
+	return 0;
+}
diff --git a/drivers/hv/mshv_page_preserve.h b/drivers/hv/mshv_page_preserve.h
new file mode 100644
index 000000000000..0609002e5f1d
--- /dev/null
+++ b/drivers/hv/mshv_page_preserve.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Microsoft Corporation, Jork Loeser <jloeser at microsoft.com>
+ */
+
+#ifndef _MSHV_PAGE_PRESERVE_H
+#define _MSHV_PAGE_PRESERVE_H
+
+struct page;
+
+int mshv_preserve_init(void);
+int mshv_register_preserve_page(struct page *pg);
+int mshv_unregister_preserve_page(struct page *pg);
+
+#endif /* _MSHV_PAGE_PRESERVE_H */
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 1f086dcb7aa1..362768786c17 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -18,6 +18,7 @@
 #include <linux/mmu_notifier.h>
 #include <uapi/linux/mshv.h>
 #include "mshv_trace.h"
+#include "mshv_page_preserve.h"
 
 /*
  * Hypervisor must be between these version numbers (inclusive)
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb55d4d4be2e..f5ff03318787 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -69,8 +69,16 @@ int hv_call_withdraw_memory(u64 count, int node, u64 partition_id)
 
 		completed = hv_repcomp(status);
 
-		for (i = 0; i < completed; i++)
-			__free_page(pfn_to_page(output_page->gpa_page_list[i]));
+		for (i = 0; i < completed; i++) {
+			struct page *pg = pfn_to_page(output_page->gpa_page_list[i]);
+			int res = mshv_unregister_preserve_page(pg);
+
+			WARN_ONCE(res, "Failed to unregister PFN %#llx\n",
+				  output_page->gpa_page_list[i]);
+
+			/* Free regardless -- HV has already released the page */
+			__free_page(pg);
+		}
 
 		if (!hv_result_success(status)) {
 			if (hv_result(status) == HV_STATUS_NO_RESOURCES)
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:41:59 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:41:59 -0700
Subject: [RFC PATCH 17/20] hyperv: Reserve crash MSR P2 for page preservation root PA
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-18-jloeser@linux.microsoft.com>

The crash MSRs have no formal semantics and nothing depends on their
contents, so the register assignment can be reshuffled freely.

Reserve crash MSR P2 for passing the KHO radix tree root physical
address to the crash kernel for MSHV page exclusion during dump
collection. Stop overwriting it in the panic reporting paths.

Move IP/PC to P3 and SP to P4 in hyperv_report_panic() on both x86
and ARM64. Remove the P2 write from hv_kmsg_dump().

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 arch/arm64/hyperv/hv_core.c | 6 +++---
 arch/x86/hyperv/hv_init.c   | 4 ++--
 drivers/hv/hv_common.c      | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/hyperv/hv_core.c b/arch/arm64/hyperv/hv_core.c
index e33a9e3c366a..b75337c4892d 100644
--- a/arch/arm64/hyperv/hv_core.c
+++ b/arch/arm64/hyperv/hv_core.c
@@ -185,9 +185,9 @@ void hyperv_report_panic(struct pt_regs *regs, long err, bool in_die)
 	 */
 	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P0, err);
 	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P1, guest_id);
-	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P2, regs->pc);
-	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P3, regs->sp);
-	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P4, 0);
+	/* P2 is reserved for the KHO preserved-pages tree root PA */
+	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P3, regs->pc);
+	hv_set_vpreg(HV_REGISTER_GUEST_CRASH_P4, regs->sp);
 
 	/*
 	 * Let Hyper-V know there is crash data available
diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
index 55a8b6de2865..cd75e2be19b2 100644
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -675,8 +675,8 @@ void hyperv_report_panic(struct pt_regs *regs, long err, bool in_die)
 
 	wrmsrq(HV_X64_MSR_CRASH_P0, err);
 	wrmsrq(HV_X64_MSR_CRASH_P1, guest_id);
-	wrmsrq(HV_X64_MSR_CRASH_P2, regs->ip);
-	wrmsrq(HV_X64_MSR_CRASH_P3, regs->ax);
+	/* P2 is reserved for the KHO preserved-pages tree root PA */
+	wrmsrq(HV_X64_MSR_CRASH_P3, regs->ip);
 	wrmsrq(HV_X64_MSR_CRASH_P4, regs->sp);
 
 	/*
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 8a593117e9b8..ae6415f42f25 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -212,7 +212,7 @@ static void hv_kmsg_dump(struct kmsg_dumper *dumper,
 	 */
 	hv_set_msr(HV_MSR_CRASH_P0, 0);
 	hv_set_msr(HV_MSR_CRASH_P1, 0);
-	hv_set_msr(HV_MSR_CRASH_P2, 0);
+	/* P2 is reserved for the KHO preserved-pages tree root PA */
 	hv_set_msr(HV_MSR_CRASH_P3, bytes_written ? virt_to_phys(hv_panic_page) : 0);
 	hv_set_msr(HV_MSR_CRASH_P4, bytes_written);
 
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:42:00 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:42:00 -0700
Subject: [RFC PATCH 18/20] mshv: Exclude Hyper-V donated pages from crash dump collection
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-19-jloeser@linux.microsoft.com>

Pages donated to Hyper-V must not be read during crash dump collection.
They are not ordinary RAM and accessing them can hang or corrupt the
crash kernel.

Use the KHO radix tree of preserved pages to drive a vmcore pfn_is_ram()
callback. The radix tree root PA is passed to the crash kernel via
Hyper-V crash MSR P2, since the old kernel's KHO FDT is not accessible
from the crash kernel's direct map.

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 drivers/hv/mshv_page_preserve.c | 80 +++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/drivers/hv/mshv_page_preserve.c b/drivers/hv/mshv_page_preserve.c
index bc3a3a688f5b..e16fb946790d 100644
--- a/drivers/hv/mshv_page_preserve.c
+++ b/drivers/hv/mshv_page_preserve.c
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) "mshv: " fmt
 
 #include <asm/mshyperv.h>
+#include <linux/crash_dump.h>
 #include <linux/kexec.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho_radix_tree.h>
@@ -327,6 +328,57 @@ static int __init alloc_tree(void)
 	return 0;
 }
 
+#ifdef CONFIG_CRASH_DUMP
+static struct kho_radix_crash_tree crash_preserved_pages_tree;
+
+/**
+ * restore_crash_tree() - Set up the crash tree for dump-time page exclusion.
+ *
+ * In the crash kernel, the old kernel's memory is not in the direct map.
+ * The old kernel stashes the radix tree root PA in Hyper-V crash MSR P2
+ * so we can retrieve it without touching the old kernel's FDT.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int __init restore_crash_tree(void)
+{
+	phys_addr_t root_pa;
+
+	root_pa = hv_get_msr(HV_MSR_CRASH_P2);
+	if (!root_pa)
+		return -ENOENT;
+
+	/*
+	 * The MSR may contain stale data from a previous
+	 * hyperv_report_panic().  Sanity-check that it looks like a
+	 * page-aligned physical address within the architectural limit.
+	 */
+	if (!PAGE_ALIGNED(root_pa) || root_pa >> MAX_POSSIBLE_PHYSMEM_BITS) {
+		pr_warn("Invalid crash tree root PA: 0x%llx\n",
+			(unsigned long long)root_pa);
+		return -EINVAL;
+	}
+
+	return kho_radix_crash_init(&crash_preserved_pages_tree, root_pa);
+}
+
+static bool mshv_vmcore_pfn_is_ram(struct vmcore_cb *cb, unsigned long pfn)
+{
+	/*
+	 * MSHV-owned pages must not be read during crash dump collection.
+	 * Currently all pages are registered at order 0. If higher-order
+	 * registrations are added, this lookup will need to handle them
+	 * (e.g. by querying multiple orders or using a range-based API).
+	 */
+	return !kho_radix_crash_contains_page(&crash_preserved_pages_tree,
+					      pfn, 0);
+}
+
+static struct vmcore_cb mshv_vmcore_cb = {
+	.pfn_is_ram = mshv_vmcore_pfn_is_ram,
+};
+#endif
+
 static struct notifier_block reboot_notifier = {
 	.notifier_call = reboot_cb,
 	.priority = 0,
@@ -347,6 +399,24 @@ int __init mshv_preserve_init(void)
 {
 	int err;
 
+#ifdef CONFIG_CRASH_DUMP
+	if (is_kdump_kernel()) {
+		/*
+		 * Crash kernel only needs the pfn_is_ram callback to exclude
+		 * MSHV-owned pages from the dump.  No page restoration, no
+		 * reboot notifier ? the crash kernel reboots after collection.
+		 */
+		err = restore_crash_tree();
+		if (err) {
+			pr_err("Could not set up crash page tree: %d; MSHV pages may appear in dump\n",
+			       err);
+			return 0;
+		}
+		register_vmcore_cb(&mshv_vmcore_cb);
+		return 0;
+	}
+#endif
+
 	if (!kho_is_enabled()) {
 		pr_err("KHO is disabled; page deposits will fail.\n");
 		return 0;
@@ -383,5 +453,15 @@ int __init mshv_preserve_init(void)
 		 */
 		panic("Could not register reboot notification: %d\n", err);
 
+	/*
+	 * Stash the radix tree root PA in crash MSR P2 so the crash
+	 * kernel can retrieve it without touching the old kernel's FDT
+	 * (which is not in the crash kernel's direct map).  The root
+	 * pointer is stable once the tree is initialized ? pages are
+	 * added/removed within the existing tree structure.
+	 */
+	hv_set_msr(HV_MSR_CRASH_P2,
+		   virt_to_phys(preserved_pages_tree.root));
+
 	return 0;
 }
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:42:01 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:42:01 -0700
Subject: [RFC PATCH 19/20] kexec: export kexec_in_progress for modules
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-20-jloeser@linux.microsoft.com>

Modules that register reboot notifiers may need to distinguish a
kexec from a regular reboot.  Export kexec_in_progress so they can
check without requiring a built-in wrapper.

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 kernel/kexec_core.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index a43d2da0fe3e..68efbba52fbd 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -53,6 +53,7 @@ atomic_t __kexec_lock = ATOMIC_INIT(0);
 
 /* Flag to indicate we are going to kexec a new kernel */
 bool kexec_in_progress = false;
+EXPORT_SYMBOL_GPL(kexec_in_progress);
 
 bool kexec_file_dbg_print;
 
-- 
2.43.0



From jloeser at linux.microsoft.com  Wed May 27 17:42:02 2026
From: jloeser at linux.microsoft.com (Jork Loeser)
Date: Wed, 27 May 2026 17:42:02 -0700
Subject: [RFC PATCH 20/20] mshv: freeze and vacuum partitions across kexec
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <20260528004204.1484584-21-jloeser@linux.microsoft.com>

Before kexec the kernel must ensure no VMs are actively running so that
no VP modifies VM-memory that Linux will re-use post-kexec, and record
the partition IDs so the next kernel can clean them up.

Add mshv_freeze_and_get_partition_ids() which:
- Sets a global frozen flag to block new partition and VP creation
- Prevents (re-)dispatching of existing VPs
- Kicks all VPs to exit their dispatch loops, then waits for each to
  finish by acquiring its mutex
- Tears down doorbell ports owned by the parent partition, which
  otherwise survive kexec and cause port ID collisions in the new kernel
- Collects all partition IDs into a kho_alloc_preserve()'d array

The freeze is triggered from the reboot notifier callback. The ID
array is serialized into the KHO FDT so the next kernel can retrieve
it via mshv_retrieve_frozen_partition_ids().

After kexec the previous kernel's partitions are still alive in the
hypervisor. vacuum_stale_partitions() retrieves their IDs from the
KHO-preserved FDT and tears each one down (finalize, withdraw
deposited memory, delete) so the pages become available to the new
kernel.

Signed-off-by: Jork Loeser <jloeser at linux.microsoft.com>
---
 drivers/hv/mshv_page_preserve.c | 100 +++++++++-
 drivers/hv/mshv_page_preserve.h |   3 +
 drivers/hv/mshv_root.h          |   2 +
 drivers/hv/mshv_root_main.c     | 339 +++++++++++++++++++++++++++++---
 4 files changed, 417 insertions(+), 27 deletions(-)

diff --git a/drivers/hv/mshv_page_preserve.c b/drivers/hv/mshv_page_preserve.c
index e16fb946790d..dba7975ab058 100644
--- a/drivers/hv/mshv_page_preserve.c
+++ b/drivers/hv/mshv_page_preserve.c
@@ -24,6 +24,8 @@
 
 static void *fdt_page;
 static struct kho_radix_tree preserved_pages_tree;
+static u64 *frozen_partition_ids;
+static unsigned int nr_frozen_partition_ids;
 
 /**
  * mshv_register_preserve_page() - Register a page to be preserved by KHO
@@ -78,7 +80,7 @@ static int preserve_table_cb(phys_addr_t phys, void *data)
 	return kho_preserve_pages(phys_to_page(phys), 1);
 }
 
-static int create_fdt(void)
+static int create_fdt(u64 *partition_ids, unsigned int nr_partition_ids)
 {
 	int err;
 	void *fdt;
@@ -106,6 +108,19 @@ static int create_fdt(void)
 	err = fdt_property(fdt, "root_table", &root_table, sizeof(root_table));
 	if (err)
 		return err;
+	if (nr_partition_ids) {
+		phys_addr_t ids_pa = virt_to_phys(partition_ids);
+		u32 count = nr_partition_ids;
+
+		err = fdt_property(fdt, "partition_ids", &ids_pa,
+				   sizeof(ids_pa));
+		if (err)
+			return err;
+		err = fdt_property(fdt, "nr_partition_ids", &count,
+				   sizeof(count));
+		if (err)
+			return err;
+	}
 	err = fdt_end_node(fdt);
 	if (err)
 		return err;
@@ -118,6 +133,8 @@ static int create_fdt(void)
 
 /**
  * preserve_tree() - Preserve pages owned by Microsoft Hypervisor
+ * @partition_ids: array of frozen partition IDs to serialize, or NULL
+ * @nr_partition_ids: number of entries in @partition_ids
  *
  * This gets called prior to kexec and is our signal to finally preserve the
  * pages with KHO, and create & register the named FDT. We also need to freeze
@@ -125,7 +142,7 @@ static int create_fdt(void)
  *
  * Return: 0 on success, -errno on error.
  */
-static int preserve_tree(void)
+static int preserve_tree(u64 *partition_ids, unsigned int nr_partition_ids)
 {
 	const struct kho_radix_walk_cb preserve_cb = {
 		.key = preserve_key_cb,
@@ -141,7 +158,7 @@ static int preserve_tree(void)
 	}
 
 	/* Populate the pre-allocated FDT page with current tree state */
-	err = create_fdt();
+	err = create_fdt(partition_ids, nr_partition_ids);
 	if (err) {
 		pr_warn("%s() - create_fdt() failed: %d\n", __func__, err);
 		return err;
@@ -177,6 +194,11 @@ static int preserve_tree(void)
 /*
  * Reboot-callback triggering page preservation prior to kexec. Other reboots
  * need no KHO preservation.
+ *
+ * The mshv_root module's higher-priority reboot notifier freezes all VPs
+ * and hands off partition IDs via mshv_set_frozen_partition_ids() before
+ * this callback runs. If the module is not loaded, no partitions exist
+ * and the tree is preserved without partition IDs.
  */
 static int reboot_cb(struct notifier_block *nb, unsigned long action,
 		     void *data)
@@ -185,9 +207,9 @@ static int reboot_cb(struct notifier_block *nb, unsigned long action,
 	if (kexec_in_progress) {
 		int err;
 
-		/* Finalize handover: write KHO descriptors, flush metadata */
 		pr_debug("%s() - KHO-preserving page tree\n", __func__);
-		err = preserve_tree();
+		err = preserve_tree(frozen_partition_ids,
+				    nr_frozen_partition_ids);
 		if (err)
 			panic("preserve_tree() failed - must not kexec: %d\n",
 			      err);
@@ -260,6 +282,74 @@ static int __init restore_tree(void)
 	return 0;
 }
 
+/**
+ * mshv_set_frozen_partition_ids() - Hand off frozen partition IDs for KHO
+ * @ids: kho_alloc_preserve()'d array of partition IDs, or NULL
+ * @nr: number of entries in @ids
+ *
+ * Called by the mshv_root module's reboot notifier (which runs at higher
+ * priority) to pass the frozen partition ID list to the built-in page
+ * preservation code before it serializes the KHO FDT.
+ */
+void mshv_set_frozen_partition_ids(u64 *ids, unsigned int nr)
+{
+	frozen_partition_ids = ids;
+	nr_frozen_partition_ids = nr;
+}
+EXPORT_SYMBOL_GPL(mshv_set_frozen_partition_ids);
+
+/**
+ * mshv_retrieve_frozen_partition_ids() - Retrieve frozen partition IDs
+ * @partition_ids: receives pointer to the preserved ID array, or NULL
+ * @nr_ids: receives the number of entries, or 0
+ *
+ * Counterpart to mshv_freeze_and_get_partition_ids(). Reads the partition
+ * ID list from the KHO-preserved FDT. The returned pointer (if non-NULL)
+ * refers to kho_alloc_preserve()'d memory from the previous kernel.
+ *
+ * Return: 0 on success (including when no IDs are found), negative errno on
+ *  error.
+ */
+int mshv_retrieve_frozen_partition_ids(u64 **partition_ids,
+				       unsigned int *nr_ids)
+{
+	int node, len;
+	const phys_addr_t *ids_pa;
+	const u32 *count_prop;
+
+	*partition_ids = NULL;
+	*nr_ids = 0;
+
+	if (!fdt_page)
+		return 0;
+
+	node = fdt_path_offset(fdt_page, "/");
+	if (node < 0)
+		return 0;
+
+	ids_pa = fdt_getprop(fdt_page, node, "partition_ids", &len);
+	if (!ids_pa)
+		return 0;
+
+	if (len != sizeof(*ids_pa)) {
+		pr_err("Malformed preserved FDT: invalid partition_ids property.\n");
+		return -EINVAL;
+	}
+
+	count_prop = fdt_getprop(fdt_page, node, "nr_partition_ids", &len);
+	if (!count_prop || len != sizeof(*count_prop)) {
+		pr_err("Malformed preserved FDT: invalid nr_partition_ids property.\n");
+		return -EINVAL;
+	}
+
+	*partition_ids = phys_to_virt(*ids_pa);
+	*nr_ids = *count_prop;
+
+	pr_info("Retrieved %u frozen partition ID(s) from KHO\n", *nr_ids);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mshv_retrieve_frozen_partition_ids);
+
 /*
  * Restore individual pages using KHO's helper during boot.
  *
diff --git a/drivers/hv/mshv_page_preserve.h b/drivers/hv/mshv_page_preserve.h
index ac99b4e33285..4625d59a3070 100644
--- a/drivers/hv/mshv_page_preserve.h
+++ b/drivers/hv/mshv_page_preserve.h
@@ -14,5 +14,8 @@ int mshv_preserve_init(void);
 int mshv_register_preserve_page(struct page *pg);
 int mshv_unregister_preserve_page(struct page *pg);
 int mshv_iterate_preserved(const struct kho_radix_walk_cb *cb, void *data);
+void mshv_set_frozen_partition_ids(u64 *ids, unsigned int nr);
+int mshv_retrieve_frozen_partition_ids(u64 **partition_ids,
+				       unsigned int *nr_ids);
 
 #endif /* _MSHV_PAGE_PRESERVE_H */
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 216053f8e0ab..7476398d3b47 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -195,6 +195,7 @@ struct mshv_root {
 	spinlock_t pt_ht_lock;
 	DECLARE_HASHTABLE(pt_htable, MSHV_PARTITIONS_HASH_BITS);
 	struct hv_partition_property_vmm_capabilities vmm_caps;
+	bool frozen;
 };
 
 /*
@@ -364,6 +365,7 @@ static inline void mshv_debugfs_vp_remove(struct mshv_vp *vp) { }
 
 extern struct mshv_root mshv_root;
 extern enum hv_scheduler_type hv_scheduler_type;
+
 extern u8 * __percpu *hv_synic_eventring_tail;
 
 struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 5fbd01c12ab8..e95abf4698f8 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -18,6 +18,7 @@
 #include <linux/anon_inodes.h>
 #include <linux/mm.h>
 #include <linux/io.h>
+#include <linux/cleanup.h>
 #include <linux/cpuhotplug.h>
 #include <linux/random.h>
 #include <asm/mshyperv.h>
@@ -27,6 +28,7 @@
 #include <linux/kexec.h>
 #include <linux/page-flags.h>
 #include <linux/crash_dump.h>
+#include <linux/kexec_handover.h>
 #include <linux/panic_notifier.h>
 #include <linux/vmalloc.h>
 #include <linux/rseq.h>
@@ -359,6 +361,7 @@ mshv_suspend_vp(const struct mshv_vp *vp, bool *message_in_flight)
  */
 static long mshv_run_vp_with_hyp_scheduler(struct mshv_vp *vp)
 {
+	bool message_in_flight;
 	long ret;
 	struct hv_register_assoc suspend_regs[2] = {
 			{ .name = HV_REGISTER_INTERCEPT_SUSPEND },
@@ -375,32 +378,40 @@ static long mshv_run_vp_with_hyp_scheduler(struct mshv_vp *vp)
 	}
 
 	ret = wait_event_interruptible(vp->run.vp_suspend_queue,
-				       vp->run.kicked_by_hv == 1);
-	if (ret) {
-		bool message_in_flight;
+				       vp->run.kicked_by_hv == 1 ||
+				       READ_ONCE(mshv_root.frozen));
 
-		/*
-		 * Otherwise the waiting was interrupted by a signal: suspend
-		 * the vCPU explicitly and copy message in flight (if any).
-		 */
-		ret = mshv_suspend_vp(vp, &message_in_flight);
-		if (ret)
-			return ret;
+	/* Normal wakeup: intercept arrived */
+	if (!ret && !READ_ONCE(mshv_root.frozen)) {
+		vp->run.kicked_by_hv = 0;
+		return 0;
+	}
 
-		/* Return if no message in flight */
-		if (!message_in_flight)
-			return -EINTR;
+	/*
+	 * Signal or frozen: VP was resumed above and may still be
+	 * running in the hypervisor. Suspend it before returning.
+	 */
+	ret = mshv_suspend_vp(vp, &message_in_flight);
+	if (ret)
+		return ret;
 
-		/* Wait for the message in flight. */
-		wait_event(vp->run.vp_suspend_queue, vp->run.kicked_by_hv == 1);
-	}
+	/* No in-flight message or frozen ? nothing to deliver */
+	if (!message_in_flight || READ_ONCE(mshv_root.frozen))
+		return -EINTR;
+
+	/* Signal case: wait for the in-flight intercept message */
+	wait_event(vp->run.vp_suspend_queue,
+		   vp->run.kicked_by_hv == 1 ||
+		   READ_ONCE(mshv_root.frozen));
+
+	if (READ_ONCE(mshv_root.frozen))
+		return -EINTR;
 
 	/*
 	 * Reset the flag to make the wait_event call above work
 	 * next time.
 	 */
 	vp->run.kicked_by_hv = 0;
-
 	return 0;
 }
 
@@ -503,7 +514,8 @@ mshv_vp_wait_for_hv_kick(struct mshv_vp *vp)
 	ret = wait_event_interruptible(vp->run.vp_suspend_queue,
 				       (vp->run.kicked_by_hv == 1 &&
 					!mshv_vp_dispatch_thread_blocked(vp)) ||
-				       mshv_vp_interrupt_pending(vp));
+				       mshv_vp_interrupt_pending(vp) ||
+				       READ_ONCE(mshv_root.frozen));
 	if (ret)
 		return -EINTR;
 
@@ -513,6 +525,9 @@ mshv_vp_wait_for_hv_kick(struct mshv_vp *vp)
 				       mshv_vp_dispatch_thread_blocked(vp),
 				       mshv_vp_interrupt_pending(vp));
 
+	if (READ_ONCE(mshv_root.frozen))
+		return -EBUSY;
+
 	vp->run.flags.root_sched_blocked = 0;
 	vp->run.kicked_by_hv = 0;
 
@@ -539,6 +554,11 @@ static long mshv_run_vp_with_root_scheduler(struct mshv_vp *vp)
 		u32 flags = 0;
 		struct hv_output_dispatch_vp output;
 
+		if (READ_ONCE(mshv_root.frozen)) {
+			ret = -EBUSY;
+			break;
+		}
+
 		if (__xfer_to_guest_mode_work_pending()) {
 			ret = xfer_to_guest_mode_handle_work();
 
@@ -712,6 +732,11 @@ static long mshv_vp_ioctl_run_vp(struct mshv_vp *vp, void __user *ret_msg)
 	trace_mshv_run_vp_entry(vp->vp_partition->pt_id, vp->vp_index);
 
 	do {
+		if (READ_ONCE(mshv_root.frozen)) {
+			rc = -EBUSY;
+			break;
+		}
+
 		if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
 			rc = mshv_run_vp_with_root_scheduler(vp);
 		else
@@ -1074,6 +1099,9 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
 	struct hv_stats_page *stats_pages[2];
 	long ret;
 
+	if (READ_ONCE(mshv_root.frozen))
+		return -EBUSY;
+
 	if (copy_from_user(&args, arg, sizeof(args)))
 		return -EFAULT;
 
@@ -1762,6 +1790,201 @@ static void drain_all_vps(const struct mshv_partition *partition)
 	}
 }
 
+/**
+ * mshv_freeze_and_get_partition_ids() - Freeze all partitions and collect IDs
+ * @partition_ids: on success, receives a kho_alloc_preserve()'d array of
+ *      partition IDs; set to NULL on failure or when no partitions exist
+ * @nr_ids: on success, receives the number of entries in @partition_ids; set to
+ *      0 on failure or when no partitions exist
+ *
+ * Sets the global frozen flag to prevent creation of new partitions and
+ * (re-)dispatching of VPs. Kicks all VPs so they exit their dispatch loops,
+ * then waits for each VP to actually finish by acquiring its mutex.
+ *
+ * Must be called before kexec to ensure no VP modifies VM-memory that Linux
+ * will re-use post-kexec.
+ *
+ * Return: 0 on success, negative errno on failure. On failure, partitions
+ *  and VPs are left in an undefined state ? the caller must not proceed
+ *  with kexec and should panic.
+ */
+static int
+mshv_freeze_and_get_partition_ids(u64 **partition_ids, unsigned int *nr_ids)
+{
+	unsigned int nr_alloc = 0, nr_ref = 0, nr_noref = 0;
+	struct mshv_partition *partition;
+	struct mshv_vp *vp;
+	int bkt, i;
+	u64 *ids;
+
+	*partition_ids = NULL;
+	*nr_ids = 0;
+
+	scoped_guard(spinlock, &mshv_root.pt_ht_lock)
+		mshv_root.frozen = true;
+
+	/*
+	 * Count partitions to size the ID array. Frozen prevents new additions,
+	 * so this is an upper bound.
+	 */
+	scoped_guard(rcu)
+		hash_for_each_rcu(mshv_root.pt_htable, bkt, partition, pt_hnode)
+			nr_alloc++;
+
+	if (!nr_alloc) {
+		pr_info("Frozen 0 partition(s) for kexec\n");
+		return 0;
+	}
+
+	ids = kho_alloc_preserve(nr_alloc * sizeof(*ids));
+	if (IS_ERR(ids)) {
+		pr_err("Failed to allocate partition ID array for freeze\n");
+		return PTR_ERR(ids);
+	}
+
+	/*
+	 * Record every partition's ID and obtain a reference for later use.
+	 *
+	 * Zero-refcount partitions (destroy_partition() in progress) still get
+	 * their ID recorded ? destruction may not complete before kexec, and
+	 * the next kernel must clean them up. Their IDs are stored at the back
+	 * of the array so the kick/drain phase can iterate only the ref'd
+	 * prefix ids[0..nr_ref).
+	 *
+	 * VP kicking is deferred to the next phase where it happens under
+	 * pt_mutex, which serializes against mshv_partition_ioctl_create_vp().
+	 */
+	rcu_read_lock();
+	hash_for_each_rcu(mshv_root.pt_htable, bkt, partition, pt_hnode) {
+		if (!mshv_partition_get(partition)) {
+			/*
+			 * Zero refcount ? destroy_partition() is in progress.
+			 * All fds are closed so no VP ioctl can be running.
+			 * Store at the back; skip VP kicking.
+			 */
+			ids[nr_alloc - 1 - nr_noref++] = partition->pt_id;
+			continue;
+		}
+
+		ids[nr_ref++] = partition->pt_id;
+	}
+	rcu_read_unlock();
+
+	/*
+	 * For each ref'd partition, acquire and release pt_mutex as a barrier
+	 * against any in-flight create_vp. After this, the frozen flag
+	 * prevents new VPs from being created, so pt_vp_array is stable.
+	 * Then kick all VPs and drain by acquiring each vp_mutex.
+	 *
+	 * Root scheduler: disable_vp_dispatch() sets
+	 * HV_REGISTER_DISPATCH_SUSPEND, which causes any in-progress dispatch
+	 * hypercall to return. This is safe regardless of VP state because the
+	 * VP only executes while the kernel thread's dispatch hypercall is
+	 * active ? once it returns, the VP cannot run until re-dispatched,
+	 * which the frozen check prevents.
+	 *
+	 * Hyp scheduler: the VP runs independently in the hypervisor and must
+	 * be explicitly suspended from within its dispatch loop (via
+	 * mshv_suspend_vp()) when the kernel thread detects the frozen flag.
+	 * wake_up_all() unblocks the kernel thread so it can do so.
+	 */
+	for (i = 0; i < nr_ref; i++) {
+		/* Ref held; partition stays in hash and alive outside RCU */
+		scoped_guard(rcu)
+			partition = mshv_partition_find(ids[i]);
+
+		/* Barrier: wait for any in-flight create_vp to complete */
+		scoped_guard(mutex, &partition->pt_mutex) {}
+
+		for (bkt = 0; bkt < MSHV_MAX_VPS; bkt++) {
+			vp = partition->pt_vp_array[bkt];
+			if (!vp)
+				continue;
+
+			if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
+				disable_vp_dispatch(vp);
+
+			wake_up_all(&vp->run.vp_suspend_queue);
+		}
+
+		/*
+		 * Wait for every VP to finish its current ioctl. Taking the VP
+		 * mutex proves the VP is no longer inside run_vp.
+		 *
+		 * On Hyp-scheduler, prior mshv_suspend_vp() might have failed.
+		 * Since it's idempotent, we can safely re-issue and fail kexec
+		 * if suspend fails again. In this case, the caller is expected
+		 * to panic, so cleanup is unnecessary.
+		 */
+		for (bkt = 0; bkt < MSHV_MAX_VPS; bkt++) {
+			vp = partition->pt_vp_array[bkt];
+			if (!vp)
+				continue;
+
+			scoped_guard(mutex, &vp->vp_mutex) {
+				if (hv_scheduler_type != HV_SCHEDULER_TYPE_ROOT) {
+					bool mif;
+					int ret;
+
+					ret = mshv_suspend_vp(vp, &mif);
+					if (ret)
+						return ret;
+				}
+			}
+		}
+
+		/*
+		 * Tear down doorbell ports owned by the parent partition.
+		 * These survive child partition deletion and kexec, so the
+		 * new kernel would collide on port IDs if we leave them.
+		 */
+		mshv_eventfd_release(partition);
+
+		mshv_partition_put(partition);
+	}
+
+	/* Move non-ref'd IDs next to ref'd IDs to form a contiguous array */
+	if (nr_noref) {
+		memmove(&ids[nr_ref], &ids[nr_alloc - nr_noref],
+			nr_noref * sizeof(*ids));
+	}
+
+	*partition_ids = ids;
+	*nr_ids = nr_ref + nr_noref;
+
+	pr_info("Frozen %u partition(s) for kexec\n", nr_ref + nr_noref);
+	return 0;
+}
+
+/*
+ * Reboot notifier for the mshv_root module. Runs at higher priority than
+ * the built-in page-preservation notifier so that all VPs are frozen and
+ * partition IDs are handed off before the tree is serialized.
+ */
+static int mshv_root_reboot_cb(struct notifier_block *nb, unsigned long action,
+			       void *data)
+{
+	if (kexec_in_progress) {
+		u64 *partition_ids;
+		unsigned int nr_partition_ids;
+		int err;
+
+		err = mshv_freeze_and_get_partition_ids(&partition_ids,
+							&nr_partition_ids);
+		if (err)
+			panic("mshv_freeze_and_get_partition_ids() failed - must not kexec: %d\n",
+			      err);
+
+		mshv_set_frozen_partition_ids(partition_ids, nr_partition_ids);
+	}
+	return NOTIFY_OK;
+}
+
+static struct notifier_block mshv_root_reboot_notifier = {
+	.notifier_call = mshv_root_reboot_cb,
+	.priority = 1, /* higher than the built-in preserve notifier (0) */
+};
+
 static void
 remove_partition(struct mshv_partition *partition)
 {
@@ -1911,13 +2134,27 @@ mshv_partition_release(struct inode *inode, struct file *filp)
 static int
 add_partition(struct mshv_partition *partition)
 {
-	spin_lock(&mshv_root.pt_ht_lock);
+	guard(spinlock)(&mshv_root.pt_ht_lock);
+
+	/*
+	 * Reject new partitions once frozen. Note: there is a small window
+	 * where a concurrent create-ioctl has already called
+	 * hv_call_create_partition() but not yet reached here. If kexec fires
+	 * during that window, the caller's error-path
+	 * hv_call_delete_partition() may never execute and the empty partition
+	 * leaks in the hypervisor.
+	 *
+	 * No pages are deposited at that point, so only the hypervisor-internal
+	 * tracking is lost. Closing this fully would require reworking the
+	 * entire mshv-locking logic so that the frozen check and the hypervisor
+	 * create call happen atomically.
+	 */
+	if (mshv_root.frozen)
+		return -EBUSY;
 
 	hash_add_rcu(mshv_root.pt_htable, &partition->pt_hnode,
 		     partition->pt_id);
 
-	spin_unlock(&mshv_root.pt_ht_lock);
-
 	return 0;
 }
 
@@ -2316,6 +2553,55 @@ root_scheduler_deinit(void)
 	free_percpu(root_scheduler_output);
 }
 
+/**
+ * vacuum_stale_partitions() - Tear down partitions left by a prior kernel.
+ * @dev: device for logging
+ *
+ * After kexec the previous kernel's partitions are still alive in the
+ * hypervisor. Retrieve their IDs from the KHO-preserved FDT and finalize,
+ * withdraw, and delete each one so the deposited pages return to the free pool.
+ */
+static void __init vacuum_stale_partitions(struct device *dev)
+{
+	u64 *ids;
+	unsigned int nr;
+	int i, err;
+
+	err = mshv_retrieve_frozen_partition_ids(&ids, &nr);
+	if (err) {
+		dev_err(dev, "Failed to retrieve stale partition IDs: %d\n",
+			err);
+		return;
+	}
+
+	for (i = 0; i < nr; i++) {
+		dev_info(dev, "Cleaning up stale partition %llu\n",
+			 ids[i]);
+
+		err = hv_call_finalize_partition(ids[i]);
+		if (err == -EINVAL) {
+			dev_info(dev, "partition %llu already gone\n",
+				 ids[i]);
+			continue;
+		}
+		if (err)
+			dev_warn(dev, "finalize partition %llu failed: %d\n",
+				 ids[i], err);
+
+		err = hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, ids[i]);
+		if (err)
+			dev_warn(dev, "withdraw memory %llu failed: %d\n",
+				 ids[i], err);
+
+		err = hv_call_delete_partition(ids[i]);
+		if (err)
+			dev_warn(dev, "delete partition %llu failed: %d\n",
+				 ids[i], err);
+	}
+
+	kho_restore_free(ids);
+}
+
 static int __init mshv_init_vmm_caps(struct device *dev)
 {
 	int ret;
@@ -2372,10 +2658,16 @@ static int __init mshv_parent_partition_init(void)
 	if (ret)
 		goto synic_cleanup;
 
-	ret = root_scheduler_init(dev);
+	vacuum_stale_partitions(dev);
+
+	ret = register_reboot_notifier(&mshv_root_reboot_notifier);
 	if (ret)
 		goto synic_cleanup;
 
+	ret = root_scheduler_init(dev);
+	if (ret)
+		goto unregister_reboot;
+
 	ret = mshv_debugfs_init();
 	if (ret)
 		goto deinit_root_scheduler;
@@ -2395,6 +2687,8 @@ static int __init mshv_parent_partition_init(void)
 	mshv_debugfs_exit();
 deinit_root_scheduler:
 	root_scheduler_deinit();
+unregister_reboot:
+	unregister_reboot_notifier(&mshv_root_reboot_notifier);
 synic_cleanup:
 	mshv_synic_exit();
 device_deregister:
@@ -2410,6 +2704,7 @@ static void __exit mshv_parent_partition_exit(void)
 	misc_deregister(&mshv_dev);
 	mshv_irqfd_wq_cleanup();
 	root_scheduler_deinit();
+	unregister_reboot_notifier(&mshv_root_reboot_notifier);
 	mshv_synic_exit();
 }
 
-- 
2.43.0



From dongtai.guo at linux.dev  Thu May 28 06:26:53 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Thu, 28 May 2026 21:26:53 +0800
Subject: [PATCH 1/7] LoongArch: Add KHO basic support
In-Reply-To: <2vxztsrv5r6y.fsf@kernel.org>
References: <2vxztsrv5r6y.fsf@kernel.org>
Message-ID: <20260528132653.190981-1-dongtai.guo@linux.dev>

On Mon, May 25 2026, Pratyush Yadav wrote:

Hi Pratyush,

> Do you have nothing else for a bootloader to pass data to the kernel?
> Passing this via the command line seems crazy... All these addresses are
> now available directly to unprivileged userspace. Can't you use the
> device tree like we do on ARM64?
>
> Also, this commandline parameter isn't documented anywhere.

I have tried the FDT /chosen approach, storing the KHO metadata as
linux,kho-fdt and linux,kho-scratch properties (the same properties
that drivers/of/kexec.c:kho_add_chosen() writes
and drivers/of/fdt.c:early_init_dt_check_kho() reads).

This passes the KHO selftest when the system boots with FDT.

I am currently debugging the ACPI boot case.

Thanks for your advice!

--
Regards,
George Guo


From pratyush at kernel.org  Thu May 28 07:20:53 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Thu, 28 May 2026 16:20:53 +0200
Subject: [PATCH v6 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <20260527202737.1345192-4-pasha.tatashin@soleen.com> (Pasha
	Tatashin's message of "Wed, 27 May 2026 20:27:35 +0000")
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
	<20260527202737.1345192-4-pasha.tatashin@soleen.com>
Message-ID: <2vxzv7c739a2.fsf@kernel.org>

On Wed, May 27 2026, Pasha Tatashin wrote:

> During the reboot() syscall, user processes may still be running
> concurrently and attempting to mutate sessions (e.g., creating,
> retrieving, or releasing sessions). To prevent this, introduce
> luo_session_serialize_rwsem to synchronize mutations with the
> serialization process.
>
> All session mutation operations (create, retrieve, release, ioctl) take
> the read lock. The serialization process (luo_session_serialize) takes
> the write lock and holds it indefinitely on success. This effectively
> freezes the LUO session subsystem during the transition to the new
> kernel. If serialization fails, the lock is released to allow recovery.
>
> Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_session.c | 56 +++++++++++++++++++++++++++++++--
>  1 file changed, 53 insertions(+), 3 deletions(-)
>
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 169131642939..c9bdad2e5ae7 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -46,6 +46,38 @@
>   * 4.  Retrieval: A userspace agent in the new kernel can then call
>   *     `luo_session_retrieve()` with a session name to get a new file
>   *     descriptor and access the preserved state.
> + *
> + * Locking:
> + *
> + * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
> + * safety and prevent deadlocks during concurrent session mutations and kexec
> + * serialization:
> + *
> + * 1. `luo_session_serialize_rwsem` (global rwsem):
> + *    Protects session mutations (creation, retrieval, release, and ioctls)
> + *    against the serialization process during reboot.
> + *
> + *    - Readers: Taken by any path modifying or accessing session state (e.g.,
> + *      `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
> + *      and `luo_session_ioctl()`).
> + *    - Writer: Taken by the serialization process (`luo_session_serialize()`)
> + *      during reboot. On success, the write lock is held indefinitely to freeze
> + *      the subsystem. On failure, it is released to allow recovery.
> + *
> + * 2. `luo_session_header->rwsem` (per-list rwsem):
> + *    Synchronizes list-level operations for the incoming and outgoing session headers.
> + *
> + *    - Writer: Taken during list mutation operations (inserting or removing a
> + *      session from the list).
> + *    - Reader: Taken when traversing the list (e.g., retrieving a session by name).
> + *
> + * 3. `luo_session->mutex` (per-session mutex):
> + *    Protects the internal state and file sets of an individual session. It is
> + *    acquired during per-session operations such as preserving, retrieving,
> + *    or freezing files.
> + *
> + * Lock Hierarchy:
> + *   `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
>   */
>  
>  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> @@ -75,6 +107,13 @@
>  		sizeof(struct luo_session_header_ser)) /		\
>  		sizeof(struct luo_session_ser))
>  
> +/*
> + * Protects session mutations during serialization. All session mutation
> + * operations must hold the read lock. The serialization process holds the write
> + * lock indefinitely on success to block all concurrent and future mutations.
> + */

Nit: this comment is redundant now. I guess you can remove this when
applying.

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>

> +static DECLARE_RWSEM(luo_session_serialize_rwsem);
> +
>  /**
>   * struct luo_session_header - Header struct for managing LUO sessions.
>   * @count:      The number of sessions currently tracked in the @list.
[...]

-- 
Regards,
Pratyush Yadav


From pratyush at kernel.org  Thu May 28 07:22:07 2026
From: pratyush at kernel.org (Pratyush Yadav)
Date: Thu, 28 May 2026 16:22:07 +0200
Subject: [PATCH] liveupdate: document current compatibility status
In-Reply-To: <20260527153725.1770394-1-luca.boccassi@gmail.com> (luca
	boccassi's message of "Wed, 27 May 2026 16:37:12 +0100")
References: <20260527153725.1770394-1-luca.boccassi@gmail.com>
Message-ID: <2vxzqzmv3980.fsf@kernel.org>

On Wed, May 27 2026, luca.boccassi at gmail.com wrote:

> From: Luca Boccassi <luca.boccassi at gmail.com>
>
> We currently do not guarantee compatibility due to ongoing
> development, so mention this explicitly in the user-facing
> documentation.
>
> Signed-off-by: Luca Boccassi <luca.boccassi at gmail.com>

Acked-by: Pratyush Yadav <pratyush at kernel.org>

[...]

-- 
Regards,
Pratyush Yadav


From rppt at kernel.org  Thu May 28 07:51:47 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Thu, 28 May 2026 17:51:47 +0300
Subject: [PATCH v6 3/5] liveupdate: block session mutations during reboot
In-Reply-To: <2vxzv7c739a2.fsf@kernel.org>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
 <20260527202737.1345192-4-pasha.tatashin@soleen.com>
 <2vxzv7c739a2.fsf@kernel.org>
Message-ID: <ahhWgyYC-OJwBYgq@kernel.org>

On Thu, May 28, 2026 at 04:20:53PM +0200, Pratyush Yadav wrote:
> On Wed, May 27 2026, Pasha Tatashin wrote:
> 
> > During the reboot() syscall, user processes may still be running
> > concurrently and attempting to mutate sessions (e.g., creating,
> > retrieving, or releasing sessions). To prevent this, introduce
> > luo_session_serialize_rwsem to synchronize mutations with the
> > serialization process.
> >
> > All session mutation operations (create, retrieve, release, ioctl) take
> > the read lock. The serialization process (luo_session_serialize) takes
> > the write lock and holds it indefinitely on success. This effectively
> > freezes the LUO session subsystem during the transition to the new
> > kernel. If serialization fails, the lock is released to allow recovery.
> >
> > Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
> > Reported-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
> > Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  kernel/liveupdate/luo_session.c | 56 +++++++++++++++++++++++++++++++--
> >  1 file changed, 53 insertions(+), 3 deletions(-)
> >  
> > +/*
> > + * Protects session mutations during serialization. All session mutation
> > + * operations must hold the read lock. The serialization process holds the write
> > + * lock indefinitely on success to block all concurrent and future mutations.
> > + */
> 
> Nit: this comment is redundant now. I guess you can remove this when
> applying.

Done :)
 
> Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
> 
> > +static DECLARE_RWSEM(luo_session_serialize_rwsem);
> > +
> >  /**
> >   * struct luo_session_header - Header struct for managing LUO sessions.
> >   * @count:      The number of sessions currently tracked in the @list.
> [...]
> 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Thu May 28 08:08:02 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Thu, 28 May 2026 18:08:02 +0300
Subject: [PATCH] liveupdate: document current compatibility status
In-Reply-To: <20260527153725.1770394-1-luca.boccassi@gmail.com>
References: <20260527153725.1770394-1-luca.boccassi@gmail.com>
Message-ID: <177998087846.707577.12006390274464324360.b4-ty@b4>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

On Wed, 27 May 2026 16:37:12 +0100, luca.boccassi at gmail.com wrote:
> We currently do not guarantee compatibility due to ongoing
> development, so mention this explicitly in the user-facing
> documentation.

Applied to next branch of liveupdate/linux.git tree, thanks!

[1/1] liveupdate: document current compatibility status
      commit: d72c0a7b321cfe3a3cb510c599fd5e9cf3df24fc

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: next

--
Sincerely yours,
Mike.



From rppt at kernel.org  Thu May 28 08:08:03 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Thu, 28 May 2026 18:08:03 +0300
Subject: [PATCH v6 0/5] liveupdate: serialization safety and race fixes
In-Reply-To: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
References: <20260527202737.1345192-1-pasha.tatashin@soleen.com>
Message-ID: <177998087859.707577.17373849853815129202.b4-ty@b4>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

On Wed, 27 May 2026 20:27:32 +0000, Pasha Tatashin wrote:
> This series addresses several issues related to the synchronization
> between the reboot process and LUO session management.
> 
> 1. Skip LUO serialization for context-preserving kexec: A
> preserve_context kexec returns to the current kernel, which is unrelated
> to live update where state is passed to the next kernel. Skipping
> serialization avoids unnecessary work and prevents sessions from being
> left in a frozen state upon return.
> 
> [...]

Applied to next branch of liveupdate/linux.git tree, thanks!

[1/5] liveupdate: skip serialization for context-preserving kexec
      commit: ef4448795cf193b2828019978d872bc0d301abe4
[2/5] liveupdate: fix TOCTOU race in luo_session_retrieve()
      commit: aedeca39112a2bee13e54097fbb98896010f375f
[3/5] liveupdate: block session mutations during reboot
      commit: 6f070471ce4fc9084165b77bfd7457ef85dc8c8c
[4/5] liveupdate: fix u-a-f in luo_file_unpreserve_files() and luo_file_finish()
      commit: 051c2ed7b42077c511654261f5efb52ab368817f
[5/5] liveupdate: Remove unused ser field from struct luo_session
      commit: 5428435567cbe06c19914592fc22ca23c9ca1de5

tree: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux
branch: next

--
Sincerely yours,
Mike.



From tarunsahu at google.com  Thu May 28 10:20:30 2026
From: tarunsahu at google.com (tarunsahu at google.com)
Date: Thu, 28 May 2026 17:20:30 +0000
Subject: [RFC PATCH v1 0/8] liveupdate: kvm: Guest_memfd preservation
In-Reply-To: <cover.1779080766.git.tarunsahu@google.com>
References: <cover.1779080766.git.tarunsahu@google.com>
Message-ID: <9huzwlwnbgdd.fsf@tarunix.c.googlers.com>

Tarun Sahu <tarunsahu at google.com> writes:


Hi,

Based on the discussion on Bi-weekly guest_memfd upstream call.

The following suggestions are made:
1. Remove the prefaulted condition and During preservation, let the VMM
   have the policy to either pause the VM or avoid any upcoming faults/conversion
   (conversion is not part of this series, but design concept for future
   extension) occurs. If new fault/conversion requests are originated,
   Kernel will return error to notify VMM.

   In future, We will revisit if this functionality can be extended
   to handle post-preserve fault/conversion with having data on preservation
   latency and complexity. Pratyush is already working on implementing
   similar thing for memfd and hugetlb preservation.

2. We are going with preserving the vm file along with guest_memfd file

3. No need to preserve kvm->mem_attr_array, as this is on deprecating
   path and For this series, where only fully shared guest_memfd is
   supported for liveupdate, preservation of these attributes not
   required.

Due to time constraints:
we have one open Question left:
1. How to check guest_memfd is fully shared. Currently that is being
   done by INIT_SHARED flag. But with in-place it is going to change
   its meaning and it will mean only durng the creation of guest_memfd
   it is shared whose folios can be converted to private.
   So for this series, I am looking for suggestion, what is the best way
   to find the sharedness of guest_memfd which will be compatible with
   in-place series. OR we can go like this (my personnel recommendation):

   if (guest_memfd_is_fully_shared())
      return true; // support the preservation

   // This series
   int guest_memfd_is_fully_shared(void) {
       if (guest_memfd->flags & GUEST_MEMFD_FLAG_INIT_SHARED)
          return true;
         return false;
   }

   TO::
   // Once in-place conversion series lands.
   int guest_memfd_is_fully_shared(void) {
       if (No Private flags in (guest_memfd->attributes))
          return true;
         return false;
   }

~ Tarun


> Hello,
>
> I am proposing this series as RFC, to initiate the discussion for
> supporting the guest_memfd preservation. This will setup basic arhitecture
> for VM preservation during liveupdate. This Cover letter has three
> sections (please feel free to skip the on you already know):
>
> A. Guest_memfd introduction:
> To make the audience familiar with guest_memfd
> B. Liveupdate introduction:
> To make the audience familiar with liveupdate
> C. Actual Implementation Design and questions.
>
> **GUEST MEMFD INTRODUCTION**
>
> Initially, guest_memfd was created to support guest private memory in
> confidential computing VMs (CoCo VMs). It was designed so that whenever
> a guest wants to grant the host access to private memory, a series of
> calls occurs: from the guest to KVM, KVM to the host userspace, host
> userspace back to KVM, and finally a new page fault maps the memory into
> a separate shared address space. Conversely, if the guest transitions the
> memory back to private, the subsequent fault is handled by guest_memfd.
> (Dual Mapping Architecture). In such a VM, all guest memory is initially
> shared. On the fly, the guest may request to change pages to private; the
> metadata indicating which parts of memory are private is stored in an
> xarray inside struct kvm (mem_attr_array). This array serves as the source
> of truth for the fault mechanism, determining whether a mapping should be
> created from host-userspace-mapped pages or directly from the guest_memfd
> file. For private memory, Fault also calls architecture-specific function
> to set up private hardware access (e.g., on SEV-SNP or TDX). This type of
> guest_memfd is fully-private where shared mapping comes from userspace
> mapped address space.
>
> Subsequently, support was added to allow the entire guest memory to be
> backed by guest_memfd. This led to the implementation of the MMAP and
> INIT_SHARED flags for the guest_memfd inode. When KVM_CREATE_GUEST_MEMFD
> is called with these flags, the guest_memfd becomes mmap-able by host
> userspace. The INIT_SHARED flag is used to make the guest_memfd completely
> shared between the host and the guest. Consequently, page faults from both
> host userspace and the guest resolve to the same guest_memfd page cache.
> However, under this configuration, marking a portion of this memory as
> private is not possible. This type of guest_memfd is fully-shared.
>
> If guest_memfd is created with INIT_SHARED without MMAP, the host
> can never access the guest_memfd. But the memory is still considered
> shared.
>
> Hence, At this point, Only use-case of guest_memfd is either fully-shared
> or fully-private.
>
> There is ongoing work to make shared and private mapping in-place backed
> by guest_memfd. [1] There is also ongoing work to back guest_memfd by
> hugetlb pages. [2]
>
> **LIVEUPDATE INTRODUCTION (LIVEUPDATE ORCHESTRATOR - LUO)**
>
> Livepdate support was added in kernel to update the host kernel by
> minimizing the downtime to minimal. This is generally achieved by
> preserving the current state of the system and retrieve after boot to
> resume from where we left it.
>
> Any subsystem that wants to preserve themselves, register their handler
> with liveupdate system. This handler includes calls to the following
>
> *can_preserve (file)*:
> This tells the luo system about the eligibility of the file. When
> preserve ioctl is called, it first loop through all the file handlers
> and call can_preserve, the one which return true, luo uses this file
> handler fh->preserve call to preserve the file.
>
> *preserve(file)*:
> This actually preserves the file.
>
> *unpreserve(file)*:
> This unpreserve the file incase userspace want to go back.
>
> *retrieve(file)*:
> On new kernel boot, this function retrieves the file.
>
> *finish(file)*:
> When userspace decides that all the files in the liveupdate session has
> been retrieved, it can trigger this to do final work of cleaning up.
>
> LUO preserve its memory using KHO (kexec-handover). All these APIs will
> be implemented using KHO calls.
>
> **GUEST MEMFD PRESERVATION**
>
> This patch sets up the basic infrastructure to preserve the guest_memfd.
> Currently this supports only fully-shared, pre-faulted guest_memfd
> (INIT_SHARED) backed by PAGE_SIZE pages.
>
> It registers a new LUO file handler for guest_memfd file to serialize
> and deserialize guest memory. This allows preserving guest memory backed
> by guest_memfd across updates, ensuring that guest instances can be
> resumed seamlessly without losing their memory contents.
>
> The preservation call is straight forward. It walks through the page
> cache, serialize the folios and preserve them.
>
> On the retrieval path:
> Currently, creating a guest_memfd requires an associated struct kvm
> (derived from vm_file / vm_fd). Since there is no direct way to pass a
> VM file descriptor via the LUO API, we considered two main approaches:
>
> Approach (1)
> Split the KVM_CREATE_GUEST_MEMFD ioctl into two separate ioctl: one
> to create the guest_memfd without a VM file (without struct kvm)
> descriptor, and another to attach a newly created VM file descriptor to
> a retrieved guest_memfd.
>
> Introducing a new ioctl is in itself a problem (UAPI). Currently, a
> guest_memfd file belongs to a single VM. Decoupling creation and
> attachment could allow a guest_memfd to be attached to any VM, or shared
> among multiple VMs when passed at different offsets. Fully supporting
> this feature would require extensive work, and it is unclear if there
> are any non-LUO use cases that justify this complexity.
> There is related work going on here [4], but not exactly same. It still
> does not allow guest_memfd to be created without vm_fd. But there be
> other ways to use it, I would like to discuss the idea.
>
> Approach (2)
> Leverage a companion patch [3] (Also added as part of this series
> PATCH[1]) that allows one file to retrieve another file from the same LUO
> session. This enables the guest_memfd retrieval path to obtain the
> preserved KVM file, use it during guest_memfd file creation, and
> subsequently populate its preserved memory.
>
> Preserving the KVM file allows us to preserve additional VM-specific
> metadata, which will be crucial in the future for cleanly resuming the
> VM. Currently, it preserves only the VM type and kvm->mem_attr_array.
>
> Though the ongoing in-place sharing series [1] transfers attributes to
> the guest_memfd file, But preserving the kvm file opens the opportunity
> to preserve other VM state in future like registers state, vCPU etc.
>
> Having the extensive usecases for preserving the kvm file, I went
> ahead with Approach (2). In future, if approach (1) become possible, it
> can easily be integrated with approach (2).
>
> Following the first approach (preserving vm_fd along with guest_memfd),
>
> ** VM FILE LIVEUPDATE ** PATCH[3] && [4]
>
> *PATCH[3]* has refactored few functions to support kvm preservation.
> During retrieval, vm_file needs to be recreated which will require kvm
> api. This patch exports those APIs. There is a new addition to struct
> kvm, vm_file. Which will be used by guest_memfd. I will discuss about
> this later.
>
> *PATCH[4]*
> The preservation of the vm file is straightforward.
>
> On the retrieval path:
> KVM normally requires a unique identifier (fdname) upon creation,
> which KVM typically assigns based on the newly created file descriptor
> number. However, in the LUO retrieval path, the retrieve call restores
> the underlying file structure and delegates actual file descriptor
> allocation to LUO (check luo_session_retrieve_fd). Currently, I used an
> atomically incremented sequence number as the fdname. I would like to
> discuss whether userspace services rely on specific naming conventions
> here. Or if we can change underlying the retrieve call
> (luo_retrieve_file) to pass fd?
>
> **GUEST_MEMFD FILE LIVEUPDATE** PATCH[5], [6] & [7]
>
> *PATCH[5]*
> During retrieval of guest_memfd file, for its creation, this patch has
> exported APIs from guest_memfd.c to be used for guest_memfd_luo.c
>
> *PATCH[6]*
> This patch implements the API for gmem inode freeze, which freeze the
> fallocate operation on this inode. Freeze check can be extended in
> future to prevent new page faults as well, when liveupdate support
> for non-pre-faulted guest_memfd will be implemented.
>
> *PATCH[7]*
> Preservation Path:
> We have discussed about this before,
> I would like to add to that and discuss here a major design decision:
> "Preservation order in between VM File and guest_memfd file"
>
> Preservation Ordering is required because guest_memfd needs to store
> vm file token as one of its data, which it can use during retrieval to
> get the vm file and use (file->private_data: struct kvm ) for its
> creation using [3]. So KVM file must be preserved before guest_memfd
> file, so that guest_memfd preserve call can find vm file token from the
> same luo session.
>
> Currently My preservation implementation does not require any strict
> ordering, they can be preserved in any sequence from userspace. I
> achieved this by implementing the freeze call for guest_memfd which
> gets run at the end just before kexec. This call freeze the luo session
> and no further changes can be done to the session. Inside guest_memfd
> luo_freeze handler, I update the token for vm_file. Which enable us to
> preserve the vm file and guest_memfd file in any order.
>
> The drawback is, incase vm_file is not preserved, freeze will fail. And
> in enforcing the preserving order fails the guest_memfd preservation
> from the start. As with VM preservation will evolve in future, it will
> keep getting complicated so avoiding the preservation order should be
> the better choice to make the userspace simpler. I would be happy to
> disucss on this further.
>
> To get the token, we need the vm_file and there is no way to get the
> vm_file from the struct kvm, as guest_memfd file only store the
> struct kvm. I have introduced a new member in struct kvm, vm_file.
> But with weak circular dependency as it is just to get the pointer
> for the file. we don't want to keep the reference of the file as vm_file
> takes for the kvm to keep itself (vm_file) alive. So whenever there is a
> need to use of kvm->vm_file, we take the reference and drop it suddenly.
>
> Retrieval Path:
> During retrieval path, we just retrieve the data from kho and populate
> into the newly created guest_memfd.
> To create guest_memfd itself, it needs struct kvm, as we discussed
> above, which will come from vm_file, hence retrieval order is needed
> here. VM file needs to be retrieved first before guest_memfd.
>
> To handle this situation, I had three approaches in mind with their own
> pros and cons:
>
> Approach (1):
> Use [3], retrieve internally using liveupdate_get_file_incoming which
> inherently retrieves the file incase it was not retrieved by the
> userspace already. But this creates an scenerio, that userspace might
> call luo_finish which will drop all the references of vm_file (and
> userspace not holding any as it has not retrieved it yet explicitly).
> And vm_file will get released. But this is a valid situation as when vm
> is going to be put down. Userspace can close the vm_fd and have
> guest_memfd yet opened and so other user of struct kvm like vCPUs etc.
> Only thing, this makes retrieved guest_memfd unusable unless, there is
> a mechanism to link to another VM (Nope).
> This leaves us with following situation:
> 	(A): As it is a valid situation, We can leave it as it, No
> 	retrieval order enforcement.
> 	(B): We can implement can_finish to check if userspace has
> 	retrieved the vm_file, otherwise can stop luo_finish from
> 	succeeding, but I did not find a way to implement such check.
> Approach (2):
> Enforce the strict order, by implementing a new call which will first
> check whether the vm_file is retrieved or not, if not, it will not
> retrieve it internally and retrurn err to the caller which is
> guest_memfd retrieve function in this case. So guest_memfd can report
> the userspace about this error.
>
> I have implemented Approach (1)(A), as it is a valid case, and does not
> enforce any retrieve order on userspace, which relieves the burden from
> the userspace when vm_file preservation will evolve. But userspace is
> now expected to retrieve the vm_file before calling luo_finish or
> guest_memfd will become unusable. As per LUO philosphy, It is userspace
> error.
>
> **KERNEL SELFTEST FOR POC** PATCH[8] & [9]
>
> *PATCH[8]* refactor kvm selftest framework to expose some raw apis to
> setup the VM.
> *PATCH[9]* implements the basic test, where it spawn a VM with guest_memfd
> or 16MB and fault it completely and write data to its 5MB portion. After
> LUO preserve call, and kexec, On retrieve, a new VM is spawn with the
> restored vm_file and restored guest_memfd and the data is verified.
>
> I will update this test in the next version to use the liveupdate
> selftests library [5].
>
> Future Work:
> 1. To support preservation for non-prefaulted guest_memfd to save memory
> in KHO. (Already working on this, will post another series soon)
> 2. Support private guest_memfd preservation.
> 3. Extend the support for guest_memfd with in-place conversion of
> shared/private.
>
> [1] https://lore.kernel.org/all/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4 at google.com/
> [2] https://lore.kernel.org/all/cover.1747264138.git.ackerleytng at google.com/
> [3] https://lore.kernel.org/all/20260427175633.1978233-2-skhawaja at google.com/
> [4] https://lore.kernel.org/all/cover.1691446946.git.ackerleytng at google.com/
> [5] https://lore.kernel.org/all/20260511201155.1488670-1-vipinsh at google.com/
>
> Pasha Tatashin (1):
>   liveupdate: luo_file: Add internal APIs for file preservation
>
> Tarun Sahu (8):
>   liveupdate: Add LIVEUPDATE_GUEST_MEMFD config option
>   kvm: Prepare core VM structs and helpers for LUO support
>   kvm: kvm_luo: Allow kvm preservation with LUO
>   kvm: guest_memfd: Move internal definitions and helper to new header
>   kvm: guest_memfd: Add support for freezing and unfreezing mappings
>   kvm: guest_memfd_luo: add support for guest_memfd preservation
>   selftests: kvm: Split ____vm_create() to expose init helpers
>   selftests: kvm: Add guest_memfd_preservation_test
>
>  MAINTAINERS                                   |  13 +
>  include/linux/kho/abi/kvm.h                   | 121 +++++
>  include/linux/kvm_host.h                      |  14 +
>  include/linux/liveupdate.h                    |  21 +
>  kernel/liveupdate/Kconfig                     |  15 +
>  kernel/liveupdate/luo_file.c                  |  69 +++
>  kernel/liveupdate/luo_internal.h              |  17 +
>  tools/testing/selftests/kvm/Makefile.kvm      |   2 +
>  .../kvm/guest_memfd_preservation_test.c       | 285 ++++++++++
>  .../testing/selftests/kvm/include/kvm_util.h  |   2 +
>  tools/testing/selftests/kvm/lib/kvm_util.c    |  26 +-
>  virt/kvm/Makefile.kvm                         |   1 +
>  virt/kvm/guest_memfd.c                        | 180 +++++--
>  virt/kvm/guest_memfd.h                        |  44 ++
>  virt/kvm/guest_memfd_luo.c                    | 495 ++++++++++++++++++
>  virt/kvm/kvm_luo.c                            | 346 ++++++++++++
>  virt/kvm/kvm_main.c                           |  79 ++-
>  virt/kvm/kvm_mm.h                             |   3 +
>  18 files changed, 1653 insertions(+), 80 deletions(-)
>  create mode 100644 include/linux/kho/abi/kvm.h
>  create mode 100644 tools/testing/selftests/kvm/guest_memfd_preservation_test.c
>  create mode 100644 virt/kvm/guest_memfd.h
>  create mode 100644 virt/kvm/guest_memfd_luo.c
>  create mode 100644 virt/kvm/kvm_luo.c
>
>
> base-commit: 5200f5f493f79f14bbdc349e402a40dfb32f23c8
> -- 
> 2.54.0.563.g4f69b47b94-goog


From dmatlack at google.com  Thu May 28 10:41:38 2026
From: dmatlack at google.com (David Matlack)
Date: Thu, 28 May 2026 17:41:38 +0000
Subject: [PATCH 0/2] liveupdate: Small FLB fixes
Message-ID: <20260528174140.1921129-1-dmatlack@google.com>

This series has 2 small fixes to how FLBs are managed. First is to
increase the outgoing FLB refcount during liveupdate_flb_get_outgoing()
so it cannot be freed while the caller is using it, and to align with
the semantics of liveupdate_flb_get_incoming(). The second is to prevent
FLB retrieve() from being called multiple times if the first attempt
fails.

Both of these changes are needed for the correctness of the PCI core
support for Live Update:

  https://lore.kernel.org/linux-pci/20260522202410.3104264-1-dmatlack at google.com/


David Matlack (2):
  liveupdate: Reference count outgoing FLB data
  liveupdate: Remember FLB retrieve() status

 include/linux/liveupdate.h  | 11 +++++++++--
 kernel/liveupdate/luo_flb.c | 20 ++++++++++++++------
 2 files changed, 23 insertions(+), 8 deletions(-)


base-commit: 5428435567cbe06c19914592fc22ca23c9ca1de5
-- 
2.54.0.823.g6e5bcc1fc9-goog



From dmatlack at google.com  Thu May 28 10:41:39 2026
From: dmatlack at google.com (David Matlack)
Date: Thu, 28 May 2026 17:41:39 +0000
Subject: [PATCH 1/2] liveupdate: Reference count outgoing FLB data
In-Reply-To: <20260528174140.1921129-1-dmatlack@google.com>
References: <20260528174140.1921129-1-dmatlack@google.com>
Message-ID: <20260528174140.1921129-2-dmatlack@google.com>

Increment the outgoing FLB refcount in liveupdate_flb_get_outgoing() so
that the FLB structure cannot be freed while the caller is actively
using it. Add an additional liveupdate_flb_put_outgoing() function so
the caller can explicitly indicate when it is done using the outgoing
FLB.

During a Live Update, the kernel may need to fetch the outgoing FLB
outside of the scope of a file handler's preserve() and unpreserve()
callbacks. In that situation there is no way for the caller to protect
itself against the outgoing FLB from being freed while it is using it.
Incrementing the reference count in liveupdate_flb_get_outgoing()
ensures it cannot be freed.

This change also aligns the outgoing FLB lifecycle management with the
incoming FLB, since the latter uses the same get/put semantics.

Fixes: cab056f2aae7 ("liveupdate: luo_flb: introduce File-Lifecycle-Bound global state")
Assisted-by: Gemini:gemini-3-pro-preview
Signed-off-by: David Matlack <dmatlack at google.com>
---
 include/linux/liveupdate.h  |  5 +++++
 kernel/liveupdate/luo_flb.c | 10 +++++++---
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 88722e5caf02..c344bf987b63 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -243,6 +243,7 @@ int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp);
 void liveupdate_flb_put_incoming(struct liveupdate_flb *flb);
 
 int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp);
+void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb);
 
 #else /* CONFIG_LIVEUPDATE */
 
@@ -292,5 +293,9 @@ static inline int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb,
 	return -EOPNOTSUPP;
 }
 
+static inline void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb)
+{
+}
+
 #endif /* CONFIG_LIVEUPDATE */
 #endif /* _LINUX_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 8f5c5dd01cd0..7ddef552ff6b 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -135,7 +135,7 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
 	return 0;
 }
 
-static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
+void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb)
 {
 	struct luo_flb_private *private = luo_flb_get_private(flb);
 
@@ -266,7 +266,7 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh)
 
 exit_err:
 	list_for_each_entry_continue_reverse(iter, flb_list, list)
-		luo_flb_file_unpreserve_one(iter->flb);
+		liveupdate_flb_put_outgoing(iter->flb);
 	up_read(&luo_register_rwlock);
 
 	return err;
@@ -291,7 +291,7 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh)
 
 	guard(rwsem_read)(&luo_register_rwlock);
 	list_for_each_entry_reverse(iter, flb_list, list)
-		luo_flb_file_unpreserve_one(iter->flb);
+		liveupdate_flb_put_outgoing(iter->flb);
 }
 
 /**
@@ -546,6 +546,10 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
 		return -EOPNOTSUPP;
 
 	guard(mutex)(&private->outgoing.lock);
+	if (!private->outgoing.obj)
+		return -ENOENT;
+
+	refcount_inc(&private->outgoing.count);
 	*objp = private->outgoing.obj;
 
 	return 0;
-- 
2.54.0.823.g6e5bcc1fc9-goog



From dmatlack at google.com  Thu May 28 10:41:40 2026
From: dmatlack at google.com (David Matlack)
Date: Thu, 28 May 2026 17:41:40 +0000
Subject: [PATCH 2/2] liveupdate: Remember FLB retrieve() status
In-Reply-To: <20260528174140.1921129-1-dmatlack@google.com>
References: <20260528174140.1921129-1-dmatlack@google.com>
Message-ID: <20260528174140.1921129-3-dmatlack@google.com>

LUO keeps track of successful retrieve attempts on an FLB. It does so
to avoid multiple retrievals of the same FLB. Multiple retrievals cause
problems because once the FLB is retrieved, the serialized data
structures are likely freed and the FLB is likely in a very different
state from what the code expects.

All this works well when retrieve succeeds. When it fails,
luo_flb_retrieve_one() returns the error immediately, without ever
storing anywhere that a retrieve was attempted or what its error code
was. If the user attempts to retrieve another file registered with the
same FLB, LUO will attempt to call the FLB's retrieve() callback again.

The retry is problematic for much of the same reasons listed above. The
FLB is likely in a very different state than what the retrieve logic
normally expects (e.g. some KHO pages may have already been restored and
freed).

There is no sane way of attempting the retrieve again. Remember the
error retrieve returned and directly return it on a retry.

This is done by changing the retrieved bool to a retrieve_status
integer. A value of 0 means retrieve was never attempted, a positive
value means it succeeded, and a negative value means it failed and the
error code is the value.

This is similar to commit f85b1c6af5bc ("liveupdate: luo_file: remember
retrieve() status") which did the same for LUO files.

Fixes: cab056f2aae7 ("liveupdate: luo_flb: introduce File-Lifecycle-Bound global state")
Assisted-by: Gemini:gemini-3-pro-preview
Signed-off-by: David Matlack <dmatlack at google.com>
---
 include/linux/liveupdate.h  |  6 ++++--
 kernel/liveupdate/luo_flb.c | 10 +++++++---
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index c344bf987b63..63ea5417de84 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -173,7 +173,9 @@ struct liveupdate_flb_ops {
  * @lock:      A mutex that protects all fields within this structure, providing
  *             the synchronization service for the FLB's ops.
  * @finished:  True once the FLB's finish() callback has run.
- * @retrieved: True once the FLB's retrieve() callback has run.
+ * @retrieve_status: Status code indicating whether retrieve() has been
+ *                   attempted. 0 means not attempted, 1 means successful,
+ *                   and negative value means it failed with that error code.
  */
 struct luo_flb_private_state {
 	refcount_t count;
@@ -181,7 +183,7 @@ struct luo_flb_private_state {
 	void *obj;
 	struct mutex lock;
 	bool finished;
-	bool retrieved;
+	int retrieve_status;
 };
 
 /*
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 7ddef552ff6b..f8852f7e62e5 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -170,7 +170,10 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 	if (private->incoming.finished)
 		return -ENODATA;
 
-	if (private->incoming.retrieved)
+	if (private->incoming.retrieve_status < 0)
+		return private->incoming.retrieve_status;
+
+	if (private->incoming.retrieve_status > 0)
 		return 0;
 
 	if (!fh->active)
@@ -196,12 +199,13 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 
 	err = flb->ops->retrieve(&args);
 	if (err) {
+		private->incoming.retrieve_status = err;
 		module_put(flb->ops->owner);
 		return err;
 	}
 
 	private->incoming.obj = args.obj;
-	private->incoming.retrieved = true;
+	private->incoming.retrieve_status = 1;
 
 	return 0;
 }
@@ -215,7 +219,7 @@ void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)
 		if (!refcount_dec_and_test(&private->incoming.count))
 			return;
 
-		if (!private->incoming.retrieved) {
+		if (private->incoming.retrieve_status <= 0) {
 			int err = luo_flb_retrieve_one(flb);
 
 			if (WARN_ON(err))
-- 
2.54.0.823.g6e5bcc1fc9-goog



From stephen.s.brennan at oracle.com  Thu May 28 11:37:48 2026
From: stephen.s.brennan at oracle.com (Stephen Brennan)
Date: Thu, 28 May 2026 11:37:48 -0700
Subject: [PATCH v5][makedumpfile 0/9] btf/kallsyms based makedumpfile
 extension for mm page filtering
In-Reply-To: <20260414102656.55200-1-ltao@redhat.com>
References: <20260414102656.55200-1-ltao@redhat.com>
Message-ID: <87o6hzpegz.fsf@oracle.com>

Tao Liu <ltao at redhat.com> writes:
> A) This patchset will introduce the following features to makedumpfile:
>
>   1) Add .so extension support to makedumpfile
>   2) Enable btf and kallsyms for symbol type and address resolving.
>
> B) The purpose of the features are:
>
>   1) Currently makedumpfile filters mm pages based on page flags, because flags
>      can help to determine one page's usage. But this page-flag-checking method
>      lacks of flexibility in certain cases, e.g. if we want to filter those mm
>      pages occupied by GPU during vmcore dumping due to:
>
>      a) GPU may be taking a large memory and contains sensitive data;
>      b) GPU mm pages have no relations to kernel crash and useless for vmcore
>         analysis.
>
>      But there is no GPU mm page specific flags, and apparently we don't need
>      to create one just for kdump use. A programmable filtering tool is more
>      suitable for such cases. In addition, different GPU vendors may use
>      different ways for mm pages allocating, programmable filtering is better
>      than hard coding these GPU specific logics into makedumpfile in this case.
>
>   2) Currently makedumpfile already contains a programmable filtering tool, aka
>      eppic script, which allows user to write customized code for data erasing.
>      However it has the following drawbacks:
>
>      a) cannot do mm page filtering.
>      b) need to access to debuginfo of both kernel and modules, which is not
>         applicable in the 2nd kernel.
>      c) eppic library has memory leaks which are not all resolved [1]. This
>         is not acceptable in 2nd kernel.
>
>      makedumpfile need to resolve the dwarf data from debuginfo, to get symbols
>      types and addresses. In recent kernel there are dwarf alternatives such
>      as btf/kallsyms which can be used for this purpose. And btf/kallsyms info
>      are already packed within vmcore, so we can use it directly.
>
>   With these, this patchset introduces makedumpfile extensions, which is based
>   on btf/kallsyms symbol resolving, and is programmable for mm page filtering.
>   The following section shows its usage and performance, please note the tests
>   are performed in 1st kernel.
>
>   3) Compile and run makedumpfile extensions:
>
>   $ make LINKTYPE=dynamic USELZO=on USESNAPPY=on USEZSTD=on EXTENSION=on
>   $ make extensions
>   
>   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
>     /tmp/extension.out --extension amdgpu_filter.so
>     Loaded extension: ./extensions/amdgpu_filter.so
>     makedumpfile Completed.
>         User time (seconds): 5.08
>         System time (seconds): 0.84
>         Percent of CPU this job got: 99%
>         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:05.95
>         Maximum resident set size (kbytes): 17360
>         ...
>  
>      To contrast with eppic script of v2 [2]:
>
>   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
>     /tmp/eppic.out --eppic eppic_scripts/filter_amdgpu_mm_pages.c   
>     makedumpfile Completed.
>         User time (seconds): 8.23
>         System time (seconds): 0.88
>         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:09.16
>         Maximum resident set size (kbytes): 57128
>         ...
>
>   -rw------- 1 root root 367475074 Jan 19 19:01 /tmp/extension.out
>   -rw------- 1 root root 367475074 Jan 19 19:48 /tmp/eppic.out
>   -rw------- 1 root root 387181418 Jun 10 18:03 /var/crash/127.0.0.1-2025-06-10-18:03:12/vmcore
>
> C) Discussion:
>
>   1) GPU types: Currently only tested with amdgpu's mm page filtering, others
>      are not tested.
>   2) OS: The code can work on rhel-10+/rhel9.5+ on x86_64/arm64/s390/ppc64.
>      rhel8.x is not supported, others are not tested.
>
> D) Testing:
>
>      If you don't want to create your vmcore, you can find a vmcore which I
>      created with amdgpu mm pages unfiltered [3], the amdgpu mm pages are
>      allocated by program [4]. You can use the vmcore in 1st kernel to filter
>      the amdgpu mm pages by the previous performance testing cmdline. To
>      verify the pages are filtered in crash:
>
>      Unfiltered:
>      crash> search -c "!QAZXSW@#EDC"
>      ffff96b7fa800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>      ffff96b87c800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>      crash> rd ffff96b7fa800000
>      ffff96b7fa800000:  405753585a415121                    !QAZXSW@
>      crash> rd ffff96b87c800000
>      ffff96b87c800000:  405753585a415121                    !QAZXSW@
>
>      Filtered:
>      crash> search -c "!QAZXSW@#EDC"
>      crash> rd ffff96b7fa800000
>      rd: page excluded: kernel virtual address: ffff96b7fa800000  type: "64-bit KVADDR"
>      crash> rd ffff96b87c800000
>      rd: page excluded: kernel virtual address: ffff96b87c800000  type: "64-bit KVADDR"
>
> [1]: https://github.com/lucchouina/eppic/pull/32
> [2]: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> [3]: https://people.redhat.com/~ltao/core/vmcore
> [4]: https://gist.github.com/liutgnu/a8cbce1c666452f1530e1410d1f352df
>
> v5 -> v4:
>
> 1) Add "make EXTENSION=on" switch to customize the extension feature.
> 2) Clean up macros within btf_info.h.
> 3) Updated doc and a sample extension to demo the extension usage.
> 4) Use MSG()/ERRMSG() rather than fprintf().
> 5) Add return value check for readmem().
> 6) Allow "makedumpfile -d 1 --extension ext.so" to enter extension.
> 7) The patches are organized as follows:
>
>     --- <customization specific> ---
>     9. Add amdgpu mm pages filtering extension
>
>     --- <code should be merged> ---
>     8. Doc: Add --extension option to makedumpfile manual
>     7. Add sample extension as an example reference
>     6. Add makedumpfile extensions support
>     5. Implement kernel module's btf resolving
>     4. Implement kernel module's kallsyms resolving
>     3. Implement kernel btf resolving
>     2. Implement kernel kallsyms resolving
>     1. Reserve sections for makedumpfile and extenions
>
>     Patch 9 is customization specific, merging depends on the strategy of
>     maintenance.
>     Patch 1 ~ 8 are common code which should be merged with makedumpfile.

Hi Tao,

Just to be thorough, I did re-read the code of each patch, in addition
to reviewing the diff from v4 to v5.

I've also re-based my userspace stack extension on this new version.
I think it is ready to go.

Reviewed-by: Stephen Brennan <stephen.s.brennan at oracle.com>

Thanks,
Stephen

> Link to v4: https://lore.kernel.org/kexec/20260317150743.69590-1-ltao at redhat.com/
> Link to v3: https://lore.kernel.org/kexec/20260120025500.25095-1-ltao at redhat.com/
> Link to v2: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> Link to v1: https://lore.kernel.org/kexec/20250610095743.18073-1-ltao at redhat.com/
>
> Tao Liu (9):
>   Reserve sections for makedumpfile and extenions
>   Implement kernel kallsyms resolving
>   Implement kernel btf resolving
>   Implement kernel module's kallsyms resolving
>   Implement kernel module's btf resolving
>   Add makedumpfile extensions support
>   Add sample extension as an example reference
>   Doc: Add --extension option to makedumpfile manual
>   Add amdgpu mm pages filtering extension
>
>  Makefile                   |  15 +-
>  README                     |   6 +
>  btf_info.c                 | 375 +++++++++++++++++++++++++
>  btf_info.h                 |  77 ++++++
>  extension.c                | 338 ++++++++++++++++++++++
>  extension.h                |  16 ++
>  extensions/Makefile        |  13 +
>  extensions/amdgpu_filter.c | 221 +++++++++++++++
>  extensions/maple_tree.c    | 328 ++++++++++++++++++++++
>  extensions/maple_tree.h    |   7 +
>  extensions/sample.c        |  69 +++++
>  kallsyms.c                 | 554 +++++++++++++++++++++++++++++++++++++
>  kallsyms.h                 |  87 ++++++
>  makedumpfile.8.in          |  11 +-
>  makedumpfile.c             |  44 ++-
>  makedumpfile.h             |  12 +
>  makedumpfile.ld            |  16 ++
>  17 files changed, 2180 insertions(+), 9 deletions(-)
>  create mode 100644 btf_info.c
>  create mode 100644 btf_info.h
>  create mode 100644 extension.c
>  create mode 100644 extension.h
>  create mode 100644 extensions/Makefile
>  create mode 100644 extensions/amdgpu_filter.c
>  create mode 100644 extensions/maple_tree.c
>  create mode 100644 extensions/maple_tree.h
>  create mode 100644 extensions/sample.c
>  create mode 100644 kallsyms.c
>  create mode 100644 kallsyms.h
>  create mode 100644 makedumpfile.ld
>
> -- 
> 2.47.0


From pasha.tatashin at soleen.com  Thu May 28 12:54:00 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Thu, 28 May 2026 19:54:00 +0000
Subject: [PATCH v2 01/10] liveupdate: centralize state management into
 struct luo_ser
In-Reply-To: <2vxz7bor78zw.fsf@kernel.org>
References: <20260514222628.931312-1-pasha.tatashin@soleen.com>
 <20260514222628.931312-2-pasha.tatashin@soleen.com>
 <2vxz7bor78zw.fsf@kernel.org>
Message-ID: <aheB1qJbwyGhQ5wj@plex>

On 05-25 18:24, Pratyush Yadav wrote:
> On Thu, May 14 2026, Pasha Tatashin wrote:
> 
> > Transition the LUO to ABI v2, which centralizes state management into a
> > single struct luo_ser header.
> >
> > Previously, LUO state was spread across multiple FDT properties and
> > subnodes. ABI v2 simplifies this by placing all core state, including
> > the liveupdate number and physical addresses for sessions and FLB
> > headers into a centralized struct luo_ser.
> 
> Makes a lot of sense I think.
> 
> But nit: this brings about a semantic difference: now sessions and FLB
> serialization format is no longer independent of LUO. I think that makes
> sense but can you please spell that out in the commit message.

Added.

> 
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> > ---
> >  include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
> >  kernel/liveupdate/luo_core.c     | 59 ++++++++++++++-------
> >  kernel/liveupdate/luo_flb.c      | 65 ++++-------------------
> >  kernel/liveupdate/luo_internal.h |  8 +--
> >  kernel/liveupdate/luo_session.c  | 57 +++-----------------
> >  5 files changed, 93 insertions(+), 187 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> > index 46750a0ddf88..1b2f865a771a 100644
> > --- a/include/linux/kho/abi/luo.h
> > +++ b/include/linux/kho/abi/luo.h
> > @@ -30,52 +30,25 @@
> >   *   .. code-block:: none
> >   *
> >   *     / {
> > - *         compatible = "luo-v1";
> > - *         liveupdate-number = <...>;
> > - *
> > - *         luo-session {
> > - *             compatible = "luo-session-v1";
> > - *             luo-session-header = <phys_addr_of_session_header_ser>;
> > - *         };
> > - *
> > - *         luo-flb {
> > - *             compatible = "luo-flb-v1";
> > - *             luo-flb-header = <phys_addr_of_flb_header_ser>;
> > - *         };
> > + *         compatible = "luo-v2";
> > + *         luo-abi-header = <phys_addr_of_luo_ser>;
> 
> While you are at it, perhaps also get rid of the LUO FDT entirely? With
> this change it only holds the compatible and a pointer to a blob. Might
> as well move the compatible string to the blob and use it directly. Do
> you see any benefits of sticking with the FDT here? We have already
> removed it from memfd for example (and AFAIK the upcoming PCI and VFIO
> patches also do not use it).

Good idea, let's remove it entirly, I am going to do this in a separate 
patch.

> 
> >   *     };
> >   *
> >   * Main LUO Node (/):
> >   *
> > - *   - compatible: "luo-v1"
> > + *   - compatible: "luo-v2"
> >   *     Identifies the overall LUO ABI version.
> > - *   - liveupdate-number: u64
> > - *     A counter tracking the number of successful live updates performed.
> > @@ -109,13 +82,26 @@
> >  
> >  /*
> >   * The LUO FDT hooks all LUO state for sessions, fds, etc.
> > - * In the root it also carries "liveupdate-number" 64-bit property that
> > - * corresponds to the number of live-updates performed on this machine.
> >   */
> >  #define LUO_FDT_SIZE		PAGE_SIZE
> >  #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
> > -#define LUO_FDT_COMPATIBLE	"luo-v1"
> > -#define LUO_FDT_LIVEUPDATE_NUM	"liveupdate-number"
> > +#define LUO_FDT_COMPATIBLE	"luo-v2"
> > +#define LUO_FDT_ABI_HEADER	"luo-abi-header"
> 
> Nit: If we do go with FDT, I suppose "luo-ser-pa" (or "luo-ser"?) would
> be more descriptive?

I think, this is a good idea to remove FDT.

> 
> > +
> > +/**
> > + * struct luo_ser - Centralized LUO ABI header.
> > diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
> > index 8f5c5dd01cd0..7ccc59981297 100644
> > --- a/kernel/liveupdate/luo_flb.c
> > +++ b/kernel/liveupdate/luo_flb.c
> > @@ -159,8 +159,8 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
> >  
> >  static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
> >  {
> > -	struct luo_flb_private *private = luo_flb_get_private(flb);
> >  	struct luo_flb_header *fh = &luo_flb_global.incoming;
> > +	struct luo_flb_private *private = luo_flb_get_private(flb);
> 
> Nit: this doesn't seem to do anything. Is this a stray change?

Removed

> 
> >  	struct liveupdate_flb_op_args args = {0};
> >  	bool found = false;
> >  	int err;
> 
> > -int __init luo_flb_setup_incoming(void *fdt_in)
> > +void __init luo_flb_setup_incoming(u64 flbs_pa)
> >  {
> >  	struct luo_flb_header_ser *header_ser;
> > -	int err, header_size, offset;
> > -	const void *ptr;
> > -	u64 header_ser_pa;
> > -
> > -	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
> > -	if (offset < 0) {
> > -		pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
> >  
> > -		return -ENOENT;
> > +	if (flbs_pa) {
> > +		header_ser = phys_to_virt(flbs_pa);
> > +		luo_flb_global.incoming.header_ser = header_ser;
> > +		luo_flb_global.incoming.ser = (void *)(header_ser + 1);
> > +		luo_flb_global.incoming.active = true;
> 
> Nit: Not getting FLB data resulted in a error print. I suppose we should
> keep that here as well.

This is no longer necessary. Previously, we relied on the FDT, which 
contained its own errors, but since everything is now passed through 
LUO's own struct, there are no errors to parse through fdtlib. The 
absence of flbs_pa indicates that it was not preserved by the previous 
kernel (i.e., there is no flbs).

> 
> >  	}
> > -
> > -	err = fdt_node_check_compatible(fdt_in, offset,
> > -					LUO_FDT_FLB_COMPATIBLE);
> > -	if (err) {
> > -		pr_err("FLB node is incompatible with '%s' [%d]\n",
> > -		       LUO_FDT_FLB_COMPATIBLE, err);
> > -
> > -		return -EINVAL;
> > -	}
> > -
> > -	header_size = 0;
> > -	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_FLB_HEADER, &header_size);
> > -	if (!ptr || header_size != sizeof(u64)) {
> > -		pr_err("Unable to get FLB header property '%s' [%d]\n",
> > -		       LUO_FDT_FLB_HEADER, header_size);
> > -
> > -		return -EINVAL;
> > -	}
> > -
> > -	header_ser_pa = get_unaligned((u64 *)ptr);
> > -	header_ser = phys_to_virt(header_ser_pa);
> > -
> > -	luo_flb_global.incoming.header_ser = header_ser;
> > -	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
> > -	luo_flb_global.incoming.active = true;
> > -
> > -	return 0;
> >  }
> >  
> >  /**
> 
> > -int __init luo_session_setup_incoming(void *fdt_in)
> > +int __init luo_session_setup_incoming(u64 sessions_pa)
> >  {
> >  	struct luo_session_header_ser *header_ser;
> > -	int err, header_size, offset;
> > -	u64 header_ser_pa;
> > -	const void *ptr;
> > -
> > -	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
> > -	if (offset < 0) {
> > -		pr_err("Unable to get session node: [%s]\n",
> > -		       LUO_FDT_SESSION_NODE_NAME);
> > -		return -EINVAL;
> > -	}
> >  
> > -	err = fdt_node_check_compatible(fdt_in, offset,
> > -					LUO_FDT_SESSION_COMPATIBLE);
> > -	if (err) {
> > -		pr_err("Session node incompatible [%s]\n",
> > -		       LUO_FDT_SESSION_COMPATIBLE);
> > -		return -EINVAL;
> > -	}
> > -
> > -	header_size = 0;
> > -	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
> > -	if (!ptr || header_size != sizeof(u64)) {
> > -		pr_err("Unable to get session header '%s' [%d]\n",
> > -		       LUO_FDT_SESSION_HEADER, header_size);
> > -		return -EINVAL;
> > +	if (sessions_pa) {
> 
> Nit: same thing about the error print here.

ditto.

> 
> > +		header_ser = phys_to_virt(sessions_pa);
> > +		luo_session_global.incoming.header_ser = header_ser;
> > +		luo_session_global.incoming.ser = (void *)(header_ser + 1);
> > +		luo_session_global.incoming.active = true;
> >  	}
> >  
> > -	header_ser_pa = get_unaligned((u64 *)ptr);
> > -	header_ser = phys_to_virt(header_ser_pa);
> > -
> > -	luo_session_global.incoming.header_ser = header_ser;
> > -	luo_session_global.incoming.ser = (void *)(header_ser + 1);
> > -	luo_session_global.incoming.active = true;
> > -
> >  	return 0;
> >  }
> 
> -- 
> Regards,
> Pratyush Yadav


From ltao at redhat.com  Thu May 28 15:02:04 2026
From: ltao at redhat.com (Tao Liu)
Date: Fri, 29 May 2026 10:02:04 +1200
Subject: [PATCH v5][makedumpfile 0/9] btf/kallsyms based makedumpfile
 extension for mm page filtering
In-Reply-To: <87o6hzpegz.fsf@oracle.com>
References: <20260414102656.55200-1-ltao@redhat.com> <87o6hzpegz.fsf@oracle.com>
Message-ID: <CAO7dBbVVun57JRpckqY8Sx+s1kYFv6TB4SADGSVgj+_QtguasQ@mail.gmail.com>

Hi Stephen,

Thanks a lot for your code review, I really appreciate it! And I have
enjoyed the process of cooperating with you on the code discussion,
design, and implementation. Thanks again for your help on this! :)

Hi maintainers,

Just a kind ask: any comments on the v5 patchset? I'm happy to make
further modifications. Thanks!

Thanks,
Tao Liu

On Fri, May 29, 2026 at 6:38?AM Stephen Brennan
<stephen.s.brennan at oracle.com> wrote:
>
> Tao Liu <ltao at redhat.com> writes:
> > A) This patchset will introduce the following features to makedumpfile:
> >
> >   1) Add .so extension support to makedumpfile
> >   2) Enable btf and kallsyms for symbol type and address resolving.
> >
> > B) The purpose of the features are:
> >
> >   1) Currently makedumpfile filters mm pages based on page flags, because flags
> >      can help to determine one page's usage. But this page-flag-checking method
> >      lacks of flexibility in certain cases, e.g. if we want to filter those mm
> >      pages occupied by GPU during vmcore dumping due to:
> >
> >      a) GPU may be taking a large memory and contains sensitive data;
> >      b) GPU mm pages have no relations to kernel crash and useless for vmcore
> >         analysis.
> >
> >      But there is no GPU mm page specific flags, and apparently we don't need
> >      to create one just for kdump use. A programmable filtering tool is more
> >      suitable for such cases. In addition, different GPU vendors may use
> >      different ways for mm pages allocating, programmable filtering is better
> >      than hard coding these GPU specific logics into makedumpfile in this case.
> >
> >   2) Currently makedumpfile already contains a programmable filtering tool, aka
> >      eppic script, which allows user to write customized code for data erasing.
> >      However it has the following drawbacks:
> >
> >      a) cannot do mm page filtering.
> >      b) need to access to debuginfo of both kernel and modules, which is not
> >         applicable in the 2nd kernel.
> >      c) eppic library has memory leaks which are not all resolved [1]. This
> >         is not acceptable in 2nd kernel.
> >
> >      makedumpfile need to resolve the dwarf data from debuginfo, to get symbols
> >      types and addresses. In recent kernel there are dwarf alternatives such
> >      as btf/kallsyms which can be used for this purpose. And btf/kallsyms info
> >      are already packed within vmcore, so we can use it directly.
> >
> >   With these, this patchset introduces makedumpfile extensions, which is based
> >   on btf/kallsyms symbol resolving, and is programmable for mm page filtering.
> >   The following section shows its usage and performance, please note the tests
> >   are performed in 1st kernel.
> >
> >   3) Compile and run makedumpfile extensions:
> >
> >   $ make LINKTYPE=dynamic USELZO=on USESNAPPY=on USEZSTD=on EXTENSION=on
> >   $ make extensions
> >
> >   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
> >     /tmp/extension.out --extension amdgpu_filter.so
> >     Loaded extension: ./extensions/amdgpu_filter.so
> >     makedumpfile Completed.
> >         User time (seconds): 5.08
> >         System time (seconds): 0.84
> >         Percent of CPU this job got: 99%
> >         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:05.95
> >         Maximum resident set size (kbytes): 17360
> >         ...
> >
> >      To contrast with eppic script of v2 [2]:
> >
> >   $ /usr/bin/time -v ./makedumpfile -d 31 -l /var/crash/127.0.0.1-2025-06-10-18\:03\:12/vmcore
> >     /tmp/eppic.out --eppic eppic_scripts/filter_amdgpu_mm_pages.c
> >     makedumpfile Completed.
> >         User time (seconds): 8.23
> >         System time (seconds): 0.88
> >         Elapsed (wall clock) time (h:mm:ss or m:ss): 0:09.16
> >         Maximum resident set size (kbytes): 57128
> >         ...
> >
> >   -rw------- 1 root root 367475074 Jan 19 19:01 /tmp/extension.out
> >   -rw------- 1 root root 367475074 Jan 19 19:48 /tmp/eppic.out
> >   -rw------- 1 root root 387181418 Jun 10 18:03 /var/crash/127.0.0.1-2025-06-10-18:03:12/vmcore
> >
> > C) Discussion:
> >
> >   1) GPU types: Currently only tested with amdgpu's mm page filtering, others
> >      are not tested.
> >   2) OS: The code can work on rhel-10+/rhel9.5+ on x86_64/arm64/s390/ppc64.
> >      rhel8.x is not supported, others are not tested.
> >
> > D) Testing:
> >
> >      If you don't want to create your vmcore, you can find a vmcore which I
> >      created with amdgpu mm pages unfiltered [3], the amdgpu mm pages are
> >      allocated by program [4]. You can use the vmcore in 1st kernel to filter
> >      the amdgpu mm pages by the previous performance testing cmdline. To
> >      verify the pages are filtered in crash:
> >
> >      Unfiltered:
> >      crash> search -c "!QAZXSW@#EDC"
> >      ffff96b7fa800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> >      ffff96b87c800000: !QAZXSW@#EDCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> >      crash> rd ffff96b7fa800000
> >      ffff96b7fa800000:  405753585a415121                    !QAZXSW@
> >      crash> rd ffff96b87c800000
> >      ffff96b87c800000:  405753585a415121                    !QAZXSW@
> >
> >      Filtered:
> >      crash> search -c "!QAZXSW@#EDC"
> >      crash> rd ffff96b7fa800000
> >      rd: page excluded: kernel virtual address: ffff96b7fa800000  type: "64-bit KVADDR"
> >      crash> rd ffff96b87c800000
> >      rd: page excluded: kernel virtual address: ffff96b87c800000  type: "64-bit KVADDR"
> >
> > [1]: https://github.com/lucchouina/eppic/pull/32
> > [2]: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> > [3]: https://people.redhat.com/~ltao/core/vmcore
> > [4]: https://gist.github.com/liutgnu/a8cbce1c666452f1530e1410d1f352df
> >
> > v5 -> v4:
> >
> > 1) Add "make EXTENSION=on" switch to customize the extension feature.
> > 2) Clean up macros within btf_info.h.
> > 3) Updated doc and a sample extension to demo the extension usage.
> > 4) Use MSG()/ERRMSG() rather than fprintf().
> > 5) Add return value check for readmem().
> > 6) Allow "makedumpfile -d 1 --extension ext.so" to enter extension.
> > 7) The patches are organized as follows:
> >
> >     --- <customization specific> ---
> >     9. Add amdgpu mm pages filtering extension
> >
> >     --- <code should be merged> ---
> >     8. Doc: Add --extension option to makedumpfile manual
> >     7. Add sample extension as an example reference
> >     6. Add makedumpfile extensions support
> >     5. Implement kernel module's btf resolving
> >     4. Implement kernel module's kallsyms resolving
> >     3. Implement kernel btf resolving
> >     2. Implement kernel kallsyms resolving
> >     1. Reserve sections for makedumpfile and extenions
> >
> >     Patch 9 is customization specific, merging depends on the strategy of
> >     maintenance.
> >     Patch 1 ~ 8 are common code which should be merged with makedumpfile.
>
> Hi Tao,
>
> Just to be thorough, I did re-read the code of each patch, in addition
> to reviewing the diff from v4 to v5.
>
> I've also re-based my userspace stack extension on this new version.
> I think it is ready to go.
>
> Reviewed-by: Stephen Brennan <stephen.s.brennan at oracle.com>
>
> Thanks,
> Stephen
>
> > Link to v4: https://lore.kernel.org/kexec/20260317150743.69590-1-ltao at redhat.com/
> > Link to v3: https://lore.kernel.org/kexec/20260120025500.25095-1-ltao at redhat.com/
> > Link to v2: https://lore.kernel.org/kexec/20251020222410.8235-1-ltao at redhat.com/
> > Link to v1: https://lore.kernel.org/kexec/20250610095743.18073-1-ltao at redhat.com/
> >
> > Tao Liu (9):
> >   Reserve sections for makedumpfile and extenions
> >   Implement kernel kallsyms resolving
> >   Implement kernel btf resolving
> >   Implement kernel module's kallsyms resolving
> >   Implement kernel module's btf resolving
> >   Add makedumpfile extensions support
> >   Add sample extension as an example reference
> >   Doc: Add --extension option to makedumpfile manual
> >   Add amdgpu mm pages filtering extension
> >
> >  Makefile                   |  15 +-
> >  README                     |   6 +
> >  btf_info.c                 | 375 +++++++++++++++++++++++++
> >  btf_info.h                 |  77 ++++++
> >  extension.c                | 338 ++++++++++++++++++++++
> >  extension.h                |  16 ++
> >  extensions/Makefile        |  13 +
> >  extensions/amdgpu_filter.c | 221 +++++++++++++++
> >  extensions/maple_tree.c    | 328 ++++++++++++++++++++++
> >  extensions/maple_tree.h    |   7 +
> >  extensions/sample.c        |  69 +++++
> >  kallsyms.c                 | 554 +++++++++++++++++++++++++++++++++++++
> >  kallsyms.h                 |  87 ++++++
> >  makedumpfile.8.in          |  11 +-
> >  makedumpfile.c             |  44 ++-
> >  makedumpfile.h             |  12 +
> >  makedumpfile.ld            |  16 ++
> >  17 files changed, 2180 insertions(+), 9 deletions(-)
> >  create mode 100644 btf_info.c
> >  create mode 100644 btf_info.h
> >  create mode 100644 extension.c
> >  create mode 100644 extension.h
> >  create mode 100644 extensions/Makefile
> >  create mode 100644 extensions/amdgpu_filter.c
> >  create mode 100644 extensions/maple_tree.c
> >  create mode 100644 extensions/maple_tree.h
> >  create mode 100644 extensions/sample.c
> >  create mode 100644 kallsyms.c
> >  create mode 100644 kallsyms.h
> >  create mode 100644 makedumpfile.ld
> >
> > --
> > 2.47.0
>



From pasha.tatashin at soleen.com  Thu May 28 20:20:55 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:20:55 +0000
Subject: [PATCH v3 00/11] liveupdate: Remove limits on the number of files and sessions
Message-ID: <20260529032106.1505796-1-pasha.tatashin@soleen.com>

This series removes the fixed limits on the number of files that can
be preserved within a single session, and the total number of sessions
managed by the Live Update Orchestrator (LUO).

The core of the change is a transition from single contiguous memory
blocks for metadata serialization to a chain of linked blocks. This
allows LUO to scale dynamically.

1.  ABI Evolution:
    - Introduced linked-block headers for both file and session
      serialization.
    - Bumped session ABI version to v4.

2.  Memory Management & Security:
    - Implemented a dynamic block allocation and reuse strategy. Blocks
      are allocated only when existing ones are exhausted and are reused
      during session/file removal cycles.
    - Introduced KHO_MAX_BLOCKS (10000) as a safeguard against stupid
      excessive allocations or corrupted cyclic lists during restore.

3.  Expanded Selftests:
    - Added new kexec-based tests verifying preservation of
      2000 sessions and 500 files per session.
    - Added self-tests for many sessions and many files management.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git
Branch: luo-remove-max-files-sessions-limits/v3

Changes v3:
    - Moved the linked-block serialization infrastructure from LUO-specific
      (luo_block.c) to generic KHO helper infrastructure (kho_block.c,
      include/linux/kho_block.h, include/linux/kho/abi/block.h).
    - Registered luo_ser directly as a raw KHO subtree instead of wrapping
      it inside FDT.
    - Collected Acked-by tags from Mike Rapoport.

Pasha Tatashin (11):
  liveupdate: centralize state management into struct luo_ser
  liveupdate: register luo_ser as KHO subtree
  liveupdate: Extract luo_file_deserialize_one helper
  liveupdate: Extract luo_session_deserialize_one helper
  kho: add support for linked-block serialization
  liveupdate: defer session block allocation and PA setting
  liveupdate: Remove limit on the number of sessions
  liveupdate: Remove limit on the number of files per session
  selftests/liveupdate: Test session and file limit removal
  selftests/liveupdate: Add stress-sessions kexec test
  selftests/liveupdate: Add stress-files kexec test

 Documentation/core-api/kho/abi.rst            |   5 +
 Documentation/core-api/kho/index.rst          |  11 +
 MAINTAINERS                                   |   1 +
 include/linux/kho/abi/block.h                 |  56 +++
 include/linux/kho/abi/luo.h                   | 149 ++-----
 include/linux/kho_block.h                     |  79 ++++
 kernel/liveupdate/Makefile                    |   1 +
 kernel/liveupdate/kho_block.c                 | 382 ++++++++++++++++++
 kernel/liveupdate/luo_core.c                  |  95 ++---
 kernel/liveupdate/luo_file.c                  | 209 +++++-----
 kernel/liveupdate/luo_flb.c                   |  65 +--
 kernel/liveupdate/luo_internal.h              |  14 +-
 kernel/liveupdate/luo_session.c               | 214 +++++-----
 tools/testing/selftests/liveupdate/Makefile   |   2 +
 .../testing/selftests/liveupdate/liveupdate.c |  75 ++++
 .../selftests/liveupdate/luo_stress_files.c   | 101 +++++
 .../liveupdate/luo_stress_sessions.c          | 102 +++++
 .../selftests/liveupdate/luo_test_utils.c     |  24 ++
 .../selftests/liveupdate/luo_test_utils.h     |   2 +
 19 files changed, 1142 insertions(+), 445 deletions(-)
 create mode 100644 include/linux/kho/abi/block.h
 create mode 100644 include/linux/kho_block.h
 create mode 100644 kernel/liveupdate/kho_block.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c


base-commit: 5428435567cbe06c19914592fc22ca23c9ca1de5
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:20:56 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:20:56 +0000
Subject: [PATCH v3 01/11] liveupdate: centralize state management into struct luo_ser
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-2-pasha.tatashin@soleen.com>

Transition the LUO to ABI v2, which centralizes state management into a
single struct luo_ser header.

Previously, LUO state was spread across multiple FDT properties and
subnodes. ABI v2 simplifies this by placing all core state, including
the liveupdate number and physical addresses for sessions and FLB
headers into a centralized struct luo_ser.

Note that this change introduces a semantic difference: the sessions
and FLB serialization formats are no longer completely independent of
the core LUO. Their metadata (such as physical addresses for sessions
and FLB headers) is now coupled to and managed via the centralized
struct luo_ser.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
 kernel/liveupdate/luo_core.c     | 60 ++++++++++++++-------
 kernel/liveupdate/luo_flb.c      | 65 +++--------------------
 kernel/liveupdate/luo_internal.h |  8 +--
 kernel/liveupdate/luo_session.c  | 64 ++++------------------
 5 files changed, 94 insertions(+), 194 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 46750a0ddf88..1b2f865a771a 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -30,52 +30,25 @@
  *   .. code-block:: none
  *
  *     / {
- *         compatible = "luo-v1";
- *         liveupdate-number = <...>;
- *
- *         luo-session {
- *             compatible = "luo-session-v1";
- *             luo-session-header = <phys_addr_of_session_header_ser>;
- *         };
- *
- *         luo-flb {
- *             compatible = "luo-flb-v1";
- *             luo-flb-header = <phys_addr_of_flb_header_ser>;
- *         };
+ *         compatible = "luo-v2";
+ *         luo-abi-header = <phys_addr_of_luo_ser>;
  *     };
  *
  * Main LUO Node (/):
  *
- *   - compatible: "luo-v1"
+ *   - compatible: "luo-v2"
  *     Identifies the overall LUO ABI version.
- *   - liveupdate-number: u64
- *     A counter tracking the number of successful live updates performed.
- *
- * Session Node (luo-session):
- *   This node describes all preserved user-space sessions.
- *
- *   - compatible: "luo-session-v1"
- *     Identifies the session ABI version.
- *   - luo-session-header: u64
- *     The physical address of a `struct luo_session_header_ser`. This structure
- *     is the header for a contiguous block of memory containing an array of
- *     `struct luo_session_ser`, one for each preserved session.
- *
- * File-Lifecycle-Bound Node (luo-flb):
- *   This node describes all preserved global objects whose lifecycle is bound
- *   to that of the preserved files (e.g., shared IOMMU state).
- *
- *   - compatible: "luo-flb-v1"
- *     Identifies the FLB ABI version.
- *   - luo-flb-header: u64
- *     The physical address of a `struct luo_flb_header_ser`. This structure is
- *     the header for a contiguous block of memory containing an array of
- *     `struct luo_flb_ser`, one for each preserved global object.
+ *   - luo-abi-header: u64
+ *     The physical address of `struct luo_ser`.
  *
  * Serialization Structures:
  *   The FDT properties point to memory regions containing arrays of simple,
  *   `__packed` structures. These structures contain the actual preserved state.
  *
+ *   - struct luo_ser:
+ *     The central ABI structure that contains the overall state of the LUO.
+ *     It includes the liveupdate-number and pointers to sessions and FLBs.
+ *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
  *     preserved memory block and the number of `struct luo_session_ser`
@@ -109,13 +82,26 @@
 
 /*
  * The LUO FDT hooks all LUO state for sessions, fds, etc.
- * In the root it also carries "liveupdate-number" 64-bit property that
- * corresponds to the number of live-updates performed on this machine.
  */
 #define LUO_FDT_SIZE		PAGE_SIZE
 #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v1"
-#define LUO_FDT_LIVEUPDATE_NUM	"liveupdate-number"
+#define LUO_FDT_COMPATIBLE	"luo-v2"
+#define LUO_FDT_ABI_HEADER	"luo-abi-header"
+
+/**
+ * struct luo_ser - Centralized LUO ABI header.
+ * @liveupdate_num: A counter tracking the number of successful live updates.
+ * @sessions_pa:    Physical address of the first session block header.
+ * @flbs_pa:        Physical address of the FLB header.
+ *
+ * This structure is the root of all preserved LUO state. It is pointed to by
+ * the "luo-abi-header" property in the LUO FDT.
+ */
+struct luo_ser {
+	u64 liveupdate_num;
+	u64 sessions_pa;
+	u64 flbs_pa;
+} __packed;
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
@@ -147,15 +133,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/*
- * LUO FDT session node
- * LUO_FDT_SESSION_HEADER:  is a u64 physical address of struct
- *                          luo_session_header_ser
- */
-#define LUO_FDT_SESSION_NODE_NAME	"luo-session"
-#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v2"
-#define LUO_FDT_SESSION_HEADER		"luo-session-header"
-
 /**
  * struct luo_session_header_ser - Header for the serialized session data block.
  * @count: The number of `struct luo_session_ser` entries that immediately
@@ -165,7 +142,7 @@ struct luo_file_set_ser {
  * physical memory preserved across the kexec. It provides the necessary
  * metadata to interpret the array of session entries that follow.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_header_ser {
 	u64 count;
@@ -182,7 +159,7 @@ struct luo_session_header_ser {
  * session) is created and passed to the new kernel, allowing it to reconstruct
  * the session context.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -192,10 +169,6 @@ struct luo_session_ser {
 /* The max size is set so it can be reliably used during in serialization */
 #define LIVEUPDATE_FLB_COMPAT_LENGTH	48
 
-#define LUO_FDT_FLB_NODE_NAME	"luo-flb"
-#define LUO_FDT_FLB_COMPATIBLE	"luo-flb-v1"
-#define LUO_FDT_FLB_HEADER	"luo-flb-header"
-
 /**
  * struct luo_flb_header_ser - Header for the serialized FLB data block.
  * @pgcnt: The total number of pages occupied by the entire preserved memory
@@ -205,11 +178,9 @@ struct luo_session_ser {
  *         in the memory block.
  *
  * This structure is located at the physical address specified by the
- * `LUO_FDT_FLB_HEADER` FDT property. It provides the new kernel with the
- * necessary information to find and iterate over the array of preserved
- * File-Lifecycle-Bound objects and to manage the underlying memory.
+ * flbs_pa in luo_ser.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
@@ -231,7 +202,7 @@ struct luo_flb_header_ser {
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..c1c290aefc76 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -57,7 +57,6 @@
 #include <linux/rwsem.h>
 #include <linux/sizes.h>
 #include <linux/string.h>
-#include <linux/unaligned.h>
 
 #include "kexec_handover_internal.h"
 #include "luo_internal.h"
@@ -82,9 +81,11 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	struct luo_ser *luo_ser;
+	int err, header_size;
 	phys_addr_t fdt_phys;
-	int err, ln_size;
 	const void *ptr;
+	u64 luo_ser_pa;
 
 	if (!kho_is_enabled()) {
 		if (liveupdate_enabled())
@@ -115,27 +116,29 @@ static int __init luo_early_startup(void)
 		return -EINVAL;
 	}
 
-	ln_size = 0;
-	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_LIVEUPDATE_NUM,
-			  &ln_size);
-	if (!ptr || ln_size != sizeof(luo_global.liveupdate_num)) {
-		pr_err("Unable to get live update number '%s' [%d]\n",
-		       LUO_FDT_LIVEUPDATE_NUM, ln_size);
+	header_size = 0;
+	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
+	if (!ptr || header_size != sizeof(u64)) {
+		pr_err("Unable to get ABI header '%s' [%d]\n",
+		       LUO_FDT_ABI_HEADER, header_size);
 
 		return -EINVAL;
 	}
 
-	luo_global.liveupdate_num = get_unaligned((u64 *)ptr);
+	luo_ser_pa = get_unaligned((u64 *)ptr);
+	luo_ser = phys_to_virt(luo_ser_pa);
+
+	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
 
-	err = luo_session_setup_incoming(luo_global.fdt_in);
+	err = luo_session_setup_incoming(luo_ser->sessions_pa);
 	if (err)
 		return err;
 
-	err = luo_flb_setup_incoming(luo_global.fdt_in);
+	luo_flb_setup_incoming(luo_ser->flbs_pa);
 
-	return err;
+	return 0;
 }
 
 static int __init liveupdate_early_init(void)
@@ -156,7 +159,8 @@ early_initcall(liveupdate_early_init);
 /* Called during boot to create outgoing LUO fdt tree */
 static int __init luo_fdt_setup(void)
 {
-	const u64 ln = luo_global.liveupdate_num + 1;
+	struct luo_ser *luo_ser;
+	u64 luo_ser_pa;
 	void *fdt_out;
 	int err;
 
@@ -166,27 +170,45 @@ static int __init luo_fdt_setup(void)
 		return PTR_ERR(fdt_out);
 	}
 
+	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
+	if (IS_ERR(luo_ser)) {
+		err = PTR_ERR(luo_ser);
+		goto exit_free_fdt;
+	}
+	luo_ser_pa = virt_to_phys(luo_ser);
+
 	err = fdt_create(fdt_out, LUO_FDT_SIZE);
 	err |= fdt_finish_reservemap(fdt_out);
 	err |= fdt_begin_node(fdt_out, "");
 	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln));
-	err |= luo_session_setup_outgoing(fdt_out);
-	err |= luo_flb_setup_outgoing(fdt_out);
+	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
+			    sizeof(luo_ser_pa));
 	err |= fdt_end_node(fdt_out);
 	err |= fdt_finish(fdt_out);
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
+
+	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
 			      fdt_totalsize(fdt_out));
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
 	luo_global.fdt_out = fdt_out;
 
 	return 0;
 
-exit_free:
+exit_free_luo_ser:
+	kho_unpreserve_free(luo_ser);
+exit_free_fdt:
 	kho_unpreserve_free(fdt_out);
 	pr_err("failed to prepare LUO FDT: %d\n", err);
 
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 8f5c5dd01cd0..c8dd30b41238 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -44,13 +44,11 @@
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
-#include <linux/libfdt.h>
 #include <linux/list_private.h>
 #include <linux/liveupdate.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
-#include <linux/unaligned.h>
 #include "luo_internal.h"
 
 #define LUO_FLB_PGCNT		1ul
@@ -551,27 +549,15 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
 	return 0;
 }
 
-int __init luo_flb_setup_outgoing(void *fdt_out)
+int __init luo_flb_setup_outgoing(u64 *flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_FLB_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
 
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_FLB_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_FLB_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_FLB_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
-
-	if (err)
-		goto err_unpreserve;
+	*flbs_pa = virt_to_phys(header_ser);
 
 	header_ser->pgcnt = LUO_FLB_PGCNT;
 	luo_flb_global.outgoing.header_ser = header_ser;
@@ -579,53 +565,18 @@ int __init luo_flb_setup_outgoing(void *fdt_out)
 	luo_flb_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-
-	return err;
 }
 
-int __init luo_flb_setup_incoming(void *fdt_in)
+void __init luo_flb_setup_incoming(u64 flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	int err, header_size, offset;
-	const void *ptr;
-	u64 header_ser_pa;
 
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
-
-		return -ENOENT;
+	if (flbs_pa) {
+		header_ser = phys_to_virt(flbs_pa);
+		luo_flb_global.incoming.header_ser = header_ser;
+		luo_flb_global.incoming.ser = (void *)(header_ser + 1);
+		luo_flb_global.incoming.active = true;
 	}
-
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_FLB_COMPATIBLE);
-	if (err) {
-		pr_err("FLB node is incompatible with '%s' [%d]\n",
-		       LUO_FDT_FLB_COMPATIBLE, err);
-
-		return -EINVAL;
-	}
-
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_FLB_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get FLB header property '%s' [%d]\n",
-		       LUO_FDT_FLB_HEADER, header_size);
-
-		return -EINVAL;
-	}
-
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_flb_global.incoming.header_ser = header_ser;
-	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
-	luo_flb_global.incoming.active = true;
-
-	return 0;
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dd53d4a7277e..9bf26c5b1495 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,8 +79,8 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(void *fdt);
-int __init luo_session_setup_incoming(void *fdt);
+int __init luo_session_setup_outgoing(u64 *sessions_pa);
+int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
 
@@ -102,8 +102,8 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_finish(struct liveupdate_file_handler *fh);
 void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
-int __init luo_flb_setup_outgoing(void *fdt);
-int __init luo_flb_setup_incoming(void *fdt);
+int __init luo_flb_setup_outgoing(u64 *flbs_pa);
+void __init luo_flb_setup_incoming(u64 flbs_pa);
 void luo_flb_serialize(void);
 
 #ifdef CONFIG_LIVEUPDATE_TEST
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 146414933977..1cad54ea8209 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -25,9 +25,8 @@
  *
  * - Serialization: Session metadata is preserved using the KHO framework. When
  *   a live update is triggered via kexec, an array of `struct luo_session_ser`
- *   is populated and placed in a preserved memory region. An FDT node is also
- *   created, containing the count of sessions and the physical address of this
- *   array.
+ *   is populated and placed in a preserved memory region. The physical address
+ *   of this array is stored in the centralized `struct luo_ser` structure.
  *
  * Session Lifecycle:
  *
@@ -91,13 +90,11 @@
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
-#include <linux/libfdt.h>
 #include <linux/list.h>
 #include <linux/liveupdate.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
 #include <linux/slab.h>
-#include <linux/unaligned.h>
 #include <uapi/linux/liveupdate.h>
 #include "luo_internal.h"
 
@@ -498,75 +495,34 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(void *fdt_out)
+int __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_SESSION_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_SESSION_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
 
-	if (err)
-		goto err_unpreserve;
+	*sessions_pa = virt_to_phys(header_ser);
 
 	luo_session_global.outgoing.header_ser = header_ser;
 	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
 	luo_session_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-	return err;
 }
 
-int __init luo_session_setup_incoming(void *fdt_in)
+int __init luo_session_setup_incoming(u64 sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	int err, header_size, offset;
-	u64 header_ser_pa;
-	const void *ptr;
-
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get session node: [%s]\n",
-		       LUO_FDT_SESSION_NODE_NAME);
-		return -EINVAL;
-	}
 
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_SESSION_COMPATIBLE);
-	if (err) {
-		pr_err("Session node incompatible [%s]\n",
-		       LUO_FDT_SESSION_COMPATIBLE);
-		return -EINVAL;
-	}
-
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get session header '%s' [%d]\n",
-		       LUO_FDT_SESSION_HEADER, header_size);
-		return -EINVAL;
+	if (sessions_pa) {
+		header_ser = phys_to_virt(sessions_pa);
+		luo_session_global.incoming.header_ser = header_ser;
+		luo_session_global.incoming.ser = (void *)(header_ser + 1);
+		luo_session_global.incoming.active = true;
 	}
 
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_session_global.incoming.header_ser = header_ser;
-	luo_session_global.incoming.ser = (void *)(header_ser + 1);
-	luo_session_global.incoming.active = true;
-
 	return 0;
 }
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:20:57 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:20:57 +0000
Subject: [PATCH v3 02/11] liveupdate: register luo_ser as KHO subtree
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-3-pasha.tatashin@soleen.com>

Entirely remove the LUO FDT wrapper since the FDT only carries the
compatible string and the pointer to the centralized struct luo_ser.
Instead, register the struct luo_ser via the KHO raw subtree
API, placing the compatibility string inside the structure itself.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h  | 57 +++++++++---------------
 kernel/liveupdate/luo_core.c | 85 +++++++++++-------------------------
 2 files changed, 46 insertions(+), 96 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 1b2f865a771a..9a4fe491812b 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -10,11 +10,11 @@
  *
  * Live Update Orchestrator uses the stable Application Binary Interface
  * defined below to pass state from a pre-update kernel to a post-update
- * kernel. The ABI is built upon the Kexec HandOver framework and uses a
- * Flattened Device Tree to describe the preserved data.
+ * kernel. The ABI is built upon the Kexec HandOver framework and registers
+ * the central `struct luo_ser` via the KHO raw subtree API.
  *
- * This interface is a contract. Any modification to the FDT structure, node
- * properties, compatible strings, or the layout of the `__packed` serialization
+ * This interface is a contract. Any modification to the structure fields,
+ * compatible strings, or the layout of the `__packed` serialization
  * structures defined here constitutes a breaking change. Such changes require
  * incrementing the version number in the relevant `_COMPATIBLE` string to
  * prevent a new kernel from misinterpreting data from an old kernel.
@@ -23,31 +23,15 @@
  * however, backward/forward compatibility is only guaranteed for kernels
  * supporting the same ABI version.
  *
- * FDT Structure Overview:
+ * KHO Structure Overview:
  *   The entire LUO state is encapsulated within a single KHO entry named "LUO".
- *   This entry contains an FDT with the following layout:
- *
- *   .. code-block:: none
- *
- *     / {
- *         compatible = "luo-v2";
- *         luo-abi-header = <phys_addr_of_luo_ser>;
- *     };
- *
- * Main LUO Node (/):
- *
- *   - compatible: "luo-v2"
- *     Identifies the overall LUO ABI version.
- *   - luo-abi-header: u64
- *     The physical address of `struct luo_ser`.
+ *   This entry contains the `struct luo_ser` structure.
  *
  * Serialization Structures:
- *   The FDT properties point to memory regions containing arrays of simple,
- *   `__packed` structures. These structures contain the actual preserved state.
- *
  *   - struct luo_ser:
  *     The central ABI structure that contains the overall state of the LUO.
- *     It includes the liveupdate-number and pointers to sessions and FLBs.
+ *     It includes the compatibility string, the liveupdate-number, and pointers
+ *     to sessions and FLBs.
  *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
@@ -78,26 +62,27 @@
 #ifndef _LINUX_KHO_ABI_LUO_H
 #define _LINUX_KHO_ABI_LUO_H
 
+#include <linux/align.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
- * The LUO FDT hooks all LUO state for sessions, fds, etc.
+ * The LUO state is registered under this KHO entry name.
  */
-#define LUO_FDT_SIZE		PAGE_SIZE
-#define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v2"
-#define LUO_FDT_ABI_HEADER	"luo-abi-header"
+#define LUO_KHO_ENTRY_NAME	"LUO"
+#define LUO_ABI_COMPATIBLE	"luo-v3"
+#define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
  * struct luo_ser - Centralized LUO ABI header.
+ * @compatible:     Compatibility string identifying the LUO ABI version.
  * @liveupdate_num: A counter tracking the number of successful live updates.
  * @sessions_pa:    Physical address of the first session block header.
  * @flbs_pa:        Physical address of the FLB header.
  *
- * This structure is the root of all preserved LUO state. It is pointed to by
- * the "luo-abi-header" property in the LUO FDT.
+ * This structure is the root of all preserved LUO state.
  */
 struct luo_ser {
+	char compatible[LUO_ABI_COMPAT_LEN];
 	u64 liveupdate_num;
 	u64 sessions_pa;
 	u64 flbs_pa;
@@ -111,7 +96,7 @@ struct luo_ser {
  * @data:        Private data
  * @token:       User provided token for this file
  *
- * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_file_ser {
 	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
@@ -142,7 +127,7 @@ struct luo_file_set_ser {
  * physical memory preserved across the kexec. It provides the necessary
  * metadata to interpret the array of session entries that follow.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_session_header_ser {
 	u64 count;
@@ -159,7 +144,7 @@ struct luo_session_header_ser {
  * session) is created and passed to the new kernel, allowing it to reconstruct
  * the session context.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -180,7 +165,7 @@ struct luo_session_ser {
  * This structure is located at the physical address specified by the
  * flbs_pa in luo_ser.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
@@ -202,7 +187,7 @@ struct luo_flb_header_ser {
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index c1c290aefc76..5e12c8f8669b 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -50,7 +50,6 @@
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
 #include <linux/kobject.h>
-#include <linux/libfdt.h>
 #include <linux/liveupdate.h>
 #include <linux/miscdevice.h>
 #include <linux/mm.h>
@@ -63,8 +62,7 @@
 
 static struct {
 	bool enabled;
-	void *fdt_out;
-	void *fdt_in;
+	struct luo_ser *luo_ser_out;
 	u64 liveupdate_num;
 } luo_global;
 
@@ -81,11 +79,10 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	phys_addr_t luo_ser_phys;
 	struct luo_ser *luo_ser;
-	int err, header_size;
-	phys_addr_t fdt_phys;
-	const void *ptr;
-	u64 luo_ser_pa;
+	size_t len;
+	int err;
 
 	if (!kho_is_enabled()) {
 		if (liveupdate_enabled())
@@ -94,40 +91,29 @@ static int __init luo_early_startup(void)
 		return 0;
 	}
 
-	/* Retrieve LUO subtree, and verify its format. */
-	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
+	/* Retrieve LUO state from KHO. */
+	err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len);
 	if (err) {
 		if (err != -ENOENT) {
-			pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
-			       LUO_FDT_KHO_ENTRY_NAME, ERR_PTR(err));
+			pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n",
+			       LUO_KHO_ENTRY_NAME, ERR_PTR(err));
 			return err;
 		}
 
 		return 0;
 	}
 
-	luo_global.fdt_in = phys_to_virt(fdt_phys);
-	err = fdt_node_check_compatible(luo_global.fdt_in, 0,
-					LUO_FDT_COMPATIBLE);
-	if (err) {
-		pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
-		       LUO_FDT_KHO_ENTRY_NAME, LUO_FDT_COMPATIBLE, err);
-
+	if (len < sizeof(*luo_ser)) {
+		pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
 		return -EINVAL;
 	}
 
-	header_size = 0;
-	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get ABI header '%s' [%d]\n",
-		       LUO_FDT_ABI_HEADER, header_size);
-
+	luo_ser = phys_to_virt(luo_ser_phys);
+	if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
+		pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
 		return -EINVAL;
 	}
 
-	luo_ser_pa = get_unaligned((u64 *)ptr);
-	luo_ser = phys_to_virt(luo_ser_pa);
-
 	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
@@ -156,37 +142,20 @@ static int __init liveupdate_early_init(void)
 }
 early_initcall(liveupdate_early_init);
 
-/* Called during boot to create outgoing LUO fdt tree */
-static int __init luo_fdt_setup(void)
+/* Called during boot to create outgoing LUO state */
+static int __init luo_state_setup(void)
 {
 	struct luo_ser *luo_ser;
-	u64 luo_ser_pa;
-	void *fdt_out;
 	int err;
 
-	fdt_out = kho_alloc_preserve(LUO_FDT_SIZE);
-	if (IS_ERR(fdt_out)) {
-		pr_err("failed to allocate/preserve FDT memory\n");
-		return PTR_ERR(fdt_out);
-	}
-
 	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
 	if (IS_ERR(luo_ser)) {
-		err = PTR_ERR(luo_ser);
-		goto exit_free_fdt;
+		pr_err("failed to allocate/preserve LUO state memory\n");
+		return PTR_ERR(luo_ser);
 	}
-	luo_ser_pa = virt_to_phys(luo_ser);
-
-	err = fdt_create(fdt_out, LUO_FDT_SIZE);
-	err |= fdt_finish_reservemap(fdt_out);
-	err |= fdt_begin_node(fdt_out, "");
-	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
-			    sizeof(luo_ser_pa));
-	err |= fdt_end_node(fdt_out);
-	err |= fdt_finish(fdt_out);
-	if (err)
-		goto exit_free_luo_ser;
+
+	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
+	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
 	if (err)
@@ -196,21 +165,17 @@ static int __init luo_fdt_setup(void)
 	if (err)
 		goto exit_free_luo_ser;
 
-	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
-
-	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
-			      fdt_totalsize(fdt_out));
+	err = kho_add_subtree(LUO_KHO_ENTRY_NAME, luo_ser, sizeof(*luo_ser));
 	if (err)
 		goto exit_free_luo_ser;
-	luo_global.fdt_out = fdt_out;
+
+	luo_global.luo_ser_out = luo_ser;
 
 	return 0;
 
 exit_free_luo_ser:
 	kho_unpreserve_free(luo_ser);
-exit_free_fdt:
-	kho_unpreserve_free(fdt_out);
-	pr_err("failed to prepare LUO FDT: %d\n", err);
+	pr_err("failed to prepare LUO state: %d\n", err);
 
 	return err;
 }
@@ -226,7 +191,7 @@ static int __init luo_late_startup(void)
 	if (!liveupdate_enabled())
 		return 0;
 
-	err = luo_fdt_setup();
+	err = luo_state_setup();
 	if (err)
 		luo_global.enabled = false;
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:20:58 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:20:58 +0000
Subject: [PATCH v3 03/11] liveupdate: Extract luo_file_deserialize_one helper
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-4-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for files into
separate helper functions. In preparation to a linked-block
serialization for files.

This is a pure code movement, no other changes intended.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 77 ++++++++++++++++++++----------------
 1 file changed, 44 insertions(+), 33 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 208987502f73..9eec07a9e9fc 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -753,6 +753,46 @@ int luo_file_finish(struct luo_file_set *file_set)
 	return 0;
 }
 
+static int luo_file_deserialize_one(struct luo_file_set *file_set,
+				    struct luo_file_ser *ser)
+{
+	struct liveupdate_file_handler *fh;
+	bool handler_found = false;
+	struct luo_file *luo_file;
+
+	down_read(&luo_register_rwlock);
+	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
+		if (!strcmp(fh->compatible, ser->compatible)) {
+			if (try_module_get(fh->ops->owner))
+				handler_found = true;
+			break;
+		}
+	}
+	up_read(&luo_register_rwlock);
+
+	if (!handler_found) {
+		pr_warn("No registered handler for compatible '%.*s'\n",
+			(int)sizeof(ser->compatible),
+			ser->compatible);
+		return -ENOENT;
+	}
+
+	luo_file = kzalloc_obj(*luo_file);
+	if (!luo_file) {
+		module_put(fh->ops->owner);
+		return -ENOMEM;
+	}
+
+	luo_file->fh = fh;
+	luo_file->file = NULL;
+	luo_file->serialized_data = ser->data;
+	luo_file->token = ser->token;
+	mutex_init(&luo_file->mutex);
+	list_add_tail(&luo_file->list, &file_set->files_list);
+
+	return 0;
+}
+
 /**
  * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
  * @file_set:     The incoming file_set to fill with deserialized data.
@@ -782,6 +822,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	int err;
 	u64 i;
 
 	if (!file_set_ser->files) {
@@ -809,39 +850,9 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 */
 	file_ser = file_set->files;
 	for (i = 0; i < file_set->count; i++) {
-		struct liveupdate_file_handler *fh;
-		bool handler_found = false;
-		struct luo_file *luo_file;
-
-		down_read(&luo_register_rwlock);
-		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
-			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
-				if (try_module_get(fh->ops->owner))
-					handler_found = true;
-				break;
-			}
-		}
-		up_read(&luo_register_rwlock);
-
-		if (!handler_found) {
-			pr_warn("No registered handler for compatible '%.*s'\n",
-				(int)sizeof(file_ser[i].compatible),
-				file_ser[i].compatible);
-			return -ENOENT;
-		}
-
-		luo_file = kzalloc_obj(*luo_file);
-		if (!luo_file) {
-			module_put(fh->ops->owner);
-			return -ENOMEM;
-		}
-
-		luo_file->fh = fh;
-		luo_file->file = NULL;
-		luo_file->serialized_data = file_ser[i].data;
-		luo_file->token = file_ser[i].token;
-		mutex_init(&luo_file->mutex);
-		list_add_tail(&luo_file->list, &file_set->files_list);
+		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+		if (err)
+			return err;
 	}
 
 	return 0;
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:20:59 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:20:59 +0000
Subject: [PATCH v3 04/11] liveupdate: Extract luo_session_deserialize_one helper
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-5-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for sessions into
separate helper functions. In preparation to a linked-block
serialization for sessions.

This is a pure code movement, no other changes intended.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 62 +++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 26 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 1cad54ea8209..5c092c71797e 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -526,6 +526,40 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 	return 0;
 }
 
+static int luo_session_deserialize_one(struct luo_session_header *sh,
+				       struct luo_session_ser *ser)
+{
+	struct luo_session *session;
+	int err;
+
+	session = luo_session_alloc(ser->name);
+	if (IS_ERR(session)) {
+		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
+			(int)sizeof(ser->name), ser->name, session);
+		return PTR_ERR(session);
+	}
+
+	err = luo_session_insert(sh, session);
+	if (err) {
+		pr_warn("Failed to insert session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		luo_session_free(session);
+		return err;
+	}
+
+	scoped_guard(mutex, &session->mutex) {
+		err = luo_file_deserialize(&session->file_set,
+					   &ser->file_set_ser);
+	}
+	if (err) {
+		pr_warn("Failed to deserialize files for session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		return err;
+	}
+
+	return 0;
+}
+
 int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
@@ -556,33 +590,9 @@ int luo_session_deserialize(void)
 	 * reliably reset devices and reclaim memory.
 	 */
 	for (int i = 0; i < sh->header_ser->count; i++) {
-		struct luo_session *session;
-
-		session = luo_session_alloc(sh->ser[i].name);
-		if (IS_ERR(session)) {
-			pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
-				(int)sizeof(sh->ser[i].name),
-				sh->ser[i].name, session);
-			return PTR_ERR(session);
-		}
-
-		err = luo_session_insert(sh, session);
-		if (err) {
-			pr_warn("Failed to insert session [%s] %pe\n",
-				session->name, ERR_PTR(err));
-			luo_session_free(session);
-			return err;
-		}
-
-		scoped_guard(mutex, &session->mutex) {
-			err = luo_file_deserialize(&session->file_set,
-						   &sh->ser[i].file_set_ser);
-		}
-		if (err) {
-			pr_warn("Failed to deserialize files for session [%s] %pe\n",
-				session->name, ERR_PTR(err));
+		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+		if (err)
 			return err;
-		}
 	}
 
 	kho_restore_free(sh->header_ser);
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:01 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:01 +0000
Subject: [PATCH v3 06/11] liveupdate: defer session block allocation and PA setting
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-7-pasha.tatashin@soleen.com>

Currently, luo_session_setup_outgoing() allocates the session block and
sets its physical address in the header immediately. With upcoming
dynamic block-based session management, this makes the first block
different from the rest. Move the allocation to where it is first needed.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_core.c     |  4 +-
 kernel/liveupdate/luo_internal.h |  2 +-
 kernel/liveupdate/luo_session.c  | 68 ++++++++++++++++++++------------
 3 files changed, 45 insertions(+), 29 deletions(-)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 5e12c8f8669b..92f8c53c7a73 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -157,9 +157,7 @@ static int __init luo_state_setup(void)
 	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
 	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
-	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
-	if (err)
-		goto exit_free_luo_ser;
+	luo_session_setup_outgoing(&luo_ser->sessions_pa);
 
 	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
 	if (err)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 9bf26c5b1495..28ebb4f3000b 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,7 +79,7 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(u64 *sessions_pa);
+void __init luo_session_setup_outgoing(u64 *sessions_pa);
 int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 5c092c71797e..ec72f16185f3 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -108,15 +108,16 @@ static DECLARE_RWSEM(luo_session_serialize_rwsem);
 
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
- * @count:      The number of sessions currently tracked in the @list.
- * @list:       The head of the linked list of `struct luo_session` instances.
- * @rwsem:      A read-write semaphore providing synchronized access to the
- *              session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser:        The serialized session data (an array of
- *              `struct luo_session_ser`).
- * @active:     Set to true when first initialized. If previous kernel did not
- *              send session data, active stays false for incoming.
+ * @count:       The number of sessions currently tracked in the @list.
+ * @list:        The head of the linked list of `struct luo_session` instances.
+ * @rwsem:       A read-write semaphore providing synchronized access to the
+ *               session list and other fields in this structure.
+ * @header_ser:  The header data of serialization array.
+ * @ser:         The serialized session data (an array of
+ *               `struct luo_session_ser`).
+ * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
+ * @active:      Set to true when first initialized. If previous kernel did not
+ *               send session data, active stays false for incoming.
  */
 struct luo_session_header {
 	long count;
@@ -124,6 +125,7 @@ struct luo_session_header {
 	struct rw_semaphore rwsem;
 	struct luo_session_header_ser *header_ser;
 	struct luo_session_ser *ser;
+	u64 *sessions_pa;
 	bool active;
 };
 
@@ -171,10 +173,30 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
+static int luo_session_grow_ser(struct luo_session_header *sh)
+{
+	struct luo_session_header_ser *header_ser;
+
+	if (sh->count == LUO_SESSION_MAX)
+		return -ENOMEM;
+
+	if (sh->header_ser)
+		return 0;
+
+	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
+	if (IS_ERR(header_ser))
+		return PTR_ERR(header_ser);
+
+	sh->header_ser = header_ser;
+	sh->ser = (void *)(header_ser + 1);
+	return 0;
+}
+
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
 	struct luo_session *it;
+	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
 
@@ -183,8 +205,9 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		if (sh->count == LUO_SESSION_MAX)
-			return -ENOMEM;
+		err = luo_session_grow_ser(sh);
+		if (err)
+			return err;
 	}
 
 	/*
@@ -495,21 +518,10 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(u64 *sessions_pa)
+void __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	*sessions_pa = virt_to_phys(header_ser);
-
-	luo_session_global.outgoing.header_ser = header_ser;
-	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
+	luo_session_global.outgoing.sessions_pa = sessions_pa;
 	luo_session_global.outgoing.active = true;
-
-	return 0;
 }
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
@@ -611,6 +623,8 @@ int luo_session_serialize(void)
 
 	down_write(&luo_session_serialize_rwsem);
 	down_write(&sh->rwsem);
+	*sh->sessions_pa = 0;
+
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -620,7 +634,11 @@ int luo_session_serialize(void)
 			sizeof(sh->ser[i].name));
 		i++;
 	}
-	sh->header_ser->count = sh->count;
+
+	if (sh->header_ser && sh->count > 0) {
+		sh->header_ser->count = sh->count;
+		*sh->sessions_pa = virt_to_phys(sh->header_ser);
+	}
 	up_write(&sh->rwsem);
 
 	return 0;
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:00 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:00 +0000
Subject: [PATCH v3 05/11] kho: add support for linked-block serialization
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-6-pasha.tatashin@soleen.com>

Introduce a linked-block serialization mechanism for state handover.

Previously, LUO used contiguous memory blocks for serializing sessions
and files, which imposed limits on the total number of items that could
be preserved across a live update.

This commit adds the infrastructure for a more flexible, block-based
approach where serialized data is stored in a chain of linked blocks.
This is a generic KHO serialization block infrastructure that can be
used by multiple subsystems.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 Documentation/core-api/kho/abi.rst   |   5 +
 Documentation/core-api/kho/index.rst |  11 +
 MAINTAINERS                          |   1 +
 include/linux/kho/abi/block.h        |  56 ++++
 include/linux/kho_block.h            |  79 ++++++
 kernel/liveupdate/Makefile           |   1 +
 kernel/liveupdate/kho_block.c        | 382 +++++++++++++++++++++++++++
 7 files changed, 535 insertions(+)
 create mode 100644 include/linux/kho/abi/block.h
 create mode 100644 include/linux/kho_block.h
 create mode 100644 kernel/liveupdate/kho_block.c

diff --git a/Documentation/core-api/kho/abi.rst b/Documentation/core-api/kho/abi.rst
index 799d743105a6..edeb5b311963 100644
--- a/Documentation/core-api/kho/abi.rst
+++ b/Documentation/core-api/kho/abi.rst
@@ -28,6 +28,11 @@ KHO persistent memory tracker ABI
 .. kernel-doc:: include/linux/kho/abi/kexec_handover.h
   :doc: KHO persistent memory tracker
 
+KHO serialization block ABI
+===========================
+
+.. kernel-doc:: include/linux/kho/abi/block.h
+
 See Also
 ========
 
diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
index 0a2dee4f8e7d..320914a42178 100644
--- a/Documentation/core-api/kho/index.rst
+++ b/Documentation/core-api/kho/index.rst
@@ -83,6 +83,17 @@ Public API
 .. kernel-doc:: kernel/liveupdate/kexec_handover.c
   :export:
 
+KHO Serialization Blocks API
+============================
+
+.. kernel-doc:: kernel/liveupdate/kho_block.c
+  :doc: KHO Serialization Blocks
+
+.. kernel-doc:: include/linux/kho_block.h
+
+.. kernel-doc:: kernel/liveupdate/kho_block.c
+  :internal:
+
 See Also
 ========
 
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..fd119b343e99 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14194,6 +14194,7 @@ F:	Documentation/admin-guide/mm/kho.rst
 F:	Documentation/core-api/kho/*
 F:	include/linux/kexec_handover.h
 F:	include/linux/kho/
+F:	include/linux/kho_block.h
 F:	kernel/liveupdate/kexec_handover*
 F:	lib/test_kho.c
 F:	tools/testing/selftests/kho/
diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
new file mode 100644
index 000000000000..8641c20b379b
--- /dev/null
+++ b/include/linux/kho/abi/block.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+/**
+ * DOC: KHO Serialization Blocks ABI
+ *
+ * Subsystems using the KHO Serialization Blocks framework 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 `__packed` serialization
+ * structures defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the `KHO_BLOCK_ABI_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.
+ */
+
+#ifndef _LINUX_KHO_ABI_BLOCK_H
+#define _LINUX_KHO_ABI_BLOCK_H
+
+#include <asm/page.h>
+#include <linux/types.h>
+
+#define KHO_BLOCK_ABI_COMPATIBLE	"kho-block-v1"
+
+/**
+ * KHO_BLOCK_SIZE - The size of each serialization block.
+ *
+ * This is defined as PAGE_SIZE. PAGE_SIZE is ABI compliant because live
+ * update between kernels with different page sizes is not supported by KHO.
+ */
+#define KHO_BLOCK_SIZE			PAGE_SIZE
+
+/**
+ * struct kho_block_header_ser - Header for the serialized data block.
+ * @next:  Physical address of the next struct kho_block_header_ser.
+ * @count: The number of entries that immediately follow this header in the
+ *         memory block.
+ *
+ * This structure is located at the beginning of a block of physical memory
+ * preserved across a kexec. It provides the necessary metadata to interpret
+ * the array of entries that follow.
+ */
+struct kho_block_header_ser {
+	u64 next;
+	u64 count;
+} __packed;
+
+#endif /* _LINUX_KHO_ABI_BLOCK_H */
diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
new file mode 100644
index 000000000000..5e6b87b1befa
--- /dev/null
+++ b/include/linux/kho_block.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+#ifndef _LINUX_KHO_BLOCK_H
+#define _LINUX_KHO_BLOCK_H
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/kho/abi/block.h>
+
+/**
+ * struct kho_block - Internal representation of a serialization block.
+ * @list: List head for linking blocks in memory.
+ * @ser:  Pointer to the serialized header in preserved memory.
+ */
+struct kho_block {
+	struct list_head list;
+	struct kho_block_header_ser *ser;
+};
+
+/**
+ * struct kho_block_set - A set of blocks that belong to the same object.
+ * @blocks:          The list of serialization blocks (struct kho_block).
+ * @nblocks:         The number of allocated serialization blocks.
+ * @head_pa:         Physical address of the first block header.
+ * @entry_size:      The size of each entry in the blocks.
+ * @count_per_block: The maximum number of entries each block can hold.
+ * @incoming:        True if this block set was restored from the previous kernel.
+ */
+struct kho_block_set {
+	struct list_head blocks;
+	long nblocks;
+	u64 head_pa;
+	size_t entry_size;
+	u64 count_per_block;
+	bool incoming;
+};
+
+/**
+ * struct kho_block_it - Iterator for serializing entries into blocks.
+ * @bs:         The block set being iterated.
+ * @block:      The current block.
+ * @i:          The current entry index within @block.
+ */
+struct kho_block_it {
+	struct kho_block_set *bs;
+	struct kho_block *block;
+	u64 i;
+};
+
+/**
+ * KHO_BLOCK_SET_INIT - Initialize a static kho_block_set.
+ * @_name:       Name of the kho_block_set variable.
+ * @_entry_size: The size of each entry in the block set.
+ */
+#define KHO_BLOCK_SET_INIT(_name, _entry_size) {                        \
+	.blocks = LIST_HEAD_INIT((_name).blocks),                       \
+	.entry_size = _entry_size,                                      \
+}
+
+void kho_block_set_init(struct kho_block_set *bs, size_t entry_size);
+
+int kho_block_grow(struct kho_block_set *bs, u64 count);
+void kho_block_shrink(struct kho_block_set *bs, u64 count);
+
+int kho_block_restore(struct kho_block_set *bs, u64 head_pa);
+void kho_block_destroy(struct kho_block_set *bs);
+void kho_block_set_clear(struct kho_block_set *bs);
+
+void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs);
+void *kho_block_it_next(struct kho_block_it *it);
+void *kho_block_it_read(struct kho_block_it *it);
+void *kho_block_it_prev(struct kho_block_it *it);
+void kho_block_it_finalize(struct kho_block_it *it);
+
+#endif /* _LINUX_KHO_BLOCK_H */
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d2f779cbe279..eec9d3ae07eb 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 luo-y :=								\
+		kho_block.o						\
 		luo_core.o						\
 		luo_file.o						\
 		luo_flb.o						\
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
new file mode 100644
index 000000000000..c781c1e36056
--- /dev/null
+++ b/kernel/liveupdate/kho_block.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+/**
+ * DOC: KHO Serialization Blocks
+ *
+ * KHO provides a mechanism to preserve stateful data across a kexec handover
+ * by serializing it into memory blocks. This file provides the common
+ * infrastructure for managing these blocks.
+ *
+ * Each block consists of a header (struct kho_block_header_ser) followed by an
+ * array of serialized entries. Multiple blocks are linked together via a
+ * physical pointer in the header, forming a linked list that can be easily
+ * traversed in both the current and the next kernel.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/block.h>
+#include <linux/kho_block.h>
+#include <linux/slab.h>
+
+/*
+ * Safeguard limit for the number of serialization blocks. This is used to
+ * prevent infinite loops and excessive memory allocation in case of memory
+ * corruption in the preserved state.
+ */
+#define KHO_MAX_BLOCKS 10000
+
+/**
+ * kho_block_set_init - Initialize a block set.
+ * @bs:         The block set to initialize.
+ * @entry_size: The size of each entry in the blocks.
+ */
+void kho_block_set_init(struct kho_block_set *bs, size_t entry_size)
+{
+	*bs = (struct kho_block_set)KHO_BLOCK_SET_INIT(*bs, entry_size);
+}
+
+static inline u64 kho_block_count_per_block(struct kho_block_set *bs)
+{
+	if (unlikely(!bs->count_per_block)) {
+		bs->count_per_block = (KHO_BLOCK_SIZE -
+				       sizeof(struct kho_block_header_ser)) /
+				      bs->entry_size;
+		WARN_ON(!bs->count_per_block);
+	}
+	return bs->count_per_block;
+}
+
+/* Free serialized data */
+static void kho_block_free_ser(struct kho_block_set *bs,
+			       struct kho_block_header_ser *ser)
+{
+	if (bs->incoming)
+		kho_restore_free(ser);
+	else
+		kho_unpreserve_free(ser);
+}
+
+static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs)
+{
+	WARN_ON(bs->incoming);
+	return kho_alloc_preserve(KHO_BLOCK_SIZE);
+}
+
+static int kho_block_add(struct kho_block_set *bs,
+			 struct kho_block_header_ser *ser)
+{
+	struct kho_block *block, *last;
+
+	if (bs->nblocks >= KHO_MAX_BLOCKS)
+		return -ENOSPC;
+
+	block = kzalloc_obj(*block);
+	if (!block)
+		return -ENOMEM;
+
+	block->ser = ser;
+	last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
+	list_add_tail(&block->list, &bs->blocks);
+	bs->nblocks++;
+
+	if (last)
+		last->ser->next = virt_to_phys(ser);
+	else
+		bs->head_pa = virt_to_phys(ser);
+
+	return 0;
+}
+
+/**
+ * kho_block_grow - Create a new block if the current capacity is reached.
+ * @bs:    The block set.
+ * @count: The current number of entries.
+ *
+ * This function handles the dynamic expansion of a block set. It allocates
+ * and links a new serialization block if the provided entry count matches
+ * the current total capacity of the set.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int kho_block_grow(struct kho_block_set *bs, u64 count)
+{
+	struct kho_block_header_ser *ser;
+	int err;
+
+	if (WARN_ON(bs->incoming))
+		return -EINVAL;
+
+	if (count != bs->nblocks * kho_block_count_per_block(bs))
+		return 0;
+
+	ser = kho_block_alloc_ser(bs);
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	err = kho_block_add(bs, ser);
+	if (err) {
+		kho_block_free_ser(bs, ser);
+		return err;
+	}
+
+	return 0;
+}
+
+/**
+ * kho_block_shrink - Conditionally destroy the last block in a block set.
+ * @bs:              The block set.
+ * @count:           The current number of entries across all blocks.
+ *
+ * This function checks if the last block in the set is redundant based on the
+ * total entry count and the capacity of the preceding blocks. If the entry
+ * count can be accommodated by the blocks that come before the last one, the
+ * last block is destroyed and removed from the set.
+ */
+void kho_block_shrink(struct kho_block_set *bs, u64 count)
+{
+	struct kho_block *last, *new_last;
+
+	if (count > (bs->nblocks - 1) * kho_block_count_per_block(bs))
+		return;
+
+	if (list_empty(&bs->blocks))
+		return;
+
+	last = list_last_entry(&bs->blocks, struct kho_block, list);
+	list_del(&last->list);
+	bs->nblocks--;
+	kho_block_free_ser(bs, last->ser);
+	kfree(last);
+
+	new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
+	if (new_last)
+		new_last->ser->next = 0;
+	else
+		bs->head_pa = 0;
+}
+
+/*
+ * kho_cyclic_blocks_check - Check for cycles in a linked list of blocks.
+ * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
+ */
+static bool kho_cyclic_blocks_check(struct kho_block_set *bs)
+{
+	struct kho_block_header_ser *fast;
+	struct kho_block_header_ser *slow;
+	int count = 0;
+
+	fast = phys_to_virt(bs->head_pa);
+	slow = fast;
+
+	while (fast) {
+		if (count++ >= KHO_MAX_BLOCKS) {
+			pr_err("Linked list too long\n");
+			return false;
+		}
+
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		slow = phys_to_virt(slow->next);
+
+		if (slow == fast) {
+			pr_err("Cyclic list detected\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/**
+ * kho_block_restore - Restore a block set from a physical address.
+ * @bs:      The block set to restore.
+ * @head_pa: Physical address of the first block header.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int kho_block_restore(struct kho_block_set *bs, u64 head_pa)
+{
+	struct kho_block_header_ser *ser;
+	u64 next_pa = head_pa;
+	int err;
+
+	/* Restored block sets use size from the previous kernel */
+	bs->incoming = true;
+	if (!head_pa)
+		return 0;
+
+	bs->head_pa = head_pa;
+	if (!kho_cyclic_blocks_check(bs))
+		return -EINVAL;
+
+	while (next_pa) {
+		ser = phys_to_virt(next_pa);
+		if (ser->count > kho_block_count_per_block(bs)) {
+			pr_warn("Block contains too many entries: %llu\n",
+				ser->count);
+			err = -EINVAL;
+			goto err_destroy;
+		}
+		err = kho_block_add(bs, ser);
+		if (err)
+			goto err_destroy;
+		next_pa = ser->next;
+	}
+
+	return 0;
+
+err_destroy:
+	kho_block_destroy(bs);
+	return err;
+}
+
+/**
+ * kho_block_destroy - Destroy all blocks in a block set.
+ * @bs:          The block set.
+ */
+void kho_block_destroy(struct kho_block_set *bs)
+{
+	u64 head_pa = bs->head_pa;
+	struct kho_block *block;
+
+	while (!list_empty(&bs->blocks)) {
+		block = list_first_entry(&bs->blocks, struct kho_block, list);
+		list_del(&block->list);
+		kfree(block);
+	}
+	bs->nblocks = 0;
+	bs->head_pa = 0;
+
+	while (head_pa) {
+		struct kho_block_header_ser *ser = phys_to_virt(head_pa);
+
+		head_pa = ser->next;
+		kho_block_free_ser(bs, ser);
+	}
+}
+
+/**
+ * kho_block_set_clear - Clear all serialized data in a block set.
+ * @bs: The block set to clear.
+ */
+void kho_block_set_clear(struct kho_block_set *bs)
+{
+	struct kho_block *block;
+
+	list_for_each_entry(block, &bs->blocks, list) {
+		block->ser->count = 0;
+		memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
+	}
+}
+
+/**
+ * kho_block_it_init - Initialize a block set iterator.
+ * @it:         The iterator to initialize.
+ * @bs:         The block set to iterate over.
+ */
+void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs)
+{
+	it->bs = bs;
+	it->block = list_first_entry_or_null(&bs->blocks, struct kho_block, list);
+	it->i = 0;
+}
+
+/**
+ * kho_block_it_next - Return the next entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current block is full, it automatically advances to the next block
+ * in the set.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more slots are
+ * available.
+ */
+void *kho_block_it_next(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == kho_block_count_per_block(it->bs)) {
+		it->block->ser->count = it->i;
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_read - Return the next entry slot for reading.
+ * @it: The block iterator.
+ *
+ * This function iterates through entries that were previously serialized,
+ * respecting the count stored in each block's header.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more entries are
+ * available.
+ */
+void *kho_block_it_read(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	while (it->i == it->block->ser->count) {
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_prev - Return the previous entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current index is at the start of a block, it automatically moves to
+ * the end of the previous block.
+ *
+ * Return: A pointer to the previous entry slot, or NULL if at the very
+ * beginning of the block set.
+ */
+void *kho_block_it_prev(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == 0) {
+		if (list_is_first(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_prev_entry(it->block, list);
+		it->i = kho_block_count_per_block(it->bs);
+	}
+
+	return (void *)(it->block->ser + 1) + (--it->i * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_finalize - Finalize the current block by setting its entry count.
+ * @it: The block iterator.
+ */
+void kho_block_it_finalize(struct kho_block_it *it)
+{
+	if (it->block)
+		it->block->ser->count = it->i;
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:02 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:02 +0000
Subject: [PATCH v3 07/11] liveupdate: Remove limit on the number of sessions
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-8-pasha.tatashin@soleen.com>

Currently, the number of LUO sessions is limited by a fixed number of
pre-allocated pages for serialization (16 pages, allowing for ~819
sessions).

This limitation is problematic if LUO is used to support things such as
systemd file descriptor store, and would be used not just as VM memory
but to save other states on the machine.

Remove this limit by transitioning to a linked-block approach for
session metadata serialization. Instead of a single contiguous block,
session metadata is now stored in a chain of 16-page blocks. Each block
starts with a header containing the physical address of the next block
and the number of session entries in the current block.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h     |  24 +------
 kernel/liveupdate/luo_session.c | 114 +++++++++++++++-----------------
 2 files changed, 56 insertions(+), 82 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 9a4fe491812b..79758d92ed5f 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -33,11 +33,6 @@
  *     It includes the compatibility string, the liveupdate-number, and pointers
  *     to sessions and FLBs.
  *
- *   - struct luo_session_header_ser:
- *     Header for the session array. Contains the total page count of the
- *     preserved memory block and the number of `struct luo_session_ser`
- *     entries that follow.
- *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
  *     to another preserved memory block containing an array of
@@ -63,13 +58,15 @@
 #define _LINUX_KHO_ABI_LUO_H
 
 #include <linux/align.h>
+#include <linux/kho/abi/block.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_ABI_COMPATIBLE	"luo-v3"
+#define LUO_COMPAT_BASE		"luo-v3"
+#define LUO_ABI_COMPATIBLE	LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
 #define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
@@ -118,21 +115,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @count: The number of `struct luo_session_ser` entries that immediately
- *         follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
- */
-struct luo_session_header_ser {
-	u64 count;
-} __packed;
-
 /**
  * struct luo_session_ser - Represents the serialized metadata for a LUO session.
  * @name:         The unique name of the session, provided by the userspace at
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index ec72f16185f3..c340ae4bf743 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -24,9 +24,10 @@
  *   ioctls on /dev/liveupdate.
  *
  * - Serialization: Session metadata is preserved using the KHO framework. When
- *   a live update is triggered via kexec, an array of `struct luo_session_ser`
- *   is populated and placed in a preserved memory region. The physical address
- *   of this array is stored in the centralized `struct luo_ser` structure.
+ *   a live update is triggered via kexec, session metadata is serialized into
+ *   a chain of linked-blocks and placed in a preserved memory region. The
+ *   physical address of the first block header is stored in the centralized
+ *   `struct luo_ser` structure.
  *
  * Session Lifecycle:
  *
@@ -89,6 +90,7 @@
 #include <linux/fs.h>
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
+#include <linux/kho_block.h>
 #include <linux/kho/abi/luo.h>
 #include <linux/list.h>
 #include <linux/liveupdate.h>
@@ -98,23 +100,14 @@
 #include <uapi/linux/liveupdate.h>
 #include "luo_internal.h"
 
-/* 16 4K pages, give space for 744 sessions */
-#define LUO_SESSION_PGCNT	16ul
-#define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
-		sizeof(struct luo_session_header_ser)) /		\
-		sizeof(struct luo_session_ser))
-
 static DECLARE_RWSEM(luo_session_serialize_rwsem);
-
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:       The number of sessions currently tracked in the @list.
  * @list:        The head of the linked list of `struct luo_session` instances.
  * @rwsem:       A read-write semaphore providing synchronized access to the
  *               session list and other fields in this structure.
- * @header_ser:  The header data of serialization array.
- * @ser:         The serialized session data (an array of
- *               `struct luo_session_ser`).
+ * @block_set:   The set of serialization blocks.
  * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
  * @active:      Set to true when first initialized. If previous kernel did not
  *               send session data, active stays false for incoming.
@@ -123,8 +116,7 @@ struct luo_session_header {
 	long count;
 	struct list_head list;
 	struct rw_semaphore rwsem;
-	struct luo_session_header_ser *header_ser;
-	struct luo_session_ser *ser;
+	struct kho_block_set block_set;
 	u64 *sessions_pa;
 	bool active;
 };
@@ -143,10 +135,14 @@ static struct luo_session_global luo_session_global = {
 	.incoming = {
 		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 	.outgoing = {
 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 };
 
@@ -173,25 +169,6 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
-static int luo_session_grow_ser(struct luo_session_header *sh)
-{
-	struct luo_session_header_ser *header_ser;
-
-	if (sh->count == LUO_SESSION_MAX)
-		return -ENOMEM;
-
-	if (sh->header_ser)
-		return 0;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	sh->header_ser = header_ser;
-	sh->ser = (void *)(header_ser + 1);
-	return 0;
-}
-
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
@@ -205,7 +182,7 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		err = luo_session_grow_ser(sh);
+		err = kho_block_grow(&sh->block_set, sh->count);
 		if (err)
 			return err;
 	}
@@ -232,6 +209,8 @@ static void luo_session_remove(struct luo_session_header *sh,
 	guard(rwsem_write)(&sh->rwsem);
 	list_del(&session->list);
 	sh->count--;
+	if (sh == &luo_session_global.outgoing)
+		kho_block_shrink(&sh->block_set, sh->count);
 }
 
 static int luo_session_finish_one(struct luo_session *session)
@@ -526,15 +505,17 @@ void __init luo_session_setup_outgoing(u64 *sessions_pa)
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
+	struct luo_session_header *sh = &luo_session_global.incoming;
+	int err;
 
-	if (sessions_pa) {
-		header_ser = phys_to_virt(sessions_pa);
-		luo_session_global.incoming.header_ser = header_ser;
-		luo_session_global.incoming.ser = (void *)(header_ser + 1);
-		luo_session_global.incoming.active = true;
-	}
+	if (!sessions_pa)
+		return 0;
 
+	err = kho_block_restore(&sh->block_set, sessions_pa);
+	if (err)
+		return err;
+
+	sh->active = true;
 	return 0;
 }
 
@@ -576,6 +557,8 @@ int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
+	struct luo_session_ser *ser;
+	struct kho_block_it it;
 	static int err;
 
 	/* If has been deserialized, always return the same error code */
@@ -601,53 +584,62 @@ int luo_session_deserialize(void)
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	for (int i = 0; i < sh->header_ser->count; i++) {
-		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+	kho_block_it_init(&it, &sh->block_set);
+	while ((ser = kho_block_it_read(&it))) {
+		err = luo_session_deserialize_one(sh, ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
 	}
 
-	kho_restore_free(sh->header_ser);
-	sh->header_ser = NULL;
-	sh->ser = NULL;
+err_destroy_blocks:
+	kho_block_destroy(&sh->block_set);
 
-	return 0;
+	return err;
 }
 
 int luo_session_serialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.outgoing;
 	struct luo_session *session;
-	int i = 0;
+	struct kho_block_it it;
 	int err;
 
 	down_write(&luo_session_serialize_rwsem);
 	down_write(&sh->rwsem);
 	*sh->sessions_pa = 0;
 
+	kho_block_it_init(&it, &sh->block_set);
+
 	list_for_each_entry(session, &sh->list, list) {
-		err = luo_session_freeze_one(session, &sh->ser[i]);
+		struct luo_session_ser *ser = kho_block_it_next(&it);
+
+		if (!ser) {
+			err = -ENOSPC;
+			goto err_undo;
+		}
+
+		err = luo_session_freeze_one(session, ser);
 		if (err)
 			goto err_undo;
 
-		strscpy(sh->ser[i].name, session->name,
-			sizeof(sh->ser[i].name));
-		i++;
+		strscpy(ser->name, session->name, sizeof(ser->name));
 	}
 
-	if (sh->header_ser && sh->count > 0) {
-		sh->header_ser->count = sh->count;
-		*sh->sessions_pa = virt_to_phys(sh->header_ser);
-	}
+	kho_block_it_finalize(&it);
+
+	if (sh->sessions_pa && sh->count > 0)
+		*sh->sessions_pa = sh->block_set.head_pa;
 	up_write(&sh->rwsem);
 
 	return 0;
 
 err_undo:
+	kho_block_it_prev(&it);
 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
-		i--;
-		luo_session_unfreeze_one(session, &sh->ser[i]);
-		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+		struct luo_session_ser *ser = kho_block_it_prev(&it);
+
+		luo_session_unfreeze_one(session, ser);
+		memset(ser->name, 0, sizeof(ser->name));
 	}
 	up_write(&sh->rwsem);
 	up_write(&luo_session_serialize_rwsem);
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:03 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:03 +0000
Subject: [PATCH v3 08/11] liveupdate: Remove limit on the number of files per session
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-9-pasha.tatashin@soleen.com>

To remove the fixed limit on the number of preserved files per session,
transition the file metadata serialization from a single contiguous
memory block to a chain of linked blocks.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      |  13 +--
 kernel/liveupdate/luo_file.c     | 142 +++++++++++++++----------------
 kernel/liveupdate/luo_internal.h |   6 +-
 3 files changed, 78 insertions(+), 83 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 79758d92ed5f..16df550ef143 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -35,8 +35,8 @@
  *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
- *     to another preserved memory block containing an array of
- *     `struct luo_file_ser` for all files in that session.
+ *     to the first `struct kho_block_header_ser` for all files in that session.
+ *     Multiple blocks are linked via the `next` field in the header.
  *
  *   - struct luo_file_ser:
  *     Metadata for a single preserved file. Contains the `compatible` string to
@@ -65,7 +65,7 @@
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_COMPAT_BASE		"luo-v3"
+#define LUO_COMPAT_BASE		"luo-v4"
 #define LUO_ABI_COMPATIBLE	LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
 #define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
@@ -103,9 +103,10 @@ struct luo_file_ser {
 
 /**
  * struct luo_file_set_ser - Represents the serialized metadata for file set
- * @files:   The physical address of a contiguous memory block that holds
- *           the serialized state of files (array of luo_file_ser) in this file
- *           set.
+ * @files:   The physical address of the first `struct kho_block_header_ser`.
+ *           This structure is the header for a block of memory containing
+ *           an array of `struct luo_file_ser` entries. Multiple blocks are
+ *           linked via the `next` field in the header.
  * @count:   The total number of files that were part of this session during
  *           serialization. Used for iteration and validation during
  *           restoration.
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 9eec07a9e9fc..06eb083cc759 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
 /* Keep track of files being preserved by LUO */
 static DEFINE_XARRAY(luo_preserved_files);
 
-/* 2 4K pages, give space for 128 files per file_set */
-#define LUO_FILE_PGCNT		2ul
-#define LUO_FILE_MAX							\
-	((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
-
 /**
  * struct luo_file - Represents a single preserved file instance.
  * @fh:            Pointer to the &struct liveupdate_file_handler that manages
@@ -174,39 +169,6 @@ struct luo_file {
 	u64 token;
 };
 
-static int luo_alloc_files_mem(struct luo_file_set *file_set)
-{
-	size_t size;
-	void *mem;
-
-	if (file_set->files)
-		return 0;
-
-	WARN_ON_ONCE(file_set->count);
-
-	size = LUO_FILE_PGCNT << PAGE_SHIFT;
-	mem = kho_alloc_preserve(size);
-	if (IS_ERR(mem))
-		return PTR_ERR(mem);
-
-	file_set->files = mem;
-
-	return 0;
-}
-
-static void luo_free_files_mem(struct luo_file_set *file_set)
-{
-	/* If file_set has files, no need to free preservation memory */
-	if (file_set->count)
-		return;
-
-	if (!file_set->files)
-		return;
-
-	kho_unpreserve_free(file_set->files);
-	file_set->files = NULL;
-}
-
 static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
 				struct file *file)
 {
@@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	if (luo_token_is_used(file_set, token))
 		return -EEXIST;
 
-	if (file_set->count == LUO_FILE_MAX)
-		return -ENOSPC;
+	err = kho_block_grow(&file_set->block_set, file_set->count);
+	if (err)
+		return err;
 
 	file = fget(fd);
-	if (!file)
-		return -EBADF;
-
-	err = luo_alloc_files_mem(file_set);
-	if (err)
-		goto  err_fput;
+	if (!file) {
+		err = -EBADF;
+		goto err_shrink;
+	}
 
 	err = -ENOENT;
 	down_read(&luo_register_rwlock);
@@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 
 	/* err is still -ENOENT if no handler was found */
 	if (err)
-		goto err_free_files_mem;
+		goto err_fput;
 
 	err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
 			file, GFP_KERNEL);
@@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	xa_erase(&luo_preserved_files, luo_get_id(fh, file));
 err_module_put:
 	module_put(fh->ops->owner);
-err_free_files_mem:
-	luo_free_files_mem(file_set);
 err_fput:
 	fput(file);
+err_shrink:
+	kho_block_shrink(&file_set->block_set, file_set->count);
 
 	return err;
 }
@@ -392,13 +353,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 
 		list_del(&luo_file->list);
 		file_set->count--;
+		kho_block_shrink(&file_set->block_set, file_set->count);
 
 		fput(luo_file->file);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	luo_free_files_mem(file_set);
+	kho_block_destroy(&file_set->block_set);
 }
 
 static int luo_file_freeze_one(struct luo_file_set *file_set,
@@ -454,7 +416,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 		luo_file_unfreeze_one(file_set, luo_file);
 	}
 
-	memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
+	kho_block_set_clear(&file_set->block_set);
 }
 
 /**
@@ -493,19 +455,23 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 int luo_file_freeze(struct luo_file_set *file_set,
 		    struct luo_file_set_ser *file_set_ser)
 {
-	struct luo_file_ser *file_ser = file_set->files;
 	struct luo_file *luo_file;
+	struct kho_block_it it;
 	int err;
-	int i;
 
 	if (!file_set->count)
 		return 0;
 
-	if (WARN_ON(!file_ser))
-		return -EINVAL;
+	kho_block_it_init(&it, &file_set->block_set);
 
-	i = 0;
 	list_for_each_entry(luo_file, &file_set->files_list, list) {
+		struct luo_file_ser *file_ser = kho_block_it_next(&it);
+
+		if (!file_ser) {
+			err = -ENOSPC;
+			goto err_unfreeze;
+		}
+
 		err = luo_file_freeze_one(file_set, luo_file);
 		if (err < 0) {
 			pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
@@ -514,16 +480,21 @@ int luo_file_freeze(struct luo_file_set *file_set,
 			goto err_unfreeze;
 		}
 
-		strscpy(file_ser[i].compatible, luo_file->fh->compatible,
-			sizeof(file_ser[i].compatible));
-		file_ser[i].data = luo_file->serialized_data;
-		file_ser[i].token = luo_file->token;
-		i++;
+		strscpy(file_ser->compatible, luo_file->fh->compatible,
+			sizeof(file_ser->compatible));
+		file_ser->data = luo_file->serialized_data;
+		file_ser->token = luo_file->token;
 	}
+	kho_block_it_finalize(&it);
 
 	file_set_ser->count = file_set->count;
-	if (file_set->files)
-		file_set_ser->files = virt_to_phys(file_set->files);
+	if (!list_empty(&file_set->block_set.blocks)) {
+		struct kho_block *block;
+
+		block = list_first_entry(&file_set->block_set.blocks,
+					 struct kho_block, list);
+		file_set_ser->files = virt_to_phys(block->ser);
+	}
 
 	return 0;
 
@@ -741,14 +712,12 @@ int luo_file_finish(struct luo_file_set *file_set)
 		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
+		kho_block_shrink(&file_set->block_set, file_set->count);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	if (file_set->files) {
-		kho_restore_free(file_set->files);
-		file_set->files = NULL;
-	}
+	kho_block_destroy(&file_set->block_set);
 
 	return 0;
 }
@@ -822,16 +791,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	struct kho_block_it it;
 	int err;
-	u64 i;
 
 	if (!file_set_ser->files) {
 		WARN_ON(file_set_ser->count);
 		return 0;
 	}
 
-	file_set->count = file_set_ser->count;
-	file_set->files = phys_to_virt(file_set_ser->files);
+	file_set->count = 0;
+	err = kho_block_restore(&file_set->block_set, file_set_ser->files);
+	if (err)
+		return err;
 
 	/*
 	 * Note on error handling:
@@ -848,25 +819,48 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	file_ser = file_set->files;
-	for (i = 0; i < file_set->count; i++) {
-		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+	kho_block_it_init(&it, &file_set->block_set);
+	while ((file_ser = kho_block_it_read(&it))) {
+		err = luo_file_deserialize_one(file_set, file_ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
+		file_set->count++;
+	}
+
+	if (file_set->count != file_set_ser->count) {
+		pr_warn("File count mismatch: expected %llu, found %ld\n",
+			file_set_ser->count, file_set->count);
 	}
 
 	return 0;
+
+err_destroy_blocks:
+	while (!list_empty(&file_set->files_list)) {
+		struct luo_file *luo_file;
+
+		luo_file = list_first_entry(&file_set->files_list,
+					    struct luo_file, list);
+		list_del(&luo_file->list);
+		module_put(luo_file->fh->ops->owner);
+		mutex_destroy(&luo_file->mutex);
+		kfree(luo_file);
+	}
+	file_set->count = 0;
+	kho_block_destroy(&file_set->block_set);
+	return err;
 }
 
 void luo_file_set_init(struct luo_file_set *file_set)
 {
 	INIT_LIST_HEAD(&file_set->files_list);
+	kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
 }
 
 void luo_file_set_destroy(struct luo_file_set *file_set)
 {
 	WARN_ON(file_set->count);
 	WARN_ON(!list_empty(&file_set->files_list));
+	WARN_ON(!list_empty(&file_set->block_set.blocks));
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 28ebb4f3000b..2c5e8bcb219c 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -10,6 +10,7 @@
 
 #include <linux/liveupdate.h>
 #include <linux/uaccess.h>
+#include <linux/kho_block.h>
 
 struct luo_ucmd {
 	void __user *ubuffer;
@@ -44,14 +45,13 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
  * struct luo_file_set - A set of files that belong to the same sessions.
  * @files_list: An ordered list of files associated with this session, it is
  *              ordered by preservation time.
- * @files:      The physically contiguous memory block that holds the serialized
- *              state of files.
+ * @block_set:  The set of serialization blocks.
  * @count:      A counter tracking the number of files currently stored in the
  *              @files_list for this session.
  */
 struct luo_file_set {
 	struct list_head files_list;
-	struct luo_file_ser *files;
+	struct kho_block_set block_set;
 	long count;
 };
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:04 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:04 +0000
Subject: [PATCH v3 09/11] selftests/liveupdate: Test session and file limit removal
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-10-pasha.tatashin@soleen.com>

With the removal of static limits on the number of sessions and files per
session, the orchestrator now uses dynamic allocation.

Add new test cases to verify that the system can handle a large number of
sessions and files. These tests ensure that the dynamic block allocation
and reuse logic for session metadata and outgoing files work correctly
beyond the previous static limits.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 .../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
 .../selftests/liveupdate/luo_test_utils.c     | 24 ++++++
 .../selftests/liveupdate/luo_test_utils.h     |  2 +
 3 files changed, 101 insertions(+)

diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index c7d94b9181e1..502fb3567e38 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -26,6 +26,7 @@
 
 #include <linux/liveupdate.h>
 
+#include "luo_test_utils.h"
 #include "../kselftest.h"
 #include "../kselftest_harness.h"
 
@@ -499,4 +500,78 @@ TEST_F(liveupdate_device, get_session_name_max_length)
 	ASSERT_EQ(close(session_fd), 0);
 }
 
+/*
+ * Test Case: Manage Many Sessions
+ *
+ * Verifies that a large number of sessions can be created and then
+ * destroyed during normal system operation. This specifically tests the
+ * dynamic block allocation and reuse logic for session metadata management
+ * without preserving any files.
+ */
+TEST_F(liveupdate_device, preserve_many_sessions)
+{
+#define MANY_SESSIONS 2000
+	int session_fds[MANY_SESSIONS];
+	int ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_SESSIONS);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_SESSIONS; i++) {
+		char name[64];
+
+		snprintf(name, sizeof(name), "many-session-%d", i);
+		session_fds[i] = create_session(self->fd1, name);
+		ASSERT_GE(session_fds[i], 0);
+	}
+
+	for (i = 0; i < MANY_SESSIONS; i++)
+		ASSERT_EQ(close(session_fds[i]), 0);
+}
+
+/*
+ * Test Case: Preserve Many Files
+ *
+ * Verifies that a large number of files can be preserved in a single session
+ * and then destroyed during normal system operation. This tests the dynamic
+ * block allocation and management for outgoing files.
+ */
+TEST_F(liveupdate_device, preserve_many_files)
+{
+#define MANY_FILES 500
+	int mem_fds[MANY_FILES];
+	int session_fd, ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	session_fd = create_session(self->fd1, "many-files-test");
+	ASSERT_GE(session_fd, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_FILES + 10);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_FILES; i++) {
+		mem_fds[i] = memfd_create("test-memfd", 0);
+		ASSERT_GE(mem_fds[i], 0);
+		ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
+	}
+
+	for (i = 0; i < MANY_FILES; i++)
+		ASSERT_EQ(close(mem_fds[i]), 0);
+
+	ASSERT_EQ(close(session_fd), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
index 3c8721c505df..333a3530051b 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.c
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -17,6 +17,7 @@
 #include <sys/syscall.h>
 #include <sys/mman.h>
 #include <sys/types.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <stdarg.h>
@@ -28,6 +29,29 @@ int luo_open_device(void)
 	return open(LUO_DEVICE, O_RDWR);
 }
 
+int luo_ensure_nofile_limit(long min_limit)
+{
+	struct rlimit hl;
+
+	/* Allow to extra files to be used by test itself */
+	min_limit += 32;
+
+	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	if (hl.rlim_cur >= min_limit)
+		return 0;
+
+	hl.rlim_cur = min_limit;
+	if (hl.rlim_cur > hl.rlim_max)
+		hl.rlim_max = hl.rlim_cur;
+
+	if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	return 0;
+}
+
 int luo_create_session(int luo_fd, const char *name)
 {
 	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
index 90099bf49577..6a0d85386613 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.h
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -26,6 +26,8 @@ int luo_create_session(int luo_fd, const char *name);
 int luo_retrieve_session(int luo_fd, const char *name);
 int luo_session_finish(int session_fd);
 
+int luo_ensure_nofile_limit(long min_limit);
+
 int create_and_preserve_memfd(int session_fd, int token, const char *data);
 int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:05 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:05 +0000
Subject: [PATCH v3 10/11] selftests/liveupdate: Add stress-sessions kexec test
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-11-pasha.tatashin@soleen.com>

Add a new test that creates 2000 LUO sessions before a kexec
reboot and verifies their presence after the reboot. This ensures
that the linked-block serialization mechanism works correctly for
a large number of sessions.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 tools/testing/selftests/liveupdate/Makefile   |   1 +
 .../liveupdate/luo_stress_sessions.c          | 102 ++++++++++++++++++
 2 files changed, 103 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 080754787ede..ed7534468386 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -6,6 +6,7 @@ TEST_GEN_PROGS += liveupdate
 
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
+TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
new file mode 100644
index 000000000000..f201b1839d1d
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of sessions across a kexec
+ * reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_SESSIONS 2000
+#define STATE_SESSION_NAME "kexec_many_state"
+#define STATE_MEMFD_TOKEN 999
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int ret, i;
+
+	ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+	ret = luo_ensure_nofile_limit(NUM_SESSIONS);
+	if (ret == -EPERM)
+		ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+	if (ret < 0)
+		ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating %d sessions...\n", NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_create_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_create_session for '%s' at index %d",
+				  name, i);
+		}
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully created %d sessions.\n",
+		       NUM_SESSIONS);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving and finishing %d sessions...\n",
+		       NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_retrieve_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_retrieve_session for '%s' at index %d",
+				  name, i);
+		}
+
+		if (luo_session_finish(s_fd) < 0) {
+			fail_exit("luo_session_finish for '%s' at index %d",
+				  name, i);
+		}
+		close(s_fd);
+	}
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-SESSIONS KEXEC TEST PASSED (%d sessions) ---\n",
+		       NUM_SESSIONS);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Thu May 28 20:21:06 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Fri, 29 May 2026 03:21:06 +0000
Subject: [PATCH v3 11/11] selftests/liveupdate: Add stress-files kexec test
In-Reply-To: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
References: <20260529032106.1505796-1-pasha.tatashin@soleen.com>
Message-ID: <20260529032106.1505796-12-pasha.tatashin@soleen.com>

Add a new luo_stress_files kexec test that verifies preserving and
retrieving 500 files across a kexec reboot.

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 tools/testing/selftests/liveupdate/Makefile   |   1 +
 .../selftests/liveupdate/luo_stress_files.c   | 101 ++++++++++++++++++
 2 files changed, 102 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index ed7534468386..30689d22cb02 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -7,6 +7,7 @@ TEST_GEN_PROGS += liveupdate
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
 TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
+TEST_GEN_PROGS_EXTENDED += luo_stress_files
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c
new file mode 100644
index 000000000000..b6a0c3899890
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_files.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of files per session across
+ * a kexec reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_FILES 500
+#define STATE_SESSION_NAME "kexec_many_files_state"
+#define STATE_MEMFD_TOKEN 9999
+#define TEST_SESSION_NAME "many_files_session"
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int session_fd, ret, i;
+
+	ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+	ret = luo_ensure_nofile_limit(NUM_FILES);
+	if (ret == -EPERM)
+		ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+	if (ret < 0)
+		ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_create_session");
+
+	ksft_print_msg("[STAGE 1] Preserving %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		if (create_and_preserve_memfd(session_fd, i, data) < 0)
+			fail_exit("create_and_preserve_memfd for index %d", i);
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully preserved %d files.\n", NUM_FILES);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int session_fd;
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_retrieve_session");
+
+	ksft_print_msg("[STAGE 2] Verifying %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		if (restore_and_verify_memfd(session_fd, i, data) < 0)
+			fail_exit("restore_and_verify_memfd for index %d", i);
+	}
+
+	ksft_print_msg("[STAGE 2] Finishing test session...\n");
+	if (luo_session_finish(session_fd) < 0)
+		fail_exit("luo_session_finish for test session");
+	close(session_fd);
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-FILES KEXEC TEST PASSED (%d files) ---\n",
+		       NUM_FILES);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From seanjc at google.com  Fri May 29 06:57:41 2026
From: seanjc at google.com (Sean Christopherson)
Date: Fri, 29 May 2026 06:57:41 -0700
Subject: [RFC PATCH v1 0/8] liveupdate: kvm: Guest_memfd preservation
In-Reply-To: <9huzwlwnbgdd.fsf@tarunix.c.googlers.com>
References: <cover.1779080766.git.tarunsahu@google.com> <9huzwlwnbgdd.fsf@tarunix.c.googlers.com>
Message-ID: <ahmbVaCUcBrYK8qO@google.com>

On Thu, May 28, 2026, tarunsahu at google.com wrote:
> 3. No need to preserve kvm->mem_attr_array, as this is on deprecating
>    path and For this series, where only fully shared guest_memfd is
>    supported for liveupdate, preservation of these attributes not
>    required.
> 
> Due to time constraints:
> we have one open Question left:
> 1. How to check guest_memfd is fully shared. Currently that is being
>    done by INIT_SHARED flag. But with in-place it is going to change
>    its meaning and it will mean only durng the creation of guest_memfd
>    it is shared whose folios can be converted to private.
>    So for this series, I am looking for suggestion, what is the best way
>    to find the sharedness of guest_memfd which will be compatible with
>    in-place series. OR we can go like this (my personnel recommendation):
> 
>    if (guest_memfd_is_fully_shared())
>       return true; // support the preservation
> 
>    // This series
>    int guest_memfd_is_fully_shared(void) {
>        if (guest_memfd->flags & GUEST_MEMFD_FLAG_INIT_SHARED)
>           return true;
>          return false;
>    }
> 
>    TO::
>    // Once in-place conversion series lands.
>    int guest_memfd_is_fully_shared(void) {
>        if (No Private flags in (guest_memfd->attributes))
>           return true;
>          return false;
>    }

Given that lack of support isn't going to be limited to _just_ guest_memfd,
simply disallow preservation if the VM supports private memory:

	if (kvm_arch_has_private_mem(kvm))
		return -EOPNOTSUPP;


From dongtai.guo at linux.dev  Fri May 29 07:32:34 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Fri, 29 May 2026 22:32:34 +0800
Subject: No subject
Message-ID: <20260529143238.169169-1-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#26: 
containing only /chosen with the two KHO properties.  Since DEVICE_TREE_GUID

ERROR: Invalid commit separator - some tools may have problems applying this
#34: 
-------------------------------

total: 2 errors, 1 warnings, 0 lines checked
>
Date: Fri, 29 May 2026 21:54:01 +0800
Subject: [PATCH v2 0/4] LoongArch: add KHO support and selftests

This series adds Kexec Handover (KHO) support for LoongArch and extends
the KHO selftest infrastructure to run on LoongArch under QEMU.

KHO passes metadata (the KHO state FDT and scratch area addresses) to the
second kernel via the FDT /chosen node, using the linux,kho-fdt and
linux,kho-scratch properties that drivers/of/kexec.c:kho_add_chosen()
writes and drivers/of/fdt.c:early_init_dt_check_kho() reads.

KHO support (patches 1-2):

Patch 1 adds KHO support for FDT-based systems (initial_boot_params !=
NULL, e.g. QEMU virt without OVMF).  kho_load_fdt() copies the running
kernel's FDT, appends linux,kho-fdt and linux,kho-scratch to /chosen,
and loads the result as a kexec segment.  machine_kexec() updates the
DEVICE_TREE_GUID entry in the EFI config table to point to this segment
so the second kernel's fdt_setup() can find and parse it.

Patch 2 adds KHO support for ACPI-only systems (initial_boot_params ==
NULL, e.g. LoongArch servers with UEFI or QEMU with OVMF).  Because no
system FDT is available, kho_load_fdt() builds a minimal FDT from
scratch containing only /chosen with the two KHO properties.  Since
DEVICE_TREE_GUID is absent from the EFI config table on ACPI-only
systems, a new extended config table is built with the entry appended
and loaded as a kexec segment; machine_kexec() switches st->tables to
point to it before jumping.  The second kernel's fdt_setup() calls
efi_fdt_pointer() to detect the KHO FDT and passes it to
early_init_dt_check_kho().

Selftest support (patches 3-4):

Patch 3 adds loongarch.conf and extends vmtest.sh to recognise loongarch64
as a build target.  The LoongArch virt machine is FDT-only (no ACPI), so
'earlycon' must appear on the kernel cmdline or the console UART is never
discovered.  PS/2 input devices are also disabled since QEMU's LoongArch
virt machine has no i8042 controller; the fallback port probe hits a page
fault and panics before reaching userspace.

Patch 4 handles QEMU not exiting after kexec on LoongArch.  QEMU provides
no EFI runtime services, so machine_restart() falls through to an infinite
idle loop.  QEMU_NEEDS_KILL=1 in loongarch.conf signals vmtest.sh to run
QEMU in the background, poll the serial output for the test verdict, and
kill QEMU once it appears, so the test completes unattended.

George Guo (4):
  LoongArch: kexec: add KHO support for FDT-based systems
  LoongArch: kexec: add KHO support for ACPI-only systems
  selftests/kho: add LoongArch vmtest support
  selftests/kho: handle QEMU not exiting after kexec on LoongArch

 arch/loongarch/Kconfig                     |   3 +
 arch/loongarch/include/asm/kexec.h         |   7 +
 arch/loongarch/kernel/machine_kexec.c      |  38 +++
 arch/loongarch/kernel/machine_kexec_file.c | 256 +++++++++++++++++++++
 arch/loongarch/kernel/setup.c              |  21 +-
 tools/testing/selftests/kho/loongarch.conf |  13 ++
 tools/testing/selftests/kho/vmtest.sh      |  35 ++-
 7 files changed, 365 insertions(+), 8 deletions(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf

-- 
2.25.1



From dongtai.guo at linux.dev  Fri May 29 07:32:36 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Fri, 29 May 2026 22:32:36 +0800
Subject: [PATCH v2 2/4] LoongArch: kexec: add KHO support for ACPI-only systems
In-Reply-To: <20260529143238.169169-1-dongtai.guo@linux.dev>
References: <20260529143238.169169-1-dongtai.guo@linux.dev>
Message-ID: <20260529143238.169169-3-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

On ACPI-only systems fdt_setup() returns early when it detects ACPI,
so initial_boot_params remains NULL and the FDT-based kho_load_fdt()
path cannot be used.

machine_kexec_file.c:
- kho_load_fdt(): add an else branch that builds a minimal FDT from
  scratch (SZ_1K) containing only a /chosen node with linux,kho-fdt
  and linux,kho-scratch properties, using the libfdt creation API.
- Since DEVICE_TREE_GUID is absent from the EFI config table on
  ACPI-only systems, build a new config table with DEVICE_TREE_GUID
  appended and load it as a kexec segment.  Store the result in
  kimage_arch so machine_kexec() can switch st->tables before jumping.
- arch_kimage_file_post_load_cleanup(): free the efi_tables kvmalloc
  buffer once the kexec image has been loaded.

machine_kexec.c:
- Before jumping, update the EFI system table pointer: for FDT-based
  systems update the existing DEVICE_TREE_GUID entry; for ACPI-only
  systems switch st->tables / st->nr_tables to the new extended table.

setup.c:
- fdt_setup(): when ACPI is detected, use efi_fdt_pointer() to detect
  whether this is a KHO kexec boot.  The first kernel switches the EFI
  config table to a new one that includes a DEVICE_TREE_GUID entry
  pointing to the minimal KHO FDT.  If found, call early_init_dt_scan()
  so early_init_dt_check_kho() can consume linux,kho-fdt and
  linux,kho-scratch, then reset initial_boot_params to NULL so the rest
  of the ACPI boot path is unaffected.

kexec.h:
- Add efi_tables, efi_tables_mem, efi_tables_cnt to kimage_arch.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/include/asm/kexec.h         |   3 +
 arch/loongarch/kernel/machine_kexec.c      |  11 ++
 arch/loongarch/kernel/machine_kexec_file.c | 162 +++++++++++++++++++--
 arch/loongarch/kernel/setup.c              |  21 ++-
 4 files changed, 184 insertions(+), 13 deletions(-)

diff --git a/arch/loongarch/include/asm/kexec.h b/arch/loongarch/include/asm/kexec.h
index adf54bfcdd49..e1abaf40b06a 100644
--- a/arch/loongarch/include/asm/kexec.h
+++ b/arch/loongarch/include/asm/kexec.h
@@ -42,6 +42,9 @@ struct kimage_arch {
 #ifdef CONFIG_KEXEC_HANDOVER
 	void *fdt;		/* virtual address of KHO FDT segment buffer */
 	unsigned long fdt_mem;	/* physical address of KHO FDT segment */
+	void *efi_tables;		/* new EFI config table buffer (virtual) */
+	unsigned long efi_tables_mem;	/* physical address of new EFI config table */
+	unsigned long efi_tables_cnt;	/* number of entries in new EFI config table */
 #endif
 };
 
diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c
index 130f1adfa515..4ee9a433d2de 100644
--- a/arch/loongarch/kernel/machine_kexec.c
+++ b/arch/loongarch/kernel/machine_kexec.c
@@ -308,6 +308,17 @@ void machine_kexec(struct kimage *image)
 				break;
 			}
 		}
+	} else if (internal->efi_tables_mem) {
+		/*
+		 * ACPI-only system: DEVICE_TREE_GUID was not in the original
+		 * EFI config table.  Switch to the new table that was built in
+		 * kho_load_fdt() with DEVICE_TREE_GUID appended.
+		 */
+		efi_system_table_t *st =
+			(efi_system_table_t *)TO_CACHE(systable_ptr);
+
+		st->tables    = internal->efi_tables_mem;
+		st->nr_tables = internal->efi_tables_cnt;
 	}
 #endif
 
diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index bf1e8c1c7e70..c1955d991061 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -10,6 +10,7 @@
 
 #define pr_fmt(fmt) "kexec_file: " fmt
 
+#include <linux/efi.h>
 #include <linux/ioport.h>
 #include <linux/kernel.h>
 #include <linux/kexec.h>
@@ -20,6 +21,7 @@
 #include <linux/string.h>
 #include <linux/types.h>
 #include <linux/vmalloc.h>
+#include <asm/addrspace.h>
 #include <asm/bootinfo.h>
 
 const struct kexec_file_ops * const kexec_file_loaders[] = {
@@ -37,6 +39,8 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 #ifdef CONFIG_KEXEC_HANDOVER
 	kvfree(image->arch.fdt);
 	image->arch.fdt = NULL;
+	kvfree(image->arch.efi_tables);
+	image->arch.efi_tables = NULL;
 #endif
 
 	return kexec_image_post_load_cleanup_default(image);
@@ -68,6 +72,13 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
  * segment.  The second kernel reads linux,kho-fdt and linux,kho-scratch
  * from /chosen via early_init_dt_check_kho() and calls kho_populate().
  *
+ * On FDT-based systems (initial_boot_params != NULL), the current FDT is
+ * copied and the KHO properties are appended to /chosen.
+ *
+ * On ACPI-only systems (initial_boot_params == NULL), a minimal FDT
+ * containing only /chosen is built from scratch.  machine_kexec() updates
+ * the EFI config table DEVICE_TREE_GUID entry to point to this segment so
+ * that the second kernel's fdt_setup() can find and parse it.
  */
 static int kho_load_fdt(struct kimage *image)
 {
@@ -143,24 +154,151 @@ static int kho_load_fdt(struct kimage *image)
 		 * once the kexec image has been loaded.
 		 */
 		fdt_pack(fdt);
+	} else {
+		/*
+		 * ACPI boot: build a minimal FDT containing only /chosen with
+		 * the two KHO properties.  No system FDT is available to copy.
+		 */
 
-		kbuf.buffer	= fdt;
-		kbuf.bufsz	= fdt_totalsize(fdt);
-		kbuf.memsz	= kbuf.bufsz;
-		kbuf.buf_align	= PAGE_SIZE;
-		kbuf.mem	= KEXEC_BUF_MEM_UNKNOWN;
+		__be64 prop[2];
 
-		ret = kexec_add_buffer(&kbuf);
-		if (ret)
+		fdt_size = SZ_1K;
+		fdt = kvmalloc(fdt_size, GFP_KERNEL);
+		if (!fdt)
+			return -ENOMEM;
+
+		ret = fdt_create(fdt, fdt_size);
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_finish_reservemap(fdt);
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_begin_node(fdt, "");	/* root */
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_property_u32(fdt, "#address-cells", 2);
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_property_u32(fdt, "#size-cells", 2);
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_begin_node(fdt, "chosen");
+		if (ret < 0)
 			goto out_free;
 
-		image->arch.fdt     = fdt;
-		image->arch.fdt_mem = kbuf.mem;
-		return 0;
-	} else {
-		return -EINVAL;
+		prop[0] = cpu_to_be64(image->kho.fdt);
+		prop[1] = cpu_to_be64(PAGE_SIZE);
+		ret = fdt_property(fdt, "linux,kho-fdt", prop, sizeof(prop));
+		if (ret < 0)
+			goto out_free;
+
+		prop[0] = cpu_to_be64(image->kho.scratch->mem);
+		prop[1] = cpu_to_be64(image->kho.scratch->bufsz);
+		ret = fdt_property(fdt, "linux,kho-scratch", prop, sizeof(prop));
+		if (ret < 0)
+			goto out_free;
+
+		ret = fdt_end_node(fdt);	/* chosen */
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_end_node(fdt);	/* root */
+		if (ret < 0)
+			goto out_free;
+		ret = fdt_finish(fdt);
+		if (ret < 0)
+			goto out_free;
 	}
 
+	kbuf.buffer	= fdt;
+	kbuf.bufsz	= fdt_totalsize(fdt);
+	kbuf.memsz	= kbuf.bufsz;
+	kbuf.buf_align	= PAGE_SIZE;
+	kbuf.mem	= KEXEC_BUF_MEM_UNKNOWN;
+
+	ret = kexec_add_buffer(&kbuf);
+	if (ret)
+		goto out_free;
+
+	image->arch.fdt     = fdt;
+	image->arch.fdt_mem = kbuf.mem;
+
+	/*
+	 * On ACPI-only systems DEVICE_TREE_GUID is not in the EFI config
+	 * table, so the second kernel's efi_fdt_pointer() cannot locate the
+	 * KHO FDT.  Build a new EFI config table with DEVICE_TREE_GUID added
+	 * and load it as a kexec segment; machine_kexec() will update
+	 * st->tables / st->nr_tables to point to it before jumping.
+	 */
+
+	/*
+	 * fw_arg2 is the EFI system table physical address passed by the
+	 * firmware/bootloader.  Use it directly here because
+	 * image->arch.systable_ptr is set later in machine_kexec_prepare(),
+	 * which runs after load_other_segments() / kho_load_fdt().
+	 */
+	if (!initial_boot_params && fw_arg2) {
+		efi_system_table_t *st =
+			(efi_system_table_t *)TO_CACHE(fw_arg2);
+		efi_config_table_t *ct =
+			(efi_config_table_t *)TO_CACHE((unsigned long)st->tables);
+		unsigned long i;
+		bool found = false;
+
+		/*
+		 * Scan the original config table;
+		 * DEVICE_TREE_GUID is absent on ACPI-only systems.
+		 */
+		for (i = 0; i < st->nr_tables; i++) {
+			if (!efi_guidcmp(ct[i].guid, DEVICE_TREE_GUID)) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found) {
+			size_t old_sz = st->nr_tables * sizeof(efi_config_table_t);
+			size_t new_sz = old_sz + sizeof(efi_config_table_t);
+			efi_config_table_t *new_ct;
+			struct kexec_buf tbuf = {
+				.image		= image,
+				.buf_min	= 0,
+				.buf_max	= ULONG_MAX,
+				.top_down	= true,
+			};
+
+			/*
+			 * Allocate a new table with n+1 entries and append
+			 * the DEVICE_TREE_GUID entry.
+			 */
+			new_ct = kvmalloc(new_sz, GFP_KERNEL);
+			if (!new_ct)
+				return -ENOMEM;
+
+			memcpy(new_ct, ct, old_sz);
+			new_ct[st->nr_tables].guid  = DEVICE_TREE_GUID;
+			new_ct[st->nr_tables].table = (void *)image->arch.fdt_mem;
+
+			/* Register the new config table as a kexec segment. */
+			tbuf.buffer   = new_ct;
+			tbuf.bufsz    = new_sz;
+			tbuf.memsz    = new_sz;
+			tbuf.buf_align = sizeof(void *);
+			tbuf.mem      = KEXEC_BUF_MEM_UNKNOWN;
+
+			ret = kexec_add_buffer(&tbuf);
+			if (ret) {
+				kvfree(new_ct);
+				return ret;
+			}
+
+			image->arch.efi_tables     = new_ct;
+			image->arch.efi_tables_mem = tbuf.mem;
+			image->arch.efi_tables_cnt = st->nr_tables + 1;
+		}
+	}
+
+	return 0;
+
 out_free:
 	kvfree(fdt);
 	return ret;
diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
index 839b23edee87..c82067d1dc75 100644
--- a/arch/loongarch/kernel/setup.c
+++ b/arch/loongarch/kernel/setup.c
@@ -286,8 +286,27 @@ static void __init fdt_setup(void)
 	void *fdt_pointer;
 
 	/* ACPI-based systems do not require parsing fdt */
-	if (acpi_os_get_root_pointer())
+	if (acpi_os_get_root_pointer()) {
+#ifdef CONFIG_KEXEC_HANDOVER
+		/*
+		 * On a KHO kexec boot the first kernel builds a minimal FDT
+		 * containing only /chosen with linux,kho-fdt and
+		 * linux,kho-scratch, and switches the EFI config table to a
+		 * new one that includes a DEVICE_TREE_GUID entry pointing to
+		 * it.  Use efi_fdt_pointer() to detect this case.
+		 *
+		 * Call early_init_dt_scan() to let early_init_dt_check_kho()
+		 * consume the KHO data, then reset initial_boot_params so the
+		 * rest of the ACPI boot path is not confused by this FDT.
+		 */
+		fdt_pointer = efi_fdt_pointer();
+		if (fdt_pointer && !fdt_check_header(fdt_pointer)) {
+			early_init_dt_scan(fdt_pointer, __pa(fdt_pointer));
+			initial_boot_params = NULL;
+		}
+#endif
 		return;
+	}
 
 	/* Prefer to use built-in dtb, checking its legality first. */
 	if (IS_ENABLED(CONFIG_BUILTIN_DTB) && !fdt_check_header(__dtb_start))
-- 
2.25.1



From dongtai.guo at linux.dev  Fri May 29 07:32:35 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Fri, 29 May 2026 22:32:35 +0800
Subject: [PATCH v2 1/4] LoongArch: kexec: add KHO support for FDT-based systems
In-Reply-To: <20260529143238.169169-1-dongtai.guo@linux.dev>
References: <20260529143238.169169-1-dongtai.guo@linux.dev>
Message-ID: <20260529143238.169169-2-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

Enable Kexec Handover (KHO) on LoongArch64 for FDT-based systems.

- Kconfig: select ARCH_SUPPORTS_KEXEC_HANDOVER for CONFIG_64BIT
- kexec.h: add fdt/fdt_mem fields to kimage_arch to hold the KHO FDT
  kexec segment virtual and physical addresses
- machine_kexec_file.c: add kho_load_fdt() which copies the running
  kernel's FDT (initial_boot_params), appends linux,kho-fdt and
  linux,kho-scratch properties to /chosen, and loads the result as a
  kexec segment; called from load_other_segments().  Returns -EINVAL
  when initial_boot_params is NULL (ACPI-only boot) since that path
  requires separate handling.
- machine_kexec.c: before jumping to the new kernel, update the
  DEVICE_TREE_GUID entry in the EFI config table to point to the KHO
  FDT segment so the second kernel finds it via efi_fdt_pointer() and
  early_init_dt_check_kho() calls kho_populate()

Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 arch/loongarch/Kconfig                     |   3 +
 arch/loongarch/include/asm/kexec.h         |   4 +
 arch/loongarch/kernel/machine_kexec.c      |  27 +++++
 arch/loongarch/kernel/machine_kexec_file.c | 118 +++++++++++++++++++++
 4 files changed, 152 insertions(+)

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 606597da46b8..d494418545f5 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -684,6 +684,9 @@ config ARCH_SUPPORTS_KEXEC
 config ARCH_SUPPORTS_KEXEC_FILE
 	def_bool 64BIT
 
+config ARCH_SUPPORTS_KEXEC_HANDOVER
+	def_bool 64BIT
+
 config ARCH_SELECTS_KEXEC_FILE
 	def_bool 64BIT
 	depends on KEXEC_FILE
diff --git a/arch/loongarch/include/asm/kexec.h b/arch/loongarch/include/asm/kexec.h
index 209fa43222e1..adf54bfcdd49 100644
--- a/arch/loongarch/include/asm/kexec.h
+++ b/arch/loongarch/include/asm/kexec.h
@@ -39,6 +39,10 @@ struct kimage_arch {
 	unsigned long efi_boot;
 	unsigned long cmdline_ptr;
 	unsigned long systable_ptr;
+#ifdef CONFIG_KEXEC_HANDOVER
+	void *fdt;		/* virtual address of KHO FDT segment buffer */
+	unsigned long fdt_mem;	/* physical address of KHO FDT segment */
+#endif
 };
 
 #ifdef CONFIG_KEXEC_FILE
diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c
index d7fafda1d541..130f1adfa515 100644
--- a/arch/loongarch/kernel/machine_kexec.c
+++ b/arch/loongarch/kernel/machine_kexec.c
@@ -6,6 +6,7 @@
  */
 #include <linux/compiler.h>
 #include <linux/cpu.h>
+#include <linux/efi.h>
 #include <linux/kexec.h>
 #include <linux/crash_dump.h>
 #include <linux/delay.h>
@@ -284,6 +285,32 @@ void machine_kexec(struct kimage *image)
 	pr_notice("We will call new kernel at 0x%lx\n", start_addr);
 	pr_notice("Bye ...\n");
 
+#ifdef CONFIG_KEXEC_HANDOVER
+	/*
+	 * Point the EFI FDTPTR config table entry at the modified FDT so the
+	 * second kernel picks up the linux,kho-fdt and linux,kho-scratch
+	 * properties via early_init_dt_check_kho().
+	 */
+	if (internal->fdt_mem) {
+		/*
+		 * FDT-based system: DEVICE_TREE_GUID already exists in the EFI
+		 * config table; just update its pointer to our KHO FDT.
+		 */
+		efi_system_table_t *st =
+			(efi_system_table_t *)TO_CACHE(systable_ptr);
+		efi_config_table_t *ct =
+			(efi_config_table_t *)TO_CACHE((unsigned long)st->tables);
+		unsigned long i;
+
+		for (i = 0; i < st->nr_tables; i++) {
+			if (!efi_guidcmp(ct[i].guid, DEVICE_TREE_GUID)) {
+				ct[i].table = (void *)internal->fdt_mem;
+				break;
+			}
+		}
+	}
+#endif
+
 	/* Make reboot code buffer available to the boot CPU. */
 	flush_cache_all();
 
diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..bf1e8c1c7e70 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -13,7 +13,9 @@
 #include <linux/ioport.h>
 #include <linux/kernel.h>
 #include <linux/kexec.h>
+#include <linux/libfdt.h>
 #include <linux/memblock.h>
+#include <linux/of_fdt.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -32,6 +34,11 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 	image->elf_headers = NULL;
 	image->elf_headers_sz = 0;
 
+#ifdef CONFIG_KEXEC_HANDOVER
+	kvfree(image->arch.fdt);
+	image->arch.fdt = NULL;
+#endif
+
 	return kexec_image_post_load_cleanup_default(image);
 }
 
@@ -55,6 +62,111 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
 	*cmdline_tmplen += initrd_strlen;
 }
 
+#ifdef CONFIG_KEXEC_HANDOVER
+/*
+ * Add KHO metadata to an FDT /chosen node and load the FDT as a kexec
+ * segment.  The second kernel reads linux,kho-fdt and linux,kho-scratch
+ * from /chosen via early_init_dt_check_kho() and calls kho_populate().
+ *
+ */
+static int kho_load_fdt(struct kimage *image)
+{
+	void *fdt;
+	int ret, chosen_node;
+	size_t fdt_size;
+	struct kexec_buf kbuf = {
+		.image		= image,
+		.buf_min	= 0,
+		.buf_max	= ULONG_MAX,
+		.top_down	= true,
+	};
+
+	if (!image->kho.fdt || !image->kho.scratch)
+		return 0;
+
+	if (initial_boot_params) {
+		/*
+		 * FDT boot: copy the running kernel's FDT and append KHO
+		 * properties to /chosen.
+		 */
+
+		/*
+		 * Only two KHO properties are added to /chosen (linux,kho-fdt
+		 * and linux,kho-scratch), so SZ_1K of extra space is
+		 * sufficient.
+		 */
+		fdt_size = fdt_totalsize(initial_boot_params) + SZ_1K;
+		fdt = kvmalloc(fdt_size, GFP_KERNEL);
+		if (!fdt)
+			return -ENOMEM;
+
+		ret = fdt_open_into(initial_boot_params, fdt, fdt_size);
+		if (ret < 0) {
+			pr_err("Failed to open FDT: %d\n", ret);
+			goto out_free;
+		}
+
+		chosen_node = fdt_path_offset(fdt, "/chosen");
+		if (chosen_node == -FDT_ERR_NOTFOUND) {
+			pr_debug("No /chosen node in FDT, creating one\n");
+			chosen_node = fdt_add_subnode(fdt,
+						      fdt_path_offset(fdt, "/"),
+						      "chosen");
+		}
+		if (chosen_node < 0) {
+			ret = chosen_node;
+			goto out_free;
+		}
+
+		/* Remove stale KHO properties left by a previous kexec load */
+		fdt_delprop(fdt, chosen_node, "linux,kho-fdt");
+		fdt_delprop(fdt, chosen_node, "linux,kho-scratch");
+
+		ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
+					       "linux,kho-fdt",
+					       image->kho.fdt, PAGE_SIZE);
+		if (ret)
+			goto out_free;
+
+		ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
+					       "linux,kho-scratch",
+					       image->kho.scratch->mem,
+					       image->kho.scratch->bufsz);
+		if (ret)
+			goto out_free;
+
+		/*
+		 * Shrink totalsize to the actual data size so the kexec segment
+		 * allocated by kexec_add_buffer() covers only the packed FDT data.
+		 * The slack added above for property insertion is part of the
+		 * kvmalloc'd buffer, which is freed by kimage_file_post_load_cleanup()
+		 * once the kexec image has been loaded.
+		 */
+		fdt_pack(fdt);
+
+		kbuf.buffer	= fdt;
+		kbuf.bufsz	= fdt_totalsize(fdt);
+		kbuf.memsz	= kbuf.bufsz;
+		kbuf.buf_align	= PAGE_SIZE;
+		kbuf.mem	= KEXEC_BUF_MEM_UNKNOWN;
+
+		ret = kexec_add_buffer(&kbuf);
+		if (ret)
+			goto out_free;
+
+		image->arch.fdt     = fdt;
+		image->arch.fdt_mem = kbuf.mem;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+
+out_free:
+	kvfree(fdt);
+	return ret;
+}
+#endif
+
 #ifdef CONFIG_CRASH_DUMP
 
 static int prepare_elf_headers(void **addr, unsigned long *sz)
@@ -230,6 +342,12 @@ int load_other_segments(struct kimage *image,
 	cmdline = modified_cmdline;
 	image->arch.cmdline_ptr = (unsigned long)cmdline;
 
+#ifdef CONFIG_KEXEC_HANDOVER
+	ret = kho_load_fdt(image);
+	if (ret)
+		goto out_err;
+#endif
+
 	return 0;
 
 out_err:
-- 
2.25.1



From dongtai.guo at linux.dev  Fri May 29 07:32:37 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Fri, 29 May 2026 22:32:37 +0800
Subject: [PATCH v2 3/4] selftests/kho: add LoongArch vmtest support
In-Reply-To: <20260529143238.169169-1-dongtai.guo@linux.dev>
References: <20260529143238.169169-1-dongtai.guo@linux.dev>
Message-ID: <20260529143238.169169-4-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

Add loongarch.conf to configure QEMU's LoongArch virt machine with a
la464 CPU, enable the 8250 serial console, and set the kernel image
to vmlinux.efi. Extend vmtest.sh to recognize loongarch64 as a
supported target and map it to the 'loongarch' kernel arch name.

QEMU's LoongArch virt machine provides no ACPI tables and relies on
FDT to describe hardware. Without 'earlycon' on the kernel command
line, the FDT is not scanned for a console UART, no output reaches
the console, and vmtest.sh's console log stays empty causing the test
to always fail. Add 'earlycon' to KERNEL_CMDLINE in loongarch.conf
to fix this.

QEMU's LoongArch virt machine has no i8042 PS/2 controller.  When PNP
detection finds nothing, i8042_init() falls back to probing the ports
directly.  On LoongArch the I/O ports are memory-mapped, and the i8042
port addresses are not backed by any device on the virt machine, so
i8042_flush() takes a page fault and the kernel panics:

  i8042: PNP: No PS/2 controller found.
  i8042: Probing ports directly.
  CPU 0 Unable to handle kernel paging request at virtual address ffff800000008064
  ERA: i8042_flush+0x50/0x198
   RA: i8042_init+0x2a8/0x35c
  Kernel panic - not syncing: Attempted to kill init!

Disable SERIO_I8042 and its dependents (KEYBOARD_ATKBD, MOUSE_PS2) in
the QEMU_KCONFIG fragment to prevent the driver from being built.

Co-developed-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: Kexin Liu <liukexin at kylinos.cn>
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 tools/testing/selftests/kho/loongarch.conf | 10 ++++++++++
 tools/testing/selftests/kho/vmtest.sh      |  3 ++-
 2 files changed, 12 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf

diff --git a/tools/testing/selftests/kho/loongarch.conf b/tools/testing/selftests/kho/loongarch.conf
new file mode 100644
index 000000000000..0145cb49e5b2
--- /dev/null
+++ b/tools/testing/selftests/kho/loongarch.conf
@@ -0,0 +1,10 @@
+QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464"
+QEMU_KCONFIG="
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_MOUSE_PS2 is not set
+# CONFIG_SERIO_I8042 is not set
+"
+KERNEL_IMAGE="vmlinux.efi"
+KERNEL_CMDLINE="console=ttyS0 earlycon"
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 49fdac8e8b15..a6ae9ac09595 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -21,7 +21,7 @@ Options:
 	-d)	path to the kernel build directory
 	-j)	number of jobs for compilation, similar to -j in make
 	-t)	run test for target_arch, requires CROSS_COMPILE set
-		supported targets: aarch64, x86_64
+		supported targets: aarch64, x86_64, loongarch64
 	-h)	display this help
 EOF
 }
@@ -123,6 +123,7 @@ function target_to_arch() {
 	case $target in
 	     aarch64) echo "arm64" ;;
 	     x86_64) echo "x86" ;;
+	     loongarch64) echo "loongarch" ;;
 	     *) skip "architecture $target is not supported"
 	esac
 }
-- 
2.25.1



From dongtai.guo at linux.dev  Fri May 29 07:32:38 2026
From: dongtai.guo at linux.dev (George Guo)
Date: Fri, 29 May 2026 22:32:38 +0800
Subject: [PATCH v2 4/4] selftests/kho: handle QEMU not exiting after kexec on LoongArch
In-Reply-To: <20260529143238.169169-1-dongtai.guo@linux.dev>
References: <20260529143238.169169-1-dongtai.guo@linux.dev>
Message-ID: <20260529143238.169169-5-dongtai.guo@linux.dev>

From: George Guo <guodongtai at kylinos.cn>

On LoongArch, QEMU provides only a minimal EFI stub with no runtime
services and no ACPI tables, so machine_restart() falls through to
its infinite idle loop and QEMU never exits after kexec.  The test
result is already printed to the serial console and vmtest.sh reports
success, but the user must press Ctrl+C to get the prompt back.

Add QEMU_NEEDS_KILL=1 to loongarch.conf so the test completes
unattended.

Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
 tools/testing/selftests/kho/loongarch.conf |  3 ++
 tools/testing/selftests/kho/vmtest.sh      | 32 ++++++++++++++++++----
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/kho/loongarch.conf b/tools/testing/selftests/kho/loongarch.conf
index 0145cb49e5b2..02a6add633f1 100644
--- a/tools/testing/selftests/kho/loongarch.conf
+++ b/tools/testing/selftests/kho/loongarch.conf
@@ -8,3 +8,6 @@ CONFIG_SERIAL_8250_CONSOLE=y
 "
 KERNEL_IMAGE="vmlinux.efi"
 KERNEL_CMDLINE="console=ttyS0 earlycon"
+# QEMU never exits after kexec on LoongArch (no EFI runtime services),
+# so vmtest.sh must kill it once the test verdict appears.
+QEMU_NEEDS_KILL=1
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index a6ae9ac09595..821e13fa69a5 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -107,12 +107,32 @@ function run_qemu() {
 
 	cmdline="$cmdline kho=on panic=-1"
 
-	$qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
-		  -accel kvm -accel hvf -accel tcg  \
-		  -serial file:"$serial" \
-		  -append "$cmdline" \
-		  -kernel "$kernel" \
-		  -initrd "$initrd"
+	local qemu_args=(
+		-m 1G -smp 2 -no-reboot -nographic -nodefaults
+		-accel kvm -accel hvf -accel tcg
+		-serial file:"$serial"
+		-append "$cmdline"
+		-kernel "$kernel"
+		-initrd "$initrd"
+	)
+
+	# If the target does not exit QEMU after kexec (e.g. no EFI runtime
+	# services), the conf file sets QEMU_NEEDS_KILL=1.  Run QEMU in the
+	# background, poll for the test verdict, then kill it.
+	if [[ "${QEMU_NEEDS_KILL:-0}" == "1" ]]; then
+		$qemu_cmd "${qemu_args[@]}" &
+		local qemu_pid=$!
+		local remaining=100
+		while ((remaining-- > 0)); do
+			grep -q "KHO restore succeeded\|KHO restore failed" \
+				"$serial" 2>/dev/null && break
+			sleep 1
+		done
+		kill "$qemu_pid" 2>/dev/null
+		wait "$qemu_pid" 2>/dev/null || true
+	else
+		$qemu_cmd "${qemu_args[@]}"
+	fi
 
 	grep "KHO restore succeeded" "$serial" &> /dev/null || fail "KHO failed"
 }
-- 
2.25.1



From will at kernel.org  Fri May 29 08:08:41 2026
From: will at kernel.org (Will Deacon)
Date: Fri, 29 May 2026 16:08:41 +0100
Subject: [PATCH v3 09/11] arm64: kdump: exclude non-dumpable reserved
 memory regions from vmcore
In-Reply-To: <20260527032917.3385849-10-chenwandun1@gmail.com>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
 <20260527032917.3385849-10-chenwandun1@gmail.com>
Message-ID: <ahmr-UjoApj2j5JS@willie-the-truck>

On Wed, May 27, 2026 at 11:29:15AM +0800, Wandun Chen wrote:
> From: Wandun Chen <chenwandun at lixiang.com>
> 
> Reserved memory regions are excluded from vmcore by default unless
> marked dumpable. Honor the dumpable flag to filter out device firmware
> regions (e.g., GPU, DSP, modem) reserved via device tree, since they
> typically contain data not useful for kernel crash analysis and can
> significantly increase vmcore size.
> 
> Use of_reserved_mem_kdump_exclude() to perform the exclusion, and
> pre-size the crash_mem array via of_reserved_mem_kdump_nr_ranges().
> 
> Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
> Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
> ---
>  arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> index e31fabed378a..1d65320c6ba4 100644
> --- a/arch/arm64/kernel/machine_kexec_file.c
> +++ b/arch/arm64/kernel/machine_kexec_file.c
> @@ -17,6 +17,7 @@
>  #include <linux/memblock.h>
>  #include <linux/of.h>
>  #include <linux/of_fdt.h>
> +#include <linux/of_reserved_mem.h>
>  #include <linux/slab.h>
>  #include <linux/string.h>
>  #include <linux/types.h>
> @@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>  	nr_ranges = 2; /* for exclusion of crashkernel region */
>  	for_each_mem_range(i, &start, &end)
>  		nr_ranges++;
> +	nr_ranges += of_reserved_mem_kdump_nr_ranges();
>  
>  	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
>  	if (!cmem)
> @@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>  			goto out;
>  	}
>  
> +	ret = of_reserved_mem_kdump_exclude(cmem);
> +	if (ret)
> +		goto out;
> +
>  	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);

This looks fine to me:

Acked-by: Will Deacon <will at kernel.org>

Although I do wonder whether there's scope to consolidate some of the
arch code here. Now that you have a helper for reserved memory, perhaps
the core code could also handle the crashkernel reservation itself as
well? If the arch code passed in its number of memory regions, the
core code could take care of (a) allocating the crash_mem ranges array
(b) excluding the crashkernel and (c) excluding the reserved regions
(the part you have here).

Obviously that would be follow-up work, but the fact that you're having
to apply basically the same diff to three architectures is a bit of a
giveaway that this could benefit from some wider cleanup.

Will


From lkp at intel.com  Fri May 29 10:17:06 2026
From: lkp at intel.com (kernel test robot)
Date: Sat, 30 May 2026 01:17:06 +0800
Subject: [liveupdate:next] BUILD SUCCESS
 5428435567cbe06c19914592fc22ca23c9ca1de5
Message-ID: <202605300156.CijG3CV5-lkp@intel.com>

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git next
branch HEAD: 5428435567cbe06c19914592fc22ca23c9ca1de5  liveupdate: Remove unused ser field from struct luo_session

elapsed time: 1481m

configs tested: 172
configs skipped: 2

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    gcc-15.2.0
arc                   randconfig-001-20260529    clang-23
arc                   randconfig-002-20260529    clang-23
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    gcc-15.2.0
arm                         lpc32xx_defconfig    clang-17
arm                   randconfig-001-20260529    clang-23
arm                   randconfig-002-20260529    clang-23
arm                   randconfig-003-20260529    clang-23
arm                   randconfig-004-20260529    clang-23
arm64                            allmodconfig    clang-19
arm64                             allnoconfig    gcc-15.2.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
hexagon                          allmodconfig    clang-17
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon               randconfig-001-20260529    gcc-8.5.0
hexagon               randconfig-002-20260529    gcc-8.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260529    gcc-12
i386        buildonly-randconfig-002-20260529    gcc-12
i386        buildonly-randconfig-003-20260529    gcc-12
i386        buildonly-randconfig-004-20260529    gcc-12
i386        buildonly-randconfig-005-20260529    gcc-12
i386        buildonly-randconfig-006-20260529    gcc-12
i386                  randconfig-001-20260529    gcc-14
i386                  randconfig-002-20260529    gcc-14
i386                  randconfig-003-20260529    gcc-14
i386                  randconfig-004-20260529    gcc-14
i386                  randconfig-005-20260529    gcc-14
i386                  randconfig-006-20260529    gcc-14
i386                  randconfig-007-20260529    gcc-14
i386                  randconfig-011-20260529    gcc-14
i386                  randconfig-012-20260529    gcc-14
i386                  randconfig-013-20260529    gcc-14
i386                  randconfig-014-20260529    gcc-14
i386                  randconfig-015-20260529    gcc-14
i386                  randconfig-016-20260529    gcc-14
i386                  randconfig-017-20260529    gcc-14
loongarch                        allmodconfig    clang-19
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260529    gcc-8.5.0
loongarch             randconfig-002-20260529    gcc-8.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    gcc-15.2.0
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260529    gcc-8.5.0
nios2                 randconfig-002-20260529    gcc-8.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                         randconfig-001    clang-19
parisc                randconfig-001-20260529    clang-19
parisc                         randconfig-002    clang-19
parisc                randconfig-002-20260529    clang-19
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                        randconfig-001    clang-19
powerpc               randconfig-001-20260529    clang-19
powerpc                        randconfig-002    clang-19
powerpc               randconfig-002-20260529    clang-19
powerpc64                      randconfig-001    clang-19
powerpc64             randconfig-001-20260529    clang-19
powerpc64                      randconfig-002    clang-19
powerpc64             randconfig-002-20260529    clang-19
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260529    gcc-11.5.0
sparc                 randconfig-002-20260529    gcc-11.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260529    gcc-11.5.0
sparc64               randconfig-002-20260529    gcc-11.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260529    gcc-11.5.0
um                    randconfig-002-20260529    gcc-11.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260529    gcc-14
x86_64      buildonly-randconfig-002-20260529    gcc-14
x86_64      buildonly-randconfig-003-20260529    gcc-14
x86_64      buildonly-randconfig-004-20260529    gcc-14
x86_64      buildonly-randconfig-005-20260529    gcc-14
x86_64      buildonly-randconfig-006-20260529    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260529    clang-20
x86_64                randconfig-002-20260529    clang-20
x86_64                randconfig-003-20260529    clang-20
x86_64                randconfig-004-20260529    clang-20
x86_64                randconfig-005-20260529    clang-20
x86_64                randconfig-006-20260529    clang-20
x86_64                         randconfig-071    clang-20
x86_64                randconfig-071-20260529    clang-20
x86_64                         randconfig-072    clang-20
x86_64                randconfig-072-20260529    clang-20
x86_64                         randconfig-073    clang-20
x86_64                randconfig-073-20260529    clang-20
x86_64                         randconfig-074    clang-20
x86_64                randconfig-074-20260529    clang-20
x86_64                         randconfig-075    clang-20
x86_64                randconfig-075-20260529    clang-20
x86_64                         randconfig-076    clang-20
x86_64                randconfig-076-20260529    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                randconfig-001-20260529    gcc-11.5.0
xtensa                randconfig-002-20260529    gcc-11.5.0

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


From kjlx at templeofstupid.com  Fri May 29 14:11:27 2026
From: kjlx at templeofstupid.com (Krister Johansen)
Date: Fri, 29 May 2026 14:11:27 -0700
Subject: [PATCH v5][makedumpfile 0/9] btf/kallsyms based makedumpfile
 extension for mm page filtering
In-Reply-To: <20260414102656.55200-1-ltao@redhat.com>
References: <20260414102656.55200-1-ltao@redhat.com>
Message-ID: <ahoA_5MfC7FuC8CQ@templeofstupid.com>

On Tue, Apr 14, 2026 at 10:26:47PM +1200, Tao Liu wrote:
> A) This patchset will introduce the following features to makedumpfile:
> 
>   1) Add .so extension support to makedumpfile
>   2) Enable btf and kallsyms for symbol type and address resolving.
> 
> B) The purpose of the features are:
> 
>   1) Currently makedumpfile filters mm pages based on page flags, because flags
>      can help to determine one page's usage. But this page-flag-checking method
>      lacks of flexibility in certain cases, e.g. if we want to filter those mm
>      pages occupied by GPU during vmcore dumping due to:
> 
>      a) GPU may be taking a large memory and contains sensitive data;
>      b) GPU mm pages have no relations to kernel crash and useless for vmcore
>         analysis.
> 
>      But there is no GPU mm page specific flags, and apparently we don't need
>      to create one just for kdump use. A programmable filtering tool is more
>      suitable for such cases. In addition, different GPU vendors may use
>      different ways for mm pages allocating, programmable filtering is better
>      than hard coding these GPU specific logics into makedumpfile in this case.
> 
>   2) Currently makedumpfile already contains a programmable filtering tool, aka
>      eppic script, which allows user to write customized code for data erasing.
>      However it has the following drawbacks:
> 
>      a) cannot do mm page filtering.
>      b) need to access to debuginfo of both kernel and modules, which is not
>         applicable in the 2nd kernel.
>      c) eppic library has memory leaks which are not all resolved [1]. This
>         is not acceptable in 2nd kernel.
> 
>      makedumpfile need to resolve the dwarf data from debuginfo, to get symbols
>      types and addresses. In recent kernel there are dwarf alternatives such
>      as btf/kallsyms which can be used for this purpose. And btf/kallsyms info
>      are already packed within vmcore, so we can use it directly.
> 
>   With these, this patchset introduces makedumpfile extensions, which is based
>   on btf/kallsyms symbol resolving, and is programmable for mm page filtering.
>   The following section shows its usage and performance, please note the tests
>   are performed in 1st kernel.
> 
>   3) Compile and run makedumpfile extensions:
> 
>   $ make LINKTYPE=dynamic USELZO=on USESNAPPY=on USEZSTD=on EXTENSION=on
>   $ make extensions

I love this idea.  Do you have time to take it further, and if not are
you open to making the extension framework more modular so that we could
add others in the future?

Could the btf lookups be extended to cover the symbol lookups used by
eppic and the erase filters so that the -x option is unnecessary for
kernels that have BTF support?

The current extension implementation is focused just on skipping pages,
but it would be great to be able to use this to erase data in structures
like the config filters and eppic, but without having to provide a
vmlinux at dump time.  What do you think about adding the ability to
use the extensions to also erase parts of data structures, in addition
to filtering whole pages?

Would you be willing to modify the extension registration options to
allow an extension to specify what kind it is? That way, in the future
we could register multiple different kinds without breaking existing
ones.  One for filtering pages, one for erasing / modifying dump
content, and others based upon whatever additional use cases develop.

Thanks,

-K


From a.martusevs at sintec.uk.com  Sat May 30 05:37:04 2026
From: a.martusevs at sintec.uk.com (a.martusevs at sintec.uk.com)
Date: Sat, 30 May 2026 12:37:04 +0000
Subject: SPENDE (DONATION)
Message-ID: <mailman.3.1780144640.1314.kexec@lists.infradead.org>

Hello,

I am Safra Catz, an American businesswoman, investor, and Chairwoman and CEO of Oracle Corporation. I am one of the owners of Oracle Corporation. I have donated 25 percent of my personal wealth to charitable causes. Furthermore, I have pledged to give away the remaining 25 percent to private individuals this year?2026. I have decided to donate ?3,000,000.00 to you. If you are interested in receiving my donation, please contact me for further information.

You can also find more information about me at the following link:

https://en.wikipedia.org/wiki/Safra_Catz

==========================================

Sincerely,
Ms. Safra Catz
CEO, Oracle Corporation
Email: info at safracatz.org


From rppt at kernel.org  Sat May 30 09:25:22 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sat, 30 May 2026 19:25:22 +0300
Subject: [PATCH v3 09/11] arm64: kdump: exclude non-dumpable reserved
 memory regions from vmcore
In-Reply-To: <ahmr-UjoApj2j5JS@willie-the-truck>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
 <20260527032917.3385849-10-chenwandun1@gmail.com>
 <ahmr-UjoApj2j5JS@willie-the-truck>
Message-ID: <ahsPcv8G1BR9rLah@kernel.org>

On Fri, May 29, 2026 at 04:08:41PM +0100, Will Deacon wrote:
> On Wed, May 27, 2026 at 11:29:15AM +0800, Wandun Chen wrote:
> > From: Wandun Chen <chenwandun at lixiang.com>
> > 
> > Reserved memory regions are excluded from vmcore by default unless
> > marked dumpable. Honor the dumpable flag to filter out device firmware
> > regions (e.g., GPU, DSP, modem) reserved via device tree, since they
> > typically contain data not useful for kernel crash analysis and can
> > significantly increase vmcore size.
> > 
> > Use of_reserved_mem_kdump_exclude() to perform the exclusion, and
> > pre-size the crash_mem array via of_reserved_mem_kdump_nr_ranges().
> > 
> > Signed-off-by: Wandun Chen <chenwandun at lixiang.com>
> > Tested-by: Meijing Zhao <zhaomeijing at lixiang.com>
> > ---
> >  arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
> >  1 file changed, 6 insertions(+)
> > 
> > diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> > index e31fabed378a..1d65320c6ba4 100644
> > --- a/arch/arm64/kernel/machine_kexec_file.c
> > +++ b/arch/arm64/kernel/machine_kexec_file.c
> > @@ -17,6 +17,7 @@
> >  #include <linux/memblock.h>
> >  #include <linux/of.h>
> >  #include <linux/of_fdt.h>
> > +#include <linux/of_reserved_mem.h>
> >  #include <linux/slab.h>
> >  #include <linux/string.h>
> >  #include <linux/types.h>
> > @@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
> >  	nr_ranges = 2; /* for exclusion of crashkernel region */
> >  	for_each_mem_range(i, &start, &end)
> >  		nr_ranges++;
> > +	nr_ranges += of_reserved_mem_kdump_nr_ranges();
> >  
> >  	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
> >  	if (!cmem)
> > @@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
> >  			goto out;
> >  	}
> >  
> > +	ret = of_reserved_mem_kdump_exclude(cmem);
> > +	if (ret)
> > +		goto out;
> > +
> >  	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
> 
> This looks fine to me:
> 
> Acked-by: Will Deacon <will at kernel.org>
> 
> Although I do wonder whether there's scope to consolidate some of the
> arch code here. Now that you have a helper for reserved memory, perhaps
> the core code could also handle the crashkernel reservation itself as
> well? If the arch code passed in its number of memory regions, the
> core code could take care of (a) allocating the crash_mem ranges array
> (b) excluding the crashkernel and (c) excluding the reserved regions
> (the part you have here).
> 
> Obviously that would be follow-up work, but the fact that you're having
> to apply basically the same diff to three architectures is a bit of a
> giveaway that this could benefit from some wider cleanup.

There are patches that move common code to kernel/crash_core.c:

https://lore.kernel.org/all/20260525084932.934910-1-ruanjinjie at huawei.com
 
Review from arch maintainers would be helpful there ;-)

> Will

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sat May 30 10:14:20 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sat, 30 May 2026 20:14:20 +0300
Subject: [GIT PULL] liveupdate: kho: two regression fixes
Message-ID: <20260530171420.1430298-1-rppt@kernel.org>

From: Mike Rapoport (Microsoft) <rppt at kernel.org>

The following changes since commit e7ae89a0c97ce2b68b0983cd01eda67cf373517d:

  Linux 7.1-rc5 (2026-05-24 13:48:06 -0700)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git tags/liveupdate-fixes-2026-05-30

for you to fetch changes up to 8fd2f26fa2a33cfe8ac043f976137ecf5b567f03:

  kho: fix order calculation for kho_unpreserve_pages() (2026-05-26 11:01:49 +0300)

----------------------------------------------------------------
liveupdate: kho: two regression fixes

* fix order calculation for kho_unpreserve_pages() to make sure sure that
  the order calculation in kho_unpreserve_pages() mathes the order
  calculation in kho_preserve_pages().
* fix math in calculation of KHO_TREE_MAX_DEPTH to make it work with 16KB
  pages.

----------------------------------------------------------------
George Guo (1):
      kho: fix KHO_TREE_MAX_DEPTH for non-4KB page sizes

Pratyush Yadav (Google) (1):
      kho: fix order calculation for kho_unpreserve_pages()

 include/linux/kho/abi/kexec_handover.h |  2 +-
 kernel/liveupdate/kexec_handover.c     | 56 +++++++++++++++++++---------------
 2 files changed, 33 insertions(+), 25 deletions(-)

--
Sincerely yours,
Mike.


From pasha.tatashin at soleen.com  Sat May 30 15:19:25 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:25 +0000
Subject: [PATCH v4 00/13] liveupdate: Remove limits on sessions and files
Message-ID: <20260530221938.115978-1-pasha.tatashin@soleen.com>

Hi all,

This series removes the fixed limits on the number of files that can
be preserved within a single session, and the total number of sessions
managed by the Live Update Orchestrator (LUO).

The core of the change is a transition from single contiguous memory
blocks for metadata serialization to a chain of linked blocks. This
allows LUO to scale dynamically.

1.  ABI Evolution:
    - Introduced linked-block headers for both file and session
      serialization.
    - Bumped session ABI version to v4.

2.  Memory Management & Security:
    - Implemented a dynamic block allocation and reuse strategy. Blocks
      are allocated only when existing ones are exhausted and are reused
      during session/file removal cycles.
    - Introduced KHO_MAX_BLOCKS (10000) as a safeguard against stupid
      excessive allocations or corrupted cyclic lists during restore.

3.  Expanded Selftests:
    - Added new kexec-based tests verifying preservation of
      2000 sessions and 500 files per session.
    - Added self-tests for many sessions and many files management.

Tree: git.kernel.org/pub/scm/linux/kernel/git/tatashin/linux.git
Branch: luo-remove-max-files-sessions-limits/v4

Changes v4:
- Addressed comments from Sashiko:
  - Enforced file count mismatches during deserialization as a hard
    failure.
  - Reset bs->head_pa to 0 in kho_block_restore() on cyclic block
    detection.
  - Added two small fixes to pre-existing problems at the beginning of
    the series:
    - "liveupdate: change file_set->count type to u64 for type safety"
    - "liveupdate: avoid mixing cleanup guards with goto in
      luo_session_retrieve_fd"

Changes v3:
- Moved the linked-block serialization infrastructure from
  LUO-specific (luo_block.c) to generic KHO helper infrastructure
  (kho_block.c, include/linux/kho_block.h,
  include/linux/kho/abi/block.h).
- Registered luo_ser directly as a raw KHO subtree instead of
  wrapping it inside FDT.
- Collected Acked-by tags from Mike Rapoport.

Please review.

Thanks,
Pasha

Pasha Tatashin (13):
  liveupdate: change file_set->count type to u64 for type safety
  liveupdate: avoid mixing cleanup guards with goto in
    luo_session_retrieve_fd
  liveupdate: centralize state management into struct luo_ser
  liveupdate: register luo_ser as KHO subtree
  liveupdate: Extract luo_file_deserialize_one helper
  liveupdate: Extract luo_session_deserialize_one helper
  kho: add support for linked-block serialization
  liveupdate: defer session block allocation and PA setting
  liveupdate: Remove limit on the number of sessions
  liveupdate: Remove limit on the number of files per session
  selftests/liveupdate: Test session and file limit removal
  selftests/liveupdate: Add stress-sessions kexec test
  selftests/liveupdate: Add stress-files kexec test

 Documentation/core-api/kho/abi.rst            |   5 +
 Documentation/core-api/kho/index.rst          |  11 +
 MAINTAINERS                                   |   1 +
 include/linux/kho/abi/block.h                 |  56 +++
 include/linux/kho/abi/luo.h                   | 149 ++-----
 include/linux/kho_block.h                     |  79 ++++
 kernel/liveupdate/Makefile                    |   1 +
 kernel/liveupdate/kho_block.c                 | 384 ++++++++++++++++++
 kernel/liveupdate/luo_core.c                  |  99 ++---
 kernel/liveupdate/luo_file.c                  | 211 +++++-----
 kernel/liveupdate/luo_flb.c                   |  65 +--
 kernel/liveupdate/luo_internal.h              |  16 +-
 kernel/liveupdate/luo_session.c               | 242 +++++------
 tools/testing/selftests/liveupdate/Makefile   |   2 +
 .../testing/selftests/liveupdate/liveupdate.c |  75 ++++
 .../selftests/liveupdate/luo_stress_files.c   |  97 +++++
 .../liveupdate/luo_stress_sessions.c          | 102 +++++
 .../selftests/liveupdate/luo_test_utils.c     |  24 ++
 .../selftests/liveupdate/luo_test_utils.h     |   2 +
 19 files changed, 1161 insertions(+), 460 deletions(-)
 create mode 100644 include/linux/kho/abi/block.h
 create mode 100644 include/linux/kho_block.h
 create mode 100644 kernel/liveupdate/kho_block.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c


base-commit: 5428435567cbe06c19914592fc22ca23c9ca1de5
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:26 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:26 +0000
Subject: [PATCH v4 01/13] liveupdate: change file_set->count type to u64 for type safety
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-2-pasha.tatashin@soleen.com>

This improves type safety and aligns the in-memory file_set->count with
the serialized count type. It avoids potential truncation or sign
conversion mismatch issues.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_internal.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dd53d4a7277e..ae58206f14ac 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -52,7 +52,7 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
 struct luo_file_set {
 	struct list_head files_list;
 	struct luo_file_ser *files;
-	long count;
+	u64 count;
 };
 
 /**
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:27 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:27 +0000
Subject: [PATCH v4 02/13] liveupdate: avoid mixing cleanup guards with goto in luo_session_retrieve_fd
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-3-pasha.tatashin@soleen.com>

Refactoring luo_session_retrieve_fd() to avoid mixing automated
cleanup-style guards with goto-based resource release, which is not
recommended under the Linux kernel coding style.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 146414933977..8d9201c25412 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -291,25 +291,24 @@ static int luo_session_retrieve_fd(struct luo_session *session,
 	if (argp->fd < 0)
 		return argp->fd;
 
-	guard(mutex)(&session->mutex);
-	err = luo_retrieve_file(&session->file_set, argp->token, &file);
-	if (err < 0)
-		goto  err_put_fd;
+	scoped_guard(mutex, &session->mutex) {
+		err = luo_retrieve_file(&session->file_set, argp->token, &file);
+		if (err < 0) {
+			put_unused_fd(argp->fd);
+			return err;
+		}
+	}
 
 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
-	if (err)
-		goto err_put_file;
+	if (err) {
+		fput(file);
+		put_unused_fd(argp->fd);
+		return err;
+	}
 
 	fd_install(argp->fd, file);
 
 	return 0;
-
-err_put_file:
-	fput(file);
-err_put_fd:
-	put_unused_fd(argp->fd);
-
-	return err;
 }
 
 static int luo_session_finish(struct luo_session *session,
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:28 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:28 +0000
Subject: [PATCH v4 03/13] liveupdate: centralize state management into struct luo_ser
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-4-pasha.tatashin@soleen.com>

Transition the LUO to ABI v2, which centralizes state management into a
single struct luo_ser header.

Previously, LUO state was spread across multiple FDT properties and
subnodes. ABI v2 simplifies this by placing all core state, including
the liveupdate number and physical addresses for sessions and FLB
headers into a centralized struct luo_ser.

Note that this change introduces a semantic difference: the sessions
and FLB serialization formats are no longer completely independent of
the core LUO. Their metadata (such as physical addresses for sessions
and FLB headers) is now coupled to and managed via the centralized
struct luo_ser.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      | 91 +++++++++++---------------------
 kernel/liveupdate/luo_core.c     | 64 +++++++++++++++-------
 kernel/liveupdate/luo_flb.c      | 65 +++--------------------
 kernel/liveupdate/luo_internal.h |  8 +--
 kernel/liveupdate/luo_session.c  | 64 ++++------------------
 5 files changed, 98 insertions(+), 194 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 46750a0ddf88..1b2f865a771a 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -30,52 +30,25 @@
  *   .. code-block:: none
  *
  *     / {
- *         compatible = "luo-v1";
- *         liveupdate-number = <...>;
- *
- *         luo-session {
- *             compatible = "luo-session-v1";
- *             luo-session-header = <phys_addr_of_session_header_ser>;
- *         };
- *
- *         luo-flb {
- *             compatible = "luo-flb-v1";
- *             luo-flb-header = <phys_addr_of_flb_header_ser>;
- *         };
+ *         compatible = "luo-v2";
+ *         luo-abi-header = <phys_addr_of_luo_ser>;
  *     };
  *
  * Main LUO Node (/):
  *
- *   - compatible: "luo-v1"
+ *   - compatible: "luo-v2"
  *     Identifies the overall LUO ABI version.
- *   - liveupdate-number: u64
- *     A counter tracking the number of successful live updates performed.
- *
- * Session Node (luo-session):
- *   This node describes all preserved user-space sessions.
- *
- *   - compatible: "luo-session-v1"
- *     Identifies the session ABI version.
- *   - luo-session-header: u64
- *     The physical address of a `struct luo_session_header_ser`. This structure
- *     is the header for a contiguous block of memory containing an array of
- *     `struct luo_session_ser`, one for each preserved session.
- *
- * File-Lifecycle-Bound Node (luo-flb):
- *   This node describes all preserved global objects whose lifecycle is bound
- *   to that of the preserved files (e.g., shared IOMMU state).
- *
- *   - compatible: "luo-flb-v1"
- *     Identifies the FLB ABI version.
- *   - luo-flb-header: u64
- *     The physical address of a `struct luo_flb_header_ser`. This structure is
- *     the header for a contiguous block of memory containing an array of
- *     `struct luo_flb_ser`, one for each preserved global object.
+ *   - luo-abi-header: u64
+ *     The physical address of `struct luo_ser`.
  *
  * Serialization Structures:
  *   The FDT properties point to memory regions containing arrays of simple,
  *   `__packed` structures. These structures contain the actual preserved state.
  *
+ *   - struct luo_ser:
+ *     The central ABI structure that contains the overall state of the LUO.
+ *     It includes the liveupdate-number and pointers to sessions and FLBs.
+ *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
  *     preserved memory block and the number of `struct luo_session_ser`
@@ -109,13 +82,26 @@
 
 /*
  * The LUO FDT hooks all LUO state for sessions, fds, etc.
- * In the root it also carries "liveupdate-number" 64-bit property that
- * corresponds to the number of live-updates performed on this machine.
  */
 #define LUO_FDT_SIZE		PAGE_SIZE
 #define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v1"
-#define LUO_FDT_LIVEUPDATE_NUM	"liveupdate-number"
+#define LUO_FDT_COMPATIBLE	"luo-v2"
+#define LUO_FDT_ABI_HEADER	"luo-abi-header"
+
+/**
+ * struct luo_ser - Centralized LUO ABI header.
+ * @liveupdate_num: A counter tracking the number of successful live updates.
+ * @sessions_pa:    Physical address of the first session block header.
+ * @flbs_pa:        Physical address of the FLB header.
+ *
+ * This structure is the root of all preserved LUO state. It is pointed to by
+ * the "luo-abi-header" property in the LUO FDT.
+ */
+struct luo_ser {
+	u64 liveupdate_num;
+	u64 sessions_pa;
+	u64 flbs_pa;
+} __packed;
 
 #define LIVEUPDATE_HNDL_COMPAT_LENGTH	48
 
@@ -147,15 +133,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/*
- * LUO FDT session node
- * LUO_FDT_SESSION_HEADER:  is a u64 physical address of struct
- *                          luo_session_header_ser
- */
-#define LUO_FDT_SESSION_NODE_NAME	"luo-session"
-#define LUO_FDT_SESSION_COMPATIBLE	"luo-session-v2"
-#define LUO_FDT_SESSION_HEADER		"luo-session-header"
-
 /**
  * struct luo_session_header_ser - Header for the serialized session data block.
  * @count: The number of `struct luo_session_ser` entries that immediately
@@ -165,7 +142,7 @@ struct luo_file_set_ser {
  * physical memory preserved across the kexec. It provides the necessary
  * metadata to interpret the array of session entries that follow.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_header_ser {
 	u64 count;
@@ -182,7 +159,7 @@ struct luo_session_header_ser {
  * session) is created and passed to the new kernel, allowing it to reconstruct
  * the session context.
  *
- * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -192,10 +169,6 @@ struct luo_session_ser {
 /* The max size is set so it can be reliably used during in serialization */
 #define LIVEUPDATE_FLB_COMPAT_LENGTH	48
 
-#define LUO_FDT_FLB_NODE_NAME	"luo-flb"
-#define LUO_FDT_FLB_COMPATIBLE	"luo-flb-v1"
-#define LUO_FDT_FLB_HEADER	"luo-flb-header"
-
 /**
  * struct luo_flb_header_ser - Header for the serialized FLB data block.
  * @pgcnt: The total number of pages occupied by the entire preserved memory
@@ -205,11 +178,9 @@ struct luo_session_ser {
  *         in the memory block.
  *
  * This structure is located at the physical address specified by the
- * `LUO_FDT_FLB_HEADER` FDT property. It provides the new kernel with the
- * necessary information to find and iterate over the array of preserved
- * File-Lifecycle-Bound objects and to manage the underlying memory.
+ * flbs_pa in luo_ser.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
@@ -231,7 +202,7 @@ struct luo_flb_header_ser {
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, LUO_FDT_FLB_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 803f51c84275..fbc18c5f4230 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -57,7 +57,6 @@
 #include <linux/rwsem.h>
 #include <linux/sizes.h>
 #include <linux/string.h>
-#include <linux/unaligned.h>
 
 #include "kexec_handover_internal.h"
 #include "luo_internal.h"
@@ -82,9 +81,11 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	struct luo_ser *luo_ser;
+	int err, header_size;
 	phys_addr_t fdt_phys;
-	int err, ln_size;
 	const void *ptr;
+	u64 luo_ser_pa;
 
 	if (!kho_is_enabled()) {
 		if (liveupdate_enabled())
@@ -115,26 +116,32 @@ static int __init luo_early_startup(void)
 		return -EINVAL;
 	}
 
-	ln_size = 0;
-	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_LIVEUPDATE_NUM,
-			  &ln_size);
-	if (!ptr || ln_size != sizeof(luo_global.liveupdate_num)) {
-		pr_err("Unable to get live update number '%s' [%d]\n",
-		       LUO_FDT_LIVEUPDATE_NUM, ln_size);
+	header_size = 0;
+	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
+	if (!ptr || header_size != sizeof(u64)) {
+		pr_err("Unable to get ABI header '%s' [%d]\n",
+		       LUO_FDT_ABI_HEADER, header_size);
 
 		return -EINVAL;
 	}
 
-	luo_global.liveupdate_num = get_unaligned((u64 *)ptr);
+	luo_ser_pa = get_unaligned((u64 *)ptr);
+	luo_ser = phys_to_virt(luo_ser_pa);
+
+	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
 
-	err = luo_session_setup_incoming(luo_global.fdt_in);
+	err = luo_session_setup_incoming(luo_ser->sessions_pa);
 	if (err)
-		return err;
+		goto out_free_ser;
+
+	luo_flb_setup_incoming(luo_ser->flbs_pa);
 
-	err = luo_flb_setup_incoming(luo_global.fdt_in);
+	err = 0;
 
+out_free_ser:
+	kho_restore_free(luo_ser);
 	return err;
 }
 
@@ -156,7 +163,8 @@ early_initcall(liveupdate_early_init);
 /* Called during boot to create outgoing LUO fdt tree */
 static int __init luo_fdt_setup(void)
 {
-	const u64 ln = luo_global.liveupdate_num + 1;
+	struct luo_ser *luo_ser;
+	u64 luo_ser_pa;
 	void *fdt_out;
 	int err;
 
@@ -166,27 +174,45 @@ static int __init luo_fdt_setup(void)
 		return PTR_ERR(fdt_out);
 	}
 
+	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
+	if (IS_ERR(luo_ser)) {
+		err = PTR_ERR(luo_ser);
+		goto exit_free_fdt;
+	}
+	luo_ser_pa = virt_to_phys(luo_ser);
+
 	err = fdt_create(fdt_out, LUO_FDT_SIZE);
 	err |= fdt_finish_reservemap(fdt_out);
 	err |= fdt_begin_node(fdt_out, "");
 	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln));
-	err |= luo_session_setup_outgoing(fdt_out);
-	err |= luo_flb_setup_outgoing(fdt_out);
+	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
+			    sizeof(luo_ser_pa));
 	err |= fdt_end_node(fdt_out);
 	err |= fdt_finish(fdt_out);
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
+
+	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
+	if (err)
+		goto exit_free_luo_ser;
+
+	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
 			      fdt_totalsize(fdt_out));
 	if (err)
-		goto exit_free;
+		goto exit_free_luo_ser;
 	luo_global.fdt_out = fdt_out;
 
 	return 0;
 
-exit_free:
+exit_free_luo_ser:
+	kho_unpreserve_free(luo_ser);
+exit_free_fdt:
 	kho_unpreserve_free(fdt_out);
 	pr_err("failed to prepare LUO FDT: %d\n", err);
 
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 8f5c5dd01cd0..c8dd30b41238 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -44,13 +44,11 @@
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
-#include <linux/libfdt.h>
 #include <linux/list_private.h>
 #include <linux/liveupdate.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
-#include <linux/unaligned.h>
 #include "luo_internal.h"
 
 #define LUO_FLB_PGCNT		1ul
@@ -551,27 +549,15 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
 	return 0;
 }
 
-int __init luo_flb_setup_outgoing(void *fdt_out)
+int __init luo_flb_setup_outgoing(u64 *flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_FLB_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
 
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_FLB_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_FLB_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_FLB_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
-
-	if (err)
-		goto err_unpreserve;
+	*flbs_pa = virt_to_phys(header_ser);
 
 	header_ser->pgcnt = LUO_FLB_PGCNT;
 	luo_flb_global.outgoing.header_ser = header_ser;
@@ -579,53 +565,18 @@ int __init luo_flb_setup_outgoing(void *fdt_out)
 	luo_flb_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-
-	return err;
 }
 
-int __init luo_flb_setup_incoming(void *fdt_in)
+void __init luo_flb_setup_incoming(u64 flbs_pa)
 {
 	struct luo_flb_header_ser *header_ser;
-	int err, header_size, offset;
-	const void *ptr;
-	u64 header_ser_pa;
 
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_FLB_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get FLB node [%s]\n", LUO_FDT_FLB_NODE_NAME);
-
-		return -ENOENT;
+	if (flbs_pa) {
+		header_ser = phys_to_virt(flbs_pa);
+		luo_flb_global.incoming.header_ser = header_ser;
+		luo_flb_global.incoming.ser = (void *)(header_ser + 1);
+		luo_flb_global.incoming.active = true;
 	}
-
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_FLB_COMPATIBLE);
-	if (err) {
-		pr_err("FLB node is incompatible with '%s' [%d]\n",
-		       LUO_FDT_FLB_COMPATIBLE, err);
-
-		return -EINVAL;
-	}
-
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_FLB_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get FLB header property '%s' [%d]\n",
-		       LUO_FDT_FLB_HEADER, header_size);
-
-		return -EINVAL;
-	}
-
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_flb_global.incoming.header_ser = header_ser;
-	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
-	luo_flb_global.incoming.active = true;
-
-	return 0;
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index ae58206f14ac..fe22086bfbeb 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,8 +79,8 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(void *fdt);
-int __init luo_session_setup_incoming(void *fdt);
+int __init luo_session_setup_outgoing(u64 *sessions_pa);
+int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
 
@@ -102,8 +102,8 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
 void luo_flb_file_finish(struct liveupdate_file_handler *fh);
 void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
-int __init luo_flb_setup_outgoing(void *fdt);
-int __init luo_flb_setup_incoming(void *fdt);
+int __init luo_flb_setup_outgoing(u64 *flbs_pa);
+void __init luo_flb_setup_incoming(u64 flbs_pa);
 void luo_flb_serialize(void);
 
 #ifdef CONFIG_LIVEUPDATE_TEST
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 8d9201c25412..3b760fefa7b9 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -25,9 +25,8 @@
  *
  * - Serialization: Session metadata is preserved using the KHO framework. When
  *   a live update is triggered via kexec, an array of `struct luo_session_ser`
- *   is populated and placed in a preserved memory region. An FDT node is also
- *   created, containing the count of sessions and the physical address of this
- *   array.
+ *   is populated and placed in a preserved memory region. The physical address
+ *   of this array is stored in the centralized `struct luo_ser` structure.
  *
  * Session Lifecycle:
  *
@@ -91,13 +90,11 @@
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
-#include <linux/libfdt.h>
 #include <linux/list.h>
 #include <linux/liveupdate.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
 #include <linux/slab.h>
-#include <linux/unaligned.h>
 #include <uapi/linux/liveupdate.h>
 #include "luo_internal.h"
 
@@ -497,75 +494,34 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(void *fdt_out)
+int __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	u64 header_ser_pa;
-	int err;
 
 	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
 	if (IS_ERR(header_ser))
 		return PTR_ERR(header_ser);
-	header_ser_pa = virt_to_phys(header_ser);
-
-	err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME);
-	err |= fdt_property_string(fdt_out, "compatible",
-				   LUO_FDT_SESSION_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_SESSION_HEADER, &header_ser_pa,
-			    sizeof(header_ser_pa));
-	err |= fdt_end_node(fdt_out);
 
-	if (err)
-		goto err_unpreserve;
+	*sessions_pa = virt_to_phys(header_ser);
 
 	luo_session_global.outgoing.header_ser = header_ser;
 	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
 	luo_session_global.outgoing.active = true;
 
 	return 0;
-
-err_unpreserve:
-	kho_unpreserve_free(header_ser);
-	return err;
 }
 
-int __init luo_session_setup_incoming(void *fdt_in)
+int __init luo_session_setup_incoming(u64 sessions_pa)
 {
 	struct luo_session_header_ser *header_ser;
-	int err, header_size, offset;
-	u64 header_ser_pa;
-	const void *ptr;
-
-	offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME);
-	if (offset < 0) {
-		pr_err("Unable to get session node: [%s]\n",
-		       LUO_FDT_SESSION_NODE_NAME);
-		return -EINVAL;
-	}
 
-	err = fdt_node_check_compatible(fdt_in, offset,
-					LUO_FDT_SESSION_COMPATIBLE);
-	if (err) {
-		pr_err("Session node incompatible [%s]\n",
-		       LUO_FDT_SESSION_COMPATIBLE);
-		return -EINVAL;
+	if (sessions_pa) {
+		header_ser = phys_to_virt(sessions_pa);
+		luo_session_global.incoming.header_ser = header_ser;
+		luo_session_global.incoming.ser = (void *)(header_ser + 1);
+		luo_session_global.incoming.active = true;
 	}
 
-	header_size = 0;
-	ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get session header '%s' [%d]\n",
-		       LUO_FDT_SESSION_HEADER, header_size);
-		return -EINVAL;
-	}
-
-	header_ser_pa = get_unaligned((u64 *)ptr);
-	header_ser = phys_to_virt(header_ser_pa);
-
-	luo_session_global.incoming.header_ser = header_ser;
-	luo_session_global.incoming.ser = (void *)(header_ser + 1);
-	luo_session_global.incoming.active = true;
-
 	return 0;
 }
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:29 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:29 +0000
Subject: [PATCH v4 04/13] liveupdate: register luo_ser as KHO subtree
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-5-pasha.tatashin@soleen.com>

Entirely remove the LUO FDT wrapper since the FDT only carries the
compatible string and the pointer to the centralized struct luo_ser.
Instead, register the struct luo_ser via the KHO raw subtree
API, placing the compatibility string inside the structure itself.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h  | 57 +++++++++---------------
 kernel/liveupdate/luo_core.c | 85 +++++++++++-------------------------
 2 files changed, 46 insertions(+), 96 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 1b2f865a771a..9a4fe491812b 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -10,11 +10,11 @@
  *
  * Live Update Orchestrator uses the stable Application Binary Interface
  * defined below to pass state from a pre-update kernel to a post-update
- * kernel. The ABI is built upon the Kexec HandOver framework and uses a
- * Flattened Device Tree to describe the preserved data.
+ * kernel. The ABI is built upon the Kexec HandOver framework and registers
+ * the central `struct luo_ser` via the KHO raw subtree API.
  *
- * This interface is a contract. Any modification to the FDT structure, node
- * properties, compatible strings, or the layout of the `__packed` serialization
+ * This interface is a contract. Any modification to the structure fields,
+ * compatible strings, or the layout of the `__packed` serialization
  * structures defined here constitutes a breaking change. Such changes require
  * incrementing the version number in the relevant `_COMPATIBLE` string to
  * prevent a new kernel from misinterpreting data from an old kernel.
@@ -23,31 +23,15 @@
  * however, backward/forward compatibility is only guaranteed for kernels
  * supporting the same ABI version.
  *
- * FDT Structure Overview:
+ * KHO Structure Overview:
  *   The entire LUO state is encapsulated within a single KHO entry named "LUO".
- *   This entry contains an FDT with the following layout:
- *
- *   .. code-block:: none
- *
- *     / {
- *         compatible = "luo-v2";
- *         luo-abi-header = <phys_addr_of_luo_ser>;
- *     };
- *
- * Main LUO Node (/):
- *
- *   - compatible: "luo-v2"
- *     Identifies the overall LUO ABI version.
- *   - luo-abi-header: u64
- *     The physical address of `struct luo_ser`.
+ *   This entry contains the `struct luo_ser` structure.
  *
  * Serialization Structures:
- *   The FDT properties point to memory regions containing arrays of simple,
- *   `__packed` structures. These structures contain the actual preserved state.
- *
  *   - struct luo_ser:
  *     The central ABI structure that contains the overall state of the LUO.
- *     It includes the liveupdate-number and pointers to sessions and FLBs.
+ *     It includes the compatibility string, the liveupdate-number, and pointers
+ *     to sessions and FLBs.
  *
  *   - struct luo_session_header_ser:
  *     Header for the session array. Contains the total page count of the
@@ -78,26 +62,27 @@
 #ifndef _LINUX_KHO_ABI_LUO_H
 #define _LINUX_KHO_ABI_LUO_H
 
+#include <linux/align.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
- * The LUO FDT hooks all LUO state for sessions, fds, etc.
+ * The LUO state is registered under this KHO entry name.
  */
-#define LUO_FDT_SIZE		PAGE_SIZE
-#define LUO_FDT_KHO_ENTRY_NAME	"LUO"
-#define LUO_FDT_COMPATIBLE	"luo-v2"
-#define LUO_FDT_ABI_HEADER	"luo-abi-header"
+#define LUO_KHO_ENTRY_NAME	"LUO"
+#define LUO_ABI_COMPATIBLE	"luo-v3"
+#define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
  * struct luo_ser - Centralized LUO ABI header.
+ * @compatible:     Compatibility string identifying the LUO ABI version.
  * @liveupdate_num: A counter tracking the number of successful live updates.
  * @sessions_pa:    Physical address of the first session block header.
  * @flbs_pa:        Physical address of the FLB header.
  *
- * This structure is the root of all preserved LUO state. It is pointed to by
- * the "luo-abi-header" property in the LUO FDT.
+ * This structure is the root of all preserved LUO state.
  */
 struct luo_ser {
+	char compatible[LUO_ABI_COMPAT_LEN];
 	u64 liveupdate_num;
 	u64 sessions_pa;
 	u64 flbs_pa;
@@ -111,7 +96,7 @@ struct luo_ser {
  * @data:        Private data
  * @token:       User provided token for this file
  *
- * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_file_ser {
 	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
@@ -142,7 +127,7 @@ struct luo_file_set_ser {
  * physical memory preserved across the kexec. It provides the necessary
  * metadata to interpret the array of session entries that follow.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_session_header_ser {
 	u64 count;
@@ -159,7 +144,7 @@ struct luo_session_header_ser {
  * session) is created and passed to the new kernel, allowing it to reconstruct
  * the session context.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_session_ser {
 	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -180,7 +165,7 @@ struct luo_session_ser {
  * This structure is located at the physical address specified by the
  * flbs_pa in luo_ser.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_flb_header_ser {
 	u64 pgcnt;
@@ -202,7 +187,7 @@ struct luo_flb_header_ser {
  * passed to the new kernel. Each entry allows the LUO core to restore one
  * global, shared object.
  *
- * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
+ * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
  */
 struct luo_flb_ser {
 	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index fbc18c5f4230..e261a03a1b47 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -50,7 +50,6 @@
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/luo.h>
 #include <linux/kobject.h>
-#include <linux/libfdt.h>
 #include <linux/liveupdate.h>
 #include <linux/miscdevice.h>
 #include <linux/mm.h>
@@ -63,8 +62,7 @@
 
 static struct {
 	bool enabled;
-	void *fdt_out;
-	void *fdt_in;
+	struct luo_ser *luo_ser_out;
 	u64 liveupdate_num;
 } luo_global;
 
@@ -81,11 +79,10 @@ early_param("liveupdate", early_liveupdate_param);
 
 static int __init luo_early_startup(void)
 {
+	phys_addr_t luo_ser_phys;
 	struct luo_ser *luo_ser;
-	int err, header_size;
-	phys_addr_t fdt_phys;
-	const void *ptr;
-	u64 luo_ser_pa;
+	size_t len;
+	int err;
 
 	if (!kho_is_enabled()) {
 		if (liveupdate_enabled())
@@ -94,40 +91,29 @@ static int __init luo_early_startup(void)
 		return 0;
 	}
 
-	/* Retrieve LUO subtree, and verify its format. */
-	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
+	/* Retrieve LUO state from KHO. */
+	err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len);
 	if (err) {
 		if (err != -ENOENT) {
-			pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
-			       LUO_FDT_KHO_ENTRY_NAME, ERR_PTR(err));
+			pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n",
+			       LUO_KHO_ENTRY_NAME, ERR_PTR(err));
 			return err;
 		}
 
 		return 0;
 	}
 
-	luo_global.fdt_in = phys_to_virt(fdt_phys);
-	err = fdt_node_check_compatible(luo_global.fdt_in, 0,
-					LUO_FDT_COMPATIBLE);
-	if (err) {
-		pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
-		       LUO_FDT_KHO_ENTRY_NAME, LUO_FDT_COMPATIBLE, err);
-
+	if (len < sizeof(*luo_ser)) {
+		pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
 		return -EINVAL;
 	}
 
-	header_size = 0;
-	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
-	if (!ptr || header_size != sizeof(u64)) {
-		pr_err("Unable to get ABI header '%s' [%d]\n",
-		       LUO_FDT_ABI_HEADER, header_size);
-
+	luo_ser = phys_to_virt(luo_ser_phys);
+	if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
+		pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
 		return -EINVAL;
 	}
 
-	luo_ser_pa = get_unaligned((u64 *)ptr);
-	luo_ser = phys_to_virt(luo_ser_pa);
-
 	luo_global.liveupdate_num = luo_ser->liveupdate_num;
 	pr_info("Retrieved live update data, liveupdate number: %lld\n",
 		luo_global.liveupdate_num);
@@ -160,37 +146,20 @@ static int __init liveupdate_early_init(void)
 }
 early_initcall(liveupdate_early_init);
 
-/* Called during boot to create outgoing LUO fdt tree */
-static int __init luo_fdt_setup(void)
+/* Called during boot to create outgoing LUO state */
+static int __init luo_state_setup(void)
 {
 	struct luo_ser *luo_ser;
-	u64 luo_ser_pa;
-	void *fdt_out;
 	int err;
 
-	fdt_out = kho_alloc_preserve(LUO_FDT_SIZE);
-	if (IS_ERR(fdt_out)) {
-		pr_err("failed to allocate/preserve FDT memory\n");
-		return PTR_ERR(fdt_out);
-	}
-
 	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
 	if (IS_ERR(luo_ser)) {
-		err = PTR_ERR(luo_ser);
-		goto exit_free_fdt;
+		pr_err("failed to allocate/preserve LUO state memory\n");
+		return PTR_ERR(luo_ser);
 	}
-	luo_ser_pa = virt_to_phys(luo_ser);
-
-	err = fdt_create(fdt_out, LUO_FDT_SIZE);
-	err |= fdt_finish_reservemap(fdt_out);
-	err |= fdt_begin_node(fdt_out, "");
-	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
-	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
-			    sizeof(luo_ser_pa));
-	err |= fdt_end_node(fdt_out);
-	err |= fdt_finish(fdt_out);
-	if (err)
-		goto exit_free_luo_ser;
+
+	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
+	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
 	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
 	if (err)
@@ -200,21 +169,17 @@ static int __init luo_fdt_setup(void)
 	if (err)
 		goto exit_free_luo_ser;
 
-	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
-
-	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
-			      fdt_totalsize(fdt_out));
+	err = kho_add_subtree(LUO_KHO_ENTRY_NAME, luo_ser, sizeof(*luo_ser));
 	if (err)
 		goto exit_free_luo_ser;
-	luo_global.fdt_out = fdt_out;
+
+	luo_global.luo_ser_out = luo_ser;
 
 	return 0;
 
 exit_free_luo_ser:
 	kho_unpreserve_free(luo_ser);
-exit_free_fdt:
-	kho_unpreserve_free(fdt_out);
-	pr_err("failed to prepare LUO FDT: %d\n", err);
+	pr_err("failed to prepare LUO state: %d\n", err);
 
 	return err;
 }
@@ -230,7 +195,7 @@ static int __init luo_late_startup(void)
 	if (!liveupdate_enabled())
 		return 0;
 
-	err = luo_fdt_setup();
+	err = luo_state_setup();
 	if (err)
 		luo_global.enabled = false;
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:30 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:30 +0000
Subject: [PATCH v4 05/13] liveupdate: Extract luo_file_deserialize_one helper
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-6-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for files into
separate helper functions. In preparation to a linked-block
serialization for files.

This is a pure code movement, no other changes intended.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_file.c | 77 ++++++++++++++++++++----------------
 1 file changed, 44 insertions(+), 33 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 208987502f73..9eec07a9e9fc 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -753,6 +753,46 @@ int luo_file_finish(struct luo_file_set *file_set)
 	return 0;
 }
 
+static int luo_file_deserialize_one(struct luo_file_set *file_set,
+				    struct luo_file_ser *ser)
+{
+	struct liveupdate_file_handler *fh;
+	bool handler_found = false;
+	struct luo_file *luo_file;
+
+	down_read(&luo_register_rwlock);
+	list_private_for_each_entry(fh, &luo_file_handler_list, list) {
+		if (!strcmp(fh->compatible, ser->compatible)) {
+			if (try_module_get(fh->ops->owner))
+				handler_found = true;
+			break;
+		}
+	}
+	up_read(&luo_register_rwlock);
+
+	if (!handler_found) {
+		pr_warn("No registered handler for compatible '%.*s'\n",
+			(int)sizeof(ser->compatible),
+			ser->compatible);
+		return -ENOENT;
+	}
+
+	luo_file = kzalloc_obj(*luo_file);
+	if (!luo_file) {
+		module_put(fh->ops->owner);
+		return -ENOMEM;
+	}
+
+	luo_file->fh = fh;
+	luo_file->file = NULL;
+	luo_file->serialized_data = ser->data;
+	luo_file->token = ser->token;
+	mutex_init(&luo_file->mutex);
+	list_add_tail(&luo_file->list, &file_set->files_list);
+
+	return 0;
+}
+
 /**
  * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
  * @file_set:     The incoming file_set to fill with deserialized data.
@@ -782,6 +822,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	int err;
 	u64 i;
 
 	if (!file_set_ser->files) {
@@ -809,39 +850,9 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 */
 	file_ser = file_set->files;
 	for (i = 0; i < file_set->count; i++) {
-		struct liveupdate_file_handler *fh;
-		bool handler_found = false;
-		struct luo_file *luo_file;
-
-		down_read(&luo_register_rwlock);
-		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
-			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
-				if (try_module_get(fh->ops->owner))
-					handler_found = true;
-				break;
-			}
-		}
-		up_read(&luo_register_rwlock);
-
-		if (!handler_found) {
-			pr_warn("No registered handler for compatible '%.*s'\n",
-				(int)sizeof(file_ser[i].compatible),
-				file_ser[i].compatible);
-			return -ENOENT;
-		}
-
-		luo_file = kzalloc_obj(*luo_file);
-		if (!luo_file) {
-			module_put(fh->ops->owner);
-			return -ENOMEM;
-		}
-
-		luo_file->fh = fh;
-		luo_file->file = NULL;
-		luo_file->serialized_data = file_ser[i].data;
-		luo_file->token = file_ser[i].token;
-		mutex_init(&luo_file->mutex);
-		list_add_tail(&luo_file->list, &file_set->files_list);
+		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+		if (err)
+			return err;
 	}
 
 	return 0;
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:31 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:31 +0000
Subject: [PATCH v4 06/13] liveupdate: Extract luo_session_deserialize_one helper
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-7-pasha.tatashin@soleen.com>

Extract the logic for deserializing single entries for sessions into
separate helper functions. In preparation to a linked-block
serialization for sessions.

This is a pure code movement, no other changes intended.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_session.c | 62 +++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 26 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 3b760fefa7b9..b728fd9e3ed2 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -525,6 +525,40 @@ int __init luo_session_setup_incoming(u64 sessions_pa)
 	return 0;
 }
 
+static int luo_session_deserialize_one(struct luo_session_header *sh,
+				       struct luo_session_ser *ser)
+{
+	struct luo_session *session;
+	int err;
+
+	session = luo_session_alloc(ser->name);
+	if (IS_ERR(session)) {
+		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
+			(int)sizeof(ser->name), ser->name, session);
+		return PTR_ERR(session);
+	}
+
+	err = luo_session_insert(sh, session);
+	if (err) {
+		pr_warn("Failed to insert session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		luo_session_free(session);
+		return err;
+	}
+
+	scoped_guard(mutex, &session->mutex) {
+		err = luo_file_deserialize(&session->file_set,
+					   &ser->file_set_ser);
+	}
+	if (err) {
+		pr_warn("Failed to deserialize files for session [%s] %pe\n",
+			session->name, ERR_PTR(err));
+		return err;
+	}
+
+	return 0;
+}
+
 int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
@@ -555,33 +589,9 @@ int luo_session_deserialize(void)
 	 * reliably reset devices and reclaim memory.
 	 */
 	for (int i = 0; i < sh->header_ser->count; i++) {
-		struct luo_session *session;
-
-		session = luo_session_alloc(sh->ser[i].name);
-		if (IS_ERR(session)) {
-			pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
-				(int)sizeof(sh->ser[i].name),
-				sh->ser[i].name, session);
-			return PTR_ERR(session);
-		}
-
-		err = luo_session_insert(sh, session);
-		if (err) {
-			pr_warn("Failed to insert session [%s] %pe\n",
-				session->name, ERR_PTR(err));
-			luo_session_free(session);
-			return err;
-		}
-
-		scoped_guard(mutex, &session->mutex) {
-			err = luo_file_deserialize(&session->file_set,
-						   &sh->ser[i].file_set_ser);
-		}
-		if (err) {
-			pr_warn("Failed to deserialize files for session [%s] %pe\n",
-				session->name, ERR_PTR(err));
+		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+		if (err)
 			return err;
-		}
 	}
 
 	kho_restore_free(sh->header_ser);
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:32 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:32 +0000
Subject: [PATCH v4 07/13] kho: add support for linked-block serialization
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-8-pasha.tatashin@soleen.com>

Introduce a linked-block serialization mechanism for state handover.

Previously, LUO used contiguous memory blocks for serializing sessions
and files, which imposed limits on the total number of items that could
be preserved across a live update.

This commit adds the infrastructure for a more flexible, block-based
approach where serialized data is stored in a chain of linked blocks.
This is a generic KHO serialization block infrastructure that can be
used by multiple subsystems.

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 Documentation/core-api/kho/abi.rst   |   5 +
 Documentation/core-api/kho/index.rst |  11 +
 MAINTAINERS                          |   1 +
 include/linux/kho/abi/block.h        |  56 ++++
 include/linux/kho_block.h            |  79 ++++++
 kernel/liveupdate/Makefile           |   1 +
 kernel/liveupdate/kho_block.c        | 384 +++++++++++++++++++++++++++
 7 files changed, 537 insertions(+)
 create mode 100644 include/linux/kho/abi/block.h
 create mode 100644 include/linux/kho_block.h
 create mode 100644 kernel/liveupdate/kho_block.c

diff --git a/Documentation/core-api/kho/abi.rst b/Documentation/core-api/kho/abi.rst
index 799d743105a6..edeb5b311963 100644
--- a/Documentation/core-api/kho/abi.rst
+++ b/Documentation/core-api/kho/abi.rst
@@ -28,6 +28,11 @@ KHO persistent memory tracker ABI
 .. kernel-doc:: include/linux/kho/abi/kexec_handover.h
   :doc: KHO persistent memory tracker
 
+KHO serialization block ABI
+===========================
+
+.. kernel-doc:: include/linux/kho/abi/block.h
+
 See Also
 ========
 
diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
index 0a2dee4f8e7d..320914a42178 100644
--- a/Documentation/core-api/kho/index.rst
+++ b/Documentation/core-api/kho/index.rst
@@ -83,6 +83,17 @@ Public API
 .. kernel-doc:: kernel/liveupdate/kexec_handover.c
   :export:
 
+KHO Serialization Blocks API
+============================
+
+.. kernel-doc:: kernel/liveupdate/kho_block.c
+  :doc: KHO Serialization Blocks
+
+.. kernel-doc:: include/linux/kho_block.h
+
+.. kernel-doc:: kernel/liveupdate/kho_block.c
+  :internal:
+
 See Also
 ========
 
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..fd119b343e99 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14194,6 +14194,7 @@ F:	Documentation/admin-guide/mm/kho.rst
 F:	Documentation/core-api/kho/*
 F:	include/linux/kexec_handover.h
 F:	include/linux/kho/
+F:	include/linux/kho_block.h
 F:	kernel/liveupdate/kexec_handover*
 F:	lib/test_kho.c
 F:	tools/testing/selftests/kho/
diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
new file mode 100644
index 000000000000..8641c20b379b
--- /dev/null
+++ b/include/linux/kho/abi/block.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+/**
+ * DOC: KHO Serialization Blocks ABI
+ *
+ * Subsystems using the KHO Serialization Blocks framework 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 `__packed` serialization
+ * structures defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the `KHO_BLOCK_ABI_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.
+ */
+
+#ifndef _LINUX_KHO_ABI_BLOCK_H
+#define _LINUX_KHO_ABI_BLOCK_H
+
+#include <asm/page.h>
+#include <linux/types.h>
+
+#define KHO_BLOCK_ABI_COMPATIBLE	"kho-block-v1"
+
+/**
+ * KHO_BLOCK_SIZE - The size of each serialization block.
+ *
+ * This is defined as PAGE_SIZE. PAGE_SIZE is ABI compliant because live
+ * update between kernels with different page sizes is not supported by KHO.
+ */
+#define KHO_BLOCK_SIZE			PAGE_SIZE
+
+/**
+ * struct kho_block_header_ser - Header for the serialized data block.
+ * @next:  Physical address of the next struct kho_block_header_ser.
+ * @count: The number of entries that immediately follow this header in the
+ *         memory block.
+ *
+ * This structure is located at the beginning of a block of physical memory
+ * preserved across a kexec. It provides the necessary metadata to interpret
+ * the array of entries that follow.
+ */
+struct kho_block_header_ser {
+	u64 next;
+	u64 count;
+} __packed;
+
+#endif /* _LINUX_KHO_ABI_BLOCK_H */
diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
new file mode 100644
index 000000000000..5e6b87b1befa
--- /dev/null
+++ b/include/linux/kho_block.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+#ifndef _LINUX_KHO_BLOCK_H
+#define _LINUX_KHO_BLOCK_H
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/kho/abi/block.h>
+
+/**
+ * struct kho_block - Internal representation of a serialization block.
+ * @list: List head for linking blocks in memory.
+ * @ser:  Pointer to the serialized header in preserved memory.
+ */
+struct kho_block {
+	struct list_head list;
+	struct kho_block_header_ser *ser;
+};
+
+/**
+ * struct kho_block_set - A set of blocks that belong to the same object.
+ * @blocks:          The list of serialization blocks (struct kho_block).
+ * @nblocks:         The number of allocated serialization blocks.
+ * @head_pa:         Physical address of the first block header.
+ * @entry_size:      The size of each entry in the blocks.
+ * @count_per_block: The maximum number of entries each block can hold.
+ * @incoming:        True if this block set was restored from the previous kernel.
+ */
+struct kho_block_set {
+	struct list_head blocks;
+	long nblocks;
+	u64 head_pa;
+	size_t entry_size;
+	u64 count_per_block;
+	bool incoming;
+};
+
+/**
+ * struct kho_block_it - Iterator for serializing entries into blocks.
+ * @bs:         The block set being iterated.
+ * @block:      The current block.
+ * @i:          The current entry index within @block.
+ */
+struct kho_block_it {
+	struct kho_block_set *bs;
+	struct kho_block *block;
+	u64 i;
+};
+
+/**
+ * KHO_BLOCK_SET_INIT - Initialize a static kho_block_set.
+ * @_name:       Name of the kho_block_set variable.
+ * @_entry_size: The size of each entry in the block set.
+ */
+#define KHO_BLOCK_SET_INIT(_name, _entry_size) {                        \
+	.blocks = LIST_HEAD_INIT((_name).blocks),                       \
+	.entry_size = _entry_size,                                      \
+}
+
+void kho_block_set_init(struct kho_block_set *bs, size_t entry_size);
+
+int kho_block_grow(struct kho_block_set *bs, u64 count);
+void kho_block_shrink(struct kho_block_set *bs, u64 count);
+
+int kho_block_restore(struct kho_block_set *bs, u64 head_pa);
+void kho_block_destroy(struct kho_block_set *bs);
+void kho_block_set_clear(struct kho_block_set *bs);
+
+void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs);
+void *kho_block_it_next(struct kho_block_it *it);
+void *kho_block_it_read(struct kho_block_it *it);
+void *kho_block_it_prev(struct kho_block_it *it);
+void kho_block_it_finalize(struct kho_block_it *it);
+
+#endif /* _LINUX_KHO_BLOCK_H */
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d2f779cbe279..eec9d3ae07eb 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 luo-y :=								\
+		kho_block.o						\
 		luo_core.o						\
 		luo_file.o						\
 		luo_flb.o						\
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
new file mode 100644
index 000000000000..a4e650af946f
--- /dev/null
+++ b/kernel/liveupdate/kho_block.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ */
+
+/**
+ * DOC: KHO Serialization Blocks
+ *
+ * KHO provides a mechanism to preserve stateful data across a kexec handover
+ * by serializing it into memory blocks. This file provides the common
+ * infrastructure for managing these blocks.
+ *
+ * Each block consists of a header (struct kho_block_header_ser) followed by an
+ * array of serialized entries. Multiple blocks are linked together via a
+ * physical pointer in the header, forming a linked list that can be easily
+ * traversed in both the current and the next kernel.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/block.h>
+#include <linux/kho_block.h>
+#include <linux/slab.h>
+
+/*
+ * Safeguard limit for the number of serialization blocks. This is used to
+ * prevent infinite loops and excessive memory allocation in case of memory
+ * corruption in the preserved state.
+ */
+#define KHO_MAX_BLOCKS 10000
+
+/**
+ * kho_block_set_init - Initialize a block set.
+ * @bs:         The block set to initialize.
+ * @entry_size: The size of each entry in the blocks.
+ */
+void kho_block_set_init(struct kho_block_set *bs, size_t entry_size)
+{
+	*bs = (struct kho_block_set)KHO_BLOCK_SET_INIT(*bs, entry_size);
+}
+
+static inline u64 kho_block_count_per_block(struct kho_block_set *bs)
+{
+	if (unlikely(!bs->count_per_block)) {
+		bs->count_per_block = (KHO_BLOCK_SIZE -
+				       sizeof(struct kho_block_header_ser)) /
+				      bs->entry_size;
+		WARN_ON(!bs->count_per_block);
+	}
+	return bs->count_per_block;
+}
+
+/* Free serialized data */
+static void kho_block_free_ser(struct kho_block_set *bs,
+			       struct kho_block_header_ser *ser)
+{
+	if (bs->incoming)
+		kho_restore_free(ser);
+	else
+		kho_unpreserve_free(ser);
+}
+
+static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs)
+{
+	WARN_ON(bs->incoming);
+	return kho_alloc_preserve(KHO_BLOCK_SIZE);
+}
+
+static int kho_block_add(struct kho_block_set *bs,
+			 struct kho_block_header_ser *ser)
+{
+	struct kho_block *block, *last;
+
+	if (bs->nblocks >= KHO_MAX_BLOCKS)
+		return -ENOSPC;
+
+	block = kzalloc_obj(*block);
+	if (!block)
+		return -ENOMEM;
+
+	block->ser = ser;
+	last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
+	list_add_tail(&block->list, &bs->blocks);
+	bs->nblocks++;
+
+	if (last)
+		last->ser->next = virt_to_phys(ser);
+	else
+		bs->head_pa = virt_to_phys(ser);
+
+	return 0;
+}
+
+/**
+ * kho_block_grow - Create a new block if the current capacity is reached.
+ * @bs:    The block set.
+ * @count: The current number of entries.
+ *
+ * This function handles the dynamic expansion of a block set. It allocates
+ * and links a new serialization block if the provided entry count matches
+ * the current total capacity of the set.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int kho_block_grow(struct kho_block_set *bs, u64 count)
+{
+	struct kho_block_header_ser *ser;
+	int err;
+
+	if (WARN_ON(bs->incoming))
+		return -EINVAL;
+
+	if (count != bs->nblocks * kho_block_count_per_block(bs))
+		return 0;
+
+	ser = kho_block_alloc_ser(bs);
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	err = kho_block_add(bs, ser);
+	if (err) {
+		kho_block_free_ser(bs, ser);
+		return err;
+	}
+
+	return 0;
+}
+
+/**
+ * kho_block_shrink - Conditionally destroy the last block in a block set.
+ * @bs:              The block set.
+ * @count:           The current number of entries across all blocks.
+ *
+ * This function checks if the last block in the set is redundant based on the
+ * total entry count and the capacity of the preceding blocks. If the entry
+ * count can be accommodated by the blocks that come before the last one, the
+ * last block is destroyed and removed from the set.
+ */
+void kho_block_shrink(struct kho_block_set *bs, u64 count)
+{
+	struct kho_block *last, *new_last;
+
+	if (count > (bs->nblocks - 1) * kho_block_count_per_block(bs))
+		return;
+
+	if (list_empty(&bs->blocks))
+		return;
+
+	last = list_last_entry(&bs->blocks, struct kho_block, list);
+	list_del(&last->list);
+	bs->nblocks--;
+	kho_block_free_ser(bs, last->ser);
+	kfree(last);
+
+	new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
+	if (new_last)
+		new_last->ser->next = 0;
+	else
+		bs->head_pa = 0;
+}
+
+/*
+ * kho_cyclic_blocks_check - Check for cycles in a linked list of blocks.
+ * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
+ */
+static bool kho_cyclic_blocks_check(struct kho_block_set *bs)
+{
+	struct kho_block_header_ser *fast;
+	struct kho_block_header_ser *slow;
+	int count = 0;
+
+	fast = phys_to_virt(bs->head_pa);
+	slow = fast;
+
+	while (fast) {
+		if (count++ >= KHO_MAX_BLOCKS) {
+			pr_err("Linked list too long\n");
+			return false;
+		}
+
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		if (!fast->next)
+			break;
+
+		fast = phys_to_virt(fast->next);
+		slow = phys_to_virt(slow->next);
+
+		if (slow == fast) {
+			pr_err("Cyclic list detected\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/**
+ * kho_block_restore - Restore a block set from a physical address.
+ * @bs:      The block set to restore.
+ * @head_pa: Physical address of the first block header.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int kho_block_restore(struct kho_block_set *bs, u64 head_pa)
+{
+	struct kho_block_header_ser *ser;
+	u64 next_pa = head_pa;
+	int err;
+
+	/* Restored block sets use size from the previous kernel */
+	bs->incoming = true;
+	if (!head_pa)
+		return 0;
+
+	bs->head_pa = head_pa;
+	if (!kho_cyclic_blocks_check(bs)) {
+		bs->head_pa = 0;
+		return -EINVAL;
+	}
+
+	while (next_pa) {
+		ser = phys_to_virt(next_pa);
+		if (ser->count > kho_block_count_per_block(bs)) {
+			pr_warn("Block contains too many entries: %llu\n",
+				ser->count);
+			err = -EINVAL;
+			goto err_destroy;
+		}
+		err = kho_block_add(bs, ser);
+		if (err)
+			goto err_destroy;
+		next_pa = ser->next;
+	}
+
+	return 0;
+
+err_destroy:
+	kho_block_destroy(bs);
+	return err;
+}
+
+/**
+ * kho_block_destroy - Destroy all blocks in a block set.
+ * @bs:          The block set.
+ */
+void kho_block_destroy(struct kho_block_set *bs)
+{
+	u64 head_pa = bs->head_pa;
+	struct kho_block *block;
+
+	while (!list_empty(&bs->blocks)) {
+		block = list_first_entry(&bs->blocks, struct kho_block, list);
+		list_del(&block->list);
+		kfree(block);
+	}
+	bs->nblocks = 0;
+	bs->head_pa = 0;
+
+	while (head_pa) {
+		struct kho_block_header_ser *ser = phys_to_virt(head_pa);
+
+		head_pa = ser->next;
+		kho_block_free_ser(bs, ser);
+	}
+}
+
+/**
+ * kho_block_set_clear - Clear all serialized data in a block set.
+ * @bs: The block set to clear.
+ */
+void kho_block_set_clear(struct kho_block_set *bs)
+{
+	struct kho_block *block;
+
+	list_for_each_entry(block, &bs->blocks, list) {
+		block->ser->count = 0;
+		memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
+	}
+}
+
+/**
+ * kho_block_it_init - Initialize a block set iterator.
+ * @it:         The iterator to initialize.
+ * @bs:         The block set to iterate over.
+ */
+void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs)
+{
+	it->bs = bs;
+	it->block = list_first_entry_or_null(&bs->blocks, struct kho_block, list);
+	it->i = 0;
+}
+
+/**
+ * kho_block_it_next - Return the next entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current block is full, it automatically advances to the next block
+ * in the set.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more slots are
+ * available.
+ */
+void *kho_block_it_next(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == kho_block_count_per_block(it->bs)) {
+		it->block->ser->count = it->i;
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_read - Return the next entry slot for reading.
+ * @it: The block iterator.
+ *
+ * This function iterates through entries that were previously serialized,
+ * respecting the count stored in each block's header.
+ *
+ * Return: A pointer to the next entry slot, or NULL if no more entries are
+ * available.
+ */
+void *kho_block_it_read(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	while (it->i == it->block->ser->count) {
+		if (list_is_last(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_next_entry(it->block, list);
+		it->i = 0;
+	}
+
+	return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_prev - Return the previous entry slot in the block set.
+ * @it: The block iterator.
+ *
+ * If the current index is at the start of a block, it automatically moves to
+ * the end of the previous block.
+ *
+ * Return: A pointer to the previous entry slot, or NULL if at the very
+ * beginning of the block set.
+ */
+void *kho_block_it_prev(struct kho_block_it *it)
+{
+	if (!it->block)
+		return NULL;
+
+	if (it->i == 0) {
+		if (list_is_first(&it->block->list, &it->bs->blocks))
+			return NULL;
+		it->block = list_prev_entry(it->block, list);
+		it->i = kho_block_count_per_block(it->bs);
+	}
+
+	return (void *)(it->block->ser + 1) + (--it->i * it->bs->entry_size);
+}
+
+/**
+ * kho_block_it_finalize - Finalize the current block by setting its entry count.
+ * @it: The block iterator.
+ */
+void kho_block_it_finalize(struct kho_block_it *it)
+{
+	if (it->block)
+		it->block->ser->count = it->i;
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:33 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:33 +0000
Subject: [PATCH v4 08/13] liveupdate: defer session block allocation and PA setting
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-9-pasha.tatashin@soleen.com>

Currently, luo_session_setup_outgoing() allocates the session block and
sets its physical address in the header immediately. With upcoming
dynamic block-based session management, this makes the first block
different from the rest. Move the allocation to where it is first needed.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 kernel/liveupdate/luo_core.c     |  4 +-
 kernel/liveupdate/luo_internal.h |  2 +-
 kernel/liveupdate/luo_session.c  | 68 ++++++++++++++++++++------------
 3 files changed, 45 insertions(+), 29 deletions(-)

diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index e261a03a1b47..fef7fc6765e3 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -161,9 +161,7 @@ static int __init luo_state_setup(void)
 	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
 	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
 
-	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
-	if (err)
-		goto exit_free_luo_ser;
+	luo_session_setup_outgoing(&luo_ser->sessions_pa);
 
 	err = luo_flb_setup_outgoing(&luo_ser->flbs_pa);
 	if (err)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index fe22086bfbeb..ee18f9a11b91 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -79,7 +79,7 @@ extern struct rw_semaphore luo_register_rwlock;
 
 int luo_session_create(const char *name, struct file **filep);
 int luo_session_retrieve(const char *name, struct file **filep);
-int __init luo_session_setup_outgoing(u64 *sessions_pa);
+void __init luo_session_setup_outgoing(u64 *sessions_pa);
 int __init luo_session_setup_incoming(u64 sessions_pa);
 int luo_session_serialize(void);
 int luo_session_deserialize(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index b728fd9e3ed2..0548d48bb014 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -108,15 +108,16 @@ static DECLARE_RWSEM(luo_session_serialize_rwsem);
 
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
- * @count:      The number of sessions currently tracked in the @list.
- * @list:       The head of the linked list of `struct luo_session` instances.
- * @rwsem:      A read-write semaphore providing synchronized access to the
- *              session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser:        The serialized session data (an array of
- *              `struct luo_session_ser`).
- * @active:     Set to true when first initialized. If previous kernel did not
- *              send session data, active stays false for incoming.
+ * @count:       The number of sessions currently tracked in the @list.
+ * @list:        The head of the linked list of `struct luo_session` instances.
+ * @rwsem:       A read-write semaphore providing synchronized access to the
+ *               session list and other fields in this structure.
+ * @header_ser:  The header data of serialization array.
+ * @ser:         The serialized session data (an array of
+ *               `struct luo_session_ser`).
+ * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
+ * @active:      Set to true when first initialized. If previous kernel did not
+ *               send session data, active stays false for incoming.
  */
 struct luo_session_header {
 	long count;
@@ -124,6 +125,7 @@ struct luo_session_header {
 	struct rw_semaphore rwsem;
 	struct luo_session_header_ser *header_ser;
 	struct luo_session_ser *ser;
+	u64 *sessions_pa;
 	bool active;
 };
 
@@ -171,10 +173,30 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
+static int luo_session_grow_ser(struct luo_session_header *sh)
+{
+	struct luo_session_header_ser *header_ser;
+
+	if (sh->count == LUO_SESSION_MAX)
+		return -ENOMEM;
+
+	if (sh->header_ser)
+		return 0;
+
+	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
+	if (IS_ERR(header_ser))
+		return PTR_ERR(header_ser);
+
+	sh->header_ser = header_ser;
+	sh->ser = (void *)(header_ser + 1);
+	return 0;
+}
+
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
 	struct luo_session *it;
+	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
 
@@ -183,8 +205,9 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		if (sh->count == LUO_SESSION_MAX)
-			return -ENOMEM;
+		err = luo_session_grow_ser(sh);
+		if (err)
+			return err;
 	}
 
 	/*
@@ -494,21 +517,10 @@ int luo_session_retrieve(const char *name, struct file **filep)
 	return err;
 }
 
-int __init luo_session_setup_outgoing(u64 *sessions_pa)
+void __init luo_session_setup_outgoing(u64 *sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	*sessions_pa = virt_to_phys(header_ser);
-
-	luo_session_global.outgoing.header_ser = header_ser;
-	luo_session_global.outgoing.ser = (void *)(header_ser + 1);
+	luo_session_global.outgoing.sessions_pa = sessions_pa;
 	luo_session_global.outgoing.active = true;
-
-	return 0;
 }
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
@@ -610,6 +622,8 @@ int luo_session_serialize(void)
 
 	down_write(&luo_session_serialize_rwsem);
 	down_write(&sh->rwsem);
+	*sh->sessions_pa = 0;
+
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -619,7 +633,11 @@ int luo_session_serialize(void)
 			sizeof(sh->ser[i].name));
 		i++;
 	}
-	sh->header_ser->count = sh->count;
+
+	if (sh->header_ser && sh->count > 0) {
+		sh->header_ser->count = sh->count;
+		*sh->sessions_pa = virt_to_phys(sh->header_ser);
+	}
 	up_write(&sh->rwsem);
 
 	return 0;
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:34 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:34 +0000
Subject: [PATCH v4 09/13] liveupdate: Remove limit on the number of sessions
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-10-pasha.tatashin@soleen.com>

Currently, the number of LUO sessions is limited by a fixed number of
pre-allocated pages for serialization (16 pages, allowing for ~819
sessions).

This limitation is problematic if LUO is used to support things such as
systemd file descriptor store, and would be used not just as VM memory
but to save other states on the machine.

Remove this limit by transitioning to a linked-block approach for
session metadata serialization. Instead of a single contiguous block,
session metadata is now stored in a chain of 16-page blocks. Each block
starts with a header containing the physical address of the next block
and the number of session entries in the current block.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h     |  24 +------
 kernel/liveupdate/luo_session.c | 117 +++++++++++++++-----------------
 2 files changed, 58 insertions(+), 83 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 9a4fe491812b..79758d92ed5f 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -33,11 +33,6 @@
  *     It includes the compatibility string, the liveupdate-number, and pointers
  *     to sessions and FLBs.
  *
- *   - struct luo_session_header_ser:
- *     Header for the session array. Contains the total page count of the
- *     preserved memory block and the number of `struct luo_session_ser`
- *     entries that follow.
- *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
  *     to another preserved memory block containing an array of
@@ -63,13 +58,15 @@
 #define _LINUX_KHO_ABI_LUO_H
 
 #include <linux/align.h>
+#include <linux/kho/abi/block.h>
 #include <uapi/linux/liveupdate.h>
 
 /*
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_ABI_COMPATIBLE	"luo-v3"
+#define LUO_COMPAT_BASE		"luo-v3"
+#define LUO_ABI_COMPATIBLE	LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
 #define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
 /**
@@ -118,21 +115,6 @@ struct luo_file_set_ser {
 	u64 count;
 } __packed;
 
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @count: The number of `struct luo_session_ser` entries that immediately
- *         follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
- */
-struct luo_session_header_ser {
-	u64 count;
-} __packed;
-
 /**
  * struct luo_session_ser - Represents the serialized metadata for a LUO session.
  * @name:         The unique name of the session, provided by the userspace at
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 0548d48bb014..f1a6c32eeb2b 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -24,9 +24,10 @@
  *   ioctls on /dev/liveupdate.
  *
  * - Serialization: Session metadata is preserved using the KHO framework. When
- *   a live update is triggered via kexec, an array of `struct luo_session_ser`
- *   is populated and placed in a preserved memory region. The physical address
- *   of this array is stored in the centralized `struct luo_ser` structure.
+ *   a live update is triggered via kexec, session metadata is serialized into
+ *   a chain of linked-blocks and placed in a preserved memory region. The
+ *   physical address of the first block header is stored in the centralized
+ *   `struct luo_ser` structure.
  *
  * Session Lifecycle:
  *
@@ -89,6 +90,7 @@
 #include <linux/fs.h>
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
+#include <linux/kho_block.h>
 #include <linux/kho/abi/luo.h>
 #include <linux/list.h>
 #include <linux/liveupdate.h>
@@ -98,23 +100,14 @@
 #include <uapi/linux/liveupdate.h>
 #include "luo_internal.h"
 
-/* 16 4K pages, give space for 744 sessions */
-#define LUO_SESSION_PGCNT	16ul
-#define LUO_SESSION_MAX		(((LUO_SESSION_PGCNT << PAGE_SHIFT) -	\
-		sizeof(struct luo_session_header_ser)) /		\
-		sizeof(struct luo_session_ser))
-
 static DECLARE_RWSEM(luo_session_serialize_rwsem);
-
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:       The number of sessions currently tracked in the @list.
  * @list:        The head of the linked list of `struct luo_session` instances.
  * @rwsem:       A read-write semaphore providing synchronized access to the
  *               session list and other fields in this structure.
- * @header_ser:  The header data of serialization array.
- * @ser:         The serialized session data (an array of
- *               `struct luo_session_ser`).
+ * @block_set:   The set of serialization blocks.
  * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
  * @active:      Set to true when first initialized. If previous kernel did not
  *               send session data, active stays false for incoming.
@@ -123,8 +116,7 @@ struct luo_session_header {
 	long count;
 	struct list_head list;
 	struct rw_semaphore rwsem;
-	struct luo_session_header_ser *header_ser;
-	struct luo_session_ser *ser;
+	struct kho_block_set block_set;
 	u64 *sessions_pa;
 	bool active;
 };
@@ -143,10 +135,14 @@ static struct luo_session_global luo_session_global = {
 	.incoming = {
 		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 	.outgoing = {
 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
+						sizeof(struct luo_session_ser)),
 	},
 };
 
@@ -173,25 +169,6 @@ static void luo_session_free(struct luo_session *session)
 	kfree(session);
 }
 
-static int luo_session_grow_ser(struct luo_session_header *sh)
-{
-	struct luo_session_header_ser *header_ser;
-
-	if (sh->count == LUO_SESSION_MAX)
-		return -ENOMEM;
-
-	if (sh->header_ser)
-		return 0;
-
-	header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
-	if (IS_ERR(header_ser))
-		return PTR_ERR(header_ser);
-
-	sh->header_ser = header_ser;
-	sh->ser = (void *)(header_ser + 1);
-	return 0;
-}
-
 static int luo_session_insert(struct luo_session_header *sh,
 			      struct luo_session *session)
 {
@@ -205,7 +182,7 @@ static int luo_session_insert(struct luo_session_header *sh,
 	 * for new session.
 	 */
 	if (sh == &luo_session_global.outgoing) {
-		err = luo_session_grow_ser(sh);
+		err = kho_block_grow(&sh->block_set, sh->count);
 		if (err)
 			return err;
 	}
@@ -232,6 +209,8 @@ static void luo_session_remove(struct luo_session_header *sh,
 	guard(rwsem_write)(&sh->rwsem);
 	list_del(&session->list);
 	sh->count--;
+	if (sh == &luo_session_global.outgoing)
+		kho_block_shrink(&sh->block_set, sh->count);
 }
 
 static int luo_session_finish_one(struct luo_session *session)
@@ -525,15 +504,17 @@ void __init luo_session_setup_outgoing(u64 *sessions_pa)
 
 int __init luo_session_setup_incoming(u64 sessions_pa)
 {
-	struct luo_session_header_ser *header_ser;
+	struct luo_session_header *sh = &luo_session_global.incoming;
+	int err;
 
-	if (sessions_pa) {
-		header_ser = phys_to_virt(sessions_pa);
-		luo_session_global.incoming.header_ser = header_ser;
-		luo_session_global.incoming.ser = (void *)(header_ser + 1);
-		luo_session_global.incoming.active = true;
-	}
+	if (!sessions_pa)
+		return 0;
+
+	err = kho_block_restore(&sh->block_set, sessions_pa);
+	if (err)
+		return err;
 
+	sh->active = true;
 	return 0;
 }
 
@@ -575,6 +556,8 @@ int luo_session_deserialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.incoming;
 	static bool is_deserialized;
+	struct luo_session_ser *ser;
+	struct kho_block_it it;
 	static int err;
 
 	/* If has been deserialized, always return the same error code */
@@ -600,53 +583,63 @@ int luo_session_deserialize(void)
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	for (int i = 0; i < sh->header_ser->count; i++) {
-		err = luo_session_deserialize_one(sh, &sh->ser[i]);
+	kho_block_it_init(&it, &sh->block_set);
+	while ((ser = kho_block_it_read(&it))) {
+		err = luo_session_deserialize_one(sh, ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
 	}
 
-	kho_restore_free(sh->header_ser);
-	sh->header_ser = NULL;
-	sh->ser = NULL;
+err_destroy_blocks:
+	kho_block_destroy(&sh->block_set);
 
-	return 0;
+	return err;
 }
 
 int luo_session_serialize(void)
 {
 	struct luo_session_header *sh = &luo_session_global.outgoing;
 	struct luo_session *session;
-	int i = 0;
+	struct kho_block_it it;
 	int err;
 
 	down_write(&luo_session_serialize_rwsem);
 	down_write(&sh->rwsem);
 	*sh->sessions_pa = 0;
 
+	kho_block_it_init(&it, &sh->block_set);
+
 	list_for_each_entry(session, &sh->list, list) {
-		err = luo_session_freeze_one(session, &sh->ser[i]);
-		if (err)
+		struct luo_session_ser *ser = kho_block_it_next(&it);
+
+		if (!ser) {
+			err = -ENOSPC;
 			goto err_undo;
+		}
 
-		strscpy(sh->ser[i].name, session->name,
-			sizeof(sh->ser[i].name));
-		i++;
-	}
+		err = luo_session_freeze_one(session, ser);
+		if (err) {
+			kho_block_it_prev(&it);
+			goto err_undo;
+		}
 
-	if (sh->header_ser && sh->count > 0) {
-		sh->header_ser->count = sh->count;
-		*sh->sessions_pa = virt_to_phys(sh->header_ser);
+		strscpy(ser->name, session->name, sizeof(ser->name));
 	}
+
+	kho_block_it_finalize(&it);
+
+	if (sh->sessions_pa && sh->count > 0)
+		*sh->sessions_pa = sh->block_set.head_pa;
 	up_write(&sh->rwsem);
 
 	return 0;
 
 err_undo:
 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
-		i--;
-		luo_session_unfreeze_one(session, &sh->ser[i]);
-		memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+		struct luo_session_ser *ser = kho_block_it_prev(&it);
+
+		luo_session_unfreeze_one(session, ser);
+		memset(ser->name, 0, sizeof(ser->name));
 	}
 	up_write(&sh->rwsem);
 	up_write(&luo_session_serialize_rwsem);
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:35 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:35 +0000
Subject: [PATCH v4 10/13] liveupdate: Remove limit on the number of files per session
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-11-pasha.tatashin@soleen.com>

To remove the fixed limit on the number of preserved files per session,
transition the file metadata serialization from a single contiguous
memory block to a chain of linked blocks.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 include/linux/kho/abi/luo.h      |  13 +--
 kernel/liveupdate/luo_file.c     | 144 +++++++++++++++----------------
 kernel/liveupdate/luo_internal.h |   6 +-
 3 files changed, 80 insertions(+), 83 deletions(-)

diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 79758d92ed5f..16df550ef143 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -35,8 +35,8 @@
  *
  *   - struct luo_session_ser:
  *     Metadata for a single session, including its name and a physical pointer
- *     to another preserved memory block containing an array of
- *     `struct luo_file_ser` for all files in that session.
+ *     to the first `struct kho_block_header_ser` for all files in that session.
+ *     Multiple blocks are linked via the `next` field in the header.
  *
  *   - struct luo_file_ser:
  *     Metadata for a single preserved file. Contains the `compatible` string to
@@ -65,7 +65,7 @@
  * The LUO state is registered under this KHO entry name.
  */
 #define LUO_KHO_ENTRY_NAME	"LUO"
-#define LUO_COMPAT_BASE		"luo-v3"
+#define LUO_COMPAT_BASE		"luo-v4"
 #define LUO_ABI_COMPATIBLE	LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
 #define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
 
@@ -103,9 +103,10 @@ struct luo_file_ser {
 
 /**
  * struct luo_file_set_ser - Represents the serialized metadata for file set
- * @files:   The physical address of a contiguous memory block that holds
- *           the serialized state of files (array of luo_file_ser) in this file
- *           set.
+ * @files:   The physical address of the first `struct kho_block_header_ser`.
+ *           This structure is the header for a block of memory containing
+ *           an array of `struct luo_file_ser` entries. Multiple blocks are
+ *           linked via the `next` field in the header.
  * @count:   The total number of files that were part of this session during
  *           serialization. Used for iteration and validation during
  *           restoration.
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 9eec07a9e9fc..a445b1950ca7 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
 /* Keep track of files being preserved by LUO */
 static DEFINE_XARRAY(luo_preserved_files);
 
-/* 2 4K pages, give space for 128 files per file_set */
-#define LUO_FILE_PGCNT		2ul
-#define LUO_FILE_MAX							\
-	((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
-
 /**
  * struct luo_file - Represents a single preserved file instance.
  * @fh:            Pointer to the &struct liveupdate_file_handler that manages
@@ -174,39 +169,6 @@ struct luo_file {
 	u64 token;
 };
 
-static int luo_alloc_files_mem(struct luo_file_set *file_set)
-{
-	size_t size;
-	void *mem;
-
-	if (file_set->files)
-		return 0;
-
-	WARN_ON_ONCE(file_set->count);
-
-	size = LUO_FILE_PGCNT << PAGE_SHIFT;
-	mem = kho_alloc_preserve(size);
-	if (IS_ERR(mem))
-		return PTR_ERR(mem);
-
-	file_set->files = mem;
-
-	return 0;
-}
-
-static void luo_free_files_mem(struct luo_file_set *file_set)
-{
-	/* If file_set has files, no need to free preservation memory */
-	if (file_set->count)
-		return;
-
-	if (!file_set->files)
-		return;
-
-	kho_unpreserve_free(file_set->files);
-	file_set->files = NULL;
-}
-
 static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
 				struct file *file)
 {
@@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	if (luo_token_is_used(file_set, token))
 		return -EEXIST;
 
-	if (file_set->count == LUO_FILE_MAX)
-		return -ENOSPC;
+	err = kho_block_grow(&file_set->block_set, file_set->count);
+	if (err)
+		return err;
 
 	file = fget(fd);
-	if (!file)
-		return -EBADF;
-
-	err = luo_alloc_files_mem(file_set);
-	if (err)
-		goto  err_fput;
+	if (!file) {
+		err = -EBADF;
+		goto err_shrink;
+	}
 
 	err = -ENOENT;
 	down_read(&luo_register_rwlock);
@@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 
 	/* err is still -ENOENT if no handler was found */
 	if (err)
-		goto err_free_files_mem;
+		goto err_fput;
 
 	err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
 			file, GFP_KERNEL);
@@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
 	xa_erase(&luo_preserved_files, luo_get_id(fh, file));
 err_module_put:
 	module_put(fh->ops->owner);
-err_free_files_mem:
-	luo_free_files_mem(file_set);
 err_fput:
 	fput(file);
+err_shrink:
+	kho_block_shrink(&file_set->block_set, file_set->count);
 
 	return err;
 }
@@ -392,13 +353,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
 
 		list_del(&luo_file->list);
 		file_set->count--;
+		kho_block_shrink(&file_set->block_set, file_set->count);
 
 		fput(luo_file->file);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	luo_free_files_mem(file_set);
+	kho_block_destroy(&file_set->block_set);
 }
 
 static int luo_file_freeze_one(struct luo_file_set *file_set,
@@ -454,7 +416,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 		luo_file_unfreeze_one(file_set, luo_file);
 	}
 
-	memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
+	kho_block_set_clear(&file_set->block_set);
 }
 
 /**
@@ -493,19 +455,23 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
 int luo_file_freeze(struct luo_file_set *file_set,
 		    struct luo_file_set_ser *file_set_ser)
 {
-	struct luo_file_ser *file_ser = file_set->files;
 	struct luo_file *luo_file;
+	struct kho_block_it it;
 	int err;
-	int i;
 
 	if (!file_set->count)
 		return 0;
 
-	if (WARN_ON(!file_ser))
-		return -EINVAL;
+	kho_block_it_init(&it, &file_set->block_set);
 
-	i = 0;
 	list_for_each_entry(luo_file, &file_set->files_list, list) {
+		struct luo_file_ser *file_ser = kho_block_it_next(&it);
+
+		if (!file_ser) {
+			err = -ENOSPC;
+			goto err_unfreeze;
+		}
+
 		err = luo_file_freeze_one(file_set, luo_file);
 		if (err < 0) {
 			pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
@@ -514,16 +480,21 @@ int luo_file_freeze(struct luo_file_set *file_set,
 			goto err_unfreeze;
 		}
 
-		strscpy(file_ser[i].compatible, luo_file->fh->compatible,
-			sizeof(file_ser[i].compatible));
-		file_ser[i].data = luo_file->serialized_data;
-		file_ser[i].token = luo_file->token;
-		i++;
+		strscpy(file_ser->compatible, luo_file->fh->compatible,
+			sizeof(file_ser->compatible));
+		file_ser->data = luo_file->serialized_data;
+		file_ser->token = luo_file->token;
 	}
+	kho_block_it_finalize(&it);
 
 	file_set_ser->count = file_set->count;
-	if (file_set->files)
-		file_set_ser->files = virt_to_phys(file_set->files);
+	if (!list_empty(&file_set->block_set.blocks)) {
+		struct kho_block *block;
+
+		block = list_first_entry(&file_set->block_set.blocks,
+					 struct kho_block, list);
+		file_set_ser->files = virt_to_phys(block->ser);
+	}
 
 	return 0;
 
@@ -741,14 +712,12 @@ int luo_file_finish(struct luo_file_set *file_set)
 		module_put(luo_file->fh->ops->owner);
 		list_del(&luo_file->list);
 		file_set->count--;
+		kho_block_shrink(&file_set->block_set, file_set->count);
 		mutex_destroy(&luo_file->mutex);
 		kfree(luo_file);
 	}
 
-	if (file_set->files) {
-		kho_restore_free(file_set->files);
-		file_set->files = NULL;
-	}
+	kho_block_destroy(&file_set->block_set);
 
 	return 0;
 }
@@ -822,16 +791,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 			 struct luo_file_set_ser *file_set_ser)
 {
 	struct luo_file_ser *file_ser;
+	struct kho_block_it it;
 	int err;
-	u64 i;
 
 	if (!file_set_ser->files) {
 		WARN_ON(file_set_ser->count);
 		return 0;
 	}
 
-	file_set->count = file_set_ser->count;
-	file_set->files = phys_to_virt(file_set_ser->files);
+	file_set->count = 0;
+	err = kho_block_restore(&file_set->block_set, file_set_ser->files);
+	if (err)
+		return err;
 
 	/*
 	 * Note on error handling:
@@ -848,25 +819,50 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	 * userspace to detect the failure and trigger a reboot, which will
 	 * reliably reset devices and reclaim memory.
 	 */
-	file_ser = file_set->files;
-	for (i = 0; i < file_set->count; i++) {
-		err = luo_file_deserialize_one(file_set, &file_ser[i]);
+	kho_block_it_init(&it, &file_set->block_set);
+	while ((file_ser = kho_block_it_read(&it))) {
+		err = luo_file_deserialize_one(file_set, file_ser);
 		if (err)
-			return err;
+			goto err_destroy_blocks;
+		file_set->count++;
+	}
+
+	if (file_set->count != file_set_ser->count) {
+		pr_warn("File count mismatch: expected %llu, found %llu\n",
+			file_set_ser->count, file_set->count);
+		err = -EINVAL;
+		goto err_destroy_blocks;
 	}
 
 	return 0;
+
+err_destroy_blocks:
+	while (!list_empty(&file_set->files_list)) {
+		struct luo_file *luo_file;
+
+		luo_file = list_first_entry(&file_set->files_list,
+					    struct luo_file, list);
+		list_del(&luo_file->list);
+		module_put(luo_file->fh->ops->owner);
+		mutex_destroy(&luo_file->mutex);
+		kfree(luo_file);
+	}
+	file_set->count = 0;
+	kho_block_destroy(&file_set->block_set);
+	return err;
 }
 
 void luo_file_set_init(struct luo_file_set *file_set)
 {
 	INIT_LIST_HEAD(&file_set->files_list);
+	kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
 }
 
 void luo_file_set_destroy(struct luo_file_set *file_set)
 {
 	WARN_ON(file_set->count);
 	WARN_ON(!list_empty(&file_set->files_list));
+	WARN_ON(!list_empty(&file_set->block_set.blocks));
 }
 
 /**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index ee18f9a11b91..64879ffe7378 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -10,6 +10,7 @@
 
 #include <linux/liveupdate.h>
 #include <linux/uaccess.h>
+#include <linux/kho_block.h>
 
 struct luo_ucmd {
 	void __user *ubuffer;
@@ -44,14 +45,13 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
  * struct luo_file_set - A set of files that belong to the same sessions.
  * @files_list: An ordered list of files associated with this session, it is
  *              ordered by preservation time.
- * @files:      The physically contiguous memory block that holds the serialized
- *              state of files.
+ * @block_set:  The set of serialization blocks.
  * @count:      A counter tracking the number of files currently stored in the
  *              @files_list for this session.
  */
 struct luo_file_set {
 	struct list_head files_list;
-	struct luo_file_ser *files;
+	struct kho_block_set block_set;
 	u64 count;
 };
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:36 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:36 +0000
Subject: [PATCH v4 11/13] selftests/liveupdate: Test session and file limit removal
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-12-pasha.tatashin@soleen.com>

With the removal of static limits on the number of sessions and files per
session, the orchestrator now uses dynamic allocation.

Add new test cases to verify that the system can handle a large number of
sessions and files. These tests ensure that the dynamic block allocation
and reuse logic for session metadata and outgoing files work correctly
beyond the previous static limits.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 .../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
 .../selftests/liveupdate/luo_test_utils.c     | 24 ++++++
 .../selftests/liveupdate/luo_test_utils.h     |  2 +
 3 files changed, 101 insertions(+)

diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index c7d94b9181e1..502fb3567e38 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -26,6 +26,7 @@
 
 #include <linux/liveupdate.h>
 
+#include "luo_test_utils.h"
 #include "../kselftest.h"
 #include "../kselftest_harness.h"
 
@@ -499,4 +500,78 @@ TEST_F(liveupdate_device, get_session_name_max_length)
 	ASSERT_EQ(close(session_fd), 0);
 }
 
+/*
+ * Test Case: Manage Many Sessions
+ *
+ * Verifies that a large number of sessions can be created and then
+ * destroyed during normal system operation. This specifically tests the
+ * dynamic block allocation and reuse logic for session metadata management
+ * without preserving any files.
+ */
+TEST_F(liveupdate_device, preserve_many_sessions)
+{
+#define MANY_SESSIONS 2000
+	int session_fds[MANY_SESSIONS];
+	int ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_SESSIONS);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_SESSIONS; i++) {
+		char name[64];
+
+		snprintf(name, sizeof(name), "many-session-%d", i);
+		session_fds[i] = create_session(self->fd1, name);
+		ASSERT_GE(session_fds[i], 0);
+	}
+
+	for (i = 0; i < MANY_SESSIONS; i++)
+		ASSERT_EQ(close(session_fds[i]), 0);
+}
+
+/*
+ * Test Case: Preserve Many Files
+ *
+ * Verifies that a large number of files can be preserved in a single session
+ * and then destroyed during normal system operation. This tests the dynamic
+ * block allocation and management for outgoing files.
+ */
+TEST_F(liveupdate_device, preserve_many_files)
+{
+#define MANY_FILES 500
+	int mem_fds[MANY_FILES];
+	int session_fd, ret, i;
+
+	self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+	if (self->fd1 < 0 && errno == ENOENT)
+		SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+	ASSERT_GE(self->fd1, 0);
+
+	session_fd = create_session(self->fd1, "many-files-test");
+	ASSERT_GE(session_fd, 0);
+
+	ret = luo_ensure_nofile_limit(MANY_FILES + 10);
+	if (ret == -EPERM)
+		SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
+	ASSERT_EQ(ret, 0);
+
+	for (i = 0; i < MANY_FILES; i++) {
+		mem_fds[i] = memfd_create("test-memfd", 0);
+		ASSERT_GE(mem_fds[i], 0);
+		ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
+	}
+
+	for (i = 0; i < MANY_FILES; i++)
+		ASSERT_EQ(close(mem_fds[i]), 0);
+
+	ASSERT_EQ(close(session_fd), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
index 3c8721c505df..333a3530051b 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.c
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -17,6 +17,7 @@
 #include <sys/syscall.h>
 #include <sys/mman.h>
 #include <sys/types.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <stdarg.h>
@@ -28,6 +29,29 @@ int luo_open_device(void)
 	return open(LUO_DEVICE, O_RDWR);
 }
 
+int luo_ensure_nofile_limit(long min_limit)
+{
+	struct rlimit hl;
+
+	/* Allow to extra files to be used by test itself */
+	min_limit += 32;
+
+	if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	if (hl.rlim_cur >= min_limit)
+		return 0;
+
+	hl.rlim_cur = min_limit;
+	if (hl.rlim_cur > hl.rlim_max)
+		hl.rlim_max = hl.rlim_cur;
+
+	if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
+		return -errno;
+
+	return 0;
+}
+
 int luo_create_session(int luo_fd, const char *name)
 {
 	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
index 90099bf49577..6a0d85386613 100644
--- a/tools/testing/selftests/liveupdate/luo_test_utils.h
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -26,6 +26,8 @@ int luo_create_session(int luo_fd, const char *name);
 int luo_retrieve_session(int luo_fd, const char *name);
 int luo_session_finish(int session_fd);
 
+int luo_ensure_nofile_limit(long min_limit);
+
 int create_and_preserve_memfd(int session_fd, int token, const char *data);
 int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
 
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:37 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:37 +0000
Subject: [PATCH v4 12/13] selftests/liveupdate: Add stress-sessions kexec test
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-13-pasha.tatashin@soleen.com>

Add a new test that creates 2000 LUO sessions before a kexec
reboot and verifies their presence after the reboot. This ensures
that the linked-block serialization mechanism works correctly for
a large number of sessions.

Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 tools/testing/selftests/liveupdate/Makefile   |   1 +
 .../liveupdate/luo_stress_sessions.c          | 102 ++++++++++++++++++
 2 files changed, 103 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_sessions.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 080754787ede..ed7534468386 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -6,6 +6,7 @@ TEST_GEN_PROGS += liveupdate
 
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
+TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
new file mode 100644
index 000000000000..f201b1839d1d
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of sessions across a kexec
+ * reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_SESSIONS 2000
+#define STATE_SESSION_NAME "kexec_many_state"
+#define STATE_MEMFD_TOKEN 999
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int ret, i;
+
+	ksft_print_msg("[STAGE 1] Increasing ulimit for open files...\n");
+	ret = luo_ensure_nofile_limit(NUM_SESSIONS);
+	if (ret == -EPERM)
+		ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE\n");
+	if (ret < 0)
+		ksft_exit_fail_msg("luo_ensure_nofile_limit failed: %s\n", strerror(-ret));
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating %d sessions...\n", NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_create_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_create_session for '%s' at index %d",
+				  name, i);
+		}
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully created %d sessions.\n",
+		       NUM_SESSIONS);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving and finishing %d sessions...\n",
+		       NUM_SESSIONS);
+
+	for (i = 0; i < NUM_SESSIONS; i++) {
+		char name[LIVEUPDATE_SESSION_NAME_LENGTH];
+		int s_fd;
+
+		snprintf(name, sizeof(name), "many-test-%d", i);
+		s_fd = luo_retrieve_session(luo_fd, name);
+		if (s_fd < 0) {
+			fail_exit("luo_retrieve_session for '%s' at index %d",
+				  name, i);
+		}
+
+		if (luo_session_finish(s_fd) < 0) {
+			fail_exit("luo_session_finish for '%s' at index %d",
+				  name, i);
+		}
+		close(s_fd);
+	}
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-SESSIONS KEXEC TEST PASSED (%d sessions) ---\n",
+		       NUM_SESSIONS);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From pasha.tatashin at soleen.com  Sat May 30 15:19:38 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sat, 30 May 2026 22:19:38 +0000
Subject: [PATCH v4 13/13] selftests/liveupdate: Add stress-files kexec test
In-Reply-To: <20260530221938.115978-1-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
Message-ID: <20260530221938.115978-14-pasha.tatashin@soleen.com>

Add a new luo_stress_files kexec test that verifies preserving and
retrieving 500 files across a kexec reboot.

Reviewed-by: Pratyush Yadav (Google) <pratyush at kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 tools/testing/selftests/liveupdate/Makefile   |  1 +
 .../selftests/liveupdate/luo_stress_files.c   | 97 +++++++++++++++++++
 2 files changed, 98 insertions(+)
 create mode 100644 tools/testing/selftests/liveupdate/luo_stress_files.c

diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index ed7534468386..30689d22cb02 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -7,6 +7,7 @@ TEST_GEN_PROGS += liveupdate
 TEST_GEN_PROGS_EXTENDED += luo_kexec_simple
 TEST_GEN_PROGS_EXTENDED += luo_multi_session
 TEST_GEN_PROGS_EXTENDED += luo_stress_sessions
+TEST_GEN_PROGS_EXTENDED += luo_stress_files
 
 TEST_FILES += do_kexec.sh
 
diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c
new file mode 100644
index 000000000000..0cdf9cd4bac7
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_stress_files.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <pasha.tatashin at soleen.com>
+ *
+ * Validate that LUO can handle a large number of files per session across
+ * a kexec reboot.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "luo_test_utils.h"
+
+#define NUM_FILES 500
+#define STATE_SESSION_NAME "kexec_many_files_state"
+#define STATE_MEMFD_TOKEN 9999
+#define TEST_SESSION_NAME "many_files_session"
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+	int session_fd, i;
+
+	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+	ksft_print_msg("[STAGE 1] Creating test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_create_session");
+
+	ksft_print_msg("[STAGE 1] Preserving %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		if (create_and_preserve_memfd(session_fd, i, data) < 0)
+			fail_exit("create_and_preserve_memfd for index %d", i);
+	}
+
+	ksft_print_msg("[STAGE 1] Successfully preserved %d files.\n", NUM_FILES);
+
+	close(luo_fd);
+	daemonize_and_wait();
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+	int session_fd;
+	int i, stage;
+
+	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+	if (stage != 2) {
+		fail_exit("Expected stage 2, but state file contains %d",
+			  stage);
+	}
+
+	ksft_print_msg("[STAGE 2] Retrieving test session '%s'...\n", TEST_SESSION_NAME);
+	session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
+	if (session_fd < 0)
+		fail_exit("luo_retrieve_session");
+
+	ksft_print_msg("[STAGE 2] Verifying %d files...\n", NUM_FILES);
+	for (i = 0; i < NUM_FILES; i++) {
+		char data[64];
+		int fd;
+
+		snprintf(data, sizeof(data), "file-data-%d", i);
+		fd = restore_and_verify_memfd(session_fd, i, data);
+		if (fd < 0)
+			fail_exit("restore_and_verify_memfd for index %d", i);
+		close(fd);
+	}
+
+	ksft_print_msg("[STAGE 2] Finishing test session...\n");
+	if (luo_session_finish(session_fd) < 0)
+		fail_exit("luo_session_finish for test session");
+	close(session_fd);
+
+	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+	if (luo_session_finish(state_session_fd) < 0)
+		fail_exit("luo_session_finish for state session");
+	close(state_session_fd);
+
+	ksft_print_msg("\n--- MANY-FILES KEXEC TEST PASSED (%d files) ---\n",
+		       NUM_FILES);
+}
+
+int main(int argc, char *argv[])
+{
+	return luo_test(argc, argv, STATE_SESSION_NAME,
+			run_stage_1, run_stage_2);
+}
-- 
2.53.0



From pr-tracker-bot at kernel.org  Sat May 30 15:43:46 2026
From: pr-tracker-bot at kernel.org (pr-tracker-bot at kernel.org)
Date: Sat, 30 May 2026 22:43:46 +0000
Subject: [GIT PULL] liveupdate: kho: two regression fixes
In-Reply-To: <20260530171420.1430298-1-rppt@kernel.org>
References: <20260530171420.1430298-1-rppt@kernel.org>
Message-ID: <178018102619.2731103.8401182140479584451.pr-tracker-bot@kernel.org>

The pull request you sent on Sat, 30 May 2026 20:14:20 +0300:

> https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git tags/liveupdate-fixes-2026-05-30

has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/9d87d0fc8c8e0a2a2b5026a169db2f270b4828c1

Thank you!

-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html


From rppt at kernel.org  Sun May 31 02:28:47 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 31 May 2026 12:28:47 +0300
Subject: No subject
In-Reply-To: <20260529143238.169169-1-dongtai.guo@linux.dev>
References: <20260529143238.169169-1-dongtai.guo@linux.dev>
Message-ID: <ahv_T6WwFEysgIeg@kernel.org>

Hi,

On Fri, May 29, 2026 at 10:32:34PM +0800, George Guo wrote:
> From: George Guo <guodongtai at kylinos.cn>
> 
> WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
> #26: 
> containing only /chosen with the two KHO properties.  Since DEVICE_TREE_GUID
> 
> ERROR: Invalid commit separator - some tools may have problems applying this
> #34: 
> -------------------------------
> 
> total: 2 errors, 1 warnings, 0 lines checked

I believe something went wrong with sending the emails :)

Please include changes from revision to revision in the cover letter next
time.

You also may want to consider using b4 prep/send for sending the patches.

> >
> Date: Fri, 29 May 2026 21:54:01 +0800
> Subject: [PATCH v2 0/4] LoongArch: add KHO support and selftests
> 
> This series adds Kexec Handover (KHO) support for LoongArch and extends
> the KHO selftest infrastructure to run on LoongArch under QEMU.
> 
> KHO passes metadata (the KHO state FDT and scratch area addresses) to the
> second kernel via the FDT /chosen node, using the linux,kho-fdt and
> linux,kho-scratch properties that drivers/of/kexec.c:kho_add_chosen()
> writes and drivers/of/fdt.c:early_init_dt_check_kho() reads.
> 
> Selftest support (patches 3-4):
> 
> Patch 3 adds loongarch.conf and extends vmtest.sh to recognise loongarch64
> as a build target.  The LoongArch virt machine is FDT-only (no ACPI), so
> 'earlycon' must appear on the kernel cmdline or the console UART is never
> discovered.  PS/2 input devices are also disabled since QEMU's LoongArch
> virt machine has no i8042 controller; the fallback port probe hits a page
> fault and panics before reaching userspace.

Patch 3 should not introduce regressions
 
> Patch 4 handles QEMU not exiting after kexec on LoongArch.  QEMU provides
> no EFI runtime services, so machine_restart() falls through to an infinite
> idle loop.  QEMU_NEEDS_KILL=1 in loongarch.conf signals vmtest.sh to run
> QEMU in the background, poll the serial output for the test verdict, and
> kill QEMU once it appears, so the test completes unattended.

And patch 4 should be folded into patch 3.
As I said during v1 review, the whole wait and kill loop can be replaced
with 'timeout' command which does not need to be specific for LoongArch.
The actual timeout value might, though.

Will wait for v3 for more detailed review.
 
> George Guo (4):
>   LoongArch: kexec: add KHO support for FDT-based systems
>   LoongArch: kexec: add KHO support for ACPI-only systems
>   selftests/kho: add LoongArch vmtest support
>   selftests/kho: handle QEMU not exiting after kexec on LoongArch
> 
>  arch/loongarch/Kconfig                     |   3 +
>  arch/loongarch/include/asm/kexec.h         |   7 +
>  arch/loongarch/kernel/machine_kexec.c      |  38 +++
>  arch/loongarch/kernel/machine_kexec_file.c | 256 +++++++++++++++++++++
>  arch/loongarch/kernel/setup.c              |  21 +-
>  tools/testing/selftests/kho/loongarch.conf |  13 ++
>  tools/testing/selftests/kho/vmtest.sh      |  35 ++-
>  7 files changed, 365 insertions(+), 8 deletions(-)
>  create mode 100644 tools/testing/selftests/kho/loongarch.conf
> 
> -- 
> 2.25.1
> 
> 

-- 
Sincerely yours,
Mike.


From pasha.tatashin at soleen.com  Sun May 31 05:52:39 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 31 May 2026 12:52:39 +0000
Subject: [PATCH v4 02/13] liveupdate: avoid mixing cleanup guards with
 goto in luo_session_retrieve_fd
In-Reply-To: <20260530221938.115978-3-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
 <20260530221938.115978-3-pasha.tatashin@soleen.com>
Message-ID: <ahwuA1tYmM0LZt0d@plex>

On 05-30 22:19, Pasha Tatashin wrote:
> Refactoring luo_session_retrieve_fd() to avoid mixing automated
> cleanup-style guards with goto-based resource release, which is not
> recommended under the Linux kernel coding style.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_session.c | 25 ++++++++++++-------------
>  1 file changed, 12 insertions(+), 13 deletions(-)
> 
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index 146414933977..8d9201c25412 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -291,25 +291,24 @@ static int luo_session_retrieve_fd(struct luo_session *session,
>  	if (argp->fd < 0)
>  		return argp->fd;
>  
> -	guard(mutex)(&session->mutex);
> -	err = luo_retrieve_file(&session->file_set, argp->token, &file);
> -	if (err < 0)
> -		goto  err_put_fd;
> +	scoped_guard(mutex, &session->mutex) {
> +		err = luo_retrieve_file(&session->file_set, argp->token, &file);
> +		if (err < 0) {
> +			put_unused_fd(argp->fd);
> +			return err;
> +		}
> +	}
>  
>  	err = luo_ucmd_respond(ucmd, sizeof(*argp));
> -	if (err)
> -		goto err_put_file;
> +	if (err) {
> +		fput(file);
> +		put_unused_fd(argp->fd);
> +		return err;
> +	}
>  
>  	fd_install(argp->fd, file);

>From Sashiko:
This is a pre-existing issue, but could this error path allow userspace
to hijack a recycled file descriptor?
...

Answer: No.

get_unused_fd_flags allocates an unused file descriptor (FD) that 
initially points to NULL. It is not associated with any "struct file"; 
this association occurs only during fd_install(), which is the final 
function called before returning to userspace.

>  
>  	return 0;
> -
> -err_put_file:
> -	fput(file);
> -err_put_fd:
> -	put_unused_fd(argp->fd);
> -
> -	return err;
>  }
>  
>  static int luo_session_finish(struct luo_session *session,
> -- 
> 2.53.0
> 


From pasha.tatashin at soleen.com  Sun May 31 06:35:48 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 31 May 2026 13:35:48 +0000
Subject: [PATCH v4 01/13] liveupdate: change file_set->count type to u64
 for type safety
In-Reply-To: <20260530221938.115978-2-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
 <20260530221938.115978-2-pasha.tatashin@soleen.com>
Message-ID: <ahwv0ZiyQ6TzoCah@plex>

On 05-30 22:19, Pasha Tatashin wrote:
> This improves type safety and aligns the in-memory file_set->count with
> the serialized count type. It avoids potential truncation or sign
> conversion mismatch issues.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  kernel/liveupdate/luo_internal.h | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index dd53d4a7277e..ae58206f14ac 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -52,7 +52,7 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
>  struct luo_file_set {
>  	struct list_head files_list;
>  	struct luo_file_ser *files;
> -	long count;
> +	u64 count;

>From Sashiko 1:
...
Since FLBs use a single contiguous block for serialization, an untrusted
KHO payload could provide an abnormally large count.
...

Answer: NO, there is a chain of trust during live update. The previous 
kernel acts as a boot loader for the next kernel, and performs all the 
necessary verifications. We trust the previous kernel to pass the right 
things if compatability strings and format matches. KHO payload has the 
same trust as the previous kernel.

Therefore, we assume the serialized metadata is well-formed and valid. 
Defending against a malicious or hostile KHO payload is outside the 
threat model of this system.

>From Sashiko 2:
...
If luo_session_finish_one() fails (for example, if a file handler returns
-EBUSY), the early return skips luo_session_remove() and
luo_session_free(). Since this is called during the VFS release operation
via fput(), VFS will unconditionally destroy the file descriptor regardless
of the return value.
...

Answer: NO. A finish failure means that we cannot safely release 
resources, as they might be associated with devices and DMA activity. We 
deliberately leak these resources to avoid memory corruption and data 
leaks. When userspace fails to finish properly and closes the session, 
the only way to recover these resources is to perform a cold reboot or 
another live update.

>  };
>  
>  /**
> -- 
> 2.53.0
> 


From pasha.tatashin at soleen.com  Sun May 31 06:44:36 2026
From: pasha.tatashin at soleen.com (Pasha Tatashin)
Date: Sun, 31 May 2026 13:44:36 +0000
Subject: [PATCH v4 04/13] liveupdate: register luo_ser as KHO subtree
In-Reply-To: <20260530221938.115978-5-pasha.tatashin@soleen.com>
References: <20260530221938.115978-1-pasha.tatashin@soleen.com>
 <20260530221938.115978-5-pasha.tatashin@soleen.com>
Message-ID: <ahw6Bqllb0CJ88mR@plex>

On 05-30 22:19, Pasha Tatashin wrote:
> Entirely remove the LUO FDT wrapper since the FDT only carries the
> compatible string and the pointer to the centralized struct luo_ser.
> Instead, register the struct luo_ser via the KHO raw subtree
> API, placing the compatibility string inside the structure itself.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> ---
>  include/linux/kho/abi/luo.h  | 57 +++++++++---------------
>  kernel/liveupdate/luo_core.c | 85 +++++++++++-------------------------
>  2 files changed, 46 insertions(+), 96 deletions(-)
> 
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 1b2f865a771a..9a4fe491812b 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -10,11 +10,11 @@
>   *
>   * Live Update Orchestrator uses the stable Application Binary Interface
>   * defined below to pass state from a pre-update kernel to a post-update
> - * kernel. The ABI is built upon the Kexec HandOver framework and uses a
> - * Flattened Device Tree to describe the preserved data.
> + * kernel. The ABI is built upon the Kexec HandOver framework and registers
> + * the central `struct luo_ser` via the KHO raw subtree API.
>   *
> - * This interface is a contract. Any modification to the FDT structure, node
> - * properties, compatible strings, or the layout of the `__packed` serialization
> + * This interface is a contract. Any modification to the structure fields,
> + * compatible strings, or the layout of the `__packed` serialization
>   * structures defined here constitutes a breaking change. Such changes require
>   * incrementing the version number in the relevant `_COMPATIBLE` string to
>   * prevent a new kernel from misinterpreting data from an old kernel.
> @@ -23,31 +23,15 @@
>   * however, backward/forward compatibility is only guaranteed for kernels
>   * supporting the same ABI version.
>   *
> - * FDT Structure Overview:
> + * KHO Structure Overview:
>   *   The entire LUO state is encapsulated within a single KHO entry named "LUO".
> - *   This entry contains an FDT with the following layout:
> - *
> - *   .. code-block:: none
> - *
> - *     / {
> - *         compatible = "luo-v2";
> - *         luo-abi-header = <phys_addr_of_luo_ser>;
> - *     };
> - *
> - * Main LUO Node (/):
> - *
> - *   - compatible: "luo-v2"
> - *     Identifies the overall LUO ABI version.
> - *   - luo-abi-header: u64
> - *     The physical address of `struct luo_ser`.
> + *   This entry contains the `struct luo_ser` structure.
>   *
>   * Serialization Structures:
> - *   The FDT properties point to memory regions containing arrays of simple,
> - *   `__packed` structures. These structures contain the actual preserved state.
> - *
>   *   - struct luo_ser:
>   *     The central ABI structure that contains the overall state of the LUO.
> - *     It includes the liveupdate-number and pointers to sessions and FLBs.
> + *     It includes the compatibility string, the liveupdate-number, and pointers
> + *     to sessions and FLBs.
>   *
>   *   - struct luo_session_header_ser:
>   *     Header for the session array. Contains the total page count of the
> @@ -78,26 +62,27 @@
>  #ifndef _LINUX_KHO_ABI_LUO_H
>  #define _LINUX_KHO_ABI_LUO_H
>  
> +#include <linux/align.h>
>  #include <uapi/linux/liveupdate.h>
>  
>  /*
> - * The LUO FDT hooks all LUO state for sessions, fds, etc.
> + * The LUO state is registered under this KHO entry name.
>   */
> -#define LUO_FDT_SIZE		PAGE_SIZE
> -#define LUO_FDT_KHO_ENTRY_NAME	"LUO"
> -#define LUO_FDT_COMPATIBLE	"luo-v2"
> -#define LUO_FDT_ABI_HEADER	"luo-abi-header"
> +#define LUO_KHO_ENTRY_NAME	"LUO"
> +#define LUO_ABI_COMPATIBLE	"luo-v3"
> +#define LUO_ABI_COMPAT_LEN	ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
>  
>  /**
>   * struct luo_ser - Centralized LUO ABI header.
> + * @compatible:     Compatibility string identifying the LUO ABI version.
>   * @liveupdate_num: A counter tracking the number of successful live updates.
>   * @sessions_pa:    Physical address of the first session block header.
>   * @flbs_pa:        Physical address of the FLB header.
>   *
> - * This structure is the root of all preserved LUO state. It is pointed to by
> - * the "luo-abi-header" property in the LUO FDT.
> + * This structure is the root of all preserved LUO state.
>   */
>  struct luo_ser {
> +	char compatible[LUO_ABI_COMPAT_LEN];
>  	u64 liveupdate_num;
>  	u64 sessions_pa;
>  	u64 flbs_pa;
> @@ -111,7 +96,7 @@ struct luo_ser {
>   * @data:        Private data
>   * @token:       User provided token for this file
>   *
> - * If this structure is modified, LUO_SESSION_COMPATIBLE must be updated.
> + * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>   */
>  struct luo_file_ser {
>  	char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
> @@ -142,7 +127,7 @@ struct luo_file_set_ser {
>   * physical memory preserved across the kexec. It provides the necessary
>   * metadata to interpret the array of session entries that follow.
>   *
> - * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
> + * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>   */
>  struct luo_session_header_ser {
>  	u64 count;
> @@ -159,7 +144,7 @@ struct luo_session_header_ser {
>   * session) is created and passed to the new kernel, allowing it to reconstruct
>   * the session context.
>   *
> - * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
> + * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>   */
>  struct luo_session_ser {
>  	char name[LIVEUPDATE_SESSION_NAME_LENGTH];
> @@ -180,7 +165,7 @@ struct luo_session_ser {
>   * This structure is located at the physical address specified by the
>   * flbs_pa in luo_ser.
>   *
> - * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
> + * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>   */
>  struct luo_flb_header_ser {
>  	u64 pgcnt;
> @@ -202,7 +187,7 @@ struct luo_flb_header_ser {
>   * passed to the new kernel. Each entry allows the LUO core to restore one
>   * global, shared object.
>   *
> - * If this structure is modified, `LUO_FDT_COMPATIBLE` must be updated.
> + * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
>   */
>  struct luo_flb_ser {
>  	char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
> diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
> index fbc18c5f4230..e261a03a1b47 100644
> --- a/kernel/liveupdate/luo_core.c
> +++ b/kernel/liveupdate/luo_core.c
> @@ -50,7 +50,6 @@
>  #include <linux/kexec_handover.h>
>  #include <linux/kho/abi/luo.h>
>  #include <linux/kobject.h>
> -#include <linux/libfdt.h>
>  #include <linux/liveupdate.h>
>  #include <linux/miscdevice.h>
>  #include <linux/mm.h>
> @@ -63,8 +62,7 @@
>  
>  static struct {
>  	bool enabled;
> -	void *fdt_out;
> -	void *fdt_in;
> +	struct luo_ser *luo_ser_out;
>  	u64 liveupdate_num;
>  } luo_global;
>  
> @@ -81,11 +79,10 @@ early_param("liveupdate", early_liveupdate_param);
>  
>  static int __init luo_early_startup(void)
>  {
> +	phys_addr_t luo_ser_phys;
>  	struct luo_ser *luo_ser;
> -	int err, header_size;
> -	phys_addr_t fdt_phys;
> -	const void *ptr;
> -	u64 luo_ser_pa;
> +	size_t len;
> +	int err;
>  
>  	if (!kho_is_enabled()) {
>  		if (liveupdate_enabled())
> @@ -94,40 +91,29 @@ static int __init luo_early_startup(void)
>  		return 0;
>  	}
>  
> -	/* Retrieve LUO subtree, and verify its format. */
> -	err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
> +	/* Retrieve LUO state from KHO. */
> +	err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len);
>  	if (err) {
>  		if (err != -ENOENT) {
> -			pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
> -			       LUO_FDT_KHO_ENTRY_NAME, ERR_PTR(err));
> +			pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n",
> +			       LUO_KHO_ENTRY_NAME, ERR_PTR(err));
>  			return err;
>  		}
>  
>  		return 0;
>  	}
>  
> -	luo_global.fdt_in = phys_to_virt(fdt_phys);
> -	err = fdt_node_check_compatible(luo_global.fdt_in, 0,
> -					LUO_FDT_COMPATIBLE);
> -	if (err) {
> -		pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
> -		       LUO_FDT_KHO_ENTRY_NAME, LUO_FDT_COMPATIBLE, err);
> -
> +	if (len < sizeof(*luo_ser)) {
> +		pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
>  		return -EINVAL;
>  	}
>  
> -	header_size = 0;
> -	ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
> -	if (!ptr || header_size != sizeof(u64)) {
> -		pr_err("Unable to get ABI header '%s' [%d]\n",
> -		       LUO_FDT_ABI_HEADER, header_size);
> -
> +	luo_ser = phys_to_virt(luo_ser_phys);
> +	if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
> +		pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
>  		return -EINVAL;
>  	}


>From Sashiko:
Does this code leak the preserved luo_ser_phys memory on these early validation
failure paths?

Answer: No, a failure in this function leads to a call to 
luo_restore_fail(), which triggers a panic.

Roman: The quality of the Sashiko review is surprisingly weak this time; 
was the model switched from 3.1 Pro to something else?

>  
> -	luo_ser_pa = get_unaligned((u64 *)ptr);
> -	luo_ser = phys_to_virt(luo_ser_pa);
> -
>  	luo_global.liveupdate_num = luo_ser->liveupdate_num;
>  	pr_info("Retrieved live update data, liveupdate number: %lld\n",
>  		luo_global.liveupdate_num);
> @@ -160,37 +146,20 @@ static int __init liveupdate_early_init(void)
>  }
>  early_initcall(liveupdate_early_init);
>  
> -/* Called during boot to create outgoing LUO fdt tree */
> -static int __init luo_fdt_setup(void)
> +/* Called during boot to create outgoing LUO state */
> +static int __init luo_state_setup(void)
>  {
>  	struct luo_ser *luo_ser;
> -	u64 luo_ser_pa;
> -	void *fdt_out;
>  	int err;
>  
> -	fdt_out = kho_alloc_preserve(LUO_FDT_SIZE);
> -	if (IS_ERR(fdt_out)) {
> -		pr_err("failed to allocate/preserve FDT memory\n");
> -		return PTR_ERR(fdt_out);
> -	}
> -
>  	luo_ser = kho_alloc_preserve(sizeof(*luo_ser));
>  	if (IS_ERR(luo_ser)) {
> -		err = PTR_ERR(luo_ser);
> -		goto exit_free_fdt;
> +		pr_err("failed to allocate/preserve LUO state memory\n");
> +		return PTR_ERR(luo_ser);
>  	}
> -	luo_ser_pa = virt_to_phys(luo_ser);
> -
> -	err = fdt_create(fdt_out, LUO_FDT_SIZE);
> -	err |= fdt_finish_reservemap(fdt_out);
> -	err |= fdt_begin_node(fdt_out, "");
> -	err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE);
> -	err |= fdt_property(fdt_out, LUO_FDT_ABI_HEADER, &luo_ser_pa,
> -			    sizeof(luo_ser_pa));
> -	err |= fdt_end_node(fdt_out);
> -	err |= fdt_finish(fdt_out);
> -	if (err)
> -		goto exit_free_luo_ser;
> +
> +	strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
> +	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
>  
>  	err = luo_session_setup_outgoing(&luo_ser->sessions_pa);
>  	if (err)
> @@ -200,21 +169,17 @@ static int __init luo_fdt_setup(void)
>  	if (err)
>  		goto exit_free_luo_ser;
>  
> -	luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
> -
> -	err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
> -			      fdt_totalsize(fdt_out));
> +	err = kho_add_subtree(LUO_KHO_ENTRY_NAME, luo_ser, sizeof(*luo_ser));
>  	if (err)
>  		goto exit_free_luo_ser;
> -	luo_global.fdt_out = fdt_out;
> +
> +	luo_global.luo_ser_out = luo_ser;
>  
>  	return 0;
>  
>  exit_free_luo_ser:
>  	kho_unpreserve_free(luo_ser);
> -exit_free_fdt:
> -	kho_unpreserve_free(fdt_out);
> -	pr_err("failed to prepare LUO FDT: %d\n", err);
> +	pr_err("failed to prepare LUO state: %d\n", err);
>  
>  	return err;
>  }
> @@ -230,7 +195,7 @@ static int __init luo_late_startup(void)
>  	if (!liveupdate_enabled())
>  		return 0;
>  
> -	err = luo_fdt_setup();
> +	err = luo_state_setup();
>  	if (err)
>  		luo_global.enabled = false;
>  
> -- 
> 2.53.0
> 


From rppt at kernel.org  Sun May 31 10:10:11 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 31 May 2026 20:10:11 +0300
Subject: [RFC PATCH 00/20] mshv: enable kexec with Hyper-V donated pages
 and partitions
In-Reply-To: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
References: <20260528004204.1484584-1-jloeser@linux.microsoft.com>
Message-ID: <ahxrc4pTvVU20RTX@kernel.org>

Hi Jork,

Only had time to skim through the patches.
I have a couple of high level questions for now.

On Wed, May 27, 2026 at 05:41:42PM -0700, Jork Loeser wrote:
> When Linux runs as an L1 Virtual Host (L1VH) under Hyper-V, the MSHV
> root partition driver deposits pages to the hypervisor and creates
> partitions for guest VMs. Prior patches enabled kexec for L1VH, but
> only when no partitions had been created and no memory had been donated.
> 
> This series lifts that limitation. It uses KHO (Kexec Handover) to:
> 
>  - Track all pages deposited to the hypervisor in a KHO radix tree
>    and preserve them across kexec so the new kernel knows which pages
>    are owned by the hypervisor.
> 
>  - Freeze running partitions before kexec, record their IDs in the
>    KHO FDT, and vacuum (tear down + reclaim memory) stale partitions
>    after kexec.
> 
>  - In case of a crash, exclude hypervisor-owned pages from crash
>    dump collection by passing the radix tree root PA via Hyper-V
>    crash MSR P2 to the crash kernel.
> 
> Dependency on Pratyush's KHO series
> ===================================
> 
> Patches 1-12 are cherry-picked from Pratyush Yadav's v1 series
> "kho: make boot time huge page allocation work nicely with KHO" [1],
> which is still under discussion. This series uses functionality from
> those patches -- specifically the meta-data page enumeration via table
> callbacks and the restructured radix tree API. It also extends the
> KHO radix tree with:
> 
>  - A freeze mechanism to lock the tree before serializing for kexec
>    (patch 13).

There were a lot of effort to make KHO stateless and drop the requirement
for finalization/freeze.

Why is this necessary to add a freeze mechanism to kho_radix_tree?
If it's a hard requirement of mshv maybe the freeze part should be handled
there?
 
>  - A crash-kernel-safe variant that memremaps radix nodes for use
>    outside the direct map (patch 14).
> 
> Patch overview
> ==============
> 
> Patches 1-12:  KHO radix tree and memblock changes (from [1])
> Patch 13:      Radix tree freeze and del_key() error reporting

del_key() error reporting sounds like something we'd want to avoid.
del_key() is called on "freeing" path and during error handling, it would
be hard if at all possible to deal with errors from del_key().

> Patch 14:      Crash-kernel-safe radix tree presence check
> Patch 15:      Page tracker using KHO radix tree for deposited pages
> Patch 16:      Debugfs interface for page tracker
> Patches 17-18: Crash MSR reshuffling + crash dump page exclusion
> Patch 19:      Export kexec_in_progress for modules

Isn't there another way to differentiate kexec reboot?

> Patch 20:      Freeze and vacuum partitions across kexec
> 
> Feedback
> ========
> 
> This is an RFC. I am looking for feedback on the overall approach as
> well as the KHO changes (patches 13-14).
> 
> [1] https://lore.kernel.org/linux-mm/20260429133928.850721-1-pratyush at kernel.org/
> 
> Based-on: linux-next/master (next-20260527)

-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 31 11:40:07 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 31 May 2026 21:40:07 +0300
Subject: [PATCH 12/12] mm/hugetlb: make bootmem allocation work with KHO
In-Reply-To: <2vxzo6i37bs6.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-13-pratyush@kernel.org>
 <agmS5yZRflzN1M8U@kernel.org>
 <2vxzo6i37bs6.fsf@kernel.org>
Message-ID: <ahyAhyVGzXdrigIw@kernel.org>

On Mon, May 25, 2026 at 05:24:09PM +0200, Pratyush Yadav wrote:
> On Sun, May 17 2026, Mike Rapoport wrote:
> > On Wed, Apr 29, 2026 at 03:39:14PM +0200, Pratyush Yadav wrote:
> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> 
> So, in summary, I would like to pursue option 1 and try to make it more
> appetizing. But I would like to at least know if you hate the "extended
> scratch" (ignore the name) as a concept or only the code it results in.

Let's retry this one :)

I looked more closely, and it seems that mixing SCRATCH and SCRATCH_EXT
should be a lesser headache than going with option 4.

Tracking the changes in gigantic pages in hugetlb also does not seem
something we'd like to pursue especially considering that memory from freed
or demoted gigantic pages could be reserved.

If we add a dedicated memblock_something to allocate gigantic pages, we
can reduce branching in alloc_bootmem() to

	if (cma)
		do_cma()
	else
		do_memblock()

For hugetlb_cma we might want to teach CMA to create pre-allocated areas
and then it could reuse the same memblock API. This seems useful even
regardless of KHO.
 
-- 
Sincerely yours,
Mike.


From rppt at kernel.org  Sun May 31 11:51:09 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Sun, 31 May 2026 21:51:09 +0300
Subject: [PATCH 09/12] memblock: introduce MEMBLOCK_KHO_SCRATCH_EXT
In-Reply-To: <2vxzse7j7ai9.fsf@kernel.org>
References: <20260429133928.850721-1-pratyush@kernel.org>
 <20260429133928.850721-10-pratyush@kernel.org>
 <agHGOxSnP_VraH1M@kernel.org>
 <2vxzecjhc2s8.fsf@kernel.org>
 <ag-a4ru9bMkDMthO@plex>
 <2vxzse7j7ai9.fsf@kernel.org>
Message-ID: <ahyDHVSH5eLsnp3C@kernel.org>

On Fri, May 22, 2026 at 05:02:38PM +0200, Pratyush Yadav wrote:
> On Fri, May 22 2026, Pasha Tatashin wrote:
> 
> > On 05-11 18:46, Pratyush Yadav wrote:
> >> On Mon, May 11 2026, Mike Rapoport wrote:
> >> 
> >> > On Wed, Apr 29, 2026 at 03:39:11PM +0200, Pratyush Yadav wrote:
> >> >> From: "Pratyush Yadav (Google)" <pratyush at kernel.org>
> >> >> 
> >> >> In the upcoming commits, the KHO will learn how to discover free blocks
> >> >> of memory by walking the KHO radix tree. It will then mark those regions
> >> >> as scratch to allow memory allocation in case scratch runs low.
> >> >> 
> >> >> To differentiate the extended scratch areas from the main scratch areas,
> >> >> introduce MEMBLOCK_KHO_SCRATCH_EXT. Use it when choosing memblock flags
> >> >> for allocations during scratch-only. Teach should_skip_region() to check
> >> >> for both flags before deciding if the region should be skipped.
> >> >
> >> > Why there's a need to differentiate SCRATCH and SCRATCH_EXT?
> >> > SCRATCH (I still hate the name) means "memory memblock can safely use for
> >
> >  +1000
> >
> > I also strongly dislike this name and mentioned it in another thread
> > earlier today.
> >
> > If we ever decide to s/scratch/something-else/ globally, that should be a
> > separate cleanup effort. However, since we are introducing a brand new flag
> > here, we can discuss a better name for the _ext portion to avoid overloading
> > the "scratch" concept.
> >
> >> > the allocations". Initially this memory comes from the reservations in the
> >> > first kernel, but if the second kernel can find more memory to extend it,
> >> > why that additional memory should be treated differently? 
> >> 
> >> Two reasons:
> >> 
> >> 1. We mark SCRATCH as MIGRATE_CMA. We don't want to do that for
> >>    SCRATCH_EXT since this memory can be used for non-movable
> >>    allocations.
> >> 
> >> 2. Gigantic (1G) huge pages can not be allocated from scratch. They can
> >>    be preserved memory and thus should not be allocated from SCRATCH.
> >>    See patch 12 that does allocations for gigantic huge pages only from
> >>    SCRATCH_EXT.
> >> 
> >> I will add this in the commit message for the next version.
> >> 
> >> Naming is hard, so if you have any better names I'm all ears :-)
> >
> > IMO, this scratch_ext is not "scratch" in the traditional KHO sense at all.
> > The traditional KHO scratch is what is passed from kernel to kernel and is
> > guaranteed to contain zero preserved memory. This new memory is not passed
> > from kernel to kernel and can contain preserved memory at runtime. It's
> > essentially just memory that we identify as currently unpreserved and release
> > early to the system.
> >
> > If we want to keep the naming aligned with the existing codebase for now:
> > MEMBLOCK_KHO_SCRATCH      -> original scratch
> > MEMBLOCK_KHO_UNPRESERVED  -> for the new memory (instead of SCRATCH_EXT)
> 
> UNPRESERVED sounds good to me. I will use that for the next revision
> unless Mike objects.
 
Can we make it shorter? ;-)

UNPRESERVED makes sense, although I'd love to completely remove KHO_ notion
and make the name reflect how it's used by memblock. I was toying with
PREFERRED instead of SCRATCH, but it didn't feel right enough.
With two of them that surely won't work :)

> > Alternatively, if we do want to tackle the global rename of "scratch" later:
> > MEMBLOCK_KHO_BOOTSTRAP    -> for the original scratch
> > MEMBLOCK_KHO_UNPRESERVED  -> for this new dynamic memory
> 
> Or perhaps BOOTMEM? I suppose either of the two are somewhat better than
> scratch.

Well, if we have BOOTMEM_HVO, we can have BOOTMEM_KHO as well :)

> Anyway, can we please do the SCRATCH rename as a separate series? I

Sure. We can continue bikeshedding in parallel.

> would like this series to not get muddled in the naming discussion. I
> will use UNPRESERVED for the new concept in v2 though.

That might warrant v3 even if everything else is perfect :)
 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


From baoquan.he at linux.dev  Sun May 31 22:00:34 2026
From: baoquan.he at linux.dev (Baoquan He)
Date: Mon, 1 Jun 2026 13:00:34 +0800
Subject: [PATCH v3 09/11] arm64: kdump: exclude non-dumpable reserved
 memory regions from vmcore
In-Reply-To: <ahsPcv8G1BR9rLah@kernel.org>
References: <20260527032917.3385849-1-chenwandun1@gmail.com>
 <20260527032917.3385849-10-chenwandun1@gmail.com>
 <ahmr-UjoApj2j5JS@willie-the-truck>
 <ahsPcv8G1BR9rLah@kernel.org>
Message-ID: <ah0R8pEIyuk4N2Is@MiWiFi-R3L-srv>

On 05/30/26 at 07:25pm, Mike Rapoport wrote:
> On Fri, May 29, 2026 at 04:08:41PM +0100, Will Deacon wrote:
> > On Wed, May 27, 2026 at 11:29:15AM +0800, Wandun Chen wrote:
...snip...
> There are patches that move common code to kernel/crash_core.c:
> 
> https://lore.kernel.org/all/20260525084932.934910-1-ruanjinjie at huawei.com
>  
> Review from arch maintainers would be helpful there ;-)

Before, Andrew would put patch candidates into his mm tree and trigger
testing. If any adjustment, he would take them off. Can we do the
similar thing for kexec/kdump patches, unless the patches are objected
explicitly?

Thanks
Baoquan


From lkp at intel.com  Sun May 31 22:25:29 2026
From: lkp at intel.com (kernel test robot)
Date: Mon, 01 Jun 2026 13:25:29 +0800
Subject: [liveupdate:next 11/21]
 kernel/liveupdate/luo_session.c:344:48: error: too few arguments provided to
 function-like macro invocation
Message-ID: <202606011344.RHiYuqso-lkp@intel.com>

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git next
head:   f4b4f5fe58a55a4212a263d9d44416778ca6e7a7
commit: 74cab0be9a5d9d91471c4dee7311dcdfc1c0a6f4 [11/21] liveupdate: validate session type before performing operation
config: x86_64-buildonly-randconfig-003-20260601 (https://download.01.org/0day-ci/archive/20260601/202606011344.RHiYuqso-lkp at intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260601/202606011344.RHiYuqso-lkp at 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 at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606011344.RHiYuqso-lkp at intel.com/

All errors (new ones prefixed by >>):

>> kernel/liveupdate/luo_session.c:344:48: error: too few arguments provided to function-like macro invocation
     344 |                  struct liveupdate_session_retrieve_fd, token),
         |                                                              ^
   kernel/liveupdate/luo_session.c:327:9: note: macro 'IOCTL_OP' defined here
     327 | #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
         |         ^
>> kernel/liveupdate/luo_session.c:343:2: error: use of undeclared identifier 'IOCTL_OP'
     343 |         IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
         |         ^
>> kernel/liveupdate/luo_session.c:378:6: error: invalid application of 'sizeof' to an incomplete type 'const struct luo_ioctl_op[]'
     378 |             ARRAY_SIZE(luo_session_ioctl_ops)) {
         |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/array_size.h:11:32: note: expanded from macro 'ARRAY_SIZE'
      11 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
         |                                ^~~~~
   3 errors generated.


vim +344 kernel/liveupdate/luo_session.c

16cec0d265219f Pasha Tatashin          2025-11-25  326  
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  327) #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
16cec0d265219f Pasha Tatashin          2025-11-25  328  	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
16cec0d265219f Pasha Tatashin          2025-11-25  329  		.size = sizeof(_struct) +                                      \
16cec0d265219f Pasha Tatashin          2025-11-25  330  			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
16cec0d265219f Pasha Tatashin          2025-11-25  331  					  sizeof(_struct)),                    \
16cec0d265219f Pasha Tatashin          2025-11-25  332  		.min_size = offsetofend(_struct, _last),                       \
16cec0d265219f Pasha Tatashin          2025-11-25  333  		.ioctl_num = _ioctl,                                           \
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  334) 		.type = _type,                                                 \
16cec0d265219f Pasha Tatashin          2025-11-25  335  		.execute = _fn,                                                \
16cec0d265219f Pasha Tatashin          2025-11-25  336  	}
16cec0d265219f Pasha Tatashin          2025-11-25  337  
16cec0d265219f Pasha Tatashin          2025-11-25  338  static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
16cec0d265219f Pasha Tatashin          2025-11-25  339  	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  340) 		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
16cec0d265219f Pasha Tatashin          2025-11-25  341  	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  342) 		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
16cec0d265219f Pasha Tatashin          2025-11-25 @343  	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
16cec0d265219f Pasha Tatashin          2025-11-25 @344  		 struct liveupdate_session_retrieve_fd, token),
4c08a9f38c50c0 Luca Boccassi           2026-04-29  345  	IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  346) 		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
16cec0d265219f Pasha Tatashin          2025-11-25  347  };
16cec0d265219f Pasha Tatashin          2025-11-25  348  
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  349) static bool luo_ioctl_type_valid(struct luo_session *session,
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  350) 				 const struct luo_ioctl_op *op)
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  351) {
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  352) 	switch (op->type) {
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  353) 	case LUO_IOCTL_INCOMING:
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  354) 		/* Retrieved is only set on incoming sessions */
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  355) 		return session->retrieved;
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  356) 	case LUO_IOCTL_OUTGOING:
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  357) 		return !session->retrieved;
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  358) 	case LUO_IOCTL_ALL:
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  359) 		return true;
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  360) 	}
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  361) 
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  362) 	/* Catch-all. */
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  363) 	return false;
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  364) }
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  365) 
16cec0d265219f Pasha Tatashin          2025-11-25  366  static long luo_session_ioctl(struct file *filep, unsigned int cmd,
16cec0d265219f Pasha Tatashin          2025-11-25  367  			      unsigned long arg)
16cec0d265219f Pasha Tatashin          2025-11-25  368  {
16cec0d265219f Pasha Tatashin          2025-11-25  369  	struct luo_session *session = filep->private_data;
16cec0d265219f Pasha Tatashin          2025-11-25  370  	const struct luo_ioctl_op *op;
16cec0d265219f Pasha Tatashin          2025-11-25  371  	struct luo_ucmd ucmd = {};
16cec0d265219f Pasha Tatashin          2025-11-25  372  	union ucmd_buffer buf;
16cec0d265219f Pasha Tatashin          2025-11-25  373  	unsigned int nr;
16cec0d265219f Pasha Tatashin          2025-11-25  374  	int ret;
16cec0d265219f Pasha Tatashin          2025-11-25  375  
16cec0d265219f Pasha Tatashin          2025-11-25  376  	nr = _IOC_NR(cmd);
16cec0d265219f Pasha Tatashin          2025-11-25  377  	if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=
16cec0d265219f Pasha Tatashin          2025-11-25 @378  	    ARRAY_SIZE(luo_session_ioctl_ops)) {
16cec0d265219f Pasha Tatashin          2025-11-25  379  		return -EINVAL;
16cec0d265219f Pasha Tatashin          2025-11-25  380  	}
16cec0d265219f Pasha Tatashin          2025-11-25  381  
16cec0d265219f Pasha Tatashin          2025-11-25  382  	ucmd.ubuffer = (void __user *)arg;
16cec0d265219f Pasha Tatashin          2025-11-25  383  	ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
16cec0d265219f Pasha Tatashin          2025-11-25  384  	if (ret)
16cec0d265219f Pasha Tatashin          2025-11-25  385  		return ret;
16cec0d265219f Pasha Tatashin          2025-11-25  386  
16cec0d265219f Pasha Tatashin          2025-11-25  387  	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
16cec0d265219f Pasha Tatashin          2025-11-25  388  	if (op->ioctl_num != cmd)
16cec0d265219f Pasha Tatashin          2025-11-25  389  		return -ENOIOCTLCMD;
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  390) 	if (!luo_ioctl_type_valid(session, op))
74cab0be9a5d9d Pratyush Yadav (Google  2026-05-19  391) 		return -EINVAL;
16cec0d265219f Pasha Tatashin          2025-11-25  392  	if (ucmd.user_size < op->min_size)
16cec0d265219f Pasha Tatashin          2025-11-25  393  		return -EINVAL;
16cec0d265219f Pasha Tatashin          2025-11-25  394  
16cec0d265219f Pasha Tatashin          2025-11-25  395  	ucmd.cmd = &buf;
16cec0d265219f Pasha Tatashin          2025-11-25  396  	ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
16cec0d265219f Pasha Tatashin          2025-11-25  397  				    ucmd.user_size);
16cec0d265219f Pasha Tatashin          2025-11-25  398  	if (ret)
16cec0d265219f Pasha Tatashin          2025-11-25  399  		return ret;
16cec0d265219f Pasha Tatashin          2025-11-25  400  
16cec0d265219f Pasha Tatashin          2025-11-25  401  	return op->execute(session, &ucmd);
16cec0d265219f Pasha Tatashin          2025-11-25  402  }
16cec0d265219f Pasha Tatashin          2025-11-25  403  

:::::: The code at line 344 was first introduced by commit
:::::: 16cec0d265219f14a7fcebcc43aeb69205adba56 liveupdate: luo_session: add ioctls for file preservation

:::::: TO: Pasha Tatashin <pasha.tatashin at soleen.com>
:::::: CC: Andrew Morton <akpm at linux-foundation.org>

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


From chenhuacai at kernel.org  Mon May 25 03:14:52 2026
From: chenhuacai at kernel.org (Huacai Chen)
Date: Mon, 25 May 2026 18:14:52 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA reservation
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>

Hi, Jinjie,

On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
>
> The crash memory allocation, and the exclude of crashk_res, crashk_low_res
> and crashk_cma memory are almost identical across different architectures,
> This patch set handle them in crash core in a general way, which eliminate
> a lot of duplication code.
>
> And add support for crashkernel CMA reservation for arm64 and riscv.
>
> Also add support for arm64 crash hotplug.
>
> This patch set is rebased on v7.1-rc1.
>
> Basic second kernel boot test were performed on QEMU platforms for x86,
> ARM64 and RISC-V architectures with the following parameters:
>
>         "cma=256M crashkernel=4G crashkernel=64M,cma"
>
> For first kernel, there will be such log:
>
>         # dmesg | grep crash
>         [    0.000000] crashkernel low memory reserved: 0xe8000000 - 0xf0000000 (128 MB)
>         [    0.000000] crashkernel reserved: 0x000000023e600000 - 0x000000033e600000 (4096 MB)
>         [    0.000000] crashkernel CMA reserved: 64 MB in 1 ranges
>
>         # dmesg | grep cma
>         [    0.000000] cma: Reserved 256 MiB at 0x00000000f0000000
>         [    0.000000] cma: Reserved 64 MiB at 0x0000000100000000
>
> For second kernel, there will be such log:
>
>         [    0.000000] OF: fdt: Looking for usable-memory-range property...
>         [    0.000000] OF: fdt: cap_mem_regions[0]: base=0x000000023e600000, size=0x0000000100000000
>         [    0.000000] OF: fdt: cap_mem_regions[1]: base=0x00000000e8000000, size=0x0000000008000000
>         [    0.000000] OF: fdt: cap_mem_regions[2]: base=0x0000000100000000, size=0x0000000004000000
>
> Changes in v14:
> - Fix image->elf_headers memory leak during retry loop for arm64 as Sashiko
>   AI code review pointed out.
> - Solve the hotplug notifier arch_crash_handle_hotplug_event() AA
>   self-deadlock problem as Sashiko AI code review pointed out.
> - Fix the TOCTOU issue in prepare_elf_headers() by get_online_mems().
> - -ENOMEM -> -EAGAIN as Breno suggested.
> - Add support for arm64 crash hotplug.
> - Link to v13: https://lore.kernel.org/all/20260511030454.1730881-1-ruanjinjie at huawei.com/
>
> Changes in v13:
> - Rebased on v7.1-rc1.
> - Update the commit message.
> - Add Reviewed-by.
> - Link to v12: https://lore.kernel.org/all/20260402072701.628293-1-ruanjinjie at huawei.com/
>
> Changes in v12:
> - Remove the unused "nr_mem_ranges" for x86.
> - Add "Fix crashk_low_res not exclude bug" test log.
> - Provide a separate patch for each architecture for using
>   crash_prepare_headers(), which will make the review more convenient.
> - Add Reviewed-by and Tested-by.
> - Link to v11: https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie at huawei.com/
>
> Changes in v11:
> - Avoid silently drop crash memory if the crash kernel is built without
>   CONFIG_CMA.
> - Remove unnecessary "cmem->nr_ranges = 0" for arch_crash_populate_cmem()
>   as we use kvzalloc().
> - Provide a separate patch for each architecture to fix the existing
>   buffer overflow issue.
> - Add Acked-bys for arm64.
>
> Changes in v10:
> - Fix crashk_low_res not excluded bug in the existing
>   RISC-V code.
> - Fix an existing memory leak issue in the existing PowerPC code.
> - Fix the ordering issue of adding CMA ranges to
>   "linux,usable-memory-range".
> - Fix an existing concurrency issue. A Concurrent memory hotplug may occur
>   between reading memblock and attempting to fill cmem during kexec_load()
>   for almost all existing architectures.
> - Link to v9: https://lore.kernel.org/all/20260323072745.2481719-1-ruanjinjie at huawei.com/
>
> Changes in v9:
> - Collect Reviewed-by and Acked-by, and prepare for Sashiko AI review.
> - Link to v8: https://lore.kernel.org/all/20260302035315.3892241-1-ruanjinjie at huawei.com/
>
> Changes in v8:
> - Fix the build issues reported by kernel test robot and Sourabh.
> - Link to v7: https://lore.kernel.org/all/20260226130437.1867658-1-ruanjinjie at huawei.com/
>
> Changes in v7:
> - Correct the inclusion of CMA-reserved ranges for kdump kernel in of/kexec
>   for arm64 and riscv.
> - Add Acked-by.
> - Link to v6: https://lore.kernel.org/all/20260224085342.387996-1-ruanjinjie at huawei.com/
>
> Changes in v6:
> - Update the crash core exclude code as Mike suggested.
> - Rebased on v7.0-rc1.
> - Add acked-by.
> - Link to v5: https://lore.kernel.org/all/20260212101001.343158-1-ruanjinjie at huawei.com/
>
> Jinjie Ruan (16):
>   riscv: kexec_file: Fix crashk_low_res not exclude bug
>   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
>   arm64: kexec: Fix image->elf_headers memory leak during retry loop
>   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
>   arm64: kexec_file: Fix potential buffer overflow in
>     prepare_elf_headers()
>   riscv: kexec_file: Fix potential buffer overflow in
>     prepare_elf_headers()
>   LoongArch: kexec: Fix potential buffer overflow in
>     prepare_elf_headers()
>   crash: Add crash_prepare_headers() to exclude crash kernel memory
>   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
>   x86/kexec: Use crash_prepare_headers() helper to simplify code
>   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
>   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
>   crash: Use crash_exclude_core_ranges() on powerpc
>   arm64: kexec: Add support for crashkernel CMA reservation
>   riscv: kexec: Add support for crashkernel CMA reservation
>   arm64/crash: Add crash hotplug support
I have some bikesheedings about the subjects. Can we unify the prefix formats?
x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....

Huacai

>
> Sourabh Jain (1):
>   powerpc/crash: sort crash memory ranges before preparing elfcorehdr
>
>  .../admin-guide/kernel-parameters.txt         |  16 +--
>  arch/arm64/Kconfig                            |   3 +
>  arch/arm64/include/asm/kexec.h                |  14 +-
>  arch/arm64/kernel/Makefile                    |   1 +
>  arch/arm64/kernel/crash.c                     | 125 ++++++++++++++++++
>  arch/arm64/kernel/kexec_image.c               |  22 ++-
>  arch/arm64/kernel/machine_kexec_file.c        |  78 ++++++-----
>  arch/arm64/mm/init.c                          |   5 +-
>  arch/loongarch/kernel/machine_kexec_file.c    |  43 +++---
>  arch/powerpc/include/asm/kexec_ranges.h       |   1 -
>  arch/powerpc/kexec/crash.c                    |   7 +-
>  arch/powerpc/kexec/ranges.c                   | 101 +-------------
>  arch/riscv/kernel/machine_kexec_file.c        |  42 +++---
>  arch/riscv/mm/init.c                          |   5 +-
>  arch/x86/kernel/crash.c                       |  92 ++-----------
>  drivers/of/fdt.c                              |   9 +-
>  drivers/of/kexec.c                            |   9 ++
>  include/linux/crash_core.h                    |  11 ++
>  include/linux/crash_reserve.h                 |   4 +-
>  kernel/crash_core.c                           | 102 +++++++++++++-
>  20 files changed, 392 insertions(+), 298 deletions(-)
>  create mode 100644 arch/arm64/kernel/crash.c
>
> --
> 2.34.1
>


From rppt at kernel.org  Mon May 25 04:36:57 2026
From: rppt at kernel.org (Mike Rapoport)
Date: Mon, 25 May 2026 14:36:57 +0300
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA
 reservation
In-Reply-To: <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
 <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
Message-ID: <ahQ0WRiMsB011tym@kernel.org>

Hi Huacai,

On Mon, May 25, 2026 at 06:14:52PM +0800, Huacai Chen wrote:
> On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
> >
> > Jinjie Ruan (16):
> >   riscv: kexec_file: Fix crashk_low_res not exclude bug
> >   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
> >   arm64: kexec: Fix image->elf_headers memory leak during retry loop
> >   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
> >   arm64: kexec_file: Fix potential buffer overflow in
> >     prepare_elf_headers()
> >   riscv: kexec_file: Fix potential buffer overflow in
> >     prepare_elf_headers()
> >   LoongArch: kexec: Fix potential buffer overflow in
> >     prepare_elf_headers()
> >   crash: Add crash_prepare_headers() to exclude crash kernel memory
> >   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
> >   x86/kexec: Use crash_prepare_headers() helper to simplify code
> >   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
> >   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
> >   crash: Use crash_exclude_core_ranges() on powerpc
> >   arm64: kexec: Add support for crashkernel CMA reservation
> >   riscv: kexec: Add support for crashkernel CMA reservation
> >   arm64/crash: Add crash hotplug support
> I have some bikesheedings about the subjects. Can we unify the prefix formats?
> x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....

It might look weird in the series, but powerpc and x86 use '/' and arm64 and
riscv use ':' so in the logs in arch/ this would be actually fine.

With the only exception of arm64/crash that should have been arm64: crash:
 
> Huacai

-- 
Sincerely yours,
Mike.


From chenhuacai at kernel.org  Mon May 25 06:17:21 2026
From: chenhuacai at kernel.org (Huacai Chen)
Date: Mon, 25 May 2026 21:17:21 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA reservation
In-Reply-To: <ahQ0WRiMsB011tym@kernel.org>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
 <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com> <ahQ0WRiMsB011tym@kernel.org>
Message-ID: <CAAhV-H5x3m08=acrUr2VOAQ8-r2PhN1xTdr3NihHocoPM5=3hQ@mail.gmail.com>

On Mon, May 25, 2026 at 7:37?PM Mike Rapoport <rppt at kernel.org> wrote:
>
> Hi Huacai,
>
> On Mon, May 25, 2026 at 06:14:52PM +0800, Huacai Chen wrote:
> > On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
> > >
> > > Jinjie Ruan (16):
> > >   riscv: kexec_file: Fix crashk_low_res not exclude bug
> > >   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
> > >   arm64: kexec: Fix image->elf_headers memory leak during retry loop
> > >   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
> > >   arm64: kexec_file: Fix potential buffer overflow in
> > >     prepare_elf_headers()
> > >   riscv: kexec_file: Fix potential buffer overflow in
> > >     prepare_elf_headers()
> > >   LoongArch: kexec: Fix potential buffer overflow in
> > >     prepare_elf_headers()
> > >   crash: Add crash_prepare_headers() to exclude crash kernel memory
> > >   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
> > >   x86/kexec: Use crash_prepare_headers() helper to simplify code
> > >   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
> > >   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
> > >   crash: Use crash_exclude_core_ranges() on powerpc
> > >   arm64: kexec: Add support for crashkernel CMA reservation
> > >   riscv: kexec: Add support for crashkernel CMA reservation
> > >   arm64/crash: Add crash hotplug support
> > I have some bikesheedings about the subjects. Can we unify the prefix formats?
> > x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....
>
> It might look weird in the series, but powerpc and x86 use '/' and arm64 and
> riscv use ':' so in the logs in arch/ this would be actually fine.
OK, but at least we can unify kexec and kexec_file.

Huacai

>
> With the only exception of arm64/crash that should have been arm64: crash:
>
> > Huacai
>
> --
> Sincerely yours,
> Mike.


From yifeichu24 at gmail.com  Sun May 24 10:31:39 2026
From: yifeichu24 at gmail.com (Yifei Chu)
Date: Mon, 25 May 2026 01:31:39 +0800
Subject: [BUG] liveupdate: incoming/outgoing session ioctls accepted in wrong phase can panic
Message-ID: <177964389908.73725.3374791433218646192@gmail.com>

Hello,

Resending this to kexec at lists.infradead.org in plain text only. The
previous copy to this list was rejected because it was sent as HTML mail.
The original report was also sent to the liveupdate maintainers,
linux-kernel, linux-mm, and the related reviewers.

Short version: I found that liveupdate session ioctls can be accepted on
the wrong kind of session. In my tests, that lets userspace drive memfd LUO
into KHO restore paths for current-kernel preserved memory, which hits
kho_restore_page() warnings and panics with panic_on_warn=1.

There are two closely related cases.

First, a newly created outgoing session accepts incoming-only operations:

  LIVEUPDATE_SESSION_FINISH
  LIVEUPDATE_SESSION_RETRIEVE_FD

After preserving a memfd, either ioctl dispatches into the incoming restore
path and reaches kho_restore_page().

Second, a retrieved incoming session accepts the outgoing-only operation:

  LIVEUPDATE_SESSION_PRESERVE_FD

After a real KHO/kexec handoff, the PoC retrieves the incoming session,
preserves a new current-kernel memfd on it, and then calls FINISH. That
again treats current-kernel state as incoming KHO state.

Tested environment:

  Linux version 7.0.9, x86_64 QEMU
  gcc 12.3.0, GNU ld 2.38
  CONFIG_LIVEUPDATE=y
  CONFIG_LIVEUPDATE_MEMFD=y
  CONFIG_KEXEC_HANDOVER=y
  Boot args included: kho=on liveupdate=on panic_on_warn=1

Outgoing-session FINISH result:

  session fd=4
  preserved memfd token=0x6f757466696e6973
  calling FINISH on outgoing session; this should be rejected
  WARNING: kernel/liveupdate/kexec_handover.c:256 at kho_restore_page+0x1e9/0x4a0
  memfd_luo_finish+0x12c/0x370
  luo_session_finish+0x35/0x130
  Kernel panic - not syncing: kernel: panic_on_warn set ...

Outgoing-session RETRIEVE_FD result:

  calling RETRIEVE_FD on outgoing session; this should be rejected
  WARNING: kernel/liveupdate/kexec_handover.c:256 at kho_restore_page+0x1e9/0x4a0
  memfd_luo_retrieve+0x224/0x9f0
  luo_session_retrieve_fd+0x124/0x320
  Kernel panic - not syncing: kernel: panic_on_warn set ...

Incoming-session PRESERVE_FD result:

  [stage2] retrieved incoming session fd=4
  [stage2] preserving new fd on incoming session; this should be rejected
  [stage2] late preserve succeeded token=0x696e636f6d653032
  [stage2] finishing session should hit KHO restore WARN
  WARNING: kernel/liveupdate/kexec_handover.c:256 at kho_restore_page+0x11e/0x280
  memfd_luo_finish+0x6d/0x190
  luo_session_finish+0x31/0xa0
  Kernel panic - not syncing: kernel: panic_on_warn set ...

My read is that the ioctl paths need to enforce session phase/origin
consistently. A minimal direction would be:

  PRESERVE_FD: require !session->retrieved
  RETRIEVE_FD: require session->retrieved
  FINISH: require session->retrieved

An explicit immutable incoming/outgoing origin bit might be clearer if the
state machine is expected to grow.

The full report artifacts are available and were included with the original
mail sent to the other recipients:

  liveupdate_phase_origin_warn_panic.tar.gz

It contains two PoC directories, full QEMU serial logs, kernel configs, and
README files. I can resend the artifacts in plain-text-friendly form if that
is preferred.

I reproduced the panics on the 7.0.9 QEMU build above. I also checked the
current liveupdate userspace API documentation and current mainline source for
related semantics/source shape, but I have not yet runtime-tested current
mainline.

Thanks,
Chuyifei


From yifeichu24 at gmail.com  Sun May 24 10:31:39 2026
From: yifeichu24 at gmail.com (Yifei Chu)
Date: Mon, 25 May 2026 01:31:39 +0800
Subject: [BUG] liveupdate/memfd: duplicate memfd preserve restores same KHO folio twice
Message-ID: <177964389908.73725.16571980458640176052@gmail.com>

Hello,

Resending this to kexec at lists.infradead.org in plain text only. The
previous copy to this list was rejected because it was sent as HTML mail.
The original report was also sent to the liveupdate maintainers,
linux-kernel, linux-mm, and the related reviewers.

Short version: I found that the same memfd can be preserved more than once
in a single liveupdate session under different tokens. After kexec, those
tokens can both refer to the same preserved KHO folio, and the second
restore path hits kho_restore_page(). With panic_on_warn=1, this is a
reproducible panic.

The core issue seems to be that mm/memfd_luo.c records the same underlying
folio PFNs for each preserved token, while KHO page preservation is one-shot
state rather than reference-counted ownership.

I reproduced two variants.

Variant A: duplicate preserve, retrieve both tokens

  1. Create one liveupdate session.
  2. Create one 4 KiB memfd and write a marker.
  3. Call LIVEUPDATE_SESSION_PRESERVE_FD twice for the same memfd with two
     distinct tokens.
  4. Keep the session fd alive across kexec.
  5. In the second kernel, retrieve token A successfully.
  6. Retrieve token B, which attempts to restore the same KHO folio again.

Observed result:

  [stage2] token A marker='duplicate-preserve-marker'
  [stage2] retrieving token B should hit duplicate KHO folio restore
  WARNING: kernel/liveupdate/kexec_handover.c:256 at kho_restore_page+0x11e/0x280
  memfd_luo_retrieve+0x1aa/0x490
  luo_session_retrieve_fd+0x73/0x130
  Kernel panic - not syncing: kernel: panic_on_warn set ...

Variant B: duplicate preserve, FINISH an unretrieved session

  1. Preserve the same memfd twice under different tokens.
  2. Kexec into the second kernel.
  3. Retrieve the session but do not retrieve individual fds.
  4. Call LIVEUPDATE_SESSION_FINISH.

Observed result:

  [stage2] finishing session should hit duplicate KHO restore
  WARNING: kernel/liveupdate/kexec_handover.c:256 at kho_restore_page+0x11e/0x280
  memfd_luo_finish+0x10f/0x190
  luo_session_finish+0x31/0xa0
  Kernel panic - not syncing: kernel: panic_on_warn set ...

Tested environment:

  Linux version 7.0.9, x86_64 QEMU
  gcc 12.3.0, GNU ld 2.38
  CONFIG_LIVEUPDATE=y
  CONFIG_LIVEUPDATE_MEMFD=y
  CONFIG_KEXEC_HANDOVER=y
  CONFIG_KASAN=y in the tested config
  Boot args included: kho=on liveupdate=on panic_on_warn=1

My read is that duplicate preservation of the same underlying memfd/inode
should either be rejected within a session, or KHO preserved-page ownership
would need reference-counted semantics so duplicate logical owners cannot
independently consume the same physical folio.

The full report artifacts are available and were included with the original
mail sent to the other recipients:

  liveupdate_duplicate_memfd_warn_panic.tar.gz

It contains both PoC directories, full QEMU serial logs, kernel configs, and
README files. I can resend the artifacts in plain-text-friendly form if that
is preferred.

I reproduced the panics on the 7.0.9 QEMU build above. I also checked current
mainline mm/memfd_luo.c for an obvious duplicate-preserve guard, but I have
not yet runtime-tested current mainline.

Thanks,
Chuyifei


From ruanjinjie at huawei.com  Mon May 25 01:49:15 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:15 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA reservation
Message-ID: <20260525084932.934910-1-ruanjinjie@huawei.com>

The crash memory allocation, and the exclude of crashk_res, crashk_low_res
and crashk_cma memory are almost identical across different architectures,
This patch set handle them in crash core in a general way, which eliminate
a lot of duplication code.

And add support for crashkernel CMA reservation for arm64 and riscv.

Also add support for arm64 crash hotplug.

This patch set is rebased on v7.1-rc1.

Basic second kernel boot test were performed on QEMU platforms for x86,
ARM64 and RISC-V architectures with the following parameters:

	"cma=256M crashkernel=4G crashkernel=64M,cma"

For first kernel, there will be such log:

	# dmesg | grep crash
	[    0.000000] crashkernel low memory reserved: 0xe8000000 - 0xf0000000 (128 MB)
	[    0.000000] crashkernel reserved: 0x000000023e600000 - 0x000000033e600000 (4096 MB)
	[    0.000000] crashkernel CMA reserved: 64 MB in 1 ranges

	# dmesg | grep cma
	[    0.000000] cma: Reserved 256 MiB at 0x00000000f0000000
	[    0.000000] cma: Reserved 64 MiB at 0x0000000100000000

For second kernel, there will be such log:

	[    0.000000] OF: fdt: Looking for usable-memory-range property...
	[    0.000000] OF: fdt: cap_mem_regions[0]: base=0x000000023e600000, size=0x0000000100000000
	[    0.000000] OF: fdt: cap_mem_regions[1]: base=0x00000000e8000000, size=0x0000000008000000
	[    0.000000] OF: fdt: cap_mem_regions[2]: base=0x0000000100000000, size=0x0000000004000000

Changes in v14:
- Fix image->elf_headers memory leak during retry loop for arm64 as Sashiko
  AI code review pointed out.
- Solve the hotplug notifier arch_crash_handle_hotplug_event() AA
  self-deadlock problem as Sashiko AI code review pointed out.
- Fix the TOCTOU issue in prepare_elf_headers() by get_online_mems().
- -ENOMEM -> -EAGAIN as Breno suggested.
- Add support for arm64 crash hotplug.
- Link to v13: https://lore.kernel.org/all/20260511030454.1730881-1-ruanjinjie at huawei.com/

Changes in v13:
- Rebased on v7.1-rc1.
- Update the commit message.
- Add Reviewed-by.
- Link to v12: https://lore.kernel.org/all/20260402072701.628293-1-ruanjinjie at huawei.com/

Changes in v12:
- Remove the unused "nr_mem_ranges" for x86.
- Add "Fix crashk_low_res not exclude bug" test log.
- Provide a separate patch for each architecture for using
  crash_prepare_headers(), which will make the review more convenient.
- Add Reviewed-by and Tested-by.
- Link to v11: https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie at huawei.com/

Changes in v11:
- Avoid silently drop crash memory if the crash kernel is built without
  CONFIG_CMA.
- Remove unnecessary "cmem->nr_ranges = 0" for arch_crash_populate_cmem()
  as we use kvzalloc().
- Provide a separate patch for each architecture to fix the existing
  buffer overflow issue.
- Add Acked-bys for arm64.

Changes in v10:
- Fix crashk_low_res not excluded bug in the existing
  RISC-V code.
- Fix an existing memory leak issue in the existing PowerPC code.
- Fix the ordering issue of adding CMA ranges to
  "linux,usable-memory-range".
- Fix an existing concurrency issue. A Concurrent memory hotplug may occur
  between reading memblock and attempting to fill cmem during kexec_load()
  for almost all existing architectures.
- Link to v9: https://lore.kernel.org/all/20260323072745.2481719-1-ruanjinjie at huawei.com/

Changes in v9:
- Collect Reviewed-by and Acked-by, and prepare for Sashiko AI review.
- Link to v8: https://lore.kernel.org/all/20260302035315.3892241-1-ruanjinjie at huawei.com/

Changes in v8:
- Fix the build issues reported by kernel test robot and Sourabh.
- Link to v7: https://lore.kernel.org/all/20260226130437.1867658-1-ruanjinjie at huawei.com/

Changes in v7:
- Correct the inclusion of CMA-reserved ranges for kdump kernel in of/kexec
  for arm64 and riscv.
- Add Acked-by.
- Link to v6: https://lore.kernel.org/all/20260224085342.387996-1-ruanjinjie at huawei.com/

Changes in v6:
- Update the crash core exclude code as Mike suggested.
- Rebased on v7.0-rc1.
- Add acked-by.
- Link to v5: https://lore.kernel.org/all/20260212101001.343158-1-ruanjinjie at huawei.com/

Jinjie Ruan (16):
  riscv: kexec_file: Fix crashk_low_res not exclude bug
  powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
  arm64: kexec: Fix image->elf_headers memory leak during retry loop
  x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
  arm64: kexec_file: Fix potential buffer overflow in
    prepare_elf_headers()
  riscv: kexec_file: Fix potential buffer overflow in
    prepare_elf_headers()
  LoongArch: kexec: Fix potential buffer overflow in
    prepare_elf_headers()
  crash: Add crash_prepare_headers() to exclude crash kernel memory
  arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
  x86/kexec: Use crash_prepare_headers() helper to simplify code
  riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
  LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
  crash: Use crash_exclude_core_ranges() on powerpc
  arm64: kexec: Add support for crashkernel CMA reservation
  riscv: kexec: Add support for crashkernel CMA reservation
  arm64/crash: Add crash hotplug support

Sourabh Jain (1):
  powerpc/crash: sort crash memory ranges before preparing elfcorehdr

 .../admin-guide/kernel-parameters.txt         |  16 +--
 arch/arm64/Kconfig                            |   3 +
 arch/arm64/include/asm/kexec.h                |  14 +-
 arch/arm64/kernel/Makefile                    |   1 +
 arch/arm64/kernel/crash.c                     | 125 ++++++++++++++++++
 arch/arm64/kernel/kexec_image.c               |  22 ++-
 arch/arm64/kernel/machine_kexec_file.c        |  78 ++++++-----
 arch/arm64/mm/init.c                          |   5 +-
 arch/loongarch/kernel/machine_kexec_file.c    |  43 +++---
 arch/powerpc/include/asm/kexec_ranges.h       |   1 -
 arch/powerpc/kexec/crash.c                    |   7 +-
 arch/powerpc/kexec/ranges.c                   | 101 +-------------
 arch/riscv/kernel/machine_kexec_file.c        |  42 +++---
 arch/riscv/mm/init.c                          |   5 +-
 arch/x86/kernel/crash.c                       |  92 ++-----------
 drivers/of/fdt.c                              |   9 +-
 drivers/of/kexec.c                            |   9 ++
 include/linux/crash_core.h                    |  11 ++
 include/linux/crash_reserve.h                 |   4 +-
 kernel/crash_core.c                           | 102 +++++++++++++-
 20 files changed, 392 insertions(+), 298 deletions(-)
 create mode 100644 arch/arm64/kernel/crash.c

-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:17 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:17 +0800
Subject: [PATCH v14 02/17] powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-3-ruanjinjie@huawei.com>

In get_crash_memory_ranges(), if crash_exclude_mem_range() failed
after realloc_mem_ranges() has successfully allocated the cmem
memory, it just returns an error but leaves cmem pointing to
the allocated memory, nor is it freed in the caller
update_crash_elfcorehdr(), which cause a memory leak, goto out
to free the cmem.

Cc: Sourabh Jain <sourabhjain at linux.ibm.com>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Fixes: 849599b702ef ("powerpc/crash: add crash memory hotplug support")
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/kexec/crash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index e6539f213b3d..a520f851c3a6 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -502,7 +502,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	ret = get_crash_memory_ranges(&cmem);
 	if (ret) {
 		pr_err("Failed to get crash mem range\n");
-		return;
+		goto out;
 	}
 
 	/*
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:16 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:16 +0800
Subject: [PATCH v14 01/17] riscv: kexec_file: Fix crashk_low_res not exclude bug
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-2-ruanjinjie@huawei.com>

As done in commit 944a45abfabc ("arm64: kdump: Reimplement crashkernel=X")
and commit 4831be702b95 ("arm64/kexec: Fix missing extra range for
crashkres_low.") for arm64, while implementing crashkernel=X,[high,low],
riscv should have excluded the "crashk_low_res" reserved ranges from
the crash kernel memory to prevent them from being exported through
/proc/vmcore, and the exclusion would need an extra crash_mem range.

Just simply tested on qemu with crashkernel=4G with kexec in [1] mentioned
in [2]. And the second kernel can be started normally.

	# dmesg | grep crash
	[    0.000000] crashkernel low memory reserved: 0xf8000000 - 0x100000000 (128 MB)
	[    0.000000] crashkernel reserved: 0x000000017fe00000 - 0x000000027fe00000 (4096 MB)

Cc: Guo Ren <guoren at kernel.org>
Cc: Baoquan He <bhe at redhat.com>
[1]: https://github.com/chenjh005/kexec-tools/tree/build-test-riscv-v2
[2]: https://lore.kernel.org/all/20230726175000.2536220-1-chenjiahao16 at huawei.com/
Fixes: 5882e5acf18d ("riscv: kdump: Implement crashkernel=X,[high,low]")
Reviewed-by: Guo Ren <guoren at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 54e2d9552e93..3f7766057cac 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -61,7 +61,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	unsigned int nr_ranges;
 	int ret;
 
-	nr_ranges = 1; /* For exclusion of crashkernel region */
+	nr_ranges = 2; /* For exclusion of crashkernel region */
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
 
 	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
@@ -76,8 +76,16 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 
 	/* Exclude crashkernel region */
 	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (!ret)
-		ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
+	if (ret)
+		goto out;
+
+	if (crashk_low_res.end) {
+		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		if (ret)
+			goto out;
+	}
+
+	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
 
 out:
 	kfree(cmem);
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:18 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:18 +0800
Subject: [PATCH v14 03/17] powerpc/crash: sort crash memory ranges before preparing elfcorehdr
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-4-ruanjinjie@huawei.com>

From: Sourabh Jain <sourabhjain at linux.ibm.com>

During a memory hot-remove event, the elfcorehdr is rebuilt to exclude
the removed memory. While updating the crash memory ranges for this
operation, the crash memory ranges array can become unsorted. This
happens because remove_mem_range() may split a memory range into two
parts and append the higher-address part as a separate range at the end
of the array.

So far, no issues have been observed due to the unsorted crash memory
ranges. However, this could lead to problems once crash memory range
removal is handled by generic code, as introduced in the upcoming
patches in this series.

Currently, powerpc uses a platform-specific function,
remove_mem_range(), to exclude hot-removed memory from the crash memory
ranges. This function performs the same task as the generic
crash_exclude_mem_range() in crash_core.c. The generic helper also
ensures that the crash memory ranges remain sorted. So remove the
redundant powerpc-specific implementation and instead call
crash_exclude_mem_range_guarded() (which internally calls
crash_exclude_mem_range()) to exclude the hot-removed memory ranges.

Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan he <bhe at redhat.com>
Cc: Jinjie Ruan <ruanjinjie at huawei.com>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Madhavan Srinivasan <maddy at linux.ibm.com>
Cc: Mahesh Salgaonkar <mahesh at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Cc: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Cc: Shivang Upadhyay <shivangu at linux.ibm.com>
Cc: linux-kernel at vger.kernel.org
Acked-by: Baoquan He <bhe at redhat.com>
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/include/asm/kexec_ranges.h |  4 +-
 arch/powerpc/kexec/crash.c              |  5 +-
 arch/powerpc/kexec/ranges.c             | 87 +------------------------
 3 files changed, 7 insertions(+), 89 deletions(-)

diff --git a/arch/powerpc/include/asm/kexec_ranges.h b/arch/powerpc/include/asm/kexec_ranges.h
index 14055896cbcb..ad95e3792d10 100644
--- a/arch/powerpc/include/asm/kexec_ranges.h
+++ b/arch/powerpc/include/asm/kexec_ranges.h
@@ -7,7 +7,9 @@
 void sort_memory_ranges(struct crash_mem *mrngs, bool merge);
 struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges);
 int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
-int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
+int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
+				    unsigned long long mstart,
+				    unsigned long long mend);
 int get_exclude_memory_ranges(struct crash_mem **mem_ranges);
 int get_reserved_memory_ranges(struct crash_mem **mem_ranges);
 int get_crash_memory_ranges(struct crash_mem **mem_ranges);
diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index a520f851c3a6..d634db67becc 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -493,7 +493,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	struct crash_mem *cmem = NULL;
 	struct kexec_segment *ksegment;
 	void *ptr, *mem, *elfbuf = NULL;
-	unsigned long elfsz, memsz, base_addr, size;
+	unsigned long elfsz, memsz, base_addr, size, end;
 
 	ksegment = &image->segment[image->elfcorehdr_index];
 	mem = (void *) ksegment->mem;
@@ -512,7 +512,8 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 	if (image->hp_action == KEXEC_CRASH_HP_REMOVE_MEMORY) {
 		base_addr = PFN_PHYS(mn->start_pfn);
 		size = mn->nr_pages * PAGE_SIZE;
-		ret = remove_mem_range(&cmem, base_addr, size);
+		end = base_addr + size - 1;
+		ret = crash_exclude_mem_range_guarded(&cmem, base_addr, end);
 		if (ret) {
 			pr_err("Failed to remove hot-unplugged memory from crash memory ranges\n");
 			goto out;
diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c
index 867135560e5c..6c58bcc3e130 100644
--- a/arch/powerpc/kexec/ranges.c
+++ b/arch/powerpc/kexec/ranges.c
@@ -553,7 +553,7 @@ int get_usable_memory_ranges(struct crash_mem **mem_ranges)
 #endif /* CONFIG_KEXEC_FILE */
 
 #ifdef CONFIG_CRASH_DUMP
-static int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
+int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
 					   unsigned long long mstart,
 					   unsigned long long mend)
 {
@@ -641,89 +641,4 @@ int get_crash_memory_ranges(struct crash_mem **mem_ranges)
 		pr_err("Failed to setup crash memory ranges\n");
 	return ret;
 }
-
-/**
- * remove_mem_range - Removes the given memory range from the range list.
- * @mem_ranges:    Range list to remove the memory range to.
- * @base:          Base address of the range to remove.
- * @size:          Size of the memory range to remove.
- *
- * (Re)allocates memory, if needed.
- *
- * Returns 0 on success, negative errno on error.
- */
-int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
-{
-	u64 end;
-	int ret = 0;
-	unsigned int i;
-	u64 mstart, mend;
-	struct crash_mem *mem_rngs = *mem_ranges;
-
-	if (!size)
-		return 0;
-
-	/*
-	 * Memory range are stored as start and end address, use
-	 * the same format to do remove operation.
-	 */
-	end = base + size - 1;
-
-	for (i = 0; i < mem_rngs->nr_ranges; i++) {
-		mstart = mem_rngs->ranges[i].start;
-		mend = mem_rngs->ranges[i].end;
-
-		/*
-		 * Memory range to remove is not part of this range entry
-		 * in the memory range list
-		 */
-		if (!(base >= mstart && end <= mend))
-			continue;
-
-		/*
-		 * Memory range to remove is equivalent to this entry in the
-		 * memory range list. Remove the range entry from the list.
-		 */
-		if (base == mstart && end == mend) {
-			for (; i < mem_rngs->nr_ranges - 1; i++) {
-				mem_rngs->ranges[i].start = mem_rngs->ranges[i+1].start;
-				mem_rngs->ranges[i].end = mem_rngs->ranges[i+1].end;
-			}
-			mem_rngs->nr_ranges--;
-			goto out;
-		}
-		/*
-		 * Start address of the memory range to remove and the
-		 * current memory range entry in the list is same. Just
-		 * move the start address of the current memory range
-		 * entry in the list to end + 1.
-		 */
-		else if (base == mstart) {
-			mem_rngs->ranges[i].start = end + 1;
-			goto out;
-		}
-		/*
-		 * End address of the memory range to remove and the
-		 * current memory range entry in the list is same.
-		 * Just move the end address of the current memory
-		 * range entry in the list to base - 1.
-		 */
-		else if (end == mend)  {
-			mem_rngs->ranges[i].end = base - 1;
-			goto out;
-		}
-		/*
-		 * Memory range to remove is not at the edge of current
-		 * memory range entry. Split the current memory entry into
-		 * two half.
-		 */
-		else {
-			size = mem_rngs->ranges[i].end - end + 1;
-			mem_rngs->ranges[i].end = base - 1;
-			ret = add_mem_range(mem_ranges, end + 1, size);
-		}
-	}
-out:
-	return ret;
-}
 #endif /* CONFIG_CRASH_DUMP */
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:19 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:19 +0800
Subject: [PATCH v14 04/17] arm64: kexec: Fix image->elf_headers memory leak during retry loop
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-5-ruanjinjie@huawei.com>

Sashiko AI code review pointed out a potential memory leak of
image->elf_headers when load_other_segments() fails on error paths.

In the arm64 kexec_file file-load path, kexec_image.c runs a retry loop
calling kexec_add_buffer() to find a suitable location for the kernel
segment. On each iteration, load_other_segments() is invoked to allocate
and populate alternative segments such as initrd, DTB, and ELF headers.

However, if a placement or allocation failure occurs later in
load_other_segments() (e.g., when adding initrd or dtb), the execution
jumps to the out_err label. While this path restores image->nr_segments
via orig_segments, it returns an error back to the caller without freeing
the previously allocated image->elf_headers vmalloc buffer.

As a result, the retry loop in image_load() unconditionally allocates
new ELF headers on the next iteration and overwrites image->elf_headers,
permanently leaking the memory blocks allocated in previous iterations.

To fix this, decouple the ELF header allocation from the target-seeking
retry loop. Since the contents and size of ELF headers only depend on
the host memory layout and do not change with the kernel's physical
placement, move prepare_elf_headers() completely outside and prior to
the while retry loop in image_load().

Concurrently, remove the prepare_elf_headers() call from inside
load_other_segments() and have it directly reuse the single, pre-allocated
image->elf_headers. Also, ensure that image->nr_segments is explicitly
rolled back to kernel_segment_number on retry failures to safely discard
stale segment tracking state.

This optimization eliminates redundant memory allocation/deallocation
overhead during kexec placement retries and eradicates the Use-After-Free
and memory leak risk.

Fixes: 108aa503657e ("arm64: kexec_file: try more regions if loading segments fails")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/include/asm/kexec.h         |  3 ++-
 arch/arm64/kernel/kexec_image.c        | 22 ++++++++++++++++++++--
 arch/arm64/kernel/machine_kexec_file.c | 16 +++-------------
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/arch/arm64/include/asm/kexec.h b/arch/arm64/include/asm/kexec.h
index 892e5bebda95..cc2f36b1b0d4 100644
--- a/arch/arm64/include/asm/kexec.h
+++ b/arch/arm64/include/asm/kexec.h
@@ -127,7 +127,8 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image);
 extern int load_other_segments(struct kimage *image,
 		unsigned long kernel_load_addr, unsigned long kernel_size,
 		char *initrd, unsigned long initrd_len,
-		char *cmdline);
+		char *cmdline, void *headers, unsigned long headers_size);
+extern int prepare_elf_headers(void **addr, unsigned long *sz);
 #endif
 
 #endif /* __ASSEMBLER__ */
diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c
index b70f4df15a1a..79efeaeb71e9 100644
--- a/arch/arm64/kernel/kexec_image.c
+++ b/arch/arm64/kernel/kexec_image.c
@@ -44,6 +44,11 @@ static void *image_load(struct kimage *image,
 	struct kexec_buf kbuf = {};
 	unsigned long text_offset, kernel_segment_number;
 	struct kexec_segment *kernel_segment;
+#ifdef CONFIG_CRASH_DUMP
+	/* load elf core header */
+	unsigned long headers_sz;
+	void *headers;
+#endif
 	int ret;
 
 	/*
@@ -89,6 +94,18 @@ static void *image_load(struct kimage *image,
 
 	kernel_segment_number = image->nr_segments;
 
+#ifdef CONFIG_CRASH_DUMP
+	if (image->type == KEXEC_TYPE_CRASH) {
+		ret = prepare_elf_headers(&headers, &headers_sz);
+		if (ret) {
+			pr_err("Preparing elf core header failed\n");
+			return ERR_PTR(ret);
+		}
+		image->elf_headers = headers;
+		image->elf_headers_sz = headers_sz;
+	}
+#endif
+
 	/*
 	 * The location of the kernel segment may make it impossible to satisfy
 	 * the other segment requirements, so we try repeatedly to find a
@@ -99,7 +116,8 @@ static void *image_load(struct kimage *image,
 		kernel_segment = &image->segment[kernel_segment_number];
 		ret = load_other_segments(image, kernel_segment->mem,
 					  kernel_segment->memsz, initrd,
-					  initrd_len, cmdline);
+					  initrd_len, cmdline,
+					  headers, headers_sz);
 		if (!ret)
 			break;
 
@@ -107,7 +125,7 @@ static void *image_load(struct kimage *image,
 		 * We couldn't find space for the other segments; erase the
 		 * kernel segment and try the next available hole.
 		 */
-		image->nr_segments -= 1;
+		image->nr_segments = kernel_segment_number;
 		kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
 	}
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..daf81a873bbd 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -40,7 +40,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 }
 
 #ifdef CONFIG_CRASH_DUMP
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+int prepare_elf_headers(void **addr, unsigned long *sz)
 {
 	struct crash_mem *cmem;
 	unsigned int nr_ranges;
@@ -92,7 +92,8 @@ int load_other_segments(struct kimage *image,
 			unsigned long kernel_load_addr,
 			unsigned long kernel_size,
 			char *initrd, unsigned long initrd_len,
-			char *cmdline)
+			char *cmdline, void *headers,
+			unsigned long headers_sz)
 {
 	struct kexec_buf kbuf = {};
 	void *dtb = NULL;
@@ -105,16 +106,7 @@ int load_other_segments(struct kimage *image,
 	kbuf.buf_min = kernel_load_addr + kernel_size;
 
 #ifdef CONFIG_CRASH_DUMP
-	/* load elf core header */
-	void *headers;
-	unsigned long headers_sz;
 	if (image->type == KEXEC_TYPE_CRASH) {
-		ret = prepare_elf_headers(&headers, &headers_sz);
-		if (ret) {
-			pr_err("Preparing elf core header failed\n");
-			goto out_err;
-		}
-
 		kbuf.buffer = headers;
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
@@ -128,9 +120,7 @@ int load_other_segments(struct kimage *image,
 			vfree(headers);
 			goto out_err;
 		}
-		image->elf_headers = headers;
 		image->elf_load_addr = kbuf.mem;
-		image->elf_headers_sz = headers_sz;
 
 		kexec_dprintk("Loaded elf core header at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
 			      image->elf_load_addr, kbuf.bufsz, kbuf.memsz);
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:20 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:20 +0800
Subject: [PATCH v14 05/17] x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-6-ruanjinjie@huawei.com>

Sashiko AI code review pointed out a there is a TOCTOU (Time-of-Check to
Time-of-Use) race condition in prepare_elf_headers() between the initial
pass that counts System RAM ranges and the second pass that populates them.
If a memory hotplug event occurs between these two steps, the number of
memory regions may increase, causing an out-of-bounds write to
the cmem->ranges[] array.

Directly introducing get_online_mems() inside prepare_elf_headers() would
trigger an immediate recursive read-after-write deadlock when invoked by
the runtime hotplug notification path (which already holds the hotplug
write lock).

To eliminate the TOCTOU window safely without deadlock risks, move the
get_online_mems() read lock to the top-level architecture image loaders.
Since these top-level loaders are strictly executed on the initial system
call path and are never re-entered by the runtime hotplug notifier, this
approach physically isolates the locking contexts. The system memory ranges
are forced to be statically frozen during the entire layout generation,
eradicating the buffer overflow vulnerability.

Cc: Thomas Gleixner <tglx at kernel.org>
Cc: Ingo Molnar <mingo at redhat.com>
Cc: Borislav Petkov <bp at alien8.de>
Cc: "H. Peter Anvin" <hpa at zytor.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan He <bhe at redhat.com>
Cc: Mike Rapoport <rppt at kernel.org>
Cc: stable at vger.kernel.org
Fixes: 8d5f894a3108 ("x86: kexec_file: lift CRASH_MAX_RANGES limit on crash_mem buffer")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/x86/kernel/crash.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index cd796818d94d..f319308b06ee 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -25,6 +25,7 @@
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
 #include <linux/memblock.h>
+#include <linux/memory_hotplug.h>
 
 #include <asm/bootparam.h>
 #include <asm/processor.h>
@@ -226,6 +227,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
 
+	if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges))
+		return -EAGAIN;
+
 	cmem->ranges[cmem->nr_ranges].start = res->start;
 	cmem->ranges[cmem->nr_ranges].end = res->end;
 	cmem->nr_ranges++;
@@ -419,10 +423,14 @@ int crash_load_segments(struct kimage *image)
 				  .buf_max = ULONG_MAX, .top_down = false };
 
 	/* Prepare elf headers and add a segment */
+	get_online_mems();
 	ret = prepare_elf_headers(&kbuf.buffer, &kbuf.bufsz, &pnum);
-	if (ret)
+	if (ret) {
+		put_online_mems();
 		return ret;
+	}
 
+	put_online_mems();
 	image->elf_headers	= kbuf.buffer;
 	image->elf_headers_sz	= kbuf.bufsz;
 	kbuf.memsz		= kbuf.bufsz;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:21 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:21 +0800
Subject: [PATCH v14 06/17] arm64: kexec_file: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-7-ruanjinjie@huawei.com>

Sashiko AI code review pointed out there is a TOCTOU (Time-of-Check to
Time-of-Use) race condition in prepare_elf_headers() between the initial
pass that counts System RAM ranges and the second pass that populates them.
If a memory hotplug event occurs between these two steps, the number of
memory regions may increase, causing an out-of-bounds write to
the cmem->ranges[] array.

Directly introducing get_online_mems() inside prepare_elf_headers() would
trigger an immediate recursive read-after-write deadlock when invoked by
the runtime hotplug notification path (which already holds the hotplug
write lock).

To eliminate the TOCTOU window safely without deadlock risks, move the
get_online_mems() read lock to the top-level architecture image loaders.
Since these top-level loaders are strictly executed on the initial system
call path and are never re-entered by the runtime hotplug notifier, this
approach physically isolates the locking contexts. The system memory ranges
are forced to be statically frozen during the entire layout generation,
eradicating the buffer overflow vulnerability.

Cc: Catalin Marinas <catalin.marinas at arm.com>
Cc: Will Deacon <will.deacon at arm.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Baoquan He <bhe at redhat.com>
Cc: Breno Leitao <leitao at debian.org>
Cc: stable at vger.kernel.org
Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/kernel/kexec_image.c        | 4 ++++
 arch/arm64/kernel/machine_kexec_file.c | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c
index 79efeaeb71e9..884e446f08e3 100644
--- a/arch/arm64/kernel/kexec_image.c
+++ b/arch/arm64/kernel/kexec_image.c
@@ -12,6 +12,7 @@
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/kexec.h>
+#include <linux/memory_hotplug.h>
 #include <linux/pe.h>
 #include <linux/string.h>
 #include <asm/byteorder.h>
@@ -96,11 +97,14 @@ static void *image_load(struct kimage *image,
 
 #ifdef CONFIG_CRASH_DUMP
 	if (image->type == KEXEC_TYPE_CRASH) {
+		get_online_mems();
 		ret = prepare_elf_headers(&headers, &headers_sz);
 		if (ret) {
 			pr_err("Preparing elf core header failed\n");
+			put_online_mems();
 			return ERR_PTR(ret);
 		}
+		put_online_mems();
 		image->elf_headers = headers;
 		image->elf_headers_sz = headers_sz;
 	}
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index daf81a873bbd..c0ace89ded92 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -59,6 +59,11 @@ int prepare_elf_headers(void **addr, unsigned long *sz)
 	cmem->max_nr_ranges = nr_ranges;
 	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
+		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges)) {
+			ret = -EAGAIN;
+			goto out;
+		}
+
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:22 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:22 +0800
Subject: [PATCH v14 07/17] riscv: kexec_file: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-8-ruanjinjie@huawei.com>

Sashiko AI code review pointed out there is a TOCTOU (Time-of-Check to
Time-of-Use) race condition in prepare_elf_headers() between the initial
pass that counts System RAM ranges and the second pass that populates them.
If a memory hotplug event occurs between these two steps, the number of
memory regions may increase, causing an out-of-bounds write to
the cmem->ranges[] array.

Directly introducing get_online_mems() inside prepare_elf_headers() would
trigger an immediate recursive read-after-write deadlock when invoked by
the runtime hotplug notification path (which already holds the hotplug
write lock).

To eliminate the TOCTOU window safely without deadlock risks, move the
get_online_mems() read lock to the top-level architecture image loaders.
Since these top-level loaders are strictly executed on the initial system
call path and are never re-entered by the runtime hotplug notifier, this
approach physically isolates the locking contexts. The system memory ranges
are forced to be statically frozen during the entire layout generation,
eradicating the buffer overflow vulnerability.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Cc: songshuaishuai at tinylab.org
Cc: bjorn at rivosinc.com
Cc: leitao at debian.org
Fixes: 8acea455fafa ("RISC-V: Support for kexec_file on panic")
Reviewed-by: Guo Ren <guoren at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 3f7766057cac..a88362dfbd89 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -8,6 +8,7 @@
  */
 #include <linux/kexec.h>
 #include <linux/elf.h>
+#include <linux/memory_hotplug.h>
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/libfdt.h>
@@ -48,6 +49,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
 
+	if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges))
+		return -EAGAIN;
+
 	cmem->ranges[cmem->nr_ranges].start = res->start;
 	cmem->ranges[cmem->nr_ranges].end = res->end;
 	cmem->nr_ranges++;
@@ -281,12 +285,15 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start,
 	if (image->type == KEXEC_TYPE_CRASH) {
 		void *headers;
 		unsigned long headers_sz;
+		get_online_mems();
 		ret = prepare_elf_headers(&headers, &headers_sz);
 		if (ret) {
+			put_online_mems();
 			pr_err("Preparing elf core header failed\n");
 			goto out;
 		}
 
+		put_online_mems();
 		kbuf.buffer = headers;
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:23 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:23 +0800
Subject: [PATCH v14 08/17] LoongArch: kexec: Fix potential buffer overflow in prepare_elf_headers()
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-9-ruanjinjie@huawei.com>

Sashiko AI code review pointed out there is a TOCTOU (Time-of-Check to
Time-of-Use) race condition in prepare_elf_headers() between the initial
pass that counts System RAM ranges and the second pass that populates them.
If a memory hotplug event occurs between these two steps, the number of
memory regions may increase, causing an out-of-bounds write to
the cmem->ranges[] array.

Directly introducing get_online_mems() inside prepare_elf_headers() would
trigger an immediate recursive read-after-write deadlock when invoked by
the runtime hotplug notification path (which already holds the hotplug
write lock).

To eliminate the TOCTOU window safely without deadlock risks, move the
get_online_mems() read lock to the top-level architecture image loaders.
Since these top-level loaders are strictly executed on the initial system
call path and are never re-entered by the runtime hotplug notifier, this
approach physically isolates the locking contexts. The system memory ranges
are forced to be statically frozen during the entire layout generation,
eradicating the buffer overflow vulnerability.

Cc: Youling Tang <tangyouling at kylinos.cn>
Cc: Huacai Chen <chenhuacai at loongson.cn>
Cc: WANG Xuerui <kernel at xen0n.name>
Cc: stable at vger.kernel.org
Fixes: 1bcca8620a91 ("LoongArch: Add crash dump support for kexec_file")
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 5584b798ba46..2a1a6124f043 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -14,6 +14,7 @@
 #include <linux/kernel.h>
 #include <linux/kexec.h>
 #include <linux/memblock.h>
+#include <linux/memory_hotplug.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -75,6 +76,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
 	cmem->max_nr_ranges = nr_ranges;
 	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
+		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges)) {
+			ret = -EAGAIN;
+			goto out;
+		}
+
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
@@ -163,12 +169,15 @@ int load_other_segments(struct kimage *image,
 		void *headers;
 		unsigned long headers_sz;
 
+		get_online_mems();
 		ret = prepare_elf_headers(&headers, &headers_sz);
 		if (ret < 0) {
+			put_online_mems();
 			pr_err("Preparing elf core header failed\n");
 			goto out_err;
 		}
 
+		put_online_mems();
 		kbuf.buffer = headers;
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:24 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:24 +0800
Subject: [PATCH v14 09/17] crash: Add crash_prepare_headers() to exclude crash kernel memory
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-10-ruanjinjie@huawei.com>

The crash memory alloc, and the exclude of crashk_res, crashk_low_res
and crashk_cma memory are almost identical across different architectures,
handling them in the crash core would eliminate a lot of duplication, so
add crash_prepare_headers() helper to handle them in the common code.

To achieve the above goal, three architecture-specific functions are
introduced:

- arch_get_system_nr_ranges(). Pre-counts the max number of memory ranges.

- arch_crash_populate_cmem(). Collects the memory ranges and fills them
  into cmem.

- arch_crash_exclude_ranges(). Architecture's additional crash memory
  ranges exclusion, defaulting to empty.

Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 include/linux/crash_core.h |  7 +++
 kernel/crash_core.c        | 95 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 99 insertions(+), 3 deletions(-)

diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index c1dee3f971a9..d69c4c7c1f5e 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -59,6 +59,10 @@ extern int crash_exclude_mem_range(struct crash_mem *mem,
 				   unsigned long long mend);
 extern int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 				       void **addr, unsigned long *sz);
+extern int crash_prepare_headers(int need_kernel_map, void **addr,
+				 unsigned long *sz, unsigned long *nr_mem_ranges);
+extern int crash_prepare_headers_locked(int need_kernel_map, void **addr,
+					unsigned long *sz, unsigned long *nr_mem_ranges);
 
 struct kimage;
 struct kexec_segment;
@@ -76,6 +80,9 @@ int kexec_should_crash(struct task_struct *p);
 int kexec_crash_loaded(void);
 void crash_save_cpu(struct pt_regs *regs, int cpu);
 extern int kimage_crash_copy_vmcoreinfo(struct kimage *image);
+extern unsigned int arch_get_system_nr_ranges(void);
+extern int arch_crash_populate_cmem(struct crash_mem *cmem);
+extern int arch_crash_exclude_ranges(struct crash_mem *cmem);
 
 #else /* !CONFIG_CRASH_DUMP*/
 struct pt_regs;
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index 4f21fc3b108b..e84f3b63e79d 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -13,6 +13,7 @@
 #include <linux/sizes.h>
 #include <linux/kexec.h>
 #include <linux/memory.h>
+#include <linux/memory_hotplug.h>
 #include <linux/mm.h>
 #include <linux/cpuhotplug.h>
 #include <linux/memblock.h>
@@ -168,9 +169,6 @@ static inline resource_size_t crash_resource_size(const struct resource *res)
 	return !res->end ? 0 : resource_size(res);
 }
 
-
-
-
 int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 			  void **addr, unsigned long *sz)
 {
@@ -272,6 +270,97 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
 	return 0;
 }
 
+static struct crash_mem *alloc_cmem(unsigned int nr_ranges)
+{
+	struct crash_mem *cmem;
+
+	cmem = kvzalloc_flex(*cmem, ranges, nr_ranges);
+	if (!cmem)
+		return NULL;
+
+	cmem->max_nr_ranges = nr_ranges;
+	return cmem;
+}
+
+unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
+int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
+int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
+
+static int crash_exclude_core_ranges(struct crash_mem *cmem)
+{
+	int ret, i;
+
+	/* Exclude crashkernel region */
+	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	if (ret)
+		return ret;
+
+	if (crashk_low_res.end) {
+		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < crashk_cma_cnt; ++i) {
+		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
+					      crashk_cma_ranges[i].end);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
+			  unsigned long *nr_mem_ranges)
+{
+	unsigned int max_nr_ranges;
+	struct crash_mem *cmem;
+	int ret;
+
+	max_nr_ranges = arch_get_system_nr_ranges();
+	if (!max_nr_ranges)
+		return -ENOMEM;
+
+	cmem = alloc_cmem(max_nr_ranges);
+	if (!cmem)
+		return -ENOMEM;
+
+	ret = arch_crash_populate_cmem(cmem);
+	if (ret)
+		goto out;
+
+	ret = crash_exclude_core_ranges(cmem);
+	if (ret)
+		goto out;
+
+	ret = arch_crash_exclude_ranges(cmem);
+	if (ret)
+		goto out;
+
+	/* Return the computed number of memory ranges, for hotplug usage */
+	if (nr_mem_ranges)
+		*nr_mem_ranges = cmem->nr_ranges;
+
+	ret = crash_prepare_elf64_headers(cmem, need_kernel_map, addr, sz);
+
+out:
+	kvfree(cmem);
+	return ret;
+}
+
+int crash_prepare_headers_locked(int need_kernel_map, void **addr, unsigned long *sz,
+				 unsigned long *nr_mem_ranges)
+{
+	int ret;
+
+	get_online_mems();
+	ret = crash_prepare_headers(need_kernel_map, addr, sz, nr_mem_ranges);
+	put_online_mems();
+
+	return ret;
+}
+
 /**
  * crash_exclude_mem_range - exclude a mem range for existing ranges
  * @mem: mem->range contains an array of ranges sorted in ascending order
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:25 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:25 +0800
Subject: [PATCH v14 10/17] arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-11-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash
kernel memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Use for_each_mem_range() to traverse
  and pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use for_each_mem_range to traverse
  and collect the memory ranges and fills them into cmem.

Acked-by: Catalin Marinas <catalin.marinas at arm.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/kernel/kexec_image.c        |  6 +---
 arch/arm64/kernel/machine_kexec_file.c | 44 ++++++++------------------
 2 files changed, 14 insertions(+), 36 deletions(-)

diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c
index 884e446f08e3..770a4c7bf573 100644
--- a/arch/arm64/kernel/kexec_image.c
+++ b/arch/arm64/kernel/kexec_image.c
@@ -12,7 +12,6 @@
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/kexec.h>
-#include <linux/memory_hotplug.h>
 #include <linux/pe.h>
 #include <linux/string.h>
 #include <asm/byteorder.h>
@@ -97,14 +96,11 @@ static void *image_load(struct kimage *image,
 
 #ifdef CONFIG_CRASH_DUMP
 	if (image->type == KEXEC_TYPE_CRASH) {
-		get_online_mems();
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers_locked(true, &headers, &headers_sz, NULL);
 		if (ret) {
 			pr_err("Preparing elf core header failed\n");
-			put_online_mems();
 			return ERR_PTR(ret);
 		}
-		put_online_mems();
 		image->elf_headers = headers;
 		image->elf_headers_sz = headers_sz;
 	}
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index c0ace89ded92..66fbfbaec1c6 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -40,51 +40,33 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 }
 
 #ifdef CONFIG_CRASH_DUMP
-int prepare_elf_headers(void **addr, unsigned long *sz)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	struct crash_mem *cmem;
-	unsigned int nr_ranges;
-	int ret;
-	u64 i;
+	unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
+	u64 i;
 
-	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
 
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
+	return nr_ranges;
+}
+
+int arch_crash_populate_cmem(struct crash_mem *cmem)
+{
+	phys_addr_t start, end;
+	u64 i;
 
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
-		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges)) {
-			ret = -EAGAIN;
-			goto out;
-		}
+		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges))
+			return -EAGAIN;
 
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return 0;
 }
 #endif
 
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:26 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:26 +0800
Subject: [PATCH v14 11/17] x86/kexec: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-12-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following three architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Call get_nr_ram_ranges_callback()
  to pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use prepare_elf64_ram_headers_callback()
  to collect the memory ranges and fills them into cmem.

- arch_crash_exclude_ranges(). Exclude the low 1M for x86.

By the way, remove the unused "nr_mem_ranges" in
arch_crash_handle_hotplug_event().

Cc: Thomas Gleixner <tglx at kernel.org>
Cc: Ingo Molnar <mingo at redhat.com>
Cc: Borislav Petkov <bp at alien8.de>
Cc: Dave Hansen <dave.hansen at linux.intel.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Vivek Goyal <vgoyal at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/x86/kernel/crash.c | 96 ++++++-----------------------------------
 1 file changed, 12 insertions(+), 84 deletions(-)

diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index f319308b06ee..26f140dff348 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -25,7 +25,6 @@
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
 #include <linux/memblock.h>
-#include <linux/memory_hotplug.h>
 
 #include <asm/bootparam.h>
 #include <asm/processor.h>
@@ -154,16 +153,8 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-/* Gather all the required information to prepare elf headers for ram regions */
-static struct crash_mem *fill_up_crash_elf_data(void)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 0;
-	struct crash_mem *cmem;
-
-	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
-	if (!nr_ranges)
-		return NULL;
-
 	/*
 	 * Exclusion of crash region, crashk_low_res and/or crashk_cma_ranges
 	 * may cause range splits. So add extra slots here.
@@ -178,49 +169,16 @@ static struct crash_mem *fill_up_crash_elf_data(void)
 	 * But in order to lest the low 1M could be changed in the future,
 	 * (e.g. [start, 1M]), add a extra slot.
 	 */
-	nr_ranges += 3 + crashk_cma_cnt;
-	cmem = vzalloc(struct_size(cmem, ranges, nr_ranges));
-	if (!cmem)
-		return NULL;
-
-	cmem->max_nr_ranges = nr_ranges;
+	unsigned int nr_ranges = 3 + crashk_cma_cnt;
 
-	return cmem;
+	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+	return nr_ranges;
 }
 
-/*
- * Look for any unwanted ranges between mstart, mend and remove them. This
- * might lead to split and split ranges are put in cmem->ranges[] array
- */
-static int elf_header_exclude_ranges(struct crash_mem *cmem)
+int arch_crash_exclude_ranges(struct crash_mem *cmem)
 {
-	int ret = 0;
-	int i;
-
 	/* Exclude the low 1M because it is always reserved */
-	ret = crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
-	if (ret)
-		return ret;
-
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		return ret;
-
-	if (crashk_low_res.end)
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start,
-					      crashk_low_res.end);
-	if (ret)
-		return ret;
-
-	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
+	return crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
 }
 
 static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
@@ -237,35 +195,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-/* Prepare elf headers. Return addr and size */
-static int prepare_elf_headers(void **addr, unsigned long *sz,
-			       unsigned long *nr_mem_ranges)
+int arch_crash_populate_cmem(struct crash_mem *cmem)
 {
-	struct crash_mem *cmem;
-	int ret;
-
-	cmem = fill_up_crash_elf_data();
-	if (!cmem)
-		return -ENOMEM;
-
-	ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
-	if (ret)
-		goto out;
-
-	/* Exclude unwanted mem ranges */
-	ret = elf_header_exclude_ranges(cmem);
-	if (ret)
-		goto out;
-
-	/* Return the computed number of memory ranges, for hotplug usage */
-	*nr_mem_ranges = cmem->nr_ranges;
-
-	/* By default prepare 64bit headers */
-	ret = crash_prepare_elf64_headers(cmem, IS_ENABLED(CONFIG_X86_64), addr, sz);
-
-out:
-	vfree(cmem);
-	return ret;
+	return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
 }
 #endif
 
@@ -423,14 +355,11 @@ int crash_load_segments(struct kimage *image)
 				  .buf_max = ULONG_MAX, .top_down = false };
 
 	/* Prepare elf headers and add a segment */
-	get_online_mems();
-	ret = prepare_elf_headers(&kbuf.buffer, &kbuf.bufsz, &pnum);
-	if (ret) {
-		put_online_mems();
+	ret = crash_prepare_headers_locked(IS_ENABLED(CONFIG_X86_64), &kbuf.buffer,
+					   &kbuf.bufsz, &pnum);
+	if (ret)
 		return ret;
-	}
 
-	put_online_mems();
 	image->elf_headers	= kbuf.buffer;
 	image->elf_headers_sz	= kbuf.bufsz;
 	kbuf.memsz		= kbuf.bufsz;
@@ -520,7 +449,6 @@ unsigned int arch_crash_get_elfcorehdr_size(void)
 void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
 {
 	void *elfbuf = NULL, *old_elfcorehdr;
-	unsigned long nr_mem_ranges;
 	unsigned long mem, memsz;
 	unsigned long elfsz = 0;
 
@@ -538,7 +466,7 @@ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
 	 * Create the new elfcorehdr reflecting the changes to CPU and/or
 	 * memory resources.
 	 */
-	if (prepare_elf_headers(&elfbuf, &elfsz, &nr_mem_ranges)) {
+	if (crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &elfbuf, &elfsz, NULL)) {
 		pr_err("unable to create new elfcorehdr");
 		goto out;
 	}
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:27 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:27 +0800
Subject: [PATCH v14 12/17] riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-13-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Call get_nr_ram_ranges_callback()
  to pre-counts the max number of memory ranges.

- arch_crash_populate_cmem(). Use prepare_elf64_ram_headers_callback()
  to collects the memory ranges and fills them into cmem.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Cc: Guo Ren <guoren at kernel.org>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/riscv/kernel/machine_kexec_file.c | 51 ++++++--------------------
 1 file changed, 12 insertions(+), 39 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index a88362dfbd89..d2bb61a8b6b6 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -8,7 +8,6 @@
  */
 #include <linux/kexec.h>
 #include <linux/elf.h>
-#include <linux/memory_hotplug.h>
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/libfdt.h>
@@ -45,6 +44,15 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 	return 0;
 }
 
+unsigned int arch_get_system_nr_ranges(void)
+{
+	unsigned int nr_ranges = 2; /* For exclusion of crashkernel region */
+
+	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
+
+	return nr_ranges;
+}
+
 static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 {
 	struct crash_mem *cmem = arg;
@@ -59,41 +67,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
 	return 0;
 }
 
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+int arch_crash_populate_cmem(struct crash_mem *cmem)
 {
-	struct crash_mem *cmem;
-	unsigned int nr_ranges;
-	int ret;
-
-	nr_ranges = 2; /* For exclusion of crashkernel region */
-	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
-
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
-
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
-	ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
-	if (ret)
-		goto out;
-
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
 }
 
 static char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
@@ -285,15 +261,12 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start,
 	if (image->type == KEXEC_TYPE_CRASH) {
 		void *headers;
 		unsigned long headers_sz;
-		get_online_mems();
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers_locked(true, &headers, &headers_sz, NULL);
 		if (ret) {
-			put_online_mems();
 			pr_err("Preparing elf core header failed\n");
 			goto out;
 		}
 
-		put_online_mems();
 		kbuf.buffer = headers;
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:28 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:28 +0800
Subject: [PATCH v14 13/17] LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-14-ruanjinjie@huawei.com>

Use the newly introduced crash_prepare_headers() function to replace
the existing prepare_elf_headers(), allocate cmem and exclude crash kernel
memory in the crash core, which reduce code duplication.

Only the following two architecture functions need to be implemented:
- arch_get_system_nr_ranges(). Use for_each_mem_range to traverse
  and pre-count the max number of memory ranges.

- arch_crash_populate_cmem(). Use for_each_mem_range to traverse
  and collect the memory ranges and fills them into cmem.

Cc: Huacai Chen <chenhuacai at kernel.org>
Cc: WANG Xuerui <kernel at xen0n.name>
Cc: Youling Tang <tangyouling at kylinos.cn>
Cc: Baoquan He <bhe at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/loongarch/kernel/machine_kexec_file.c | 50 ++++++----------------
 1 file changed, 14 insertions(+), 36 deletions(-)

diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index 2a1a6124f043..a845fbe8883d 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -14,7 +14,6 @@
 #include <linux/kernel.h>
 #include <linux/kexec.h>
 #include <linux/memblock.h>
-#include <linux/memory_hotplug.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -57,51 +56,33 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
 }
 
 #ifdef CONFIG_CRASH_DUMP
-
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+unsigned int arch_get_system_nr_ranges(void)
 {
-	int ret, nr_ranges;
-	uint64_t i;
+	int nr_ranges = 2; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
-	struct crash_mem *cmem;
+	uint64_t i;
 
-	nr_ranges = 2; /* for exclusion of crashkernel region */
 	for_each_mem_range(i, &start, &end)
 		nr_ranges++;
 
-	cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
-	if (!cmem)
-		return -ENOMEM;
+	return nr_ranges;
+}
+
+int arch_crash_populate_cmem(struct crash_mem *cmem)
+{
+	phys_addr_t start, end;
+	uint64_t i;
 
-	cmem->max_nr_ranges = nr_ranges;
-	cmem->nr_ranges = 0;
 	for_each_mem_range(i, &start, &end) {
-		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges)) {
-			ret = -EAGAIN;
-			goto out;
-		}
+		if (WARN_ON_ONCE(cmem->nr_ranges >= cmem->max_nr_ranges))
+			return -EAGAIN;
 
 		cmem->ranges[cmem->nr_ranges].start = start;
 		cmem->ranges[cmem->nr_ranges].end = end - 1;
 		cmem->nr_ranges++;
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
-	if (ret < 0)
-		goto out;
-
-	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
-		if (ret < 0)
-			goto out;
-	}
-
-	ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
-
-out:
-	kfree(cmem);
-	return ret;
+	return 0;
 }
 
 /*
@@ -169,15 +150,12 @@ int load_other_segments(struct kimage *image,
 		void *headers;
 		unsigned long headers_sz;
 
-		get_online_mems();
-		ret = prepare_elf_headers(&headers, &headers_sz);
+		ret = crash_prepare_headers_locked(true, &headers, &headers_sz, NULL);
 		if (ret < 0) {
-			put_online_mems();
 			pr_err("Preparing elf core header failed\n");
 			goto out_err;
 		}
 
-		put_online_mems();
 		kbuf.buffer = headers;
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:29 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:29 +0800
Subject: [PATCH v14 14/17] crash: Use crash_exclude_core_ranges() on powerpc
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-15-ruanjinjie@huawei.com>

The crash memory exclude of crashk_res and crashk_cma memory on powerpc
are almost identical to the generic crash_exclude_core_ranges().

By introducing the architecture-specific arch_crash_exclude_mem_range()
function with a default implementation of crash_exclude_mem_range(),
and using crash_exclude_mem_range_guarded as powerpc's separate
implementation, the generic crash_exclude_core_ranges() helper function
can be reused.

Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Hari Bathini <hbathini at linux.ibm.com>
Cc: Madhavan Srinivasan <maddy at linux.ibm.com>
Cc: Mahesh Salgaonkar <mahesh at linux.ibm.com>
Cc: Michael Ellerman <mpe at ellerman.id.au>
Cc: Ritesh Harjani (IBM) <ritesh.list at gmail.com>
Cc: Shivang Upadhyay <shivangu at linux.ibm.com>
Acked-by: Baoquan He <bhe at redhat.com>
Reviewed-by: Sourabh Jain <sourabhjain at linux.ibm.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/powerpc/include/asm/kexec_ranges.h |  3 ---
 arch/powerpc/kexec/crash.c              |  2 +-
 arch/powerpc/kexec/ranges.c             | 16 ++++------------
 include/linux/crash_core.h              |  4 ++++
 kernel/crash_core.c                     | 19 +++++++++++++------
 5 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/arch/powerpc/include/asm/kexec_ranges.h b/arch/powerpc/include/asm/kexec_ranges.h
index ad95e3792d10..8489e844b447 100644
--- a/arch/powerpc/include/asm/kexec_ranges.h
+++ b/arch/powerpc/include/asm/kexec_ranges.h
@@ -7,9 +7,6 @@
 void sort_memory_ranges(struct crash_mem *mrngs, bool merge);
 struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges);
 int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
-int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
-				    unsigned long long mstart,
-				    unsigned long long mend);
 int get_exclude_memory_ranges(struct crash_mem **mem_ranges);
 int get_reserved_memory_ranges(struct crash_mem **mem_ranges);
 int get_crash_memory_ranges(struct crash_mem **mem_ranges);
diff --git a/arch/powerpc/kexec/crash.c b/arch/powerpc/kexec/crash.c
index d634db67becc..775895f31037 100644
--- a/arch/powerpc/kexec/crash.c
+++ b/arch/powerpc/kexec/crash.c
@@ -513,7 +513,7 @@ static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *
 		base_addr = PFN_PHYS(mn->start_pfn);
 		size = mn->nr_pages * PAGE_SIZE;
 		end = base_addr + size - 1;
-		ret = crash_exclude_mem_range_guarded(&cmem, base_addr, end);
+		ret = arch_crash_exclude_mem_range(&cmem, base_addr, end);
 		if (ret) {
 			pr_err("Failed to remove hot-unplugged memory from crash memory ranges\n");
 			goto out;
diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c
index 6c58bcc3e130..e5fea23b191b 100644
--- a/arch/powerpc/kexec/ranges.c
+++ b/arch/powerpc/kexec/ranges.c
@@ -553,9 +553,9 @@ int get_usable_memory_ranges(struct crash_mem **mem_ranges)
 #endif /* CONFIG_KEXEC_FILE */
 
 #ifdef CONFIG_CRASH_DUMP
-int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
-					   unsigned long long mstart,
-					   unsigned long long mend)
+int arch_crash_exclude_mem_range(struct crash_mem **mem_ranges,
+				 unsigned long long mstart,
+				 unsigned long long mend)
 {
 	struct crash_mem *tmem = *mem_ranges;
 
@@ -604,18 +604,10 @@ int get_crash_memory_ranges(struct crash_mem **mem_ranges)
 			sort_memory_ranges(*mem_ranges, true);
 	}
 
-	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_res.start, crashk_res.end);
+	ret = crash_exclude_core_ranges(mem_ranges);
 	if (ret)
 		goto out;
 
-	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
-		if (ret)
-			goto out;
-	}
-
 	/*
 	 * FIXME: For now, stay in parity with kexec-tools but if RTAS/OPAL
 	 *        regions are exported to save their context at the time of
diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index d69c4c7c1f5e..0f4ea7ffd066 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -63,6 +63,7 @@ extern int crash_prepare_headers(int need_kernel_map, void **addr,
 				 unsigned long *sz, unsigned long *nr_mem_ranges);
 extern int crash_prepare_headers_locked(int need_kernel_map, void **addr,
 					unsigned long *sz, unsigned long *nr_mem_ranges);
+extern int crash_exclude_core_ranges(struct crash_mem **cmem);
 
 struct kimage;
 struct kexec_segment;
@@ -83,6 +84,9 @@ extern int kimage_crash_copy_vmcoreinfo(struct kimage *image);
 extern unsigned int arch_get_system_nr_ranges(void);
 extern int arch_crash_populate_cmem(struct crash_mem *cmem);
 extern int arch_crash_exclude_ranges(struct crash_mem *cmem);
+extern int arch_crash_exclude_mem_range(struct crash_mem **mem,
+					unsigned long long mstart,
+					unsigned long long mend);
 
 #else /* !CONFIG_CRASH_DUMP*/
 struct pt_regs;
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index e84f3b63e79d..33e945da6776 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -286,24 +286,31 @@ unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
 int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
 int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
 
-static int crash_exclude_core_ranges(struct crash_mem *cmem)
+int __weak arch_crash_exclude_mem_range(struct crash_mem **mem,
+					unsigned long long mstart,
+					unsigned long long mend)
+{
+	return crash_exclude_mem_range(*mem, mstart, mend);
+}
+
+int crash_exclude_core_ranges(struct crash_mem **cmem)
 {
 	int ret, i;
 
 	/* Exclude crashkernel region */
-	ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+	ret = arch_crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
 	if (ret)
 		return ret;
 
 	if (crashk_low_res.end) {
-		ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+		ret = arch_crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
 		if (ret)
 			return ret;
 	}
 
 	for (i = 0; i < crashk_cma_cnt; ++i) {
-		ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
-					      crashk_cma_ranges[i].end);
+		ret = arch_crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
+						   crashk_cma_ranges[i].end);
 		if (ret)
 			return ret;
 	}
@@ -330,7 +337,7 @@ int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
 	if (ret)
 		goto out;
 
-	ret = crash_exclude_core_ranges(cmem);
+	ret = crash_exclude_core_ranges(&cmem);
 	if (ret)
 		goto out;
 
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:30 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:30 +0800
Subject: [PATCH v14 15/17] arm64: kexec: Add support for crashkernel CMA reservation
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-16-ruanjinjie@huawei.com>

Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
crashkernel= command line option") and commit ab475510e042 ("kdump:
implement reserve_crashkernel_cma") added CMA support for kdump
crashkernel reservation.

Crash kernel memory reservation wastes production resources if too
large, risks kdump failure if too small, and faces allocation difficulties
on fragmented systems due to contiguous block constraints. The new
CMA-based crashkernel reservation scheme splits the "large fixed
reservation" into a "small fixed region + large CMA dynamic region": the
CMA memory is available to userspace during normal operation to avoid
waste, and is reclaimed for kdump upon crash?saving memory while
improving reliability.

So extend crashkernel CMA reservation support to arm64. The following
changes are made to enable CMA reservation:

- Parse and obtain the CMA reservation size along with other crashkernel
  parameters.
- Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
- Include the CMA-reserved ranges for kdump kernel to use.
- Exclude the CMA-reserved ranges from the crash kernel memory to
  prevent them from being exported through /proc/vmcore, which is already
  done in the crash core.

Update kernel-parameters.txt to document CMA support for crashkernel on
arm64 architecture.

Tested-by: Breno Leitao <leitao at debian.org>
Acked-by: Catalin Marinas <catalin.marinas at arm.com>
Acked-by: Rob Herring (Arm) <robh at kernel.org>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Acked-by: Ard Biesheuvel <ardb at kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
v7:
- Correct the inclusion of CMA-reserved ranges for kdump
  kernel in of/kexec.
v3:
- Add Acked-by.
v2:
- Free cmem in prepare_elf_headers()
- Add the mtivation.
---
 Documentation/admin-guide/kernel-parameters.txt | 2 +-
 arch/arm64/kernel/machine_kexec_file.c          | 2 +-
 arch/arm64/mm/init.c                            | 5 +++--
 drivers/of/fdt.c                                | 9 +++++----
 drivers/of/kexec.c                              | 9 +++++++++
 include/linux/crash_reserve.h                   | 4 +++-
 6 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..52742fab49a9 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1119,7 +1119,7 @@ Kernel parameters
 			It will be ignored when crashkernel=X,high is not used
 			or memory reserved is below 4G.
 	crashkernel=size[KMG],cma
-			[KNL, X86, ppc] Reserve additional crash kernel memory from
+			[KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
 			CMA. This reservation is usable by the first system's
 			userspace memory and kernel movable allocations (memory
 			balloon, zswap). Pages allocated from this memory range
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index 66fbfbaec1c6..d2985ce62306 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -42,7 +42,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 #ifdef CONFIG_CRASH_DUMP
 unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
+	unsigned int nr_ranges = 2 + crashk_cma_cnt; /* for exclusion of crashkernel region */
 	phys_addr_t start, end;
 	u64 i;
 
diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
index 97987f850a33..227f58522dad 100644
--- a/arch/arm64/mm/init.c
+++ b/arch/arm64/mm/init.c
@@ -96,8 +96,8 @@ phys_addr_t __ro_after_init arm64_dma_phys_limit;
 
 static void __init arch_reserve_crashkernel(void)
 {
+	unsigned long long crash_base, crash_size, cma_size = 0;
 	unsigned long long low_size = 0;
-	unsigned long long crash_base, crash_size;
 	bool high = false;
 	int ret;
 
@@ -106,11 +106,12 @@ static void __init arch_reserve_crashkernel(void)
 
 	ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
 				&crash_size, &crash_base,
-				&low_size, NULL, &high);
+				&low_size, &cma_size, &high);
 	if (ret)
 		return;
 
 	reserve_crashkernel_generic(crash_size, crash_base, low_size, high);
+	reserve_crashkernel_cma(cma_size);
 }
 
 static phys_addr_t __init max_zone_phys(phys_addr_t zone_limit)
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 82f7327c59ea..0470acbd1fcf 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -880,11 +880,12 @@ static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;
 /*
  * The main usage of linux,usable-memory-range is for crash dump kernel.
  * Originally, the number of usable-memory regions is one. Now there may
- * be two regions, low region and high region.
- * To make compatibility with existing user-space and older kdump, the low
- * region is always the last range of linux,usable-memory-range if exist.
+ * be 2 + CRASHK_CMA_RANGES_MAX regions, low region, high region and cma
+ * regions. To make compatibility with existing user-space and older kdump,
+ * the high and low region are always the first two ranges of
+ * linux,usable-memory-range if exist.
  */
-#define MAX_USABLE_RANGES		2
+#define MAX_USABLE_RANGES		(2 + CRASHK_CMA_RANGES_MAX)
 
 /**
  * early_init_dt_check_for_usable_mem_range - Decode usable memory range
diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c
index b6837e299e7f..029903b986cb 100644
--- a/drivers/of/kexec.c
+++ b/drivers/of/kexec.c
@@ -458,6 +458,15 @@ void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
 			if (ret)
 				goto out;
 		}
+
+		for (int i = 0; i < crashk_cma_cnt; i++) {
+			ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
+					"linux,usable-memory-range",
+					crashk_cma_ranges[i].start,
+					crashk_cma_ranges[i].end - crashk_cma_ranges[i].start + 1);
+			if (ret)
+				goto out;
+		}
 #endif
 	}
 
diff --git a/include/linux/crash_reserve.h b/include/linux/crash_reserve.h
index f0dc03d94ca2..30864d90d7f5 100644
--- a/include/linux/crash_reserve.h
+++ b/include/linux/crash_reserve.h
@@ -14,9 +14,11 @@
 extern struct resource crashk_res;
 extern struct resource crashk_low_res;
 extern struct range crashk_cma_ranges[];
+
+#define CRASHK_CMA_RANGES_MAX 4
 #if defined(CONFIG_CMA) && defined(CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION)
 #define CRASHKERNEL_CMA
-#define CRASHKERNEL_CMA_RANGES_MAX 4
+#define CRASHKERNEL_CMA_RANGES_MAX (CRASHK_CMA_RANGES_MAX)
 extern int crashk_cma_cnt;
 #else
 #define crashk_cma_cnt 0
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:31 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:31 +0800
Subject: [PATCH v14 16/17] riscv: kexec: Add support for crashkernel CMA reservation
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-17-ruanjinjie@huawei.com>

Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
crashkernel= command line option") and commit ab475510e042 ("kdump:
implement reserve_crashkernel_cma") added CMA support for kdump
crashkernel reservation. This allows the kernel to dynamically allocate
contiguous memory for crash dumping when needed, rather than permanently
reserving a fixed region at boot time.

So extend crashkernel CMA reservation support to riscv. The following
changes are made to enable CMA reservation:

- Parse and obtain the CMA reservation size along with other crashkernel
  parameters.
- Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
- Include the CMA-reserved ranges for kdump kernel to use, which was
  already done in of_kexec_alloc_and_setup_fdt().
- Exclude the CMA-reserved ranges from the crash kernel memory to
  prevent them from being exported through /proc/vmcore, which was
  already done in the crash core.

Update kernel-parameters.txt to document CMA support for crashkernel on
riscv architecture.

Cc: Paul Walmsley <pjw at kernel.org>
Cc: Palmer Dabbelt <palmer at dabbelt.com>
Cc: Albert Ou <aou at eecs.berkeley.edu>
Cc: Alexandre Ghiti <alex at ghiti.fr>
Acked-by: Baoquan He <bhe at redhat.com>
Acked-by: Mike Rapoport (Microsoft) <rppt at kernel.org>
Acked-by: Paul Walmsley <pjw at kernel.org> # arch/riscv
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 Documentation/admin-guide/kernel-parameters.txt | 16 ++++++++--------
 arch/riscv/kernel/machine_kexec_file.c          |  2 +-
 arch/riscv/mm/init.c                            |  5 +++--
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 52742fab49a9..3ff3ddd516cf 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1119,14 +1119,14 @@ Kernel parameters
 			It will be ignored when crashkernel=X,high is not used
 			or memory reserved is below 4G.
 	crashkernel=size[KMG],cma
-			[KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
-			CMA. This reservation is usable by the first system's
-			userspace memory and kernel movable allocations (memory
-			balloon, zswap). Pages allocated from this memory range
-			will not be included in the vmcore so this should not
-			be used if dumping of userspace memory is intended and
-			it has to be expected that some movable kernel pages
-			may be missing from the dump.
+			[KNL, X86, ARM64, RISCV, PPC] Reserve additional crash
+			kernel memory from CMA. This reservation is usable by
+			the first system's userspace memory and kernel movable
+			allocations (memory balloon, zswap). Pages allocated
+			from this memory range will not be included in the vmcore
+			so this should not be used if dumping of userspace memory
+			is intended and it has to be expected that some movable
+			kernel pages may be missing from the dump.
 
 			A standard crashkernel reservation, as described above,
 			is still needed to hold the crash kernel and initrd.
diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index d2bb61a8b6b6..075acf222fc0 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -46,7 +46,7 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
 
 unsigned int arch_get_system_nr_ranges(void)
 {
-	unsigned int nr_ranges = 2; /* For exclusion of crashkernel region */
+	unsigned int nr_ranges = 2 + crashk_cma_cnt; /* For exclusion of crashkernel region */
 
 	walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
 
diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c
index decd7df40fa4..c848454b8349 100644
--- a/arch/riscv/mm/init.c
+++ b/arch/riscv/mm/init.c
@@ -1295,7 +1295,7 @@ static inline void setup_vm_final(void)
  */
 static void __init arch_reserve_crashkernel(void)
 {
-	unsigned long long low_size = 0;
+	unsigned long long low_size = 0, cma_size = 0;
 	unsigned long long crash_base, crash_size;
 	bool high = false;
 	int ret;
@@ -1305,11 +1305,12 @@ static void __init arch_reserve_crashkernel(void)
 
 	ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
 				&crash_size, &crash_base,
-				&low_size, NULL, &high);
+				&low_size, &cma_size, &high);
 	if (ret)
 		return;
 
 	reserve_crashkernel_generic(crash_size, crash_base, low_size, high);
+	reserve_crashkernel_cma(cma_size);
 }
 
 void __init paging_init(void)
-- 
2.34.1



From ruanjinjie at huawei.com  Mon May 25 01:49:32 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Mon, 25 May 2026 16:49:32 +0800
Subject: [PATCH v14 17/17] arm64/crash: Add crash hotplug support
In-Reply-To: <20260525084932.934910-1-ruanjinjie@huawei.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
Message-ID: <20260525084932.934910-18-ruanjinjie@huawei.com>

Due to CPU/Memory hotplug or online/offline events, the elfcorehdr
(which describes the CPUs and memory of the crashed kernel) of kdump
image becomes outdated. Consequently, attempting dump collection with
an outdated elfcorehdr can lead to inaccurate dump collection.

The current solution to address the above issue involves monitoring
the CPU/Memory add/remove events in userspace using udev rules and
whenever there are changes in CPU and memory resources, the entire
kdump image is loaded again. The kdump image includes kernel, initrd,
elfcorehdr, FDT, purgatory. Given that only elfcorehdr gets outdated
due to CPU/Memory add/remove events, reloading the entire kdump image
is inefficient. More importantly, kdump remains inactive for a
substantial amount of time until the kdump reload completes.

To address the aforementioned issue, commit 247262756121 ("crash: add
generic infrastructure for crash hotplug support") added a generic
infrastructure that allows architectures to selectively update the
kdump image component during CPU or memory add/remove events within
the kernel itself.

In the event of a CPU or memory add/remove events, the generic crash
hotplug event handler, crash_handle_hotplug_event(), is triggered. It
then acquires the necessary locks to update the kdump image and invokes
the architecture-specific crash hotplug handler,
arch_crash_handle_hotplug_event(), to update the required kdump image
components.

[1] has supported virtual CPU hotplug in virtual machines for ARM64,
allowing vCPUs to be added or removed at runtime to meet Kubernetes
demands.

On ARM64, only memory add/remove events are handled. Here's why:

1. Physical CPU hotplug: Not supported on ARM64 hardware.

2. ACPI vCPU hotplug (KVM virtual machine):
   - vCPU hotplug is implemented as a static firmware policy where all
     possible vCPUs are pre-described in the MADT table at boot.
   - The vCPU status will be automatically updated after vCPU hotplug.
   - No FDT or elfcorehdr update needed.

3. Device tree booted Virtual Machine vCPU hotplug:
  - The elfcorehdr is built using for_each_possible_cpu(), so it
    already includes all possible CPUs and doesn't need updates.

For memory add/remove events, the elfcorehdr is updated to reflect
the current memory layout.

This patch adds the ARCH_SUPPORTS_CRASH_HOTPLUG config option and
implements:
- arch_crash_hotplug_support(): Check if hotplug update is supported
- arch_crash_get_elfcorehdr_size(): Return elfcorehdr buffer size
- arch_crash_handle_hotplug_event(): Handle memory hotplug events

This follows the same approach as x86 commit
ea53ad9cf73b ("x86/crash: add x86 crash hotplug support") and powerpc
commit b741092d5976 ("powerpc/crash: add crash CPU hotplug support")
and commit 849599b702ef ("powerpc/crash: add crash memory hotplug
support").

The test is based on the following QEMU version:
	https://github.com/salil-mehta/qemu.git virt-cpuhp-armv8/rfc-v2

Replace your '-smp' argument with something like:
 | -smp cpus=1,maxcpus=3,cores=3,threads=1,sockets=1

then feed the following to the Qemu montior to hotplug vCPU;
 | (qemu) device_add driver=host-arm-cpu,core-id=1,id=cpu1
 | (qemu) device_del cpu1

feed the following to the Qemu montior to hotplug memory;
 | (qemu) object_add memory-backend-ram,id=mem1,size=256M
 | (qemu) device_add pc-dimm,id=dimm1,memdev=mem1
 | (qemu) device_del dimm1

The qemu startup configuration is as follows:
qemu-system-aarch64 \
		-M virt,gic-version=3,acpi=on,highmem=on \
		-enable-kvm \
		-cpu host \
		-kernel Image \
		-smp cpus=1,maxcpus=3,cores=3,threads=1,sockets=1 \
		-bios /usr/share/edk2/aarch64/QEMU_EFI.fd \
		-m 2G,slots=64,maxmem=16G \
		-nographic \
		-no-reboot \
		-device virtio-rng-pci \
		-append "root=/dev/vda rw console=ttyAMA0 kgdboc=ttyAMA0,115200 \
			earlycon acpi=on crashkernel=512M" \
		-drive if=none,file=images/rootfs.ext4,format=raw,id=hd0 \
		-device virtio-blk-device,drive=hd0 \

There are two system calls, `kexec_file_load` and `kexec_load`, used to
load the kdump image. Only kexec_file_load syscall way is tested now.

This patch is based on following rework:
	https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie at huawei.com/

Cc: Catalin Marinas <catalin.marinas at arm.com>
Cc: Will Deacon <will at kernel.org>
Cc: Baoquan He <bhe at redhat.com>
Cc: "Mike Rapoport (Microsoft)" <rppt at kernel.org>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Breno Leitao <leitao at debian.org>
Cc: Kees Cook <kees at kernel.org>
[1]: https://lore.kernel.org/all/20240529133446.28446-1-Jonathan.Cameron at huawei.com/
Signed-off-by: Jinjie Ruan <ruanjinjie at huawei.com>
---
 arch/arm64/Kconfig                     |   3 +
 arch/arm64/include/asm/kexec.h         |  11 +++
 arch/arm64/kernel/Makefile             |   1 +
 arch/arm64/kernel/crash.c              | 125 +++++++++++++++++++++++++
 arch/arm64/kernel/machine_kexec_file.c |  23 ++++-
 5 files changed, 161 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm64/kernel/crash.c

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fe60738e5943..9091c67e1cc2 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1609,6 +1609,9 @@ config ARCH_DEFAULT_CRASH_DUMP
 config ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION
 	def_bool CRASH_RESERVE
 
+config ARCH_SUPPORTS_CRASH_HOTPLUG
+	def_bool y
+
 config TRANS_TABLE
 	def_bool y
 	depends on HIBERNATION || KEXEC_CORE
diff --git a/arch/arm64/include/asm/kexec.h b/arch/arm64/include/asm/kexec.h
index cc2f36b1b0d4..f338d162dec1 100644
--- a/arch/arm64/include/asm/kexec.h
+++ b/arch/arm64/include/asm/kexec.h
@@ -131,6 +131,17 @@ extern int load_other_segments(struct kimage *image,
 extern int prepare_elf_headers(void **addr, unsigned long *sz);
 #endif
 
+#ifdef CONFIG_CRASH_HOTPLUG
+void arch_crash_handle_hotplug_event(struct kimage *image, void *arg);
+#define arch_crash_handle_hotplug_event arch_crash_handle_hotplug_event
+
+int arch_crash_hotplug_support(struct kimage *image, unsigned long kexec_flags);
+#define arch_crash_hotplug_support arch_crash_hotplug_support
+
+unsigned int arch_crash_get_elfcorehdr_size(void);
+#define crash_get_elfcorehdr_size arch_crash_get_elfcorehdr_size
+#endif
+
 #endif /* __ASSEMBLER__ */
 
 #endif
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 74b76bb70452..629e962813ab 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_KEXEC_FILE)		+= machine_kexec_file.o kexec_image.o
 obj-$(CONFIG_ARM64_RELOC_TEST)		+= arm64-reloc-test.o
 arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
 obj-$(CONFIG_CRASH_DUMP)		+= crash_dump.o
+obj-$(CONFIG_CRASH_HOTPLUG)		+= crash.o
 obj-$(CONFIG_VMCORE_INFO)		+= vmcore_info.o
 obj-$(CONFIG_ARM_SDE_INTERFACE)		+= sdei.o
 obj-$(CONFIG_ARM64_PTR_AUTH)		+= pointer_auth.o
diff --git a/arch/arm64/kernel/crash.c b/arch/arm64/kernel/crash.c
new file mode 100644
index 000000000000..2114375820da
--- /dev/null
+++ b/arch/arm64/kernel/crash.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Architecture specific functions for kexec based crash dumps.
+ */
+
+#define pr_fmt(fmt)	"crash hp: " fmt
+
+#include <linux/kexec.h>
+#include <linux/elf.h>
+#include <linux/vmalloc.h>
+#include <linux/crash_core.h>
+
+#include <asm/kexec.h>
+
+#ifdef CONFIG_CRASH_HOTPLUG
+
+int arch_crash_hotplug_support(struct kimage *image, unsigned long kexec_flags)
+{
+#ifdef CONFIG_KEXEC_FILE
+	if (image->file_mode)
+		return 1;
+#endif
+	/*
+	 * For kexec_load syscall, crash hotplug support requires
+	 * KEXEC_CRASH_HOTPLUG_SUPPORT flag to be passed by userspace.
+	 */
+	return kexec_flags & KEXEC_CRASH_HOTPLUG_SUPPORT;
+}
+
+unsigned int arch_crash_get_elfcorehdr_size(void)
+{
+	unsigned int phdr_cnt;
+
+	/* A program header for possible CPUs, vmcoreinfo and kernel_map */
+	phdr_cnt = 2 + num_possible_cpus();
+	if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
+		phdr_cnt += CONFIG_CRASH_MAX_MEMORY_RANGES;
+
+	return sizeof(Elf64_Ehdr) + phdr_cnt * sizeof(Elf64_Phdr);
+}
+
+/**
+ * update_crash_elfcorehdr() - Recreate the elfcorehdr and replace it with old
+ *			       elfcorehdr in the kexec segment array.
+ * @image: the active struct kimage
+ */
+static void update_crash_elfcorehdr(struct kimage *image)
+{
+	void *elfbuf = NULL, *old_elfcorehdr;
+	unsigned long mem, memsz;
+	unsigned long elfsz = 0;
+
+	/*
+	 * Create the new elfcorehdr reflecting the changes to CPU and/or
+	 * memory resources.
+	 */
+	if (crash_prepare_headers(true, &elfbuf, &elfsz, NULL)) {
+		pr_err("unable to create new elfcorehdr");
+		goto out;
+	}
+
+	/*
+	 * Obtain address and size of the elfcorehdr segment, and
+	 * check it against the new elfcorehdr buffer.
+	 */
+	mem = image->segment[image->elfcorehdr_index].mem;
+	memsz = image->segment[image->elfcorehdr_index].memsz;
+	if (elfsz > memsz) {
+		pr_err("update elfcorehdr elfsz %lu > memsz %lu",
+			elfsz, memsz);
+		goto out;
+	}
+
+	/*
+	 * Copy new elfcorehdr over the old elfcorehdr at destination.
+	 */
+	old_elfcorehdr = (void *)__va(mem);
+	if (!old_elfcorehdr) {
+		pr_err("mapping elfcorehdr segment failed\n");
+		goto out;
+	}
+
+	/*
+	 * Temporarily invalidate the crash image while the
+	 * elfcorehdr is updated.
+	 */
+	xchg(&kexec_crash_image, NULL);
+	memcpy_flushcache(old_elfcorehdr, elfbuf, elfsz);
+	xchg(&kexec_crash_image, image);
+	pr_debug("updated elfcorehdr\n");
+
+out:
+	vfree(elfbuf);
+}
+
+/**
+ * arch_crash_handle_hotplug_event() - Handle hotplug elfcorehdr changes
+ * @image: a pointer to kexec_crash_image
+ * @arg: struct memory_notify handler for memory hotplug case and
+ *       NULL for CPU hotplug case.
+ *
+ * Update the kdump image based on the type of hotplug event:
+ * - CPU add and remove: No action is needed.
+ * - Memory add/remove: Update the elfcorehdr to reflect the current memory layout.
+ *
+ * Prepare the new elfcorehdr and replace the existing elfcorehdr.
+ */
+void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
+{
+	switch (image->hp_action) {
+	case KEXEC_CRASH_HP_ADD_CPU:
+		fallthrough;
+	case KEXEC_CRASH_HP_REMOVE_CPU:
+		if (image->file_mode || image->elfcorehdr_updated)
+			return;
+		fallthrough;
+	case KEXEC_CRASH_HP_ADD_MEMORY:
+	case KEXEC_CRASH_HP_REMOVE_MEMORY:
+		update_crash_elfcorehdr(image);
+		return;
+	default:
+		pr_warn_once("Unknown hotplug action\n");
+	}
+}
+#endif /* CONFIG_CRASH_HOTPLUG */
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index d2985ce62306..781febd0f6db 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -82,10 +82,11 @@ int load_other_segments(struct kimage *image,
 			char *cmdline, void *headers,
 			unsigned long headers_sz)
 {
-	struct kexec_buf kbuf = {};
-	void *dtb = NULL;
 	unsigned long initrd_load_addr = 0, dtb_len,
 		      orig_segments = image->nr_segments;
+	struct kexec_buf kbuf = {};
+	unsigned long pnum = 0;
+	void *dtb = NULL;
 	int ret = 0;
 
 	kbuf.image = image;
@@ -98,6 +99,23 @@ int load_other_segments(struct kimage *image,
 		kbuf.bufsz = headers_sz;
 		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
 		kbuf.memsz = headers_sz;
+
+#ifdef CONFIG_CRASH_HOTPLUG
+		/*
+		 * The elfcorehdr segment size accounts for VMCOREINFO, kernel_map
+		 * maximum CPUs and maximum memory ranges.
+		 */
+		if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
+			pnum = 2 + num_possible_cpus() + CONFIG_CRASH_MAX_MEMORY_RANGES;
+		else
+			pnum += 2 + num_possible_cpus();
+
+		if (pnum < (unsigned long)PN_XNUM)
+			kbuf.memsz = pnum * sizeof(Elf64_Phdr) + sizeof(Elf64_Ehdr);
+		else
+			pr_err("number of Phdrs %lu exceeds max\n", pnum);
+#endif
+
 		kbuf.buf_align = SZ_64K; /* largest supported page size */
 		kbuf.buf_max = ULONG_MAX;
 		kbuf.top_down = true;
@@ -108,6 +126,7 @@ int load_other_segments(struct kimage *image,
 			goto out_err;
 		}
 		image->elf_load_addr = kbuf.mem;
+		image->elf_headers_sz = kbuf.memsz;
 
 		kexec_dprintk("Loaded elf core header at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
 			      image->elf_load_addr, kbuf.bufsz, kbuf.memsz);
-- 
2.34.1



From ruanjinjie at huawei.com  Thu May 28 00:38:31 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Thu, 28 May 2026 15:38:31 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA
 reservation
In-Reply-To: <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
 <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
Message-ID: <7ddab21c-bdb2-4a43-87fa-198b26d82f20@huawei.com>



On 5/25/2026 6:14 PM, Huacai Chen wrote:
> Hi, Jinjie,
> 
> On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
>>
>> The crash memory allocation, and the exclude of crashk_res, crashk_low_res
>> and crashk_cma memory are almost identical across different architectures,
>> This patch set handle them in crash core in a general way, which eliminate
>> a lot of duplication code.
>>
>> And add support for crashkernel CMA reservation for arm64 and riscv.
>>
>> Also add support for arm64 crash hotplug.
>>
>> This patch set is rebased on v7.1-rc1.
>>
>> Basic second kernel boot test were performed on QEMU platforms for x86,
>> ARM64 and RISC-V architectures with the following parameters:
>>
>>         "cma=256M crashkernel=4G crashkernel=64M,cma"
>>
>> For first kernel, there will be such log:
>>
>>         # dmesg | grep crash
>>         [    0.000000] crashkernel low memory reserved: 0xe8000000 - 0xf0000000 (128 MB)
>>         [    0.000000] crashkernel reserved: 0x000000023e600000 - 0x000000033e600000 (4096 MB)
>>         [    0.000000] crashkernel CMA reserved: 64 MB in 1 ranges
>>
>>         # dmesg | grep cma
>>         [    0.000000] cma: Reserved 256 MiB at 0x00000000f0000000
>>         [    0.000000] cma: Reserved 64 MiB at 0x0000000100000000
>>
>> For second kernel, there will be such log:
>>
>>         [    0.000000] OF: fdt: Looking for usable-memory-range property...
>>         [    0.000000] OF: fdt: cap_mem_regions[0]: base=0x000000023e600000, size=0x0000000100000000
>>         [    0.000000] OF: fdt: cap_mem_regions[1]: base=0x00000000e8000000, size=0x0000000008000000
>>         [    0.000000] OF: fdt: cap_mem_regions[2]: base=0x0000000100000000, size=0x0000000004000000
>>
>> Changes in v14:
>> - Fix image->elf_headers memory leak during retry loop for arm64 as Sashiko
>>   AI code review pointed out.
>> - Solve the hotplug notifier arch_crash_handle_hotplug_event() AA
>>   self-deadlock problem as Sashiko AI code review pointed out.
>> - Fix the TOCTOU issue in prepare_elf_headers() by get_online_mems().
>> - -ENOMEM -> -EAGAIN as Breno suggested.
>> - Add support for arm64 crash hotplug.
>> - Link to v13: https://lore.kernel.org/all/20260511030454.1730881-1-ruanjinjie at huawei.com/
>>
>> Changes in v13:
>> - Rebased on v7.1-rc1.
>> - Update the commit message.
>> - Add Reviewed-by.
>> - Link to v12: https://lore.kernel.org/all/20260402072701.628293-1-ruanjinjie at huawei.com/
>>
>> Changes in v12:
>> - Remove the unused "nr_mem_ranges" for x86.
>> - Add "Fix crashk_low_res not exclude bug" test log.
>> - Provide a separate patch for each architecture for using
>>   crash_prepare_headers(), which will make the review more convenient.
>> - Add Reviewed-by and Tested-by.
>> - Link to v11: https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie at huawei.com/
>>
>> Changes in v11:
>> - Avoid silently drop crash memory if the crash kernel is built without
>>   CONFIG_CMA.
>> - Remove unnecessary "cmem->nr_ranges = 0" for arch_crash_populate_cmem()
>>   as we use kvzalloc().
>> - Provide a separate patch for each architecture to fix the existing
>>   buffer overflow issue.
>> - Add Acked-bys for arm64.
>>
>> Changes in v10:
>> - Fix crashk_low_res not excluded bug in the existing
>>   RISC-V code.
>> - Fix an existing memory leak issue in the existing PowerPC code.
>> - Fix the ordering issue of adding CMA ranges to
>>   "linux,usable-memory-range".
>> - Fix an existing concurrency issue. A Concurrent memory hotplug may occur
>>   between reading memblock and attempting to fill cmem during kexec_load()
>>   for almost all existing architectures.
>> - Link to v9: https://lore.kernel.org/all/20260323072745.2481719-1-ruanjinjie at huawei.com/
>>
>> Changes in v9:
>> - Collect Reviewed-by and Acked-by, and prepare for Sashiko AI review.
>> - Link to v8: https://lore.kernel.org/all/20260302035315.3892241-1-ruanjinjie at huawei.com/
>>
>> Changes in v8:
>> - Fix the build issues reported by kernel test robot and Sourabh.
>> - Link to v7: https://lore.kernel.org/all/20260226130437.1867658-1-ruanjinjie at huawei.com/
>>
>> Changes in v7:
>> - Correct the inclusion of CMA-reserved ranges for kdump kernel in of/kexec
>>   for arm64 and riscv.
>> - Add Acked-by.
>> - Link to v6: https://lore.kernel.org/all/20260224085342.387996-1-ruanjinjie at huawei.com/
>>
>> Changes in v6:
>> - Update the crash core exclude code as Mike suggested.
>> - Rebased on v7.0-rc1.
>> - Add acked-by.
>> - Link to v5: https://lore.kernel.org/all/20260212101001.343158-1-ruanjinjie at huawei.com/
>>
>> Jinjie Ruan (16):
>>   riscv: kexec_file: Fix crashk_low_res not exclude bug
>>   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
>>   arm64: kexec: Fix image->elf_headers memory leak during retry loop
>>   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
>>   arm64: kexec_file: Fix potential buffer overflow in
>>     prepare_elf_headers()
>>   riscv: kexec_file: Fix potential buffer overflow in
>>     prepare_elf_headers()
>>   LoongArch: kexec: Fix potential buffer overflow in
>>     prepare_elf_headers()
>>   crash: Add crash_prepare_headers() to exclude crash kernel memory
>>   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
>>   x86/kexec: Use crash_prepare_headers() helper to simplify code
>>   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
>>   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
>>   crash: Use crash_exclude_core_ranges() on powerpc
>>   arm64: kexec: Add support for crashkernel CMA reservation
>>   riscv: kexec: Add support for crashkernel CMA reservation
>>   arm64/crash: Add crash hotplug support
> I have some bikesheedings about the subjects. Can we unify the prefix formats?
> x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....
> 
> Huacai

Sure, will update it in next version.

> 
>>
>> Sourabh Jain (1):
>>   powerpc/crash: sort crash memory ranges before preparing elfcorehdr
>>
>>  .../admin-guide/kernel-parameters.txt         |  16 +--
>>  arch/arm64/Kconfig                            |   3 +
>>  arch/arm64/include/asm/kexec.h                |  14 +-
>>  arch/arm64/kernel/Makefile                    |   1 +
>>  arch/arm64/kernel/crash.c                     | 125 ++++++++++++++++++
>>  arch/arm64/kernel/kexec_image.c               |  22 ++-
>>  arch/arm64/kernel/machine_kexec_file.c        |  78 ++++++-----
>>  arch/arm64/mm/init.c                          |   5 +-
>>  arch/loongarch/kernel/machine_kexec_file.c    |  43 +++---
>>  arch/powerpc/include/asm/kexec_ranges.h       |   1 -
>>  arch/powerpc/kexec/crash.c                    |   7 +-
>>  arch/powerpc/kexec/ranges.c                   | 101 +-------------
>>  arch/riscv/kernel/machine_kexec_file.c        |  42 +++---
>>  arch/riscv/mm/init.c                          |   5 +-
>>  arch/x86/kernel/crash.c                       |  92 ++-----------
>>  drivers/of/fdt.c                              |   9 +-
>>  drivers/of/kexec.c                            |   9 ++
>>  include/linux/crash_core.h                    |  11 ++
>>  include/linux/crash_reserve.h                 |   4 +-
>>  kernel/crash_core.c                           | 102 +++++++++++++-
>>  20 files changed, 392 insertions(+), 298 deletions(-)
>>  create mode 100644 arch/arm64/kernel/crash.c
>>
>> --
>> 2.34.1
>>
> 



From ruanjinjie at huawei.com  Thu May 28 00:39:27 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Thu, 28 May 2026 15:39:27 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA
 reservation
In-Reply-To: <ahQ0WRiMsB011tym@kernel.org>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
 <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
 <ahQ0WRiMsB011tym@kernel.org>
Message-ID: <8aaa1d2e-09cc-4d58-b90e-1869d1bf55b2@huawei.com>



On 5/25/2026 7:36 PM, Mike Rapoport wrote:
> Hi Huacai,
> 
> On Mon, May 25, 2026 at 06:14:52PM +0800, Huacai Chen wrote:
>> On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
>>>
>>> Jinjie Ruan (16):
>>>   riscv: kexec_file: Fix crashk_low_res not exclude bug
>>>   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
>>>   arm64: kexec: Fix image->elf_headers memory leak during retry loop
>>>   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
>>>   arm64: kexec_file: Fix potential buffer overflow in
>>>     prepare_elf_headers()
>>>   riscv: kexec_file: Fix potential buffer overflow in
>>>     prepare_elf_headers()
>>>   LoongArch: kexec: Fix potential buffer overflow in
>>>     prepare_elf_headers()
>>>   crash: Add crash_prepare_headers() to exclude crash kernel memory
>>>   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
>>>   x86/kexec: Use crash_prepare_headers() helper to simplify code
>>>   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
>>>   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
>>>   crash: Use crash_exclude_core_ranges() on powerpc
>>>   arm64: kexec: Add support for crashkernel CMA reservation
>>>   riscv: kexec: Add support for crashkernel CMA reservation
>>>   arm64/crash: Add crash hotplug support
>> I have some bikesheedings about the subjects. Can we unify the prefix formats?
>> x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....
> 
> It might look weird in the series, but powerpc and x86 use '/' and arm64 and
> riscv use ':' so in the logs in arch/ this would be actually fine.
> 
> With the only exception of arm64/crash that should have been arm64: crash:

Thanks, will update it in next version.

>  
>> Huacai
> 



From ruanjinjie at huawei.com  Thu May 28 00:48:15 2026
From: ruanjinjie at huawei.com (Jinjie Ruan)
Date: Thu, 28 May 2026 15:48:15 +0800
Subject: [PATCH v14 00/17] arm64/riscv: Add support for crashkernel CMA
 reservation
In-Reply-To: <CAAhV-H5x3m08=acrUr2VOAQ8-r2PhN1xTdr3NihHocoPM5=3hQ@mail.gmail.com>
References: <20260525084932.934910-1-ruanjinjie@huawei.com>
 <CAAhV-H4NA7vgyxKnK+N_3C6pWBnwXc2URUyLh_h1m-MO=MnsGQ@mail.gmail.com>
 <ahQ0WRiMsB011tym@kernel.org>
 <CAAhV-H5x3m08=acrUr2VOAQ8-r2PhN1xTdr3NihHocoPM5=3hQ@mail.gmail.com>
Message-ID: <08ab6756-091c-4bee-9d49-cb84d0be027c@huawei.com>



On 5/25/2026 9:17 PM, Huacai Chen wrote:
> On Mon, May 25, 2026 at 7:37?PM Mike Rapoport <rppt at kernel.org> wrote:
>>
>> Hi Huacai,
>>
>> On Mon, May 25, 2026 at 06:14:52PM +0800, Huacai Chen wrote:
>>> On Mon, May 25, 2026 at 4:50?PM Jinjie Ruan <ruanjinjie at huawei.com> wrote:
>>>>
>>>> Jinjie Ruan (16):
>>>>   riscv: kexec_file: Fix crashk_low_res not exclude bug
>>>>   powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
>>>>   arm64: kexec: Fix image->elf_headers memory leak during retry loop
>>>>   x86/kexec: Fix potential buffer overflow in prepare_elf_headers()
>>>>   arm64: kexec_file: Fix potential buffer overflow in
>>>>     prepare_elf_headers()
>>>>   riscv: kexec_file: Fix potential buffer overflow in
>>>>     prepare_elf_headers()
>>>>   LoongArch: kexec: Fix potential buffer overflow in
>>>>     prepare_elf_headers()
>>>>   crash: Add crash_prepare_headers() to exclude crash kernel memory
>>>>   arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
>>>>   x86/kexec: Use crash_prepare_headers() helper to simplify code
>>>>   riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
>>>>   LoongArch: kexec: Use crash_prepare_headers() helper to simplify code
>>>>   crash: Use crash_exclude_core_ranges() on powerpc
>>>>   arm64: kexec: Add support for crashkernel CMA reservation
>>>>   riscv: kexec: Add support for crashkernel CMA reservation
>>>>   arm64/crash: Add crash hotplug support
>>> I have some bikesheedings about the subjects. Can we unify the prefix formats?
>>> x86/kexec, arm64: kexec_file, riscv: kexec_file, LoongArch: kexec .....
>>
>> It might look weird in the series, but powerpc and x86 use '/' and arm64 and
>> riscv use ':' so in the logs in arch/ this would be actually fine.
> OK, but at least we can unify kexec and kexec_file.

That looks fine.

Jinjie

> 
> Huacai
> 
>>
>> With the only exception of arm64/crash that should have been arm64: crash:
>>
>>> Huacai
>>
>> --
>> Sincerely yours,
>> Mike.
> 



