diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-30 13:31:32 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-30 13:31:32 +0100 |
| commit | 39f8a23bd04b6250554f555bdf13e1415fcdcac7 (patch) | |
| tree | d7f11ff47ae54ab0f3cac44f4f0b8f102773fef6 | |
| parent | 702c3cf3d279d701e1596cc90b71893b339bec56 (diff) | |
| parent | 5221d141653a83a974595b4e5eedd9d6983ab270 (diff) | |
| download | linux-next-39f8a23bd04b6250554f555bdf13e1415fcdcac7.tar.gz linux-next-39f8a23bd04b6250554f555bdf13e1415fcdcac7.zip | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
# Conflicts:
# mm/memblock.c
# mm/mm_init.c
45 files changed, 1872 insertions, 356 deletions
diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst index 5d720d2a415e..23fb737ac969 100644 --- a/Documentation/PCI/index.rst +++ b/Documentation/PCI/index.rst @@ -20,3 +20,4 @@ PCI Bus Subsystem controller/index boot-interrupts tph + liveupdate diff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst new file mode 100644 index 000000000000..96b1d7f5df3a --- /dev/null +++ b/Documentation/PCI/liveupdate.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=========================== +PCI Support for Live Update +=========================== + +.. kernel-doc:: drivers/pci/liveupdate.c + :doc: PCI Live Update + +Driver API +========== + +.. kernel-doc:: drivers/pci/liveupdate.c + :export: + +Internal API +============ + +.. kernel-doc:: drivers/pci/liveupdate.c + :internal: + +Live Update ABI +=============== + +.. kernel-doc:: include/linux/kho/abi/pci.h + :doc: PCI File-Lifecycle Bound (FLB) Live Update ABI + +.. kernel-doc:: include/linux/kho/abi/pci.h + :internal: + +See Also +======== + + * :doc:`/core-api/liveupdate` + * :doc:`/core-api/kho/index` diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 7881b2a3c82f..596866ff39cf 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3020,11 +3020,12 @@ Kernel parameters "0" | "off" | "n" - kexec handover is disabled "1" | "on" | "y" - kexec handover is enabled - kho_scratch= [KEXEC,EARLY] + kho_bootmem= [KEXEC,EARLY] Format: ll[KMG],mm[KMG],nn[KMG] | nn% - Defines the size of the KHO scratch region. The KHO - scratch regions are physically contiguous memory - ranges that can only be used for non-kernel + + Defines the size of the KHO boot memory region. The + KHO boot memory regions are physically contiguous + memory ranges that can only be used for non-kernel allocations. That way, even when memory is heavily fragmented with handed over memory, the kexeced kernel will always have enough contiguous ranges to @@ -3033,10 +3034,10 @@ Kernel parameters It is possible to specify the exact amount of memory in the form of "ll[KMG],mm[KMG],nn[KMG]" where the first parameter defines the size of a low - memory scratch area, the second parameter defines - the size of a global scratch area and the third + memory bootmem area, the second parameter defines + the size of a global bootmem area and the third parameter defines the size of additional per-node - scratch areas. The form "nn%" defines scale factor + bootmem areas. The form "nn%" defines scale factor (in percents) of memory that was used during boot. kmac= [MIPS] Korina ethernet MAC address. @@ -5091,7 +5092,12 @@ Kernel parameters explicitly which ones they are. assign-busses [X86] Always assign all PCI bus numbers ourselves, overriding - whatever the firmware may have done. + whatever the firmware may have done. Ignored + if any device was preserved across a Live + Update, where the kernel must preserve the + PCI topology (including bus numbers) to + avoid interrupting ongoing memory transactions + of preserved devices. usepirqmask [X86] Honor the possible IRQ mask stored in the BIOS $PIR table. This is needed on some systems with broken BIOSes, notably diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst index 2c26e560bd78..902d171a780a 100644 --- a/Documentation/admin-guide/mm/kho.rst +++ b/Documentation/admin-guide/mm/kho.rst @@ -20,10 +20,10 @@ need to enable if you would like to preserve their respective state across kexec. To use KHO, please boot the kernel with the ``kho=on`` command line -parameter. You may use ``kho_scratch`` parameter to define size of the -scratch regions. For example ``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. +parameter. You may use ``kho_bootmem`` parameter to define size of the +boot memory regions. For example ``kho_bootmem=16M,512M,256M`` will reserve a +16 MiB low memory bootmem area, a 512 MiB global bootmem region, and 256 MiB +per NUMA node bootmem regions on boot. Perform a KHO kexec =================== @@ -96,15 +96,15 @@ stabilized. current KHO state in this file. Kexec user space tooling can use this as input file for the KHO payload image. -``/sys/kernel/debug/kho/out/scratch_len`` - Lengths of KHO scratch regions, which are physically contiguous +``/sys/kernel/debug/kho/out/bootmem_len`` + Lengths of KHO bootmem regions, which are physically contiguous memory regions that will always stay available for future kexec allocations. Kexec user space tools can use this file to determine where it should place its payload images. -``/sys/kernel/debug/kho/out/scratch_phys`` - Physical locations of KHO scratch regions. Kexec user space tools - can use this file in conjunction to scratch_phys to determine where +``/sys/kernel/debug/kho/out/bootmem_phys`` + Physical locations of KHO bootmem regions. Kexec user space tools + can use this file in conjunction to bootmem_len to determine where it should place its payload images. ``/sys/kernel/debug/kho/out/sub_fdts/`` diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst index 320914a42178..e78a2bbe2be1 100644 --- a/Documentation/core-api/kho/index.rst +++ b/Documentation/core-api/kho/index.rst @@ -13,8 +13,8 @@ Kexec HandOver (KHO) is a mechanism that allows Linux to preserve memory regions, which could contain serialized system states, across kexec. KHO uses :ref:`flattened device tree (FDT) <kho_fdt>` to pass information about -the preserved state from pre-exec kernel to post-kexec kernel and :ref:`scratch -memory regions <kho_scratch>` to ensure integrity of the preserved memory. +the preserved state from pre-exec kernel to post-kexec kernel and :ref:`boot +memory regions <kho_bootmem>` to ensure integrity of the preserved memory. .. _kho_fdt: @@ -40,31 +40,31 @@ and post-kexec kernels. This ABI is defined by header files in abi.rst -.. _kho_scratch: +.. _kho_bootmem: -Scratch Regions -=============== +Boot Memory Regions +=================== To boot into kexec, we need to have a physically contiguous memory range that contains no handed over memory. Kexec then places the target kernel and initrd into that region. The new kernel exclusively uses this region for memory allocations before during boot up to the initialization of the page allocator. -We guarantee that we always have such regions through the scratch regions: On -first boot KHO allocates several physically contiguous memory regions. Since +We guarantee that we always have such regions through the boot memory regions: +On first boot KHO allocates several physically contiguous memory regions. Since after kexec these regions will be used by early memory allocations, there is a -scratch region per NUMA node plus a scratch region to satisfy allocations -requests that do not require particular NUMA node assignment. -By default, size of the scratch region is calculated based on amount of memory -allocated during boot. The ``kho_scratch`` kernel command line option may be -used to explicitly define size of the scratch regions. -The scratch regions are declared as CMA when page allocator is initialized so -that their memory can be used during system lifetime. CMA gives us the -guarantee that no handover pages land in that region, because handover pages -must be at a static physical memory location and CMA enforces that only -movable pages can be located inside. - -After KHO kexec, we ignore the ``kho_scratch`` kernel command line option and +boot memory region per NUMA node plus a boot memory region to satisfy +allocations requests that do not require particular NUMA node assignment. By +default, size of the boot memory region is calculated based on amount of memory +allocated during boot. The ``kho_bootmem`` kernel command line option may be +used to explicitly define size of the boot memory regions. The boot memory +regions are declared as CMA when page allocator is initialized so that their +memory can be used during system lifetime. CMA gives us the guarantee that no +handover pages land in that region, because handover pages must be at a static +physical memory location and CMA enforces that only movable pages can be located +inside. + +After KHO kexec, we ignore the ``kho_bootmem`` kernel command line option and instead reuse the exact same region that was originally allocated. This allows us to recursively execute any amount of KHO kexecs. Because we used this region for boot memory allocations and as target memory for kexec blobs, some parts diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst index 5a292d0f3706..2bce2644eba2 100644 --- a/Documentation/core-api/liveupdate.rst +++ b/Documentation/core-api/liveupdate.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _luo: + ======================== Live Update Orchestrator ======================== @@ -18,6 +20,8 @@ LUO Preserving File Descriptors .. kernel-doc:: kernel/liveupdate/luo_file.c :doc: LUO File Descriptors +.. _flb: + LUO File Lifecycle Bound Global Data ==================================== .. kernel-doc:: kernel/liveupdate/luo_flb.c @@ -70,3 +74,4 @@ See Also - :doc:`Live Update uAPI </userspace-api/liveupdate>` - :doc:`/core-api/kho/index` +- :doc:`PCI </PCI/liveupdate>` diff --git a/MAINTAINERS b/MAINTAINERS index d4a65bd29a4a..32324114b29c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21292,6 +21292,21 @@ L: linux-pci@vger.kernel.org S: Supported F: Documentation/PCI/pci-error-recovery.rst +PCI LIVE UPDATE +M: David Matlack <dmatlack@google.com> +R: Pasha Tatashin <pasha.tatashin@soleen.com> +R: Mike Rapoport <rppt@kernel.org> +R: Pratyush Yadav <pratyush@kernel.org> +L: kexec@lists.infradead.org +L: linux-pci@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git +F: Documentation/PCI/liveupdate.rst +F: drivers/pci/liveupdate.c +F: drivers/pci/liveupdate.h +F: include/linux/kho/abi/pci.h +F: include/linux/pci_liveupdate.h + PCI MSI DRIVER FOR ALTERA MSI IP L: linux-pci@vger.kernel.org S: Orphan diff --git a/arch/x86/boot/compressed/kaslr.c b/arch/x86/boot/compressed/kaslr.c index 22267a83e064..290153818d0f 100644 --- a/arch/x86/boot/compressed/kaslr.c +++ b/arch/x86/boot/compressed/kaslr.c @@ -764,12 +764,12 @@ static void process_e820_entries(unsigned long minimum, } /* - * If KHO is active, only process its scratch areas to ensure we are not + * If KHO is active, only process its bootmem areas to ensure we are not * stepping onto preserved memory. */ static bool process_kho_entries(unsigned long minimum, unsigned long image_size) { - struct kho_scratch *kho_scratch; + struct kho_bootmem *kho_bootmem; struct setup_data *ptr; struct kho_data *kho; int i, nr_areas = 0; @@ -781,8 +781,8 @@ static bool process_kho_entries(unsigned long minimum, unsigned long image_size) while (ptr) { if (ptr->type == SETUP_KEXEC_KHO) { kho = (struct kho_data *)(unsigned long)ptr->data; - kho_scratch = (void *)(unsigned long)kho->scratch_addr; - nr_areas = kho->scratch_size / sizeof(*kho_scratch); + kho_bootmem = (void *)(unsigned long)kho->bootmem_addr; + nr_areas = kho->bootmem_size / sizeof(*kho_bootmem); break; } @@ -793,7 +793,7 @@ static bool process_kho_entries(unsigned long minimum, unsigned long image_size) return false; for (i = 0; i < nr_areas; i++) { - struct kho_scratch *area = &kho_scratch[i]; + struct kho_bootmem *area = &kho_bootmem[i]; struct mem_vector region = { .start = area->addr, .size = area->size, @@ -822,7 +822,7 @@ static unsigned long find_random_phys_addr(unsigned long minimum, } /* - * During kexec handover only process KHO scratch areas that are known + * During kexec handover only process KHO bootmem areas that are known * not to contain any data that must be preserved. */ if (!process_kho_entries(minimum, image_size) && diff --git a/arch/x86/include/uapi/asm/setup_data.h b/arch/x86/include/uapi/asm/setup_data.h index 2671c4e1b3a0..61090db5065c 100644 --- a/arch/x86/include/uapi/asm/setup_data.h +++ b/arch/x86/include/uapi/asm/setup_data.h @@ -85,8 +85,8 @@ struct ima_setup_data { struct kho_data { __u64 fdt_addr; __u64 fdt_size; - __u64 scratch_addr; - __u64 scratch_size; + __u64 bootmem_addr; + __u64 bootmem_size; } __attribute__((packed)); #endif /* __ASSEMBLER__ */ diff --git a/arch/x86/kernel/e820.c b/arch/x86/kernel/e820.c index eb72537bc0b1..0967b485f38b 100644 --- a/arch/x86/kernel/e820.c +++ b/arch/x86/kernel/e820.c @@ -1352,18 +1352,17 @@ __init void e820__memblock_setup(void) * below 1M (aka ISA_END_ADDRESS) up until direct map is completely set * up in init_mem_mapping(). * - * KHO kernels are special and use only scratch memory for memblock + * KHO kernels are special and use only NOPRSRV memory for memblock * allocations, but memory below 1M is ignored by kernel after early - * boot and cannot be naturally marked as scratch. + * boot and cannot be naturally marked as NOPRSRV. * * To allow allocation of the real-mode trampoline and a few (if any) * other very early allocations from below 1M forcibly mark the memory - * below 1M as scratch. + * below 1M as NOPRSRV. * - * After real mode trampoline is allocated, we clear that scratch - * marking. + * After real mode trampoline is allocated, we clear that marking. */ - memblock_mark_kho_scratch(0, SZ_1M); + memblock_mark_kho_noprsrv(0, SZ_1M); /* * 32-bit systems are limited to 4BG of memory even with HIGHMEM and diff --git a/arch/x86/kernel/kexec-bzimage64.c b/arch/x86/kernel/kexec-bzimage64.c index 7e980ea49d8d..3aa77006adab 100644 --- a/arch/x86/kernel/kexec-bzimage64.c +++ b/arch/x86/kernel/kexec-bzimage64.c @@ -285,14 +285,14 @@ static void setup_kho(const struct kimage *image, struct boot_params *params, sd->len = sizeof(struct kho_data); /* Only add if we have all KHO images in place */ - if (!image->kho.fdt || !image->kho.scratch) + if (!image->kho.fdt || !image->kho.bootmem) return; /* Add setup data */ kho->fdt_addr = image->kho.fdt; kho->fdt_size = PAGE_SIZE; - kho->scratch_addr = image->kho.scratch->mem; - kho->scratch_size = image->kho.scratch->bufsz; + kho->bootmem_addr = image->kho.bootmem->mem; + kho->bootmem_size = image->kho.bootmem->bufsz; sd->next = params->hdr.setup_data; params->hdr.setup_data = params_load_addr + setup_data_offset; } diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index cda6adb9f69c..19fc8e1714e6 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -472,7 +472,7 @@ static void __init add_kho(u64 phys_addr, u32 data_len) return; } - kho_populate(kho->fdt_addr, kho->fdt_size, kho->scratch_addr, kho->scratch_size); + kho_populate(kho->fdt_addr, kho->fdt_size, kho->bootmem_addr, kho->bootmem_size); early_memunmap(kho, size); } diff --git a/arch/x86/realmode/init.c b/arch/x86/realmode/init.c index 694d80a5c68e..65110b2ee1a1 100644 --- a/arch/x86/realmode/init.c +++ b/arch/x86/realmode/init.c @@ -66,7 +66,7 @@ void __init reserve_real_mode(void) */ memblock_reserve(0, SZ_1M); - memblock_clear_kho_scratch(0, SZ_1M); + memblock_clear_kho_noprsrv(0, SZ_1M); } static void __init sme_sev_setup_real_mode(struct trampoline_header *th) diff --git a/drivers/firmware/efi/efi-init.c b/drivers/firmware/efi/efi-init.c index 6103b1a082d2..9f09b0d2daa0 100644 --- a/drivers/firmware/efi/efi-init.c +++ b/drivers/firmware/efi/efi-init.c @@ -165,10 +165,10 @@ static __init void reserve_regions(void) pr_info("Processing EFI memory map:\n"); /* - * Discard memblocks discovered so far except for KHO scratch + * Discard memblocks discovered so far except for KHO bootmem * regions. Most memblocks at this point originate from memory nodes * in the DT and UEFI uses its own memory map instead. However, if - * KHO is enabled, scratch regions, which are good known memory + * KHO is enabled, bootmem regions, which are good known memory * must be preserved. */ memblock_dump_all(); @@ -178,7 +178,7 @@ static __init void reserve_regions(void) /* Remove all non-KHO regions */ for_each_mem_region(r) { - if (!memblock_is_kho_scratch(r)) { + if (!memblock_is_kho_noprsrv(r)) { memblock_remove(r->base, r->size); r--; } diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index a64afc3ded3d..ecc923102622 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -929,7 +929,7 @@ void __init early_init_dt_check_for_usable_mem_range(void) static void __init early_init_dt_check_kho(void) { unsigned long node = chosen_node_offset; - u64 fdt_start, fdt_size, scratch_start, scratch_size; + u64 fdt_start, fdt_size, bootmem_start, bootmem_size; if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER) || (long)node < 0) return; @@ -938,11 +938,11 @@ static void __init early_init_dt_check_kho(void) &fdt_start, &fdt_size)) return; - if (!of_flat_dt_get_addr_size(node, "linux,kho-scratch", - &scratch_start, &scratch_size)) + if (!of_flat_dt_get_addr_size(node, "linux,kho-bootmem", + &bootmem_start, &bootmem_size)) return; - kho_populate(fdt_start, fdt_size, scratch_start, scratch_size); + kho_populate(fdt_start, fdt_size, bootmem_start, bootmem_size); } #ifdef CONFIG_SERIAL_EARLYCON diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c index 029903b986cb..bb4e0d9bfd38 100644 --- a/drivers/of/kexec.c +++ b/drivers/of/kexec.c @@ -261,23 +261,23 @@ static int kho_add_chosen(const struct kimage *image, void *fdt, int chosen_node #ifdef CONFIG_KEXEC_HANDOVER phys_addr_t fdt_mem = 0; phys_addr_t fdt_len = 0; - phys_addr_t scratch_mem = 0; - phys_addr_t scratch_len = 0; + phys_addr_t bootmem_arr = 0; + phys_addr_t bootmem_len = 0; ret = fdt_delprop(fdt, chosen_node, "linux,kho-fdt"); if (ret && ret != -FDT_ERR_NOTFOUND) return ret; - ret = fdt_delprop(fdt, chosen_node, "linux,kho-scratch"); + ret = fdt_delprop(fdt, chosen_node, "linux,kho-bootmem"); if (ret && ret != -FDT_ERR_NOTFOUND) return ret; - if (!image->kho.fdt || !image->kho.scratch) + if (!image->kho.fdt || !image->kho.bootmem) return 0; fdt_mem = image->kho.fdt; fdt_len = PAGE_SIZE; - scratch_mem = image->kho.scratch->mem; - scratch_len = image->kho.scratch->bufsz; + bootmem_arr = image->kho.bootmem->mem; + bootmem_len = image->kho.bootmem->bufsz; pr_debug("Adding kho metadata to DT"); @@ -285,8 +285,8 @@ static int kho_add_chosen(const struct kimage *image, void *fdt, int chosen_node fdt_mem, fdt_len); if (ret) return ret; - ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-scratch", - scratch_mem, scratch_len); + ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-bootmem", + bootmem_arr, bootmem_len); #endif /* CONFIG_KEXEC_HANDOVER */ return ret; diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 0c7408509ba2..16fbd4212e0f 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -271,6 +271,21 @@ config VGA_ARB_MAX_GPUS Reserves space in the kernel to maintain resource locking for multiple GPUS. The overhead for each GPU is very small. +config PCI_LIVEUPDATE + bool "PCI Live Update Support" + depends on PCI && LIVEUPDATE && 64BIT && !CARDBUS + help + Enable PCI core support for preserving PCI devices across Live + Update. This, in combination with support in a device's driver, + enables PCI devices to run and perform memory transactions + uninterrupted during a kexec for Live Update. + + This option should only be enabled by users who plan to use Live + Update for kernel upgrades and require preserving PCI devices during + those upgrades. + + If unsure, say N. + source "drivers/pci/hotplug/Kconfig" source "drivers/pci/controller/Kconfig" source "drivers/pci/endpoint/Kconfig" diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 41ebc3b9a518..e8d003cb6757 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSFS) += pci-sysfs.o slot.o obj-$(CONFIG_ACPI) += pci-acpi.o obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o +obj-$(CONFIG_PCI_LIVEUPDATE) += liveupdate.o endif obj-$(CONFIG_OF) += of.o diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c new file mode 100644 index 000000000000..51695782ed42 --- /dev/null +++ b/drivers/pci/liveupdate.c @@ -0,0 +1,1015 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2026, Google LLC. + * David Matlack <dmatlack@google.com> + */ + +/** + * DOC: PCI Live Update + * + * The PCI subsystem participates in the Live Update process to enable drivers + * to preserve their PCI devices across kexec. + * + * Preserving a device requires preserving two independent sets of state: the + * driver's own state, which the driver preserves with no involvement from the + * PCI core, and the PCI core's state about the device, which the next kernel + * needs so that enumeration does not disturb a device that is still running. + * This file implements the latter. + * + * :ref:`FLB <flb>` Data + * ===================== + * + * Userspace decides which devices are preserved, using :ref:`LUO <luo>` file + * preservation: a driver exposes a file that represents a single PCI device, + * and userspace preserves the device with + * ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)`` on that file. Binding preservation + * to a file gives it proper lifecycle management, e.g. the preservation is + * undone if userspace cancels it or goes away. How a driver exposes that file + * is up to the driver and invisible to the PCI core (vfio-pci variant drivers, + * the first intended use-case, use their per-device cdev). + * + * LUO only knows that a file was preserved; it does not know that the file + * represents a PCI device. Drivers therefore register their + * struct liveupdate_file_handler with the PCI core: + * + * * ``pci_liveupdate_register_flb(driver_file_handler)`` + * * ``pci_liveupdate_unregister_flb(driver_file_handler)`` + * + * LUO then refcounts the PCI core's FLB against the files preserved by that + * handler, and that refcount drives the lifetime of struct pci_ser: + * pci_flb_preserve() allocates and preserves it when the first file is + * preserved, and pci_flb_unpreserve() frees it when the last file is + * unpreserved. In the next kernel, pci_flb_retrieve() hands the PCI core the + * struct pci_ser built by the previous kernel, whenever the PCI core asks for + * it (e.g. during enumeration), and pci_flb_finish() frees it once the PCI + * core is done with it. + * + * State handed over by the previous kernel is trusted. The PCI core validates + * it only far enough to detect an incompatible or corrupt hand over, and makes + * no attempt to defend against deliberate modification, since a previous kernel + * able to corrupt preserved state is able to corrupt arbitrary memory anyway. + * + * Call Flow + * --------- + * + * :: + * + * # Driver initialization + * pci_liveupdate_register_flb(fh) + * + * # Userspace: ioctl(LIVEUPDATE_SESSION_PRESERVE_FD, devfd) + * luo_preserve_file() + * luo_flb_file_preserve() + * luo_flb_file_preserve_one() # first preserved file only + * pci_flb_preserve() # alloc and preserve struct pci_ser + * fh->ops->preserve() # driver callback + * pci_liveupdate_preserve(dev) # record this device in struct pci_ser + * + * # Userspace: preservation cancelled or session torn down + * luo_file_unpreserve_files() + * luo_flb_file_unpreserve() + * liveupdate_flb_put_outgoing() # last unpreserved file only + * pci_flb_unpreserve() # free struct pci_ser + * + * # ---------------- kexec ---------------- + * + * # New kernel: PCI enumeration + * pci_setup_device() + * pci_liveupdate_setup_device() + * liveupdate_flb_get_incoming() + * luo_flb_retrieve_one() # first request only + * pci_flb_retrieve() # previous kernel's struct pci_ser + * + * # Userspace: ioctl(LIVEUPDATE_SESSION_FINISH) + * luo_file_finish_one() + * fh->ops->finish() # driver callback + * pci_liveupdate_finish(dev) # release this device's pci_dev_ser + * luo_flb_file_finish() + * liveupdate_flb_put_incoming() # last incoming file only + * pci_flb_finish() # free struct pci_ser + * + * Device Tracking + * =============== + * + * Drivers must notify the PCI core when specific devices are preserved or + * unpreserved with the following APIs: + * + * * ``pci_liveupdate_preserve(pci_dev)`` + * * ``pci_liveupdate_unpreserve(pci_dev)`` + * + * This allows the PCI core to keep its FLB data (struct pci_ser) up to date + * with the list of **outgoing** preserved devices for the next kernel. + * + * After kexec, whenever a device is enumerated, the PCI core will check if it + * is an **incoming** preserved device (i.e. preserved by the previous kernel) + * by checking the incoming FLB data (struct pci_ser). + * + * Drivers must notify the PCI core when an **incoming** device is done + * participating in the incoming Live Update with the following API: + * + * * ``pci_liveupdate_finish(pci_dev)`` + * + * The PCI core does not enforce any ordering of ``pci_liveupdate_finish()`` and + * ``pci_liveupdate_preserve()``, i.e., a PCI device can be **outgoing** + * (preserved for next kernel) and **incoming** (preserved by previous kernel) + * at the same time. + * + * Restrictions + * ============ + * + * The PCI core enforces the following restrictions on which devices can be + * preserved. These may be relaxed in the future: + * + * * The device cannot be a Virtual Function (VF). + * + * * The device cannot require device-specific quirks to enable Access + * Control Services (ACS). + * + * Driver Binding + * ============== + * + * In the outgoing kernel, the driver must ensure that it does not release a + * device between pci_liveupdate_preserve() and pci_liveupdate_unpreserve(). + * + * In the incoming kernel, the driver must ensure that it does not release a + * preserved device between probe() and pci_liveupdate_finish(). + * + * It is the user's responsibility to ensure that incoming preserved devices are + * bound to the correct driver. The PCI core does not protect against a device + * getting preserved by driver A in the outgoing kernel and then getting bound + * to driver B in the incoming kernel. + * + * PCI-to-PCI Bridges + * ================== + * + * Any PCI-to-PCI bridges upstream of a preserved device are automatically + * preserved when the device is preserved. The PCI core keeps track of the + * number of downstream devices that are preserved under a bridge so that the + * bridge is only unpreserved once all downstream devices are unpreserved. + * + * This enables the PCI core and any drivers bound to the bridge to participate + * in the Live Update so that preserved endpoints can continue issuing memory + * transactions during the Live Update. + * + * BDF Stability + * ============= + * + * The PCI core guarantees that preserved devices can be identified by the same + * bus, device, and function numbers for as long as they are preserved + * (including across kexec). To accomplish this, the PCI core keeps the + * secondary and subordinate bus numbers that the previous kernel programmed + * into bridges, if the previous kernel preserved any device. This is true even + * on architectures that always assign new bus numbers during scanning. The + * kernel assumes the previous kernel established a sane bus topology across + * kexec. + * + * Bridges that do not have bus numbers are assigned new ones as usual, so + * hot-adding a bridge keeps working, both during and after a Live Update. The + * two-pass bridge scan ensures such bridges are only assigned bus numbers above + * those already claimed by preserved bridges. + * + * If a preserved bridge comes up without a valid bus number configuration, e.g. + * because it was reset during kexec, the PCI core refuses to assign it new bus + * numbers and does not enumerate anything below it. Assigning new bus numbers + * would silently change the BDF of every preserved device in its hierarchy. The + * PCI core also stops assigning bus numbers to the other bridges on the same + * bus, since the bus numbers of the failed bridge can no longer be read from + * hardware and handing them to another bridge would let an unrelated device + * inherit the BDF of a preserved device. + * + * Handling Preserved Devices + * ========================== + * + * The PCI core treats preserved devices differently than non-preserved devices. + * This section enumerates those differences. + * + * * The PCI core adopts all ACS controls enabled on incoming preserved devices + * rather than assigning new ones. This ensures that TLPs are routed the same + * way after Live Update and ensures that IOMMU groups do not change. Note + * that a device will use its adopted ACS controls for the lifetime of its + * struct pci_dev (i.e. even after pci_liveupdate_finish()). + * + * * The PCI core adopts ARI Forwarding Enable on all bridges with downstream + * preserved devices to ensure that all preserved devices on the bridge's + * secondary bus are addressable after the Live Update. + * + * * The PCI core does not disable bus mastering on outgoing preserved devices + * during kexec. This allows preserved devices to issue memory transactions + * throughout the Live Update. + */ + +#define pr_fmt(fmt) "PCI: liveupdate: " fmt + +#include <linux/io.h> +#include <linux/kexec_handover.h> +#include <linux/kho/abi/pci.h> +#include <linux/kho_block.h> +#include <linux/liveupdate.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/slab.h> + +#include "liveupdate.h" +#include "pci.h" + +/** + * struct pci_liveupdate_global - Global state for PCI Live Update support + * @rwsem: Reader/writer semaphore used to protect the incoming and outgoing + * FLBs, and the references to them in struct pci_dev. + * @had_incoming: True if the previous kernel preserved at least one PCI device. + * Set when the incoming FLB is retrieved and never cleared, so + * it stays true after Live Update finishes. + */ +struct pci_liveupdate_global { + struct rw_semaphore rwsem; + bool had_incoming; +}; + +static struct pci_liveupdate_global pci_liveupdate = { + .rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem), +}; + +/** + * struct pci_flb_outgoing - Outgoing PCI FLB object + * @ser: Pointer to the preserved struct pci_ser. + * @block_set: The KHO block set holding the outgoing devices. + * + * This structure holds the runtime state for the outgoing PCI Live Update + * state. It wraps the serialized pci_ser and the block_set used to manage + * the serialized entries. + */ +struct pci_flb_outgoing { + struct pci_ser *ser; + struct kho_block_set block_set; +}; + +/** + * struct pci_flb_incoming - Incoming PCI FLB object + * @ser: The incoming struct pci_ser from the previous kernel. + * @xa: Xarray used to quickly lookup devices in @ser. + * @block_set: The KHO block set holding the incoming devices. + * + * This structure holds the runtime state for the incoming PCI Live Update + * state. It wraps the serialized pci_ser, the block_set used to restore + * the serialized entries, and an xarray for fast lookups. + */ +struct pci_flb_incoming { + struct pci_ser *ser; + struct xarray xa; + struct kho_block_set block_set; +}; + +static unsigned long pci_ser_xa_key(u32 domain, u16 bdf) +{ + return (unsigned long)domain << 16 | bdf; +} + +static int pci_flb_preserve(struct liveupdate_flb_op_args *args) +{ + struct pci_flb_outgoing *outgoing __free(kfree) = NULL; + struct pci_ser *ser; + + outgoing = kzalloc_obj(*outgoing); + if (!outgoing) + return -ENOMEM; + + ser = kho_alloc_preserve(sizeof(*ser)); + if (IS_ERR(ser)) + return PTR_ERR(ser); + + ser->version = PCI_LUO_FLB_VERSION; + ser->nr_devices = 0; + ser->devices = 0; + + outgoing->ser = ser; + kho_block_set_init(&outgoing->block_set, sizeof(struct pci_dev_ser)); + + args->obj = no_free_ptr(outgoing); + args->data = virt_to_phys(ser); + pr_debug("Preserved struct pci_ser (0x%llx)\n", args->data); + return 0; +} + +static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args) +{ + struct pci_flb_outgoing *outgoing = args->obj; + + pr_debug("Unpreserving struct pci_ser (0x%llx)\n", args->data); + + WARN_ON(outgoing->ser->nr_devices); + kho_block_set_destroy(&outgoing->block_set); + kho_unpreserve_free(outgoing->ser); + kfree(outgoing); +} + +/* + * Any failure here is fatal. The previous kernel handed over devices that are + * still performing DMA, and this kernel cannot identify them without this + * state. Continuing would let the PCI core reassign bus numbers and rebind + * drivers underneath live devices, so fail loudly instead of unwinding. + */ +static int pci_flb_retrieve(struct liveupdate_flb_op_args *args) +{ + struct pci_ser *ser = phys_to_virt(args->data); + struct pci_flb_incoming *incoming; + struct pci_dev_ser *dev_ser; + struct kho_block_set_it it; + int ret; + + pr_debug("Retrieving struct pci_ser (0x%llx)\n", args->data); + + if (ser->version != PCI_LUO_FLB_VERSION) + panic("Incoming PCI FLB version (v%d) is incompatible with this kernel (v%d)\n", + ser->version, PCI_LUO_FLB_VERSION); + + incoming = kzalloc_obj(*incoming); + if (!incoming) + panic("Failed to allocate struct pci_flb_incoming\n"); + + incoming->ser = ser; + xa_init(&incoming->xa); + + kho_block_set_init(&incoming->block_set, sizeof(struct pci_dev_ser)); + ret = kho_block_set_restore(&incoming->block_set, ser->devices); + if (ret) + panic("Failed to restore devices KHO block set (%d)\n", ret); + + kho_block_set_it_init(&it, &incoming->block_set); + while ((dev_ser = kho_block_set_it_read_entry(&it))) { + unsigned long key; + + if (!dev_ser->refcount) + continue; + + key = pci_ser_xa_key(dev_ser->domain, dev_ser->bdf); + ret = xa_insert(&incoming->xa, key, dev_ser, GFP_KERNEL); + if (ret) + panic("Failed to insert PCI device %04x:%02x:%02x.%d into xarray (%d)\n", + dev_ser->domain, PCI_BUS_NUM(dev_ser->bdf), + PCI_SLOT(dev_ser->bdf), PCI_FUNC(dev_ser->bdf), + ret); + } + + /* + * Remember that the previous kernel preserved devices for the lifetime + * of this kernel, even after Live Update finishes and the incoming FLB + * is freed. See pci_liveupdate_preserve_bus_numbers(). + */ + if (!xa_empty(&incoming->xa)) + pci_liveupdate.had_incoming = true; + + args->obj = incoming; + return 0; +} + +static void pci_check_all_devices_finished(struct pci_flb_incoming *incoming) +{ + struct pci_dev_ser *dev_ser; + unsigned long index; + u32 nr_devices; + + /* + * nr_devices is only decremented by pci_liveupdate_finish_device(). + * This runs once the last reference to the incoming FLB is dropped, so + * there are no finishers left in flight. + */ + nr_devices = incoming->ser->nr_devices; + if (nr_devices == 0) + return; + + /* + * Report the unfinished devices from the incoming FLB rather than by + * walking struct pci_dev, so that devices that never showed up after + * kexec, or that were destroyed before they finished, are identified + * as well. + */ + xa_for_each(&incoming->xa, index, dev_ser) { + if (!dev_ser->refcount) + continue; + + pr_emerg("%04x:%02x:%02x.%d was never finished!\n", + dev_ser->domain, PCI_BUS_NUM(dev_ser->bdf), + PCI_SLOT(dev_ser->bdf), PCI_FUNC(dev_ser->bdf)); + } + + /* + * This should only happen if a driver violated the contract to call + * pci_liveupdate_finish() (something is extremely broken). + */ + panic("%u preserved device(s) were never finished!\n", nr_devices); +} + +static void pci_flb_finish(struct liveupdate_flb_op_args *args) +{ + struct pci_flb_incoming *incoming = args->obj; + + pr_debug("Finished struct pci_ser (0x%llx)\n", args->data); + pci_check_all_devices_finished(incoming); + + xa_destroy(&incoming->xa); + kho_block_set_destroy(&incoming->block_set); + kho_restore_free(incoming->ser); + kfree(incoming); +} + +static struct liveupdate_flb_ops pci_liveupdate_flb_ops = { + .preserve = pci_flb_preserve, + .unpreserve = pci_flb_unpreserve, + .retrieve = pci_flb_retrieve, + .finish = pci_flb_finish, + .owner = THIS_MODULE, +}; + +static struct liveupdate_flb pci_liveupdate_flb = { + .ops = &pci_liveupdate_flb_ops, + .compatible = PCI_LUO_FLB_COMPATIBLE, +}; + +static void pci_liveupdate_flb_put_outgoing(void) +{ + liveupdate_flb_put_outgoing(&pci_liveupdate_flb); +} + +static struct pci_flb_outgoing *pci_liveupdate_flb_get_outgoing(void) +{ + struct pci_flb_outgoing *outgoing = NULL; + int ret; + + ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing); + if (ret) + return ERR_PTR(ret); + + if (!outgoing) + return ERR_PTR(-ENOENT); + + return outgoing; +} + +static struct pci_dev_ser *pci_flb_alloc_dev_ser(struct pci_flb_outgoing *outgoing) +{ + struct pci_dev_ser *dev_ser; + struct kho_block_set_it it; + u64 count = 0; + int err; + + kho_block_set_it_init(&it, &outgoing->block_set); + + /* Try to find an existing, previously unpreserved, entry. */ + while ((dev_ser = kho_block_set_it_read_entry(&it))) { + if (!dev_ser->refcount) + return dev_ser; + + count++; + } + + /* Otherwise grow the block set and reserve a new entry. */ + err = kho_block_set_grow(&outgoing->block_set, count + 1); + if (err) + return ERR_PTR(err); + + if (!count) + kho_block_set_it_init(&it, &outgoing->block_set); + + /* This should always succeed since kho_block_set_grow() succeeded. */ + dev_ser = kho_block_set_it_reserve_entry(&it); + if (WARN_ON_ONCE(!dev_ser)) + return ERR_PTR(-ENOSPC); + + return dev_ser; +} + +static int pci_liveupdate_unpreserve_device(struct pci_flb_outgoing *outgoing, + struct pci_dev *dev) +{ + struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing; + + if (dev->liveupdate.frozen) { + pci_warn(dev, "Cannot unpreserve device after it is frozen!\n"); + return -EINVAL; + } + + if (!dev_ser) { + pci_warn(dev, "Cannot unpreserve device that is not preserved\n"); + return -EINVAL; + } + + if (!dev_ser->refcount) { + pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n"); + return -EINVAL; + } + + if (--dev_ser->refcount) + return 0; + + pci_info(dev, "Device will no longer be preserved across next Live Update\n"); + outgoing->ser->nr_devices--; + memset(dev_ser, 0, sizeof(*dev_ser)); + dev->liveupdate.outgoing = NULL; + return 0; +} + +static void pci_liveupdate_unpreserve_path(struct pci_flb_outgoing *outgoing, + struct pci_dev *dev, + struct pci_dev *end) +{ + for_each_pci_dev_in_path(dev) { + if (dev == end) + break; + + if (pci_liveupdate_unpreserve_device(outgoing, dev)) + return; + } +} + +static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing, + struct pci_dev *dev) +{ + if (dev->is_virtfn) { + pci_warn(dev, "Cannot preserve Virtual Functions\n"); + return -EINVAL; + } + + /* + * Do not preserve devices that rely on device-specific ACS equivalents + * (for now) since that would complicate keeping ACS constant across + * Live Update. + */ + if (pci_need_dev_specific_enable_acs(dev)) { + pci_warn(dev, "Refusing to preserve device that relies on ACS quirks\n"); + return -EINVAL; + } + + /* + * Endpoint devices should not be preserved more than once. + * Bridges are preserved once for every downstream device that + * is preserved. + */ + if (dev->liveupdate.outgoing && !dev->subordinate) { + pci_warn(dev, "Device is already preserved\n"); + return -EBUSY; + } + + if (dev->liveupdate.outgoing && !dev->liveupdate.outgoing->refcount) { + pci_WARN(dev, 1, "Preserved device with 0 refcount!\n"); + return -EINVAL; + } + + if (dev->liveupdate.frozen) { + pci_warn(dev, "Cannot preserve device after it is frozen!\n"); + return -EINVAL; + } + + if (!dev->liveupdate.outgoing) { + struct pci_dev_ser *dev_ser; + + dev_ser = pci_flb_alloc_dev_ser(outgoing); + if (IS_ERR(dev_ser)) + return PTR_ERR(dev_ser); + + pci_info(dev, "Device will be preserved across next Live Update\n"); + outgoing->ser->nr_devices++; + outgoing->ser->devices = kho_block_set_head_pa(&outgoing->block_set); + + dev_ser->domain = pci_domain_nr(dev->bus); + dev_ser->bdf = pci_dev_id(dev); + dev->liveupdate.outgoing = dev_ser; + } + + dev->liveupdate.outgoing->refcount++; + return 0; +} + +/** + * pci_liveupdate_preserve() - Preserve a PCI device across Live Update + * @dev: The PCI device to preserve. + * + * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be + * preserved across the next Live Update. Drivers are expected to call + * pci_liveupdate_preserve() from their struct liveupdate_file_handler + * preserve() callback to ensure the outgoing struct pci_ser is already set up. + * + * pci_liveupdate_preserve() automatically preserves all bridges upstream of + * @dev. + * + * Returns: 0 on success, <0 on failure. + */ +int pci_liveupdate_preserve(struct pci_dev *dev) +{ + struct pci_flb_outgoing *outgoing = NULL; + struct pci_dev *start = dev; + int ret = -ENODEV; + + guard(rwsem_write)(&pci_liveupdate.rwsem); + + outgoing = pci_liveupdate_flb_get_outgoing(); + if (IS_ERR(outgoing)) + return PTR_ERR(outgoing); + + for_each_pci_dev_in_path(dev) { + ret = pci_liveupdate_preserve_device(outgoing, dev); + if (ret) { + pci_liveupdate_unpreserve_path(outgoing, start, dev); + break; + } + } + + pci_liveupdate_flb_put_outgoing(); + return ret; +} +EXPORT_SYMBOL_GPL(pci_liveupdate_preserve); + +/** + * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device + * @dev: The PCI device to unpreserve. + * + * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no + * longer be preserved across the next Live Update. Drivers are expected to call + * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler + * unpreserve() callback to ensure the outgoing struct pci_ser is already set + * up. + * + * pci_liveupdate_unpreserve() automatically unpreserves all bridges upstream of + * @dev. + */ +void pci_liveupdate_unpreserve(struct pci_dev *dev) +{ + struct pci_flb_outgoing *outgoing = NULL; + + guard(rwsem_write)(&pci_liveupdate.rwsem); + + outgoing = pci_liveupdate_flb_get_outgoing(); + if (IS_ERR(outgoing)) { + pci_warn(dev, "Cannot unpreserve device without outgoing Live Update state\n"); + return; + } + + pci_liveupdate_unpreserve_path(outgoing, dev, /*end=*/NULL); + pci_liveupdate_flb_put_outgoing(); +} +EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve); + +static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void) +{ + struct pci_flb_incoming *incoming = NULL; + int ret; + + ret = liveupdate_flb_get_incoming(&pci_liveupdate_flb, (void **)&incoming); + + /* Live Update is not enabled. */ + if (ret == -EOPNOTSUPP) + return NULL; + + /* Live Update is enabled, but there is no incoming FLB data. */ + if (ret == -ENODATA) + return NULL; + + /* + * Live Update is enabled and there is incoming FLB data, but none of it + * matches pci_liveupdate_flb.compatible. + */ + if (ret == -ENOENT) + return NULL; + + /* + * There is incoming FLB data that matches pci_liveupdate_flb.compatible + * but retrieve failed (pci_flb_retrieve() returned an error or LUO + * failed to acquire a reference to pci_liveupdate_flb_ops.owner). + */ + if (ret) + panic("Failed to retrieve incoming FLB data (%d)\n", ret); + + return incoming; +} + +static void pci_liveupdate_flb_put_incoming(void) +{ + liveupdate_flb_put_incoming(&pci_liveupdate_flb); +} + +/** + * pci_liveupdate_preserve_bus_numbers() - Determine if the PCI core should + * preserve bus numbers when scanning + * bridges. + * + * This function is called by the PCI core when it is scanning a bridge. It + * determines whether the PCI core should preserve the secondary and subordinate + * bus numbers that the previous kernel programmed into that bridge, rather than + * assigning new ones. This is necessary to keep RequesterIDs constant for + * preserved devices issuing memory transactions. + * + * Bus numbers are preserved everywhere, and for the lifetime of the kernel, if + * the previous kernel preserved any device. Bus numbers have to be preserved + * above a preserved device anyway, since an upstream bridge cannot expand its + * window. Applying the same policy everywhere matches the scope of + * pcibios_assign_all_busses(), and gives an answer that cannot change part way + * through the two passes of a bridge scan. + * + * The incoming FLB is retrieved while setting up the first device, which always + * happens before any bridge is scanned, so this returns the same answer for the + * entire enumeration. + * + * Note that this does not prevent the PCI core from assigning bus numbers to + * bridges that do not have any, e.g. bridges that are hot-added after the + * Live Update. See pci_liveupdate_refuse_bus_numbers() for the one case where + * the PCI core must refuse to do so. + * + * Return: True if bus numbers should be preserved, false otherwise. + */ +bool pci_liveupdate_preserve_bus_numbers(void) +{ + return pci_liveupdate.had_incoming; +} + +/** + * pci_liveupdate_refuse_bus_numbers() - Determine if the PCI core must refuse + * to assign bus numbers to the provided + * bridge. + * @bus: The PCI bus the bus numbers would be assigned from. + * @dev: The PCI bridge device the bus numbers would be assigned to. + * + * This function is called by the PCI core before it assigns bus numbers to a + * bridge that does not have any. + * + * A bridge that was preserved by the previous kernel but came up without a + * valid bus number configuration, e.g. because it was reset during kexec, is + * left alone by the PCI core and therefore has no child bus once the first pass + * of the bridge scan is done. + * + * The PCI core must not assign bus numbers from @bus while such a bridge is on + * it, including to the failed bridge itself. Assigning new bus numbers to the + * failed bridge would silently change the BDF of every preserved device in its + * hierarchy. Its bus numbers cannot be read from hardware anymore either, so + * they cannot be excluded from assignment, and handing them to another bridge + * would let an unrelated device inherit the BDF of a preserved device. + * + * Return: True if @dev must not be assigned bus numbers, false otherwise. + */ +bool pci_liveupdate_refuse_bus_numbers(struct pci_bus *bus, struct pci_dev *dev) +{ + struct pci_dev *bridge; + + for_each_pci_bridge(bridge, bus) { + if (!bridge->liveupdate.was_incoming || bridge->subordinate) + continue; + + pci_err(dev, "Not assigning bus numbers, preserved bridge %s lost its bus number configuration\n", + pci_name(bridge)); + return true; + } + + return false; +} + +void pci_liveupdate_setup_device(struct pci_dev *dev) +{ + struct pci_flb_incoming *incoming; + struct pci_dev_ser *dev_ser; + unsigned long key; + + guard(rwsem_write)(&pci_liveupdate.rwsem); + + incoming = pci_liveupdate_flb_get_incoming(); + if (!incoming) + return; + + key = pci_ser_xa_key(pci_domain_nr(dev->bus), pci_dev_id(dev)); + dev_ser = xa_load(&incoming->xa, key); + + /* + * This device was not preserved across Live Update, or it was preserved + * but has already been probed and gone through pci_liveupdate_finish(), + * e.g. due to removing and re-adding the device. Either way, it's not + * treated as incoming-preserved. + */ + if (!dev_ser || !dev_ser->refcount) { + pci_liveupdate_flb_put_incoming(); + return; + } + + pci_info(dev, "Device was preserved by previous kernel across Live Update\n"); + dev->liveupdate.incoming = dev_ser; + dev->liveupdate.was_incoming = true; + + pci_liveupdate_flb_put_incoming(); +} + +void pci_liveupdate_cleanup_device(struct pci_dev *dev) +{ + /* + * It should be safe to READ_ONCE() outside of the rwsem during cleanup + * since there should no longer be any references to @dev on the system. + * + * This should never happen in practice. Drivers should block removal + * while a device is preserved. + */ + if (READ_ONCE(dev->liveupdate.outgoing)) + pci_WARN(dev, 1, "Destroying outgoing-preserved device!\n"); + + if (READ_ONCE(dev->liveupdate.incoming)) + pci_WARN(dev, 1, "Destroying incoming-preserved device!\n"); +} + +void pci_liveupdate_freeze(struct pci_dev *dev) +{ + guard(rwsem_write)(&pci_liveupdate.rwsem); + dev->liveupdate.frozen = true; +} + +static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev) +{ + if (!dev->liveupdate.incoming) { + pci_warn(dev, "Cannot finish preserving an unpreserved device\n"); + return -EINVAL; + } + + if (!dev->liveupdate.incoming->refcount) { + pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n"); + return -EINVAL; + } + + /* + * Decrement the refcount so this device does not get treated as an + * incoming device again, e.g. in case pci_liveupdate_setup_device() + * gets called again because the device is hot-plugged. + */ + if (--dev->liveupdate.incoming->refcount) + return 0; + + pci_info(dev, "Device is finished participating in Live Update\n"); + dev->liveupdate.incoming = NULL; + ser->nr_devices--; + return 0; +} + +/** + * pci_liveupdate_finish() - Finish the preservation of a PCI device + * @dev: The PCI device + * + * pci_liveupdate_finish() notifies the PCI core that a PCI device that was + * preserved across the previous Live Update has finished participating in Live + * Update. Drivers must call pci_liveupdate_finish() from their struct + * liveupdate_file_handler finish() callback to ensure the incoming struct + * pci_ser is allocated. + * + * pci_liveupdate_finish() automatically finishes all bridges upstream of @dev. + */ +void pci_liveupdate_finish(struct pci_dev *dev) +{ + struct pci_flb_incoming *incoming; + + guard(rwsem_write)(&pci_liveupdate.rwsem); + + incoming = pci_liveupdate_flb_get_incoming(); + if (!incoming) { + pci_warn(dev, "Cannot finish preserving device without incoming FLB\n"); + return; + } + + for_each_pci_dev_in_path(dev) { + if (pci_liveupdate_finish_device(incoming->ser, dev)) + break; + } + + pci_liveupdate_flb_put_incoming(); +} +EXPORT_SYMBOL_GPL(pci_liveupdate_finish); + +/** + * pci_liveupdate_adopt_acs() - Adopt ACS controls + * @dev: The PCI device to adopt ACS controls for + * + * For devices preserved across a Live Update, leave the ACS controls + * established by the previous kernel alone instead of programming new ones. + * The adopted controls are recorded by the pci_save_state() call in + * pci_bus_add_device(), so they are reapplied by pci_restore_state() if the + * device is subsequently reset. + * + * Return: 0 on success, or -EINVAL if the device was not preserved, requires + * device-specific quirks, or has nowhere to record the adopted controls. + */ +int pci_liveupdate_adopt_acs(struct pci_dev *dev) +{ + /* + * Check if the device was preserved over a previous Live Update (even + * if it has already gone through pci_liveupdate_finish()). This ensures + * that the device continues to use the ACS controls established by the + * previous kernel. + */ + if (!dev->liveupdate.was_incoming) + return -EINVAL; + + /* + * The previous kernel should not have preserved any devices that + * require device-specific quirks to enable ACS, but if such a device is + * detected (e.g. new device-specific ACS quirk in the current kernel), + * log a big warning and fall back to the normal enable ACS path. + */ + if (pci_need_dev_specific_enable_acs(dev)) { + pci_warn(dev, "Device-specific quirk required to enable ACS!\n"); + WARN_ON_ONCE(true); + return -EINVAL; + } + + /* + * Adopting the previous kernel's controls depends on them being + * captured in the ACS save buffer, so that they are reapplied if the + * device is later reset. Without that buffer, e.g. because it could + * not be allocated under memory pressure, the adopted controls would + * be silently lost by the first reset. Program ACS from scratch + * instead, which is a better outcome than leaving ACS disabled. + */ + if (dev->acs_cap && !pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_ACS)) { + pci_err(dev, "No ACS save buffer, not adopting ACS controls\n"); + return -EINVAL; + } + + return 0; +} + +/** + * pci_liveupdate_adopt_ari() - Adopt ARI configuration + * @dev: The PCI device to adopt ARI configuration for + * + * For devices preserved across a Live Update, read the ARI state from + * hardware and adopt it. This ensures the device continues to use the ARI + * configuration established by the previous kernel. + * + * Return: 0 on success, or -EINVAL if the device was not preserved. + */ +int pci_liveupdate_adopt_ari(struct pci_dev *dev) +{ + u16 val; + + guard(rwsem_read)(&pci_liveupdate.rwsem); + + if (!dev->liveupdate.incoming) + return -EINVAL; + + pcie_capability_read_word(dev, PCI_EXP_DEVCTL2, &val); + + /* Safe to modify dev->ari_enabled bitfield during enumeration. */ + dev->ari_enabled = !!(val & PCI_EXP_DEVCTL2_ARI); + return 0; +} + +bool pci_liveupdate_is_outgoing(struct pci_dev *dev) +{ + guard(rwsem_read)(&pci_liveupdate.rwsem); + pci_WARN_ONCE(dev, !dev->liveupdate.frozen, "Preservation status is unstable!\n"); + return dev->liveupdate.outgoing; +} + +/** + * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved + * @dev: The PCI device to check + * + * Check if a device was preserved across Live Update by the previous kernel, + * i.e. the device is incoming-preserved. Note that a device is only considered + * incoming-preserved prior to pci_liveupdate_finish(). It is up to drivers to + * synchronize usage of pci_liveupdate_is_incoming() with their own call to + * pci_liveupdate_finish() to avoid acting on stale data. + * + * Returns: True if the device is incoming-preserved, false otherwise. + */ +bool pci_liveupdate_is_incoming(struct pci_dev *dev) +{ + guard(rwsem_read)(&pci_liveupdate.rwsem); + return dev->liveupdate.incoming; +} +EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming); + +/** + * pci_liveupdate_register_flb() - Register a file handler with the PCI core + * @fh: The file handler to register. + * + * Drivers that support preserving PCI devices across Live Update must call + * pci_liveupdate_register_flb() to register their + * struct liveupdate_file_handler with the PCI core, typically at module init, + * and always before any file managed by @fh can be preserved. + * + * Registering links the PCI core's FLB to @fh, so that LUO allocates the PCI + * core's outgoing struct pci_ser (via pci_flb_preserve()) when the first file + * managed by any registered handler is preserved, and frees it (via + * pci_flb_unpreserve()) when the last such file is unpreserved. + * + * Return: 0 on success, <0 on failure. + */ +int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh) +{ + pr_debug("Registering file handler \"%s\"\n", fh->compatible); + return liveupdate_register_flb(fh, &pci_liveupdate_flb); +} +EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb); + +/** + * pci_liveupdate_unregister_flb() - Unregister a file handler with the PCI core + * @fh: The file handler to unregister. + */ +void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh) +{ + pr_debug("Unregistering file handler \"%s\"\n", fh->compatible); + liveupdate_unregister_flb(fh, &pci_liveupdate_flb); +} +EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb); diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h new file mode 100644 index 000000000000..333ddd8f36c5 --- /dev/null +++ b/drivers/pci/liveupdate.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PCI Live Update support (core API) + * + * Copyright (c) 2026, Google LLC. + * David Matlack <dmatlack@google.com> + */ +#ifndef DRIVERS_PCI_LIVEUPDATE_H +#define DRIVERS_PCI_LIVEUPDATE_H + +#include <linux/pci.h> + +#ifdef CONFIG_PCI_LIVEUPDATE +void pci_liveupdate_setup_device(struct pci_dev *dev); +void pci_liveupdate_cleanup_device(struct pci_dev *dev); +void pci_liveupdate_freeze(struct pci_dev *dev); +bool pci_liveupdate_preserve_bus_numbers(void); +bool pci_liveupdate_refuse_bus_numbers(struct pci_bus *bus, struct pci_dev *dev); +int pci_liveupdate_adopt_acs(struct pci_dev *dev); +int pci_liveupdate_adopt_ari(struct pci_dev *dev); +bool pci_liveupdate_is_outgoing(struct pci_dev *dev); +#else +static inline void pci_liveupdate_setup_device(struct pci_dev *dev) +{ +} + +static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev) +{ +} + +static inline void pci_liveupdate_freeze(struct pci_dev *dev) +{ +} + +static inline bool pci_liveupdate_preserve_bus_numbers(void) +{ + return false; +} + +static inline bool pci_liveupdate_refuse_bus_numbers(struct pci_bus *bus, + struct pci_dev *dev) +{ + return false; +} + +static inline int pci_liveupdate_adopt_acs(struct pci_dev *dev) +{ + return -EINVAL; +} + +static inline int pci_liveupdate_adopt_ari(struct pci_dev *dev) +{ + return -EINVAL; +} + +static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev) +{ + return false; +} +#endif + +#endif /* DRIVERS_PCI_LIVEUPDATE_H */ diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index e16aa59dd7ac..c1ac2ef025e2 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -21,6 +21,7 @@ #include <linux/acpi.h> #include <linux/dma-map-ops.h> #include <linux/iommu.h> +#include "liveupdate.h" #include "pci.h" #include "pcie/portdrv.h" @@ -559,6 +560,7 @@ static void pci_device_shutdown(struct device *dev) struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; + pci_liveupdate_freeze(pci_dev); pm_runtime_resume(dev); if (drv && drv->shutdown) @@ -567,11 +569,14 @@ static void pci_device_shutdown(struct device *dev) /* * If this is a kexec reboot, turn off Bus Master bit on the * device to tell it to not continue to do DMA. Don't touch - * devices in D3cold or unknown states. + * devices being preserved for Live Update or in D3cold or + * unknown states. + * * If it is not a kexec reboot, firmware will hit the PCI * devices with big hammer and stop their DMA any way. */ - if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot)) + if (kexec_in_progress && !pci_liveupdate_is_outgoing(pci_dev) && + pci_dev->current_state <= PCI_D3hot) pci_clear_master(pci_dev); } diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2879a6be5f8..0ae405b86b2f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -35,6 +35,8 @@ #include <linux/aer.h> #include <linux/bitfield.h> #include <linux/suspend.h> + +#include "liveupdate.h" #include "pci.h" DEFINE_MUTEX(pci_slot_mutex); @@ -1022,6 +1024,55 @@ static void pci_std_enable_acs(struct pci_dev *dev, struct pci_acs *caps) } /** + * pci_save_acs_state - save the ACS Control register + * @dev: the PCI device + * + * Record the ACS controls currently programmed in hardware so that + * pci_restore_acs_state() can reapply them after a reset. + */ +static void pci_save_acs_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state; + + if (!dev->acs_cap) + return; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_ACS); + if (!save_state) + return; + + pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, + (u16 *)&save_state->cap.data[0]); +} + +/** + * pci_restore_acs_state - restore the ACS Control register + * @dev: the PCI device + */ +static void pci_restore_acs_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state = NULL; + + if (dev->acs_cap && !pci_need_dev_specific_enable_acs(dev)) + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_ACS); + + /* + * Devices that rely on device-specific quirks to enable an ACS + * equivalent keep that configuration outside of the ACS Control + * register, so there is nothing useful to restore for them. Configure + * ACS from scratch instead, which also covers devices that have no + * saved ACS state at all. + */ + if (!save_state) { + pci_enable_acs(dev); + return; + } + + pci_write_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, + *(u16 *)&save_state->cap.data[0]); +} + +/** * pci_enable_acs - enable ACS if hardware support it * @dev: the PCI device */ @@ -1031,6 +1082,9 @@ void pci_enable_acs(struct pci_dev *dev) bool enable_acs = false; int pos; + if (!pci_liveupdate_adopt_acs(dev)) + return; + /* If an iommu is present we start with kernel default caps */ if (pci_acs_enable) { if (pci_dev_specific_enable_acs(dev)) @@ -1057,6 +1111,15 @@ void pci_enable_acs(struct pci_dev *dev) __pci_config_acs(dev, &caps, config_acs_param, 0, 0); pci_write_config_word(dev, pos + PCI_ACS_CTRL, caps.ctrl); + + /* + * pci_enable_acs() runs when a driver binds to the device, i.e. after + * pci_bus_add_device() has already saved the device's state. Refresh + * the saved ACS Control register so that a subsequent reset restores + * the controls programmed here rather than the ones left behind by + * firmware. + */ + pci_save_acs_state(dev); } /** @@ -1800,6 +1863,7 @@ int pci_save_state(struct pci_dev *dev) pci_save_aer_state(dev); pci_save_ptm_state(dev); pci_save_tph_state(dev); + pci_save_acs_state(dev); return pci_save_vc_state(dev); } EXPORT_SYMBOL(pci_save_state); @@ -1877,7 +1941,7 @@ void pci_restore_state(struct pci_dev *dev) pci_restore_msi_state(dev); /* Restore ACS and IOV configuration state */ - pci_enable_acs(dev); + pci_restore_acs_state(dev); pci_restore_iov_state(dev); dev->state_saved = false; @@ -3532,6 +3596,11 @@ void pci_allocate_cap_save_buffers(struct pci_dev *dev) if (error) pci_err(dev, "unable to allocate suspend buffer for LTR\n"); + error = pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_ACS, + sizeof(u16)); + if (error) + pci_err(dev, "unable to allocate suspend buffer for ACS\n"); + pci_allocate_vc_save_buffers(dev); } @@ -3556,7 +3625,7 @@ void pci_configure_ari(struct pci_dev *dev) u32 cap; struct pci_dev *bridge; - if (pcie_ari_disabled || !pci_is_pcie(dev) || dev->devfn) + if (!pci_is_pcie(dev) || dev->devfn) return; bridge = dev->bus->self; @@ -3567,6 +3636,12 @@ void pci_configure_ari(struct pci_dev *dev) if (!(cap & PCI_EXP_DEVCAP2_ARI)) return; + if (!pci_liveupdate_adopt_ari(bridge)) + return; + + if (pcie_ari_disabled) + return; + if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI)) { pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_ARI); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index a002fe1a3f4c..b7b83bfb95a6 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1100,6 +1100,7 @@ void pci_acs_init(struct pci_dev *dev); void pci_enable_acs(struct pci_dev *dev); #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); +bool pci_need_dev_specific_enable_acs(struct pci_dev *dev); int pci_dev_specific_enable_acs(struct pci_dev *dev); int pci_dev_specific_disable_acs_redir(struct pci_dev *dev); void pci_disable_broken_acs_cap(struct pci_dev *pdev); @@ -1110,6 +1111,10 @@ static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, { return -ENOTTY; } +static inline bool pci_need_dev_specific_enable_acs(struct pci_dev *dev) +{ + return false; +} static inline int pci_dev_specific_enable_acs(struct pci_dev *dev) { return -ENOTTY; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 721daf5c5184..fbc4d02c81e5 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -24,6 +24,7 @@ #include <linux/pm_runtime.h> #include <linux/bitfield.h> #include <trace/events/pci.h> +#include "liveupdate.h" #include "pci.h" static struct resource busn_resource = { @@ -1396,6 +1397,8 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, int max, unsigned int available_buses, int pass) { + bool preserve_bus_numbers = !pcibios_assign_all_busses() || + pci_liveupdate_preserve_bus_numbers(); struct pci_bus *child; u32 buses; u16 bctl; @@ -1448,8 +1451,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, goto out; } - if ((secondary || subordinate) && - !pcibios_assign_all_busses() && !broken) { + if ((secondary || subordinate) && preserve_bus_numbers && !broken) { unsigned int cmax, buses; /* @@ -1491,8 +1493,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, * do in the second pass. */ if (!pass) { - if (pcibios_assign_all_busses() || broken) - + if (!preserve_bus_numbers || broken) /* * Temporarily disable forwarding of the * configuration cycles on all bridges in @@ -1506,6 +1507,9 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, goto out; } + if (pci_liveupdate_refuse_bus_numbers(bus, dev)) + goto out; + /* Clear errors */ pci_write_config_word(dev, PCI_STATUS, 0xffff); @@ -2069,6 +2073,8 @@ int pci_setup_device(struct pci_dev *dev) if (pci_early_dump) early_dump_pci_device(dev); + pci_liveupdate_setup_device(dev); + /* Need to have dev->class ready */ dev->cfg_size = pci_cfg_space_size(dev); @@ -2192,6 +2198,7 @@ int pci_setup_device(struct pci_dev *dev) default: /* unknown header */ pci_err(dev, "unknown header type %02x, ignoring device\n", dev->hdr_type); + pci_liveupdate_cleanup_device(dev); pci_release_of_node(dev); return -EIO; @@ -2490,6 +2497,7 @@ static void pci_release_dev(struct device *dev) pci_dev = to_pci_dev(dev); pci_release_capabilities(pci_dev); + pci_liveupdate_cleanup_device(pci_dev); pci_release_of_node(pci_dev); pcibios_release_device(pci_dev); pci_bus_put(pci_dev->bus); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 88624e5ff70d..5a26e24a485e 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5497,9 +5497,6 @@ static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev) */ static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev) { - if (!pci_quirk_intel_pch_acs_match(dev)) - return -ENOTTY; - if (pci_quirk_enable_intel_lpc_acs(dev)) { pci_warn(dev, "Failed to enable Intel PCH ACS quirk\n"); return 0; @@ -5519,9 +5516,6 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev) int pos; u32 cap, ctrl; - if (!pci_quirk_intel_spt_pch_acs_match(dev)) - return -ENOTTY; - pos = dev->acs_cap; if (!pos) return -ENOTTY; @@ -5549,9 +5543,6 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev) int pos; u32 cap, ctrl; - if (!pci_quirk_intel_spt_pch_acs_match(dev)) - return -ENOTTY; - pos = dev->acs_cap; if (!pos) return -ENOTTY; @@ -5571,56 +5562,63 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev) static const struct pci_dev_acs_ops { u16 vendor; u16 device; + bool (*match)(struct pci_dev *dev); int (*enable_acs)(struct pci_dev *dev); int (*disable_acs_redir)(struct pci_dev *dev); } pci_dev_acs_ops[] = { { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + .match = pci_quirk_intel_pch_acs_match, .enable_acs = pci_quirk_enable_intel_pch_acs, }, { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + .match = pci_quirk_intel_spt_pch_acs_match, .enable_acs = pci_quirk_enable_intel_spt_pch_acs, .disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir, }, }; -int pci_dev_specific_enable_acs(struct pci_dev *dev) +static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev) { const struct pci_dev_acs_ops *p; - int i, ret; + int i; for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { p = &pci_dev_acs_ops[i]; if ((p->vendor == dev->vendor || p->vendor == (u16)PCI_ANY_ID) && (p->device == dev->device || - p->device == (u16)PCI_ANY_ID) && - p->enable_acs) { - ret = p->enable_acs(dev); - if (ret >= 0) - return ret; + p->device == (u16)PCI_ANY_ID)) { + if (!p->match || p->match(dev)) + return p; } } + return NULL; +} + +bool pci_need_dev_specific_enable_acs(struct pci_dev *dev) +{ + const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev); + + return p && p->enable_acs; +} + +int pci_dev_specific_enable_acs(struct pci_dev *dev) +{ + const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev); + + if (p && p->enable_acs) + return p->enable_acs(dev); + return -ENOTTY; } int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) { - const struct pci_dev_acs_ops *p; - int i, ret; + const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev); - for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { - p = &pci_dev_acs_ops[i]; - if ((p->vendor == dev->vendor || - p->vendor == (u16)PCI_ANY_ID) && - (p->device == dev->device || - p->device == (u16)PCI_ANY_ID) && - p->disable_acs_redir) { - ret = p->disable_acs_redir(dev); - if (ret >= 0) - return ret; - } - } + if (p && p->disable_acs_redir) + return p->disable_acs_redir(dev); return -ENOTTY; } diff --git a/include/asm-generic/kexec_handover.h b/include/asm-generic/kexec_handover.h index 50839fb5ee8e..cf73d9b34c3c 100644 --- a/include/asm-generic/kexec_handover.h +++ b/include/asm-generic/kexec_handover.h @@ -4,7 +4,7 @@ #include <linux/types.h> -struct kho_scratch { +struct kho_bootmem { phys_addr_t addr; phys_addr_t size; }; diff --git a/include/linux/kexec.h b/include/linux/kexec.h index e5f1cfc11fef..f970ca2c8ce9 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -403,7 +403,7 @@ struct kimage { #endif struct { - struct kexec_segment *scratch; + struct kexec_segment *bootmem; phys_addr_t fdt; } kho; diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h index 46de86dc343e..ef2b188c8ed0 100644 --- a/include/linux/kexec_handover.h +++ b/include/linux/kexec_handover.h @@ -36,15 +36,15 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size); void kho_memory_init(void); void kho_memory_init_early(void); -void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys, - u64 scratch_len); +void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t bootmem_phys, + u64 bootmem_len); -bool kho_scratch_overlap(phys_addr_t phys, size_t size); +bool kho_bootmem_overlap(phys_addr_t phys, size_t size); -static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, +static inline enum migratetype kho_bootmem_migratetype(unsigned long pfn, enum migratetype mt) { - if (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT)) + if (kho_bootmem_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT)) return MIGRATE_CMA; return mt; } @@ -123,16 +123,16 @@ static inline void kho_memory_init(void) { } static inline void kho_memory_init_early(void) { } static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, - phys_addr_t scratch_phys, u64 scratch_len) + phys_addr_t bootmem_phys, u64 bootmem_len) { } -static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size) +static inline bool kho_bootmem_overlap(phys_addr_t phys, size_t size) { return false; } -static inline enum migratetype kho_scratch_migratetype(unsigned long pfn, +static inline enum migratetype kho_bootmem_migratetype(unsigned long pfn, enum migratetype mt) { return mt; diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h new file mode 100644 index 000000000000..9485ed73c351 --- /dev/null +++ b/include/linux/kho/abi/pci.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (c) 2026, Google LLC. + * David Matlack <dmatlack@google.com> + */ + +#ifndef _LINUX_KHO_ABI_PCI_H +#define _LINUX_KHO_ABI_PCI_H + +#include <linux/bug.h> +#include <linux/compiler.h> +#include <linux/types.h> + +/** + * DOC: PCI File-Lifecycle Bound (FLB) Live Update ABI + * + * This header defines the ABI for preserving core PCI state across kexec using + * Live Update File-Lifecycle Bound (FLB) data. + * + * This interface is a contract. Any modification to any of the serialization + * structs defined here constitutes a breaking change. Such changes require + * incrementing the version number in the PCI_LUO_FLB_VERSION number. + */ + +#define PCI_LUO_FLB_COMPATIBLE "pci" +#define PCI_LUO_FLB_VERSION 2 + +/** + * struct pci_dev_ser - Serialized state about a single PCI device. + * + * @domain: The device's PCI domain number (segment). + * @bdf: The device's PCI bus, device, and function number. + * @refcount: Reference count used by the PCI core to keep track of whether it + * is done using a device's struct pci_dev_ser. The value of the + * refcount is equal to the number of preserved devices at or below + * it in the PCI hierarchy when the struct pci_dev_ser is in use, and + * 0 otherwise. + */ +struct pci_dev_ser { + u32 domain; + u16 bdf; + u16 refcount; +} __packed; + +/** + * struct pci_ser - PCI Subsystem Live Update State + * + * This struct tracks state about all devices that are being preserved across + * a Live Update for the next kernel. It contains only state owned by the PCI + * core; the state a driver needs to resume its device is preserved separately + * by that driver. + * + * @version: The version of the "pci" FLB struct. This field must never be + * deleted, moved, or resized, as the kernel depends on always being + * able to check the struct pci_ser version number. + * @nr_devices: The number of devices that were preserved. + * @devices: Physical address of the first KHO block containing pci_dev_ser. + */ +struct pci_ser { + u32 version; + u32 nr_devices; + u64 devices; +} __packed; + +#endif /* _LINUX_KHO_ABI_PCI_H */ diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index 63ea5417de84..6051abc0612c 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -25,6 +25,7 @@ struct file; /** * struct liveupdate_file_op_args - Arguments for file operation callbacks. * @handler: The file handler being called. + * @session: The session this file belongs to. * @retrieve_status: The retrieve status for the 'can_finish / finish' * operation. A value of 0 means the retrieve has not been * attempted, a positive value means the retrieve was @@ -45,6 +46,7 @@ struct file; */ struct liveupdate_file_op_args { struct liveupdate_file_handler *handler; + struct liveupdate_session *session; int retrieve_status; struct file *file; u64 serialized_data; @@ -247,6 +249,14 @@ void liveupdate_flb_put_incoming(struct liveupdate_flb *flb); int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp); void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb); +/* kernel can internally retrieve files */ +int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token, + struct file **filep); + +/* Get a token for an outgoing file, or -ENOENT if file is not preserved */ +int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp); + #else /* CONFIG_LIVEUPDATE */ static inline bool liveupdate_enabled(void) @@ -299,5 +309,17 @@ static inline void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb) { } +static inline int liveupdate_get_file_incoming(struct liveupdate_session *s, + u64 token, struct file **filep) +{ + return -EOPNOTSUPP; +} + +static inline int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_LIVEUPDATE */ #endif /* _LINUX_LIVEUPDATE_H */ diff --git a/include/linux/memblock.h b/include/linux/memblock.h index aa845f488327..ea288cf21400 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -46,11 +46,11 @@ extern unsigned long long max_possible_pfn; * @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. - * @MEMBLOCK_KHO_SCRATCH: memory region that kexec can pass to the next - * kernel in handover mode. During early boot, we do not know about all - * memory reservations yet, so we get scratch memory from the previous - * kernel that we know is good to use. It is the only memory that - * allocations may happen from in this phase. + * @MEMBLOCK_KHO_NOPRSRV: memory region with no preservation from kexec + * handover. During early boot, we do not know about all memory preservations + * yet, so we get memory with no preservations from the previous kernel that we + * know is good to use. It is the only memory that allocations may happen from + * in this phase. * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages */ enum memblock_flags { @@ -61,7 +61,7 @@ enum memblock_flags { 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 */ - MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */ + MEMBLOCK_KHO_NOPRSRV = 0x40, /* memory with no KHO preservations */ MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */ }; @@ -158,8 +158,8 @@ int memblock_mark_nomap(phys_addr_t base, phys_addr_t size); int memblock_clear_nomap(phys_addr_t base, phys_addr_t size); int memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size); int memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t size); -int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size); -int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size); +int memblock_mark_kho_noprsrv(phys_addr_t base, phys_addr_t size); +int memblock_clear_kho_noprsrv(phys_addr_t base, phys_addr_t size); void memblock_free(void *ptr, size_t size); @@ -300,9 +300,9 @@ static inline bool memblock_is_driver_managed(struct memblock_region *m) return m->flags & MEMBLOCK_DRIVER_MANAGED; } -static inline bool memblock_is_kho_scratch(struct memblock_region *m) +static inline bool memblock_is_kho_noprsrv(struct memblock_region *m) { - return m->flags & MEMBLOCK_KHO_SCRATCH; + return m->flags & MEMBLOCK_KHO_NOPRSRV; } int memblock_search_pfn_nid(unsigned long pfn, unsigned long *start_pfn, @@ -613,12 +613,12 @@ static inline void early_memtest(phys_addr_t start, phys_addr_t end) { } static inline void memtest_report_meminfo(struct seq_file *m) { } #endif -#ifdef CONFIG_MEMBLOCK_KHO_SCRATCH -void memblock_set_kho_scratch_only(void); -void memblock_clear_kho_scratch_only(void); +#ifdef CONFIG_KEXEC_HANDOVER +void memblock_set_kho_noprsrv_only(void); +void memblock_clear_kho_noprsrv_only(void); #else -static inline void memblock_set_kho_scratch_only(void) { } -static inline void memblock_clear_kho_scratch_only(void) { } +static inline void memblock_set_kho_noprsrv_only(void) { } +static inline void memblock_clear_kho_noprsrv_only(void) { } #endif #endif /* _LINUX_MEMBLOCK_H */ diff --git a/include/linux/pci.h b/include/linux/pci.h index 8650c627bebb..d50cf9ae142d 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -43,6 +43,7 @@ #include <uapi/linux/pci.h> #include <linux/pci_ids.h> +#include <linux/pci_liveupdate.h> #define PCI_STATUS_ERROR_BITS (PCI_STATUS_DETECTED_PARITY | \ PCI_STATUS_SIG_SYSTEM_ERROR | \ @@ -598,6 +599,9 @@ struct pci_dev { u8 tph_mode; /* TPH mode */ u8 tph_req_type; /* TPH requester type */ #endif +#ifdef CONFIG_PCI_LIVEUPDATE + struct pci_liveupdate liveupdate; +#endif }; static inline struct pci_dev *pci_physfn(struct pci_dev *dev) @@ -832,6 +836,9 @@ static inline struct pci_dev *pci_upstream_bridge(struct pci_dev *dev) return dev->bus->self; } +#define for_each_pci_dev_in_path(dev) \ + for (; dev; dev = pci_upstream_bridge(dev)) + #ifdef CONFIG_PCI_MSI static inline bool pci_dev_msi_enabled(struct pci_dev *pci_dev) { diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h new file mode 100644 index 000000000000..bb79348769b7 --- /dev/null +++ b/include/linux/pci_liveupdate.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PCI Live Update support (Public/Driver API) + * + * Copyright (c) 2026, Google LLC. + * David Matlack <dmatlack@google.com> + */ +#ifndef LINUX_PCI_LIVEUPDATE_H +#define LINUX_PCI_LIVEUPDATE_H + +#include <linux/kho/abi/pci.h> +#include <linux/liveupdate.h> +#include <linux/spinlock_types.h> +#include <linux/types.h> + +/** + * struct pci_liveupdate - PCI Live Update state for a struct pci_dev + * @outgoing: State preserved for the next kernel. + * @incoming: State preserved by the previous kernel. + * @was_incoming: True if this struct pci_dev was incoming-preserved when it was + * set up, i.e. it was matched to state preserved by the previous + * kernel. Unlike @incoming, this is never cleared, so it stays + * true after the device finishes participating in Live Update. + * @frozen: True if the outgoing preservation status of this device is frozen + * and thus cannot be changed. + */ +struct pci_liveupdate { + struct pci_dev_ser *outgoing; + struct pci_dev_ser *incoming; + bool was_incoming; + bool frozen; +}; + +struct pci_dev; + +#ifdef CONFIG_PCI_LIVEUPDATE +int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh); +void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh); +int pci_liveupdate_preserve(struct pci_dev *dev); +void pci_liveupdate_unpreserve(struct pci_dev *dev); +void pci_liveupdate_finish(struct pci_dev *dev); +bool pci_liveupdate_is_incoming(struct pci_dev *dev); +#else +static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh) +{ + return -EOPNOTSUPP; +} + +static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh) +{ +} + +static inline int pci_liveupdate_preserve(struct pci_dev *dev) +{ + return -EOPNOTSUPP; +} + +static inline void pci_liveupdate_unpreserve(struct pci_dev *dev) +{ +} + +static inline void pci_liveupdate_finish(struct pci_dev *dev) +{ +} + +static inline bool pci_liveupdate_is_incoming(struct pci_dev *dev) +{ + return false; +} +#endif + +#endif /* LINUX_PCI_LIVEUPDATE_H */ diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index a8455481f639..b668ce8d68ee 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -742,8 +742,8 @@ int kexec_locate_mem_hole(struct kexec_buf *kbuf) return 0; /* - * If KHO is active, only use KHO scratch memory. All other memory - * could potentially be handed over. + * If KHO is active, only use KHO bootmem. All other memory could + * potentially be handed over. */ ret = kho_locate_mem_hole(kbuf, locate_mem_hole_callback); if (ret <= 0) diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig index c13af38ba23a..f50daee8eff0 100644 --- a/kernel/liveupdate/Kconfig +++ b/kernel/liveupdate/Kconfig @@ -5,7 +5,6 @@ menu "Live Update and Kexec HandOver" config KEXEC_HANDOVER bool "kexec handover" depends on ARCH_SUPPORTS_KEXEC_HANDOVER && ARCH_SUPPORTS_KEXEC_FILE - select MEMBLOCK_KHO_SCRATCH select KEXEC_FILE select LIBFDT select CMA diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 7c4d86daf86d..7018e25e4eda 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -12,6 +12,7 @@ #include <linux/cleanup.h> #include <linux/cma.h> +#include <linux/crash_dump.h> #include <linux/kmemleak.h> #include <linux/count_zeros.h> #include <linux/kasan.h> @@ -42,12 +43,12 @@ /* * This is the minimal alignment required by deferred struct page init. * deferred_init_memmap_chunk frees memory to the buddy allocator, which looks - * at the neighboring pages (up to MAX_PAGE_ORDER) to merge them. - * If KHO scratch is not aligned to that value, buddy can access uninitialized - * struct pages, which can cause a crash. + * at the neighboring pages (up to MAX_PAGE_ORDER) to merge them. If KHO bootmem + * is not aligned to that value, buddy can access uninitialized struct pages, + * which can cause a crash. */ -#define SCRATCH_ALIGNMENT_BYTES (PAGE_SIZE * MAX_ORDER_NR_PAGES) -static_assert(SCRATCH_ALIGNMENT_BYTES >= CMA_MIN_ALIGNMENT_BYTES); +#define BOOTMEM_ALIGNMENT_BYTES (PAGE_SIZE * MAX_ORDER_NR_PAGES) +static_assert(BOOTMEM_ALIGNMENT_BYTES >= CMA_MIN_ALIGNMENT_BYTES); /* The magic token for preserved pages */ #define KHO_PAGE_MAGIC 0x4b484f50U /* ASCII for 'KHOP' */ @@ -97,7 +98,7 @@ static struct kho_out kho_out = { struct kho_in { phys_addr_t fdt_phys; - phys_addr_t scratch_phys; + phys_addr_t bootmem_phys; char previous_release[__NEW_UTS_LEN + 1]; u32 kexec_count; struct kho_debugfs dbg; @@ -657,34 +658,34 @@ static void __init *kho_get_mem_map(const void *fdt) /* * With KHO enabled, memory can become fragmented because KHO regions may - * be anywhere in physical address space. The scratch regions give us a + * be anywhere in physical address space. The bootmem regions give us a * safe zones that we will never see KHO allocations from. This is where we - * can later safely load our new kexec images into and then use the scratch + * can later safely load our new kexec images into and then use the bootmem * area for early allocations that happen before page allocator is * initialized. */ -struct kho_scratch *kho_scratch; -unsigned int kho_scratch_cnt; +struct kho_bootmem *kho_bootmem; +unsigned int kho_bootmem_cnt; /* - * The scratch areas are scaled by default as percent of memory allocated from + * The bootmem areas are scaled by default as percent of memory allocated from * memblock. A user can override the scale with command line parameter: * - * kho_scratch=N% + * kho_bootmem=N% * * It is also possible to explicitly define size for a lowmem, a global and - * per-node scratch areas: + * per-node bootmem areas: * - * kho_scratch=l[KMG],n[KMG],m[KMG] + * kho_bootmem=l[KMG],n[KMG],m[KMG] * * The explicit size definition takes precedence over scale definition. */ -static unsigned int scratch_scale __initdata = 200; -static phys_addr_t scratch_size_global __initdata; -static phys_addr_t scratch_size_pernode __initdata; -static phys_addr_t scratch_size_lowmem __initdata; +static unsigned int bootmem_scale __initdata = 200; +static phys_addr_t bootmem_size_global __initdata; +static phys_addr_t bootmem_size_pernode __initdata; +static phys_addr_t bootmem_size_lowmem __initdata; -static int __init kho_parse_scratch_size(char *p) +static int __init kho_parse_bootmem_size(char *p) { size_t len; unsigned long sizes[3]; @@ -708,9 +709,9 @@ static int __init kho_parse_scratch_size(char *p) return -EINVAL; memcpy(s_scale, p, len - 1); - ret = kstrtouint(s_scale, 10, &scratch_scale); + ret = kstrtouint(s_scale, 10, &bootmem_scale); if (!ret) - pr_notice("scratch scale is %d%%\n", scratch_scale); + pr_notice("bootmem scale is %d%%\n", bootmem_scale); return ret; } @@ -738,81 +739,81 @@ static int __init kho_parse_scratch_size(char *p) if (*p) return -EINVAL; - scratch_size_lowmem = sizes[0]; - scratch_size_global = sizes[1]; - scratch_size_pernode = sizes[2]; - scratch_scale = 0; + bootmem_size_lowmem = sizes[0]; + bootmem_size_global = sizes[1]; + bootmem_size_pernode = sizes[2]; + bootmem_scale = 0; - pr_notice("scratch areas: lowmem: %lluMiB global: %lluMiB pernode: %lldMiB\n", - (u64)(scratch_size_lowmem >> 20), - (u64)(scratch_size_global >> 20), - (u64)(scratch_size_pernode >> 20)); + pr_notice("bootmem areas: lowmem: %lluMiB global: %lluMiB pernode: %lldMiB\n", + (u64)(bootmem_size_lowmem >> 20), + (u64)(bootmem_size_global >> 20), + (u64)(bootmem_size_pernode >> 20)); return 0; } -early_param("kho_scratch", kho_parse_scratch_size); +early_param("kho_bootmem", kho_parse_bootmem_size); -static void __init scratch_size_update(void) +static void __init bootmem_size_update(void) { /* * If fixed sizes are not provided via command line, calculate them now. * Remove HugeTLB allocations from it because they never get allocated - * from scratch. + * from bootmem. */ - if (scratch_scale) { + if (bootmem_scale) { phys_addr_t size; size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE); size -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT, NUMA_NO_NODE); - size = size * scratch_scale / 100; - scratch_size_lowmem = size; + size = size * bootmem_scale / 100; + bootmem_size_lowmem = size; size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE); size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE, NUMA_NO_NODE); - size = size * scratch_scale / 100 - scratch_size_lowmem; - scratch_size_global = size; + size = size * bootmem_scale / 100 - bootmem_size_lowmem; + bootmem_size_global = size; } /* - * Scratch areas are released as MIGRATE_CMA. Round them up to the right + * bootmem areas are released as MIGRATE_CMA. Round them up to the right * size. */ - scratch_size_lowmem = round_up(scratch_size_lowmem, SCRATCH_ALIGNMENT_BYTES); - scratch_size_global = round_up(scratch_size_global, SCRATCH_ALIGNMENT_BYTES); + bootmem_size_lowmem = round_up(bootmem_size_lowmem, BOOTMEM_ALIGNMENT_BYTES); + bootmem_size_global = round_up(bootmem_size_global, BOOTMEM_ALIGNMENT_BYTES); } -static phys_addr_t __init scratch_size_node(int nid) +static phys_addr_t __init bootmem_size_node(int nid) { phys_addr_t size; - if (scratch_scale) { + if (bootmem_scale) { size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, nid); /* Do not count HugeTLB pages. */ size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE, nid); - size = size * scratch_scale / 100; + size = size * bootmem_scale / 100; } else { - size = scratch_size_pernode; + size = bootmem_size_pernode; } - return round_up(size, SCRATCH_ALIGNMENT_BYTES); + return round_up(size, BOOTMEM_ALIGNMENT_BYTES); } -bool kho_scratch_overlap(phys_addr_t phys, size_t size) +bool kho_bootmem_overlap(phys_addr_t phys, size_t size) { - phys_addr_t scratch_start, scratch_end; + phys_addr_t bootmem_start, bootmem_end; unsigned int i; - for (i = 0; i < kho_scratch_cnt; i++) { - scratch_start = kho_scratch[i].addr; - scratch_end = kho_scratch[i].addr + kho_scratch[i].size; + for (i = 0; i < kho_bootmem_cnt; i++) { + bootmem_start = kho_bootmem[i].addr; + bootmem_end = kho_bootmem[i].addr + kho_bootmem[i].size; - if (phys < scratch_end && (phys + size) > scratch_start) + if (phys < bootmem_end && (phys + size) > bootmem_start) return true; } @@ -820,7 +821,7 @@ bool kho_scratch_overlap(phys_addr_t phys, size_t size) } /** - * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec + * kho_reserve_bootmem - Reserve a contiguous chunk of memory for kexec * * With KHO we can preserve arbitrary pages in the system. To ensure we still * have a large contiguous region of memory when we search the physical address @@ -828,7 +829,7 @@ bool kho_scratch_overlap(phys_addr_t phys, size_t size) * active. This CMA region will only be used for movable pages which are not a * problem for us during KHO because we can just move them somewhere else. */ -static void __init kho_reserve_scratch(void) +static void __init kho_reserve_bootmem(void) { phys_addr_t addr, size; int nid, i = 0; @@ -836,73 +837,73 @@ static void __init kho_reserve_scratch(void) if (!kho_enable) return; - scratch_size_update(); + bootmem_size_update(); /* FIXME: deal with node hot-plug/remove */ - kho_scratch_cnt = nodes_weight(node_states[N_MEMORY]) + 2; - size = kho_scratch_cnt * sizeof(*kho_scratch); - kho_scratch = memblock_alloc(size, PAGE_SIZE); - if (!kho_scratch) { - pr_err("Failed to reserve scratch array\n"); + kho_bootmem_cnt = nodes_weight(node_states[N_MEMORY]) + 2; + size = kho_bootmem_cnt * sizeof(*kho_bootmem); + kho_bootmem = memblock_alloc(size, PAGE_SIZE); + if (!kho_bootmem) { + pr_err("Failed to reserve bootmem array\n"); goto err_disable_kho; } /* - * reserve scratch area in low memory for lowmem allocations in the + * reserve bootmem area in low memory for lowmem allocations in the * next kernel */ - size = scratch_size_lowmem; - addr = memblock_phys_alloc_range(size, SCRATCH_ALIGNMENT_BYTES, 0, + size = bootmem_size_lowmem; + addr = memblock_phys_alloc_range(size, BOOTMEM_ALIGNMENT_BYTES, 0, ARCH_LOW_ADDRESS_LIMIT); if (!addr) { - pr_err("Failed to reserve lowmem scratch buffer\n"); - goto err_free_scratch_desc; + pr_err("Failed to reserve lowmem bootmem\n"); + goto err_free_bootmem_desc; } - kho_scratch[i].addr = addr; - kho_scratch[i].size = size; + kho_bootmem[i].addr = addr; + kho_bootmem[i].size = size; i++; /* reserve large contiguous area for allocations without nid */ - size = scratch_size_global; - addr = memblock_phys_alloc(size, SCRATCH_ALIGNMENT_BYTES); + size = bootmem_size_global; + addr = memblock_phys_alloc(size, BOOTMEM_ALIGNMENT_BYTES); if (!addr) { - pr_err("Failed to reserve global scratch buffer\n"); - goto err_free_scratch_areas; + pr_err("Failed to reserve global bootmem\n"); + goto err_free_bootmem_areas; } - kho_scratch[i].addr = addr; - kho_scratch[i].size = size; + kho_bootmem[i].addr = addr; + kho_bootmem[i].size = size; i++; /* * Loop over nodes that have both memory and are online. Skip - * memoryless nodes, as we can not allocate scratch areas there. + * memoryless nodes, as we can not allocate bootmem areas there. */ for_each_node_state(nid, N_MEMORY) { - size = scratch_size_node(nid); - addr = memblock_alloc_range_nid(size, SCRATCH_ALIGNMENT_BYTES, + size = bootmem_size_node(nid); + addr = memblock_alloc_range_nid(size, BOOTMEM_ALIGNMENT_BYTES, 0, MEMBLOCK_ALLOC_ACCESSIBLE, nid, true); if (!addr) { - pr_err("Failed to reserve nid %d scratch buffer\n", nid); - goto err_free_scratch_areas; + pr_err("Failed to reserve nid %d bootmem\n", nid); + goto err_free_bootmem_areas; } - kho_scratch[i].addr = addr; - kho_scratch[i].size = size; + kho_bootmem[i].addr = addr; + kho_bootmem[i].size = size; i++; } return; -err_free_scratch_areas: +err_free_bootmem_areas: for (i--; i >= 0; i--) - memblock_phys_free(kho_scratch[i].addr, kho_scratch[i].size); -err_free_scratch_desc: - memblock_free(kho_scratch, kho_scratch_cnt * sizeof(*kho_scratch)); + memblock_phys_free(kho_bootmem[i].addr, kho_bootmem[i].size); +err_free_bootmem_desc: + memblock_free(kho_bootmem, kho_bootmem_cnt * sizeof(*kho_bootmem)); err_disable_kho: - pr_warn("Failed to reserve scratch area, disabling kexec handover\n"); + pr_warn("Failed to reserve bootmem, disabling kexec handover\n"); kho_enable = false; } @@ -912,11 +913,11 @@ err_disable_kho: * on smaller systems. The algorithm itself doesn't depend on the actual value, * so it can be changed to a different heuristic later if needed. */ -#define KHO_SCRATCH_EXT_BLKSIZE SZ_1G -#define KHO_SCRATCH_EXT_BLKSHIFT const_ilog2(KHO_SCRATCH_EXT_BLKSIZE) +#define KHO_DISCOVER_BLKSIZE SZ_1G +#define KHO_DISCOVER_BLKSHIFT const_ilog2(KHO_DISCOVER_BLKSIZE) /* Called for the KHO preserved memory radix tree. */ -static int __init kho_ext_walk_leaf(unsigned long key, void *data) +static int __init kho_discover_walk_leaf(unsigned long key, void *data) { struct kho_radix_tree *busy_blocks = data; phys_addr_t start, end; @@ -931,54 +932,57 @@ static int __init kho_ext_walk_leaf(unsigned long key, void *data) end = start + (1UL << (order + PAGE_SHIFT)); while (start < end) { - err = kho_radix_add_key(busy_blocks, start >> KHO_SCRATCH_EXT_BLKSHIFT); + err = kho_radix_add_key(busy_blocks, start >> KHO_DISCOVER_BLKSHIFT); if (err) return err; - start += (1UL << KHO_SCRATCH_EXT_BLKSHIFT); + start += (1UL << KHO_DISCOVER_BLKSHIFT); } return 0; } /* Called for the KHO preserved memory radix tree. */ -static int __init kho_ext_walk_node(phys_addr_t phys, void *data) +static int __init kho_discover_walk_node(phys_addr_t phys, void *data) { struct kho_radix_tree *busy_blocks = data; - return kho_radix_add_key(busy_blocks, phys >> KHO_SCRATCH_EXT_BLKSHIFT); + return kho_radix_add_key(busy_blocks, phys >> KHO_DISCOVER_BLKSHIFT); } /* Called for the busy block radix tree. */ -static int __init kho_ext_mark_scratch(unsigned long key, void *data) +static int __init kho_discover_mark_noprsrv(unsigned long key, void *data) { phys_addr_t *prev_end = data; - phys_addr_t start = key << KHO_SCRATCH_EXT_BLKSHIFT; + phys_addr_t start = key << KHO_DISCOVER_BLKSHIFT; int err; if (start > *prev_end) { - err = memblock_mark_kho_scratch(*prev_end, start - *prev_end); + err = memblock_mark_kho_noprsrv(*prev_end, start - *prev_end); if (err) return err; } - *prev_end = start + (1UL << KHO_SCRATCH_EXT_BLKSHIFT); + *prev_end = start + (1UL << KHO_DISCOVER_BLKSHIFT); return 0; } /* - * kho_extend_scratch - Extend the scratch regions + * kho_discover_noprsrv - Discover memory with no preservations + * + * Discovers memory ranges with no preserved memory and marks it as NOPRSRV. + * This lets memblock allocate memory from areas outside KHO bootmem. * * The KHO preserved memory radix tree mixes both physical address and order * into a single key. This makes it hard to look for free ranges directly. This * function first walks the radix tree and digests it down into another radix - * tree, whose keys identify blocks of size KHO_SCRATCH_EXT_BLKSIZE which - * contain preserved memory. + * tree, whose keys identify blocks of size KHO_DISCOVER_BLKSIZE which contain + * preserved memory. * * Then it walks the digested radix tree and marks everything that doesn't have - * preserved memory as scratch. + * preserved memory as NOPRSRV. * - * NOTE: This function allocates memory so it should be called when scratch has + * NOTE: This function allocates memory so it should be called when bootmem has * available space. * * NOTE: The pages of the KHO preserved memory radix tree tables are not marked @@ -987,18 +991,18 @@ static int __init kho_ext_mark_scratch(unsigned long key, void *data) * them to be "preserved memory" and marks their blocks as busy. * * NOTE: efi_init()::reserve_regions() removes all regions except - * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO - * scratch memory, so they should not be removed. This function should always be + * MEMBLOCK_KHO_NOPRSRV. This function adds such regions but they are not KHO + * boot memory, so they should not be removed. This function should always be * called after reserve_regions(). */ -static void __init kho_extend_scratch(void) +static void __init kho_discover_noprsrv(void) { const struct kho_radix_walk_cb kho_cb = { - .leaf = kho_ext_walk_leaf, - .node = kho_ext_walk_node, + .leaf = kho_discover_walk_leaf, + .node = kho_discover_walk_node, }; const struct kho_radix_walk_cb ext_cb = { - .leaf = kho_ext_mark_scratch, + .leaf = kho_discover_mark_noprsrv, }; static struct lock_class_key busy_radix_class; struct kho_radix_tree busy_blocks; @@ -1023,21 +1027,21 @@ static void __init kho_extend_scratch(void) if (err) goto out; - /* Walk the busy blocks and mark everything between keys as scratch. */ + /* Walk the busy blocks and mark everything between keys as noprsrv. */ err = kho_radix_walk_tree(&busy_blocks, &ext_cb, &prev_end); if (err) goto out; /* Mark everything from last busy block to end of DRAM. */ if (prev_end < memblock_end_of_DRAM()) - err = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end); + err = memblock_mark_kho_noprsrv(prev_end, memblock_end_of_DRAM() - prev_end); /* fallthrough */ out: kho_radix_destroy_tree(&busy_blocks); print: if (err) - pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err)); + pr_err("Failed to discover extra allocatable memory: %pe\n", ERR_PTR(err)); } /** @@ -1151,7 +1155,7 @@ int kho_preserve_folio(struct folio *folio) const unsigned int order = folio_order(folio); if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) && - WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order))) + WARN_ON(kho_bootmem_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order))) return -EINVAL; return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), @@ -1229,7 +1233,7 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages) int err = 0; if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) && - WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT, + WARN_ON(kho_bootmem_overlap(start_pfn << PAGE_SHIFT, nr_pages << PAGE_SHIFT))) { return -EINVAL; } @@ -1826,7 +1830,7 @@ static __init int kho_init(void) err = kho_radix_init_tree(tree, NULL); if (err) - goto err_free_scratch; + goto err_free_bootmem; kho_out.fdt = kho_alloc_preserve(PAGE_SIZE); if (IS_ERR(kho_out.fdt)) { @@ -1855,9 +1859,9 @@ static __init int kho_init(void) return 0; } - for (int i = 0; i < kho_scratch_cnt; i++) { - unsigned long base_pfn = PHYS_PFN(kho_scratch[i].addr); - unsigned long count = kho_scratch[i].size >> PAGE_SHIFT; + for (int i = 0; i < kho_bootmem_cnt; i++) { + unsigned long base_pfn = PHYS_PFN(kho_bootmem[i].addr); + unsigned long count = kho_bootmem[i].size >> PAGE_SHIFT; unsigned long pfn; /* @@ -1865,10 +1869,10 @@ static __init int kho_init(void) * corresponding PRESENT bit in the kernel page table. * Subsequent kmemleak scans of these pages cause the * non-PRESENT page faults. - * Mark scratch areas with kmemleak_ignore_phys() to exclude + * Mark bootmem areas with kmemleak_ignore_phys() to exclude * them from kmemleak scanning. */ - kmemleak_ignore_phys(kho_scratch[i].addr); + kmemleak_ignore_phys(kho_bootmem[i].addr); for (pfn = base_pfn; pfn < base_pfn + count; pfn += pageblock_nr_pages) init_cma_reserved_pageblock(pfn_to_page(pfn)); @@ -1884,11 +1888,11 @@ err_free_fdt: kho_unpreserve_free(kho_out.fdt); err_free_kho_radix_tree: kho_radix_destroy_tree(tree); -err_free_scratch: +err_free_bootmem: kho_out.fdt = NULL; - for (int i = 0; i < kho_scratch_cnt; i++) { - void *start = __va(kho_scratch[i].addr); - void *end = start + kho_scratch[i].size; + for (int i = 0; i < kho_bootmem_cnt; i++) { + void *start = __va(kho_bootmem[i].addr); + void *end = start + kho_bootmem[i].size; free_reserved_area(start, end, -1, ""); } @@ -1907,7 +1911,7 @@ void __init kho_memory_init_early(void) /* * kho_get_mem_map() should always succeed. If it fails, kho_populate() - * catches that and never sets kho_in.scratch_phys, which stops memory + * catches that and never sets kho_in.bootmem_phys, which stops memory * retrieval. */ mem_map = kho_get_mem_map(fdt); @@ -1915,41 +1919,46 @@ void __init kho_memory_init_early(void) goto err; /* - * kho_scratch_overlap() needs kho_scratch to be initialized. It - * is used by free_area_init() on KHO boots, so initialize it - * early. + * kho_bootmem_overlap() needs kho_bootmem to be initialized. It is used + * by free_area_init() on KHO boots, so initialize it early. */ - kho_scratch = phys_to_virt(kho_in.scratch_phys); + kho_bootmem = phys_to_virt(kho_in.bootmem_phys); if (kho_radix_init_tree(&kho_in.radix_tree, mem_map)) goto err; - kho_extend_scratch(); + kho_discover_noprsrv(); return; err: /* * Failed to initialize preserved memory radix tree. Clear FDT - * and scratch so KHO users don't treat it as a KHO boot. + * and bootmem so KHO users don't treat it as a KHO boot. */ kho_in.fdt_phys = 0; - kho_in.scratch_phys = 0; + kho_in.bootmem_phys = 0; } void __init kho_memory_init(void) { - if (kho_in.scratch_phys) + if (is_kdump_kernel()) { + kho_enable = false; + pr_info("disabled in the kdump kernel\n"); + return; + } + + if (kho_in.bootmem_phys) kho_mem_retrieve(); else - kho_reserve_scratch(); + kho_reserve_bootmem(); } void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len, - phys_addr_t scratch_phys, u64 scratch_len) + phys_addr_t bootmem_phys, u64 bootmem_len) { - unsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch); - struct kho_scratch *scratch = NULL; + unsigned int bootmem_cnt = bootmem_len / sizeof(*kho_bootmem); + struct kho_bootmem *bootmem = NULL; phys_addr_t mem_map_phys; void *fdt = NULL; bool populated = false; @@ -1978,52 +1987,52 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len, if (!mem_map_phys) goto unmap_fdt; - scratch = early_memremap(scratch_phys, scratch_len); - if (!scratch) { - pr_warn("setup: failed to memremap scratch (phys=0x%llx, len=%lld)\n", - scratch_phys, scratch_len); + bootmem = early_memremap(bootmem_phys, bootmem_len); + if (!bootmem) { + pr_warn("setup: failed to memremap bootmem (phys=0x%llx, len=%lld)\n", + bootmem_phys, bootmem_len); goto unmap_fdt; } /* - * We pass a safe contiguous blocks of memory to use for early boot - * purporses from the previous kernel so that we can resize the - * memblock array as needed. + * We pass a safe contiguous blocks of memory with no preservations to + * use for early boot purporses from the previous kernel so that we can + * resize the memblock array as needed. */ - for (int i = 0; i < scratch_cnt; i++) { - struct kho_scratch *area = &scratch[i]; + for (int i = 0; i < bootmem_cnt; i++) { + struct kho_bootmem *area = &bootmem[i]; u64 size = area->size; memblock_add(area->addr, size); - err = memblock_mark_kho_scratch(area->addr, size); + err = memblock_mark_kho_noprsrv(area->addr, size); if (err) { - pr_warn("failed to mark the scratch region 0x%pa+0x%pa: %pe", + pr_warn("failed to mark the bootmem region 0x%pa+0x%pa: %pe", &area->addr, &size, ERR_PTR(err)); - goto unmap_scratch; + goto unmap_bootmem; } - pr_debug("Marked 0x%pa+0x%pa as scratch", &area->addr, &size); + pr_debug("Marked 0x%pa+0x%pa as bootmem", &area->addr, &size); } - memblock_reserve(scratch_phys, scratch_len); + memblock_reserve(bootmem_phys, bootmem_len); /* - * Now that we have a viable region of scratch memory, let's tell + * Now that we have a viable region of boot memory, let's tell * the memblocks allocator to only use that for any allocations. * That way we ensure that nothing scribbles over in use data while * we initialize the page tables which we will need to ingest all * memory reservations from the previous kernel. */ - memblock_set_kho_scratch_only(); + memblock_set_kho_noprsrv_only(); kho_in.fdt_phys = fdt_phys; - kho_in.scratch_phys = scratch_phys; - kho_scratch_cnt = scratch_cnt; + kho_in.bootmem_phys = bootmem_phys; + kho_bootmem_cnt = bootmem_cnt; populated = true; pr_info("found kexec handover data.\n"); -unmap_scratch: - early_memunmap(scratch, scratch_len); +unmap_bootmem: + early_memunmap(bootmem, bootmem_len); unmap_fdt: early_memunmap(fdt, fdt_len); report: @@ -2035,47 +2044,47 @@ report: int kho_fill_kimage(struct kimage *image) { - ssize_t scratch_size; + ssize_t bootmem_size; int err = 0; - struct kexec_buf scratch; + struct kexec_buf bootmem; if (!kho_enable || image->type == KEXEC_TYPE_CRASH) return 0; image->kho.fdt = virt_to_phys(kho_out.fdt); - scratch_size = sizeof(*kho_scratch) * kho_scratch_cnt; - scratch = (struct kexec_buf){ + bootmem_size = sizeof(*kho_bootmem) * kho_bootmem_cnt; + bootmem = (struct kexec_buf){ .image = image, - .buffer = kho_scratch, - .bufsz = scratch_size, + .buffer = kho_bootmem, + .bufsz = bootmem_size, .mem = KEXEC_BUF_MEM_UNKNOWN, - .memsz = scratch_size, + .memsz = bootmem_size, .buf_align = SZ_64K, /* Makes it easier to map */ .buf_max = ULONG_MAX, .top_down = true, }; - err = kexec_add_buffer(&scratch); + err = kexec_add_buffer(&bootmem); if (err) return err; - image->kho.scratch = &image->segment[image->nr_segments - 1]; + image->kho.bootmem = &image->segment[image->nr_segments - 1]; return 0; } -static int kho_walk_scratch(struct kexec_buf *kbuf, +static int kho_walk_bootmem(struct kexec_buf *kbuf, int (*func)(struct resource *, void *)) { int ret = 0; int i; - for (i = 0; i < kho_scratch_cnt; i++) { + for (i = 0; i < kho_bootmem_cnt; i++) { struct resource res = { - .start = kho_scratch[i].addr, - .end = kho_scratch[i].addr + kho_scratch[i].size - 1, + .start = kho_bootmem[i].addr, + .end = kho_bootmem[i].addr + kho_bootmem[i].size - 1, }; - /* Try to fit the kimage into our KHO scratch region */ + /* Try to fit the kimage into our KHO bootmem region */ ret = func(&res, kbuf); if (ret) break; @@ -2092,7 +2101,7 @@ int kho_locate_mem_hole(struct kexec_buf *kbuf, if (!kho_enable || kbuf->image->type == KEXEC_TYPE_CRASH) return 1; - ret = kho_walk_scratch(kbuf, func); + ret = kho_walk_bootmem(kbuf, func); return ret == 1 ? 0 : -EADDRNOTAVAIL; } diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c index 257ee8a52be6..7d74477da0b7 100644 --- a/kernel/liveupdate/kexec_handover_debugfs.c +++ b/kernel/liveupdate/kexec_handover_debugfs.c @@ -77,23 +77,23 @@ void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob) } } -static int scratch_phys_show(struct seq_file *m, void *v) +static int bootmem_phys_show(struct seq_file *m, void *v) { - for (int i = 0; i < kho_scratch_cnt; i++) - seq_printf(m, "0x%llx\n", kho_scratch[i].addr); + for (int i = 0; i < kho_bootmem_cnt; i++) + seq_printf(m, "0x%llx\n", kho_bootmem[i].addr); return 0; } -DEFINE_SHOW_ATTRIBUTE(scratch_phys); +DEFINE_SHOW_ATTRIBUTE(bootmem_phys); -static int scratch_len_show(struct seq_file *m, void *v) +static int bootmem_len_show(struct seq_file *m, void *v) { - for (int i = 0; i < kho_scratch_cnt; i++) - seq_printf(m, "0x%llx\n", kho_scratch[i].size); + for (int i = 0; i < kho_bootmem_cnt; i++) + seq_printf(m, "0x%llx\n", kho_bootmem[i].size); return 0; } -DEFINE_SHOW_ATTRIBUTE(scratch_len); +DEFINE_SHOW_ATTRIBUTE(bootmem_len); __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt) { @@ -186,13 +186,13 @@ __init int kho_out_debugfs_init(struct kho_debugfs *dbg) if (IS_ERR(sub_fdt_dir)) goto err_rmdir; - f = debugfs_create_file("scratch_phys", 0400, dir, NULL, - &scratch_phys_fops); + f = debugfs_create_file("bootmem_phys", 0400, dir, NULL, + &bootmem_phys_fops); if (IS_ERR(f)) goto err_rmdir; - f = debugfs_create_file("scratch_len", 0400, dir, NULL, - &scratch_len_fops); + f = debugfs_create_file("bootmem_len", 0400, dir, NULL, + &bootmem_len_fops); if (IS_ERR(f)) goto err_rmdir; diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h index 805d2a76c388..e9685e1ef6d7 100644 --- a/kernel/liveupdate/kexec_handover_internal.h +++ b/kernel/liveupdate/kexec_handover_internal.h @@ -19,8 +19,8 @@ struct kho_debugfs { struct kho_debugfs {}; #endif -extern struct kho_scratch *kho_scratch; -extern unsigned int kho_scratch_cnt; +extern struct kho_bootmem *kho_bootmem; +extern unsigned int kho_bootmem_cnt; #ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS int kho_debugfs_init(void); diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index c39f96961a85..a39992da504f 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -91,6 +91,21 @@ * again later. If the issue cannot be resolved, these resources will be held * by LUO until the next live update cycle, at which point they will be * discarded. + * + * Dependencies: + * + * Preserved files may have dependencies on other files that also need to be + * preserved during a live update. A file handler can resolve these dependencies + * by querying the token of the dependency. That token can then be serialized + * and used to fetch the dependency during the .retrieve() callback in the next + * kernel. + * + * Some of these dependencies can be resolved late during file handler's + * .freeze() callback, but some of these might need resolution during file + * handler's .preserve() callback based on constraints such as immutability and + * performance. This introduces an order of preservation. Note that the order of + * preservation of dependencies can be safely relaxed later, but it cannot be + * enforced later. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -284,6 +299,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) mutex_init(&luo_file->mutex); args.handler = fh; + args.session = luo_session_from_file_set(file_set); args.file = file; err = fh->ops->preserve(&args); if (err) @@ -341,6 +357,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set) struct luo_file, list); args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -374,6 +391,7 @@ static int luo_file_freeze_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -395,6 +413,7 @@ static void luo_file_unfreeze_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.private_data = luo_file->private_data; @@ -587,6 +606,7 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token, } args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.serialized_data = luo_file->serialized_data; err = luo_file->fh->ops->retrieve(&args); if (err) { @@ -620,6 +640,7 @@ static int luo_file_can_finish_one(struct luo_file_set *file_set, struct liveupdate_file_op_args args = {0}; args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.retrieve_status = luo_file->retrieve_status; @@ -637,6 +658,7 @@ static void luo_file_finish_one(struct luo_file_set *file_set, guard(mutex)(&luo_file->mutex); args.handler = luo_file->fh; + args.session = luo_session_from_file_set(file_set); args.file = luo_file->file; args.serialized_data = luo_file->serialized_data; args.retrieve_status = luo_file->retrieve_status; @@ -926,3 +948,65 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh) luo_flb_unregister_all(fh); list_del(&ACCESS_PRIVATE(fh, list)); } + +/** + * liveupdate_get_token_outgoing - Get the token for a preserved file. + * @s: The outgoing liveupdate session. + * @file: The file object to search for. + * @tokenp: Output parameter for the found token. + * + * Searches the list of preserved files in an outgoing session for a matching + * file object. If found, the corresponding user-provided token is returned. + * + * This function is intended for in-kernel callers that need to correlate a + * file with its liveupdate token. + * + * Context: It must be called with session mutex acquired. + * Return: 0 on success, -ENOENT if the file is not preserved in this session. + */ +int liveupdate_get_token_outgoing(struct liveupdate_session *s, + struct file *file, u64 *tokenp) +{ + struct luo_file_set *file_set = luo_file_set_from_session_locked(s); + struct luo_file *luo_file; + int err = -ENOENT; + + list_for_each_entry(luo_file, &file_set->files_list, list) { + if (luo_file->file == file) { + if (tokenp) + *tokenp = luo_file->token; + err = 0; + break; + } + } + + return err; +} +EXPORT_SYMBOL_GPL(liveupdate_get_token_outgoing); + +/** + * liveupdate_get_file_incoming - Retrieves a preserved file for in-kernel use. + * @s: The incoming liveupdate session (restored from the previous kernel). + * @token: The unique token identifying the file to retrieve. + * @filep: On success, this will be populated with a pointer to the retrieved + * 'struct file'. + * + * Provides a kernel-internal API for other subsystems to retrieve their + * preserved files after a live update. This function is a simple wrapper + * around luo_retrieve_file(), allowing callers to find a file by its token. + * + * The caller receives a new reference to the file and must call fput() when it + * is no longer needed. The file's lifetime is managed by LUO and any userspace + * file descriptors. + * + * Context: It must be called with session mutex acquired of a restored session. + * Return: 0 on success. Returns -ENOENT if no file with the matching token is + * found, or any other negative errno on failure. + */ +int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token, + struct file **filep) +{ + return luo_retrieve_file(luo_file_set_from_session_locked(s), + token, filep); +} +EXPORT_SYMBOL_GPL(liveupdate_get_file_incoming); diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h index 64879ffe7378..dac6644bfb18 100644 --- a/kernel/liveupdate/luo_internal.h +++ b/kernel/liveupdate/luo_internal.h @@ -77,6 +77,23 @@ struct luo_session { extern struct rw_semaphore luo_register_rwlock; +static inline struct liveupdate_session *luo_session_from_file_set(struct luo_file_set *file_set) +{ + struct luo_session *session; + + session = container_of(file_set, struct luo_session, file_set); + + return (struct liveupdate_session *)session; +} + +static inline struct luo_file_set *luo_file_set_from_session_locked(struct liveupdate_session *s) +{ + struct luo_session *session = (struct luo_session *)s; + + lockdep_assert_held(&session->mutex); + return &session->file_set; +} + int luo_session_create(const char *name, struct file **filep); int luo_session_retrieve(const char *name, struct file **filep); void __init luo_session_setup_outgoing(u64 *sessions_pa); diff --git a/mm/Kconfig b/mm/Kconfig index acefc994d9a8..afff1b25b543 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -482,10 +482,6 @@ config HAVE_GUP_FAST depends on MMU bool -# Enable memblock support for scratch memory which is needed for kexec handover -config MEMBLOCK_KHO_SCRATCH - bool - # Don't discard allocated memory used to track "memory" and "reserved" memblocks # after early boot, so it can still be used to test for validity of memory. # Also, memblocks are updated with memory hot(un)plug. diff --git a/mm/memblock.c b/mm/memblock.c index 952237df583a..cbb172fbf06a 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -113,11 +113,11 @@ unsigned long min_low_pfn; unsigned long max_pfn; unsigned long long max_possible_pfn; -#ifdef CONFIG_MEMBLOCK_KHO_SCRATCH -/* When set to true, only allocate from MEMBLOCK_KHO_SCRATCH ranges */ -static bool kho_scratch_only; +#ifdef CONFIG_KEXEC_HANDOVER +/* When set to true, only allocate from MEMBLOCK_KHO_NOPRSRV ranges */ +static bool kho_noprsrv_only; #else -#define kho_scratch_only false +#define kho_noprsrv_only false #endif static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_REGIONS] __initdata_memblock; @@ -179,9 +179,9 @@ bool __init_memblock memblock_has_mirror(void) static enum memblock_flags __init_memblock choose_memblock_flags(void) { - /* skip non-scratch memory for kho early boot allocations */ - if (kho_scratch_only) - return MEMBLOCK_KHO_SCRATCH; + /* only use KHO_NOPRSRV memory for kho early boot allocations */ + if (kho_noprsrv_only) + return MEMBLOCK_KHO_NOPRSRV; return system_has_some_mirror ? MEMBLOCK_MIRROR : MEMBLOCK_NONE; } @@ -1175,33 +1175,33 @@ int __init_memblock memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t si } /** - * memblock_mark_kho_scratch - Mark a memory region as MEMBLOCK_KHO_SCRATCH. + * memblock_mark_kho_noprsrv - Mark a memory region as MEMBLOCK_KHO_NOPRSRV. * @base: the base phys addr of the region * @size: the size of the region * - * Only memory regions marked with %MEMBLOCK_KHO_SCRATCH will be considered + * Only memory regions marked with %MEMBLOCK_KHO_NOPRSRV will be considered * for allocations during early boot with kexec handover. * * Return: 0 on success, -errno on failure. */ -__init int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size) +__init int memblock_mark_kho_noprsrv(phys_addr_t base, phys_addr_t size) { return memblock_setclr_flag(&memblock.memory, base, size, 1, - MEMBLOCK_KHO_SCRATCH); + MEMBLOCK_KHO_NOPRSRV); } /** - * memblock_clear_kho_scratch - Clear MEMBLOCK_KHO_SCRATCH flag for a + * memblock_clear_kho_noprsrv - Clear MEMBLOCK_KHO_NOPRSRV flag for a * specified region. * @base: the base phys addr of the region * @size: the size of the region * * Return: 0 on success, -errno on failure. */ -__init int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size) +__init int memblock_clear_kho_noprsrv(phys_addr_t base, phys_addr_t size) { return memblock_setclr_flag(&memblock.memory, base, size, 0, - MEMBLOCK_KHO_SCRATCH); + MEMBLOCK_KHO_NOPRSRV); } static bool should_skip_region(struct memblock_type *type, @@ -1237,9 +1237,9 @@ static bool should_skip_region(struct memblock_type *type, /* * In early alloc during kexec handover, we can only consider - * MEMBLOCK_KHO_SCRATCH regions for the allocations + * MEMBLOCK_KHO_NOPRSRV regions for the allocations */ - if ((flags & MEMBLOCK_KHO_SCRATCH) && !memblock_is_kho_scratch(m)) + if ((flags & MEMBLOCK_KHO_NOPRSRV) && !memblock_is_kho_noprsrv(m)) return true; return false; @@ -1794,7 +1794,7 @@ retry: if (numa_valid_node(nid) && !exact_nid) { nid = NUMA_NO_NODE; /* - * If a previous candidate overlapped with KHO scratch, it would + * If a previous candidate overlapped with KHO bootmem, it would * update start or end. Now that the search is opening to all * nodes, reset them. */ @@ -1810,12 +1810,12 @@ retry: found: /* * HugeTLB pages can be preserved with KHO and no preserved memory can - * be in scratch. So retry if found address overlaps with scratch. + * be in bootmem. So retry if found address overlaps with bootmem. * - * Scratch areas are normally not very large, so this shouldn't take too + * Bootmem areas are normally not very large, so this shouldn't take too * many retries. */ - if (kho_scratch_overlap(addr, size)) { + if (kho_bootmem_overlap(addr, size)) { if (memblock_bottom_up()) start = addr + size; else @@ -2491,7 +2491,7 @@ void __init memblock_free_all(void) free_unused_memmap(); reset_all_zones_managed_pages(); - memblock_clear_kho_scratch_only(); + memblock_clear_kho_noprsrv_only(); pages = free_low_memory_core_early(); totalram_pages_add(pages); } @@ -2589,15 +2589,15 @@ int reserve_mem_release_by_name(const char *name) return 1; } -#ifdef CONFIG_MEMBLOCK_KHO_SCRATCH -__init void memblock_set_kho_scratch_only(void) +#ifdef CONFIG_KEXEC_HANDOVER +__init void memblock_set_kho_noprsrv_only(void) { - kho_scratch_only = true; + kho_noprsrv_only = true; } -__init void memblock_clear_kho_scratch_only(void) +__init void memblock_clear_kho_noprsrv_only(void) { - kho_scratch_only = false; + kho_noprsrv_only = false; } #endif @@ -2853,7 +2853,7 @@ static int __init reserve_mem(char *p) if (reserve_mem_kho_revive(name, size, align)) return 1; - /* TODO: Allocation must be outside of scratch region */ + /* TODO: Allocation must be outside of KHO_NOPRSRV region */ start = memblock_phys_alloc(size, align); if (!start) { pr_err("reserve_mem: memblock allocation failed\n"); @@ -2878,7 +2878,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_DRIVER_MANAGED)] = "DRV_MNG", [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT", [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", - [ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH", + [ilog2(MEMBLOCK_KHO_NOPRSRV)] = "KHO_NOPRSRV", [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", }; diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index 59de210bee5f..6b68220275eb 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -121,8 +121,7 @@ static int memfd_luo_preserve_folios(struct file *file, /* * Pin the folios so they don't move around behind our back. This also * ensures none of the folios are in CMA -- which ensures they don't - * fall in KHO scratch memory. It also moves swapped out folios back to - * memory. + * fall in KHO bootmem. It also moves swapped out folios back to memory. * * A side effect of doing this is that it allocates a folio for all * indices in the file. This might waste memory on sparse memfds. If diff --git a/mm/mm_init.c b/mm/mm_init.c index dc835f753ea9..96db0c3bc90f 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -684,7 +684,7 @@ static __meminit void pageblock_migratetype_init_range(unsigned long pfn, const unsigned long end = pfn + nr_pages; for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) { - enum migratetype mt = kho_scratch_migratetype(pfn, migratetype); + enum migratetype mt = kho_bootmem_migratetype(pfn, migratetype); init_pageblock_migratetype(pfn_to_page(pfn), mt, isolate); if (!atomic && IS_ALIGNED(pfn, PFN_DOWN(SZ_1G))) @@ -774,7 +774,7 @@ static void __meminit __init_deferred_page(unsigned long pfn, int nid) zone ? zone_idx(zone) : MAX_NR_ZONES, nid); if (pageblock_aligned(pfn)) { enum migratetype mt = - kho_scratch_migratetype(pfn, MIGRATE_MOVABLE); + kho_bootmem_migratetype(pfn, MIGRATE_MOVABLE); init_pageblock_migratetype(pfn_to_page(pfn), mt, false); } } diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h index f1974e009013..ba4d1c02fde5 100644 --- a/tools/testing/memblock/internal.h +++ b/tools/testing/memblock/internal.h @@ -46,7 +46,7 @@ static inline bool __is_kernel(unsigned long addr) #define __SetPageReserved(p) ((void)(p)) -static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size) +static inline bool kho_bootmem_overlap(phys_addr_t phys, size_t size) { return false; } |
