Add a registry identifying page use to a kdump kernel. Intended uses are
wiping of crypto keys on crash, or omitting pages holding e.g. crypto
keys or unimportant caches from crash dumps.

The registry is a bitmap with one bit for each physical page. The
meaning of the bits is set by the crash_memaction= cmdline param. When
not present, no bitmap is allocated. Marking is a lock-free bitmap
update with no allocation, so it is safe from any context.

The bitmap and its metadata is handed over to the kdump kernel through
elfcorehdr.

Signed-off-by: Jan Sebastian Götte <[email protected]>
Assisted-by: Claude Opus 5 <[email protected]>
---
 Documentation/admin-guide/kernel-parameters.txt |   9 +
 MAINTAINERS                                     |   1 +
 drivers/of/kexec.c                              |   7 +
 include/linux/crash_memaction.h                 |  71 ++++
 include/linux/kexec.h                           |   6 +
 kernel/Kconfig.kexec                            |  21 ++
 kernel/crash_core.c                             | 416 ++++++++++++++++++++++++
 kernel/kexec_core.c                             |   4 +
 kernel/kexec_file.c                             |  10 +
 kernel/ksysfs.c                                 |  15 +
 10 files changed, 560 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt 
b/Documentation/admin-guide/kernel-parameters.txt
index 2041dd9a8f18..26a78335c156 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1040,6 +1040,15 @@ Kernel parameters
                        Default is enabled if CONFIG_HOTPLUG_PARALLEL=y. 
Otherwise
                        the parameter has no effect.
 
+       crash_memaction={secret|cache}[,...]
+                       [KNL,EARLY] When CONFIG_CRASH_MEMACTION is set, this
+                       parameter instructs the kernel to hand an eventual
+                       kdump kernel a bitmap of pages annotated with the
+                       given types. Annotations are made with madvise(2).
+                       Kernel crypto keys are annotated secret. What the
+                       kdump kernel does with this is up to it. Default is
+                       nothing is tracked and no bitmap is created.
+
        crash_kexec_post_notifiers
                        Only jump to kdump kernel after running the panic
                        notifiers and dumping kmsg. This option increases
diff --git a/MAINTAINERS b/MAINTAINERS
index dccddc99ac9a..c6350fd34f4c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14259,6 +14259,7 @@ F:      Documentation/admin-guide/kdump/
 F:     fs/proc/vmcore.c
 F:     include/linux/crash_core.h
 F:     include/linux/crash_dump.h
+F:     include/linux/crash_memaction.h
 F:     include/uapi/linux/vmcore.h
 F:     kernel/crash_*.c
 
diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c
index 029903b986cb..a4d9eca9debe 100644
--- a/drivers/of/kexec.c
+++ b/drivers/of/kexec.c
@@ -442,6 +442,13 @@ void *of_kexec_alloc_and_setup_fdt(const struct kimage 
*image,
                                goto out;
                }
 
+               if (image->memaction_addr != 0) {
+                       ret = fdt_add_mem_rsv(fdt, image->memaction_addr,
+                                             image->memaction_sz);
+                       if (ret)
+                               goto out;
+               }
+
 #ifdef CONFIG_CRASH_DUMP
                /* add linux,usable-memory-range */
                ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
