aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/pci/pci.c43
1 files changed, 40 insertions, 3 deletions
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index c68498c0de..cbc7a32568 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -2646,12 +2646,49 @@ AddressSpace *pci_device_iommu_address_space(PCIDevice *dev)
{
PCIBus *bus = pci_get_bus(dev);
PCIBus *iommu_bus = bus;
+ uint8_t devfn = dev->devfn;
- while(iommu_bus && !iommu_bus->iommu_fn && iommu_bus->parent_dev) {
- iommu_bus = pci_get_bus(iommu_bus->parent_dev);
+ while (iommu_bus && !iommu_bus->iommu_fn && iommu_bus->parent_dev) {
+ PCIBus *parent_bus = pci_get_bus(iommu_bus->parent_dev);
+
+ /*
+ * The requester ID of the provided device may be aliased, as seen from
+ * the IOMMU, due to topology limitations. The IOMMU relies on a
+ * requester ID to provide a unique AddressSpace for devices, but
+ * conventional PCI buses pre-date such concepts. Instead, the PCIe-
+ * to-PCI bridge creates and accepts transactions on behalf of down-
+ * stream devices. When doing so, all downstream devices are masked
+ * (aliased) behind a single requester ID. The requester ID used
+ * depends on the format of the bridge devices. Proper PCIe-to-PCI
+ * bridges, with a PCIe capability indicating such, follow the
+ * guidelines of chapter 2.3 of the PCIe-to-PCI/X bridge specification,
+ * where the bridge uses the seconary bus as the bridge portion of the
+ * requester ID and devfn of 00.0. For other bridges, typically those
+ * found on the root complex such as the dmi-to-pci-bridge, we follow
+ * the convention of typical bare-metal hardware, which uses the
+ * requester ID of the bridge itself. There are device specific
+ * exceptions to these rules, but these are the defaults that the
+ * Linux kernel uses when determining DMA aliases itself and believed
+ * to be true for the bare metal equivalents of the devices emulated
+ * in QEMU.
+ */
+ if (!pci_bus_is_express(iommu_bus)) {
+ PCIDevice *parent = iommu_bus->parent_dev;
+
+ if (pci_is_express(parent) &&
+ pcie_cap_get_type(parent) == PCI_EXP_TYPE_PCI_BRIDGE) {
+ devfn = PCI_DEVFN(0, 0);
+ bus = iommu_bus;
+ } else {
+ devfn = parent->devfn;
+ bus = parent_bus;
+ }
+ }
+
+ iommu_bus = parent_bus;
}
if (iommu_bus && iommu_bus->iommu_fn) {
- return iommu_bus->iommu_fn(bus, iommu_bus->iommu_opaque, dev->devfn);
+ return iommu_bus->iommu_fn(bus, iommu_bus->iommu_opaque, devfn);
}
return &address_space_memory;
}