AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/10 13:41 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "kho_scratch_overlap",
    "kho_init_scratch_migratetype",
    "memblock_alloc_hugetlb"
  ],
  "Reasoning": "The patch modifies the Kexec Handover (KHO) radix tree implementation, introduces new functions for KHO scratch memory management, and changes how HugeTLB pages are allocated during boot using memblock. These are functional changes to core kernel logic and memory management that can be reached during boot and runtime, making them worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/07/10 13:41 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 15a895b27d7069b43e3b128df857ebccd3e38092\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 10 13:41:24 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h\nindex 48a9793c2b76cd..30a71710565a32 100644\n--- a/include/linux/kexec_handover.h\n+++ b/include/linux/kexec_handover.h\n@@ -33,9 +33,13 @@ void kho_remove_subtree(void *blob);\n int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);\n \n void kho_memory_init(void);\n+void kho_memory_init_early(void);\n \n void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,\n \t\t  u64 scratch_len);\n+\n+bool kho_scratch_overlap(phys_addr_t phys, size_t size);\n+void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn);\n #else\n static inline bool kho_is_enabled(void)\n {\n@@ -108,10 +112,22 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,\n \n static inline void kho_memory_init(void) { }\n \n+static inline void kho_memory_init_early(void) { }\n+\n static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n \t\t\t\tphys_addr_t scratch_phys, u64 scratch_len)\n {\n }\n+\n+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\treturn false;\n+}\n+\n+static inline void kho_init_scratch_migratetype(unsigned long start_pfn,\n+\t\t\t\t\t\tunsigned long end_pfn);\n+{\n+}\n #endif /* CONFIG_KEXEC_HANDOVER */\n \n #endif /* LINUX_KEXEC_HANDOVER_H */\ndiff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h\nindex 5e2eb8519bda6d..2f4fb9c639421a 100644\n--- a/include/linux/kho/abi/kexec_handover.h\n+++ b/include/linux/kho/abi/kexec_handover.h\n@@ -257,11 +257,8 @@ struct kho_vmalloc {\n  * memory. These constants govern the indexing, sizing, and depth of the tree.\n  */\n enum kho_radix_consts {\n-\t/*\n-\t * The bit position of the order bit (and also the length of the\n-\t * shifted physical address) for an order-0 page.\n-\t */\n-\tKHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,\n+\t/* Need to store the PFN, plus one bit for order. */\n+\tKHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1,\n \n \t/* Size of the table in kho_radix_node, in log2 */\n \tKHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),\n@@ -274,7 +271,7 @@ enum kho_radix_consts {\n \t * and 1 bitmap level.\n \t */\n \tKHO_TREE_MAX_DEPTH =\n-\t\tDIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,\n+\t\tDIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2,\n \t\t\t     KHO_TABLE_SIZE_LOG2) + 1,\n };\n \ndiff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h\nindex 84e918b96e53df..5d6ae2893684a4 100644\n--- a/include/linux/kho_radix_tree.h\n+++ b/include/linux/kho_radix_tree.h\n@@ -34,37 +34,53 @@ struct kho_radix_tree {\n \tstruct mutex lock; /* protects the tree's structure and root pointer */\n };\n \n-typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,\n-\t\t\t\t\t      unsigned int order);\n+/**\n+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.\n+ * @leaf:      Called on each present key in the radix tree.\n+ * @node:      Called on each node of the radix tree itself. Receives the\n+ *             physical address of the page containing the node.\n+ *\n+ * For each callback, a return value of 0 continues the walk and a non-zero\n+ * return value is directly returned to the caller.\n+ */\n+struct kho_radix_walk_cb {\n+\tint (*leaf)(unsigned long key, void *data);\n+\tint (*node)(phys_addr_t phys, void *data);\n+};\n \n #ifdef CONFIG_KEXEC_HANDOVER\n \n-int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t       unsigned int order);\n-\n-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t\tunsigned int order);\n-\n+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);\n+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);\n int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\tkho_radix_tree_walk_callback_t cb);\n+\t\t\tconst struct kho_radix_walk_cb *cb, void *data);\n+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);\n+void kho_radix_destroy_tree(struct kho_radix_tree *tree);\n \n #else  /* #ifdef CONFIG_KEXEC_HANDOVER */\n \n-static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,\n-\t\t\t\t     unsigned int order)\n+static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\n {\n \treturn -EOPNOTSUPP;\n }\n \n-static inline void kho_radix_del_page(struct kho_radix_tree *tree,\n-\t\t\t\t      unsigned long pfn, unsigned int order) { }\n+static inline void kho_radix_del_key(struct kho_radix_tree *tree,\n+\t\t\t\t     unsigned long key) { }\n \n static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\t\t      kho_radix_tree_walk_callback_t cb)\n+\t\t\t\t      const struct kho_radix_walk_cb *cb, void *data)\n {\n \treturn -EOPNOTSUPP;\n }\n \n+static inline int kho_radix_init_tree(struct kho_radix_tree *tree,\n+\t\t\t\t      struct kho_radix_node *root)\n+{\n+\treturn 0;\n+}\n+\n+static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }\n+\n #endif /* #ifdef CONFIG_KEXEC_HANDOVER */\n \n #endif\t/* _LINUX_KHO_RADIX_TREE_H */\ndiff --git a/include/linux/memblock.h b/include/linux/memblock.h\nindex 5afcd99aa8c144..d62db9e776cf94 100644\n--- a/include/linux/memblock.h\n+++ b/include/linux/memblock.h\n@@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn;\n  * memory reservations yet, so we get scratch memory from the previous\n  * kernel that we know is good to use. It is the only memory that\n  * allocations may happen from in this phase.\n+ * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages\n  */\n enum memblock_flags {\n \tMEMBLOCK_NONE\t\t= 0x0,\t/* No special request */\n@@ -61,6 +62,7 @@ enum memblock_flags {\n \tMEMBLOCK_RSRV_NOINIT\t= 0x10,\t/* don't initialize struct pages */\n \tMEMBLOCK_RSRV_KERN\t= 0x20,\t/* memory reserved for kernel use */\n \tMEMBLOCK_KHO_SCRATCH\t= 0x40,\t/* scratch memory for kexec handover */\n+\tMEMBLOCK_RSRV_HUGETLB\t= 0x80, /* memory reserved for hugetlb pages */\n };\n \n /**\n@@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,\n void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,\n \t\t\t     phys_addr_t min_addr, phys_addr_t max_addr,\n \t\t\t     int nid);\n+void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);\n \n static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)\n {\n@@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void)\n phys_addr_t memblock_phys_mem_size(void);\n phys_addr_t memblock_reserved_size(void);\n phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);\n+phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid);\n unsigned long memblock_estimated_nr_free_pages(void);\n phys_addr_t memblock_start_of_DRAM(void);\n phys_addr_t memblock_end_of_DRAM(void);\n@@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { }\n #ifdef CONFIG_MEMBLOCK_KHO_SCRATCH\n void memblock_set_kho_scratch_only(void);\n void memblock_clear_kho_scratch_only(void);\n-bool memblock_is_kho_scratch_memory(phys_addr_t addr);\n-\n-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n-\t\t\t\t\t\t       enum migratetype mt)\n-{\n-\tif (memblock_is_kho_scratch_memory(PFN_PHYS(pfn)))\n-\t\treturn MIGRATE_CMA;\n-\treturn mt;\n-}\n #else\n static inline void memblock_set_kho_scratch_only(void) { }\n static inline void memblock_clear_kho_scratch_only(void) { }\n-static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr)\n-{\n-\treturn false;\n-}\n-\n-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n-\t\t\t\t\t\t       enum migratetype mt)\n-{\n-\treturn mt;\n-}\n #endif\n \n #endif /* _LINUX_MEMBLOCK_H */\ndiff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile\nindex eec9d3ae07eb4b..d9f46946255673 100644\n--- a/kernel/liveupdate/Makefile\n+++ b/kernel/liveupdate/Makefile\n@@ -8,7 +8,6 @@ luo-y :=\t\t\t\t\t\t\t\t\\\n \t\tluo_session.o\n \n obj-$(CONFIG_KEXEC_HANDOVER)\t\t+= kexec_handover.o\n-obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)\t+= kexec_handover_debug.o\n obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)\t+= kexec_handover_debugfs.o\n \n obj-$(CONFIG_LIVEUPDATE)\t\t+= luo.o\ndiff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c\nindex 4834a809985ab7..9dc79b7103bdbf 100644\n--- a/kernel/liveupdate/kexec_handover.c\n+++ b/kernel/liveupdate/kexec_handover.c\n@@ -84,8 +84,25 @@ static struct kho_out kho_out = {\n \t},\n };\n \n+struct kho_in {\n+\tphys_addr_t fdt_phys;\n+\tphys_addr_t scratch_phys;\n+\tchar previous_release[__NEW_UTS_LEN + 1];\n+\tu32 kexec_count;\n+\tstruct kho_debugfs dbg;\n+\tstruct kho_radix_tree radix_tree;\n+};\n+\n+static struct kho_in kho_in = {\n+};\n+\n+static const void *kho_get_fdt(void)\n+{\n+\treturn kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;\n+}\n+\n /**\n- * kho_radix_encode_key - Encodes a physical address and order into a radix key.\n+ * kho_encode_radix_key - Encodes a physical address and order into a radix key.\n  * @phys: The physical address of the page.\n  * @order: The order of the page.\n  *\n@@ -95,35 +112,38 @@ static struct kho_out kho_out = {\n  *\n  * Return: The encoded unsigned long radix key.\n  */\n-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)\n+static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)\n {\n-\t/* Order bits part */\n-\tunsigned long h = 1UL \u003c\u003c (KHO_ORDER_0_LOG2 - order);\n-\t/* Shifted physical address part */\n-\tunsigned long l = phys \u003e\u003e (PAGE_SHIFT + order);\n+\t/* The physical address is encoded by shifting the PFN by its order. */\n+\tunsigned long shift = PAGE_SHIFT + order;\n+\t/* Order bit goes right before the shifted PFN. */\n+\tunsigned long h = 1UL \u003c\u003c (64 - shift);\n+\t/* Shifted PFN. */\n+\tunsigned long l = phys \u003e\u003e shift;\n \n \treturn h | l;\n }\n \n /**\n- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.\n+ * kho_decode_radix_key - Decodes a radix key back into a physical address and order.\n  * @key: The unsigned long key to decode.\n  * @order: An output parameter, a pointer to an unsigned int where the decoded\n  *         page order will be stored.\n  *\n- * This function reverses the encoding performed by kho_radix_encode_key(),\n+ * This function reverses the encoding performed by kho_encode_radix_key(),\n  * extracting the original physical address and page order from a given key.\n  *\n  * Return: The decoded physical address.\n  */\n-static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)\n+static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)\n {\n-\tunsigned int order_bit = fls64(key);\n+\t/* fls64() indexes starting from 1. */\n+\tunsigned int order_bit = fls64(key) - 1;\n \tphys_addr_t phys;\n \n-\t/* order_bit is numbered starting at 1 from fls64 */\n-\t*order = KHO_ORDER_0_LOG2 - order_bit + 1;\n-\t/* The order is discarded by the shift */\n+\t/* order bit goes right before the shifted PFN. */\n+\t*order = 64 - (PAGE_SHIFT + order_bit);\n+\t/* The order bit is discarded by the shift */\n \tphys = key \u003c\u003c (PAGE_SHIFT + *order);\n \n \treturn phys;\n@@ -143,25 +163,47 @@ static unsigned long kho_radix_get_table_index(unsigned long key,\n \treturn (key \u003e\u003e s) % (1 \u003c\u003c KHO_TABLE_SIZE_LOG2);\n }\n \n+static void __ref *kho_radix_alloc_node(void)\n+{\n+\tstruct kho_radix_node *node;\n+\n+\tif (slab_is_available())\n+\t\tnode = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);\n+\telse\n+\t\tnode = memblock_alloc(PAGE_SIZE, PAGE_SIZE);\n+\n+\treturn node;\n+}\n+\n+static void __ref kho_radix_free_node(struct kho_radix_node *node)\n+{\n+\tif (slab_is_available())\n+\t\tfree_page((unsigned long)node);\n+\telse\n+\t\tmemblock_free(node, PAGE_SIZE);\n+}\n+\n /**\n- * kho_radix_add_page - Marks a page as preserved in the radix tree.\n+ * kho_radix_add_key - Add a key to the radix tree.\n  * @tree: The KHO radix tree.\n- * @pfn: The page frame number of the page to preserve.\n- * @order: The order of the page.\n+ * @key: The key to add.\n  *\n- * This function traverses the radix tree based on the key derived from @pfn\n- * and @order. It sets the corresponding bit in the leaf bitmap to mark the\n- * page for preservation. If intermediate nodes do not exist along the path,\n- * they are allocated and added to the tree.\n+ * This function traverses the radix tree based on the @key provided. It sets the\n+ * corresponding bit in the leaf bitmap to mark the @key as present. If\n+ * intermediate nodes do not exist along the path, they are allocated and added\n+ * to the tree.\n+ *\n+ * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported.\n+ * This limit only exists because current users of the radix tree don't use more\n+ * than that. Changing the maximum width requires changing the tree depth, which\n+ * needs bumping the ABI version.\n  *\n  * Return: 0 on success, or a negative error code on failure.\n  */\n-int kho_radix_add_page(struct kho_radix_tree *tree,\n-\t\t       unsigned long pfn, unsigned int order)\n+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\n {\n \t/* Newly allocated nodes for error cleanup */\n \tstruct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };\n-\tunsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);\n \tstruct kho_radix_node *anchor_node = NULL;\n \tstruct kho_radix_node *node = tree-\u003eroot;\n \tstruct kho_radix_node *new_node;\n@@ -172,6 +214,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn -EINVAL;\n \n+\tif (unlikely(fls64(key) \u003e KHO_RADIX_KEY_WIDTH))\n+\t\treturn -ERANGE;\n+\n \tmight_sleep();\n \n \tguard(mutex)(\u0026tree-\u003elock);\n@@ -186,7 +231,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n \t\t}\n \n \t\t/* Next node is empty, create a new node for it */\n-\t\tnew_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);\n+\t\tnew_node = kho_radix_alloc_node();\n \t\tif (!new_node) {\n \t\t\terr = -ENOMEM;\n \t\t\tgoto err_free_nodes;\n@@ -217,29 +262,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n err_free_nodes:\n \tfor (i = KHO_TREE_MAX_DEPTH - 1; i \u003e 0; i--) {\n \t\tif (intermediate_nodes[i])\n-\t\t\tfree_page((unsigned long)intermediate_nodes[i]);\n+\t\t\tkho_radix_free_node(intermediate_nodes[i]);\n \t}\n \tif (anchor_node)\n \t\tanchor_node-\u003etable[anchor_idx] = 0;\n \n \treturn err;\n }\n-EXPORT_SYMBOL_GPL(kho_radix_add_page);\n+EXPORT_SYMBOL_GPL(kho_radix_add_key);\n \n /**\n- * kho_radix_del_page - Removes a page's preservation status from the radix tree.\n+ * kho_radix_del_key - Removes the key from the radix tree.\n  * @tree: The KHO radix tree.\n- * @pfn: The page frame number of the page to unpreserve.\n- * @order: The order of the page.\n+ * @key: The key to remove.\n  *\n  * This function traverses the radix tree and clears the bit corresponding to\n- * the page, effectively removing its \"preserved\" status. It does not free\n- * the tree's intermediate nodes, even if they become empty.\n+ * the @key, effectively removing it from the tree. It does not free the tree's\n+ * intermediate nodes, even if they become empty.\n  */\n-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t\tunsigned int order)\n+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)\n {\n-\tunsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);\n \tstruct kho_radix_node *node = tree-\u003eroot;\n \tstruct kho_radix_leaf *leaf;\n \tunsigned int i, idx;\n@@ -247,6 +289,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn;\n \n+\t/* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */\n+\tif (unlikely(fls64(key) \u003e KHO_RADIX_KEY_WIDTH))\n+\t\treturn;\n+\n \tmight_sleep();\n \n \tguard(mutex)(\u0026tree-\u003elock);\n@@ -270,21 +316,85 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n \tidx = kho_radix_get_bitmap_index(key);\n \t__clear_bit(idx, leaf-\u003ebitmap);\n }\n-EXPORT_SYMBOL_GPL(kho_radix_del_page);\n+EXPORT_SYMBOL_GPL(kho_radix_del_key);\n+\n+static void __kho_radix_destroy_tree(struct kho_radix_node *root,\n+\t\t\t\t     unsigned int level)\n+{\n+\tunsigned long i;\n+\n+\tif (level == 0) {\n+\t\tkho_radix_free_node(root);\n+\t\treturn;\n+\t}\n+\n+\tfor (i = 0; i \u003c PAGE_SIZE / sizeof(phys_addr_t); i++) {\n+\t\tif (root-\u003etable[i])\n+\t\t\t__kho_radix_destroy_tree(phys_to_virt(root-\u003etable[i]),\n+\t\t\t\t\t\t level - 1);\n+\t}\n+\n+\tkho_radix_free_node(root);\n+}\n+\n+/**\n+ * kho_radix_init_tree - initialize the radix tree.\n+ * @tree:   the tree to initialize.\n+ * @root:   root table of the radix tree.\n+ *\n+ * Initialize the radix tree with the given root node. If root is %NULL, an\n+ * empty root table is allocated. If root is not %NULL, it is the caller's\n+ * responsibility to make sure the root is valid and in the correct format.\n+ *\n+ * Return: 0 on success, -errno on failure.\n+ */\n+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)\n+{\n+\tif (!root)\n+\t\troot = kho_radix_alloc_node();\n+\tif (!root)\n+\t\treturn -ENOMEM;\n+\n+\ttree-\u003eroot = root;\n+\tmutex_init(\u0026tree-\u003elock);\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(kho_radix_init_tree);\n+\n+/**\n+ * kho_radix_destroy_tree - Destroy the radix tree\n+ * @tree: The radix tree to destroy\n+ *\n+ * Walk @tree and free all its nodes.\n+ */\n+void kho_radix_destroy_tree(struct kho_radix_tree *tree)\n+{\n+\tif (!tree-\u003eroot)\n+\t\treturn;\n+\n+\t__kho_radix_destroy_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1);\n+\ttree-\u003eroot = NULL;\n+}\n+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);\n \n-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,\n-\t\t\t       unsigned long key,\n-\t\t\t       kho_radix_tree_walk_callback_t cb)\n+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,\n+\t\t\t       const struct kho_radix_walk_cb *cb, void *data)\n {\n \tunsigned long *bitmap = (unsigned long *)leaf;\n-\tunsigned int order;\n-\tphys_addr_t phys;\n \tunsigned int i;\n \tint err;\n \n+\tif (cb-\u003enode) {\n+\t\terr = cb-\u003enode(virt_to_phys(leaf), data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (!cb-\u003eleaf)\n+\t\treturn 0;\n+\n \tfor_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {\n-\t\tphys = kho_radix_decode_key(key | i, \u0026order);\n-\t\terr = cb(phys, order);\n+\t\terr = cb-\u003eleaf(key | i, data);\n \t\tif (err)\n \t\t\treturn err;\n \t}\n@@ -294,7 +404,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,\n \n static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \t\t\t\t unsigned int level, unsigned long start,\n-\t\t\t\t kho_radix_tree_walk_callback_t cb)\n+\t\t\t\t const struct kho_radix_walk_cb *cb, void *data)\n {\n \tstruct kho_radix_node *node;\n \tstruct kho_radix_leaf *leaf;\n@@ -302,6 +412,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \tunsigned int shift;\n \tint err;\n \n+\tif (cb-\u003enode) {\n+\t\terr = cb-\u003enode(virt_to_phys(root), data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n \tfor (i = 0; i \u003c PAGE_SIZE / sizeof(phys_addr_t); i++) {\n \t\tif (!root-\u003etable[i])\n \t\t\tcontinue;\n@@ -318,10 +434,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \t\t\t * node is pointing to the level 0 bitmap.\n \t\t\t */\n \t\t\tleaf = (struct kho_radix_leaf *)node;\n-\t\t\terr = kho_radix_walk_leaf(leaf, key, cb);\n+\t\t\terr = kho_radix_walk_leaf(leaf, key, cb, data);\n \t\t} else {\n \t\t\terr  = __kho_radix_walk_tree(node, level - 1,\n-\t\t\t\t\t\t     key, cb);\n+\t\t\t\t\t\t     key, cb, data);\n \t\t}\n \n \t\tif (err)\n@@ -332,28 +448,27 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n }\n \n /**\n- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.\n+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.\n  * @tree: A pointer to the KHO radix tree to walk.\n- * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be\n- *      invoked for each preserved page found in the tree. The callback receives\n- *      the physical address and order of the preserved page.\n+ * @cb:   Set of callbacks to be invoked during the tree walk.\n+ * @data: Opaque data pointer passed to each callback in @cb.\n  *\n- * This function walks the radix tree, searching from the specified top level\n- * down to the lowest level (level 0). For each preserved page found, it invokes\n- * the provided callback, passing the page's physical address and order.\n+ * This function walks the radix tree, searching from the top level down to the\n+ * lowest level (level 0), invoking the appropriate callbacks.\n  *\n  * Return: 0 if the walk completed the specified tree, or the non-zero return\n  *         value from the callback that stopped the walk.\n  */\n int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\tkho_radix_tree_walk_callback_t cb)\n+\t\t\tconst struct kho_radix_walk_cb *cb, void *data)\n {\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn -EINVAL;\n \n \tguard(mutex)(\u0026tree-\u003elock);\n \n-\treturn __kho_radix_walk_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1, 0, cb);\n+\treturn __kho_radix_walk_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1, 0, cb,\n+\t\t\t\t     data);\n }\n EXPORT_SYMBOL_GPL(kho_radix_walk_tree);\n \n@@ -484,13 +599,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,\n \treturn pfn_to_page(pfn);\n }\n \n-static int __init kho_preserved_memory_reserve(phys_addr_t phys,\n-\t\t\t\t\t       unsigned int order)\n+static int __init kho_preserved_memory_reserve(unsigned long key, void *data)\n {\n \tunion kho_page_info info;\n \tstruct page *page;\n+\tunsigned int order;\n+\tphys_addr_t phys;\n \tu64 sz;\n \n+\tphys = kho_decode_radix_key(key, \u0026order);\n+\n \tsz = 1 \u003c\u003c (order + PAGE_SHIFT);\n \tpage = kho_get_preserved_page(phys, order);\n \n@@ -504,19 +622,24 @@ static int __init kho_preserved_memory_reserve(phys_addr_t phys,\n \treturn 0;\n }\n \n-/* Returns physical address of the preserved memory map from FDT */\n-static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)\n+/* Returns virtual address of the preserved memory map from FDT */\n+static __init void *kho_get_mem_map(const void *fdt)\n {\n \tconst void *mem_ptr;\n+\tphys_addr_t mem_map_phys;\n \tint len;\n \n \tmem_ptr = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, \u0026len);\n \tif (!mem_ptr || len != sizeof(u64)) {\n \t\tpr_err(\"failed to get preserved memory map\\n\");\n-\t\treturn 0;\n+\t\treturn NULL;\n \t}\n \n-\treturn get_unaligned((const u64 *)mem_ptr);\n+\tmem_map_phys = get_unaligned((const u64 *)mem_ptr);\n+\tif (!mem_map_phys)\n+\t\treturn NULL;\n+\n+\treturn phys_to_virt(mem_map_phys);\n }\n \n /*\n@@ -619,19 +742,24 @@ early_param(\"kho_scratch\", kho_parse_scratch_size);\n static void __init scratch_size_update(void)\n {\n \t/*\n-\t * If fixed sizes are not provided via command line, calculate them\n-\t * now.\n+\t * If fixed sizes are not provided via command line, calculate them now.\n+\t * Remove HugeTLB allocations from it because they never get allocated\n+\t * from scratch.\n \t */\n \tif (scratch_scale) {\n \t\tphys_addr_t size;\n \n \t\tsize = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,\n \t\t\t\t\t\t   NUMA_NO_NODE);\n+\t\tsize -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT,\n+\t\t\t\t\t\t       NUMA_NO_NODE);\n \t\tsize = size * scratch_scale / 100;\n \t\tscratch_size_lowmem = size;\n \n \t\tsize = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,\n \t\t\t\t\t\t   NUMA_NO_NODE);\n+\t\tsize -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,\n+\t\t\t\t\t\t       NUMA_NO_NODE);\n \t\tsize = size * scratch_scale / 100 - scratch_size_lowmem;\n \t\tscratch_size_global = size;\n \t}\n@@ -651,6 +779,9 @@ static phys_addr_t __init scratch_size_node(int nid)\n \tif (scratch_scale) {\n \t\tsize = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,\n \t\t\t\t\t\t   nid);\n+\t\t/* Do not count HugeTLB pages. */\n+\t\tsize -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,\n+\t\t\t\t\t\t       nid);\n \t\tsize = size * scratch_scale / 100;\n \t} else {\n \t\tsize = scratch_size_pernode;\n@@ -659,6 +790,49 @@ static phys_addr_t __init scratch_size_node(int nid)\n \treturn round_up(size, CMA_MIN_ALIGNMENT_BYTES);\n }\n \n+bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\tphys_addr_t scratch_start, scratch_end;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c kho_scratch_cnt; i++) {\n+\t\tscratch_start = kho_scratch[i].addr;\n+\t\tscratch_end = kho_scratch[i].addr + kho_scratch[i].size;\n+\n+\t\tif (phys \u003c scratch_end \u0026\u0026 (phys + size) \u003e scratch_start)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+/*\n+ * Mark all KHO scratch pageblocks between start_pfn and end_pfn as\n+ * MIGRATE_CMA.\n+ */\n+void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn)\n+{\n+\tfor (unsigned int i = 0; i \u003c kho_scratch_cnt; i++) {\n+\t\tunsigned long scratch_start, scratch_end, pfn;\n+\n+\t\tscratch_start = PHYS_PFN(kho_scratch[i].addr);\n+\t\tscratch_end = PHYS_PFN(kho_scratch[i].addr + kho_scratch[i].size);\n+\n+\t\t/* Target range doesn't overlap with scratch. */\n+\t\tif (start_pfn \u003e= scratch_end || end_pfn \u003c= scratch_start)\n+\t\t\tcontinue;\n+\n+\t\tpfn = max_t(unsigned long, start_pfn, scratch_start);\n+\t\tpfn = pageblock_start_pfn(pfn);\n+\n+\t\twhile (pfn \u003c end_pfn \u0026\u0026 pfn \u003c scratch_end) {\n+\t\t\tinit_pageblock_migratetype(pfn_to_page(pfn),\n+\t\t\t\t\t\t   MIGRATE_CMA, false);\n+\t\t\tpfn += pageblock_nr_pages;\n+\t\t}\n+\t}\n+}\n+\n /**\n  * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec\n  *\n@@ -746,6 +920,128 @@ static void __init kho_reserve_scratch(void)\n \tkho_enable = false;\n }\n \n+/*\n+ * Look for free blocks of 1G. This is a heuristic chosen to work efficiently\n+ * with large systems with hundreds of gigabytes of memory. It will work poorly\n+ * on smaller systems. The algorithm itself doesn't depend on the actual value,\n+ * so it can be changed to a different heuristic later if needed.\n+ */\n+#define KHO_EXT_BLKSIZE\t\tSZ_1G\n+#define KHO_EXT_SHIFT\t\tconst_ilog2(KHO_EXT_BLKSIZE)\n+\n+/* Called for the KHO preserved memory radix tree. */\n+static int __init kho_ext_walk_leaf(unsigned long key, void *data)\n+{\n+\t/* Radix tree tracking free blocks. */\n+\tstruct kho_radix_tree *tree = data;\n+\tphys_addr_t start, end;\n+\tunsigned int order;\n+\tint err;\n+\n+\tstart = kho_decode_radix_key(key, \u0026order);\n+\tend = start + (1UL \u003c\u003c (order + PAGE_SHIFT));\n+\n+\twhile (start \u003c end) {\n+\t\terr = kho_radix_add_key(tree, start \u003e\u003e KHO_EXT_SHIFT);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n+\t\tstart += (1UL \u003c\u003c KHO_EXT_SHIFT);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Called for the KHO preserved memory radix tree. */\n+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)\n+{\n+\t/* Radix tree tracking free blocks. */\n+\tstruct kho_radix_tree *tree = data;\n+\n+\treturn kho_radix_add_key(tree, phys \u003e\u003e KHO_EXT_SHIFT);\n+}\n+\n+/* Called for the free block radix tree. */\n+static int __init kho_ext_mark_scratch(unsigned long key, void *data)\n+{\n+\tphys_addr_t *prev_end = data;\n+\tphys_addr_t start = key \u003c\u003c KHO_EXT_SHIFT;\n+\tint err;\n+\n+\tif (start \u003e *prev_end) {\n+\t\terr = memblock_mark_kho_scratch(*prev_end, start - *prev_end);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\t*prev_end = start + (1UL \u003c\u003c KHO_EXT_SHIFT);\n+\treturn 0;\n+}\n+\n+/*\n+ * kho_extend_scratch - Extend the scratch regions\n+ *\n+ * The KHO preserved memory radix tree mixes both physical address and order\n+ * into a single key. This makes it hard to look for free ranges directly. This\n+ * function first walks the radix tree and digests it down into another radix\n+ * tree, whose keys identify blocks of KHO_EXT_SHIFT which contain preserved\n+ * memory.\n+ *\n+ * Then it walks the digested radix tree and marks everything that doesn't have\n+ * preserved memory as scratch.\n+ *\n+ * NOTE: This function allocates memory so it should be called when scratch has\n+ * available space.\n+ *\n+ * NOTE: The pages of the KHO preserved memory radix tree tables are not marked\n+ * as preserved in the preserved memory tree. But they are expected to remain\n+ * untouched until the tree is fully parsed. So this function also considers\n+ * them to be \"preserved memory\" and marks their blocks as busy.\n+ *\n+ * NOTE: efi_init()::reserve_regions() removes all regions except\n+ * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO\n+ * scratch memory, so they should not be removed. This function should always be\n+ * called after reserve_regions().\n+ */\n+static void __init kho_extend_scratch(void)\n+{\n+\tconst struct kho_radix_walk_cb kho_cb = {\n+\t\t.leaf = kho_ext_walk_leaf,\n+\t\t.node = kho_ext_walk_node,\n+\t};\n+\tconst struct kho_radix_walk_cb ext_cb = {\n+\t\t.leaf = kho_ext_mark_scratch,\n+\t};\n+\tstruct kho_radix_tree radix;\n+\tphys_addr_t prev_end = 0;\n+\tint err = 0;\n+\n+\terr = kho_radix_init_tree(\u0026radix, NULL);\n+\tif (err)\n+\t\tgoto print;\n+\n+\t/* Walk the KHO radix tree to find busy blocks. */\n+\terr = kho_radix_walk_tree(\u0026kho_in.radix_tree, \u0026kho_cb, \u0026radix);\n+\tif (err)\n+\t\tgoto out;\n+\n+\t/* Walk the blocks and mark everything between keys as scratch. */\n+\terr = kho_radix_walk_tree(\u0026radix, \u0026ext_cb, \u0026prev_end);\n+\tif (err)\n+\t\tgoto out;\n+\n+\t/* Mark everything from last busy block to end of DRAM. */\n+\tif (prev_end \u003c memblock_end_of_DRAM())\n+\t\terr = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end);\n+\n+\t/* fallthrough */\n+out:\n+\tkho_radix_destroy_tree(\u0026radix);\n+print:\n+\tif (err)\n+\t\tpr_err(\"Failed to extend scratch: %pe\\n\", ERR_PTR(err));\n+}\n+\n /**\n  * kho_add_subtree - record the physical address of a sub blob in KHO root tree.\n  * @name: name of the sub tree.\n@@ -856,10 +1152,12 @@ int kho_preserve_folio(struct folio *folio)\n \tconst unsigned long pfn = folio_pfn(folio);\n \tconst unsigned int order = folio_order(folio);\n \n-\tif (WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\n+\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\n+\t    WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\n \t\treturn -EINVAL;\n \n-\treturn kho_radix_add_page(tree, pfn, order);\n+\treturn kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t    order));\n }\n EXPORT_SYMBOL_GPL(kho_preserve_folio);\n \n@@ -877,7 +1175,7 @@ void kho_unpreserve_folio(struct folio *folio)\n \tconst unsigned long pfn = folio_pfn(folio);\n \tconst unsigned int order = folio_order(folio);\n \n-\tkho_radix_del_page(tree, pfn, order);\n+\tkho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));\n }\n EXPORT_SYMBOL_GPL(kho_unpreserve_folio);\n \n@@ -906,7 +1204,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,\n \twhile (pfn \u003c end_pfn) {\n \t\torder = __kho_preserve_pages_order(pfn, end_pfn);\n \n-\t\tkho_radix_del_page(tree, pfn, order);\n+\t\tkho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t     order));\n \n \t\tpfn += 1 \u003c\u003c order;\n \t}\n@@ -931,7 +1230,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n \tunsigned long failed_pfn = 0;\n \tint err = 0;\n \n-\tif (WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\n+\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\n+\t    WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\n \t\t\t\t\tnr_pages \u003c\u003c PAGE_SHIFT))) {\n \t\treturn -EINVAL;\n \t}\n@@ -939,7 +1239,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n \twhile (pfn \u003c end_pfn) {\n \t\tunsigned int order = __kho_preserve_pages_order(pfn, end_pfn);\n \n-\t\terr = kho_radix_add_page(tree, pfn, order);\n+\t\terr = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t\t   order));\n \t\tif (err) {\n \t\t\tfailed_pfn = pfn;\n \t\t\tbreak;\n@@ -1315,22 +1616,6 @@ void kho_restore_free(void *mem)\n }\n EXPORT_SYMBOL_GPL(kho_restore_free);\n \n-struct kho_in {\n-\tphys_addr_t fdt_phys;\n-\tphys_addr_t scratch_phys;\n-\tchar previous_release[__NEW_UTS_LEN + 1];\n-\tu32 kexec_count;\n-\tstruct kho_debugfs dbg;\n-};\n-\n-static struct kho_in kho_in = {\n-};\n-\n-static const void *kho_get_fdt(void)\n-{\n-\treturn kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;\n-}\n-\n /**\n  * is_kho_boot - check if current kernel was booted via KHO-enabled\n  * kexec\n@@ -1398,26 +1683,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)\n }\n EXPORT_SYMBOL_GPL(kho_retrieve_subtree);\n \n-static int __init kho_mem_retrieve(const void *fdt)\n+static void __init kho_mem_retrieve(void)\n {\n-\tstruct kho_radix_tree tree;\n-\tconst phys_addr_t *mem;\n-\tint len;\n+\tconst struct kho_radix_walk_cb cb = {\n+\t\t.leaf = kho_preserved_memory_reserve,\n+\t};\n \n-\t/* Retrieve the KHO radix tree from passed-in FDT. */\n-\tmem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, \u0026len);\n+\tif (kho_radix_walk_tree(\u0026kho_in.radix_tree, \u0026cb, NULL))\n+\t\tgoto err;\n \n-\tif (!mem || len != sizeof(*mem)) {\n-\t\tpr_err(\"failed to get preserved KHO memory tree\\n\");\n-\t\treturn -ENOENT;\n-\t}\n-\n-\tif (!*mem)\n-\t\treturn -EINVAL;\n+\treturn;\n \n-\ttree.root = phys_to_virt(*mem);\n-\tmutex_init(\u0026tree.lock);\n-\treturn kho_radix_walk_tree(\u0026tree, kho_preserved_memory_reserve);\n+err:\n+\t/*\n+\t * Failed to initialize preserved memory. Clear FDT and radix so KHO\n+\t * users don't treat it as a KHO boot.\n+\t */\n+\tkho_in.fdt_phys = 0;\n+\tkho_in.radix_tree.root = NULL;\n }\n \n static __init int kho_out_fdt_setup(void)\n@@ -1543,16 +1826,14 @@ static __init int kho_init(void)\n \tif (!kho_enable)\n \t\treturn 0;\n \n-\ttree-\u003eroot = kzalloc(PAGE_SIZE, GFP_KERNEL);\n-\tif (!tree-\u003eroot) {\n-\t\terr = -ENOMEM;\n+\terr = kho_radix_init_tree(tree, NULL);\n+\tif (err)\n \t\tgoto err_free_scratch;\n-\t}\n \n \tkho_out.fdt = kho_alloc_preserve(PAGE_SIZE);\n \tif (IS_ERR(kho_out.fdt)) {\n \t\terr = PTR_ERR(kho_out.fdt);\n-\t\tgoto err_free_kho_radix_tree_root;\n+\t\tgoto err_free_kho_radix_tree;\n \t}\n \n \terr = kho_debugfs_init();\n@@ -1603,9 +1884,8 @@ static __init int kho_init(void)\n \n err_free_fdt:\n \tkho_unpreserve_free(kho_out.fdt);\n-err_free_kho_radix_tree_root:\n-\tkfree(tree-\u003eroot);\n-\ttree-\u003eroot = NULL;\n+err_free_kho_radix_tree:\n+\tkho_radix_destroy_tree(tree);\n err_free_scratch:\n \tkho_out.fdt = NULL;\n \tfor (int i = 0; i \u003c kho_scratch_cnt; i++) {\n@@ -1619,16 +1899,43 @@ static __init int kho_init(void)\n }\n fs_initcall(kho_init);\n \n-void __init kho_memory_init(void)\n+void __init kho_memory_init_early(void)\n {\n-\tif (kho_in.scratch_phys) {\n-\t\tkho_scratch = phys_to_virt(kho_in.scratch_phys);\n+\tconst void *fdt = kho_get_fdt();\n \n-\t\tif (kho_mem_retrieve(kho_get_fdt()))\n-\t\t\tkho_in.fdt_phys = 0;\n-\t} else {\n-\t\tkho_reserve_scratch();\n+\tif (!is_kho_boot())\n+\t\treturn;\n+\n+\t/*\n+\t * kho_scratch_overlap() needs kho_scratch to be initialized. It\n+\t * is used by free_area_init() on KHO boots, so initialize it\n+\t * early.\n+\t */\n+\tkho_scratch = phys_to_virt(kho_in.scratch_phys);\n+\n+\t/*\n+\t * kho_get_mem_map() should always succeed. If it fails, kho_populate()\n+\t * catches that and never sets kho_in.fdt_phys.\n+\t */\n+\tif (kho_radix_init_tree(\u0026kho_in.radix_tree, kho_get_mem_map(fdt))) {\n+\t\t/*\n+\t\t * Failed to initialize preserved memory radix tree. Clear FDT\n+\t\t * and scratch so KHO users don't treat it as a KHO boot.\n+\t\t */\n+\t\tkho_in.fdt_phys = 0;\n+\t\tkho_in.scratch_phys = 0;\n+\t\treturn;\n \t}\n+\n+\tkho_extend_scratch();\n+}\n+\n+void __init kho_memory_init(void)\n+{\n+\tif (kho_in.scratch_phys)\n+\t\tkho_mem_retrieve();\n+\telse\n+\t\tkho_reserve_scratch();\n }\n \n void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n@@ -1636,9 +1943,8 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n {\n \tunsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch);\n \tstruct kho_scratch *scratch = NULL;\n-\tphys_addr_t mem_map_phys;\n-\tvoid *fdt = NULL;\n \tbool populated = false;\n+\tvoid *fdt = NULL;\n \tint err;\n \n \t/* Validate the input FDT */\n@@ -1660,8 +1966,13 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n \t\tgoto unmap_fdt;\n \t}\n \n-\tmem_map_phys = kho_get_mem_map_phys(fdt);\n-\tif (!mem_map_phys)\n+\t/*\n+\t * At this point phys_to_virt() doesn't work properly and so\n+\t * kho_get_mem_map() can return a pre-KASLR virtual address. But here we\n+\t * only want to make sure the mem_map is valid so the actual value\n+\t * doesn't matter as long as it isn't NULL.\n+\t */\n+\tif (!kho_get_mem_map(fdt))\n \t\tgoto unmap_fdt;\n \n \tscratch = early_memremap(scratch_phys, scratch_len);\ndiff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c\ndeleted file mode 100644\nindex 6efb696f542612..00000000000000\n--- a/kernel/liveupdate/kexec_handover_debug.c\n+++ /dev/null\n@@ -1,25 +0,0 @@\n-// SPDX-License-Identifier: GPL-2.0-only\n-/*\n- * kexec_handover_debug.c - kexec handover optional debug functionality\n- * Copyright (C) 2025 Google LLC, Pasha Tatashin \u003cpasha.tatashin@soleen.com\u003e\n- */\n-\n-#define pr_fmt(fmt) \"KHO: \" fmt\n-\n-#include \"kexec_handover_internal.h\"\n-\n-bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n-{\n-\tphys_addr_t scratch_start, scratch_end;\n-\tunsigned int i;\n-\n-\tfor (i = 0; i \u003c kho_scratch_cnt; i++) {\n-\t\tscratch_start = kho_scratch[i].addr;\n-\t\tscratch_end = kho_scratch[i].addr + kho_scratch[i].size;\n-\n-\t\tif (phys \u003c scratch_end \u0026\u0026 (phys + size) \u003e scratch_start)\n-\t\t\treturn true;\n-\t}\n-\n-\treturn false;\n-}\ndiff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h\nindex 0399ff1077750b..805d2a76c38818 100644\n--- a/kernel/liveupdate/kexec_handover_internal.h\n+++ b/kernel/liveupdate/kexec_handover_internal.h\n@@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,\n \t\t\t\t\t   void *blob) { }\n #endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */\n \n-#ifdef CONFIG_KEXEC_HANDOVER_DEBUG\n-bool kho_scratch_overlap(phys_addr_t phys, size_t size);\n-#else\n-static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n-{\n-\treturn false;\n-}\n-#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */\n-\n #endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */\ndiff --git a/mm/hugetlb.c b/mm/hugetlb.c\nindex 571212b80835ec..ab4afc818e8cbd 100644\n--- a/mm/hugetlb.c\n+++ b/mm/hugetlb.c\n@@ -3033,29 +3033,21 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)\n \tif (hugetlb_early_cma(h))\n \t\tm = hugetlb_cma_alloc_bootmem(h, \u0026listnode, node_exact);\n \telse {\n-\t\tif (node_exact)\n-\t\t\tm = memblock_alloc_exact_nid_raw(huge_page_size(h),\n-\t\t\t\thuge_page_size(h), 0,\n-\t\t\t\tMEMBLOCK_ALLOC_ACCESSIBLE, nid);\n-\t\telse {\n-\t\t\tm = memblock_alloc_try_nid_raw(huge_page_size(h),\n-\t\t\t\thuge_page_size(h), 0,\n-\t\t\t\tMEMBLOCK_ALLOC_ACCESSIBLE, nid);\n+\t\tm = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);\n+\t\tif (m) {\n+\t\t\tm-\u003eflags = 0;\n+\t\t\tm-\u003ecma = NULL;\n+\n \t\t\t/*\n \t\t\t * For pre-HVO to work correctly, pages need to be on\n \t\t\t * the list for the node they were actually allocated\n \t\t\t * from. That node may be different in the case of\n-\t\t\t * fallback by memblock_alloc_try_nid_raw. So,\n+\t\t\t * fallback by memblock_alloc_hugetlb_bootmem. So,\n \t\t\t * extract the actual node first.\n \t\t\t */\n-\t\t\tif (m)\n+\t\t\tif (!node_exact)\n \t\t\t\tlistnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));\n \t\t}\n-\n-\t\tif (m) {\n-\t\t\tm-\u003eflags = 0;\n-\t\t\tm-\u003ecma = NULL;\n-\t\t}\n \t}\n \n \tif (m) {\ndiff --git a/mm/memblock.c b/mm/memblock.c\nindex 6349c48154f4b1..c10dfb054806a7 100644\n--- a/mm/memblock.c\n+++ b/mm/memblock.c\n@@ -1506,6 +1506,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,\n \treturn 0;\n }\n \n+static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size,\n+\t\t\t\t     bool leaktrace)\n+{\n+\t/*\n+\t * Skip kmemleak for those places like kasan_init() and\n+\t * early_pgtable_alloc() due to high volume.\n+\t */\n+\tif (leaktrace)\n+\t\t/*\n+\t\t * Memblock allocated blocks are never reported as\n+\t\t * leaks. This is because many of these blocks are\n+\t\t * only referred via the physical address which is\n+\t\t * not looked up by kmemleak.\n+\t\t */\n+\t\tkmemleak_alloc_phys(start, size, 0);\n+\n+\t/*\n+\t * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,\n+\t * require memory to be accepted before it can be used by the\n+\t * guest.\n+\t *\n+\t * Accept the memory of the allocated buffer.\n+\t */\n+\taccept_memory(start, size);\n+}\n+\n /**\n  * memblock_alloc_range_nid - allocate boot memory block\n  * @size: size of memory block to be allocated in bytes\n@@ -1580,28 +1606,7 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,\n \treturn 0;\n \n done:\n-\t/*\n-\t * Skip kmemleak for those places like kasan_init() and\n-\t * early_pgtable_alloc() due to high volume.\n-\t */\n-\tif (end != MEMBLOCK_ALLOC_NOLEAKTRACE)\n-\t\t/*\n-\t\t * Memblock allocated blocks are never reported as\n-\t\t * leaks. This is because many of these blocks are\n-\t\t * only referred via the physical address which is\n-\t\t * not looked up by kmemleak.\n-\t\t */\n-\t\tkmemleak_alloc_phys(found, size, 0);\n-\n-\t/*\n-\t * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,\n-\t * require memory to be accepted before it can be used by the\n-\t * guest.\n-\t *\n-\t * Accept the memory of the allocated buffer.\n-\t */\n-\taccept_memory(found, size);\n-\n+\tmemblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE);\n \treturn found;\n }\n \n@@ -1756,6 +1761,69 @@ void * __init memblock_alloc_try_nid_raw(\n \t\t\t\t       false);\n }\n \n+/**\n+ * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages\n+ * @size:      size of the memory to be allocated in bytes\n+ * @nid:       nid of the free memory to find, %NUMA_NO_NODE for any node\n+ * @exact_nid: only allocate from the specified nid. If %false, the specified\n+ *             nid is tried first, and then all nodes are tried as fallback.\n+ *\n+ * HugeTLB pages are always aligned by their size, so the alignment matches\n+ * @size. Since the memory is for userspace, mirrored memory is not used. The\n+ * memory is not zeroed. Does not panic if request cannot be satisfied.\n+ *\n+ * Return:\n+ * Virtual address of allocated memory block on success, %NULL on failure.\n+ */\n+void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)\n+{\n+\tenum memblock_flags flags = choose_memblock_flags();\n+\tphys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE;\n+\n+\tmemblock_dbg(\"%s: %llu bytes, nid=%d, exact_nid=%d %pS\\n\", __func__,\n+\t\t     (u64)size, nid, exact_nid, (void *)_RET_IP_);\n+\n+\t/* Don't waste mirrored memory on HugeTLB pages. */\n+\tflags \u0026= ~MEMBLOCK_MIRROR;\n+retry:\n+\t/* HugeTLB pages are always aligned by their size. */\n+\taddr = memblock_find_in_range_node(size, size, start, end, nid, flags);\n+\tif (addr)\n+\t\tgoto found;\n+\n+\t/* Try all nodes if allowed. */\n+\tif (numa_valid_node(nid) \u0026\u0026 !exact_nid) {\n+\t\tnid = NUMA_NO_NODE;\n+\t\tgoto retry;\n+\t}\n+\n+\t/* Found nothing... :-( */\n+\treturn NULL;\n+\n+found:\n+\t/*\n+\t * HugeTLB pages can be preserved with KHO and no preserved memory can\n+\t * be in scratch. So retry if found address overlaps with scratch.\n+\t *\n+\t * Scratch areas are normally not very large, so this shouldn't take too\n+\t * many retries.\n+\t */\n+\tif (kho_scratch_overlap(addr, size)) {\n+\t\tif (memblock_bottom_up())\n+\t\t\tstart = addr + size;\n+\t\telse\n+\t\t\tstart = addr - size;\n+\n+\t\tgoto retry;\n+\t}\n+\n+\tif (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB))\n+\t\treturn NULL;\n+\n+\tmemblock_prep_allocation(addr, size, true);\n+\treturn phys_to_virt(addr);\n+}\n+\n /**\n  * memblock_alloc_try_nid - allocate boot memory block\n  * @size: size of memory block to be allocated in bytes\n@@ -1825,6 +1893,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void)\n \treturn memblock.reserved.total_size;\n }\n \n+phys_addr_t __init_memblock memblock_reserved_hugetlb_size(phys_addr_t limit, int nid)\n+{\n+\tstruct memblock_region *r;\n+\tphys_addr_t total = 0;\n+\n+\tfor_each_reserved_mem_region(r) {\n+\t\tphys_addr_t size = r-\u003esize;\n+\n+\t\tif (r-\u003ebase \u003e limit)\n+\t\t\tbreak;\n+\n+\t\tif (r-\u003ebase + r-\u003esize \u003e limit)\n+\t\t\tsize = limit - r-\u003ebase;\n+\n+\t\tif (nid == memblock_get_region_node(r) || !numa_valid_node(nid))\n+\t\t\tif (r-\u003eflags \u0026 MEMBLOCK_RSRV_HUGETLB)\n+\t\t\t\ttotal += size;\n+\t}\n+\n+\treturn total;\n+}\n+\n phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid)\n {\n \tstruct memblock_region *r;\n@@ -2511,16 +2601,6 @@ __init void memblock_clear_kho_scratch_only(void)\n {\n \tkho_scratch_only = false;\n }\n-\n-bool __init_memblock memblock_is_kho_scratch_memory(phys_addr_t addr)\n-{\n-\tint i = memblock_search(\u0026memblock.memory, addr);\n-\n-\tif (i == -1)\n-\t\treturn false;\n-\n-\treturn memblock_is_kho_scratch(\u0026memblock.memory.regions[i]);\n-}\n #endif\n \n #ifdef CONFIG_KEXEC_HANDOVER\ndiff --git a/mm/mm_init.c b/mm/mm_init.c\nindex 0f64909e8d20e2..1d2f302d8cd9c1 100644\n--- a/mm/mm_init.c\n+++ b/mm/mm_init.c\n@@ -707,9 +707,10 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid)\n \t__init_single_page(pfn_to_page(pfn), pfn, zid, nid);\n \n \tif (pageblock_aligned(pfn)) {\n-\t\tenum migratetype mt =\n-\t\t\tkho_scratch_migratetype(pfn, MIGRATE_MOVABLE);\n-\t\tinit_pageblock_migratetype(pfn_to_page(pfn), mt, false);\n+\t\tinit_pageblock_migratetype(pfn_to_page(pfn), MIGRATE_MOVABLE,\n+\t\t\t\t\t   false);\n+\t\t/* Update the migrate type in case it is KHO scratch. */\n+\t\tkho_init_scratch_migratetype(pfn, pfn + 1);\n \t}\n }\n \n@@ -938,13 +939,22 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone\n \t\t}\n \t\tpfn++;\n \t}\n+\n+\t/*\n+\t * Update the migrate type for any pageblocks that fall in a KHO scratch\n+\t * area. Do this here outside the loop because most pageblocks will not\n+\t * be KHO scratch, so only search once.\n+\t *\n+\t * Only pageblocks up to pfn are updated, deferred pageblocks will be\n+\t * updated later.\n+\t */\n+\tkho_init_scratch_migratetype(start_pfn, pfn);\n }\n \n static void __init memmap_init_zone_range(struct zone *zone,\n \t\t\t\t\t  unsigned long start_pfn,\n \t\t\t\t\t  unsigned long end_pfn,\n-\t\t\t\t\t  unsigned long *hole_pfn,\n-\t\t\t\t\t  enum migratetype mt)\n+\t\t\t\t\t  unsigned long *hole_pfn)\n {\n \tunsigned long zone_start_pfn = zone-\u003ezone_start_pfn;\n \tunsigned long zone_end_pfn = zone_start_pfn + zone-\u003espanned_pages;\n@@ -957,7 +967,8 @@ static void __init memmap_init_zone_range(struct zone *zone,\n \t\treturn;\n \n \tmemmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,\n-\t\t\t  zone_end_pfn, MEMINIT_EARLY, NULL, mt, false);\n+\t\t\t  zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE,\n+\t\t\t  false);\n \n \tif (*hole_pfn \u003c start_pfn)\n \t\tinit_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);\n@@ -973,8 +984,6 @@ static void __init memmap_init(void)\n \n \tfor_each_mem_pfn_range(i, MAX_NUMNODES, \u0026start_pfn, \u0026end_pfn, \u0026nid) {\n \t\tstruct pglist_data *node = NODE_DATA(nid);\n-\t\tenum migratetype mt =\n-\t\t\tkho_scratch_migratetype(start_pfn, MIGRATE_MOVABLE);\n \n \t\tfor (j = 0; j \u003c MAX_NR_ZONES; j++) {\n \t\t\tstruct zone *zone = node-\u003enode_zones + j;\n@@ -983,7 +992,7 @@ static void __init memmap_init(void)\n \t\t\t\tcontinue;\n \n \t\t\tmemmap_init_zone_range(zone, start_pfn, end_pfn,\n-\t\t\t\t\t       \u0026hole_pfn, mt);\n+\t\t\t\t\t       \u0026hole_pfn);\n \t\t\tzone_id = j;\n \t\t}\n \t}\n@@ -1973,7 +1982,7 @@ unsigned long __init node_map_pfn_alignment(void)\n \n #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT\n static void __init deferred_free_pages(unsigned long pfn,\n-\t\tunsigned long nr_pages, enum migratetype mt)\n+\t\tunsigned long nr_pages)\n {\n \tstruct page *page;\n \tunsigned long i;\n@@ -1985,8 +1994,6 @@ static void __init deferred_free_pages(unsigned long pfn,\n \n \t/* Free a large naturally-aligned chunk if possible */\n \tif (nr_pages == MAX_ORDER_NR_PAGES \u0026\u0026 IS_MAX_ORDER_ALIGNED(pfn)) {\n-\t\tfor (i = 0; i \u003c nr_pages; i += pageblock_nr_pages)\n-\t\t\tinit_pageblock_migratetype(page + i, mt, false);\n \t\t__free_pages_core(page, MAX_PAGE_ORDER, MEMINIT_EARLY);\n \t\treturn;\n \t}\n@@ -1994,11 +2001,8 @@ static void __init deferred_free_pages(unsigned long pfn,\n \t/* Accept chunks smaller than MAX_PAGE_ORDER upfront */\n \taccept_memory(PFN_PHYS(pfn), nr_pages * PAGE_SIZE);\n \n-\tfor (i = 0; i \u003c nr_pages; i++, page++, pfn++) {\n-\t\tif (pageblock_aligned(pfn))\n-\t\t\tinit_pageblock_migratetype(page, mt, false);\n+\tfor (i = 0; i \u003c nr_pages; i++, page++, pfn++)\n \t\t__free_pages_core(page, 0, MEMINIT_EARLY);\n-\t}\n }\n \n /* Completion tracking for deferred_init_memmap() threads */\n@@ -2017,15 +2021,29 @@ static inline void __init pgdat_init_report_one_done(void)\n  * Return number of pages initialized.\n  */\n static unsigned long __init deferred_init_pages(struct zone *zone,\n-\t\tunsigned long pfn, unsigned long end_pfn)\n+\t\tunsigned long start_pfn, unsigned long end_pfn)\n {\n \tint nid = zone_to_nid(zone);\n-\tunsigned long nr_pages = end_pfn - pfn;\n+\tunsigned long nr_pages = end_pfn - start_pfn, pfn = start_pfn;\n \tint zid = zone_idx(zone);\n \tstruct page *page = pfn_to_page(pfn);\n \n \tfor (; pfn \u003c end_pfn; pfn++, page++)\n \t\t__init_single_page(page, pfn, zid, nid);\n+\n+\t/* Now initialize migrate types for these pages. */\n+\tpfn = pageblock_align(start_pfn);\n+\tpage = pfn_to_page(pfn);\n+\tfor (; pfn \u003c end_pfn; pfn += pageblock_nr_pages, page += pageblock_nr_pages)\n+\t\tinit_pageblock_migratetype(page, MIGRATE_MOVABLE, false);\n+\n+\t/*\n+\t * Update the migrate type for any pageblocks that fall in a KHO scratch\n+\t * area. Since most pageblocks will not be KHO scratch, do it outside\n+\t * the loop to only do the search once.\n+\t */\n+\tkho_init_scratch_migratetype(start_pfn, end_pfn);\n+\n \treturn nr_pages;\n }\n \n@@ -2054,8 +2072,6 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,\n \tfor_each_free_mem_range(i, nid, 0, \u0026start, \u0026end, NULL) {\n \t\tunsigned long spfn = PFN_UP(start);\n \t\tunsigned long epfn = PFN_DOWN(end);\n-\t\tenum migratetype mt =\n-\t\t\tkho_scratch_migratetype(spfn, MIGRATE_MOVABLE);\n \n \t\tif (spfn \u003e= end_pfn)\n \t\t\tbreak;\n@@ -2068,7 +2084,7 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,\n \t\t\tunsigned long chunk_end = min(mo_pfn, epfn);\n \n \t\t\tnr_pages += deferred_init_pages(zone, spfn, chunk_end);\n-\t\t\tdeferred_free_pages(spfn, chunk_end - spfn, mt);\n+\t\t\tdeferred_free_pages(spfn, chunk_end - spfn);\n \n \t\t\tspfn = chunk_end;\n \n@@ -2687,6 +2703,7 @@ void __init __weak mem_init(void)\n \n void __init mm_core_init_early(void)\n {\n+\tkho_memory_init_early();\n \thugetlb_cma_reserve();\n \thugetlb_bootmem_alloc();\n \ndiff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h\nindex b6b1d147fd750e..e86bb9000b22f8 100644\n--- a/tools/testing/memblock/internal.h\n+++ b/tools/testing/memblock/internal.h\n@@ -66,4 +66,9 @@ static inline void init_deferred_page(unsigned long pfn, int nid)\n \n #define __SetPageReserved(p)\t((void)(p))\n \n+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\treturn false;\n+}\n+\n #endif\n"
}

