yeahuh / rpms / qemu-kvm

Forked from rpms/qemu-kvm 2 years ago
Clone
76daa3
From ddc12e82e90ec51a5e3323058e52da090fae81cc Mon Sep 17 00:00:00 2001
76daa3
From: Peter Xu <peterx@redhat.com>
76daa3
Date: Mon, 24 Apr 2017 02:52:51 +0200
76daa3
Subject: [PATCH 12/23] intel_iommu: provide its own replay() callback
76daa3
76daa3
RH-Author: Peter Xu <peterx@redhat.com>
76daa3
Message-id: <1493002373-13010-8-git-send-email-peterx@redhat.com>
76daa3
Patchwork-id: 74855
76daa3
O-Subject: [RHEL7.4 qemu-kvm-rhev PATCH v2 7/9] intel_iommu: provide its own replay() callback
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
The default replay() don't work for VT-d since vt-d will have a huge
76daa3
default memory region which covers address range 0-(2^64-1). This will
76daa3
normally consumes a lot of time (which looks like a dead loop).
76daa3
76daa3
The solution is simple - we don't walk over all the regions. Instead, we
76daa3
jump over the regions when we found that the page directories are empty.
76daa3
It'll greatly reduce the time to walk the whole region.
76daa3
76daa3
To achieve this, we provided a page walk helper to do that, invoking
76daa3
corresponding hook function when we found an page we are interested in.
76daa3
vtd_page_walk_level() is the core logic for the page walking. It's
76daa3
interface is designed to suite further use case, e.g., to invalidate a
76daa3
range of addresses.
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-8-git-send-email-peterx@redhat.com>
76daa3
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
76daa3
(cherry picked from commit f06a696dc958dd80f7eaf5be66fdefac77741ee0)
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 | 182 ++++++++++++++++++++++++++++++++++++++++++++++++--
76daa3
 hw/i386/trace-events  |   7 ++
76daa3
 include/exec/memory.h |   2 +
76daa3
 3 files changed, 186 insertions(+), 5 deletions(-)
76daa3
76daa3
diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
76daa3
index 2412df4..7af4e22 100644
76daa3
--- a/hw/i386/intel_iommu.c
76daa3
+++ b/hw/i386/intel_iommu.c
76daa3
@@ -595,6 +595,22 @@ static inline uint32_t vtd_get_agaw_from_context_entry(VTDContextEntry *ce)
76daa3
     return 30 + (ce->hi & VTD_CONTEXT_ENTRY_AW) * 9;
76daa3
 }
76daa3
 
76daa3
+static inline uint64_t vtd_iova_limit(VTDContextEntry *ce)
76daa3
+{
76daa3
+    uint32_t ce_agaw = vtd_get_agaw_from_context_entry(ce);
76daa3
+    return 1ULL << MIN(ce_agaw, VTD_MGAW);
76daa3
+}
76daa3
+
76daa3
+/* Return true if IOVA passes range check, otherwise false. */
76daa3
+static inline bool vtd_iova_range_check(uint64_t iova, VTDContextEntry *ce)
76daa3
+{
76daa3
+    /*
76daa3
+     * Check if @iova is above 2^X-1, where X is the minimum of MGAW
76daa3
+     * in CAP_REG and AW in context-entry.
76daa3
+     */
76daa3
+    return !(iova & ~(vtd_iova_limit(ce) - 1));
76daa3
+}
76daa3
+
76daa3
 static const uint64_t vtd_paging_entry_rsvd_field[] = {
76daa3
     [0] = ~0ULL,
76daa3
     /* For not large page */
76daa3
@@ -630,13 +646,9 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,
76daa3
     uint32_t level = vtd_get_level_from_context_entry(ce);
76daa3
     uint32_t offset;
76daa3
     uint64_t slpte;
76daa3
-    uint32_t ce_agaw = vtd_get_agaw_from_context_entry(ce);
76daa3
     uint64_t access_right_check;
76daa3
 
76daa3
-    /* Check if @iova is above 2^X-1, where X is the minimum of MGAW
76daa3
-     * in CAP_REG and AW in context-entry.
76daa3
-     */
76daa3
-    if (iova & ~((1ULL << MIN(ce_agaw, VTD_MGAW)) - 1)) {
76daa3
+    if (!vtd_iova_range_check(iova, ce)) {
76daa3
         VTD_DPRINTF(GENERAL, "error: iova 0x%"PRIx64 " exceeds limits", iova);
76daa3
         return -VTD_FR_ADDR_BEYOND_MGAW;
76daa3
     }
76daa3
@@ -684,6 +696,134 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,
76daa3
     }
76daa3
 }
