Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] kho: fix bootmem sizing
@ 2026-11-03 11:49 Mike Rapoport (Microsoft)
  2026-11-03 11:49 ` [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas Mike Rapoport (Microsoft)
  2026-11-03 11:49 ` [PATCH 2/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
  0 siblings, 2 replies; 8+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-11-03 11:49 UTC (permalink / raw)
  To: Mike Rapoport, Pasha Tatashin, Pratyush Yadav
  Cc: Alexander Graf, Andrew Morton, Chen Pei, George Guo, Kexin Liu,
	Sourabh Jain, kexec, linux-kernel, linux-mm

There were several reports about issues with KHO bootmem sizing.

The bootmem areas are sized from overlapping views of the memory reserved
in memblock, so on NUMA systems memory allocated on a node is accounted
twice and with CONFIG_NUMA=n the lowmem and the global areas are accounted
once again in the area of node 0. Besides, the lowmem and the global areas
are reserved even when they are empty.

The first patch skips empty bootmem areas and the second one makes the
lowmem, per-node and global areas cover separate parts of the reserved
memory.

---
Mike Rapoport (Microsoft) (2):
      kho: skip zero-sized lowmem and global bootmem areas
      kho: fix bootmem sizing

 kernel/liveupdate/kexec_handover.c | 142 +++++++++++++++++++------------------
 1 file changed, 72 insertions(+), 70 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261103-kho-bootmem-sizing-5d6aed47a243

--
Sincerely yours,
Mike.



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas
  2026-11-03 11:49 [PATCH 0/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
@ 2026-11-03 11:49 ` Mike Rapoport (Microsoft)
  2026-11-03 14:14   ` Pratyush Yadav
  2026-11-03 19:48   ` Pasha Tatashin
  2026-11-03 11:49 ` [PATCH 2/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
  1 sibling, 2 replies; 8+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-11-03 11:49 UTC (permalink / raw)
  To: Mike Rapoport, Pasha Tatashin, Pratyush Yadav
  Cc: Alexander Graf, Andrew Morton, Chen Pei, George Guo, Kexin Liu,
	Sourabh Jain, kexec, linux-kernel, linux-mm

The lowmem and the global bootmem areas can have zero size, for example
when none or all of the memory is below ARCH_LOW_ADDRESS_LIMIT, or when
either of those areas is explicitly set to zero with kho_bootmem=.

For a zero-sized allocation memblock returns an address without reserving
anything, and that empty area is recorded in kho_bootmem. If there is no
memory below ARCH_LOW_ADDRESS_LIMIT, allocation of the empty lowmem area
fails and KHO gets disabled.

Extract a helper that checks for zero size and allocates memory from
memblock, and use it to skip empty areas the same way as empty per-node
areas are skipped.

Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
Reported-by: Chen Pei <cp0613@linux.alibaba.com>
Closes: https://lore.kernel.org/all/20261026131851.9942-1-cp0613@linux.alibaba.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 kernel/liveupdate/kexec_handover.c | 61 ++++++++++++++++++--------------------
 1 file changed, 29 insertions(+), 32 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 6f1f55fe31819..f268c57413a7e 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -820,6 +820,27 @@ bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
 	return false;
 }
 
+static int __init kho_reserve_bootmem_area(int *idx, phys_addr_t size,
+					   phys_addr_t end, int nid)
+{
+	phys_addr_t addr;
+	int i = *idx;
+
+	if (!size)
+		return 0;
+
+	addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES, 0, end,
+					nid, true);
+	if (!addr)
+		return -ENOMEM;
+
+	kho_bootmem[i].addr = addr;
+	kho_bootmem[i].size = size;
+	*idx = i + 1;
+
+	return 0;
+}
+
 /**
  * kho_reserve_bootmem - Reserve a contiguous chunk of memory for kexec
  *
@@ -832,7 +853,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;
-	phys_addr_t addr, size;
+	phys_addr_t size;
 
 	if (!kho_enable)
 		return;
@@ -852,56 +873,32 @@ static void __init kho_reserve_bootmem(void)
 	 * reserve bootmem area in low memory for lowmem allocations in the
 	 * next kernel
 	 */
-	size = bootmem_size_lowmem;
-	addr = memblock_phys_alloc_range(size, BOOTMEM_ALIGNMENT_BYTES, 0,
-					 ARCH_LOW_ADDRESS_LIMIT);
-	if (!addr) {
+	if (kho_reserve_bootmem_area(&i, bootmem_size_lowmem,
+				     ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE)) {
 		pr_err("Failed to reserve lowmem bootmem\n");
 		goto err_free_bootmem_desc;
 	}
 
-	kho_bootmem[i].addr = addr;
-	kho_bootmem[i].size = size;
-	i++;
-
 	/* reserve large contiguous area for allocations without nid */
-	size = bootmem_size_global;
-	addr = memblock_phys_alloc(size, BOOTMEM_ALIGNMENT_BYTES);
-	if (!addr) {
+	if (kho_reserve_bootmem_area(&i, bootmem_size_global,
+				     MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE)) {
 		pr_err("Failed to reserve global bootmem\n");
 		goto err_free_bootmem_areas;
 	}
 
-	kho_bootmem[i].addr = addr;
-	kho_bootmem[i].size = size;
-	i++;
-
 	/*
 	 * Loop over nodes that have both memory and are online. Skip
 	 * memoryless nodes, as we can not allocate bootmem areas there.
 	 */
 	for_each_node_state(nid, N_MEMORY) {
-		size = bootmem_size_node(nid);
-		if (!size)
-			continue;
-
-		addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES,
-						0, MEMBLOCK_ALLOC_ACCESSIBLE,
-						nid, true);
-		if (!addr) {
+		if (kho_reserve_bootmem_area(&i, bootmem_size_node(nid),
+					     MEMBLOCK_ALLOC_ACCESSIBLE, nid)) {
 			pr_err("Failed to reserve nid %d bootmem\n", nid);
 			goto err_free_bootmem_areas;
 		}
-
-		kho_bootmem[i].addr = addr;
-		kho_bootmem[i].size = size;
-		i++;
 	}
 
-	/*
-	 * Nodes that needed no bootmem were skipped, so fewer areas than
-	 * initially estimated may have been reserved.
-	 */
+	/* Areas with zero size were skipped */
 	kho_bootmem_cnt = i;
 
 	return;

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] kho: fix bootmem sizing
  2026-11-03 11:49 [PATCH 0/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
  2026-11-03 11:49 ` [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas Mike Rapoport (Microsoft)
@ 2026-11-03 11:49 ` Mike Rapoport (Microsoft)
  2026-11-03 16:01   ` Pratyush Yadav
  1 sibling, 1 reply; 8+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-11-03 11:49 UTC (permalink / raw)
  To: Mike Rapoport, Pasha Tatashin, Pratyush Yadav
  Cc: Alexander Graf, Andrew Morton, Chen Pei, George Guo, Kexin Liu,
	Sourabh Jain, kexec, linux-kernel, linux-mm

Bootmem areas are sized from overlapping views of the memory reserved in
memblock:

* the global area is sized from all reserved memory, including memory
  allocated on specific nodes, so on NUMA systems everything allocated
  on a node is accounted in both the global area and the area of that
  node.
* per-node sizes are calculated after the lowmem and the global areas
  are reserved. With CONFIG_NUMA=n all memblock regions belong to node 0,
  so the lowmem and the global areas are accounted in the size of node 0
  area and scaled once again.

As a result, bootmem is about twice bigger than it should be on NUMA
systems and about four times bigger with CONFIG_NUMA=n. On small systems
reservation of the bootmem may fail and then KHO gets disabled.

Make the areas cover separate parts of the reserved memory: the lowmem
area covers allocations below ARCH_LOW_ADDRESS_LIMIT, per-node areas
cover allocations above it on each node and the global area covers
allocations that are not bound to any node. Calculate all sizes before
reserving any of the areas.

With CONFIG_NUMA=n the global area becomes empty and it is not reserved.

Bootmem sizes in MiB in x86 QEMU VMs, before -> after:

                     CONFIG_NUMA=y     CONFIG_NUMA=n
  1G,  1 node         176 ->   92       320 ->   80
  4G,  1 node         508 ->  332      1264 ->  316
  16G, 2 nodes       1268 ->  704      2944 ->  736
  32G, 4 nodes       2296 -> 1224      5196 -> 1300
  64G, 4 nodes       4344 -> 2248      9664 -> 2420

Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
Reported-by: Kexin Liu <liukexin@kylinos.cn>
Reported-by: George Guo <guodongtai@kylinos.cn>
Closes: https://lore.kernel.org/all/20260904025101.9959-1-dongtai.guo@linux.dev
Reported-by: Sourabh Jain <sourabhjain@linux.ibm.com>
Closes: https://lore.kernel.org/all/20260922131217.698809-1-sourabhjain@linux.ibm.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 kernel/liveupdate/kexec_handover.c | 83 ++++++++++++++++++++------------------
 1 file changed, 44 insertions(+), 39 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index f268c57413a7e..eaeb895bb7595 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -684,6 +684,7 @@ static unsigned int bootmem_scale __initdata = 200;
 static phys_addr_t bootmem_size_global __initdata;
 static phys_addr_t bootmem_size_pernode __initdata;
 static phys_addr_t bootmem_size_lowmem __initdata;
+static phys_addr_t bootmem_size_node[MAX_NUMNODES] __initdata;
 
 static int __init kho_parse_bootmem_size(char *p)
 {
@@ -753,29 +754,48 @@ static int __init kho_parse_bootmem_size(char *p)
 }
 early_param("kho_bootmem", kho_parse_bootmem_size);
 
+static phys_addr_t __init reserved_mem_size(phys_addr_t limit, int nid)
+{
+	return memblock_reserved_kern_size(limit, nid) -
+	       memblock_reserved_hugetlb_size(limit, nid);
+}
+
+static phys_addr_t __init bootmem_scale_size(phys_addr_t size)
+{
+	return size * bootmem_scale / 100;
+}
+
+/*
+ * The lowmem area covers allocations below ARCH_LOW_ADDRESS_LIMIT, per-node
+ * areas cover allocations above it on each node and the global area covers
+ * allocations that are not bound to any node.
+ *
+ * All sizes must be calculated before any area is reserved, otherwise the
+ * areas reserved first will be accounted in the sizes of the others.
+ */
 static void __init bootmem_size_update(void)
 {
-	/*
-	 * If fixed sizes are not provided via command line, calculate them now.
-	 * Remove HugeTLB allocations from it because they never get allocated
-	 * from bootmem.
-	 */
+	int nid;
+
 	if (bootmem_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 * bootmem_scale / 100;
-		bootmem_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 * bootmem_scale / 100 - bootmem_size_lowmem;
-		bootmem_size_global = size;
+		phys_addr_t lowmem, global, size;
+
+		lowmem = reserved_mem_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE);
+		global = reserved_mem_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE);
+		global -= lowmem;
+
+		for_each_node_state(nid, N_MEMORY) {
+			size = reserved_mem_size(MEMBLOCK_ALLOC_ANYWHERE, nid) -
+			       reserved_mem_size(ARCH_LOW_ADDRESS_LIMIT, nid);
+			bootmem_size_node[nid] = bootmem_scale_size(size);
+			global -= size;
+		}
+
+		bootmem_size_lowmem = bootmem_scale_size(lowmem);
+		bootmem_size_global = bootmem_scale_size(global);
+	} else {
+		for_each_node_state(nid, N_MEMORY)
+			bootmem_size_node[nid] = bootmem_size_pernode;
 	}
 
 	/*
@@ -784,24 +804,9 @@ static void __init bootmem_size_update(void)
 	 */
 	bootmem_size_lowmem = round_up(bootmem_size_lowmem, BOOTMEM_ALIGNMENT_BYTES);
 	bootmem_size_global = round_up(bootmem_size_global, BOOTMEM_ALIGNMENT_BYTES);
-}
-
-static phys_addr_t __init bootmem_size_node(int nid)
-{
-	phys_addr_t size;
-
-	if (bootmem_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 * bootmem_scale / 100;
-	} else {
-		size = bootmem_size_pernode;
-	}
-
-	return round_up(size, BOOTMEM_ALIGNMENT_BYTES);
+	for_each_node_state(nid, N_MEMORY)
+		bootmem_size_node[nid] = round_up(bootmem_size_node[nid],
+						  BOOTMEM_ALIGNMENT_BYTES);
 }
 
 bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
@@ -891,7 +896,7 @@ static void __init kho_reserve_bootmem(void)
 	 * memoryless nodes, as we can not allocate bootmem areas there.
 	 */
 	for_each_node_state(nid, N_MEMORY) {
-		if (kho_reserve_bootmem_area(&i, bootmem_size_node(nid),
+		if (kho_reserve_bootmem_area(&i, bootmem_size_node[nid],
 					     MEMBLOCK_ALLOC_ACCESSIBLE, nid)) {
 			pr_err("Failed to reserve nid %d bootmem\n", nid);
 			goto err_free_bootmem_areas;

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas
  2026-11-03 11:49 ` [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas Mike Rapoport (Microsoft)
@ 2026-11-03 14:14   ` Pratyush Yadav
  2026-11-03 18:32     ` Mike Rapoport
  2026-11-03 19:48   ` Pasha Tatashin
  1 sibling, 1 reply; 8+ messages in thread
From: Pratyush Yadav @ 2026-11-03 14:14 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft)
  Cc: Pasha Tatashin, Pratyush Yadav, Alexander Graf, Andrew Morton,
	Chen Pei, George Guo, Kexin Liu, Sourabh Jain, kexec,
	linux-kernel, linux-mm

Hi Mike,

On Tue, Nov 03 2026, Mike Rapoport (Microsoft) wrote:

> The lowmem and the global bootmem areas can have zero size, for example
> when none or all of the memory is below ARCH_LOW_ADDRESS_LIMIT, or when
> either of those areas is explicitly set to zero with kho_bootmem=.
>
> For a zero-sized allocation memblock returns an address without reserving
> anything, and that empty area is recorded in kho_bootmem. If there is no
> memory below ARCH_LOW_ADDRESS_LIMIT, allocation of the empty lowmem area
> fails and KHO gets disabled.
>
> Extract a helper that checks for zero size and allocates memory from
> memblock, and use it to skip empty areas the same way as empty per-node
> areas are skipped.
>
> Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
> Reported-by: Chen Pei <cp0613@linux.alibaba.com>
> Closes: https://lore.kernel.org/all/20261026131851.9942-1-cp0613@linux.alibaba.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Thanks for the patch! LGTM for the most part, one minor nitpick below.

> ---
>  kernel/liveupdate/kexec_handover.c | 61 ++++++++++++++++++--------------------
>  1 file changed, 29 insertions(+), 32 deletions(-)
>
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 6f1f55fe31819..f268c57413a7e 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -820,6 +820,27 @@ bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
>  	return false;
>  }
>  
> +static int __init kho_reserve_bootmem_area(int *idx, phys_addr_t size,
> +					   phys_addr_t end, int nid)
> +{
> +	phys_addr_t addr;
> +	int i = *idx;
> +
> +	if (!size)
> +		return 0;
> +
> +	addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES, 0, end,
> +					nid, true);
> +	if (!addr)
> +		return -ENOMEM;
> +
> +	kho_bootmem[i].addr = addr;
> +	kho_bootmem[i].size = size;
> +	*idx = i + 1;
> +
> +	return 0;

I reckon it will be cleaner if you update kho_bootmem_cnt here directly.
So you directly do:

	kho_bootmem[kho_bootmem_cnt].addr = addr;
	kho_bootmem[kho_bootmem_cnt].size = size;
	kho_bootmem_cnt++;

You won't have to pass around *idx and you won't need the final
kho_bootmem_cnt assignment.

You might also want to have a kho_bootmem_max_cnt and do some bounds
checking here.

What do you think?

> +}
> +
>  /**
>   * kho_reserve_bootmem - Reserve a contiguous chunk of memory for kexec
>   *
> @@ -832,7 +853,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;
> -	phys_addr_t addr, size;
> +	phys_addr_t size;
>  
>  	if (!kho_enable)
>  		return;
> @@ -852,56 +873,32 @@ static void __init kho_reserve_bootmem(void)
>  	 * reserve bootmem area in low memory for lowmem allocations in the
>  	 * next kernel
>  	 */
> -	size = bootmem_size_lowmem;
> -	addr = memblock_phys_alloc_range(size, BOOTMEM_ALIGNMENT_BYTES, 0,
> -					 ARCH_LOW_ADDRESS_LIMIT);
> -	if (!addr) {
> +	if (kho_reserve_bootmem_area(&i, bootmem_size_lowmem,
> +				     ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE)) {
>  		pr_err("Failed to reserve lowmem bootmem\n");
>  		goto err_free_bootmem_desc;
>  	}
>  
> -	kho_bootmem[i].addr = addr;
> -	kho_bootmem[i].size = size;
> -	i++;
> -
>  	/* reserve large contiguous area for allocations without nid */
> -	size = bootmem_size_global;
> -	addr = memblock_phys_alloc(size, BOOTMEM_ALIGNMENT_BYTES);
> -	if (!addr) {
> +	if (kho_reserve_bootmem_area(&i, bootmem_size_global,
> +				     MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE)) {
>  		pr_err("Failed to reserve global bootmem\n");
>  		goto err_free_bootmem_areas;
>  	}
>  
> -	kho_bootmem[i].addr = addr;
> -	kho_bootmem[i].size = size;
> -	i++;
> -
>  	/*
>  	 * Loop over nodes that have both memory and are online. Skip
>  	 * memoryless nodes, as we can not allocate bootmem areas there.
>  	 */
>  	for_each_node_state(nid, N_MEMORY) {
> -		size = bootmem_size_node(nid);
> -		if (!size)
> -			continue;
> -
> -		addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES,
> -						0, MEMBLOCK_ALLOC_ACCESSIBLE,
> -						nid, true);
> -		if (!addr) {
> +		if (kho_reserve_bootmem_area(&i, bootmem_size_node(nid),
> +					     MEMBLOCK_ALLOC_ACCESSIBLE, nid)) {
>  			pr_err("Failed to reserve nid %d bootmem\n", nid);
>  			goto err_free_bootmem_areas;
>  		}
> -
> -		kho_bootmem[i].addr = addr;
> -		kho_bootmem[i].size = size;
> -		i++;
>  	}
>  
> -	/*
> -	 * Nodes that needed no bootmem were skipped, so fewer areas than
> -	 * initially estimated may have been reserved.
> -	 */
> +	/* Areas with zero size were skipped */
>  	kho_bootmem_cnt = i;
>  
>  	return;

-- 
Regards,
Pratyush Yadav


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] kho: fix bootmem sizing
  2026-11-03 11:49 ` [PATCH 2/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
@ 2026-11-03 16:01   ` Pratyush Yadav
  2026-11-03 18:36     ` Mike Rapoport
  0 siblings, 1 reply; 8+ messages in thread
From: Pratyush Yadav @ 2026-11-03 16:01 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft)
  Cc: Pasha Tatashin, Pratyush Yadav, Alexander Graf, Andrew Morton,
	Chen Pei, George Guo, Kexin Liu, Sourabh Jain, kexec,
	linux-kernel, linux-mm

Hi Mike,

On Tue, Nov 03 2026, Mike Rapoport (Microsoft) wrote:

> Bootmem areas are sized from overlapping views of the memory reserved in
> memblock:
>
> * the global area is sized from all reserved memory, including memory
>   allocated on specific nodes, so on NUMA systems everything allocated
>   on a node is accounted in both the global area and the area of that
>   node.
> * per-node sizes are calculated after the lowmem and the global areas
>   are reserved. With CONFIG_NUMA=n all memblock regions belong to node 0,
>   so the lowmem and the global areas are accounted in the size of node 0
>   area and scaled once again.
>
> As a result, bootmem is about twice bigger than it should be on NUMA
> systems and about four times bigger with CONFIG_NUMA=n. On small systems
> reservation of the bootmem may fail and then KHO gets disabled.
>
> Make the areas cover separate parts of the reserved memory: the lowmem
> area covers allocations below ARCH_LOW_ADDRESS_LIMIT, per-node areas
> cover allocations above it on each node and the global area covers
> allocations that are not bound to any node. Calculate all sizes before
> reserving any of the areas.
>
> With CONFIG_NUMA=n the global area becomes empty and it is not reserved.
>
> Bootmem sizes in MiB in x86 QEMU VMs, before -> after:
>
>                      CONFIG_NUMA=y     CONFIG_NUMA=n
>   1G,  1 node         176 ->   92       320 ->   80
>   4G,  1 node         508 ->  332      1264 ->  316
>   16G, 2 nodes       1268 ->  704      2944 ->  736
>   32G, 4 nodes       2296 -> 1224      5196 -> 1300
>   64G, 4 nodes       4344 -> 2248      9664 -> 2420
>
> Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
> Reported-by: Kexin Liu <liukexin@kylinos.cn>
> Reported-by: George Guo <guodongtai@kylinos.cn>
> Closes: https://lore.kernel.org/all/20260904025101.9959-1-dongtai.guo@linux.dev
> Reported-by: Sourabh Jain <sourabhjain@linux.ibm.com>
> Closes: https://lore.kernel.org/all/20260922131217.698809-1-sourabhjain@linux.ibm.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Thanks for the patch! My LLM caught an interesting problems that is
worth considering.

On arm64, ARCH_LOW_ADDRESS_LIMIT is arm64_dma_phys_limit - 1, and
dma_limits_init() sets the default value to PHYS_MASK + 1, and it can
also be memblock_end_of_dram() in some cases. So on arm64, it is
possible for lowmem to span the entire RAM.

Currently, that results in over-allocation of bootmem, and it ends up
being 4x what it should be. While obviously problematic, it should still
work. With this patch, all memory is in lowmem, and nothing ever goes to
per-node bootmem (except for the node that hosts the lowmem area).

All this is from reading the code, I haven't tried anything out yet.

Not sure how to fix this though. Should we perhaps use
min(ARCH_LOW_ADDRESS_LIMIT, U32_MAX) for the lowmem limit?

Also, a couple nitpicks below.

> ---
>  kernel/liveupdate/kexec_handover.c | 83 ++++++++++++++++++++------------------
>  1 file changed, 44 insertions(+), 39 deletions(-)
>
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index f268c57413a7e..eaeb895bb7595 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -684,6 +684,7 @@ static unsigned int bootmem_scale __initdata = 200;
>  static phys_addr_t bootmem_size_global __initdata;
>  static phys_addr_t bootmem_size_pernode __initdata;
>  static phys_addr_t bootmem_size_lowmem __initdata;
> +static phys_addr_t bootmem_size_node[MAX_NUMNODES] __initdata;
>  
>  static int __init kho_parse_bootmem_size(char *p)
>  {
> @@ -753,29 +754,48 @@ static int __init kho_parse_bootmem_size(char *p)
>  }
>  early_param("kho_bootmem", kho_parse_bootmem_size);
>  
> +static phys_addr_t __init reserved_mem_size(phys_addr_t limit, int nid)
> +{
> +	return memblock_reserved_kern_size(limit, nid) -
> +	       memblock_reserved_hugetlb_size(limit, nid);
> +}
> +
> +static phys_addr_t __init bootmem_scale_size(phys_addr_t size)
> +{
> +	return size * bootmem_scale / 100;
> +}
> +
> +/*
> + * The lowmem area covers allocations below ARCH_LOW_ADDRESS_LIMIT, per-node
> + * areas cover allocations above it on each node and the global area covers
> + * allocations that are not bound to any node.
> + *
> + * All sizes must be calculated before any area is reserved, otherwise the
> + * areas reserved first will be accounted in the sizes of the others.
> + */
>  static void __init bootmem_size_update(void)
>  {
> -	/*
> -	 * If fixed sizes are not provided via command line, calculate them now.
> -	 * Remove HugeTLB allocations from it because they never get allocated
> -	 * from bootmem.
> -	 */
> +	int nid;
> +
>  	if (bootmem_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 * bootmem_scale / 100;
> -		bootmem_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 * bootmem_scale / 100 - bootmem_size_lowmem;
> -		bootmem_size_global = size;
> +		phys_addr_t lowmem, global, size;
> +
> +		lowmem = reserved_mem_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE);
> +		global = reserved_mem_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE);
> +		global -= lowmem;

Nit: Please add a short comment above this to explain why you remove
lowmem from global.

> +
> +		for_each_node_state(nid, N_MEMORY) {
> +			size = reserved_mem_size(MEMBLOCK_ALLOC_ANYWHERE, nid) -
> +			       reserved_mem_size(ARCH_LOW_ADDRESS_LIMIT, nid);
> +			bootmem_size_node[nid] = bootmem_scale_size(size);
> +			global -= size;

Nit: same here, a comment here would make it easier to understand.

> +		}
> +
> +		bootmem_size_lowmem = bootmem_scale_size(lowmem);
> +		bootmem_size_global = bootmem_scale_size(global);
> +	} else {
> +		for_each_node_state(nid, N_MEMORY)
> +			bootmem_size_node[nid] = bootmem_size_pernode;
>  	}
>  
>  	/*
> @@ -784,24 +804,9 @@ static void __init bootmem_size_update(void)
>  	 */
>  	bootmem_size_lowmem = round_up(bootmem_size_lowmem, BOOTMEM_ALIGNMENT_BYTES);
>  	bootmem_size_global = round_up(bootmem_size_global, BOOTMEM_ALIGNMENT_BYTES);
> -}
> -
> -static phys_addr_t __init bootmem_size_node(int nid)
> -{
> -	phys_addr_t size;
> -
> -	if (bootmem_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 * bootmem_scale / 100;
> -	} else {
> -		size = bootmem_size_pernode;
> -	}
> -
> -	return round_up(size, BOOTMEM_ALIGNMENT_BYTES);
> +	for_each_node_state(nid, N_MEMORY)
> +		bootmem_size_node[nid] = round_up(bootmem_size_node[nid],
> +						  BOOTMEM_ALIGNMENT_BYTES);
>  }
>  
>  bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
> @@ -891,7 +896,7 @@ static void __init kho_reserve_bootmem(void)
>  	 * memoryless nodes, as we can not allocate bootmem areas there.
>  	 */
>  	for_each_node_state(nid, N_MEMORY) {
> -		if (kho_reserve_bootmem_area(&i, bootmem_size_node(nid),
> +		if (kho_reserve_bootmem_area(&i, bootmem_size_node[nid],
>  					     MEMBLOCK_ALLOC_ACCESSIBLE, nid)) {
>  			pr_err("Failed to reserve nid %d bootmem\n", nid);
>  			goto err_free_bootmem_areas;

-- 
Regards,
Pratyush Yadav


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas
  2026-11-03 14:14   ` Pratyush Yadav
@ 2026-11-03 18:32     ` Mike Rapoport
  0 siblings, 0 replies; 8+ messages in thread
From: Mike Rapoport @ 2026-11-03 18:32 UTC (permalink / raw)
  To: Pratyush Yadav
  Cc: Pasha Tatashin, Alexander Graf, Andrew Morton, Chen Pei,
	George Guo, Kexin Liu, Sourabh Jain, kexec, linux-kernel,
	linux-mm

On Tue, Nov 03, 2026 at 03:14:36PM +0100, Pratyush Yadav wrote:
> Hi Mike,
> 
> On Tue, Nov 03 2026, Mike Rapoport (Microsoft) wrote:
> 
> > The lowmem and the global bootmem areas can have zero size, for example
> > when none or all of the memory is below ARCH_LOW_ADDRESS_LIMIT, or when
> > either of those areas is explicitly set to zero with kho_bootmem=.
> >
> > For a zero-sized allocation memblock returns an address without reserving
> > anything, and that empty area is recorded in kho_bootmem. If there is no
> > memory below ARCH_LOW_ADDRESS_LIMIT, allocation of the empty lowmem area
> > fails and KHO gets disabled.
> >
> > Extract a helper that checks for zero size and allocates memory from
> > memblock, and use it to skip empty areas the same way as empty per-node
> > areas are skipped.
> >
> > Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
> > Reported-by: Chen Pei <cp0613@linux.alibaba.com>
> > Closes: https://lore.kernel.org/all/20261026131851.9942-1-cp0613@linux.alibaba.com
> > Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> 
> Thanks for the patch! LGTM for the most part, one minor nitpick below.
> 
> > ---
> >  kernel/liveupdate/kexec_handover.c | 61 ++++++++++++++++++--------------------
> >  1 file changed, 29 insertions(+), 32 deletions(-)
> >
> > diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> > index 6f1f55fe31819..f268c57413a7e 100644
> > --- a/kernel/liveupdate/kexec_handover.c
> > +++ b/kernel/liveupdate/kexec_handover.c
> > @@ -820,6 +820,27 @@ bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
> >  	return false;
> >  }
> >  
> > +static int __init kho_reserve_bootmem_area(int *idx, phys_addr_t size,
> > +					   phys_addr_t end, int nid)
> > +{
> > +	phys_addr_t addr;
> > +	int i = *idx;
> > +
> > +	if (!size)
> > +		return 0;
> > +
> > +	addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES, 0, end,
> > +					nid, true);
> > +	if (!addr)
> > +		return -ENOMEM;
> > +
> > +	kho_bootmem[i].addr = addr;
> > +	kho_bootmem[i].size = size;
> > +	*idx = i + 1;
> > +
> > +	return 0;
> 
> I reckon it will be cleaner if you update kho_bootmem_cnt here directly.
> So you directly do:
> 
> 	kho_bootmem[kho_bootmem_cnt].addr = addr;
> 	kho_bootmem[kho_bootmem_cnt].size = size;
> 	kho_bootmem_cnt++;
> 
> You won't have to pass around *idx and you won't need the final
> kho_bootmem_cnt assignment.

Ack.
 
> You might also want to have a kho_bootmem_max_cnt and do some bounds
> checking here.

Hmm, feels like over protecting. And if future patches will miss it AI will
yell anyway :)
 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] kho: fix bootmem sizing
  2026-11-03 16:01   ` Pratyush Yadav
@ 2026-11-03 18:36     ` Mike Rapoport
  0 siblings, 0 replies; 8+ messages in thread
From: Mike Rapoport @ 2026-11-03 18:36 UTC (permalink / raw)
  To: Pratyush Yadav
  Cc: Pasha Tatashin, Alexander Graf, Andrew Morton, Chen Pei,
	George Guo, Kexin Liu, Sourabh Jain, kexec, linux-kernel,
	linux-mm

On Tue, Nov 03, 2026 at 05:01:08PM +0100, Pratyush Yadav wrote:
> Hi Mike,
> 
> On Tue, Nov 03 2026, Mike Rapoport (Microsoft) wrote:
> 
> > Bootmem areas are sized from overlapping views of the memory reserved in
> > memblock:
> >
> > * the global area is sized from all reserved memory, including memory
> >   allocated on specific nodes, so on NUMA systems everything allocated
> >   on a node is accounted in both the global area and the area of that
> >   node.
> > * per-node sizes are calculated after the lowmem and the global areas
> >   are reserved. With CONFIG_NUMA=n all memblock regions belong to node 0,
> >   so the lowmem and the global areas are accounted in the size of node 0
> >   area and scaled once again.
> >
> > As a result, bootmem is about twice bigger than it should be on NUMA
> > systems and about four times bigger with CONFIG_NUMA=n. On small systems
> > reservation of the bootmem may fail and then KHO gets disabled.
> >
> > Make the areas cover separate parts of the reserved memory: the lowmem
> > area covers allocations below ARCH_LOW_ADDRESS_LIMIT, per-node areas
> > cover allocations above it on each node and the global area covers
> > allocations that are not bound to any node. Calculate all sizes before
> > reserving any of the areas.
> >
> > With CONFIG_NUMA=n the global area becomes empty and it is not reserved.
> >
> > Bootmem sizes in MiB in x86 QEMU VMs, before -> after:
> >
> >                      CONFIG_NUMA=y     CONFIG_NUMA=n
> >   1G,  1 node         176 ->   92       320 ->   80
> >   4G,  1 node         508 ->  332      1264 ->  316
> >   16G, 2 nodes       1268 ->  704      2944 ->  736
> >   32G, 4 nodes       2296 -> 1224      5196 -> 1300
> >   64G, 4 nodes       4344 -> 2248      9664 -> 2420
> >
> > Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
> > Reported-by: Kexin Liu <liukexin@kylinos.cn>
> > Reported-by: George Guo <guodongtai@kylinos.cn>
> > Closes: https://lore.kernel.org/all/20260904025101.9959-1-dongtai.guo@linux.dev
> > Reported-by: Sourabh Jain <sourabhjain@linux.ibm.com>
> > Closes: https://lore.kernel.org/all/20260922131217.698809-1-sourabhjain@linux.ibm.com
> > Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> 
> Thanks for the patch! My LLM caught an interesting problems that is
> worth considering.
> 
> On arm64, ARCH_LOW_ADDRESS_LIMIT is arm64_dma_phys_limit - 1, and
> dma_limits_init() sets the default value to PHYS_MASK + 1, and it can
> also be memblock_end_of_dram() in some cases. So on arm64, it is
> possible for lowmem to span the entire RAM.
> 
> Currently, that results in over-allocation of bootmem, and it ends up
> being 4x what it should be. While obviously problematic, it should still
> work. With this patch, all memory is in lowmem, and nothing ever goes to
> per-node bootmem (except for the node that hosts the lowmem area).
> 
> All this is from reading the code, I haven't tried anything out yet.
> 
> Not sure how to fix this though. Should we perhaps use
> min(ARCH_LOW_ADDRESS_LIMIT, U32_MAX) for the lowmem limit?

Argh :)

Maybe we reset lowmem to zero if memblock_end_of_DRAM() <
ARCH_LOW_ADDRESS_LIMIT?
 
> Also, a couple nitpicks below.
> 
> > ---
> >  kernel/liveupdate/kexec_handover.c | 83 ++++++++++++++++++++------------------
> >  1 file changed, 44 insertions(+), 39 deletions(-)
> >
> > +
> > +		lowmem = reserved_mem_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE);
> > +		global = reserved_mem_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE);
> > +		global -= lowmem;
> 
> Nit: Please add a short comment above this to explain why you remove
> lowmem from global.

