AMD IOMMU: cover all functions of a device even if ACPI only tells us of func 0 This ought to work as all functions of a device have the same place in the bus topology, i.e. use the same IOMMU. Also fix the type of ivrs_bdf_entries (when it's 'unsigned short' and the last device found on a segment is ff:1f.x, it would otherwise end up being zero). And drop the bogus 'last_bdf' static variable, which conflicted anyway with various functions' parameters. Signed-off-by: Jan Beulich --- a/xen/drivers/passthrough/amd/iommu_acpi.c +++ b/xen/drivers/passthrough/amd/iommu_acpi.c @@ -54,8 +54,6 @@ union acpi_ivhd_device { struct acpi_ivrs_device8c special; }; -static unsigned short __initdata last_bdf; - static void __init add_ivrs_mapping_entry( u16 bdf, u16 alias_id, u8 flags, struct amd_iommu *iommu) { @@ -991,6 +989,7 @@ static int __init get_last_bdf_ivhd( { const union acpi_ivhd_device *ivhd_device; u16 block_length, dev_length; + int last_bdf = 0; if ( ivhd_block->header.length < sizeof(*ivhd_block) ) { @@ -1051,27 +1050,34 @@ static int __init get_last_bdf_ivhd( return -ENODEV; } - return 0; + return last_bdf; } static int __init get_last_bdf_acpi(struct acpi_table_header *table) { const struct acpi_ivrs_header *ivrs_block; unsigned long length = sizeof(struct acpi_table_ivrs); + int last_bdf = 0; while ( table->length > (length + sizeof(*ivrs_block)) ) { ivrs_block = (struct acpi_ivrs_header *)((u8 *)table + length); if ( table->length < (length + ivrs_block->length) ) return -ENODEV; - if ( ivrs_block->type == ACPI_IVRS_TYPE_HARDWARE && - get_last_bdf_ivhd( + if ( ivrs_block->type == ACPI_IVRS_TYPE_HARDWARE ) + { + int ret = get_last_bdf_ivhd( container_of(ivrs_block, const struct acpi_ivrs_hardware, - header)) != 0 ) - return -ENODEV; + header)); + + if ( ret < 0 ) + return ret; + UPDATE_LAST_BDF(ret); + } length += ivrs_block->length; } - return 0; + + return last_bdf; } int __init amd_iommu_detect_acpi(void) @@ -1081,8 +1087,9 @@ int __init amd_iommu_detect_acpi(void) int __init amd_iommu_get_ivrs_dev_entries(void) { - acpi_table_parse(ACPI_SIG_IVRS, get_last_bdf_acpi); - return last_bdf + 1; + int ret = acpi_table_parse(ACPI_SIG_IVRS, get_last_bdf_acpi); + + return ret < 0 ? ret : (ret | PCI_FUNC(~0)) + 1; } int __init amd_iommu_update_ivrs_mapping_acpi(void) --- a/xen/drivers/passthrough/amd/iommu_init.c +++ b/xen/drivers/passthrough/amd/iommu_init.c @@ -35,7 +35,7 @@ static int __initdata nr_amd_iommus; static struct tasklet amd_iommu_irq_tasklet; -unsigned short ivrs_bdf_entries; +unsigned int __read_mostly ivrs_bdf_entries; static struct radix_tree_root ivrs_maps; struct list_head amd_iommu_head; struct table_struct device_table; --- a/xen/drivers/passthrough/amd/pci_amd_iommu.c +++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c @@ -28,12 +28,38 @@ #include #include "../ats.h" +static bool_t __read_mostly init_done; + struct amd_iommu *find_iommu_for_device(int seg, int bdf) { struct ivrs_mappings *ivrs_mappings = get_ivrs_mappings(seg); - return ivrs_mappings && bdf < ivrs_bdf_entries ? ivrs_mappings[bdf].iommu - : NULL; + if ( !ivrs_mappings || bdf >= ivrs_bdf_entries ) + return NULL; + + if ( unlikely(!ivrs_mappings[bdf].iommu) && likely(init_done) ) + { + unsigned int bd0 = bdf & ~PCI_FUNC(~0); + + if ( ivrs_mappings[bd0].iommu ) + { + struct ivrs_mappings tmp = ivrs_mappings[bd0]; + + tmp.iommu = NULL; + if ( tmp.dte_requestor_id == bd0 ) + tmp.dte_requestor_id = bdf; + ivrs_mappings[bdf] = tmp; + + printk(XENLOG_WARNING "%04x:%02x:%02x.%u not found in ACPI tables;" + " using same IOMMU as function 0\n", + seg, PCI_BUS(bdf), PCI_SLOT(bdf), PCI_FUNC(bdf)); + + /* write iommu field last */ + ivrs_mappings[bdf].iommu = ivrs_mappings[bd0].iommu; + } + } + + return ivrs_mappings[bdf].iommu; } /* @@ -179,6 +205,8 @@ int __init amd_iov_detect(void) return -ENODEV; } + init_done = 1; + /* * AMD IOMMUs don't distinguish between vectors destined for * different cpus when doing interrupt remapping. This means --- a/xen/include/asm-x86/amd-iommu.h +++ b/xen/include/asm-x86/amd-iommu.h @@ -125,7 +125,7 @@ struct ivrs_mappings { u8 device_flags; }; -extern unsigned short ivrs_bdf_entries; +extern unsigned int ivrs_bdf_entries; struct ivrs_mappings *get_ivrs_mappings(u16 seg); int iterate_ivrs_mappings(int (*)(u16 seg, struct ivrs_mappings *));