summaryrefslogtreecommitdiff
path: root/drivers/iommu/tegra-smmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iommu/tegra-smmu.c')
-rw-r--r--drivers/iommu/tegra-smmu.c37
1 files changed, 23 insertions, 14 deletions
diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
index 083354903a1a..a7a7645fb268 100644
--- a/drivers/iommu/tegra-smmu.c
+++ b/drivers/iommu/tegra-smmu.c
@@ -509,29 +509,35 @@ static u32 *as_get_pte(struct tegra_smmu_as *as, dma_addr_t iova,
return &pt[pte];
}
-static void as_put_pte(struct tegra_smmu_as *as, dma_addr_t iova)
+static void tegra_smmu_pte_put_use(struct tegra_smmu_as *as, unsigned long iova)
{
+ struct tegra_smmu *smmu = as->smmu;
u32 pde = (iova >> SMMU_PDE_SHIFT) & 0x3ff;
- u32 pte = (iova >> SMMU_PTE_SHIFT) & 0x3ff;
u32 *count = page_address(as->count);
- u32 *pd = page_address(as->pd), *pt;
+ u32 *pd = page_address(as->pd);
struct page *page;
- page = pfn_to_page(pd[pde] & as->smmu->pfn_mask);
- pt = page_address(page);
+ page = pfn_to_page(pd[pde] & smmu->pfn_mask);
/*
* When no entries in this page table are used anymore, return the
* memory page to the system.
*/
- if (pt[pte] != 0) {
- if (--count[pde] == 0) {
- ClearPageReserved(page);
- __free_page(page);
- pd[pde] = 0;
- }
+ if (--count[pde] == 0) {
+ unsigned int offset = pde * sizeof(*pd);
- pt[pte] = 0;
+ /* Clear the page directory entry first */
+ pd[pde] = 0;
+
+ /* Flush the page directory entry */
+ smmu->soc->ops->flush_dcache(as->pd, offset, sizeof(*pd));
+ smmu_flush_ptc(smmu, as->pd, offset);
+ smmu_flush_tlb_section(smmu, as->id, iova);
+ smmu_flush(smmu);
+
+ /* Finally, free the page */
+ ClearPageReserved(page);
+ __free_page(page);
}
}
@@ -569,17 +575,20 @@ static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
u32 *pte;
pte = as_get_pte(as, iova, &page);
- if (!pte)
+ if (!pte || !*pte)
return 0;
+ *pte = 0;
+
offset = offset_in_page(pte);
- as_put_pte(as, iova);
smmu->soc->ops->flush_dcache(page, offset, 4);
smmu_flush_ptc(smmu, page, offset);
smmu_flush_tlb_group(smmu, as->id, iova);
smmu_flush(smmu);
+ tegra_smmu_pte_put_use(as, iova);
+
return size;
}