[PATCH v8 02/12] PCI: liveupdate: Track outgoing preserved PCI devices

Zhu Yanjun yanjun.zhu at linux.dev
Tue Sep 15 20:10:04 PDT 2026


在 2026/7/28 15:09, David Matlack 写道:
> Add APIs to allow drivers to notify the PCI core of which devices are
> being preserved across a Live Update for the next kernel, i.e.
> "outgoing" devices.
> 
> Drivers must notify the PCI core when devices are preserved so that the
> PCI core can update its FLB data (struct pci_ser) and track the list of
> outgoing devices. pci_liveupdate_preserve() notifies the PCI core that a
> device must be preserved across Live Update. pci_liveupdate_unpreserve()
> reverses this (cancels the preservation of the device).

Hi David,

I have a question about the pci_liveupdate_preserve() and 
pci_liveupdate_unpreserve() APIs.

Is it supported to call these APIs multiple times for the same PCI 
device? For example, a driver could do:

pci_liveupdate_preserve(pdev) -- > pci_liveupdate_unpreserve(pdev) -- > 
perform some operations -- > pci_liveupdate_preserve(pdev) again, before 
the actual Live Update starts.

 From the implementation, it looks like an unpreserved pci_dev_ser entry 
can be reused, so I wanted to confirm whether this preserve -- > 
unpreserve -- > preserve sequence is an intended and supported usage.

Thanks!

Yanjun.Zhu

