[PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper

Jose Ignacio Tornos Martinez jtornosm at redhat.com
Sun Oct 11 00:45:08 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 walks the extended capability chain (with TTL to prevent
infinite loops on malformed chains), then scans for a contiguous region
of zeroed space of at least the requested size. This avoids hardcoding
capability sizes and correctly handles capabilities of any length.

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>
---
v2: Address Sashiko comments:
    - Add TTL to capability chain walk to prevent infinite loops on
      malformed capability chains (matching pci_find_next_ext_capability)
    - Replace hardcoded 64-byte offset with zero-scan approach to
      correctly handle capabilities of any size
    - Fix potential integer underflow in size comparison
    - Add cfg_size and size parameter validation
v1: https://lore.kernel.org/all/20260930140833.576941-2-jtornosm@redhat.com/

 drivers/pci/pci.c   | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pci.h |  3 +++
 2 files changed, 75 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,78 @@ 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 and then searches for a contiguous
+ * region of zeroed space 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 ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8;
+	int pos = PCI_CFG_SPACE_SIZE;
+	int start = 0, run = 0;
+	int highest_pos = 0;
+	u32 header;
+	int offset;
+
+	if (!pci_is_pcie(dev) || dev->cfg_size <= PCI_CFG_SPACE_SIZE)
+		return 0;
+
+	if (size == 0 || size > PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE)
+		return 0;
+
+	if (pci_read_config_dword(dev, pos, &header))
+		return 0;
+	if (header == 0 || header == 0xffffffff)
+		return 0;
+
+	while (ttl-- > 0) {
+		if (pos > highest_pos)
+			highest_pos = pos;
+
+		pos = PCI_EXT_CAP_NEXT(header);
+		if (!pos || pos < PCI_CFG_SPACE_SIZE)
+			break;
+
+		if (pci_read_config_dword(dev, pos, &header))
+			break;
+		if (header == 0 || header == 0xffffffff)
+			break;
+	}
+
+	if (!highest_pos)
+		return 0;
+
+	for (offset = highest_pos; offset <= (int)PCI_CFG_SPACE_EXP_SIZE - 4;
+	     offset += 4) {
+		u32 val;
+
+		if (pci_read_config_dword(dev, offset, &val))
+			break;
+
+		if (val == 0) {
+			if (run == 0)
+				start = offset;
+			run += 4;
+			if (run >= (int)size)
+				return start;
+		} else {
+			run = 0;
+		}
+	}
+
+	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