pci_set_long(dev->config + pos, header);
}
+/*
+ * Insert a PCIe extended capability at a given offset.
+ *
+ * This helper only validates that the insertion does not overwrite an
+ * existing PCIe extended capability header, as corrupting a header would
+ * break the extended capability linked list.
+ *
+ * The caller must ensure that (offset, size) does not overlap with other
+ * registers or capability-specific register blocks. Overlaps with
+ * capability-specific registers are not checked and are considered a
+ * user-controlled override.
+ *
+ * Note: Best effort helper. The PCIe spec does not require extended
+ * capabilities to be ordered, but most devices use a forward-linked list.
+ * Devices that do not consistently use a forward-linked list may cause
+ * insertion to fail.
+ */
+bool pcie_insert_capability(PCIDevice *dev, uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size)
+{
+ uint16_t pos = PCI_CONFIG_SPACE_SIZE, prev = 0;
+ uint32_t header;
+
+ assert(pci_is_express(dev));
+
+ if (!QEMU_IS_ALIGNED(offset, PCI_EXT_CAP_ALIGN) ||
+ size < 8 ||
+ offset < PCI_CONFIG_SPACE_SIZE ||
+ offset >= PCIE_CONFIG_SPACE_SIZE ||
+ offset + size > PCIE_CONFIG_SPACE_SIZE) {
+ return false;
+ }
+
+ header = pci_get_long(dev->config + pos);
+ if (!header) {
+ /* No extended capability present, insertion must be at the ECAP head */
+ if (offset != pos) {
+ return false;
+ }
+ pci_set_long(dev->config + pos, PCI_EXT_CAP(cap_id, cap_ver, 0));
+ goto out;
+ }
+
+ while (header && pos && offset >= pos) {
+ uint16_t next = PCI_EXT_CAP_NEXT(header);
+
+ /* Reject insertion inside an existing ECAP header (4 bytes) */
+ if (offset < pos + PCI_EXT_CAP_ALIGN) {
+ return false;
+ }
+
+ prev = pos;
+ pos = next;
+ header = pos ? pci_get_long(dev->config + pos) : 0;
+ }
+
+ pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, pos));
+ if (prev) {
+ pcie_ext_cap_set_next(dev, prev, offset);
+ }
+
+out:
+ /* Make capability read-only by default */
+ memset(dev->wmask + offset, 0, size);
+ memset(dev->w1cmask + offset, 0, size);
+ /* Check capability by default */
+ memset(dev->cmask + offset, 0xFF, size);
+ return true;
+}
/*
* Caller must supply valid (offset, size) such that the range wouldn't
* overlap with other capability or other registers.
void pcie_add_capability(PCIDevice *dev,
uint16_t cap_id, uint8_t cap_ver,
uint16_t offset, uint16_t size);
+bool pcie_insert_capability(PCIDevice *dev, uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size);
void pcie_sync_bridge_lnk(PCIDevice *dev);
void pcie_acs_init(PCIDevice *dev, uint16_t offset);