162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * CPU-agnostic ARM page table allocator. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * ARMv7 Short-descriptor format, supporting 662306a36Sopenharmony_ci * - Basic memory attributes 762306a36Sopenharmony_ci * - Simplified access permissions (AP[2:1] model) 862306a36Sopenharmony_ci * - Backwards-compatible TEX remap 962306a36Sopenharmony_ci * - Large pages/supersections (if indicated by the caller) 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Not supporting: 1262306a36Sopenharmony_ci * - Legacy access permissions (AP[2:0] model) 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Almost certainly never supporting: 1562306a36Sopenharmony_ci * - PXN 1662306a36Sopenharmony_ci * - Domains 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * Copyright (C) 2014-2015 ARM Limited 1962306a36Sopenharmony_ci * Copyright (c) 2014-2015 MediaTek Inc. 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define pr_fmt(fmt) "arm-v7s io-pgtable: " fmt 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <linux/atomic.h> 2562306a36Sopenharmony_ci#include <linux/dma-mapping.h> 2662306a36Sopenharmony_ci#include <linux/gfp.h> 2762306a36Sopenharmony_ci#include <linux/io-pgtable.h> 2862306a36Sopenharmony_ci#include <linux/iommu.h> 2962306a36Sopenharmony_ci#include <linux/kernel.h> 3062306a36Sopenharmony_ci#include <linux/kmemleak.h> 3162306a36Sopenharmony_ci#include <linux/sizes.h> 3262306a36Sopenharmony_ci#include <linux/slab.h> 3362306a36Sopenharmony_ci#include <linux/spinlock.h> 3462306a36Sopenharmony_ci#include <linux/types.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#include <asm/barrier.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* Struct accessors */ 3962306a36Sopenharmony_ci#define io_pgtable_to_data(x) \ 4062306a36Sopenharmony_ci container_of((x), struct arm_v7s_io_pgtable, iop) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define io_pgtable_ops_to_data(x) \ 4362306a36Sopenharmony_ci io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* 4662306a36Sopenharmony_ci * We have 32 bits total; 12 bits resolved at level 1, 8 bits at level 2, 4762306a36Sopenharmony_ci * and 12 bits in a page. 4862306a36Sopenharmony_ci * MediaTek extend 2 bits to reach 34bits, 14 bits at lvl1 and 8 bits at lvl2. 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_ci#define ARM_V7S_ADDR_BITS 32 5162306a36Sopenharmony_ci#define _ARM_V7S_LVL_BITS(lvl, cfg) ((lvl) == 1 ? ((cfg)->ias - 20) : 8) 5262306a36Sopenharmony_ci#define ARM_V7S_LVL_SHIFT(lvl) ((lvl) == 1 ? 20 : 12) 5362306a36Sopenharmony_ci#define ARM_V7S_TABLE_SHIFT 10 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define ARM_V7S_PTES_PER_LVL(lvl, cfg) (1 << _ARM_V7S_LVL_BITS(lvl, cfg)) 5662306a36Sopenharmony_ci#define ARM_V7S_TABLE_SIZE(lvl, cfg) \ 5762306a36Sopenharmony_ci (ARM_V7S_PTES_PER_LVL(lvl, cfg) * sizeof(arm_v7s_iopte)) 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci#define ARM_V7S_BLOCK_SIZE(lvl) (1UL << ARM_V7S_LVL_SHIFT(lvl)) 6062306a36Sopenharmony_ci#define ARM_V7S_LVL_MASK(lvl) ((u32)(~0U << ARM_V7S_LVL_SHIFT(lvl))) 6162306a36Sopenharmony_ci#define ARM_V7S_TABLE_MASK ((u32)(~0U << ARM_V7S_TABLE_SHIFT)) 6262306a36Sopenharmony_ci#define _ARM_V7S_IDX_MASK(lvl, cfg) (ARM_V7S_PTES_PER_LVL(lvl, cfg) - 1) 6362306a36Sopenharmony_ci#define ARM_V7S_LVL_IDX(addr, lvl, cfg) ({ \ 6462306a36Sopenharmony_ci int _l = lvl; \ 6562306a36Sopenharmony_ci ((addr) >> ARM_V7S_LVL_SHIFT(_l)) & _ARM_V7S_IDX_MASK(_l, cfg); \ 6662306a36Sopenharmony_ci}) 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* 6962306a36Sopenharmony_ci * Large page/supersection entries are effectively a block of 16 page/section 7062306a36Sopenharmony_ci * entries, along the lines of the LPAE contiguous hint, but all with the 7162306a36Sopenharmony_ci * same output address. For want of a better common name we'll call them 7262306a36Sopenharmony_ci * "contiguous" versions of their respective page/section entries here, but 7362306a36Sopenharmony_ci * noting the distinction (WRT to TLB maintenance) that they represent *one* 7462306a36Sopenharmony_ci * entry repeated 16 times, not 16 separate entries (as in the LPAE case). 7562306a36Sopenharmony_ci */ 7662306a36Sopenharmony_ci#define ARM_V7S_CONT_PAGES 16 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* PTE type bits: these are all mixed up with XN/PXN bits in most cases */ 7962306a36Sopenharmony_ci#define ARM_V7S_PTE_TYPE_TABLE 0x1 8062306a36Sopenharmony_ci#define ARM_V7S_PTE_TYPE_PAGE 0x2 8162306a36Sopenharmony_ci#define ARM_V7S_PTE_TYPE_CONT_PAGE 0x1 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci#define ARM_V7S_PTE_IS_VALID(pte) (((pte) & 0x3) != 0) 8462306a36Sopenharmony_ci#define ARM_V7S_PTE_IS_TABLE(pte, lvl) \ 8562306a36Sopenharmony_ci ((lvl) == 1 && (((pte) & 0x3) == ARM_V7S_PTE_TYPE_TABLE)) 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/* Page table bits */ 8862306a36Sopenharmony_ci#define ARM_V7S_ATTR_XN(lvl) BIT(4 * (2 - (lvl))) 8962306a36Sopenharmony_ci#define ARM_V7S_ATTR_B BIT(2) 9062306a36Sopenharmony_ci#define ARM_V7S_ATTR_C BIT(3) 9162306a36Sopenharmony_ci#define ARM_V7S_ATTR_NS_TABLE BIT(3) 9262306a36Sopenharmony_ci#define ARM_V7S_ATTR_NS_SECTION BIT(19) 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci#define ARM_V7S_CONT_SECTION BIT(18) 9562306a36Sopenharmony_ci#define ARM_V7S_CONT_PAGE_XN_SHIFT 15 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci/* 9862306a36Sopenharmony_ci * The attribute bits are consistently ordered*, but occupy bits [17:10] of 9962306a36Sopenharmony_ci * a level 1 PTE vs. bits [11:4] at level 2. Thus we define the individual 10062306a36Sopenharmony_ci * fields relative to that 8-bit block, plus a total shift relative to the PTE. 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_ci#define ARM_V7S_ATTR_SHIFT(lvl) (16 - (lvl) * 6) 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci#define ARM_V7S_ATTR_MASK 0xff 10562306a36Sopenharmony_ci#define ARM_V7S_ATTR_AP0 BIT(0) 10662306a36Sopenharmony_ci#define ARM_V7S_ATTR_AP1 BIT(1) 10762306a36Sopenharmony_ci#define ARM_V7S_ATTR_AP2 BIT(5) 10862306a36Sopenharmony_ci#define ARM_V7S_ATTR_S BIT(6) 10962306a36Sopenharmony_ci#define ARM_V7S_ATTR_NG BIT(7) 11062306a36Sopenharmony_ci#define ARM_V7S_TEX_SHIFT 2 11162306a36Sopenharmony_ci#define ARM_V7S_TEX_MASK 0x7 11262306a36Sopenharmony_ci#define ARM_V7S_ATTR_TEX(val) (((val) & ARM_V7S_TEX_MASK) << ARM_V7S_TEX_SHIFT) 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci/* MediaTek extend the bits below for PA 32bit/33bit/34bit */ 11562306a36Sopenharmony_ci#define ARM_V7S_ATTR_MTK_PA_BIT32 BIT(9) 11662306a36Sopenharmony_ci#define ARM_V7S_ATTR_MTK_PA_BIT33 BIT(4) 11762306a36Sopenharmony_ci#define ARM_V7S_ATTR_MTK_PA_BIT34 BIT(5) 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/* *well, except for TEX on level 2 large pages, of course :( */ 12062306a36Sopenharmony_ci#define ARM_V7S_CONT_PAGE_TEX_SHIFT 6 12162306a36Sopenharmony_ci#define ARM_V7S_CONT_PAGE_TEX_MASK (ARM_V7S_TEX_MASK << ARM_V7S_CONT_PAGE_TEX_SHIFT) 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* Simplified access permissions */ 12462306a36Sopenharmony_ci#define ARM_V7S_PTE_AF ARM_V7S_ATTR_AP0 12562306a36Sopenharmony_ci#define ARM_V7S_PTE_AP_UNPRIV ARM_V7S_ATTR_AP1 12662306a36Sopenharmony_ci#define ARM_V7S_PTE_AP_RDONLY ARM_V7S_ATTR_AP2 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci/* Register bits */ 12962306a36Sopenharmony_ci#define ARM_V7S_RGN_NC 0 13062306a36Sopenharmony_ci#define ARM_V7S_RGN_WBWA 1 13162306a36Sopenharmony_ci#define ARM_V7S_RGN_WT 2 13262306a36Sopenharmony_ci#define ARM_V7S_RGN_WB 3 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci#define ARM_V7S_PRRR_TYPE_DEVICE 1 13562306a36Sopenharmony_ci#define ARM_V7S_PRRR_TYPE_NORMAL 2 13662306a36Sopenharmony_ci#define ARM_V7S_PRRR_TR(n, type) (((type) & 0x3) << ((n) * 2)) 13762306a36Sopenharmony_ci#define ARM_V7S_PRRR_DS0 BIT(16) 13862306a36Sopenharmony_ci#define ARM_V7S_PRRR_DS1 BIT(17) 13962306a36Sopenharmony_ci#define ARM_V7S_PRRR_NS0 BIT(18) 14062306a36Sopenharmony_ci#define ARM_V7S_PRRR_NS1 BIT(19) 14162306a36Sopenharmony_ci#define ARM_V7S_PRRR_NOS(n) BIT((n) + 24) 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci#define ARM_V7S_NMRR_IR(n, attr) (((attr) & 0x3) << ((n) * 2)) 14462306a36Sopenharmony_ci#define ARM_V7S_NMRR_OR(n, attr) (((attr) & 0x3) << ((n) * 2 + 16)) 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci#define ARM_V7S_TTBR_S BIT(1) 14762306a36Sopenharmony_ci#define ARM_V7S_TTBR_NOS BIT(5) 14862306a36Sopenharmony_ci#define ARM_V7S_TTBR_ORGN_ATTR(attr) (((attr) & 0x3) << 3) 14962306a36Sopenharmony_ci#define ARM_V7S_TTBR_IRGN_ATTR(attr) \ 15062306a36Sopenharmony_ci ((((attr) & 0x1) << 6) | (((attr) & 0x2) >> 1)) 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci#ifdef CONFIG_ZONE_DMA32 15362306a36Sopenharmony_ci#define ARM_V7S_TABLE_GFP_DMA GFP_DMA32 15462306a36Sopenharmony_ci#define ARM_V7S_TABLE_SLAB_FLAGS SLAB_CACHE_DMA32 15562306a36Sopenharmony_ci#else 15662306a36Sopenharmony_ci#define ARM_V7S_TABLE_GFP_DMA GFP_DMA 15762306a36Sopenharmony_ci#define ARM_V7S_TABLE_SLAB_FLAGS SLAB_CACHE_DMA 15862306a36Sopenharmony_ci#endif 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_citypedef u32 arm_v7s_iopte; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic bool selftest_running; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistruct arm_v7s_io_pgtable { 16562306a36Sopenharmony_ci struct io_pgtable iop; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci arm_v7s_iopte *pgd; 16862306a36Sopenharmony_ci struct kmem_cache *l2_tables; 16962306a36Sopenharmony_ci spinlock_t split_lock; 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic bool arm_v7s_pte_is_cont(arm_v7s_iopte pte, int lvl); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic dma_addr_t __arm_v7s_dma_addr(void *pages) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci return (dma_addr_t)virt_to_phys(pages); 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic bool arm_v7s_is_mtk_enabled(struct io_pgtable_cfg *cfg) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci return IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) && 18262306a36Sopenharmony_ci (cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_EXT); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic arm_v7s_iopte to_mtk_iopte(phys_addr_t paddr, arm_v7s_iopte pte) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci if (paddr & BIT_ULL(32)) 18862306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_MTK_PA_BIT32; 18962306a36Sopenharmony_ci if (paddr & BIT_ULL(33)) 19062306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_MTK_PA_BIT33; 19162306a36Sopenharmony_ci if (paddr & BIT_ULL(34)) 19262306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_MTK_PA_BIT34; 19362306a36Sopenharmony_ci return pte; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic arm_v7s_iopte paddr_to_iopte(phys_addr_t paddr, int lvl, 19762306a36Sopenharmony_ci struct io_pgtable_cfg *cfg) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci arm_v7s_iopte pte = paddr & ARM_V7S_LVL_MASK(lvl); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (arm_v7s_is_mtk_enabled(cfg)) 20262306a36Sopenharmony_ci return to_mtk_iopte(paddr, pte); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return pte; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic phys_addr_t iopte_to_paddr(arm_v7s_iopte pte, int lvl, 20862306a36Sopenharmony_ci struct io_pgtable_cfg *cfg) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci arm_v7s_iopte mask; 21162306a36Sopenharmony_ci phys_addr_t paddr; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci if (ARM_V7S_PTE_IS_TABLE(pte, lvl)) 21462306a36Sopenharmony_ci mask = ARM_V7S_TABLE_MASK; 21562306a36Sopenharmony_ci else if (arm_v7s_pte_is_cont(pte, lvl)) 21662306a36Sopenharmony_ci mask = ARM_V7S_LVL_MASK(lvl) * ARM_V7S_CONT_PAGES; 21762306a36Sopenharmony_ci else 21862306a36Sopenharmony_ci mask = ARM_V7S_LVL_MASK(lvl); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci paddr = pte & mask; 22162306a36Sopenharmony_ci if (!arm_v7s_is_mtk_enabled(cfg)) 22262306a36Sopenharmony_ci return paddr; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (pte & ARM_V7S_ATTR_MTK_PA_BIT32) 22562306a36Sopenharmony_ci paddr |= BIT_ULL(32); 22662306a36Sopenharmony_ci if (pte & ARM_V7S_ATTR_MTK_PA_BIT33) 22762306a36Sopenharmony_ci paddr |= BIT_ULL(33); 22862306a36Sopenharmony_ci if (pte & ARM_V7S_ATTR_MTK_PA_BIT34) 22962306a36Sopenharmony_ci paddr |= BIT_ULL(34); 23062306a36Sopenharmony_ci return paddr; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic arm_v7s_iopte *iopte_deref(arm_v7s_iopte pte, int lvl, 23462306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci return phys_to_virt(iopte_to_paddr(pte, lvl, &data->iop.cfg)); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic void *__arm_v7s_alloc_table(int lvl, gfp_t gfp, 24062306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct io_pgtable_cfg *cfg = &data->iop.cfg; 24362306a36Sopenharmony_ci struct device *dev = cfg->iommu_dev; 24462306a36Sopenharmony_ci phys_addr_t phys; 24562306a36Sopenharmony_ci dma_addr_t dma; 24662306a36Sopenharmony_ci size_t size = ARM_V7S_TABLE_SIZE(lvl, cfg); 24762306a36Sopenharmony_ci void *table = NULL; 24862306a36Sopenharmony_ci gfp_t gfp_l1; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci /* 25162306a36Sopenharmony_ci * ARM_MTK_TTBR_EXT extend the translation table base support larger 25262306a36Sopenharmony_ci * memory address. 25362306a36Sopenharmony_ci */ 25462306a36Sopenharmony_ci gfp_l1 = cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT ? 25562306a36Sopenharmony_ci GFP_KERNEL : ARM_V7S_TABLE_GFP_DMA; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (lvl == 1) 25862306a36Sopenharmony_ci table = (void *)__get_free_pages(gfp_l1 | __GFP_ZERO, get_order(size)); 25962306a36Sopenharmony_ci else if (lvl == 2) 26062306a36Sopenharmony_ci table = kmem_cache_zalloc(data->l2_tables, gfp); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci if (!table) 26362306a36Sopenharmony_ci return NULL; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci phys = virt_to_phys(table); 26662306a36Sopenharmony_ci if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT ? 26762306a36Sopenharmony_ci phys >= (1ULL << cfg->oas) : phys != (arm_v7s_iopte)phys) { 26862306a36Sopenharmony_ci /* Doesn't fit in PTE */ 26962306a36Sopenharmony_ci dev_err(dev, "Page table does not fit in PTE: %pa", &phys); 27062306a36Sopenharmony_ci goto out_free; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci if (!cfg->coherent_walk) { 27362306a36Sopenharmony_ci dma = dma_map_single(dev, table, size, DMA_TO_DEVICE); 27462306a36Sopenharmony_ci if (dma_mapping_error(dev, dma)) 27562306a36Sopenharmony_ci goto out_free; 27662306a36Sopenharmony_ci /* 27762306a36Sopenharmony_ci * We depend on the IOMMU being able to work with any physical 27862306a36Sopenharmony_ci * address directly, so if the DMA layer suggests otherwise by 27962306a36Sopenharmony_ci * translating or truncating them, that bodes very badly... 28062306a36Sopenharmony_ci */ 28162306a36Sopenharmony_ci if (dma != phys) 28262306a36Sopenharmony_ci goto out_unmap; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci if (lvl == 2) 28562306a36Sopenharmony_ci kmemleak_ignore(table); 28662306a36Sopenharmony_ci return table; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ciout_unmap: 28962306a36Sopenharmony_ci dev_err(dev, "Cannot accommodate DMA translation for IOMMU page tables\n"); 29062306a36Sopenharmony_ci dma_unmap_single(dev, dma, size, DMA_TO_DEVICE); 29162306a36Sopenharmony_ciout_free: 29262306a36Sopenharmony_ci if (lvl == 1) 29362306a36Sopenharmony_ci free_pages((unsigned long)table, get_order(size)); 29462306a36Sopenharmony_ci else 29562306a36Sopenharmony_ci kmem_cache_free(data->l2_tables, table); 29662306a36Sopenharmony_ci return NULL; 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic void __arm_v7s_free_table(void *table, int lvl, 30062306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct io_pgtable_cfg *cfg = &data->iop.cfg; 30362306a36Sopenharmony_ci struct device *dev = cfg->iommu_dev; 30462306a36Sopenharmony_ci size_t size = ARM_V7S_TABLE_SIZE(lvl, cfg); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (!cfg->coherent_walk) 30762306a36Sopenharmony_ci dma_unmap_single(dev, __arm_v7s_dma_addr(table), size, 30862306a36Sopenharmony_ci DMA_TO_DEVICE); 30962306a36Sopenharmony_ci if (lvl == 1) 31062306a36Sopenharmony_ci free_pages((unsigned long)table, get_order(size)); 31162306a36Sopenharmony_ci else 31262306a36Sopenharmony_ci kmem_cache_free(data->l2_tables, table); 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic void __arm_v7s_pte_sync(arm_v7s_iopte *ptep, int num_entries, 31662306a36Sopenharmony_ci struct io_pgtable_cfg *cfg) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci if (cfg->coherent_walk) 31962306a36Sopenharmony_ci return; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci dma_sync_single_for_device(cfg->iommu_dev, __arm_v7s_dma_addr(ptep), 32262306a36Sopenharmony_ci num_entries * sizeof(*ptep), DMA_TO_DEVICE); 32362306a36Sopenharmony_ci} 32462306a36Sopenharmony_cistatic void __arm_v7s_set_pte(arm_v7s_iopte *ptep, arm_v7s_iopte pte, 32562306a36Sopenharmony_ci int num_entries, struct io_pgtable_cfg *cfg) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci int i; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci for (i = 0; i < num_entries; i++) 33062306a36Sopenharmony_ci ptep[i] = pte; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci __arm_v7s_pte_sync(ptep, num_entries, cfg); 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic arm_v7s_iopte arm_v7s_prot_to_pte(int prot, int lvl, 33662306a36Sopenharmony_ci struct io_pgtable_cfg *cfg) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci bool ap = !(cfg->quirks & IO_PGTABLE_QUIRK_NO_PERMS); 33962306a36Sopenharmony_ci arm_v7s_iopte pte = ARM_V7S_ATTR_NG | ARM_V7S_ATTR_S; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (!(prot & IOMMU_MMIO)) 34262306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_TEX(1); 34362306a36Sopenharmony_ci if (ap) { 34462306a36Sopenharmony_ci pte |= ARM_V7S_PTE_AF; 34562306a36Sopenharmony_ci if (!(prot & IOMMU_PRIV)) 34662306a36Sopenharmony_ci pte |= ARM_V7S_PTE_AP_UNPRIV; 34762306a36Sopenharmony_ci if (!(prot & IOMMU_WRITE)) 34862306a36Sopenharmony_ci pte |= ARM_V7S_PTE_AP_RDONLY; 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci pte <<= ARM_V7S_ATTR_SHIFT(lvl); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if ((prot & IOMMU_NOEXEC) && ap) 35362306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_XN(lvl); 35462306a36Sopenharmony_ci if (prot & IOMMU_MMIO) 35562306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_B; 35662306a36Sopenharmony_ci else if (prot & IOMMU_CACHE) 35762306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_B | ARM_V7S_ATTR_C; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci pte |= ARM_V7S_PTE_TYPE_PAGE; 36062306a36Sopenharmony_ci if (lvl == 1 && (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS)) 36162306a36Sopenharmony_ci pte |= ARM_V7S_ATTR_NS_SECTION; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return pte; 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_cistatic int arm_v7s_pte_to_prot(arm_v7s_iopte pte, int lvl) 36762306a36Sopenharmony_ci{ 36862306a36Sopenharmony_ci int prot = IOMMU_READ; 36962306a36Sopenharmony_ci arm_v7s_iopte attr = pte >> ARM_V7S_ATTR_SHIFT(lvl); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (!(attr & ARM_V7S_PTE_AP_RDONLY)) 37262306a36Sopenharmony_ci prot |= IOMMU_WRITE; 37362306a36Sopenharmony_ci if (!(attr & ARM_V7S_PTE_AP_UNPRIV)) 37462306a36Sopenharmony_ci prot |= IOMMU_PRIV; 37562306a36Sopenharmony_ci if ((attr & (ARM_V7S_TEX_MASK << ARM_V7S_TEX_SHIFT)) == 0) 37662306a36Sopenharmony_ci prot |= IOMMU_MMIO; 37762306a36Sopenharmony_ci else if (pte & ARM_V7S_ATTR_C) 37862306a36Sopenharmony_ci prot |= IOMMU_CACHE; 37962306a36Sopenharmony_ci if (pte & ARM_V7S_ATTR_XN(lvl)) 38062306a36Sopenharmony_ci prot |= IOMMU_NOEXEC; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci return prot; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic arm_v7s_iopte arm_v7s_pte_to_cont(arm_v7s_iopte pte, int lvl) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci if (lvl == 1) { 38862306a36Sopenharmony_ci pte |= ARM_V7S_CONT_SECTION; 38962306a36Sopenharmony_ci } else if (lvl == 2) { 39062306a36Sopenharmony_ci arm_v7s_iopte xn = pte & ARM_V7S_ATTR_XN(lvl); 39162306a36Sopenharmony_ci arm_v7s_iopte tex = pte & ARM_V7S_CONT_PAGE_TEX_MASK; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci pte ^= xn | tex | ARM_V7S_PTE_TYPE_PAGE; 39462306a36Sopenharmony_ci pte |= (xn << ARM_V7S_CONT_PAGE_XN_SHIFT) | 39562306a36Sopenharmony_ci (tex << ARM_V7S_CONT_PAGE_TEX_SHIFT) | 39662306a36Sopenharmony_ci ARM_V7S_PTE_TYPE_CONT_PAGE; 39762306a36Sopenharmony_ci } 39862306a36Sopenharmony_ci return pte; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic arm_v7s_iopte arm_v7s_cont_to_pte(arm_v7s_iopte pte, int lvl) 40262306a36Sopenharmony_ci{ 40362306a36Sopenharmony_ci if (lvl == 1) { 40462306a36Sopenharmony_ci pte &= ~ARM_V7S_CONT_SECTION; 40562306a36Sopenharmony_ci } else if (lvl == 2) { 40662306a36Sopenharmony_ci arm_v7s_iopte xn = pte & BIT(ARM_V7S_CONT_PAGE_XN_SHIFT); 40762306a36Sopenharmony_ci arm_v7s_iopte tex = pte & (ARM_V7S_CONT_PAGE_TEX_MASK << 40862306a36Sopenharmony_ci ARM_V7S_CONT_PAGE_TEX_SHIFT); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci pte ^= xn | tex | ARM_V7S_PTE_TYPE_CONT_PAGE; 41162306a36Sopenharmony_ci pte |= (xn >> ARM_V7S_CONT_PAGE_XN_SHIFT) | 41262306a36Sopenharmony_ci (tex >> ARM_V7S_CONT_PAGE_TEX_SHIFT) | 41362306a36Sopenharmony_ci ARM_V7S_PTE_TYPE_PAGE; 41462306a36Sopenharmony_ci } 41562306a36Sopenharmony_ci return pte; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic bool arm_v7s_pte_is_cont(arm_v7s_iopte pte, int lvl) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci if (lvl == 1 && !ARM_V7S_PTE_IS_TABLE(pte, lvl)) 42162306a36Sopenharmony_ci return pte & ARM_V7S_CONT_SECTION; 42262306a36Sopenharmony_ci else if (lvl == 2) 42362306a36Sopenharmony_ci return !(pte & ARM_V7S_PTE_TYPE_PAGE); 42462306a36Sopenharmony_ci return false; 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_cistatic size_t __arm_v7s_unmap(struct arm_v7s_io_pgtable *, 42862306a36Sopenharmony_ci struct iommu_iotlb_gather *, unsigned long, 42962306a36Sopenharmony_ci size_t, int, arm_v7s_iopte *); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_cistatic int arm_v7s_init_pte(struct arm_v7s_io_pgtable *data, 43262306a36Sopenharmony_ci unsigned long iova, phys_addr_t paddr, int prot, 43362306a36Sopenharmony_ci int lvl, int num_entries, arm_v7s_iopte *ptep) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci struct io_pgtable_cfg *cfg = &data->iop.cfg; 43662306a36Sopenharmony_ci arm_v7s_iopte pte; 43762306a36Sopenharmony_ci int i; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci for (i = 0; i < num_entries; i++) 44062306a36Sopenharmony_ci if (ARM_V7S_PTE_IS_TABLE(ptep[i], lvl)) { 44162306a36Sopenharmony_ci /* 44262306a36Sopenharmony_ci * We need to unmap and free the old table before 44362306a36Sopenharmony_ci * overwriting it with a block entry. 44462306a36Sopenharmony_ci */ 44562306a36Sopenharmony_ci arm_v7s_iopte *tblp; 44662306a36Sopenharmony_ci size_t sz = ARM_V7S_BLOCK_SIZE(lvl); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci tblp = ptep - ARM_V7S_LVL_IDX(iova, lvl, cfg); 44962306a36Sopenharmony_ci if (WARN_ON(__arm_v7s_unmap(data, NULL, iova + i * sz, 45062306a36Sopenharmony_ci sz, lvl, tblp) != sz)) 45162306a36Sopenharmony_ci return -EINVAL; 45262306a36Sopenharmony_ci } else if (ptep[i]) { 45362306a36Sopenharmony_ci /* We require an unmap first */ 45462306a36Sopenharmony_ci WARN_ON(!selftest_running); 45562306a36Sopenharmony_ci return -EEXIST; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci pte = arm_v7s_prot_to_pte(prot, lvl, cfg); 45962306a36Sopenharmony_ci if (num_entries > 1) 46062306a36Sopenharmony_ci pte = arm_v7s_pte_to_cont(pte, lvl); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci pte |= paddr_to_iopte(paddr, lvl, cfg); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci __arm_v7s_set_pte(ptep, pte, num_entries, cfg); 46562306a36Sopenharmony_ci return 0; 46662306a36Sopenharmony_ci} 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_cistatic arm_v7s_iopte arm_v7s_install_table(arm_v7s_iopte *table, 46962306a36Sopenharmony_ci arm_v7s_iopte *ptep, 47062306a36Sopenharmony_ci arm_v7s_iopte curr, 47162306a36Sopenharmony_ci struct io_pgtable_cfg *cfg) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci phys_addr_t phys = virt_to_phys(table); 47462306a36Sopenharmony_ci arm_v7s_iopte old, new; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci new = phys | ARM_V7S_PTE_TYPE_TABLE; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT) 47962306a36Sopenharmony_ci new = to_mtk_iopte(phys, new); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS) 48262306a36Sopenharmony_ci new |= ARM_V7S_ATTR_NS_TABLE; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci /* 48562306a36Sopenharmony_ci * Ensure the table itself is visible before its PTE can be. 48662306a36Sopenharmony_ci * Whilst we could get away with cmpxchg64_release below, this 48762306a36Sopenharmony_ci * doesn't have any ordering semantics when !CONFIG_SMP. 48862306a36Sopenharmony_ci */ 48962306a36Sopenharmony_ci dma_wmb(); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci old = cmpxchg_relaxed(ptep, curr, new); 49262306a36Sopenharmony_ci __arm_v7s_pte_sync(ptep, 1, cfg); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci return old; 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic int __arm_v7s_map(struct arm_v7s_io_pgtable *data, unsigned long iova, 49862306a36Sopenharmony_ci phys_addr_t paddr, size_t size, int prot, 49962306a36Sopenharmony_ci int lvl, arm_v7s_iopte *ptep, gfp_t gfp) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci struct io_pgtable_cfg *cfg = &data->iop.cfg; 50262306a36Sopenharmony_ci arm_v7s_iopte pte, *cptep; 50362306a36Sopenharmony_ci int num_entries = size >> ARM_V7S_LVL_SHIFT(lvl); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci /* Find our entry at the current level */ 50662306a36Sopenharmony_ci ptep += ARM_V7S_LVL_IDX(iova, lvl, cfg); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci /* If we can install a leaf entry at this level, then do so */ 50962306a36Sopenharmony_ci if (num_entries) 51062306a36Sopenharmony_ci return arm_v7s_init_pte(data, iova, paddr, prot, 51162306a36Sopenharmony_ci lvl, num_entries, ptep); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* We can't allocate tables at the final level */ 51462306a36Sopenharmony_ci if (WARN_ON(lvl == 2)) 51562306a36Sopenharmony_ci return -EINVAL; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* Grab a pointer to the next level */ 51862306a36Sopenharmony_ci pte = READ_ONCE(*ptep); 51962306a36Sopenharmony_ci if (!pte) { 52062306a36Sopenharmony_ci cptep = __arm_v7s_alloc_table(lvl + 1, gfp, data); 52162306a36Sopenharmony_ci if (!cptep) 52262306a36Sopenharmony_ci return -ENOMEM; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci pte = arm_v7s_install_table(cptep, ptep, 0, cfg); 52562306a36Sopenharmony_ci if (pte) 52662306a36Sopenharmony_ci __arm_v7s_free_table(cptep, lvl + 1, data); 52762306a36Sopenharmony_ci } else { 52862306a36Sopenharmony_ci /* We've no easy way of knowing if it's synced yet, so... */ 52962306a36Sopenharmony_ci __arm_v7s_pte_sync(ptep, 1, cfg); 53062306a36Sopenharmony_ci } 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci if (ARM_V7S_PTE_IS_TABLE(pte, lvl)) { 53362306a36Sopenharmony_ci cptep = iopte_deref(pte, lvl, data); 53462306a36Sopenharmony_ci } else if (pte) { 53562306a36Sopenharmony_ci /* We require an unmap first */ 53662306a36Sopenharmony_ci WARN_ON(!selftest_running); 53762306a36Sopenharmony_ci return -EEXIST; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci /* Rinse, repeat */ 54162306a36Sopenharmony_ci return __arm_v7s_map(data, iova, paddr, size, prot, lvl + 1, cptep, gfp); 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_cistatic int arm_v7s_map_pages(struct io_pgtable_ops *ops, unsigned long iova, 54562306a36Sopenharmony_ci phys_addr_t paddr, size_t pgsize, size_t pgcount, 54662306a36Sopenharmony_ci int prot, gfp_t gfp, size_t *mapped) 54762306a36Sopenharmony_ci{ 54862306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops); 54962306a36Sopenharmony_ci int ret = -EINVAL; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias) || 55262306a36Sopenharmony_ci paddr >= (1ULL << data->iop.cfg.oas))) 55362306a36Sopenharmony_ci return -ERANGE; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* If no access, then nothing to do */ 55662306a36Sopenharmony_ci if (!(prot & (IOMMU_READ | IOMMU_WRITE))) 55762306a36Sopenharmony_ci return 0; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci while (pgcount--) { 56062306a36Sopenharmony_ci ret = __arm_v7s_map(data, iova, paddr, pgsize, prot, 1, data->pgd, 56162306a36Sopenharmony_ci gfp); 56262306a36Sopenharmony_ci if (ret) 56362306a36Sopenharmony_ci break; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci iova += pgsize; 56662306a36Sopenharmony_ci paddr += pgsize; 56762306a36Sopenharmony_ci *mapped += pgsize; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci /* 57062306a36Sopenharmony_ci * Synchronise all PTE updates for the new mapping before there's 57162306a36Sopenharmony_ci * a chance for anything to kick off a table walk for the new iova. 57262306a36Sopenharmony_ci */ 57362306a36Sopenharmony_ci wmb(); 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci return ret; 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_cistatic void arm_v7s_free_pgtable(struct io_pgtable *iop) 57962306a36Sopenharmony_ci{ 58062306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data = io_pgtable_to_data(iop); 58162306a36Sopenharmony_ci int i; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci for (i = 0; i < ARM_V7S_PTES_PER_LVL(1, &data->iop.cfg); i++) { 58462306a36Sopenharmony_ci arm_v7s_iopte pte = data->pgd[i]; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci if (ARM_V7S_PTE_IS_TABLE(pte, 1)) 58762306a36Sopenharmony_ci __arm_v7s_free_table(iopte_deref(pte, 1, data), 58862306a36Sopenharmony_ci 2, data); 58962306a36Sopenharmony_ci } 59062306a36Sopenharmony_ci __arm_v7s_free_table(data->pgd, 1, data); 59162306a36Sopenharmony_ci kmem_cache_destroy(data->l2_tables); 59262306a36Sopenharmony_ci kfree(data); 59362306a36Sopenharmony_ci} 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_cistatic arm_v7s_iopte arm_v7s_split_cont(struct arm_v7s_io_pgtable *data, 59662306a36Sopenharmony_ci unsigned long iova, int idx, int lvl, 59762306a36Sopenharmony_ci arm_v7s_iopte *ptep) 59862306a36Sopenharmony_ci{ 59962306a36Sopenharmony_ci struct io_pgtable *iop = &data->iop; 60062306a36Sopenharmony_ci arm_v7s_iopte pte; 60162306a36Sopenharmony_ci size_t size = ARM_V7S_BLOCK_SIZE(lvl); 60262306a36Sopenharmony_ci int i; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci /* Check that we didn't lose a race to get the lock */ 60562306a36Sopenharmony_ci pte = *ptep; 60662306a36Sopenharmony_ci if (!arm_v7s_pte_is_cont(pte, lvl)) 60762306a36Sopenharmony_ci return pte; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci ptep -= idx & (ARM_V7S_CONT_PAGES - 1); 61062306a36Sopenharmony_ci pte = arm_v7s_cont_to_pte(pte, lvl); 61162306a36Sopenharmony_ci for (i = 0; i < ARM_V7S_CONT_PAGES; i++) 61262306a36Sopenharmony_ci ptep[i] = pte + i * size; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci __arm_v7s_pte_sync(ptep, ARM_V7S_CONT_PAGES, &iop->cfg); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci size *= ARM_V7S_CONT_PAGES; 61762306a36Sopenharmony_ci io_pgtable_tlb_flush_walk(iop, iova, size, size); 61862306a36Sopenharmony_ci return pte; 61962306a36Sopenharmony_ci} 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_cistatic size_t arm_v7s_split_blk_unmap(struct arm_v7s_io_pgtable *data, 62262306a36Sopenharmony_ci struct iommu_iotlb_gather *gather, 62362306a36Sopenharmony_ci unsigned long iova, size_t size, 62462306a36Sopenharmony_ci arm_v7s_iopte blk_pte, 62562306a36Sopenharmony_ci arm_v7s_iopte *ptep) 62662306a36Sopenharmony_ci{ 62762306a36Sopenharmony_ci struct io_pgtable_cfg *cfg = &data->iop.cfg; 62862306a36Sopenharmony_ci arm_v7s_iopte pte, *tablep; 62962306a36Sopenharmony_ci int i, unmap_idx, num_entries, num_ptes; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci tablep = __arm_v7s_alloc_table(2, GFP_ATOMIC, data); 63262306a36Sopenharmony_ci if (!tablep) 63362306a36Sopenharmony_ci return 0; /* Bytes unmapped */ 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci num_ptes = ARM_V7S_PTES_PER_LVL(2, cfg); 63662306a36Sopenharmony_ci num_entries = size >> ARM_V7S_LVL_SHIFT(2); 63762306a36Sopenharmony_ci unmap_idx = ARM_V7S_LVL_IDX(iova, 2, cfg); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci pte = arm_v7s_prot_to_pte(arm_v7s_pte_to_prot(blk_pte, 1), 2, cfg); 64062306a36Sopenharmony_ci if (num_entries > 1) 64162306a36Sopenharmony_ci pte = arm_v7s_pte_to_cont(pte, 2); 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci for (i = 0; i < num_ptes; i += num_entries, pte += size) { 64462306a36Sopenharmony_ci /* Unmap! */ 64562306a36Sopenharmony_ci if (i == unmap_idx) 64662306a36Sopenharmony_ci continue; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci __arm_v7s_set_pte(&tablep[i], pte, num_entries, cfg); 64962306a36Sopenharmony_ci } 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci pte = arm_v7s_install_table(tablep, ptep, blk_pte, cfg); 65262306a36Sopenharmony_ci if (pte != blk_pte) { 65362306a36Sopenharmony_ci __arm_v7s_free_table(tablep, 2, data); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci if (!ARM_V7S_PTE_IS_TABLE(pte, 1)) 65662306a36Sopenharmony_ci return 0; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci tablep = iopte_deref(pte, 1, data); 65962306a36Sopenharmony_ci return __arm_v7s_unmap(data, gather, iova, size, 2, tablep); 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci io_pgtable_tlb_add_page(&data->iop, gather, iova, size); 66362306a36Sopenharmony_ci return size; 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic size_t __arm_v7s_unmap(struct arm_v7s_io_pgtable *data, 66762306a36Sopenharmony_ci struct iommu_iotlb_gather *gather, 66862306a36Sopenharmony_ci unsigned long iova, size_t size, int lvl, 66962306a36Sopenharmony_ci arm_v7s_iopte *ptep) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci arm_v7s_iopte pte[ARM_V7S_CONT_PAGES]; 67262306a36Sopenharmony_ci struct io_pgtable *iop = &data->iop; 67362306a36Sopenharmony_ci int idx, i = 0, num_entries = size >> ARM_V7S_LVL_SHIFT(lvl); 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci /* Something went horribly wrong and we ran out of page table */ 67662306a36Sopenharmony_ci if (WARN_ON(lvl > 2)) 67762306a36Sopenharmony_ci return 0; 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci idx = ARM_V7S_LVL_IDX(iova, lvl, &iop->cfg); 68062306a36Sopenharmony_ci ptep += idx; 68162306a36Sopenharmony_ci do { 68262306a36Sopenharmony_ci pte[i] = READ_ONCE(ptep[i]); 68362306a36Sopenharmony_ci if (WARN_ON(!ARM_V7S_PTE_IS_VALID(pte[i]))) 68462306a36Sopenharmony_ci return 0; 68562306a36Sopenharmony_ci } while (++i < num_entries); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci /* 68862306a36Sopenharmony_ci * If we've hit a contiguous 'large page' entry at this level, it 68962306a36Sopenharmony_ci * needs splitting first, unless we're unmapping the whole lot. 69062306a36Sopenharmony_ci * 69162306a36Sopenharmony_ci * For splitting, we can't rewrite 16 PTEs atomically, and since we 69262306a36Sopenharmony_ci * can't necessarily assume TEX remap we don't have a software bit to 69362306a36Sopenharmony_ci * mark live entries being split. In practice (i.e. DMA API code), we 69462306a36Sopenharmony_ci * will never be splitting large pages anyway, so just wrap this edge 69562306a36Sopenharmony_ci * case in a lock for the sake of correctness and be done with it. 69662306a36Sopenharmony_ci */ 69762306a36Sopenharmony_ci if (num_entries <= 1 && arm_v7s_pte_is_cont(pte[0], lvl)) { 69862306a36Sopenharmony_ci unsigned long flags; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci spin_lock_irqsave(&data->split_lock, flags); 70162306a36Sopenharmony_ci pte[0] = arm_v7s_split_cont(data, iova, idx, lvl, ptep); 70262306a36Sopenharmony_ci spin_unlock_irqrestore(&data->split_lock, flags); 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci /* If the size matches this level, we're in the right place */ 70662306a36Sopenharmony_ci if (num_entries) { 70762306a36Sopenharmony_ci size_t blk_size = ARM_V7S_BLOCK_SIZE(lvl); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci __arm_v7s_set_pte(ptep, 0, num_entries, &iop->cfg); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci for (i = 0; i < num_entries; i++) { 71262306a36Sopenharmony_ci if (ARM_V7S_PTE_IS_TABLE(pte[i], lvl)) { 71362306a36Sopenharmony_ci /* Also flush any partial walks */ 71462306a36Sopenharmony_ci io_pgtable_tlb_flush_walk(iop, iova, blk_size, 71562306a36Sopenharmony_ci ARM_V7S_BLOCK_SIZE(lvl + 1)); 71662306a36Sopenharmony_ci ptep = iopte_deref(pte[i], lvl, data); 71762306a36Sopenharmony_ci __arm_v7s_free_table(ptep, lvl + 1, data); 71862306a36Sopenharmony_ci } else if (!iommu_iotlb_gather_queued(gather)) { 71962306a36Sopenharmony_ci io_pgtable_tlb_add_page(iop, gather, iova, blk_size); 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci iova += blk_size; 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci return size; 72462306a36Sopenharmony_ci } else if (lvl == 1 && !ARM_V7S_PTE_IS_TABLE(pte[0], lvl)) { 72562306a36Sopenharmony_ci /* 72662306a36Sopenharmony_ci * Insert a table at the next level to map the old region, 72762306a36Sopenharmony_ci * minus the part we want to unmap 72862306a36Sopenharmony_ci */ 72962306a36Sopenharmony_ci return arm_v7s_split_blk_unmap(data, gather, iova, size, pte[0], 73062306a36Sopenharmony_ci ptep); 73162306a36Sopenharmony_ci } 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci /* Keep on walkin' */ 73462306a36Sopenharmony_ci ptep = iopte_deref(pte[0], lvl, data); 73562306a36Sopenharmony_ci return __arm_v7s_unmap(data, gather, iova, size, lvl + 1, ptep); 73662306a36Sopenharmony_ci} 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_cistatic size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova, 73962306a36Sopenharmony_ci size_t pgsize, size_t pgcount, 74062306a36Sopenharmony_ci struct iommu_iotlb_gather *gather) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops); 74362306a36Sopenharmony_ci size_t unmapped = 0, ret; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias))) 74662306a36Sopenharmony_ci return 0; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci while (pgcount--) { 74962306a36Sopenharmony_ci ret = __arm_v7s_unmap(data, gather, iova, pgsize, 1, data->pgd); 75062306a36Sopenharmony_ci if (!ret) 75162306a36Sopenharmony_ci break; 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci unmapped += pgsize; 75462306a36Sopenharmony_ci iova += pgsize; 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci return unmapped; 75862306a36Sopenharmony_ci} 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_cistatic phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops, 76162306a36Sopenharmony_ci unsigned long iova) 76262306a36Sopenharmony_ci{ 76362306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops); 76462306a36Sopenharmony_ci arm_v7s_iopte *ptep = data->pgd, pte; 76562306a36Sopenharmony_ci int lvl = 0; 76662306a36Sopenharmony_ci u32 mask; 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci do { 76962306a36Sopenharmony_ci ptep += ARM_V7S_LVL_IDX(iova, ++lvl, &data->iop.cfg); 77062306a36Sopenharmony_ci pte = READ_ONCE(*ptep); 77162306a36Sopenharmony_ci ptep = iopte_deref(pte, lvl, data); 77262306a36Sopenharmony_ci } while (ARM_V7S_PTE_IS_TABLE(pte, lvl)); 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci if (!ARM_V7S_PTE_IS_VALID(pte)) 77562306a36Sopenharmony_ci return 0; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci mask = ARM_V7S_LVL_MASK(lvl); 77862306a36Sopenharmony_ci if (arm_v7s_pte_is_cont(pte, lvl)) 77962306a36Sopenharmony_ci mask *= ARM_V7S_CONT_PAGES; 78062306a36Sopenharmony_ci return iopte_to_paddr(pte, lvl, &data->iop.cfg) | (iova & ~mask); 78162306a36Sopenharmony_ci} 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_cistatic struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg, 78462306a36Sopenharmony_ci void *cookie) 78562306a36Sopenharmony_ci{ 78662306a36Sopenharmony_ci struct arm_v7s_io_pgtable *data; 78762306a36Sopenharmony_ci slab_flags_t slab_flag; 78862306a36Sopenharmony_ci phys_addr_t paddr; 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci if (cfg->ias > (arm_v7s_is_mtk_enabled(cfg) ? 34 : ARM_V7S_ADDR_BITS)) 79162306a36Sopenharmony_ci return NULL; 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci if (cfg->oas > (arm_v7s_is_mtk_enabled(cfg) ? 35 : ARM_V7S_ADDR_BITS)) 79462306a36Sopenharmony_ci return NULL; 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci if (cfg->quirks & ~(IO_PGTABLE_QUIRK_ARM_NS | 79762306a36Sopenharmony_ci IO_PGTABLE_QUIRK_NO_PERMS | 79862306a36Sopenharmony_ci IO_PGTABLE_QUIRK_ARM_MTK_EXT | 79962306a36Sopenharmony_ci IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT)) 80062306a36Sopenharmony_ci return NULL; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci /* If ARM_MTK_4GB is enabled, the NO_PERMS is also expected. */ 80362306a36Sopenharmony_ci if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_EXT && 80462306a36Sopenharmony_ci !(cfg->quirks & IO_PGTABLE_QUIRK_NO_PERMS)) 80562306a36Sopenharmony_ci return NULL; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci if ((cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT) && 80862306a36Sopenharmony_ci !arm_v7s_is_mtk_enabled(cfg)) 80962306a36Sopenharmony_ci return NULL; 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci data = kmalloc(sizeof(*data), GFP_KERNEL); 81262306a36Sopenharmony_ci if (!data) 81362306a36Sopenharmony_ci return NULL; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci spin_lock_init(&data->split_lock); 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci /* 81862306a36Sopenharmony_ci * ARM_MTK_TTBR_EXT extend the translation table base support larger 81962306a36Sopenharmony_ci * memory address. 82062306a36Sopenharmony_ci */ 82162306a36Sopenharmony_ci slab_flag = cfg->quirks & IO_PGTABLE_QUIRK_ARM_MTK_TTBR_EXT ? 82262306a36Sopenharmony_ci 0 : ARM_V7S_TABLE_SLAB_FLAGS; 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci data->l2_tables = kmem_cache_create("io-pgtable_armv7s_l2", 82562306a36Sopenharmony_ci ARM_V7S_TABLE_SIZE(2, cfg), 82662306a36Sopenharmony_ci ARM_V7S_TABLE_SIZE(2, cfg), 82762306a36Sopenharmony_ci slab_flag, NULL); 82862306a36Sopenharmony_ci if (!data->l2_tables) 82962306a36Sopenharmony_ci goto out_free_data; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci data->iop.ops = (struct io_pgtable_ops) { 83262306a36Sopenharmony_ci .map_pages = arm_v7s_map_pages, 83362306a36Sopenharmony_ci .unmap_pages = arm_v7s_unmap_pages, 83462306a36Sopenharmony_ci .iova_to_phys = arm_v7s_iova_to_phys, 83562306a36Sopenharmony_ci }; 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci /* We have to do this early for __arm_v7s_alloc_table to work... */ 83862306a36Sopenharmony_ci data->iop.cfg = *cfg; 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci /* 84162306a36Sopenharmony_ci * Unless the IOMMU driver indicates supersection support by 84262306a36Sopenharmony_ci * having SZ_16M set in the initial bitmap, they won't be used. 84362306a36Sopenharmony_ci */ 84462306a36Sopenharmony_ci cfg->pgsize_bitmap &= SZ_4K | SZ_64K | SZ_1M | SZ_16M; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci /* TCR: T0SZ=0, EAE=0 (if applicable) */ 84762306a36Sopenharmony_ci cfg->arm_v7s_cfg.tcr = 0; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci /* 85062306a36Sopenharmony_ci * TEX remap: the indices used map to the closest equivalent types 85162306a36Sopenharmony_ci * under the non-TEX-remap interpretation of those attribute bits, 85262306a36Sopenharmony_ci * excepting various implementation-defined aspects of shareability. 85362306a36Sopenharmony_ci */ 85462306a36Sopenharmony_ci cfg->arm_v7s_cfg.prrr = ARM_V7S_PRRR_TR(1, ARM_V7S_PRRR_TYPE_DEVICE) | 85562306a36Sopenharmony_ci ARM_V7S_PRRR_TR(4, ARM_V7S_PRRR_TYPE_NORMAL) | 85662306a36Sopenharmony_ci ARM_V7S_PRRR_TR(7, ARM_V7S_PRRR_TYPE_NORMAL) | 85762306a36Sopenharmony_ci ARM_V7S_PRRR_DS0 | ARM_V7S_PRRR_DS1 | 85862306a36Sopenharmony_ci ARM_V7S_PRRR_NS1 | ARM_V7S_PRRR_NOS(7); 85962306a36Sopenharmony_ci cfg->arm_v7s_cfg.nmrr = ARM_V7S_NMRR_IR(7, ARM_V7S_RGN_WBWA) | 86062306a36Sopenharmony_ci ARM_V7S_NMRR_OR(7, ARM_V7S_RGN_WBWA); 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci /* Looking good; allocate a pgd */ 86362306a36Sopenharmony_ci data->pgd = __arm_v7s_alloc_table(1, GFP_KERNEL, data); 86462306a36Sopenharmony_ci if (!data->pgd) 86562306a36Sopenharmony_ci goto out_free_data; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci /* Ensure the empty pgd is visible before any actual TTBR write */ 86862306a36Sopenharmony_ci wmb(); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci /* TTBR */ 87162306a36Sopenharmony_ci paddr = virt_to_phys(data->pgd); 87262306a36Sopenharmony_ci if (arm_v7s_is_mtk_enabled(cfg)) 87362306a36Sopenharmony_ci cfg->arm_v7s_cfg.ttbr = paddr | upper_32_bits(paddr); 87462306a36Sopenharmony_ci else 87562306a36Sopenharmony_ci cfg->arm_v7s_cfg.ttbr = paddr | ARM_V7S_TTBR_S | 87662306a36Sopenharmony_ci (cfg->coherent_walk ? (ARM_V7S_TTBR_NOS | 87762306a36Sopenharmony_ci ARM_V7S_TTBR_IRGN_ATTR(ARM_V7S_RGN_WBWA) | 87862306a36Sopenharmony_ci ARM_V7S_TTBR_ORGN_ATTR(ARM_V7S_RGN_WBWA)) : 87962306a36Sopenharmony_ci (ARM_V7S_TTBR_IRGN_ATTR(ARM_V7S_RGN_NC) | 88062306a36Sopenharmony_ci ARM_V7S_TTBR_ORGN_ATTR(ARM_V7S_RGN_NC))); 88162306a36Sopenharmony_ci return &data->iop; 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ciout_free_data: 88462306a36Sopenharmony_ci kmem_cache_destroy(data->l2_tables); 88562306a36Sopenharmony_ci kfree(data); 88662306a36Sopenharmony_ci return NULL; 88762306a36Sopenharmony_ci} 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_cistruct io_pgtable_init_fns io_pgtable_arm_v7s_init_fns = { 89062306a36Sopenharmony_ci .alloc = arm_v7s_alloc_pgtable, 89162306a36Sopenharmony_ci .free = arm_v7s_free_pgtable, 89262306a36Sopenharmony_ci}; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci#ifdef CONFIG_IOMMU_IO_PGTABLE_ARMV7S_SELFTEST 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_cistatic struct io_pgtable_cfg *cfg_cookie __initdata; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_cistatic void __init dummy_tlb_flush_all(void *cookie) 89962306a36Sopenharmony_ci{ 90062306a36Sopenharmony_ci WARN_ON(cookie != cfg_cookie); 90162306a36Sopenharmony_ci} 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_cistatic void __init dummy_tlb_flush(unsigned long iova, size_t size, 90462306a36Sopenharmony_ci size_t granule, void *cookie) 90562306a36Sopenharmony_ci{ 90662306a36Sopenharmony_ci WARN_ON(cookie != cfg_cookie); 90762306a36Sopenharmony_ci WARN_ON(!(size & cfg_cookie->pgsize_bitmap)); 90862306a36Sopenharmony_ci} 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_cistatic void __init dummy_tlb_add_page(struct iommu_iotlb_gather *gather, 91162306a36Sopenharmony_ci unsigned long iova, size_t granule, 91262306a36Sopenharmony_ci void *cookie) 91362306a36Sopenharmony_ci{ 91462306a36Sopenharmony_ci dummy_tlb_flush(iova, granule, granule, cookie); 91562306a36Sopenharmony_ci} 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_cistatic const struct iommu_flush_ops dummy_tlb_ops __initconst = { 91862306a36Sopenharmony_ci .tlb_flush_all = dummy_tlb_flush_all, 91962306a36Sopenharmony_ci .tlb_flush_walk = dummy_tlb_flush, 92062306a36Sopenharmony_ci .tlb_add_page = dummy_tlb_add_page, 92162306a36Sopenharmony_ci}; 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci#define __FAIL(ops) ({ \ 92462306a36Sopenharmony_ci WARN(1, "selftest: test failed\n"); \ 92562306a36Sopenharmony_ci selftest_running = false; \ 92662306a36Sopenharmony_ci -EFAULT; \ 92762306a36Sopenharmony_ci}) 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_cistatic int __init arm_v7s_do_selftests(void) 93062306a36Sopenharmony_ci{ 93162306a36Sopenharmony_ci struct io_pgtable_ops *ops; 93262306a36Sopenharmony_ci struct io_pgtable_cfg cfg = { 93362306a36Sopenharmony_ci .tlb = &dummy_tlb_ops, 93462306a36Sopenharmony_ci .oas = 32, 93562306a36Sopenharmony_ci .ias = 32, 93662306a36Sopenharmony_ci .coherent_walk = true, 93762306a36Sopenharmony_ci .quirks = IO_PGTABLE_QUIRK_ARM_NS, 93862306a36Sopenharmony_ci .pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M, 93962306a36Sopenharmony_ci }; 94062306a36Sopenharmony_ci unsigned int iova, size, iova_start; 94162306a36Sopenharmony_ci unsigned int i, loopnr = 0; 94262306a36Sopenharmony_ci size_t mapped; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci selftest_running = true; 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci cfg_cookie = &cfg; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci ops = alloc_io_pgtable_ops(ARM_V7S, &cfg, &cfg); 94962306a36Sopenharmony_ci if (!ops) { 95062306a36Sopenharmony_ci pr_err("selftest: failed to allocate io pgtable ops\n"); 95162306a36Sopenharmony_ci return -EINVAL; 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci /* 95562306a36Sopenharmony_ci * Initial sanity checks. 95662306a36Sopenharmony_ci * Empty page tables shouldn't provide any translations. 95762306a36Sopenharmony_ci */ 95862306a36Sopenharmony_ci if (ops->iova_to_phys(ops, 42)) 95962306a36Sopenharmony_ci return __FAIL(ops); 96062306a36Sopenharmony_ci 96162306a36Sopenharmony_ci if (ops->iova_to_phys(ops, SZ_1G + 42)) 96262306a36Sopenharmony_ci return __FAIL(ops); 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci if (ops->iova_to_phys(ops, SZ_2G + 42)) 96562306a36Sopenharmony_ci return __FAIL(ops); 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci /* 96862306a36Sopenharmony_ci * Distinct mappings of different granule sizes. 96962306a36Sopenharmony_ci */ 97062306a36Sopenharmony_ci iova = 0; 97162306a36Sopenharmony_ci for_each_set_bit(i, &cfg.pgsize_bitmap, BITS_PER_LONG) { 97262306a36Sopenharmony_ci size = 1UL << i; 97362306a36Sopenharmony_ci if (ops->map_pages(ops, iova, iova, size, 1, 97462306a36Sopenharmony_ci IOMMU_READ | IOMMU_WRITE | 97562306a36Sopenharmony_ci IOMMU_NOEXEC | IOMMU_CACHE, 97662306a36Sopenharmony_ci GFP_KERNEL, &mapped)) 97762306a36Sopenharmony_ci return __FAIL(ops); 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci /* Overlapping mappings */ 98062306a36Sopenharmony_ci if (!ops->map_pages(ops, iova, iova + size, size, 1, 98162306a36Sopenharmony_ci IOMMU_READ | IOMMU_NOEXEC, GFP_KERNEL, 98262306a36Sopenharmony_ci &mapped)) 98362306a36Sopenharmony_ci return __FAIL(ops); 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) 98662306a36Sopenharmony_ci return __FAIL(ops); 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci iova += SZ_16M; 98962306a36Sopenharmony_ci loopnr++; 99062306a36Sopenharmony_ci } 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_ci /* Partial unmap */ 99362306a36Sopenharmony_ci i = 1; 99462306a36Sopenharmony_ci size = 1UL << __ffs(cfg.pgsize_bitmap); 99562306a36Sopenharmony_ci while (i < loopnr) { 99662306a36Sopenharmony_ci iova_start = i * SZ_16M; 99762306a36Sopenharmony_ci if (ops->unmap_pages(ops, iova_start + size, size, 1, NULL) != size) 99862306a36Sopenharmony_ci return __FAIL(ops); 99962306a36Sopenharmony_ci 100062306a36Sopenharmony_ci /* Remap of partial unmap */ 100162306a36Sopenharmony_ci if (ops->map_pages(ops, iova_start + size, size, size, 1, 100262306a36Sopenharmony_ci IOMMU_READ, GFP_KERNEL, &mapped)) 100362306a36Sopenharmony_ci return __FAIL(ops); 100462306a36Sopenharmony_ci 100562306a36Sopenharmony_ci if (ops->iova_to_phys(ops, iova_start + size + 42) 100662306a36Sopenharmony_ci != (size + 42)) 100762306a36Sopenharmony_ci return __FAIL(ops); 100862306a36Sopenharmony_ci i++; 100962306a36Sopenharmony_ci } 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ci /* Full unmap */ 101262306a36Sopenharmony_ci iova = 0; 101362306a36Sopenharmony_ci for_each_set_bit(i, &cfg.pgsize_bitmap, BITS_PER_LONG) { 101462306a36Sopenharmony_ci size = 1UL << i; 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci if (ops->unmap_pages(ops, iova, size, 1, NULL) != size) 101762306a36Sopenharmony_ci return __FAIL(ops); 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_ci if (ops->iova_to_phys(ops, iova + 42)) 102062306a36Sopenharmony_ci return __FAIL(ops); 102162306a36Sopenharmony_ci 102262306a36Sopenharmony_ci /* Remap full block */ 102362306a36Sopenharmony_ci if (ops->map_pages(ops, iova, iova, size, 1, IOMMU_WRITE, 102462306a36Sopenharmony_ci GFP_KERNEL, &mapped)) 102562306a36Sopenharmony_ci return __FAIL(ops); 102662306a36Sopenharmony_ci 102762306a36Sopenharmony_ci if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) 102862306a36Sopenharmony_ci return __FAIL(ops); 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci iova += SZ_16M; 103162306a36Sopenharmony_ci } 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci free_io_pgtable_ops(ops); 103462306a36Sopenharmony_ci 103562306a36Sopenharmony_ci selftest_running = false; 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci pr_info("self test ok\n"); 103862306a36Sopenharmony_ci return 0; 103962306a36Sopenharmony_ci} 104062306a36Sopenharmony_cisubsys_initcall(arm_v7s_do_selftests); 104162306a36Sopenharmony_ci#endif 1042