| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Tegra host1x Job |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2010-2015, NVIDIA Corporation. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify it |
|---|
| 7 | | - * under the terms and conditions of the GNU General Public License, |
|---|
| 8 | | - * version 2, as published by the Free Software Foundation. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope it will be useful, but WITHOUT |
|---|
| 11 | | - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|---|
| 12 | | - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|---|
| 13 | | - * more details. |
|---|
| 14 | | - * |
|---|
| 15 | | - * You should have received a copy of the GNU General Public License |
|---|
| 16 | | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | 6 | */ |
|---|
| 18 | 7 | |
|---|
| 19 | 8 | #include <linux/dma-mapping.h> |
|---|
| 20 | 9 | #include <linux/err.h> |
|---|
| 21 | 10 | #include <linux/host1x.h> |
|---|
| 11 | +#include <linux/iommu.h> |
|---|
| 22 | 12 | #include <linux/kref.h> |
|---|
| 23 | 13 | #include <linux/module.h> |
|---|
| 24 | 14 | #include <linux/scatterlist.h> |
|---|
| .. | .. |
|---|
| 37 | 27 | u32 num_cmdbufs, u32 num_relocs) |
|---|
| 38 | 28 | { |
|---|
| 39 | 29 | struct host1x_job *job = NULL; |
|---|
| 40 | | - unsigned int num_unpins = num_cmdbufs + num_relocs; |
|---|
| 30 | + unsigned int num_unpins = num_relocs; |
|---|
| 41 | 31 | u64 total; |
|---|
| 42 | 32 | void *mem; |
|---|
| 33 | + |
|---|
| 34 | + if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL)) |
|---|
| 35 | + num_unpins += num_cmdbufs; |
|---|
| 43 | 36 | |
|---|
| 44 | 37 | /* Check that we're not going to overflow */ |
|---|
| 45 | 38 | total = sizeof(struct host1x_job) + |
|---|
| .. | .. |
|---|
| 110 | 103 | |
|---|
| 111 | 104 | static unsigned int pin_job(struct host1x *host, struct host1x_job *job) |
|---|
| 112 | 105 | { |
|---|
| 106 | + struct host1x_client *client = job->client; |
|---|
| 107 | + struct device *dev = client->dev; |
|---|
| 108 | + struct host1x_job_gather *g; |
|---|
| 109 | + struct iommu_domain *domain; |
|---|
| 113 | 110 | unsigned int i; |
|---|
| 114 | 111 | int err; |
|---|
| 115 | 112 | |
|---|
| 113 | + domain = iommu_get_domain_for_dev(dev); |
|---|
| 116 | 114 | job->num_unpins = 0; |
|---|
| 117 | 115 | |
|---|
| 118 | 116 | for (i = 0; i < job->num_relocs; i++) { |
|---|
| 119 | 117 | struct host1x_reloc *reloc = &job->relocs[i]; |
|---|
| 118 | + dma_addr_t phys_addr, *phys; |
|---|
| 120 | 119 | struct sg_table *sgt; |
|---|
| 121 | | - dma_addr_t phys_addr; |
|---|
| 122 | 120 | |
|---|
| 123 | 121 | reloc->target.bo = host1x_bo_get(reloc->target.bo); |
|---|
| 124 | 122 | if (!reloc->target.bo) { |
|---|
| .. | .. |
|---|
| 126 | 124 | goto unpin; |
|---|
| 127 | 125 | } |
|---|
| 128 | 126 | |
|---|
| 129 | | - phys_addr = host1x_bo_pin(reloc->target.bo, &sgt); |
|---|
| 127 | + /* |
|---|
| 128 | + * If the client device is not attached to an IOMMU, the |
|---|
| 129 | + * physical address of the buffer object can be used. |
|---|
| 130 | + * |
|---|
| 131 | + * Similarly, when an IOMMU domain is shared between all |
|---|
| 132 | + * host1x clients, the IOVA is already available, so no |
|---|
| 133 | + * need to map the buffer object again. |
|---|
| 134 | + * |
|---|
| 135 | + * XXX Note that this isn't always safe to do because it |
|---|
| 136 | + * relies on an assumption that no cache maintenance is |
|---|
| 137 | + * needed on the buffer objects. |
|---|
| 138 | + */ |
|---|
| 139 | + if (!domain || client->group) |
|---|
| 140 | + phys = &phys_addr; |
|---|
| 141 | + else |
|---|
| 142 | + phys = NULL; |
|---|
| 143 | + |
|---|
| 144 | + sgt = host1x_bo_pin(dev, reloc->target.bo, phys); |
|---|
| 145 | + if (IS_ERR(sgt)) { |
|---|
| 146 | + err = PTR_ERR(sgt); |
|---|
| 147 | + goto unpin; |
|---|
| 148 | + } |
|---|
| 149 | + |
|---|
| 150 | + if (sgt) { |
|---|
| 151 | + unsigned long mask = HOST1X_RELOC_READ | |
|---|
| 152 | + HOST1X_RELOC_WRITE; |
|---|
| 153 | + enum dma_data_direction dir; |
|---|
| 154 | + |
|---|
| 155 | + switch (reloc->flags & mask) { |
|---|
| 156 | + case HOST1X_RELOC_READ: |
|---|
| 157 | + dir = DMA_TO_DEVICE; |
|---|
| 158 | + break; |
|---|
| 159 | + |
|---|
| 160 | + case HOST1X_RELOC_WRITE: |
|---|
| 161 | + dir = DMA_FROM_DEVICE; |
|---|
| 162 | + break; |
|---|
| 163 | + |
|---|
| 164 | + case HOST1X_RELOC_READ | HOST1X_RELOC_WRITE: |
|---|
| 165 | + dir = DMA_BIDIRECTIONAL; |
|---|
| 166 | + break; |
|---|
| 167 | + |
|---|
| 168 | + default: |
|---|
| 169 | + err = -EINVAL; |
|---|
| 170 | + goto unpin; |
|---|
| 171 | + } |
|---|
| 172 | + |
|---|
| 173 | + err = dma_map_sgtable(dev, sgt, dir, 0); |
|---|
| 174 | + if (err) |
|---|
| 175 | + goto unpin; |
|---|
| 176 | + |
|---|
| 177 | + job->unpins[job->num_unpins].dev = dev; |
|---|
| 178 | + job->unpins[job->num_unpins].dir = dir; |
|---|
| 179 | + phys_addr = sg_dma_address(sgt->sgl); |
|---|
| 180 | + } |
|---|
| 130 | 181 | |
|---|
| 131 | 182 | job->addr_phys[job->num_unpins] = phys_addr; |
|---|
| 132 | 183 | job->unpins[job->num_unpins].bo = reloc->target.bo; |
|---|
| .. | .. |
|---|
| 134 | 185 | job->num_unpins++; |
|---|
| 135 | 186 | } |
|---|
| 136 | 187 | |
|---|
| 188 | + /* |
|---|
| 189 | + * We will copy gathers BO content later, so there is no need to |
|---|
| 190 | + * hold and pin them. |
|---|
| 191 | + */ |
|---|
| 192 | + if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL)) |
|---|
| 193 | + return 0; |
|---|
| 194 | + |
|---|
| 137 | 195 | for (i = 0; i < job->num_gathers; i++) { |
|---|
| 138 | | - struct host1x_job_gather *g = &job->gathers[i]; |
|---|
| 139 | 196 | size_t gather_size = 0; |
|---|
| 140 | 197 | struct scatterlist *sg; |
|---|
| 141 | 198 | struct sg_table *sgt; |
|---|
| 142 | 199 | dma_addr_t phys_addr; |
|---|
| 143 | 200 | unsigned long shift; |
|---|
| 144 | 201 | struct iova *alloc; |
|---|
| 202 | + dma_addr_t *phys; |
|---|
| 145 | 203 | unsigned int j; |
|---|
| 146 | 204 | |
|---|
| 205 | + g = &job->gathers[i]; |
|---|
| 147 | 206 | g->bo = host1x_bo_get(g->bo); |
|---|
| 148 | 207 | if (!g->bo) { |
|---|
| 149 | 208 | err = -EINVAL; |
|---|
| 150 | 209 | goto unpin; |
|---|
| 151 | 210 | } |
|---|
| 152 | 211 | |
|---|
| 153 | | - phys_addr = host1x_bo_pin(g->bo, &sgt); |
|---|
| 212 | + /** |
|---|
| 213 | + * If the host1x is not attached to an IOMMU, there is no need |
|---|
| 214 | + * to map the buffer object for the host1x, since the physical |
|---|
| 215 | + * address can simply be used. |
|---|
| 216 | + */ |
|---|
| 217 | + if (!iommu_get_domain_for_dev(host->dev)) |
|---|
| 218 | + phys = &phys_addr; |
|---|
| 219 | + else |
|---|
| 220 | + phys = NULL; |
|---|
| 154 | 221 | |
|---|
| 155 | | - if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) && host->domain) { |
|---|
| 156 | | - for_each_sg(sgt->sgl, sg, sgt->nents, j) |
|---|
| 222 | + sgt = host1x_bo_pin(host->dev, g->bo, phys); |
|---|
| 223 | + if (IS_ERR(sgt)) { |
|---|
| 224 | + err = PTR_ERR(sgt); |
|---|
| 225 | + goto put; |
|---|
| 226 | + } |
|---|
| 227 | + |
|---|
| 228 | + if (host->domain) { |
|---|
| 229 | + for_each_sgtable_sg(sgt, sg, j) |
|---|
| 157 | 230 | gather_size += sg->length; |
|---|
| 158 | 231 | gather_size = iova_align(&host->iova, gather_size); |
|---|
| 159 | 232 | |
|---|
| .. | .. |
|---|
| 162 | 235 | host->iova_end >> shift, true); |
|---|
| 163 | 236 | if (!alloc) { |
|---|
| 164 | 237 | err = -ENOMEM; |
|---|
| 165 | | - goto unpin; |
|---|
| 238 | + goto put; |
|---|
| 166 | 239 | } |
|---|
| 167 | 240 | |
|---|
| 168 | | - err = iommu_map_sg(host->domain, |
|---|
| 241 | + err = iommu_map_sgtable(host->domain, |
|---|
| 169 | 242 | iova_dma_addr(&host->iova, alloc), |
|---|
| 170 | | - sgt->sgl, sgt->nents, IOMMU_READ); |
|---|
| 243 | + sgt, IOMMU_READ); |
|---|
| 171 | 244 | if (err == 0) { |
|---|
| 172 | 245 | __free_iova(&host->iova, alloc); |
|---|
| 173 | 246 | err = -EINVAL; |
|---|
| 174 | | - goto unpin; |
|---|
| 247 | + goto put; |
|---|
| 175 | 248 | } |
|---|
| 176 | 249 | |
|---|
| 177 | | - job->addr_phys[job->num_unpins] = |
|---|
| 178 | | - iova_dma_addr(&host->iova, alloc); |
|---|
| 179 | 250 | job->unpins[job->num_unpins].size = gather_size; |
|---|
| 180 | | - } else { |
|---|
| 181 | | - job->addr_phys[job->num_unpins] = phys_addr; |
|---|
| 251 | + phys_addr = iova_dma_addr(&host->iova, alloc); |
|---|
| 252 | + } else if (sgt) { |
|---|
| 253 | + err = dma_map_sgtable(host->dev, sgt, DMA_TO_DEVICE, 0); |
|---|
| 254 | + if (err) |
|---|
| 255 | + goto put; |
|---|
| 256 | + |
|---|
| 257 | + job->unpins[job->num_unpins].dir = DMA_TO_DEVICE; |
|---|
| 258 | + job->unpins[job->num_unpins].dev = host->dev; |
|---|
| 259 | + phys_addr = sg_dma_address(sgt->sgl); |
|---|
| 182 | 260 | } |
|---|
| 183 | 261 | |
|---|
| 184 | | - job->gather_addr_phys[i] = job->addr_phys[job->num_unpins]; |
|---|
| 262 | + job->addr_phys[job->num_unpins] = phys_addr; |
|---|
| 263 | + job->gather_addr_phys[i] = phys_addr; |
|---|
| 185 | 264 | |
|---|
| 186 | 265 | job->unpins[job->num_unpins].bo = g->bo; |
|---|
| 187 | 266 | job->unpins[job->num_unpins].sgt = sgt; |
|---|
| .. | .. |
|---|
| 190 | 269 | |
|---|
| 191 | 270 | return 0; |
|---|
| 192 | 271 | |
|---|
| 272 | +put: |
|---|
| 273 | + host1x_bo_put(g->bo); |
|---|
| 193 | 274 | unpin: |
|---|
| 194 | 275 | host1x_job_unpin(job); |
|---|
| 195 | 276 | return err; |
|---|
| .. | .. |
|---|
| 197 | 278 | |
|---|
| 198 | 279 | static int do_relocs(struct host1x_job *job, struct host1x_job_gather *g) |
|---|
| 199 | 280 | { |
|---|
| 200 | | - u32 last_page = ~0; |
|---|
| 201 | | - void *cmdbuf_page_addr = NULL; |
|---|
| 281 | + void *cmdbuf_addr = NULL; |
|---|
| 202 | 282 | struct host1x_bo *cmdbuf = g->bo; |
|---|
| 203 | 283 | unsigned int i; |
|---|
| 204 | 284 | |
|---|
| .. | .. |
|---|
| 220 | 300 | goto patch_reloc; |
|---|
| 221 | 301 | } |
|---|
| 222 | 302 | |
|---|
| 223 | | - if (last_page != reloc->cmdbuf.offset >> PAGE_SHIFT) { |
|---|
| 224 | | - if (cmdbuf_page_addr) |
|---|
| 225 | | - host1x_bo_kunmap(cmdbuf, last_page, |
|---|
| 226 | | - cmdbuf_page_addr); |
|---|
| 303 | + if (!cmdbuf_addr) { |
|---|
| 304 | + cmdbuf_addr = host1x_bo_mmap(cmdbuf); |
|---|
| 227 | 305 | |
|---|
| 228 | | - cmdbuf_page_addr = host1x_bo_kmap(cmdbuf, |
|---|
| 229 | | - reloc->cmdbuf.offset >> PAGE_SHIFT); |
|---|
| 230 | | - last_page = reloc->cmdbuf.offset >> PAGE_SHIFT; |
|---|
| 231 | | - |
|---|
| 232 | | - if (unlikely(!cmdbuf_page_addr)) { |
|---|
| 306 | + if (unlikely(!cmdbuf_addr)) { |
|---|
| 233 | 307 | pr_err("Could not map cmdbuf for relocation\n"); |
|---|
| 234 | 308 | return -ENOMEM; |
|---|
| 235 | 309 | } |
|---|
| 236 | 310 | } |
|---|
| 237 | 311 | |
|---|
| 238 | | - target = cmdbuf_page_addr + (reloc->cmdbuf.offset & ~PAGE_MASK); |
|---|
| 312 | + target = cmdbuf_addr + reloc->cmdbuf.offset; |
|---|
| 239 | 313 | patch_reloc: |
|---|
| 240 | 314 | *target = reloc_addr; |
|---|
| 241 | 315 | } |
|---|
| 242 | 316 | |
|---|
| 243 | | - if (cmdbuf_page_addr) |
|---|
| 244 | | - host1x_bo_kunmap(cmdbuf, last_page, cmdbuf_page_addr); |
|---|
| 317 | + if (cmdbuf_addr) |
|---|
| 318 | + host1x_bo_munmap(cmdbuf, cmdbuf_addr); |
|---|
| 245 | 319 | |
|---|
| 246 | 320 | return 0; |
|---|
| 247 | 321 | } |
|---|
| .. | .. |
|---|
| 569 | 643 | |
|---|
| 570 | 644 | for (i = 0; i < job->num_unpins; i++) { |
|---|
| 571 | 645 | struct host1x_job_unpin_data *unpin = &job->unpins[i]; |
|---|
| 646 | + struct device *dev = unpin->dev ?: host->dev; |
|---|
| 647 | + struct sg_table *sgt = unpin->sgt; |
|---|
| 572 | 648 | |
|---|
| 573 | 649 | if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) && |
|---|
| 574 | 650 | unpin->size && host->domain) { |
|---|
| .. | .. |
|---|
| 578 | 654 | iova_pfn(&host->iova, job->addr_phys[i])); |
|---|
| 579 | 655 | } |
|---|
| 580 | 656 | |
|---|
| 581 | | - host1x_bo_unpin(unpin->bo, unpin->sgt); |
|---|
| 657 | + if (unpin->dev && sgt) |
|---|
| 658 | + dma_unmap_sgtable(unpin->dev, sgt, unpin->dir, 0); |
|---|
| 659 | + |
|---|
| 660 | + host1x_bo_unpin(dev, unpin->bo, sgt); |
|---|
| 582 | 661 | host1x_bo_put(unpin->bo); |
|---|
| 583 | 662 | } |
|---|
| 584 | 663 | |
|---|