76daa3
 
76daa3
+typedef int (*vtd_page_walk_hook)(IOMMUTLBEntry *entry, void *private);
76daa3
+
76daa3
+/**
76daa3
+ * vtd_page_walk_level - walk over specific level for IOVA range
76daa3
+ *
76daa3
+ * @addr: base GPA addr to start the walk
76daa3
+ * @start: IOVA range start address
76daa3
+ * @end: IOVA range end address (start <= addr < end)
76daa3
+ * @hook_fn: hook func to be called when detected page
76daa3
+ * @private: private data to be passed into hook func
76daa3
+ * @read: whether parent level has read permission
76daa3
+ * @write: whether parent level has write permission
76daa3
+ * @notify_unmap: whether we should notify invalid entries
76daa3
+ */
76daa3
+static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,
76daa3
+                               uint64_t end, vtd_page_walk_hook hook_fn,
76daa3
+                               void *private, uint32_t level,
76daa3
+                               bool read, bool write, bool notify_unmap)
76daa3
+{
76daa3
+    bool read_cur, write_cur, entry_valid;
76daa3
+    uint32_t offset;
76daa3
+    uint64_t slpte;
76daa3
+    uint64_t subpage_size, subpage_mask;
76daa3
+    IOMMUTLBEntry entry;
76daa3
+    uint64_t iova = start;
76daa3
+    uint64_t iova_next;
76daa3
+    int ret = 0;
76daa3
+
76daa3
+    trace_vtd_page_walk_level(addr, level, start, end);
76daa3
+
76daa3
+    subpage_size = 1ULL << vtd_slpt_level_shift(level);
76daa3
+    subpage_mask = vtd_slpt_level_page_mask(level);
76daa3
+
76daa3
+    while (iova < end) {
76daa3
+        iova_next = (iova & subpage_mask) + subpage_size;
76daa3
+
76daa3
+        offset = vtd_iova_level_offset(iova, level);
76daa3
+        slpte = vtd_get_slpte(addr, offset);
76daa3
+
76daa3
+        if (slpte == (uint64_t)-1) {
76daa3
+            trace_vtd_page_walk_skip_read(iova, iova_next);
76daa3
+            goto next;
76daa3
+        }
76daa3
+
76daa3
+        if (vtd_slpte_nonzero_rsvd(slpte, level)) {
76daa3
+            trace_vtd_page_walk_skip_reserve(iova, iova_next);
76daa3
+            goto next;
76daa3
+        }
76daa3
+
76daa3
+        /* Permissions are stacked with parents' */
76daa3
+        read_cur = read && (slpte & VTD_SL_R);
76daa3
+        write_cur = write && (slpte & VTD_SL_W);
76daa3
+
76daa3
+        /*
76daa3
+         * As long as we have either read/write permission, this is a
76daa3
+         * valid entry. The rule works for both page entries and page
76daa3
+         * table entries.
76daa3
+         */
76daa3
+        entry_valid = read_cur | write_cur;
76daa3
+
76daa3
+        if (vtd_is_last_slpte(slpte, level)) {
76daa3
+            entry.target_as = &address_space_memory;
76daa3
+            entry.iova = iova & subpage_mask;
76daa3
+            /* NOTE: this is only meaningful if entry_valid == true */
76daa3
+            entry.translated_addr = vtd_get_slpte_addr(slpte);
76daa3
+            entry.addr_mask = ~subpage_mask;
76daa3
+            entry.perm = IOMMU_ACCESS_FLAG(read_cur, write_cur);
76daa3
+            if (!entry_valid && !notify_unmap) {
76daa3
+                trace_vtd_page_walk_skip_perm(iova, iova_next);
76daa3
+                goto next;
76daa3
+            }
76daa3
+            trace_vtd_page_walk_one(level, entry.iova, entry.translated_addr,
76daa3
+                                    entry.addr_mask, entry.perm);
76daa3
+            if (hook_fn) {
76daa3
+                ret = hook_fn(&entry, private);
76daa3
+                if (ret < 0) {
76daa3
+                    return ret;
76daa3
+                }
76daa3
+            }
76daa3
+        } else {
76daa3
+            if (!entry_valid) {
76daa3
+                trace_vtd_page_walk_skip_perm(iova, iova_next);
76daa3
+                goto next;
76daa3
+            }
76daa3
+            ret = vtd_page_walk_level(vtd_get_slpte_addr(slpte), iova,
76daa3
+                                      MIN(iova_next, end), hook_fn, private,
76daa3
+                                      level - 1, read_cur, write_cur,
76daa3
+                                      notify_unmap);
76daa3
+            if (ret < 0) {
76daa3
+                return ret;
76daa3
+            }
76daa3
+        }
76daa3
+
76daa3
+next:
76daa3
+        iova = iova_next;
76daa3
+    }
76daa3
+
76daa3
+    return 0;
76daa3
+}
76daa3
+
76daa3
+/**
76daa3
+ * vtd_page_walk - walk specific IOVA range, and call the hook
76daa3
+ *
76daa3
+ * @ce: context entry to walk upon
76daa3
+ * @start: IOVA address to start the walk
76daa3
+ * @end: IOVA range end address (start <= addr < end)
76daa3
+ * @hook_fn: the hook that to be called for each detected area
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
+{
76daa3
+    dma_addr_t addr = vtd_get_slpt_base_from_context(ce);
76daa3
+    uint32_t level = vtd_get_level_from_context_entry(ce);
76daa3
+
76daa3
+    if (!vtd_iova_range_check(start, ce)) {
76daa3
+        return -VTD_FR_ADDR_BEYOND_MGAW;
76daa3
+    }
76daa3
+
76daa3
+    if (!vtd_iova_range_check(end, ce)) {
76daa3
+        /* Fix end so that it reaches the maximum */
76daa3
+        end = vtd_iova_limit(ce);
76daa3
+    }
76daa3
+
76daa3
+    return vtd_page_walk_level(addr, start, end, hook_fn, private,
76daa3
+                               level, true, true, false);
76daa3
+}
76daa3
+
76daa3
 /* Map a device to its corresponding domain (context-entry) */