2/1 2026/07/10 13:41 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "kho_scratch_overlap",
    "kho_init_scratch_migratetype",
    "memblock_alloc_hugetlb"
  ],
  "Reasoning": "The patch modifies the Kexec Handover (KHO) radix tree implementation, introduces new functions for KHO scratch memory management, and changes how HugeTLB pages are allocated during boot using memblock. These are functional changes to core kernel logic and memory management that can be reached during boot and runtime, making them worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 15a895b27d7069b43e3b128df857ebccd3e38092
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 10 13:41:24 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 48a9793c2b76cd..30a71710565a32 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -33,9 +33,13 @@ 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);
+void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn);
 #else
 static inline bool kho_is_enabled(void)
 {
@@ -108,10 +112,22 @@ 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 void kho_init_scratch_migratetype(unsigned long start_pfn,
+						unsigned long end_pfn);
+{
+}
 #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 5e2eb8519bda6d..2f4fb9c639421a 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 84e918b96e53df..5d6ae2893684a4 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -34,37 +34,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/memblock.h b/include/linux/memblock.h
index 5afcd99aa8c144..d62db9e776cf94 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 eec9d3ae07eb4b..d9f46946255673 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 4834a809985ab7..9dc79b7103bdbf 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -84,8 +84,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.
  *
@@ -95,35 +112,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;
@@ -143,25 +163,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;
@@ -172,6 +214,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);
@@ -186,7 +231,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;
@@ -217,29 +262,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;
@@ -247,6 +289,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);
@@ -270,21 +316,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;
 	}