I prefer to extend the comment above the function to say that calculations
for global and node that includes lowmem remove that.
 
> -- 
> Regards,
> Pratyush Yadav

-- 
Sincerely yours,
Mike.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas
  2026-11-03 11:49 ` [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas Mike Rapoport (Microsoft)
  2026-11-03 14:14   ` Pratyush Yadav
@ 2026-11-03 19:48   ` Pasha Tatashin
  1 sibling, 0 replies; 8+ messages in thread
From: Pasha Tatashin @ 2026-11-03 19:48 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft)
  Cc: Pasha Tatashin, Pratyush Yadav, Alexander Graf, Andrew Morton,
	Chen Pei, George Guo, Kexin Liu, Sourabh Jain, kexec,
	linux-kernel, linux-mm

On 11-03 13:49, Mike Rapoport (Microsoft) wrote:
> The lowmem and the global bootmem areas can have zero size, for example
> when none or all of the memory is below ARCH_LOW_ADDRESS_LIMIT, or when
> either of those areas is explicitly set to zero with kho_bootmem=.
> 
> For a zero-sized allocation memblock returns an address without reserving
> anything, and that empty area is recorded in kho_bootmem. If there is no
> memory below ARCH_LOW_ADDRESS_LIMIT, allocation of the empty lowmem area
> fails and KHO gets disabled.
> 
> Extract a helper that checks for zero size and allocates memory from
> memblock, and use it to skip empty areas the same way as empty per-node
> areas are skipped.
> 
> Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
> Reported-by: Chen Pei <cp0613@linux.alibaba.com>
> Closes: https://lore.kernel.org/all/20261026131851.9942-1-cp0613@linux.alibaba.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>  kernel/liveupdate/kexec_handover.c | 61 ++++++++++++++++++--------------------
>  1 file changed, 29 insertions(+), 32 deletions(-)
> 
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 6f1f55fe31819..f268c57413a7e 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -820,6 +820,27 @@ bool kho_bootmem_overlap(phys_addr_t phys, size_t size)
>  	return false;
>  }
>  
> +static int __init kho_reserve_bootmem_area(int *idx, phys_addr_t size,
> +					   phys_addr_t end, int nid)
> +{
> +	phys_addr_t addr;
> +	int i = *idx;
> +
> +	if (!size)
> +		return 0;
> +
> +	addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES, 0, end,
> +					nid, true);
> +	if (!addr)
> +		return -ENOMEM;
> +
> +	kho_bootmem[i].addr = addr;
> +	kho_bootmem[i].size = size;
> +	*idx = i + 1;
> +
> +	return 0;
> +}
> +
>  /**
>   * kho_reserve_bootmem - Reserve a contiguous chunk of memory for kexec
>   *
> @@ -832,7 +853,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;
> -	phys_addr_t addr, size;
> +	phys_addr_t size;
>  
>  	if (!kho_enable)
>  		return;
> @@ -852,56 +873,32 @@ static void __init kho_reserve_bootmem(void)
>  	 * reserve bootmem area in low memory for lowmem allocations in the
>  	 * next kernel
>  	 */
> -	size = bootmem_size_lowmem;
> -	addr = memblock_phys_alloc_range(size, BOOTMEM_ALIGNMENT_BYTES, 0,
> -					 ARCH_LOW_ADDRESS_LIMIT);
> -	if (!addr) {
> +	if (kho_reserve_bootmem_area(&i, bootmem_size_lowmem,
> +				     ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE)) {
>  		pr_err("Failed to reserve lowmem bootmem\n");

You are adding a helper that returns an error code and then ignoring it 
at all call sites. Even though it can only return -ENOMEM right now, 
dropping the return value is not good practice.

Either capture and print the error in pr_err(), or even better have 
kho_reserve_bootmem_area() return bool and move the pr_err() 
inside it, using nid != NUMA_NO_NODE for per-node areas and a name 
argument for "lowmem"/"global".


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-11-03 19:48 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 11:49 [PATCH 0/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
2026-11-03 11:49 ` [PATCH 1/2] kho: skip zero-sized lowmem and global bootmem areas Mike Rapoport (Microsoft)
2026-11-03 14:14   ` Pratyush Yadav
2026-11-03 18:32     ` Mike Rapoport
2026-11-03 19:48   ` Pasha Tatashin
2026-11-03 11:49 ` [PATCH 2/2] kho: fix bootmem sizing Mike Rapoport (Microsoft)
2026-11-03 16:01   ` Pratyush Yadav
2026-11-03 18:36     ` Mike Rapoport

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox