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