@@ -294,7 +404,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;
@@ -302,6 +412,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;
@@ -318,10 +434,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)
@@ -332,28 +448,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);
 
@@ -484,13 +599,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 = 1 << (order + PAGE_SHIFT);
 	page = kho_get_preserved_page(phys, order);
 
@@ -504,19 +622,24 @@ static int __init kho_preserved_memory_reserve(phys_addr_t phys,
 	return 0;
 }
 
-/* Returns physical address of the preserved memory map from FDT */
-static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
+/* Returns virtual address of the preserved memory map from FDT */
+static __init void *kho_get_mem_map(const void *fdt)
 {
 	const void *mem_ptr;
+	phys_addr_t mem_map_phys;
 	int len;
 
 	mem_ptr = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
 	if (!mem_ptr || len != sizeof(u64)) {
 		pr_err("failed to get preserved memory map\n");
-		return 0;
+		return NULL;
 	}
 
-	return get_unaligned((const u64 *)mem_ptr);
+	mem_map_phys = get_unaligned((const u64 *)mem_ptr);
+	if (!mem_map_phys)
+		return NULL;
+
+	return phys_to_virt(mem_map_phys);
 }
 
 /*
@@ -619,19 +742,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;
 	}
@@ -651,6 +779,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;
@@ -659,6 +790,49 @@ static phys_addr_t __init scratch_size_node(int nid)
 	return round_up(size, CMA_MIN_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;
+}
+
+/*
+ * Mark all KHO scratch pageblocks between start_pfn and end_pfn as
+ * MIGRATE_CMA.
+ */
+void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn)
+{
+	for (unsigned int i = 0; i < kho_scratch_cnt; i++) {
+		unsigned long scratch_start, scratch_end, pfn;
+
+		scratch_start = PHYS_PFN(kho_scratch[i].addr);
+		scratch_end = PHYS_PFN(kho_scratch[i].addr + kho_scratch[i].size);
+
+		/* Target range doesn't overlap with scratch. */
+		if (start_pfn >= scratch_end || end_pfn <= scratch_start)
+			continue;
+
+		pfn = max_t(unsigned long, start_pfn, scratch_start);
+		pfn = pageblock_start_pfn(pfn);
+
+		while (pfn < end_pfn && pfn < scratch_end) {
+			init_pageblock_migratetype(pfn_to_page(pfn),
+						   MIGRATE_CMA, false);
+			pfn += pageblock_nr_pages;
+		}
+	}
+}
+
 /**
  * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec
  *
@@ -746,6 +920,128 @@ static void __init kho_reserve_scratch(void)
 	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_EXT_BLKSIZE		SZ_1G
+#define KHO_EXT_SHIFT		const_ilog2(KHO_EXT_BLKSIZE)
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_leaf(unsigned long key, void *data)
+{
+	/* Radix tree tracking free blocks. */
+	struct kho_radix_tree *tree = data;
+	phys_addr_t start, end;
+	unsigned int order;
+	int err;
+
+	start = kho_decode_radix_key(key, &order);
+	end = start + (1UL << (order + PAGE_SHIFT));
+
+	while (start < end) {
+		err = kho_radix_add_key(tree, start >> KHO_EXT_SHIFT);
+		if (err)
+			return err;
+
+		start += (1UL << KHO_EXT_SHIFT);
+	}
+
+	return 0;
+}
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)
+{
+	/* Radix tree tracking free blocks. */
+	struct kho_radix_tree *tree = data;
+
+	return kho_radix_add_key(tree, phys >> KHO_EXT_SHIFT);
+}
+
+/* Called for the free 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_EXT_SHIFT;
+	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_EXT_SHIFT);
+	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 KHO_EXT_SHIFT which contain preserved
+ * memory.
+ *
+ * Then it walks the digested radix tree and marks everything that doesn't have
+ * preserved memory as scratch.
+ *
+ * NOTE: This function allocates memory so it should be called when scratch has
+ * available space.
+ *
+ * NOTE: The pages of the KHO 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,
+	};
+	struct kho_radix_tree radix;
+	phys_addr_t prev_end = 0;
+	int err = 0;
+
+	err = kho_radix_init_tree(&radix, NULL);
+	if (err)
+		goto print;
+
+	/* Walk the KHO radix tree to find busy blocks. */
+	err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &radix);
+	if (err)
+		goto out;
+
+	/* Walk the blocks and mark everything between keys as scratch. */
+	err = kho_radix_walk_tree(&radix, &ext_cb, &prev_end);
+	if (err)
+		goto out;
+
+	/* Mark everything from last busy block to end of DRAM. */
+	if (prev_end < memblock_end_of_DRAM())
+		err = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end);
+
+	/* fallthrough */
+out:
+	kho_radix_destroy_tree(&radix);
+print:
+	if (err)
+		pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err));
+}
+
 /**
  * kho_add_subtree - record the physical address of a sub blob in KHO root tree.
  * @name: name of the sub tree.
@@ -856,10 +1152,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);
 
@@ -877,7 +1175,7 @@ void kho_unpreserve_folio(struct folio *folio)
 	const unsigned long pfn = folio_pfn(folio);
 	const unsigned int order = folio_order(folio);
 
-	kho_radix_del_page(tree, pfn, order);
+	kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));
 }
 EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
 
@@ -906,7 +1204,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;
 	}
@@ -931,7 +1230,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;
 	}
@@ -939,7 +1239,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;
@@ -1315,22 +1616,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
@@ -1398,26 +1683,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;
+	const struct kho_radix_walk_cb cb = {
+		.leaf = kho_preserved_memory_reserve,
+	};
 
-	/* Retrieve the KHO radix tree from passed-in FDT. */
-	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
+	if (kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL))
+		goto err;
 
