yeahuh / rpms / qemu-kvm

Forked from rpms/qemu-kvm 2 years ago
Clone
76daa3
From e229f18722e7b141ca06b3323114989e9a7355b4 Mon Sep 17 00:00:00 2001
76daa3
From: Peter Xu <peterx@redhat.com>
76daa3
Date: Mon, 24 Apr 2017 02:52:53 +0200
76daa3
Subject: [PATCH 14/23] intel_iommu: enable remote IOTLB
76daa3
76daa3
RH-Author: Peter Xu <peterx@redhat.com>
76daa3
Message-id: <1493002373-13010-10-git-send-email-peterx@redhat.com>
76daa3
Patchwork-id: 74857
76daa3
O-Subject: [RHEL7.4 qemu-kvm-rhev PATCH v2 9/9] intel_iommu: enable remote IOTLB
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 patch is based on Aviv Ben-David (<bd.aviv@gmail.com>)'s patch
76daa3
upstream:
76daa3
76daa3
  "IOMMU: enable intel_iommu map and unmap notifiers"
76daa3
  https://lists.gnu.org/archive/html/qemu-devel/2016-11/msg01453.html
76daa3
76daa3
However I removed/fixed some content, and added my own codes.
76daa3
76daa3
Instead of translate() every page for iotlb invalidations (which is
76daa3
slower), we walk the pages when needed and notify in a hook function.
76daa3
76daa3
This patch enables vfio devices for VT-d emulation.
76daa3
76daa3
And, since we already have vhost DMAR support via device-iotlb, a
76daa3
natural benefit that this patch brings is that vt-d enabled vhost can
76daa3
live even without ATS capability now. Though more tests are needed.
76daa3
76daa3
Signed-off-by: Aviv Ben-David <bdaviv@cs.technion.ac.il>
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-10-git-send-email-peterx@redhat.com>
76daa3
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
76daa3
(cherry picked from commit dd4d607e40dcd2cb7646b510504880a70939d91b)
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          | 191 ++++++++++++++++++++++++++++++++++++++---
76daa3
 hw/i386/intel_iommu_internal.h |   1 +
76daa3
 hw/i386/trace-events           |   1 +
76daa3
 include/hw/i386/intel_iommu.h  |   8 ++
76daa3
 4 files changed, 188 insertions(+), 13 deletions(-)
76daa3
76daa3
diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
76daa3
index f7dec82..02f047c 100644
76daa3
--- a/hw/i386/intel_iommu.c
76daa3
+++ b/hw/i386/intel_iommu.c
76daa3
@@ -806,7 +806,8 @@ next:
76daa3
  * @private: private data for the hook function
76daa3
  */
76daa3
 static int vtd_page_walk(VTDContextEntry *ce, uint64_t start, uint64_t end,
76daa3
-                         vtd_page_walk_hook hook_fn, void *private)
76daa3
+                         vtd_page_walk_hook hook_fn, void *private,
76daa3
+                         bool notify_unmap)
76daa3
 {
76daa3
     dma_addr_t addr = vtd_get_slpt_base_from_context(ce);
76daa3
     uint32_t level = vtd_get_level_from_context_entry(ce);
76daa3
@@ -821,7 +822,7 @@ static int vtd_page_walk(VTDContextEntry *ce, uint64_t start, uint64_t end,
76daa3
     }
76daa3
 
76daa3
     return vtd_page_walk_level(addr, start, end, hook_fn, private,
76daa3
-                               level, true, true, false);
76daa3
+                               level, true, true, notify_unmap);
76daa3
 }
76daa3
 
76daa3
 /* Map a device to its corresponding domain (context-entry) */
76daa3
@@ -1038,6 +1039,15 @@ static void vtd_interrupt_remap_table_setup(IntelIOMMUState *s)
76daa3
                 s->intr_root, s->intr_size);
76daa3
 }
76daa3
 
76daa3
+static void vtd_iommu_replay_all(IntelIOMMUState *s)
76daa3
+{
76daa3
+    IntelIOMMUNotifierNode *node;
76daa3
+
76daa3
+    QLIST_FOREACH(node, &s->notifiers_list, next) {
76daa3
+        memory_region_iommu_replay_all(&node->vtd_as->iommu);
76daa3
+    }
76daa3
+}
76daa3
+
76daa3
 static void vtd_context_global_invalidate(IntelIOMMUState *s)
