From: Lu Baolu <baolu.lu-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
To: David Woodhouse <dwmw2-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org>,
Joerg Roedel <joro-zLv9SwRftAIdnm+yROfE0A@public.gmane.org>
Cc: kevin.tian-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
ashok.raj-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
Konrad Rzeszutek Wilk
<konrad.wilk-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>,
alan.cox-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
Robin Murphy <robin.murphy-5wv7dgnIgG8@public.gmane.org>,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
pengfei.xu-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
jacob.jun.pan-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
mika.westerberg-VuQAYsv1563Yd54FQh9/CA@public.gmane.org,
Christoph Hellwig <hch-jcswGhMUV9g@public.gmane.org>
Subject: [PATCH v3 10/10] iommu/vt-d: Use bounce buffer for untrusted devices
Date: Sun, 21 Apr 2019 09:17:19 +0800 [thread overview]
Message-ID: <20190421011719.14909-11-baolu.lu@linux.intel.com> (raw)
In-Reply-To: <20190421011719.14909-1-baolu.lu-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
The Intel VT-d hardware uses paging for DMA remapping.
The minimum mapped window is a page size. The device
drivers may map buffers not filling the whole IOMMU
window. This allows the device to access to possibly
unrelated memory and a malicious device could exploit
this to perform DMA attacks. To address this, the
Intel IOMMU driver will use bounce pages for those
buffers which don't fill a whole IOMMU page.
Cc: Ashok Raj <ashok.raj-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
Cc: Jacob Pan <jacob.jun.pan-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Cc: Kevin Tian <kevin.tian-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
Signed-off-by: Lu Baolu <baolu.lu-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Tested-by: Xu Pengfei <pengfei.xu-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
Tested-by: Mika Westerberg <mika.westerberg-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
drivers/iommu/intel-iommu.c | 138 ++++++++++++++++++++++++++----------
1 file changed, 99 insertions(+), 39 deletions(-)
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index ed941ec9b9d5..52ccbd3f1425 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -52,6 +52,7 @@
#include <asm/irq_remapping.h>
#include <asm/cacheflush.h>
#include <asm/iommu.h>
+#include <trace/events/intel_iommu.h>
#include "irq_remapping.h"
#include "intel-pasid.h"
@@ -3410,15 +3411,17 @@ static int iommu_no_mapping(struct device *dev)
}
static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
- size_t size, int dir, u64 dma_mask)
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, u64 dma_mask)
{
struct dmar_domain *domain;
- phys_addr_t start_paddr;
+ dma_addr_t start_dma;
unsigned long iova_pfn;
int prot = 0;
int ret;
struct intel_iommu *iommu;
unsigned long paddr_pfn = paddr >> PAGE_SHIFT;
+ unsigned long nrpages;
BUG_ON(dir == DMA_NONE);
@@ -3430,9 +3433,10 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
return DMA_MAPPING_ERROR;
iommu = domain_get_iommu(domain);
- size = aligned_nrpages(paddr, size);
+ nrpages = aligned_nrpages(paddr, size);
- iova_pfn = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), dma_mask);
+ iova_pfn = intel_alloc_iova(dev, domain,
+ dma_to_mm_pfn(nrpages), dma_mask);
if (!iova_pfn)
goto error;
@@ -3445,24 +3449,33 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
prot |= DMA_PTE_READ;
if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
prot |= DMA_PTE_WRITE;
+
+ start_dma = (dma_addr_t)iova_pfn << PAGE_SHIFT;
+ start_dma += offset_in_page(paddr);
+
/*
* paddr - (paddr + size) might be partial page, we should map the whole
* page. Note: if two part of one page are separately mapped, we
* might have two guest_addr mapping to the same host paddr, but this
* is not a big problem
*/
- ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
- mm_to_dma_pfn(paddr_pfn), size, prot);
+ if (device_needs_bounce(dev)) {
+ ret = iommu_bounce_map(dev, start_dma, paddr, size, dir, attrs);
+ if (!ret)
+ trace_bounce_map_single(dev, start_dma, paddr, size);
+ } else {
+ ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
+ mm_to_dma_pfn(paddr_pfn),
+ nrpages, prot);
+ }
if (ret)
goto error;
- start_paddr = (phys_addr_t)iova_pfn << PAGE_SHIFT;
- start_paddr += paddr & ~PAGE_MASK;
- return start_paddr;
-
+ return start_dma;
error:
if (iova_pfn)
- free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size));
+ free_iova_fast(&domain->iovad, iova_pfn,
+ dma_to_mm_pfn(nrpages));
dev_err(dev, "Device request: %zx@%llx dir %d --- failed\n",
size, (unsigned long long)paddr, dir);
return DMA_MAPPING_ERROR;
@@ -3474,44 +3487,79 @@ static dma_addr_t intel_map_page(struct device *dev, struct page *page,
unsigned long attrs)
{
return __intel_map_single(dev, page_to_phys(page) + offset, size,
- dir, *dev->dma_mask);
+ dir, attrs, *dev->dma_mask);
}
static dma_addr_t intel_map_resource(struct device *dev, phys_addr_t phys_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- return __intel_map_single(dev, phys_addr, size, dir, *dev->dma_mask);
+ return __intel_map_single(dev, phys_addr, size,
+ dir, attrs, *dev->dma_mask);
}
-static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size)
+static void
+intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size,
+ struct scatterlist *sglist, int nelems,
+ enum dma_data_direction dir, unsigned long attrs)
{
struct dmar_domain *domain;
unsigned long start_pfn, last_pfn;
- unsigned long nrpages;
+ unsigned long nrpages = 0;
unsigned long iova_pfn;
struct intel_iommu *iommu;
- struct page *freelist;
+ struct page *freelist = NULL;
+ struct pci_dev *pdev = NULL;
if (iommu_no_mapping(dev))
return;
+ if (dev_is_pci(dev))
+ pdev = to_pci_dev(dev);
+
domain = find_domain(dev);
BUG_ON(!domain);
iommu = domain_get_iommu(domain);
- iova_pfn = IOVA_PFN(dev_addr);
-
- nrpages = aligned_nrpages(dev_addr, size);
- start_pfn = mm_to_dma_pfn(iova_pfn);
- last_pfn = start_pfn + nrpages - 1;
-
- dev_dbg(dev, "Device unmapping: pfn %lx-%lx\n", start_pfn, last_pfn);
+ if (sglist) {
+ struct scatterlist *sg;
+ int i;
- freelist = domain_unmap(domain, start_pfn, last_pfn);
+ dev_addr = sg_dma_address(sglist) & PAGE_MASK;
+ iova_pfn = IOVA_PFN(dev_addr);
+ for_each_sg(sglist, sg, nelems, i) {
+ nrpages += aligned_nrpages(sg_dma_address(sg),
+ sg_dma_len(sg));
+ }
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev))
+ for_each_sg(sglist, sg, nelems, i) {
+ iommu_bounce_unmap(dev, sg_dma_address(sg),
+ sg->length, dir, attrs);
+ trace_bounce_unmap_sg(dev, i, nelems,
+ sg_dma_address(sg),
+ sg_phys(sg), sg->length);
+ }
+ else
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ } else {
+ iova_pfn = IOVA_PFN(dev_addr);
+ nrpages = aligned_nrpages(dev_addr, size);
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev)) {
+ iommu_bounce_unmap(dev, dev_addr, size, dir, attrs);
+ trace_bounce_unmap_single(dev, dev_addr, size);
+ } else {
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ }
+ }
- if (intel_iommu_strict) {
+ if (intel_iommu_strict || (pdev && pdev->untrusted)) {
iommu_flush_iotlb_psi(iommu, domain, start_pfn,
nrpages, !freelist, 0);
/* free iova */
@@ -3531,7 +3579,7 @@ static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- intel_unmap(dev, dev_addr, size);
+ intel_unmap(dev, dev_addr, size, NULL, 0, dir, attrs);
}
static void *intel_alloc_coherent(struct device *dev, size_t size,
@@ -3572,7 +3620,7 @@ static void *intel_alloc_coherent(struct device *dev, size_t size,
memset(page_address(page), 0, size);
*dma_handle = __intel_map_single(dev, page_to_phys(page), size,
- DMA_BIDIRECTIONAL,
+ DMA_BIDIRECTIONAL, attrs,
dev->coherent_dma_mask);
if (*dma_handle != DMA_MAPPING_ERROR)
return page_address(page);
@@ -3591,7 +3639,7 @@ static void intel_free_coherent(struct device *dev, size_t size, void *vaddr,
size = PAGE_ALIGN(size);
order = get_order(size);
- intel_unmap(dev, dma_handle, size);
+ intel_unmap(dev, dma_handle, size, NULL, 0, 0, attrs);
if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT))
__free_pages(page, order);
}
@@ -3600,16 +3648,7 @@ static void intel_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction dir,
unsigned long attrs)
{
- dma_addr_t startaddr = sg_dma_address(sglist) & PAGE_MASK;
- unsigned long nrpages = 0;
- struct scatterlist *sg;
- int i;
-
- for_each_sg(sglist, sg, nelems, i) {
- nrpages += aligned_nrpages(sg_dma_address(sg), sg_dma_len(sg));
- }
-
- intel_unmap(dev, startaddr, nrpages << VTD_PAGE_SHIFT);
+ intel_unmap(dev, 0, 0, sglist, nelems, dir, attrs);
}
static int intel_nontranslate_map_sg(struct device *hddev,
@@ -3671,7 +3710,28 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele
start_vpfn = mm_to_dma_pfn(iova_pfn);
- ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ if (device_needs_bounce(dev)) {
+ for_each_sg(sglist, sg, nelems, i) {
+ unsigned int pgoff = offset_in_page(sg->offset);
+ dma_addr_t addr;
+
+ addr = ((dma_addr_t)iova_pfn << PAGE_SHIFT) + pgoff;
+ ret = iommu_bounce_map(dev, addr, sg_phys(sg),
+ sg->length, dir, attrs);
+ if (ret)
+ break;
+
+ trace_bounce_map_sg(dev, i, nelems, addr,
+ sg_phys(sg), sg->length);
+
+ sg->dma_address = addr;
+ sg->dma_length = sg->length;
+ iova_pfn += aligned_nrpages(sg->offset, sg->length);
+ }
+ } else {
+ ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ }
+
if (unlikely(ret)) {
dma_pte_free_pagetable(domain, start_vpfn,
start_vpfn + size - 1,
--
2.17.1
WARNING: multiple messages have this Message-ID (diff)
From: Lu Baolu <baolu.lu@linux.intel.com>
To: David Woodhouse <dwmw2@infradead.org>, Joerg Roedel <joro@8bytes.org>
Cc: kevin.tian@intel.com, ashok.raj@intel.com,
Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>,
alan.cox@intel.com, Robin Murphy <robin.murphy@arm.com>,
iommu@lists.linux-foundation.org, linux-kernel@vger.kernel.org,
pengfei.xu@intel.com, jacob.jun.pan@intel.com,
mika.westerberg@linux.intel.com, Christoph Hellwig <hch@lst.de>
Subject: [PATCH v3 10/10] iommu/vt-d: Use bounce buffer for untrusted devices
Date: Sun, 21 Apr 2019 09:17:19 +0800 [thread overview]
Message-ID: <20190421011719.14909-11-baolu.lu@linux.intel.com> (raw)
Message-ID: <20190421011719.eVESgUomFpRsdg4BFFdaC6dHv6M_U2VsyBeA1lYpiiQ@z> (raw)
In-Reply-To: <20190421011719.14909-1-baolu.lu@linux.intel.com>
The Intel VT-d hardware uses paging for DMA remapping.
The minimum mapped window is a page size. The device
drivers may map buffers not filling the whole IOMMU
window. This allows the device to access to possibly
unrelated memory and a malicious device could exploit
this to perform DMA attacks. To address this, the
Intel IOMMU driver will use bounce pages for those
buffers which don't fill a whole IOMMU page.
Cc: Ashok Raj <ashok.raj@intel.com>
Cc: Jacob Pan <jacob.jun.pan@linux.intel.com>
Cc: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Tested-by: Xu Pengfei <pengfei.xu@intel.com>
Tested-by: Mika Westerberg <mika.westerberg@intel.com>
---
drivers/iommu/intel-iommu.c | 138 ++++++++++++++++++++++++++----------
1 file changed, 99 insertions(+), 39 deletions(-)
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index ed941ec9b9d5..52ccbd3f1425 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -52,6 +52,7 @@
#include <asm/irq_remapping.h>
#include <asm/cacheflush.h>
#include <asm/iommu.h>
+#include <trace/events/intel_iommu.h>
#include "irq_remapping.h"
#include "intel-pasid.h"
@@ -3410,15 +3411,17 @@ static int iommu_no_mapping(struct device *dev)
}
static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
- size_t size, int dir, u64 dma_mask)
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, u64 dma_mask)
{
struct dmar_domain *domain;
- phys_addr_t start_paddr;
+ dma_addr_t start_dma;
unsigned long iova_pfn;
int prot = 0;
int ret;
struct intel_iommu *iommu;
unsigned long paddr_pfn = paddr >> PAGE_SHIFT;
+ unsigned long nrpages;
BUG_ON(dir == DMA_NONE);
@@ -3430,9 +3433,10 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
return DMA_MAPPING_ERROR;
iommu = domain_get_iommu(domain);
- size = aligned_nrpages(paddr, size);
+ nrpages = aligned_nrpages(paddr, size);
- iova_pfn = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), dma_mask);
+ iova_pfn = intel_alloc_iova(dev, domain,
+ dma_to_mm_pfn(nrpages), dma_mask);
if (!iova_pfn)
goto error;
@@ -3445,24 +3449,33 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
prot |= DMA_PTE_READ;
if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
prot |= DMA_PTE_WRITE;
+
+ start_dma = (dma_addr_t)iova_pfn << PAGE_SHIFT;
+ start_dma += offset_in_page(paddr);
+
/*
* paddr - (paddr + size) might be partial page, we should map the whole
* page. Note: if two part of one page are separately mapped, we
* might have two guest_addr mapping to the same host paddr, but this
* is not a big problem
*/
- ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
- mm_to_dma_pfn(paddr_pfn), size, prot);
+ if (device_needs_bounce(dev)) {
+ ret = iommu_bounce_map(dev, start_dma, paddr, size, dir, attrs);
+ if (!ret)
+ trace_bounce_map_single(dev, start_dma, paddr, size);
+ } else {
+ ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
+ mm_to_dma_pfn(paddr_pfn),
+ nrpages, prot);
+ }
if (ret)
goto error;
- start_paddr = (phys_addr_t)iova_pfn << PAGE_SHIFT;
- start_paddr += paddr & ~PAGE_MASK;
- return start_paddr;
-
+ return start_dma;
error:
if (iova_pfn)
- free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size));
+ free_iova_fast(&domain->iovad, iova_pfn,
+ dma_to_mm_pfn(nrpages));
dev_err(dev, "Device request: %zx@%llx dir %d --- failed\n",
size, (unsigned long long)paddr, dir);
return DMA_MAPPING_ERROR;
@@ -3474,44 +3487,79 @@ static dma_addr_t intel_map_page(struct device *dev, struct page *page,
unsigned long attrs)
{
return __intel_map_single(dev, page_to_phys(page) + offset, size,
- dir, *dev->dma_mask);
+ dir, attrs, *dev->dma_mask);
}
static dma_addr_t intel_map_resource(struct device *dev, phys_addr_t phys_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- return __intel_map_single(dev, phys_addr, size, dir, *dev->dma_mask);
+ return __intel_map_single(dev, phys_addr, size,
+ dir, attrs, *dev->dma_mask);
}
-static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size)
+static void
+intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size,
+ struct scatterlist *sglist, int nelems,
+ enum dma_data_direction dir, unsigned long attrs)
{
struct dmar_domain *domain;
unsigned long start_pfn, last_pfn;
- unsigned long nrpages;
+ unsigned long nrpages = 0;
unsigned long iova_pfn;
struct intel_iommu *iommu;
- struct page *freelist;
+ struct page *freelist = NULL;
+ struct pci_dev *pdev = NULL;
if (iommu_no_mapping(dev))
return;
+ if (dev_is_pci(dev))
+ pdev = to_pci_dev(dev);
+
domain = find_domain(dev);
BUG_ON(!domain);
iommu = domain_get_iommu(domain);
- iova_pfn = IOVA_PFN(dev_addr);
-
- nrpages = aligned_nrpages(dev_addr, size);
- start_pfn = mm_to_dma_pfn(iova_pfn);
- last_pfn = start_pfn + nrpages - 1;
-
- dev_dbg(dev, "Device unmapping: pfn %lx-%lx\n", start_pfn, last_pfn);
+ if (sglist) {
+ struct scatterlist *sg;
+ int i;
- freelist = domain_unmap(domain, start_pfn, last_pfn);
+ dev_addr = sg_dma_address(sglist) & PAGE_MASK;
+ iova_pfn = IOVA_PFN(dev_addr);
+ for_each_sg(sglist, sg, nelems, i) {
+ nrpages += aligned_nrpages(sg_dma_address(sg),
+ sg_dma_len(sg));
+ }
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev))
+ for_each_sg(sglist, sg, nelems, i) {
+ iommu_bounce_unmap(dev, sg_dma_address(sg),
+ sg->length, dir, attrs);
+ trace_bounce_unmap_sg(dev, i, nelems,
+ sg_dma_address(sg),
+ sg_phys(sg), sg->length);
+ }
+ else
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ } else {
+ iova_pfn = IOVA_PFN(dev_addr);
+ nrpages = aligned_nrpages(dev_addr, size);
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev)) {
+ iommu_bounce_unmap(dev, dev_addr, size, dir, attrs);
+ trace_bounce_unmap_single(dev, dev_addr, size);
+ } else {
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ }
+ }
- if (intel_iommu_strict) {
+ if (intel_iommu_strict || (pdev && pdev->untrusted)) {
iommu_flush_iotlb_psi(iommu, domain, start_pfn,
nrpages, !freelist, 0);
/* free iova */
@@ -3531,7 +3579,7 @@ static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- intel_unmap(dev, dev_addr, size);
+ intel_unmap(dev, dev_addr, size, NULL, 0, dir, attrs);
}
static void *intel_alloc_coherent(struct device *dev, size_t size,
@@ -3572,7 +3620,7 @@ static void *intel_alloc_coherent(struct device *dev, size_t size,
memset(page_address(page), 0, size);
*dma_handle = __intel_map_single(dev, page_to_phys(page), size,
- DMA_BIDIRECTIONAL,
+ DMA_BIDIRECTIONAL, attrs,
dev->coherent_dma_mask);
if (*dma_handle != DMA_MAPPING_ERROR)
return page_address(page);
@@ -3591,7 +3639,7 @@ static void intel_free_coherent(struct device *dev, size_t size, void *vaddr,
size = PAGE_ALIGN(size);
order = get_order(size);
- intel_unmap(dev, dma_handle, size);
+ intel_unmap(dev, dma_handle, size, NULL, 0, 0, attrs);
if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT))
__free_pages(page, order);
}
@@ -3600,16 +3648,7 @@ static void intel_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction dir,
unsigned long attrs)
{
- dma_addr_t startaddr = sg_dma_address(sglist) & PAGE_MASK;
- unsigned long nrpages = 0;
- struct scatterlist *sg;
- int i;
-
- for_each_sg(sglist, sg, nelems, i) {
- nrpages += aligned_nrpages(sg_dma_address(sg), sg_dma_len(sg));
- }
-
- intel_unmap(dev, startaddr, nrpages << VTD_PAGE_SHIFT);
+ intel_unmap(dev, 0, 0, sglist, nelems, dir, attrs);
}
static int intel_nontranslate_map_sg(struct device *hddev,
@@ -3671,7 +3710,28 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele
start_vpfn = mm_to_dma_pfn(iova_pfn);
- ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ if (device_needs_bounce(dev)) {
+ for_each_sg(sglist, sg, nelems, i) {
+ unsigned int pgoff = offset_in_page(sg->offset);
+ dma_addr_t addr;
+
+ addr = ((dma_addr_t)iova_pfn << PAGE_SHIFT) + pgoff;
+ ret = iommu_bounce_map(dev, addr, sg_phys(sg),
+ sg->length, dir, attrs);
+ if (ret)
+ break;
+
+ trace_bounce_map_sg(dev, i, nelems, addr,
+ sg_phys(sg), sg->length);
+
+ sg->dma_address = addr;
+ sg->dma_length = sg->length;
+ iova_pfn += aligned_nrpages(sg->offset, sg->length);
+ }
+ } else {
+ ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ }
+
if (unlikely(ret)) {
dma_pte_free_pagetable(domain, start_vpfn,
start_vpfn + size - 1,
--
2.17.1
_______________________________________________
iommu mailing list
iommu@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu
WARNING: multiple messages have this Message-ID (diff)
From: Lu Baolu <baolu.lu@linux.intel.com>
To: David Woodhouse <dwmw2@infradead.org>, Joerg Roedel <joro@8bytes.org>
Cc: ashok.raj@intel.com, jacob.jun.pan@intel.com, alan.cox@intel.com,
kevin.tian@intel.com, mika.westerberg@linux.intel.com,
pengfei.xu@intel.com,
Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>,
Christoph Hellwig <hch@lst.de>,
Marek Szyprowski <m.szyprowski@samsung.com>,
Robin Murphy <robin.murphy@arm.com>,
iommu@lists.linux-foundation.org, linux-kernel@vger.kernel.org,
Lu Baolu <baolu.lu@linux.intel.com>,
Jacob Pan <jacob.jun.pan@linux.intel.com>
Subject: [PATCH v3 10/10] iommu/vt-d: Use bounce buffer for untrusted devices
Date: Sun, 21 Apr 2019 09:17:19 +0800 [thread overview]
Message-ID: <20190421011719.14909-11-baolu.lu@linux.intel.com> (raw)
In-Reply-To: <20190421011719.14909-1-baolu.lu@linux.intel.com>
The Intel VT-d hardware uses paging for DMA remapping.
The minimum mapped window is a page size. The device
drivers may map buffers not filling the whole IOMMU
window. This allows the device to access to possibly
unrelated memory and a malicious device could exploit
this to perform DMA attacks. To address this, the
Intel IOMMU driver will use bounce pages for those
buffers which don't fill a whole IOMMU page.
Cc: Ashok Raj <ashok.raj@intel.com>
Cc: Jacob Pan <jacob.jun.pan@linux.intel.com>
Cc: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Tested-by: Xu Pengfei <pengfei.xu@intel.com>
Tested-by: Mika Westerberg <mika.westerberg@intel.com>
---
drivers/iommu/intel-iommu.c | 138 ++++++++++++++++++++++++++----------
1 file changed, 99 insertions(+), 39 deletions(-)
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index ed941ec9b9d5..52ccbd3f1425 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -52,6 +52,7 @@
#include <asm/irq_remapping.h>
#include <asm/cacheflush.h>
#include <asm/iommu.h>
+#include <trace/events/intel_iommu.h>
#include "irq_remapping.h"
#include "intel-pasid.h"
@@ -3410,15 +3411,17 @@ static int iommu_no_mapping(struct device *dev)
}
static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
- size_t size, int dir, u64 dma_mask)
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, u64 dma_mask)
{
struct dmar_domain *domain;
- phys_addr_t start_paddr;
+ dma_addr_t start_dma;
unsigned long iova_pfn;
int prot = 0;
int ret;
struct intel_iommu *iommu;
unsigned long paddr_pfn = paddr >> PAGE_SHIFT;
+ unsigned long nrpages;
BUG_ON(dir == DMA_NONE);
@@ -3430,9 +3433,10 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
return DMA_MAPPING_ERROR;
iommu = domain_get_iommu(domain);
- size = aligned_nrpages(paddr, size);
+ nrpages = aligned_nrpages(paddr, size);
- iova_pfn = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), dma_mask);
+ iova_pfn = intel_alloc_iova(dev, domain,
+ dma_to_mm_pfn(nrpages), dma_mask);
if (!iova_pfn)
goto error;
@@ -3445,24 +3449,33 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
prot |= DMA_PTE_READ;
if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
prot |= DMA_PTE_WRITE;
+
+ start_dma = (dma_addr_t)iova_pfn << PAGE_SHIFT;
+ start_dma += offset_in_page(paddr);
+
/*
* paddr - (paddr + size) might be partial page, we should map the whole
* page. Note: if two part of one page are separately mapped, we
* might have two guest_addr mapping to the same host paddr, but this
* is not a big problem
*/
- ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
- mm_to_dma_pfn(paddr_pfn), size, prot);
+ if (device_needs_bounce(dev)) {
+ ret = iommu_bounce_map(dev, start_dma, paddr, size, dir, attrs);
+ if (!ret)
+ trace_bounce_map_single(dev, start_dma, paddr, size);
+ } else {
+ ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn),
+ mm_to_dma_pfn(paddr_pfn),
+ nrpages, prot);
+ }
if (ret)
goto error;
- start_paddr = (phys_addr_t)iova_pfn << PAGE_SHIFT;
- start_paddr += paddr & ~PAGE_MASK;
- return start_paddr;
-
+ return start_dma;
error:
if (iova_pfn)
- free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size));
+ free_iova_fast(&domain->iovad, iova_pfn,
+ dma_to_mm_pfn(nrpages));
dev_err(dev, "Device request: %zx@%llx dir %d --- failed\n",
size, (unsigned long long)paddr, dir);
return DMA_MAPPING_ERROR;
@@ -3474,44 +3487,79 @@ static dma_addr_t intel_map_page(struct device *dev, struct page *page,
unsigned long attrs)
{
return __intel_map_single(dev, page_to_phys(page) + offset, size,
- dir, *dev->dma_mask);
+ dir, attrs, *dev->dma_mask);
}
static dma_addr_t intel_map_resource(struct device *dev, phys_addr_t phys_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- return __intel_map_single(dev, phys_addr, size, dir, *dev->dma_mask);
+ return __intel_map_single(dev, phys_addr, size,
+ dir, attrs, *dev->dma_mask);
}
-static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size)
+static void
+intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size,
+ struct scatterlist *sglist, int nelems,
+ enum dma_data_direction dir, unsigned long attrs)
{
struct dmar_domain *domain;
unsigned long start_pfn, last_pfn;
- unsigned long nrpages;
+ unsigned long nrpages = 0;
unsigned long iova_pfn;
struct intel_iommu *iommu;
- struct page *freelist;
+ struct page *freelist = NULL;
+ struct pci_dev *pdev = NULL;
if (iommu_no_mapping(dev))
return;
+ if (dev_is_pci(dev))
+ pdev = to_pci_dev(dev);
+
domain = find_domain(dev);
BUG_ON(!domain);
iommu = domain_get_iommu(domain);
- iova_pfn = IOVA_PFN(dev_addr);
-
- nrpages = aligned_nrpages(dev_addr, size);
- start_pfn = mm_to_dma_pfn(iova_pfn);
- last_pfn = start_pfn + nrpages - 1;
-
- dev_dbg(dev, "Device unmapping: pfn %lx-%lx\n", start_pfn, last_pfn);
+ if (sglist) {
+ struct scatterlist *sg;
+ int i;
- freelist = domain_unmap(domain, start_pfn, last_pfn);
+ dev_addr = sg_dma_address(sglist) & PAGE_MASK;
+ iova_pfn = IOVA_PFN(dev_addr);
+ for_each_sg(sglist, sg, nelems, i) {
+ nrpages += aligned_nrpages(sg_dma_address(sg),
+ sg_dma_len(sg));
+ }
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev))
+ for_each_sg(sglist, sg, nelems, i) {
+ iommu_bounce_unmap(dev, sg_dma_address(sg),
+ sg->length, dir, attrs);
+ trace_bounce_unmap_sg(dev, i, nelems,
+ sg_dma_address(sg),
+ sg_phys(sg), sg->length);
+ }
+ else
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ } else {
+ iova_pfn = IOVA_PFN(dev_addr);
+ nrpages = aligned_nrpages(dev_addr, size);
+ start_pfn = mm_to_dma_pfn(iova_pfn);
+ last_pfn = start_pfn + nrpages - 1;
+
+ if (device_needs_bounce(dev)) {
+ iommu_bounce_unmap(dev, dev_addr, size, dir, attrs);
+ trace_bounce_unmap_single(dev, dev_addr, size);
+ } else {
+ freelist = domain_unmap(domain, start_pfn, last_pfn);
+ }
+ }
- if (intel_iommu_strict) {
+ if (intel_iommu_strict || (pdev && pdev->untrusted)) {
iommu_flush_iotlb_psi(iommu, domain, start_pfn,
nrpages, !freelist, 0);
/* free iova */
@@ -3531,7 +3579,7 @@ static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
- intel_unmap(dev, dev_addr, size);
+ intel_unmap(dev, dev_addr, size, NULL, 0, dir, attrs);
}
static void *intel_alloc_coherent(struct device *dev, size_t size,
@@ -3572,7 +3620,7 @@ static void *intel_alloc_coherent(struct device *dev, size_t size,
memset(page_address(page), 0, size);
*dma_handle = __intel_map_single(dev, page_to_phys(page), size,
- DMA_BIDIRECTIONAL,
+ DMA_BIDIRECTIONAL, attrs,
dev->coherent_dma_mask);
if (*dma_handle != DMA_MAPPING_ERROR)
return page_address(page);
@@ -3591,7 +3639,7 @@ static void intel_free_coherent(struct device *dev, size_t size, void *vaddr,
size = PAGE_ALIGN(size);
order = get_order(size);
- intel_unmap(dev, dma_handle, size);
+ intel_unmap(dev, dma_handle, size, NULL, 0, 0, attrs);
if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT))
__free_pages(page, order);
}
@@ -3600,16 +3648,7 @@ static void intel_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction dir,
unsigned long attrs)
{
- dma_addr_t startaddr = sg_dma_address(sglist) & PAGE_MASK;
- unsigned long nrpages = 0;
- struct scatterlist *sg;
- int i;
-
- for_each_sg(sglist, sg, nelems, i) {
- nrpages += aligned_nrpages(sg_dma_address(sg), sg_dma_len(sg));
- }
-
- intel_unmap(dev, startaddr, nrpages << VTD_PAGE_SHIFT);
+ intel_unmap(dev, 0, 0, sglist, nelems, dir, attrs);
}
static int intel_nontranslate_map_sg(struct device *hddev,
@@ -3671,7 +3710,28 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele
start_vpfn = mm_to_dma_pfn(iova_pfn);
- ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ if (device_needs_bounce(dev)) {
+ for_each_sg(sglist, sg, nelems, i) {
+ unsigned int pgoff = offset_in_page(sg->offset);
+ dma_addr_t addr;
+
+ addr = ((dma_addr_t)iova_pfn << PAGE_SHIFT) + pgoff;
+ ret = iommu_bounce_map(dev, addr, sg_phys(sg),
+ sg->length, dir, attrs);
+ if (ret)
+ break;
+
+ trace_bounce_map_sg(dev, i, nelems, addr,
+ sg_phys(sg), sg->length);
+
+ sg->dma_address = addr;
+ sg->dma_length = sg->length;
+ iova_pfn += aligned_nrpages(sg->offset, sg->length);
+ }
+ } else {
+ ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
+ }
+
if (unlikely(ret)) {
dma_pte_free_pagetable(domain, start_vpfn,
start_vpfn + size - 1,
--
2.17.1
next prev parent reply other threads:[~2019-04-21 1:17 UTC|newest]
Thread overview: 86+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-04-21 1:17 [PATCH v3 00/10] iommu: Bounce page for untrusted devices Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
[not found] ` <20190421011719.14909-1-baolu.lu-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
2019-04-21 1:17 ` [PATCH v3 01/10] iommu: Add helper to get minimal page size of domain Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-29 10:55 ` Robin Murphy
2019-04-29 10:55 ` Robin Murphy
2019-04-30 0:40 ` Lu Baolu
2019-04-30 0:40 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 02/10] swiotlb: Factor out slot allocation and free Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-22 16:45 ` Christoph Hellwig
2019-04-22 16:45 ` Christoph Hellwig
2019-04-23 1:58 ` Lu Baolu
2019-04-23 1:58 ` Lu Baolu
2019-04-23 6:12 ` Christoph Hellwig
2019-04-23 6:12 ` Christoph Hellwig
2019-04-23 7:32 ` Lu Baolu
2019-04-23 7:32 ` Lu Baolu
2019-04-24 14:45 ` Christoph Hellwig
2019-04-24 14:45 ` Christoph Hellwig
[not found] ` <20190424144532.GA21480-jcswGhMUV9g@public.gmane.org>
2019-04-25 2:07 ` Lu Baolu
2019-04-25 2:07 ` Lu Baolu
2019-04-25 2:07 ` Lu Baolu
2019-04-26 15:04 ` Christoph Hellwig
2019-04-26 15:04 ` Christoph Hellwig
2019-04-29 5:10 ` Lu Baolu
2019-04-29 5:10 ` Lu Baolu
2019-04-29 11:06 ` Robin Murphy
2019-04-29 11:06 ` Robin Murphy
2019-04-29 11:44 ` Christoph Hellwig
2019-04-29 11:44 ` Christoph Hellwig
2019-05-06 1:54 ` Lu Baolu
2019-05-06 1:54 ` Lu Baolu
2019-05-13 7:05 ` Christoph Hellwig
2019-05-13 7:05 ` Christoph Hellwig
2019-05-16 1:53 ` Lu Baolu
2019-05-16 1:53 ` Lu Baolu
2019-04-30 2:02 ` Lu Baolu
2019-04-30 2:02 ` Lu Baolu
2019-04-30 9:53 ` Robin Murphy
2019-04-30 9:53 ` Robin Murphy
2019-05-02 1:47 ` Lu Baolu
2019-05-02 1:47 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 03/10] swiotlb: Limit tlb address range inside slot pool Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 04/10] swiotlb: Extend swiotlb to support page bounce Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 05/10] iommu: Add bounce page APIs Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 06/10] iommu/vt-d: Add trace events for domain map/unmap Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 07/10] iommu/vt-d: Keep swiotlb on if bounce page is necessary Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-22 16:47 ` Christoph Hellwig
2019-04-22 16:47 ` Christoph Hellwig
2019-04-23 2:00 ` Lu Baolu
2019-04-23 2:00 ` Lu Baolu
2019-04-21 1:17 ` [PATCH v3 08/10] iommu/vt-d: Check whether device requires bounce buffer Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-22 16:47 ` Christoph Hellwig
2019-04-22 16:47 ` Christoph Hellwig
[not found] ` <20190422164755.GC31181-jcswGhMUV9g@public.gmane.org>
2019-04-23 2:03 ` Lu Baolu
2019-04-23 2:03 ` Lu Baolu
2019-04-23 2:03 ` Lu Baolu
2019-04-23 6:08 ` Christoph Hellwig
2019-04-23 6:08 ` Christoph Hellwig
2019-04-23 7:35 ` Lu Baolu
2019-04-23 7:35 ` Lu Baolu
[not found] ` <e11489cf-87cc-a956-7c6c-ffdd26a4e3ec-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
2019-04-24 18:27 ` Konrad Rzeszutek Wilk
2019-04-24 18:27 ` Konrad Rzeszutek Wilk
2019-04-24 18:27 ` Konrad Rzeszutek Wilk
2019-04-21 1:17 ` [PATCH v3 09/10] iommu/vt-d: Add dma sync ops for untrusted devices Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu
2019-04-21 1:17 ` Lu Baolu [this message]
2019-04-21 1:17 ` [PATCH v3 10/10] iommu/vt-d: Use bounce buffer " Lu Baolu
2019-04-21 1:17 ` Lu Baolu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190421011719.14909-11-baolu.lu@linux.intel.com \
--to=baolu.lu-vuqaysv1563yd54fqh9/ca@public.gmane.org \
--cc=alan.cox-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
--cc=ashok.raj-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
--cc=dwmw2-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org \
--cc=hch-jcswGhMUV9g@public.gmane.org \
--cc=iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA@public.gmane.org \
--cc=jacob.jun.pan-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
--cc=joro-zLv9SwRftAIdnm+yROfE0A@public.gmane.org \
--cc=kevin.tian-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
--cc=konrad.wilk-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org \
--cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=mika.westerberg-VuQAYsv1563Yd54FQh9/CA@public.gmane.org \
--cc=pengfei.xu-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
--cc=robin.murphy-5wv7dgnIgG8@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.