-	if (!mem || len != sizeof(*mem)) {
-		pr_err("failed to get preserved KHO memory tree\n");
-		return -ENOENT;
-	}
-
-	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)
@@ -1543,16 +1826,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();
@@ -1603,9 +1884,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++) {
@@ -1619,16 +1899,43 @@ static __init int kho_init(void)
 }
 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();
 
-		if (kho_mem_retrieve(kho_get_fdt()))
-			kho_in.fdt_phys = 0;
-	} else {
-		kho_reserve_scratch();
+	if (!is_kho_boot())
+		return;
+
+	/*
+	 * 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);
+
+	/*
+	 * kho_get_mem_map() should always succeed. If it fails, kho_populate()
+	 * catches that and never sets kho_in.fdt_phys.
+	 */
+	if (kho_radix_init_tree(&kho_in.radix_tree, kho_get_mem_map(fdt))) {
+		/*
+		 * 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;
+		return;
 	}
+
+	kho_extend_scratch();
+}
+
+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,
@@ -1636,9 +1943,8 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 {
 	unsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch);
 	struct kho_scratch *scratch = NULL;
-	phys_addr_t mem_map_phys;
-	void *fdt = NULL;
 	bool populated = false;
+	void *fdt = NULL;
 	int err;
 
 	/* Validate the input FDT */
@@ -1660,8 +1966,13 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 		goto unmap_fdt;
 	}
 
-	mem_map_phys = kho_get_mem_map_phys(fdt);
-	if (!mem_map_phys)
+	/*
+	 * At this point phys_to_virt() doesn't work properly and so
+	 * kho_get_mem_map() can return a pre-KASLR virtual address. But here we
+	 * only want to make sure the mem_map is valid so the actual value
+	 * doesn't matter as long as it isn't NULL.
+	 */
+	if (!kho_get_mem_map(fdt))
 		goto unmap_fdt;
 
 	scratch = early_memremap(scratch_phys, scratch_len);
diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c
deleted file mode 100644
index 6efb696f542612..00000000000000
--- 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 0399ff1077750b..805d2a76c38818 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/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835ec..ab4afc818e8cbd 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3033,29 +3033,21 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)
 	if (hugetlb_early_cma(h))
 		m = hugetlb_cma_alloc_bootmem(h, &listnode, node_exact);
 	else {
-		if (node_exact)
-			m = memblock_alloc_exact_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
-		else {
-			m = memblock_alloc_try_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
+		m = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);
+		if (m) {
+			m->flags = 0;
+			m->cma = NULL;
+
 			/*
 			 * For pre-HVO to work correctly, pages need to be on
 			 * the list for the node they were actually allocated
 			 * from. That node may be different in the case of
-			 * fallback by memblock_alloc_try_nid_raw. So,
+			 * fallback by memblock_alloc_hugetlb_bootmem. So,
 			 * extract the actual node first.
 			 */
-			if (m)
+			if (!node_exact)
 				listnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));
 		}
