Skip to contentSkip to conversation

kho: add support for preserving vmalloc allocations

PATCH In review Mike Rapoport 6 participants 18 messages Sep 21, 2025 – Sep 25, 2025

Subsystems (6)
lore
Share & tools
lore

Mike Rapoport

author maintainer
#0 lore raw

v5 cover kho: add support for preserving vmalloc allocations

From: "Mike Rapoport (Microsoft)" <[email protected]>

Hi,

Following the discussion about preservation of memfd with LUO [1] these patches add support for preserving vmalloc allocations.

Any KHO uses case presumes that there's a data structure that lists physical addresses of preserved folios (and potentially some additional metadata). Allowing vmalloc preservations with KHO allows scalable preservation of such data structures.

For instance, instead of allocating array describing preserved folios in the fdt, memfd preservation can use vmalloc:

        preserved_folios = vmalloc_array(nr_folios, sizeof(*preserved_folios));
        memfd_luo_preserve_folios(preserved_folios, folios, nr_folios);
        kho_preserve_vmalloc(preserved_folios, &folios_info);

[1] https://lore.kernel.org/all/[email protected]

v5 changes:
* rebase on mm-new of September 17
* use kho_restore_pages() in kho_restore_vmalloc(). Required to resolve a conflict with "kho: make sure page being restored is actually from KHO"
* use more descriptive local variable names in kho_restore_vmalloc()

v4: https://lore.kernel.org/all/[email protected]
* rebase on mm-stable
* use 'struct kho_vmalloc' as preservation metadata instead of storing it in the first chunk
* separate KHO flags from VM_ flags
* unreserve vmalloc pages on error
* add checks for under- and overflow of preserved metadata

v3: https://lore.kernel.org/all/[email protected]
* rebase on mm-unstable

v2: https://lore.kernel.org/all/[email protected]
* support preservation of vmalloc backed by large pages
* add check for supported vmalloc flags and preserve the flags to be able to identify incompatible preservations
* don't use kho_preserve_phys()
* add kernel-doc

v1: https://lore.kernel.org/all/[email protected]

Mike Rapoport (Microsoft) (4):
kho: check if kho is finalized in __kho_preserve_order()
kho: replace kho_preserve_phys() with kho_preserve_pages()
kho: add support for preserving vmalloc allocations lib/test_kho: use kho_preserve_vmalloc instead of storing addresses in
fdt

 include/linux/kexec_handover.h |  33 ++-
 kernel/kexec_handover.c        | 361 +++++++++++++++++++++++++++++----
 lib/test_kho.c                 |  41 ++--
 mm/memblock.c                  |   4 +-
 4 files changed, 381 insertions(+), 58 deletions(-)

base-commit: c565f9816932dc8d164bd8c69acf077840565ce7

Signature · 1 line
2.50.1
author maintainer
#1 lore raw

PATCH v5 1/4 kho: check if kho is finalized in __kho_preserve_order()

From: "Mike Rapoport (Microsoft)" <[email protected]>

Instead of checking if kho is finalized in each caller of __kho_preserve_order(), do it in the core function itself.

---
 kernel/kexec_handover.c | 55 +++++++++++++++++++----------------------
 1 file changed, 26 insertions(+), 29 deletions(-)
kernel/kexec_handover.c +26 -29
index 555488eb1a18..85f1bf38b123 100644
@@ -107,6 +107,29 @@ struct kho_serialization {
 	struct khoser_mem_chunk *preserved_mem_map;
 };
 
