Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/msm/iommu: optimize map/unmap

Using map_pages/unmap_pages cuts down on the # of pgtable walks needed
in the process of finding where to insert/remove an entry. The end
result is ~5-10x faster than mapping a single page at a time.

v2: Rename iommu_pgsize(), drop obsolete comments, fix error handling
in msm_iommu_pagetable_map()

Signed-off-by: Rob Clark <robdclark@chromium.org>
Reviewed-by: Sai Prakash Ranjan <quic_saipraka@quicinc.com>
Patchwork: https://patchwork.freedesktop.org/patch/498892/
Link: https://lore.kernel.org/r/20220823163719.90399-1-robdclark@gmail.com

+86 -15
+86 -15
drivers/gpu/drm/msm/msm_iommu.c
··· 21 21 struct msm_mmu base; 22 22 struct msm_mmu *parent; 23 23 struct io_pgtable_ops *pgtbl_ops; 24 + unsigned long pgsize_bitmap; /* Bitmap of page sizes in use */ 24 25 phys_addr_t ttbr; 25 26 u32 asid; 26 27 }; ··· 30 29 return container_of(mmu, struct msm_iommu_pagetable, base); 31 30 } 32 31 32 + /* based on iommu_pgsize() in iommu.c: */ 33 + static size_t calc_pgsize(struct msm_iommu_pagetable *pagetable, 34 + unsigned long iova, phys_addr_t paddr, 35 + size_t size, size_t *count) 36 + { 37 + unsigned int pgsize_idx, pgsize_idx_next; 38 + unsigned long pgsizes; 39 + size_t offset, pgsize, pgsize_next; 40 + unsigned long addr_merge = paddr | iova; 41 + 42 + /* Page sizes supported by the hardware and small enough for @size */ 43 + pgsizes = pagetable->pgsize_bitmap & GENMASK(__fls(size), 0); 44 + 45 + /* Constrain the page sizes further based on the maximum alignment */ 46 + if (likely(addr_merge)) 47 + pgsizes &= GENMASK(__ffs(addr_merge), 0); 48 + 49 + /* Make sure we have at least one suitable page size */ 50 + BUG_ON(!pgsizes); 51 + 52 + /* Pick the biggest page size remaining */ 53 + pgsize_idx = __fls(pgsizes); 54 + pgsize = BIT(pgsize_idx); 55 + if (!count) 56 + return pgsize; 57 + 58 + /* Find the next biggest support page size, if it exists */ 59 + pgsizes = pagetable->pgsize_bitmap & ~GENMASK(pgsize_idx, 0); 60 + if (!pgsizes) 61 + goto out_set_count; 62 + 63 + pgsize_idx_next = __ffs(pgsizes); 64 + pgsize_next = BIT(pgsize_idx_next); 65 + 66 + /* 67 + * There's no point trying a bigger page size unless the virtual 68 + * and physical addresses are similarly offset within the larger page. 69 + */ 70 + if ((iova ^ paddr) & (pgsize_next - 1)) 71 + goto out_set_count; 72 + 73 + /* Calculate the offset to the next page size alignment boundary */ 74 + offset = pgsize_next - (addr_merge & (pgsize_next - 1)); 75 + 76 + /* 77 + * If size is big enough to accommodate the larger page, reduce 78 + * the number of smaller pages. 79 + */ 80 + if (offset + pgsize_next <= size) 81 + size = offset; 82 + 83 + out_set_count: 84 + *count = size >> pgsize_idx; 85 + return pgsize; 86 + } 87 + 33 88 static int msm_iommu_pagetable_unmap(struct msm_mmu *mmu, u64 iova, 34 89 size_t size) 35 90 { 36 91 struct msm_iommu_pagetable *pagetable = to_pagetable(mmu); 37 92 struct io_pgtable_ops *ops = pagetable->pgtbl_ops; 38 - size_t unmapped = 0; 39 93 40 - /* Unmap the block one page at a time */ 41 94 while (size) { 42 - unmapped += ops->unmap(ops, iova, 4096, NULL); 43 - iova += 4096; 44 - size -= 4096; 95 + size_t unmapped, pgsize, count; 96 + 97 + pgsize = calc_pgsize(pagetable, iova, iova, size, &count); 98 + 99 + unmapped = ops->unmap_pages(ops, iova, pgsize, count, NULL); 100 + if (!unmapped) 101 + break; 102 + 103 + iova += unmapped; 104 + size -= unmapped; 45 105 } 46 106 47 107 iommu_flush_iotlb_all(to_msm_iommu(pagetable->parent)->domain); 48 108 49 - return (unmapped == size) ? 0 : -EINVAL; 109 + return (size == 0) ? 0 : -EINVAL; 50 110 } 51 111 52 112 static int msm_iommu_pagetable_map(struct msm_mmu *mmu, u64 iova, ··· 116 54 struct msm_iommu_pagetable *pagetable = to_pagetable(mmu); 117 55 struct io_pgtable_ops *ops = pagetable->pgtbl_ops; 118 56 struct scatterlist *sg; 119 - size_t mapped = 0; 120 57 u64 addr = iova; 121 58 unsigned int i; 122 59 ··· 123 62 size_t size = sg->length; 124 63 phys_addr_t phys = sg_phys(sg); 125 64 126 - /* Map the block one page at a time */ 127 65 while (size) { 128 - if (ops->map(ops, addr, phys, 4096, prot, GFP_KERNEL)) { 129 - msm_iommu_pagetable_unmap(mmu, iova, mapped); 66 + size_t pgsize, count, mapped = 0; 67 + int ret; 68 + 69 + pgsize = calc_pgsize(pagetable, addr, phys, size, &count); 70 + 71 + ret = ops->map_pages(ops, addr, phys, pgsize, count, 72 + prot, GFP_KERNEL, &mapped); 73 + 74 + /* map_pages could fail after mapping some of the pages, 75 + * so update the counters before error handling. 76 + */ 77 + phys += mapped; 78 + addr += mapped; 79 + size -= mapped; 80 + 81 + if (ret) { 82 + msm_iommu_pagetable_unmap(mmu, iova, addr - iova); 130 83 return -EINVAL; 131 84 } 132 - 133 - phys += 4096; 134 - addr += 4096; 135 - size -= 4096; 136 - mapped += 4096; 137 85 } 138 86 } 139 87 ··· 277 207 278 208 /* Needed later for TLB flush */ 279 209 pagetable->parent = parent; 210 + pagetable->pgsize_bitmap = ttbr0_cfg.pgsize_bitmap; 280 211 pagetable->ttbr = ttbr0_cfg.arm_lpae_s1_cfg.ttbr; 281 212 282 213 /*