-
-		if (m) {
-			m->flags = 0;
-			m->cma = NULL;
-		}
 	}
 
 	if (m) {
diff --git a/mm/memblock.c b/mm/memblock.c
index 6349c48154f4b1..c10dfb054806a7 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -1506,6 +1506,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 leaktrace)
+{
+	/*
+	 * Skip kmemleak for those places like kasan_init() and
+	 * early_pgtable_alloc() due to high volume.
+	 */
+	if (leaktrace)
+		/*
+		 * 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
@@ -1580,28 +1606,7 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
 	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;
 }
 
@@ -1756,6 +1761,69 @@ void * __init memblock_alloc_try_nid_raw(
 				       false);
 }
 
+/**
+ * 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;
+		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
+			start = addr - size;
+
+		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
@@ -1825,6 +1893,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;
@@ -2511,16 +2601,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 0f64909e8d20e2..1d2f302d8cd9c1 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -707,9 +707,10 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid)
 	__init_single_page(pfn_to_page(pfn), pfn, zid, nid);
 
 	if (pageblock_aligned(pfn)) {
-		enum migratetype mt =
-			kho_scratch_migratetype(pfn, MIGRATE_MOVABLE);
-		init_pageblock_migratetype(pfn_to_page(pfn), mt, false);
+		init_pageblock_migratetype(pfn_to_page(pfn), MIGRATE_MOVABLE,
+					   false);
+		/* Update the migrate type in case it is KHO scratch. */
+		kho_init_scratch_migratetype(pfn, pfn + 1);
 	}
 }
 
@@ -938,13 +939,22 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 		}
 		pfn++;
 	}
+
+	/*
+	 * Update the migrate type for any pageblocks that fall in a KHO scratch
+	 * area. Do this here outside the loop because most pageblocks will not
+	 * be KHO scratch, so only search once.
+	 *
+	 * Only pageblocks up to pfn are updated, deferred pageblocks will be
+	 * updated later.
+	 */
+	kho_init_scratch_migratetype(start_pfn, pfn);
 }
 
 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;
@@ -957,7 +967,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);
@@ -973,8 +984,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;
@@ -983,7 +992,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;
 		}
 	}
@@ -1973,7 +1982,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;
@@ -1985,8 +1994,6 @@ static void __init deferred_free_pages(unsigned long pfn,
 
 	/* Free a large naturally-aligned chunk if possible */
 	if (nr_pages == MAX_ORDER_NR_PAGES && IS_MAX_ORDER_ALIGNED(pfn)) {
-		for (i = 0; i < nr_pages; i += pageblock_nr_pages)
-			init_pageblock_migratetype(page + i, mt, false);
 		__free_pages_core(page, MAX_PAGE_ORDER, MEMINIT_EARLY);
 		return;
 	}
@@ -1994,11 +2001,8 @@ static void __init deferred_free_pages(unsigned long pfn,
 	/* Accept chunks smaller than MAX_PAGE_ORDER upfront */
 	accept_memory(PFN_PHYS(pfn), nr_pages * PAGE_SIZE);
 
-	for (i = 0; i < nr_pages; i++, page++, pfn++) {
-		if (pageblock_aligned(pfn))
-			init_pageblock_migratetype(page, mt, false);
+	for (i = 0; i < nr_pages; i++, page++, pfn++)
 		__free_pages_core(page, 0, MEMINIT_EARLY);
-	}
 }
 
 /* Completion tracking for deferred_init_memmap() threads */
@@ -2017,15 +2021,29 @@ static inline void __init pgdat_init_report_one_done(void)
  * Return number of pages initialized.
  */
 static unsigned long __init deferred_init_pages(struct zone *zone,
-		unsigned long pfn, unsigned long end_pfn)
+		unsigned long start_pfn, unsigned long end_pfn)
 {
 	int nid = zone_to_nid(zone);
-	unsigned long nr_pages = end_pfn - pfn;
+	unsigned long nr_pages = end_pfn - start_pfn, pfn = start_pfn;
 	int zid = zone_idx(zone);
 	struct page *page = pfn_to_page(pfn);
 
 	for (; pfn < end_pfn; pfn++, page++)
 		__init_single_page(page, pfn, zid, nid);
+
+	/* Now initialize migrate types for these pages. */
+	pfn = pageblock_align(start_pfn);
+	page = pfn_to_page(pfn);
+	for (; pfn < end_pfn; pfn += pageblock_nr_pages, page += pageblock_nr_pages)
+		init_pageblock_migratetype(page, MIGRATE_MOVABLE, false);
+
+	/*
+	 * Update the migrate type for any pageblocks that fall in a KHO scratch
+	 * area. Since most pageblocks will not be KHO scratch, do it outside
+	 * the loop to only do the search once.
+	 */
+	kho_init_scratch_migratetype(start_pfn, end_pfn);
+
 	return nr_pages;
 }
 