+struct kho_out {
+	struct blocking_notifier_head chain_head;
+
+	struct dentry *dir;
+
+	struct mutex lock; /* protects KHO FDT finalization */
+
+	struct kho_serialization ser;
+	bool finalized;
+};
+
+static struct kho_out kho_out = {
+	.chain_head = BLOCKING_NOTIFIER_INIT(kho_out.chain_head),
+	.lock = __MUTEX_INITIALIZER(kho_out.lock),
+	.ser = {
+		.fdt_list = LIST_HEAD_INIT(kho_out.ser.fdt_list),
+		.track = {
+			.orders = XARRAY_INIT(kho_out.ser.track.orders, 0),
+		},
+	},
+	.finalized = false,
+};
+
 static void *xa_load_or_alloc(struct xarray *xa, unsigned long index, size_t sz)
 {
 	void *elm, *res;
@@ -165,6 +188,9 @@ static int __kho_preserve_order(struct kho_mem_track *track, unsigned long pfn,
 
 	might_sleep();
 
+	if (kho_out.finalized)
+		return -EBUSY;
+
 	physxa = xa_load(&track->orders, order);
 	if (!physxa) {
 		int err;
@@ -667,29 +693,6 @@ int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt)
 }
 EXPORT_SYMBOL_GPL(kho_add_subtree);
 
-struct kho_out {
-	struct blocking_notifier_head chain_head;
-
-	struct dentry *dir;
-
-	struct mutex lock; /* protects KHO FDT finalization */
-
-	struct kho_serialization ser;
-	bool finalized;
-};
-
-static struct kho_out kho_out = {
-	.chain_head = BLOCKING_NOTIFIER_INIT(kho_out.chain_head),
-	.lock = __MUTEX_INITIALIZER(kho_out.lock),
-	.ser = {
-		.fdt_list = LIST_HEAD_INIT(kho_out.ser.fdt_list),
-		.track = {
-			.orders = XARRAY_INIT(kho_out.ser.track.orders, 0),
-		},
-	},
-	.finalized = false,
-};
-
 int register_kho_notifier(struct notifier_block *nb)
 {
 	return blocking_notifier_chain_register(&kho_out.chain_head, nb);
@@ -717,9 +720,6 @@ int kho_preserve_folio(struct folio *folio)
 	const unsigned int order = folio_order(folio);
 	struct kho_mem_track *track = &kho_out.ser.track;
 
-	if (kho_out.finalized)
-		return -EBUSY;
-
 	return __kho_preserve_order(track, pfn, order);
 }
 EXPORT_SYMBOL_GPL(kho_preserve_folio);
@@ -743,9 +743,6 @@ int kho_preserve_phys(phys_addr_t phys, size_t size)
 	int err = 0;
 	struct kho_mem_track *track = &kho_out.ser.track;
 
-	if (kho_out.finalized)
-		return -EBUSY;
-
 	if (!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(size))
 		return -EINVAL;
 
Signature · 1 line
2.50.1
author maintainer
#2 lore raw

PATCH v5 2/4 kho: replace kho_preserve_phys() with kho_preserve_pages()

In reply to Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

to make it clear that KHO operates on pages rather than on a random physical address.

The kho_preserve_pages() will be also used in upcoming support for vmalloc preservation.

---
 include/linux/kexec_handover.h |  5 +++--
 kernel/kexec_handover.c        | 25 +++++++++++--------------
 mm/memblock.c                  |  4 +++-
 3 files changed, 17 insertions(+), 17 deletions(-)
include/linux/kexec_handover.h +3 -2
index 348844cffb13..cc5c49b0612b 100644
@@ -18,6 +18,7 @@ enum kho_event {
 
 struct folio;
 struct notifier_block;
+struct page;
 
 #define DECLARE_KHOSER_PTR(name, type) \
 	union {                        \
@@ -42,7 +43,7 @@ struct kho_serialization;
 bool kho_is_enabled(void);
 
 int kho_preserve_folio(struct folio *folio);
-int kho_preserve_phys(phys_addr_t phys, size_t size);
+int kho_preserve_pages(struct page *page, unsigned int nr_pages);
 struct folio *kho_restore_folio(phys_addr_t phys);
 int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
@@ -65,7 +66,7 @@ static inline int kho_preserve_folio(struct folio *folio)
 	return -EOPNOTSUPP;
 }
 
-static inline int kho_preserve_phys(phys_addr_t phys, size_t size)
+static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
 {
 	return -EOPNOTSUPP;
 }
kernel/kexec_handover.c +11 -14
index 85f1bf38b123..fd80be3b12fd 100644
@@ -725,26 +725,23 @@ int kho_preserve_folio(struct folio *folio)
 EXPORT_SYMBOL_GPL(kho_preserve_folio);
 
 /**
- * kho_preserve_phys - preserve a physically contiguous range across kexec.
- * @phys: physical address of the range.
- * @size: size of the range.
+ * kho_preserve_pages - preserve contiguous pages across kexec
+ * @page: first page in the list.
+ * @nr_pages: number of pages.
  *
- * Instructs KHO to preserve the memory range from @phys to @phys + @size
- * across kexec.
+ * Preserve a contiguous list of order 0 pages. Must be restored using
+ * kho_restore_pages() to ensure the pages are restored properly as order 0.
  *
  * Return: 0 on success, error code on failure
  */
-int kho_preserve_phys(phys_addr_t phys, size_t size)
+int kho_preserve_pages(struct page *page, unsigned int nr_pages)
 {
-	unsigned long pfn = PHYS_PFN(phys);
+	struct kho_mem_track *track = &kho_out.ser.track;
+	const unsigned long start_pfn = page_to_pfn(page);
+	const unsigned long end_pfn = start_pfn + nr_pages;
+	unsigned long pfn = start_pfn;
 	unsigned long failed_pfn = 0;
-	const unsigned long start_pfn = pfn;
-	const unsigned long end_pfn = PHYS_PFN(phys + size);
 	int err = 0;
-	struct kho_mem_track *track = &kho_out.ser.track;
-
-	if (!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(size))
-		return -EINVAL;
 
 	while (pfn < end_pfn) {
 		const unsigned int order =
@@ -764,7 +761,7 @@ int kho_preserve_phys(phys_addr_t phys, size_t size)
 
 	return err;
 }
-EXPORT_SYMBOL_GPL(kho_preserve_phys);
+EXPORT_SYMBOL_GPL(kho_preserve_pages);
 
 /* Handling for debug/kho/out */
 
mm/memblock.c +3 -1
index 117d963e677c..6ec3eaa4e8d1 100644
@@ -2516,8 +2516,10 @@ static int reserve_mem_kho_finalize(struct kho_serialization *ser)
 
 	for (i = 0; i < reserved_mem_count; i++) {
 		struct reserve_mem_table *map = &reserved_mem_table[i];
+		struct page *page = phys_to_page(map->start);
+		unsigned int nr_pages = map->size >> PAGE_SHIFT;
 
-		err |= kho_preserve_phys(map->start, map->size);
+		err |= kho_preserve_pages(page, nr_pages);
 	}
 
 	err |= kho_preserve_folio(page_folio(kho_fdt));
Signature · 1 line
2.50.1
Reviewed-by on v5 2/4
#3 lore raw

Re: [PATCH v5 2/4] kho: replace kho_preserve_phys() with kho_preserve_pages()

Quoted 8 lines · Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

to make it clear that KHO operates on pages rather than on a random physical address.

The kho_preserve_pages() will be also used in upcoming support for vmalloc preservation.

Quoted 6 lines
---
 include/linux/kexec_handover.h |  5 +++--
 kernel/kexec_handover.c        | 25 +++++++++++--------------
 mm/memblock.c                  |  4 +++-
 3 files changed, 17 insertions(+), 17 deletions(-)

Jason

maintainer Reviewed-by on v5 2/4
#4 lore raw

Re: [PATCH v5 2/4] kho: replace kho_preserve_phys() with kho_preserve_pages()

In reply to Mike Rapoport
Quoted 9 lines · Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

to make it clear that KHO operates on pages rather than on a random physical address.

The kho_preserve_pages() will be also used in upcoming support for vmalloc preservation.

[...]

Signature · 2 lines
Regards,
Pratyush Yadav
author maintainer
#5 lore raw

PATCH v5 3/4 kho: add support for preserving vmalloc allocations

In reply to Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

A vmalloc allocation is preserved using binary structure similar to global KHO memory tracker. It's a linked list of pages where each page is an array of physical address of pages in vmalloc area.

kho_preserve_vmalloc() hands out the physical address of the head page to the caller. This address is used as the argument to kho_vmalloc_restore() to restore the mapping in the vmalloc address space and populate it with the preserved pages.

---
 include/linux/kexec_handover.h |  28 ++++
 kernel/kexec_handover.c        | 281 +++++++++++++++++++++++++++++++++
 2 files changed, 309 insertions(+)
include/linux/kexec_handover.h +28 -0
index cc5c49b0612b..49e36d4ae5dc 100644
@@ -39,12 +39,23 @@ struct page;
 
 struct kho_serialization;
 
+struct kho_vmalloc_chunk;
+struct kho_vmalloc {
+        DECLARE_KHOSER_PTR(first, struct kho_vmalloc_chunk *);
+        unsigned int total_pages;
+        unsigned short flags;
+        unsigned short order;
+};
+
 #ifdef CONFIG_KEXEC_HANDOVER
 bool kho_is_enabled(void);
 
 int kho_preserve_folio(struct folio *folio);
 int kho_preserve_pages(struct page *page, unsigned int nr_pages);
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
 struct folio *kho_restore_folio(phys_addr_t phys);
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages);
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
 int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
 
@@ -71,11 +82,28 @@ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_preserve_vmalloc(void *ptr,
+				       struct kho_vmalloc *preservation)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline struct folio *kho_restore_folio(phys_addr_t phys)
 {
 	return NULL;
 }
 
+static inline struct page *kho_restore_pages(phys_addr_t phys,
+					     unsigned int nr_pages)
+{
+	return NULL;
+}
+
+static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+	return NULL;
+}
+
 static inline int kho_add_subtree(struct kho_serialization *ser,
 				  const char *name, void *fdt)
 {
kernel/kexec_handover.c +281 -0

299 lines not on this page · raw message

Signature · 1 line
2.50.1
Reviewed-by on v5 3/4
#6 lore raw

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 818

Quoted 3 lines · Mike Rapoport
kernel/kexec_handover.c +3 -0
+/* KHO internal flags for vmalloc preservations */
+#define KHO_VMALLOC_ALLOC	0x0001
+#define KHO_VMALLOC_HUGE_VMAP	0x0002

Maybe something for a followup, but we should really move all these "ABI" structs and constants into clear ABI header files, include/linux/kho/abi/XX.h?

Having them sprinkled about makes it harder to notice when people propose to change them and that makes it harder to enforce ABI rules.

Quoted 35 lines
kernel/kexec_handover.c +35 -0
+static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)
+{
+	struct kho_vmalloc_chunk *chunk;
+	int err;
+
+	chunk = (struct kho_vmalloc_chunk *)get_zeroed_page(GFP_KERNEL);
+	if (!chunk)
+		return NULL;
+
+	err = kho_preserve_pages(virt_to_page(chunk), 1);
+	if (err)
+		goto err_free;
+	if (cur)
+		KHOSER_STORE_PTR(cur->hdr.next, chunk);
+	return chunk;
+
+err_free:
+	free_page((unsigned long)chunk);
+	return NULL;
+}
+
+static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk)
+{
+	struct kho_mem_track *track = &kho_out.ser.track;
+	unsigned long pfn = PHYS_PFN(virt_to_phys(chunk));
+
+	__kho_unpreserve(track, pfn, pfn + 1);
+
+	for (int i = 0; chunk->phys[i]; i++) {
+		pfn = PHYS_PFN(chunk->phys[i]);
+		__kho_unpreserve(track, pfn, pfn + 1);
+	}
+}
+
+static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 888

