summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-08-21 14:07:39 +0100
committerMark Brown <broonie@kernel.org>2026-08-21 14:07:39 +0100
commit8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1 (patch)
treed67964a2565800b1b3f18481156b00817e7b9271
parentb641675828f273b653697be9b37ac423dc01503a (diff)
parent352ebb57013a08679788bc18c7f9af90903db256 (diff)
downloadlinux-next-8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1.tar.gz
linux-next-8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1.zip
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
-rw-r--r--MAINTAINERS1
-rw-r--r--arch/x86/boot/compressed/kaslr.c2
-rw-r--r--arch/x86/include/asm/Kbuild1
-rw-r--r--arch/x86/include/asm/setup.h2
-rw-r--r--arch/x86/kernel/setup.c1
-rw-r--r--include/asm-generic/kexec_handover.h12
-rw-r--r--include/linux/kexec_handover.h31
-rw-r--r--include/linux/kho/abi/kexec_handover.h9
-rw-r--r--include/linux/kho_radix_tree.h60
-rw-r--r--include/linux/liveupdate.h22
-rw-r--r--include/linux/memblock.h23
-rw-r--r--kernel/liveupdate/Makefile1
-rw-r--r--kernel/liveupdate/kexec_handover.c525
-rw-r--r--kernel/liveupdate/kexec_handover_debug.c25
-rw-r--r--kernel/liveupdate/kexec_handover_internal.h9
-rw-r--r--kernel/liveupdate/luo_file.c69
-rw-r--r--kernel/liveupdate/luo_internal.h17
-rw-r--r--mm/hugetlb.c9
-rw-r--r--mm/memblock.c154
-rw-r--r--mm/mm_init.c29
-rw-r--r--tools/testing/memblock/internal.h5
-rw-r--r--tools/testing/memblock/linux/kexec_handover.h9
-rw-r--r--tools/testing/memblock/linux/kho/abi/memblock.h10
-rw-r--r--tools/testing/memblock/linux/libfdt.h9
24 files changed, 781 insertions, 254 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index c4e6dc192502..8cb9209b8c3e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14421,6 +14421,7 @@ S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
F: Documentation/admin-guide/mm/kho.rst
F: Documentation/core-api/kho/*
+F: include/asm-generic/kexec_handover.h
F: include/linux/kexec_handover.h
F: include/linux/kho/
F: include/linux/kho_block.h
diff --git a/arch/x86/boot/compressed/kaslr.c b/arch/x86/boot/compressed/kaslr.c
index 8e4bf5365ac6..22267a83e064 100644
--- a/arch/x86/boot/compressed/kaslr.c
+++ b/arch/x86/boot/compressed/kaslr.c
@@ -32,6 +32,8 @@
#include <asm/setup.h> /* For COMMAND_LINE_SIZE */
#undef _SETUP
+#include <asm/kexec_handover.h>
+
extern unsigned long get_cmd_line_ptr(void);
/* Simplified build-specific string for starting entropy. */
diff --git a/arch/x86/include/asm/Kbuild b/arch/x86/include/asm/Kbuild
index 078fd2c0d69d..47ef8cb482e3 100644
--- a/arch/x86/include/asm/Kbuild
+++ b/arch/x86/include/asm/Kbuild
@@ -15,3 +15,4 @@ generic-y += fprobe.h
generic-y += mcs_spinlock.h
generic-y += mmzone.h
generic-y += ring_buffer.h
+generic-y += kexec_handover.h
diff --git a/arch/x86/include/asm/setup.h b/arch/x86/include/asm/setup.h
index 914eb32581c7..895d09faaf83 100644
--- a/arch/x86/include/asm/setup.h
+++ b/arch/x86/include/asm/setup.h
@@ -69,8 +69,6 @@ extern void x86_ce4100_early_setup(void);
static inline void x86_ce4100_early_setup(void) { }
#endif
-#include <linux/kexec_handover.h>
-
#ifndef _SETUP
#include <asm/espfix.h>
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index 1cc4aa65930c..a8c873356f17 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -16,6 +16,7 @@
#include <linux/init_ohci1394_dma.h>
#include <linux/initrd.h>
#include <linux/iscsi_ibft.h>
+#include <linux/kexec_handover.h>
#include <linux/memblock.h>
#include <linux/panic_notifier.h>
#include <linux/pci.h>
diff --git a/include/asm-generic/kexec_handover.h b/include/asm-generic/kexec_handover.h
new file mode 100644
index 000000000000..50839fb5ee8e
--- /dev/null
+++ b/include/asm-generic/kexec_handover.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_GENERIC_KEXEC_HANDOVER_H
+#define __ASM_GENERIC_KEXEC_HANDOVER_H
+
+#include <linux/types.h>
+
+struct kho_scratch {
+ phys_addr_t addr;
+ phys_addr_t size;
+};
+
+#endif /* __ASM_GENERIC_KEXEC_HANDOVER_H */
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 8968c56d2d73..46de86dc343e 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -5,11 +5,8 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/types.h>
-
-struct kho_scratch {
- phys_addr_t addr;
- phys_addr_t size;
-};
+#include <linux/mm.h>
+#include <asm-generic/kexec_handover.h>
struct kho_vmalloc;
@@ -37,9 +34,20 @@ 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_memory_init_early(void);
void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,
u64 scratch_len);
+
+bool kho_scratch_overlap(phys_addr_t phys, size_t size);
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+ enum migratetype mt)
+{
+ if (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT))
+ return MIGRATE_CMA;
+ return mt;
+}
#else
static inline bool kho_is_enabled(void)
{
@@ -112,10 +120,23 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,
static inline void kho_memory_init(void) { }
+static inline void kho_memory_init_early(void) { }
+
static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
phys_addr_t scratch_phys, u64 scratch_len)
{
}
+
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+ return false;
+}
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+ enum migratetype mt)
+{
+ return mt;
+}
#endif /* CONFIG_KEXEC_HANDOVER */
#endif /* LINUX_KEXEC_HANDOVER_H */
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 5e2eb8519bda..2f4fb9c63942 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -257,11 +257,8 @@ struct kho_vmalloc {
* memory. These constants govern the indexing, sizing, and depth of the tree.
*/
enum kho_radix_consts {
- /*
- * The bit position of the order bit (and also the length of the
- * shifted physical address) for an order-0 page.
- */
- KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,
+ /* Need to store the PFN, plus one bit for order. */
+ KHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1,
/* Size of the table in kho_radix_node, in log2 */
KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),
@@ -274,7 +271,7 @@ enum kho_radix_consts {
* and 1 bitmap level.
*/
KHO_TREE_MAX_DEPTH =
- DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
+ DIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2,
KHO_TABLE_SIZE_LOG2) + 1,
};
diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 84e918b96e53..fcd69639721b 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -11,15 +11,19 @@
/**
* DOC: Kexec Handover Radix Tree
*
- * This is a radix tree implementation for tracking physical memory pages
- * across kexec transitions. It was developed for the KHO mechanism but is
- * designed for broader use by any subsystem that needs to preserve pages.
+ * This is a radix tree implementation for tracking numeric keys across kexec
+ * transitions. It was developed for the KHO preserved memory map but is
+ * designed for broader use by any subsystem that needs to track keys.
+ * Conceptually speaking, the data structure is similar to a set. It tracks the
+ * presence or absence of numeric keys.
*
* The radix tree is a multi-level tree where leaf nodes are bitmaps
- * representing individual pages. To allow pages of different sizes (orders)
+ * representing individual keys.
+ *
+ * For the KHO preserved memory map, to allow pages of different sizes (orders)
* to be stored efficiently in a single tree, it uses a unique key encoding
- * scheme. Each key is an unsigned long that combines a page's physical
- * address and its order.
+ * 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
@@ -34,37 +38,53 @@ 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);
+/**
+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
+ * @leaf: Called on each present key in the radix tree.
+ * @node: Called on each node of the radix tree itself. Receives the
+ * physical address of the page containing the node.
+ *
+ * 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 (*leaf)(unsigned long key, void *data);
+ int (*node)(phys_addr_t phys, void *data);
+};
#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);
+ 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 */
-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)
+ const struct kho_radix_walk_cb *cb, void *data)
{
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 */
#endif /* _LINUX_KHO_RADIX_TREE_H */
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 63ea5417de84..6051abc0612c 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -25,6 +25,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
@@ -45,6 +46,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;
@@ -247,6 +249,14 @@ 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);
+/* 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 */
static inline bool liveupdate_enabled(void)
@@ -299,5 +309,17 @@ static inline void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb)
{
}
+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/include/linux/memblock.h b/include/linux/memblock.h
index 5afcd99aa8c1..d62db9e776cf 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -51,6 +51,7 @@ 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_RSRV_HUGETLB: memory is reserved for hugetlb pages
*/
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* No special request */
@@ -61,6 +62,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_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */
};
/**
@@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,
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_hugetlb(phys_addr_t size, int nid, bool exact_nid);
static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
{
@@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void)
phys_addr_t memblock_phys_mem_size(void);
phys_addr_t memblock_reserved_size(void);
phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);
+phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid);
unsigned long memblock_estimated_nr_free_pages(void);
phys_addr_t memblock_start_of_DRAM(void);
phys_addr_t memblock_end_of_DRAM(void);
@@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { }
#ifdef CONFIG_MEMBLOCK_KHO_SCRATCH
void memblock_set_kho_scratch_only(void);
void memblock_clear_kho_scratch_only(void);
-bool memblock_is_kho_scratch_memory(phys_addr_t addr);
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
- enum migratetype mt)
-{
- if (memblock_is_kho_scratch_memory(PFN_PHYS(pfn)))
- return MIGRATE_CMA;
- return mt;
-}
#else
static inline void memblock_set_kho_scratch_only(void) { }
static inline void memblock_clear_kho_scratch_only(void) { }
-static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr)
-{
- return false;
-}
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
- enum migratetype mt)
-{
- return mt;
-}
#endif
#endif /* _LINUX_MEMBLOCK_H */
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index eec9d3ae07eb..d9f469462556 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -8,7 +8,6 @@ luo-y := \
luo_session.o
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_LIVEUPDATE) += luo.o
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 9260e601c61d..7c4d86daf86d 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -95,8 +95,25 @@ 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_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.
*
@@ -106,35 +123,38 @@ 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);
- /* Shifted physical address part */
- unsigned long l = phys >> (PAGE_SHIFT + order);
+ /* The physical address is encoded by shifting the PFN by its order. */
+ unsigned long shift = PAGE_SHIFT + order;
+ /* Order bit goes right before the shifted PFN. */
+ unsigned long h = 1UL << (64 - shift);
+ /* Shifted PFN. */
+ unsigned long l = phys >> shift;
return h | l;
}
/**
- * 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);
+ /* fls64() indexes starting from 1. */
+ unsigned int order_bit = fls64(key) - 1;
phys_addr_t phys;
- /* order_bit is numbered starting at 1 from fls64 */
- *order = KHO_ORDER_0_LOG2 - order_bit + 1;
- /* The order is discarded by the shift */
+ /* order bit goes right before the shifted PFN. */
+ *order = 64 - (PAGE_SHIFT + order_bit);
+ /* The order bit is discarded by the shift */
phys = key << (PAGE_SHIFT + *order);
return phys;
@@ -154,25 +174,47 @@ 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_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.
+ *
+ * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported.
+ * This limit only exists because current users of the radix tree don't use more
+ * than that. Changing the maximum width requires changing the tree depth, which
+ * needs bumping the ABI version.
*
* 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;
@@ -183,6 +225,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
if (WARN_ON_ONCE(!tree->root))
return -EINVAL;
+ if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+ return -ERANGE;
+
might_sleep();
guard(mutex)(&tree->lock);
@@ -197,7 +242,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
}
/* 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;
@@ -228,29 +273,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
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;
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;
@@ -258,6 +300,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
if (WARN_ON_ONCE(!tree->root))
return;
+ /* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */
+ if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+ return;
+
might_sleep();
guard(mutex)(&tree->lock);
@@ -281,21 +327,85 @@ 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 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_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)
+{
+ 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
+ *
+ * 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);
+ tree->root = NULL;
+}
+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
-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, void *data)
{
unsigned long *bitmap = (unsigned long *)leaf;
- unsigned int order;
- phys_addr_t phys;
unsigned int i;
int err;
+ if (cb->node) {
+ err = cb->node(virt_to_phys(leaf), data);
+ if (err)
+ return err;
+ }
+
+ if (!cb->leaf)
+ return 0;
+
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->leaf(key | i, data);
if (err)
return err;
}
@@ -305,7 +415,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, void *data)
{
struct kho_radix_node *node;
struct kho_radix_leaf *leaf;
@@ -313,6 +423,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
unsigned int shift;
int err;
+ if (cb->node) {
+ err = cb->node(virt_to_phys(root), data);
+ if (err)
+ return err;
+ }
+
for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
if (!root->table[i])
continue;
@@ -329,10 +445,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)
@@ -343,28 +459,27 @@ 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.
+ * @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 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.
+ * 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, 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);
@@ -495,13 +610,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, void *data)
{
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 = 1UL << (order + PAGE_SHIFT);
page = kho_get_preserved_page(phys, order);
@@ -530,6 +648,13 @@ static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
return get_unaligned((const u64 *)mem_ptr);
}
+static void __init *kho_get_mem_map(const void *fdt)
+{
+ phys_addr_t phys = kho_get_mem_map_phys(fdt);
+
+ return phys ? phys_to_virt(phys) : NULL;
+}
+
/*
* With KHO enabled, memory can become fragmented because KHO regions may
* be anywhere in physical address space. The scratch regions give us a
@@ -630,19 +755,24 @@ 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 fixed sizes are not provided via command line, calculate them now.
+ * Remove HugeTLB allocations from it because they never get allocated
+ * from scratch.
*/
if (scratch_scale) {
phys_addr_t size;
size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
NUMA_NO_NODE);
+ size -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT,
+ NUMA_NO_NODE);
size = size * scratch_scale / 100;
scratch_size_lowmem = size;
size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
NUMA_NO_NODE);
+ size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+ NUMA_NO_NODE);
size = size * scratch_scale / 100 - scratch_size_lowmem;
scratch_size_global = size;
}
@@ -662,6 +792,9 @@ static phys_addr_t __init scratch_size_node(int nid)
if (scratch_scale) {
size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
nid);
+ /* Do not count HugeTLB pages. */
+ size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+ nid);
size = size * scratch_scale / 100;
} else {
size = scratch_size_pernode;
@@ -670,6 +803,22 @@ static phys_addr_t __init scratch_size_node(int nid)
return round_up(size, SCRATCH_ALIGNMENT_BYTES);
}
+bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+ phys_addr_t scratch_start, scratch_end;
+ unsigned int i;
+
+ for (i = 0; i < kho_scratch_cnt; i++) {
+ scratch_start = kho_scratch[i].addr;
+ scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
+
+ if (phys < scratch_end && (phys + size) > scratch_start)
+ return true;
+ }
+
+ return false;
+}
+
/**
* kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec
*
@@ -757,6 +906,140 @@ err_disable_kho:
kho_enable = false;
}
+/*
+ * Look for free blocks of 1G. This is a heuristic chosen to work efficiently
+ * with large systems with hundreds of gigabytes of memory. It will work poorly
+ * on smaller systems. The algorithm itself doesn't depend on the actual value,
+ * so it can be changed to a different heuristic later if needed.
+ */
+#define KHO_SCRATCH_EXT_BLKSIZE SZ_1G
+#define KHO_SCRATCH_EXT_BLKSHIFT const_ilog2(KHO_SCRATCH_EXT_BLKSIZE)
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_leaf(unsigned long key, void *data)
+{
+ struct kho_radix_tree *busy_blocks = data;
+ phys_addr_t start, end;
+ unsigned int order;
+ int err;
+
+ /*
+ * The key is from the KHO preserved memory radix tree. It is decoded to
+ * a physical address of a preservation and its order.
+ */
+ start = kho_decode_radix_key(key, &order);
+ end = start + (1UL << (order + PAGE_SHIFT));
+
+ while (start < end) {
+ err = kho_radix_add_key(busy_blocks, start >> KHO_SCRATCH_EXT_BLKSHIFT);
+ if (err)
+ return err;
+
+ start += (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+ }
+
+ return 0;
+}
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)
+{
+ struct kho_radix_tree *busy_blocks = data;
+
+ return kho_radix_add_key(busy_blocks, phys >> KHO_SCRATCH_EXT_BLKSHIFT);
+}
+
+/* Called for the busy block radix tree. */
+static int __init kho_ext_mark_scratch(unsigned long key, void *data)
+{
+ phys_addr_t *prev_end = data;
+ phys_addr_t start = key << KHO_SCRATCH_EXT_BLKSHIFT;
+ int err;
+
+ if (start > *prev_end) {
+ err = memblock_mark_kho_scratch(*prev_end, start - *prev_end);
+ if (err)
+ return err;
+ }
+
+ *prev_end = start + (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+ return 0;
+}
+
+/*
+ * kho_extend_scratch - Extend the scratch regions
+ *
+ * The KHO preserved memory 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 size KHO_SCRATCH_EXT_BLKSIZE 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 preserved memory radix tree tables are not marked
+ * as preserved in the preserved memory 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.
+ *
+ * NOTE: efi_init()::reserve_regions() removes all regions except
+ * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO
+ * scratch memory, so they should not be removed. This function should always be
+ * called after reserve_regions().
+ */
+static void __init kho_extend_scratch(void)
+{
+ const struct kho_radix_walk_cb kho_cb = {
+ .leaf = kho_ext_walk_leaf,
+ .node = kho_ext_walk_node,
+ };
+ const struct kho_radix_walk_cb ext_cb = {
+ .leaf = kho_ext_mark_scratch,
+ };
+ static struct lock_class_key busy_radix_class;
+ struct kho_radix_tree busy_blocks;
+ phys_addr_t prev_end = 0;
+ int err = 0;
+
+ err = kho_radix_init_tree(&busy_blocks, NULL);
+ if (err)
+ goto print;
+
+ /*
+ * The walk of kho_in.radix_tree adds keys to busy_blocks. The walk
+ * takes the kho_in radix tree lock and adding the key takes busy_blocks
+ * lock. Since both are struct kho_radix_tree and share the same lock
+ * class, lockdep gets confused. Set a different class for
+ * busy_blocks.lock to make lockdep happy.
+ */
+ lockdep_set_class(&busy_blocks.lock, &busy_radix_class);
+
+ /* Walk the KHO radix tree to find busy blocks. */
+ err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &busy_blocks);
+ if (err)
+ goto out;
+
+ /* Walk the busy blocks and mark everything between keys as scratch. */
+ err = kho_radix_walk_tree(&busy_blocks, &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(prev_end, memblock_end_of_DRAM() - prev_end);
+
+ /* fallthrough */
+out:
+ kho_radix_destroy_tree(&busy_blocks);
+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.
@@ -867,10 +1150,12 @@ int kho_preserve_folio(struct folio *folio)
const unsigned long pfn = folio_pfn(folio);
const unsigned int order = folio_order(folio);
- if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
+ if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+ 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);
@@ -888,7 +1173,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);
@@ -917,7 +1202,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;
}
@@ -942,7 +1228,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
unsigned long failed_pfn = 0;
int err = 0;
- if (WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
+ if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+ WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
nr_pages << PAGE_SHIFT))) {
return -EINVAL;
}
@@ -950,7 +1237,8 @@ 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);
- 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;
@@ -1326,22 +1614,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;
-};
-
-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
@@ -1409,26 +1681,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
-static int __init kho_mem_retrieve(const void *fdt)
+static void __init kho_mem_retrieve(void)
{
- 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);
+ const struct kho_radix_walk_cb cb = {
+ .leaf = kho_preserved_memory_reserve,
+ };
- if (!mem || len != sizeof(*mem)) {
- pr_err("failed to get preserved KHO memory tree\n");
- return -ENOENT;
- }
+ if (kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL))
+ goto err;
- if (!*mem)
- return -EINVAL;
+ return;
- tree.root = phys_to_virt(*mem);
- mutex_init(&tree.lock);
- return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
+err:
+ /*
+ * Failed to initialize preserved memory. Clear FDT and radix so KHO
+ * users don't treat it as a KHO boot.
+ */
+ kho_in.fdt_phys = 0;
+ kho_in.radix_tree.root = NULL;
}
static __init int kho_out_fdt_setup(void)
@@ -1554,16 +1824,14 @@ 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)) {
err = PTR_ERR(kho_out.fdt);
- goto err_free_kho_radix_tree_root;
+ goto err_free_kho_radix_tree;
}
err = kho_debugfs_init();
@@ -1614,9 +1882,8 @@ static __init int kho_init(void)
err_free_fdt:
kho_unpreserve_free(kho_out.fdt);
-err_free_kho_radix_tree_root:
- kfree(tree->root);
- tree->root = NULL;
+err_free_kho_radix_tree:
+ kho_radix_destroy_tree(tree);
err_free_scratch:
kho_out.fdt = NULL;
for (int i = 0; i < kho_scratch_cnt; i++) {
@@ -1630,16 +1897,52 @@ err_free_scratch:
}
fs_initcall(kho_init);
-void __init kho_memory_init(void)
+void __init kho_memory_init_early(void)
{
- if (kho_in.scratch_phys) {
- kho_scratch = phys_to_virt(kho_in.scratch_phys);
+ const void *fdt = kho_get_fdt();
+ void *mem_map;
- if (kho_mem_retrieve(kho_get_fdt()))
- kho_in.fdt_phys = 0;
- } else {
+ if (!is_kho_boot())
+ return;
+
+ /*
+ * kho_get_mem_map() should always succeed. If it fails, kho_populate()
+ * catches that and never sets kho_in.scratch_phys, which stops memory
+ * retrieval.
+ */
+ mem_map = kho_get_mem_map(fdt);
+ if (WARN_ON(!mem_map))
+ goto err;
+
+ /*
+ * kho_scratch_overlap() needs kho_scratch to be initialized. It
+ * is used by free_area_init() on KHO boots, so initialize it
+ * early.
+ */
+ kho_scratch = phys_to_virt(kho_in.scratch_phys);
+
+ if (kho_radix_init_tree(&kho_in.radix_tree, mem_map))
+ goto err;
+
+ kho_extend_scratch();
+
+ return;
+
+err:
+ /*
+ * Failed to initialize preserved memory radix tree. Clear FDT
+ * and scratch so KHO users don't treat it as a KHO boot.
+ */
+ kho_in.fdt_phys = 0;
+ kho_in.scratch_phys = 0;
+}
+
+void __init kho_memory_init(void)
+{
+ if (kho_in.scratch_phys)
+ kho_mem_retrieve();
+ else
kho_reserve_scratch();
- }
}
void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c
deleted file mode 100644
index 6efb696f5426..000000000000
--- a/kernel/liveupdate/kexec_handover_debug.c
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * kexec_handover_debug.c - kexec handover optional debug functionality
- * Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com>
- */
-
-#define pr_fmt(fmt) "KHO: " fmt
-
-#include "kexec_handover_internal.h"
-
-bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
- phys_addr_t scratch_start, scratch_end;
- unsigned int i;
-
- for (i = 0; i < kho_scratch_cnt; i++) {
- scratch_start = kho_scratch[i].addr;
- scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
-
- if (phys < scratch_end && (phys + size) > scratch_start)
- return true;
- }
-
- return false;
-}
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 0399ff107775..805d2a76c388 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,
void *blob) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
-#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
-bool kho_scratch_overlap(phys_addr_t phys, size_t size);
-#else
-static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
- return false;
-}
-#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */
-
#endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index c39f96961a85..dbae0715220b 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -284,6 +284,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)
@@ -341,6 +342,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;
@@ -374,6 +376,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;
@@ -395,6 +398,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;
@@ -587,6 +591,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) {
@@ -620,6 +625,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;
@@ -637,6 +643,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;
@@ -926,3 +933,65 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
}
+
+/**
+ * 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;
+}
+EXPORT_SYMBOL_GPL(liveupdate_get_token_outgoing);
+
+/**
+ * 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.
+ *
+ * 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);
+}
+EXPORT_SYMBOL_GPL(liveupdate_get_file_incoming);
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 64879ffe7378..dac6644bfb18 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -77,6 +77,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);
void __init luo_session_setup_outgoing(u64 *sessions_pa);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 1878edafab23..4f6f58bf3db6 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3081,14 +3081,7 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)
if (hugetlb_early_cma(h))
return hugetlb_cma_alloc_bootmem(h, nid, node_exact);
- if (node_exact)
- return memblock_alloc_exact_nid_raw(huge_page_size(h),
- huge_page_size(h), 0,
- MEMBLOCK_ALLOC_ACCESSIBLE, nid);
-
- return memblock_alloc_try_nid_raw(huge_page_size(h),
- huge_page_size(h), 0,
- MEMBLOCK_ALLOC_ACCESSIBLE, nid);
+ return memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);
}
void *__init arch_alloc_bootmem_huge_page(struct hstate *h, int nid)
diff --git a/mm/memblock.c b/mm/memblock.c
index 036316e9138b..9ce86349a29f 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -19,11 +19,9 @@
#include <linux/mutex.h>
#include <linux/string_helpers.h>
-#ifdef CONFIG_KEXEC_HANDOVER
#include <linux/libfdt.h>
#include <linux/kexec_handover.h>
#include <linux/kho/abi/memblock.h>
-#endif /* CONFIG_KEXEC_HANDOVER */
#include <asm/sections.h>
#include <linux/io.h>
@@ -1507,6 +1505,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,
return 0;
}
+static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size,
+ bool kmemleak_trace)
+{
+ /*
+ * Skip kmemleak for those places like kasan_init() and
+ * early_pgtable_alloc() due to high volume.
+ */
+ if (kmemleak_trace)
+ /*
+ * Memblock allocated blocks are never reported as
+ * leaks. This is because many of these blocks are
+ * only referred via the physical address which is
+ * not looked up by kmemleak.
+ */
+ kmemleak_alloc_phys(start, size, 0);
+
+ /*
+ * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
+ * require memory to be accepted before it can be used by the
+ * guest.
+ *
+ * Accept the memory of the allocated buffer.
+ */
+ accept_memory(start, size);
+}
+
/**
* memblock_alloc_range_nid - allocate boot memory block
* @size: size of memory block to be allocated in bytes
@@ -1581,28 +1605,7 @@ again:
return 0;
done:
- /*
- * Skip kmemleak for those places like kasan_init() and
- * early_pgtable_alloc() due to high volume.
- */
- if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)
- /*
- * Memblock allocated blocks are never reported as
- * leaks. This is because many of these blocks are
- * only referred via the physical address which is
- * not looked up by kmemleak.
- */
- kmemleak_alloc_phys(found, size, 0);
-
- /*
- * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
- * require memory to be accepted before it can be used by the
- * guest.
- *
- * Accept the memory of the allocated buffer.
- */
- accept_memory(found, size);
-
+ memblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE);
return found;
}
@@ -1758,6 +1761,77 @@ void * __init memblock_alloc_try_nid_raw(
}
/**
+ * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages
+ * @size: size of the memory to be allocated in bytes
+ * @nid: nid of the free memory to find, %NUMA_NO_NODE for any node
+ * @exact_nid: only allocate from the specified nid. If %false, the specified
+ * nid is tried first, and then all nodes are tried as fallback.
+ *
+ * HugeTLB pages are always aligned by their size, so the alignment matches
+ * @size. Since the memory is for userspace, mirrored memory is not used. The
+ * memory is not zeroed. Does not panic if request cannot be satisfied.
+ *
+ * Return:
+ * Virtual address of allocated memory block on success, %NULL on failure.
+ */
+void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)
+{
+ enum memblock_flags flags = choose_memblock_flags();
+ phys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE;
+
+ memblock_dbg("%s: %llu bytes, nid=%d, exact_nid=%d %pS\n", __func__,
+ (u64)size, nid, exact_nid, (void *)_RET_IP_);
+
+ /* Don't waste mirrored memory on HugeTLB pages. */
+ flags &= ~MEMBLOCK_MIRROR;
+retry:
+ /* HugeTLB pages are always aligned by their size. */
+ addr = memblock_find_in_range_node(size, size, start, end, nid, flags);
+ if (addr)
+ goto found;
+
+ /* Try all nodes if allowed. */
+ if (numa_valid_node(nid) && !exact_nid) {
+ nid = NUMA_NO_NODE;
+ /*
+ * If a previous candidate overlapped with KHO scratch, it would
+ * update start or end. Now that the search is opening to all
+ * nodes, reset them.
+ */
+ start = 0;
+ end = MEMBLOCK_ALLOC_ACCESSIBLE;
+
+ goto retry;
+ }
+
+ /* Found nothing... :-( */
+ return NULL;
+
+found:
+ /*
+ * HugeTLB pages can be preserved with KHO and no preserved memory can
+ * be in scratch. So retry if found address overlaps with scratch.
+ *
+ * Scratch areas are normally not very large, so this shouldn't take too
+ * many retries.
+ */
+ if (kho_scratch_overlap(addr, size)) {
+ if (memblock_bottom_up())
+ start = addr + size;
+ else
+ end = addr;
+
+ goto retry;
+ }
+
+ if (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB))
+ return NULL;
+
+ memblock_prep_allocation(addr, size, true);
+ return phys_to_virt(addr);
+}
+
+/**
* memblock_alloc_try_nid - allocate boot memory block
* @size: size of memory block to be allocated in bytes
* @align: alignment of the region and block's size
@@ -1826,6 +1900,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void)
return memblock.reserved.total_size;
}
+phys_addr_t __init_memblock memblock_reserved_hugetlb_size(phys_addr_t limit, int nid)
+{
+ struct memblock_region *r;
+ phys_addr_t total = 0;
+
+ for_each_reserved_mem_region(r) {
+ phys_addr_t size = r->size;
+
+ if (r->base > limit)
+ break;
+
+ if (r->base + r->size > limit)
+ size = limit - r->base;
+
+ if (nid == memblock_get_region_node(r) || !numa_valid_node(nid))
+ if (r->flags & MEMBLOCK_RSRV_HUGETLB)
+ total += size;
+ }
+
+ return total;
+}
+
phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid)
{
struct memblock_region *r;
@@ -2510,16 +2606,6 @@ __init void memblock_clear_kho_scratch_only(void)
{
kho_scratch_only = false;
}
-
-bool __init_memblock memblock_is_kho_scratch_memory(phys_addr_t addr)
-{
- int i = memblock_search(&memblock.memory, addr);
-
- if (i == -1)
- return false;
-
- return memblock_is_kho_scratch(&memblock.memory.regions[i]);
-}
#endif
#ifdef CONFIG_KEXEC_HANDOVER
diff --git a/mm/mm_init.c b/mm/mm_init.c
index d859058c1d14..1533aebafb68 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -684,7 +684,9 @@ static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
const unsigned long end = pfn + nr_pages;
for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) {
- init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false);
+ enum migratetype mt = kho_scratch_migratetype(pfn, migratetype);
+
+ init_pageblock_migratetype(pfn_to_page(pfn), mt, false);
if (!atomic && IS_ALIGNED(pfn, PAGES_PER_SECTION))
cond_resched();
}
@@ -901,8 +903,9 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
* over the place during system boot.
*/
if (pageblock_aligned(pfn)) {
- init_pageblock_migratetype(page, migratetype,
- isolate_pageblock);
+ enum migratetype mt = kho_scratch_migratetype(pfn, migratetype);
+
+ init_pageblock_migratetype(page, mt, isolate_pageblock);
cond_resched();
}
pfn++;
@@ -912,8 +915,7 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
static void __init memmap_init_zone_range(struct zone *zone,
unsigned long start_pfn,
unsigned long end_pfn,
- unsigned long *hole_pfn,
- enum migratetype mt)
+ unsigned long *hole_pfn)
{
unsigned long zone_start_pfn = zone->zone_start_pfn;
unsigned long zone_end_pfn = zone_start_pfn + zone->spanned_pages;
@@ -926,7 +928,8 @@ static void __init memmap_init_zone_range(struct zone *zone,
return;
memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,
- zone_end_pfn, MEMINIT_EARLY, NULL, mt, false);
+ zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE,
+ false);
if (*hole_pfn < start_pfn)
init_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);
@@ -942,8 +945,6 @@ static void __init memmap_init(void)
for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
struct pglist_data *node = NODE_DATA(nid);
- enum migratetype mt =
- kho_scratch_migratetype(start_pfn, MIGRATE_MOVABLE);
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = node->node_zones + j;
@@ -952,7 +953,7 @@ static void __init memmap_init(void)
continue;
memmap_init_zone_range(zone, start_pfn, end_pfn,
- &hole_pfn, mt);
+ &hole_pfn);
zone_id = j;
}
}
@@ -1912,7 +1913,7 @@ unsigned long __init node_map_pfn_alignment(void)
#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
static void __init deferred_free_pages(unsigned long pfn,
- unsigned long nr_pages, enum migratetype mt)
+ unsigned long nr_pages)
{
struct page *page;
unsigned long i;
@@ -1920,7 +1921,7 @@ static void __init deferred_free_pages(unsigned long pfn,
if (!nr_pages)
return;
- pageblock_migratetype_init_range(pfn, nr_pages, mt, true);
+ pageblock_migratetype_init_range(pfn, nr_pages, MIGRATE_MOVABLE, true);
page = pfn_to_page(pfn);
@@ -1990,8 +1991,6 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,
for_each_free_mem_range(i, nid, 0, &start, &end, NULL) {
unsigned long spfn = PFN_UP(start);
unsigned long epfn = PFN_DOWN(end);
- enum migratetype mt =
- kho_scratch_migratetype(spfn, MIGRATE_MOVABLE);
if (spfn >= end_pfn)
break;
@@ -2004,7 +2003,7 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,
unsigned long chunk_end = min(mo_pfn, epfn);
nr_pages += deferred_init_pages(zone, spfn, chunk_end);
- deferred_free_pages(spfn, chunk_end - spfn, mt);
+ deferred_free_pages(spfn, chunk_end - spfn);
spfn = chunk_end;
@@ -2627,6 +2626,8 @@ void __init __weak mem_init(void)
void __init mm_core_init_early(void)
{
+ kho_memory_init_early();
+
free_area_init();
hugetlb_cma_reserve();
diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h
index 4168e646af09..f1974e009013 100644
--- a/tools/testing/memblock/internal.h
+++ b/tools/testing/memblock/internal.h
@@ -46,4 +46,9 @@ static inline bool __is_kernel(unsigned long addr)
#define __SetPageReserved(p) ((void)(p))
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+ return false;
+}
+
#endif
diff --git a/tools/testing/memblock/linux/kexec_handover.h b/tools/testing/memblock/linux/kexec_handover.h
new file mode 100644
index 000000000000..bdfd9445b1fd
--- /dev/null
+++ b/tools/testing/memblock/linux/kexec_handover.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef LINUX_KEXEC_HANDOVER_H
+#define LINUX_KEXEC_HANDOVER_H
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually implement
+ * any KHO functions as they are not used in the tests.
+ */
+#endif /* LINUX_KEXEC_HANDOVER_H */
diff --git a/tools/testing/memblock/linux/kho/abi/memblock.h b/tools/testing/memblock/linux/kho/abi/memblock.h
new file mode 100644
index 000000000000..71cf6982a2b9
--- /dev/null
+++ b/tools/testing/memblock/linux/kho/abi/memblock.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_KHO_ABI_MEMBLOCK_H
+#define _LINUX_KHO_ABI_MEMBLOCK_H
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually define
+ * any ABI as they are not used in the tests.
+ */
+#endif /* _LINUX_KHO_ABI_MEMBLOCK_H */
diff --git a/tools/testing/memblock/linux/libfdt.h b/tools/testing/memblock/linux/libfdt.h
new file mode 100644
index 000000000000..6137b788fe8d
--- /dev/null
+++ b/tools/testing/memblock/linux/libfdt.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INCLUDE_LIBFDT_H_
+#define _INCLUDE_LIBFDT_H_
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually implement
+ * any FDT functions as they are not used in the tests.
+ */
+#endif /* _INCLUDE_LIBFDT_H_ */