summaryrefslogtreecommitdiff
path: root/hw/i386/acpi-build.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/i386/acpi-build.c')
-rw-r--r--hw/i386/acpi-build.c127
1 files changed, 120 insertions, 7 deletions
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 266d9b534b..12ff55fcfb 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -2517,12 +2517,105 @@ build_dmar_q35(GArray *table_data, BIOSLinker *linker)
*/
#define IOAPIC_SB_DEVID (uint64_t)PCI_BUILD_BDF(0, PCI_DEVFN(0x14, 0))
+/*
+ * Insert IVHD entry for device and recurse, insert alias, or insert range as
+ * necessary for the PCI topology.
+ */
+static void
+insert_ivhd(PCIBus *bus, PCIDevice *dev, void *opaque)
+{
+ GArray *table_data = opaque;
+ uint32_t entry;
+
+ /* "Select" IVHD entry, type 0x2 */
+ entry = PCI_BUILD_BDF(pci_bus_num(bus), dev->devfn) << 8 | 0x2;
+ build_append_int_noprefix(table_data, entry, 4);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) {
+ PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
+ uint8_t sec = pci_bus_num(sec_bus);
+ uint8_t sub = dev->config[PCI_SUBORDINATE_BUS];
+
+ if (pci_bus_is_express(sec_bus)) {
+ /*
+ * Walk the bus if there are subordinates, otherwise use a range
+ * to cover an entire leaf bus. We could potentially also use a
+ * range for traversed buses, but we'd need to take care not to
+ * create both Select and Range entries covering the same device.
+ * This is easier and potentially more compact.
+ *
+ * An example bare metal system seems to use Select entries for
+ * root ports without a slot (ie. built-ins) and Range entries
+ * when there is a slot. The same system also only hard-codes
+ * the alias range for an onboard PCIe-to-PCI bridge, apparently
+ * making no effort to support nested bridges. We attempt to
+ * be more thorough here.
+ */
+ if (sec == sub) { /* leaf bus */
+ /* "Start of Range" IVHD entry, type 0x3 */
+ entry = PCI_BUILD_BDF(sec, PCI_DEVFN(0, 0)) << 8 | 0x3;
+ build_append_int_noprefix(table_data, entry, 4);
+ /* "End of Range" IVHD entry, type 0x4 */
+ entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
+ build_append_int_noprefix(table_data, entry, 4);
+ } else {
+ pci_for_each_device(sec_bus, sec, insert_ivhd, table_data);
+ }
+ } else {
+ /*
+ * If the secondary bus is conventional, then we need to create an
+ * Alias range for everything downstream. The range covers the
+ * first devfn on the secondary bus to the last devfn on the
+ * subordinate bus. The alias target depends on legacy versus
+ * express bridges, just as in pci_device_iommu_address_space().
+ * DeviceIDa vs DeviceIDb as per the AMD IOMMU spec.
+ */
+ uint16_t dev_id_a, dev_id_b;
+
+ dev_id_a = PCI_BUILD_BDF(sec, PCI_DEVFN(0, 0));
+
+ if (pci_is_express(dev) &&
+ pcie_cap_get_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE) {
+ dev_id_b = dev_id_a;
+ } else {
+ dev_id_b = PCI_BUILD_BDF(pci_bus_num(bus), dev->devfn);
+ }
+
+ /* "Alias Start of Range" IVHD entry, type 0x43, 8 bytes */
+ build_append_int_noprefix(table_data, dev_id_a << 8 | 0x43, 4);
+ build_append_int_noprefix(table_data, dev_id_b << 8 | 0x0, 4);
+
+ /* "End of Range" IVHD entry, type 0x4 */
+ entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
+ build_append_int_noprefix(table_data, entry, 4);
+ }
+ }
+}
+
+/* For all PCI host bridges, walk and insert IVHD entries */
+static int
+ivrs_host_bridges(Object *obj, void *opaque)
+{
+ GArray *ivhd_blob = opaque;
+
+ if (object_dynamic_cast(obj, TYPE_PCI_HOST_BRIDGE)) {
+ PCIBus *bus = PCI_HOST_BRIDGE(obj)->bus;
+
+ if (bus) {
+ pci_for_each_device(bus, pci_bus_num(bus), insert_ivhd, ivhd_blob);
+ }
+ }
+
+ return 0;
+}
+
static void
build_amd_iommu(GArray *table_data, BIOSLinker *linker)
{
- int ivhd_table_len = 28;
+ int ivhd_table_len = 24;
int iommu_start = table_data->len;
AMDVIState *s = AMD_IOMMU_DEVICE(x86_iommu_get_default());
+ GArray *ivhd_blob = g_array_new(false, true, 1);
/* IVRS header */
acpi_data_push(table_data, sizeof(AcpiTableHeader));
@@ -2544,12 +2637,34 @@ build_amd_iommu(GArray *table_data, BIOSLinker *linker)
1);
/*
+ * A PCI bus walk, for each PCI host bridge, is necessary to create a
+ * complete set of IVHD entries. Do this into a separate blob so that we
+ * can calculate the total IVRS table length here and then append the new
+ * blob further below. Fall back to an entry covering all devices, which
+ * is sufficient when no aliases are present.
+ */
+ object_child_foreach_recursive(object_get_root(),
+ ivrs_host_bridges, ivhd_blob);
+
+ if (!ivhd_blob->len) {
+ /*
+ * Type 1 device entry reporting all devices
+ * These are 4-byte device entries currently reporting the range of
+ * Refer to Spec - Table 95:IVHD Device Entry Type Codes(4-byte)
+ */
+ build_append_int_noprefix(ivhd_blob, 0x0000001, 4);
+ }
+
+ ivhd_table_len += ivhd_blob->len;
+
+ /*
* When interrupt remapping is supported, we add a special IVHD device
* for type IO-APIC.
*/
if (x86_iommu_ir_supported(x86_iommu_get_default())) {
ivhd_table_len += 8;
}
+
/* IVHD length */
build_append_int_noprefix(table_data, ivhd_table_len, 2);
/* DeviceID */
@@ -2569,12 +2684,10 @@ build_amd_iommu(GArray *table_data, BIOSLinker *linker)
(1UL << 2) | /* GTSup */
(1UL << 6), /* GASup */
4);
- /*
- * Type 1 device entry reporting all devices
- * These are 4-byte device entries currently reporting the range of
- * Refer to Spec - Table 95:IVHD Device Entry Type Codes(4-byte)
- */
- build_append_int_noprefix(table_data, 0x0000001, 4);
+
+ /* IVHD entries as found above */
+ g_array_append_vals(table_data, ivhd_blob->data, ivhd_blob->len);
+ g_array_free(ivhd_blob, TRUE);
/*
* Add a special IVHD device type.