@@ -2054,8 +2072,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;
@@ -2068,7 +2084,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;
 
@@ -2687,6 +2703,7 @@ void __init __weak mem_init(void)
 
 void __init mm_core_init_early(void)
 {
+	kho_memory_init_early();
 	hugetlb_cma_reserve();
 	hugetlb_bootmem_alloc();
 
diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h
index b6b1d147fd750e..e86bb9000b22f8 100644
--- a/tools/testing/memblock/internal.h
+++ b/tools/testing/memblock/internal.h
@@ -66,4 +66,9 @@ static inline void init_deferred_page(unsigned long pfn, int nid)
 
 #define __SetPageReserved(p)	((void)(p))
 
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+	return false;
+}
+
 #endif

3/2 2026/07/10 13:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20728
							output: 20
							thoughts: 205
						

Thoughts:
**Analyzing the Kexec Handover Patch Series**

Okay, so I'm looking at this patch series that's tinkering with the Kexec Handover (KHO) functionality. From the description, it seems like we're dealing with some significant changes: key encoding within the KHO radix tree, the introduction of a new early initialization function `kho_memory_init_early()`, modifications to `memblock_alloc_hugetlb()`, and a revamp of how scratch memory is extended and migrated. That's quite a wide scope of impact.

The patch touches a good collection of core files. I'm seeing changes in the header files for Kexec Handover, the KHO ABI, the KHO radix tree, and memblock. That's expected, given the nature of the changes. The C files affected include `kexec_handover.c`, `hugetlb.c`, `memblock.c`, and `mm_init.c`. This definitely affects critical memory management and handover components, so this warrants a careful review.

Before diving deeper into the individual patch details, I need to understand the larger context. Is Kexec Handover even enabled and how is it configured? I'll quickly check for `CONFIG_KEXEC_HANDOVER` to see if it's in play. I'll use `grepper` for that, a quick scan will confirm if this code path is relevant or if it's guarded by a specific configuration option. Let's see what that reveals.



4/2 2026/07/10 13:41 tool grepper
0m Args:
{
  "Expression": "config KEXEC_HANDOVER"
}

Results:
{
  "Output": "kernel/liveupdate/Kconfig=3=menu \"Live Update and Kexec HandOver\"\nkernel/liveupdate/Kconfig-4-\nkernel/liveupdate/Kconfig:5:config KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-6-\tbool \"kexec handover\"\n--\nkernel/liveupdate/Kconfig-17-\nkernel/liveupdate/Kconfig:18:config KEXEC_HANDOVER_DEBUG\nkernel/liveupdate/Kconfig-19-\tbool \"Enable Kexec Handover debug checks\"\n--\nkernel/liveupdate/Kconfig-26-\nkernel/liveupdate/Kconfig:27:config KEXEC_HANDOVER_DEBUGFS\nkernel/liveupdate/Kconfig-28-\tbool \"kexec handover debugfs interface\"\n--\nkernel/liveupdate/Kconfig-37-\nkernel/liveupdate/Kconfig:38:config KEXEC_HANDOVER_ENABLE_DEFAULT\nkernel/liveupdate/Kconfig-39-\tbool \"Enable kexec handover by default\"\n"
}

5/2 2026/07/10 13:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21522
							output: 31
							thoughts: 277
						

Thoughts:
**Analyzing the Patch: Kexec Handover and HugeTLB Integration**

Okay, so this patch is touching `kexec_handover.c` and `memblock.c`. It looks like it's a mix of new functions and some integration work. In `kexec_handover.c`, I see a whole slew of radix tree functions being added: `kho_radix_add_key`, `kho_radix_del_key`, the walking, initialization, and destruction functions (`kho_radix_walk_tree`, `kho_radix_init_tree`, `kho_radix_destroy_tree`), plus some more specific utility functions like `kho_extend_scratch`, `kho_memory_init_early`, `kho_scratch_overlap`, and `kho_init_scratch_migratetype`. That's a significant addition, clearly building some kind of data structure and related management logic within the kexec handover process.

Now, over in `memblock.c`, the new function `memblock_alloc_hugetlb` is being introduced. Interesting. And it appears that within `mm/hugetlb.c`, `alloc_bootmem` is using `memblock_alloc_hugetlb`.

My first thought is, is `CONFIG_KEXEC_HANDOVER` the only configuration option affected? I assume that's the primary driver here. Okay, I've confirmed that `CONFIG_KEXEC_HANDOVER` is the main thing. But wait, `memblock_alloc_hugetlb` is called in `mm/hugetlb.c`, so `CONFIG_HUGETLB_PAGE` is also going to be relevant here. I need to make sure those dependencies are properly handled. Let me double-check the `mm/hugetlb.c` code to confirm that `alloc_bootmem` is indeed using the function in question and determine if there are other relevant configurations.



6/2 2026/07/10 13:41 tool grepper
0m Args:
{
  "Expression": "alloc_bootmem",
  "PathPrefix": "mm/hugetlb.c"
}

Results:
{
  "Output": "mm/hugetlb.c=2855=struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n--\nmm/hugetlb.c-3027-\nmm/hugetlb.c:3028:static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)\nmm/hugetlb.c-3029-{\n--\nmm/hugetlb.c-3033-\tif (hugetlb_early_cma(h))\nmm/hugetlb.c:3034:\t\tm = hugetlb_cma_alloc_bootmem(h, \u0026listnode, node_exact);\nmm/hugetlb.c-3035-\telse {\n--\nmm/hugetlb.c-3069-\nmm/hugetlb.c:3070:int alloc_bootmem_huge_page(struct hstate *h, int nid)\nmm/hugetlb.c:3071:\t__attribute__ ((weak, alias(\"__alloc_bootmem_huge_page\")));\nmm/hugetlb.c:3072:int __alloc_bootmem_huge_page(struct hstate *h, int nid)\nmm/hugetlb.c-3073-{\n--\nmm/hugetlb.c-3078-\tif (nid != NUMA_NO_NODE) {\nmm/hugetlb.c:3079:\t\tm = alloc_bootmem(h, node, true);\nmm/hugetlb.c-3080-\t\tif (!m)\n--\nmm/hugetlb.c-3087-\t\t\t\t    \u0026hugetlb_bootmem_nodes) {\nmm/hugetlb.c:3088:\t\tm = alloc_bootmem(h, node, false);\nmm/hugetlb.c-3089-\t\tif (!m)\n--\nmm/hugetlb.c=3364=static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid)\n--\nmm/hugetlb.c-3371-\t\tif (hstate_is_gigantic(h)) {\nmm/hugetlb.c:3372:\t\t\tif (!alloc_bootmem_huge_page(h, nid))\nmm/hugetlb.c-3373-\t\t\t\tbreak;\n--\nmm/hugetlb.c=3465=static unsigned long __init hugetlb_gigantic_pages_alloc_boot(struct hstate *h)\n--\nmm/hugetlb.c-3469-\tfor (i = 0; i \u003c h-\u003emax_huge_pages; ++i) {\nmm/hugetlb.c:3470:\t\tif (!alloc_bootmem_huge_page(h, NUMA_NO_NODE))\nmm/hugetlb.c-3471-\t\t\tbreak;\n"
}

7/2 2026/07/10 13:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22975
							output: 22
							thoughts: 197
						

Thoughts:
**Fuzzing Target Identification**

Okay, so this patch is touching `mm/memblock.c` and `mm/hugetlb.c` – interesting. Particularly, I see `memblock_alloc_hugetlb` is used in `alloc_bootmem` which lives in `mm/hugetlb.c`. That screams potential, this looks like a good target for fuzzing. Definitely worth investigating more closely.

Now, let's identify the specific functions within that area that warrant deeper examination. I'm seeing a number of functions related to radix trees and memory management that I should focus on:

*   `kho_radix_add_key`
*   `kho_radix_del_key`
*   `kho_radix_walk_tree`
*   `kho_radix_init_tree`
*   `kho_radix_destroy_tree`
*   `kho_extend_scratch`
*   `kho_memory_init_early`
*   `kho_scratch_overlap`
*   `kho_init_scratch_migratetype`
*   `memblock_alloc_hugetlb`

Let's begin by checking to see if `kho_radix_add_key` is exported or used. This could be a good starting point.



8/2 2026/07/10 13:41 tool grepper
0m Args:
{
  "Expression": "kho_radix_add_key"
}

Results:
{
  "Output": "include/linux/kho_radix_tree.h=46=struct kho_radix_walk_cb {\n--\ninclude/linux/kho_radix_tree.h-52-\ninclude/linux/kho_radix_tree.h:53:int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);\ninclude/linux/kho_radix_tree.h-54-void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);\n--\ninclude/linux/kho_radix_tree.h=58=void kho_radix_destroy_tree(struct kho_radix_tree *tree);\n--\ninclude/linux/kho_radix_tree.h-61-\ninclude/linux/kho_radix_tree.h:62:static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\ninclude/linux/kho_radix_tree.h-63-{\n--\nkernel/liveupdate/kexec_handover.c=178=static void __ref kho_radix_free_node(struct kho_radix_node *node)\n--\nkernel/liveupdate/kexec_handover.c-186-/**\nkernel/liveupdate/kexec_handover.c:187: * kho_radix_add_key - Add a key to the radix tree.\nkernel/liveupdate/kexec_handover.c-188- * @tree: The KHO radix tree.\n--\nkernel/liveupdate/kexec_handover.c-202- */\nkernel/liveupdate/kexec_handover.c:203:int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\nkernel/liveupdate/kexec_handover.c-204-{\n--\nkernel/liveupdate/kexec_handover.c-271-}\nkernel/liveupdate/kexec_handover.c:272:EXPORT_SYMBOL_GPL(kho_radix_add_key);\nkernel/liveupdate/kexec_handover.c-273-\n--\nkernel/liveupdate/kexec_handover.c=933=static int __init kho_ext_walk_leaf(unsigned long key, void *data)\n--\nkernel/liveupdate/kexec_handover.c-944-\twhile (start \u003c end) {\nkernel/liveupdate/kexec_handover.c:945:\t\terr = kho_radix_add_key(tree, start \u003e\u003e KHO_EXT_SHIFT);\nkernel/liveupdate/kexec_handover.c-946-\t\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=956=static int __init kho_ext_walk_node(phys_addr_t phys, void *data)\n--\nkernel/liveupdate/kexec_handover.c-960-\nkernel/liveupdate/kexec_handover.c:961:\treturn kho_radix_add_key(tree, phys \u003e\u003e KHO_EXT_SHIFT);\nkernel/liveupdate/kexec_handover.c-962-}\n--\nkernel/liveupdate/kexec_handover.c=1149=int kho_preserve_folio(struct folio *folio)\n--\nkernel/liveupdate/kexec_handover.c-1158-\nkernel/liveupdate/kexec_handover.c:1159:\treturn kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\nkernel/liveupdate/kexec_handover.c-1160-\t\t\t\t\t\t\t    order));\n--\nkernel/liveupdate/kexec_handover.c=1224=int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n--\nkernel/liveupdate/kexec_handover.c-1241-\nkernel/liveupdate/kexec_handover.c:1242:\t\terr = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\nkernel/liveupdate/kexec_handover.c-1243-\t\t\t\t\t\t\t\t   order));\n"
}

9/2 2026/07/10 13:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24533
							output: 22
							thoughts: 14
						

Thoughts:
Okay, let's dive into this. Here's my thought process on checking `memblock_alloc_hugetlb`, summarized as if I'm thinking it:

**Analyzing the Allocation of HugeTLB Memory**

Alright, time to get into the details. I need to take a look at `memblock_alloc_hugetlb`. My initial focus is on *how* this function allocates HugeTLB memory. This implies a deeper understanding of the underlying memory management mechanisms, likely involving page table manipulation, physical address allocation, and potential considerations for NUMA architectures. I'll need to see how it integrates with the existing `memblock` system, particularly how the reserved memory regions for HugeTLBs are managed and if there is any overlap with other memory allocation schemes. I'll also be searching for any potential optimization, and will be watching how it deals with fragmentation and potential errors. Let's see how the kernel handles the whole thing.



10/2 2026/07/10 13:41 tool grepper
0m Args:
{
  "Expression": "memblock_alloc_hugetlb"
}

Results:
{
  "Output": "include/linux/memblock.h=422=void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,\n--\ninclude/linux/memblock.h-424-\t\t\t     int nid);\ninclude/linux/memblock.h:425:void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);\ninclude/linux/memblock.h-426-\n--\nmm/hugetlb.c=3028=static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)\n--\nmm/hugetlb.c-3035-\telse {\nmm/hugetlb.c:3036:\t\tm = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);\nmm/hugetlb.c-3037-\t\tif (m) {\n--\nmm/hugetlb.c-3044-\t\t\t * from. That node may be different in the case of\nmm/hugetlb.c:3045:\t\t\t * fallback by memblock_alloc_hugetlb_bootmem. So,\nmm/hugetlb.c-3046-\t\t\t * extract the actual node first.\n--\nmm/memblock.c=1751=void * __init memblock_alloc_try_nid_raw(\n--\nmm/memblock.c-1764-/**\nmm/memblock.c:1765: * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages\nmm/memblock.c-1766- * @size:      size of the memory to be allocated in bytes\n--\nmm/memblock.c-1777- */\nmm/memblock.c:1778:void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)\nmm/memblock.c-1779-{\n"
}

11/2 2026/07/10 13:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25270
							output: 19
							thoughts: 11
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm an expert in the field and considering it as my own thought:

**Checking `kho_scratch_overlap`**

