76daa3
From 00c6524796e9fcbcf21804f24f84b835fd667498 Mon Sep 17 00:00:00 2001
76daa3
From: Peter Xu <peterx@redhat.com>
76daa3
Date: Mon, 24 Apr 2017 02:52:52 +0200
76daa3
Subject: [PATCH 13/23] intel_iommu: allow dynamic switch of IOMMU region
76daa3
76daa3
RH-Author: Peter Xu <peterx@redhat.com>
76daa3
Message-id: <1493002373-13010-9-git-send-email-peterx@redhat.com>
76daa3
Patchwork-id: 74856
76daa3
O-Subject: [RHEL7.4 qemu-kvm-rhev PATCH v2 8/9] intel_iommu: allow dynamic switch of IOMMU region
76daa3
Bugzilla: 1335808
76daa3
RH-Acked-by: Marcel Apfelbaum <marcel@redhat.com>
76daa3
RH-Acked-by: Michael S. Tsirkin <mst@redhat.com>
76daa3
RH-Acked-by: Xiao Wang <jasowang@redhat.com>
76daa3
76daa3
This is preparation work to finally enabled dynamic switching ON/OFF for
76daa3
VT-d protection. The old VT-d codes is using static IOMMU address space,
76daa3
and that won't satisfy vfio-pci device listeners.
76daa3
76daa3
Let me explain.
76daa3
76daa3
vfio-pci devices depend on the memory region listener and IOMMU replay
76daa3
mechanism to make sure the device mapping is coherent with the guest
76daa3
even if there are domain switches. And there are two kinds of domain
76daa3
switches:
76daa3
76daa3
  (1) switch from domain A -> B
76daa3
  (2) switch from domain A -> no domain (e.g., turn DMAR off)
76daa3
76daa3
Case (1) is handled by the context entry invalidation handling by the
76daa3
VT-d replay logic. What the replay function should do here is to replay
76daa3
the existing page mappings in domain B.
76daa3
76daa3
However for case (2), we don't want to replay any domain mappings - we
76daa3
just need the default GPA->HPA mappings (the address_space_memory
76daa3
mapping). And this patch helps on case (2) to build up the mapping
76daa3
automatically by leveraging the vfio-pci memory listeners.
76daa3
76daa3
Another important thing that this patch does is to seperate
76daa3
IR (Interrupt Remapping) from DMAR (DMA Remapping). IR region should not
76daa3
depend on the DMAR region (like before this patch). It should be a
76daa3
standalone region, and it should be able to be activated without
76daa3
DMAR (which is a common behavior of Linux kernel - by default it enables
76daa3
IR while disabled DMAR).
76daa3
76daa3
Reviewed-by: Jason Wang <jasowang@redhat.com>
76daa3
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
76daa3
Reviewed-by: \"Michael S. Tsirkin\" <mst@redhat.com>
76daa3
Signed-off-by: Peter Xu <peterx@redhat.com>
76daa3
Message-Id: <1491562755-23867-9-git-send-email-peterx@redhat.com>
76daa3
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
76daa3
(cherry picked from commit 558e0024a428a8f21605dc8aa026612ccc0f14cd)
76daa3
Signed-off-by: Peter Xu <peterx@redhat.com>
76daa3
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
76daa3
---
76daa3
 hw/i386/intel_iommu.c         | 81 ++++++++++++++++++++++++++++++++++++++++---
76daa3
 hw/i386/trace-events          |  2 +-
76daa3
 include/hw/i386/intel_iommu.h |  2 ++
76daa3
 3 files changed, 79 insertions(+), 6 deletions(-)
76daa3
76daa3
diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
76daa3
index 7af4e22..f7dec82 100644
76daa3
--- a/hw/i386/intel_iommu.c
76daa3
+++ b/hw/i386/intel_iommu.c
76daa3
@@ -1291,9 +1291,49 @@ static void vtd_handle_gcmd_sirtp(IntelIOMMUState *s)
76daa3
     vtd_set_clear_mask_long(s, DMAR_GSTS_REG, 0, VTD_GSTS_IRTPS);
76daa3
 }
76daa3
 