> 
> This tracking ensures the PCI core is fully aware of which devices may
> need special handling during shutdown and kexec, and so the list of
> preserved devices can be handed off to the next kernel.
> 
> For now, the API only supports preserving non-VF devices on a root bus
> (not behind an PCI-to-PCI bridges).
> 
> Reviewed-by: Pranjal Shrivastava <praan at google.com>
> Reviewed-by: Pasha Tatashin <pasha.tatashin at soleen.com>
> Reviewed-by: Bjorn Helgaas <bhelgaas at google.com>
> Signed-off-by: David Matlack <dmatlack at google.com>
> ---
>   drivers/pci/liveupdate.c       | 210 +++++++++++++++++++++++++++++++++
>   drivers/pci/liveupdate.h       |  21 ++++
>   drivers/pci/probe.c            |   2 +
>   include/linux/pci.h            |   3 +
>   include/linux/pci_liveupdate.h |  21 ++++
>   5 files changed, 257 insertions(+)
>   create mode 100644 drivers/pci/liveupdate.h
> 
> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> index fffb41a66ca7..b003b7069cdb 100644
> --- a/drivers/pci/liveupdate.c
> +++ b/drivers/pci/liveupdate.c
> @@ -36,6 +36,27 @@
>    *
>    *  * ``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.
> + *
> + * 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 be behind a PCI-to-PCI bridge.
>    */
>   
>   #define pr_fmt(fmt) "PCI: liveupdate: " fmt
> @@ -50,6 +71,21 @@
>   #include <linux/pci.h>
>   #include <linux/slab.h>
>   
> +#include "liveupdate.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.
> @@ -128,6 +164,180 @@ static struct liveupdate_flb pci_liveupdate_flb = {
>   	.compatible = PCI_LUO_FLB_COMPATIBLE,
>   };
>   
> +static void pci_liveupdate_flb_put_outgoing(void)
> +{
> +	liveupdate_flb_put_outgoing(&pci_liveupdate_flb);
> +}
> +
> +static struct pci_flb_outgoing *pci_liveupdate_flb_get_outgoing(void)
> +{
> +	struct pci_flb_outgoing *outgoing = NULL;
> +	int ret;
> +
> +	ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&outgoing);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	if (!outgoing)
> +		return ERR_PTR(-ENOENT);
> +
> +	return outgoing;
> +}
> +
> +static struct pci_dev_ser *pci_flb_alloc_dev_ser(struct pci_flb_outgoing *outgoing)
> +{
> +	struct pci_dev_ser *dev_ser;
> +	struct kho_block_set_it it;
> +	u64 count = 0;
> +	int err;
> +
> +	kho_block_set_it_init(&it, &outgoing->block_set);
> +
> +	/* Try to find an existing, previously unpreserved, entry. */
> +	while ((dev_ser = kho_block_set_it_read_entry(&it))) {
> +		if (!dev_ser->refcount)
> +			return dev_ser;
> +
> +		count++;
> +	}
> +
> +	/* Otherwise grow the block set and reserve a new entry. */
> +	err = kho_block_set_grow(&outgoing->block_set, count + 1);
> +	if (err)
> +		return ERR_PTR(err);
> +
> +	if (!count)
> +		kho_block_set_it_init(&it, &outgoing->block_set);
> +
> +	/* This should always succeed since kho_block_set_grow() succeeded. */
> +	dev_ser = kho_block_set_it_reserve_entry(&it);
> +	if (WARN_ON_ONCE(!dev_ser))
> +		return ERR_PTR(-ENOSPC);
> +
> +	return dev_ser;
> +}
> +
> +static void pci_liveupdate_unpreserve_device(struct pci_flb_outgoing *outgoing,
> +					     struct pci_dev *dev)
> +{
> +	struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
> +
> +	if (!dev_ser) {
> +		pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
> +		return;
> +	}
> +
> +	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;
> +}
> +
> +static int pci_liveupdate_preserve_device(struct pci_flb_outgoing *outgoing,
> +					  struct pci_dev *dev)
> +{
> +	struct pci_dev_ser *dev_ser;
> +
> +	if (dev->is_virtfn) {
> +		pci_warn(dev, "Cannot preserve Virtual Functions\n");
> +		return -EINVAL;
> +	}
> +
> +	if (dev->liveupdate.outgoing) {
> +		pci_warn(dev, "Device is already preserved\n");
> +		return -EBUSY;
> +	}
> +
> +	if (!pci_is_root_bus(dev->bus)) {
> +		pci_warn(dev, "Cannot preserve devices behind bridges\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_ser = pci_flb_alloc_dev_ser(outgoing);
> +	if (IS_ERR(dev_ser))
> +		return PTR_ERR(dev_ser);
> +
> +	pci_info(dev, "Device will be preserved across next Live Update\n");
> +	outgoing->ser->nr_devices++;
> +	outgoing->ser->devices = kho_block_set_head_pa(&outgoing->block_set);
> +
> +	dev_ser->domain = pci_domain_nr(dev->bus);
> +	dev_ser->bdf = pci_dev_id(dev);
> +	dev_ser->refcount = 1;
> +
> +	dev->liveupdate.outgoing = dev_ser;
> +	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.
> + *
> + * Returns: 0 on success, <0 on failure.
> + */
> +int pci_liveupdate_preserve(struct pci_dev *dev)
> +{
> +	struct pci_flb_outgoing *outgoing = NULL;
> +	int ret;
> +
> +	guard(rwsem_write)(&pci_liveupdate.rwsem);
> +
> +	outgoing = pci_liveupdate_flb_get_outgoing();
> +	if (IS_ERR(outgoing))
> +		return PTR_ERR(outgoing);
> +
> +	ret = pci_liveupdate_preserve_device(outgoing, dev);
> +
> +	pci_liveupdate_flb_put_outgoing();
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
> +
> +/**
> + * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device
> + * @dev: The PCI device to unpreserve.
> + *
> + * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no
> + * longer be preserved across the next Live Update. Drivers are expected to call
> + * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
> + * unpreserve() callback to ensure the outgoing struct pci_ser is already set
> + * up.
> + */
> +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_device(outgoing, dev);
> +	pci_liveupdate_flb_put_outgoing();
> +}
> +EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
> +
> +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");
> +}
> +
>   /**
>    * pci_liveupdate_register_flb() - Register a file handler with the PCI core
>    * @fh: The file handler to register.
> diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
> new file mode 100644
> index 000000000000..b2335581f8d0
> --- /dev/null
> +++ b/drivers/pci/liveupdate.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * PCI Live Update support (core API)
> + *
> + * Copyright (c) 2026, Google LLC.
> + * David Matlack <dmatlack at google.com>
> + */
> +#ifndef DRIVERS_PCI_LIVEUPDATE_H
> +#define DRIVERS_PCI_LIVEUPDATE_H
> +
> +#include <linux/pci.h>
> +
> +#ifdef CONFIG_PCI_LIVEUPDATE
> +void pci_liveupdate_cleanup_device(struct pci_dev *dev);
> +#else
> +static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
> +{
> +}
> +#endif
> +
> +#endif /* DRIVERS_PCI_LIVEUPDATE_H */
> diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
> index dd0abbc63e18..14b66acbdb15 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 = {
> @@ -2485,6 +2486,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/include/linux/pci.h b/include/linux/pci.h
> index da58aa101e4c..b41dd572a2d6 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -593,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
> index 8ec98beefcb4..894052ad6961 100644
> --- a/include/linux/pci_liveupdate.h
> +++ b/include/linux/pci_liveupdate.h
> @@ -8,14 +8,26 @@
>   #ifndef LINUX_PCI_LIVEUPDATE_H
>   #define LINUX_PCI_LIVEUPDATE_H
>   
> +#include <linux/kho/abi/pci.h>
>   #include <linux/liveupdate.h>
> +#include <linux/spinlock_types.h>
>   #include <linux/types.h>
>   
> +/**
> + * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
> + * @outgoing: State preserved for the next kernel.
> + */
> +struct pci_liveupdate {
> +	struct pci_dev_ser *outgoing;
> +};
> +
>   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);
>   #else
>   static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
>   {
> @@ -25,6 +37,15 @@ static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh
>   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)
> +{
> +}
>   #endif
>   
>   #endif /* LINUX_PCI_LIVEUPDATE_H */




More information about the kexec mailing list