Add Intel IOMMU emulation to q35 chipset and expose it to the guest. 1. Add a machine option. Users can use "-machine iommu=on|off" in the command line to enable/disable Intel IOMMU. The default is off. 2. Accroding to the machine option, q35 will initialize the Intel IOMMU and use pci_setup_iommu() to setup q35_host_dma_iommu() as the IOMMU function for the pci bus. 3. q35_host_dma_iommu() will return different address space according to the bus_num and devfn of the device.
Signed-off-by: Le Tan <tamlokv...@gmail.com> --- hw/core/machine.c | 27 +++++++++++++++++--- hw/pci-host/q35.c | 64 +++++++++++++++++++++++++++++++++++++++++++---- include/hw/boards.h | 1 + include/hw/pci-host/q35.h | 2 ++ qemu-options.hx | 5 +++- vl.c | 4 +++ 6 files changed, 94 insertions(+), 9 deletions(-) diff --git a/hw/core/machine.c b/hw/core/machine.c index 7a66c57..f0046d6 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -235,6 +235,20 @@ static void machine_set_firmware(Object *obj, const char *value, Error **errp) ms->firmware = g_strdup(value); } +static bool machine_get_iommu(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return ms->iommu; +} + +static void machine_set_iommu(Object *obj, bool value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->iommu = value; +} + static void machine_initfn(Object *obj) { object_property_add_str(obj, "accel", @@ -270,10 +284,17 @@ static void machine_initfn(Object *obj) machine_set_dump_guest_core, NULL); object_property_add_bool(obj, "mem-merge", - machine_get_mem_merge, machine_set_mem_merge, NULL); - object_property_add_bool(obj, "usb", machine_get_usb, machine_set_usb, NULL); + machine_get_mem_merge, + machine_set_mem_merge, NULL); + object_property_add_bool(obj, "usb", + machine_get_usb, + machine_set_usb, NULL); object_property_add_str(obj, "firmware", - machine_get_firmware, machine_set_firmware, NULL); + machine_get_firmware, + machine_set_firmware, NULL); + object_property_add_bool(obj, "iommu", + machine_get_iommu, + machine_set_iommu, NULL); } static void machine_finalize(Object *obj) diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c index a0a3068..3342711 100644 --- a/hw/pci-host/q35.c +++ b/hw/pci-host/q35.c @@ -347,6 +347,53 @@ static void mch_reset(DeviceState *qdev) mch_update(mch); } +static AddressSpace *q35_host_dma_iommu(PCIBus *bus, void *opaque, int devfn) +{ + IntelIOMMUState *s = opaque; + VTDAddressSpace **pvtd_as; + VTDAddressSpace *vtd_as; + int bus_num = pci_bus_num(bus); + + assert(devfn >= 0); + + pvtd_as = s->address_spaces[bus_num]; + if (!pvtd_as) { + /* No corresponding free() */ + pvtd_as = g_malloc0(sizeof(VTDAddressSpace *) * + VTD_PCI_SLOT_MAX * VTD_PCI_FUNC_MAX); + s->address_spaces[bus_num] = pvtd_as; + } + + vtd_as = *(pvtd_as + devfn); + if (!vtd_as) { + vtd_as = g_malloc0(sizeof(*vtd_as)); + *(pvtd_as + devfn) = vtd_as; + + vtd_as->bus_num = bus_num; + vtd_as->devfn = devfn; + vtd_as->iommu_state = s; + memory_region_init_iommu(&vtd_as->iommu, OBJECT(s), &s->iommu_ops, + "intel_iommu", UINT64_MAX); + address_space_init(&vtd_as->as, &vtd_as->iommu, "intel_iommu"); + } + + return &vtd_as->as; +} + +static void mch_init_dmar(MCHPCIState *mch) +{ + PCIBus *pci_bus = PCI_BUS(qdev_get_parent_bus(DEVICE(mch))); + + mch->iommu = INTEL_IOMMU_DEVICE(qdev_create(NULL, TYPE_INTEL_IOMMU_DEVICE)); + object_property_add_child(OBJECT(mch), "intel-iommu", + OBJECT(mch->iommu), NULL); + qdev_init_nofail(DEVICE(mch->iommu)); + sysbus_mmio_map(SYS_BUS_DEVICE(mch->iommu), 0, Q35_HOST_BRIDGE_IOMMU_ADDR); + + pci_setup_iommu(pci_bus, q35_host_dma_iommu, mch->iommu); +} + + static int mch_init(PCIDevice *d) { int i; @@ -363,13 +410,20 @@ static int mch_init(PCIDevice *d) memory_region_add_subregion_overlap(mch->system_memory, 0xa0000, &mch->smram_region, 1); memory_region_set_enabled(&mch->smram_region, false); - init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory, mch->pci_address_space, - &mch->pam_regions[0], PAM_BIOS_BASE, PAM_BIOS_SIZE); + init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory, + mch->pci_address_space, &mch->pam_regions[0], PAM_BIOS_BASE, + PAM_BIOS_SIZE); for (i = 0; i < 12; ++i) { - init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory, mch->pci_address_space, - &mch->pam_regions[i+1], PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, - PAM_EXPAN_SIZE); + init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory, + mch->pci_address_space, &mch->pam_regions[i+1], + PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE); + } + + /* Intel IOMMU (VT-d) */ + if (qemu_opt_get_bool(qemu_get_machine_opts(), "iommu", false)) { + mch_init_dmar(mch); } + return 0; } diff --git a/include/hw/boards.h b/include/hw/boards.h index 605a970..dfb6718 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -123,6 +123,7 @@ struct MachineState { bool mem_merge; bool usb; char *firmware; + bool iommu; ram_addr_t ram_size; ram_addr_t maxram_size; diff --git a/include/hw/pci-host/q35.h b/include/hw/pci-host/q35.h index d9ee978..025d6e6 100644 --- a/include/hw/pci-host/q35.h +++ b/include/hw/pci-host/q35.h @@ -33,6 +33,7 @@ #include "hw/acpi/acpi.h" #include "hw/acpi/ich9.h" #include "hw/pci-host/pam.h" +#include "hw/i386/intel_iommu.h" #define TYPE_Q35_HOST_DEVICE "q35-pcihost" #define Q35_HOST_DEVICE(obj) \ @@ -60,6 +61,7 @@ typedef struct MCHPCIState { uint64_t pci_hole64_size; PcGuestInfo *guest_info; uint32_t short_root_bus; + IntelIOMMUState *iommu; } MCHPCIState; typedef struct Q35PCIHost { diff --git a/qemu-options.hx b/qemu-options.hx index 96516c1..7406a17 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -35,7 +35,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " kernel_irqchip=on|off controls accelerated irqchip support\n" " kvm_shadow_mem=size of KVM shadow MMU\n" " dump-guest-core=on|off include guest memory in a core dump (default=on)\n" - " mem-merge=on|off controls memory merge support (default: on)\n", + " mem-merge=on|off controls memory merge support (default: on)\n" + " iommu=on|off controls emulated Intel IOMMU (VT-d) support (default=off)\n", QEMU_ARCH_ALL) STEXI @item -machine [type=]@var{name}[,prop=@var{value}[,...]] @@ -58,6 +59,8 @@ Include guest memory in a core dump. The default is on. Enables or disables memory merge support. This feature, when supported by the host, de-duplicates identical memory pages among VMs instances (enabled by default). +@item iommu=on|off +Enables or disables emulated Intel IOMMU (VT-d) support. The default is off. @end table ETEXI diff --git a/vl.c b/vl.c index a8029d5..2ab1643 100644 --- a/vl.c +++ b/vl.c @@ -388,6 +388,10 @@ static QemuOptsList qemu_machine_opts = { .name = PC_MACHINE_MAX_RAM_BELOW_4G, .type = QEMU_OPT_SIZE, .help = "maximum ram below the 4G boundary (32bit boundary)", + },{ + .name = "iommu", + .type = QEMU_OPT_BOOL, + .help = "Set on/off to enable/disable Intel IOMMU (VT-d)", }, { /* End of list */ } }, -- 1.9.1