grub2/SOURCES/0580-kern-ieee1275-init-ppc...

211 lines
7.1 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Stefan Berger <stefanb@linux.ibm.com>
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 <stefanb@linux.ibm.com>
Cc: Hari Bathini <hbathini@linux.ibm.com>
Cc: Pavithra Prakash <pavrampu@in.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Carolyn Scherrer <cpscherr@us.ibm.com>
Cc: Mahesh Salgaonkar <mahesh@linux.ibm.com>
Cc: Sourabh Jain <sourabhjain@linux.ibm.com>
---
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 <http://www.gnu.org/licenses/>.
*/
+#include <stddef.h> /* offsetof() */
+
#include <grub/kernel.h>
#include <grub/dl.h>
#include <grub/disk.h>
@@ -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. */