76daa3
 static int vtd_dev_to_context_entry(IntelIOMMUState *s, uint8_t bus_num,
76daa3
                                     uint8_t devfn, VTDContextEntry *ce)
76daa3
@@ -2402,6 +2542,37 @@ VTDAddressSpace *vtd_find_add_as(IntelIOMMUState *s, PCIBus *bus, int devfn)
76daa3
     return vtd_dev_as;
76daa3
 }
76daa3
 
76daa3
+static int vtd_replay_hook(IOMMUTLBEntry *entry, void *private)
76daa3
+{
76daa3
+    memory_region_notify_one((IOMMUNotifier *)private, entry);
76daa3
+    return 0;
76daa3
+}
76daa3
+
76daa3
+static void vtd_iommu_replay(MemoryRegion *mr, IOMMUNotifier *n)
76daa3
+{
76daa3
+    VTDAddressSpace *vtd_as = container_of(mr, VTDAddressSpace, iommu);
76daa3
+    IntelIOMMUState *s = vtd_as->iommu_state;
76daa3
+    uint8_t bus_n = pci_bus_num(vtd_as->bus);
76daa3
+    VTDContextEntry ce;
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
+    } else {
76daa3
+        trace_vtd_replay_ce_invalid(bus_n, PCI_SLOT(vtd_as->devfn),
76daa3
+                                    PCI_FUNC(vtd_as->devfn));
76daa3
+    }
76daa3
+
76daa3
+    return;
76daa3
+}
76daa3
+
76daa3
 /* Do the initialization. It will also be called when reset, so pay
76daa3
  * attention when adding new initialization stuff.
76daa3
  */
