/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include "msm_iommu_priv.h" #include #include "msm_iommu_pagetable.h" #define NUM_FL_PTE 4 /* First level */ #define NUM_SL_PTE 512 /* Second level */ #define NUM_TL_PTE 512 /* Third level */ #define PTE_SIZE 8 #define FL_ALIGN 0x20 /* First-level/second-level page table bits */ #define FL_OFFSET(va) (((va) & 0xC0000000) >> 30) #define FLSL_BASE_MASK (0xFFFFFFF000ULL) #define FLSL_1G_BLOCK_MASK (0xFFC0000000ULL) #define FLSL_BLOCK_MASK (0xFFFFE00000ULL) #define FLSL_TYPE_BLOCK (1 << 0) #define FLSL_TYPE_TABLE (3 << 0) #define FLSL_PTE_TYPE_MASK (3 << 0) #define FLSL_APTABLE_RO (2 << 61) #define FLSL_APTABLE_RW (0 << 61) #define FL_TYPE_SECT (2 << 0) #define FL_SUPERSECTION (1 << 18) #define FL_AP0 (1 << 10) #define FL_AP1 (1 << 11) #define FL_AP2 (1 << 15) #define FL_SHARED (1 << 16) #define FL_BUFFERABLE (1 << 2) #define FL_CACHEABLE (1 << 3) #define FL_TEX0 (1 << 12) #define FL_NG (1 << 17) /* Second-level page table bits */ #define SL_OFFSET(va) (((va) & 0x3FE00000) >> 21) /* Third-level page table bits */ #define TL_OFFSET(va) (((va) & 0x1FF000) >> 12) #define TL_TYPE_PAGE (3 << 0) #define TL_PAGE_MASK (0xFFFFFFF000ULL) #define TL_ATTR_INDEX_MASK (0x7) #define TL_ATTR_INDEX_SHIFT (0x2) #define TL_NS (0x1 << 5) #define TL_AP_RO (0x3 << 6) /* Access Permission: R */ #define TL_AP_RW (0x1 << 6) /* Access Permission: RW */ #define TL_AP_PR_RW (0x0 << 6) /* Privileged Mode RW */ #define TL_AP_PR_RO (0x2 << 6) /* Privileged Mode R */ #define TL_SH_ISH (0x3 << 8) /* Inner shareable */ #define TL_SH_OSH (0x2 << 8) /* Outer shareable */ #define TL_SH_NSH (0x0 << 8) /* Non-shareable */ #define TL_AF (0x1 << 10) /* Access Flag */ #define TL_NG (0x1 << 11) /* Non-Global */ #define TL_CH (0x1ULL << 52) /* Contiguous hint */ #define TL_PXN (0x1ULL << 53) /* Privilege Execute Never */ #define TL_XN (0x1ULL << 54) /* Execute Never */ /* normal non-cacheable */ #define PTE_MT_BUFFERABLE (1 << 2) /* normal inner write-alloc */ #define PTE_MT_WRITEALLOC (7 << 2) #define PTE_MT_MASK (7 << 2) #define FOLLOW_TO_NEXT_TABLE(pte) ((u64 *) __va(((*pte) & FLSL_BASE_MASK))) static void __msm_iommu_pagetable_unmap_range(struct msm_iommu_pt *pt, unsigned long va, size_t len, u32 silent); static inline void clean_pte(u64 *start, u64 *end, s32 redirect) { if (!redirect) dmac_flush_range(start, end); } s32 msm_iommu_pagetable_alloc(struct msm_iommu_pt *pt) { u32 size = PTE_SIZE * NUM_FL_PTE + FL_ALIGN; phys_addr_t fl_table_phys; pt->unaligned_fl_table = kzalloc(size, GFP_KERNEL); if (!pt->unaligned_fl_table) return -ENOMEM; fl_table_phys = virt_to_phys(pt->unaligned_fl_table); fl_table_phys = ALIGN(fl_table_phys, FL_ALIGN); pt->fl_table = phys_to_virt(fl_table_phys); pt->sl_table_shadow = kzalloc(sizeof(u64 *) * NUM_FL_PTE, GFP_KERNEL); if (!pt->sl_table_shadow) { kfree(pt->unaligned_fl_table); return -ENOMEM; } clean_pte(pt->fl_table, pt->fl_table + NUM_FL_PTE, pt->redirect); return 0; } void msm_iommu_pagetable_free(struct msm_iommu_pt *pt) { s32 i; u64 *fl_table = pt->fl_table; for (i = 0; i < NUM_FL_PTE; ++i) { if ((fl_table[i] & FLSL_TYPE_TABLE) == FLSL_TYPE_TABLE) { u64 p = fl_table[i] & FLSL_BASE_MASK; free_page((u32)phys_to_virt(p)); } if ((pt->sl_table_shadow[i])) free_page((u32)pt->sl_table_shadow[i]); } kfree(pt->unaligned_fl_table); pt->unaligned_fl_table = 0; pt->fl_table = 0; kfree(pt->sl_table_shadow); } void msm_iommu_pagetable_free_tables(struct msm_iommu_pt *pt, unsigned long va, size_t len) { /* * Adding 2 for worst case. We could be spanning 3 second level pages * if we unmapped just over 2MB. */ u32 n_entries = len / SZ_2M + 2; u32 fl_offset = FL_OFFSET(va); u32 sl_offset = SL_OFFSET(va); u32 i; for (i = 0; i < n_entries && fl_offset < NUM_FL_PTE; ++i) { void *tl_table_va; u32 entry; u64 *sl_pte_shadow; sl_pte_shadow = pt->sl_table_shadow[fl_offset]; if (!sl_pte_shadow) break; sl_pte_shadow += sl_offset; entry = *sl_pte_shadow; tl_table_va = __va(((*sl_pte_shadow) & ~0xFFF)); if (entry && !(entry & 0xFFF)) { free_page((unsigned long)tl_table_va); *sl_pte_shadow = 0; } ++sl_offset; if (sl_offset >= NUM_TL_PTE) { sl_offset = 0; ++fl_offset; } } } #ifdef CONFIG_ARM_LPAE /* * If LPAE is enabled in the ARM processor then just use the same * cache policy as the kernel for the SMMU cached mappings. */ static inline u32 __get_cache_attr(void) { return pgprot_kernel & PTE_MT_MASK; } #else /* * If LPAE is NOT enabled in the ARM processor then hard code the policy. * This is mostly for debugging so that we can enable SMMU LPAE without * ARM CPU LPAE. */ static inline u32 __get_cache_attr(void) { return PTE_MT_WRITEALLOC; } #endif /* * Get the IOMMU attributes for the ARM LPAE long descriptor format page * table entry bits. The only upper attribute bits we currently use is the * contiguous bit which is set when we actually have a contiguous mapping. * Lower attribute bits specify memory attributes and the protection * (Read/Write/Execute). */ static inline void __get_attr(s32 prot, u64 *upper_attr, u64 *lower_attr) { u32 attr_idx = PTE_MT_BUFFERABLE; *upper_attr = 0; *lower_attr = 0; if (!(prot & (IOMMU_READ | IOMMU_WRITE))) { prot |= IOMMU_READ | IOMMU_WRITE; WARN_ONCE(1, "No attributes in iommu mapping; assuming RW\n"); } if ((prot & IOMMU_WRITE) && !(prot & IOMMU_READ)) { prot |= IOMMU_READ; WARN_ONCE(1, "Write-only unsupported; falling back to RW\n"); } if (prot & IOMMU_CACHE) attr_idx = __get_cache_attr(); *lower_attr |= attr_idx; *lower_attr |= TL_NG | TL_AF; *lower_attr |= (prot & IOMMU_CACHE) ? TL_SH_ISH : TL_SH_NSH; if (prot & IOMMU_PRIV) *lower_attr |= (prot & IOMMU_WRITE) ? TL_AP_PR_RW : TL_AP_PR_RO; else *lower_attr |= (prot & IOMMU_WRITE) ? TL_AP_RW : TL_AP_RO; } static inline u64 *make_second_level_tbl(struct msm_iommu_pt *pt, u32 offset) { u64 *sl = (u64 *) __get_free_page(GFP_KERNEL); u64 *fl_pte = pt->fl_table + offset; if (!sl) { pr_err("Could not allocate second level table\n"); goto fail; } pt->sl_table_shadow[offset] = (u64 *) __get_free_page(GFP_KERNEL); if (!pt->sl_table_shadow[offset]) { free_page((u32) sl); pr_err("Could not allocate second level shadow table\n"); goto fail; } memset(sl, 0, SZ_4K); memset(pt->sl_table_shadow[offset], 0, SZ_4K); clean_pte(sl, sl + NUM_SL_PTE, pt->redirect); /* Leave APTable bits 0 to let next level decide access permissinons */ *fl_pte = (((phys_addr_t)__pa(sl)) & FLSL_BASE_MASK) | FLSL_TYPE_TABLE; clean_pte(fl_pte, fl_pte + 1, pt->redirect); fail: return sl; } static inline u64 *make_third_level_tbl(s32 redirect, u64 *sl_pte, u64 *sl_pte_shadow) { u64 *tl = (u64 *) __get_free_page(GFP_KERNEL); if (!tl) { pr_err("Could not allocate third level table\n"); goto fail; } memset(tl, 0, SZ_4K); clean_pte(tl, tl + NUM_TL_PTE, redirect); /* Leave APTable bits 0 to let next level decide access permissions */ *sl_pte = (((phys_addr_t)__pa(tl)) & FLSL_BASE_MASK) | FLSL_TYPE_TABLE; *sl_pte_shadow = *sl_pte & ~0xFFF; clean_pte(sl_pte, sl_pte + 1, redirect); fail: return tl; } static inline s32 tl_4k_map(u64 *tl_pte, phys_addr_t pa, u64 upper_attr, u64 lower_attr, s32 redirect) { s32 ret = 0; if (*tl_pte) { ret = -EBUSY; goto fail; } *tl_pte = upper_attr | (pa & TL_PAGE_MASK) | lower_attr | TL_TYPE_PAGE; clean_pte(tl_pte, tl_pte + 1, redirect); fail: return ret; } static inline s32 tl_64k_map(u64 *tl_pte, phys_addr_t pa, u64 upper_attr, u64 lower_attr, s32 redirect) { s32 ret = 0; s32 i; for (i = 0; i < 16; ++i) if (*(tl_pte+i)) { ret = -EBUSY; goto fail; } /* Add Contiguous hint TL_CH */ upper_attr |= TL_CH; for (i = 0; i < 16; ++i) *(tl_pte+i) = upper_attr | (pa & TL_PAGE_MASK) | lower_attr | TL_TYPE_PAGE; clean_pte(tl_pte, tl_pte + 16, redirect); fail: return ret; } static inline s32 sl_2m_map(u64 *sl_pte, phys_addr_t pa, u64 upper_attr, u64 lower_attr, s32 redirect) { s32 ret = 0; if (*sl_pte) { ret = -EBUSY; goto fail; } *sl_pte = upper_attr | (pa & FLSL_BLOCK_MASK) | lower_attr | FLSL_TYPE_BLOCK; clean_pte(sl_pte, sl_pte + 1, redirect); fail: return ret; } static inline s32 sl_32m_map(u64 *sl_pte, phys_addr_t pa, u64 upper_attr, u64 lower_attr, s32 redirect) { s32 i; s32 ret = 0; for (i = 0; i < 16; ++i) { if (*(sl_pte+i)) { ret = -EBUSY; goto fail; } } /* Add Contiguous hint TL_CH */ upper_attr |= TL_CH; for (i = 0; i < 16; ++i) *(sl_pte+i) = upper_attr | (pa & FLSL_BLOCK_MASK) | lower_attr | FLSL_TYPE_BLOCK; clean_pte(sl_pte, sl_pte + 16, redirect); fail: return ret; } static inline s32 fl_1G_map(u64 *fl_pte, phys_addr_t pa, u64 upper_attr, u64 lower_attr, s32 redirect) { s32 ret = 0; if (*fl_pte) { ret = -EBUSY; goto fail; } *fl_pte = upper_attr | (pa & FLSL_1G_BLOCK_MASK) | lower_attr | FLSL_TYPE_BLOCK; clean_pte(fl_pte, fl_pte + 1, redirect); fail: return ret; } static inline s32 common_error_check(size_t len, u64 const *fl_table) { s32 ret = 0; if (len != SZ_1G && len != SZ_32M && len != SZ_2M && len != SZ_64K && len != SZ_4K) { pr_err("Bad length: %d\n", len); ret = -EINVAL; } else if (!fl_table) { pr_err("Null page table\n"); ret = -EINVAL; } return ret; } static inline s32 handle_1st_lvl(struct msm_iommu_pt *pt, u32 offset, phys_addr_t pa, size_t len, u64 upper_attr, u64 lower_attr) { s32 ret = 0; u64 *fl_pte = pt->fl_table + offset; if (len == SZ_1G) { ret = fl_1G_map(fl_pte, pa, upper_attr, lower_attr, pt->redirect); } else { /* Need second level page table */ if (*fl_pte == 0) { if (make_second_level_tbl(pt, offset) == NULL) ret = -ENOMEM; } if (!ret) { if ((*fl_pte & FLSL_TYPE_TABLE) != FLSL_TYPE_TABLE) ret = -EBUSY; } } return ret; } static inline s32 handle_3rd_lvl(u64 *sl_pte, u64 *sl_pte_shadow, u32 va, phys_addr_t pa, u64 upper_attr, u64 lower_attr, size_t len, s32 redirect) { u64 *tl_table; u64 *tl_pte; u32 tl_offset; s32 ret = 0; u32 n_entries; /* Need a 3rd level table */ if (*sl_pte == 0) { if (make_third_level_tbl(redirect, sl_pte, sl_pte_shadow) == NULL) { ret = -ENOMEM; goto fail; } } if ((*sl_pte & FLSL_TYPE_TABLE) != FLSL_TYPE_TABLE) { ret = -EBUSY; goto fail; } tl_table = FOLLOW_TO_NEXT_TABLE(sl_pte); tl_offset = TL_OFFSET(va); tl_pte = tl_table + tl_offset; if (len == SZ_64K) { ret = tl_64k_map(tl_pte, pa, upper_attr, lower_attr, redirect); n_entries = 16; } else { ret = tl_4k_map(tl_pte, pa, upper_attr, lower_attr, redirect); n_entries = 1; } /* Increment map count */ if (!ret) *sl_pte_shadow += n_entries; fail: return ret; } int msm_iommu_pagetable_map(struct msm_iommu_pt *pt, unsigned long va, phys_addr_t pa, size_t len, int prot) { s32 ret; struct scatterlist sg; ret = common_error_check(len, pt->fl_table); if (ret) goto fail; sg_init_table(&sg, 1); sg_dma_address(&sg) = pa; sg.length = len; ret = msm_iommu_pagetable_map_range(pt, va, &sg, len, prot); fail: return ret; } static void fl_1G_unmap(u64 *fl_pte, s32 redirect) { *fl_pte = 0; clean_pte(fl_pte, fl_pte + 1, redirect); } size_t msm_iommu_pagetable_unmap(struct msm_iommu_pt *pt, unsigned long va, size_t len) { msm_iommu_pagetable_unmap_range(pt, va, len); return len; } static phys_addr_t get_phys_addr(struct scatterlist *sg) { /* * Try sg_dma_address first so that we can * map carveout regions that do not have a * struct page associated with them. */ phys_addr_t pa = sg_dma_address(sg); if (pa == 0) pa = sg_phys(sg); return pa; } #ifdef CONFIG_IOMMU_FORCE_4K_MAPPINGS static inline int is_fully_aligned(unsigned int va, phys_addr_t pa, size_t len, int align) { if (align == SZ_4K) return IS_ALIGNED(va | pa, align) && (len >= align); else return 0; } #else static inline int is_fully_aligned(unsigned int va, phys_addr_t pa, size_t len, int align) { return IS_ALIGNED(va | pa, align) && (len >= align); } #endif s32 msm_iommu_pagetable_map_range(struct msm_iommu_pt *pt, unsigned long va, struct scatterlist *sg, size_t len, s32 prot) { phys_addr_t pa; u32 offset = 0; u64 *fl_pte; u64 *sl_pte; u64 *sl_pte_shadow; u32 fl_offset; u32 sl_offset; u64 *sl_table = NULL; u32 chunk_size, chunk_offset = 0; s32 ret = 0; u64 up_at; u64 lo_at; u32 redirect = pt->redirect; unsigned int start_va = va; BUG_ON(len & (SZ_4K - 1)); if (!pt->fl_table) { pr_err("Null page table\n"); ret = -EINVAL; goto fail; } __get_attr(prot, &up_at, &lo_at); pa = get_phys_addr(sg); while (offset < len) { u32 chunk_left = sg->length - chunk_offset; fl_offset = FL_OFFSET(va); fl_pte = pt->fl_table + fl_offset; chunk_size = SZ_4K; if (is_fully_aligned(va, pa, chunk_left, SZ_1G)) chunk_size = SZ_1G; else if (is_fully_aligned(va, pa, chunk_left, SZ_32M)) chunk_size = SZ_32M; else if (is_fully_aligned(va, pa, chunk_left, SZ_2M)) chunk_size = SZ_2M; else if (is_fully_aligned(va, pa, chunk_left, SZ_64K)) chunk_size = SZ_64K; trace_iommu_map_range(va, pa, sg->length, chunk_size); ret = handle_1st_lvl(pt, fl_offset, pa, chunk_size, up_at, lo_at); if (ret) goto fail; sl_table = FOLLOW_TO_NEXT_TABLE(fl_pte); sl_offset = SL_OFFSET(va); sl_pte = sl_table + sl_offset; sl_pte_shadow = pt->sl_table_shadow[fl_offset] + sl_offset; if (chunk_size == SZ_32M) ret = sl_32m_map(sl_pte, pa, up_at, lo_at, redirect); else if (chunk_size == SZ_2M) ret = sl_2m_map(sl_pte, pa, up_at, lo_at, redirect); else if (chunk_size == SZ_64K || chunk_size == SZ_4K) ret = handle_3rd_lvl(sl_pte, sl_pte_shadow, va, pa, up_at, lo_at, chunk_size, redirect); if (ret) goto fail; offset += chunk_size; chunk_offset += chunk_size; va += chunk_size; pa += chunk_size; if (chunk_offset >= sg->length && offset < len) { chunk_offset = 0; sg = sg_next(sg); pa = get_phys_addr(sg); } } fail: if (ret && offset > 0) __msm_iommu_pagetable_unmap_range(pt, start_va, offset, 1); return ret; } void msm_iommu_pagetable_unmap_range(struct msm_iommu_pt *pt, unsigned long va, size_t len) { __msm_iommu_pagetable_unmap_range(pt, va, len, 0); } static void __msm_iommu_pagetable_unmap_range(struct msm_iommu_pt *pt, unsigned long va, size_t len, u32 silent) { u32 offset = 0; u64 *fl_pte; u64 *sl_pte; u64 *tl_pte; u32 fl_offset; u32 sl_offset; u64 *sl_table; u64 *tl_table; u32 tl_start, tl_end; u32 redirect = pt->redirect; BUG_ON(len & (SZ_4K - 1)); while (offset < len) { u32 entries; u32 left_to_unmap = len - offset; u32 type; fl_offset = FL_OFFSET(va); fl_pte = pt->fl_table + fl_offset; if (*fl_pte == 0) { if (!silent) pr_err("First level PTE is 0 at index 0x%x (offset: 0x%x)\n", fl_offset, offset); return; } type = *fl_pte & FLSL_PTE_TYPE_MASK; if (type == FLSL_TYPE_BLOCK) { fl_1G_unmap(fl_pte, redirect); va += SZ_1G; offset += SZ_1G; } else if (type == FLSL_TYPE_TABLE) { sl_table = FOLLOW_TO_NEXT_TABLE(fl_pte); sl_offset = SL_OFFSET(va); sl_pte = sl_table + sl_offset; type = *sl_pte & FLSL_PTE_TYPE_MASK; if (type == FLSL_TYPE_BLOCK) { *sl_pte = 0; clean_pte(sl_pte, sl_pte + 1, redirect); offset += SZ_2M; va += SZ_2M; } else if (type == FLSL_TYPE_TABLE) { u64 *sl_pte_shadow = pt->sl_table_shadow[fl_offset] + sl_offset; tl_start = TL_OFFSET(va); tl_table = FOLLOW_TO_NEXT_TABLE(sl_pte); tl_end = (left_to_unmap / SZ_4K) + tl_start; if (tl_end > NUM_TL_PTE) tl_end = NUM_TL_PTE; entries = tl_end - tl_start; memset(tl_table + tl_start, 0, entries * sizeof(*tl_pte)); clean_pte(tl_table + tl_start, tl_table + tl_end, redirect); BUG_ON((*sl_pte_shadow & 0xFFF) < entries); /* Decrement map count */ *sl_pte_shadow -= entries; if (!(*sl_pte_shadow & 0xFFF)) { *sl_pte = 0; clean_pte(sl_pte, sl_pte + 1, pt->redirect); } offset += entries * SZ_4K; va += entries * SZ_4K; } else { if (!silent) pr_err("Second level PTE (0x%llx) is invalid at index 0x%x (offset: 0x%x)\n", *sl_pte, sl_offset, offset); } } else { if (!silent) pr_err("First level PTE (0x%llx) is invalid at index 0x%x (offset: 0x%x)\n", *fl_pte, fl_offset, offset); } } } void msm_iommu_flush_pagetable(struct msm_iommu_pt *pt, unsigned long va, size_t len) { /* Consolidated flush of page tables has not been implemented for * LPAE driver as of now. */ } phys_addr_t msm_iommu_iova_to_phys_soft(struct iommu_domain *domain, dma_addr_t va) { pr_err("iova_to_phys is not implemented for LPAE\n"); return 0; } void __init msm_iommu_pagetable_init(void) { }