From: George Guo kho_reserve_bootmem() calculated the per-node bootmem sizes inside the allocation loop, after the lowmem and global areas had been allocated. Without CONFIG_NUMA, memblock_get_region_node() returns 0 for every region, and memblock_reserved_kern_size() attributes a region to a node by memblock_get_region_node(). Since allocations are marked MEMBLOCK_RSRV_KERN, the newly allocated lowmem and global areas enter node 0's baseline and are scaled again by bootmem_size_node(). In the original 1 GiB QEMU reproducer, the default 200% scale made node 0 request 672 MiB: twice the sum of the 98.45 MiB baseline and 224 MiB lowmem area, rounded up to 32 MiB alignment. The reservation failed and KHO disabled itself. With CONFIG_NUMA=y, node-specific accounting excludes the NUMA_NO_NODE reservations. The lowmem and global sizes are already calculated by bootmem_size_update() before any allocation. Calculate the per-node sizes into the kho_bootmem descriptors before reserving any bootmem area as well, and allocate the per-node areas from the stored sizes. Use separate read and write indices to preserve the saved sizes when zero-sized per-node areas are skipped. Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers") Reported-by: Kexin Liu Co-developed-by: Kexin Liu Signed-off-by: Kexin Liu Signed-off-by: George Guo --- Changes in v2: - Calculate every size before any allocation (Mike). The lowmem and global sizes are already calculated by bootmem_size_update(), so only the per-node sizes move out of the allocation loop. - Rebase onto liveupdate-7.4: follow the "scratch" to "bootmem" rename, and keep the zero-size node skip added by "kho: do not reserve empty per-node bootmem areas" by reading the saved sizes with a separate index. - Explain why only CONFIG_NUMA=n kernels are affected (Pratyush, Sourabh). - The global size over-allocation on NUMA systems, which I planned to look at as a follow-up, is handled by Sourabh's "kho: fix global scratch size calculation" [1]. Mike asked there for a broader rework of the sizing; this patch only fixes the CONFIG_NUMA=n double counting. - No Assisted-by tag, although I said on v1 that I would add one: the Assisted-by references have since been removed from the documentation in mainline. Tested with the KHO vmtest on a 1 GiB LoongArch QEMU guest, with the LoongArch KHO series on top: it passes with both CONFIG_NUMA=n and CONFIG_NUMA=y. Without this patch, KHO disables itself in the CONFIG_NUMA=n run. v1: https://lore.kernel.org/all/20260904025101.9959-1-dongtai.guo@linux.dev/ [1] https://lore.kernel.org/all/20260922131217.698809-1-sourabhjain@linux.ibm.com/ kernel/liveupdate/kexec_handover.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 6f1f55fe3181..9a53d7750367 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -831,7 +831,7 @@ bool kho_bootmem_overlap(phys_addr_t phys, size_t size) */ static void __init kho_reserve_bootmem(void) { - int nid, i = 0, max_nr_bootmem; + int nid, i, j, max_nr_bootmem; phys_addr_t addr, size; if (!kho_enable) @@ -848,6 +848,15 @@ static void __init kho_reserve_bootmem(void) goto err_disable_kho; } + /* + * Save the per-node sizes before allocating bootmem areas. Without + * CONFIG_NUMA, new lowmem and global allocations would otherwise be + * counted as node 0 kernel reservations and scaled again. + */ + j = 2; + for_each_node_state(nid, N_MEMORY) + kho_bootmem[j++].size = bootmem_size_node(nid); + /* * reserve bootmem area in low memory for lowmem allocations in the * next kernel @@ -860,6 +869,7 @@ static void __init kho_reserve_bootmem(void) goto err_free_bootmem_desc; } + i = 0; kho_bootmem[i].addr = addr; kho_bootmem[i].size = size; i++; @@ -879,9 +889,12 @@ static void __init kho_reserve_bootmem(void) /* * Loop over nodes that have both memory and are online. Skip * memoryless nodes, as we can not allocate bootmem areas there. + * Skipping zero-sized areas leaves i behind j, so writes to + * kho_bootmem[i] cannot overwrite unread sizes. */ + j = i; for_each_node_state(nid, N_MEMORY) { - size = bootmem_size_node(nid); + size = kho_bootmem[j++].size; if (!size) continue; base-commit: 9a6a70aefe77f6fb9ff2b3037dcf87f6dff65c8c -- 2.53.0