diff --git a/include/linux/crash_memaction.h b/include/linux/crash_memaction.h
new file mode 100644
index 000000000000..5f3e114c60ad
--- /dev/null
+++ b/include/linux/crash_memaction.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef LINUX_CRASH_MEMACTION_H
+#define LINUX_CRASH_MEMACTION_H
+
+#include <linux/init.h>
+#include <linux/jump_label.h>
+#include <linux/types.h>
+
+struct kimage;
+
+enum crash_memaction_types {
+       CRASH_MEMACTION_CACHE = 0x1000,
+       CRASH_MEMACTION_SECRET = 0x2000,
+};
+
+#define CRASH_MEMACTION_NOTE_NAME      "MEMACTION"
+
+/*
+ * Bit n of the bitmap at @bitmap_paddr stands for the page frame at
+ * @start_pfn + n.
+ */
+struct crash_memaction_region_note {
+       u64 start_pfn;
+       u64 nr_pages;
+       u64 bitmap_paddr;
+};
+
+/* Wire format between the two kernels */
+struct crash_memaction_note {
+       u32 types; /* CRASH_MEMACTION_* types that set bitmap bits stand for */
+       u32 page_shift;
+       u32 nr_regions;
+       u32 _pad;
+       struct crash_memaction_region_note regions[];
+};
+
+#ifdef CONFIG_CRASH_MEMACTION
+void __init crash_memaction_init(void);
+
+DECLARE_STATIC_KEY_FALSE(crash_memaction_active);
+
+/* Safe from any context. */
+void __crash_memaction_mark_pfns(unsigned long pfn, unsigned long nr_pages);
+void __crash_memaction_unmark_pfns(unsigned long pfn, unsigned long nr_pages);
+
+static inline void crash_memaction_unmark_pfns(unsigned long pfn,
+               unsigned long nr_pages)
+{
+       if (static_branch_unlikely(&crash_memaction_active))
+               __crash_memaction_unmark_pfns(pfn, nr_pages);
+}
+
+void crash_memaction_mark(void *addr, size_t size, int types);
+void crash_memaction_unmark(void *addr, size_t size);
+
+int crash_memaction_types(void);
+ssize_t crash_memaction_types_str(char *buf);
+
+int crash_load_memaction(struct kimage *image);
+void crash_memaction_unload(struct kimage *image);
+#else
+static inline void crash_memaction_init(void) { }
+static inline void crash_memaction_unmark_pfns(unsigned long pfn,
+               unsigned long nr_pages) { }
+static inline void crash_memaction_mark(void *addr, size_t size, int types) { }
+static inline void crash_memaction_unmark(void *addr, size_t size) { }
+static inline int crash_memaction_types(void) { return 0; }
+static inline int crash_load_memaction(struct kimage *image) { return 0; }
+static inline void crash_memaction_unload(struct kimage *image) { }
+#endif /* CONFIG_CRASH_MEMACTION */
+#endif /* LINUX_CRASH_MEMACTION_H */
diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index e5f1cfc11fef..f4a2450be879 100644
--- a/include/linux/kexec.h
+++ b/include/linux/kexec.h
@@ -415,6 +415,12 @@ struct kimage {
        /* dm crypt keys buffer */
        unsigned long dm_crypt_keys_addr;
        unsigned long dm_crypt_keys_sz;
+
+       /* crash memaction descriptor buffer */
+       void *memaction_note_va;
+       unsigned long memaction_addr;
+       unsigned long memaction_sz;
+       int memaction_index;
 };
 
 /* kexec interface functions */
diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
index a97ed9605602..f7326fa6476b 100644
--- a/kernel/Kconfig.kexec
+++ b/kernel/Kconfig.kexec
@@ -179,4 +179,25 @@ config CRASH_MAX_MEMORY_RANGES
          the computation behind the value provided through the
          /sys/kernel/crash_elfcorehdr_size attribute.
 
+config ARCH_SUPPORTS_CRASH_MEMACTION
+       bool
+
+config CRASH_MEMACTION
+       bool "Register kdump actions for certain memory ranges"
+       depends on CRASH_DUMP
+       depends on KEXEC_FILE
+       depends on ARCH_SUPPORTS_CRASH_MEMACTION
+       help
+         Track pages that may require special handling by the kdump kernel:
+         pages holding secrets such as crypto keys, which it can wipe before
+         anything reads the dump, and pages not worth dumping at all. This
+         kernel only records which pages are which; what happens to them is
+         the kdump kernel's decision.
+
+         Nothing is tracked and no memory is spent unless the kernel is booted
+         with the crash_memaction= parameter, which also says which types the
+         bits stand for. The bitmap then costs about 32 KiB per GiB of memory.
+
+         If unsure, say N.
+
 endmenu
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index d0bd2d0cf899..6bb2f5645ca6 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -18,11 +18,23 @@
 #include <linux/memblock.h>
 #include <linux/kmemleak.h>
 #include <linux/crash_core.h>
