summaryrefslogtreecommitdiff
path: root/include/linux
diff options
context:
space:
mode:
authorMike Rapoport (Microsoft) <rppt@kernel.org>2026-08-04 09:50:03 +0300
committerMike Rapoport (Microsoft) <rppt@kernel.org>2026-08-04 09:50:03 +0300
commitbc0f793c5c41601b273a2ca64c768adca1d447f5 (patch)
treeacf4d825eac409f78834430f91000398945107e9 /include/linux
parent4b9c548d9bf16f46d75db79f192506e9929eb3ec (diff)
parentfdd843f2be1a639a1f7522388e43283b60f26397 (diff)
Merge patch series "kho: make boot time huge page allocation work nicely with KHO"
Pratyush Yadav <pratyush@kernel.org> says: Gigantic huge page allocation is somewhat broken currently with KHO. First, they break scratch size accounting. Since they are allocated using the memblock alloc APIs, they count towards RSRV_KERN, and this scratch size when using scratch_scale. This means if huge pages take a large enough chunk of system memory scratch size will blow up and fail to allocate. Second, scratch can not contain preserved memory, and if huge pages are allocated from scratch, they will fail to be preserved with the upcoming hugetlb preservation series [0]. Fix this by introducing the concept of extended scratch areas. They are areas that the kernel discovers on boot by walking the KHO preserved memory radix tree and finding free memory ranges. [0] https://lore.kernel.org/linux-mm/20251206230222.853493-1-pratyush@kernel.org/T/#u *patches from https://patch.msgid.link/20260801084833.1897543-1-pratyush@kernel.org/ kho: generalize radix tree APIs kho: make radix max key width more obvious kho: disallow wide keys in radix tree kho: store incoming radix tree in kho_in kho: move all memory retrieval logic to kho_mem_retrieve() kho: add a struct for radix callbacks kho: add callback for table pages kho: add data argument to radix walk callback kho: allow early-boot usage of the KHO radix tree kho: allow destroying KHO radix tree kho: add kho_radix_init_tree() kho: expose kho_scratch_overlap() to kexec_handover.h kho: initialize kho_scratch pointer earlier in boot kho: initialize preserved memory map radix tree earlier mm/mm_init: don't rely on memblock to get KHO scratch migratetype kho: extend scratch memblock: always include KHO headers memblock: make HugeTLB bootmem allocation work with KHO memblock: add memblock_reserved_hugetlb_size() kho: exclude hugetlb memory from scratch size calculation
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/kexec_handover.h25
-rw-r--r--include/linux/kho/abi/kexec_handover.h9
-rw-r--r--include/linux/kho_radix_tree.h60
-rw-r--r--include/linux/memblock.h23
4 files changed, 72 insertions, 45 deletions
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 48a9793c2b76..46de86dc343e 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -5,6 +5,7 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/types.h>
+#include <linux/mm.h>
#include <asm-generic/kexec_handover.h>
struct kho_vmalloc;
@@ -33,9 +34,20 @@ void kho_remove_subtree(void *blob);
int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
void kho_memory_init(void);
+void kho_memory_init_early(void);
void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,
u64 scratch_len);
+
+bool kho_scratch_overlap(phys_addr_t phys, size_t size);
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+ enum migratetype mt)
+{
+ if (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT))
+ return MIGRATE_CMA;
+ return mt;
+}
#else
static inline bool kho_is_enabled(void)
{
@@ -108,10 +120,23 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,
static inline void kho_memory_init(void) { }
+static inline void kho_memory_init_early(void) { }
+
static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
phys_addr_t scratch_phys, u64 scratch_len)
{
}
+
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+ return false;
+}
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+ enum migratetype mt)
+{
+ return mt;
+}
#endif /* CONFIG_KEXEC_HANDOVER */
#endif /* LINUX_KEXEC_HANDOVER_H */
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 5e2eb8519bda..2f4fb9c63942 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -257,11 +257,8 @@ struct kho_vmalloc {
* memory. These constants govern the indexing, sizing, and depth of the tree.
*/
enum kho_radix_consts {
- /*
- * The bit position of the order bit (and also the length of the
- * shifted physical address) for an order-0 page.
- */
- KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,
+ /* Need to store the PFN, plus one bit for order. */
+ KHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1,
/* Size of the table in kho_radix_node, in log2 */
KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),
@@ -274,7 +271,7 @@ enum kho_radix_consts {
* and 1 bitmap level.
*/
KHO_TREE_MAX_DEPTH =
- DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
+ DIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2,
KHO_TABLE_SIZE_LOG2) + 1,
};
diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 84e918b96e53..fcd69639721b 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -11,15 +11,19 @@
/**
* DOC: Kexec Handover Radix Tree
*
- * This is a radix tree implementation for tracking physical memory pages
- * across kexec transitions. It was developed for the KHO mechanism but is
- * designed for broader use by any subsystem that needs to preserve pages.
+ * This is a radix tree implementation for tracking numeric keys across kexec
+ * transitions. It was developed for the KHO preserved memory map but is
+ * designed for broader use by any subsystem that needs to track keys.
+ * Conceptually speaking, the data structure is similar to a set. It tracks the
+ * presence or absence of numeric keys.
*
* The radix tree is a multi-level tree where leaf nodes are bitmaps
- * representing individual pages. To allow pages of different sizes (orders)
+ * representing individual keys.
+ *
+ * For the KHO preserved memory map, to allow pages of different sizes (orders)
* to be stored efficiently in a single tree, it uses a unique key encoding
- * scheme. Each key is an unsigned long that combines a page's physical
- * address and its order.
+ * scheme. Each key is an unsigned long that combines a page's physical address
+ * and its order.
*
* Client code is responsible for allocating the root node of the tree,
* initializing the mutex lock, and managing its lifecycle. It must use the
@@ -34,37 +38,53 @@ struct kho_radix_tree {
struct mutex lock; /* protects the tree's structure and root pointer */
};
-typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,
- unsigned int order);
+/**
+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
+ * @leaf: Called on each present key in the radix tree.
+ * @node: Called on each node of the radix tree itself. Receives the
+ * physical address of the page containing the node.
+ *
+ * For each callback, a return value of 0 continues the walk and a non-zero
+ * return value is directly returned to the caller.
+ */
+struct kho_radix_walk_cb {
+ int (*leaf)(unsigned long key, void *data);
+ int (*node)(phys_addr_t phys, void *data);
+};
#ifdef CONFIG_KEXEC_HANDOVER
-int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,
- unsigned int order);
-
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
- unsigned int order);
-
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
int kho_radix_walk_tree(struct kho_radix_tree *tree,
- kho_radix_tree_walk_callback_t cb);
+ const struct kho_radix_walk_cb *cb, void *data);
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);
+void kho_radix_destroy_tree(struct kho_radix_tree *tree);
#else /* #ifdef CONFIG_KEXEC_HANDOVER */
-static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,
- unsigned int order)
+static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
{
return -EOPNOTSUPP;
}
-static inline void kho_radix_del_page(struct kho_radix_tree *tree,
- unsigned long pfn, unsigned int order) { }
+static inline void kho_radix_del_key(struct kho_radix_tree *tree,
+ unsigned long key) { }
static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
- kho_radix_tree_walk_callback_t cb)
+ const struct kho_radix_walk_cb *cb, void *data)
{
return -EOPNOTSUPP;
}
+static inline int kho_radix_init_tree(struct kho_radix_tree *tree,
+ struct kho_radix_node *root)
+{
+ return 0;
+}
+
+static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
+
#endif /* #ifdef CONFIG_KEXEC_HANDOVER */
#endif /* _LINUX_KHO_RADIX_TREE_H */
diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index 5afcd99aa8c1..d62db9e776cf 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn;
* memory reservations yet, so we get scratch memory from the previous
* kernel that we know is good to use. It is the only memory that
* allocations may happen from in this phase.
+ * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages
*/
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* No special request */
@@ -61,6 +62,7 @@ enum memblock_flags {
MEMBLOCK_RSRV_NOINIT = 0x10, /* don't initialize struct pages */
MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */
MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */
+ MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */
};
/**
@@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,
void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,
phys_addr_t min_addr, phys_addr_t max_addr,
int nid);
+void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);
static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
{
@@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void)
phys_addr_t memblock_phys_mem_size(void);
phys_addr_t memblock_reserved_size(void);
phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);
+phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid);
unsigned long memblock_estimated_nr_free_pages(void);
phys_addr_t memblock_start_of_DRAM(void);
phys_addr_t memblock_end_of_DRAM(void);
@@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { }
#ifdef CONFIG_MEMBLOCK_KHO_SCRATCH
void memblock_set_kho_scratch_only(void);
void memblock_clear_kho_scratch_only(void);
-bool memblock_is_kho_scratch_memory(phys_addr_t addr);
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
- enum migratetype mt)
-{
- if (memblock_is_kho_scratch_memory(PFN_PHYS(pfn)))
- return MIGRATE_CMA;
- return mt;
-}
#else
static inline void memblock_set_kho_scratch_only(void) { }
static inline void memblock_clear_kho_scratch_only(void) { }
-static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr)
-{
- return false;
-}
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
- enum migratetype mt)
-{
- return mt;
-}
#endif
#endif /* _LINUX_MEMBLOCK_H */