76daa3
@@ -2416,6 +2587,7 @@ static void vtd_init(IntelIOMMUState *s)
76daa3
 
76daa3
     s->iommu_ops.translate = vtd_iommu_translate;
76daa3
     s->iommu_ops.notify_flag_changed = vtd_iommu_notify_flag_changed;
76daa3
+    s->iommu_ops.replay = vtd_iommu_replay;
76daa3
     s->root = 0;
76daa3
     s->root_extended = false;
76daa3
     s->dmar_enabled = false;
76daa3
diff --git a/hw/i386/trace-events b/hw/i386/trace-events
76daa3
index baed874..f725bca 100644
76daa3
--- a/hw/i386/trace-events
76daa3
+++ b/hw/i386/trace-events
76daa3
@@ -30,6 +30,13 @@ vtd_iotlb_cc_hit(uint8_t bus, uint8_t devfn, uint64_t high, uint64_t low, uint32
76daa3
 vtd_iotlb_cc_update(uint8_t bus, uint8_t devfn, uint64_t high, uint64_t low, uint32_t gen1, uint32_t gen2) "IOTLB context update bus 0x%"PRIx8" devfn 0x%"PRIx8" high 0x%"PRIx64" low 0x%"PRIx64" gen %"PRIu32" -> gen %"PRIu32
76daa3
 vtd_iotlb_reset(const char *reason) "IOTLB reset (reason: %s)"
76daa3
 vtd_fault_disabled(void) "Fault processing disabled for context entry"
76daa3
+vtd_replay_ce_valid(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t domain, uint64_t hi, uint64_t lo) "replay valid context device %02"PRIx8":%02"PRIx8".%02"PRIx8" domain 0x%"PRIx16" hi 0x%"PRIx64" lo 0x%"PRIx64
76daa3
+vtd_replay_ce_invalid(uint8_t bus, uint8_t dev, uint8_t fn) "replay invalid context device %02"PRIx8":%02"PRIx8".%02"PRIx8
76daa3
+vtd_page_walk_level(uint64_t addr, uint32_t level, uint64_t start, uint64_t end) "walk (base=0x%"PRIx64", level=%"PRIu32") iova range 0x%"PRIx64" - 0x%"PRIx64
76daa3
+vtd_page_walk_one(uint32_t level, uint64_t iova, uint64_t gpa, uint64_t mask, int perm) "detected page level 0x%"PRIx32" iova 0x%"PRIx64" -> gpa 0x%"PRIx64" mask 0x%"PRIx64" perm %d"
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
 
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/exec/memory.h b/include/exec/memory.h
76daa3
index c0280b7..c4fc94d 100644
76daa3
--- a/include/exec/memory.h
76daa3
+++ b/include/exec/memory.h
76daa3
@@ -55,6 +55,8 @@ typedef enum {
76daa3
     IOMMU_RW   = 3,
76daa3
 } IOMMUAccessFlags;
76daa3
 
76daa3
+#define IOMMU_ACCESS_FLAG(r, w) (((r) ? IOMMU_RO : 0) | ((w) ? IOMMU_WO : 0))
76daa3
+
76daa3
 struct IOMMUTLBEntry {
76daa3
     AddressSpace    *target_as;
76daa3
     hwaddr           iova;
76daa3
-- 
76daa3
1.8.3.1
76daa3