+#include <linux/crash_memaction.h>
 #include <linux/reboot.h>
 #include <linux/btf.h>
 #include <linux/objtool.h>
 #include <linux/delay.h>
 #include <linux/panic.h>
+#include <linux/timekeeping.h>
+#include <linux/atomic.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/jump_label.h>
+#include <linux/overflow.h>
+#include <linux/pfn.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/vmcore_info.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -33,6 +45,406 @@
 /* Per cpu memory for storing cpu states in case of system crash. */
 note_buf_t __percpu *crash_notes;
 
+#ifdef CONFIG_CRASH_MEMACTION
+
+#define CRASH_MEMACTION_NOTE_NAME_BYTES \
+       ALIGN(sizeof(CRASH_MEMACTION_NOTE_NAME), 4)
+
+/* Bitmap a hole may waste before it gets a region of its own. */
+#define CRASH_MEMACTION_MAX_HOLE_BYTES SZ_128K
+
+DEFINE_STATIC_KEY_FALSE(crash_memaction_active);
+EXPORT_SYMBOL_GPL(crash_memaction_active);
+
+struct crash_memaction_region {
+       unsigned long start_pfn;
+       unsigned long nr_pages;
+       unsigned long *bits;
+};
+
+static struct crash_memaction_region *crash_memaction_regions __ro_after_init;
+static unsigned int crash_memaction_nr_regions __ro_after_init;
+static size_t crash_memaction_note_bytes __ro_after_init;
+
+static struct crash_memaction_note *crash_memaction_desc __ro_after_init;
+static size_t crash_memaction_desc_bytes __ro_after_init;
+
+static phys_addr_t crash_memaction_note_paddr;
+
+static int crash_memaction_type_mask __ro_after_init;
+
+int crash_memaction_types(void)
+{
+       return crash_memaction_type_mask;
+}
+
+static int __init crash_memaction_param(char *str)
+{
+       char *tok;
+
+       while ((tok = strsep(&str, ",")) != NULL) {
+               if (!strcmp(tok, "secret"))
+                       crash_memaction_type_mask |= CRASH_MEMACTION_SECRET;
+               else if (!strcmp(tok, "cache"))
+                       crash_memaction_type_mask |= CRASH_MEMACTION_CACHE;
+               else if (*tok)
+                       pr_warn("memaction: ignoring unknown type \"%s\"\n",
+                               tok);
+       }
+       return 0;
+}
+early_param("crash_memaction", crash_memaction_param);
+
+ssize_t crash_memaction_types_str(char *buf)
+{
+       int mask = crash_memaction_type_mask;
+       int len = 0;
+
+       if (mask & CRASH_MEMACTION_SECRET)
+               len += sysfs_emit_at(buf, len, "secret");
+       if (mask & CRASH_MEMACTION_CACHE)
+               len += sysfs_emit_at(buf, len, "%scache", len ? "," : "");
+
+       return len + sysfs_emit_at(buf, len, "\n");
+}
+
+void __init crash_memaction_init(void)
+{
+       struct crash_memaction_region *regions;
+       struct crash_memaction_note *desc;
+       unsigned long total_bytes = 0;
+       unsigned long start, end, prev_end;
+       unsigned int nr = 0, i;
+       size_t desc_bytes;
+       void *bits;
+       int idx;
+
+       if (!crash_memaction_type_mask)
+               return;
+
+       regions = memblock_alloc(memblock.memory.cnt * sizeof(*regions),
+                                SMP_CACHE_BYTES);
+       if (!regions)
+               goto nomem;
+
+       for_each_mem_pfn_range(idx, NUMA_NO_NODE, &start, &end, NULL) {
+               if (nr && bitmap_size(start - prev_end) <=
+                               CRASH_MEMACTION_MAX_HOLE_BYTES) {
+                       regions[nr - 1].nr_pages = max(
+                                       end - regions[nr - 1].start_pfn,
+                                       regions[nr - 1].nr_pages);
+               } else {
+                       regions[nr].start_pfn = start;
+                       regions[nr].nr_pages = end - start;
+                       nr++;
+               }
+               prev_end = end;
+       }
+
+       for (i = 0; i < nr; i++)
+               total_bytes += bitmap_size(regions[i].nr_pages);
+
+       bits = memblock_alloc(total_bytes, PAGE_SIZE);
+       if (!bits)
+               goto nomem;
+
+       for (i = 0; i < nr; i++) {
+               regions[i].bits = bits;
+               bits += bitmap_size(regions[i].nr_pages);
+       }
+
+       desc_bytes = struct_size_t(struct crash_memaction_note, regions, nr);
+       desc = memblock_alloc(desc_bytes, sizeof(u64));
+       if (!desc)
+               goto nomem;
+
+       desc->types = crash_memaction_type_mask;
+       desc->page_shift = PAGE_SHIFT;
+       desc->nr_regions = nr;
+       for (i = 0; i < nr; i++) {
+               desc->regions[i].start_pfn = regions[i].start_pfn;
+               desc->regions[i].nr_pages = regions[i].nr_pages;
+               desc->regions[i].bitmap_paddr = __pa(regions[i].bits);
+       }
+
+       crash_memaction_regions = regions;
+       crash_memaction_nr_regions = nr;
+       crash_memaction_desc = desc;
+       crash_memaction_desc_bytes = desc_bytes;
+       crash_memaction_note_bytes =
+               PAGE_ALIGN(2 * sizeof(struct elf_note) +
+                          CRASH_MEMACTION_NOTE_NAME_BYTES +
+                          ALIGN(desc_bytes, 4));
+
+       static_branch_enable(&crash_memaction_active);
+
+       pr_info("crash_memaction: types 0x%x, %u memory region(s), %lu KiB of 
bitmap\n",
+               crash_memaction_type_mask, nr, total_bytes >> 10);
+       return;
+
+nomem:
+       crash_memaction_type_mask = 0;
+       pr_err("crash_memaction: cannot allocate page bitmaps, disabled\n");
+}
+
+/*
+ * The clear reads before it writes: it runs on every frame the page allocator
+ * hands out and almost never has anything to do, so this keeps an allocation
+ * from dirtying a cache line for frames it has nothing to do with.
+ */
+static __always_inline void crash_memaction_word(atomic_long_t *p, unsigned 
long mask, bool set)
+{
+       unsigned long val = atomic_long_read(p);
+
+       if (set) {
+               if ((val & mask) != mask)
+                       atomic_long_or(mask, p);
+       } else if (val & mask) {
+               atomic_long_andnot(mask, p);
+       }
+}
+
+/*
+ * __bitmap_set() and __bitmap_clear() merged, with the store made atomic: the
+ * partial words at either end can be shared with an unrelated folio, and
+ * marking runs from rmap with no lock. Counts are unsigned long rather than
+ * the header's unsigned int, which a bit per page frame outgrows.
+ */
+static void __crash_memaction_bits(unsigned long *map, unsigned long start,
+               unsigned long nr, bool set)
+{
+       atomic_long_t *p = (atomic_long_t *)(map + BIT_WORD(start));
+       const unsigned long size = start + nr;
+       unsigned long bits = BITS_PER_LONG - (start % BITS_PER_LONG);
+       unsigned long mask = BITMAP_FIRST_WORD_MASK(start);
+
+       /* Unsigned, so this is __bitmap_set()'s "nr - bits >= 0". */
+       while (nr >= bits) {
+               crash_memaction_word(p, mask, set);
+               nr -= bits;
+               bits = BITS_PER_LONG;
+               mask = ~0UL;
+               p++;
+       }
+
+       if (nr) {
+               mask &= BITMAP_LAST_WORD_MASK(size);
+               crash_memaction_word(p, mask, set);
+       }
+}
+
+static __always_inline void crash_memaction_bits(unsigned long *map,
+               unsigned long start, unsigned long nr, bool set)
+{
+       if (nr == 1) {
+               if (set) {
+                       if (!test_bit(start, map))
+                               set_bit(start, map);
+               } else if (test_bit(start, map)) {
+                       clear_bit(start, map);
+               }
+               return;
+       }
+
+       __crash_memaction_bits(map, start, nr, set);
+}
+
+static void crash_memaction_update(unsigned long start_pfn,
+               unsigned long nr_pages, bool set)
+{
+       unsigned long end_pfn = start_pfn + nr_pages;
+       unsigned int i;
+
+       for (i = 0; i < crash_memaction_nr_regions; i++) {
+               struct crash_memaction_region *reg = 
&crash_memaction_regions[i];
+               unsigned long from = max(start_pfn, reg->start_pfn);
+               unsigned long to = min(end_pfn, reg->start_pfn + reg->nr_pages);
+
+               if (reg->start_pfn >= end_pfn)
+                       break;
+               if (from < to)
+                       crash_memaction_bits(reg->bits, from - reg->start_pfn,
+                                            to - from, set);
+       }
+}
+
+void __crash_memaction_mark_pfns(unsigned long pfn, unsigned long nr_pages)
+{
+       crash_memaction_update(pfn, nr_pages, true);
+}
+EXPORT_SYMBOL_GPL(__crash_memaction_mark_pfns);
+
+void __crash_memaction_unmark_pfns(unsigned long pfn, unsigned long nr_pages)
+{
+       crash_memaction_update(pfn, nr_pages, false);
+}
+EXPORT_SYMBOL_GPL(__crash_memaction_unmark_pfns);
+
+static void crash_memaction_va(void *addr, size_t size, bool set)
+{
+       unsigned long start = ALIGN_DOWN((unsigned long)addr, PAGE_SIZE);
+       unsigned long end = ALIGN((unsigned long)addr + size, PAGE_SIZE);
+       unsigned long va;
+
+       if (!addr || !size)
+               return;
+
+       if (virt_addr_valid(addr)) {
+               if (!virt_addr_valid((void *)(end - 1))) {
+                       pr_warn("memaction: invalid range 0x%p+%zx\n",
+                               addr, size);
+                       return;
+               }
+
+               /* The linear map is physically contiguous. */
+               crash_memaction_update(PHYS_PFN(virt_to_phys((void *)start)),
+                                      (end - start) >> PAGE_SHIFT, set);
+               return;
+       }
+
+       if (!is_vmalloc_addr(addr)) {
+               pr_warn("memaction: invalid range 0x%p+%zx: neither linear nor 
vmalloc\n",
+                       addr, size);
+               return;
+       }
+
+       for (va = start; va < end; va += PAGE_SIZE) {
+               struct page *page = vmalloc_to_page((void *)va);
+
+               if (!page) {
+                       pr_warn("memaction: unmapped page 0x%lx inside range 
0x%p+%zx\n",
+                               va, addr, size);
+                       return;
+               }
+
+               crash_memaction_update(page_to_pfn(page), 1, set);
+       }
+}
+
+void crash_memaction_mark(void *addr, size_t size, int types)
+{
+       if (!static_branch_unlikely(&crash_memaction_active))
+               return;
+       if (!(types & crash_memaction_type_mask))
+               return;
+
+       crash_memaction_va(addr, size, true);
+}
+EXPORT_SYMBOL_GPL(crash_memaction_mark);
+
+void crash_memaction_unmark(void *addr, size_t size)
+{
+       if (!static_branch_unlikely(&crash_memaction_active))
+               return;
+
+       crash_memaction_va(addr, size, false);
+}
+EXPORT_SYMBOL_GPL(crash_memaction_unmark);
+
+int crash_load_memaction(struct kimage *image)
+{
+       unsigned long nr_pages, i;
+       struct page **pages;
+       int ret;
+       struct kexec_buf kbuf = {
+               .image = image,
+               .buffer = NULL,
+               .bufsz = 0,
+               .mem = KEXEC_BUF_MEM_UNKNOWN,
+               .memsz = crash_memaction_note_bytes,
+               .buf_align = PAGE_SIZE,
+               .buf_min = 0,
+               .buf_max = ULONG_MAX,
+               .top_down = true,
+               .random = true,
+       };
+
+       if (!crash_memaction_nr_regions)
+               return 0;
+
+       ret = kexec_add_buffer(&kbuf);
+       if (ret)
+               return ret;
+
+       nr_pages = crash_memaction_note_bytes / PAGE_SIZE;
+       pages = kmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
+       if (!pages)
+               return -ENOMEM;
+
+       for (i = 0; i < nr_pages; i++)
+               pages[i] = pfn_to_page((kbuf.mem >> PAGE_SHIFT) + i);
+
+       image->memaction_note_va = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL);
+       kfree(pages);
+       if (!image->memaction_note_va)
+               return -ENOMEM;
+
+       image->memaction_addr = kbuf.mem;
+       image->memaction_sz = kbuf.memsz;
+       image->memaction_index = image->nr_segments - 1;
+       crash_memaction_note_paddr = kbuf.mem;
+
+       return 0;
+}
+
+void crash_memaction_unload(struct kimage *image)
+{
+       if (!image->memaction_note_va)
+               return;
+
+       vunmap(image->memaction_note_va);
+       image->memaction_note_va = NULL;
+       image->memaction_addr = 0;
+       image->memaction_sz = 0;
+       image->memaction_index = -1;
+       crash_memaction_note_paddr = 0;
+}
+
+static void crash_memaction_save(struct kimage *image)
+{
+       if (!image || !image->memaction_note_va)
+               return;
+
+       final_note(append_elf_note(image->memaction_note_va,
+                                  CRASH_MEMACTION_NOTE_NAME, 0,
+                                  crash_memaction_desc,
+                                  crash_memaction_desc_bytes));
+}
+
+static unsigned long crash_memaction_nr_phdr(void)
+{
+       return crash_memaction_note_paddr ? 1 : 0;
+}
+
+static Elf64_Phdr *crash_memaction_emit_phdr(Elf64_Ehdr *ehdr,
+               Elf64_Phdr *phdr)
+{
+       if (!crash_memaction_note_paddr)
+               return phdr;
+
+       phdr->p_type = PT_NOTE;
+       phdr->p_offset = phdr->p_paddr = crash_memaction_note_paddr;
+       phdr->p_filesz = phdr->p_memsz = crash_memaction_note_bytes;
+       (ehdr->e_phnum)++;
+
+       return phdr + 1;
+}
+
+#else
+static inline void crash_memaction_save(struct kimage *image) { }
+
+static inline unsigned long crash_memaction_nr_phdr(void)
+{
+       return 0;
+}
+
+static inline Elf64_Phdr *crash_memaction_emit_phdr(Elf64_Ehdr *ehdr,
+               Elf64_Phdr *phdr)
+{
+       return phdr;
+}
+#endif /* CONFIG_CRASH_MEMACTION */
+
 /* time to wait for possible DMA to finish before starting the kdump kernel
  * when a CMA reservation is used
  */
