[PATCH v1 4/8] s390/boot: Allow additional usable memory ranges

From: Mikhail Zaslonko

Date: Thu Nov 12 2026 - 06:41:21 EST


All online memory above the usable limit is left unused. A kdump kernel
will need a few ranges above that limit for the crashkernel CMA memory
of the crashed kernel.

Add physmem_add_usable_range() to register usable memory ranges. Clip
them to ident_map_size and merge overlapping or adjacent ranges.
for_each_physmem_usable_range() walks their intersection with the
online memory ranges.

Use the same ranges for boot allocations, identity mappings, KASAN
shadow and memblock. Keep the allocation position separate from the
usable ranges.

When no extra ranges are added, nothing changes.

Co-developed-by: Vasily Gorbik <gor@xxxxxxxxxxxxx>
Signed-off-by: Vasily Gorbik <gor@xxxxxxxxxxxxx>
Signed-off-by: Mikhail Zaslonko <zaslonko@xxxxxxxxxxxxx>
---
arch/s390/boot/boot.h | 3 +-
arch/s390/boot/kaslr.c | 4 +-
arch/s390/boot/physmem_info.c | 65 ++++++++++++++++++++++++----
arch/s390/boot/startup.c | 3 +-
arch/s390/boot/vmem.c | 7 +--
arch/s390/include/asm/physmem_info.h | 63 ++++++++++++++++++++-------
arch/s390/kernel/setup.c | 3 +-
7 files changed, 116 insertions(+), 32 deletions(-)

