intel-iommu: nested vIOMMU, cleanups, bug fixes
v3 cover intel-iommu: nested vIOMMU, cleanups, bug fixes
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)
I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new vtd_sync_shadow_page_table() helper to do the page sync.
- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned device
Please review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]
Online repo:
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
1. unmap the whole address space
2. replay the whole address space, map existing pages
However during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Patch 10 is another big cleanup only can work after patch 9.
Tests:
- device assignments to L1, even L2 guests. With this series applied (and the kernel IOMMU patches: https://lkml.org/lkml/2018/4/18/5),
we can even nest vIOMMU now, e.g., we can specify vIOMMU in L2 guest with assigned devices and things will work. We can't before.
- vhost smoke test for regression.
Please review. Thanks,
Peter Xu (12):
intel-iommu: send PSI always even if across PDEs
intel-iommu: remove IntelIOMMUNotifierNode
intel-iommu: add iommu lock
intel-iommu: only do page walk for MAP notifiers
intel-iommu: introduce vtd_page_walk_info
intel-iommu: pass in address space when page walk
intel-iommu: trace domain id during page walk
util: implement simple iova tree
intel-iommu: maintain per-device iova ranges
intel-iommu: simplify page walk logic
intel-iommu: new vtd_sync_shadow_page_table_range
intel-iommu: new sync_shadow_page_table
include/hw/i386/intel_iommu.h | 19 +- include/qemu/iova-tree.h | 134 ++++++++++++ hw/i386/intel_iommu.c | 381 +++++++++++++++++++++++++--------- util/iova-tree.c | 114 ++++++++++ MAINTAINERS | 6 + hw/i386/trace-events | 5 +- util/Makefile.objs | 1 + 7 files changed, 556 insertions(+), 104 deletions(-) create mode 100644 include/qemu/iova-tree.h create mode 100644 util/iova-tree.c
Signature · 1 line
2.17.0
PATCH v3 1/12 intel-iommu: send PSI always even if across PDEs
During IOVA page table walking, there is a special case when the PSI covers one whole PDE (Page Directory Entry, which contains 512 Page Table Entries) or more. In the past, we skip that entry and we don't notify the IOMMU notifiers. This is not correct. We should send UNMAP notification to registered UNMAP notifiers in this case.
For UNMAP only notifiers, this might cause IOTLBs cached in the devices even if they were already invalid. For MAP/UNMAP notifiers like vfio-pci, this will cause stale page mappings.
This special case doesn't trigger often, but it is very easy to be triggered by nested device assignments, since in that case we'll possibly map the whole L2 guest RAM region into the device's IOVA address space (several GBs at least), which is far bigger than normal kernel driver usages of the device (tens of MBs normally).
Without this patch applied to L1 QEMU, nested device assignment to L2 guests will dump some errors like:
qemu-system-x86_64: VFIO_MAP_DMA: -17
qemu-system-x86_64: vfio_dma_map(0x557305420c30, 0xad000, 0x1000,
0x7f89a920d000) = -17 (File exists)
--- hw/i386/intel_iommu.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-)
hw/i386/intel_iommu.c +30 -12
@@ -722,6 +722,15 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,typedef int (*vtd_page_walk_hook )(IOMMUTLBEntry *entry,void *private); +static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, +vtd_page_walk_hook hook_fn,void *private) +{ +assert (hook_fn); +trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, + entry->addr_mask, entry->perm); +return hook_fn (entry, private); +} +/** * vtd_page_walk_level - walk over specific level for IOVA range *
@@ -781,28 +790,37 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,*/ entry_valid = read_cur | write_cur; + entry.target_as = &address_space_memory; + entry.iova = iova & subpage_mask; + entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur); + entry.addr_mask = ~subpage_mask; +if (vtd_is_last_slpte (slpte, level)) { - entry.target_as = &address_space_memory; - entry.iova = iova & subpage_mask;/* NOTE: this is only meaningful if entry_valid == true */ entry.translated_addr =vtd_get_slpte_addr (slpte, aw); - entry.addr_mask = ~subpage_mask; - entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur);if (!entry_valid && !notify_unmap) {trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } -trace_vtd_page_walk_one (level, entry.iova, entry.translated_addr, - entry.addr_mask, entry.perm); -if (hook_fn) { - ret =hook_fn (&entry, private); -if (ret <0 ) { -return ret; - } + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; } }else {if (!entry_valid) { -trace_vtd_page_walk_skip_perm (iova, iova_next); +if (notify_unmap) { +/* +* The whole entry is invalid; unmap it all. +* Translated address is meaningless, zero it. +*/ + entry.translated_addr =0x0 ; + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; + } + }else { +trace_vtd_page_walk_skip_perm (iova, iova_next); + } goto next; } ret = vtd_page_walk_level(vtd_get_slpte_addr(slpte, aw), iova,
Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 01/12] intel-iommu: send PSI always even if across PDEs
Quoted 16 lines · Peter Xu
During IOVA page table walking, there is a special case when the PSI covers one whole PDE (Page Directory Entry, which contains 512 Page Table Entries) or more. In the past, we skip that entry and we don't notify the IOMMU notifiers. This is not correct. We should send UNMAP notification to registered UNMAP notifiers in this case.
For UNMAP only notifiers, this might cause IOTLBs cached in the devices even if they were already invalid. For MAP/UNMAP notifiers like vfio-pci, this will cause stale page mappings.
This special case doesn't trigger often, but it is very easy to be triggered by nested device assignments, since in that case we'll possibly map the whole L2 guest RAM region into the device's IOVA address space (several GBs at least), which is far bigger than normal kernel driver usages of the device (tens of MBs normally).
Quoted 6 lines
Without this patch applied to L1 QEMU, nested device assignment to L2 guests will dump some errors like:
qemu-system-x86_64:
VFIO_MAP_DMA: -17
qemu-system-x86_64: vfio_dma_map(0x557305420c30,0xad000,0x1000,0x7f89a920d000) = -17 (File exists)
Could you add description of security implications if any and their severity?
Quoted 3 lines
Acked-by Jason Wang <[email protected]>[peterx: rewrite the commit message]Signed-off-by Peter Xu <[email protected]>
Cc stable here? For any other patches as well?
Quoted 76 lines
--- hw/i386/intel_iommu.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-)hw/i386/intel_iommu.c +30
-12@@ -722,6 +722,15 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,typedef int (*vtd_page_walk_hook )(IOMMUTLBEntry *entry,void *private); +static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, +vtd_page_walk_hook hook_fn,void *private) +{ +assert (hook_fn); +trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, + entry->addr_mask, entry->perm); +return hook_fn (entry, private); +} +/** * vtd_page_walk_level - walk over specific level for IOVA range * @@ -781,28 +790,37 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,*/ entry_valid = read_cur | write_cur; + entry.target_as = &address_space_memory; + entry.iova = iova & subpage_mask; + entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur); + entry.addr_mask = ~subpage_mask; +if (vtd_is_last_slpte (slpte, level)) { - entry.target_as = &address_space_memory; - entry.iova = iova & subpage_mask;/* NOTE: this is only meaningful if entry_valid == true */ entry.translated_addr =vtd_get_slpte_addr (slpte, aw); - entry.addr_mask = ~subpage_mask; - entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur);if (!entry_valid && !notify_unmap) {trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } -trace_vtd_page_walk_one (level, entry.iova, entry.translated_addr, - entry.addr_mask, entry.perm); -if (hook_fn) { - ret =hook_fn (&entry, private); -if (ret <0 ) { -return ret; - } + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; } }else {if (!entry_valid) { -trace_vtd_page_walk_skip_perm (iova, iova_next); +if (notify_unmap) { +/* +* The whole entry is invalid; unmap it all. +* Translated address is meaningless, zero it. +*/ + entry.translated_addr =0x0 ; + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; + } + }else { +trace_vtd_page_walk_skip_perm (iova, iova_next); + } goto next; } ret = vtd_page_walk_level(vtd_get_slpte_addr(slpte, aw), iova,Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 01/12] intel-iommu: send PSI always even if across PDEs
In reply to Peter XuHi Peter,
Quoted 35 lines · Peter Xu
During IOVA page table walking, there is a special case when the PSI covers one whole PDE (Page Directory Entry, which contains 512 Page Table Entries) or more. In the past, we skip that entry and we don't notify the IOMMU notifiers. This is not correct. We should send UNMAP notification to registered UNMAP notifiers in this case.
For UNMAP only notifiers, this might cause IOTLBs cached in the devices even if they were already invalid. For MAP/UNMAP notifiers like vfio-pci, this will cause stale page mappings.
This special case doesn't trigger often, but it is very easy to be triggered by nested device assignments, since in that case we'll possibly map the whole L2 guest RAM region into the device's IOVA address space (several GBs at least), which is far bigger than normal kernel driver usages of the device (tens of MBs normally).
Without this patch applied to L1 QEMU, nested device assignment to L2 guests will dump some errors like:
qemu-system-x86_64:
VFIO_MAP_DMA: -17
qemu-system-x86_64: vfio_dma_map(0x557305420c30,0xad000,0x1000,0x7f89a920d000) = -17 (File exists)Acked-by Jason Wang <[email protected]>[peterx: rewrite the commit message]Signed-off-by Peter Xu <[email protected]>--- hw/i386/intel_iommu.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-)hw/i386/intel_iommu.c +0
-0
hw/i386/intel_iommu.c static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write, · line 726
Quoted 5 lines
hw/i386/intel_iommu.c +2
-0@@ -722,6 +722,15 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,typedef int (*vtd_page_walk_hook )(IOMMUTLBEntry *entry,void *private); +static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, +vtd_page_walk_hook hook_fn,void *private)
Besides the name of this function (nit)
Thanks
Eric
Quoted 61 lines
hw/i386/intel_iommu.c +28
-12+{ +assert (hook_fn); +trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, + entry->addr_mask, entry->perm); +return hook_fn (entry, private); +} +/** * vtd_page_walk_level - walk over specific level for IOVA range * @@ -781,28 +790,37 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,*/ entry_valid = read_cur | write_cur; + entry.target_as = &address_space_memory; + entry.iova = iova & subpage_mask; + entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur); + entry.addr_mask = ~subpage_mask; +if (vtd_is_last_slpte (slpte, level)) { - entry.target_as = &address_space_memory; - entry.iova = iova & subpage_mask;/* NOTE: this is only meaningful if entry_valid == true */ entry.translated_addr =vtd_get_slpte_addr (slpte, aw); - entry.addr_mask = ~subpage_mask; - entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur);if (!entry_valid && !notify_unmap) {trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } -trace_vtd_page_walk_one (level, entry.iova, entry.translated_addr, - entry.addr_mask, entry.perm); -if (hook_fn) { - ret =hook_fn (&entry, private); -if (ret <0 ) { -return ret; - } + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; } }else {if (!entry_valid) { -trace_vtd_page_walk_skip_perm (iova, iova_next); +if (notify_unmap) { +/* +* The whole entry is invalid; unmap it all. +* Translated address is meaningless, zero it. +*/ + entry.translated_addr =0x0 ; + ret =vtd_page_walk_one (&entry, level, hook_fn, private); +if (ret <0 ) { +return ret; + } + }else { +trace_vtd_page_walk_skip_perm (iova, iova_next); + } goto next; } ret = vtd_page_walk_level(vtd_get_slpte_addr(slpte, aw), iova,
PATCH v3 2/12 intel-iommu: remove IntelIOMMUNotifierNode
In reply to Peter XuThat is not really necessary. Removing that node struct and put the list entry directly into VTDAddressSpace. It simplfies the code a lot.
--- include/hw/i386/intel_iommu.h | 9 ++------ hw/i386/intel_iommu.c | 41 ++++++++++------------------------- 2 files changed, 14 insertions(+), 36 deletions(-)
include/hw/i386/intel_iommu.h +2 -7
@@ -67,7 +67,6 @@ typedef union VTD_IR_TableEntry VTD_IR_TableEntry;typedef union VTD_IR_MSIAddress VTD_IR_MSIAddress ;typedef struct VTDIrq VTDIrq ;typedef struct VTD_MSIMessage VTD_MSIMessage ; -typedef struct IntelIOMMUNotifierNode IntelIOMMUNotifierNode ;/* Context-Entry */ struct VTDContextEntry {
@@ -93,6 +92,7 @@ struct VTDAddressSpace {MemoryRegion iommu_ir;/* Interrupt region: 0xfeeXXXXX */ IntelIOMMUState *iommu_state;VTDContextCacheEntry context_cache_entry; +QLIST_ENTRY (VTDAddressSpace) next; };struct VTDBus {
@@ -253,11 +253,6 @@ struct VTD_MSIMessage {/* When IR is enabled, all MSI/MSI-X data bits should be zero */ #define VTD_IR_MSI_DATA (0) -struct IntelIOMMUNotifierNode { -VTDAddressSpace *vtd_as; -QLIST_ENTRY (IntelIOMMUNotifierNode) next; -}; -/* The iommu (DMAR) device state struct */ struct IntelIOMMUState {X86IOMMUState x86_iommu;
@@ -295,7 +290,7 @@ struct IntelIOMMUState {GHashTable *vtd_as_by_busptr;/* VTDBus objects indexed by PCIBus* reference */ VTDBus *vtd_as_by_bus_num[VTD_PCI_BUS_MAX ];/* VTDBus objects indexed by bus number */ /* list of registered notifiers */ - QLIST_HEAD(, IntelIOMMUNotifierNode) notifiers_list; + QLIST_HEAD(, VTDAddressSpace) notifiers_list;/* interrupt remapping */ bool intr_enabled;/* Whether guest enabled IR */
hw/i386/intel_iommu.c +12 -29
90 lines not on this page · raw message
Signature · 1 line
2.17.0
PATCH v3 3/12 intel-iommu: add iommu lock
In reply to Peter XuAdd a per-iommu big lock to protect IOMMU status. Currently the only thing to be protected is the IOTLB/context cache, since that can be accessed even without BQL, e.g., in IO dataplane.
Note that we don't need to protect device page tables since that's fully controlled by the guest kernel. However there is still possibility that malicious drivers will program the device to not obey the rule. In that case QEMU can't really do anything useful, instead the guest itself will be responsible for all uncertainties.
--- include/hw/i386/intel_iommu.h | 6 +++++ hw/i386/intel_iommu.c | 43 +++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-)
include/hw/i386/intel_iommu.h +6 -0
@@ -300,6 +300,12 @@ struct IntelIOMMUState {OnOffAuto intr_eim;/* Toggle for EIM cabability */ bool buggy_eim;/* Force buggy EIM unless eim=off */ uint8_t aw_bits;/* Host/IOVA address width (in bits) */ + +/* +* Protects IOMMU states in general. Currently it protects the +* per-IOMMU IOTLB cache, and context entry cache in VTDAddressSpace. +*/ +QemuMutex iommu_lock; };/* Find the VTD Address space associated with the given bus pointer,
hw/i386/intel_iommu.c +38 -5
145 lines not on this page · raw message
Signature · 1 line
2.17.0
PATCH v3 4/12 intel-iommu: only do page walk for MAP notifiers
In reply to Peter XuFor UNMAP-only IOMMU notifiers, we don't really need to walk the page tables. Fasten that procedure by skipping the page table walk. That should boost performance for UNMAP-only notifiers like vhost.
--- include/hw/i386/intel_iommu.h | 2 ++ hw/i386/intel_iommu.c | 43 +++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-)
include/hw/i386/intel_iommu.h +2 -0
@@ -93,6 +93,8 @@ struct VTDAddressSpace {IntelIOMMUState *iommu_state;VTDContextCacheEntry context_cache_entry;QLIST_ENTRY (VTDAddressSpace) next; +/* Superset of notifier flags that this address space has */ +IOMMUNotifierFlag notifier_flags; };struct VTDBus {
hw/i386/intel_iommu.c +38 -5
72 lines not on this page · raw message
Signature · 1 line
2.17.0
PATCH v3 5/12 intel-iommu: introduce vtd_page_walk_info
In reply to Peter XuDuring the recursive page walking of IOVA page tables, some stack variables are constant variables and never changed during the whole page walking procedure. Isolate them into a struct so that we don't need to pass those contants down the stack every time and multiple times.
--- hw/i386/intel_iommu.c | 84 ++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 32 deletions(-)
hw/i386/intel_iommu.c +52 -32
@@ -747,9 +747,27 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,typedef int (*vtd_page_walk_hook )(IOMMUTLBEntry *entry,void *private); +/** +* Constant information used during page walking +* +* @hook_fn: hook func to be called when detected page +* @private: private data to be passed into hook func +* @notify_unmap: whether we should notify invalid entries +* @aw: maximum address width +*/ +typedef struct { +vtd_page_walk_hook hook_fn; +void *private; +bool notify_unmap; +uint8_t aw; +}vtd_page_walk_info ; +static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, -vtd_page_walk_hook hook_fn,void *private) +vtd_page_walk_info *info) { +vtd_page_walk_hook hook_fn = info->hook_fn; +void *private = info->private; +assert (hook_fn);trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, entry->addr_mask, entry->perm);
@@ -762,17 +780,13 @@ static int vtd_page_walk_one(IOMMUTLBEntry *entry, int level,* @addr: base GPA addr to start the walk * @start: IOVA range start address * @end: IOVA range end address (start <= addr < end) -* @hook_fn: hook func to be called when detected page -* @private: private data to be passed into hook func * @read: whether parent level has read permission * @write: whether parent level has write permission -* @notify_unmap: whether we should notify invalid entries -* @aw: maximum address width +* @info: constant information for the page walk */ static int vtd_page_walk_level (dma_addr_t addr,uint64_t start, -uint64_t end,vtd_page_walk_hook hook_fn, -void *private,uint32_t level,bool read, -bool write,bool notify_unmap,uint8_t aw) +uint64_t end,uint32_t level,bool read, +bool write,vtd_page_walk_info *info) {bool read_cur, write_cur, entry_valid;uint32_t offset;
@@ -822,24 +836,24 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,if (vtd_is_last_slpte (slpte, level)) {/* NOTE: this is only meaningful if entry_valid == true */ - entry.translated_addr =vtd_get_slpte_addr (slpte, aw); -if (!entry_valid && !notify_unmap) { + entry.translated_addr =vtd_get_slpte_addr (slpte, info->aw); +if (!entry_valid && !info->notify_unmap) {trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } - ret =vtd_page_walk_one (&entry, level, hook_fn, private); + ret =vtd_page_walk_one (&entry, level, info);if (ret <0 ) {return ret; } }else {if (!entry_valid) { -if (notify_unmap) { +if (info->notify_unmap) {/* * The whole entry is invalid; unmap it all. * Translated address is meaningless, zero it. */ entry.translated_addr =0x0 ; - ret =vtd_page_walk_one (&entry, level, hook_fn, private); + ret =vtd_page_walk_one (&entry, level, info);if (ret <0 ) {return ret; }
@@ -848,10 +862,9 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start, } goto next; } - ret =vtd_page_walk_level (vtd_get_slpte_addr (slpte, aw), iova, -MIN (iova_next, end), hook_fn, private, - level -1 , read_cur, write_cur, - notify_unmap, aw); + ret =vtd_page_walk_level (vtd_get_slpte_addr (slpte, info->aw), + iova,MIN (iova_next, end), level -1 , + read_cur, write_cur, info);if (ret <0 ) {return ret; }
@@ -870,28 +883,24 @@ next:* @ce: context entry to walk upon * @start: IOVA address to start the walk * @end: IOVA range end address (start <= addr < end) -* @hook_fn: the hook that to be called for each detected area -* @private: private data for the hook function -* @aw: maximum address width +* @info: page walking information struct */ static int vtd_page_walk (VTDContextEntry *ce,uint64_t start,uint64_t end, -vtd_page_walk_hook hook_fn,void *private, -bool notify_unmap,uint8_t aw) +vtd_page_walk_info *info) {dma_addr_t addr =vtd_ce_get_slpt_base (ce);uint32_t level =vtd_ce_get_level (ce); -if (!vtd_iova_range_check (start, ce, aw)) { +if (!vtd_iova_range_check (start, ce, info->aw)) {return -VTD_FR_ADDR_BEYOND_MGAW ; } -if (!vtd_iova_range_check (end, ce, aw)) { +if (!vtd_iova_range_check (end, ce, info->aw)) {/* Fix end so that it reaches the maximum */ - end =vtd_iova_limit (ce, aw); + end =vtd_iova_limit (ce, info->aw); } -return vtd_page_walk_level (addr, start, end, hook_fn, private, - level, true, true, notify_unmap, aw); +return vtd_page_walk_level (addr, start, end, level, true, true, info); }/* Map a device to its corresponding domain (context-entry) */
@@ -1446,13 +1455,18 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, vtd_as->devfn, &ce);if (!ret && domain_id ==VTD_CONTEXT_ENTRY_DID (ce.hi)) {if (vtd_as_notify_mappings (vtd_as)) { +vtd_page_walk_info info = { + .hook_fn = vtd_page_invalidate_notify_hook, + .private = (void *)&vtd_as->iommu, + .notify_unmap = true, + .aw = s->aw_bits, + }; +/* * For MAP-inclusive notifiers, we need to walk the * page table to sync the shadow page table. */ -vtd_page_walk (&ce, addr, addr + size, - vtd_page_invalidate_notify_hook, - (void *)&vtd_as->iommu, true, s->aw_bits); +vtd_page_walk (&ce, addr, addr + size, &info); }else {/* * For UNMAP-only notifiers, we don't need to walk the
@@ -2922,8 +2936,14 @@ static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) ce.hi, ce.lo);if (vtd_as_notify_mappings (vtd_as)) {/* This is required only for MAP typed notifiers */ -vtd_page_walk (&ce,0 , ~0ULL , vtd_replay_hook, (void *)n, false, - s->aw_bits); +vtd_page_walk_info info = { + .hook_fn = vtd_replay_hook, + .private = (void *)n, + .notify_unmap = false, + .aw = s->aw_bits, + }; + +vtd_page_walk (&ce,0 , ~0ULL , &info); } }else { trace_vtd_replay_ce_invalid(bus_n, PCI_SLOT(vtd_as->devfn),
Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 05/12] intel-iommu: introduce vtd_page_walk_info
Quoted 4 lines · Peter Xu
During the recursive page walking of IOVA page tables, some stack variables are constant variables and never changed during the whole page walking procedure. Isolate them into a struct so that we don't need to pass those contants down the stack every time and multiple times.
s/contants/constants
Quoted 11 lines
Signed-off-by Peter Xu <[email protected]>--- hw/i386/intel_iommu.c | 84 ++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 32 deletions(-)hw/i386/intel_iommu.c +0
-0
hw/i386/intel_iommu.c static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write, · line 751
Quoted 5 lines
hw/i386/intel_iommu.c +2
-0@@ -747,9 +747,27 @@ static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write,typedef int (*vtd_page_walk_hook )(IOMMUTLBEntry *entry,void *private); +/** +* Constant information used during page walking
page table walk?
hw/i386/intel_iommu.c static int vtd_iova_to_slpte(VTDContextEntry *ce, uint64_t iova, bool is_write, · line 753
Quoted 2 lines
hw/i386/intel_iommu.c +2
-0+* +* @hook_fn: hook func to be called when detected page
nit: when detected page? Not necessarily as it may be called on invalid entries. on leaf ptw entries?
Quoted 151 lines
hw/i386/intel_iommu.c +41
-32+* @private: private data to be passed into hook func +* @notify_unmap: whether we should notify invalid entries +* @aw: maximum address width +*/ +typedef struct { +vtd_page_walk_hook hook_fn; +void *private; +bool notify_unmap; +uint8_t aw; +}vtd_page_walk_info ; +static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, -vtd_page_walk_hook hook_fn,void *private) +vtd_page_walk_info *info) { +vtd_page_walk_hook hook_fn = info->hook_fn; +void *private = info->private; +assert (hook_fn);trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, entry->addr_mask, entry->perm);@@ -762,17 +780,13 @@ static int vtd_page_walk_one(IOMMUTLBEntry *entry, int level,* @addr: base GPA addr to start the walk * @start: IOVA range start address * @end: IOVA range end address (start <= addr < end) -* @hook_fn: hook func to be called when detected page -* @private: private data to be passed into hook func * @read: whether parent level has read permission * @write: whether parent level has write permission -* @notify_unmap: whether we should notify invalid entries -* @aw: maximum address width +* @info: constant information for the page walk */ static int vtd_page_walk_level (dma_addr_t addr,uint64_t start, -uint64_t end,vtd_page_walk_hook hook_fn, -void *private,uint32_t level,bool read, -bool write,bool notify_unmap,uint8_t aw) +uint64_t end,uint32_t level,bool read, +bool write,vtd_page_walk_info *info) {bool read_cur, write_cur, entry_valid;uint32_t offset;@@ -822,24 +836,24 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,if (vtd_is_last_slpte (slpte, level)) {/* NOTE: this is only meaningful if entry_valid == true */ - entry.translated_addr =vtd_get_slpte_addr (slpte, aw); -if (!entry_valid && !notify_unmap) { + entry.translated_addr =vtd_get_slpte_addr (slpte, info->aw); +if (!entry_valid && !info->notify_unmap) {trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } - ret =vtd_page_walk_one (&entry, level, hook_fn, private); + ret =vtd_page_walk_one (&entry, level, info);if (ret <0 ) {return ret; } }else {if (!entry_valid) { -if (notify_unmap) { +if (info->notify_unmap) {/* * The whole entry is invalid; unmap it all. * Translated address is meaningless, zero it. */ entry.translated_addr =0x0 ; - ret =vtd_page_walk_one (&entry, level, hook_fn, private); + ret =vtd_page_walk_one (&entry, level, info);if (ret <0 ) {return ret; }@@ -848,10 +862,9 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start, } goto next; } - ret =vtd_page_walk_level (vtd_get_slpte_addr (slpte, aw), iova, -MIN (iova_next, end), hook_fn, private, - level -1 , read_cur, write_cur, - notify_unmap, aw); + ret =vtd_page_walk_level (vtd_get_slpte_addr (slpte, info->aw), + iova,MIN (iova_next, end), level -1 , + read_cur, write_cur, info);if (ret <0 ) {return ret; }@@ -870,28 +883,24 @@ next:* @ce: context entry to walk upon * @start: IOVA address to start the walk * @end: IOVA range end address (start <= addr < end) -* @hook_fn: the hook that to be called for each detected area -* @private: private data for the hook function -* @aw: maximum address width +* @info: page walking information struct */ static int vtd_page_walk (VTDContextEntry *ce,uint64_t start,uint64_t end, -vtd_page_walk_hook hook_fn,void *private, -bool notify_unmap,uint8_t aw) +vtd_page_walk_info *info) {dma_addr_t addr =vtd_ce_get_slpt_base (ce);uint32_t level =vtd_ce_get_level (ce); -if (!vtd_iova_range_check (start, ce, aw)) { +if (!vtd_iova_range_check (start, ce, info->aw)) {return -VTD_FR_ADDR_BEYOND_MGAW ; } -if (!vtd_iova_range_check (end, ce, aw)) { +if (!vtd_iova_range_check (end, ce, info->aw)) {/* Fix end so that it reaches the maximum */ - end =vtd_iova_limit (ce, aw); + end =vtd_iova_limit (ce, info->aw); } -return vtd_page_walk_level (addr, start, end, hook_fn, private, - level, true, true, notify_unmap, aw); +return vtd_page_walk_level (addr, start, end, level, true, true, info); }/* Map a device to its corresponding domain (context-entry) */ @@ -1446,13 +1455,18 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, vtd_as->devfn, &ce);if (!ret && domain_id ==VTD_CONTEXT_ENTRY_DID (ce.hi)) {if (vtd_as_notify_mappings (vtd_as)) { +vtd_page_walk_info info = { + .hook_fn = vtd_page_invalidate_notify_hook, + .private = (void *)&vtd_as->iommu, + .notify_unmap = true, + .aw = s->aw_bits, + }; +/* * For MAP-inclusive notifiers, we need to walk the * page table to sync the shadow page table. */ -vtd_page_walk (&ce, addr, addr + size, - vtd_page_invalidate_notify_hook, - (void *)&vtd_as->iommu, true, s->aw_bits); +vtd_page_walk (&ce, addr, addr + size, &info); }else {/* * For UNMAP-only notifiers, we don't need to walk the @@ -2922,8 +2936,14 @@ static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) ce.hi, ce.lo);if (vtd_as_notify_mappings (vtd_as)) {/* This is required only for MAP typed notifiers */ -vtd_page_walk (&ce,0 , ~0ULL , vtd_replay_hook, (void *)n, false, - s->aw_bits); +vtd_page_walk_info info = {
hw/i386/intel_iommu.c static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) · line 2949
Quoted 10 lines
hw/i386/intel_iommu.c +7
-0+ .hook_fn = vtd_replay_hook, + .private = (void *)n, + .notify_unmap = false, + .aw = s->aw_bits, + }; + +vtd_page_walk (&ce,0 , ~0ULL , &info); } }else { trace_vtd_replay_ce_invalid(bus_n, PCI_SLOT(vtd_as->devfn),
Besides the nit
Thanks
Eric
PATCH v3 6/12 intel-iommu: pass in address space when page walk
In reply to Peter XuWe pass in the VTDAddressSpace too. It'll be used in the follow up patches.
--- hw/i386/intel_iommu.c | 4 ++++ 1 file changed, 4 insertions(+)
hw/i386/intel_iommu.c +4 -0
@@ -753,9 +753,11 @@ typedef int (*vtd_page_walk_hook)(IOMMUTLBEntry *entry, void *private);* @hook_fn: hook func to be called when detected page * @private: private data to be passed into hook func * @notify_unmap: whether we should notify invalid entries +* @as: VT-d address space of the device * @aw: maximum address width */ typedef struct { + VTDAddressSpace *as; vtd_page_walk_hook hook_fn; void *private; bool notify_unmap;
@@ -1460,6 +1462,7 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, .private = (void *)&vtd_as->iommu, .notify_unmap = true, .aw = s->aw_bits, + .as = vtd_as, };/*
@@ -2941,6 +2944,7 @@ static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) .private = (void *)n, .notify_unmap = false, .aw = s->aw_bits, + .as = vtd_as, };vtd_page_walk (&ce,0 , ~0ULL , &info);
Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 06/12] intel-iommu: pass in address space when page walk
Hi Peter,
Quoted 2 lines · Peter Xu
We pass in the VTDAddressSpace too. It'll be used in the follow up patches.
So you evetually preferred to keep .aw. I don't have a strong opinion but maybe a small preference to v2 version.
Nevertheless
Eric
Quoted 39 lines
Signed-off-by Peter Xu <[email protected]>--- hw/i386/intel_iommu.c | 4 ++++ 1 file changed, 4 insertions(+)hw/i386/intel_iommu.c +4
-0@@ -753,9 +753,11 @@ typedef int (*vtd_page_walk_hook)(IOMMUTLBEntry *entry, void *private);* @hook_fn: hook func to be called when detected page * @private: private data to be passed into hook func * @notify_unmap: whether we should notify invalid entries +* @as: VT-d address space of the device * @aw: maximum address width */ typedef struct { + VTDAddressSpace *as; vtd_page_walk_hook hook_fn; void *private; bool notify_unmap;@@ -1460,6 +1462,7 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, .private = (void *)&vtd_as->iommu, .notify_unmap = true, .aw = s->aw_bits, + .as = vtd_as, };/* @@ -2941,6 +2944,7 @@ static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) .private = (void *)n, .notify_unmap = false, .aw = s->aw_bits, + .as = vtd_as, };vtd_page_walk (&ce,0 , ~0ULL , &info);
PATCH v3 7/12 intel-iommu: trace domain id during page walk
In reply to Peter XuThis patch only modifies the trace points.
Previously we were tracing page walk levels. They are redundant since we have page mask (size) already. Now we trace something much more useful which is the domain ID of the page walking. That can be very useful when we trace more than one devices on the same system, so that we can know which map is for which domain.
--- hw/i386/intel_iommu.c | 16 ++++++++++------ hw/i386/trace-events | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-)
hw/i386/intel_iommu.c +10 -6
@@ -755,6 +755,7 @@ typedef int (*vtd_page_walk_hook)(IOMMUTLBEntry *entry, void *private);* @notify_unmap: whether we should notify invalid entries * @as: VT-d address space of the device * @aw: maximum address width +* @domain: domain ID of the page walk */ typedef struct { VTDAddressSpace *as;
@@ -762,17 +763,18 @@ typedef struct {void *private;bool notify_unmap;uint8_t aw; +uint16_t domain_id; } vtd_page_walk_info; -static int vtd_page_walk_one (IOMMUTLBEntry *entry,int level, -vtd_page_walk_info *info) +static int vtd_page_walk_one (IOMMUTLBEntry *entry,vtd_page_walk_info *info) {vtd_page_walk_hook hook_fn = info->hook_fn;void *private = info->private;assert (hook_fn); -trace_vtd_page_walk_one (level, entry->iova, entry->translated_addr, - entry->addr_mask, entry->perm); +trace_vtd_page_walk_one (info->domain_id, entry->iova, + entry->translated_addr, entry->addr_mask, + entry->perm);return hook_fn (entry, private); }
@@ -843,7 +845,7 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,trace_vtd_page_walk_skip_perm (iova, iova_next); goto next; } - ret =vtd_page_walk_one (&entry, level, info); + ret =vtd_page_walk_one (&entry, info);if (ret <0 ) {return ret; }
@@ -855,7 +857,7 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,* Translated address is meaningless, zero it. */ entry.translated_addr =0x0 ; - ret =vtd_page_walk_one (&entry, level, info); + ret =vtd_page_walk_one (&entry, info);if (ret <0 ) {return ret; }
@@ -1463,6 +1465,7 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, .notify_unmap = true, .aw = s->aw_bits, .as = vtd_as, + .domain_id = domain_id, };/*
@@ -2945,6 +2948,7 @@ static void vtd_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n) .notify_unmap = false, .aw = s->aw_bits, .as = vtd_as, + .domain_id = VTD_CONTEXT_ENTRY_DID(ce.hi), };vtd_page_walk (&ce,0 , ~0ULL , &info);
hw/i386/trace-events +1 -1
@@ -39,7 +39,7 @@ vtd_fault_disabled(void) "Fault processing disabled for context entry" 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 vtd_replay_ce_invalid(uint8_t bus, uint8_t dev, uint8_t fn) "replay invalid context device %02"PRIx8":%02"PRIx8".%02"PRIx8 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 -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" +vtd_page_walk_one(uint16_t domain, uint64_t iova, uint64_t gpa, uint64_t mask, int perm) "domain 0x%"PRIu16" iova 0x%"PRIx64" -> gpa 0x%"PRIx64" mask 0x%"PRIx64" perm %d" vtd_page_walk_skip_read(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to unable to read" vtd_page_walk_skip_perm(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to perm empty" vtd_page_walk_skip_reserve(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to rsrv set"
Signature · 1 line
2.17.0
PATCH v3 8/12 util: implement simple iova tree
In reply to Peter XuIntroduce a simplest iova tree implementation based on GTree.
--- include/qemu/iova-tree.h | 134 +++++++++++++++++++++++++++++++++++++++ util/iova-tree.c | 114 +++++++++++++++++++++++++++++++++ MAINTAINERS | 6 ++ util/Makefile.objs | 1 + 4 files changed, 255 insertions(+) create mode 100644 include/qemu/iova-tree.h create mode 100644 util/iova-tree.c
include/qemu/iova-tree.h new file +134 -0
@@ -0,0 +1,134 @@ +/* +* An very simplified iova tree implementation based on GTree. +* +* Copyright 2018 Red Hat, Inc. +* +* Authors: +* Peter Xu <[email protected]> +* +* This work is licensed under the terms of the GNU GPL, version 2 or later. +*/ +#ifndef IOVA_TREE_H +#define IOVA_TREE_H + +/* +* Currently the iova tree will only allow to keep ranges +* information, and no extra user data is allowed for each element. A +* benefit is that we can merge adjacent ranges internally within the +* tree. It can save a lot of memory when the ranges are splitted but +* mostly continuous. +* +* Note that current implementation does not provide any thread +* protections. Callers of the iova tree should be responsible +* for the thread safety issue. +*/ + +#include "qemu/osdep.h" +#include "exec/memory.h" +#include "exec/hwaddr.h" + +#define IOVA_OK (0) +#define IOVA_ERR_INVALID (-1)/* Invalid parameters */ +#define IOVA_ERR_OVERLAP (-2)/* IOVA range overlapped */ + +typedef struct IOVATree IOVATree ; +typedef struct DMAMap { +hwaddr iova; +hwaddr translated_addr; +hwaddr size;/* Inclusive */ +IOMMUAccessFlags perm; +} QEMU_PACKED DMAMap; +typedef gboolean (*iova_tree_iterator )(DMAMap *map); + +/** +* iova_tree_new: +* +* Create a new iova tree. +* +* Returns: the tree pointer when succeeded, or NULL if error. +*/ +IOVATree *iova_tree_new (void ); + +/** +* iova_tree_insert: +* +* @tree: the iova tree to insert +* @map: the mapping to insert +* +* Insert an iova range to the tree. If there is overlapped +* ranges, IOVA_ERR_OVERLAP will be returned. +* +* Return: 0 if succeeded, or <0 if error. +*/ +int iova_tree_insert (IOVATree *tree,DMAMap *map); + +/** +* iova_tree_remove: +* +* @tree: the iova tree to remove range from +* @map: the map range to remove +* +* Remove mappings from the tree that are covered by the map range +* provided. The range does not need to be exactly what has inserted, +* all the mappings that are included in the provided range will be +* removed from the tree. Here map->translated_addr is meaningless. +* +* Return: 0 if succeeded, or <0 if error. +*/ +int iova_tree_remove (IOVATree *tree,DMAMap *map); + +/** +* iova_tree_find: +* +* @tree: the iova tree to search from +* @map: the mapping to search +* +* Search for a mapping in the iova tree that overlaps with the +* mapping range specified. Only the first found mapping will be +* returned. +* +* Return: DMAMap pointer if found, or NULL if not found. Note that +* the returned DMAMap pointer is maintained internally. User should +* only read the content but never modify or free the content. Also, +* user is responsible to make sure the pointer is valid (say, no +* concurrent deletion in progress). +*/ +DMAMap *iova_tree_find (IOVATree *tree,DMAMap *map); + +/** +* iova_tree_find_address: +* +* @tree: the iova tree to search from +* @iova: the iova address to find +* +* Similar to iova_tree_find(), but it tries to find mapping with +* range iova=iova & size=0. +* +* Return: same as iova_tree_find(). +*/ +DMAMap *iova_tree_find_address (IOVATree *tree,hwaddr iova); + +/** +* iova_tree_foreach: +* +* @tree: the iova tree to iterate on +* @iterator: the interator for the mappings, return true to stop +* +* Iterate over the iova tree. +* +* Return: 1 if found any overlap, 0 if not, <0 if error. +*/ +void iova_tree_foreach (IOVATree *tree,iova_tree_iterator iterator); + +/** +* iova_tree_destroy: +* +* @tree: the iova tree to destroy +* +* Destroy an existing iova tree. +* +* Return: None. +*/ +void iova_tree_destroy (IOVATree *tree); + +#endif
util/iova-tree.c new file +114 -0
114 lines not on this page · raw message
MAINTAINERS +6 -0
@@ -1783,6 +1783,12 @@ F: include/sysemu/replay.h F: docs/replay.txt F: stubs/replay.c +IOVA Tree +M: Peter Xu <[email protected]> +S: Maintained +F: include/qemu/iova-tree.h +F: util/iova-tree.c + Usermode Emulation ------------------ Overall
util/Makefile.objs +1 -0
@@ -47,4 +47,5 @@ util-obj-y += qht.o util-obj-y += range.o util-obj-y += stats64.o util-obj-y += systemd.o +util-obj-y += iova-tree.o util-obj-$(CONFIG_LINUX) += vfio-helpers.o
Signature · 1 line
2.17.0
PATCH v3 9/12 intel-iommu: maintain per-device iova ranges
In reply to Peter XuFor each VTDAddressSpace, now we maintain what IOVA ranges we have mapped and what we have not. With that information, now we only send MAP or UNMAP when necessary. Say, we don't send MAP notifies if we know we have already mapped the range, meanwhile we don't send UNMAP notifies if we know we never mapped the range at all.
--- include/hw/i386/intel_iommu.h | 2 ++ hw/i386/intel_iommu.c | 67 +++++++++++++++++++++++++++++++++++ hw/i386/trace-events | 2 ++ 3 files changed, 71 insertions(+)
include/hw/i386/intel_iommu.h +2 -0
@@ -27,6 +27,7 @@#include "hw/i386/ioapic.h" #include "hw/pci/msi.h" #include "hw/sysbus.h" +#include "qemu/iova-tree.h" #define TYPE_INTEL_IOMMU_DEVICE "intel-iommu"#define INTEL_IOMMU_DEVICE (obj) \
@@ -95,6 +96,7 @@ struct VTDAddressSpace {QLIST_ENTRY (VTDAddressSpace) next;/* Superset of notifier flags that this address space has */ IOMMUNotifierFlag notifier_flags; +IOVATree *iova_tree;/* Traces mapped IOVA ranges */ };struct VTDBus {
hw/i386/intel_iommu.c +67 -0
95 lines not on this page · raw message
hw/i386/trace-events +2 -0
@@ -40,6 +40,8 @@ vtd_replay_ce_valid(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t domain, uint6 vtd_replay_ce_invalid(uint8_t bus, uint8_t dev, uint8_t fn) "replay invalid context device %02"PRIx8":%02"PRIx8".%02"PRIx8 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 vtd_page_walk_one(uint16_t domain, uint64_t iova, uint64_t gpa, uint64_t mask, int perm) "domain 0x%"PRIu16" iova 0x%"PRIx64" -> gpa 0x%"PRIx64" mask 0x%"PRIx64" perm %d" +vtd_page_walk_one_skip_map(uint64_t iova, uint64_t mask, uint64_t translated) "iova 0x%"PRIx64" mask 0x%"PRIx64" translated 0x%"PRIx64 +vtd_page_walk_one_skip_unmap(uint64_t iova, uint64_t mask) "iova 0x%"PRIx64" mask 0x%"PRIx64 vtd_page_walk_skip_read(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to unable to read" vtd_page_walk_skip_perm(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to perm empty" vtd_page_walk_skip_reserve(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to rsrv set"
Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 09/12] intel-iommu: maintain per-device iova ranges
On Thu, May 17, 2018 at 04:59:24PM +0800, Peter Xu wrote:
[...]
Quoted 6 lines
hw/i386/intel_iommu.c +6
-0+/* Update local IOVA mapped ranges */ +if (entry->perm) { +if (mapped) { +/* If it's exactly the same translation, skip */ +if (!memcmp (mapped, &target,sizeof (target))) { + trace_vtd_page_walk_one_skip_map(entry->iova, entry->addr_mask,
hw/i386/intel_iommu.c typedef struct { · line 799
Quoted 10 lines
hw/i386/intel_iommu.c +10
-0+ entry->translated_addr); +return 0 ; + }else { +/* +* Translation changed. This should not happen with +* "intel_iommu=on,strict", but it can happen when +* delayed flushing is used in guest IOMMU driver +* (when without "strict") when page A is reused +* before its previous unmap (the unmap can still be +* queued in the delayed flushing queue). Now we do
This comment is wrong. We can ignore above comments for now since as I explained in the other thread Linux IOVA deferred flushing won't free IOVA range until the unmap is flushed. But still, below comment is valid.
Regards,
Quoted 39 lines
hw/i386/intel_iommu.c +36
-0+* our best to remap. Note that there will be a small +* window that we don't have map at all. But that's +* the best effort we can do, and logically +* well-behaved guests should not really using this +* DMA region yet so we should be very safe. +*/ +IOMMUAccessFlags cache_perm = entry->perm; +int ret; + +/* Emulate an UNMAP */ + entry->perm =IOMMU_NONE ; +trace_vtd_page_walk_one (info->domain_id, + entry->iova, + entry->translated_addr, + entry->addr_mask, + entry->perm); + ret =hook_fn (entry, private); +if (ret) { +return ret; + } +/* Drop any existing mapping */ +iova_tree_remove (as->iova_tree, &target); +/* Recover the correct permission */ + entry->perm = cache_perm; + } + } +iova_tree_insert (as->iova_tree, &target); + }else { +if (!mapped) { +/* Skip since we didn't map this range at all */ +trace_vtd_page_walk_one_skip_unmap (entry->iova, entry->addr_mask); +return 0 ; + } +iova_tree_remove (as->iova_tree, &target); + } +trace_vtd_page_walk_one (info->domain_id, entry->iova, entry->translated_addr, entry->addr_mask, entry->perm);
Signature · 1 line
Peter Xu
PATCH v3 10/12 intel-iommu: simplify page walk logic
In reply to Peter XuLet's move the notify_unmap check into the new vtd_page_walk_one() function so that we can greatly simplify the vtd_page_walk_level() logic.
No functional change at all.
--- hw/i386/intel_iommu.c | 66 ++++++++++++++++++++----------------------- hw/i386/trace-events | 1 - 2 files changed, 30 insertions(+), 37 deletions(-)
hw/i386/intel_iommu.c +30 -36
@@ -779,6 +779,11 @@ static int vtd_page_walk_one(IOMMUTLBEntry *entry, vtd_page_walk_info *info) };DMAMap *mapped =iova_tree_find (as->iova_tree, &target); +if (entry->perm ==IOMMU_NONE && !info->notify_unmap) { +trace_vtd_page_walk_one_skip_unmap (entry->iova, entry->addr_mask); +return 0 ; + } +assert (hook_fn);/* Update local IOVA mapped ranges */
@@ -894,45 +899,34 @@ static int vtd_page_walk_level(dma_addr_t addr, uint64_t start,*/ entry_valid = read_cur | write_cur; - entry.target_as = &address_space_memory; - entry.iova = iova & subpage_mask; - entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur); - entry.addr_mask = ~subpage_mask; - -if (vtd_is_last_slpte (slpte, level)) { -/* NOTE: this is only meaningful if entry_valid == true */ - entry.translated_addr =vtd_get_slpte_addr (slpte, info->aw); -if (!entry_valid && !info->notify_unmap) { -trace_vtd_page_walk_skip_perm (iova, iova_next); - goto next; - } - ret =vtd_page_walk_one (&entry, info); -if (ret <0 ) { -return ret; - } - }else { -if (!entry_valid) { -if (info->notify_unmap) { -/* -* The whole entry is invalid; unmap it all. -* Translated address is meaningless, zero it. -*/ - entry.translated_addr =0x0 ; - ret =vtd_page_walk_one (&entry, info); -if (ret <0 ) { -return ret; - } - }else { -trace_vtd_page_walk_skip_perm (iova, iova_next); - } - goto next; - } +if (!vtd_is_last_slpte (slpte, level) && entry_valid) { +/* +* This is a valid PDE (or even bigger than PDE). We need +* to walk one further level. +*/ ret =vtd_page_walk_level (vtd_get_slpte_addr (slpte, info->aw), iova,MIN (iova_next, end), level -1 , read_cur, write_cur, info); -if (ret <0 ) { -return ret; - } + }else { +/* +* This means we are either: +* +* (1) the real page entry (either 4K page, or huge page) +* (2) the whole range is invalid +* +* In either case, we send an IOTLB notification down. +*/ + entry.target_as = &address_space_memory; + entry.iova = iova & subpage_mask; + entry.perm =IOMMU_ACCESS_FLAG (read_cur, write_cur); + entry.addr_mask = ~subpage_mask; +/* NOTE: this is only meaningful if entry_valid == true */ + entry.translated_addr =vtd_get_slpte_addr (slpte, info->aw); + ret =vtd_page_walk_one (&entry, info); + } + +if (ret <0 ) { +return ret; } next:
hw/i386/trace-events +0 -1
@@ -43,7 +43,6 @@ vtd_page_walk_one(uint16_t domain, uint64_t iova, uint64_t gpa, uint64_t mask, i vtd_page_walk_one_skip_map(uint64_t iova, uint64_t mask, uint64_t translated) "iova 0x%"PRIx64" mask 0x%"PRIx64" translated 0x%"PRIx64 vtd_page_walk_one_skip_unmap(uint64_t iova, uint64_t mask) "iova 0x%"PRIx64" mask 0x%"PRIx64 vtd_page_walk_skip_read(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to unable to read" -vtd_page_walk_skip_perm(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to perm empty" vtd_page_walk_skip_reserve(uint64_t iova, uint64_t next) "Page walk skip iova 0x%"PRIx64" - 0x%"PRIx64" due to rsrv set" 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)" 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
Signature · 1 line
2.17.0
PATCH v3 11/12 intel-iommu: new vtd_sync_shadow_page_table_range
In reply to Peter XuI pick part of the page walk implementation out into this single function. It was only used now for PSIs, but actually it can be used not only for invalidations but for any place that we want to synchronize the shadow page table. No functional change at all.
Here I passed in context entry explicit to avoid fetching it again. However I enhanced the helper to even be able to fetch it on its own, since in follow up patches we might call without context entries.
--- hw/i386/intel_iommu.c | 65 ++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 17 deletions(-)
hw/i386/intel_iommu.c +48 -17
@@ -1018,6 +1018,53 @@ static int vtd_dev_to_context_entry(IntelIOMMUState *s, uint8_t bus_num,return 0 ; } +static int vtd_sync_shadow_page_hook (IOMMUTLBEntry *entry, +void *private) +{ +memory_region_notify_iommu ((IOMMUMemoryRegion *)private, *entry); +return 0 ; +} + +/* If context entry is NULL, we'll try to fetch it on our own. */ +static int vtd_sync_shadow_page_table_range (VTDAddressSpace *vtd_as, +VTDContextEntry *ce, +hwaddr addr,hwaddr size) +{ +IntelIOMMUState *s = vtd_as->iommu_state; +vtd_page_walk_info info = { + .hook_fn = vtd_sync_shadow_page_hook, + .private = (void *)&vtd_as->iommu, + .notify_unmap = true, + .aw = s->aw_bits, + .as = vtd_as, + }; +VTDContextEntry ce_cache; +int ret; + +if (ce) { +/* If the caller provided context entry, use it */ + ce_cache = *ce; + }else { +/* If the caller didn't provide ce, try to fetch */ + ret =vtd_dev_to_context_entry (s,pci_bus_num (vtd_as->bus), + vtd_as->devfn, &ce_cache); +if (ret) { +/* +* This should not really happen, but in case it happens, +* we just skip the sync for this time. After all we even +* don't have the root table pointer! +*/ +trace_vtd_err ("Detected invalid context entry when " +"trying to sync shadow page table" ); +return 0 ; + } + } + + info.domain_id =VTD_CONTEXT_ENTRY_DID (ce_cache.hi); + +return vtd_page_walk (&ce_cache, addr, addr + size, &info); +} +/* * Fetch translation type for specific device. Returns <0 if error * happens, otherwise return the shifted type to check against
@@ -1493,13 +1540,6 @@ static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id) } } -static int vtd_page_invalidate_notify_hook (IOMMUTLBEntry *entry, -void *private) -{ -memory_region_notify_iommu ((IOMMUMemoryRegion *)private, *entry); -return 0 ; -} -static void vtd_iotlb_page_invalidate_notify (IntelIOMMUState *s,uint16_t domain_id,hwaddr addr,uint8_t am)
@@ -1514,20 +1554,11 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, vtd_as->devfn, &ce);if (!ret && domain_id ==VTD_CONTEXT_ENTRY_DID (ce.hi)) {if (vtd_as_notify_mappings (vtd_as)) { -vtd_page_walk_info info = { - .hook_fn = vtd_page_invalidate_notify_hook, - .private = (void *)&vtd_as->iommu, - .notify_unmap = true, - .aw = s->aw_bits, - .as = vtd_as, - .domain_id = domain_id, - }; -/* * For MAP-inclusive notifiers, we need to walk the * page table to sync the shadow page table. */ -vtd_page_walk (&ce, addr, addr + size, &info); +vtd_sync_shadow_page_table_range (vtd_as, &ce, addr, size); }else {/* * For UNMAP-only notifiers, we don't need to walk the
Signature · 1 line
2.17.0
PATCH v3 12/12 intel-iommu: new sync_shadow_page_table
In reply to Peter XuFirstly, introduce the sync_shadow_page_table() helper to resync the whole shadow page table of an IOMMU address space. Meanwhile, when we receive domain invalidation or similar requests (for example, context entry invalidations, global invalidations, ...), we should not really run the replay logic, instead we can now use the new sync shadow page table API to resync the whole shadow page table.
There will be two major differences:
1. We don't unmap-all before walking the page table, we just sync. The global unmap-all can create a very small window that the page table is invalid or incomplete
2. We only walk the page table once now (while replay can be triggered multiple times depending on how many notifiers there are)
--- hw/i386/intel_iommu.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
hw/i386/intel_iommu.c +9 -5
@@ -1065,6 +1065,11 @@ static int vtd_sync_shadow_page_table_range(VTDAddressSpace *vtd_as,return vtd_page_walk (&ce_cache, addr, addr + size, &info); } +static int vtd_sync_shadow_page_table (VTDAddressSpace *vtd_as) +{ +return vtd_sync_shadow_page_table_range (vtd_as,NULL ,0 ,UINT64_MAX ); +} +/* * Fetch translation type for specific device. Returns <0 if error * happens, otherwise return the shifted type to check against
@@ -1397,7 +1402,7 @@ static void vtd_iommu_replay_all(IntelIOMMUState *s)VTDAddressSpace *vtd_as; QLIST_FOREACH(vtd_as, &s->notifiers_list, next) { -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } }
@@ -1470,14 +1475,13 @@ static void vtd_context_device_invalidate(IntelIOMMUState *s,vtd_switch_address_space (vtd_as);/* * So a device is moving out of (or moving into) a -* domain, a replay() suites here to notify all the -* IOMMU_NOTIFIER_MAP registers about this change. +* domain, resync the shadow page table. * This won't bring bad even if we have no such * notifier registered - the IOMMU notification * framework will skip MAP notifications if that * happened. */ -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } } }
@@ -1535,7 +1539,7 @@ static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)if (!vtd_dev_to_context_entry (s,pci_bus_num (vtd_as->bus), vtd_as->devfn, &ce) && domain_id ==VTD_CONTEXT_ENTRY_DID (ce.hi)) { -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } } }
Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 12/12] intel-iommu: new sync_shadow_page_table
Quoted 7 lines · Peter Xu
Firstly, introduce the
sync_shadow_page_table()helper to resync the whole shadow page table of an IOMMU address space. Meanwhile, when we receive domain invalidation or similar requests (for example, context entry invalidations, global invalidations, ...), we should not really run the replay logic, instead we can now use the new sync shadow page table API to resync the whole shadow page table.
Quoted 5 lines
There will be two major differences:
1. We don't unmap-all before walking the page table, we just sync. The global unmap-all can create a very small window that the page table is invalid or incomplete
The implication is that with vfio, device might stop working without this change.
Quoted 61 lines
2. We only walk the page table once now (while replay can be triggered multiple times depending on how many notifiers there are)
Signed-off-by Peter Xu <[email protected]>--- hw/i386/intel_iommu.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)hw/i386/intel_iommu.c +9
-5@@ -1065,6 +1065,11 @@ static int vtd_sync_shadow_page_table_range(VTDAddressSpace *vtd_as,return vtd_page_walk (&ce_cache, addr, addr + size, &info); } +static int vtd_sync_shadow_page_table (VTDAddressSpace *vtd_as) +{ +return vtd_sync_shadow_page_table_range (vtd_as,NULL ,0 ,UINT64_MAX ); +} +/* * Fetch translation type for specific device. Returns <0 if error * happens, otherwise return the shifted type to check against @@ -1397,7 +1402,7 @@ static void vtd_iommu_replay_all(IntelIOMMUState *s)VTDAddressSpace *vtd_as; QLIST_FOREACH(vtd_as, &s->notifiers_list, next) { -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } }@@ -1470,14 +1475,13 @@ static void vtd_context_device_invalidate(IntelIOMMUState *s,vtd_switch_address_space (vtd_as);/* * So a device is moving out of (or moving into) a -* domain, a replay() suites here to notify all the -* IOMMU_NOTIFIER_MAP registers about this change. +* domain, resync the shadow page table. * This won't bring bad even if we have no such * notifier registered - the IOMMU notification * framework will skip MAP notifications if that * happened. */ -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } } }@@ -1535,7 +1539,7 @@ static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)if (!vtd_dev_to_context_entry (s,pci_bus_num (vtd_as->bus), vtd_as->devfn, &ce) && domain_id ==VTD_CONTEXT_ENTRY_DID (ce.hi)) { -memory_region_iommu_replay_all (&vtd_as->iommu); +vtd_sync_shadow_page_table (vtd_as); } } }Signature · 1 line
2.17.0
Re: [Qemu-devel] [PATCH v3 12/12] intel-iommu: new sync_shadow_page_table
Quoted 8 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu
Firstly, introduce the
sync_shadow_page_table()helper to resync the whole shadow page table of an IOMMU address space. Meanwhile, when we receive domain invalidation or similar requests (for example, context entry invalidations, global invalidations, ...), we should not really run the replay logic, instead we can now use the new sync shadow page table API to resync the whole shadow page table.
Quoted 8 lines
Quoted 5 lines
There will be two major differences:
1. We don't unmap-all before walking the page table, we just sync. The global unmap-all can create a very small window that the page table is invalid or incomplete
The implication is that with vfio, device might stop working without this change.
I guess it was working before. However patch 9 changed the page walking to be state-ful since we have IOVA tree now (it was not) so we might encounter the scp DMA error after patch 9. I still kept the patches 9-12 to be separate since logically they are isolated. However if you want maybe we can also squash 9-12 patches into one single (but a bit huge) patch.
Thanks,
Signature · 1 line
Peter Xu
Quoted 2 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)
Hi Peter,
Quoted 6 lines
I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
Quoted 8 lines
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.
I checked that the scp problem I had (i.e. scp from the host to the guest having virtual IOMMU and an assigned network device) was gone with this patch series. Cool!
Please feel free to move this tag if this is not the right place!
Thanks,
Jintack
Quoted 124 lines
- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned devicePlease review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]Online repo:
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
1. unmap the whole address space
2. replay the whole address space, map existing pagesHowever during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Patch 10 is another big cleanup only can work after patch 9.
Tests:
- device assignments to L1, even L2 guests. With this series applied (and the kernel IOMMU patches: https://lkml.org/lkml/2018/4/18/5),
we can even nest vIOMMU now, e.g., we can specify vIOMMU in L2 guest with assigned devices and things will work. We can't before.- vhost smoke test for regression.
Please review. Thanks,
Peter Xu (12):
intel-iommu: send PSI always even if across PDEs
intel-iommu: remove IntelIOMMUNotifierNode
intel-iommu: add iommu lock
intel-iommu: only do page walk for MAP notifiers
intel-iommu: introducevtd_page_walk_info
intel-iommu: pass in address space when page walk
intel-iommu: trace domain id during page walk
util: implement simple iova tree
intel-iommu: maintain per-device iova ranges
intel-iommu: simplify page walk logic
intel-iommu: newvtd_sync_shadow_page_table_range
intel-iommu: newsync_shadow_page_tableinclude/hw/i386/intel_iommu.h | 19 +- include/qemu/iova-tree.h | 134 ++++++++++++ hw/i386/intel_iommu.c | 381 +++++++++++++++++++++++++--------- util/iova-tree.c | 114 ++++++++++ MAINTAINERS | 6 + hw/i386/trace-events | 5 +- util/Makefile.objs | 1 + 7 files changed, 556 insertions(+), 104 deletions(-) create mode 100644 include/qemu/iova-tree.h create mode 100644 util/iova-tree.c--
2.17.0
Quoted 21 lines · Jintack Lim
Quoted 2 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)Hi Peter,
Quoted 6 lines
I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
Quoted 8 lines
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.
Quoted 6 lines
I checked that the scp problem I had (i.e. scp from the host to the guest having virtual IOMMU and an assigned network device) was gone with this patch series. Cool!
Please feel free to move this tag if this is not the right place!
Tested-by Jintack Lim <[email protected]>
Thanks for the quick feedback!
I'll temporarily consider putting it at the last patch of series if no one jumps out and tells me another more correct way. Also I'll possibly make bold to append your suggested-by too to further claim your contribution to this problem.
Regards,
Signature · 1 line
Peter Xu
On Fri, May 18, 2018 at 02:26:59PM +0800, Peter Xu wrote:
[...]
Quoted 3 lines
I'll temporarily consider putting it at the last patch of series if no one jumps out and tells me another more correct way. Also I'll possibly make bold to append your suggested-by too to further claim
^^^^^^^^^^^^
Sorry, I mean "Reported-by".
Quoted 1 line
your contribution to this problem.
Regards,
Signature · 1 line
Peter Xu
Quoted 52 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned devicePlease review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]Online repo:
Quoted 7 lines
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
security issue
Quoted 2 lines
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
security issue
Quoted 3 lines
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
optimization
Quoted 5 lines
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
Quoted 8 lines
1. unmap the whole address space
2. replay the whole address space, map existing pagesHowever during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
correctness but not a security issue
Quoted 17 lines
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Quoted 1 line
Patch 10 is another big cleanup only can work after patch 9.
So 1-2 are needed on stable. 1-9 would be nice to have there too, even though they are big and it looks risky.
Quoted 38 lines
Tests:
- device assignments to L1, even L2 guests. With this series applied (and the kernel IOMMU patches: https://lkml.org/lkml/2018/4/18/5),
we can even nest vIOMMU now, e.g., we can specify vIOMMU in L2 guest with assigned devices and things will work. We can't before.- vhost smoke test for regression.
Please review. Thanks,
Peter Xu (12):
intel-iommu: send PSI always even if across PDEs
intel-iommu: remove IntelIOMMUNotifierNode
intel-iommu: add iommu lock
intel-iommu: only do page walk for MAP notifiers
intel-iommu: introducevtd_page_walk_info
intel-iommu: pass in address space when page walk
intel-iommu: trace domain id during page walk
util: implement simple iova tree
intel-iommu: maintain per-device iova ranges
intel-iommu: simplify page walk logic
intel-iommu: newvtd_sync_shadow_page_table_range
intel-iommu: newsync_shadow_page_tableinclude/hw/i386/intel_iommu.h | 19 +- include/qemu/iova-tree.h | 134 ++++++++++++ hw/i386/intel_iommu.c | 381 +++++++++++++++++++++++++--------- util/iova-tree.c | 114 ++++++++++ MAINTAINERS | 6 + hw/i386/trace-events | 5 +- util/Makefile.objs | 1 + 7 files changed, 556 insertions(+), 104 deletions(-) create mode 100644 include/qemu/iova-tree.h create mode 100644 util/iova-tree.cSignature · 1 line
2.17.0
Quoted 107 lines · Michael S. Tsirkin
Quoted 52 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned devicePlease review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]Online repo:
Quoted 7 lines
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
security issue
Quoted 2 lines
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
security issue
Quoted 3 lines
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
optimization
Quoted 5 lines
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
Quoted 8 lines
1. unmap the whole address space
2. replay the whole address space, map existing pagesHowever during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
correctness but not a security issue
Quoted 17 lines
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Quoted 5 lines
Quoted 1 line
Patch 10 is another big cleanup only can work after patch 9.
So 1-2 are needed on stable. 1-9 would be nice to have there too, even though they are big and it looks risky.
Yes, although issue 4 is not a security issue, but it might cause DMA errors and unusability of devices to happen. I don't know very much on the details of how stable tree should treat patches like this, but considering that this whole series only touches VT-d code, and as you mentioned merely all the patches would be nice to have even for stable, I'll just CC stable for all the patches, refine messages and repost. Thanks,
Signature · 1 line
Peter Xu
Quoted 98 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned devicePlease review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]Online repo:
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
1. unmap the whole address space
2. replay the whole address space, map existing pagesHowever during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Quoted 1 line
Patch 10 is another big cleanup only can work after patch 9.
I think patch numbers are wrong somehow.
Given you also want to tweak one comment, could you please repost with this fix, and also in commit log for each patch
- Cc stable
- for security patches mention as much, if possible add data about the issue and its severity
Quoted 38 lines
Tests:
- device assignments to L1, even L2 guests. With this series applied (and the kernel IOMMU patches: https://lkml.org/lkml/2018/4/18/5),
we can even nest vIOMMU now, e.g., we can specify vIOMMU in L2 guest with assigned devices and things will work. We can't before.- vhost smoke test for regression.
Please review. Thanks,
Peter Xu (12):
intel-iommu: send PSI always even if across PDEs
intel-iommu: remove IntelIOMMUNotifierNode
intel-iommu: add iommu lock
intel-iommu: only do page walk for MAP notifiers
intel-iommu: introducevtd_page_walk_info
intel-iommu: pass in address space when page walk
intel-iommu: trace domain id during page walk
util: implement simple iova tree
intel-iommu: maintain per-device iova ranges
intel-iommu: simplify page walk logic
intel-iommu: newvtd_sync_shadow_page_table_range
intel-iommu: newsync_shadow_page_tableinclude/hw/i386/intel_iommu.h | 19 +- include/qemu/iova-tree.h | 134 ++++++++++++ hw/i386/intel_iommu.c | 381 +++++++++++++++++++++++++--------- util/iova-tree.c | 114 ++++++++++ MAINTAINERS | 6 + hw/i386/trace-events | 5 +- util/Makefile.objs | 1 + 7 files changed, 556 insertions(+), 104 deletions(-) create mode 100644 include/qemu/iova-tree.h create mode 100644 util/iova-tree.cSignature · 1 line
2.17.0
Quoted 99 lines · Michael S. Tsirkin
Quoted 98 lines · Peter Xu
(Hello, Jintack, Feel free to test this branch again against your scp
error case when you got free time)I rewrote some of the patches in V3. Major changes:
- Dropped mergable interval tree, instead introduced IOVA tree, which is even simpler.
- Fix the scp error issue that Jintack reported. Please see patches for detailed information. That's the major reason to rewrite a few of the patches. We use replay for domain flushes are possibly incorrect in the past. The thing is that IOMMU replay has an "definition" that "we should only send MAP when new page detected", while for shadow page syncing we actually need something else than that. So in this version I started to use a new
vtd_sync_shadow_page_table()helper to do the page sync.- Some other refines after the refactoring.
I'll add unit test for the IOVA tree after this series merged to make sure we won't switch to another new tree implementaion...
The element size in the new IOVA tree should be around sizeof(GTreeNode + IOMMUTLBEntry) ~= (5*8+4*8) = 72 bytes. So the worst case usage ratio would be 72/4K=2%, which still seems acceptable (it means 8G L2 guest will use 8G*2%=160MB as metadata to maintain the mapping in QEMU).
I did explicit test with scp this time, copying 1G sized file for >10 times on each of the following case:
- L1 guest, with vIOMMU and with assigned device
- L2 guest, without vIOMMU and with assigned device
- L2 guest, with vIOMMU (so 3-layer nested IOMMU) and with assigned devicePlease review. Thanks,
(Below are old content from previous cover letter)
==========================
v2:
- fix patchew code style warnings
- interval tree: postpone malloc when inserting; simplify node remove a bit where proper [Jason]
- fix up comment and commit message for iommu lock patch [Kevin]
- protect context cache too using the iommu lock [Kevin, Jason]
- add vast comment in patch 8 to explain the modify-PTE problem [Jason, Kevin]Online repo:
https://github.com/xzpeter/qemu/tree/fix-vtd-dma
This series fixes several major problems that current code has:
- Issue 1: when getting very big PSI UNMAP invalidations, the current code is buggy in that we might skip the notification while actually we should always send that notification.
- Issue 2: IOTLB is not thread safe, while block dataplane can be accessing and updating it in parallel.
- Issue 3: For devices that only registered with UNMAP-only notifiers, we don't really need to do page walking for PSIs, we can directly deliver the notification down. For example, vhost.
- Issue 4: unsafe window for MAP notified devices like vfio-pci (and in the future, vDPA as well). The problem is that, now for domain invalidations we do this to make sure the shadow page tables are correctly synced:
1. unmap the whole address space
2. replay the whole address space, map existing pagesHowever during step 1 and 2 there will be a very tiny window (it can be as big as 3ms) that the shadow page table is either invalid or incomplete (since we're rebuilding it up). That's fatal error since devices never know that happending and it's still possible to DMA to memories.
Patch 1 fixes issue 1. I put it at the first since it's picked from an old post.
Patch 2 is a cleanup to remove useless IntelIOMMUNotifierNode struct.
Patch 3 fixes issue 2.
Patch 4 fixes issue 3.
Patch 5-9 fix issue 4. Here a very simple interval tree is implemented based on Gtree. It's different with general interval tree in that it does not allow user to pass in private data (e.g., translated addresses). However that benefits us that then we can merge adjacent interval leaves so that hopefully we won't consume much memory even if the mappings are a lot (that happens for nested virt - when mapping the whole L2 guest RAM range, it can be at least in GBs).
Quoted 3 lines
Quoted 1 line
Patch 10 is another big cleanup only can work after patch 9.
I think patch numbers are wrong somehow.
Yes, this is the old cover letter, so the numbers do not match.
Quoted 7 lines
Given you also want to tweak one comment, could you please repost with this fix, and also in commit log for each patch
- Cc stable
- for security patches mention as much, if possible add data about the issue and its severity
The rest I can try to do. Thanks,
Signature · 1 line
Peter Xu