]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
memblock: add MEMBLOCK_RSRV_KERN flag
authorMike Rapoport (Microsoft) <rppt@kernel.org>
Fri, 9 May 2025 07:46:19 +0000 (00:46 -0700)
committerAndrew Morton <akpm@linux-foundation.org>
Tue, 13 May 2025 06:50:38 +0000 (23:50 -0700)
Patch series "kexec: introduce Kexec HandOver (KHO)", v8.

Kexec today considers itself purely a boot loader: When we enter the new
kernel, any state the previous kernel left behind is irrelevant and the
new kernel reinitializes the system.

However, there are use cases where this mode of operation is not what we
actually want.  In virtualization hosts for example, we want to use kexec
to update the host kernel while virtual machine memory stays untouched.
When we add device assignment to the mix, we also need to ensure that
IOMMU and VFIO states are untouched.  If we add PCIe peer to peer DMA, we
need to do the same for the PCI subsystem.  If we want to kexec while an
SEV-SNP enabled virtual machine is running, we need to preserve the VM
context pages and physical memory.  See "pkernfs: Persisting guest memory
and kernel/device state safely across kexec" Linux Plumbers Conference
2023 presentation for details:

  https://lpc.events/event/17/contributions/1485/

To start us on the journey to support all the use cases above, this patch
implements basic infrastructure to allow hand over of kernel state across
kexec (Kexec HandOver, aka KHO).  As a really simple example target, we
use memblock's reserve_mem.

With this patchset applied, memory that was reserved using "reserve_mem"
command line options remains intact after kexec and it is guaranteed to
reside at the same physical address.

== Alternatives ==

There are alternative approaches to (parts of) the problems above:

  * Memory Pools [1] - preallocated persistent memory region + allocator
  * PRMEM [2] - resizable persistent memory regions with fixed metadata
                pointer on the kernel command line + allocator
  * Pkernfs [3] - preallocated file system for in-kernel data with fixed
                  address location on the kernel command line
  * PKRAM [4] - handover of user space pages using a fixed metadata page
                specified via command line

All of the approaches above fundamentally have the same problem: They
require the administrator to explicitly carve out a physical memory
location because they have no mechanism outside of the kernel command line
to pass data (including memory reservations) between kexec'ing kernels.

KHO provides that base foundation.  We will determine later whether we
still need any of the approaches above for fast bulk memory handover of
for example IOMMU page tables.  But IMHO they would all be users of KHO,
with KHO providing the foundational primitive to pass metadata and bulk
memory reservations as well as provide easy versioning for data.

== Overview ==

We introduce a metadata file that the kernels pass between each other.
How they pass it is architecture specific.  The file's format is a
Flattened Device Tree (fdt) which has a generator and parser already
included in Linux.  KHO is enabled in the kernel command line by `kho=on`.
When the root user enables KHO through
/sys/kernel/debug/kho/out/finalize, the kernel invokes callbacks to every
KHO users to register preserved memory regions, which contain drivers'
states.

When the actual kexec happens, the fdt is part of the image set that we
boot into.  In addition, we keep "scratch regions" available for kexec:
physically contiguous memory regions that are guaranteed to not have any
memory that KHO would preserve.  The new kernel bootstraps itself using
the scratch regions and sets all handed over memory as in use.  When
drivers initialize that support KHO, they introspect the fdt, restore
preserved memory regions, and retrieve their states stored in the
preserved memory.

== Limitations ==

Currently KHO is only implemented for file based kexec.  The kernel
interfaces in the patch set are already in place to support user space
kexec as well, but it is still not implemented it yet inside kexec tools.

== How to Use ==

To use the code, please boot the kernel with the "kho=on" command line
parameter.  KHO will automatically create scratch regions.  If you want to
set the scratch size explicitly you can use "kho_scratch=" command line
parameter.  For instance, "kho_scratch=16M,512M,256M" will reserve a 16
MiB low memory scratch area, a 512 MiB global scratch region, and 256 MiB
per NUMA node scratch regions on boot.

Make sure to have a reserved memory range requested with reserv_mem
command line option, for example, "reserve_mem=64m:4k:n1".

Then before you invoke file based "kexec -l", finalize KHO FDT:

  # echo 1 > /sys/kernel/debug/kho/out/finalize

You can preview the generated FDT using `dtc`,

  # dtc /sys/kernel/debug/kho/out/fdt
  # dtc /sys/kernel/debug/kho/out/sub_fdts/memblock

`dtc` is available on ubuntu by `sudo apt-get install device-tree-compiler`.

Now kexec into the new kernel,

  # kexec -l Image --initrd=initrd -s
  # kexec -e