76daa3
 {
76daa3
     trace_vtd_inv_desc_cc_global();
76daa3
@@ -1045,6 +1055,14 @@ static void vtd_context_global_invalidate(IntelIOMMUState *s)
76daa3
     if (s->context_cache_gen == VTD_CONTEXT_CACHE_GEN_MAX) {
76daa3
         vtd_reset_context_cache(s);
76daa3
     }
76daa3
+    /*
76daa3
+     * From VT-d spec 6.5.2.1, a global context entry invalidation
76daa3
+     * should be followed by a IOTLB global invalidation, so we should
76daa3
+     * be safe even without this. Hoewever, let's replay the region as
76daa3
+     * well to be safer, and go back here when we need finer tunes for
76daa3
+     * VT-d emulation codes.
76daa3
+     */
76daa3
+    vtd_iommu_replay_all(s);
76daa3
 }
76daa3
 
76daa3
 
76daa3
@@ -1111,6 +1129,16 @@ static void vtd_context_device_invalidate(IntelIOMMUState *s,
76daa3
                 trace_vtd_inv_desc_cc_device(bus_n, VTD_PCI_SLOT(devfn_it),
76daa3
                                              VTD_PCI_FUNC(devfn_it));
76daa3
                 vtd_as->context_cache_entry.context_cache_gen = 0;
76daa3
+                /*
76daa3
+                 * So a device is moving out of (or moving into) a
76daa3
+                 * domain, a replay() suites here to notify all the
76daa3
+                 * IOMMU_NOTIFIER_MAP registers about this change.
76daa3
+                 * This won't bring bad even if we have no such
76daa3
+                 * notifier registered - the IOMMU notification
76daa3
+                 * framework will skip MAP notifications if that
76daa3
+                 * happened.
76daa3
+                 */
76daa3
+                memory_region_iommu_replay_all(&vtd_as->iommu);
76daa3
             }
76daa3
         }
76daa3
     }
76daa3
@@ -1152,12 +1180,53 @@ static void vtd_iotlb_global_invalidate(IntelIOMMUState *s)
76daa3
 {
76daa3
     trace_vtd_iotlb_reset("global invalidation recved");
76daa3
     vtd_reset_iotlb(s);
76daa3
+    vtd_iommu_replay_all(s);
76daa3
 }
76daa3
 
76daa3
 static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)
76daa3
 {
76daa3
+    IntelIOMMUNotifierNode *node;
76daa3
+    VTDContextEntry ce;
76daa3
+    VTDAddressSpace *vtd_as;
76daa3
+
76daa3
     g_hash_table_foreach_remove(s->iotlb, vtd_hash_remove_by_domain,
76daa3
                                 &domain_id);
76daa3
+
76daa3
+    QLIST_FOREACH(node, &s->notifiers_list, next) {
76daa3
+        vtd_as = node->vtd_as;
76daa3
+        if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
76daa3
+                                      vtd_as->devfn, &ce) &&
76daa3
+            domain_id == VTD_CONTEXT_ENTRY_DID(ce.hi)) {
76daa3
+            memory_region_iommu_replay_all(&vtd_as->iommu);
76daa3
+        }
76daa3
+    }
76daa3
+}
76daa3
+
76daa3
+static int vtd_page_invalidate_notify_hook(IOMMUTLBEntry *entry,
76daa3
+                                           void *private)
76daa3
+{
76daa3
+    memory_region_notify_iommu((MemoryRegion *)private, *entry);
76daa3
+    return 0;
76daa3
+}
76daa3
+
76daa3
+static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
76daa3
+                                           uint16_t domain_id, hwaddr addr,
76daa3
+                                           uint8_t am)
76daa3
+{
76daa3
+    IntelIOMMUNotifierNode *node;
76daa3
+    VTDContextEntry ce;
76daa3
+    int ret;
76daa3
+
76daa3
+    QLIST_FOREACH(node, &(s->notifiers_list), next) {
76daa3
+        VTDAddressSpace *vtd_as = node->vtd_as;
76daa3
+        ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
76daa3
+                                       vtd_as->devfn, &ce);
76daa3
+        if (!ret && domain_id == VTD_CONTEXT_ENTRY_DID(ce.hi)) {
76daa3
+            vtd_page_walk(&ce, addr, addr + (1 << am) * VTD_PAGE_SIZE,
76daa3
+                          vtd_page_invalidate_notify_hook,
76daa3
+                          (void *)&vtd_as->iommu, true);
76daa3
+        }
76daa3
+    }
76daa3
 }
