diff options
| author | Mark Brown <broonie@kernel.org> | 2026-08-21 14:07:39 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-08-21 14:07:39 +0100 |
| commit | 8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1 (patch) | |
| tree | d67964a2565800b1b3f18481156b00817e7b9271 | |
| parent | b641675828f273b653697be9b37ac423dc01503a (diff) | |
| parent | 352ebb57013a08679788bc18c7f9af90903db256 (diff) | |
| download | linux-next-8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1.tar.gz linux-next-8a1f9777c6afa3e6e2239d227353bae7f7e9bcb1.zip | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
24 files changed, 781 insertions, 254 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index c4e6dc192502..8cb9209b8c3e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14421,6 +14421,7 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git F: Documentation/admin-guide/mm/kho.rst F: Documentation/core-api/kho/* +F: include/asm-generic/kexec_handover.h F: include/linux/kexec_handover.h F: include/linux/kho/ F: include/linux/kho_block.h diff --git a/arch/x86/boot/compressed/kaslr.c b/arch/x86/boot/compressed/kaslr.c index 8e4bf5365ac6..22267a83e064 100644 --- a/arch/x86/boot/compressed/kaslr.c +++ b/arch/x86/boot/compressed/kaslr.c @@ -32,6 +32,8 @@ #include <asm/setup.h> /* For COMMAND_LINE_SIZE */ #undef _SETUP +#include <asm/kexec_handover.h> + extern unsigned long get_cmd_line_ptr(void); /* Simplified build-specific string for starting entropy. */ diff --git a/arch/x86/include/asm/Kbuild b/arch/x86/include/asm/Kbuild index 078fd2c0d69d..47ef8cb482e3 100644 --- a/arch/x86/include/asm/Kbuild +++ b/arch/x86/include/asm/Kbuild @@ -15,3 +15,4 @@ generic-y += fprobe.h generic-y += mcs_spinlock.h generic-y += mmzone.h generic-y += ring_buffer.h +generic-y += kexec_handover.h diff --git a/arch/x86/include/asm/setup.h b/arch/x86/include/asm/setup.h index 914eb32581c7..895d09faaf83 100644 --- a/arch/x86/include/asm/setup.h +++ b/arch/x86/include/asm/setup.h @@ -69,8 +69,6 @@ extern void x86_ce4100_early_setup(void); static inline void x86_ce4100_early_setup(void) { } #endif -#include <linux/kexec_handover.h> - #ifndef _SETUP #include <asm/espfix.h> diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index 1cc4aa65930c..a8c873356f17 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -16,6 +16,7 @@ #include <linux/init_ohci1394_dma.h> #include <linux/initrd.h> #include <linux/iscsi_ibft.h> +#include <linux/kexec_handover.h> #include <linux/memblock.h> #include <linux/panic_notifier.h> #include <linux/pci.h> diff --git a/include/asm-generic/kexec_handover.h b/include/asm-generic/kexec_handover.h new file mode 100644 index 000000000000..50839fb5ee8e --- /dev/null +++ b/include/asm-generic/kexec_handover.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_GENERIC_KEXEC_HANDOVER_H +#define __ASM_GENERIC_KEXEC_HANDOVER_H + +#include <linux/types.h> + +struct kho_scratch { + phys_addr_t addr; + phys_addr_t size; +}; + +#endif /* __ASM_GENERIC_KEXEC_HANDOVER_H */ diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h index 8968c56d2d73..46de86dc343e 100644 --- a/include/linux/kexec_handover.h +++ b/include/linux/kexec_handover.h @@ -5,11 +5,8 @@ #include <linux/err.h> #include <linux/errno.h> #include <linux/types.h> - -struct kho_scratch { - phys_addr_t addr; - phys_addr_t size; -}; +#include <linux/mm.h> +#include <asm-generic/kexec_handover.h> struct kho_vmalloc; @@ -37,9 +34,20 @@ void kho_remove_subtree(void *blob); int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size); void kho_memory_init(void); +void kho_memory_init_early(void); void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys, u64 scratch_len); + +bool kho_scratch_overlap(phys_addr_t phys, size_t size); + +static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, + enum migratetype mt) +{ + if (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT)) + return MIGRATE_CMA; + return mt; +} #else static inline bool kho_is_enabled(void) { @@ -112,10 +120,23 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys, static inline void kho_memory_init(void) { } +static inline void kho_memory_init_early(void) { } + static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys, u64 scratch_len) { } + +static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size) +{ + return false; +} + +static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, + enum migratetype mt) +{ + return mt; +} #endif /* CONFIG_KEXEC_HANDOVER */ #endif /* LINUX_KEXEC_HANDOVER_H */ diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h index 5e2eb8519bda..2f4fb9c63942 100644 --- a/include/linux/kho/abi/kexec_handover.h +++ b/include/linux/kho/abi/kexec_handover.h @@ -257,11 +257,8 @@ struct kho_vmalloc { * memory. These constants govern the indexing, sizing, and depth of the tree. */ enum kho_radix_consts { - /* - * The bit position of the order bit (and also the length of the - * shifted physical address) for an order-0 page. - */ - KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT, + /* Need to store the PFN, plus one bit for order. */ + KHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1, /* Size of the table in kho_radix_node, in log2 */ KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)), @@ -274,7 +271,7 @@ enum kho_radix_consts { * and 1 bitmap level. */ KHO_TREE_MAX_DEPTH = - DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1, + DIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2, KHO_TABLE_SIZE_LOG2) + 1, }; diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h index 84e918b96e53..fcd69639721b 100644 --- a/include/linux/kho_radix_tree.h +++ b/include/linux/kho_radix_tree.h @@ -11,15 +11,19 @@ /** * DOC: Kexec Handover Radix Tree * - * This is a radix tree implementation for tracking physical memory pages - * across kexec transitions. It was developed for the KHO mechanism but is - * designed for broader use by any subsystem that needs to preserve pages. + * This is a radix tree implementation for tracking numeric keys across kexec + * transitions. It was developed for the KHO preserved memory map but is + * designed for broader use by any subsystem that needs to track keys. + * Conceptually speaking, the data structure is similar to a set. It tracks the + * presence or absence of numeric keys. * * The radix tree is a multi-level tree where leaf nodes are bitmaps - * representing individual pages. To allow pages of different sizes (orders) + * representing individual keys. + * + * For the KHO preserved memory map, to allow pages of different sizes (orders) * to be stored efficiently in a single tree, it uses a unique key encoding - * scheme. Each key is an unsigned long that combines a page's physical - * address and its order. + * scheme. Each key is an unsigned long that combines a page's physical address + * and its order. * * Client code is responsible for allocating the root node of the tree, * initializing the mutex lock, and managing its lifecycle. It must use the @@ -34,37 +38,53 @@ struct kho_radix_tree { struct mutex lock; /* protects the tree's structure and root pointer */ }; -typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys, - unsigned int order); +/** + * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk. + * @leaf: Called on each present key in the radix tree. + * @node: Called on each node of the radix tree itself. Receives the + * physical address of the page containing the node. + * + * For each callback, a return value of 0 continues the walk and a non-zero + * return value is directly returned to the caller. + */ +struct kho_radix_walk_cb { + int (*leaf)(unsigned long key, void *data); + int (*node)(phys_addr_t phys, void *data); +}; #ifdef CONFIG_KEXEC_HANDOVER -int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn, - unsigned int order); - -void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn, - unsigned int order); - +int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key); +void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key); int kho_radix_walk_tree(struct kho_radix_tree *tree, - kho_radix_tree_walk_callback_t cb); + const struct kho_radix_walk_cb *cb, void *data); +int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root); +void kho_radix_destroy_tree(struct kho_radix_tree *tree); #else /* #ifdef CONFIG_KEXEC_HANDOVER */ -static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn, - unsigned int order) +static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key) { return -EOPNOTSUPP; } -static inline void kho_radix_del_page(struct kho_radix_tree *tree, - unsigned long pfn, unsigned int order) { } +static inline void kho_radix_del_key(struct kho_radix_tree *tree, + unsigned long key) { } static inline int kho_radix_walk_tree(struct kho_radix_tree *tree, - kho_radix_tree_walk_callback_t cb) + const struct kho_radix_walk_cb *cb, void *data) { return -EOPNOTSUPP; } +static inline int kho_radix_init_tree(struct kho_radix_tree *tree, + struct kho_radix_node *root) +{ + return 0; +} + +static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { } + #endif /* #ifdef CONFIG_KEXEC_HANDOVER */ #endif /* _LINUX_KHO_RADIX_TREE_H */ diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index 63ea5417de84..6051abc0612c 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -25,6 +25,7 @@ struct file; /** * struct liveupdate_file_op_args - Arguments for file operation callbacks. * @handler: The file handler being called. + * @session: The session this file belongs to. * @retrieve_status: The retrieve status for the 'can_finish / finish' * operation. A value of 0 means the retrieve has not been * attempted, a positive value means the retrieve was @@ -45,6 +46,7 @@ struct file; */ struct liveupdate_file_op_args { struct liveupdate_file_handler *handler; + struct liveupdate_session *session; int retrieve_status; struct file *file; u64 serialized_data; @@ -247,6 +249,14 @@ void liveupdate_flb_put_incoming(struct liveupdate_flb *flb); int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp); void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb); +/* kernel can internally retrieve files */ +int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token, + struct file **filep); + +/* Get a token for an outgoing file, or -ENOENT if file is not preserved */ +int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp); + #else /* CONFIG_LIVEUPDATE */ static inline bool liveupdate_enabled(void) @@ -299,5 +309,17 @@ static inline void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb) { } +static inline int liveupdate_get_file_incoming(struct liveupdate_session *s, + u64 token, struct file **filep) +{ + return -EOPNOTSUPP; +} + +static inline int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_LIVEUPDATE */ #endif /* _LINUX_LIVEUPDATE_H */ diff --git a/include/linux/memblock.h b/include/linux/memblock.h index 5afcd99aa8c1..d62db9e776cf 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn; * memory reservations yet, so we get scratch memory from the previous * kernel that we know is good to use. It is the only memory that * allocations may happen from in this phase. + * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages */ enum memblock_flags { MEMBLOCK_NONE = 0x0, /* No special request */ @@ -61,6 +62,7 @@ enum memblock_flags { MEMBLOCK_RSRV_NOINIT = 0x10, /* don't initialize struct pages */ MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */ MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */ + MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */ }; /** @@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align, void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid); +void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid); static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align) { @@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void) phys_addr_t memblock_phys_mem_size(void); phys_addr_t memblock_reserved_size(void); phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid); +phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid); unsigned long memblock_estimated_nr_free_pages(void); phys_addr_t memblock_start_of_DRAM(void); phys_addr_t memblock_end_of_DRAM(void); @@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { } #ifdef CONFIG_MEMBLOCK_KHO_SCRATCH void memblock_set_kho_scratch_only(void); void memblock_clear_kho_scratch_only(void); -bool memblock_is_kho_scratch_memory(phys_addr_t addr); - -static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, - enum migratetype mt) -{ - if (memblock_is_kho_scratch_memory(PFN_PHYS(pfn))) - return MIGRATE_CMA; - return mt; -} #else static inline void memblock_set_kho_scratch_only(void) { } static inline void memblock_clear_kho_scratch_only(void) { } -static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr) -{ - return false; -} - -static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, - enum migratetype mt) -{ - return mt; -} #endif #endif /* _LINUX_MEMBLOCK_H */ diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile index eec9d3ae07eb..d9f469462556 100644 --- a/kernel/liveupdate/Makefile +++ b/kernel/liveupdate/Makefile @@ -8,7 +8,6 @@ luo-y := \ luo_session.o obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o -obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS) += kexec_handover_debugfs.o obj-$(CONFIG_LIVEUPDATE) += luo.o diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 9260e601c61d..7c4d86daf86d 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -95,8 +95,25 @@ static struct kho_out kho_out = { }, }; +struct kho_in { + phys_addr_t fdt_phys; + phys_addr_t scratch_phys; + char previous_release[__NEW_UTS_LEN + 1]; + u32 kexec_count; + struct kho_debugfs dbg; + struct kho_radix_tree radix_tree; +}; + +static struct kho_in kho_in = { +}; + +static const void *kho_get_fdt(void) +{ + return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL; +} + /** - * kho_radix_encode_key - Encodes a physical address and order into a radix key. + * kho_encode_radix_key - Encodes a physical address and order into a radix key. * @phys: The physical address of the page. * @order: The order of the page. * @@ -106,35 +123,38 @@ static struct kho_out kho_out = { * * Return: The encoded unsigned long radix key. */ -static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order) +static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order) { - /* Order bits part */ - unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order); - /* Shifted physical address part */ - unsigned long l = phys >> (PAGE_SHIFT + order); + /* The physical address is encoded by shifting the PFN by its order. */ + unsigned long shift = PAGE_SHIFT + order; + /* Order bit goes right before the shifted PFN. */ + unsigned long h = 1UL << (64 - shift); + /* Shifted PFN. */ + unsigned long l = phys >> shift; return h | l; } /** - * kho_radix_decode_key - Decodes a radix key back into a physical address and order. + * kho_decode_radix_key - Decodes a radix key back into a physical address and order. * @key: The unsigned long key to decode. * @order: An output parameter, a pointer to an unsigned int where the decoded * page order will be stored. * - * This function reverses the encoding performed by kho_radix_encode_key(), + * This function reverses the encoding performed by kho_encode_radix_key(), * extracting the original physical address and page order from a given key. * * Return: The decoded physical address. */ -static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order) +static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order) { - unsigned int order_bit = fls64(key); + /* fls64() indexes starting from 1. */ + unsigned int order_bit = fls64(key) - 1; phys_addr_t phys; - /* order_bit is numbered starting at 1 from fls64 */ - *order = KHO_ORDER_0_LOG2 - order_bit + 1; - /* The order is discarded by the shift */ + /* order bit goes right before the shifted PFN. */ + *order = 64 - (PAGE_SHIFT + order_bit); + /* The order bit is discarded by the shift */ phys = key << (PAGE_SHIFT + *order); return phys; @@ -154,25 +174,47 @@ static unsigned long kho_radix_get_table_index(unsigned long key, return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2); } +static void __ref *kho_radix_alloc_node(void) +{ + struct kho_radix_node *node; + + if (slab_is_available()) + node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL); + else + node = memblock_alloc(PAGE_SIZE, PAGE_SIZE); + + return node; +} + +static void __ref kho_radix_free_node(struct kho_radix_node *node) +{ + if (slab_is_available()) + free_page((unsigned long)node); + else + memblock_free(node, PAGE_SIZE); +} + /** - * kho_radix_add_page - Marks a page as preserved in the radix tree. + * kho_radix_add_key - Add a key to the radix tree. * @tree: The KHO radix tree. - * @pfn: The page frame number of the page to preserve. - * @order: The order of the page. + * @key: The key to add. * - * This function traverses the radix tree based on the key derived from @pfn - * and @order. It sets the corresponding bit in the leaf bitmap to mark the - * page for preservation. If intermediate nodes do not exist along the path, - * they are allocated and added to the tree. + * This function traverses the radix tree based on the @key provided. It sets the + * corresponding bit in the leaf bitmap to mark the @key as present. If + * intermediate nodes do not exist along the path, they are allocated and added + * to the tree. + * + * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported. + * This limit only exists because current users of the radix tree don't use more + * than that. Changing the maximum width requires changing the tree depth, which + * needs bumping the ABI version. * * Return: 0 on success, or a negative error code on failure. */ -int kho_radix_add_page(struct kho_radix_tree *tree, - unsigned long pfn, unsigned int order) +int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key) { /* Newly allocated nodes for error cleanup */ struct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 }; - unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order); struct kho_radix_node *anchor_node = NULL; struct kho_radix_node *node = tree->root; struct kho_radix_node *new_node; @@ -183,6 +225,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree, if (WARN_ON_ONCE(!tree->root)) return -EINVAL; + if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH)) + return -ERANGE; + might_sleep(); guard(mutex)(&tree->lock); @@ -197,7 +242,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree, } /* Next node is empty, create a new node for it */ - new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL); + new_node = kho_radix_alloc_node(); if (!new_node) { err = -ENOMEM; goto err_free_nodes; @@ -228,29 +273,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree, err_free_nodes: for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) { if (intermediate_nodes[i]) - free_page((unsigned long)intermediate_nodes[i]); + kho_radix_free_node(intermediate_nodes[i]); } if (anchor_node) anchor_node->table[anchor_idx] = 0; return err; } -EXPORT_SYMBOL_GPL(kho_radix_add_page); +EXPORT_SYMBOL_GPL(kho_radix_add_key); /** - * kho_radix_del_page - Removes a page's preservation status from the radix tree. + * kho_radix_del_key - Removes the key from the radix tree. * @tree: The KHO radix tree. - * @pfn: The page frame number of the page to unpreserve. - * @order: The order of the page. + * @key: The key to remove. * * This function traverses the radix tree and clears the bit corresponding to - * the page, effectively removing its "preserved" status. It does not free - * the tree's intermediate nodes, even if they become empty. + * the @key, effectively removing it from the tree. It does not free the tree's + * intermediate nodes, even if they become empty. */ -void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn, - unsigned int order) +void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key) { - unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order); struct kho_radix_node *node = tree->root; struct kho_radix_leaf *leaf; unsigned int i, idx; @@ -258,6 +300,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn, if (WARN_ON_ONCE(!tree->root)) return; + /* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */ + if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH)) + return; + might_sleep(); guard(mutex)(&tree->lock); @@ -281,21 +327,85 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn, idx = kho_radix_get_bitmap_index(key); __clear_bit(idx, leaf->bitmap); } -EXPORT_SYMBOL_GPL(kho_radix_del_page); +EXPORT_SYMBOL_GPL(kho_radix_del_key); + +static void __kho_radix_destroy_tree(struct kho_radix_node *root, + unsigned int level) +{ + unsigned long i; + + if (level == 0) { + kho_radix_free_node(root); + return; + } + + for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) { + if (root->table[i]) + __kho_radix_destroy_tree(phys_to_virt(root->table[i]), + level - 1); + } + + kho_radix_free_node(root); +} + +/** + * kho_radix_init_tree - initialize the radix tree. + * @tree: the tree to initialize. + * @root: root table of the radix tree. + * + * Initialize the radix tree with the given root node. If root is %NULL, an + * empty root table is allocated. If root is not %NULL, it is the caller's + * responsibility to make sure the root is valid and in the correct format. + * + * Return: 0 on success, -errno on failure. + */ +int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root) +{ + if (!root) + root = kho_radix_alloc_node(); + if (!root) + return -ENOMEM; + + tree->root = root; + mutex_init(&tree->lock); + return 0; +} +EXPORT_SYMBOL_GPL(kho_radix_init_tree); + +/** + * kho_radix_destroy_tree - Destroy the radix tree + * @tree: The radix tree to destroy + * + * Walk @tree and free all its nodes. + */ +void kho_radix_destroy_tree(struct kho_radix_tree *tree) +{ + if (!tree->root) + return; + + __kho_radix_destroy_tree(tree->root, KHO_TREE_MAX_DEPTH - 1); + tree->root = NULL; +} +EXPORT_SYMBOL_GPL(kho_radix_destroy_tree); -static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, - unsigned long key, - kho_radix_tree_walk_callback_t cb) +static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key, + const struct kho_radix_walk_cb *cb, void *data) { unsigned long *bitmap = (unsigned long *)leaf; - unsigned int order; - phys_addr_t phys; unsigned int i; int err; + if (cb->node) { + err = cb->node(virt_to_phys(leaf), data); + if (err) + return err; + } + + if (!cb->leaf) + return 0; + for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) { - phys = kho_radix_decode_key(key | i, &order); - err = cb(phys, order); + err = cb->leaf(key | i, data); if (err) return err; } @@ -305,7 +415,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, static int __kho_radix_walk_tree(struct kho_radix_node *root, unsigned int level, unsigned long start, - kho_radix_tree_walk_callback_t cb) + const struct kho_radix_walk_cb *cb, void *data) { struct kho_radix_node *node; struct kho_radix_leaf *leaf; @@ -313,6 +423,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root, unsigned int shift; int err; + if (cb->node) { + err = cb->node(virt_to_phys(root), data); + if (err) + return err; + } + for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) { if (!root->table[i]) continue; @@ -329,10 +445,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root, * node is pointing to the level 0 bitmap. */ leaf = (struct kho_radix_leaf *)node; - err = kho_radix_walk_leaf(leaf, key, cb); + err = kho_radix_walk_leaf(leaf, key, cb, data); } else { err = __kho_radix_walk_tree(node, level - 1, - key, cb); + key, cb, data); } if (err) @@ -343,28 +459,27 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root, } /** - * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page. + * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key. * @tree: A pointer to the KHO radix tree to walk. - * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be - * invoked for each preserved page found in the tree. The callback receives - * the physical address and order of the preserved page. + * @cb: Set of callbacks to be invoked during the tree walk. + * @data: Opaque data pointer passed to each callback in @cb. * - * This function walks the radix tree, searching from the specified top level - * down to the lowest level (level 0). For each preserved page found, it invokes - * the provided callback, passing the page's physical address and order. + * This function walks the radix tree, searching from the top level down to the + * lowest level (level 0), invoking the appropriate callbacks. * * Return: 0 if the walk completed the specified tree, or the non-zero return * value from the callback that stopped the walk. */ int kho_radix_walk_tree(struct kho_radix_tree *tree, - kho_radix_tree_walk_callback_t cb) + const struct kho_radix_walk_cb *cb, void *data) { if (WARN_ON_ONCE(!tree->root)) return -EINVAL; guard(mutex)(&tree->lock); - return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb); + return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb, + data); } EXPORT_SYMBOL_GPL(kho_radix_walk_tree); @@ -495,13 +610,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys, return pfn_to_page(pfn); } -static int __init kho_preserved_memory_reserve(phys_addr_t phys, - unsigned int order) +static int __init kho_preserved_memory_reserve(unsigned long key, void *data) { union kho_page_info info; struct page *page; + unsigned int order; + phys_addr_t phys; u64 sz; + phys = kho_decode_radix_key(key, &order); + sz = 1UL << (order + PAGE_SHIFT); page = kho_get_preserved_page(phys, order); @@ -530,6 +648,13 @@ static phys_addr_t __init kho_get_mem_map_phys(const void *fdt) return get_unaligned((const u64 *)mem_ptr); } +static void __init *kho_get_mem_map(const void *fdt) +{ + phys_addr_t phys = kho_get_mem_map_phys(fdt); + + return phys ? phys_to_virt(phys) : NULL; +} + /* * With KHO enabled, memory can become fragmented because KHO regions may * be anywhere in physical address space. The scratch regions give us a @@ -630,19 +755,24 @@ early_param("kho_scratch", kho_parse_scratch_size); static void __init scratch_size_update(void) { /* - * If fixed sizes are not provided via command line, calculate them - * now. + * If fixed sizes are not provided via command line, calculate them now. + * Remove HugeTLB allocations from it because they never get allocated + * from scratch. */ if (scratch_scale) { phys_addr_t size; size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE); + size -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT, + NUMA_NO_NODE); size = size * scratch_scale / 100; scratch_size_lowmem = size; size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE); + size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE, + NUMA_NO_NODE); size = size * scratch_scale / 100 - scratch_size_lowmem; scratch_size_global = size; } @@ -662,6 +792,9 @@ static phys_addr_t __init scratch_size_node(int nid) if (scratch_scale) { size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, nid); + /* Do not count HugeTLB pages. */ + size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE, + nid); size = size * scratch_scale / 100; } else { size = scratch_size_pernode; @@ -670,6 +803,22 @@ static phys_addr_t __init scratch_size_node(int nid) return round_up(size, SCRATCH_ALIGNMENT_BYTES); } +bool kho_scratch_overlap(phys_addr_t phys, size_t size) +{ + phys_addr_t scratch_start, scratch_end; + unsigned int i; + + for (i = 0; i < kho_scratch_cnt; i++) { + scratch_start = kho_scratch[i].addr; + scratch_end = kho_scratch[i].addr + kho_scratch[i].size; + + if (phys < scratch_end && (phys + size) > scratch_start) + return true; + } + + return false; +} + /** * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec * @@ -757,6 +906,140 @@ err_disable_kho: kho_enable = false; } +/* + * Look for free blocks of 1G. This is a heuristic chosen to work efficiently + * with large systems with hundreds of gigabytes of memory. It will work poorly + * on smaller systems. The algorithm itself doesn't depend on the actual value, + * so it can be changed to a different heuristic later if needed. + */ +#define KHO_SCRATCH_EXT_BLKSIZE SZ_1G +#define KHO_SCRATCH_EXT_BLKSHIFT const_ilog2(KHO_SCRATCH_EXT_BLKSIZE) + +/* Called for the KHO preserved memory radix tree. */ +static int __init kho_ext_walk_leaf(unsigned long key, void *data) +{ + struct kho_radix_tree *busy_blocks = data; + phys_addr_t start, end; + unsigned int order; + int err; + + /* + * The key is from the KHO preserved memory radix tree. It is decoded to + * a physical address of a preservation and its order. + */ + start = kho_decode_radix_key(key, &order); + end = start + (1UL << (order + PAGE_SHIFT)); + + while (start < end) { + err = kho_radix_add_key(busy_blocks, start >> KHO_SCRATCH_EXT_BLKSHIFT); + if (err) + return err; + + start += (1UL << KHO_SCRATCH_EXT_BLKSHIFT); + } + + return 0; +} + +/* Called for the KHO preserved memory radix tree. */ +static int __init kho_ext_walk_node(phys_addr_t phys, void *data) +{ + struct kho_radix_tree *busy_blocks = data; + + return kho_radix_add_key(busy_blocks, phys >> KHO_SCRATCH_EXT_BLKSHIFT); +} + +/* Called for the busy block radix tree. */ +static int __init kho_ext_mark_scratch(unsigned long key, void *data) +{ + phys_addr_t *prev_end = data; + phys_addr_t start = key << KHO_SCRATCH_EXT_BLKSHIFT; + int err; + + if (start > *prev_end) { + err = memblock_mark_kho_scratch(*prev_end, start - *prev_end); + if (err) + return err; + } + + *prev_end = start + (1UL << KHO_SCRATCH_EXT_BLKSHIFT); + return 0; +} + +/* + * kho_extend_scratch - Extend the scratch regions + * + * The KHO preserved memory radix tree mixes both physical address and order + * into a single key. This makes it hard to look for free ranges directly. This + * function first walks the radix tree and digests it down into another radix + * tree, whose keys identify blocks of size KHO_SCRATCH_EXT_BLKSIZE which + * contain preserved memory. + * + * Then it walks the digested radix tree and marks everything that doesn't have + * preserved memory as scratch. + * + * NOTE: This function allocates memory so it should be called when scratch has + * available space. + * + * NOTE: The pages of the KHO preserved memory radix tree tables are not marked + * as preserved in the preserved memory tree. But they are expected to remain + * untouched until the tree is fully parsed. So this function also considers + * them to be "preserved memory" and marks their blocks as busy. + * + * NOTE: efi_init()::reserve_regions() removes all regions except + * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO + * scratch memory, so they should not be removed. This function should always be + * called after reserve_regions(). + */ +static void __init kho_extend_scratch(void) +{ + const struct kho_radix_walk_cb kho_cb = { + .leaf = kho_ext_walk_leaf, + .node = kho_ext_walk_node, + }; + const struct kho_radix_walk_cb ext_cb = { + .leaf = kho_ext_mark_scratch, + }; + static struct lock_class_key busy_radix_class; + struct kho_radix_tree busy_blocks; + phys_addr_t prev_end = 0; + int err = 0; + + err = kho_radix_init_tree(&busy_blocks, NULL); + if (err) + goto print; + + /* + * The walk of kho_in.radix_tree adds keys to busy_blocks. The walk + * takes the kho_in radix tree lock and adding the key takes busy_blocks + * lock. Since both are struct kho_radix_tree and share the same lock + * class, lockdep gets confused. Set a different class for + * busy_blocks.lock to make lockdep happy. + */ + lockdep_set_class(&busy_blocks.lock, &busy_radix_class); + + /* Walk the KHO radix tree to find busy blocks. */ + err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &busy_blocks); + if (err) + goto out; + + /* Walk the busy blocks and mark everything between keys as scratch. */ + err = kho_radix_walk_tree(&busy_blocks, &ext_cb, &prev_end); + if (err) + goto out; + + /* Mark everything from last busy block to end of DRAM. */ + if (prev_end < memblock_end_of_DRAM()) + err = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end); + + /* fallthrough */ +out: + kho_radix_destroy_tree(&busy_blocks); +print: + if (err) + pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err)); +} + /** * kho_add_subtree - record the physical address of a sub blob in KHO root tree. * @name: name of the sub tree. @@ -867,10 +1150,12 @@ int kho_preserve_folio(struct folio *folio) const unsigned long pfn = folio_pfn(folio); const unsigned int order = folio_order(folio); - if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order))) + if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) && + WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order))) return -EINVAL; - return kho_radix_add_page(tree, pfn, order); + return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), + order)); } EXPORT_SYMBOL_GPL(kho_preserve_folio); @@ -888,7 +1173,7 @@ void kho_unpreserve_folio(struct folio *folio) const unsigned long pfn = folio_pfn(folio); const unsigned int order = folio_order(folio); - kho_radix_del_page(tree, pfn, order); + kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order)); } EXPORT_SYMBOL_GPL(kho_unpreserve_folio); @@ -917,7 +1202,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree, while (pfn < end_pfn) { order = __kho_preserve_pages_order(pfn, end_pfn); - kho_radix_del_page(tree, pfn, order); + kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), + order)); pfn += 1 << order; } @@ -942,7 +1228,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages) unsigned long failed_pfn = 0; int err = 0; - if (WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT, + if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) && + WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT, nr_pages << PAGE_SHIFT))) { return -EINVAL; } @@ -950,7 +1237,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages) while (pfn < end_pfn) { unsigned int order = __kho_preserve_pages_order(pfn, end_pfn); - err = kho_radix_add_page(tree, pfn, order); + err = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), + order)); if (err) { failed_pfn = pfn; break; @@ -1326,22 +1614,6 @@ void kho_restore_free(void *mem) } EXPORT_SYMBOL_GPL(kho_restore_free); -struct kho_in { - phys_addr_t fdt_phys; - phys_addr_t scratch_phys; - char previous_release[__NEW_UTS_LEN + 1]; - u32 kexec_count; - struct kho_debugfs dbg; -}; - -static struct kho_in kho_in = { -}; - -static const void *kho_get_fdt(void) -{ - return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL; -} - /** * is_kho_boot - check if current kernel was booted via KHO-enabled * kexec @@ -1409,26 +1681,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size) } EXPORT_SYMBOL_GPL(kho_retrieve_subtree); -static int __init kho_mem_retrieve(const void *fdt) +static void __init kho_mem_retrieve(void) { - struct kho_radix_tree tree; - const phys_addr_t *mem; - int len; - - /* Retrieve the KHO radix tree from passed-in FDT. */ - mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len); + const struct kho_radix_walk_cb cb = { + .leaf = kho_preserved_memory_reserve, + }; - if (!mem || len != sizeof(*mem)) { - pr_err("failed to get preserved KHO memory tree\n"); - return -ENOENT; - } + if (kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL)) + goto err; - if (!*mem) - return -EINVAL; + return; - tree.root = phys_to_virt(*mem); - mutex_init(&tree.lock); - return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve); +err: + /* + * Failed to initialize preserved memory. Clear FDT and radix so KHO + * users don't treat it as a KHO boot. + */ + kho_in.fdt_phys = 0; + kho_in.radix_tree.root = NULL; } static __init int kho_out_fdt_setup(void) @@ -1554,16 +1824,14 @@ static __init int kho_init(void) if (!kho_enable) return 0; - tree->root = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!tree->root) { - err = -ENOMEM; + err = kho_radix_init_tree(tree, NULL); + if (err) goto err_free_scratch; - } kho_out.fdt = kho_alloc_preserve(PAGE_SIZE); if (IS_ERR(kho_out.fdt)) { err = PTR_ERR(kho_out.fdt); - goto err_free_kho_radix_tree_root; + goto err_free_kho_radix_tree; } err = kho_debugfs_init(); @@ -1614,9 +1882,8 @@ static __init int kho_init(void) err_free_fdt: kho_unpreserve_free(kho_out.fdt); -err_free_kho_radix_tree_root: - kfree(tree->root); - tree->root = NULL; +err_free_kho_radix_tree: + kho_radix_destroy_tree(tree); err_free_scratch: kho_out.fdt = NULL; for (int i = 0; i < kho_scratch_cnt; i++) { @@ -1630,16 +1897,52 @@ err_free_scratch: } fs_initcall(kho_init); -void __init kho_memory_init(void) +void __init kho_memory_init_early(void) { - if (kho_in.scratch_phys) { - kho_scratch = phys_to_virt(kho_in.scratch_phys); + const void *fdt = kho_get_fdt(); + void *mem_map; - if (kho_mem_retrieve(kho_get_fdt())) - kho_in.fdt_phys = 0; - } else { + if (!is_kho_boot()) + return; + + /* + * kho_get_mem_map() should always succeed. If it fails, kho_populate() + * catches that and never sets kho_in.scratch_phys, which stops memory + * retrieval. + */ + mem_map = kho_get_mem_map(fdt); + if (WARN_ON(!mem_map)) + goto err; + + /* + * kho_scratch_overlap() needs kho_scratch to be initialized. It + * is used by free_area_init() on KHO boots, so initialize it + * early. + */ + kho_scratch = phys_to_virt(kho_in.scratch_phys); + + if (kho_radix_init_tree(&kho_in.radix_tree, mem_map)) + goto err; + + kho_extend_scratch(); + + return; + +err: + /* + * Failed to initialize preserved memory radix tree. Clear FDT + * and scratch so KHO users don't treat it as a KHO boot. + */ + kho_in.fdt_phys = 0; + kho_in.scratch_phys = 0; +} + +void __init kho_memory_init(void) +{ + if (kho_in.scratch_phys) + kho_mem_retrieve(); + else kho_reserve_scratch(); - } } void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len, diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c deleted file mode 100644 index 6efb696f5426..000000000000 --- a/kernel/liveupdate/kexec_handover_debug.c +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * kexec_handover_debug.c - kexec handover optional debug functionality - * Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com> - */ - -#define pr_fmt(fmt) "KHO: " fmt - -#include "kexec_handover_internal.h" - -bool kho_scratch_overlap(phys_addr_t phys, size_t size) -{ - phys_addr_t scratch_start, scratch_end; - unsigned int i; - - for (i = 0; i < kho_scratch_cnt; i++) { - scratch_start = kho_scratch[i].addr; - scratch_end = kho_scratch[i].addr + kho_scratch[i].size; - - if (phys < scratch_end && (phys + size) > scratch_start) - return true; - } - - return false; -} diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h index 0399ff107775..805d2a76c388 100644 --- a/kernel/liveupdate/kexec_handover_internal.h +++ b/kernel/liveupdate/kexec_handover_internal.h @@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob) { } #endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */ -#ifdef CONFIG_KEXEC_HANDOVER_DEBUG -bool kho_scratch_overlap(phys_addr_t phys, size_t size); -#else -static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size) -{ - return false; -} -#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */ - #endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */ diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index c39f96961a85..dbae0715220b 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -284,6 +284,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) mutex_init(&luo_file->mutex); args.handler = fh; + args.session = luo_session_from_file_set(file_set); args.file = file; err = fh->ops->preserve(&args); if (err) @@ -341,6 +342,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set) struct luo_file, list); args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -374,6 +376,7 @@ static int luo_file_freeze_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -395,6 +398,7 @@ static void luo_file_unfreeze_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -587,6 +591,7 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token, } args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.serialized_data = luo_file->serialized_data; err = luo_file->fh->ops->retrieve(&args); if (err) { @@ -620,6 +625,7 @@ static int luo_file_can_finish_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.retrieve_status = luo_file->retrieve_status; @@ -637,6 +643,7 @@ static void luo_file_finish_one(struct luo_file_set *file_set, guard(mutex)(&luo_file->mutex); args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.retrieve_status = luo_file->retrieve_status; @@ -926,3 +933,65 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh) luo_flb_unregister_all(fh); list_del(&ACCESS_PRIVATE(fh, list)); } + +/** + * liveupdate_get_token_outgoing - Get the token for a preserved file. + * @s: The outgoing liveupdate session. + * @file: The file object to search for. + * @tokenp: Output parameter for the found token. + * + * Searches the list of preserved files in an outgoing session for a matching + * file object. If found, the corresponding user-provided token is returned. + * + * This function is intended for in-kernel callers that need to correlate a + * file with its liveupdate token. + * + * Context: It must be called with session mutex acquired. + * Return: 0 on success, -ENOENT if the file is not preserved in this session. + */ +int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp) +{ + struct luo_file_set *file_set = luo_file_set_from_session_locked(s); + struct luo_file *luo_file; + int err = -ENOENT; + + list_for_each_entry(luo_file, &file_set->files_list, list) { + if (luo_file->file == file) { + if (tokenp) + *tokenp = luo_file->token; + err = 0; + break; + } + } + + return err; +} +EXPORT_SYMBOL_GPL(liveupdate_get_token_outgoing); + +/** + * liveupdate_get_file_incoming - Retrieves a preserved file for in-kernel use. + * @s: The incoming liveupdate session (restored from the previous kernel). + * @token: The unique token identifying the file to retrieve. + * @filep: On success, this will be populated with a pointer to the retrieved + * 'struct file'. + * + * Provides a kernel-internal API for other subsystems to retrieve their + * preserved files after a live update. This function is a simple wrapper + * around luo_retrieve_file(), allowing callers to find a file by its token. + * + * The caller receives a new reference to the file and must call fput() when it + * is no longer needed. The file's lifetime is managed by LUO and any userspace + * file descriptors. + * + * Context: It must be called with session mutex acquired of a restored session. + * Return: 0 on success. Returns -ENOENT if no file with the matching token is + * found, or any other negative errno on failure. + */ +int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token, + struct file **filep) +{ + return luo_retrieve_file(luo_file_set_from_session_locked(s), + token, filep); +} +EXPORT_SYMBOL_GPL(liveupdate_get_file_incoming); diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h index 64879ffe7378..dac6644bfb18 100644 --- a/kernel/liveupdate/luo_internal.h +++ b/kernel/liveupdate/luo_internal.h @@ -77,6 +77,23 @@ struct luo_session { extern struct rw_semaphore luo_register_rwlock; +static inline struct liveupdate_session *luo_session_from_file_set(struct luo_file_set *file_set) +{ + struct luo_session *session; + + session = container_of(file_set, struct luo_session, file_set); + + return (struct liveupdate_session *)session; +} + +static inline struct luo_file_set *luo_file_set_from_session_locked(struct liveupdate_session *s) +{ + struct luo_session *session = (struct luo_session *)s; + + lockdep_assert_held(&session->mutex); + return &session->file_set; +} + int luo_session_create(const char *name, struct file **filep); int luo_session_retrieve(const char *name, struct file **filep); void __init luo_session_setup_outgoing(u64 *sessions_pa); diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 1878edafab23..4f6f58bf3db6 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3081,14 +3081,7 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact) if (hugetlb_early_cma(h)) return hugetlb_cma_alloc_bootmem(h, nid, node_exact); - if (node_exact) - return memblock_alloc_exact_nid_raw(huge_page_size(h), - huge_page_size(h), 0, - MEMBLOCK_ALLOC_ACCESSIBLE, nid); - - return memblock_alloc_try_nid_raw(huge_page_size(h), - huge_page_size(h), 0, - MEMBLOCK_ALLOC_ACCESSIBLE, nid); + return memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact); } void *__init arch_alloc_bootmem_huge_page(struct hstate *h, int nid) diff --git a/mm/memblock.c b/mm/memblock.c index 036316e9138b..9ce86349a29f 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -19,11 +19,9 @@ #include <linux/mutex.h> #include <linux/string_helpers.h> -#ifdef CONFIG_KEXEC_HANDOVER #include <linux/libfdt.h> #include <linux/kexec_handover.h> #include <linux/kho/abi/memblock.h> -#endif /* CONFIG_KEXEC_HANDOVER */ #include <asm/sections.h> #include <linux/io.h> @@ -1507,6 +1505,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size, return 0; } +static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size, + bool kmemleak_trace) +{ + /* + * Skip kmemleak for those places like kasan_init() and + * early_pgtable_alloc() due to high volume. + */ + if (kmemleak_trace) + /* + * Memblock allocated blocks are never reported as + * leaks. This is because many of these blocks are + * only referred via the physical address which is + * not looked up by kmemleak. + */ + kmemleak_alloc_phys(start, size, 0); + + /* + * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP, + * require memory to be accepted before it can be used by the + * guest. + * + * Accept the memory of the allocated buffer. + */ + accept_memory(start, size); +} + /** * memblock_alloc_range_nid - allocate boot memory block * @size: size of memory block to be allocated in bytes @@ -1581,28 +1605,7 @@ again: return 0; done: - /* - * Skip kmemleak for those places like kasan_init() and - * early_pgtable_alloc() due to high volume. - */ - if (end != MEMBLOCK_ALLOC_NOLEAKTRACE) - /* - * Memblock allocated blocks are never reported as - * leaks. This is because many of these blocks are - * only referred via the physical address which is - * not looked up by kmemleak. - */ - kmemleak_alloc_phys(found, size, 0); - - /* - * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP, - * require memory to be accepted before it can be used by the - * guest. - * - * Accept the memory of the allocated buffer. - */ - accept_memory(found, size); - + memblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE); return found; } @@ -1758,6 +1761,77 @@ void * __init memblock_alloc_try_nid_raw( } /** + * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages + * @size: size of the memory to be allocated in bytes + * @nid: nid of the free memory to find, %NUMA_NO_NODE for any node + * @exact_nid: only allocate from the specified nid. If %false, the specified + * nid is tried first, and then all nodes are tried as fallback. + * + * HugeTLB pages are always aligned by their size, so the alignment matches + * @size. Since the memory is for userspace, mirrored memory is not used. The + * memory is not zeroed. Does not panic if request cannot be satisfied. + * + * Return: + * Virtual address of allocated memory block on success, %NULL on failure. + */ +void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid) +{ + enum memblock_flags flags = choose_memblock_flags(); + phys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE; + + memblock_dbg("%s: %llu bytes, nid=%d, exact_nid=%d %pS\n", __func__, + (u64)size, nid, exact_nid, (void *)_RET_IP_); + + /* Don't waste mirrored memory on HugeTLB pages. */ + flags &= ~MEMBLOCK_MIRROR; +retry: + /* HugeTLB pages are always aligned by their size. */ + addr = memblock_find_in_range_node(size, size, start, end, nid, flags); + if (addr) + goto found; + + /* Try all nodes if allowed. */ + if (numa_valid_node(nid) && !exact_nid) { + nid = NUMA_NO_NODE; + /* + * If a previous candidate overlapped with KHO scratch, it would + * update start or end. Now that the search is opening to all + * nodes, reset them. + */ + start = 0; + end = MEMBLOCK_ALLOC_ACCESSIBLE; + + goto retry; + } + + /* Found nothing... :-( */ + return NULL; + +found: + /* + * HugeTLB pages can be preserved with KHO and no preserved memory can + * be in scratch. So retry if found address overlaps with scratch. + * + * Scratch areas are normally not very large, so this shouldn't take too + * many retries. + */ + if (kho_scratch_overlap(addr, size)) { + if (memblock_bottom_up()) + start = addr + size; + else + end = addr; + + goto retry; + } + + if (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB)) + return NULL; + + memblock_prep_allocation(addr, size, true); + return phys_to_virt(addr); +} + +/** * memblock_alloc_try_nid - allocate boot memory block * @size: size of memory block to be allocated in bytes * @align: alignment of the region and block's size @@ -1826,6 +1900,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void) return memblock.reserved.total_size; } +phys_addr_t __init_memblock memblock_reserved_hugetlb_size(phys_addr_t limit, int nid) +{ + struct memblock_region *r; + phys_addr_t total = 0; + + for_each_reserved_mem_region(r) { + phys_addr_t size = r->size; + + if (r->base > limit) + break; + + if (r->base + r->size > limit) + size = limit - r->base; + + if (nid == memblock_get_region_node(r) || !numa_valid_node(nid)) + if (r->flags & MEMBLOCK_RSRV_HUGETLB) + total += size; + } + + return total; +} + phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid) { struct memblock_region *r; @@ -2510,16 +2606,6 @@ __init void memblock_clear_kho_scratch_only(void) { kho_scratch_only = false; } - -bool __init_memblock memblock_is_kho_scratch_memory(phys_addr_t addr) -{ - int i = memblock_search(&memblock.memory, addr); - - if (i == -1) - return false; - - return memblock_is_kho_scratch(&memblock.memory.regions[i]); -} #endif #ifdef CONFIG_KEXEC_HANDOVER diff --git a/mm/mm_init.c b/mm/mm_init.c index d859058c1d14..1533aebafb68 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -684,7 +684,9 @@ static __meminit void pageblock_migratetype_init_range(unsigned long pfn, const unsigned long end = pfn + nr_pages; for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) { - init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false); + enum migratetype mt = kho_scratch_migratetype(pfn, migratetype); + + init_pageblock_migratetype(pfn_to_page(pfn), mt, false); if (!atomic && IS_ALIGNED(pfn, PAGES_PER_SECTION)) cond_resched(); } @@ -901,8 +903,9 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone * over the place during system boot. */ if (pageblock_aligned(pfn)) { - init_pageblock_migratetype(page, migratetype, - isolate_pageblock); + enum migratetype mt = kho_scratch_migratetype(pfn, migratetype); + + init_pageblock_migratetype(page, mt, isolate_pageblock); cond_resched(); } pfn++; @@ -912,8 +915,7 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone static void __init memmap_init_zone_range(struct zone *zone, unsigned long start_pfn, unsigned long end_pfn, - unsigned long *hole_pfn, - enum migratetype mt) + unsigned long *hole_pfn) { unsigned long zone_start_pfn = zone->zone_start_pfn; unsigned long zone_end_pfn = zone_start_pfn + zone->spanned_pages; @@ -926,7 +928,8 @@ static void __init memmap_init_zone_range(struct zone *zone, return; memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn, - zone_end_pfn, MEMINIT_EARLY, NULL, mt, false); + zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE, + false); if (*hole_pfn < start_pfn) init_unavailable_range(*hole_pfn, start_pfn, zone_id, nid); @@ -942,8 +945,6 @@ static void __init memmap_init(void) for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) { struct pglist_data *node = NODE_DATA(nid); - enum migratetype mt = - kho_scratch_migratetype(start_pfn, MIGRATE_MOVABLE); for (j = 0; j < MAX_NR_ZONES; j++) { struct zone *zone = node->node_zones + j; @@ -952,7 +953,7 @@ static void __init memmap_init(void) continue; memmap_init_zone_range(zone, start_pfn, end_pfn, - &hole_pfn, mt); + &hole_pfn); zone_id = j; } } @@ -1912,7 +1913,7 @@ unsigned long __init node_map_pfn_alignment(void) #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT static void __init deferred_free_pages(unsigned long pfn, - unsigned long nr_pages, enum migratetype mt) + unsigned long nr_pages) { struct page *page; unsigned long i; @@ -1920,7 +1921,7 @@ static void __init deferred_free_pages(unsigned long pfn, if (!nr_pages) return; - pageblock_migratetype_init_range(pfn, nr_pages, mt, true); + pageblock_migratetype_init_range(pfn, nr_pages, MIGRATE_MOVABLE, true); page = pfn_to_page(pfn); @@ -1990,8 +1991,6 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn, for_each_free_mem_range(i, nid, 0, &start, &end, NULL) { unsigned long spfn = PFN_UP(start); unsigned long epfn = PFN_DOWN(end); - enum migratetype mt = - kho_scratch_migratetype(spfn, MIGRATE_MOVABLE); if (spfn >= end_pfn) break; @@ -2004,7 +2003,7 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn, unsigned long chunk_end = min(mo_pfn, epfn); nr_pages += deferred_init_pages(zone, spfn, chunk_end); - deferred_free_pages(spfn, chunk_end - spfn, mt); + deferred_free_pages(spfn, chunk_end - spfn); spfn = chunk_end; @@ -2627,6 +2626,8 @@ void __init __weak mem_init(void) void __init mm_core_init_early(void) { + kho_memory_init_early(); + free_area_init(); hugetlb_cma_reserve(); diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h index 4168e646af09..f1974e009013 100644 --- a/tools/testing/memblock/internal.h +++ b/tools/testing/memblock/internal.h @@ -46,4 +46,9 @@ static inline bool __is_kernel(unsigned long addr) #define __SetPageReserved(p) ((void)(p)) +static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size) +{ + return false; +} + #endif diff --git a/tools/testing/memblock/linux/kexec_handover.h b/tools/testing/memblock/linux/kexec_handover.h new file mode 100644 index 000000000000..bdfd9445b1fd --- /dev/null +++ b/tools/testing/memblock/linux/kexec_handover.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LINUX_KEXEC_HANDOVER_H +#define LINUX_KEXEC_HANDOVER_H + +/* + * Header stub to avoid test build breakage; we don't need to actually implement + * any KHO functions as they are not used in the tests. + */ +#endif /* LINUX_KEXEC_HANDOVER_H */ diff --git a/tools/testing/memblock/linux/kho/abi/memblock.h b/tools/testing/memblock/linux/kho/abi/memblock.h new file mode 100644 index 000000000000..71cf6982a2b9 --- /dev/null +++ b/tools/testing/memblock/linux/kho/abi/memblock.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_KHO_ABI_MEMBLOCK_H +#define _LINUX_KHO_ABI_MEMBLOCK_H + +/* + * Header stub to avoid test build breakage; we don't need to actually define + * any ABI as they are not used in the tests. + */ +#endif /* _LINUX_KHO_ABI_MEMBLOCK_H */ diff --git a/tools/testing/memblock/linux/libfdt.h b/tools/testing/memblock/linux/libfdt.h new file mode 100644 index 000000000000..6137b788fe8d --- /dev/null +++ b/tools/testing/memblock/linux/libfdt.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INCLUDE_LIBFDT_H_ +#define _INCLUDE_LIBFDT_H_ + +/* + * Header stub to avoid test build breakage; we don't need to actually implement + * any FDT functions as they are not used in the tests. + */ +#endif /* _INCLUDE_LIBFDT_H_ */ |
