summaryrefslogtreecommitdiff
path: root/drivers/iommu/io-pgtable-arm.c
diff options
context:
space:
mode:
authorMitchel Humpherys <mitchelh@codeaurora.org>2015-06-01 15:37:23 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:12:51 -0700
commitaf687acfebc7a81d68dcee8a17d5e2cac5c3220a (patch)
treeb49b614f9e673b50fed33650f5ef2c74ab297111 /drivers/iommu/io-pgtable-arm.c
parent3b842460f124e1575227f9820b6b7b196ec7c81c (diff)
iommu: io-pgtable-arm: Correctly unmap the last level
Currently we have an optimization in place for unmapping the last level of the page tables. We do this by memset()'ing the entire last level at once rather than calling unmap on each individual page mapping at the last level. For this to work we have to pass in sizes that aren't equal to any of the supported IOMMU page sizes. However, our optimization only applies at the last level. Unmapping at the other levels still relies on the fact that unmap is only called with supported IOMMU page sizes, which means it's currently broken. Fix this by always calling unmap with an IOMMU page size, unless we're at the last level of the page tables (i.e. the size to be unmapped is less than the block size at the second-to-last level), in which case we can pass in the entire remaining size. Change-Id: Ie3716002c793af3dca51e0e3363d261f345e9e25 Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
Diffstat (limited to 'drivers/iommu/io-pgtable-arm.c')
-rw-r--r--drivers/iommu/io-pgtable-arm.c19
1 files changed, 15 insertions, 4 deletions
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 07500ca51a62..2c5fc3579ab5 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -475,7 +475,6 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
const struct iommu_gather_ops *tlb = data->iop.cfg.tlb;
void *cookie = data->iop.cookie;
size_t blk_size = ARM_LPAE_BLOCK_SIZE(lvl, data);
- size_t pgsize = iommu_pgsize(data->iop.cfg.pgsize_bitmap, iova, size);
ptep += ARM_LPAE_LVL_IDX(iova, lvl, data);
pte = *ptep;
@@ -485,7 +484,7 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
return 0;
/* If the size matches this level, we're in the right place */
- if (size == pgsize && size == blk_size) {
+ if (size == blk_size) {
*ptep = 0;
tlb->flush_pgtable(ptep, sizeof(*ptep), cookie);
@@ -545,13 +544,25 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,
size_t size)
{
- size_t unmapped;
+ size_t unmapped = 0;
struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
struct io_pgtable *iop = &data->iop;
arm_lpae_iopte *ptep = data->pgd;
int lvl = ARM_LPAE_START_LVL(data);
- unmapped = __arm_lpae_unmap(data, iova, size, lvl, ptep);
+ while (unmapped < size) {
+ size_t ret, size_to_unmap, remaining;
+
+ remaining = (size - unmapped);
+ size_to_unmap = remaining < SZ_2M
+ ? remaining
+ : iommu_pgsize(data->iop.cfg.pgsize_bitmap, iova, size);
+ ret = __arm_lpae_unmap(data, iova, size_to_unmap, lvl, ptep);
+ if (ret != size_to_unmap)
+ break;
+ unmapped += ret;
+ iova += ret;
+ }
if (unmapped)
iop->cfg.tlb->tlb_flush_all(iop->cookie);