summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Williamson <alex.williamson@redhat.com>2013-06-21 14:33:19 -0600
committerBen Hutchings <ben@decadent.org.uk>2013-07-27 05:34:15 +0100
commit6d2d894ed086b26f8e507219e9f5709526efa4b2 (patch)
treef11cd143869f1c4b6445080f84a2c13e4bcb9cbd
parenta1b0505ef3dbb8a55f85e0ae7f8114d71474b32f (diff)
iommu/amd: Only unmap large pages from the first pte
commit 60d0ca3cfd199b6612bbbbf4999a3470dad38bb1 upstream. If we use a large mapping, the expectation is that only unmaps from the first pte in the superpage are supported. Unmaps from offsets into the superpage should fail (ie. return zero sized unmap). In the current code, unmapping from an offset clears the size of the full mapping starting from an offset. For instance, if we map a 16k physically contiguous range at IOVA 0x0 with a large page, then attempt to unmap 4k at offset 12k, 4 ptes are cleared (12k - 28k) and the unmap returns 16k unmapped. This potentially incorrectly clears valid mappings and confuses drivers like VFIO that use the unmap size to release pinned pages. Fix by refusing to unmap from offsets into the page. Signed-off-by: Alex Williamson <alex.williamson@redhat.com> Signed-off-by: Joerg Roedel <joro@8bytes.org> Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
-rw-r--r--drivers/iommu/amd_iommu.c6
1 files changed, 5 insertions, 1 deletions
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c
index 07cb1a6a9584..6cc8e67c1282 100644
--- a/drivers/iommu/amd_iommu.c
+++ b/drivers/iommu/amd_iommu.c
@@ -1076,6 +1076,10 @@ static unsigned long iommu_unmap_page(struct protection_domain *dom,
/* Large PTE found which maps this address */
unmap_size = PTE_PAGE_SIZE(*pte);
+
+ /* Only unmap from the first pte in the page */
+ if ((unmap_size - 1) & bus_addr)
+ break;
count = PAGE_SIZE_PTE_COUNT(unmap_size);
for (i = 0; i < count; i++)
pte[i] = 0ULL;
@@ -1085,7 +1089,7 @@ static unsigned long iommu_unmap_page(struct protection_domain *dom,
unmapped += unmap_size;
}
- BUG_ON(!is_power_of_2(unmapped));
+ BUG_ON(unmapped && !is_power_of_2(unmapped));
return unmapped;
}