Quoted 10 lines
kernel/kexec_handover.c +10 -0
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(kho_vmalloc->first);
+
+	while (chunk) {
+		struct kho_vmalloc_chunk *tmp = chunk;
+
+		kho_vmalloc_unpreserve_chunk(chunk);
+
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		kfree(tmp);

Shouldn't this be free_page()?

Otherwise looks OK

Jason

maintainer
#7 lore raw
Quoted 5 lines · On Mon, 22 Sep 2025 10:19:48 -0300 Jason Gunthorpe <[email protected]> wrote:
Quoted 5 lines
kernel/kexec_handover.c +5 -0
+static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(kho_vmalloc->first);
+
+	while (chunk) {
Quoted 8 lines

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 888

Quoted 6 lines
kernel/kexec_handover.c +6 -0
+		struct kho_vmalloc_chunk *tmp = chunk;
+
+		kho_vmalloc_unpreserve_chunk(chunk);
+
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		kfree(tmp);

Shouldn't this be free_page()?

Or vfree()?

Not sure why this code works - I'll suspend the series from linux-next for now.

maintainer
#8 lore raw
Quoted 16 lines · Andrew Morton
Quoted 5 lines · On Mon, 22 Sep 2025 10:19:48 -0300 Jason Gunthorpe <[email protected]> wrote:
Quoted 5 lines
kernel/kexec_handover.c +5 -0
+static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(kho_vmalloc->first);
+
+	while (chunk) {
Quoted 8 lines

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 888

Quoted 6 lines
kernel/kexec_handover.c +6 -0
+		struct kho_vmalloc_chunk *tmp = chunk;
+
+		kho_vmalloc_unpreserve_chunk(chunk);
+
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		kfree(tmp);

Shouldn't this be free_page()?

Quoted 1 line

Or vfree()?

It should be free_page(). The chunk isn't in the vmalloc-ed area, it is the metadata to track it. It gets allocated in new_vmalloc_chunk() using get_zeroed_page().

Quoted 3 lines

Not sure why this code works - I'll suspend the series from linux-next for now.

It only gets called in the error path and that didn't get hit during testing I suppose. Until v3 the chunk was being allocated using kzalloc() so I guess this got missed in the move to get_zeroed_page().

I think Mike is out of office this week. Do you think this series is stable enough to land in the upcoming merge window? If so, I can send a v6 with the fix today.

Signature · 2 lines
Regards,
Pratyush Yadav
maintainer
#9 lore raw
Quoted 11 lines · On Wed, 24 Sep 2025 17:28:07 +0200 Pratyush Yadav <[email protected]> wrote:
Quoted 3 lines

Not sure why this code works - I'll suspend the series from linux-next for now.

It only gets called in the error path and that didn't get hit during testing I suppose. Until v3 the chunk was being allocated using kzalloc() so I guess this got missed in the move to get_zeroed_page().

I think Mike is out of office this week. Do you think this series is stable enough to land in the upcoming merge window? If so, I can send a v6 with the fix today.

A one-liner fiup would be preferred, if no other changes are required, thanks.

maintainer
#10 lore raw
Quoted 10 lines · Andrew Morton
Quoted 8 lines · On Wed, 24 Sep 2025 17:28:07 +0200 Pratyush Yadav <[email protected]> wrote:
Quoted 3 lines

Not sure why this code works - I'll suspend the series from linux-next for now.

It only gets called in the error path and that didn't get hit during testing I suppose. Until v3 the chunk was being allocated using kzalloc() so I guess this got missed in the move to get_zeroed_page().

Quoted 6 lines
Quoted 3 lines

I think Mike is out of office this week. Do you think this series is stable enough to land in the upcoming merge window? If so, I can send a v6 with the fix today.

A one-liner fiup would be preferred, if no other changes are required, thanks.

--- 8< ---
From f746718a99da3c670089a42f7c59660b455f265d Mon Sep 17 00:00:00 2001
From: Pratyush Yadav <[email protected]>
Date: Thu, 25 Sep 2025 13:05:08 +0200
Subject: [PATCH] fixup! kho: add support for preserving vmalloc allocations

Chunks are allocated using get_zeroed_page() so they should be freed using free_page() not kfree().

---
 kernel/kexec_handover.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/kexec_handover.c +1 -1
index e6380d8dce574..d06ca232683cd 100644
@@ -885,7 +885,7 @@ static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)
 		kho_vmalloc_unpreserve_chunk(chunk);
 
 		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
-		kfree(tmp);
+		free_page((unsigned long)tmp);
 	}
 }
 
