[PATCH 1/7] PCI: Add pci_find_free_ext_cap_offset() helper
Jose Ignacio Tornos Martinez
jtornosm at redhat.com
Wed Sep 30 07:08:27 PDT 2026
Add helper function to find free space in PCI extended configuration
space. This is needed by VFIO variant drivers that need to use unused
extended config space for device-specific purposes, such as passing
metadata to VMs.
The function scans the extended capability chain and returns an offset
to a gap of at least the requested size. This allows drivers to safely
use extended config space without conflicting with existing capabilities.
Use case: The qcom-vfio-pci variant driver uses this to find space for
caching host MSI addresses that need to be passed to Qualcomm device
firmware in VMs.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm at redhat.com>
---
drivers/pci/pci.c | 54 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/pci.h | 3 +++
2 files changed, 57 insertions(+)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 8f7cfcc00090..7fa261140457 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -552,6 +552,60 @@ u16 pci_find_ext_capability(struct pci_dev *dev, int cap)
}
EXPORT_SYMBOL_GPL(pci_find_ext_capability);
+/**
+ * pci_find_free_ext_cap_offset - Find free space in extended config
+ * @dev: PCI device to query
+ * @size: Minimum size needed (in bytes)
+ *
+ * Scans the extended capability chain to find a gap of at least @size bytes
+ * in the extended configuration space (0x100-0xFFF). This can be used by
+ * drivers that need to use unused extended config space for device-specific
+ * purposes (e.g., VFIO quirks for VM passthrough).
+ *
+ * Returns the offset to free space, or 0 if no suitable gap found.
+ */
+int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size)
+{
+ int pos = PCI_CFG_SPACE_SIZE;
+ int last_cap_start = 0;
+ int free_offset;
+ u32 header;
+ int next;
+
+ if (!pci_is_pcie(dev))
+ return 0;
+
+ while (pos >= PCI_CFG_SPACE_SIZE) {
+ if (pci_read_config_dword(dev, pos, &header))
+ break;
+
+ if (header == 0 || header == 0xffffffff)
+ break;
+
+ last_cap_start = pos;
+ next = PCI_EXT_CAP_NEXT(header);
+ if (!next)
+ break;
+ pos = next;
+ }
+
+ /*
+ * Use space after the last capability. We align to the next capability
+ * boundary (typically 4-byte aligned). Start at a safe offset past the
+ * last capability - capabilities vary in size, so use a conservative
+ * offset. Most extended capabilities are <= 64 bytes.
+ */
+ if (last_cap_start) {
+ free_offset = last_cap_start + 64;
+ free_offset = ALIGN(free_offset, 4);
+ if (PCI_CFG_SPACE_EXP_SIZE - free_offset >= size)
+ return free_offset;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_find_free_ext_cap_offset);
+
/**
* pci_get_dsn - Read and return the 8-byte Device Serial Number
* @dev: PCI device to query
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..5d1ccbb2ed29 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1287,6 +1287,7 @@ u8 pci_find_ht_capability(struct pci_dev *dev, int ht_cap);
u8 pci_find_next_ht_capability(struct pci_dev *dev, u8 pos, int ht_cap);
u16 pci_find_ext_capability(struct pci_dev *dev, int cap);
u16 pci_find_next_ext_capability(struct pci_dev *dev, u16 pos, int cap);
+int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size);
struct pci_bus *pci_find_next_bus(const struct pci_bus *from);
u16 pci_find_vsec_capability(struct pci_dev *dev, u16 vendor, int cap);
u16 pci_find_dvsec_capability(struct pci_dev *dev, u16 vendor, u16 dvsec);
@@ -2160,6 +2161,8 @@ static inline u8 pci_find_next_capability(struct pci_dev *dev, u8 post, int cap)
{ return 0; }
static inline u16 pci_find_ext_capability(struct pci_dev *dev, int cap)
{ return 0; }
+static inline int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size)
+{ return 0; }
static inline u64 pci_get_dsn(struct pci_dev *dev)
{ return 0; }
--
2.53.0
More information about the ath12k
mailing list