Xen Security Advisory 321 v3 (CVE-2020-15565) - insufficient cache write-back under VT-d
Xen.org security team <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1jsmbd-0002Fv-4w__22060.7808021427$1594124668$gmane$org@xenbits.xenproject.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2020-15565 / XSA-321
version 3
insufficient cache write-back under VT-d
UPDATES IN VERSION 3
====================
Public release.
ISSUE DESCRIPTION
=================
When page tables are shared between IOMMU and CPU, changes to them
require flushing of both TLBs. Furthermore, IOMMUs may be non-coherent,
and hence prior to flushing IOMMU TLBs CPU cached also needs writing
back to memory after changes were made. Such writing back of cached
data was missing in particular when splitting large page mappings into
smaller granularity ones.
IMPACT
======
A malicious guest may be able to retain read/write DMA access to
frames returned to Xen's free pool, and later reused for another
purpose. Host crashes (leading to a Denial of Service) and privilege
escalation cannot be ruled out.
VULNERABLE SYSTEMS
==================
Xen versions from at least 3.2 onwards are affected.
Only x86 Intel systems are affected. x86 AMD as well as Arm systems are
not affected.
Only x86 HVM guests using hardware assisted paging (HAP), having a
passed through PCI device assigned, and having page table sharing
enabled can leverage the vulnerability. Note that page table
sharing will be enabled (by default) only if Xen considers IOMMU and
CPU large page size support compatible.
MITIGATION
==========
Suppressing the use of page table sharing will avoid the vulnerability
(command line option "iommu=no-sharept"). Note however that as of Xen
version 4.13 there's also a respective per-guest control ("passthrough="
libxl guest config file option). If any guests have been created with
an explicit setting here, this setting may conflict with the addition of
the "iommu=no-sharept" Xen command line option.
Suppressing the use of large HAP pages will avoid the vulnerability
(command line options "hap_2mb=no hap_1gb=no").
Not passing through PCI devices to HVM guests will avoid the
vulnerability.
CREDITS
=======
This issue was discovered by Roger Pau Monné of Citrix.
RESOLUTION
==========
Applying the appropriate set of attached patches resolves this issue.
Note that unlike implied by the numbering, the patches here are intended
to go on top of XSA-328's.
Note also that patches for released versions are generally prepared to
apply to the stable branches, and may not apply cleanly to the most
recent release tarball. Downstreams are encouraged to update to the
tip of the stable branch before applying these patches.
xsa321/xsa321-?.patch xen-unstable
xsa321/xsa321-4.13-?.patch Xen 4.13.x
xsa321/xsa321-4.12-?.patch Xen 4.12.x
xsa321/xsa321-4.11-?.patch Xen 4.11.x
xsa321/xsa321-4.10-?.patch Xen 4.10.x
xsa321/xsa321-4.9-?.patch Xen 4.9.x
$ sha256sum xsa321* xsa321*/*
f0824c6b6e5de723301223927dbad916e0e5fbeb70f30a7e2467a04094dd840b xsa321.meta
35ed3be5e66da0580de8fb14ee7e6c073ac60e08e022c35ef194a714698641ad xsa321/xsa321-1.patch
b2bbb4cf397b7b532dcab120a4d678938c50ca0df6ff2724a416ac8567bd667b xsa321/xsa321-2.patch
87d2e0446ee3fb013c8f307e71c0ddeae8122d6beee3e5d2871aa429d8d19daa xsa321/xsa321-3.patch
38d7e715d4ed751a9ce503b61cacaf2d06c91b2eab4be95cbc3a9ae4d2a05efb xsa321/xsa321-4.9-1.patch
e4d5238233c883ea62491f852e543550bce9d74d7239a866f5e117df46838abc xsa321/xsa321-4.9-2.patch
d9140aee60c848e2e07a59741bab1fde4669f2627923e5d3f08b8f2971f589c4 xsa321/xsa321-4.9-3.patch
be8e320f64185bb29c52c0c1472d9d9aa1319768076ff70e691d4b40f7938a27 xsa321/xsa321-4.9-4.patch
7d83cb2d7de293f8534fa4eae1c56979984d01d8842ac06cfcb645191f27e51f xsa321/xsa321-4.9-5.patch
99c7cf186f0fea47ef516e3d477a5f5068adaad44624b406694b9ff33268e05b xsa321/xsa321-4.9-6.patch
9731286e9af9d83c5bf191aa5a6be0dfa34c79bca15660cd9b9e1c8e930cf974 xsa321/xsa321-4.9-7.patch
360765e859866c466dc1c9c6893dd800407d8f09b0b6f2b07fa403c290c4f0c6 xsa321/xsa321-4.10-1.patch
e4d5238233c883ea62491f852e543550bce9d74d7239a866f5e117df46838abc xsa321/xsa321-4.10-2.patch
74b5c19a469cc7252a296cb19288f1ab53a411530d06dd364a0e3292c6aa273f xsa321/xsa321-4.10-3.patch
be8e320f64185bb29c52c0c1472d9d9aa1319768076ff70e691d4b40f7938a27 xsa321/xsa321-4.10-4.patch
7d83cb2d7de293f8534fa4eae1c56979984d01d8842ac06cfcb645191f27e51f xsa321/xsa321-4.10-5.patch
99c7cf186f0fea47ef516e3d477a5f5068adaad44624b406694b9ff33268e05b xsa321/xsa321-4.10-6.patch
fb3122d23ae7381d798721fe92c622ea2d37baac369fe89b0707030315dfc896 xsa321/xsa321-4.10-7.patch
360765e859866c466dc1c9c6893dd800407d8f09b0b6f2b07fa403c290c4f0c6 xsa321/xsa321-4.11-1.patch
02e2fda4b467f10a7f38cb2a095b9da04289d9e8489db88bf542d6527b823a23 xsa321/xsa321-4.11-2.patch
04c9bc347f8d3cbb8aecede370189bba2ed47be560d1871b91eb01b962a578cc xsa321/xsa321-4.11-3.patch
be8e320f64185bb29c52c0c1472d9d9aa1319768076ff70e691d4b40f7938a27 xsa321/xsa321-4.11-4.patch
c1b143b43b59244d5dc755f6a99de70ac39e803a7204296bb47300b9ffe26e59 xsa321/xsa321-4.11-5.patch
38456ff553416e48f2f5438c2a5a163b20929e8a58dbe811942d0d47aacfc9ea xsa321/xsa321-4.11-6.patch
d3b6df41682e6b88898545590bee8242c00b4593773ba8070ce57a0473094189 xsa321/xsa321-4.11-7.patch
c6d00d7a988002687be9a19a2d631c3562d8ec9f02ae24efc23eb0039f9e0ddb xsa321/xsa321-4.12-1.patch
64dd3aa18be3ccb17ab6d813df16e2025adabbe38127f2f00175a6a481651d86 xsa321/xsa321-4.12-2.patch
935346f3d0f2759699b0ccb8002abfb0dc173ec3ed616fb9042ad86751445757 xsa321/xsa321-4.12-3.patch
be8e320f64185bb29c52c0c1472d9d9aa1319768076ff70e691d4b40f7938a27 xsa321/xsa321-4.12-4.patch
c1b143b43b59244d5dc755f6a99de70ac39e803a7204296bb47300b9ffe26e59 xsa321/xsa321-4.12-5.patch
0da20aeb89e18490d60649dbfdb9c374e5861032da784a7724216c329f2cc5f0 xsa321/xsa321-4.12-6.patch
4d1954600eeca7e2cb9143ea8e32969731071f991a9a88a245c18e860c57c22c xsa321/xsa321-4.12-7.patch
946053a8bba53d87b4164acaf3343e30689d91b505b6355d873c016166d87103 xsa321/xsa321-4.13-1.patch
f09e8cbf0cce17647d47f38137792517c8b108c3b54f57793d03578b0d5ccf99 xsa321/xsa321-4.13-2.patch
bd50ad52d23c6fc12b69ecaaf41073833cbe9b1d66a9f4e148df078e30dd45d4 xsa321/xsa321-4.13-3.patch
b181511962ce397302be8b7d5a130abe0995b3fda68b96f1afa95ae64f62dd09 xsa321/xsa321-4.13-4.patch
3286fc184fb377c1ce94344d1dbae3b78e95b0ae766eabb80b2fc612e59ffb69 xsa321/xsa321-4.13-5.patch
03a193197d176109dc586f4d6a76aebe32a4aa147e88c79d57582cf0a186c4ef xsa321/xsa321-4.13-6.patch
ef7f9ac74313d2dabfb258b2519b2144e4feed3c85b5f705c4b1b7ba31ec316a xsa321/xsa321-4.13-7.patch
e6d4b77063d4cd7a7242ac54b150ce42ce684ecbf46c7eaff5715976f272f4bc xsa321/xsa321-4.patch
920771be10110a3eef8e4b8644145794d274042092f3aa14e04fa94fc1e78e8a xsa321/xsa321-5.patch
b10c5583e01f1c26862806562f30e393960b0bbdd7cf7fca6640f4daa88fe017 xsa321/xsa321-6.patch
18da003fb05b7aebe868ff9f1c77063b8a51be3b07ab0c9fc4821bf46ca86eeb xsa321/xsa321-7.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or mitigations described above (or
others which are substantially similar) is permitted during the
embargo, even on public-facing systems with untrusted guest users and
administrators.
But: Distribution of updated software is prohibited (except to other
members of the predisclosure list).
Predisclosure list members who wish to deploy significantly different
patches and/or mitigations, please contact the Xen Project Security
Team.
(Note: this during-embargo deployment notice is retained in
post-embargo publicly released Xen Project advisories, even though it
is then no longer applicable. This is to enable the community to have
oversight of the Xen Project Security Team's decisionmaking.)
For more information about permissible uses of embargoed information,
consult the Xen Project community's agreed Security Policy:
http://www.xenproject.org/security-policy.html
-----BEGIN PGP SIGNATURE-----
iQFABAEBCAAqFiEEI+MiLBRfRHX6gGCng/4UyVfoK9kFAl8EaM8MHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZ35IH/iNi7HaBQrIqks4MB/0odUAIYyUEVsI4eAavChkX
oKO+IQ7sDOyjKG+VHWgMxtnZhcQk9A+qHMnfCjL7igp0HMonT5C1r38x/+Nf203+
V/mQ0h/Vj1Fz7qSk0mtX2j2zkAS7hEFnOQcT5TIkxAt5ZO3wSbPEwmt9UqR7VON9
rXFX6WyAqDhO7Hw2lngPXc2VGoORHqybII4XZGb24TO7q9U4vFhBR0ZVgWKBo1pt
82gl2h2jQn8IA0Rrack+ucfsoD9D+E3AQYtipZVd9PI/SJNsZHvHJdaPxBf2CUqO
Jb1e5MMXRG9Htpe0GPu8Y0TSUAUCoHqBsJTE1wkn4hun5SQ=
=/CNm
-----END PGP SIGNATURE-----
xsa321.meta
(application/octet-stream, 3.2 KB) - not displayed
xsa321/xsa321-1.patch
(application/octet-stream, 1.6 KB)
From: Jan Beulich <[email protected]> Subject: [PATCH v5 1/9] vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Roger Pau Monné <[email protected]> --- xen/drivers/passthrough/vtd/iommu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index 208b33c0e4..dcc9b7a35e 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -576,13 +576,14 @@ static int __must_check iommu_flush_iotlb(struct domain *d, dfn_t dfn, if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || dfn_eq(dfn, INVALID_DFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + dfn_eq(dfn, INVALID_DFN) || !IS_ALIGNED(dfn_x(dfn), page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, dfn_to_daddr(dfn), - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb); -- 2.26.2
xsa321/xsa321-2.patch
(application/octet-stream, 7.2 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 4/9] vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v3: - Constify addr parameter. Changes since v2: - New in this version. --- xen/drivers/passthrough/vtd/extern.h | 3 +-- xen/drivers/passthrough/vtd/intremap.c | 6 ++--- xen/drivers/passthrough/vtd/iommu.c | 33 ++++++++++---------------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/xen/drivers/passthrough/vtd/extern.h b/xen/drivers/passthrough/vtd/extern.h index 994d360e90..52b5e1c60d 100644 --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -43,8 +43,7 @@ void disable_qinval(struct vtd_iommu *iommu); int enable_intremap(struct vtd_iommu *iommu, int eim); void disable_intremap(struct vtd_iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); diff --git a/xen/drivers/passthrough/vtd/intremap.c b/xen/drivers/passthrough/vtd/intremap.c index bf846195c4..a2f02c1bea 100644 --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -230,7 +230,7 @@ static void free_remap_entry(struct vtd_iommu *iommu, int index) iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -406,7 +406,7 @@ static int ioapic_rte_to_remap_entry(struct vtd_iommu *iommu, } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -695,7 +695,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index dcc9b7a35e..55eb140033 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -146,7 +146,8 @@ static int context_get_domain_id(struct context_entry *context, } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -161,16 +162,6 @@ static void __iommu_flush_cache(void *addr, unsigned int size) cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node) { @@ -189,7 +180,7 @@ uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node) vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -222,7 +213,7 @@ static u64 bus_to_context_maddr(struct vtd_iommu *iommu, u8 bus) } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -269,7 +260,7 @@ static u64 addr_to_dma_page_maddr(struct domain *domain, u64 addr, int alloc) */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -645,7 +636,7 @@ static void dma_pte_clear_one(struct domain *domain, uint64_t addr, *flush_flags |= IOMMU_FLUSHF_modified; spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); unmap_vtd_domain_page(page); } @@ -682,7 +673,7 @@ static void iommu_free_page_table(struct page_info *pg) iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1401,7 +1392,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1565,7 +1556,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1792,7 +1783,7 @@ static int __must_check intel_iommu_map_page(struct domain *d, dfn_t dfn, *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1869,7 +1860,7 @@ int iommu_pte_flush(struct domain *d, uint64_t dfn, uint64_t *pte, int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) { @@ -2739,7 +2730,7 @@ static int __init intel_iommu_quarantine_init(struct domain *d) dma_set_pte_addr(*pte, maddr); dma_set_pte_readable(*pte); } - iommu_flush_cache_page(parent, 1); + iommu_sync_cache(parent, PAGE_SIZE); unmap_vtd_domain_page(parent); parent = map_vtd_domain_page(maddr); -- 2.26.2
xsa321/xsa321-3.patch
(application/octet-stream, 3.9 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 5/9] x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v3: - Use a macro instead of a function. Changes since v2: - Rename to iommu_sync_cache. - Move to iommu.c in order to use the alternative call patching. --- xen/drivers/passthrough/vtd/extern.h | 1 - xen/drivers/passthrough/vtd/iommu.c | 5 +++-- xen/include/asm-x86/iommu.h | 7 +++++++ xen/include/xen/iommu.h | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/xen/drivers/passthrough/vtd/extern.h b/xen/drivers/passthrough/vtd/extern.h index 52b5e1c60d..f15947af1f 100644 --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -43,7 +43,6 @@ void disable_qinval(struct vtd_iommu *iommu); int enable_intremap(struct vtd_iommu *iommu, int eim); void disable_intremap(struct vtd_iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index 55eb140033..93bcd72f84 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -147,7 +147,7 @@ static int context_get_domain_id(struct context_entry *context, static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -180,7 +180,7 @@ uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node) vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2778,6 +2778,7 @@ const struct iommu_ops __initconstrel intel_iommu_ops = { .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; const struct iommu_init_ops __initconstrel intel_iommu_init_ops = { diff --git a/xen/include/asm-x86/iommu.h b/xen/include/asm-x86/iommu.h index 85741f7c96..864e025078 100644 --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -121,6 +121,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + iommu_vcall(ops, sync_cache, addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: diff --git a/xen/include/xen/iommu.h b/xen/include/xen/iommu.h index 6264d3d07f..3272874958 100644 --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -275,6 +275,7 @@ struct iommu_ops { int (*setup_hpet_msi)(struct msi_desc *); int (*adjust_irq_affinities)(void); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void); -- 2.26.2
xsa321/xsa321-4.9-1.patch
(application/octet-stream, 1.3 KB)
From: Jan Beulich <[email protected]> Subject: vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Roger Pau Monné <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,13 +612,14 @@ static int __must_check iommu_flush_iotl if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || gfn == gfn_x(INVALID_GFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + gfn == gfn_x(INVALID_GFN) || !IS_ALIGNED(gfn, page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, (paddr_t)gfn << PAGE_SHIFT_4K, - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb);
xsa321/xsa321-4.9-2.patch
(application/octet-stream, 5.9 KB)
From: <[email protected]> Subject: vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,8 +37,7 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -231,7 +231,7 @@ static void free_remap_entry(struct iomm iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -403,7 +403,7 @@ static int ioapic_rte_to_remap_entry(str } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -694,7 +694,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -158,7 +158,8 @@ static void __init free_intel_iommu(stru } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -173,16 +174,6 @@ static void __iommu_flush_cache(void *ad cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages) { @@ -207,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -242,7 +233,7 @@ static u64 bus_to_context_maddr(struct i } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -300,7 +291,7 @@ static u64 addr_to_dma_page_maddr(struct */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -674,7 +665,7 @@ static int __must_check dma_pte_clear_on dma_clear_pte(*pte); spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); if ( !this_cpu(iommu_dont_flush_iotlb) ) rc = iommu_flush_iotlb_pages(domain, addr >> PAGE_SHIFT_4K, 1); @@ -716,7 +707,7 @@ static void iommu_free_page_table(struct iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1447,7 +1438,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1594,7 +1585,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1824,7 +1815,7 @@ static int __must_check intel_iommu_map_ *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1858,7 +1849,7 @@ int iommu_pte_flush(struct domain *d, u6 int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) {
xsa321/xsa321-4.9-3.patch
(application/octet-stream, 2.9 KB)
From: <[email protected]> Subject: x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,7 +37,6 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -159,7 +159,7 @@ static void __init free_intel_iommu(stru static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -198,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2696,6 +2696,7 @@ const struct iommu_ops intel_iommu_ops = .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; /* --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -98,6 +98,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + ops->sync_cache(addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -176,6 +176,7 @@ struct iommu_ops { void (*update_ire_from_apic)(unsigned int apic, unsigned int reg, unsigned int value); unsigned int (*read_apic_from_ire)(unsigned int apic, unsigned int reg); int (*setup_hpet_msi)(struct msi_desc *); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void); void (*resume)(void);
xsa321/xsa321-4.9-4.patch
(application/octet-stream, 1.2 KB)
From: <[email protected]> Subject: vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -161,8 +161,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -170,8 +170,9 @@ static void sync_cache(const void *addr, if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-4.9-5.patch
(application/octet-stream, 793 B)
From: <[email protected]> Subject: x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Acked-by: Jan Beulich <[email protected]> --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -85,6 +85,11 @@ extern void alternative_instructions(voi #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. *
xsa321/xsa321-4.9-6.patch
(application/octet-stream, 3.1 KB)
From: <[email protected]> Subject: vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -63,7 +63,6 @@ int __must_check qinval_device_iotlb_syn u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -172,7 +173,42 @@ static void sync_cache(const void *addr, addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2(ASM_NOP3, "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -53,11 +53,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-4.9-7.patch
(application/octet-stream, 6 KB)
From: <[email protected]> Subject: x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -90,6 +90,19 @@ static int atomic_write_ept_entry(ept_en write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_hap_pt_share ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + if ( unlikely(oldmfn != mfn_x(INVALID_MFN)) ) put_page(mfn_to_page(oldmfn)); @@ -319,6 +332,9 @@ static bool_t ept_split_super_page(struc break; } + if ( iommu_hap_pt_share ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -378,6 +394,9 @@ static int ept_next_level(struct p2m_dom if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_hap_pt_share ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(ept_entry, e, next_level); ASSERT(rc == 0); } @@ -873,7 +892,7 @@ out: need_modify_vtd_table ) { if ( iommu_hap_pt_share ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_flush_iotlb(d, gfn, vtd_pte_present, 1u << order); else { if ( iommu_flags ) --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,10 +612,8 @@ static int __must_check iommu_flush_all( return rc; } -static int __must_check iommu_flush_iotlb(struct domain *d, - unsigned long gfn, - bool_t dma_old_pte_present, - unsigned int page_count) +int iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, unsigned int page_count) { struct domain_iommu *hd = dom_iommu(d); struct acpi_drhd_unit *drhd; @@ -1876,53 +1874,6 @@ static int __must_check intel_iommu_unma return dma_pte_clear_one(d, (paddr_t)gfn << PAGE_SHIFT_4K); } -int iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - (paddr_t)gfn << PAGE_SHIFT_4K, - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -87,8 +87,9 @@ int iommu_setup_hpet_msi(struct msi_desc /* While VT-d specific, this must get declared in a generic header. */ int adjust_vtd_irq_affinities(void); -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); +int __must_check iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, + unsigned int page_count); bool_t iommu_supports_eim(void); int iommu_enable_x2apic_IR(void); void iommu_disable_x2apic_IR(void);
xsa321/xsa321-4.10-1.patch
(application/octet-stream, 1.3 KB)
From: Jan Beulich <[email protected]> Subject: vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,13 +612,14 @@ static int __must_check iommu_flush_iotl if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || gfn == gfn_x(INVALID_GFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + gfn == gfn_x(INVALID_GFN) || !IS_ALIGNED(gfn, page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, (paddr_t)gfn << PAGE_SHIFT_4K, - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb);
xsa321/xsa321-4.10-2.patch
(application/octet-stream, 5.9 KB)
From: <[email protected]> Subject: vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,8 +37,7 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -231,7 +231,7 @@ static void free_remap_entry(struct iomm iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -403,7 +403,7 @@ static int ioapic_rte_to_remap_entry(str } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -694,7 +694,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -158,7 +158,8 @@ static void __init free_intel_iommu(stru } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -173,16 +174,6 @@ static void __iommu_flush_cache(void *ad cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages) { @@ -207,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -242,7 +233,7 @@ static u64 bus_to_context_maddr(struct i } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -300,7 +291,7 @@ static u64 addr_to_dma_page_maddr(struct */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -674,7 +665,7 @@ static int __must_check dma_pte_clear_on dma_clear_pte(*pte); spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); if ( !this_cpu(iommu_dont_flush_iotlb) ) rc = iommu_flush_iotlb_pages(domain, addr >> PAGE_SHIFT_4K, 1); @@ -716,7 +707,7 @@ static void iommu_free_page_table(struct iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1447,7 +1438,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1594,7 +1585,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1824,7 +1815,7 @@ static int __must_check intel_iommu_map_ *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1858,7 +1849,7 @@ int iommu_pte_flush(struct domain *d, u6 int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) {
xsa321/xsa321-4.10-3.patch
(application/octet-stream, 2.9 KB)
From: <[email protected]> Subject: x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,7 +37,6 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -159,7 +159,7 @@ static void __init free_intel_iommu(stru static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -198,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2696,6 +2696,7 @@ const struct iommu_ops intel_iommu_ops = .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; /* --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -98,6 +98,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + ops->sync_cache(addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -160,6 +160,7 @@ struct iommu_ops { void (*update_ire_from_apic)(unsigned int apic, unsigned int reg, unsigned int value); unsigned int (*read_apic_from_ire)(unsigned int apic, unsigned int reg); int (*setup_hpet_msi)(struct msi_desc *); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void); void (*resume)(void);
xsa321/xsa321-4.10-4.patch
(application/octet-stream, 1.2 KB)
From: <[email protected]> Subject: vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -161,8 +161,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -170,8 +170,9 @@ static void sync_cache(const void *addr, if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-4.10-5.patch
(application/octet-stream, 793 B)
From: <[email protected]> Subject: x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Acked-by: Jan Beulich <[email protected]> --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -85,6 +85,11 @@ extern void alternative_instructions(voi #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. *
xsa321/xsa321-4.10-6.patch
(application/octet-stream, 3.1 KB)
From: <[email protected]> Subject: vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -63,7 +63,6 @@ int __must_check qinval_device_iotlb_syn u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -172,7 +173,42 @@ static void sync_cache(const void *addr, addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2(ASM_NOP3, "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -53,11 +53,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-4.10-7.patch
(application/octet-stream, 6 KB)
From: <[email protected]> Subject: x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -90,6 +90,19 @@ static int atomic_write_ept_entry(ept_en write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_hap_pt_share ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + if ( unlikely(oldmfn != mfn_x(INVALID_MFN)) ) put_page(mfn_to_page(oldmfn)); @@ -319,6 +332,9 @@ static bool_t ept_split_super_page(struc break; } + if ( iommu_hap_pt_share ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -378,6 +394,9 @@ static int ept_next_level(struct p2m_dom if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_hap_pt_share ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(ept_entry, e, next_level); ASSERT(rc == 0); } @@ -874,7 +893,7 @@ out: need_modify_vtd_table ) { if ( iommu_hap_pt_share ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_flush_iotlb(d, gfn, vtd_pte_present, 1u << order); else { if ( iommu_flags ) --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,10 +612,8 @@ static int __must_check iommu_flush_all( return rc; } -static int __must_check iommu_flush_iotlb(struct domain *d, - unsigned long gfn, - bool_t dma_old_pte_present, - unsigned int page_count) +int iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, unsigned int page_count) { struct domain_iommu *hd = dom_iommu(d); struct acpi_drhd_unit *drhd; @@ -1876,53 +1874,6 @@ static int __must_check intel_iommu_unma return dma_pte_clear_one(d, (paddr_t)gfn << PAGE_SHIFT_4K); } -int iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - (paddr_t)gfn << PAGE_SHIFT_4K, - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -87,8 +87,9 @@ int iommu_setup_hpet_msi(struct msi_desc /* While VT-d specific, this must get declared in a generic header. */ int adjust_vtd_irq_affinities(void); -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); +int __must_check iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, + unsigned int page_count); bool_t iommu_supports_eim(void); int iommu_enable_x2apic_IR(void); void iommu_disable_x2apic_IR(void);
xsa321/xsa321-4.11-1.patch
(application/octet-stream, 1.3 KB)
From: Jan Beulich <[email protected]> Subject: vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,13 +612,14 @@ static int __must_check iommu_flush_iotl if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || gfn == gfn_x(INVALID_GFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + gfn == gfn_x(INVALID_GFN) || !IS_ALIGNED(gfn, page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, (paddr_t)gfn << PAGE_SHIFT_4K, - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb);
xsa321/xsa321-4.11-2.patch
(application/octet-stream, 6.2 KB)
From: <[email protected]> Subject: vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,8 +37,7 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -231,7 +231,7 @@ static void free_remap_entry(struct iomm iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -403,7 +403,7 @@ static int ioapic_rte_to_remap_entry(str } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -694,7 +694,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -158,7 +158,8 @@ static void __init free_intel_iommu(stru } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -173,16 +174,6 @@ static void __iommu_flush_cache(void *ad cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages) { @@ -207,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -242,7 +233,7 @@ static u64 bus_to_context_maddr(struct i } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -300,7 +291,7 @@ static u64 addr_to_dma_page_maddr(struct */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -674,7 +665,7 @@ static int __must_check dma_pte_clear_on dma_clear_pte(*pte); spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); if ( !this_cpu(iommu_dont_flush_iotlb) ) rc = iommu_flush_iotlb_pages(domain, addr >> PAGE_SHIFT_4K, 1); @@ -716,7 +707,7 @@ static void iommu_free_page_table(struct iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1449,7 +1440,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1602,7 +1593,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1828,7 +1819,7 @@ static int __must_check intel_iommu_map_ *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1862,7 +1853,7 @@ int iommu_pte_flush(struct domain *d, u6 int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) { @@ -2725,7 +2716,7 @@ static int __init intel_iommu_quarantine dma_set_pte_addr(*pte, maddr); dma_set_pte_readable(*pte); } - iommu_flush_cache_page(parent, 1); + iommu_sync_cache(parent, PAGE_SIZE); unmap_vtd_domain_page(parent); parent = map_vtd_domain_page(maddr);
xsa321/xsa321-4.11-3.patch
(application/octet-stream, 2.9 KB)
From: <[email protected]> Subject: x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -37,7 +37,6 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -159,7 +159,7 @@ static void __init free_intel_iommu(stru static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -198,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2760,6 +2760,7 @@ const struct iommu_ops intel_iommu_ops = .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; /* --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -98,6 +98,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + ops->sync_cache(addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -161,6 +161,7 @@ struct iommu_ops { void (*update_ire_from_apic)(unsigned int apic, unsigned int reg, unsigned int value); unsigned int (*read_apic_from_ire)(unsigned int apic, unsigned int reg); int (*setup_hpet_msi)(struct msi_desc *); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void); void (*resume)(void);
xsa321/xsa321-4.11-4.patch
(application/octet-stream, 1.2 KB)
From: <[email protected]> Subject: vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -161,8 +161,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -170,8 +170,9 @@ static void sync_cache(const void *addr, if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-4.11-5.patch
(application/octet-stream, 795 B)
From: <[email protected]> Subject: x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Acked-by: Jan Beulich <[email protected]> --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -113,6 +113,11 @@ extern void alternative_instructions(voi #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. *
xsa321/xsa321-4.11-6.patch
(application/octet-stream, 3.1 KB)
From: <[email protected]> Subject: vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -63,7 +63,6 @@ int __must_check qinval_device_iotlb_syn u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -172,7 +173,42 @@ static void sync_cache(const void *addr, addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2("", "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -53,11 +53,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-4.11-7.patch
(application/octet-stream, 6 KB)
From: <[email protected]> Subject: x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -90,6 +90,19 @@ static int atomic_write_ept_entry(ept_en write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_hap_pt_share ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + if ( unlikely(oldmfn != mfn_x(INVALID_MFN)) ) put_page(mfn_to_page(_mfn(oldmfn))); @@ -319,6 +332,9 @@ static bool_t ept_split_super_page(struc break; } + if ( iommu_hap_pt_share ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -378,6 +394,9 @@ static int ept_next_level(struct p2m_dom if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_hap_pt_share ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(ept_entry, e, next_level); ASSERT(rc == 0); } @@ -875,7 +894,7 @@ out: need_modify_vtd_table ) { if ( iommu_hap_pt_share ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_flush_iotlb(d, gfn, vtd_pte_present, 1u << order); else { if ( iommu_flags ) --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -612,10 +612,8 @@ static int __must_check iommu_flush_all( return rc; } -static int __must_check iommu_flush_iotlb(struct domain *d, - unsigned long gfn, - bool_t dma_old_pte_present, - unsigned int page_count) +int iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, unsigned int page_count) { struct domain_iommu *hd = dom_iommu(d); struct acpi_drhd_unit *drhd; @@ -1880,53 +1878,6 @@ static int __must_check intel_iommu_unma return dma_pte_clear_one(d, (paddr_t)gfn << PAGE_SHIFT_4K); } -int iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - (paddr_t)gfn << PAGE_SHIFT_4K, - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -87,8 +87,9 @@ int iommu_setup_hpet_msi(struct msi_desc /* While VT-d specific, this must get declared in a generic header. */ int adjust_vtd_irq_affinities(void); -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); +int __must_check iommu_flush_iotlb(struct domain *d, unsigned long gfn, + bool dma_old_pte_present, + unsigned int page_count); bool_t iommu_supports_eim(void); int iommu_enable_x2apic_IR(void); void iommu_disable_x2apic_IR(void);
xsa321/xsa321-4.12-1.patch
(application/octet-stream, 1.3 KB)
From: Jan Beulich <[email protected]> Subject: vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -611,13 +611,14 @@ static int __must_check iommu_flush_iotl if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || dfn_eq(dfn, INVALID_DFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + dfn_eq(dfn, INVALID_DFN) || !IS_ALIGNED(dfn_x(dfn), page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, dfn_to_daddr(dfn), - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb);
xsa321/xsa321-4.12-2.patch
(application/octet-stream, 6.2 KB)
From: <[email protected]> Subject: vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -38,8 +38,7 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -231,7 +231,7 @@ static void free_remap_entry(struct iomm iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -403,7 +403,7 @@ static int ioapic_rte_to_remap_entry(str } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -694,7 +694,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -158,7 +158,8 @@ static void __init free_intel_iommu(stru } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -173,16 +174,6 @@ static void __iommu_flush_cache(void *ad cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages) { @@ -207,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -242,7 +233,7 @@ static u64 bus_to_context_maddr(struct i } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -300,7 +291,7 @@ static u64 addr_to_dma_page_maddr(struct */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -681,7 +672,7 @@ static int __must_check dma_pte_clear_on *flush_flags |= IOMMU_FLUSHF_modified; spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); unmap_vtd_domain_page(page); @@ -720,7 +711,7 @@ static void iommu_free_page_table(struct iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1449,7 +1440,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1602,7 +1593,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1837,7 +1828,7 @@ static int __must_check intel_iommu_map_ *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1912,7 +1903,7 @@ int iommu_pte_flush(struct domain *d, ui int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) { @@ -2777,7 +2768,7 @@ static int __init intel_iommu_quarantine dma_set_pte_addr(*pte, maddr); dma_set_pte_readable(*pte); } - iommu_flush_cache_page(parent, 1); + iommu_sync_cache(parent, PAGE_SIZE); unmap_vtd_domain_page(parent); parent = map_vtd_domain_page(maddr);
xsa321/xsa321-4.12-3.patch
(application/octet-stream, 2.9 KB)
From: <[email protected]> Subject: x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -38,7 +38,6 @@ void disable_qinval(struct iommu *iommu) int enable_intremap(struct iommu *iommu, int eim); void disable_intremap(struct iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -159,7 +159,7 @@ static void __init free_intel_iommu(stru static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -198,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2813,6 +2813,7 @@ const struct iommu_ops __initconstrel in .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; /* --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -101,6 +101,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + ops->sync_cache(addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -221,6 +221,7 @@ struct iommu_ops { void (*update_ire_from_apic)(unsigned int apic, unsigned int reg, unsigned int value); unsigned int (*read_apic_from_ire)(unsigned int apic, unsigned int reg); int (*setup_hpet_msi)(struct msi_desc *); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void); void (*resume)(void);
xsa321/xsa321-4.12-4.patch
(application/octet-stream, 1.2 KB)
From: <[email protected]> Subject: vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -161,8 +161,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -170,8 +170,9 @@ static void sync_cache(const void *addr, if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-4.12-5.patch
(application/octet-stream, 795 B)
From: <[email protected]> Subject: x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Acked-by: Jan Beulich <[email protected]> --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -113,6 +113,11 @@ extern void alternative_instructions(voi #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. *
xsa321/xsa321-4.12-6.patch
(application/octet-stream, 3.1 KB)
From: <[email protected]> Subject: vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -64,7 +64,6 @@ int __must_check qinval_device_iotlb_syn u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -172,7 +173,42 @@ static void sync_cache(const void *addr, addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2("", "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -51,11 +51,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-4.12-7.patch
(application/octet-stream, 5.5 KB)
From: <[email protected]> Subject: x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -58,6 +58,19 @@ static int atomic_write_ept_entry(struct write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + return 0; } @@ -278,6 +291,9 @@ static bool_t ept_split_super_page(struc break; } + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -337,6 +353,9 @@ static int ept_next_level(struct p2m_dom if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(p2m, ept_entry, e, next_level); ASSERT(rc == 0); } @@ -815,7 +834,10 @@ out: need_modify_vtd_table ) { if ( iommu_use_hap_pt(d) ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_iotlb_flush(d, _dfn(gfn), (1u << order), + (iommu_flags ? IOMMU_FLUSHF_added : 0) | + (vtd_pte_present ? IOMMU_FLUSHF_modified + : 0)); else if ( need_iommu_pt_sync(d) ) rc = iommu_flags ? iommu_legacy_map(d, _dfn(gfn), mfn, order, iommu_flags) : --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -1930,53 +1930,6 @@ static int intel_iommu_lookup_page(struc return 0; } -int iommu_pte_flush(struct domain *d, uint64_t dfn, uint64_t *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - __dfn_to_daddr(dfn), - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -90,8 +90,6 @@ int iommu_setup_hpet_msi(struct msi_desc /* While VT-d specific, this must get declared in a generic header. */ int adjust_vtd_irq_affinities(void); -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); bool_t iommu_supports_eim(void); int iommu_enable_x2apic_IR(void); void iommu_disable_x2apic_IR(void);
xsa321/xsa321-4.13-1.patch
(application/octet-stream, 1.3 KB)
From: Jan Beulich <[email protected]> Subject: vtd: improve IOMMU TLB flush Do not limit PSI flushes to order 0 pages, in order to avoid doing a full TLB flush if the passed in page has an order greater than 0 and is aligned. Should increase the performance of IOMMU TLB flushes when dealing with page orders greater than 0. This is part of XSA-321. Signed-off-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -570,13 +570,14 @@ static int __must_check iommu_flush_iotl if ( iommu_domid == -1 ) continue; - if ( page_count != 1 || dfn_eq(dfn, INVALID_DFN) ) + if ( !page_count || (page_count & (page_count - 1)) || + dfn_eq(dfn, INVALID_DFN) || !IS_ALIGNED(dfn_x(dfn), page_count) ) rc = iommu_flush_iotlb_dsi(iommu, iommu_domid, 0, flush_dev_iotlb); else rc = iommu_flush_iotlb_psi(iommu, iommu_domid, dfn_to_daddr(dfn), - PAGE_ORDER_4K, + get_order_from_pages(page_count), !dma_old_pte_present, flush_dev_iotlb);
xsa321/xsa321-4.13-2.patch
(application/octet-stream, 6.2 KB)
From: <[email protected]> Subject: vtd: prune (and rename) cache flush functions Rename __iommu_flush_cache to iommu_sync_cache and remove iommu_flush_cache_page. Also remove the iommu_flush_cache_entry wrapper and just use iommu_sync_cache instead. Note the _entry suffix was meaningless as the wrapper was already taking a size parameter in bytes. While there also constify the addr parameter. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -43,8 +43,7 @@ void disable_qinval(struct vtd_iommu *io int enable_intremap(struct vtd_iommu *iommu, int eim); void disable_intremap(struct vtd_iommu *iommu); -void iommu_flush_cache_entry(void *addr, unsigned int size); -void iommu_flush_cache_page(void *addr, unsigned long npages); +void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/intremap.c +++ b/xen/drivers/passthrough/vtd/intremap.c @@ -230,7 +230,7 @@ static void free_remap_entry(struct vtd_ iremap_entries, iremap_entry); update_irte(iommu, iremap_entry, &new_ire, false); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -406,7 +406,7 @@ static int ioapic_rte_to_remap_entry(str } update_irte(iommu, iremap_entry, &new_ire, !init); - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); @@ -695,7 +695,7 @@ static int msi_msg_to_remap_entry( update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized); msi_desc->irte_initialized = true; - iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry)); + iommu_sync_cache(iremap_entry, sizeof(*iremap_entry)); iommu_flush_iec_index(iommu, 0, index); unmap_vtd_domain_page(iremap_entries); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -140,7 +140,8 @@ static int context_get_domain_id(struct } static int iommus_incoherent; -static void __iommu_flush_cache(void *addr, unsigned int size) + +void iommu_sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -155,16 +156,6 @@ static void __iommu_flush_cache(void *ad cacheline_flush((char *)addr + i); } -void iommu_flush_cache_entry(void *addr, unsigned int size) -{ - __iommu_flush_cache(addr, size); -} - -void iommu_flush_cache_page(void *addr, unsigned long npages) -{ - __iommu_flush_cache(addr, PAGE_SIZE * npages); -} - /* Allocate page table, return its machine address */ uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node) { @@ -183,7 +174,7 @@ uint64_t alloc_pgtable_maddr(unsigned lo vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_flush_cache_page(vaddr, 1); + iommu_sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -216,7 +207,7 @@ static u64 bus_to_context_maddr(struct v } set_root_value(*root, maddr); set_root_present(*root); - iommu_flush_cache_entry(root, sizeof(struct root_entry)); + iommu_sync_cache(root, sizeof(struct root_entry)); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); @@ -263,7 +254,7 @@ static u64 addr_to_dma_page_maddr(struct */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } if ( level == 2 ) @@ -640,7 +631,7 @@ static int __must_check dma_pte_clear_on *flush_flags |= IOMMU_FLUSHF_modified; spin_unlock(&hd->arch.mapping_lock); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); unmap_vtd_domain_page(page); @@ -679,7 +670,7 @@ static void iommu_free_page_table(struct iommu_free_pagetable(dma_pte_addr(*pte), next_level); dma_clear_pte(*pte); - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); } unmap_vtd_domain_page(pt_vaddr); @@ -1400,7 +1391,7 @@ int domain_context_mapping_one( context_set_address_width(*context, agaw); context_set_fault_enable(*context); context_set_present(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); spin_unlock(&iommu->lock); /* Context entry was previously non-present (with domid 0). */ @@ -1564,7 +1555,7 @@ int domain_context_unmap_one( context_clear_present(*context); context_clear_entry(*context); - iommu_flush_cache_entry(context, sizeof(struct context_entry)); + iommu_sync_cache(context, sizeof(struct context_entry)); iommu_domid= domain_iommu_domid(domain, iommu); if ( iommu_domid == -1 ) @@ -1791,7 +1782,7 @@ static int __must_check intel_iommu_map_ *pte = new; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); spin_unlock(&hd->arch.mapping_lock); unmap_vtd_domain_page(page); @@ -1866,7 +1857,7 @@ int iommu_pte_flush(struct domain *d, ui int iommu_domid; int rc = 0; - iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); + iommu_sync_cache(pte, sizeof(struct dma_pte)); for_each_drhd_unit ( drhd ) { @@ -2724,7 +2715,7 @@ static int __init intel_iommu_quarantine dma_set_pte_addr(*pte, maddr); dma_set_pte_readable(*pte); } - iommu_flush_cache_page(parent, 1); + iommu_sync_cache(parent, PAGE_SIZE); unmap_vtd_domain_page(parent); parent = map_vtd_domain_page(maddr);
xsa321/xsa321-4.13-3.patch
(application/octet-stream, 2.8 KB)
From: <[email protected]> Subject: x86/iommu: introduce a cache sync hook The hook is only implemented for VT-d and it uses the already existing iommu_sync_cache function present in VT-d code. The new hook is added so that the cache can be flushed by code outside of VT-d when using shared page tables. Note that alloc_pgtable_maddr must use the now locally defined sync_cache function, because IOMMU ops are not yet setup the first time the function gets called during IOMMU initialization. No functional change intended. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -43,7 +43,6 @@ void disable_qinval(struct vtd_iommu *io int enable_intremap(struct vtd_iommu *iommu, int eim); void disable_intremap(struct vtd_iommu *iommu); -void iommu_sync_cache(const void *addr, unsigned int size); int iommu_alloc(struct acpi_drhd_unit *drhd); void iommu_free(struct acpi_drhd_unit *drhd); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -141,7 +141,7 @@ static int context_get_domain_id(struct static int iommus_incoherent; -void iommu_sync_cache(const void *addr, unsigned int size) +static void sync_cache(const void *addr, unsigned int size) { int i; static unsigned int clflush_size = 0; @@ -174,7 +174,7 @@ uint64_t alloc_pgtable_maddr(unsigned lo vaddr = __map_domain_page(cur_pg); memset(vaddr, 0, PAGE_SIZE); - iommu_sync_cache(vaddr, PAGE_SIZE); + sync_cache(vaddr, PAGE_SIZE); unmap_domain_page(vaddr); cur_pg++; } @@ -2763,6 +2763,7 @@ const struct iommu_ops __initconstrel in .iotlb_flush_all = iommu_flush_iotlb_all, .get_reserved_device_memory = intel_iommu_get_reserved_device_memory, .dump_p2m_table = vtd_dump_p2m_table, + .sync_cache = sync_cache, }; const struct iommu_init_ops __initconstrel intel_iommu_init_ops = { --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -121,6 +121,13 @@ extern bool untrusted_msi; int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq, const uint8_t gvec); +#define iommu_sync_cache(addr, size) ({ \ + const struct iommu_ops *ops = iommu_get_ops(); \ + \ + if ( ops->sync_cache ) \ + iommu_vcall(ops, sync_cache, addr, size); \ +}) + #endif /* !__ARCH_X86_IOMMU_H__ */ /* * Local variables: --- a/xen/include/xen/iommu.h +++ b/xen/include/xen/iommu.h @@ -250,6 +250,7 @@ struct iommu_ops { int (*setup_hpet_msi)(struct msi_desc *); int (*adjust_irq_affinities)(void); + void (*sync_cache)(const void *addr, unsigned int size); #endif /* CONFIG_X86 */ int __must_check (*suspend)(void);
xsa321/xsa321-4.13-4.patch
(application/octet-stream, 1.2 KB)
From: <[email protected]> Subject: vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -143,8 +143,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -152,8 +152,9 @@ static void sync_cache(const void *addr, if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-4.13-5.patch
(application/octet-stream, 794 B)
From: <[email protected]> Subject: x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Acked-by: Jan Beulich <[email protected]> --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -114,6 +114,11 @@ extern void alternative_branches(void); #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. *
xsa321/xsa321-4.13-6.patch
(application/octet-stream, 3 KB)
From: <[email protected]> Subject: vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -68,7 +68,6 @@ int __must_check qinval_device_iotlb_syn u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -154,7 +155,42 @@ static void sync_cache(const void *addr, addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2("", "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -51,11 +51,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-4.13-7.patch
(application/octet-stream, 5.5 KB)
From: <[email protected]> Subject: x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -58,6 +58,19 @@ static int atomic_write_ept_entry(struct write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + return 0; } @@ -278,6 +291,9 @@ static bool_t ept_split_super_page(struc break; } + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -337,6 +353,9 @@ static int ept_next_level(struct p2m_dom if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(p2m, ept_entry, e, next_level); ASSERT(rc == 0); } @@ -821,7 +840,10 @@ out: need_modify_vtd_table ) { if ( iommu_use_hap_pt(d) ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_iotlb_flush(d, _dfn(gfn), (1u << order), + (iommu_flags ? IOMMU_FLUSHF_added : 0) | + (vtd_pte_present ? IOMMU_FLUSHF_modified + : 0)); else if ( need_iommu_pt_sync(d) ) rc = iommu_flags ? iommu_legacy_map(d, _dfn(gfn), mfn, order, iommu_flags) : --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -1884,53 +1884,6 @@ static int intel_iommu_lookup_page(struc return 0; } -int iommu_pte_flush(struct domain *d, uint64_t dfn, uint64_t *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct vtd_iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - __dfn_to_daddr(dfn), - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct vtd_iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -97,10 +97,6 @@ static inline int iommu_adjust_irq_affin : 0; } -/* While VT-d specific, this must get declared in a generic header. */ -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); - static inline bool iommu_supports_x2apic(void) { return iommu_init_ops && iommu_init_ops->supports_x2apic
xsa321/xsa321-4.patch
(application/octet-stream, 1.5 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 6/9] vtd: don't assume addresses are aligned in sync_cache Current code in sync_cache assume that the address passed in is aligned to a cache line size. Fix the code to support passing in arbitrary addresses not necessarily aligned to a cache line size. This is part of XSA-321. Reported-by: Jan Beulich <[email protected]> Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v5: - build fix Changes since v3: - Avoid one cast by using a subtraction. Changes since v2: - New in this version. --- xen/drivers/passthrough/vtd/iommu.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -149,8 +149,8 @@ static int iommus_incoherent; static void sync_cache(const void *addr, unsigned int size) { - int i; - static unsigned int clflush_size = 0; + static unsigned long clflush_size = 0; + const void *end = addr + size; if ( !iommus_incoherent ) return; @@ -158,8 +158,9 @@ static void sync_cache(const void *addr, unsigned int size) if ( clflush_size == 0 ) clflush_size = get_cache_line_size(); - for ( i = 0; i < size; i += clflush_size ) - cacheline_flush((char *)addr + i); + addr -= (unsigned long)addr & (clflush_size - 1); + for ( ; addr < end; addr += clflush_size ) + cacheline_flush((char *)addr); } /* Allocate page table, return its machine address */
xsa321/xsa321-5.patch
(application/octet-stream, 1.2 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 7/9] x86/alternative: introduce alternative_2 It's based on alternative_io_2 without inputs or outputs but with an added memory clobber. This is part of XSA-321. Signed-off-by: Roger Pau Monné <[email protected]> Acked-by: Jan Beulich <[email protected]> --- Changes since v3: - Slightly reword commit message. Changes since v2: - Reword the commit message to note the addition of the memory clobber. --- xen/include/asm-x86/alternative.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xen/include/asm-x86/alternative.h b/xen/include/asm-x86/alternative.h index 92e3581bc2..8e78cc91c3 100644 --- a/xen/include/asm-x86/alternative.h +++ b/xen/include/asm-x86/alternative.h @@ -114,6 +114,11 @@ extern void alternative_branches(void); #define alternative(oldinstr, newinstr, feature) \ asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory") +#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \ + asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \ + newinstr2, feature2) \ + : : : "memory") + /* * Alternative inline assembly with input. * -- 2.26.2
xsa321/xsa321-6.patch
(application/octet-stream, 3.7 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 8/9] vtd: optimize CPU cache sync Some VT-d IOMMUs are non-coherent, which requires a cache write back in order for the changes made by the CPU to be visible to the IOMMU. This cache write back was unconditionally done using clflush, but there are other more efficient instructions to do so, hence implement support for them using the alternative framework. This is part of XSA-321. Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v5: - re-base over change to patch 6 - also drop cacheline_flush()'s declaration Changes since v2: - Do not use preprocessor directives mixed in macro arguments: it's undefined behavior. - Remove cacheline_flush as sync_cache was the only user. Changes since v1: - New in this version. --- xen/drivers/passthrough/vtd/extern.h | 1 - xen/drivers/passthrough/vtd/iommu.c | 38 ++++++++++++++++++++++++++- xen/drivers/passthrough/vtd/x86/vtd.c | 5 ---- 3 files changed, 37 insertions(+), 7 deletions(-) --- a/xen/drivers/passthrough/vtd/extern.h +++ b/xen/drivers/passthrough/vtd/extern.h @@ -68,7 +68,6 @@ int __must_check qinval_device_iotlb_sync(struct vtd_iommu *iommu, u16 did, u16 size, u64 addr); unsigned int get_cache_line_size(void); -void cacheline_flush(char *); void flush_all_cache(void); uint64_t alloc_pgtable_maddr(unsigned long npages, nodeid_t node); --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -31,6 +31,7 @@ #include <xen/pci_regs.h> #include <xen/keyhandler.h> #include <asm/msi.h> +#include <asm/nops.h> #include <asm/irq.h> #include <asm/hvm/vmx/vmx.h> #include <asm/p2m.h> @@ -160,7 +161,42 @@ static void sync_cache(const void *addr, unsigned int size) addr -= (unsigned long)addr & (clflush_size - 1); for ( ; addr < end; addr += clflush_size ) - cacheline_flush((char *)addr); +/* + * The arguments to a macro must not include preprocessor directives. Doing so + * results in undefined behavior, so we have to create some defines here in + * order to avoid it. + */ +#if defined(HAVE_AS_CLWB) +# define CLWB_ENCODING "clwb %[p]" +#elif defined(HAVE_AS_XSAVEOPT) +# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */ +#else +# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */ +#endif + +#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr)) +#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT) +# define INPUT BASE_INPUT +#else +# define INPUT(addr) "a" (addr), BASE_INPUT(addr) +#endif + /* + * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush + * + prefix than a clflush + nop, and hence the prefix is added instead + * of letting the alternative framework fill the gap by appending nops. + */ + alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]", + "data16 clflush %[p]", /* clflushopt */ + X86_FEATURE_CLFLUSHOPT, + CLWB_ENCODING, + X86_FEATURE_CLWB, /* no outputs */, + INPUT(addr)); +#undef INPUT +#undef BASE_INPUT +#undef CLWB_ENCODING + + alternative_2("", "sfence", X86_FEATURE_CLFLUSHOPT, + "sfence", X86_FEATURE_CLWB); } /* Allocate page table, return its machine address */ --- a/xen/drivers/passthrough/vtd/x86/vtd.c +++ b/xen/drivers/passthrough/vtd/x86/vtd.c @@ -52,11 +52,6 @@ unsigned int get_cache_line_size(void) return ((cpuid_ebx(1) >> 8) & 0xff) * 8; } -void cacheline_flush(char * addr) -{ - clflush(addr); -} - void flush_all_cache() { wbinvd();
xsa321/xsa321-7.patch
(application/octet-stream, 6.7 KB)
From: Roger Pau Monné <[email protected]> Subject: [PATCH v5 9/9] x86/ept: flush cache when modifying PTEs and sharing page tables Modifications made to the page tables by EPT code need to be written to memory when the page tables are shared with the IOMMU, as Intel IOMMUs can be non-coherent and thus require changes to be written to memory in order to be visible to the IOMMU. In order to achieve this make sure data is written back to memory after writing an EPT entry when the recalc bit is not set in atomic_write_ept_entry. If such bit is set, the entry will be adjusted and atomic_write_ept_entry will be called a second time without the recalc bit set. Note that when splitting a super page the new tables resulting of the split should also be written back. Failure to do so can allow devices behind the IOMMU access to the stale super page, or cause coherency issues as changes made by the processor to the page tables are not visible to the IOMMU. This allows to remove the VT-d specific iommu_pte_flush helper, since the cache write back is now performed by atomic_write_ept_entry, and hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly used method (iommu_iotlb_flush) can result in less flushes, since it might sometimes be called rightly with 0 flags, in which case it becomes a no-op. This is part of XSA-321. Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v3: - Note that iommu_iotlb_flush can be called with 0 flags. - Remove the flush from ept_set_middle_entry, should be done by the callers. Changes since v2: - Write back table in ept_set_middle_entry also. - Remove iommu_pte_flush and instead use iommu_iotlb_flush. Changes since v1: - Only flush cache when !recalc. - Move the flush into atomic_write_ept_entry. - Do a single call to iommu_flush_cache_entry that flushes the whole new table. --- xen/arch/x86/mm/p2m-ept.c | 24 ++++++++++++++- xen/drivers/passthrough/vtd/iommu.c | 47 ----------------------------- xen/include/asm-x86/iommu.h | 4 --- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/xen/arch/x86/mm/p2m-ept.c b/xen/arch/x86/mm/p2m-ept.c index 87a14f6f22..b8154a7ecc 100644 --- a/xen/arch/x86/mm/p2m-ept.c +++ b/xen/arch/x86/mm/p2m-ept.c @@ -58,6 +58,19 @@ static int atomic_write_ept_entry(struct p2m_domain *p2m, write_atomic(&entryptr->epte, new.epte); + /* + * The recalc field on the EPT is used to signal either that a + * recalculation of the EMT field is required (which doesn't effect the + * IOMMU), or a type change. Type changes can only be between ram_rw, + * logdirty and ioreq_server: changes to/from logdirty won't work well with + * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to + * aborts, and changes to/from ioreq_server are already fully flushed + * before returning to guest context (see + * XEN_DMOP_map_mem_type_to_ioreq_server). + */ + if ( !new.recalc && iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(entryptr, sizeof(*entryptr)); + return 0; } @@ -277,6 +290,9 @@ static bool_t ept_split_super_page(struct p2m_domain *p2m, break; } + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + unmap_domain_page(table); /* Even failed we should install the newly allocated ept page. */ @@ -336,6 +352,9 @@ static int ept_next_level(struct p2m_domain *p2m, bool_t read_only, if ( !next ) return GUEST_TABLE_MAP_FAILED; + if ( iommu_use_hap_pt(p2m->domain) ) + iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t)); + rc = atomic_write_ept_entry(p2m, ept_entry, e, next_level); ASSERT(rc == 0); } @@ -824,7 +843,10 @@ out: need_modify_vtd_table ) { if ( iommu_use_hap_pt(d) ) - rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present); + rc = iommu_iotlb_flush(d, _dfn(gfn), (1u << order), + (iommu_flags ? IOMMU_FLUSHF_added : 0) | + (vtd_pte_present ? IOMMU_FLUSHF_modified + : 0)); else if ( need_iommu_pt_sync(d) ) rc = iommu_flags ? iommu_legacy_map(d, _dfn(gfn), mfn, order, iommu_flags) : diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index cf651fba34..2a99cd208f 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -1887,53 +1887,6 @@ static int intel_iommu_lookup_page(struct domain *d, dfn_t dfn, mfn_t *mfn, return 0; } -int iommu_pte_flush(struct domain *d, uint64_t dfn, uint64_t *pte, - int order, int present) -{ - struct acpi_drhd_unit *drhd; - struct vtd_iommu *iommu = NULL; - struct domain_iommu *hd = dom_iommu(d); - bool_t flush_dev_iotlb; - int iommu_domid; - int rc = 0; - - iommu_sync_cache(pte, sizeof(struct dma_pte)); - - for_each_drhd_unit ( drhd ) - { - iommu = drhd->iommu; - if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) ) - continue; - - flush_dev_iotlb = !!find_ats_dev_drhd(iommu); - iommu_domid= domain_iommu_domid(d, iommu); - if ( iommu_domid == -1 ) - continue; - - rc = iommu_flush_iotlb_psi(iommu, iommu_domid, - __dfn_to_daddr(dfn), - order, !present, flush_dev_iotlb); - if ( rc > 0 ) - { - iommu_flush_write_buffer(iommu); - rc = 0; - } - } - - if ( unlikely(rc) ) - { - if ( !d->is_shutting_down && printk_ratelimit() ) - printk(XENLOG_ERR VTDPREFIX - " d%d: IOMMU pages flush failed: %d\n", - d->domain_id, rc); - - if ( !is_hardware_domain(d) ) - domain_crash(d); - } - - return rc; -} - static int __init vtd_ept_page_compatible(struct vtd_iommu *iommu) { u64 ept_cap, vtd_cap = iommu->cap; diff --git a/xen/include/asm-x86/iommu.h b/xen/include/asm-x86/iommu.h index 864e025078..6c9d5e5632 100644 --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -97,10 +97,6 @@ static inline int iommu_adjust_irq_affinities(void) : 0; } -/* While VT-d specific, this must get declared in a generic header. */ -int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte, - int order, int present); - static inline bool iommu_supports_x2apic(void) { return iommu_init_ops && iommu_init_ops->supports_x2apic -- 2.26.2