Signature · 1 line
2.47.3
maintainer Reviewed-by on v5 3/4
#11 lore raw
In reply to Mike Rapoport
Quoted 6 lines · Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

A vmalloc allocation is preserved using binary structure similar to global KHO memory tracker. It's a linked list of pages where each page is an array of physical address of pages in vmalloc area.

Quoted 7 lines

kho_preserve_vmalloc() hands out the physical address of the head page to the caller. This address is used as the argument to kho_vmalloc_restore() to restore the mapping in the vmalloc address space and populate it with the preserved pages.

For the new kho_restore_pages() as well:

Thanks for working on this series!

[...]

Signature · 2 lines
Regards,
Pratyush Yadav
maintainer
#12 lore raw
In reply to Mike Rapoport
Quoted 102 lines · Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

A vmalloc allocation is preserved using binary structure similar to global KHO memory tracker. It's a linked list of pages where each page is an array of physical address of pages in vmalloc area.

kho_preserve_vmalloc() hands out the physical address of the head page to the caller. This address is used as the argument to kho_vmalloc_restore() to restore the mapping in the vmalloc address space and populate it with the preserved pages.

---
 include/linux/kexec_handover.h |  28 ++++
 kernel/kexec_handover.c        | 281 +++++++++++++++++++++++++++++++++
 2 files changed, 309 insertions(+)
include/linux/kexec_handover.h +28 -0
index cc5c49b0612b..49e36d4ae5dc 100644
@@ -39,12 +39,23 @@ struct page;
 
 struct kho_serialization;
 
+struct kho_vmalloc_chunk;
+struct kho_vmalloc {
+        DECLARE_KHOSER_PTR(first, struct kho_vmalloc_chunk *);
+        unsigned int total_pages;
+        unsigned short flags;
+        unsigned short order;
+};
+
 #ifdef CONFIG_KEXEC_HANDOVER
 bool kho_is_enabled(void);
 
 int kho_preserve_folio(struct folio *folio);
 int kho_preserve_pages(struct page *page, unsigned int nr_pages);
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
 struct folio *kho_restore_folio(phys_addr_t phys);
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages);
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
 int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
 
@@ -71,11 +82,28 @@ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
        return -EOPNOTSUPP;
 }
 
+static inline int kho_preserve_vmalloc(void *ptr,
+                                      struct kho_vmalloc *preservation)
+{
+       return -EOPNOTSUPP;
+}
+
 static inline struct folio *kho_restore_folio(phys_addr_t phys)
 {
        return NULL;
 }
 
+static inline struct page *kho_restore_pages(phys_addr_t phys,
+                                            unsigned int nr_pages)
+{
+       return NULL;
+}
+
+static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+       return NULL;
+}
+
 static inline int kho_add_subtree(struct kho_serialization *ser,
                                  const char *name, void *fdt)
 {
kernel/kexec_handover.c +11 -0
index fd80be3b12fd..e6380d8dce57 100644
@@ -18,6 +18,7 @@
 #include <linux/memblock.h>
 #include <linux/notifier.h>
 #include <linux/page-isolation.h>
+#include <linux/vmalloc.h>
 
 #include <asm/early_ioremap.h>
 
@@ -274,6 +275,37 @@ struct folio *kho_restore_folio(phys_addr_t phys)
 }
 EXPORT_SYMBOL_GPL(kho_restore_folio);
 
+/**
+ * kho_restore_pages - restore list of contiguous order 0 pages.
+ * @phys: physical address of the first page.
+ * @nr_pages: number of pages.
+ *
+ * Restore a contiguous list of order 0 pages that was preserved with
+ * kho_preserve_pages().
+ *
+ * Return: 0 on success, error code on failure
+ */

kernel/kexec_handover.c struct folio *kho_restore_folio(phys_addr_t phys) · line 297