Alright, time to get to it. First things first, I need to take a look at `kho_scratch_overlap`. This variable or process likely relates to a critical aspect of my current project, whatever that is, likely `kho`, which based on the variable name is the overarching project name. I suspect this either relates to how different `kho` iterations or components interact. Or the data is related to overlap, which could be spatial, temporal, algorithmic, or semantic. I'm probably trying to assess its validity, completeness, or perhaps its impact on something else. Specifically, "scratch" probably means it's an intermediate calculation or temporary holding spot. I have to see how it's working and functioning. This will be key. Depending on what it is, I have certain assumptions about its characteristics, and I need to verify that those hold true. The "overlap" part probably tells me the data has duplicates or a data that needs to be consolidated.



12/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_scratch_overlap"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=38=void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,\n--\ninclude/linux/kexec_handover.h-40-\ninclude/linux/kexec_handover.h:41:bool kho_scratch_overlap(phys_addr_t phys, size_t size);\ninclude/linux/kexec_handover.h-42-void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn);\n--\ninclude/linux/kexec_handover.h=117=static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n--\ninclude/linux/kexec_handover.h-121-\ninclude/linux/kexec_handover.h:122:static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\ninclude/linux/kexec_handover.h-123-{\n--\nkernel/liveupdate/kexec_handover.c=775=static phys_addr_t __init scratch_size_node(int nid)\n--\nkernel/liveupdate/kexec_handover.c-792-\nkernel/liveupdate/kexec_handover.c:793:bool kho_scratch_overlap(phys_addr_t phys, size_t size)\nkernel/liveupdate/kexec_handover.c-794-{\n--\nkernel/liveupdate/kexec_handover.c=1149=int kho_preserve_folio(struct folio *folio)\n--\nkernel/liveupdate/kexec_handover.c-1155-\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\nkernel/liveupdate/kexec_handover.c:1156:\t    WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\nkernel/liveupdate/kexec_handover.c-1157-\t\treturn -EINVAL;\n--\nkernel/liveupdate/kexec_handover.c=1224=int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n--\nkernel/liveupdate/kexec_handover.c-1233-\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\nkernel/liveupdate/kexec_handover.c:1234:\t    WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\nkernel/liveupdate/kexec_handover.c-1235-\t\t\t\t\tnr_pages \u003c\u003c PAGE_SHIFT))) {\n--\nkernel/liveupdate/kexec_handover.c=1902=void __init kho_memory_init_early(void)\n--\nkernel/liveupdate/kexec_handover.c-1909-\t/*\nkernel/liveupdate/kexec_handover.c:1910:\t * kho_scratch_overlap() needs kho_scratch to be initialized. It\nkernel/liveupdate/kexec_handover.c-1911-\t * is used by free_area_init() on KHO boots, so initialize it\n--\nmm/memblock.c=1778=void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)\n--\nmm/memblock.c-1810-\t */\nmm/memblock.c:1811:\tif (kho_scratch_overlap(addr, size)) {\nmm/memblock.c-1812-\t\tif (memblock_bottom_up())\n--\ntools/testing/memblock/internal.h=63=static inline void init_deferred_page(unsigned long pfn, int nid)\n--\ntools/testing/memblock/internal.h-68-\ntools/testing/memblock/internal.h:69:static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\ntools/testing/memblock/internal.h-70-{\n"
}

13/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26554
							output: 23
							thoughts: 15
						

Thoughts:
Here's a summary of my thought process:

**Diving into `kho_init_scratch_migratetype`**

Alright, let's get down to brass tacks. I'm taking a look at this `kho_init_scratch_migratetype` thing. Based on the naming convention, it's pretty clear this is probably related to the initialization of a scratch environment and possibly something related to migrating some data type or schema. I'm immediately thinking about potential use cases and gotchas. Is this for a specific database system? Is there some kind of versioning or compatibility concern that I need to keep in mind? Are we talking about a simple copy, or some sort of data transformation? The implications could be wide-ranging. I need to understand what this scratch environment is for, and precisely what data migration is involved. From there, I can determine the potential complexity and risks. I'll be looking for configuration variables, error handling, logging, and performance considerations. My mind starts running through the scenarios quickly.



14/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_init_scratch_migratetype"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=41=bool kho_scratch_overlap(phys_addr_t phys, size_t size);\ninclude/linux/kexec_handover.h:42:void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn);\ninclude/linux/kexec_handover.h-43-#else\n--\ninclude/linux/kexec_handover.h=122=static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n--\ninclude/linux/kexec_handover.h-126-\ninclude/linux/kexec_handover.h:127:static inline void kho_init_scratch_migratetype(unsigned long start_pfn,\ninclude/linux/kexec_handover.h-128-\t\t\t\t\t\tunsigned long end_pfn);\n--\nkernel/liveupdate/kexec_handover.c=793=bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n--\nkernel/liveupdate/kexec_handover.c-812- */\nkernel/liveupdate/kexec_handover.c:813:void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn)\nkernel/liveupdate/kexec_handover.c-814-{\n--\nmm/mm_init.c=694=void __meminit __init_page_from_nid(unsigned long pfn, int nid)\n--\nmm/mm_init.c-712-\t\t/* Update the migrate type in case it is KHO scratch. */\nmm/mm_init.c:713:\t\tkho_init_scratch_migratetype(pfn, pfn + 1);\nmm/mm_init.c-714-\t}\n--\nmm/mm_init.c=875=void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone,\n--\nmm/mm_init.c-950-\t */\nmm/mm_init.c:951:\tkho_init_scratch_migratetype(start_pfn, pfn);\nmm/mm_init.c-952-}\n--\nmm/mm_init.c=2023=static unsigned long __init deferred_init_pages(struct zone *zone,\n--\nmm/mm_init.c-2044-\t */\nmm/mm_init.c:2045:\tkho_init_scratch_migratetype(start_pfn, end_pfn);\nmm/mm_init.c-2046-\n"
}

15/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27467
							output: 21
							thoughts: 13
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing `kho_memory_init_early`**

Okay, time to dissect this. My first thought is to zero in on `kho_memory_init_early`. That function name itself is pretty indicative. "Early" suggests it's a critical initialization routine, likely executed before a significant portion of the system is up and running. I'm immediately thinking low-level, probably dealing with hardware, memory mapping, or at least setting up the foundation for the memory management subsystem.

My primary concern now is understanding its scope. Does it deal with just the kernel's memory, or is it setting up initial mappings for user space too, even at this stage? That would be interesting, and potentially tricky, requiring careful attention to address spaces.

I need to understand what specific hardware contexts this function is operating under. Does this routine deal with MMU configuration, page table initialization, or is it more related to bootloader-like activities like reserving memory regions? Is there anything specific about the architecture (e.g., ARM, x86) that should dictate my analysis?

I'm mentally preparing for a deep dive into the code. I'll need to look at any included headers for data structures and potential dependencies on other initial routines. I expect to be looking for potential race conditions, especially if this function is called from multiple parts of the boot process. Debugging this type of function is always fun! Let's get going.



16/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_memory_init_early"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=35=void kho_memory_init(void);\ninclude/linux/kexec_handover.h:36:void kho_memory_init_early(void);\ninclude/linux/kexec_handover.h-37-\n--\ninclude/linux/kexec_handover.h=113=static inline void kho_memory_init(void) { }\ninclude/linux/kexec_handover.h-114-\ninclude/linux/kexec_handover.h:115:static inline void kho_memory_init_early(void) { }\ninclude/linux/kexec_handover.h-116-\n--\nkernel/liveupdate/kexec_handover.c=1900=fs_initcall(kho_init);\nkernel/liveupdate/kexec_handover.c-1901-\nkernel/liveupdate/kexec_handover.c:1902:void __init kho_memory_init_early(void)\nkernel/liveupdate/kexec_handover.c-1903-{\n--\nmm/mm_init.c=2704=void __init mm_core_init_early(void)\nmm/mm_init.c-2705-{\nmm/mm_init.c:2706:\tkho_memory_init_early();\nmm/mm_init.c-2707-\thugetlb_cma_reserve();\n"
}

17/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28186
							output: 19
							thoughts: 265
						

Thoughts:
**Fuzzing Kexec Handover: A Strategy**

Okay, so this patch is introducing Kexec Handover (KHO) functionality and messing with how the kernel allocates memory for huge pages (hugetlb) using memblock. My gut says: **fuzz this!** This hits *core* kernel logic – memblock, mm_init, hugetlb – and, crucially, adds entirely new functions that can be called during both boot and runtime. That's a fuzzing goldmine right there.

Hmm, let's see, `kho_memory_init_early` is called from `mm_core_init_early`. And `memblock_alloc_hugetlb` is actually used with `alloc_bootmem` in `mm/hugetlb.c`. These are boot-time functions. Can we even fuzz them effectively?

Syzbot is amazing, but typically it hits things post-boot. However, we could potentially fuzz boot parameters to influence these early stages, or perhaps Syzbot could trigger KHO directly if it’s exposed to userspace.

Alright, the plot thickens. `kho_radix_add_key` and `kho_radix_del_key` are exported symbols. That's huge! And they’re called from `kho_preserve_folio`, `kho_unpreserve_folio`, `kho_preserve_pages`, and `kho_unpreserve_pages`. Those functions are how drivers and subsystems are going to deal with preserving memory across a kexec transition! That means runtime exposure. Excellent. This significantly increases the fuzzing attack surface, because now we can potentially hit these during runtime!

I think I will check to see how `kho_preserve_folio` is used elsewhere in the kernel.



18/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_preserve_folio"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=17=bool is_kho_boot(void);\ninclude/linux/kexec_handover.h-18-\ninclude/linux/kexec_handover.h:19:int kho_preserve_folio(struct folio *folio);\ninclude/linux/kexec_handover.h-20-void kho_unpreserve_folio(struct folio *folio);\n--\ninclude/linux/kexec_handover.h=49=static inline bool is_kho_boot(void)\n--\ninclude/linux/kexec_handover.h-53-\ninclude/linux/kexec_handover.h:54:static inline int kho_preserve_folio(struct folio *folio)\ninclude/linux/kexec_handover.h-55-{\n--\nkernel/liveupdate/kexec_handover.c=1138=EXPORT_SYMBOL_GPL(kho_remove_subtree);\n--\nkernel/liveupdate/kexec_handover.c-1140-/**\nkernel/liveupdate/kexec_handover.c:1141: * kho_preserve_folio - preserve a folio across kexec.\nkernel/liveupdate/kexec_handover.c-1142- * @folio: folio to preserve.\n--\nkernel/liveupdate/kexec_handover.c-1148- */\nkernel/liveupdate/kexec_handover.c:1149:int kho_preserve_folio(struct folio *folio)\nkernel/liveupdate/kexec_handover.c-1150-{\n--\nkernel/liveupdate/kexec_handover.c-1161-}\nkernel/liveupdate/kexec_handover.c:1162:EXPORT_SYMBOL_GPL(kho_preserve_folio);\nkernel/liveupdate/kexec_handover.c-1163-\n--\nkernel/liveupdate/kexec_handover.c-1168- * Instructs KHO to unpreserve a folio that was preserved by\nkernel/liveupdate/kexec_handover.c:1169: * kho_preserve_folio() before. The provided @folio (pfn and order)\nkernel/liveupdate/kexec_handover.c-1170- * must exactly match a previously preserved folio.\n--\nkernel/liveupdate/kexec_handover.c=1547=void *kho_alloc_preserve(size_t size)\n--\nkernel/liveupdate/kexec_handover.c-1562-\nkernel/liveupdate/kexec_handover.c:1563:\tret = kho_preserve_folio(folio);\nkernel/liveupdate/kexec_handover.c-1564-\tif (ret) {\n--\nlib/test_kho.c=54=static int kho_test_preserve_data(struct kho_test_state *state)\n--\nlib/test_kho.c-75-\t\tfolios_info[i] = virt_to_phys(folio_address(folio)) | order;\nlib/test_kho.c:76:\t\terr = kho_preserve_folio(folio);\nlib/test_kho.c-77-\t\tif (err)\n--\nlib/test_kho.c=124=static int kho_test_preserve(struct kho_test_state *state)\n--\nlib/test_kho.c-133-\nlib/test_kho.c:134:\terr = kho_preserve_folio(state-\u003efdt);\nlib/test_kho.c-135-\tif (err)\n--\nmm/memfd_luo.c=87=static int memfd_luo_preserve_folios(struct file *file,\n--\nmm/memfd_luo.c-151-\nmm/memfd_luo.c:152:\t\terr = kho_preserve_folio(folio);\nmm/memfd_luo.c-153-\t\tif (err)\n"
}

19/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29817
							output: 19
							thoughts: 283
						

Thoughts:
**My Initial Assessment of the 'kho' and 'memfd_luo' Code**

Okay, so `memfd_luo.c` is definitely making use of this, which makes me think it's likely a memfd extension related to live updates. Interesting. And then I see it again in `lib/test_kho.c`.  That means `kho_preserve_folio` is definitely reachable, which is a key point to remember.

Now, let's think about `memblock_alloc_hugetlb`.  I know that's called from `alloc_bootmem` in `mm/hugetlb.c`. That's allocated during boot when hugetlb pages get allocated.  Hang on... `alloc_bootmem` is linked with the `hugepages=` kernel command-line option, right?  Would fuzzing be useful here? Probably not, since `alloc_bootmem` is `__init`, so it would be a boot time issue and less useful to syzbot.

But wait, `kho_radix_add_key`, `kho_radix_del_key`, and `kho_radix_walk_tree` are reachable during runtime! That's promising. And `kho_extend_scratch` is `__init`, so not immediately fuzzable.  Similarly, `kho_memory_init_early` is marked as `__init` as well.

The more interesting find is that `kho_scratch_overlap` is called from `kho_preserve_folio` and `kho_preserve_pages`, and we've already established that those can be called at runtime, which is a great target.

Let's dig into `kho_preserve_pages` to understand the implications of that call chain.