(The order of KHO finalization and "kexec -l" does not matter.)

The new kernel will boot up and contain the previous kernel's reserve_mem
contents at the same physical address as the first kernel.

You can also review the FDT passed from the old kernel,

  # dtc /sys/kernel/debug/kho/in/fdt
  # dtc /sys/kernel/debug/kho/in/sub_fdts/memblock

This patch (of 17):

To denote areas that were reserved for kernel use either directly with
memblock_reserve_kern() or via memblock allocations.

Link: https://lore.kernel.org/lkml/20250424083258.2228122-1-changyuanl@google.com/
Link: https://lore.kernel.org/lkml/aAeaJ2iqkrv_ffhT@kernel.org/
Link: https://lore.kernel.org/lkml/35c58191-f774-40cf-8d66-d1e2aaf11a62@intel.com/
Link: https://lore.kernel.org/lkml/20250424093302.3894961-1-arnd@kernel.org/
Link: https://lkml.kernel.org/r/20250509074635.3187114-1-changyuanl@google.com
Link: https://lkml.kernel.org/r/20250509074635.3187114-2-changyuanl@google.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Co-developed-by: Changyuan Lyu <changyuanl@google.com>
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
Cc: Alexander Graf <graf@amazon.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Anthony Yznaga <anthony.yznaga@oracle.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Ashish Kalra <ashish.kalra@amd.com>
Cc: Ben Herrenschmidt <benh@kernel.crashing.org>
Cc: Borislav Betkov <bp@alien8.de>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Eric Biederman <ebiederm@xmission.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Gowans <jgowans@amazon.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Krzysztof Kozlowski <krzk@kernel.org>
Cc: Marc Rutland <mark.rutland@arm.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Pratyush Yadav <ptyadav@amazon.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Saravana Kannan <saravanak@google.com>
Cc: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Thomas Gleinxer <tglx@linutronix.de>
Cc: Thomas Lendacky <thomas.lendacky@amd.com>
Cc: Will Deacon <will@kernel.org>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/memblock.h
mm/memblock.c
tools/testing/memblock/tests/alloc_api.c
tools/testing/memblock/tests/alloc_helpers_api.c
tools/testing/memblock/tests/alloc_nid_api.c

index ef5a1ecc6e595436baab453d6efea062b8118cc8..6c00fbc085132e447cbfdcf16ee90e50f2165e17 100644 (file)
@@ -42,6 +42,9 @@ extern unsigned long long max_possible_pfn;
  * kernel resource tree.
  * @MEMBLOCK_RSRV_NOINIT: memory region for which struct pages are
  * not initialized (only for reserved regions).