Quoted 10 lines
kernel/kexec_handover.c +10 -0
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages)
+{
+       const unsigned long start_pfn = PHYS_PFN(phys);
+       const unsigned long end_pfn = start_pfn + nr_pages;
+       unsigned long pfn = start_pfn;
+
+       while (pfn < end_pfn) {
+               const unsigned int order =
+                       min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+               struct page *page = kho_restore_page(PFN_PHYS(pfn));

This line breaks linux-next build because, kho_restore_page has a prototype:
void kho_restore_page(struct page *page, unsigned int order).

Quoted 273 lines
kernel/kexec_handover.c +260 -1
+
+               if (!page)
+                       return NULL;
+               split_page(page, order);
+               pfn += 1 << order;
+       }
+
+       return pfn_to_page(start_pfn);
+}
+EXPORT_SYMBOL_GPL(kho_restore_pages);
+
 /* Serialize and deserialize struct kho_mem_phys across kexec
  *
  * Record all the bitmaps in a linked list of pages for the next kernel to
@@ -763,6 +795,255 @@ int kho_preserve_pages(struct page *page, unsigned int nr_pages)
 }
 EXPORT_SYMBOL_GPL(kho_preserve_pages);
 
+struct kho_vmalloc_hdr {
+       DECLARE_KHOSER_PTR(next, struct kho_vmalloc_chunk *);
+};
+
+#define KHO_VMALLOC_SIZE                               \
+       ((PAGE_SIZE - sizeof(struct kho_vmalloc_hdr)) / \
+        sizeof(phys_addr_t))
+
+struct kho_vmalloc_chunk {
+       struct kho_vmalloc_hdr hdr;
+       phys_addr_t phys[KHO_VMALLOC_SIZE];
+};
+
+static_assert(sizeof(struct kho_vmalloc_chunk) == PAGE_SIZE);
+
+/* vmalloc flags KHO supports */
+#define KHO_VMALLOC_SUPPORTED_FLAGS    (VM_ALLOC | VM_ALLOW_HUGE_VMAP)
+
+/* KHO internal flags for vmalloc preservations */
+#define KHO_VMALLOC_ALLOC      0x0001
+#define KHO_VMALLOC_HUGE_VMAP  0x0002
+
+static unsigned short vmalloc_flags_to_kho(unsigned int vm_flags)
+{
+       unsigned short kho_flags = 0;
+
+       if (vm_flags & VM_ALLOC)
+               kho_flags |= KHO_VMALLOC_ALLOC;
+       if (vm_flags & VM_ALLOW_HUGE_VMAP)
+               kho_flags |= KHO_VMALLOC_HUGE_VMAP;
+
+       return kho_flags;
+}
+
+static unsigned int kho_flags_to_vmalloc(unsigned short kho_flags)
+{
+       unsigned int vm_flags = 0;
+
+       if (kho_flags & KHO_VMALLOC_ALLOC)
+               vm_flags |= VM_ALLOC;
+       if (kho_flags & KHO_VMALLOC_HUGE_VMAP)
+               vm_flags |= VM_ALLOW_HUGE_VMAP;
+
+       return vm_flags;
+}
+
+static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)
+{
+       struct kho_vmalloc_chunk *chunk;
+       int err;
+
+       chunk = (struct kho_vmalloc_chunk *)get_zeroed_page(GFP_KERNEL);
+       if (!chunk)
+               return NULL;
+
+       err = kho_preserve_pages(virt_to_page(chunk), 1);
+       if (err)
+               goto err_free;
+       if (cur)
+               KHOSER_STORE_PTR(cur->hdr.next, chunk);
+       return chunk;
+
+err_free:
+       free_page((unsigned long)chunk);
+       return NULL;
+}
+
+static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk)
+{
+       struct kho_mem_track *track = &kho_out.ser.track;
+       unsigned long pfn = PHYS_PFN(virt_to_phys(chunk));
+
+       __kho_unpreserve(track, pfn, pfn + 1);
+
+       for (int i = 0; chunk->phys[i]; i++) {
+               pfn = PHYS_PFN(chunk->phys[i]);
+               __kho_unpreserve(track, pfn, pfn + 1);
+       }
+}
+
+static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)
+{
+       struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(kho_vmalloc->first);
+
+       while (chunk) {
+               struct kho_vmalloc_chunk *tmp = chunk;
+
+               kho_vmalloc_unpreserve_chunk(chunk);
+
+               chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+               kfree(tmp);
+       }
+}
+
+/**
+ * kho_preserve_vmalloc - preserve memory allocated with vmalloc() across kexec
+ * @ptr: pointer to the area in vmalloc address space
+ * @preservation: placeholder for preservation metadata
+ *
+ * Instructs KHO to preserve the area in vmalloc address space at @ptr. The
+ * physical pages mapped at @ptr will be preserved and on successful return
+ * @preservation will hold the physical address of a structure that describes
+ * the preservation.
+ *
+ * NOTE: The memory allocated with vmalloc_node() variants cannot be reliably
+ * restored on the same node
+ *
+ * Return: 0 on success, error code on failure
+ */
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+{
+       struct kho_vmalloc_chunk *chunk;
+       struct vm_struct *vm = find_vm_area(ptr);
+       unsigned int order, flags, nr_contig_pages;
+       unsigned int idx = 0;
+       int err;
+
+       if (!vm)
+               return -EINVAL;
+
+       if (vm->flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+               return -EOPNOTSUPP;
+
+       flags = vmalloc_flags_to_kho(vm->flags);
+       order = get_vm_area_page_order(vm);
+
+       chunk = new_vmalloc_chunk(NULL);
+       if (!chunk)
+               return -ENOMEM;
+       KHOSER_STORE_PTR(preservation->first, chunk);
+
+       nr_contig_pages = (1 << order);
+       for (int i = 0; i < vm->nr_pages; i += nr_contig_pages) {
+               phys_addr_t phys = page_to_phys(vm->pages[i]);
+
+               err = kho_preserve_pages(vm->pages[i], nr_contig_pages);
+               if (err)
+                       goto err_free;
+
+               chunk->phys[idx++] = phys;
+               if (idx == ARRAY_SIZE(chunk->phys)) {
+                       chunk = new_vmalloc_chunk(chunk);
+                       if (!chunk)
+                               goto err_free;
+                       idx = 0;
+               }
+       }
+
+       preservation->total_pages = vm->nr_pages;
+       preservation->flags = flags;
+       preservation->order = order;
+
+       return 0;
+
+err_free:
+       kho_vmalloc_free_chunks(preservation);
+       return err;
+}
+EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
+
+/**
+ * kho_restore_vmalloc - recreates and populates an area in vmalloc address
+ * space from the preserved memory.
+ * @preservation: preservation metadata.
+ *
+ * Recreates an area in vmalloc address space and populates it with memory that
+ * was preserved using kho_preserve_vmalloc().
+ *
+ * Return: pointer to the area in the vmalloc address space, NULL on failure.
+ */
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+       struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+       unsigned int align, order, shift, vm_flags;
+       unsigned long total_pages, contig_pages;
+       unsigned long addr, size;
+       struct vm_struct *area;
+       struct page **pages;
+       unsigned int idx = 0;
+       int err;
+
+       vm_flags = kho_flags_to_vmalloc(preservation->flags);
+       if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+               return NULL;
+
+       total_pages = preservation->total_pages;
+       pages = kvmalloc_array(total_pages, sizeof(*pages), GFP_KERNEL);
+       if (!pages)
+               return NULL;
+       order = preservation->order;
+       contig_pages = (1 << order);
+       shift = PAGE_SHIFT + order;
+       align = 1 << shift;
+
+       while (chunk) {
+               struct page *page;
+
+               for (int i = 0; chunk->phys[i]; i++) {
+                       phys_addr_t phys = chunk->phys[i];
+
+                       if (idx + contig_pages > total_pages)
+                               goto err_free_pages_array;
+
+                       page = kho_restore_pages(phys, contig_pages);
+                       if (!page)
+                               goto err_free_pages_array;
+
+                       for (int j = 0; j < contig_pages; j++)
+                               pages[idx++] = page;
+
+                       phys += contig_pages * PAGE_SIZE;
+               }
+
+               page = kho_restore_pages(virt_to_phys(chunk), 1);
+               if (!page)
+                       goto err_free_pages_array;
+               chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+               __free_page(page);
+       }
+
+       if (idx != total_pages)
+               goto err_free_pages_array;
+
+       area = __get_vm_area_node(total_pages * PAGE_SIZE, align, shift,
+                                 vm_flags, VMALLOC_START, VMALLOC_END,
+                                 NUMA_NO_NODE, GFP_KERNEL,
+                                 __builtin_return_address(0));
+       if (!area)
+               goto err_free_pages_array;
+
+       addr = (unsigned long)area->addr;
+       size = get_vm_area_size(area);
+       err = vmap_pages_range(addr, addr + size, PAGE_KERNEL, pages, shift);
+       if (err)
+               goto err_free_vm_area;
+
+       area->nr_pages = total_pages;
+       area->pages = pages;
+
+       return area->addr;
+
+err_free_vm_area:
+       free_vm_area(area);
+err_free_pages_array:
+       kvfree(pages);
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
+
 /* Handling for debug/kho/out */
 
 static struct dentry *debugfs_root;
--

2.50.1

maintainer
#13 lore raw
Quoted 5 lines · On Mon, 22 Sep 2025 10:17:56 -0400 Pasha Tatashin <[email protected]>…
Quoted 5 lines
kernel/kexec_handover.c +5 -0
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages)
+{
+       const unsigned long start_pfn = PHYS_PFN(phys);
+       const unsigned long end_pfn = start_pfn + nr_pages;
+       unsigned long pfn = start_pfn;
Quoted 8 lines

kernel/kexec_handover.c struct folio *kho_restore_folio(phys_addr_t phys) · line 297

Quoted 5 lines
kernel/kexec_handover.c +5 -0
+
+       while (pfn < end_pfn) {
+               const unsigned int order =
+                       min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+               struct page *page = kho_restore_page(PFN_PHYS(pfn));

This line breaks linux-next build because, kho_restore_page has a prototype:
void kho_restore_page(struct page *page, unsigned int order).

Thanks. I think that's all fixed up in mm.git's current mm-nonmm-unstable branch.

#14 lore raw
In reply to Mike Rapoport
Quoted 235 lines · Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

A vmalloc allocation is preserved using binary structure similar to global KHO memory tracker. It's a linked list of pages where each page is an array of physical address of pages in vmalloc area.

kho_preserve_vmalloc() hands out the physical address of the head page to the caller. This address is used as the argument to kho_vmalloc_restore() to restore the mapping in the vmalloc address space and populate it with the preserved pages.

---
  include/linux/kexec_handover.h |  28 ++++
  kernel/kexec_handover.c        | 281 +++++++++++++++++++++++++++++++++
  2 files changed, 309 insertions(+)
include/linux/kexec_handover.h +28 -0
index cc5c49b0612b..49e36d4ae5dc 100644
@@ -39,12 +39,23 @@ struct page;
 
  struct kho_serialization;
 
+struct kho_vmalloc_chunk;
+struct kho_vmalloc {
+        DECLARE_KHOSER_PTR(first, struct kho_vmalloc_chunk *);
+        unsigned int total_pages;
+        unsigned short flags;
+        unsigned short order;
+};
+
  #ifdef CONFIG_KEXEC_HANDOVER
  bool kho_is_enabled(void);
 
  int kho_preserve_folio(struct folio *folio);
  int kho_preserve_pages(struct page *page, unsigned int nr_pages);
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
  struct folio *kho_restore_folio(phys_addr_t phys);
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages);
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
  int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt);
  int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
 
