From: David Matlack <dmatlack@google.com>
To: kexec@lists.infradead.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-pci@vger.kernel.org, linuxppc-dev@lists.ozlabs.org
Cc: Alexander Graf <graf@amazon.com>,
Alex Williamson <alex@shazbot.org>,
Bjorn Helgaas <bhelgaas@google.com>,
David Matlack <dmatlack@google.com>,
Jason Gunthorpe <jgg@nvidia.com>,
Jonathan Corbet <corbet@lwn.net>, Josh Hilke <jrhilke@google.com>,
Lukas Wunner <lukas@wunner.de>,
Mahesh J Salgaonkar <mahesh@linux.ibm.com>,
Mike Rapoport <rppt@kernel.org>,
"Oliver O'Halloran" <oohall@gmail.com>,
Pasha Tatashin <pasha.tatashin@soleen.com>,
Pratyush Yadav <pratyush@kernel.org>,
Randy Dunlap <rdunlap@infradead.org>,
Samiullah Khawaja <skhawaja@google.com>,
Shuah Khan <skhan@linuxfoundation.org>,
Vipin Sharma <vipinsh@google.com>
Subject: [PATCH v2 03/17] PCI: Lay out struct pci_saved_state by configuration space offset
Date: Fri, 6 Nov 2026 00:42:04 +0000 [thread overview]
Message-ID: <20261106004218.604799-4-dmatlack@google.com> (raw)
In-Reply-To: <20261106004218.604799-1-dmatlack@google.com>
Store a device's saved state as a bitmap of the configuration space DWORDs
that were saved plus their values, instead of a copy of the configuration
space header followed by a list of pci_cap_saved_data records.
The old layout is described entirely by the kernel: which capabilities have
a record, how large each record is, and what each word within a record
means are all properties of the code that happens to be saving them. That
ties the blob to the kernel that produced it, so VFIO cannot carry a
device's saved state across a Live Update. The new layout is
described by the device: a value's position is the offset of its
register in the device's own configuration space.
Keep refusing state that the device has nowhere to put in
pci_load_saved_state(), the equivalent of the old check that the
capability existed and its record was the expected size. Use
pci_saved_cap_reserved() for the check, since state saved from another
device or kernel can legitimately name an offset that this device did
not reserve.
No capability uses the store yet, and the patches that follow move them
into it one at a time, so keep appending the records of the capabilities
that still have their own buffer after the saved DWORDs. The blob
therefore continues to describe all of a device's saved state at every
step; a later patch drops the records once there are none left.
struct pci_saved_state is opaque to everything outside drivers/pci, so no
caller needs to change.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/pci.c | 89 +++++++++++++++++++++++++++++++++++-----
drivers/pci/pci.h | 4 ++
drivers/pci/saved-caps.c | 14 +++++++
3 files changed, 96 insertions(+), 11 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index e5b960ca26e6..007f9bb289d5 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2032,11 +2032,40 @@ void pci_restore_state(struct pci_dev *dev)
}
EXPORT_SYMBOL(pci_restore_state);
+/**
+ * struct pci_saved_state - a device's saved configuration space
+ * @dword_map: one bit per DWORD of configuration space, set if that DWORD
+ * was saved
+ * @dword_val: saved values, in ascending configuration space offset order
+ *
+ * Laid out so that the position of every value is described by the device's
+ * configuration space rather than by the kernel.
+ *
+ * Capabilities that have not moved to the saved capability store yet keep
+ * their own buffer, and their records trail @dword_val until the last of
+ * those buffers goes away. Both areas vary in length and only one of them
+ * can be a flexible array member, so the records are reached with
+ * pci_saved_state_records() rather than declared here.
+ */
struct pci_saved_state {
- u32 config_space[16];
- struct pci_cap_saved_data cap[];
+ unsigned long dword_map[BITS_TO_LONGS(PCI_CFG_SPACE_EXP_DWORDS)];
+ u32 dword_val[];
+ /* struct pci_cap_saved_data cap[] follows dword_val */
};
+/*
+ * The records that follow the saved DWORDs, one per capability that still has
+ * its own save buffer, terminated by an empty record.
+ */
+static struct pci_cap_saved_data *
+pci_saved_state_records(struct pci_saved_state *state)
+{
+ unsigned int nr_dwords = bitmap_weight(state->dword_map,
+ PCI_CFG_SPACE_EXP_DWORDS);
+
+ return (struct pci_cap_saved_data *)&state->dword_val[nr_dwords];
+}
+
/**
* pci_store_saved_state - Allocate and return an opaque struct containing
* the device saved state.
@@ -2046,16 +2075,28 @@ struct pci_saved_state {
*/
struct pci_saved_state *pci_store_saved_state(struct pci_dev *dev)
{
- struct pci_saved_state *state;
+ DECLARE_BITMAP(map, PCI_CFG_SPACE_EXP_DWORDS);
+ struct pci_saved_caps *caps = &dev->saved_caps;
struct pci_cap_saved_state *tmp;
struct pci_cap_saved_data *cap;
+ struct pci_saved_state *state;
+ unsigned int dword, nr_dwords, i = 0;
size_t size;
if (!dev->state_saved)
return NULL;
- size = sizeof(*state) + sizeof(struct pci_cap_saved_data);
+ bitmap_zero(map, PCI_CFG_SPACE_EXP_DWORDS);
+ bitmap_set(map, 0, PCI_STD_HEADER_DWORDS);
+ if (caps->dword_val)
+ bitmap_or(map, map, caps->dword_map, PCI_CFG_SPACE_EXP_DWORDS);
+
+ nr_dwords = bitmap_weight(map, PCI_CFG_SPACE_EXP_DWORDS);
+
+ size = struct_size(state, dword_val, nr_dwords);
+ /* Room for the buffers that are left, and for the terminator */
+ size += sizeof(struct pci_cap_saved_data);
hlist_for_each_entry(tmp, &dev->saved_cap_space, next)
size += sizeof(struct pci_cap_saved_data) + tmp->cap.size;
@@ -2063,16 +2104,25 @@ struct pci_saved_state *pci_store_saved_state(struct pci_dev *dev)
if (!state)
return NULL;
- memcpy(state->config_space, dev->saved_config_space,
- sizeof(state->config_space));
+ bitmap_copy(state->dword_map, map, PCI_CFG_SPACE_EXP_DWORDS);
- cap = state->cap;
+ for_each_set_bit(dword, state->dword_map, PCI_CFG_SPACE_EXP_DWORDS) {
+ u32 *val = &state->dword_val[i++];
+
+ if (dword < PCI_STD_HEADER_DWORDS)
+ *val = dev->saved_config_space[dword];
+ else
+ pci_read_saved_cap_dword(dev, dword * sizeof(u32), val);
+ }
+
+ cap = pci_saved_state_records(state);
hlist_for_each_entry(tmp, &dev->saved_cap_space, next) {
size_t len = sizeof(struct pci_cap_saved_data) + tmp->cap.size;
+
memcpy(cap, &tmp->cap, len);
cap = (struct pci_cap_saved_data *)((u8 *)cap + len);
}
- /* Empty cap_save terminates list */
+ /* Empty record terminates the list */
return state;
}
@@ -2087,16 +2137,33 @@ int pci_load_saved_state(struct pci_dev *dev,
struct pci_saved_state *state)
{
struct pci_cap_saved_data *cap;
+ unsigned int dword, i = 0;
dev->state_saved = false;
if (!state)
return 0;
- memcpy(dev->saved_config_space, state->config_space,
- sizeof(state->config_space));
+ for_each_set_bit(dword, state->dword_map, PCI_CFG_SPACE_EXP_DWORDS) {
+ unsigned int off = dword * sizeof(u32);
+ u32 val = state->dword_val[i++];
+
+ if (dword < PCI_STD_HEADER_DWORDS) {
+ dev->saved_config_space[dword] = val;
+ continue;
+ }
+
+ /*
+ * Refuse state that the device has nowhere to put, e.g.
+ * because it was saved from a different device.
+ */
+ if (!pci_saved_cap_reserved(dev, off))
+ return -EINVAL;
+
+ pci_write_saved_cap_dword(dev, off, val);
+ }
- cap = state->cap;
+ cap = pci_saved_state_records(state);
while (cap->size) {
struct pci_cap_saved_state *tmp;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 6676177706ba..206e0d04e06e 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -258,6 +258,9 @@ struct pci_cap_saved_state *pci_find_saved_cap(struct pci_dev *dev, char cap);
struct pci_cap_saved_state *pci_find_saved_ext_cap(struct pci_dev *dev,
u16 cap);
+/* DWORDs of the configuration space header, i.e. pci_dev.saved_config_space */
+#define PCI_STD_HEADER_DWORDS (PCI_STD_HEADER_SIZEOF / sizeof(u32))
+
void pci_saved_caps_finalize(struct pci_dev *dev);
void pci_saved_caps_release(struct pci_dev *dev);
int pci_reserve_saved_cap(struct pci_dev *dev, unsigned int off, unsigned int len);
@@ -269,6 +272,7 @@ void pci_restore_cap_dword(struct pci_dev *dev, unsigned int off);
bool pci_read_saved_cap_word(struct pci_dev *dev, unsigned int off, u16 *val);
bool pci_read_saved_cap_dword(struct pci_dev *dev, unsigned int off, u32 *val);
void pci_write_saved_cap_word(struct pci_dev *dev, unsigned int off, u16 val);
+void pci_write_saved_cap_dword(struct pci_dev *dev, unsigned int off, u32 val);
#define PCI_PM_D2_DELAY 200 /* usec; see PCIe r4.0, sec 5.9.1 */
#define PCI_PM_D3HOT_WAIT 10 /* msec */
diff --git a/drivers/pci/saved-caps.c b/drivers/pci/saved-caps.c
index 92865891514e..4647ce59ce74 100644
--- a/drivers/pci/saved-caps.c
+++ b/drivers/pci/saved-caps.c
@@ -211,6 +211,20 @@ void pci_write_saved_cap_word(struct pci_dev *dev, unsigned int off, u16 val)
*slot |= (u32)val << shift;
}
+/**
+ * pci_write_saved_cap_dword - change the saved value of a 32-bit register
+ * @dev: the PCI device
+ * @off: offset of the register in configuration space
+ * @val: value to save
+ */
+void pci_write_saved_cap_dword(struct pci_dev *dev, unsigned int off, u32 val)
+{
+ u32 *slot = pci_saved_cap_slot(dev, off);
+
+ if (slot)
+ *slot = val;
+}
+
/**
* pci_restore_cap_dword - restore a 32-bit capability register
* @dev: the PCI device
--
2.56.0.455.g6c5a503b38-goog
next prev parent reply other threads:[~2026-11-06 0:43 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 0:42 [PATCH v2 00/17] PCI: Index saved capability state " David Matlack
2026-11-06 0:42 ` [PATCH v2 01/17] PCI/DPC: Allocate the DPC save buffer during device setup David Matlack
2026-11-06 0:42 ` [PATCH v2 02/17] PCI: Add an offset-indexed store for saved capability registers David Matlack
2026-11-06 0:42 ` David Matlack [this message]
2026-11-06 0:42 ` [PATCH v2 04/17] PCI: Save PCIe state in the saved capability store David Matlack
2026-11-06 0:42 ` [PATCH v2 05/17] PCI: Save PCI-X " David Matlack
2026-11-06 0:42 ` [PATCH v2 06/17] PCI/ASPM: Save LTR " David Matlack
2026-11-06 0:42 ` [PATCH v2 07/17] PCI/ASPM: Save L1SS " David Matlack
2026-11-06 0:42 ` [PATCH v2 08/17] PCI/AER: Save AER " David Matlack
2026-11-06 0:42 ` [PATCH v2 09/17] PCI/PTM: Save PTM " David Matlack
2026-11-06 0:42 ` [PATCH v2 10/17] PCI/TPH: Save TPH " David Matlack
2026-11-06 0:42 ` [PATCH v2 11/17] PCI/DPC: Save DPC " David Matlack
2026-11-06 0:42 ` [PATCH v2 12/17] PCI: Save ACS " David Matlack
2026-11-06 0:42 ` [PATCH v2 13/17] PCI/VC: Split the VC Resource Control restore into a helper David Matlack
2026-11-06 0:42 ` [PATCH v2 14/17] PCI/VC: Save VC state in the saved capability store David Matlack
2026-11-06 0:42 ` [PATCH v2 15/17] PCI: Save reserved capability registers in a single pass David Matlack
2026-11-06 0:42 ` [PATCH v2 16/17] PCI: Remove the per-capability save buffers David Matlack
2026-11-06 0:42 ` [PATCH v2 17/17] PCI: liveupdate: Add helpers to preserve struct pci_saved_state David Matlack
2026-11-06 0:46 ` [PATCH v2 00/17] PCI: Index saved capability state by configuration space offset David Matlack
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106004218.604799-4-dmatlack@google.com \
--to=dmatlack@google.com \
--cc=alex@shazbot.org \
--cc=bhelgaas@google.com \
--cc=corbet@lwn.net \
--cc=graf@amazon.com \
--cc=jgg@nvidia.com \
--cc=jrhilke@google.com \
--cc=kexec@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-pci@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=lukas@wunner.de \
--cc=mahesh@linux.ibm.com \
--cc=oohall@gmail.com \
--cc=pasha.tatashin@soleen.com \
--cc=pratyush@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=skhawaja@google.com \
--cc=vipinsh@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®