From: David Matlack <dmatlack@google.com>
To: linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org,
linuxppc-dev@lists.ozlabs.org
Cc: Alex Williamson <alex@shazbot.org>,
Bjorn Helgaas <bhelgaas@google.com>,
Jason Gunthorpe <jgg@nvidia.com>,
Josh Hilke <jrhilke@google.com>, Lukas Wunner <lukas@wunner.de>,
Mahesh J Salgaonkar <mahesh@linux.ibm.com>,
"Oliver O'Halloran" <oohall@gmail.com>,
Pasha Tatashin <pasha.tatashin@soleen.com>,
Pratyush Yadav <pratyush@kernel.org>,
Samiullah Khawaja <skhawaja@google.com>,
Vipin Sharma <vipinsh@google.com>,
David Matlack <dmatlack@google.com>
Subject: [PATCH 03/15] PCI: Lay out struct pci_saved_state by configuration space offset
Date: Thu, 24 Sep 2026 17:34:49 +0000 [thread overview]
Message-ID: <20260924173501.856380-4-dmatlack@google.com> (raw)
In-Reply-To: <20260924173501.856380-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
makes the blob impossible to interpret outside the kernel that produced it,
which is a problem for VFIO, which needs to carry a device's saved state
across a Live Update kexec. 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() to ask the store that question without the WARN
that pci_saved_cap_slot() raises for an offset no capability reserved,
since here an unreserved offset means untrusted input rather than a
kernel bug.
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; the last patch of the series 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.
Assisted-by: LLM
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 b2879a6be5f8..7be54751a5d3 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1884,11 +1884,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.
@@ -1898,16 +1927,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;
@@ -1915,16 +1956,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;
}
@@ -1939,16 +1989,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 93916b0cdd21..e8e7bc8a63e7 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 649d2cd09e81..eaacc3b3ea74 100644
--- a/drivers/pci/saved-caps.c
+++ b/drivers/pci/saved-caps.c
@@ -218,6 +218,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.rc1.315.gc6ed9934b7-goog
next prev parent reply other threads:[~2026-09-24 17:35 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-24 17:34 [PATCH 00/15] PCI: Index saved capability state " David Matlack
2026-09-24 17:34 ` [PATCH 01/15] PCI/DPC: Allocate the DPC save buffer during device setup David Matlack
2026-09-24 17:34 ` [PATCH 02/15] PCI: Add an offset-indexed store for saved capability registers David Matlack
2026-09-24 17:34 ` David Matlack [this message]
2026-09-24 17:34 ` [PATCH 04/15] PCI: Save PCIe state in the saved capability store David Matlack
2026-09-24 17:34 ` [PATCH 05/15] PCI: Save PCI-X " David Matlack
2026-09-24 17:34 ` [PATCH 06/15] PCI/ASPM: Save LTR " David Matlack
2026-09-24 17:34 ` [PATCH 07/15] PCI/ASPM: Save L1SS " David Matlack
2026-09-24 17:34 ` [PATCH 08/15] PCI/AER: Save AER " David Matlack
2026-09-24 17:34 ` [PATCH 09/15] PCI/PTM: Save PTM " David Matlack
2026-09-24 17:34 ` [PATCH 10/15] PCI/TPH: Save TPH " David Matlack
2026-09-24 17:34 ` [PATCH 11/15] PCI/DPC: Save DPC " David Matlack
2026-09-24 17:34 ` [PATCH 12/15] PCI/VC: Split the VC Resource Control restore into a helper David Matlack
2026-09-24 17:34 ` [PATCH 13/15] PCI/VC: Save VC state in the saved capability store David Matlack
2026-09-24 17:35 ` [PATCH 14/15] PCI: Save reserved capability registers in a single pass David Matlack
2026-09-24 17:35 ` [PATCH 15/15] PCI: Remove the per-capability save buffers 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=20260924173501.856380-4-dmatlack@google.com \
--to=dmatlack@google.com \
--cc=alex@shazbot.org \
--cc=bhelgaas@google.com \
--cc=jgg@nvidia.com \
--cc=jrhilke@google.com \
--cc=linux-kernel@vger.kernel.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=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®