@@ -142,6 +554,7 @@ void __noclone __crash_kexec(struct pt_regs *regs)
                        crash_save_vmcoreinfo();
                        machine_crash_shutdown(&fixed_regs);
                        crash_cma_clear_pending_dma();
+                       crash_memaction_save(kexec_crash_image);
                        machine_kexec(kexec_crash_image);
                }
                kexec_unlock();
@@ -182,6 +595,7 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int 
need_kernel_map,
        /* extra phdr for vmcoreinfo ELF note */
        nr_phdr = nr_cpus + 1;
        nr_phdr += mem->nr_ranges;
+       nr_phdr += crash_memaction_nr_phdr();
 
        /*
         * kexec-tools creates an extra PT_LOAD phdr for kernel text mapping
@@ -231,6 +645,8 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int 
need_kernel_map,
        (ehdr->e_phnum)++;
        phdr++;
 
+       phdr = crash_memaction_emit_phdr(ehdr, phdr);
+
        /* Prepare PT_LOAD type program header for kernel text region */
        if (need_kernel_map) {
                phdr->p_type = PT_LOAD;
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index 7ee8c9f078f6..ac7406d68bdf 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -8,6 +8,7 @@
 
 #include <linux/btf.h>
 #include <linux/capability.h>
+#include <linux/crash_memaction.h>
 #include <linux/mm.h>
 #include <linux/file.h>
 #include <linux/slab.h>
@@ -264,6 +265,8 @@ struct kimage *do_kimage_alloc_init(void)
        image->elfcorehdr_updated = false;
 #endif
 
+       image->memaction_index = -1;
+
        return image;
 }
 
