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 <[email protected]>
---
 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


Reply via email to