| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/11 18:26 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"LIVEUPDATE",
"PCI_LIVEUPDATE"
],
"FocusSymbols": [
"pci_liveupdate_setup_device",
"pci_liveupdate_cleanup_device",
"pci_liveupdate_freeze",
"pci_liveupdate_scan_bridge_begin",
"pci_liveupdate_scan_bridge_end",
"pci_liveupdate_init_acs",
"pci_liveupdate_enable_acs",
"pci_liveupdate_configure_ari",
"pci_liveupdate_preserve",
"pci_liveupdate_unpreserve",
"pci_liveupdate_finish",
"pci_liveupdate_register_flb",
"pci_liveupdate_unregister_flb"
],
"Reasoning": "The patch introduces PCI Live Update support, which modifies core PCI enumeration, shutdown, and capability configuration paths (e.g., ACS, ARI). These changes are functional and reachable during PCI device probe, removal, and system shutdown/kexec. Fuzzing should focus on the new pci_liveupdate_* functions to ensure they handle various PCI topologies and states correctly.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/11 18:26 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d6b30ab279fb0ef05787d56e33e57436ab6c88e6\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Jul 11 18:26:37 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst\nindex 5d720d2a415e01..23fb737ac969ea 100644\n--- a/Documentation/PCI/index.rst\n+++ b/Documentation/PCI/index.rst\n@@ -20,3 +20,4 @@ PCI Bus Subsystem\n controller/index\n boot-interrupts\n tph\n+ liveupdate\ndiff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst\nnew file mode 100644\nindex 00000000000000..eba55f8a92ae12\n--- /dev/null\n+++ b/Documentation/PCI/liveupdate.rst\n@@ -0,0 +1,29 @@\n+.. SPDX-License-Identifier: GPL-2.0-or-later\n+\n+===========================\n+PCI Support for Live Update\n+===========================\n+\n+.. kernel-doc:: drivers/pci/liveupdate.c\n+ :doc: PCI Live Update\n+\n+Driver API\n+==========\n+\n+.. kernel-doc:: drivers/pci/liveupdate.c\n+ :export:\n+\n+Live Update ABI\n+===============\n+\n+.. kernel-doc:: include/linux/kho/abi/pci.h\n+ :doc: PCI File-Lifecycle Bound (FLB) Live Update ABI\n+\n+.. kernel-doc:: include/linux/kho/abi/pci.h\n+ :internal:\n+\n+See Also\n+========\n+\n+ * :doc:`/core-api/liveupdate`\n+ * :doc:`/core-api/kho/index`\ndiff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt\nindex 4f65b2a3752102..ed9eef8de64cd3 100644\n--- a/Documentation/admin-guide/kernel-parameters.txt\n+++ b/Documentation/admin-guide/kernel-parameters.txt\n@@ -5105,7 +5105,11 @@ Kernel parameters\n \t\t\t\texplicitly which ones they are.\n \t\tassign-busses\t[X86] Always assign all PCI bus\n \t\t\t\tnumbers ourselves, overriding\n-\t\t\t\twhatever the firmware may have done.\n+\t\t\t\twhatever the firmware may have done. Ignored\n+\t\t\t\tduring a Live Update, where the kernel must\n+\t\t\t\tinherit the PCI topology (including bus numbers)\n+\t\t\t\tto avoid interrupting ongoing memory\n+\t\t\t\ttransactions of preserved devices.\n \t\tusepirqmask\t[X86] Honor the possible IRQ mask stored\n \t\t\t\tin the BIOS $PIR table. This is needed on\n \t\t\t\tsome systems with broken BIOSes, notably\ndiff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst\nindex 5a292d0f37064d..2bce2644eba223 100644\n--- a/Documentation/core-api/liveupdate.rst\n+++ b/Documentation/core-api/liveupdate.rst\n@@ -1,5 +1,7 @@\n .. SPDX-License-Identifier: GPL-2.0\n \n+.. _luo:\n+\n ========================\n Live Update Orchestrator\n ========================\n@@ -18,6 +20,8 @@ LUO Preserving File Descriptors\n .. kernel-doc:: kernel/liveupdate/luo_file.c\n :doc: LUO File Descriptors\n \n+.. _flb:\n+\n LUO File Lifecycle Bound Global Data\n ====================================\n .. kernel-doc:: kernel/liveupdate/luo_flb.c\n@@ -70,3 +74,4 @@ See Also\n \n - :doc:`Live Update uAPI \u003c/userspace-api/liveupdate\u003e`\n - :doc:`/core-api/kho/index`\n+- :doc:`PCI \u003c/PCI/liveupdate\u003e`\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex a3ed337e827d86..347c435ca4047c 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -20824,6 +20824,21 @@ L:\tlinux-pci@vger.kernel.org\n S:\tSupported\n F:\tDocumentation/PCI/pci-error-recovery.rst\n \n+PCI LIVE UPDATE\n+M:\tDavid Matlack \u003cdmatlack@google.com\u003e\n+R:\tPasha Tatashin \u003cpasha.tatashin@soleen.com\u003e\n+R:\tMike Rapoport \u003crppt@kernel.org\u003e\n+R:\tPratyush Yadav \u003cpratyush@kernel.org\u003e\n+L:\tkexec@lists.infradead.org\n+L:\tlinux-pci@vger.kernel.org\n+S:\tMaintained\n+T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git\n+F:\tDocumentation/PCI/liveupdate.rst\n+F:\tdrivers/pci/liveupdate.c\n+F:\tdrivers/pci/liveupdate.h\n+F:\tinclude/linux/kho/abi/pci.h\n+F:\tinclude/linux/pci_liveupdate.h\n+\n PCI MSI DRIVER FOR ALTERA MSI IP\n L:\tlinux-pci@vger.kernel.org\n S:\tOrphan\ndiff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig\nindex 0c7408509ba24b..16fbd4212e0f55 100644\n--- a/drivers/pci/Kconfig\n+++ b/drivers/pci/Kconfig\n@@ -271,6 +271,21 @@ config VGA_ARB_MAX_GPUS\n \t Reserves space in the kernel to maintain resource locking for\n \t multiple GPUS. The overhead for each GPU is very small.\n \n+config PCI_LIVEUPDATE\n+\tbool \"PCI Live Update Support\"\n+\tdepends on PCI \u0026\u0026 LIVEUPDATE \u0026\u0026 64BIT \u0026\u0026 !CARDBUS\n+\thelp\n+\t Enable PCI core support for preserving PCI devices across Live\n+\t Update. This, in combination with support in a device's driver,\n+\t enables PCI devices to run and perform memory transactions\n+\t uninterrupted during a kexec for Live Update.\n+\n+\t This option should only be enabled by users who plan to use Live\n+\t Update for kernel upgrades and require preserving PCI devices during\n+\t those upgrades.\n+\n+\t If unsure, say N.\n+\n source \"drivers/pci/hotplug/Kconfig\"\n source \"drivers/pci/controller/Kconfig\"\n source \"drivers/pci/endpoint/Kconfig\"\ndiff --git a/drivers/pci/Makefile b/drivers/pci/Makefile\nindex 41ebc3b9a51824..e8d003cb675763 100644\n--- a/drivers/pci/Makefile\n+++ b/drivers/pci/Makefile\n@@ -16,6 +16,7 @@ obj-$(CONFIG_PROC_FS)\t\t+= proc.o\n obj-$(CONFIG_SYSFS)\t\t+= pci-sysfs.o slot.o\n obj-$(CONFIG_ACPI)\t\t+= pci-acpi.o\n obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o\n+obj-$(CONFIG_PCI_LIVEUPDATE)\t+= liveupdate.o\n endif\n \n obj-$(CONFIG_OF)\t\t+= of.o\ndiff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c\nnew file mode 100644\nindex 00000000000000..5935e88de073dc\n--- /dev/null\n+++ b/drivers/pci/liveupdate.c\n@@ -0,0 +1,910 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+/*\n+ * Copyright (c) 2026, Google LLC.\n+ * David Matlack \u003cdmatlack@google.com\u003e\n+ */\n+\n+/**\n+ * DOC: PCI Live Update\n+ *\n+ * The PCI subsystem participates in the Live Update process to enable drivers\n+ * to preserve their PCI devices across kexec.\n+ *\n+ * :ref:`FLB \u003cflb\u003e` Data\n+ * =====================\n+ *\n+ * PCI device preservation across Live Update is built on top of the\n+ * :ref:`LUO \u003cluo\u003e` support for file preservation across kexec. Drivers are\n+ * expected to expose a file to represent a single PCI device and support\n+ * preservation of that file with ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)``.\n+ * This allows userspace to control the preservation of devices and ensure\n+ * proper lifecycle management while a device is preserved. The first intended\n+ * use-case is preserving vfio-pci device files.\n+ *\n+ * The PCI core maintains its own state about what devices are being preserved\n+ * across Live Update using FLB data in LUO. Essentially, this allows the PCI\n+ * core to allocate struct pci_ser when the first device (file) is preserved\n+ * and free it when the last device (file) is unpreserved. After kexec, the\n+ * PCI core can fetch the struct pci_ser (which was constructed by the previous\n+ * kernel) from LUO at any time (e.g. during enumeration) so that it knows\n+ * which devices were preserved.\n+ *\n+ * To enable the PCI core to be notified whenever a file representing a device\n+ * is preserved, drivers must register their struct liveupdate_file_handler with\n+ * the PCI core by using the following APIs:\n+ *\n+ * * ``pci_liveupdate_register_flb(driver_file_handler)``\n+ * * ``pci_liveupdate_unregister_flb(driver_file_handler)``\n+ *\n+ * Device Tracking\n+ * ===============\n+ *\n+ * Drivers must notify the PCI core when specific devices are preserved or\n+ * unpreserved with the following APIs:\n+ *\n+ * * ``pci_liveupdate_preserve(pci_dev)``\n+ * * ``pci_liveupdate_unpreserve(pci_dev)``\n+ *\n+ * This allows the PCI core to keep its FLB data (struct pci_ser) up to date\n+ * with the list of **outgoing** preserved devices for the next kernel.\n+ *\n+ * After kexec, whenever a device is enumerated, the PCI core will check if it\n+ * is an **incoming** preserved device (i.e. preserved by the previous kernel)\n+ * by checking the incoming FLB data (struct pci_ser).\n+ *\n+ * Drivers must notify the PCI core when an **incoming** device is done\n+ * participating in the incoming Live Update with the following API:\n+ *\n+ * * ``pci_liveupdate_finish(pci_dev)``\n+ *\n+ * The PCI core does not enforce any ordering of ``pci_liveupdate_finish()`` and\n+ * ``pci_liveupdate_preserve()``. i.e. A PCI device can be **outgoing**\n+ * (preserved for next kernel) and **incoming** (preserved by previous kernel)\n+ * at the same time.\n+ *\n+ * Restrictions\n+ * ============\n+ *\n+ * The PCI core enforces the following restrictions on which devices can be\n+ * preserved. These may be relaxed in the future:\n+ *\n+ * * The device cannot be a Virtual Function (VF).\n+ *\n+ * * The device cannot require device-specific quirks to enable Access\n+ * Control Services (ACS).\n+ *\n+ * Driver Binding\n+ * ==============\n+ *\n+ * In the outgoing kernel, it is the driver's responsibility to ensure that it\n+ * does not release a device between pci_liveupdate_preserve() and\n+ * pci_liveupdate_unpreserve().\n+ *\n+ * In the incoming kernel, it is the driver's responsibility to ensure that it\n+ * does not release a preserved device between probe() and\n+ * pci_liveupdate_finish().\n+ *\n+ * It is the user's responsibility to ensure that incoming preserved devices are\n+ * bound to the correct driver. i.e. The PCI core does not protect against a\n+ * device getting preserved by driver A in the outgoing kernel and then getting\n+ * bound to driver B in the incoming kernel. This may change in the future.\n+ *\n+ * BDF Stability\n+ * =============\n+ *\n+ * The PCI core guarantees that preserved devices can be identified by the same\n+ * bus, device, and function numbers for as long as they are preserved\n+ * (including across kexec). To accomplish this, the PCI core always inherits\n+ * the secondary and subordinate bus numbers assigned to bridges during scanning\n+ * if any device is preserved. This is true even on architectures that always\n+ * assign new bus numbers during scanning. The kernel assumes the previous\n+ * kernel established a sane bus topology across kexec.\n+ *\n+ * If a misconfigured or unconfigured bridge is encountered during enumeration\n+ * while there are preserved devices, its secondary and subordinate bus numbers\n+ * will be cleared and devices below it will not be enumerated.\n+ *\n+ * PCI-to-PCI Bridges\n+ * ==================\n+ *\n+ * Any PCI-to-PCI bridges upstream of a preserved device are automatically\n+ * preserved when the device is preserved. The PCI core keeps track of the\n+ * number of downstream devices that are preserved under a bridge so that the\n+ * bridge is only unpreserved once all downstream devices are unpreserved.\n+ *\n+ * This enables the PCI core and any drivers bound to the bridge to participate\n+ * in the Live Update so that preserved endpoints can continue issuing memory\n+ * transactions during the Live Update.\n+ *\n+ * Handling Preserved Devices\n+ * ==========================\n+ *\n+ * The PCI core treats preserved devices differently than non-preserved devices.\n+ * This section enumerates those differences.\n+ *\n+ * * The PCI core inherits all ACS flags enabled on incoming preserved devices\n+ * rather than assigning new ones. This ensures that TLPs are routed the same\n+ * way after Live Update and ensures that IOMMU groups do not change. Note\n+ * that a device will use its inherited ACS flags for the lifetime of its\n+ * struct pci_dev (i.e. even after pci_liveupdate_finish()).\n+ *\n+ * * The PCI core inherits ARI Forwarding Enable on all bridges with downstream\n+ * preserved devices to ensure that all preserved devices on the bridge's\n+ * secondary bus are addressable after the Live Update.\n+ *\n+ * * The PCI core does not disable bus mastering on outgoing preserved devices\n+ * during kexec. This allows preserved devices to issue memory transactions\n+ * throughout the Live Update.\n+ */\n+\n+#define pr_fmt(fmt) \"PCI: \" KBUILD_BASENAME \": \" fmt\n+\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/kexec_handover.h\u003e\n+#include \u003clinux/kho/abi/pci.h\u003e\n+#include \u003clinux/liveupdate.h\u003e\n+#include \u003clinux/mm.h\u003e\n+#include \u003clinux/kho_block.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/pci.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"liveupdate.h\"\n+#include \"pci.h\"\n+\n+/**\n+ * struct pci_liveupdate_global - Global state for PCI Live Update support\n+ * @rwsem: Reader/writer semaphore used to protect the incoming and outgoing\n+ * FLBs, and the references to them in struct pci_dev.\n+ */\n+struct pci_liveupdate_global {\n+\tstruct rw_semaphore rwsem;\n+};\n+\n+static struct pci_liveupdate_global pci_liveupdate = {\n+\t.rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem),\n+};\n+\n+/**\n+ * struct pci_flb_outgoing - Outgoing PCI FLB object\n+ * @ser: Pointer to the preserved struct pci_ser.\n+ * @block_set: The KHO block set holding the outgoing devices.\n+ *\n+ * This structure holds the runtime state for the outgoing PCI Live Update\n+ * state. It wraps the serialized pci_ser and the block_set used to manage\n+ * the serialized entries.\n+ */\n+struct pci_flb_outgoing {\n+\tstruct pci_ser *ser;\n+\tstruct kho_block_set block_set;\n+};\n+\n+/**\n+ * struct pci_flb_incoming - Incoming PCI FLB object\n+ * @ser: The incoming struct pci_ser from the previous kernel.\n+ * @xa: Xarray used to quickly lookup devices in @ser.\n+ * @block_set: The KHO block set holding the incoming devices.\n+ *\n+ * This structure holds the runtime state for the incoming PCI Live Update\n+ * state. It wraps the serialized pci_ser, the block_set used to restore\n+ * the serialized entries, and an xarray for fast lookups.\n+ */\n+struct pci_flb_incoming {\n+\tstruct pci_ser *ser;\n+\tstruct xarray xa;\n+\tstruct kho_block_set block_set;\n+};\n+\n+static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)\n+{\n+\treturn (unsigned long)domain \u003c\u003c 16 | bdf;\n+}\n+static int pci_flb_preserve(struct liveupdate_flb_op_args *args)\n+{\n+\tstruct pci_flb_outgoing *outgoing;\n+\tstruct pci_ser *ser;\n+\n+\toutgoing = kzalloc_obj(*outgoing);\n+\tif (!outgoing)\n+\t\treturn -ENOMEM;\n+\n+\tser = kho_alloc_preserve(sizeof(*ser));\n+\tif (IS_ERR(ser)) {\n+\t\tkfree(outgoing);\n+\t\treturn PTR_ERR(ser);\n+\t}\n+\n+\tser-\u003enr_devices = 0;\n+\tser-\u003edevices = 0;\n+\n+\toutgoing-\u003eser = ser;\n+\tkho_block_set_init(\u0026outgoing-\u003eblock_set, sizeof(struct pci_dev_ser));\n+\n+\targs-\u003eobj = outgoing;\n+\targs-\u003edata = virt_to_phys(ser);\n+\treturn 0;\n+}\n+\n+static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)\n+{\n+\tstruct pci_flb_outgoing *outgoing = args-\u003eobj;\n+\n+\tWARN_ON(outgoing-\u003eser-\u003enr_devices);\n+\tkho_block_set_destroy(\u0026outgoing-\u003eblock_set);\n+\tkho_unpreserve_free(outgoing-\u003eser);\n+\tkfree(outgoing);\n+\tpr_debug(\"Unpreserved struct pci_ser\\n\");\n+}\n+\n+static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)\n+{\n+\tstruct pci_ser *ser = phys_to_virt(args-\u003edata);\n+\tstruct pci_flb_incoming *incoming;\n+\tstruct pci_dev_ser *dev_ser;\n+\tstruct kho_block_set_it it;\n+\tint ret = -ENOMEM;\n+\n+\tincoming = kzalloc_obj(*incoming);\n+\tif (!incoming)\n+\t\tgoto err_restore_free;\n+\n+\tincoming-\u003eser = ser;\n+\txa_init(\u0026incoming-\u003exa);\n+\n+\tkho_block_set_init(\u0026incoming-\u003eblock_set, sizeof(struct pci_dev_ser));\n+\tret = kho_block_set_restore(\u0026incoming-\u003eblock_set, ser-\u003edevices);\n+\tif (ret)\n+\t\tgoto err_free_incoming;\n+\n+\tkho_block_set_it_init(\u0026it, \u0026incoming-\u003eblock_set);\n+\twhile ((dev_ser = kho_block_set_it_read_entry(\u0026it))) {\n+\t\tunsigned long key;\n+\n+\t\tif (!dev_ser-\u003erefcount)\n+\t\t\tcontinue;\n+\n+\t\tkey = pci_ser_xa_key(dev_ser-\u003edomain, dev_ser-\u003ebdf);\n+\t\tret = xa_insert(\u0026incoming-\u003exa, key, dev_ser, GFP_KERNEL);\n+\t\tif (ret)\n+\t\t\tgoto err_block_set_destroy;\n+\t}\n+\n+\targs-\u003eobj = incoming;\n+\treturn 0;\n+\n+err_block_set_destroy:\n+\tkho_block_set_destroy(\u0026incoming-\u003eblock_set);\n+err_free_incoming:\n+\txa_destroy(\u0026incoming-\u003exa);\n+\tkfree(incoming);\n+err_restore_free:\n+\tkho_restore_free(ser);\n+\treturn ret;\n+}\n+\n+static void pci_flb_finish(struct liveupdate_flb_op_args *args)\n+{\n+\tstruct pci_flb_incoming *incoming = args-\u003eobj;\n+\n+\txa_destroy(\u0026incoming-\u003exa);\n+\tkho_block_set_destroy(\u0026incoming-\u003eblock_set);\n+\tkho_restore_free(incoming-\u003eser);\n+\tkfree(incoming);\n+}\n+\n+static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {\n+\t.preserve = pci_flb_preserve,\n+\t.unpreserve = pci_flb_unpreserve,\n+\t.retrieve = pci_flb_retrieve,\n+\t.finish = pci_flb_finish,\n+\t.owner = THIS_MODULE,\n+};\n+\n+static struct liveupdate_flb pci_liveupdate_flb = {\n+\t.ops = \u0026pci_liveupdate_flb_ops,\n+\t.compatible = PCI_LUO_FLB_COMPATIBLE,\n+};\n+\n+static struct pci_flb_outgoing *pci_liveupdate_flb_get_outgoing(void)\n+{\n+\tstruct pci_flb_outgoing *outgoing = NULL;\n+\tint ret;\n+\n+\tret = liveupdate_flb_get_outgoing(\u0026pci_liveupdate_flb, (void **)\u0026outgoing);\n+\tif (ret)\n+\t\treturn ERR_PTR(ret);\n+\n+\tif (!outgoing)\n+\t\treturn ERR_PTR(-ENOENT);\n+\n+\treturn outgoing;\n+}\n+\n+static struct pci_dev_ser *pci_get_empty_or_append(struct pci_flb_outgoing *outgoing)\n+{\n+\tstruct pci_dev_ser *dev_ser, *found = NULL;\n+\tstruct kho_block_set_it it;\n+\tint err;\n+\tu32 count = 0;\n+\n+\tkho_block_set_it_init(\u0026it, \u0026outgoing-\u003eblock_set);\n+\twhile ((dev_ser = kho_block_set_it_read_entry(\u0026it))) {\n+\t\tcount++;\n+\t\tif (dev_ser-\u003erefcount == 0 \u0026\u0026 !found)\n+\t\t\tfound = dev_ser;\n+\t}\n+\n+\tif (found)\n+\t\treturn found;\n+\n+\terr = kho_block_set_grow(\u0026outgoing-\u003eblock_set, count + 1);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\n+\tif (count == 0)\n+\t\tkho_block_set_it_init(\u0026it, \u0026outgoing-\u003eblock_set);\n+\n+\tdev_ser = kho_block_set_it_reserve_entry(\u0026it);\n+\tif (!dev_ser)\n+\t\treturn ERR_PTR(-ENOSPC);\n+\n+\treturn dev_ser;\n+}\n+\n+static int pci_liveupdate_unpreserve_device(struct pci_flb_outgoing *outgoing, struct pci_dev *dev)\n+{\n+\tstruct pci_dev_ser *dev_ser = dev-\u003eliveupdate.outgoing;\n+\n+\tif (dev-\u003eliveupdate.frozen) {\n+\t\tpci_warn(dev, \"Cannot unpreserve device after it is frozen!\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!dev_ser) {\n+\t\tpci_warn(dev, \"Cannot unpreserve device that is not preserved\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!dev_ser-\u003erefcount) {\n+\t\tpci_WARN(dev, 1, \"Preserved device has a 0 refcount!\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (--dev_ser-\u003erefcount)\n+\t\treturn 0;\n+\n+\tpci_info(dev, \"Device will no longer be preserved across next Live Update\\n\");\n+\toutgoing-\u003eser-\u003enr_devices--;\n+\tmemset(dev_ser, 0, sizeof(*dev_ser));\n+\tdev-\u003eliveupdate.outgoing = NULL;\n+\treturn 0;\n+}\n+\n+static int pci_liveupdate_preserve_device_again(struct pci_dev *dev)\n+{\n+\tif (!dev-\u003eliveupdate.outgoing-\u003erefcount) {\n+\t\tpci_WARN(dev, 1, \"Preserved device with 0 refcount!\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Endpoint devices should not be preserved more than once. Bridges are\n+\t * preserved once for every downstream device that is preserved.\n+\t */\n+\tif (!dev-\u003esubordinate)\n+\t\treturn -EBUSY;\n+\n+\tdev-\u003eliveupdate.outgoing-\u003erefcount++;\n+\treturn 0;\n+}\n+\n+static int __pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing, struct pci_dev *dev)\n+{\n+\tstruct pci_dev_ser *dev_ser;\n+\n+\t/*\n+\t * Do not preserve devices that rely on device-specific ACS equivalents\n+\t * (for now) since that would complicate keeping ACS constant across\n+\t * Live Update.\n+\t */\n+\tif (pci_need_dev_specific_enable_acs(dev)) {\n+\t\tpci_warn(dev, \"Refusing to preserve device that relies on ACS quirks\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tdev_ser = pci_get_empty_or_append(outgoing);\n+\tif (IS_ERR(dev_ser))\n+\t\treturn PTR_ERR(dev_ser);\n+\n+\tpci_info(dev, \"Device will be preserved across next Live Update\\n\");\n+\toutgoing-\u003eser-\u003enr_devices++;\n+\toutgoing-\u003eser-\u003edevices = kho_block_set_head_pa(\u0026outgoing-\u003eblock_set);\n+\n+\tdev_ser-\u003edomain = pci_domain_nr(dev-\u003ebus);\n+\tdev_ser-\u003ebdf = pci_dev_id(dev);\n+\tdev_ser-\u003erefcount = 1;\n+\n+\tdev-\u003eliveupdate.outgoing = dev_ser;\n+\treturn 0;\n+}\n+\n+static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing, struct pci_dev *dev)\n+{\n+\tif (dev-\u003eliveupdate.frozen) {\n+\t\tpci_warn(dev, \"Cannot preserve device after it is frozen!\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (dev-\u003eliveupdate.outgoing)\n+\t\treturn pci_liveupdate_preserve_device_again(dev);\n+\n+\treturn __pci_liveupdate_preserve_device(outgoing, dev);\n+}\n+\n+#define for_each_pci_dev_in_path(_d, _start, _end) \\\n+\tfor ((_d) = (_start); (_d) != (_end); (_d) = (_d)-\u003ebus-\u003eself)\n+\n+static void __pci_liveupdate_unpreserve_path(struct pci_flb_outgoing *outgoing,\n+\t\t\t\t\t struct pci_dev *start,\n+\t\t\t\t\t struct pci_dev *end)\n+{\n+\tstruct pci_dev *dev;\n+\n+\tfor_each_pci_dev_in_path(dev, start, end) {\n+\t\tif (pci_liveupdate_unpreserve_device(outgoing, dev))\n+\t\t\treturn;\n+\t}\n+}\n+\n+static void pci_liveupdate_unpreserve_path(struct pci_flb_outgoing *outgoing,\n+\t\t\t\t\t struct pci_dev *start)\n+{\n+\t__pci_liveupdate_unpreserve_path(outgoing, start, /*end=*/NULL);\n+}\n+\n+static int pci_liveupdate_preserve_path(struct pci_flb_outgoing *outgoing,\n+\t\t\t\t\tstruct pci_dev *start)\n+{\n+\tstruct pci_dev *dev;\n+\tint ret;\n+\n+\tfor_each_pci_dev_in_path(dev, start, NULL) {\n+\t\tret = pci_liveupdate_preserve_device(outgoing, dev);\n+\t\tif (ret) {\n+\t\t\t__pci_liveupdate_unpreserve_path(outgoing, start, dev);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * pci_liveupdate_preserve() - Preserve a PCI device across Live Update\n+ * @dev: The PCI device to preserve.\n+ *\n+ * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be\n+ * preserved across the next Live Update. Drivers are expected to call\n+ * pci_liveupdate_preserve() from their struct liveupdate_file_handler\n+ * preserve() callback to ensure the outgoing struct pci_ser is already set up.\n+ *\n+ * pci_liveupdate_preserve() automatically preserves all bridges upstream of\n+ * @dev.\n+ *\n+ * Returns: 0 on success, \u003c0 on failure.\n+ */\n+int pci_liveupdate_preserve(struct pci_dev *dev)\n+{\n+\tstruct pci_flb_outgoing *outgoing = NULL;\n+\n+\tif (dev-\u003eis_virtfn)\n+\t\treturn -EINVAL;\n+\n+\tguard(rwsem_write)(\u0026pci_liveupdate.rwsem);\n+\n+\toutgoing = pci_liveupdate_flb_get_outgoing();\n+\tif (IS_ERR(outgoing))\n+\t\treturn PTR_ERR(outgoing);\n+\n+\treturn pci_liveupdate_preserve_path(outgoing, dev);\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);\n+\n+/**\n+ * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device\n+ * @dev: The PCI device to unpreserve.\n+ *\n+ * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no\n+ * longer be preserved across the next Live Update. Drivers are expected to call\n+ * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler\n+ * unpreserve() callback to ensure the outgoing struct pci_ser is already set\n+ * up.\n+ *\n+ * pci_liveupdate_unpreserve() automatically unpreserves all bridges upstream of\n+ * @dev.\n+ */\n+void pci_liveupdate_unpreserve(struct pci_dev *dev)\n+{\n+\tstruct pci_flb_outgoing *outgoing = NULL;\n+\n+\tguard(rwsem_write)(\u0026pci_liveupdate.rwsem);\n+\n+\toutgoing = pci_liveupdate_flb_get_outgoing();\n+\tif (IS_ERR(outgoing)) {\n+\t\tpci_warn(dev, \"Cannot unpreserve device without outgoing Live Update state\\n\");\n+\t\treturn;\n+\t}\n+\n+\tpci_liveupdate_unpreserve_path(outgoing, dev);\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);\n+\n+static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)\n+{\n+\tstruct pci_flb_incoming *incoming = NULL;\n+\tint ret;\n+\n+\tret = liveupdate_flb_get_incoming(\u0026pci_liveupdate_flb, (void **)\u0026incoming);\n+\n+\t/* Live Update is not enabled. */\n+\tif (ret == -EOPNOTSUPP)\n+\t\treturn NULL;\n+\n+\t/* Live Update is enabled, but there is no incoming FLB data. */\n+\tif (ret == -ENODATA)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * Live Update is enabled and there is incoming FLB data, but none of it\n+\t * matches pci_liveupdate_flb.compatible.\n+\t *\n+\t * This could mean that no PCI FLB data was passed by the previous\n+\t * kernel, but it could also mean the previous kernel used a different\n+\t * compatibility string (i.e. a different ABI).\n+\t */\n+\tif (ret == -ENOENT) {\n+\t\tpr_info_once(\"No incoming FLB matched %s\\n\", pci_liveupdate_flb.compatible);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * There is incoming FLB data that matches pci_liveupdate_flb.compatible\n+\t * but it cannot be retrieved.\n+\t */\n+\tif (ret)\n+\t\tpanic(\"Failed to retrieve incoming FLB data (%d)\\n\", ret);\n+\n+\treturn incoming;\n+}\n+\n+static void pci_liveupdate_flb_put_incoming(void)\n+{\n+\tliveupdate_flb_put_incoming(\u0026pci_liveupdate_flb);\n+}\n+\n+/**\n+ * pci_liveupdate_scan_bridge_begin() - Determine if a bridge should inherit bus numbers\n+ * @bus: The parent bus of the bridge.\n+ * @dev: The PCI bridge device.\n+ * @pass: The scan pass (0 for first pass, 1 for second pass).\n+ *\n+ * This function is called by the PCI core when it begins scanning a bridge.\n+ * It determines whether the bridge should inherit the secondary and subordinate\n+ * bus numbers assigned to it by the previous kernel. This is necessary to\n+ * keep bus numbers constant for preserved devices downstream of the bridge.\n+ *\n+ * Return: True if bus numbers should be inherited, false otherwise.\n+ */\n+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,\n+\t\t\t\t int pass)\n+{\n+\tstruct pci_dev *parent = bus-\u003eself;\n+\n+\t/*\n+\t * On the second pass, reuse the value that was set on the first pass\n+\t * so that the passes are consistent with one another.\n+\t */\n+\tif (pass)\n+\t\treturn dev-\u003eliveupdate.inherit_buses;\n+\n+\t/*\n+\t * If the parent bridge is being forced to inherit its bus numbers\n+\t * during this scan then this bridge must as well, otherwise the PCI\n+\t * core could expand this bridge's reservation beyond its parent (which\n+\t * cannot expand).\n+\t */\n+\tif (parent \u0026\u0026 parent-\u003eliveupdate.inherit_buses) {\n+\t\tdev-\u003eliveupdate.inherit_buses = true;\n+\t\treturn true;\n+\t}\n+\n+\t/*\n+\t * Otherwise, if there are any incoming preserved devices, force the\n+\t * bus numbers to be inherited to avoid changing the bus numbers\n+\t * assigned to those devices during enumeration.\n+\t *\n+\t * To keep things simple, inherit bus numbers on all bridges if any PCI\n+\t * devices are incoming, to ensure that no bridge's reservation is\n+\t * expanded to overlap with a preserved device downstream of a different\n+\t * bridge.\n+\t */\n+\tscoped_guard(rwsem_read, \u0026pci_liveupdate.rwsem) {\n+\t\tstruct pci_flb_incoming *incoming;\n+\n+\t\tincoming = pci_liveupdate_flb_get_incoming();\n+\t\tif (!incoming) {\n+\t\t\tdev-\u003eliveupdate.inherit_buses = false;\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t/*\n+\t\t * It is safe to sample incoming-\u003eser-\u003enr_devices and then\n+\t\t * drop the rwsem since nr_devices will only decrease. Thus the\n+\t\t * only \"race\" is that the current scan will be overly\n+\t\t * conservative and force bus inheritance.\n+\t\t */\n+\t\tdev-\u003eliveupdate.inherit_buses = !!incoming-\u003eser-\u003enr_devices;\n+\t\tpci_liveupdate_flb_put_incoming();\n+\t}\n+\n+\treturn dev-\u003eliveupdate.inherit_buses;\n+}\n+\n+/**\n+ * pci_liveupdate_scan_bridge_end() - Finish scanning a PCI bridge\n+ * @dev: The PCI bridge device.\n+ * @pass: The scan pass (0 for first pass, 1 for second pass).\n+ *\n+ * This function is called by the PCI core when it finishes scanning a bridge.\n+ * It clears the inheritance status after the second pass so it can be\n+ * re-evaluated on future scans.\n+ */\n+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)\n+{\n+\t/*\n+\t * Clear inherit_buses after the second pass so it can be re-evaluated\n+\t * on future scans.\n+\t */\n+\tif (pass)\n+\t\tdev-\u003eliveupdate.inherit_buses = false;\n+}\n+\n+void pci_liveupdate_setup_device(struct pci_dev *dev)\n+{\n+\tstruct pci_flb_incoming *incoming;\n+\tstruct pci_dev_ser *dev_ser;\n+\tunsigned long key;\n+\n+\tguard(rwsem_write)(\u0026pci_liveupdate.rwsem);\n+\n+\tincoming = pci_liveupdate_flb_get_incoming();\n+\tif (!incoming)\n+\t\treturn;\n+\n+\tkey = pci_ser_xa_key(pci_domain_nr(dev-\u003ebus), pci_dev_id(dev));\n+\tdev_ser = xa_load(\u0026incoming-\u003exa, key);\n+\n+\t/*\n+\t * This device was not preserved across Live Update, or it was preserved\n+\t * but has already been probed and gone through pci_liveupdate_finish(),\n+\t * e.g. due to removing and re-adding the device. Either way, it's not\n+\t * treated as incoming-preserved.\n+\t */\n+\tif (!dev_ser || !dev_ser-\u003erefcount) {\n+\t\tpci_liveupdate_flb_put_incoming();\n+\t\treturn;\n+\t}\n+\n+\n+\tpci_info(dev, \"Device was preserved by previous kernel across Live Update\\n\");\n+\tdev-\u003eliveupdate.incoming = dev_ser;\n+\tdev-\u003eliveupdate.was_preserved = true;\n+\n+\t/*\n+\t * Hold the ref on the incoming FLB until pci_liveupdate_finish() so\n+\t * that dev-\u003eliveupdate.incoming cannot get freed while the PCI core\n+\t * has a pointer to it. It's better to leak the incoming FLB than do a\n+\t * use-after-free if driver does not call pci_liveupdate_finish().\n+\t */\n+}\n+\n+void pci_liveupdate_cleanup_device(struct pci_dev *dev)\n+{\n+\t/*\n+\t * It should be safe to READ_ONCE() outside of the rwsem during cleanup\n+\t * since there should no longer be any references to @dev on the system.\n+\t *\n+\t * This should never happen in practice. Drivers should block removal\n+\t * while a device is preserved.\n+\t */\n+\tif (READ_ONCE(dev-\u003eliveupdate.outgoing))\n+\t\tpci_WARN(dev, 1, \"Destroying outgoing-preserved device!\\n\");\n+\n+\tif (READ_ONCE(dev-\u003eliveupdate.incoming)) {\n+\t\tpci_WARN(dev, 1, \"Destroying incoming-preserved device!\\n\");\n+\t\tpci_liveupdate_flb_put_incoming();\n+\t}\n+}\n+\n+void pci_liveupdate_freeze(struct pci_dev *dev)\n+{\n+\tguard(rwsem_write)(\u0026pci_liveupdate.rwsem);\n+\tdev-\u003eliveupdate.frozen = 1;\n+}\n+\n+static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)\n+{\n+\tif (!dev-\u003eliveupdate.incoming) {\n+\t\tpci_warn(dev, \"Cannot finish preserving an unpreserved device\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!dev-\u003eliveupdate.incoming-\u003erefcount) {\n+\t\tpci_WARN(dev, 1, \"Preserved device has a 0 refcount!\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Decrement the refcount so this device does not get treated as an\n+\t * incoming device again, e.g. in case pci_liveupdate_setup_device()\n+\t * gets called again because the device is hot-plugged.\n+\t */\n+\tif (--dev-\u003eliveupdate.incoming-\u003erefcount)\n+\t\treturn 0;\n+\n+\tpci_info(dev, \"Device is finished participating in Live Update\\n\");\n+\tdev-\u003eliveupdate.incoming = NULL;\n+\tser-\u003enr_devices--;\n+\tpci_liveupdate_flb_put_incoming();\n+\treturn 0;\n+}\n+\n+static void pci_liveupdate_finish_path(struct pci_ser *ser, struct pci_dev *start)\n+{\n+\tstruct pci_dev *dev;\n+\n+\tfor_each_pci_dev_in_path(dev, start, NULL) {\n+\t\tif (pci_liveupdate_finish_device(ser, dev))\n+\t\t\treturn;\n+\t}\n+}\n+\n+/**\n+ * pci_liveupdate_finish() - Finish the preservation of a PCI device\n+ * @dev: The PCI device\n+ *\n+ * pci_liveupdate_finish() notifies the PCI core that a PCI device that was\n+ * preserved across the previous Live Update has finished participating in Live\n+ * Update. Drivers must call pci_liveupdate_finish() from their struct\n+ * liveupdate_file_handler finish() callback to ensure the incoming struct\n+ * pci_ser is allocated.\n+ *\n+ * pci_liveupdate_finish() automatically finishes all bridges upstream of @dev.\n+ */\n+void pci_liveupdate_finish(struct pci_dev *dev)\n+{\n+\tstruct pci_flb_incoming *incoming;\n+\n+\tguard(rwsem_write)(\u0026pci_liveupdate.rwsem);\n+\n+\tincoming = pci_liveupdate_flb_get_incoming();\n+\tif (!incoming) {\n+\t\tpci_warn(dev, \"Cannot finish preserving device without incoming FLB\\n\");\n+\t\treturn;\n+\t}\n+\n+\tpci_liveupdate_finish_path(incoming-\u003eser, dev);\n+\tpci_liveupdate_flb_put_incoming();\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_finish);\n+\n+void pci_liveupdate_init_acs(struct pci_dev *dev)\n+{\n+\tguard(rwsem_read)(\u0026pci_liveupdate.rwsem);\n+\n+\tif (!dev-\u003eacs_cap || !dev-\u003eliveupdate.incoming)\n+\t\treturn;\n+\n+\tpci_read_config_word(dev, dev-\u003eacs_cap + PCI_ACS_CTRL, \u0026dev-\u003eliveupdate.acs_ctrl);\n+}\n+\n+int pci_liveupdate_enable_acs(struct pci_dev *dev)\n+{\n+\tu16 acs_ctrl = dev-\u003eliveupdate.acs_ctrl;\n+\tu16 acs_cap = dev-\u003eacs_cap;\n+\n+\t/*\n+\t * Use liveupdate.was_preserved instead of liveupdate.incoming since the\n+\t * device's ACS controls should not change even after the device is\n+\t * finished participating in the Live Update.\n+\t */\n+\tif (!dev-\u003eliveupdate.was_preserved)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * The previous kernel should not have preserved any devices that\n+\t * require device-specific quirks to enable ACS, but if such a device is\n+\t * detected (e.g. new device-specific ACS quirk in the current kernel),\n+\t * log a big warning and fall back to the normal enable ACS path.\n+\t */\n+\tif (pci_need_dev_specific_enable_acs(dev)) {\n+\t\tpci_warn(dev, \"Device-specific quirk required to enable ACS!\\n\");\n+\t\tWARN_ON_ONCE(true);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (acs_cap)\n+\t\tpci_write_config_word(dev, acs_cap + PCI_ACS_CTRL, acs_ctrl);\n+\n+\treturn 0;\n+}\n+\n+int pci_liveupdate_configure_ari(struct pci_dev *dev)\n+{\n+\tu16 val;\n+\n+\tguard(rwsem_read)(\u0026pci_liveupdate.rwsem);\n+\n+\tif (!dev-\u003eliveupdate.incoming)\n+\t\treturn -EINVAL;\n+\n+\tpcie_capability_read_word(dev, PCI_EXP_DEVCTL2, \u0026val);\n+\tdev-\u003eari_enabled = !!(val \u0026 PCI_EXP_DEVCTL2_ARI);\n+\treturn 0;\n+}\n+\n+bool pci_liveupdate_is_outgoing(struct pci_dev *dev)\n+{\n+\tguard(rwsem_read)(\u0026pci_liveupdate.rwsem);\n+\tpci_WARN_ONCE(dev, !dev-\u003eliveupdate.frozen, \"Preservation status is unstable!\\n\");\n+\treturn dev-\u003eliveupdate.outgoing;\n+}\n+\n+/**\n+ * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved\n+ * @dev: The PCI device to check\n+ *\n+ * Check if a device was preserved across Live Update by the previous kernel,\n+ * i.e. the device is incoming-preserved. Note that a device is only considered\n+ * incoming-preserved prior to pci_liveupdate_finish(). It is up to drivers to\n+ * synchronize usage of pci_liveupdate_is_incoming() with their own call to\n+ * pci_liveupdate_finish() to avoid acting on stale data.\n+ *\n+ * Returns: True if the device is incoming-preserved, false otherwise.\n+ */\n+bool pci_liveupdate_is_incoming(struct pci_dev *dev)\n+{\n+\tguard(rwsem_read)(\u0026pci_liveupdate.rwsem);\n+\treturn dev-\u003eliveupdate.incoming;\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);\n+\n+/**\n+ * pci_liveupdate_register_flb() - Register a file handler with the PCI core\n+ * @fh: The file handler to register.\n+ *\n+ * Drivers should call pci_liveupdate_register_flb() to register their\n+ * struct liveupdate_file_handler with the PCI core. This enables the PCI core\n+ * to allocate its outgoing struct pci_ser whenever the first device is\n+ * preserved, and free it when the last device is unpreserved.\n+ *\n+ * Return: 0 on success, \u003c0 on failure.\n+ */\n+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)\n+{\n+\tpr_debug(\"Registering file handler \\\"%s\\\"\\n\", fh-\u003ecompatible);\n+\treturn liveupdate_register_flb(fh, \u0026pci_liveupdate_flb);\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);\n+\n+/**\n+ * pci_liveupdate_unregister_flb() - Unregister a file handler with the PCI core\n+ * @fh: The file handler to unregister.\n+ */\n+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)\n+{\n+\tpr_debug(\"Unregistering file handler \\\"%s\\\"\\n\", fh-\u003ecompatible);\n+\tliveupdate_unregister_flb(fh, \u0026pci_liveupdate_flb);\n+}\n+EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);\ndiff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h\nnew file mode 100644\nindex 00000000000000..b266406aaac80d\n--- /dev/null\n+++ b/drivers/pci/liveupdate.h\n@@ -0,0 +1,68 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * PCI Live Update support (core API)\n+ *\n+ * Copyright (c) 2026, Google LLC.\n+ * David Matlack \u003cdmatlack@google.com\u003e\n+ */\n+#ifndef DRIVERS_PCI_LIVEUPDATE_H\n+#define DRIVERS_PCI_LIVEUPDATE_H\n+\n+#include \u003clinux/pci.h\u003e\n+\n+#ifdef CONFIG_PCI_LIVEUPDATE\n+void pci_liveupdate_setup_device(struct pci_dev *dev);\n+void pci_liveupdate_cleanup_device(struct pci_dev *dev);\n+void pci_liveupdate_freeze(struct pci_dev *dev);\n+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,\n+\t\t\t\t int pass);\n+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);\n+void pci_liveupdate_init_acs(struct pci_dev *dev);\n+int pci_liveupdate_enable_acs(struct pci_dev *dev);\n+int pci_liveupdate_configure_ari(struct pci_dev *dev);\n+bool pci_liveupdate_is_outgoing(struct pci_dev *dev);\n+#else\n+static inline void pci_liveupdate_setup_device(struct pci_dev *dev)\n+{\n+}\n+\n+static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)\n+{\n+}\n+\n+static inline void pci_liveupdate_freeze(struct pci_dev *dev)\n+{\n+}\n+\n+static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,\n+\t\t\t\t\t\t struct pci_dev *dev,\n+\t\t\t\t\t\t int pass)\n+{\n+\treturn false;\n+}\n+\n+static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)\n+{\n+}\n+\n+static inline void pci_liveupdate_init_acs(struct pci_dev *dev)\n+{\n+}\n+\n+static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)\n+{\n+\treturn -EINVAL;\n+}\n+\n+static inline int pci_liveupdate_configure_ari(struct pci_dev *dev)\n+{\n+\treturn -EINVAL;\n+}\n+\n+static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev)\n+{\n+\treturn false;\n+}\n+#endif\n+\n+#endif /* DRIVERS_PCI_LIVEUPDATE_H */\ndiff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c\nindex f36778e62ac1a5..47cb3dcaa92782 100644\n--- a/drivers/pci/pci-driver.c\n+++ b/drivers/pci/pci-driver.c\n@@ -21,6 +21,7 @@\n #include \u003clinux/acpi.h\u003e\n #include \u003clinux/dma-map-ops.h\u003e\n #include \u003clinux/iommu.h\u003e\n+#include \"liveupdate.h\"\n #include \"pci.h\"\n #include \"pcie/portdrv.h\"\n \n@@ -536,6 +537,7 @@ static void pci_device_shutdown(struct device *dev)\n \tstruct pci_dev *pci_dev = to_pci_dev(dev);\n \tstruct pci_driver *drv = pci_dev-\u003edriver;\n \n+\tpci_liveupdate_freeze(pci_dev);\n \tpm_runtime_resume(dev);\n \n \tif (drv \u0026\u0026 drv-\u003eshutdown)\n@@ -544,11 +546,14 @@ static void pci_device_shutdown(struct device *dev)\n \t/*\n \t * If this is a kexec reboot, turn off Bus Master bit on the\n \t * device to tell it to not continue to do DMA. Don't touch\n-\t * devices in D3cold or unknown states.\n+\t * devices being preserved for Live Update or in D3cold or\n+\t * unknown states.\n+\t *\n \t * If it is not a kexec reboot, firmware will hit the PCI\n \t * devices with big hammer and stop their DMA any way.\n \t */\n-\tif (kexec_in_progress \u0026\u0026 (pci_dev-\u003ecurrent_state \u003c= PCI_D3hot))\n+\tif (kexec_in_progress \u0026\u0026 !pci_liveupdate_is_outgoing(pci_dev) \u0026\u0026\n+\t pci_dev-\u003ecurrent_state \u003c= PCI_D3hot)\n \t\tpci_clear_master(pci_dev);\n }\n \ndiff --git a/drivers/pci/pci.c b/drivers/pci/pci.c\nindex 77b17b13ee6154..e0c133b66a3592 100644\n--- a/drivers/pci/pci.c\n+++ b/drivers/pci/pci.c\n@@ -34,6 +34,8 @@\n #include \u003clinux/aer.h\u003e\n #include \u003clinux/bitfield.h\u003e\n #include \u003clinux/suspend.h\u003e\n+\n+#include \"liveupdate.h\"\n #include \"pci.h\"\n \n DEFINE_MUTEX(pci_slot_mutex);\n@@ -1008,6 +1010,9 @@ void pci_enable_acs(struct pci_dev *dev)\n \tbool enable_acs = false;\n \tint pos;\n \n+\tif (!pci_liveupdate_enable_acs(dev))\n+\t\treturn;\n+\n \t/* If an iommu is present we start with kernel default caps */\n \tif (pci_acs_enable) {\n \t\tif (pci_dev_specific_enable_acs(dev))\n@@ -3523,7 +3528,7 @@ void pci_configure_ari(struct pci_dev *dev)\n \tu32 cap;\n \tstruct pci_dev *bridge;\n \n-\tif (pcie_ari_disabled || !pci_is_pcie(dev) || dev-\u003edevfn)\n+\tif (!pci_is_pcie(dev) || dev-\u003edevfn)\n \t\treturn;\n \n \tbridge = dev-\u003ebus-\u003eself;\n@@ -3534,6 +3539,12 @@ void pci_configure_ari(struct pci_dev *dev)\n \tif (!(cap \u0026 PCI_EXP_DEVCAP2_ARI))\n \t\treturn;\n \n+\tif (!pci_liveupdate_configure_ari(bridge))\n+\t\treturn;\n+\n+\tif (pcie_ari_disabled)\n+\t\treturn;\n+\n \tif (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI)) {\n \t\tpcie_capability_set_word(bridge, PCI_EXP_DEVCTL2,\n \t\t\t\t\t PCI_EXP_DEVCTL2_ARI);\n@@ -3689,6 +3700,7 @@ void pci_acs_init(struct pci_dev *dev)\n \n \tpci_read_config_word(dev, pos + PCI_ACS_CAP, \u0026dev-\u003eacs_capabilities);\n \tpci_disable_broken_acs_cap(dev);\n+\tpci_liveupdate_init_acs(dev);\n }\n \n /**\ndiff --git a/drivers/pci/pci.h b/drivers/pci/pci.h\nindex 4469e1a77f3c13..988a18b3204af5 100644\n--- a/drivers/pci/pci.h\n+++ b/drivers/pci/pci.h\n@@ -1047,6 +1047,7 @@ void pci_acs_init(struct pci_dev *dev);\n void pci_enable_acs(struct pci_dev *dev);\n #ifdef CONFIG_PCI_QUIRKS\n int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);\n+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev);\n int pci_dev_specific_enable_acs(struct pci_dev *dev);\n int pci_dev_specific_disable_acs_redir(struct pci_dev *dev);\n void pci_disable_broken_acs_cap(struct pci_dev *pdev);\n@@ -1057,6 +1058,10 @@ static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev,\n {\n \treturn -ENOTTY;\n }\n+static inline bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)\n+{\n+\treturn false;\n+}\n static inline int pci_dev_specific_enable_acs(struct pci_dev *dev)\n {\n \treturn -ENOTTY;\ndiff --git a/drivers/pci/probe.c b/drivers/pci/probe.c\nindex dd0abbc63e18d1..165973bc92aabb 100644\n--- a/drivers/pci/probe.c\n+++ b/drivers/pci/probe.c\n@@ -24,6 +24,7 @@\n #include \u003clinux/pm_runtime.h\u003e\n #include \u003clinux/bitfield.h\u003e\n #include \u003ctrace/events/pci.h\u003e\n+#include \"liveupdate.h\"\n #include \"pci.h\"\n \n static struct resource busn_resource = {\n@@ -1396,6 +1397,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \t\t\t\t int max, unsigned int available_buses,\n \t\t\t\t int pass)\n {\n+\tbool liveupdate, assign_new_buses = pcibios_assign_all_busses();\n \tstruct pci_bus *child;\n \tu32 buses;\n \tu16 bctl;\n@@ -1405,6 +1407,10 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \tu8 fixed_sec, fixed_sub;\n \tint next_busnr;\n \n+\tliveupdate = pci_liveupdate_scan_bridge_begin(bus, dev, pass);\n+\tif (liveupdate)\n+\t\tassign_new_buses = false;\n+\n \t/*\n \t * Make sure the bridge is powered on to be able to access config\n \t * space of devices below it.\n@@ -1448,8 +1454,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \t\tgoto out;\n \t}\n \n-\tif ((secondary || subordinate) \u0026\u0026\n-\t !pcibios_assign_all_busses() \u0026\u0026 !broken) {\n+\tif ((secondary || subordinate) \u0026\u0026 !assign_new_buses \u0026\u0026 !broken) {\n \t\tunsigned int cmax, buses;\n \n \t\t/*\n@@ -1491,8 +1496,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \t\t * do in the second pass.\n \t\t */\n \t\tif (!pass) {\n-\t\t\tif (pcibios_assign_all_busses() || broken)\n-\n+\t\t\tif (assign_new_buses || broken)\n \t\t\t\t/*\n \t\t\t\t * Temporarily disable forwarding of the\n \t\t\t\t * configuration cycles on all bridges in\n@@ -1506,6 +1510,11 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \t\t\tgoto out;\n \t\t}\n \n+\t\tif (liveupdate) {\n+\t\t\tpci_err(dev, \"Cannot reconfigure bridge during Live Update, skipping\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\t/* Clear errors */\n \t\tpci_write_config_word(dev, PCI_STATUS, 0xffff);\n \n@@ -1566,6 +1575,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,\n \tpci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);\n \n \tpm_runtime_put(\u0026dev-\u003edev);\n+\tpci_liveupdate_scan_bridge_end(dev, pass);\n \n \treturn max;\n }\n@@ -2064,6 +2074,8 @@ int pci_setup_device(struct pci_dev *dev)\n \tif (pci_early_dump)\n \t\tearly_dump_pci_device(dev);\n \n+\tpci_liveupdate_setup_device(dev);\n+\n \t/* Need to have dev-\u003eclass ready */\n \tdev-\u003ecfg_size = pci_cfg_space_size(dev);\n \n@@ -2187,6 +2199,7 @@ int pci_setup_device(struct pci_dev *dev)\n \tdefault:\t\t\t\t /* unknown header */\n \t\tpci_err(dev, \"unknown header type %02x, ignoring device\\n\",\n \t\t\tdev-\u003ehdr_type);\n+\t\tpci_liveupdate_cleanup_device(dev);\n \t\tpci_release_of_node(dev);\n \t\treturn -EIO;\n \n@@ -2485,6 +2498,7 @@ static void pci_release_dev(struct device *dev)\n \n \tpci_dev = to_pci_dev(dev);\n \tpci_release_capabilities(pci_dev);\n+\tpci_liveupdate_cleanup_device(pci_dev);\n \tpci_release_of_node(pci_dev);\n \tpcibios_release_device(pci_dev);\n \tpci_bus_put(pci_dev-\u003ebus);\ndiff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c\nindex b09f27f7846fcd..99b819f38e49c2 100644\n--- a/drivers/pci/quirks.c\n+++ b/drivers/pci/quirks.c\n@@ -5374,9 +5374,6 @@ static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev)\n */\n static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev)\n {\n-\tif (!pci_quirk_intel_pch_acs_match(dev))\n-\t\treturn -ENOTTY;\n-\n \tif (pci_quirk_enable_intel_lpc_acs(dev)) {\n \t\tpci_warn(dev, \"Failed to enable Intel PCH ACS quirk\\n\");\n \t\treturn 0;\n@@ -5396,9 +5393,6 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev)\n \tint pos;\n \tu32 cap, ctrl;\n \n-\tif (!pci_quirk_intel_spt_pch_acs_match(dev))\n-\t\treturn -ENOTTY;\n-\n \tpos = dev-\u003eacs_cap;\n \tif (!pos)\n \t\treturn -ENOTTY;\n@@ -5426,9 +5420,6 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)\n \tint pos;\n \tu32 cap, ctrl;\n \n-\tif (!pci_quirk_intel_spt_pch_acs_match(dev))\n-\t\treturn -ENOTTY;\n-\n \tpos = dev-\u003eacs_cap;\n \tif (!pos)\n \t\treturn -ENOTTY;\n@@ -5448,56 +5439,63 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)\n static const struct pci_dev_acs_ops {\n \tu16 vendor;\n \tu16 device;\n+\tbool (*match)(struct pci_dev *dev);\n \tint (*enable_acs)(struct pci_dev *dev);\n \tint (*disable_acs_redir)(struct pci_dev *dev);\n } pci_dev_acs_ops[] = {\n \t{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,\n+\t .match = pci_quirk_intel_pch_acs_match,\n \t .enable_acs = pci_quirk_enable_intel_pch_acs,\n \t},\n \t{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,\n+\t .match = pci_quirk_intel_spt_pch_acs_match,\n \t .enable_acs = pci_quirk_enable_intel_spt_pch_acs,\n \t .disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir,\n \t},\n };\n \n-int pci_dev_specific_enable_acs(struct pci_dev *dev)\n+static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev)\n {\n \tconst struct pci_dev_acs_ops *p;\n-\tint i, ret;\n+\tint i;\n \n \tfor (i = 0; i \u003c ARRAY_SIZE(pci_dev_acs_ops); i++) {\n \t\tp = \u0026pci_dev_acs_ops[i];\n \t\tif ((p-\u003evendor == dev-\u003evendor ||\n \t\t p-\u003evendor == (u16)PCI_ANY_ID) \u0026\u0026\n \t\t (p-\u003edevice == dev-\u003edevice ||\n-\t\t p-\u003edevice == (u16)PCI_ANY_ID) \u0026\u0026\n-\t\t p-\u003eenable_acs) {\n-\t\t\tret = p-\u003eenable_acs(dev);\n-\t\t\tif (ret \u003e= 0)\n-\t\t\t\treturn ret;\n+\t\t p-\u003edevice == (u16)PCI_ANY_ID)) {\n+\t\t\tif (!p-\u003ematch || p-\u003ematch(dev))\n+\t\t\t\treturn p;\n \t\t}\n \t}\n \n+\treturn NULL;\n+}\n+\n+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)\n+{\n+\tconst struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);\n+\n+\treturn p \u0026\u0026 p-\u003eenable_acs;\n+}\n+\n+int pci_dev_specific_enable_acs(struct pci_dev *dev)\n+{\n+\tconst struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);\n+\n+\tif (p \u0026\u0026 p-\u003eenable_acs)\n+\t\treturn p-\u003eenable_acs(dev);\n+\n \treturn -ENOTTY;\n }\n \n int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)\n {\n-\tconst struct pci_dev_acs_ops *p;\n-\tint i, ret;\n+\tconst struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);\n \n-\tfor (i = 0; i \u003c ARRAY_SIZE(pci_dev_acs_ops); i++) {\n-\t\tp = \u0026pci_dev_acs_ops[i];\n-\t\tif ((p-\u003evendor == dev-\u003evendor ||\n-\t\t p-\u003evendor == (u16)PCI_ANY_ID) \u0026\u0026\n-\t\t (p-\u003edevice == dev-\u003edevice ||\n-\t\t p-\u003edevice == (u16)PCI_ANY_ID) \u0026\u0026\n-\t\t p-\u003edisable_acs_redir) {\n-\t\t\tret = p-\u003edisable_acs_redir(dev);\n-\t\t\tif (ret \u003e= 0)\n-\t\t\t\treturn ret;\n-\t\t}\n-\t}\n+\tif (p \u0026\u0026 p-\u003edisable_acs_redir)\n+\t\treturn p-\u003edisable_acs_redir(dev);\n \n \treturn -ENOTTY;\n }\ndiff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h\nnew file mode 100644\nindex 00000000000000..acf1b38dff024a\n--- /dev/null\n+++ b/include/linux/kho/abi/pci.h\n@@ -0,0 +1,59 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+\n+/*\n+ * Copyright (c) 2026, Google LLC.\n+ * David Matlack \u003cdmatlack@google.com\u003e\n+ */\n+\n+#ifndef _LINUX_KHO_ABI_PCI_H\n+#define _LINUX_KHO_ABI_PCI_H\n+\n+#include \u003clinux/bug.h\u003e\n+#include \u003clinux/compiler.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+/**\n+ * DOC: PCI File-Lifecycle Bound (FLB) Live Update ABI\n+ *\n+ * This header defines the ABI for preserving core PCI state across kexec using\n+ * Live Update File-Lifecycle Bound (FLB) data.\n+ *\n+ * This interface is a contract. Any modification to any of the serialization\n+ * structs defined here constitutes a breaking change. Such changes require\n+ * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.\n+ */\n+\n+#define PCI_LUO_FLB_COMPATIBLE \"pci-v2\"\n+\n+/**\n+ * struct pci_dev_ser - Serialized state about a single PCI device.\n+ *\n+ * @domain: The device's PCI domain number (segment).\n+ * @bdf: The device's PCI bus, device, and function number.\n+ * @refcount: Reference count used by the PCI core to keep track of whether it\n+ * is done using a device's struct pci_dev_ser. The value of the\n+ * refcount is equal to the number of preserved devices at or below\n+ * it in the PCI hierarchy when the struct pci_dev_ser is in use, and\n+ * 0 otherwise.\n+ */\n+struct pci_dev_ser {\n+\tu32 domain;\n+\tu16 bdf;\n+\tu16 refcount;\n+} __packed;\n+\n+/**\n+ * struct pci_ser - PCI Subsystem Live Update State\n+ *\n+ * This struct tracks state about all devices that are being preserved across\n+ * a Live Update for the next kernel.\n+ *\n+ * @nr_devices: The number of devices that were preserved.\n+ * @devices: Physical address of the first KHO block containing pci_dev_ser.\n+ */\n+struct pci_ser {\n+\tu32 nr_devices;\n+\tu64 devices;\n+} __packed;\n+\n+#endif /* _LINUX_KHO_ABI_PCI_H */\ndiff --git a/include/linux/pci.h b/include/linux/pci.h\nindex ebb5b9d763609e..b41dd572a2d6b3 100644\n--- a/include/linux/pci.h\n+++ b/include/linux/pci.h\n@@ -43,6 +43,7 @@\n #include \u003cuapi/linux/pci.h\u003e\n \n #include \u003clinux/pci_ids.h\u003e\n+#include \u003clinux/pci_liveupdate.h\u003e\n \n #define PCI_STATUS_ERROR_BITS (PCI_STATUS_DETECTED_PARITY | \\\n \t\t\t PCI_STATUS_SIG_SYSTEM_ERROR | \\\n@@ -592,6 +593,9 @@ struct pci_dev {\n \tu8\t\ttph_mode;\t/* TPH mode */\n \tu8\t\ttph_req_type;\t/* TPH requester type */\n #endif\n+#ifdef CONFIG_PCI_LIVEUPDATE\n+\tstruct pci_liveupdate liveupdate;\n+#endif\n };\n \n static inline struct pci_dev *pci_physfn(struct pci_dev *dev)\ndiff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h\nnew file mode 100644\nindex 00000000000000..150993405754ba\n--- /dev/null\n+++ b/include/linux/pci_liveupdate.h\n@@ -0,0 +1,77 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * PCI Live Update support (Public/Driver API)\n+ *\n+ * Copyright (c) 2026, Google LLC.\n+ * David Matlack \u003cdmatlack@google.com\u003e\n+ */\n+#ifndef LINUX_PCI_LIVEUPDATE_H\n+#define LINUX_PCI_LIVEUPDATE_H\n+\n+#include \u003clinux/kho/abi/pci.h\u003e\n+#include \u003clinux/liveupdate.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/spinlock_types.h\u003e\n+\n+/**\n+ * struct pci_liveupdate - PCI Live Update state for a struct pci_dev\n+ * @outgoing: State preserved for the next kernel.\n+ * @incoming: State preserved by the previous kernel.\n+ * @acs_ctrl: ACS features established by the previous kernel.\n+ * @inherit_buses: True if the PCI core should inherit the secondary and\n+ * subordinate bus numbers assigned to this device due to\n+ * an ongoing Live Update.\n+ * @was_preserved: True if this struct pci_dev was preserved by the previous\n+ * kernel. Unlike @incoming, this field is not cleared after\n+ * the device is finished participating in Live Update.\n+ * @frozen: True if the outgoing preservation status of this device is frozen\n+ * and thus cannot be changed.\n+ */\n+struct pci_liveupdate {\n+\tstruct pci_dev_ser *outgoing;\n+\tstruct pci_dev_ser *incoming;\n+\tu16 acs_ctrl;\n+\tbool inherit_buses;\n+\tbool was_preserved;\n+\tbool frozen;\n+};\n+\n+struct pci_dev;\n+\n+#ifdef CONFIG_PCI_LIVEUPDATE\n+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);\n+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);\n+int pci_liveupdate_preserve(struct pci_dev *dev);\n+void pci_liveupdate_unpreserve(struct pci_dev *dev);\n+void pci_liveupdate_finish(struct pci_dev *dev);\n+bool pci_liveupdate_is_incoming(struct pci_dev *dev);\n+#else\n+static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)\n+{\n+}\n+\n+static inline int pci_liveupdate_preserve(struct pci_dev *dev)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)\n+{\n+}\n+\n+static inline void pci_liveupdate_finish(struct pci_dev *dev)\n+{\n+}\n+\n+static inline bool pci_liveupdate_is_incoming(struct pci_dev *dev)\n+{\n+\treturn false;\n+}\n+#endif\n+\n+#endif /* LINUX_PCI_LIVEUPDATE_H */\n"
} |
| 2/1 | 2026/07/11 18:26 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"LIVEUPDATE",
"PCI_LIVEUPDATE"
],
"FocusSymbols": [
"pci_liveupdate_setup_device",
"pci_liveupdate_cleanup_device",
"pci_liveupdate_freeze",
"pci_liveupdate_scan_bridge_begin",
"pci_liveupdate_scan_bridge_end",
"pci_liveupdate_init_acs",
"pci_liveupdate_enable_acs",
"pci_liveupdate_configure_ari",
"pci_liveupdate_preserve",
"pci_liveupdate_unpreserve",
"pci_liveupdate_finish",
"pci_liveupdate_register_flb",
"pci_liveupdate_unregister_flb"
],
"Reasoning": "The patch introduces PCI Live Update support, which modifies core PCI enumeration, shutdown, and capability configuration paths (e.g., ACS, ARI). These changes are functional and reachable during PCI device probe, removal, and system shutdown/kexec. Fuzzing should focus on the new pci_liveupdate_* functions to ensure they handle various PCI topologies and states correctly.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d6b30ab279fb0ef05787d56e33e57436ab6c88e6
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Jul 11 18:26:37 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e01..23fb737ac969ea 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 00000000000000..eba55f8a92ae12
--- /dev/null
+++ b/Documentation/PCI/liveupdate.rst
@@ -0,0 +1,29 @@
+.. 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:
+
+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 4f65b2a3752102..ed9eef8de64cd3 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5105,7 +5105,11 @@ 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
+ during a Live Update, where the kernel must
+ inherit 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/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 5a292d0f37064d..2bce2644eba223 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 a3ed337e827d86..347c435ca4047c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20824,6 +20824,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/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c7408509ba24b..16fbd4212e0f55 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 41ebc3b9a51824..e8d003cb675763 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 00000000000000..5935e88de073dc
--- /dev/null
+++ b/drivers/pci/liveupdate.c
@@ -0,0 +1,910 @@
+// 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.
+ *
+ * :ref:`FLB <flb>` Data
+ * =====================
+ *
+ * PCI device preservation across Live Update is built on top of the
+ * :ref:`LUO <luo>` support for file preservation across kexec. Drivers are
+ * expected to expose a file to represent a single PCI device and support
+ * preservation of that file with ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)``.
+ * This allows userspace to control the preservation of devices and ensure
+ * proper lifecycle management while a device is preserved. The first intended
+ * use-case is preserving vfio-pci device files.
+ *
+ * The PCI core maintains its own state about what devices are being preserved
+ * across Live Update using FLB data in LUO. Essentially, this allows the PCI
+ * core to allocate struct pci_ser when the first device (file) is preserved
+ * and free it when the last device (file) is unpreserved. After kexec, the
+ * PCI core can fetch the struct pci_ser (which was constructed by the previous
+ * kernel) from LUO at any time (e.g. during enumeration) so that it knows
+ * which devices were preserved.
+ *
+ * To enable the PCI core to be notified whenever a file representing a device
+ * is preserved, drivers must register their struct liveupdate_file_handler with
+ * the PCI core by using the following APIs:
+ *
+ * * ``pci_liveupdate_register_flb(driver_file_handler)``
+ * * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ *
+ * 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, it is the driver's responsibility to ensure that it
+ * does not release a device between pci_liveupdate_preserve() and
+ * pci_liveupdate_unpreserve().
+ *
+ * In the incoming kernel, it is the driver's responsibility to 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. i.e. 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. This may change in the future.
+ *
+ * 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 always inherits
+ * the secondary and subordinate bus numbers assigned to bridges during scanning
+ * if any device is preserved. 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.
+ *
+ * If a misconfigured or unconfigured bridge is encountered during enumeration
+ * while there are preserved devices, its secondary and subordinate bus numbers
+ * will be cleared and devices below it will not be enumerated.
+ *
+ * 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.
+ *
+ * Handling Preserved Devices
+ * ==========================
+ *
+ * The PCI core treats preserved devices differently than non-preserved devices.
+ * This section enumerates those differences.
+ *
+ * * The PCI core inherits all ACS flags 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 inherited ACS flags for the lifetime of its
+ * struct pci_dev (i.e. even after pci_liveupdate_finish()).
+ *
+ * * The PCI core inherits 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: " KBUILD_BASENAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/pci.h>
+#include <linux/liveupdate.h>
+#include <linux/mm.h>
+#include <linux/kho_block.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.
+ */
+struct pci_liveupdate_global {
+ struct rw_semaphore rwsem;
+};
+
+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;
+ struct pci_ser *ser;
+
+ outgoing = kzalloc_obj(*outgoing);
+ if (!outgoing)
+ return -ENOMEM;
+
+ ser = kho_alloc_preserve(sizeof(*ser));
+ if (IS_ERR(ser)) {
+ kfree(outgoing);
+ return PTR_ERR(ser);
+ }
+
+ ser->nr_devices = 0;
+ ser->devices = 0;
+
+ outgoing->ser = ser;
+ kho_block_set_init(&outgoing->block_set, sizeof(struct pci_dev_ser));
+
+ args->obj = outgoing;
+ args->data = virt_to_phys(ser);
+ return 0;
+}
+
+static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
+{
+ struct pci_flb_outgoing *outgoing = args->obj;
+
+ WARN_ON(outgoing->ser->nr_devices);
+ kho_block_set_destroy(&outgoing->block_set);
+ kho_unpreserve_free(outgoing->ser);
+ kfree(outgoing);
+ pr_debug("Unpreserved struct pci_ser\n");
+}
+
+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 = -ENOMEM;
+
+ incoming = kzalloc_obj(*incoming);
+ if (!incoming)
+ goto err_restore_free;
+
+ 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)
+ goto err_free_incoming;
+
+ 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)
+ goto err_block_set_destroy;
+ }
+
+ args->obj = incoming;
+ return 0;
+
+err_block_set_destroy:
+ kho_block_set_destroy(&incoming->block_set);
+err_free_incoming:
+ xa_destroy(&incoming->xa);
+ kfree(incoming);
+err_restore_free:
+ kho_restore_free(ser);
+ return ret;
+}
+
+static void pci_flb_finish(struct liveupdate_flb_op_args *args)
+{
+ struct pci_flb_incoming *incoming = args->obj;
+
+ 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 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_get_empty_or_append(struct pci_flb_outgoing *outgoing)
+{
+ struct pci_dev_ser *dev_ser, *found = NULL;
+ struct kho_block_set_it it;
+ int err;
+ u32 count = 0;
+
+ kho_block_set_it_init(&it, &outgoing->block_set);
+ while ((dev_ser = kho_block_set_it_read_entry(&it))) {
+ count++;
+ if (dev_ser->refcount == 0 && !found)
+ found = dev_ser;
+ }
+
+ if (found)
+ return found;
+
+ err = kho_block_set_grow(&outgoing->block_set, count + 1);
+ if (err)
+ return ERR_PTR(err);
+
+ if (count == 0)
+ kho_block_set_it_init(&it, &outgoing->block_set);
+
+ dev_ser = kho_block_set_it_reserve_entry(&it);
+ if (!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 int pci_liveupdate_preserve_device_again(struct pci_dev *dev)
+{
+ if (!dev->liveupdate.outgoing->refcount) {
+ pci_WARN(dev, 1, "Preserved device with 0 refcount!\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->subordinate)
+ return -EBUSY;
+
+ dev->liveupdate.outgoing->refcount++;
+ return 0;
+}
+
+static int __pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing, struct pci_dev *dev)
+{
+ struct pci_dev_ser *dev_ser;
+
+ /*
+ * 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;
+ }
+
+ dev_ser = pci_get_empty_or_append(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_ser->refcount = 1;
+
+ dev->liveupdate.outgoing = dev_ser;
+ return 0;
+}
+
+static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing, struct pci_dev *dev)
+{
+ if (dev->liveupdate.frozen) {
+ pci_warn(dev, "Cannot preserve device after it is frozen!\n");
+ return -EINVAL;
+ }
+
+ if (dev->liveupdate.outgoing)
+ return pci_liveupdate_preserve_device_again(dev);
+
+ return __pci_liveupdate_preserve_device(outgoing, dev);
+}
+
+#define for_each_pci_dev_in_path(_d, _start, _end) \
+ for ((_d) = (_start); (_d) != (_end); (_d) = (_d)->bus->self)
+
+static void __pci_liveupdate_unpreserve_path(struct pci_flb_outgoing *outgoing,
+ struct pci_dev *start,
+ struct pci_dev *end)
+{
+ struct pci_dev *dev;
+
+ for_each_pci_dev_in_path(dev, start, end) {
+ if (pci_liveupdate_unpreserve_device(outgoing, dev))
+ return;
+ }
+}
+
+static void pci_liveupdate_unpreserve_path(struct pci_flb_outgoing *outgoing,
+ struct pci_dev *start)
+{
+ __pci_liveupdate_unpreserve_path(outgoing, start, /*end=*/NULL);
+}
+
+static int pci_liveupdate_preserve_path(struct pci_flb_outgoing *outgoing,
+ struct pci_dev *start)
+{
+ struct pci_dev *dev;
+ int ret;
+
+ for_each_pci_dev_in_path(dev, start, NULL) {
+ ret = pci_liveupdate_preserve_device(outgoing, dev);
+ if (ret) {
+ __pci_liveupdate_unpreserve_path(outgoing, start, dev);
+ return ret;
+ }
+ }
+
+ 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;
+
+ if (dev->is_virtfn)
+ return -EINVAL;
+
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+ outgoing = pci_liveupdate_flb_get_outgoing();
+ if (IS_ERR(outgoing))
+ return PTR_ERR(outgoing);
+
+ return pci_liveupdate_preserve_path(outgoing, dev);
+}
+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);
+}
+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.
+ *
+ * This could mean that no PCI FLB data was passed by the previous
+ * kernel, but it could also mean the previous kernel used a different
+ * compatibility string (i.e. a different ABI).
+ */
+ if (ret == -ENOENT) {
+ pr_info_once("No incoming FLB matched %s\n", pci_liveupdate_flb.compatible);
+ return NULL;
+ }
+
+ /*
+ * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+ * but it cannot be retrieved.
+ */
+ 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_scan_bridge_begin() - Determine if a bridge should inherit bus numbers
+ * @bus: The parent bus of the bridge.
+ * @dev: The PCI bridge device.
+ * @pass: The scan pass (0 for first pass, 1 for second pass).
+ *
+ * This function is called by the PCI core when it begins scanning a bridge.
+ * It determines whether the bridge should inherit the secondary and subordinate
+ * bus numbers assigned to it by the previous kernel. This is necessary to
+ * keep bus numbers constant for preserved devices downstream of the bridge.
+ *
+ * Return: True if bus numbers should be inherited, false otherwise.
+ */
+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+ int pass)
+{
+ struct pci_dev *parent = bus->self;
+
+ /*
+ * On the second pass, reuse the value that was set on the first pass
+ * so that the passes are consistent with one another.
+ */
+ if (pass)
+ return dev->liveupdate.inherit_buses;
+
+ /*
+ * If the parent bridge is being forced to inherit its bus numbers
+ * during this scan then this bridge must as well, otherwise the PCI
+ * core could expand this bridge's reservation beyond its parent (which
+ * cannot expand).
+ */
+ if (parent && parent->liveupdate.inherit_buses) {
+ dev->liveupdate.inherit_buses = true;
+ return true;
+ }
+
+ /*
+ * Otherwise, if there are any incoming preserved devices, force the
+ * bus numbers to be inherited to avoid changing the bus numbers
+ * assigned to those devices during enumeration.
+ *
+ * To keep things simple, inherit bus numbers on all bridges if any PCI
+ * devices are incoming, to ensure that no bridge's reservation is
+ * expanded to overlap with a preserved device downstream of a different
+ * bridge.
+ */
+ scoped_guard(rwsem_read, &pci_liveupdate.rwsem) {
+ struct pci_flb_incoming *incoming;
+
+ incoming = pci_liveupdate_flb_get_incoming();
+ if (!incoming) {
+ dev->liveupdate.inherit_buses = false;
+ return false;
+ }
+
+ /*
+ * It is safe to sample incoming->ser->nr_devices and then
+ * drop the rwsem since nr_devices will only decrease. Thus the
+ * only "race" is that the current scan will be overly
+ * conservative and force bus inheritance.
+ */
+ dev->liveupdate.inherit_buses = !!incoming->ser->nr_devices;
+ pci_liveupdate_flb_put_incoming();
+ }
+
+ return dev->liveupdate.inherit_buses;
+}
+
+/**
+ * pci_liveupdate_scan_bridge_end() - Finish scanning a PCI bridge
+ * @dev: The PCI bridge device.
+ * @pass: The scan pass (0 for first pass, 1 for second pass).
+ *
+ * This function is called by the PCI core when it finishes scanning a bridge.
+ * It clears the inheritance status after the second pass so it can be
+ * re-evaluated on future scans.
+ */
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+ /*
+ * Clear inherit_buses after the second pass so it can be re-evaluated
+ * on future scans.
+ */
+ if (pass)
+ dev->liveupdate.inherit_buses = 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_preserved = true;
+
+ /*
+ * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
+ * that dev->liveupdate.incoming cannot get freed while the PCI core
+ * has a pointer to it. It's better to leak the incoming FLB than do a
+ * use-after-free if driver does not call pci_liveupdate_finish().
+ */
+}
+
+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");
+ pci_liveupdate_flb_put_incoming();
+ }
+}
+
+void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+ dev->liveupdate.frozen = 1;
+}
+
+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--;
+ pci_liveupdate_flb_put_incoming();
+ return 0;
+}
+
+static void pci_liveupdate_finish_path(struct pci_ser *ser, struct pci_dev *start)
+{
+ struct pci_dev *dev;
+
+ for_each_pci_dev_in_path(dev, start, NULL) {
+ if (pci_liveupdate_finish_device(ser, dev))
+ return;
+ }
+}
+
+/**
+ * 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;
+ }
+
+ pci_liveupdate_finish_path(incoming->ser, dev);
+ pci_liveupdate_flb_put_incoming();
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
+
+void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+ guard(rwsem_read)(&pci_liveupdate.rwsem);
+
+ if (!dev->acs_cap || !dev->liveupdate.incoming)
+ return;
+
+ pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, &dev->liveupdate.acs_ctrl);
+}
+
+int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+ u16 acs_ctrl = dev->liveupdate.acs_ctrl;
+ u16 acs_cap = dev->acs_cap;
+
+ /*
+ * Use liveupdate.was_preserved instead of liveupdate.incoming since the
+ * device's ACS controls should not change even after the device is
+ * finished participating in the Live Update.
+ */
+ if (!dev->liveupdate.was_preserved)
+ 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;
+ }
+
+ if (acs_cap)
+ pci_write_config_word(dev, acs_cap + PCI_ACS_CTRL, acs_ctrl);
+
+ return 0;
+}
+
+int pci_liveupdate_configure_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);
+ 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 should call pci_liveupdate_register_flb() to register their
+ * struct liveupdate_file_handler with the PCI core. This enables the PCI core
+ * to allocate its outgoing struct pci_ser whenever the first device is
+ * preserved, and free it when the last device 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 00000000000000..b266406aaac80d
--- /dev/null
+++ b/drivers/pci/liveupdate.h
@@ -0,0 +1,68 @@
+/* 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_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+ int pass);
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
+void pci_liveupdate_init_acs(struct pci_dev *dev);
+int pci_liveupdate_enable_acs(struct pci_dev *dev);
+int pci_liveupdate_configure_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_scan_bridge_begin(struct pci_bus *bus,
+ struct pci_dev *dev,
+ int pass)
+{
+ return false;
+}
+
+static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+}
+
+static inline void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+}
+
+static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+ return -EINVAL;
+}
+
+static inline int pci_liveupdate_configure_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 f36778e62ac1a5..47cb3dcaa92782 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"
@@ -536,6 +537,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)
@@ -544,11 +546,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 77b17b13ee6154..e0c133b66a3592 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -34,6 +34,8 @@
#include <linux/aer.h>
#include <linux/bitfield.h>
#include <linux/suspend.h>
+
+#include "liveupdate.h"
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -1008,6 +1010,9 @@ void pci_enable_acs(struct pci_dev *dev)
bool enable_acs = false;
int pos;
+ if (!pci_liveupdate_enable_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))
@@ -3523,7 +3528,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;
@@ -3534,6 +3539,12 @@ void pci_configure_ari(struct pci_dev *dev)
if (!(cap & PCI_EXP_DEVCAP2_ARI))
return;
+ if (!pci_liveupdate_configure_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);
@@ -3689,6 +3700,7 @@ void pci_acs_init(struct pci_dev *dev)
pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities);
pci_disable_broken_acs_cap(dev);
+ pci_liveupdate_init_acs(dev);
}
/**
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4469e1a77f3c13..988a18b3204af5 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1047,6 +1047,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);
@@ -1057,6 +1058,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 dd0abbc63e18d1..165973bc92aabb 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,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
int max, unsigned int available_buses,
int pass)
{
+ bool liveupdate, assign_new_buses = pcibios_assign_all_busses();
struct pci_bus *child;
u32 buses;
u16 bctl;
@@ -1405,6 +1407,10 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
u8 fixed_sec, fixed_sub;
int next_busnr;
+ liveupdate = pci_liveupdate_scan_bridge_begin(bus, dev, pass);
+ if (liveupdate)
+ assign_new_buses = false;
+
/*
* Make sure the bridge is powered on to be able to access config
* space of devices below it.
@@ -1448,8 +1454,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) && !assign_new_buses && !broken) {
unsigned int cmax, buses;
/*
@@ -1491,8 +1496,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 (assign_new_buses || broken)
/*
* Temporarily disable forwarding of the
* configuration cycles on all bridges in
@@ -1506,6 +1510,11 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
goto out;
}
+ if (liveupdate) {
+ pci_err(dev, "Cannot reconfigure bridge during Live Update, skipping\n");
+ goto out;
+ }
+
/* Clear errors */
pci_write_config_word(dev, PCI_STATUS, 0xffff);
@@ -1566,6 +1575,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);
pm_runtime_put(&dev->dev);
+ pci_liveupdate_scan_bridge_end(dev, pass);
return max;
}
@@ -2064,6 +2074,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);
@@ -2187,6 +2199,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;
@@ -2485,6 +2498,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 b09f27f7846fcd..99b819f38e49c2 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5374,9 +5374,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;
@@ -5396,9 +5393,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;
@@ -5426,9 +5420,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;
@@ -5448,56 +5439,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/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
new file mode 100644
index 00000000000000..acf1b38dff024a
--- /dev/null
+++ b/include/linux/kho/abi/pci.h
@@ -0,0 +1,59 @@
+/* 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_COMPATIBLE string.
+ */
+
+#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
+
+/**
+ * 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.
+ *
+ * @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 nr_devices;
+ u64 devices;
+} __packed;
+
+#endif /* _LINUX_KHO_ABI_PCI_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index ebb5b9d763609e..b41dd572a2d6b3 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 | \
@@ -592,6 +593,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)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
new file mode 100644
index 00000000000000..150993405754ba
--- /dev/null
+++ b/include/linux/pci_liveupdate.h
@@ -0,0 +1,77 @@
+/* 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/types.h>
+#include <linux/spinlock_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.
+ * @acs_ctrl: ACS features established by the previous kernel.
+ * @inherit_buses: True if the PCI core should inherit the secondary and
+ * subordinate bus numbers assigned to this device due to
+ * an ongoing Live Update.
+ * @was_preserved: True if this struct pci_dev was preserved by the previous
+ * kernel. Unlike @incoming, this field is not cleared after
+ * the device is finished 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;
+ u16 acs_ctrl;
+ bool inherit_buses;
+ bool was_preserved;
+ 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 */
|
| 3/2 | 2026/07/11 18:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20552 output: 17 thoughts: 364 Thoughts: **PCI Live Update Implementation Assessment** Okay, so I'm looking at this patch series, and it's introducing PCI Live Update support – interesting. Seems like a significant functional change to the PCI subsystem. The core of it seems to be centered around the new `drivers/pci/liveupdate.c` file, with functions like `pci_liveupdate_preserve`, `pci_liveupdate_unpreserve`, `pci_liveupdate_finish`, and so on. Pretty standard stuff for preserving and restoring device state during a hot update. Looks like the modifications to `pci-driver.c`, `pci.c`, `probe.c`, and `quirks.c` are where the integration points are. `pci_scan_bridge_extend` is getting calls to begin and end the scan, `pci_setup_device` is integrating the setup, and `pci_device_shutdown` is freezing the device state. Now, a key question for me is whether this code is even *reachable* in a typical virtualized environment. PCI devices *are* present in things like QEMU and GCE, so that's a positive start. But the configuration dependency is what I need to dig into. The `CONFIG_PCI_LIVEUPDATE` is contingent on `PCI && LIVEUPDATE && 64BIT && !CARDBUS`. So, I'll need to enable `PCI_LIVEUPDATE`, which probably means I'll also need to enable `LIVEUPDATE` first. I'll need to check the exact config name for `LIVEUPDATE` to be sure. Time to dig into the config options. |
| 4/2 | 2026/07/11 18:26 | tool | grepper |
0mArgs:{
"Expression": "config LIVEUPDATE"
}Results: {
"Output": "kernel/liveupdate/Kconfig=38=config KEXEC_HANDOVER_ENABLE_DEFAULT\n--\nkernel/liveupdate/Kconfig-51-\nkernel/liveupdate/Kconfig:52:config LIVEUPDATE\nkernel/liveupdate/Kconfig-53-\tbool \"Live Update Orchestrator\"\n--\nkernel/liveupdate/Kconfig-72-\nkernel/liveupdate/Kconfig:73:config LIVEUPDATE_MEMFD\nkernel/liveupdate/Kconfig-74-\tbool \"Live update support for memfd\"\n--\nlib/Kconfig.debug=2901=config CONTEXT_ANALYSIS_TEST\n--\nlib/Kconfig.debug-2914-\nlib/Kconfig.debug:2915:config LIVEUPDATE_TEST\nlib/Kconfig.debug-2916-\tbool \"Live Update Kernel Test\"\n"
} |
| 5/2 | 2026/07/11 18:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21496 output: 23 thoughts: 423 Thoughts: **Analyzing the PCI Live Update Patch** Okay, so I'm looking at this `LIVEUPDATE` config, and the patch introduces `PCI_LIVEUPDATE`. The immediate question is, where's the best place to focus our fuzzing efforts? I start by identifying potential targets. There are these functions: `pci_liveupdate_preserve`, `pci_liveupdate_unpreserve`, `pci_liveupdate_finish`, `pci_liveupdate_is_incoming`, `pci_liveupdate_register_flb`, and `pci_liveupdate_unregister_flb`. But wait, those are exported for *drivers* to use. Hmmm, are any drivers actually using them in *this* patch? No, doesn't look like it. Alright, what about the internal PCI core functions? Aha! `pci_liveupdate_setup_device`, `pci_liveupdate_cleanup_device`, `pci_liveupdate_freeze`, `pci_liveupdate_scan_bridge_begin`, `pci_liveupdate_scan_bridge_end`, `pci_liveupdate_init_acs`, `pci_liveupdate_enable_acs`, and `pci_liveupdate_configure_ari` are all called from the PCI core. That's a good starting point. Let's see if we can trace their call chains. `pci_liveupdate_setup_device` is called from `pci_setup_device`. Good. Then `pci_liveupdate_scan_bridge_begin` is called from `pci_scan_bridge_extend`. `pci_liveupdate_cleanup_device` is called from `pci_release_dev`. `pci_liveupdate_freeze` is called from `pci_device_shutdown`. `pci_liveupdate_init_acs` is called from `pci_acs_init`. And `pci_liveupdate_enable_acs` is called from `pci_enable_acs`. Finally, `pci_liveupdate_configure_ari` is called from `pci_configure_ari`. So, it's those core functions that are the real targets. Let's dig deeper into `pci_liveupdate_setup_device`, and I'll start by checking its implementation in `drivers/pci/liveupdate.c`. Let's see what's going on in there. |
| 6/2 | 2026/07/11 18:27 | tool | grepper |
0mArgs:{
"Expression": "pci_liveupdate_setup_device"
}Results: {
"Output": "drivers/pci/liveupdate.c=663=void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)\n--\ndrivers/pci/liveupdate.c-672-\ndrivers/pci/liveupdate.c:673:void pci_liveupdate_setup_device(struct pci_dev *dev)\ndrivers/pci/liveupdate.c-674-{\n--\ndrivers/pci/liveupdate.c=736=static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)\n--\ndrivers/pci/liveupdate.c-749-\t * Decrement the refcount so this device does not get treated as an\ndrivers/pci/liveupdate.c:750:\t * incoming device again, e.g. in case pci_liveupdate_setup_device()\ndrivers/pci/liveupdate.c-751-\t * gets called again because the device is hot-plugged.\n--\ndrivers/pci/liveupdate.h-13-#ifdef CONFIG_PCI_LIVEUPDATE\ndrivers/pci/liveupdate.h:14:void pci_liveupdate_setup_device(struct pci_dev *dev);\ndrivers/pci/liveupdate.h-15-void pci_liveupdate_cleanup_device(struct pci_dev *dev);\n--\ndrivers/pci/liveupdate.h=23=bool pci_liveupdate_is_outgoing(struct pci_dev *dev);\ndrivers/pci/liveupdate.h-24-#else\ndrivers/pci/liveupdate.h:25:static inline void pci_liveupdate_setup_device(struct pci_dev *dev)\ndrivers/pci/liveupdate.h-26-{\n--\ndrivers/pci/probe.c=2026=int pci_setup_device(struct pci_dev *dev)\n--\ndrivers/pci/probe.c-2076-\ndrivers/pci/probe.c:2077:\tpci_liveupdate_setup_device(dev);\ndrivers/pci/probe.c-2078-\n"
} |
| 7/2 | 2026/07/11 18:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23010 output: 19 thoughts: 759 Thoughts: **PCI Live Update Fuzzing Strategy** Okay, so this patch is introducing some fresh functionality for PCI live update, which means it's definitely a prime target for fuzzing. It's modifying core PCI enumeration and shutdown paths, so we're talking about fundamental stuff here. The immediate thought is, let's get a fuzzing campaign set up for this. First, I need to figure out the right configuration options. The patch mentions `PCI_LIVEUPDATE` and `LIVEUPDATE` as the key enablers. Checking the dependencies, `PCI_LIVEUPDATE` relies on `PCI && LIVEUPDATE && 64BIT && !CARDBUS`, so we definitely need to ensure those are set. So, `PCI_LIVEUPDATE` and `LIVEUPDATE` are the configs to enable. Now, let's identify the critical symbols to focus on. Based on the patch, the most promising entry points seem to be: `pci_liveupdate_setup_device`, `pci_liveupdate_cleanup_device`, `pci_liveupdate_freeze`, `pci_liveupdate_scan_bridge_begin`, `pci_liveupdate_scan_bridge_end`, `pci_liveupdate_init_acs`, `pci_liveupdate_enable_acs`, `pci_liveupdate_configure_ari`, `pci_liveupdate_preserve`, `pci_liveupdate_unpreserve`, `pci_liveupdate_finish`, `pci_liveupdate_register_flb`, and `pci_liveupdate_unregister_flb`. These should be the ones we prioritize. Next, I need to think about reachability. `pci_liveupdate_preserve` is exported, meaning it's accessible to drivers, but if no drivers *call* it, we have to consider whether it's reachable at all from a fuzzing perspective. Syzkaller doesn't typically call arbitrary exported functions unless there's a syscall interface. However, the good news is that symbols like `pci_liveupdate_setup_device` and others are called from the PCI core itself. And, since the PCI core is involved during both boot and hotplug events, we know those core changes are reachable, which is a big win. So, to summarize for the fuzzing setup, we're looking at these configs: * `LIVEUPDATE` * `PCI_LIVEUPDATE` And the key focus symbols are: * `pci_liveupdate_setup_device` * `pci_liveupdate_cleanup_device` * `pci_liveupdate_freeze` * `pci_liveupdate_scan_bridge_begin` * `pci_liveupdate_scan_bridge_end` * `pci_liveupdate_init_acs` * `pci_liveupdate_enable_acs` * `pci_liveupdate_configure_ari` * `pci_liveupdate_preserve` * `pci_liveupdate_unpreserve` * `pci_liveupdate_finish` * `pci_liveupdate_register_flb` * `pci_liveupdate_unregister_flb` And just to be absolutely certain, let's double check there are *no* other relevant configs. |
| 8/2 | 2026/07/11 18:27 | tool | grepper |
0mArgs:{
"Expression": "config PCI_LIVEUPDATE"
}Results: {
"Output": "drivers/pci/Kconfig=266=config VGA_ARB_MAX_GPUS\n--\ndrivers/pci/Kconfig-273-\ndrivers/pci/Kconfig:274:config PCI_LIVEUPDATE\ndrivers/pci/Kconfig-275-\tbool \"PCI Live Update Support\"\n"
} |
| 9/2 | 2026/07/11 18:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24606 output: 271 thoughts: 109 Thoughts: Okay, here's my take on those thoughts, framed as my own internal process, with a focus on PCI Live Update: **PCI Live Update Fuzzing Strategy** Right, let's zero in on this `PCI_LIVEUPDATE` thing. That's the key. This patch introduces functionality specifically around PCI Live Update, and that means it’s altering the core routines involved in PCI enumeration, how devices shut down, and how we configure PCI capabilities, particularly things like ACS and ARI. These aren't just theoretical changes; they're *reachable*. They're going to be hit during device probing, when a device is removed, and during system shutdown or kexec. The critical focus for fuzzing, therefore, has to be the new `pci_liveupdate_*` functions. We need to make sure these new functions, in conjunction with the existing code, can handle a wide variety of PCI topologies and device states flawlessly. We're talking about a comprehensive approach. We need to explore all the potential edge cases: what happens when things aren't as expected, what happens with hot-plug, cold-plug, and everything in between? And how do we account for the possibility of a device going into a weird state after an update? Okay, time to call `set-results` and ensure that the fuzzing strategy is properly implemented. |
| 10/2 | 2026/07/11 18:27 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"LIVEUPDATE",
"PCI_LIVEUPDATE"
],
"FocusSymbols": [
"pci_liveupdate_setup_device",
"pci_liveupdate_cleanup_device",
"pci_liveupdate_freeze",
"pci_liveupdate_scan_bridge_begin",
"pci_liveupdate_scan_bridge_end",
"pci_liveupdate_init_acs",
"pci_liveupdate_enable_acs",
"pci_liveupdate_configure_ari",
"pci_liveupdate_preserve",
"pci_liveupdate_unpreserve",
"pci_liveupdate_finish",
"pci_liveupdate_register_flb",
"pci_liveupdate_unregister_flb"
],
"Reasoning": "The patch introduces PCI Live Update support, which modifies core PCI enumeration, shutdown, and capability configuration paths (e.g., ACS, ARI). These changes are functional and reachable during PCI device probe, removal, and system shutdown/kexec. Fuzzing should focus on the new pci_liveupdate_* functions to ensure they handle various PCI topologies and states correctly.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"LIVEUPDATE",
"PCI_LIVEUPDATE"
],
"FocusSymbols": [
"pci_liveupdate_setup_device",
"pci_liveupdate_cleanup_device",
"pci_liveupdate_freeze",
"pci_liveupdate_scan_bridge_begin",
"pci_liveupdate_scan_bridge_end",
"pci_liveupdate_init_acs",
"pci_liveupdate_enable_acs",
"pci_liveupdate_configure_ari",
"pci_liveupdate_preserve",
"pci_liveupdate_unpreserve",
"pci_liveupdate_finish",
"pci_liveupdate_register_flb",
"pci_liveupdate_unregister_flb"
],
"Reasoning": "The patch introduces PCI Live Update support, which modifies core PCI enumeration, shutdown, and capability configuration paths (e.g., ACS, ARI). These changes are functional and reachable during PCI device probe, removal, and system shutdown/kexec. Fuzzing should focus on the new pci_liveupdate_* functions to ensure they handle various PCI topologies and states correctly.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|