@@ -71,11 +82,28 @@ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
  	return -EOPNOTSUPP;
  }
 
+static inline int kho_preserve_vmalloc(void *ptr,
+				       struct kho_vmalloc *preservation)
+{
+	return -EOPNOTSUPP;
+}
+
  static inline struct folio *kho_restore_folio(phys_addr_t phys)
  {
  	return NULL;
  }
 
+static inline struct page *kho_restore_pages(phys_addr_t phys,
+					     unsigned int nr_pages)
+{
+	return NULL;
+}
+
+static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+	return NULL;
+}
+
  static inline int kho_add_subtree(struct kho_serialization *ser,
  				  const char *name, void *fdt)
  {
kernel/kexec_handover.c +138 -0
index fd80be3b12fd..e6380d8dce57 100644
@@ -18,6 +18,7 @@
  #include <linux/memblock.h>
  #include <linux/notifier.h>
  #include <linux/page-isolation.h>
+#include <linux/vmalloc.h>
 
  #include <asm/early_ioremap.h>
 
@@ -274,6 +275,37 @@ struct folio *kho_restore_folio(phys_addr_t phys)
  }
  EXPORT_SYMBOL_GPL(kho_restore_folio);
 
+/**
+ * kho_restore_pages - restore list of contiguous order 0 pages.
+ * @phys: physical address of the first page.
+ * @nr_pages: number of pages.
+ *
+ * Restore a contiguous list of order 0 pages that was preserved with
+ * kho_preserve_pages().
+ *
+ * Return: 0 on success, error code on failure
+ */
+struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages)
+{
+	const unsigned long start_pfn = PHYS_PFN(phys);
+	const unsigned long end_pfn = start_pfn + nr_pages;
+	unsigned long pfn = start_pfn;
+
+	while (pfn < end_pfn) {
+		const unsigned int order =
+			min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+		struct page *page = kho_restore_page(PFN_PHYS(pfn));
+
+		if (!page)
+			return NULL;
+		split_page(page, order);
+		pfn += 1 << order;
+	}
+
+	return pfn_to_page(start_pfn);
+}
+EXPORT_SYMBOL_GPL(kho_restore_pages);
+
  /* Serialize and deserialize struct kho_mem_phys across kexec
   *
   * Record all the bitmaps in a linked list of pages for the next kernel to
@@ -763,6 +795,255 @@ int kho_preserve_pages(struct page *page, unsigned int nr_pages)
  }
  EXPORT_SYMBOL_GPL(kho_preserve_pages);
 
+struct kho_vmalloc_hdr {
+	DECLARE_KHOSER_PTR(next, struct kho_vmalloc_chunk *);
+};
+
+#define KHO_VMALLOC_SIZE				\
+	((PAGE_SIZE - sizeof(struct kho_vmalloc_hdr)) / \
+	 sizeof(phys_addr_t))
+
+struct kho_vmalloc_chunk {
+	struct kho_vmalloc_hdr hdr;
+	phys_addr_t phys[KHO_VMALLOC_SIZE];
+};
+
+static_assert(sizeof(struct kho_vmalloc_chunk) == PAGE_SIZE);
+
+/* vmalloc flags KHO supports */
+#define KHO_VMALLOC_SUPPORTED_FLAGS	(VM_ALLOC | VM_ALLOW_HUGE_VMAP)
+
+/* KHO internal flags for vmalloc preservations */
+#define KHO_VMALLOC_ALLOC	0x0001
+#define KHO_VMALLOC_HUGE_VMAP	0x0002
+
+static unsigned short vmalloc_flags_to_kho(unsigned int vm_flags)
+{
+	unsigned short kho_flags = 0;
+
+	if (vm_flags & VM_ALLOC)
+		kho_flags |= KHO_VMALLOC_ALLOC;
+	if (vm_flags & VM_ALLOW_HUGE_VMAP)
+		kho_flags |= KHO_VMALLOC_HUGE_VMAP;
+
+	return kho_flags;
+}
+
+static unsigned int kho_flags_to_vmalloc(unsigned short kho_flags)
+{
+	unsigned int vm_flags = 0;
+
+	if (kho_flags & KHO_VMALLOC_ALLOC)
+		vm_flags |= VM_ALLOC;
+	if (kho_flags & KHO_VMALLOC_HUGE_VMAP)
+		vm_flags |= VM_ALLOW_HUGE_VMAP;
+
+	return vm_flags;
+}
+
+static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)
+{
+	struct kho_vmalloc_chunk *chunk;
+	int err;
+
+	chunk = (struct kho_vmalloc_chunk *)get_zeroed_page(GFP_KERNEL);
+	if (!chunk)
+		return NULL;
+
+	err = kho_preserve_pages(virt_to_page(chunk), 1);
+	if (err)
+		goto err_free;
+	if (cur)
+		KHOSER_STORE_PTR(cur->hdr.next, chunk);
+	return chunk;
+
+err_free:
+	free_page((unsigned long)chunk);
+	return NULL;
+}
+
+static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk)
+{
+	struct kho_mem_track *track = &kho_out.ser.track;
+	unsigned long pfn = PHYS_PFN(virt_to_phys(chunk));
+
+	__kho_unpreserve(track, pfn, pfn + 1);
+
+	for (int i = 0; chunk->phys[i]; i++) {
+		pfn = PHYS_PFN(chunk->phys[i]);
+		__kho_unpreserve(track, pfn, pfn + 1);
+	}
+}
+
+static void kho_vmalloc_free_chunks(struct kho_vmalloc *kho_vmalloc)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(kho_vmalloc->first);
+
+	while (chunk) {
+		struct kho_vmalloc_chunk *tmp = chunk;
+
+		kho_vmalloc_unpreserve_chunk(chunk);
+
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		kfree(tmp);
+	}
+}
+
+/**
+ * kho_preserve_vmalloc - preserve memory allocated with vmalloc() across kexec
+ * @ptr: pointer to the area in vmalloc address space
+ * @preservation: placeholder for preservation metadata
+ *
+ * Instructs KHO to preserve the area in vmalloc address space at @ptr. The
+ * physical pages mapped at @ptr will be preserved and on successful return
+ * @preservation will hold the physical address of a structure that describes
+ * the preservation.
+ *
+ * NOTE: The memory allocated with vmalloc_node() variants cannot be reliably
+ * restored on the same node

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 913

Quoted 10 lines
kernel/kexec_handover.c +10 -0
+ *
+ * Return: 0 on success, error code on failure
+ */
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk;
+	struct vm_struct *vm = find_vm_area(ptr);
+	unsigned int order, flags, nr_contig_pages;
+	unsigned int idx = 0;
+	int err;