@@ -595,6 +598,7 @@ void kimage_free(struct kimage *image)
                crash_update_vmcoreinfo_safecopy(NULL);
                vunmap(image->vmcoreinfo_data_copy);
        }
+       crash_memaction_unload(image);
 #endif
 
        kimage_free_extra_pages(image);
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index a8455481f639..e87194524fd7 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -10,6 +10,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/capability.h>
+#include <linux/crash_memaction.h>
 #include <linux/mm.h>
 #include <linux/file.h>
 #include <linux/slab.h>
@@ -325,6 +326,10 @@ kimage_file_alloc_init(struct kimage **rimage, int 
kernel_fd,
                /* Enable special crash kernel control page alloc policy. */
                image->control_page = crashk_res.start;
                image->type = KEXEC_TYPE_CRASH;
+
+               ret = crash_load_memaction(image);
+               if (ret)
+                       goto out_free_image;
        }
 #endif
 
@@ -360,6 +365,7 @@ kimage_file_alloc_init(struct kimage **rimage, int 
kernel_fd,
 out_free_post_load_bufs:
        kimage_file_post_load_cleanup(image);
 out_free_image:
+       crash_memaction_unload(image);
        kfree(image);
        return ret;
 }
@@ -877,6 +883,10 @@ static int kexec_calculate_store_digests(struct kimage 
*image)
                        continue;
 #endif
 