76daa3
+static void vtd_switch_address_space(VTDAddressSpace *as)
76daa3
+{
76daa3
+    assert(as);
76daa3
+
76daa3
+    trace_vtd_switch_address_space(pci_bus_num(as->bus),
76daa3
+                                   VTD_PCI_SLOT(as->devfn),
76daa3
+                                   VTD_PCI_FUNC(as->devfn),
76daa3
+                                   as->iommu_state->dmar_enabled);
76daa3
+
76daa3
+    /* Turn off first then on the other */
76daa3
+    if (as->iommu_state->dmar_enabled) {
76daa3
+        memory_region_set_enabled(&as->sys_alias, false);
76daa3
+        memory_region_set_enabled(&as->iommu, true);
76daa3
+    } else {
76daa3
+        memory_region_set_enabled(&as->iommu, false);
76daa3
+        memory_region_set_enabled(&as->sys_alias, true);
76daa3
+    }
76daa3
+}
76daa3
+
76daa3
+static void vtd_switch_address_space_all(IntelIOMMUState *s)
76daa3
+{
76daa3
+    GHashTableIter iter;
76daa3
+    VTDBus *vtd_bus;
76daa3
+    int i;
76daa3
+
76daa3
+    g_hash_table_iter_init(&iter, s->vtd_as_by_busptr);
76daa3
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&vtd_bus)) {
76daa3
+        for (i = 0; i < X86_IOMMU_PCI_DEVFN_MAX; i++) {
76daa3
+            if (!vtd_bus->dev_as[i]) {
76daa3
+                continue;
76daa3
+            }
76daa3
+            vtd_switch_address_space(vtd_bus->dev_as[i]);
76daa3
+        }
76daa3
+    }
76daa3
+}
76daa3
+
76daa3
 /* Handle Translation Enable/Disable */
76daa3
 static void vtd_handle_gcmd_te(IntelIOMMUState *s, bool en)
76daa3
 {
76daa3
+    if (s->dmar_enabled == en) {
76daa3
+        return;
76daa3
+    }
76daa3
+
76daa3
     VTD_DPRINTF(CSR, "Translation Enable %s", (en ? "on" : "off"));
76daa3
 
76daa3
     if (en) {
76daa3
@@ -1308,6 +1348,8 @@ static void vtd_handle_gcmd_te(IntelIOMMUState *s, bool en)
76daa3
         /* Ok - report back to driver */
76daa3
         vtd_set_clear_mask_long(s, DMAR_GSTS_REG, VTD_GSTS_TES, 0);
76daa3
     }
76daa3
+
76daa3
+    vtd_switch_address_space_all(s);
76daa3
 }
76daa3
 
76daa3
 /* Handle Interrupt Remap Enable/Disable */
76daa3
@@ -2529,15 +2571,44 @@ VTDAddressSpace *vtd_find_add_as(IntelIOMMUState *s, PCIBus *bus, int devfn)
76daa3
         vtd_dev_as->devfn = (uint8_t)devfn;
76daa3
         vtd_dev_as->iommu_state = s;
76daa3
         vtd_dev_as->context_cache_entry.context_cache_gen = 0;
76daa3
+
76daa3
+        /*
76daa3
+         * Memory region relationships looks like (Address range shows
76daa3
+         * only lower 32 bits to make it short in length...):
76daa3
+         *
76daa3
+         * |-----------------+-------------------+----------|
76daa3
+         * | Name            | Address range     | Priority |
76daa3
+         * |-----------------+-------------------+----------+
76daa3
+         * | vtd_root        | 00000000-ffffffff |        0 |
76daa3
+         * |  intel_iommu    | 00000000-ffffffff |        1 |
76daa3
+         * |  vtd_sys_alias  | 00000000-ffffffff |        1 |
76daa3
+         * |  intel_iommu_ir | fee00000-feefffff |       64 |
76daa3
+         * |-----------------+-------------------+----------|
76daa3
+         *
76daa3
+         * We enable/disable DMAR by switching enablement for
76daa3
+         * vtd_sys_alias and intel_iommu regions. IR region is always
76daa3
+         * enabled.
76daa3
+         */
76daa3
         memory_region_init_iommu(&vtd_dev_as->iommu, OBJECT(s),
76daa3
-                                 &s->iommu_ops, "intel_iommu", UINT64_MAX);
76daa3
+                                 &s->iommu_ops, "intel_iommu_dmar",
76daa3
+                                 UINT64_MAX);
76daa3
+        memory_region_init_alias(&vtd_dev_as->sys_alias, OBJECT(s),
76daa3
+                                 "vtd_sys_alias", get_system_memory(),
76daa3
+                                 0, memory_region_size(get_system_memory()));
76daa3
         memory_region_init_io(&vtd_dev_as->iommu_ir, OBJECT(s),
76daa3
                               &vtd_mem_ir_ops, s, "intel_iommu_ir",
76daa3
                               VTD_INTERRUPT_ADDR_SIZE);