This is a trivial issue. I’m not sure whether RCT (Reverse Christmas Trees) is used in the Linux MM mailing list.

If it is, the variable declarations above do not comply with the RCT rules.

Yanjun.Zhu

Quoted 136 lines
kernel/kexec_handover.c +133 -0
+
+	if (!vm)
+		return -EINVAL;
+
+	if (vm->flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+		return -EOPNOTSUPP;
+
+	flags = vmalloc_flags_to_kho(vm->flags);
+	order = get_vm_area_page_order(vm);
+
+	chunk = new_vmalloc_chunk(NULL);
+	if (!chunk)
+		return -ENOMEM;
+	KHOSER_STORE_PTR(preservation->first, chunk);
+
+	nr_contig_pages = (1 << order);
+	for (int i = 0; i < vm->nr_pages; i += nr_contig_pages) {
+		phys_addr_t phys = page_to_phys(vm->pages[i]);
+
+		err = kho_preserve_pages(vm->pages[i], nr_contig_pages);
+		if (err)
+			goto err_free;
+
+		chunk->phys[idx++] = phys;
+		if (idx == ARRAY_SIZE(chunk->phys)) {
+			chunk = new_vmalloc_chunk(chunk);
+			if (!chunk)
+				goto err_free;
+			idx = 0;
+		}
+	}
+
+	preservation->total_pages = vm->nr_pages;
+	preservation->flags = flags;
+	preservation->order = order;
+
+	return 0;
+
+err_free:
+	kho_vmalloc_free_chunks(preservation);
+	return err;
+}
+EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
+
+/**
+ * kho_restore_vmalloc - recreates and populates an area in vmalloc address
+ * space from the preserved memory.
+ * @preservation: preservation metadata.
+ *
+ * Recreates an area in vmalloc address space and populates it with memory that
+ * was preserved using kho_preserve_vmalloc().
+ *
+ * Return: pointer to the area in the vmalloc address space, NULL on failure.
+ */
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+	unsigned int align, order, shift, vm_flags;
+	unsigned long total_pages, contig_pages;
+	unsigned long addr, size;
+	struct vm_struct *area;
+	struct page **pages;
+	unsigned int idx = 0;
+	int err;
+
+	vm_flags = kho_flags_to_vmalloc(preservation->flags);
+	if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+		return NULL;
+
+	total_pages = preservation->total_pages;
+	pages = kvmalloc_array(total_pages, sizeof(*pages), GFP_KERNEL);
+	if (!pages)
+		return NULL;
+	order = preservation->order;
+	contig_pages = (1 << order);
+	shift = PAGE_SHIFT + order;
+	align = 1 << shift;
+
+	while (chunk) {
+		struct page *page;
+
+		for (int i = 0; chunk->phys[i]; i++) {
+			phys_addr_t phys = chunk->phys[i];
+
+			if (idx + contig_pages > total_pages)
+				goto err_free_pages_array;
+
+			page = kho_restore_pages(phys, contig_pages);
+			if (!page)
+				goto err_free_pages_array;
+
+			for (int j = 0; j < contig_pages; j++)
+				pages[idx++] = page;
+
+			phys += contig_pages * PAGE_SIZE;
+		}
+
+		page = kho_restore_pages(virt_to_phys(chunk), 1);
+		if (!page)
+			goto err_free_pages_array;
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		__free_page(page);
+	}
+
+	if (idx != total_pages)
+		goto err_free_pages_array;
+
+	area = __get_vm_area_node(total_pages * PAGE_SIZE, align, shift,
+				  vm_flags, VMALLOC_START, VMALLOC_END,
+				  NUMA_NO_NODE, GFP_KERNEL,
+				  __builtin_return_address(0));
+	if (!area)
+		goto err_free_pages_array;
+
+	addr = (unsigned long)area->addr;
+	size = get_vm_area_size(area);
+	err = vmap_pages_range(addr, addr + size, PAGE_KERNEL, pages, shift);
+	if (err)
+		goto err_free_vm_area;
+
+	area->nr_pages = total_pages;
+	area->pages = pages;
+
+	return area->addr;
+
+err_free_vm_area:
+	free_vm_area(area);
+err_free_pages_array:
+	kvfree(pages);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
+
  /* Handling for debug/kho/out */
 
  static struct dentry *debugfs_root;