+               /* Exclude the memaction buffer, written during the crash */
+               if (i == image->memaction_index)
+                       continue;
+
                ksegment = &image->segment[i];
                /*
                 * Skip purgatory as it will be modified once we put digest
diff --git a/kernel/ksysfs.c b/kernel/ksysfs.c
index f45ade718054..846bd850a4fb 100644
--- a/kernel/ksysfs.c
+++ b/kernel/ksysfs.c
@@ -14,6 +14,7 @@
 #include <linux/export.h>
 #include <linux/init.h>
 #include <linux/vmcore_info.h>
+#include <linux/crash_memaction.h>
 #include <linux/profile.h>
 #include <linux/stat.h>
 #include <linux/sched.h>
@@ -133,6 +134,17 @@ KERNEL_ATTR_RO(vmcoreinfo);
 
 #endif /* CONFIG_VMCORE_INFO */
 
+#ifdef CONFIG_CRASH_MEMACTION
+
+static ssize_t crash_memaction_show(struct kobject *kobj,
+                                   struct kobj_attribute *attr, char *buf)
+{
+       return crash_memaction_types_str(buf);
+}
+KERNEL_ATTR_RO(crash_memaction);
+
+#endif /* CONFIG_CRASH_MEMACTION */
+
 /* whether file capabilities are enabled */
 static ssize_t fscaps_show(struct kobject *kobj,
                                  struct kobj_attribute *attr, char *buf)
@@ -203,6 +215,9 @@ static struct attribute * kernel_attrs[] = {
 #ifdef CONFIG_VMCORE_INFO
        &vmcoreinfo_attr.attr,
 #endif
+#ifdef CONFIG_CRASH_MEMACTION
+       &crash_memaction_attr.attr,
+#endif
 #ifndef CONFIG_TINY_RCU
        &rcu_expedited_attr.attr,
        &rcu_normal_attr.attr,

-- 
2.55.0


Reply via email to