[PATCH v8 8/8] powerpc/kexec: add crash memory hotplug support
Sourabh Jain
sourabhjain at linux.ibm.com
Tue Jan 31 22:38:41 PST 2023
Extend PowerPC arch crash hotplug handler to support memory hotplug
events. Since elfcorehdr is used to exchange the memory info between the
kernels hence it needs to be recreated to reflect the changes due to
memory hotplug events.
The way memory hotplug events are handled on PowerPC and the notifier call
chain used in generic code to trigger the arch crash handler, the process
to recreate the elfcorehdr is different for memory add and remove events.
In the hot remove case, the memory region is first marked offline then the
notifier call chain is triggered (same is used to initiate arch crash
hotplug handler) and at last the memblock structure is updated. Whereas
in the hot add case, memblock structure is updated before the notifier
call chain is triggered.
On PowerPC, memblock structure is used to identify the memory ranges for
elfcorehdr. In case of memory hot remove the memblock structure is updated
after the arch crash hotplug handler is triggered, hence an additional
step is taken to ensure that memory ranges used to build elfcorehdr do
not include hot removed memory. Whereas no such extra steps are needed
for the hot add case because memblock structure is updated before the
arch crash hotplug handler is triggered.
The hot removed memory is explicitly removed from the memory ranges list
before creating elfcorehdr for the hot remove case.
To accommodate a growing number of memory regions, elfcorehdr is built
with additional buffer space to hold max memory regions.
The changes done here will also work for the kexec_load system call given
that the kexec tool builds the elfcoredhr with additional space to
accommodate future memory regions as it is done for kexec_file_load
system call.
Signed-off-by: Sourabh Jain <sourabhjain at linux.ibm.com>
---
arch/powerpc/include/asm/kexec_ranges.h | 1 +
arch/powerpc/kexec/core_64.c | 59 ++++++++++++++++++++++--
arch/powerpc/kexec/file_load_64.c | 36 ++++++++++++++-
arch/powerpc/kexec/ranges.c | 60 +++++++++++++++++++++++++
4 files changed, 151 insertions(+), 5 deletions(-)
diff --git a/arch/powerpc/include/asm/kexec_ranges.h b/arch/powerpc/include/asm/kexec_ranges.h
index f83866a19e870..802abf580cf0f 100644
--- a/arch/powerpc/include/asm/kexec_ranges.h
+++ b/arch/powerpc/include/asm/kexec_ranges.h
@@ -7,6 +7,7 @@
void sort_memory_ranges(struct crash_mem *mrngs, bool merge);
struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges);
int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
+int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size);
int add_tce_mem_ranges(struct crash_mem **mem_ranges);
int add_initrd_mem_range(struct crash_mem **mem_ranges);
#ifdef CONFIG_PPC_64S_HASH_MMU
diff --git a/arch/powerpc/kexec/core_64.c b/arch/powerpc/kexec/core_64.c
index 27a360120bc55..d877b8c2e7427 100644
--- a/arch/powerpc/kexec/core_64.c
+++ b/arch/powerpc/kexec/core_64.c
@@ -19,6 +19,7 @@
#include <linux/of.h>
#include <linux/libfdt.h>
#include <linux/memblock.h>
+#include <linux/memory.h>
#include <asm/page.h>
#include <asm/current.h>
@@ -616,6 +617,60 @@ int update_cpus_node(void *fdt)
}
#if defined(CONFIG_CRASH_HOTPLUG)
+int update_crash_elfcorehdr(struct kimage *image, unsigned int hp_action, void *arg)
+{
+ int ret;
+ struct crash_mem *cmem = NULL;
+ struct kexec_segment *ksegment;
+ unsigned long elfsz;
+ void *elfbuf = NULL;
+ void *mem;
+ unsigned long memsz;
+ char *ptr;
+ struct memory_notify *mn = (struct memory_notify *) arg;
+ unsigned long base_addr;
+ unsigned long size;
+
+ ksegment = &image->segment[image->elfcorehdr_index];
+ mem = (void *) ksegment->mem;
+ memsz = ksegment->memsz;
+
+ ret = get_crash_memory_ranges(&cmem);
+ if (ret) {
+ pr_err("crash hp: failed to get crash mem range\n");
+ return -1;
+ }
+
+ if (hp_action == KEXEC_CRASH_HP_REMOVE_MEMORY) {
+ base_addr = PFN_PHYS(mn->start_pfn);
+ size = mn->nr_pages * PAGE_SIZE;
+ ret = remove_mem_range(&cmem, base_addr, size);
+ if (ret)
+ return -1;
+ }
+
+ ret = crash_prepare_elf64_headers(image, cmem, false, &elfbuf, &elfsz);
+ if (ret) {
+ pr_err("crash hp: failed to prepare elf header\n");
+ return -1;
+ }
+
+ if (elfsz > memsz) {
+ pr_err("crash hp: updated crash elfcorehdr elfsz %lu > memsz %lu", elfsz, memsz);
+ return -1;
+ }
+
+ ptr = __va(mem);
+ if (ptr) {
+ xchg(&kexec_crash_image, NULL);
+ memcpy((void *)ptr, elfbuf, elfsz);
+ xchg(&kexec_crash_image, image);
+ }
+
+ vfree(elfbuf);
+ return 0;
+}
+
/**
* arch_crash_hotplug_handler() - Handle hotplug kexec segements changes FDT, elfcorehdr
* @image: the active struct kimage
@@ -633,9 +688,8 @@ void arch_crash_handle_hotplug_event(struct kimage *image, unsigned int hp_actio
if (hp_action == KEXEC_CRASH_HP_REMOVE_CPU)
return;
- /* crash update on memory hotplug is not support yet */
if (hp_action == KEXEC_CRASH_HP_REMOVE_MEMORY || hp_action == KEXEC_CRASH_HP_ADD_MEMORY) {
- pr_info_once("crash hp: crash update is not supported with memory hotplug\n");
+ update_crash_elfcorehdr(image, hp_action, arg);
return;
}
@@ -650,7 +704,6 @@ void arch_crash_handle_hotplug_event(struct kimage *image, unsigned int hp_actio
/* Temporarily invalidate the crash image while it is replaced */
xchg(&kexec_crash_image, NULL);
- /* update FDT to refelect changes to CPU resrouces */
if (update_cpus_node(fdt))
pr_err("crash hp: failed to update crash FDT");
diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c
index ceac592be72b9..4b96c6017cf1b 100644
--- a/arch/powerpc/kexec/file_load_64.c
+++ b/arch/powerpc/kexec/file_load_64.c
@@ -21,6 +21,8 @@
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
+#include <linux/elf.h>
+
#include <asm/setup.h>
#include <asm/drmem.h>
#include <asm/firmware.h>
@@ -704,6 +706,30 @@ static void update_backup_region_phdr(struct kimage *image, Elf64_Ehdr *ehdr)
}
}
+/* get_max_phdr - Find the total number of Phdr needed to represent the
+ * max memory in the kdump elfcorehdr.
+ *
+ * @cmem: crash memory ranges in the system.
+ */
+static int get_max_phdr(struct crash_mem *cmem)
+{
+ int max_lmb;
+
+ /* In the worst case, a Phdr is needed for every other LMB to be represented
+ * as an individual crash range.
+ */
+ max_lmb = memory_hotplug_max() / 2 * drmem_lmb_size();
+
+ /* Do not cross the Phdr max limit of the elf header.
+ * Avoid counting Phdr for crash ranges (cmem->nr_ranges) which
+ * are already part of elfcorehdr.
+ */
+ if (max_lmb > PN_XNUM)
+ return PN_XNUM - cmem->nr_ranges;
+
+ return max_lmb - cmem->nr_ranges;
+}
+
/**
* load_elfcorehdr_segment - Setup crash memory ranges and initialize elfcorehdr
* segment needed to load kdump kernel.
@@ -735,7 +761,13 @@ static int load_elfcorehdr_segment(struct kimage *image, struct kexec_buf *kbuf)
kbuf->buffer = headers;
kbuf->mem = KEXEC_BUF_MEM_UNKNOWN;
- kbuf->bufsz = kbuf->memsz = headers_sz;
+ kbuf->bufsz = headers_sz;
+/* Additional buffer space to accommodate future memory ranges */
+#if defined(CONFIG_MEMORY_HOTPLUG)
+ kbuf->memsz = headers_sz + get_max_phdr(cmem) * sizeof(Elf64_Phdr);
+#else
+ kbuf->memsz = headers_sz;
+#endif
kbuf->top_down = false;
ret = kexec_add_buffer(kbuf);
@@ -745,7 +777,7 @@ static int load_elfcorehdr_segment(struct kimage *image, struct kexec_buf *kbuf)
}
image->elf_load_addr = kbuf->mem;
- image->elf_headers_sz = headers_sz;
+ image->elf_headers_sz = kbuf->memsz;
image->elf_headers = headers;
out:
kfree(cmem);
diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c
index 5fc53a5fcfdf6..2bb90874df781 100644
--- a/arch/powerpc/kexec/ranges.c
+++ b/arch/powerpc/kexec/ranges.c
@@ -234,6 +234,66 @@ int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
return __add_mem_range(mem_ranges, base, size);
}
+/**
+ * remove_mem_range - Removes the given memory range from the range list.
+ * @mem_ranges: Range list to remove the memory range to.
+ * @base: Base address of the range to remove.
+ * @size: Size of the memory range to remove.
+ *
+ * (Re)allocates memory, if needed.
+ *
+ * Returns 0 on success, negative errno on error.
+ */
+int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
+{
+ int ret = 0;
+ unsigned int i;
+ struct crash_mem *mem_rngs = *mem_ranges;
+ u64 mstart, mend;
+ u64 range_start, range_end;
+
+ if (!size)
+ return 0;
+
+ range_start = base;
+ range_end = base + size - 1;
+
+ for (i = 0; i < mem_rngs->nr_ranges; i++) {
+ mstart = mem_rngs->ranges[i].start;
+ mend = mem_rngs->ranges[i].end;
+
+ if (!(range_start >= mstart && range_end <= mend))
+ continue;
+
+ if (range_start == mstart) {
+ if (range_end == mend) {
+ for (; i < mem_rngs->nr_ranges - 1; i++) {
+ mem_rngs->ranges[i].start = mem_rngs->ranges[i+1].start;
+ mem_rngs->ranges[i].end = mem_rngs->ranges[i+1].end;
+ }
+ mem_rngs->nr_ranges--;
+ goto out;
+ }
+ mem_rngs->ranges[i].start = range_end + 1;
+ goto out;
+ } else if (range_end == mend) {
+ mem_rngs->ranges[i].end = range_start - 1;
+ goto out;
+ } else {
+ size = mem_rngs->ranges[i].end - range_end;
+ mem_rngs->ranges[i].end = range_start - 1;
+ if (add_mem_range(mem_ranges, range_end + 1, size))
+ goto error;
+ goto out;
+ }
+ }
+error:
+ return -1;
+
+out:
+ return ret;
+}
+
/**
* add_tce_mem_ranges - Adds tce-table range to the given memory ranges list.
* @mem_ranges: Range list to add the memory range(s) to.
--
2.39.1
More information about the kexec
mailing list