+ * @MEMBLOCK_RSRV_KERN: memory region that is reserved for kernel use,
+ * either explictitly with memblock_reserve_kern() or via memblock
+ * allocation APIs. All memblock allocations set this flag.
  */
 enum memblock_flags {
        MEMBLOCK_NONE           = 0x0,  /* No special request */
@@ -50,6 +53,7 @@ enum memblock_flags {
        MEMBLOCK_NOMAP          = 0x4,  /* don't add to kernel direct mapping */
        MEMBLOCK_DRIVER_MANAGED = 0x8,  /* always detected via a driver */
        MEMBLOCK_RSRV_NOINIT    = 0x10, /* don't initialize struct pages */
+       MEMBLOCK_RSRV_KERN      = 0x20, /* memory reserved for kernel use */
 };
 
 /**
@@ -116,7 +120,19 @@ int memblock_add_node(phys_addr_t base, phys_addr_t size, int nid,
 int memblock_add(phys_addr_t base, phys_addr_t size);
 int memblock_remove(phys_addr_t base, phys_addr_t size);
 int memblock_phys_free(phys_addr_t base, phys_addr_t size);
-int memblock_reserve(phys_addr_t base, phys_addr_t size);
+int __memblock_reserve(phys_addr_t base, phys_addr_t size, int nid,
+                      enum memblock_flags flags);
+
+static __always_inline int memblock_reserve(phys_addr_t base, phys_addr_t size)
+{
+       return __memblock_reserve(base, size, NUMA_NO_NODE, 0);
+}
+
+static __always_inline int memblock_reserve_kern(phys_addr_t base, phys_addr_t size)
+{
+       return __memblock_reserve(base, size, NUMA_NO_NODE, MEMBLOCK_RSRV_KERN);
+}
+
 #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
 int memblock_physmem_add(phys_addr_t base, phys_addr_t size);
 #endif
@@ -476,6 +492,7 @@ static inline __init_memblock bool memblock_bottom_up(void)
 
 phys_addr_t memblock_phys_mem_size(void);
 phys_addr_t memblock_reserved_size(void);
+phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);
 unsigned long memblock_estimated_nr_free_pages(void);
 phys_addr_t memblock_start_of_DRAM(void);
 phys_addr_t memblock_end_of_DRAM(void);
index 0e9ebb8aa7fe540294deb1ef2db5fb4122086bac..ac377cd610291d27b706f8f773ff4e1203598d77 100644 (file)
@@ -499,7 +499,7 @@ static int __init_memblock memblock_double_array(struct memblock_type *type,
         * needn't do it
         */
        if (!use_slab)
-               BUG_ON(memblock_reserve(addr, new_alloc_size));
+               BUG_ON(memblock_reserve_kern(addr, new_alloc_size));
 
        /* Update slab flag */
        *in_slab = use_slab;
@@ -649,7 +649,7 @@ repeat:
 #ifdef CONFIG_NUMA
                        WARN_ON(nid != memblock_get_region_node(rgn));
 #endif
-                       WARN_ON(flags != rgn->flags);
+                       WARN_ON(flags != MEMBLOCK_NONE && flags != rgn->flags);
                        nr_new++;
                        if (insert) {
                                if (start_rgn == -1)
@@ -909,14 +909,15 @@ int __init_memblock memblock_phys_free(phys_addr_t base, phys_addr_t size)
        return memblock_remove_range(&memblock.reserved, base, size);
 }
 
-int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
+int __init_memblock __memblock_reserve(phys_addr_t base, phys_addr_t size,
+                                      int nid, enum memblock_flags flags)
 {
        phys_addr_t end = base + size - 1;
 
-       memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
-                    &base, &end, (void *)_RET_IP_);
+       memblock_dbg("%s: [%pa-%pa] nid=%d flags=%x %pS\n", __func__,
+                    &base, &end, nid, flags, (void *)_RET_IP_);
 
-       return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
+       return memblock_add_range(&memblock.reserved, base, size, nid, flags);
 }
 
 #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
@@ -1467,14 +1468,14 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
 again:
        found = memblock_find_in_range_node(size, align, start, end, nid,
                                            flags);
-       if (found && !memblock_reserve(found, size))
+       if (found && !__memblock_reserve(found, size, nid, MEMBLOCK_RSRV_KERN))
                goto done;
 
        if (numa_valid_node(nid) && !exact_nid) {
                found = memblock_find_in_range_node(size, align, start,
                                                    end, NUMA_NO_NODE,
                                                    flags);
-               if (found && !memblock_reserve(found, size))
+               if (found && !memblock_reserve_kern(found, size))
                        goto done;
        }
 
@@ -1759,6 +1760,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void)
        return memblock.reserved.total_size;
 }
 
+phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid)
+{
+       struct memblock_region *r;
+       phys_addr_t total = 0;
+
+       for_each_reserved_mem_region(r) {
+               phys_addr_t size = r->size;
+
+               if (r->base > limit)
+                       break;
+
+               if (r->base + r->size > limit)
+                       size = limit - r->base;
+
+               if (nid == memblock_get_region_node(r) || !numa_valid_node(nid))
+                       if (r->flags & MEMBLOCK_RSRV_KERN)
+                               total += size;
+       }
+
+       return total;
+}
+
 /**
  * memblock_estimated_nr_free_pages - return estimated number of free pages
  * from memblock point of view
@@ -2458,6 +2481,7 @@ static const char * const flagname[] = {
        [ilog2(MEMBLOCK_NOMAP)] = "NOMAP",
        [ilog2(MEMBLOCK_DRIVER_MANAGED)] = "DRV_MNG",
        [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT",
+       [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN",
 };
 
 static int memblock_debug_show(struct seq_file *m, void *private)
index 68f1a75cd72c440d7a8a5301fc100e451605f7e7..c55f67dd367d05a684eb39568dd36298d7438baa 100644 (file)
@@ -134,7 +134,7 @@ static int alloc_top_down_before_check(void)
        PREFIX_PUSH();
        setup_memblock();
 
-       memblock_reserve(memblock_end_of_DRAM() - total_size, r1_size);
+       memblock_reserve_kern(memblock_end_of_DRAM() - total_size, r1_size);
 
        allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES);
 
@@ -182,7 +182,7 @@ static int alloc_top_down_after_check(void)
 
        total_size = r1.size + r2_size;
 
-       memblock_reserve(r1.base, r1.size);
+       memblock_reserve_kern(r1.base, r1.size);
 
        allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES);
 
@@ -231,8 +231,8 @@ static int alloc_top_down_second_fit_check(void)
 
        total_size = r1.size + r2.size + r3_size;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES);
 
@@ -285,8 +285,8 @@ static int alloc_in_between_generic_check(void)
 
        total_size = r1.size + r2.size + r3_size;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES);
 
@@ -422,7 +422,7 @@ static int alloc_limited_space_generic_check(void)
        setup_memblock();
 
        /* Simulate almost-full memory */