76daa3
-        memory_region_add_subregion(&vtd_dev_as->iommu, VTD_INTERRUPT_ADDR_FIRST,
76daa3
-                                    &vtd_dev_as->iommu_ir);
76daa3
-        address_space_init(&vtd_dev_as->as,
76daa3
-                           &vtd_dev_as->iommu, name);
76daa3
+        memory_region_init(&vtd_dev_as->root, OBJECT(s),
76daa3
+                           "vtd_root", UINT64_MAX);
76daa3
+        memory_region_add_subregion_overlap(&vtd_dev_as->root,
76daa3
+                                            VTD_INTERRUPT_ADDR_FIRST,
76daa3
+                                            &vtd_dev_as->iommu_ir, 64);
76daa3
+        address_space_init(&vtd_dev_as->as, &vtd_dev_as->root, name);
76daa3
+        memory_region_add_subregion_overlap(&vtd_dev_as->root, 0,
76daa3
+                                            &vtd_dev_as->sys_alias, 1);
76daa3
+        memory_region_add_subregion_overlap(&vtd_dev_as->root, 0,
76daa3
+                                            &vtd_dev_as->iommu, 1);
76daa3
+        vtd_switch_address_space(vtd_dev_as);
76daa3
     }
76daa3
     return vtd_dev_as;
76daa3
 }
76daa3
diff --git a/hw/i386/trace-events b/hw/i386/trace-events
76daa3
index f725bca..3c3a167 100644
76daa3
--- a/hw/i386/trace-events
76daa3
+++ b/hw/i386/trace-events
76daa3
@@ -4,7 +4,6 @@
76daa3
 x86_iommu_iec_notify(bool global, uint32_t index, uint32_t mask) "Notify IEC invalidation: global=%d index=%" PRIu32 " mask=%" PRIu32
76daa3
 
76daa3
 # hw/i386/intel_iommu.c
76daa3
-vtd_switch_address_space(uint8_t bus, uint8_t slot, uint8_t fn, bool on) "Device %02x:%02x.%x switching address space (iommu enabled=%d)"
76daa3
 vtd_inv_desc(const char *type, uint64_t hi, uint64_t lo) "invalidate desc type %s high 0x%"PRIx64" low 0x%"PRIx64
76daa3
 vtd_inv_desc_invalid(uint64_t hi, uint64_t lo) "invalid inv desc hi 0x%"PRIx64" lo 0x%"PRIx64
76daa3
 vtd_inv_desc_cc_domain(uint16_t domain) "context invalidate domain 0x%"PRIx16
76daa3
@@ -37,6 +36,7 @@ vtd_page_walk_one(uint32_t level, uint64_t iova, uint64_t gpa, uint64_t mask, in
76daa3
 vtd_page_walk_skip_read(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to unable to read"
76daa3
 vtd_page_walk_skip_perm(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to perm empty"
76daa3
 vtd_page_walk_skip_reserve(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to rsrv set"
76daa3
+vtd_switch_address_space(uint8_t bus, uint8_t slot, uint8_t fn, bool on) "Device %02x:%02x.%x switching address space (iommu enabled=%d)"
76daa3
 
76daa3
 # hw/i386/amd_iommu.c
76daa3
 amdvi_evntlog_fail(uint64_t addr, uint32_t head) "error: fail to write at addr 0x%"PRIx64" +  offset 0x%"PRIx32
76daa3
diff --git a/include/hw/i386/intel_iommu.h b/include/hw/i386/intel_iommu.h
76daa3
index fe645aa..8f212a1 100644
76daa3
--- a/include/hw/i386/intel_iommu.h
76daa3
+++ b/include/hw/i386/intel_iommu.h
76daa3
@@ -83,6 +83,8 @@ struct VTDAddressSpace {
76daa3
     uint8_t devfn;
76daa3
     AddressSpace as;
76daa3
     MemoryRegion iommu;
76daa3
+    MemoryRegion root;
76daa3
+    MemoryRegion sys_alias;
76daa3
     MemoryRegion iommu_ir;      /* Interrupt region: 0xfeeXXXXX */
76daa3
     IntelIOMMUState *iommu_state;
76daa3
     VTDContextCacheEntry context_cache_entry;
76daa3
-- 
76daa3
1.8.3.1
76daa3