From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 8 Aug 2023 12:21:55 -0400 Subject: [PATCH] kern/ieee1275/init: ppc64: Restrict high memory in presence of fadump This is a backport of the patch with the same name to grub 2.02. When a kernel dump is present then restrict the high memory regions to avoid allocating memory where the kernel dump resides. Use the ibm,kernel-dump node under /rtas to determine whether a kernel dump exists and up to which limit grub can use available memory. Set the upper_mem_limit to the size of the kernel dump section of type 'REAL_MODE_REGION' and therefore only allow grub's memory usage for high addresses from 768MB to 'upper_mem_limit'. This means that grub can use high memory in the range of 768MB to upper_mem_limit and the kernel-dump memory regions above 'upper_mem_limit' remain untouched. This change has no effect on memory allocations below 640MB. Also, fall back to allocating below 640MB in case the chunk of memory there would be larger than the chunk of memory above 768MB. This can for example occur if a free memory area is found starting at 300MB extending up to 1GB but a kernel dump is located at 768MB and therefore does not allow the allocation of the high memory area but requiring to use the chunk starting at 300MB to avoid an unnecessary out-of-memory condition. Signed-off-by: Stefan Berger Cc: Hari Bathini Cc: Pavithra Prakash Cc: Michael Ellerman Cc: Carolyn Scherrer Cc: Mahesh Salgaonkar Cc: Sourabh Jain --- grub-core/kern/ieee1275/init.c | 139 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/grub-core/kern/ieee1275/init.c b/grub-core/kern/ieee1275/init.c index 1fae84440403..31843ab70a62 100644 --- a/grub-core/kern/ieee1275/init.c +++ b/grub-core/kern/ieee1275/init.c @@ -17,6 +17,8 @@ * along with GRUB. If not, see . */ +#include /* offsetof() */ + #include #include #include @@ -180,6 +182,97 @@ grub_claim_heap (void) + GRUB_KERNEL_MACHINE_STACK_SIZE), 0x200000); } #else + +/* ibm,kernel-dump data structures */ +struct kd_section +{ + grub_uint32_t flags; + grub_uint16_t src_datatype; +#define KD_SRC_DATATYPE_REAL_MODE_REGION 0x0011 + grub_uint16_t error_flags; + grub_uint64_t src_address; + grub_uint64_t num_bytes; + grub_uint64_t act_bytes; + grub_uint64_t dst_address; +} GRUB_PACKED; + +#define MAX_KD_SECTIONS 10 + +struct kernel_dump +{ + grub_uint32_t format; + grub_uint16_t num_sections; + grub_uint16_t status_flags; + grub_uint32_t offset_1st_section; + grub_uint32_t num_blocks; + grub_uint64_t start_block; + grub_uint64_t num_blocks_avail; + grub_uint32_t offet_path_string; + grub_uint32_t max_time_allowed; + struct kd_section kds[MAX_KD_SECTIONS]; /* offset_1st_section should point to kds[0] */ +} GRUB_PACKED; + +/* + * Determine if a kernel dump exists and if it does, then determine the highest + * address that grub can use for memory allocations. + * The caller must have initialized *highest to ~0. *highest will not + * be modified if no kernel dump is found. + */ +static int +check_kernel_dump (grub_uint64_t *highest) +{ + struct kernel_dump kernel_dump; + grub_ssize_t kernel_dump_size; + grub_ieee1275_phandle_t rtas; + struct kd_section *kds; + grub_size_t i; + + /* If there's a kernel-dump it must have at least one section */ + if (grub_ieee1275_finddevice ("/rtas", &rtas) || + grub_ieee1275_get_property (rtas, "ibm,kernel-dump", &kernel_dump, + sizeof (kernel_dump), &kernel_dump_size) || + kernel_dump_size <= (grub_ssize_t) offsetof (struct kernel_dump, kds[1])) + return 0; + + kernel_dump_size = grub_min (kernel_dump_size, (grub_ssize_t) sizeof (kernel_dump)); + + if (grub_be_to_cpu32 (kernel_dump.format) != 1) + { + grub_printf (_("Error: ibm,kernel-dump has an unexpected format version '%u'\n"), + grub_be_to_cpu32 (kernel_dump.format)); + return 0; + } + + if (grub_be_to_cpu16 (kernel_dump.num_sections) > MAX_KD_SECTIONS) + { + grub_printf (_("Error: Too many kernel dump sections: %d\n"), + grub_be_to_cpu32 (kernel_dump.num_sections)); + return 0; + } + + for (i = 0; i < grub_be_to_cpu16 (kernel_dump.num_sections); i++) + { + kds = (struct kd_section *) ((grub_addr_t) &kernel_dump + + grub_be_to_cpu32 (kernel_dump.offset_1st_section) + + i * sizeof (struct kd_section)); + /* sanity check the address is within the 'kernel_dump' struct */ + if ((grub_addr_t) kds > (grub_addr_t) &kernel_dump + kernel_dump_size + sizeof (*kds)) + { + grub_printf (_("Error: 'kds' address beyond last available section\n")); + return 0; + } + + if ((grub_be_to_cpu16 (kds->src_datatype) == KD_SRC_DATATYPE_REAL_MODE_REGION) && + (grub_be_to_cpu64 (kds->src_address) == 0)) + { + *highest = grub_min (*highest, grub_be_to_cpu64 (kds->num_bytes)); + break; + } + } + + return 1; +} + /* Helper for grub_claim_heap on powerpc. */ static int heap_size (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type, @@ -207,7 +300,9 @@ static int heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type, void *data) { + grub_uint64_t upper_mem_limit = ~0; grub_uint32_t total = *(grub_uint32_t *)data; + int has_kernel_dump; if (type != GRUB_MEMORY_AVAILABLE) return 0; @@ -243,6 +338,50 @@ heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type, len = 0; } + has_kernel_dump = check_kernel_dump (&upper_mem_limit); + if (has_kernel_dump) + { + grub_uint64_t lo_len = 0, hi_len = 0; + + if (addr > upper_mem_limit || upper_mem_limit == (grub_uint64_t)~0) + return 0; + + /* limit len to stay below upper_mem_limit */ + if (addr < upper_mem_limit && (addr + len) > upper_mem_limit) + { + len = grub_min (len, upper_mem_limit - addr); + } + + /* We can allocate below 640MB or above 768MB. + * Choose the bigger chunk below 640MB or above 768MB. + */ + if (addr < 0x28000000) + { + lo_len = grub_min (len, 0x28000000 - addr); + } + if (addr + len > 0x30000000) + { + if (addr < 0x30000000) + hi_len = len - (0x30000000 - addr); + else + hi_len = len; + } + + if (hi_len > lo_len) + { + len = hi_len; + if (addr < 0x30000000) + addr = 0x30000000; + } + else + { + len = lo_len; + } + + if (len == 0) + return 0; + } + /* If this block contains 0x30000000 (768MB), do not claim below that. Linux likes to claim memory at min(RMO top, 768MB) and works down without reference to /memory/available. */