From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id C1738C54F5B for ; Tue, 28 Jul 2026 22:10:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Type:Cc:To:From: Subject:Message-ID:References:Mime-Version:In-Reply-To:Date:Reply-To: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=WBGGp3NNtpYgk9Q2kiQw/pkm7DbF0Q30FPkZ7rMim4s=; b=Qno0hodOTKQg/LqWtn6LyXAOBt eHhptqg7Et2qXmEPvL/0CZrMfl3v4bk62CpDFEy5UWvu1fL5dc09e4bdmj6MoTq4qXNBEEdgqe1B3 scx3jzJiqbzOns4cqqftyh42zW8TEK0kC3miI1EP65GJ43BHqpheffJxKo+bPR9/rEhjT7/4egYfn 1z/X+HFYtBn7C8unls6HruTq5rKCO1U6ClZv2SXdCHPDoKGCGpXRox78al7VtKEM3/41INF4yNAHm lHL/yAxaUsf1iCXzrsMgq221jDuJAaiZv2ZgWkoYtRt/12Vn5CKxDKHH2ZT86zglzJy68RtdOxwXv VYKvBclw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1woq0K-00000006NrN-1cYe; Tue, 28 Jul 2026 22:10:28 +0000 Received: from mail-pf1-x445.google.com ([2607:f8b0:4864:20::445]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1woq0G-00000006NpE-48lm for kexec@lists.infradead.org; Tue, 28 Jul 2026 22:10:26 +0000 Received: by mail-pf1-x445.google.com with SMTP id d2e1a72fcca58-84e048a801dso414392b3a.3 for ; Tue, 28 Jul 2026 15:10:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1785276624; x=1785881424; darn=lists.infradead.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=WBGGp3NNtpYgk9Q2kiQw/pkm7DbF0Q30FPkZ7rMim4s=; b=vm+vZNnB1g65FkZEUWDradh1RDodTV36iRuKJ9Xx1Xmo6UL0f1eiJAcOyd1s4i/mfx TKigOBPDbsaTx3P3IsJBpkvkHYJFA4IVevZtpXvJFH9znCj0xGxJE/yOmhVKOdQbdZ8D J3kgxJw+Ek5m6Sw58Y0ehLyu/uks2cbQ6+O5NPUhsMj8V3/DiluLt6WZ7iAmdc4uQoWa MNb6cdyLwSFOS4N9rEEqE2gfDax642beXhv1H799NSlCsyHLIgpbqKXyohuECoQtNptB ZQzR7mxGy2DhREBsGmC5nn8qo8Am1pj7MulFMlnE4vXan3k5k1D6vC82jiGqJ9fAhM5j FrJA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785276624; x=1785881424; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=WBGGp3NNtpYgk9Q2kiQw/pkm7DbF0Q30FPkZ7rMim4s=; b=avfoc5O3dY+vC1aDEd4T73LKY92g9qpF3qfhFG0388V8LIXnamyrfoSZjvvbd/5QeG ZSx49zwvwJcDScWdrX5h1lpOK9WHC0QiVydcOdJ9wPyH9rMohCCBVB9mBUFz2iweXK9D uTZqHJ3K51hBT54Q9gJKVnRhN69RrYEMiYg987+3mXIf7SOLkCnh3LUKbDHeoIp9Uexz xXYJfU3Q/GveVOH60lZ/pa9bYjBYFQpeZhd0hl6hBD73YAHtwSrZ2NZHOMNLBuRT3O3R NQpKaEVoawLvqY8Xa7WtlPbbN/hBW5HAkJ7e+nmQddwx8ep5637OQjufTAGaPgiluHFQ TiiQ== X-Gm-Message-State: AOJu0YxwwIxTJMs3JNPtnZJ3hd9fzM4X6wveEyIFzEBkHACS6j7yVzXL 65VIgG3JkUDpmi2DJ540JKklmeEUXwtfZCCc1wpmoOglI/+nC675FJz02BYI+n7W7swicsmxJtJ v2JWXwkHXNIzNcd2gwONj3rzQ2eyl5Zed1N0kk/HPCrjj/qq0C+1qpw6ew9S1C2J5s0dbnGaotL 9fj8kWyCx+nzBuH9/Zd1+i+k7oa+c01p13zTlpVsKW/nt22U++SjA= X-Received: from pfgr18.prod.google.com ([2002:a05:6a00:c492:b0:845:c4b8:9730]) (user=dmatlack job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a00:ccb:b0:848:4c0c:9482 with SMTP id d2e1a72fcca58-84e933b0569mr4329533b3a.35.1785276623197; Tue, 28 Jul 2026 15:10:23 -0700 (PDT) Date: Tue, 28 Jul 2026 22:09:57 +0000 In-Reply-To: <20260728221007.2098560-1-dmatlack@google.com> Mime-Version: 1.0 References: <20260728221007.2098560-1-dmatlack@google.com> X-Mailer: git-send-email 2.55.0.487.gaf234c4eb3-goog Message-ID: <20260728221007.2098560-4-dmatlack@google.com> Subject: [PATCH v8 03/12] PCI: liveupdate: Track incoming preserved PCI devices From: David Matlack To: kexec@lists.infradead.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, linux-pci@vger.kernel.org Cc: Adithya Jayachandran , Alexander Graf , Alex Williamson , Bjorn Helgaas , Chris Li , David Matlack , David Rientjes , Jacob Pan , Jason Gunthorpe , Jonathan Corbet , Josh Hilke , Leon Romanovsky , Lukas Wunner , Mike Rapoport , Parav Pandit , Pasha Tatashin , Pranjal Shrivastava , Pratyush Yadav , Saeed Mahameed , Samiullah Khawaja , Shuah Khan , Vipin Sharma , William Tu , Yi Liu Content-Type: text/plain; charset="UTF-8" X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.9.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260728_151025_084604_78B5F8A9 X-CRM114-Status: GOOD ( 36.70 ) X-BeenThere: kexec@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "kexec" Errors-To: kexec-bounces+kexec=archiver.kernel.org@lists.infradead.org During PCI enumeration, the previous kernel might have passed state about devices that were preserved across kexec. The PCI core needs to fetch this state to identify which devices are "incoming" and require special handling. Add pci_liveupdate_setup_device() which is called during device setup to fetch the serialized state (struct pci_ser) from the Live Update Orchestrator. The first time this happens, pci_flb_retrieve() will run and convert the array of pci_dev_ser structs into an xarray so that it can be looked up efficiently. If a device is found in the xarray, the PCI core stores a pointer to its state in dev->liveupdate_incoming until pci_liveupdate_finish() is called by the driver. This pointer allows the PCI core and drivers to apply Live Update-specific logic to incoming devices in subsequent commits. Drivers can check if a device is an incoming preserved device (e.g. during probe) by calling pci_liveupdate_is_incoming(). CONFIG_64BIT is now required to enable CONFIG_PCI_LIVEUPDATE so that the domain and bdf can be guaranteed to fit in an unsigned long and be used as the xarray key. Reviewed-by: Pranjal Shrivastava Signed-off-by: David Matlack --- MAINTAINERS | 1 + drivers/pci/Kconfig | 2 +- drivers/pci/liveupdate.c | 256 ++++++++++++++++++++++++++++++++- drivers/pci/liveupdate.h | 5 + drivers/pci/probe.c | 3 + include/linux/pci_liveupdate.h | 13 ++ 6 files changed, 277 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 9cc7b9291ace..08a724b860dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20834,6 +20834,7 @@ L: linux-pci@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git F: drivers/pci/liveupdate.c +F: drivers/pci/liveupdate.h F: include/linux/kho/abi/pci.h F: include/linux/pci_liveupdate.h diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 3781e2b5f095..8af20f558086 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -273,7 +273,7 @@ config VGA_ARB_MAX_GPUS config PCI_LIVEUPDATE bool "PCI Live Update Support" - depends on PCI && LIVEUPDATE + depends on PCI && LIVEUPDATE && 64BIT help Enable PCI core support for preserving PCI devices across Live Update. This, in combination with support in a device's driver, diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c index b003b7069cdb..5ce5f8b36902 100644 --- a/drivers/pci/liveupdate.c +++ b/drivers/pci/liveupdate.c @@ -49,6 +49,20 @@ * 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 * ============ * @@ -100,6 +114,26 @@ struct pci_flb_outgoing { struct kho_block_set block_set; }; +/** + * struct pci_flb_incoming - Incoming PCI FLB object + * @ser: The incoming struct pci_ser from the previous kernel. + * @xa: Xarray used to quickly lookup devices in @ser. + * @block_set: The KHO block set holding the incoming devices. + * + * This structure holds the runtime state for the incoming PCI Live Update + * state. It wraps the serialized pci_ser, the block_set used to restore + * the serialized entries, and an xarray for fast lookups. + */ +struct pci_flb_incoming { + struct pci_ser *ser; + struct xarray xa; + struct kho_block_set block_set; +}; + +static unsigned long pci_ser_xa_key(u32 domain, u16 bdf) +{ + return (unsigned long)domain << 16 | bdf; +} static int pci_flb_preserve(struct liveupdate_flb_op_args *args) { struct pci_flb_outgoing *outgoing __free(kfree) = NULL; @@ -140,15 +174,91 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args) static int pci_flb_retrieve(struct liveupdate_flb_op_args *args) { + struct pci_ser *ser = phys_to_virt(args->data); + struct pci_flb_incoming *incoming; + struct pci_dev_ser *dev_ser; + struct kho_block_set_it it; + int ret; + pr_debug("Retrieving struct pci_ser (0x%llx)\n", args->data); - args->obj = phys_to_virt(args->data); + + if (ser->version != PCI_LUO_FLB_VERSION) { + pr_err("Incoming PCI FLB version (v%d) is incompatible with this kernel (v%d)\n", + ser->version, PCI_LUO_FLB_VERSION); + ret = -EINVAL; + goto err_restore_free; + } + + incoming = kzalloc_obj(*incoming); + if (!incoming) { + ret = -ENOMEM; + 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_check_all_devices_finished(struct pci_flb_incoming *incoming) +{ + struct pci_dev *dev = NULL; + + if (READ_ONCE(incoming->ser->nr_devices) == 0) + return; + + for_each_pci_dev(dev) { + if (READ_ONCE(dev->liveupdate.incoming)) + pci_emerg(dev, "Preserved device was never finished!\n"); + } + + /* + * This should only happen if a driver violated the contract to call + * pci_liveupdate_finish() (something is extremely broken). + */ + panic("Some preserved devices were never finished!\n"); } static void pci_flb_finish(struct liveupdate_flb_op_args *args) { + struct pci_flb_incoming *incoming = args->obj; + pr_debug("Finished struct pci_ser (0x%llx)\n", args->data); - kho_restore_free(args->obj); + pci_check_all_devices_finished(incoming); + + xa_destroy(&incoming->xa); + kho_block_set_destroy(&incoming->block_set); + kho_restore_free(incoming->ser); + kfree(incoming); } static struct liveupdate_flb_ops pci_liveupdate_flb_ops = { @@ -325,6 +435,75 @@ void pci_liveupdate_unpreserve(struct pci_dev *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. + */ + if (ret == -ENOENT) + return NULL; + + /* + * There is incoming FLB data that matches pci_liveupdate_flb.compatible + * but retrieve failed (pci_flb_retrieve() returned an error or LUO + * failed to acquire a reference to pci_liveupdate_flb_ops.owner). + */ + if (ret) + panic("Failed to retrieve incoming FLB data (%d)\n", ret); + + return incoming; +} + +static void pci_liveupdate_flb_put_incoming(void) +{ + liveupdate_flb_put_incoming(&pci_liveupdate_flb); +} + +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; + pci_liveupdate_flb_put_incoming(); +} + void pci_liveupdate_cleanup_device(struct pci_dev *dev) { /* @@ -336,7 +515,80 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev) */ 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"); +} + +static void 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; + } + + if (dev->liveupdate.incoming->refcount != 1) { + pci_WARN(dev, 1, "Preserved device has a corrupted refcount!\n"); + return; + } + + /* + * Drop 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. + */ + dev->liveupdate.incoming->refcount = 0; + + pci_info(dev, "Device is finished participating in Live Update\n"); + dev->liveupdate.incoming = NULL; + ser->nr_devices--; +} + +/** + * 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. + */ +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_device(incoming->ser, dev); + pci_liveupdate_flb_put_incoming(); +} +EXPORT_SYMBOL_GPL(pci_liveupdate_finish); + +/** + * 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 diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h index b2335581f8d0..eaaa3559fd77 100644 --- a/drivers/pci/liveupdate.h +++ b/drivers/pci/liveupdate.h @@ -11,8 +11,13 @@ #include #ifdef CONFIG_PCI_LIVEUPDATE +void pci_liveupdate_setup_device(struct pci_dev *dev); void pci_liveupdate_cleanup_device(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) { } diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 14b66acbdb15..5dc9d86e3597 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2065,6 +2065,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); @@ -2188,6 +2190,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; diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h index 894052ad6961..710026ada2d5 100644 --- a/include/linux/pci_liveupdate.h +++ b/include/linux/pci_liveupdate.h @@ -16,9 +16,11 @@ /** * 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. */ struct pci_liveupdate { struct pci_dev_ser *outgoing; + struct pci_dev_ser *incoming; }; struct pci_dev; @@ -28,6 +30,8 @@ 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) { @@ -46,6 +50,15 @@ static inline int pci_liveupdate_preserve(struct pci_dev *dev) 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 */ -- 2.55.0.487.gaf234c4eb3-goog