20/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_preserve_pages"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=20=void kho_unpreserve_folio(struct folio *folio);\ninclude/linux/kexec_handover.h:21:int kho_preserve_pages(struct page *page, unsigned long nr_pages);\ninclude/linux/kexec_handover.h-22-void kho_unpreserve_pages(struct page *page, unsigned long nr_pages);\n--\ninclude/linux/kexec_handover.h=59=static inline void kho_unpreserve_folio(struct folio *folio) { }\ninclude/linux/kexec_handover.h-60-\ninclude/linux/kexec_handover.h:61:static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)\ninclude/linux/kexec_handover.h-62-{\n--\nkernel/liveupdate/kexec_handover.c=545=EXPORT_SYMBOL_GPL(kho_restore_folio);\n--\nkernel/liveupdate/kexec_handover.c-552- * Restore a contiguous list of order 0 pages that was preserved with\nkernel/liveupdate/kexec_handover.c:553: * kho_preserve_pages().\nkernel/liveupdate/kexec_handover.c-554- *\n--\nkernel/liveupdate/kexec_handover.c=1180=EXPORT_SYMBOL_GPL(kho_unpreserve_folio);\nkernel/liveupdate/kexec_handover.c-1181-\nkernel/liveupdate/kexec_handover.c:1182:static unsigned int __kho_preserve_pages_order(unsigned long start_pfn,\nkernel/liveupdate/kexec_handover.c-1183-\t\t\t\t\t       unsigned long end_pfn)\n--\nkernel/liveupdate/kexec_handover.c=1199=static void __kho_unpreserve(struct kho_radix_tree *tree,\n--\nkernel/liveupdate/kexec_handover.c-1204-\twhile (pfn \u003c end_pfn) {\nkernel/liveupdate/kexec_handover.c:1205:\t\torder = __kho_preserve_pages_order(pfn, end_pfn);\nkernel/liveupdate/kexec_handover.c-1206-\n--\nkernel/liveupdate/kexec_handover.c-1214-/**\nkernel/liveupdate/kexec_handover.c:1215: * kho_preserve_pages - preserve contiguous pages across kexec\nkernel/liveupdate/kexec_handover.c-1216- * @page: first page in the list.\n--\nkernel/liveupdate/kexec_handover.c-1223- */\nkernel/liveupdate/kexec_handover.c:1224:int kho_preserve_pages(struct page *page, unsigned long nr_pages)\nkernel/liveupdate/kexec_handover.c-1225-{\n--\nkernel/liveupdate/kexec_handover.c-1239-\twhile (pfn \u003c end_pfn) {\nkernel/liveupdate/kexec_handover.c:1240:\t\tunsigned int order = __kho_preserve_pages_order(pfn, end_pfn);\nkernel/liveupdate/kexec_handover.c-1241-\n--\nkernel/liveupdate/kexec_handover.c-1256-}\nkernel/liveupdate/kexec_handover.c:1257:EXPORT_SYMBOL_GPL(kho_preserve_pages);\nkernel/liveupdate/kexec_handover.c-1258-\n--\nkernel/liveupdate/kexec_handover.c-1265- * This must be called with the same @page and @nr_pages as the corresponding\nkernel/liveupdate/kexec_handover.c:1266: * kho_preserve_pages() call. Unpreserving arbitrary sub-ranges of larger\nkernel/liveupdate/kexec_handover.c-1267- * preserved blocks is not supported.\n--\nkernel/liveupdate/kexec_handover.c=1310=static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)\n--\nkernel/liveupdate/kexec_handover.c-1318-\nkernel/liveupdate/kexec_handover.c:1319:\terr = kho_preserve_pages(virt_to_page(chunk), 1);\nkernel/liveupdate/kexec_handover.c-1320-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1360=int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)\n--\nkernel/liveupdate/kexec_handover.c-1385-\nkernel/liveupdate/kexec_handover.c:1386:\t\terr = kho_preserve_pages(vm-\u003epages[i], nr_contig_pages);\nkernel/liveupdate/kexec_handover.c-1387-\t\tif (err)\n--\nmm/memblock.c=2608=static int __init reserved_mem_preserve(void)\n--\nmm/memblock.c-2617-\nmm/memblock.c:2618:\t\terr = kho_preserve_pages(page, nr_pages);\nmm/memblock.c-2619-\t\tif (err)\n--\nmm/memblock.c=2637=static int __init prepare_kho_fdt(void)\n--\nmm/memblock.c-2649-\tfdt = page_to_virt(fdt_page);\nmm/memblock.c:2650:\terr = kho_preserve_pages(fdt_page, 1);\nmm/memblock.c-2651-\tif (err)\n"
}

21/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31951
							output: 22
							thoughts: 434
						

Thoughts:
**Fuzzing Target Identification**

Okay, so I'm looking at this patch and tracing the function calls. It looks like `kho_preserve_pages` is key, called by `kho_preserve_vmalloc`, which is in turn used by drivers or subsystems - interesting. The patch modifies `kho_radix_add_key` and `kho_radix_del_key`. Those are called by `kho_preserve_folio` and `kho_preserve_pages`, which makes them good candidates. There's also this new function, `kho_scratch_overlap`, that's introduced and also called by `kho_preserve_folio` and `kho_preserve_pages`. And then there's a modification to `memblock_alloc_hugetlb` called by `alloc_bootmem`, deep in `mm/hugetlb.c`. Right, this is definitely worth some fuzzing effort.

Now, let's identify the specific functions to focus on. I've got a list forming: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, `kho_radix_destroy_tree`, `kho_scratch_overlap`, `kho_init_scratch_migratetype`, and `memblock_alloc_hugetlb`.

Hold on, I see `kho_extend_scratch` and `kho_memory_init_early` are `__init` functions. Can syzbot even fuzz those? No, not directly. Syzbot triggers `__init` functions if they are called during boot, so they aren't great candidates for the `FocusSymbols` list. That list is for fuzzer targets. So, I will include `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, `kho_radix_destroy_tree`, and `kho_scratch_overlap`. I need to double-check `kho_radix_init_tree` and `kho_radix_destroy_tree` though. They are exported symbols, but are they actually used outside their module? I should look into that before committing this as a proper fuzzing target list.



22/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_radix_init_tree"
}

Results:
{
  "Output": "include/linux/kho_radix_tree.h=55=int kho_radix_walk_tree(struct kho_radix_tree *tree,\ninclude/linux/kho_radix_tree.h-56-\t\t\tconst struct kho_radix_walk_cb *cb, void *data);\ninclude/linux/kho_radix_tree.h:57:int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);\ninclude/linux/kho_radix_tree.h-58-void kho_radix_destroy_tree(struct kho_radix_tree *tree);\n--\ninclude/linux/kho_radix_tree.h=70=static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,\n--\ninclude/linux/kho_radix_tree.h-75-\ninclude/linux/kho_radix_tree.h:76:static inline int kho_radix_init_tree(struct kho_radix_tree *tree,\ninclude/linux/kho_radix_tree.h-77-\t\t\t\t      struct kho_radix_node *root)\n--\nkernel/liveupdate/kexec_handover.c=321=static void __kho_radix_destroy_tree(struct kho_radix_node *root,\n--\nkernel/liveupdate/kexec_handover.c-340-/**\nkernel/liveupdate/kexec_handover.c:341: * kho_radix_init_tree - initialize the radix tree.\nkernel/liveupdate/kexec_handover.c-342- * @tree:   the tree to initialize.\n--\nkernel/liveupdate/kexec_handover.c-350- */\nkernel/liveupdate/kexec_handover.c:351:int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)\nkernel/liveupdate/kexec_handover.c-352-{\n--\nkernel/liveupdate/kexec_handover.c-361-}\nkernel/liveupdate/kexec_handover.c:362:EXPORT_SYMBOL_GPL(kho_radix_init_tree);\nkernel/liveupdate/kexec_handover.c-363-\n--\nkernel/liveupdate/kexec_handover.c=1006=static void __init kho_extend_scratch(void)\n--\nkernel/liveupdate/kexec_handover.c-1018-\nkernel/liveupdate/kexec_handover.c:1019:\terr = kho_radix_init_tree(\u0026radix, NULL);\nkernel/liveupdate/kexec_handover.c-1020-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1820=static __init int kho_init(void)\n--\nkernel/liveupdate/kexec_handover.c-1828-\nkernel/liveupdate/kexec_handover.c:1829:\terr = kho_radix_init_tree(tree, NULL);\nkernel/liveupdate/kexec_handover.c-1830-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1902=void __init kho_memory_init_early(void)\n--\nkernel/liveupdate/kexec_handover.c-1919-\t */\nkernel/liveupdate/kexec_handover.c:1920:\tif (kho_radix_init_tree(\u0026kho_in.radix_tree, kho_get_mem_map(fdt))) {\nkernel/liveupdate/kexec_handover.c-1921-\t\t/*\n"
}

23/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33842
							output: 23
							thoughts: 156
						

Thoughts:
**My Mental Checklist for Memory Allocation Functions**

Okay, so I'm putting together a mental map of where these key memory allocation functions are used. I need to make sure I have a comprehensive overview, especially since these functions are so fundamental to the system's operation.  I know they're definitely exported and utilized in `kho_init`, `kho_extend_scratch`, and `kho_memory_init_early`.

I should probably also include these radix tree functions in my thinking: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`. I'm also remembering `kho_scratch_overlap` and `kho_init_scratch_migratetype` are probably worth listing. And of course, the big one: `memblock_alloc_hugetlb`.

Let me quickly verify something about `memblock_alloc_hugetlb`.  Ah, yes, that's defined as `__init`. Now, what about `kho_init_scratch_migratetype`? Hmm, it looks like it isn't `__init`. Let me double-check that... just to be certain.



24/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "kho_init_scratch_migratetype"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=41=bool kho_scratch_overlap(phys_addr_t phys, size_t size);\ninclude/linux/kexec_handover.h:42:void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn);\ninclude/linux/kexec_handover.h-43-#else\n--\ninclude/linux/kexec_handover.h=122=static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n--\ninclude/linux/kexec_handover.h-126-\ninclude/linux/kexec_handover.h:127:static inline void kho_init_scratch_migratetype(unsigned long start_pfn,\ninclude/linux/kexec_handover.h-128-\t\t\t\t\t\tunsigned long end_pfn);\n--\nkernel/liveupdate/kexec_handover.c=793=bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n--\nkernel/liveupdate/kexec_handover.c-812- */\nkernel/liveupdate/kexec_handover.c:813:void kho_init_scratch_migratetype(unsigned long start_pfn, unsigned long end_pfn)\nkernel/liveupdate/kexec_handover.c-814-{\n--\nmm/mm_init.c=694=void __meminit __init_page_from_nid(unsigned long pfn, int nid)\n--\nmm/mm_init.c-712-\t\t/* Update the migrate type in case it is KHO scratch. */\nmm/mm_init.c:713:\t\tkho_init_scratch_migratetype(pfn, pfn + 1);\nmm/mm_init.c-714-\t}\n--\nmm/mm_init.c=875=void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone,\n--\nmm/mm_init.c-950-\t */\nmm/mm_init.c:951:\tkho_init_scratch_migratetype(start_pfn, pfn);\nmm/mm_init.c-952-}\n--\nmm/mm_init.c=2023=static unsigned long __init deferred_init_pages(struct zone *zone,\n--\nmm/mm_init.c-2044-\t */\nmm/mm_init.c:2045:\tkho_init_scratch_migratetype(start_pfn, end_pfn);\nmm/mm_init.c-2046-\n"
}

25/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34954
							output: 34
							thoughts: 179
						

Thoughts:
**Initial Assessment of Memory Initialization Functions**

Okay, so I'm looking at this code, and a few things jump out. The function `kho_init_scratch_migratetype` isn't explicitly marked as an `__init` function, which is a bit odd, but I see it's called from both `__init` and `__meminit` functions. That's something to keep an eye on.

My initial thought is that `kho_radix_add_key`, `kho_radix_del_key`, and `kho_radix_walk_tree` look like prime candidates for fuzzing. They seem like good starting points to look for memory-related vulnerabilities. `kho_scratch_overlap` seems potentially interesting too, so I'll include it on the list.

Now, let me shift gears and check for dependencies and configurations. I need to understand the environment these functions operate in. I'm going to check `CONFIG_KEXEC_HANDOVER` and `CONFIG_HUGETLB_PAGE`. Right, `CONFIG_KEXEC_HANDOVER` seems to be the primary configuration item. And `CONFIG_HUGETLB_PAGE` is crucial because it's required for `memblock_alloc_hugetlb` to be used. I need to understand that call. Wait, `memblock_alloc_hugetlb` is defined in `mm/memblock.c`. It's defined there, but I need to make sure it isn't compiled conditionally. I'll need to check the build configurations to verify this.



26/2 2026/07/10 13:42 tool grepper
0m Args:
{
  "Expression": "memblock_alloc_hugetlb",
  "PathPrefix": "mm/memblock.c"
}

Results:
{
  "Output": "mm/memblock.c=1751=void * __init memblock_alloc_try_nid_raw(\n--\nmm/memblock.c-1764-/**\nmm/memblock.c:1765: * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages\nmm/memblock.c-1766- * @size:      size of the memory to be allocated in bytes\n--\nmm/memblock.c-1777- */\nmm/memblock.c:1778:void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)\nmm/memblock.c-1779-{\n"
}

27/2 2026/07/10 13:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35686
							output: 198
							thoughts: 245
						

Thoughts:
**Analyzing the Dependencies of a Code Snippet**

Okay, so I'm looking at this piece of code and it's not guarded by any preprocessor directives like `#ifdef`. That's a little unusual. However, after a quick check, I can see that this code is only called from within `mm/hugetlb.c`. Now, `mm/hugetlb.c` itself is only compiled when the `CONFIG_HUGETLB_PAGE` option is enabled in the kernel configuration. That means the relevant configurations are `KEXEC_HANDOVER` and `HUGETLB_PAGE`. Right, got it.

Let me just make sure those are properly summarized:

*   `KEXEC_HANDOVER`
*   `HUGETLB_PAGE`

Now, let's look at the key symbols that are related to this. Okay, I see:

*   `kho_radix_add_key`
*   `kho_radix_del_key`
*   `kho_radix_walk_tree`
*   `kho_radix_init_tree`
*   `kho_radix_destroy_tree`
*   `kho_scratch_overlap`
*   `kho_init_scratch_migratetype`
*   `memblock_alloc_hugetlb`

Hmm, after a quick evaluation, `kho_radix_add_key` and `kho_radix_del_key` stand out as the most crucial functions here, at least in the context of what I'm looking at. `kho_scratch_overlap` also seems pretty important. And `memblock_alloc_hugetlb` looks like a relevant memory allocation function.

Alright, I think I have a good handle on things. Let's set the results now.



28/2 2026/07/10 13:43 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "kho_scratch_overlap",
    "kho_init_scratch_migratetype",
    "memblock_alloc_hugetlb"
  ],
  "Reasoning": "The patch modifies the Kexec Handover (KHO) radix tree implementation, introduces new functions for KHO scratch memory management, and changes how HugeTLB pages are allocated during boot using memblock. These are functional changes to core kernel logic and memory management that can be reached during boot and runtime, making them worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "kho_scratch_overlap",
    "kho_init_scratch_migratetype",
    "memblock_alloc_hugetlb"
  ],
  "Reasoning": "The patch modifies the Kexec Handover (KHO) radix tree implementation, introduces new functions for KHO scratch memory management, and changes how HugeTLB pages are allocated during boot using memblock. These are functional changes to core kernel logic and memory management that can be reached during boot and runtime, making them worth fuzzing.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)