RISC-V: Add crash kernel support
authorNick Kossifidis <mick@ics.forth.gr>
Mon, 19 Apr 2021 00:55:39 +0000 (03:55 +0300)
committerPalmer Dabbelt <palmerdabbelt@google.com>
Mon, 26 Apr 2021 15:25:24 +0000 (08:25 -0700)
This patch allows Linux to act as a crash kernel for use with
kdump. Userspace will let the crash kernel know about the
memory region it can use through linux,usable-memory property
on the /memory node (overriding its reg property), and about the
memory region where the elf core header of the previous kernel
is saved, through a reserved-memory node with a compatible string
of "linux,elfcorehdr". This approach is the least invasive and
re-uses functionality already present.

I tested this on riscv64 qemu and it works as expected, you
may test it by retrieving the dmesg of the previous kernel
through /proc/vmcore, using the vmcore-dmesg utility from
kexec-tools.

Signed-off-by: Nick Kossifidis <mick@ics.forth.gr>
Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
arch/riscv/Kconfig
arch/riscv/kernel/Makefile
arch/riscv/kernel/crash_dump.c [new file with mode: 0644]
arch/riscv/kernel/setup.c
arch/riscv/mm/init.c

index e38bd04..61514e0 100644 (file)
@@ -405,6 +405,16 @@ config KEXEC
 
          The name comes from the similarity to the exec system call.
 
+config CRASH_DUMP
+       bool "Build kdump crash kernel"
+       help
+         Generate crash dump after being started by kexec. This should
+         be normally only set in special crash dump kernels which are
+         loaded in the main kernel with kexec-tools into a specially
+         reserved region and then later executed after a crash by
+         kdump/kexec.
+
+         For more details see Documentation/admin-guide/kdump/kdump.rst
 
 endmenu
 
index 3ee07bf..56d5cd2 100644 (file)
@@ -59,6 +59,7 @@ endif
 obj-$(CONFIG_HOTPLUG_CPU)      += cpu-hotplug.o
 obj-$(CONFIG_KGDB)             += kgdb.o
 obj-$(CONFIG_KEXEC)            += kexec_relocate.o crash_save_regs.o machine_kexec.o
+obj-$(CONFIG_CRASH_DUMP)       += crash_dump.o
 
 obj-$(CONFIG_JUMP_LABEL)       += jump_label.o
 
diff --git a/arch/riscv/kernel/crash_dump.c b/arch/riscv/kernel/crash_dump.c
new file mode 100644 (file)
index 0000000..86cc0ad
--- /dev/null
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This code comes from arch/arm64/kernel/crash_dump.c
+ * Created by: AKASHI Takahiro <takahiro.akashi@linaro.org>
+ * Copyright (C) 2017 Linaro Limited
+ */
+
+#include <linux/crash_dump.h>
+#include <linux/io.h>
+
+/**
+ * copy_oldmem_page() - copy one page from old kernel memory
+ * @pfn: page frame number to be copied
+ * @buf: buffer where the copied page is placed
+ * @csize: number of bytes to copy
+ * @offset: offset in bytes into the page
+ * @userbuf: if set, @buf is in a user address space
+ *
+ * This function copies one page from old kernel memory into buffer pointed by
+ * @buf. If @buf is in userspace, set @userbuf to %1. Returns number of bytes
+ * copied or negative error in case of failure.
+ */
+ssize_t copy_oldmem_page(unsigned long pfn, char *buf,
+                        size_t csize, unsigned long offset,
+                        int userbuf)
+{
+       void *vaddr;
+
+       if (!csize)
+               return 0;
+
+       vaddr = memremap(__pfn_to_phys(pfn), PAGE_SIZE, MEMREMAP_WB);
+       if (!vaddr)
+               return -ENOMEM;
+
+       if (userbuf) {
+               if (copy_to_user((char __user *)buf, vaddr + offset, csize)) {
+                       memunmap(vaddr);
+                       return -EFAULT;
+               }
+       } else
+               memcpy(buf, vaddr + offset, csize);
+
+       memunmap(vaddr);
+       return csize;
+}
index 0aa9f83..932ef73 100644 (file)
@@ -66,6 +66,9 @@ static struct resource code_res = { .name = "Kernel code", };
 static struct resource data_res = { .name = "Kernel data", };
 static struct resource rodata_res = { .name = "Kernel rodata", };
 static struct resource bss_res = { .name = "Kernel bss", };
+#ifdef CONFIG_CRASH_DUMP
+static struct resource elfcorehdr_res = { .name = "ELF Core hdr", };
+#endif
 
 static int __init add_resource(struct resource *parent,
                                struct resource *res)
@@ -169,6 +172,15 @@ static void __init init_resources(void)
        }
 #endif
 
+#ifdef CONFIG_CRASH_DUMP
+       if (elfcorehdr_size > 0) {
+               elfcorehdr_res.start = elfcorehdr_addr;
+               elfcorehdr_res.end = elfcorehdr_addr + elfcorehdr_size - 1;
+               elfcorehdr_res.flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
+               add_resource(&iomem_resource, &elfcorehdr_res);
+       }
+#endif
+
        for_each_reserved_mem_region(region) {
                res = &mem_res[res_idx--];
 
index 63c94d3..3cb4c1b 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/swap.h>
 #include <linux/sizes.h>
 #include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/libfdt.h>
 #include <linux/set_memory.h>
 #include <linux/dma-map-ops.h>
@@ -678,6 +679,18 @@ static void __init reserve_crashkernel(void)
 
        int ret = 0;
 
+       /*
+        * Don't reserve a region for a crash kernel on a crash kernel
+        * since it doesn't make much sense and we have limited memory
+        * resources.
+        */
+#ifdef CONFIG_CRASH_DUMP
+       if (is_kdump_kernel()) {
+               pr_info("crashkernel: ignoring reservation request\n");
+               return;
+       }
+#endif
+
        ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
                                &crash_size, &crash_base);
        if (ret || !crash_size)
@@ -726,6 +739,26 @@ static void __init reserve_crashkernel(void)
 }
 #endif /* CONFIG_KEXEC_CORE */
 
+#ifdef CONFIG_CRASH_DUMP
+/*
+ * We keep track of the ELF core header of the crashed
+ * kernel with a reserved-memory region with compatible
+ * string "linux,elfcorehdr". Here we register a callback
+ * to populate elfcorehdr_addr/size when this region is
+ * present. Note that this region will be marked as
+ * reserved once we call early_init_fdt_scan_reserved_mem()
+ * later on.
+ */
+static int elfcore_hdr_setup(struct reserved_mem *rmem)
+{
+       elfcorehdr_addr = rmem->base;
+       elfcorehdr_size = rmem->size;
+       return 0;
+}
+
+RESERVEDMEM_OF_DECLARE(elfcorehdr, "linux,elfcorehdr", elfcore_hdr_setup);
+#endif
+
 void __init paging_init(void)
 {
        setup_vm_final();