maintainer
#15 lore raw
Quoted 10 lines · On Mon, 22 Sep 2025 15:31:42 -0700 "yanjun.zhu" <[email protected]> wrote:

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 913

Quoted 7 lines
kernel/kexec_handover.c +7 -0
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk;
+	struct vm_struct *vm = find_vm_area(ptr);
+	unsigned int order, flags, nr_contig_pages;
+	unsigned int idx = 0;
+	int err;

This is a trivial issue. I’m not sure whether RCT (Reverse Christmas Trees) is used in the Linux MM mailing list.

Not as far as I know. Some like it.

If I was to have a preference I'd suggest that things be laid out in a logical order rather than by column count. Group like things together, avoid use of unintialized storage in initializers(!).

#16 lore raw
Quoted 11 lines · Andrew Morton
Quoted 9 lines · On Mon, 22 Sep 2025 15:31:42 -0700 "yanjun.zhu" <[email protected]> wrote:

kernel/kexec_handover.c int kho_preserve_pages(struct page *page, unsigned int nr_pages) · line 913

Quoted 7 lines
kernel/kexec_handover.c +7 -0
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk;
+	struct vm_struct *vm = find_vm_area(ptr);
+	unsigned int order, flags, nr_contig_pages;
+	unsigned int idx = 0;
+	int err;

This is a trivial issue. I’m not sure whether RCT (Reverse Christmas Trees) is used in the Linux MM mailing list.

Quoted 5 lines

Not as far as I know. Some like it.

If I was to have a preference I'd suggest that things be laid out in a logical order rather than by column count. Group like things together, avoid use of unintialized storage in initializers(!).

Thanks for your input. I consider this a trivial issue. RCT will help make the local variables more organized. Since the Linux MM code does not currently use RCT and prefers to group similar logical variables and initialize these local variables, I am fine with the current approach.

Yanjun.Zhu

Quoted 1 line
Signature · 2 lines
Best Regards,
Yanjun.Zhu
author maintainer
#17 lore raw

PATCH v5 4/4 lib/test_kho: use kho_preserve_vmalloc instead of storing addresses in fdt

In reply to Mike Rapoport

From: "Mike Rapoport (Microsoft)" <[email protected]>

KHO test stores physical addresses of the preserved folios directly in fdt.
Use kho_preserve_vmalloc() instead of it and kho_restore_vmalloc() to retrieve the addresses after kexec.

This makes the test more scalable from one side and adds tests coverage for kho_preserve_vmalloc() from the other.

---
 lib/test_kho.c | 41 +++++++++++++++++++++++++++++------------
 1 file changed, 29 insertions(+), 12 deletions(-)
lib/test_kho.c +29 -12
index fe8504e3407b..60cd899ea745 100644
@@ -32,6 +32,7 @@ module_param(max_mem, long, 0644);
 struct kho_test_state {
 	unsigned int nr_folios;
 	struct folio **folios;
+	phys_addr_t *folios_info;
 	struct folio *fdt;
 	__wsum csum;
 };
@@ -67,18 +68,15 @@ static struct notifier_block kho_test_nb = {
 
 static int kho_test_save_data(struct kho_test_state *state, void *fdt)
 {
-	phys_addr_t *folios_info;
+	phys_addr_t *folios_info __free(kvfree) = NULL;
+	struct kho_vmalloc folios_info_phys;
 	int err = 0;
 
-	err |= fdt_begin_node(fdt, "data");
-	err |= fdt_property(fdt, "nr_folios", &state->nr_folios,
-			    sizeof(state->nr_folios));
-	err |= fdt_property_placeholder(fdt, "folios_info",
-					state->nr_folios * sizeof(*folios_info),
-					(void **)&folios_info);
-	err |= fdt_property(fdt, "csum", &state->csum, sizeof(state->csum));
-	err |= fdt_end_node(fdt);
+	folios_info = vmalloc_array(state->nr_folios, sizeof(*folios_info));
+	if (!folios_info)
+		return -ENOMEM;
 
+	err = kho_preserve_vmalloc(folios_info, &folios_info_phys);
 	if (err)
 		return err;
 
@@ -93,6 +91,17 @@ static int kho_test_save_data(struct kho_test_state *state, void *fdt)
 			break;
 	}
 
+	err |= fdt_begin_node(fdt, "data");
+	err |= fdt_property(fdt, "nr_folios", &state->nr_folios,
+			    sizeof(state->nr_folios));
+	err |= fdt_property(fdt, "folios_info", &folios_info_phys,
+			    sizeof(folios_info_phys));
+	err |= fdt_property(fdt, "csum", &state->csum, sizeof(state->csum));
+	err |= fdt_end_node(fdt);
+
+	if (!err)
+		state->folios_info = no_free_ptr(folios_info);
+
 	return err;
 }
 
@@ -209,8 +218,9 @@ static int kho_test_save(void)
 
 static int kho_test_restore_data(const void *fdt, int node)
 {
+	const struct kho_vmalloc *folios_info_phys;
 	const unsigned int *nr_folios;
-	const phys_addr_t *folios_info;
+	phys_addr_t *folios_info;
 	const __wsum *old_csum;
 	__wsum csum = 0;
 	int len;
@@ -225,8 +235,12 @@ static int kho_test_restore_data(const void *fdt, int node)
 	if (!old_csum || len != sizeof(*old_csum))
 		return -EINVAL;
 
-	folios_info = fdt_getprop(fdt, node, "folios_info", &len);
-	if (!folios_info || len != sizeof(*folios_info) * *nr_folios)
+	folios_info_phys = fdt_getprop(fdt, node, "folios_info", &len);
+	if (!folios_info_phys || len != sizeof(*folios_info_phys))
+		return -EINVAL;
+
+	folios_info = kho_restore_vmalloc(folios_info_phys);
+	if (!folios_info)
 		return -EINVAL;
 
 	for (int i = 0; i < *nr_folios; i++) {
@@ -246,6 +260,8 @@ static int kho_test_restore_data(const void *fdt, int node)
 		folio_put(folio);
 	}
 
+	vfree(folios_info);
+
 	if (csum != *old_csum)
 		return -EINVAL;
 
@@ -304,6 +320,7 @@ static void kho_test_cleanup(void)
 		folio_put(kho_test_state.folios[i]);
 
 	kvfree(kho_test_state.folios);
+	vfree(kho_test_state.folios_info);
 	folio_put(kho_test_state.fdt);
 }
 
Signature · 1 line
2.50.1