76daa3
 
76daa3
 static void vtd_iotlb_page_invalidate(IntelIOMMUState *s, uint16_t domain_id,
76daa3
@@ -1170,6 +1239,7 @@ static void vtd_iotlb_page_invalidate(IntelIOMMUState *s, uint16_t domain_id,
76daa3
     info.addr = addr;
76daa3
     info.mask = ~((1 << am) - 1);
76daa3
     g_hash_table_foreach_remove(s->iotlb, vtd_hash_remove_by_page, &info;;
76daa3
+    vtd_iotlb_page_invalidate_notify(s, domain_id, addr, am);
76daa3
 }
76daa3
 
76daa3
 /* Flush IOTLB
76daa3
@@ -2187,15 +2257,33 @@ static void vtd_iommu_notify_flag_changed(MemoryRegion *iommu,
76daa3
                                           IOMMUNotifierFlag new)
76daa3
 {
76daa3
     VTDAddressSpace *vtd_as = container_of(iommu, VTDAddressSpace, iommu);
76daa3
+    IntelIOMMUState *s = vtd_as->iommu_state;
76daa3
+    IntelIOMMUNotifierNode *node = NULL;
76daa3
+    IntelIOMMUNotifierNode *next_node = NULL;
76daa3
 
76daa3
-    if (new & IOMMU_NOTIFIER_MAP) {
76daa3
-        error_report("Device at bus %s addr %02x.%d requires iommu "
76daa3
-                     "notifier which is currently not supported by "
76daa3
-                     "intel-iommu emulation",
76daa3
-                     vtd_as->bus->qbus.name, PCI_SLOT(vtd_as->devfn),
76daa3
-                     PCI_FUNC(vtd_as->devfn));
76daa3
+    if (!s->caching_mode && new & IOMMU_NOTIFIER_MAP) {
76daa3
+        error_report("We need to set cache_mode=1 for intel-iommu to enable "
76daa3
+                     "device assignment with IOMMU protection.");
76daa3
         exit(1);
76daa3
     }
76daa3
+
76daa3
+    if (old == IOMMU_NOTIFIER_NONE) {
76daa3
+        node = g_malloc0(sizeof(*node));
76daa3
+        node->vtd_as = vtd_as;
76daa3
+        QLIST_INSERT_HEAD(&s->notifiers_list, node, next);
76daa3
+        return;
76daa3
+    }
76daa3
+
76daa3
+    /* update notifier node with new flags */
76daa3
+    QLIST_FOREACH_SAFE(node, &s->notifiers_list, next, next_node) {
76daa3
+        if (node->vtd_as == vtd_as) {
76daa3
+            if (new == IOMMU_NOTIFIER_NONE) {
76daa3
+                QLIST_REMOVE(node, next);
76daa3
+                g_free(node);
76daa3
+            }
76daa3
+            return;
76daa3
+        }
76daa3
+    }
76daa3
 }
76daa3
 
76daa3
 static const VMStateDescription vtd_vmstate = {
76daa3
@@ -2613,6 +2701,74 @@ VTDAddressSpace *vtd_find_add_as(IntelIOMMUState *s, PCIBus *bus, int devfn)
76daa3
     return vtd_dev_as;
76daa3
 }
76daa3
 
76daa3
+/* Unmap the whole range in the notifier's scope. */
76daa3
+static void vtd_address_space_unmap(VTDAddressSpace *as, IOMMUNotifier *n)
76daa3
+{
76daa3
+    IOMMUTLBEntry entry;
76daa3
+    hwaddr size;
76daa3
+    hwaddr start = n->start;
76daa3
+    hwaddr end = n->end;
76daa3
+
76daa3
+    /*
76daa3
+     * Note: all the codes in this function has a assumption that IOVA
76daa3
+     * bits are no more than VTD_MGAW bits (which is restricted by
76daa3
+     * VT-d spec), otherwise we need to consider overflow of 64 bits.
76daa3
+     */
76daa3
+
76daa3
+    if (end > VTD_ADDRESS_SIZE) {
76daa3
+        /*
76daa3
+         * Don't need to unmap regions that is bigger than the whole
76daa3
+         * VT-d supported address space size
76daa3
+         */
76daa3
+        end = VTD_ADDRESS_SIZE;
76daa3
+    }
76daa3
+
76daa3
+    assert(start <= end);
76daa3
+    size = end - start;
76daa3
+
76daa3
+    if (ctpop64(size) != 1) {
76daa3
+        /*
76daa3
+         * This size cannot format a correct mask. Let's enlarge it to
76daa3
+         * suite the minimum available mask.
76daa3
+         */
76daa3
+        int n = 64 - clz64(size);
76daa3
+        if (n > VTD_MGAW) {
76daa3
+            /* should not happen, but in case it happens, limit it */
76daa3
+            n = VTD_MGAW;
76daa3
+        }
76daa3
+        size = 1ULL << n;
76daa3
+    }
76daa3
+
76daa3
+    entry.target_as = &address_space_memory;
76daa3
+    /* Adjust iova for the size */
76daa3
+    entry.iova = n->start & ~(size - 1);
76daa3
+    /* This field is meaningless for unmap */
76daa3
+    entry.translated_addr = 0;
76daa3
+    entry.perm = IOMMU_NONE;
76daa3
+    entry.addr_mask = size - 1;
76daa3
+
76daa3
+    trace_vtd_as_unmap_whole(pci_bus_num(as->bus),
76daa3
+                             VTD_PCI_SLOT(as->devfn),
76daa3
+                             VTD_PCI_FUNC(as->devfn),
76daa3
+                             entry.iova, size);
76daa3
+
76daa3
+    memory_region_notify_one(n, &entry);
76daa3
+}
76daa3
+
76daa3
+static void vtd_address_space_unmap_all(IntelIOMMUState *s)
76daa3
+{
76daa3
+    IntelIOMMUNotifierNode *node;
76daa3
+    VTDAddressSpace *vtd_as;
76daa3
+    IOMMUNotifier *n;
76daa3
+
76daa3
+    QLIST_FOREACH(node, &s->notifiers_list, next) {
76daa3
+        vtd_as = node->vtd_as;
76daa3
+        IOMMU_NOTIFIER_FOREACH(n, &vtd_as->iommu) {
76daa3
+            vtd_address_space_unmap(vtd_as, n);
76daa3
+        }
76daa3
+    }
76daa3
+}
76daa3
+
76daa3
 static int vtd_replay_hook(IOMMUTLBEntry *entry, void *private)
76daa3
 {
76daa3
     memory_region_notify_one((IOMMUNotifier *)private, entry);
76daa3
@@ -2626,16 +2782,19 @@ static void vtd_iommu_replay(MemoryRegion *mr, IOMMUNotifier *n)
76daa3
     uint8_t bus_n = pci_bus_num(vtd_as->bus);
76daa3
     VTDContextEntry ce;
76daa3
 
76daa3
+    /*
76daa3
+     * The replay can be triggered by either a invalidation or a newly
76daa3
+     * created entry. No matter what, we release existing mappings
76daa3
+     * (it means flushing caches for UNMAP-only registers).
76daa3
+     */
76daa3
+    vtd_address_space_unmap(vtd_as, n);
76daa3
+
76daa3
     if (vtd_dev_to_context_entry(s, bus_n, vtd_as->devfn, &ce) == 0) {
76daa3
-        /*
76daa3
-         * Scanned a valid context entry, walk over the pages and
76daa3
-         * notify when needed.
76daa3
-         */
76daa3
         trace_vtd_replay_ce_valid(bus_n, PCI_SLOT(vtd_as->devfn),
76daa3
                                   PCI_FUNC(vtd_as->devfn),
76daa3
                                   VTD_CONTEXT_ENTRY_DID(ce.hi),
76daa3
                                   ce.hi, ce.lo);
76daa3
-        vtd_page_walk(&ce, 0, ~0ULL, vtd_replay_hook, (void *)n);
76daa3
+        vtd_page_walk(&ce, 0, ~0ULL, vtd_replay_hook, (void *)n, false);
76daa3
     } else {
76daa3
         trace_vtd_replay_ce_invalid(bus_n, PCI_SLOT(vtd_as->devfn),
76daa3
                                     PCI_FUNC(vtd_as->devfn));
76daa3
@@ -2754,6 +2913,11 @@ static void vtd_reset(DeviceState *dev)
76daa3
 
76daa3
     VTD_DPRINTF(GENERAL, "");
76daa3
     vtd_init(s);
76daa3
+
76daa3
+    /*
76daa3
+     * When device reset, throw away all mappings and external caches
76daa3
+     */
76daa3
+    vtd_address_space_unmap_all(s);
76daa3
 }
76daa3
 
76daa3
 static AddressSpace *vtd_host_dma_iommu(PCIBus *bus, void *opaque, int devfn)
76daa3
@@ -2817,6 +2981,7 @@ static void vtd_realize(DeviceState *dev, Error **errp)
76daa3
         return;
76daa3
     }
76daa3
 
76daa3
+    QLIST_INIT(&s->notifiers_list);
76daa3
     memset(s->vtd_as_by_bus_num, 0, sizeof(s->vtd_as_by_bus_num));
76daa3
     memory_region_init_io(&s->csrmem, OBJECT(s), &vtd_mem_ops, s,
76daa3
                           "intel_iommu", DMAR_REG_SIZE);
76daa3
diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h
76daa3
index 4104121..29d6707 100644
76daa3
--- a/hw/i386/intel_iommu_internal.h
76daa3
+++ b/hw/i386/intel_iommu_internal.h
76daa3
@@ -197,6 +197,7 @@
76daa3
 #define VTD_DOMAIN_ID_MASK          ((1UL << VTD_DOMAIN_ID_SHIFT) - 1)
76daa3
 #define VTD_CAP_ND                  (((VTD_DOMAIN_ID_SHIFT - 4) / 2) & 7ULL)
76daa3
 #define VTD_MGAW                    39  /* Maximum Guest Address Width */
76daa3
+#define VTD_ADDRESS_SIZE            (1ULL << VTD_MGAW)
76daa3
 #define VTD_CAP_MGAW                (((VTD_MGAW - 1) & 0x3fULL) << 16)
76daa3
 #define VTD_MAMV                    18ULL
76daa3
 #define VTD_CAP_MAMV                (VTD_MAMV << 48)
76daa3
diff --git a/hw/i386/trace-events b/hw/i386/trace-events
76daa3
index 3c3a167..04a6980 100644
76daa3
--- a/hw/i386/trace-events
76daa3
+++ b/hw/i386/trace-events
76daa3
@@ -37,6 +37,7 @@ vtd_page_walk_skip_read(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"P
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
+vtd_as_unmap_whole(uint8_t bus, uint8_t slot, uint8_t fn, uint64_t iova, uint64_t size) "Device %02x:%02x.%x start 0x%"PRIx64" size 0x%"PRIx64
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 8f212a1..3e51876 100644
76daa3
--- a/include/hw/i386/intel_iommu.h
76daa3
+++ b/include/hw/i386/intel_iommu.h
76daa3
@@ -63,6 +63,7 @@ typedef union VTD_IR_TableEntry VTD_IR_TableEntry;
76daa3
 typedef union VTD_IR_MSIAddress VTD_IR_MSIAddress;
76daa3
 typedef struct VTDIrq VTDIrq;
76daa3
 typedef struct VTD_MSIMessage VTD_MSIMessage;
76daa3
+typedef struct IntelIOMMUNotifierNode IntelIOMMUNotifierNode;
76daa3
 
76daa3
 /* Context-Entry */
76daa3
 struct VTDContextEntry {
76daa3
@@ -249,6 +250,11 @@ struct VTD_MSIMessage {
76daa3
 /* When IR is enabled, all MSI/MSI-X data bits should be zero */
76daa3
 #define VTD_IR_MSI_DATA          (0)
76daa3
 
76daa3
+struct IntelIOMMUNotifierNode {
76daa3
+    VTDAddressSpace *vtd_as;
76daa3
+    QLIST_ENTRY(IntelIOMMUNotifierNode) next;
76daa3
+};
76daa3
+
76daa3
 /* The iommu (DMAR) device state struct */
76daa3
 struct IntelIOMMUState {
76daa3
     X86IOMMUState x86_iommu;
76daa3
@@ -286,6 +292,8 @@ struct IntelIOMMUState {
76daa3
     MemoryRegionIOMMUOps iommu_ops;
76daa3
     GHashTable *vtd_as_by_busptr;   /* VTDBus objects indexed by PCIBus* reference */
76daa3
     VTDBus *vtd_as_by_bus_num[VTD_PCI_BUS_MAX]; /* VTDBus objects indexed by bus number */
76daa3
+    /* list of registered notifiers */
76daa3
+    QLIST_HEAD(, IntelIOMMUNotifierNode) notifiers_list;
76daa3
 
76daa3
     /* interrupt remapping */
76daa3
     bool intr_enabled;              /* Whether guest enabled IR */
76daa3
-- 
76daa3
1.8.3.1
76daa3