diff --git a/arch/s390/boot/boot.h b/arch/s390/boot/boot.h
index 61a205b489fb..39d5fc520c0c 100644
--- a/arch/s390/boot/boot.h
+++ b/arch/s390/boot/boot.h
@@ -44,7 +44,8 @@ struct vmlinux_info {
void startup_kernel(void);
unsigned long detect_max_physmem_end(void);
void detect_physmem_online_ranges(unsigned long max_physmem_end);
-void physmem_set_usable_limit(unsigned long limit);
+void physmem_set_alloc_limit(unsigned long limit);
+void physmem_add_usable_range(unsigned long start, unsigned long end);
void physmem_reserve(enum reserved_range_type type, unsigned long addr, unsigned long size);
void physmem_free(enum reserved_range_type type);
/* for continuous/multiple allocations per type */
diff --git a/arch/s390/boot/kaslr.c b/arch/s390/boot/kaslr.c
index 941f4c9e27cc..f2de79a69031 100644
--- a/arch/s390/boot/kaslr.c
+++ b/arch/s390/boot/kaslr.c
@@ -112,11 +112,11 @@ static unsigned long iterate_valid_positions(unsigned long size, unsigned long a
unsigned long start, end, tmp_end, range_pos, pos = 0;
struct reserved_range *res_end = res + res_count;
struct reserved_range *skip_res;
- int i;
+ struct physmem_iter iter;

align = max(align, 8UL);
_min = round_up(_min, align);
- for_each_physmem_usable_range(i, &start, &end) {
+ for_each_physmem_usable_range(iter, &start, &end) {
if (_min >= end)
continue;
start = round_up(start, align);
diff --git a/arch/s390/boot/physmem_info.c b/arch/s390/boot/physmem_info.c
index e5248b637326..c9888d1669e3 100644
--- a/arch/s390/boot/physmem_info.c
+++ b/arch/s390/boot/physmem_info.c
@@ -195,19 +195,44 @@ void detect_physmem_online_ranges(unsigned long max_physmem_end)
boot_debug(" online [%d]: 0x%016lx-0x%016lx\n", i, start, end);
}

-void physmem_set_usable_limit(unsigned long limit)
+void physmem_set_alloc_limit(unsigned long limit)
{
- physmem_info.usable = limit;
physmem_alloc_pos = limit;
- boot_debug("Usable memory limit: 0x%016lx\n", limit);
+ boot_debug("Upper alloc limit: 0x%016lx\n", limit);
+}
+
+/*
+ * Add usable memory ranges, merging overlapping or adjacent ranges.
+ * The kdump code passes CMA ranges sorted by start address.
+ */
+void physmem_add_usable_range(unsigned long start, unsigned long end)
+{
+ struct physmem_range *range;
+ u32 n = physmem_info.usable_count;
+
+ boot_debug("Usable memory range: 0x%016lx-0x%016lx\n", start, end);
+ end = min(end, ident_map_size);
+ if (start >= end)
+ return;
+ if (n) {
+ range = &physmem_info.usable[n - 1];
+ if (start <= range->end) {
+ range->end = max_t(u64, range->end, end);
+ return;
+ }
+ }
+ range = &physmem_info.usable[n];
+ range->start = start;
+ range->end = end;
+ physmem_info.usable_count++;
}

static void die_oom(unsigned long size, unsigned long align, unsigned long min, unsigned long max)
{
unsigned long start, end, total_mem = 0, total_reserved_mem = 0;
struct reserved_range *range;
+ struct physmem_iter iter;
enum reserved_range_type t;
- int i;

boot_emerg("Linux version %s\n", kernel_version);
if (!is_prot_virt_guest() && early_command_line[0])
@@ -221,7 +246,7 @@ static void die_oom(unsigned long size, unsigned long align, unsigned long min,
}
boot_emerg("Usable online memory ranges (info source: %s [%d]):\n",
get_physmem_info_source(), physmem_info.info_source);
- for_each_physmem_usable_range(i, &start, &end) {
+ for_each_physmem_usable_range(iter, &start, &end) {
boot_emerg("%016lx %016lx\n", start, end);
total_mem += end - start;
}
@@ -269,6 +294,26 @@ static bool __physmem_alloc_intersects(unsigned long addr, unsigned long size,
return ipl_report_certs_intersects(addr, size, intersection_start);
}

+/* Find the highest usable part of an online range below max. */
+static int __get_physmem_alloc_range(u32 n, unsigned long max,
+ unsigned long *start, unsigned long *end)
+{
+ unsigned long online_start, online_end;
+ struct physmem_range *range;
+ u32 i = physmem_info.usable_count;
+
+ __get_physmem_online_range(n, &online_start, &online_end);
+ online_end = min(online_end, max);
+ while (i) {
+ range = &physmem_info.usable[--i];
+ *start = max_t(unsigned long, online_start, range->start);
+ *end = min_t(unsigned long, online_end, range->end);
+ if (*start < *end)
+ return 0;
+ }
+ return -1;
+}
+
static unsigned long __physmem_alloc_range(unsigned long size, unsigned long align,
unsigned long min, unsigned long max,
unsigned int from_ranges, unsigned int *ranges_left,
@@ -281,14 +326,18 @@ static unsigned long __physmem_alloc_range(unsigned long size, unsigned long ali

align = max(align, 8UL);
while (nranges) {
- __get_physmem_range(nranges - 1, &range_start, &range_end, false);
- pos = min(range_end, pos);
+ if (__get_physmem_alloc_range(nranges - 1, pos, &range_start, &range_end)) {
+ nranges--;
+ continue;
+ }
+ pos = range_end;

if (round_up(min, align) + size > pos)
break;
addr = round_down(pos - size, align);
if (range_start > addr) {
- nranges--;
+ /* A lower usable range may fit within the same online range. */
+ pos = range_start;
continue;
}
if (__physmem_alloc_intersects(addr, size, &intersection_start)) {
diff --git a/arch/s390/boot/startup.c b/arch/s390/boot/startup.c
index 5eb7a3e1f9f1..95543656d1b8 100644
--- a/arch/s390/boot/startup.c
+++ b/arch/s390/boot/startup.c
@@ -545,7 +545,8 @@ void startup_kernel(void)
setup_vmalloc_size();
asce_limit = setup_kernel_memory_layout(kernel_size);
/* got final ident_map_size, physmem allocations could be performed now */
- physmem_set_usable_limit(ident_map_size);
+ physmem_add_usable_range(0, ident_map_size);
+ physmem_set_alloc_limit(ident_map_size);
detect_physmem_online_ranges(max_physmem_end);
save_ipl_cert_comp_list();
rescue_initrd(safe_addr, ident_map_size);
diff --git a/arch/s390/boot/vmem.c b/arch/s390/boot/vmem.c
index ff6d58a476ba..a4fb8254f156 100644
--- a/arch/s390/boot/vmem.c
+++ b/arch/s390/boot/vmem.c
@@ -99,8 +99,8 @@ static void kasan_populate_shadow(unsigned long kernel_start, unsigned long kern
pud_t pud_z = __pud(__pa(kasan_early_shadow_pmd) | _REGION3_ENTRY);
p4d_t p4d_z = __p4d(__pa(kasan_early_shadow_pud) | _REGION2_ENTRY);
unsigned long memgap_start = 0;
+ struct physmem_iter iter;
unsigned long start, end;
- int i;

pte_z = __pte(__pa(kasan_early_shadow_page) | pgprot_val(PAGE_KERNEL_RO));
crst_table_init((unsigned long *)kasan_early_shadow_p4d, p4d_val(p4d_z));
@@ -112,7 +112,7 @@ static void kasan_populate_shadow(unsigned long kernel_start, unsigned long kern
__arch_set_page_dat(kasan_early_shadow_pmd, 1UL << CRST_ALLOC_ORDER);
__arch_set_page_dat(kasan_early_shadow_pte, 1);

- for_each_physmem_usable_range(i, &start, &end) {
+ for_each_physmem_usable_range(iter, &start, &end) {
kasan_populate((unsigned long)__identity_va(start),
(unsigned long)__identity_va(end),
POPULATE_KASAN_MAP_SHADOW);
@@ -473,6 +473,7 @@ void setup_vmem(unsigned long kernel_start, unsigned long kernel_end, unsigned l
unsigned long start, end;
unsigned long asce_type;
unsigned long asce_bits;
+ struct physmem_iter iter;
pgd_t *init_mm_pgd;
int i;

@@ -518,7 +519,7 @@ void setup_vmem(unsigned long kernel_start, unsigned long kernel_end, unsigned l
pgtable_populate(lowcore_address,
lowcore_address + sizeof(struct lowcore),
POPULATE_LOWCORE);
- for_each_physmem_usable_range(i, &start, &end) {
+ for_each_physmem_usable_range(iter, &start, &end) {
/* Do not map lowcore with identity mapping */
if (!start)
start = sizeof(struct lowcore);
diff --git a/arch/s390/include/asm/physmem_info.h b/arch/s390/include/asm/physmem_info.h
index 7ef3bbec98b0..c6f9f2f5d26b 100644
--- a/arch/s390/include/asm/physmem_info.h
+++ b/arch/s390/include/asm/physmem_info.h
@@ -2,8 +2,10 @@
#ifndef _ASM_S390_MEM_DETECT_H
#define _ASM_S390_MEM_DETECT_H

+#include <linux/minmax.h>
#include <linux/types.h>
#include <asm/page.h>
+#include <asm/setup.h>

enum physmem_info_source {
MEM_DETECT_NONE = 0,
@@ -47,21 +49,26 @@ struct reserved_range {
*/
#define MEM_INLINED_ENTRIES 255 /* (PAGE_SIZE - 16) / 16 */

+/* Primary memory and the handed-over crashkernel CMA ranges. */
+#define MEM_USABLE_ENTRIES (1 + CRASHK_CMA_ENTRIES)
+
struct physmem_info {
u32 range_count;
u8 info_source;
- unsigned long usable;
struct reserved_range reserved[RR_MAX];
struct physmem_range online[MEM_INLINED_ENTRIES];
struct physmem_range *online_extended;
+ /* Sorted usable ranges, intersected with online memory when iterating. */
+ u32 usable_count;
+ struct physmem_range usable[MEM_USABLE_ENTRIES];
};

extern struct physmem_info physmem_info;

void add_physmem_online_range(u64 start, u64 end);

-static inline int __get_physmem_range(u32 n, unsigned long *start,
- unsigned long *end, bool respect_usable_limit)
+static inline int __get_physmem_online_range(u32 n, unsigned long *start,
+ unsigned long *end)
{
if (n >= physmem_info.range_count) {
*start = 0;
@@ -77,29 +84,53 @@ static inline int __get_physmem_range(u32 n, unsigned long *start,
*end = (unsigned long)physmem_info.online_extended[n - MEM_INLINED_ENTRIES].end;
}

- if (respect_usable_limit && physmem_info.usable) {
- if (*start >= physmem_info.usable)
- return -1;
- if (*end > physmem_info.usable)
- *end = physmem_info.usable;
- }
return 0;
}

+struct physmem_iter {
+ u32 online;
+ u32 usable;
+};
+
+static inline int __next_physmem_usable_range(struct physmem_iter *iter,
+ unsigned long *start, unsigned long *end)
+{
+ unsigned long online_start, online_end, usable_start, usable_end;
+
+ while (iter->usable < physmem_info.usable_count) {
+ if (__get_physmem_online_range(iter->online, &online_start, &online_end))
+ break;
+ usable_start = physmem_info.usable[iter->usable].start;
+ usable_end = physmem_info.usable[iter->usable].end;
+ if (online_end <= usable_end)
+ iter->online++;
+ if (usable_end <= online_end)
+ iter->usable++;
+ *start = max(online_start, usable_start);
+ *end = min(online_end, usable_end);
+ if (*start < *end)
+ return 0;
+ }
+ *start = 0;
+ *end = 0;
+ return -1;
+}
+
/**
- * for_each_physmem_usable_range - early online memory range iterator
- * @i: an integer used as loop variable
+ * for_each_physmem_usable_range - early usable memory range iterator
+ * @iter: a struct physmem_iter used as loop cursor
* @p_start: ptr to unsigned long for start address of the range
* @p_end: ptr to unsigned long for end address of the range
*
- * Walks over detected online memory ranges below usable limit.
+ * Walks over the intersection of detected online memory and usable ranges.
*/
-#define for_each_physmem_usable_range(i, p_start, p_end) \
- for (i = 0; !__get_physmem_range(i, p_start, p_end, true); i++)
+#define for_each_physmem_usable_range(iter, p_start, p_end) \
+ for (iter = (struct physmem_iter) { 0 }; \
+ !__next_physmem_usable_range(&iter, p_start, p_end);)

-/* Walks over all detected online memory ranges disregarding usable limit. */
+/* Walks over all detected online memory ranges. */
#define for_each_physmem_online_range(i, p_start, p_end) \
- for (i = 0; !__get_physmem_range(i, p_start, p_end, false); i++)
+ for (i = 0; !__get_physmem_online_range(i, p_start, p_end); i++)

static inline const char *get_physmem_info_source(void)
{
diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c
index 3c1c66e6c281..9ec9dfc06761 100644
--- a/arch/s390/kernel/setup.c
+++ b/arch/s390/kernel/setup.c
@@ -712,6 +712,7 @@ static void __init free_physmem_info(void)

static void __init memblock_add_physmem_info(void)
{
+ struct physmem_iter iter;
unsigned long start, end;
int i;

@@ -719,7 +720,7 @@ static void __init memblock_add_physmem_info(void)
get_physmem_info_source(), physmem_info.info_source);
/* keep memblock lists close to the kernel */
memblock_set_bottom_up(true);
- for_each_physmem_usable_range(i, &start, &end)
+ for_each_physmem_usable_range(iter, &start, &end)
memblock_add(start, end - start);
for_each_physmem_online_range(i, &start, &end)
memblock_physmem_add(start, end - start);
--
2.55.0