-       memblock_reserve(memblock_start_of_DRAM(), reserved_size);
+       memblock_reserve_kern(memblock_start_of_DRAM(), reserved_size);
 
        allocated_ptr = run_memblock_alloc(available_size, SMP_CACHE_BYTES);
 
@@ -608,7 +608,7 @@ static int alloc_bottom_up_before_check(void)
        PREFIX_PUSH();
        setup_memblock();
 
-       memblock_reserve(memblock_start_of_DRAM() + r1_size, r2_size);
+       memblock_reserve_kern(memblock_start_of_DRAM() + r1_size, r2_size);
 
        allocated_ptr = run_memblock_alloc(r1_size, SMP_CACHE_BYTES);
 
@@ -655,7 +655,7 @@ static int alloc_bottom_up_after_check(void)
 
        total_size = r1.size + r2_size;
 
-       memblock_reserve(r1.base, r1.size);
+       memblock_reserve_kern(r1.base, r1.size);
 
        allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES);
 
@@ -705,8 +705,8 @@ static int alloc_bottom_up_second_fit_check(void)
 
        total_size = r1.size + r2.size + r3_size;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES);
 
index 3ef9486da8a09350422634ceaafeb8b687c7757e..e5362cfd2ff303592cc04f3a844cf51473542075 100644 (file)
@@ -163,7 +163,7 @@ static int alloc_from_top_down_no_space_above_check(void)
        min_addr = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2;
 
        /* No space above this address */
-       memblock_reserve(min_addr, r2_size);
+       memblock_reserve_kern(min_addr, r2_size);
 
        allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr);
 
@@ -199,7 +199,7 @@ static int alloc_from_top_down_min_addr_cap_check(void)
        start_addr = (phys_addr_t)memblock_start_of_DRAM();
        min_addr = start_addr - SMP_CACHE_BYTES * 3;
 
-       memblock_reserve(start_addr + r1_size, MEM_SIZE - r1_size);
+       memblock_reserve_kern(start_addr + r1_size, MEM_SIZE - r1_size);
 
        allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr);
 
index 49bb416d34ffcc3bbf5578fec3504bccb24f0719..562e4701b0e0265ce169deb0e426e6dabab84420 100644 (file)
@@ -324,7 +324,7 @@ static int alloc_nid_min_reserved_generic_check(void)
        min_addr = max_addr - r2_size;
        reserved_base = min_addr - r1_size;
 
-       memblock_reserve(reserved_base, r1_size);
+       memblock_reserve_kern(reserved_base, r1_size);
 
        allocated_ptr = run_memblock_alloc_nid(r2_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,
@@ -374,7 +374,7 @@ static int alloc_nid_max_reserved_generic_check(void)
        max_addr = memblock_end_of_DRAM() - r1_size;
        min_addr = max_addr - r2_size;
 
-       memblock_reserve(max_addr, r1_size);
+       memblock_reserve_kern(max_addr, r1_size);
 
        allocated_ptr = run_memblock_alloc_nid(r2_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,
@@ -436,8 +436,8 @@ static int alloc_nid_top_down_reserved_with_space_check(void)
        min_addr = r2.base + r2.size;
        max_addr = r1.base;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc_nid(r3_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,
@@ -499,8 +499,8 @@ static int alloc_nid_reserved_full_merge_generic_check(void)
        min_addr = r2.base + r2.size;
        max_addr = r1.base;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc_nid(r3_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,
@@ -563,8 +563,8 @@ static int alloc_nid_top_down_reserved_no_space_check(void)
        min_addr = r2.base + r2.size;
        max_addr = r1.base;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc_nid(r3_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,
@@ -909,8 +909,8 @@ static int alloc_nid_bottom_up_reserved_with_space_check(void)
        min_addr = r2.base + r2.size;
        max_addr = r1.base;
 
-       memblock_reserve(r1.base, r1.size);
-       memblock_reserve(r2.base, r2.size);
+       memblock_reserve_kern(r1.base, r1.size);
+       memblock_reserve_kern(r2.base, r2.size);
 
        allocated_ptr = run_memblock_alloc_nid(r3_size, SMP_CACHE_BYTES,
                                               min_addr, max_addr,