aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Murphy <robin.murphy@arm.com>2017-06-22 11:53:51 -0400
committerWill Deacon <will.deacon@arm.com>2017-06-23 12:57:59 -0400
commitfb3a95795da53d05a4fc5fcdc0d3ec69e7163355 (patch)
tree2ee208675bc99afdbb3e1eb30e9dd47e74c1cd72
parent9db829d2818501f07583542c05d01513b9161e14 (diff)
iommu/io-pgtable-arm: Improve split_blk_unmap
The current split_blk_unmap implementation suffers from some inscrutable pointer trickery for creating the tables to replace the block entry, but more than that it also suffers from hideous inefficiency. For example, the most pathological case of unmapping a level 3 page from a level 1 block will allocate 513 lower-level tables to remap the entire block at page granularity, when only 2 are actually needed (the rest can be covered by level 2 block entries). Also, we would like to be able to relax the spinlock requirement in future, for which the roll-back-and-try-again logic for race resolution would be pretty hideous under the current paradigm. Both issues can be resolved most neatly by turning things sideways: instead of repeatedly recursing into __arm_lpae_map() map to build up an entire new sub-table depth-first, we can directly replace the block entry with a next-level table of block/page entries, then repeat by unmapping at the next level if necessary. With a little refactoring of some helper functions, the code ends up not much bigger than before, but considerably easier to follow and to adapt in future. Signed-off-by: Robin Murphy <robin.murphy@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r--drivers/iommu/io-pgtable-arm.c114
1 files changed, 67 insertions, 47 deletions
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 6e5df5e0a3bd..dd7477010291 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -264,19 +264,38 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
264 unsigned long iova, size_t size, int lvl, 264 unsigned long iova, size_t size, int lvl,
265 arm_lpae_iopte *ptep); 265 arm_lpae_iopte *ptep);
266 266
267static void __arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,
268 phys_addr_t paddr, arm_lpae_iopte prot,
269 int lvl, arm_lpae_iopte *ptep)
270{
271 arm_lpae_iopte pte = prot;
272
273 if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_ARM_NS)
274 pte |= ARM_LPAE_PTE_NS;
275
276 if (lvl == ARM_LPAE_MAX_LEVELS - 1)
277 pte |= ARM_LPAE_PTE_TYPE_PAGE;
278 else
279 pte |= ARM_LPAE_PTE_TYPE_BLOCK;
280
281 pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_IS;
282 pte |= pfn_to_iopte(paddr >> data->pg_shift, data);
283
284 __arm_lpae_set_pte(ptep, pte, &data->iop.cfg);
285}
286
267static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, 287static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,
268 unsigned long iova, phys_addr_t paddr, 288 unsigned long iova, phys_addr_t paddr,
269 arm_lpae_iopte prot, int lvl, 289 arm_lpae_iopte prot, int lvl,
270 arm_lpae_iopte *ptep) 290 arm_lpae_iopte *ptep)
271{ 291{
272 arm_lpae_iopte pte = prot; 292 arm_lpae_iopte pte = *ptep;
273 struct io_pgtable_cfg *cfg = &data->iop.cfg;
274 293
275 if (iopte_leaf(*ptep, lvl)) { 294 if (iopte_leaf(pte, lvl)) {
276 /* We require an unmap first */ 295 /* We require an unmap first */
277 WARN_ON(!selftest_running); 296 WARN_ON(!selftest_running);
278 return -EEXIST; 297 return -EEXIST;
279 } else if (iopte_type(*ptep, lvl) == ARM_LPAE_PTE_TYPE_TABLE) { 298 } else if (iopte_type(pte, lvl) == ARM_LPAE_PTE_TYPE_TABLE) {
280 /* 299 /*
281 * We need to unmap and free the old table before 300 * We need to unmap and free the old table before
282 * overwriting it with a block entry. 301 * overwriting it with a block entry.
@@ -289,19 +308,22 @@ static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,
289 return -EINVAL; 308 return -EINVAL;
290 } 309 }
291 310
292 if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS) 311 __arm_lpae_init_pte(data, paddr, prot, lvl, ptep);
293 pte |= ARM_LPAE_PTE_NS; 312 return 0;
313}
294 314
295 if (lvl == ARM_LPAE_MAX_LEVELS - 1) 315static arm_lpae_iopte arm_lpae_install_table(arm_lpae_iopte *table,
296 pte |= ARM_LPAE_PTE_TYPE_PAGE; 316 arm_lpae_iopte *ptep,
297 else 317 struct io_pgtable_cfg *cfg)
298 pte |= ARM_LPAE_PTE_TYPE_BLOCK; 318{
319 arm_lpae_iopte new;
299 320
300 pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_IS; 321 new = __pa(table) | ARM_LPAE_PTE_TYPE_TABLE;
301 pte |= pfn_to_iopte(paddr >> data->pg_shift, data); 322 if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS)
323 new |= ARM_LPAE_PTE_NSTABLE;
302 324
303 __arm_lpae_set_pte(ptep, pte, cfg); 325 __arm_lpae_set_pte(ptep, new, cfg);
304 return 0; 326 return new;
305} 327}
306 328
307static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, 329static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova,
@@ -331,10 +353,7 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova,
331 if (!cptep) 353 if (!cptep)
332 return -ENOMEM; 354 return -ENOMEM;
333 355
334 pte = __pa(cptep) | ARM_LPAE_PTE_TYPE_TABLE; 356 arm_lpae_install_table(cptep, ptep, cfg);
335 if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS)
336 pte |= ARM_LPAE_PTE_NSTABLE;
337 __arm_lpae_set_pte(ptep, pte, cfg);
338 } else if (!iopte_leaf(pte, lvl)) { 357 } else if (!iopte_leaf(pte, lvl)) {
339 cptep = iopte_deref(pte, data); 358 cptep = iopte_deref(pte, data);
340 } else { 359 } else {
@@ -452,40 +471,43 @@ static void arm_lpae_free_pgtable(struct io_pgtable *iop)
452 471
453static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, 472static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data,
454 unsigned long iova, size_t size, 473 unsigned long iova, size_t size,
455 arm_lpae_iopte prot, int lvl, 474 arm_lpae_iopte blk_pte, int lvl,
456 arm_lpae_iopte *ptep, size_t blk_size) 475 arm_lpae_iopte *ptep)
457{ 476{
458 unsigned long blk_start, blk_end; 477 struct io_pgtable_cfg *cfg = &data->iop.cfg;
478 arm_lpae_iopte pte, *tablep;
459 phys_addr_t blk_paddr; 479 phys_addr_t blk_paddr;
460 arm_lpae_iopte table = 0; 480 size_t tablesz = ARM_LPAE_GRANULE(data);
481 size_t split_sz = ARM_LPAE_BLOCK_SIZE(lvl, data);
482 int i, unmap_idx = -1;
483
484 if (WARN_ON(lvl == ARM_LPAE_MAX_LEVELS))
485 return 0;
461 486
462 blk_start = iova & ~(blk_size - 1); 487 tablep = __arm_lpae_alloc_pages(tablesz, GFP_ATOMIC, cfg);
463 blk_end = blk_start + blk_size; 488 if (!tablep)
464 blk_paddr = iopte_to_pfn(*ptep, data) << data->pg_shift; 489 return 0; /* Bytes unmapped */
465 490
466 for (; blk_start < blk_end; blk_start += size, blk_paddr += size) { 491 if (size == split_sz)
467 arm_lpae_iopte *tablep; 492 unmap_idx = ARM_LPAE_LVL_IDX(iova, lvl, data);
468 493
494 blk_paddr = iopte_to_pfn(blk_pte, data) << data->pg_shift;
495 pte = iopte_prot(blk_pte);
496
497 for (i = 0; i < tablesz / sizeof(pte); i++, blk_paddr += split_sz) {
469 /* Unmap! */ 498 /* Unmap! */
470 if (blk_start == iova) 499 if (i == unmap_idx)
471 continue; 500 continue;
472 501
473 /* __arm_lpae_map expects a pointer to the start of the table */ 502 __arm_lpae_init_pte(data, blk_paddr, pte, lvl, &tablep[i]);
474 tablep = &table - ARM_LPAE_LVL_IDX(blk_start, lvl, data);
475 if (__arm_lpae_map(data, blk_start, blk_paddr, size, prot, lvl,
476 tablep) < 0) {
477 if (table) {
478 /* Free the table we allocated */
479 tablep = iopte_deref(table, data);
480 __arm_lpae_free_pgtable(data, lvl + 1, tablep);
481 }
482 return 0; /* Bytes unmapped */
483 }
484 } 503 }
485 504
486 __arm_lpae_set_pte(ptep, table, &data->iop.cfg); 505 arm_lpae_install_table(tablep, ptep, cfg);
487 iova &= ~(blk_size - 1); 506
488 io_pgtable_tlb_add_flush(&data->iop, iova, blk_size, blk_size, true); 507 if (unmap_idx < 0)
508 return __arm_lpae_unmap(data, iova, size, lvl, tablep);
509
510 io_pgtable_tlb_add_flush(&data->iop, iova, size, size, true);
489 return size; 511 return size;
490} 512}
491 513
@@ -495,7 +517,6 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
495{ 517{
496 arm_lpae_iopte pte; 518 arm_lpae_iopte pte;
497 struct io_pgtable *iop = &data->iop; 519 struct io_pgtable *iop = &data->iop;
498 size_t blk_size = ARM_LPAE_BLOCK_SIZE(lvl, data);
499 520
500 /* Something went horribly wrong and we ran out of page table */ 521 /* Something went horribly wrong and we ran out of page table */
501 if (WARN_ON(lvl == ARM_LPAE_MAX_LEVELS)) 522 if (WARN_ON(lvl == ARM_LPAE_MAX_LEVELS))
@@ -507,7 +528,7 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
507 return 0; 528 return 0;
508 529
509 /* If the size matches this level, we're in the right place */ 530 /* If the size matches this level, we're in the right place */
510 if (size == blk_size) { 531 if (size == ARM_LPAE_BLOCK_SIZE(lvl, data)) {
511 __arm_lpae_set_pte(ptep, 0, &iop->cfg); 532 __arm_lpae_set_pte(ptep, 0, &iop->cfg);
512 533
513 if (!iopte_leaf(pte, lvl)) { 534 if (!iopte_leaf(pte, lvl)) {
@@ -527,9 +548,8 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
527 * Insert a table at the next level to map the old region, 548 * Insert a table at the next level to map the old region,
528 * minus the part we want to unmap 549 * minus the part we want to unmap
529 */ 550 */
530 return arm_lpae_split_blk_unmap(data, iova, size, 551 return arm_lpae_split_blk_unmap(data, iova, size, pte,
531 iopte_prot(pte), lvl, ptep, 552 lvl + 1, ptep);
532 blk_size);
533 } 553 }
534 554
535 /* Keep on walkin' */ 555 /* Keep on walkin' */