From eba484b51b8c1346759785d40fa4d6ec5590b705 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Mon, 19 Dec 2016 22:46:58 +0800 Subject: iommu/iova: Use rb_entry() To make the code clearer, use rb_entry() instead of container_of() to deal with rbtree. Signed-off-by: Geliang Tang Signed-off-by: Joerg Roedel --- drivers/iommu/iova.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c index 080beca..b7268a1 100644 --- a/drivers/iommu/iova.c +++ b/drivers/iommu/iova.c @@ -62,7 +62,7 @@ __get_cached_rbnode(struct iova_domain *iovad, unsigned long *limit_pfn) else { struct rb_node *prev_node = rb_prev(iovad->cached32_node); struct iova *curr_iova = - container_of(iovad->cached32_node, struct iova, node); + rb_entry(iovad->cached32_node, struct iova, node); *limit_pfn = curr_iova->pfn_lo - 1; return prev_node; } @@ -86,11 +86,11 @@ __cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free) if (!iovad->cached32_node) return; curr = iovad->cached32_node; - cached_iova = container_of(curr, struct iova, node); + cached_iova = rb_entry(curr, struct iova, node); if (free->pfn_lo >= cached_iova->pfn_lo) { struct rb_node *node = rb_next(&free->node); - struct iova *iova = container_of(node, struct iova, node); + struct iova *iova = rb_entry(node, struct iova, node); /* only cache if it's below 32bit pfn */ if (node && iova->pfn_lo < iovad->dma_32bit_pfn) @@ -125,7 +125,7 @@ static int __alloc_and_insert_iova_range(struct iova_domain *iovad, curr = __get_cached_rbnode(iovad, &limit_pfn); prev = curr; while (curr) { - struct iova *curr_iova = container_of(curr, struct iova, node); + struct iova *curr_iova = rb_entry(curr, struct iova, node); if (limit_pfn < curr_iova->pfn_lo) goto move_left; @@ -171,8 +171,7 @@ move_left: /* Figure out where to put new node */ while (*entry) { - struct iova *this = container_of(*entry, - struct iova, node); + struct iova *this = rb_entry(*entry, struct iova, node); parent = *entry; if (new->pfn_lo < this->pfn_lo) @@ -201,7 +200,7 @@ iova_insert_rbtree(struct rb_root *root, struct iova *iova) struct rb_node **new = &(root->rb_node), *parent = NULL; /* Figure out where to put new node */ while (*new) { - struct iova *this = container_of(*new, struct iova, node); + struct iova *this = rb_entry(*new, struct iova, node); parent = *new; @@ -311,7 +310,7 @@ private_find_iova(struct iova_domain *iovad, unsigned long pfn) assert_spin_locked(&iovad->iova_rbtree_lock); while (node) { - struct iova *iova = container_of(node, struct iova, node); + struct iova *iova = rb_entry(node, struct iova, node); /* If pfn falls within iova's range, return iova */ if ((pfn >= iova->pfn_lo) && (pfn <= iova->pfn_hi)) { @@ -463,7 +462,7 @@ void put_iova_domain(struct iova_domain *iovad) spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); node = rb_first(&iovad->rbroot); while (node) { - struct iova *iova = container_of(node, struct iova, node); + struct iova *iova = rb_entry(node, struct iova, node); rb_erase(node, &iovad->rbroot); free_iova_mem(iova); @@ -477,7 +476,7 @@ static int __is_range_overlap(struct rb_node *node, unsigned long pfn_lo, unsigned long pfn_hi) { - struct iova *iova = container_of(node, struct iova, node); + struct iova *iova = rb_entry(node, struct iova, node); if ((pfn_lo <= iova->pfn_hi) && (pfn_hi >= iova->pfn_lo)) return 1; @@ -541,7 +540,7 @@ reserve_iova(struct iova_domain *iovad, spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); for (node = rb_first(&iovad->rbroot); node; node = rb_next(node)) { if (__is_range_overlap(node, pfn_lo, pfn_hi)) { - iova = container_of(node, struct iova, node); + iova = rb_entry(node, struct iova, node); __adjust_overlap_range(iova, &pfn_lo, &pfn_hi); if ((pfn_lo >= iova->pfn_lo) && (pfn_hi <= iova->pfn_hi)) @@ -578,7 +577,7 @@ copy_reserved_iova(struct iova_domain *from, struct iova_domain *to) spin_lock_irqsave(&from->iova_rbtree_lock, flags); for (node = rb_first(&from->rbroot); node; node = rb_next(node)) { - struct iova *iova = container_of(node, struct iova, node); + struct iova *iova = rb_entry(node, struct iova, node); struct iova *new_iova; new_iova = reserve_iova(to, iova->pfn_lo, iova->pfn_hi); -- cgit v1.1 From 99e8ccd3837a1028c98559d3c19e8b796eb9906c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 10 Jan 2017 14:57:28 +0100 Subject: iommu/amd: Fix error code path in early_amd_iommu_init() Prevent early_amd_iommu_init() from leaking memory mapped via acpi_get_table() if check_ivrs_checksum() returns an error. Signed-off-by: Rafael J. Wysocki Signed-off-by: Joerg Roedel --- drivers/iommu/amd_iommu_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index 6799cf9..3d3eb15 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -2230,7 +2230,7 @@ static int __init early_amd_iommu_init(void) */ ret = check_ivrs_checksum(ivrs_base); if (ret) - return ret; + goto out; amd_iommu_target_ivhd_type = get_highest_supported_ivhd_type(ivrs_base); DUMP_printk("Using IVHD type %#x\n", amd_iommu_target_ivhd_type); -- cgit v1.1 From ec5d241b5f8b2d5f002070fd8fdbf71b6664bacb Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 9 Jan 2017 13:03:53 +0100 Subject: iommu/exynos: Improve page fault debug message Add master device name to default IOMMU fault message to make easier to find which device triggered the fault. While at it, move printing some information (like page table base and first level entry addresses) to dev_dbg(), because those are typically not very useful for typical device driver user/developer not equipped with hardware debugging tools. Signed-off-by: Marek Szyprowski Signed-off-by: Joerg Roedel --- drivers/iommu/exynos-iommu.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 57ba0d3..ac726e1 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -381,13 +381,14 @@ static void show_fault_information(struct sysmmu_drvdata *data, { sysmmu_pte_t *ent; - dev_err(data->sysmmu, "%s FAULT occurred at %#x (page table base: %pa)\n", - finfo->name, fault_addr, &data->pgtable); + dev_err(data->sysmmu, "%s: %s FAULT occurred at %#x\n", + dev_name(data->master), finfo->name, fault_addr); + dev_dbg(data->sysmmu, "Page table base: %pa\n", &data->pgtable); ent = section_entry(phys_to_virt(data->pgtable), fault_addr); - dev_err(data->sysmmu, "\tLv1 entry: %#x\n", *ent); + dev_dbg(data->sysmmu, "\tLv1 entry: %#x\n", *ent); if (lv1ent_page(ent)) { ent = page_entry(ent, fault_addr); - dev_err(data->sysmmu, "\t Lv2 entry: %#x\n", *ent); + dev_dbg(data->sysmmu, "\t Lv2 entry: %#x\n", *ent); } } -- cgit v1.1 From 0d6d3da46ac5949ab5d373fdecf4b4e85a04731c Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 9 Jan 2017 13:03:54 +0100 Subject: iommu/exynos: Fix warnings from DMA-debug Add a simple checks for dma_map_single() return value to make DMA-debug checker happly. Exynos IOMMU on Samsung Exynos SoCs always use device, which has linear DMA mapping ops (dma address is equal to physical memory address), so no failures are returned from dma_map_single(). Signed-off-by: Marek Szyprowski Signed-off-by: Joerg Roedel --- drivers/iommu/exynos-iommu.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index ac726e1..dda4e59 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -744,6 +744,8 @@ static struct iommu_domain *exynos_iommu_domain_alloc(unsigned type) DMA_TO_DEVICE); /* For mapping page table entries we rely on dma == phys */ BUG_ON(handle != virt_to_phys(domain->pgtable)); + if (dma_mapping_error(dma_dev, handle)) + goto err_lv2ent; spin_lock_init(&domain->lock); spin_lock_init(&domain->pgtablelock); @@ -755,6 +757,8 @@ static struct iommu_domain *exynos_iommu_domain_alloc(unsigned type) return &domain->domain; +err_lv2ent: + free_pages((unsigned long)domain->lv2entcnt, 1); err_counter: free_pages((unsigned long)domain->pgtable, 2); err_dma_cookie: @@ -898,6 +902,7 @@ static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *domain, } if (lv1ent_fault(sent)) { + dma_addr_t handle; sysmmu_pte_t *pent; bool need_flush_flpd_cache = lv1ent_zero(sent); @@ -909,7 +914,12 @@ static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *domain, update_pte(sent, mk_lv1ent_page(virt_to_phys(pent))); kmemleak_ignore(pent); *pgcounter = NUM_LV2ENTRIES; - dma_map_single(dma_dev, pent, LV2TABLE_SIZE, DMA_TO_DEVICE); + handle = dma_map_single(dma_dev, pent, LV2TABLE_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(dma_dev, handle)) { + kmem_cache_free(lv2table_kmem_cache, pent); + return ERR_PTR(-EADDRINUSE); + } /* * If pre-fetched SLPD is a faulty SLPD in zero_l2_table, -- cgit v1.1 From 0bd5a0c77ad49ce5cf631bbe5616b6625ff50d96 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 9 Jan 2017 13:03:55 +0100 Subject: iommu/exynos: Ensure that SYSMMU is added only once to its master device This patch prepares Exynos IOMMU driver for deferred probing support. Once it gets added, of_xlate() callback might be called more than once for the same SYSMMU controller and master device (for example it happens when masters device driver fails with EPROBE_DEFER). This patch adds a check, which ensures that SYSMMU controller is added to its master device (owner) only once. Signed-off-by: Marek Szyprowski Signed-off-by: Joerg Roedel --- drivers/iommu/exynos-iommu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index dda4e59..91f7827 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -1253,7 +1253,7 @@ static int exynos_iommu_of_xlate(struct device *dev, { struct exynos_iommu_owner *owner = dev->archdata.iommu; struct platform_device *sysmmu = of_find_device_by_node(spec->np); - struct sysmmu_drvdata *data; + struct sysmmu_drvdata *data, *entry; if (!sysmmu) return -ENODEV; @@ -1272,6 +1272,10 @@ static int exynos_iommu_of_xlate(struct device *dev, dev->archdata.iommu = owner; } + list_for_each_entry(entry, &owner->controllers, owner_node) + if (entry == data) + return 0; + list_add_tail(&data->owner_node, &owner->controllers); data->master = dev; -- cgit v1.1 From fff2fd1a9e4be3267f8ace2b4d7d80da13d1f0d9 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 9 Jan 2017 13:03:56 +0100 Subject: iommu/exynos: Properly release device from the default domain in ->remove IOMMU core doesn't detach device from the default domain before calling ->iommu_remove_device, so check that and do the proper cleanup or warn if device is still attached to non-default domain. Signed-off-by: Marek Szyprowski Signed-off-by: Joerg Roedel --- drivers/iommu/exynos-iommu.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 91f7827..fa529c2 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -1242,9 +1242,21 @@ static int exynos_iommu_add_device(struct device *dev) static void exynos_iommu_remove_device(struct device *dev) { + struct exynos_iommu_owner *owner = dev->archdata.iommu; + if (!has_sysmmu(dev)) return; + if (owner->domain) { + struct iommu_group *group = iommu_group_get(dev); + + if (group) { + WARN_ON(owner->domain != + iommu_group_default_domain(group)); + exynos_iommu_detach_device(owner->domain, dev); + iommu_group_put(group); + } + } iommu_group_remove_device(dev); } -- cgit v1.1 From 797a8b4d768c58caac58ee3e8cb36a164d1b7751 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Mon, 16 Jan 2017 12:58:07 +0000 Subject: iommu: Handle default domain attach failure We wouldn't normally expect ops->attach_dev() to fail, but on IOMMUs with limited hardware resources, or generally misconfigured systems, it is certainly possible. We report failure correctly from the external iommu_attach_device() interface, but do not do so in iommu_group_add() when attaching to the default domain. The result of failure there is that the device, group and domain all get left in a broken, part-configured state which leads to weird errors and misbehaviour down the line when IOMMU API calls sort-of-but-don't-quite work. Check the return value of __iommu_attach_device() on the default domain, and refactor the error handling paths to cope with its failure and clean up correctly in such cases. Fixes: e39cb8a3aa98 ("iommu: Make sure a device is always attached to a domain") Reported-by: Punit Agrawal Signed-off-by: Robin Murphy Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index dbe7f65..aed906a 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -383,36 +383,30 @@ int iommu_group_add_device(struct iommu_group *group, struct device *dev) device->dev = dev; ret = sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group"); - if (ret) { - kfree(device); - return ret; - } + if (ret) + goto err_free_device; device->name = kasprintf(GFP_KERNEL, "%s", kobject_name(&dev->kobj)); rename: if (!device->name) { - sysfs_remove_link(&dev->kobj, "iommu_group"); - kfree(device); - return -ENOMEM; + ret = -ENOMEM; + goto err_remove_link; } ret = sysfs_create_link_nowarn(group->devices_kobj, &dev->kobj, device->name); if (ret) { - kfree(device->name); if (ret == -EEXIST && i >= 0) { /* * Account for the slim chance of collision * and append an instance to the name. */ + kfree(device->name); device->name = kasprintf(GFP_KERNEL, "%s.%d", kobject_name(&dev->kobj), i++); goto rename; } - - sysfs_remove_link(&dev->kobj, "iommu_group"); - kfree(device); - return ret; + goto err_free_name; } kobject_get(group->devices_kobj); @@ -424,8 +418,10 @@ rename: mutex_lock(&group->mutex); list_add_tail(&device->list, &group->devices); if (group->domain) - __iommu_attach_device(group->domain, dev); + ret = __iommu_attach_device(group->domain, dev); mutex_unlock(&group->mutex); + if (ret) + goto err_put_group; /* Notify any listeners about change to group. */ blocking_notifier_call_chain(&group->notifier, @@ -436,6 +432,21 @@ rename: pr_info("Adding device %s to group %d\n", dev_name(dev), group->id); return 0; + +err_put_group: + mutex_lock(&group->mutex); + list_del(&device->list); + mutex_unlock(&group->mutex); + dev->iommu_group = NULL; + kobject_put(group->devices_kobj); +err_free_name: + kfree(device->name); +err_remove_link: + sysfs_remove_link(&dev->kobj, "iommu_group"); +err_free_device: + kfree(device); + pr_err("Failed to add device %s to group %d: %d\n", dev_name(dev), group->id, ret); + return ret; } EXPORT_SYMBOL_GPL(iommu_group_add_device); -- cgit v1.1 From e7468a23daec1a7737a004c659fd041ddff8b918 Mon Sep 17 00:00:00 2001 From: Jeremy Gebben Date: Fri, 6 Jan 2017 18:58:09 +0530 Subject: iommu/io-pgtable-arm: add support for the IOMMU_PRIV flag Allow the creation of privileged mode mappings, for stage 1 only. Reviewed-by: Robin Murphy Tested-by: Robin Murphy Acked-by: Will Deacon Signed-off-by: Jeremy Gebben Signed-off-by: Will Deacon --- drivers/iommu/io-pgtable-arm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c index a40ce34..feacc54 100644 --- a/drivers/iommu/io-pgtable-arm.c +++ b/drivers/iommu/io-pgtable-arm.c @@ -350,11 +350,14 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, if (data->iop.fmt == ARM_64_LPAE_S1 || data->iop.fmt == ARM_32_LPAE_S1) { - pte = ARM_LPAE_PTE_AP_UNPRIV | ARM_LPAE_PTE_nG; + pte = ARM_LPAE_PTE_nG; if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ)) pte |= ARM_LPAE_PTE_AP_RDONLY; + if (!(prot & IOMMU_PRIV)) + pte |= ARM_LPAE_PTE_AP_UNPRIV; + if (prot & IOMMU_MMIO) pte |= (ARM_LPAE_MAIR_ATTR_IDX_DEV << ARM_LPAE_PTE_ATTRINDX_SHIFT); -- cgit v1.1 From 5baf1e9d0bb223b086c422b880359c9fe2ee1476 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Fri, 6 Jan 2017 18:58:10 +0530 Subject: iommu/io-pgtable-arm-v7s: Add support for the IOMMU_PRIV flag The short-descriptor format also allows privileged-only mappings, so let's wire it up. Signed-off-by: Robin Murphy Tested-by: Sricharan R Signed-off-by: Will Deacon --- drivers/iommu/io-pgtable-arm-v7s.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c index 0769276..1c049e2 100644 --- a/drivers/iommu/io-pgtable-arm-v7s.c +++ b/drivers/iommu/io-pgtable-arm-v7s.c @@ -265,7 +265,9 @@ static arm_v7s_iopte arm_v7s_prot_to_pte(int prot, int lvl, if (!(prot & IOMMU_MMIO)) pte |= ARM_V7S_ATTR_TEX(1); if (ap) { - pte |= ARM_V7S_PTE_AF | ARM_V7S_PTE_AP_UNPRIV; + pte |= ARM_V7S_PTE_AF; + if (!(prot & IOMMU_PRIV)) + pte |= ARM_V7S_PTE_AP_UNPRIV; if (!(prot & IOMMU_WRITE)) pte |= ARM_V7S_PTE_AP_RDONLY; } @@ -288,6 +290,8 @@ static int arm_v7s_pte_to_prot(arm_v7s_iopte pte, int lvl) if (!(attr & ARM_V7S_PTE_AP_RDONLY)) prot |= IOMMU_WRITE; + if (!(attr & ARM_V7S_PTE_AP_UNPRIV)) + prot |= IOMMU_PRIV; if ((attr & (ARM_V7S_TEX_MASK << ARM_V7S_TEX_SHIFT)) == 0) prot |= IOMMU_MMIO; else if (pte & ARM_V7S_ATTR_C) -- cgit v1.1 From 737c85ca1c3af4f97acb61cd53415ec039b31111 Mon Sep 17 00:00:00 2001 From: Mitchel Humpherys Date: Fri, 6 Jan 2017 18:58:12 +0530 Subject: arm64/dma-mapping: Implement DMA_ATTR_PRIVILEGED The newly added DMA_ATTR_PRIVILEGED is useful for creating mappings that are only accessible to privileged DMA engines. Implement it in dma-iommu.c so that the ARM64 DMA IOMMU mapper can make use of it. Reviewed-by: Robin Murphy Tested-by: Robin Murphy Acked-by: Will Deacon Signed-off-by: Mitchel Humpherys Signed-off-by: Will Deacon --- drivers/iommu/dma-iommu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 2db0d64..3006eee 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -181,16 +181,22 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, EXPORT_SYMBOL(iommu_dma_init_domain); /** - * dma_direction_to_prot - Translate DMA API directions to IOMMU API page flags + * dma_info_to_prot - Translate DMA API directions and attributes to IOMMU API + * page flags. * @dir: Direction of DMA transfer * @coherent: Is the DMA master cache-coherent? + * @attrs: DMA attributes for the mapping * * Return: corresponding IOMMU API page protection flags */ -int dma_direction_to_prot(enum dma_data_direction dir, bool coherent) +int dma_info_to_prot(enum dma_data_direction dir, bool coherent, + unsigned long attrs) { int prot = coherent ? IOMMU_CACHE : 0; + if (attrs & DMA_ATTR_PRIVILEGED) + prot |= IOMMU_PRIV; + switch (dir) { case DMA_BIDIRECTIONAL: return prot | IOMMU_READ | IOMMU_WRITE; @@ -633,7 +639,7 @@ dma_addr_t iommu_dma_map_resource(struct device *dev, phys_addr_t phys, size_t size, enum dma_data_direction dir, unsigned long attrs) { return __iommu_dma_map(dev, phys, size, - dma_direction_to_prot(dir, false) | IOMMU_MMIO); + dma_info_to_prot(dir, false, attrs) | IOMMU_MMIO); } void iommu_dma_unmap_resource(struct device *dev, dma_addr_t handle, -- cgit v1.1 From 1b2354dbcc7b232b50f26dfd47fd4c529edfc20a Mon Sep 17 00:00:00 2001 From: Mitchel Humpherys Date: Fri, 6 Jan 2017 18:58:14 +0530 Subject: dmaengine: pl330: Make sure microcode is privileged The PL330 is hard-wired such that instruction fetches on both the manager and channel threads go out onto the bus with the "privileged" bit set. This can become troublesome once there is an IOMMU or other form of memory protection downstream, since those will typically be programmed by the DMA mapping subsystem in the expectation of normal unprivileged transactions (such as the PL330 channel threads' own data accesses as currently configured by this driver). To avoid the case of, say, an IOMMU blocking an unexpected privileged transaction with a permission fault, use the newly-introduced DMA_ATTR_PRIVILEGED attribute for the mapping of our microcode buffer. That way the DMA layer can do whatever it needs to do to make things continue to work as expected on more complex systems. Cc: Dan Williams Cc: Vinod Koul Reviewed-by: Robin Murphy Tested-by: Robin Murphy Acked-by: Will Deacon Acked-by: Vinod Koul Signed-off-by: Mitchel Humpherys [rm: remove now-redundant local variable, clarify commit message] Signed-off-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/dma/pl330.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 87fd015..5a90d0c 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -1864,9 +1864,10 @@ static int dmac_alloc_resources(struct pl330_dmac *pl330) * Alloc MicroCode buffer for 'chans' Channel threads. * A channel's buffer offset is (Channel_Id * MCODE_BUFF_PERCHAN) */ - pl330->mcode_cpu = dma_alloc_coherent(pl330->ddma.dev, + pl330->mcode_cpu = dma_alloc_attrs(pl330->ddma.dev, chans * pl330->mcbufsz, - &pl330->mcode_bus, GFP_KERNEL); + &pl330->mcode_bus, GFP_KERNEL, + DMA_ATTR_PRIVILEGED); if (!pl330->mcode_cpu) { dev_err(pl330->ddma.dev, "%s:%d Can't allocate memory!\n", __func__, __LINE__); -- cgit v1.1 From e19898077cfb642fe151ba22981e795c74d9e114 Mon Sep 17 00:00:00 2001 From: Sricharan R Date: Fri, 6 Jan 2017 18:58:15 +0530 Subject: iommu/arm-smmu: Set privileged attribute to 'default' instead of 'unprivileged' Currently the driver sets all the device transactions privileges to UNPRIVILEGED, but there are cases where the iommu masters wants to isolate privileged supervisor and unprivileged user. So don't override the privileged setting to unprivileged, instead set it to default as incoming and let it be controlled by the pagetable settings. Acked-by: Will Deacon Signed-off-by: Sricharan R Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a60cded..73a0a25 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1214,7 +1214,7 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, continue; s2cr[idx].type = type; - s2cr[idx].privcfg = S2CR_PRIVCFG_UNPRIV; + s2cr[idx].privcfg = S2CR_PRIVCFG_DEFAULT; s2cr[idx].cbndx = cbndx; arm_smmu_write_s2cr(smmu, idx); } -- cgit v1.1 From 14b4dbafa7e7e13323e402efd7723aafa391d69a Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Fri, 6 Jan 2017 18:58:16 +0530 Subject: Revert "iommu/arm-smmu: Set PRIVCFG in stage 1 STEs" This reverts commit df5e1a0f2a2d779ad467a691203bcbc74d75690e. Now that proper privileged mappings can be requested via IOMMU_PRIV, unconditionally overriding the incoming PRIVCFG becomes the wrong thing to do, so stop it. Signed-off-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu-v3.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 4d6ec44..7d45d8b 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -269,9 +269,6 @@ #define STRTAB_STE_1_SHCFG_INCOMING 1UL #define STRTAB_STE_1_SHCFG_SHIFT 44 -#define STRTAB_STE_1_PRIVCFG_UNPRIV 2UL -#define STRTAB_STE_1_PRIVCFG_SHIFT 48 - #define STRTAB_STE_2_S2VMID_SHIFT 0 #define STRTAB_STE_2_S2VMID_MASK 0xffffUL #define STRTAB_STE_2_VTCR_SHIFT 32 @@ -1073,9 +1070,7 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid, #ifdef CONFIG_PCI_ATS STRTAB_STE_1_EATS_TRANS << STRTAB_STE_1_EATS_SHIFT | #endif - STRTAB_STE_1_STRW_NSEL1 << STRTAB_STE_1_STRW_SHIFT | - STRTAB_STE_1_PRIVCFG_UNPRIV << - STRTAB_STE_1_PRIVCFG_SHIFT); + STRTAB_STE_1_STRW_NSEL1 << STRTAB_STE_1_STRW_SHIFT); if (smmu->features & ARM_SMMU_FEAT_STALLS) dst[1] |= cpu_to_le64(STRTAB_STE_1_S1STALLD); -- cgit v1.1 From fdbe574eb69312a7fbe09674d69c01b80e4ed9dc Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Thu, 19 Jan 2017 20:57:46 +0000 Subject: iommu/dma: Allow MSI-only cookies IOMMU domain users such as VFIO face a similar problem to DMA API ops with regard to mapping MSI messages in systems where the MSI write is subject to IOMMU translation. With the relevant infrastructure now in place for managed DMA domains, it's actually really simple for other users to piggyback off that and reap the benefits without giving up their own IOVA management, and without having to reinvent their own wheel in the MSI layer. Allow such users to opt into automatic MSI remapping by dedicating a region of their IOVA space to a managed cookie, and extend the mapping routine to implement a trivial linear allocator in such cases, to avoid the needless overhead of a full-blown IOVA domain. Signed-off-by: Robin Murphy Reviewed-by: Tomasz Nowicki Reviewed-by: Eric Auger Tested-by: Eric Auger Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/dma-iommu.c | 119 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 2db0d64..de41ead 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -37,15 +37,50 @@ struct iommu_dma_msi_page { phys_addr_t phys; }; +enum iommu_dma_cookie_type { + IOMMU_DMA_IOVA_COOKIE, + IOMMU_DMA_MSI_COOKIE, +}; + struct iommu_dma_cookie { - struct iova_domain iovad; - struct list_head msi_page_list; - spinlock_t msi_lock; + enum iommu_dma_cookie_type type; + union { + /* Full allocator for IOMMU_DMA_IOVA_COOKIE */ + struct iova_domain iovad; + /* Trivial linear page allocator for IOMMU_DMA_MSI_COOKIE */ + dma_addr_t msi_iova; + }; + struct list_head msi_page_list; + spinlock_t msi_lock; }; +static inline size_t cookie_msi_granule(struct iommu_dma_cookie *cookie) +{ + if (cookie->type == IOMMU_DMA_IOVA_COOKIE) + return cookie->iovad.granule; + return PAGE_SIZE; +} + static inline struct iova_domain *cookie_iovad(struct iommu_domain *domain) { - return &((struct iommu_dma_cookie *)domain->iova_cookie)->iovad; + struct iommu_dma_cookie *cookie = domain->iova_cookie; + + if (cookie->type == IOMMU_DMA_IOVA_COOKIE) + return &cookie->iovad; + return NULL; +} + +static struct iommu_dma_cookie *cookie_alloc(enum iommu_dma_cookie_type type) +{ + struct iommu_dma_cookie *cookie; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (cookie) { + spin_lock_init(&cookie->msi_lock); + INIT_LIST_HEAD(&cookie->msi_page_list); + cookie->type = type; + } + return cookie; } int iommu_dma_init(void) @@ -62,25 +97,53 @@ int iommu_dma_init(void) */ int iommu_get_dma_cookie(struct iommu_domain *domain) { + if (domain->iova_cookie) + return -EEXIST; + + domain->iova_cookie = cookie_alloc(IOMMU_DMA_IOVA_COOKIE); + if (!domain->iova_cookie) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL(iommu_get_dma_cookie); + +/** + * iommu_get_msi_cookie - Acquire just MSI remapping resources + * @domain: IOMMU domain to prepare + * @base: Start address of IOVA region for MSI mappings + * + * Users who manage their own IOVA allocation and do not want DMA API support, + * but would still like to take advantage of automatic MSI remapping, can use + * this to initialise their own domain appropriately. Users should reserve a + * contiguous IOVA region, starting at @base, large enough to accommodate the + * number of PAGE_SIZE mappings necessary to cover every MSI doorbell address + * used by the devices attached to @domain. + */ +int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base) +{ struct iommu_dma_cookie *cookie; + if (domain->type != IOMMU_DOMAIN_UNMANAGED) + return -EINVAL; + if (domain->iova_cookie) return -EEXIST; - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + cookie = cookie_alloc(IOMMU_DMA_MSI_COOKIE); if (!cookie) return -ENOMEM; - spin_lock_init(&cookie->msi_lock); - INIT_LIST_HEAD(&cookie->msi_page_list); + cookie->msi_iova = base; domain->iova_cookie = cookie; return 0; } -EXPORT_SYMBOL(iommu_get_dma_cookie); +EXPORT_SYMBOL(iommu_get_msi_cookie); /** * iommu_put_dma_cookie - Release a domain's DMA mapping resources - * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie() + * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie() or + * iommu_get_msi_cookie() * * IOMMU drivers should normally call this from their domain_free callback. */ @@ -92,7 +155,7 @@ void iommu_put_dma_cookie(struct iommu_domain *domain) if (!cookie) return; - if (cookie->iovad.granule) + if (cookie->type == IOMMU_DMA_IOVA_COOKIE && cookie->iovad.granule) put_iova_domain(&cookie->iovad); list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list) { @@ -137,11 +200,12 @@ static void iova_reserve_pci_windows(struct pci_dev *dev, int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, u64 size, struct device *dev) { - struct iova_domain *iovad = cookie_iovad(domain); + struct iommu_dma_cookie *cookie = domain->iova_cookie; + struct iova_domain *iovad = &cookie->iovad; unsigned long order, base_pfn, end_pfn; - if (!iovad) - return -ENODEV; + if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE) + return -EINVAL; /* Use the smallest supported page size for IOVA granularity */ order = __ffs(domain->pgsize_bitmap); @@ -662,11 +726,12 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev, { struct iommu_dma_cookie *cookie = domain->iova_cookie; struct iommu_dma_msi_page *msi_page; - struct iova_domain *iovad = &cookie->iovad; + struct iova_domain *iovad = cookie_iovad(domain); struct iova *iova; int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO; + size_t size = cookie_msi_granule(cookie); - msi_addr &= ~(phys_addr_t)iova_mask(iovad); + msi_addr &= ~(phys_addr_t)(size - 1); list_for_each_entry(msi_page, &cookie->msi_page_list, list) if (msi_page->phys == msi_addr) return msi_page; @@ -675,13 +740,18 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev, if (!msi_page) return NULL; - iova = __alloc_iova(domain, iovad->granule, dma_get_mask(dev)); - if (!iova) - goto out_free_page; - msi_page->phys = msi_addr; - msi_page->iova = iova_dma_addr(iovad, iova); - if (iommu_map(domain, msi_page->iova, msi_addr, iovad->granule, prot)) + if (iovad) { + iova = __alloc_iova(domain, size, dma_get_mask(dev)); + if (!iova) + goto out_free_page; + msi_page->iova = iova_dma_addr(iovad, iova); + } else { + msi_page->iova = cookie->msi_iova; + cookie->msi_iova += size; + } + + if (iommu_map(domain, msi_page->iova, msi_addr, size, prot)) goto out_free_iova; INIT_LIST_HEAD(&msi_page->list); @@ -689,7 +759,10 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev, return msi_page; out_free_iova: - __free_iova(iovad, iova); + if (iovad) + __free_iova(iovad, iova); + else + cookie->msi_iova -= size; out_free_page: kfree(msi_page); return NULL; @@ -730,7 +803,7 @@ void iommu_dma_map_msi_msg(int irq, struct msi_msg *msg) msg->data = ~0U; } else { msg->address_hi = upper_32_bits(msi_page->iova); - msg->address_lo &= iova_mask(&cookie->iovad); + msg->address_lo &= cookie_msi_granule(cookie) - 1; msg->address_lo += lower_32_bits(msi_page->iova); } } -- cgit v1.1 From e5b5234a36ca283158721d3d2e0cddfa324abdf9 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:47 +0000 Subject: iommu: Rename iommu_dm_regions into iommu_resv_regions We want to extend the callbacks used for dm regions and use them for reserved regions. Reserved regions can be - directly mapped regions - regions that cannot be iommu mapped (PCI host bridge windows, ...) - MSI regions (because they belong to another address space or because they are not translated by the IOMMU and need special handling) So let's rename the struct and also the callbacks. Signed-off-by: Eric Auger Acked-by: Robin Murphy Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/amd_iommu.c | 20 ++++++++++---------- drivers/iommu/iommu.c | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 3ef0f42..f7a024f 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -3161,8 +3161,8 @@ static bool amd_iommu_capable(enum iommu_cap cap) return false; } -static void amd_iommu_get_dm_regions(struct device *dev, - struct list_head *head) +static void amd_iommu_get_resv_regions(struct device *dev, + struct list_head *head) { struct unity_map_entry *entry; int devid; @@ -3172,7 +3172,7 @@ static void amd_iommu_get_dm_regions(struct device *dev, return; list_for_each_entry(entry, &amd_iommu_unity_map, list) { - struct iommu_dm_region *region; + struct iommu_resv_region *region; if (devid < entry->devid_start || devid > entry->devid_end) continue; @@ -3195,18 +3195,18 @@ static void amd_iommu_get_dm_regions(struct device *dev, } } -static void amd_iommu_put_dm_regions(struct device *dev, +static void amd_iommu_put_resv_regions(struct device *dev, struct list_head *head) { - struct iommu_dm_region *entry, *next; + struct iommu_resv_region *entry, *next; list_for_each_entry_safe(entry, next, head, list) kfree(entry); } -static void amd_iommu_apply_dm_region(struct device *dev, +static void amd_iommu_apply_resv_region(struct device *dev, struct iommu_domain *domain, - struct iommu_dm_region *region) + struct iommu_resv_region *region) { struct dma_ops_domain *dma_dom = to_dma_ops_domain(to_pdomain(domain)); unsigned long start, end; @@ -3230,9 +3230,9 @@ static const struct iommu_ops amd_iommu_ops = { .add_device = amd_iommu_add_device, .remove_device = amd_iommu_remove_device, .device_group = amd_iommu_device_group, - .get_dm_regions = amd_iommu_get_dm_regions, - .put_dm_regions = amd_iommu_put_dm_regions, - .apply_dm_region = amd_iommu_apply_dm_region, + .get_resv_regions = amd_iommu_get_resv_regions, + .put_resv_regions = amd_iommu_put_resv_regions, + .apply_resv_region = amd_iommu_apply_resv_region, .pgsize_bitmap = AMD_IOMMU_PGSIZES, }; diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index dbe7f65..1cee5c3 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -318,7 +318,7 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group, struct device *dev) { struct iommu_domain *domain = group->default_domain; - struct iommu_dm_region *entry; + struct iommu_resv_region *entry; struct list_head mappings; unsigned long pg_size; int ret = 0; @@ -331,14 +331,14 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group, pg_size = 1UL << __ffs(domain->pgsize_bitmap); INIT_LIST_HEAD(&mappings); - iommu_get_dm_regions(dev, &mappings); + iommu_get_resv_regions(dev, &mappings); /* We need to consider overlapping regions for different devices */ list_for_each_entry(entry, &mappings, list) { dma_addr_t start, end, addr; - if (domain->ops->apply_dm_region) - domain->ops->apply_dm_region(dev, domain, entry); + if (domain->ops->apply_resv_region) + domain->ops->apply_resv_region(dev, domain, entry); start = ALIGN(entry->start, pg_size); end = ALIGN(entry->start + entry->length, pg_size); @@ -358,7 +358,7 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group, } out: - iommu_put_dm_regions(dev, &mappings); + iommu_put_resv_regions(dev, &mappings); return ret; } @@ -1559,20 +1559,20 @@ int iommu_domain_set_attr(struct iommu_domain *domain, } EXPORT_SYMBOL_GPL(iommu_domain_set_attr); -void iommu_get_dm_regions(struct device *dev, struct list_head *list) +void iommu_get_resv_regions(struct device *dev, struct list_head *list) { const struct iommu_ops *ops = dev->bus->iommu_ops; - if (ops && ops->get_dm_regions) - ops->get_dm_regions(dev, list); + if (ops && ops->get_resv_regions) + ops->get_resv_regions(dev, list); } -void iommu_put_dm_regions(struct device *dev, struct list_head *list) +void iommu_put_resv_regions(struct device *dev, struct list_head *list) { const struct iommu_ops *ops = dev->bus->iommu_ops; - if (ops && ops->put_dm_regions) - ops->put_dm_regions(dev, list); + if (ops && ops->put_resv_regions) + ops->put_resv_regions(dev, list); } /* Request that a device is direct mapped by the IOMMU */ -- cgit v1.1 From d30ddcaa7b028049cdfee3a40248002d07b2bbf3 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:48 +0000 Subject: iommu: Add a new type field in iommu_resv_region We introduce a new field to differentiate the reserved region types and specialize the apply_resv_region implementation. Legacy direct mapped regions have IOMMU_RESV_DIRECT type. We introduce 2 new reserved memory types: - IOMMU_RESV_MSI will characterize MSI regions that are mapped - IOMMU_RESV_RESERVED characterize regions that cannot by mapped. Signed-off-by: Eric Auger Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/amd_iommu.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index f7a024f..5f7ea4f 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -3186,6 +3186,7 @@ static void amd_iommu_get_resv_regions(struct device *dev, region->start = entry->address_start; region->length = entry->address_end - entry->address_start; + region->type = IOMMU_RESV_DIRECT; if (entry->prot & IOMMU_PROT_IR) region->prot |= IOMMU_READ; if (entry->prot & IOMMU_PROT_IW) -- cgit v1.1 From 2b20cbba3390a55c511acba2f0f517dd27a528b2 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:49 +0000 Subject: iommu: iommu_alloc_resv_region Introduce a new helper serving the purpose to allocate a reserved region. This will be used in iommu driver implementing reserved region callbacks. Signed-off-by: Eric Auger Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/iommu.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 1cee5c3..927878d 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1575,6 +1575,24 @@ void iommu_put_resv_regions(struct device *dev, struct list_head *list) ops->put_resv_regions(dev, list); } +struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start, + size_t length, + int prot, int type) +{ + struct iommu_resv_region *region; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return NULL; + + INIT_LIST_HEAD(®ion->list); + region->start = start; + region->length = length; + region->prot = prot; + region->type = type; + return region; +} + /* Request that a device is direct mapped by the IOMMU */ int iommu_request_dm_for_dev(struct device *dev) { -- cgit v1.1 From 544a25d904cab2ae68bd71b334603ec3a49b60dd Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:50 +0000 Subject: iommu: Only map direct mapped regions As we introduced new reserved region types which do not require mapping, let's make sure we only map direct mapped regions. Signed-off-by: Eric Auger Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/iommu.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 927878d..41c1906 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -343,6 +343,9 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group, start = ALIGN(entry->start, pg_size); end = ALIGN(entry->start + entry->length, pg_size); + if (entry->type != IOMMU_RESV_DIRECT) + continue; + for (addr = start; addr < end; addr += pg_size) { phys_addr_t phys_addr; -- cgit v1.1 From 6c65fb318e8bbf21e939e651028b955324f1d873 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:51 +0000 Subject: iommu: iommu_get_group_resv_regions Introduce iommu_get_group_resv_regions whose role consists in enumerating all devices from the group and collecting their reserved regions. The list is sorted and overlaps between regions of the same type are handled by merging the regions. Signed-off-by: Eric Auger Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/iommu.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 41c1906..640056b 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -133,6 +133,104 @@ static ssize_t iommu_group_show_name(struct iommu_group *group, char *buf) return sprintf(buf, "%s\n", group->name); } +/** + * iommu_insert_resv_region - Insert a new region in the + * list of reserved regions. + * @new: new region to insert + * @regions: list of regions + * + * The new element is sorted by address with respect to the other + * regions of the same type. In case it overlaps with another + * region of the same type, regions are merged. In case it + * overlaps with another region of different type, regions are + * not merged. + */ +static int iommu_insert_resv_region(struct iommu_resv_region *new, + struct list_head *regions) +{ + struct iommu_resv_region *region; + phys_addr_t start = new->start; + phys_addr_t end = new->start + new->length - 1; + struct list_head *pos = regions->next; + + while (pos != regions) { + struct iommu_resv_region *entry = + list_entry(pos, struct iommu_resv_region, list); + phys_addr_t a = entry->start; + phys_addr_t b = entry->start + entry->length - 1; + int type = entry->type; + + if (end < a) { + goto insert; + } else if (start > b) { + pos = pos->next; + } else if ((start >= a) && (end <= b)) { + if (new->type == type) + goto done; + else + pos = pos->next; + } else { + if (new->type == type) { + phys_addr_t new_start = min(a, start); + phys_addr_t new_end = max(b, end); + + list_del(&entry->list); + entry->start = new_start; + entry->length = new_end - new_start + 1; + iommu_insert_resv_region(entry, regions); + } else { + pos = pos->next; + } + } + } +insert: + region = iommu_alloc_resv_region(new->start, new->length, + new->prot, new->type); + if (!region) + return -ENOMEM; + + list_add_tail(®ion->list, pos); +done: + return 0; +} + +static int +iommu_insert_device_resv_regions(struct list_head *dev_resv_regions, + struct list_head *group_resv_regions) +{ + struct iommu_resv_region *entry; + int ret; + + list_for_each_entry(entry, dev_resv_regions, list) { + ret = iommu_insert_resv_region(entry, group_resv_regions); + if (ret) + break; + } + return ret; +} + +int iommu_get_group_resv_regions(struct iommu_group *group, + struct list_head *head) +{ + struct iommu_device *device; + int ret = 0; + + mutex_lock(&group->mutex); + list_for_each_entry(device, &group->devices, list) { + struct list_head dev_resv_regions; + + INIT_LIST_HEAD(&dev_resv_regions); + iommu_get_resv_regions(device->dev, &dev_resv_regions); + ret = iommu_insert_device_resv_regions(&dev_resv_regions, head); + iommu_put_resv_regions(device->dev, &dev_resv_regions); + if (ret) + break; + } + mutex_unlock(&group->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(iommu_get_group_resv_regions); + static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL); static void iommu_group_release(struct kobject *kobj) -- cgit v1.1 From bc7d12b91bd35477fd650c4d72b61239de9d9066 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:52 +0000 Subject: iommu: Implement reserved_regions iommu-group sysfs file A new iommu-group sysfs attribute file is introduced. It contains the list of reserved regions for the iommu-group. Each reserved region is described on a separate line: - first field is the start IOVA address, - second is the end IOVA address, - third is the type. Signed-off-by: Eric Auger Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/iommu.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 640056b..f4a176e 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -68,6 +68,12 @@ struct iommu_group_attribute { const char *buf, size_t count); }; +static const char * const iommu_group_resv_type_string[] = { + [IOMMU_RESV_DIRECT] = "direct", + [IOMMU_RESV_RESERVED] = "reserved", + [IOMMU_RESV_MSI] = "msi", +}; + #define IOMMU_GROUP_ATTR(_name, _mode, _show, _store) \ struct iommu_group_attribute iommu_group_attr_##_name = \ __ATTR(_name, _mode, _show, _store) @@ -231,8 +237,33 @@ int iommu_get_group_resv_regions(struct iommu_group *group, } EXPORT_SYMBOL_GPL(iommu_get_group_resv_regions); +static ssize_t iommu_group_show_resv_regions(struct iommu_group *group, + char *buf) +{ + struct iommu_resv_region *region, *next; + struct list_head group_resv_regions; + char *str = buf; + + INIT_LIST_HEAD(&group_resv_regions); + iommu_get_group_resv_regions(group, &group_resv_regions); + + list_for_each_entry_safe(region, next, &group_resv_regions, list) { + str += sprintf(str, "0x%016llx 0x%016llx %s\n", + (long long int)region->start, + (long long int)(region->start + + region->length - 1), + iommu_group_resv_type_string[region->type]); + kfree(region); + } + + return (str - buf); +} + static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL); +static IOMMU_GROUP_ATTR(reserved_regions, 0444, + iommu_group_show_resv_regions, NULL); + static void iommu_group_release(struct kobject *kobj) { struct iommu_group *group = to_iommu_group(kobj); @@ -310,6 +341,11 @@ struct iommu_group *iommu_group_alloc(void) */ kobject_put(&group->kobj); + ret = iommu_group_create_file(group, + &iommu_group_attr_reserved_regions); + if (ret) + return ERR_PTR(ret); + pr_debug("Allocated group %d\n", group->id); return group; -- cgit v1.1 From 0659b8dc45a6b13a4fec73b2ebb51c96b41974c4 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:53 +0000 Subject: iommu/vt-d: Implement reserved region get/put callbacks This patch registers the [FEE0_0000h - FEF0_000h] 1MB MSI range as a reserved region and RMRR regions as direct regions. This will allow to report those reserved regions in the iommu-group sysfs. Signed-off-by: Eric Auger Signed-off-by: Will Deacon --- drivers/iommu/intel-iommu.c | 92 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 8a18525..bce59a5 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -440,6 +440,7 @@ struct dmar_rmrr_unit { u64 end_address; /* reserved end address */ struct dmar_dev_scope *devices; /* target devices */ int devices_cnt; /* target device count */ + struct iommu_resv_region *resv; /* reserved region handle */ }; struct dmar_atsr_unit { @@ -4246,27 +4247,40 @@ static inline void init_iommu_pm_ops(void) {} int __init dmar_parse_one_rmrr(struct acpi_dmar_header *header, void *arg) { struct acpi_dmar_reserved_memory *rmrr; + int prot = DMA_PTE_READ|DMA_PTE_WRITE; struct dmar_rmrr_unit *rmrru; + size_t length; rmrru = kzalloc(sizeof(*rmrru), GFP_KERNEL); if (!rmrru) - return -ENOMEM; + goto out; rmrru->hdr = header; rmrr = (struct acpi_dmar_reserved_memory *)header; rmrru->base_address = rmrr->base_address; rmrru->end_address = rmrr->end_address; + + length = rmrr->end_address - rmrr->base_address + 1; + rmrru->resv = iommu_alloc_resv_region(rmrr->base_address, length, prot, + IOMMU_RESV_DIRECT); + if (!rmrru->resv) + goto free_rmrru; + rmrru->devices = dmar_alloc_dev_scope((void *)(rmrr + 1), ((void *)rmrr) + rmrr->header.length, &rmrru->devices_cnt); - if (rmrru->devices_cnt && rmrru->devices == NULL) { - kfree(rmrru); - return -ENOMEM; - } + if (rmrru->devices_cnt && rmrru->devices == NULL) + goto free_all; list_add(&rmrru->list, &dmar_rmrr_units); return 0; +free_all: + kfree(rmrru->resv); +free_rmrru: + kfree(rmrru); +out: + return -ENOMEM; } static struct dmar_atsr_unit *dmar_find_atsr(struct acpi_dmar_atsr *atsr) @@ -4480,6 +4494,7 @@ static void intel_iommu_free_dmars(void) list_for_each_entry_safe(rmrru, rmrr_n, &dmar_rmrr_units, list) { list_del(&rmrru->list); dmar_free_dev_scope(&rmrru->devices, &rmrru->devices_cnt); + kfree(rmrru->resv); kfree(rmrru); } @@ -5203,6 +5218,45 @@ static void intel_iommu_remove_device(struct device *dev) iommu_device_unlink(iommu->iommu_dev, dev); } +static void intel_iommu_get_resv_regions(struct device *device, + struct list_head *head) +{ + struct iommu_resv_region *reg; + struct dmar_rmrr_unit *rmrr; + struct device *i_dev; + int i; + + rcu_read_lock(); + for_each_rmrr_units(rmrr) { + for_each_active_dev_scope(rmrr->devices, rmrr->devices_cnt, + i, i_dev) { + if (i_dev != device) + continue; + + list_add_tail(&rmrr->resv->list, head); + } + } + rcu_read_unlock(); + + reg = iommu_alloc_resv_region(IOAPIC_RANGE_START, + IOAPIC_RANGE_END - IOAPIC_RANGE_START + 1, + 0, IOMMU_RESV_RESERVED); + if (!reg) + return; + list_add_tail(®->list, head); +} + +static void intel_iommu_put_resv_regions(struct device *dev, + struct list_head *head) +{ + struct iommu_resv_region *entry, *next; + + list_for_each_entry_safe(entry, next, head, list) { + if (entry->type == IOMMU_RESV_RESERVED) + kfree(entry); + } +} + #ifdef CONFIG_INTEL_IOMMU_SVM #define MAX_NR_PASID_BITS (20) static inline unsigned long intel_iommu_get_pts(struct intel_iommu *iommu) @@ -5333,19 +5387,21 @@ struct intel_iommu *intel_svm_device_to_iommu(struct device *dev) #endif /* CONFIG_INTEL_IOMMU_SVM */ static const struct iommu_ops intel_iommu_ops = { - .capable = intel_iommu_capable, - .domain_alloc = intel_iommu_domain_alloc, - .domain_free = intel_iommu_domain_free, - .attach_dev = intel_iommu_attach_device, - .detach_dev = intel_iommu_detach_device, - .map = intel_iommu_map, - .unmap = intel_iommu_unmap, - .map_sg = default_iommu_map_sg, - .iova_to_phys = intel_iommu_iova_to_phys, - .add_device = intel_iommu_add_device, - .remove_device = intel_iommu_remove_device, - .device_group = pci_device_group, - .pgsize_bitmap = INTEL_IOMMU_PGSIZES, + .capable = intel_iommu_capable, + .domain_alloc = intel_iommu_domain_alloc, + .domain_free = intel_iommu_domain_free, + .attach_dev = intel_iommu_attach_device, + .detach_dev = intel_iommu_detach_device, + .map = intel_iommu_map, + .unmap = intel_iommu_unmap, + .map_sg = default_iommu_map_sg, + .iova_to_phys = intel_iommu_iova_to_phys, + .add_device = intel_iommu_add_device, + .remove_device = intel_iommu_remove_device, + .get_resv_regions = intel_iommu_get_resv_regions, + .put_resv_regions = intel_iommu_put_resv_regions, + .device_group = pci_device_group, + .pgsize_bitmap = INTEL_IOMMU_PGSIZES, }; static void quirk_iommu_g4x_gfx(struct pci_dev *dev) -- cgit v1.1 From 4397f32c03a757acb3e44d268c20233fa1758ed9 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:54 +0000 Subject: iommu/amd: Declare MSI and HT regions as reserved IOVA regions This patch registers the MSI and HT regions as non mappable reserved regions. They will be exposed in the iommu-group sysfs. For direct-mapped regions let's also use iommu_alloc_resv_region(). Signed-off-by: Eric Auger Signed-off-by: Will Deacon --- drivers/iommu/amd_iommu.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 5f7ea4f..d109e41 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -3164,6 +3164,7 @@ static bool amd_iommu_capable(enum iommu_cap cap) static void amd_iommu_get_resv_regions(struct device *dev, struct list_head *head) { + struct iommu_resv_region *region; struct unity_map_entry *entry; int devid; @@ -3172,28 +3173,42 @@ static void amd_iommu_get_resv_regions(struct device *dev, return; list_for_each_entry(entry, &amd_iommu_unity_map, list) { - struct iommu_resv_region *region; + size_t length; + int prot = 0; if (devid < entry->devid_start || devid > entry->devid_end) continue; - region = kzalloc(sizeof(*region), GFP_KERNEL); + length = entry->address_end - entry->address_start; + if (entry->prot & IOMMU_PROT_IR) + prot |= IOMMU_READ; + if (entry->prot & IOMMU_PROT_IW) + prot |= IOMMU_WRITE; + + region = iommu_alloc_resv_region(entry->address_start, + length, prot, + IOMMU_RESV_DIRECT); if (!region) { pr_err("Out of memory allocating dm-regions for %s\n", dev_name(dev)); return; } - - region->start = entry->address_start; - region->length = entry->address_end - entry->address_start; - region->type = IOMMU_RESV_DIRECT; - if (entry->prot & IOMMU_PROT_IR) - region->prot |= IOMMU_READ; - if (entry->prot & IOMMU_PROT_IW) - region->prot |= IOMMU_WRITE; - list_add_tail(®ion->list, head); } + + region = iommu_alloc_resv_region(MSI_RANGE_START, + MSI_RANGE_END - MSI_RANGE_START + 1, + 0, IOMMU_RESV_RESERVED); + if (!region) + return; + list_add_tail(®ion->list, head); + + region = iommu_alloc_resv_region(HT_RANGE_START, + HT_RANGE_END - HT_RANGE_START + 1, + 0, IOMMU_RESV_RESERVED); + if (!region) + return; + list_add_tail(®ion->list, head); } static void amd_iommu_put_resv_regions(struct device *dev, -- cgit v1.1 From f3ebee80b3131d4cdb49d39cf07b32afe40b811c Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:55 +0000 Subject: iommu/arm-smmu: Implement reserved region get/put callbacks The get() populates the list with the MSI IOVA reserved window. At the moment an arbitray MSI IOVA window is set at 0x8000000 of size 1MB. This will allow to report those info in iommu-group sysfs. Signed-off-by: Eric Auger Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Acked-by: Will Deacon Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a60cded..a354572 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -281,6 +281,9 @@ enum arm_smmu_s2cr_privcfg { #define FSYNR0_WNR (1 << 4) +#define MSI_IOVA_BASE 0x8000000 +#define MSI_IOVA_LENGTH 0x100000 + static int force_stage; module_param(force_stage, int, S_IRUGO); MODULE_PARM_DESC(force_stage, @@ -1549,6 +1552,29 @@ static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args) return iommu_fwspec_add_ids(dev, &fwid, 1); } +static void arm_smmu_get_resv_regions(struct device *dev, + struct list_head *head) +{ + struct iommu_resv_region *region; + int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO; + + region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH, + prot, IOMMU_RESV_MSI); + if (!region) + return; + + list_add_tail(®ion->list, head); +} + +static void arm_smmu_put_resv_regions(struct device *dev, + struct list_head *head) +{ + struct iommu_resv_region *entry, *next; + + list_for_each_entry_safe(entry, next, head, list) + kfree(entry); +} + static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc, @@ -1564,6 +1590,8 @@ static struct iommu_ops arm_smmu_ops = { .domain_get_attr = arm_smmu_domain_get_attr, .domain_set_attr = arm_smmu_domain_set_attr, .of_xlate = arm_smmu_of_xlate, + .get_resv_regions = arm_smmu_get_resv_regions, + .put_resv_regions = arm_smmu_put_resv_regions, .pgsize_bitmap = -1UL, /* Restricted during device attach */ }; -- cgit v1.1 From 50019f09a4baa0bd6635d4933c1bfed22d66b640 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:57:56 +0000 Subject: iommu/arm-smmu-v3: Implement reserved region get/put callbacks The get() populates the list with the MSI IOVA reserved window. At the moment an arbitray MSI IOVA window is set at 0x8000000 of size 1MB. This will allow to report those info in iommu-group sysfs. Signed-off-by: Eric Auger Acked-by: Will Deacon Reviewed-by: Tomasz Nowicki Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu-v3.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 4d6ec44..6c4111c 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -412,6 +412,9 @@ /* High-level queue structures */ #define ARM_SMMU_POLL_TIMEOUT_US 100 +#define MSI_IOVA_BASE 0x8000000 +#define MSI_IOVA_LENGTH 0x100000 + static bool disable_bypass; module_param_named(disable_bypass, disable_bypass, bool, S_IRUGO); MODULE_PARM_DESC(disable_bypass, @@ -1883,6 +1886,29 @@ static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args) return iommu_fwspec_add_ids(dev, args->args, 1); } +static void arm_smmu_get_resv_regions(struct device *dev, + struct list_head *head) +{ + struct iommu_resv_region *region; + int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO; + + region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH, + prot, IOMMU_RESV_MSI); + if (!region) + return; + + list_add_tail(®ion->list, head); +} + +static void arm_smmu_put_resv_regions(struct device *dev, + struct list_head *head) +{ + struct iommu_resv_region *entry, *next; + + list_for_each_entry_safe(entry, next, head, list) + kfree(entry); +} + static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc, @@ -1898,6 +1924,8 @@ static struct iommu_ops arm_smmu_ops = { .domain_get_attr = arm_smmu_domain_get_attr, .domain_set_attr = arm_smmu_domain_set_attr, .of_xlate = arm_smmu_of_xlate, + .get_resv_regions = arm_smmu_get_resv_regions, + .put_resv_regions = arm_smmu_put_resv_regions, .pgsize_bitmap = -1UL, /* Restricted during device attach */ }; -- cgit v1.1 From 59768527db3de41afb0586e677e322012e6a392b Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:58:00 +0000 Subject: irqchip/gicv3-its: Sets IRQ_DOMAIN_FLAG_MSI_REMAP The GICv3 ITS is MSI remapping capable. Let's advertise this property so that VFIO passthrough can assess IRQ safety. Signed-off-by: Eric Auger Reviewed-by: Marc Zyngier Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/irqchip/irq-gic-v3-its.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 69b040f..9d4fefc 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1642,6 +1642,7 @@ static int its_init_domain(struct fwnode_handle *handle, struct its_node *its) inner_domain->parent = its_parent; inner_domain->bus_token = DOMAIN_BUS_NEXUS; + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_REMAP; info->ops = &its_msi_domain_ops; info->data = its; inner_domain->host_data = info; -- cgit v1.1 From 5d704992189fe8683a23f583a2f4f137a9b9d28b Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:58:01 +0000 Subject: vfio/type1: Allow transparent MSI IOVA allocation When attaching a group to the container, check the group's reserved regions and test whether the IOMMU translates MSI transactions. If yes, we initialize an IOVA allocator through the iommu_get_msi_cookie API. This will allow the MSI IOVAs to be transparently allocated on MSI controller's compose(). Signed-off-by: Eric Auger Acked-by: Alex Williamson Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/vfio/vfio_iommu_type1.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'drivers') diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 9266271..5651faf 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -39,6 +39,7 @@ #include #include #include +#include #define DRIVER_VERSION "0.2" #define DRIVER_AUTHOR "Alex Williamson " @@ -1181,6 +1182,28 @@ static struct vfio_group *find_iommu_group(struct vfio_domain *domain, return NULL; } +static bool vfio_iommu_has_resv_msi(struct iommu_group *group, + phys_addr_t *base) +{ + struct list_head group_resv_regions; + struct iommu_resv_region *region, *next; + bool ret = false; + + INIT_LIST_HEAD(&group_resv_regions); + iommu_get_group_resv_regions(group, &group_resv_regions); + list_for_each_entry(region, &group_resv_regions, list) { + if (region->type & IOMMU_RESV_MSI) { + *base = region->start; + ret = true; + goto out; + } + } +out: + list_for_each_entry_safe(region, next, &group_resv_regions, list) + kfree(region); + return ret; +} + static int vfio_iommu_type1_attach_group(void *iommu_data, struct iommu_group *iommu_group) { @@ -1189,6 +1212,8 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, struct vfio_domain *domain, *d; struct bus_type *bus = NULL, *mdev_bus; int ret; + bool resv_msi; + phys_addr_t resv_msi_base; mutex_lock(&iommu->lock); @@ -1258,6 +1283,8 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, if (ret) goto out_domain; + resv_msi = vfio_iommu_has_resv_msi(iommu_group, &resv_msi_base); + INIT_LIST_HEAD(&domain->group_list); list_add(&group->next, &domain->group_list); @@ -1304,6 +1331,9 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, if (ret) goto out_detach; + if (resv_msi && iommu_get_msi_cookie(domain->domain, resv_msi_base)) + goto out_detach; + list_add(&domain->next, &iommu->domain_list); mutex_unlock(&iommu->lock); -- cgit v1.1 From 9d72f87babf144ff3ca5d85655c710de05110038 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:58:02 +0000 Subject: vfio/type1: Check MSI remapping at irq domain level In case the IOMMU translates MSI transactions (typical case on ARM), we check MSI remapping capability at IRQ domain level. Otherwise it is checked at IOMMU level. At this stage the arm-smmu-(v3) still advertise the IOMMU_CAP_INTR_REMAP capability at IOMMU level. This will be removed in subsequent patches. Signed-off-by: Eric Auger Acked-by: Alex Williamson Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/vfio/vfio_iommu_type1.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 5651faf..ec903a0 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -40,6 +40,7 @@ #include #include #include +#include #define DRIVER_VERSION "0.2" #define DRIVER_AUTHOR "Alex Williamson " @@ -1212,7 +1213,7 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, struct vfio_domain *domain, *d; struct bus_type *bus = NULL, *mdev_bus; int ret; - bool resv_msi; + bool resv_msi, msi_remap; phys_addr_t resv_msi_base; mutex_lock(&iommu->lock); @@ -1288,8 +1289,10 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, INIT_LIST_HEAD(&domain->group_list); list_add(&group->next, &domain->group_list); - if (!allow_unsafe_interrupts && - !iommu_capable(bus, IOMMU_CAP_INTR_REMAP)) { + msi_remap = resv_msi ? irq_domain_check_msi_remap() : + iommu_capable(bus, IOMMU_CAP_INTR_REMAP); + + if (!allow_unsafe_interrupts && !msi_remap) { pr_warn("%s: No interrupt remapping support. Use the module param \"allow_unsafe_interrupts\" to enable VFIO IOMMU support on this platform\n", __func__); ret = -EPERM; -- cgit v1.1 From 5018c8d5ef0c172592eb98cf10e253d47b544ba8 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Thu, 19 Jan 2017 20:58:03 +0000 Subject: iommu/arm-smmu: Do not advertise IOMMU_CAP_INTR_REMAP anymore IOMMU_CAP_INTR_REMAP has been advertised in arm-smmu(-v3) although on ARM this property is not attached to the IOMMU but rather is implemented in the MSI controller (GICv3 ITS). Now vfio_iommu_type1 checks MSI remapping capability at MSI controller level, let's correct this. Signed-off-by: Eric Auger Acked-by: Will Deacon Reviewed-by: Tomasz Nowicki Tested-by: Tomasz Nowicki Tested-by: Bharat Bhushan Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu-v3.c | 2 -- drivers/iommu/arm-smmu.c | 2 -- 2 files changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 6c4111c..d9cf6cb 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -1375,8 +1375,6 @@ static bool arm_smmu_capable(enum iommu_cap cap) switch (cap) { case IOMMU_CAP_CACHE_COHERENCY: return true; - case IOMMU_CAP_INTR_REMAP: - return true; /* MSIs are just memory writes */ case IOMMU_CAP_NOEXEC: return true; default: diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a354572..13d2600 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1374,8 +1374,6 @@ static bool arm_smmu_capable(enum iommu_cap cap) * requests. */ return true; - case IOMMU_CAP_INTR_REMAP: - return true; /* MSIs are just memory writes */ case IOMMU_CAP_NOEXEC: return true; default: -- cgit v1.1 From 810871c57011eb3e89e6768932757f169d666cd2 Mon Sep 17 00:00:00 2001 From: Nate Watterson Date: Tue, 20 Dec 2016 23:11:48 -0500 Subject: iommu/arm-smmu-v3: Clear prior settings when updating STEs To prevent corruption of the stage-1 context pointer field when updating STEs, rebuild the entire containing dword instead of clearing individual fields. Signed-off-by: Nate Watterson Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu-v3.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 4d6ec44..94f305d 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -1042,13 +1042,8 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid, } } - /* Nuke the existing Config, as we're going to rewrite it */ - val &= ~(STRTAB_STE_0_CFG_MASK << STRTAB_STE_0_CFG_SHIFT); - - if (ste->valid) - val |= STRTAB_STE_0_V; - else - val &= ~STRTAB_STE_0_V; + /* Nuke the existing STE_0 value, as we're going to rewrite it */ + val = ste->valid ? STRTAB_STE_0_V : 0; if (ste->bypass) { val |= disable_bypass ? STRTAB_STE_0_CFG_ABORT @@ -1083,7 +1078,6 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid, val |= (ste->s1_cfg->cdptr_dma & STRTAB_STE_0_S1CTXPTR_MASK << STRTAB_STE_0_S1CTXPTR_SHIFT) | STRTAB_STE_0_CFG_S1_TRANS; - } if (ste->s2_cfg) { -- cgit v1.1 From 692c4e422dae023a5247c0c6fc3b7b89305ca974 Mon Sep 17 00:00:00 2001 From: Nate Watterson Date: Tue, 10 Jan 2017 14:47:13 -0500 Subject: iommu/arm-smmu-v3: limit use of 2-level stream tables In the current arm-smmu-v3 driver, all smmus that support 2-level stream tables are being forced to use them. This is suboptimal for smmus that support fewer stream id bits than would fill in a single second level table. This patch limits the use of 2-level tables to smmus that both support the feature and whose first level table can possibly contain more than a single entry. Signed-off-by: Nate Watterson Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu-v3.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 94f305d..947807c 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -1977,17 +1977,9 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) u32 size, l1size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; - /* - * If we can resolve everything with a single L2 table, then we - * just need a single L1 descriptor. Otherwise, calculate the L1 - * size, capped to the SIDSIZE. - */ - if (smmu->sid_bits < STRTAB_SPLIT) { - size = 0; - } else { - size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3); - size = min(size, smmu->sid_bits - STRTAB_SPLIT); - } + /* Calculate the L1 size, capped to the SIDSIZE. */ + size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3); + size = min(size, smmu->sid_bits - STRTAB_SPLIT); cfg->num_l1_ents = 1 << size; size += STRTAB_SPLIT; @@ -2498,6 +2490,13 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) smmu->ssid_bits = reg >> IDR1_SSID_SHIFT & IDR1_SSID_MASK; smmu->sid_bits = reg >> IDR1_SID_SHIFT & IDR1_SID_MASK; + /* + * If the SMMU supports fewer bits than would fill a single L2 stream + * table, use a linear table instead. + */ + if (smmu->sid_bits <= STRTAB_SPLIT) + smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB; + /* IDR5 */ reg = readl_relaxed(smmu->base + ARM_SMMU_IDR5); -- cgit v1.1 From 65e251a4634c5644efca6f7e15803f0962d8943d Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Tue, 3 Jan 2017 17:34:56 +0000 Subject: iommu: Drop the of_iommu_{set/get}_ops() interface With the introduction of the new iommu_{register/get}_instance() interface in commit e4f10ffe4c9b ("iommu: Make of_iommu_set/get_ops() DT agnostic") (based on struct fwnode_handle as look-up token, so firmware agnostic) to register IOMMU instances with the core IOMMU layer there is no reason to keep the old OF based interface around any longer. Convert all the IOMMU drivers (and OF IOMMU core code) that rely on the of_iommu_{set/get}_ops() to the new kernel interface to register/retrieve IOMMU instances and remove the of_iommu_{set/get}_ops() remaining glue code in order to complete the interface rework. Cc: Matthias Brugger Cc: Will Deacon Cc: Joerg Roedel Cc: Marek Szyprowski Reviewed-by: Robin Murphy Tested-by: Sricharan R Tested-by: Yong Wu Signed-off-by: Lorenzo Pieralisi Signed-off-by: Will Deacon --- drivers/iommu/exynos-iommu.c | 2 +- drivers/iommu/msm_iommu.c | 2 +- drivers/iommu/mtk_iommu.c | 2 +- drivers/iommu/of_iommu.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 57ba0d3..b79e4c4 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -628,7 +628,7 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev) pm_runtime_enable(dev); - of_iommu_set_ops(dev->of_node, &exynos_iommu_ops); + iommu_register_instance(dev->fwnode, &exynos_iommu_ops); return 0; } diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c index b09692b..9cd3cee 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -737,7 +737,7 @@ static int msm_iommu_probe(struct platform_device *pdev) } list_add(&iommu->dev_node, &qcom_iommu_devices); - of_iommu_set_ops(pdev->dev.of_node, &msm_iommu_ops); + iommu_register_instance(pdev->dev.fwnode, &msm_iommu_ops); pr_info("device mapped at %p, irq %d with %d ctx banks\n", iommu->base, iommu->irq, iommu->ncb); diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c index 1479c76..0596ab2 100644 --- a/drivers/iommu/mtk_iommu.c +++ b/drivers/iommu/mtk_iommu.c @@ -655,7 +655,7 @@ static int mtk_iommu_init_fn(struct device_node *np) return ret; } - of_iommu_set_ops(np, &mtk_iommu_ops); + iommu_register_instance(&np->fwnode, &mtk_iommu_ops); return 0; } diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 0f57ddc..d7f480a 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -127,7 +127,7 @@ static const struct iommu_ops "iommu-map-mask", &iommu_spec.np, iommu_spec.args)) return NULL; - ops = of_iommu_get_ops(iommu_spec.np); + ops = iommu_get_instance(&iommu_spec.np->fwnode); if (!ops || !ops->of_xlate || iommu_fwspec_init(&pdev->dev, &iommu_spec.np->fwnode, ops) || ops->of_xlate(&pdev->dev, &iommu_spec)) @@ -157,7 +157,7 @@ const struct iommu_ops *of_iommu_configure(struct device *dev, "#iommu-cells", idx, &iommu_spec)) { np = iommu_spec.np; - ops = of_iommu_get_ops(np); + ops = iommu_get_instance(&np->fwnode); if (!ops || !ops->of_xlate || iommu_fwspec_init(dev, &np->fwnode, ops) || -- cgit v1.1 From dc0eaa4e19a7db1cb046b3979945536fdc914517 Mon Sep 17 00:00:00 2001 From: Aleksey Makarov Date: Thu, 19 Jan 2017 17:36:36 +0300 Subject: iommu/arm-smmu: Support for Extended Stream ID (16 bit) It is the time we have the real 16-bit Stream ID user, which is the ThunderX. Its IO topology uses 1:1 map for Requester ID to Stream ID translation for each root complex which allows to get full 16-bit Stream ID. Firmware assigns bus IDs that are greater than 128 (0x80) to some buses under PEM (external PCIe interface). Eventually SMMU drops devices on that buses because their Stream ID is out of range: pci 0006:90:00.0: stream ID 0x9000 out of range for SMMU (0x7fff) To fix above issue enable the Extended Stream ID optional feature when available. Reviewed-by: Tomasz Nowicki Signed-off-by: Aleksey Makarov Tested-by: Tomasz Nowicki Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 69 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a60cded..93b9177 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -24,6 +24,7 @@ * - v7/v8 long-descriptor format * - Non-secure access to the SMMU * - Context fault reporting + * - Extended Stream ID (16 bit) */ #define pr_fmt(fmt) "arm-smmu: " fmt @@ -87,6 +88,7 @@ #define sCR0_CLIENTPD (1 << 0) #define sCR0_GFRE (1 << 1) #define sCR0_GFIE (1 << 2) +#define sCR0_EXIDENABLE (1 << 3) #define sCR0_GCFGFRE (1 << 4) #define sCR0_GCFGFIE (1 << 5) #define sCR0_USFCFG (1 << 10) @@ -126,6 +128,7 @@ #define ID0_NUMIRPT_MASK 0xff #define ID0_NUMSIDB_SHIFT 9 #define ID0_NUMSIDB_MASK 0xf +#define ID0_EXIDS (1 << 8) #define ID0_NUMSMRG_SHIFT 0 #define ID0_NUMSMRG_MASK 0xff @@ -169,6 +172,7 @@ #define ARM_SMMU_GR0_S2CR(n) (0xc00 + ((n) << 2)) #define S2CR_CBNDX_SHIFT 0 #define S2CR_CBNDX_MASK 0xff +#define S2CR_EXIDVALID (1 << 10) #define S2CR_TYPE_SHIFT 16 #define S2CR_TYPE_MASK 0x3 enum arm_smmu_s2cr_type { @@ -351,6 +355,7 @@ struct arm_smmu_device { #define ARM_SMMU_FEAT_FMT_AARCH64_64K (1 << 9) #define ARM_SMMU_FEAT_FMT_AARCH32_L (1 << 10) #define ARM_SMMU_FEAT_FMT_AARCH32_S (1 << 11) +#define ARM_SMMU_FEAT_EXIDS (1 << 12) u32 features; #define ARM_SMMU_OPT_SECURE_CFG_ACCESS (1 << 0) @@ -1048,7 +1053,7 @@ static void arm_smmu_write_smr(struct arm_smmu_device *smmu, int idx) struct arm_smmu_smr *smr = smmu->smrs + idx; u32 reg = smr->id << SMR_ID_SHIFT | smr->mask << SMR_MASK_SHIFT; - if (smr->valid) + if (!(smmu->features & ARM_SMMU_FEAT_EXIDS) && smr->valid) reg |= SMR_VALID; writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_SMR(idx)); } @@ -1060,6 +1065,9 @@ static void arm_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx) (s2cr->cbndx & S2CR_CBNDX_MASK) << S2CR_CBNDX_SHIFT | (s2cr->privcfg & S2CR_PRIVCFG_MASK) << S2CR_PRIVCFG_SHIFT; + if (smmu->features & ARM_SMMU_FEAT_EXIDS && smmu->smrs && + smmu->smrs[idx].valid) + reg |= S2CR_EXIDVALID; writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_S2CR(idx)); } @@ -1070,6 +1078,34 @@ static void arm_smmu_write_sme(struct arm_smmu_device *smmu, int idx) arm_smmu_write_smr(smmu, idx); } +/* + * The width of SMR's mask field depends on sCR0_EXIDENABLE, so this function + * should be called after sCR0 is written. + */ +static void arm_smmu_test_smr_masks(struct arm_smmu_device *smmu) +{ + void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + u32 smr; + + if (!smmu->smrs) + return; + + /* + * SMR.ID bits may not be preserved if the corresponding MASK + * bits are set, so check each one separately. We can reject + * masters later if they try to claim IDs outside these masks. + */ + smr = smmu->streamid_mask << SMR_ID_SHIFT; + writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); + smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); + smmu->streamid_mask = smr >> SMR_ID_SHIFT; + + smr = smmu->streamid_mask << SMR_MASK_SHIFT; + writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); + smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); + smmu->smr_mask_mask = smr >> SMR_MASK_SHIFT; +} + static int arm_smmu_find_sme(struct arm_smmu_device *smmu, u16 id, u16 mask) { struct arm_smmu_smr *smrs = smmu->smrs; @@ -1648,6 +1684,9 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) if (smmu->features & ARM_SMMU_FEAT_VMID16) reg |= sCR0_VMID16EN; + if (smmu->features & ARM_SMMU_FEAT_EXIDS) + reg |= sCR0_EXIDENABLE; + /* Push the button */ __arm_smmu_tlb_sync(smmu); writel(reg, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0); @@ -1735,11 +1774,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) "\t(IDR0.CTTW overridden by FW configuration)\n"); /* Max. number of entries we have for stream matching/indexing */ - size = 1 << ((id >> ID0_NUMSIDB_SHIFT) & ID0_NUMSIDB_MASK); + if (smmu->version == ARM_SMMU_V2 && id & ID0_EXIDS) { + smmu->features |= ARM_SMMU_FEAT_EXIDS; + size = 1 << 16; + } else { + size = 1 << ((id >> ID0_NUMSIDB_SHIFT) & ID0_NUMSIDB_MASK); + } smmu->streamid_mask = size - 1; if (id & ID0_SMS) { - u32 smr; - smmu->features |= ARM_SMMU_FEAT_STREAM_MATCH; size = (id >> ID0_NUMSMRG_SHIFT) & ID0_NUMSMRG_MASK; if (size == 0) { @@ -1748,21 +1790,6 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENODEV; } - /* - * SMR.ID bits may not be preserved if the corresponding MASK - * bits are set, so check each one separately. We can reject - * masters later if they try to claim IDs outside these masks. - */ - smr = smmu->streamid_mask << SMR_ID_SHIFT; - writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); - smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); - smmu->streamid_mask = smr >> SMR_ID_SHIFT; - - smr = smmu->streamid_mask << SMR_MASK_SHIFT; - writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); - smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); - smmu->smr_mask_mask = smr >> SMR_MASK_SHIFT; - /* Zero-initialised to mark as invalid */ smmu->smrs = devm_kcalloc(smmu->dev, size, sizeof(*smmu->smrs), GFP_KERNEL); @@ -1770,8 +1797,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENOMEM; dev_notice(smmu->dev, - "\tstream matching with %lu register groups, mask 0x%x", - size, smmu->smr_mask_mask); + "\tstream matching with %lu register groups", size); } /* s2cr->type == 0 means translation, so initialise explicitly */ smmu->s2crs = devm_kmalloc_array(smmu->dev, size, sizeof(*smmu->s2crs), @@ -2094,6 +2120,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) iommu_register_instance(dev->fwnode, &arm_smmu_ops); platform_set_drvdata(pdev, smmu); arm_smmu_device_reset(smmu); + arm_smmu_test_smr_masks(smmu); /* Oh, for a proper bus abstraction */ if (!iommu_present(&platform_bus_type)) -- cgit v1.1 From 3677a649a751c8f061ba379a98c369473ccac980 Mon Sep 17 00:00:00 2001 From: Tomasz Nowicki Date: Mon, 16 Jan 2017 08:16:07 +0100 Subject: iommu/arm-smmu: Fix for ThunderX erratum #27704 The goal of erratum #27704 workaround was to make sure that ASIDs and VMIDs are unique across all SMMU instances on affected Cavium systems. Currently, the workaround code partitions ASIDs and VMIDs by increasing global cavium_smmu_context_count which in turn becomes the base ASID and VMID value for the given SMMU instance upon the context bank initialization. For systems with multiple SMMU instances this approach implies the risk of crossing 8-bit ASID, like for 1-socket CN88xx capable of 4 SMMUv2, 128 context banks each: SMMU_0 (0-127 ASID RANGE) SMMU_1 (127-255 ASID RANGE) SMMU_2 (256-383 ASID RANGE) <--- crossing 8-bit ASID SMMU_3 (384-511 ASID RANGE) <--- crossing 8-bit ASID Since now we use 8-bit ASID (SMMU_CBn_TCR2.AS = 0) we effectively misconfigure ASID[15:8] bits of SMMU_CBn_TTBRm register for SMMU_2/3. Moreover, we still assume non-zero ASID[15:8] bits upon context invalidation. In the end, except SMMU_0/1 devices all other devices under other SMMUs will fail on guest power off/on. Since we try to invalidate TLB with 16-bit ASID but we actually have 8-bit zero padded 16-bit entry. This patch adds 16-bit ASID support for stage-1 AArch64 contexts so that we use ASIDs consistently for all SMMU instances. Signed-off-by: Tomasz Nowicki Reviewed-by: Robin Murphy Reviewed-by: Tirumalesh Chalamarla Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 93b9177..d01802e 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -264,6 +264,7 @@ enum arm_smmu_s2cr_privcfg { #define TTBCR2_SEP_SHIFT 15 #define TTBCR2_SEP_UPSTREAM (0x7 << TTBCR2_SEP_SHIFT) +#define TTBCR2_AS (1 << 4) #define TTBRn_ASID_SHIFT 48 @@ -783,6 +784,8 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, reg = pgtbl_cfg->arm_lpae_s1_cfg.tcr; reg2 = pgtbl_cfg->arm_lpae_s1_cfg.tcr >> 32; reg2 |= TTBCR2_SEP_UPSTREAM; + if (cfg->fmt == ARM_SMMU_CTX_FMT_AARCH64) + reg2 |= TTBCR2_AS; } if (smmu->version > ARM_SMMU_V1) writel_relaxed(reg2, cb_base + ARM_SMMU_CB_TTBCR2); -- cgit v1.1 From f51d7bb79c1124f7f02e9f472ef935eba13bca8e Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Mon, 16 Jan 2017 13:24:54 +0000 Subject: iommu/dma: Stop getting dma_32bit_pfn wrong iommu_dma_init_domain() was originally written under the misconception that dma_32bit_pfn represented some sort of size limit for IOVA domains. Since the truth is almost the exact opposite of that, rework the logic and comments to reflect its real purpose of optimising lookups when allocating from a subset of the available 64-bit space. Signed-off-by: Robin Murphy Signed-off-by: Joerg Roedel --- drivers/iommu/dma-iommu.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 7195d46..6c6e70c 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -203,6 +203,7 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, struct iommu_dma_cookie *cookie = domain->iova_cookie; struct iova_domain *iovad = &cookie->iovad; unsigned long order, base_pfn, end_pfn; + bool pci = dev && dev_is_pci(dev); if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE) return -EINVAL; @@ -225,19 +226,31 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, end_pfn = min_t(unsigned long, end_pfn, domain->geometry.aperture_end >> order); } + /* + * PCI devices may have larger DMA masks, but still prefer allocating + * within a 32-bit mask to avoid DAC addressing. Such limitations don't + * apply to the typical platform device, so for those we may as well + * leave the cache limit at the top of their range to save an rb_last() + * traversal on every allocation. + */ + if (pci) + end_pfn &= DMA_BIT_MASK(32) >> order; - /* All we can safely do with an existing domain is enlarge it */ + /* start_pfn is always nonzero for an already-initialised domain */ if (iovad->start_pfn) { if (1UL << order != iovad->granule || - base_pfn != iovad->start_pfn || - end_pfn < iovad->dma_32bit_pfn) { + base_pfn != iovad->start_pfn) { pr_warn("Incompatible range for DMA domain\n"); return -EFAULT; } - iovad->dma_32bit_pfn = end_pfn; + /* + * If we have devices with different DMA masks, move the free + * area cache limit down for the benefit of the smaller one. + */ + iovad->dma_32bit_pfn = min(end_pfn, iovad->dma_32bit_pfn); } else { init_iova_domain(iovad, 1UL << order, base_pfn, end_pfn); - if (dev && dev_is_pci(dev)) + if (pci) iova_reserve_pci_windows(to_pci_dev(dev), iovad); } return 0; -- cgit v1.1 From 122fac030e912ed723fe94d8eb0d5d0f6b31535e Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Mon, 16 Jan 2017 13:24:55 +0000 Subject: iommu/dma: Implement PCI allocation optimisation Whilst PCI devices may have 64-bit DMA masks, they still benefit from using 32-bit addresses wherever possible in order to avoid DAC (PCI) or longer address packets (PCIe), which may incur a performance overhead. Implement the same optimisation as other allocators by trying to get a 32-bit address first, only falling back to the full mask if that fails. Signed-off-by: Robin Murphy Signed-off-by: Joerg Roedel --- drivers/iommu/dma-iommu.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 6c6e70c..1c9ac26 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -287,19 +287,28 @@ int dma_info_to_prot(enum dma_data_direction dir, bool coherent, } static struct iova *__alloc_iova(struct iommu_domain *domain, size_t size, - dma_addr_t dma_limit) + dma_addr_t dma_limit, struct device *dev) { struct iova_domain *iovad = cookie_iovad(domain); unsigned long shift = iova_shift(iovad); unsigned long length = iova_align(iovad, size) >> shift; + struct iova *iova = NULL; if (domain->geometry.force_aperture) dma_limit = min(dma_limit, domain->geometry.aperture_end); + + /* Try to get PCI devices a SAC address */ + if (dma_limit > DMA_BIT_MASK(32) && dev_is_pci(dev)) + iova = alloc_iova(iovad, length, DMA_BIT_MASK(32) >> shift, + true); /* * Enforce size-alignment to be safe - there could perhaps be an * attribute to control this per-device, or at least per-domain... */ - return alloc_iova(iovad, length, dma_limit >> shift, true); + if (!iova) + iova = alloc_iova(iovad, length, dma_limit >> shift, true); + + return iova; } /* The IOVA allocator knows what we mapped, so just unmap whatever that was */ @@ -452,7 +461,7 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, if (!pages) return NULL; - iova = __alloc_iova(domain, size, dev->coherent_dma_mask); + iova = __alloc_iova(domain, size, dev->coherent_dma_mask, dev); if (!iova) goto out_free_pages; @@ -523,7 +532,7 @@ static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys, struct iova_domain *iovad = cookie_iovad(domain); size_t iova_off = iova_offset(iovad, phys); size_t len = iova_align(iovad, size + iova_off); - struct iova *iova = __alloc_iova(domain, len, dma_get_mask(dev)); + struct iova *iova = __alloc_iova(domain, len, dma_get_mask(dev), dev); if (!iova) return DMA_ERROR_CODE; @@ -681,7 +690,7 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, prev = s; } - iova = __alloc_iova(domain, iova_len, dma_get_mask(dev)); + iova = __alloc_iova(domain, iova_len, dma_get_mask(dev), dev); if (!iova) goto out_restore_sg; @@ -761,7 +770,7 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev, msi_page->phys = msi_addr; if (iovad) { - iova = __alloc_iova(domain, size, dma_get_mask(dev)); + iova = __alloc_iova(domain, size, dma_get_mask(dev), dev); if (!iova) goto out_free_page; msi_page->iova = iova_dma_addr(iovad, iova); -- cgit v1.1 From 21e722c4c8377b5bc82ad058fed12165af739c1b Mon Sep 17 00:00:00 2001 From: Ashok Raj Date: Mon, 30 Jan 2017 09:39:53 -0800 Subject: iommu/vt-d: Tylersburg isoch identity map check is done too late. The check to set identity map for tylersburg is done too late. It needs to be done before the check for identity_map domain is done. To: Joerg Roedel To: David Woodhouse Cc: iommu@lists.linux-foundation.org Cc: linux-kernel@vger.kernel.org Cc: stable@vger.kernel.org Cc: Ashok Raj Fixes: 86080ccc22 ("iommu/vt-d: Allocate si_domain in init_dmars()") Signed-off-by: Ashok Raj Reported-by: Yunhong Jiang Signed-off-by: Joerg Roedel --- drivers/iommu/intel-iommu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 8a18525..23eead3 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -3325,13 +3325,14 @@ static int __init init_dmars(void) iommu_identity_mapping |= IDENTMAP_GFX; #endif + check_tylersburg_isoch(); + if (iommu_identity_mapping) { ret = si_domain_init(hw_pass_through); if (ret) goto free_iommu; } - check_tylersburg_isoch(); /* * If we copied translations from a previous kernel in the kdump -- cgit v1.1 From f7116e115acdd74bc75a4daf6492b11d43505125 Mon Sep 17 00:00:00 2001 From: David Dillow Date: Mon, 30 Jan 2017 19:11:11 -0800 Subject: iommu/vt-d: Don't over-free page table directories dma_pte_free_level() recurses down the IOMMU page tables and frees directory pages that are entirely contained in the given PFN range. Unfortunately, it incorrectly calculates the starting address covered by the PTE under consideration, which can lead to it clearing an entry that is still in use. This occurs if we have a scatterlist with an entry that has a length greater than 1026 MB and is aligned to 2 MB for both the IOMMU and physical addresses. For example, if __domain_mapping() is asked to map a two-entry scatterlist with 2 MB and 1028 MB segments to PFN 0xffff80000, it will ask if dma_pte_free_pagetable() is asked to PFNs from 0xffff80200 to 0xffffc05ff, it will also incorrectly clear the PFNs from 0xffff80000 to 0xffff801ff because of this issue. The current code will set level_pfn to 0xffff80200, and 0xffff80200-0xffffc01ff fits inside the range being cleared. Properly setting the level_pfn for the current level under consideration catches that this PTE is outside of the range being cleared. This patch also changes the value passed into dma_pte_free_level() when it recurses. This only affects the first PTE of the range being cleared, and is handled by the existing code that ensures we start our cursor no lower than start_pfn. This was found when using dma_map_sg() to map large chunks of contiguous memory, which immediatedly led to faults on the first access of the erroneously-deleted mappings. Fixes: 3269ee0bd668 ("intel-iommu: Fix leaks in pagetable freeing") Reviewed-by: Benjamin Serebrin Signed-off-by: David Dillow Signed-off-by: Joerg Roedel --- drivers/iommu/intel-iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 23eead3..5d179c8 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -1144,7 +1144,7 @@ static void dma_pte_free_level(struct dmar_domain *domain, int level, if (!dma_pte_present(pte) || dma_pte_superpage(pte)) goto next; - level_pfn = pfn & level_mask(level - 1); + level_pfn = pfn & level_mask(level); level_pte = phys_to_virt(dma_pte_addr(pte)); if (level > 2) -- cgit v1.1 From 3b6bb5b705a4051c9899f5e3100c117c261d2742 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 31 Jan 2017 12:17:07 +0100 Subject: iommu/ipmmu-vmsa: Restrict IOMMU Domain Geometry to 32-bit address space Currently, the IPMMU/VMSA driver supports 32-bit I/O Virtual Addresses only, and thus sets io_pgtable_cfg.ias = 32. However, it doesn't force a 32-bit IOVA space through the IOMMU Domain Geometry. Hence if a device (e.g. SYS-DMAC) rightfully configures a 40-bit DMA mask, it will still be handed out a 40-bit IOVA, outside the 32-bit IOVA space, leading to out-of-bounds accesses of the PGD when mapping the IOVA. Force a 32-bit IOMMU Domain Geometry to fix this. Signed-off-by: Geert Uytterhoeven Reviewed-by: Robin Murphy Reviewed-by: Laurent Pinchart Signed-off-by: Joerg Roedel --- drivers/iommu/ipmmu-vmsa.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c index ace331d..b7e14ee 100644 --- a/drivers/iommu/ipmmu-vmsa.c +++ b/drivers/iommu/ipmmu-vmsa.c @@ -313,6 +313,8 @@ static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain) domain->cfg.ias = 32; domain->cfg.oas = 40; domain->cfg.tlb = &ipmmu_gather_ops; + domain->io_domain.geometry.aperture_end = DMA_BIT_MASK(32); + domain->io_domain.geometry.force_aperture = true; /* * TODO: Add support for coherent walk through CCI with DVM and remove * cache handling. For now, delegate it to the io-pgtable code. -- cgit v1.1 From a1831bb9403720db6d4c033fe2d6bd0116dd28fe Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Wed, 1 Feb 2017 17:53:04 +0000 Subject: iommu/dma: Remove bogus dma_supported() implementation Back when this was first written, dma_supported() was somewhat of a murky mess, with subtly different interpretations being relied upon in various places. The "does device X support DMA to address range Y?" uses assuming Y to be physical addresses, which motivated the current iommu_dma_supported() implementation and are alluded to in the comment therein, have since been cleaned up, leaving only the far less ambiguous "can device X drive address bits Y" usage internal to DMA API mask setting. As such, there is no reason to keep a slightly misleading callback which does nothing but duplicate the current default behaviour; we already constrain IOVA allocations to the iommu_domain aperture where necessary, so let's leave DMA mask business to architecture-specific code where it belongs. Signed-off-by: Robin Murphy Signed-off-by: Joerg Roedel --- drivers/iommu/dma-iommu.c | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 1c9ac26..48d36ce 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -734,16 +734,6 @@ void iommu_dma_unmap_resource(struct device *dev, dma_addr_t handle, __iommu_dma_unmap(iommu_get_domain_for_dev(dev), handle); } -int iommu_dma_supported(struct device *dev, u64 mask) -{ - /* - * 'Special' IOMMUs which don't have the same addressing capability - * as the CPU will have to wait until we have some way to query that - * before they'll be able to use this framework. - */ - return 1; -} - int iommu_dma_mapping_error(struct device *dev, dma_addr_t dma_addr) { return dma_addr == DMA_ERROR_CODE; -- cgit v1.1 From 087a908f533f588d39c95e3d5f68545f132baf10 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 2 Feb 2017 15:29:03 +0100 Subject: iommu/mediatek: Remove bogus 'select' statements The mediatek IOMMU driver enables some drivers that it does not directly rely on, and that causes a warning for build testing: warning: (MTK_IOMMU_V1) selects COMMON_CLK_MT2701_VDECSYS which has unmet direct dependencies (COMMON_CLK && COMMON_CLK_MT2701) warning: (MTK_IOMMU_V1) selects COMMON_CLK_MT2701_IMGSYS which has unmet direct dependencies (COMMON_CLK && COMMON_CLK_MT2701) warning: (MTK_IOMMU_V1) selects COMMON_CLK_MT2701_MMSYS which has unmet direct dependencies (COMMON_CLK && COMMON_CLK_MT2701) This removes the select statements. Signed-off-by: Arnd Bergmann Signed-off-by: Joerg Roedel --- drivers/iommu/Kconfig | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 8ee54d7..37e204f 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -352,9 +352,6 @@ config MTK_IOMMU_V1 select IOMMU_API select MEMORY select MTK_SMI - select COMMON_CLK_MT2701_MMSYS - select COMMON_CLK_MT2701_IMGSYS - select COMMON_CLK_MT2701_VDECSYS help Support for the M4U on certain Mediatek SoCs. M4U generation 1 HW is Multimedia Memory Managememt Unit. This option enables remapping of -- cgit v1.1 From 909111ba0ba6aa228d9b6e9cf01dd68a46586cb4 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Fri, 3 Feb 2017 17:35:02 +0800 Subject: iommu: Avoid unnecessary assignment of dev->iommu_fwspec Move the assignment statement into if branch above, where it only needs to be. Signed-off-by: Zhen Lei Reviewed-by: Robin Murphy Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index f4a176e..428455a 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1869,13 +1869,14 @@ int iommu_fwspec_add_ids(struct device *dev, u32 *ids, int num_ids) fwspec = krealloc(dev->iommu_fwspec, size, GFP_KERNEL); if (!fwspec) return -ENOMEM; + + dev->iommu_fwspec = fwspec; } for (i = 0; i < num_ids; i++) fwspec->ids[fwspec->num_ids + i] = ids[i]; fwspec->num_ids += num_ids; - dev->iommu_fwspec = fwspec; return 0; } EXPORT_SYMBOL_GPL(iommu_fwspec_add_ids); -- cgit v1.1 From a514a6e241f051dd8a4a00a456382dec0b1af21b Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Mon, 6 Feb 2017 10:11:38 +0100 Subject: iommu: Fix static checker warning in iommu_insert_device_resv_regions In case the device reserved region list is void, the returned value of iommu_insert_device_resv_regions is uninitialized. Let's return 0 in that case. This fixes commit 6c65fb318e8b ("iommu: iommu_get_group_resv_regions"). Reported-by: Dan Carpenter Signed-off-by: Eric Auger Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 428455a..c37d701 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -205,7 +205,7 @@ iommu_insert_device_resv_regions(struct list_head *dev_resv_regions, struct list_head *group_resv_regions) { struct iommu_resv_region *entry; - int ret; + int ret = 0; list_for_each_entry(entry, dev_resv_regions, list) { ret = iommu_insert_resv_region(entry, group_resv_regions); -- cgit v1.1 From 534766dfef999f7e7349bbd91cd19c1673792af3 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Tue, 31 Jan 2017 16:58:42 +0100 Subject: iommu: Rename iommu_get_instance() Rename the function to iommu_ops_from_fwnode(), because that is what the function actually does. The new name is much more descriptive about what the function does. Signed-off-by: Joerg Roedel --- drivers/acpi/arm64/iort.c | 2 +- drivers/iommu/iommu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c index e0d2e6e..3752521 100644 --- a/drivers/acpi/arm64/iort.c +++ b/drivers/acpi/arm64/iort.c @@ -536,7 +536,7 @@ static const struct iommu_ops *iort_iommu_xlate(struct device *dev, if (!iort_fwnode) return NULL; - ops = iommu_get_instance(iort_fwnode); + ops = iommu_ops_from_fwnode(iort_fwnode); if (!ops) return NULL; diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index aed906a..2bb61e8 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1664,7 +1664,7 @@ void iommu_register_instance(struct fwnode_handle *fwnode, spin_unlock(&iommu_instance_lock); } -const struct iommu_ops *iommu_get_instance(struct fwnode_handle *fwnode) +const struct iommu_ops *iommu_ops_from_fwnode(struct fwnode_handle *fwnode) { struct iommu_instance *instance; const struct iommu_ops *ops = NULL; -- cgit v1.1 From c09e22d5370739e16463c113525df51b5980b1d5 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 1 Feb 2017 12:19:46 +0100 Subject: iommu: Rename struct iommu_device The struct is used to link devices to iommu-groups, so 'struct group_device' is a better name. Further this makes the name iommu_device available for a struct representing hardware iommus. Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 2bb61e8..cc569b1 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -55,7 +55,7 @@ struct iommu_group { struct iommu_domain *domain; }; -struct iommu_device { +struct group_device { struct list_head list; struct device *dev; char *name; @@ -374,7 +374,7 @@ out: int iommu_group_add_device(struct iommu_group *group, struct device *dev) { int ret, i = 0; - struct iommu_device *device; + struct group_device *device; device = kzalloc(sizeof(*device), GFP_KERNEL); if (!device) @@ -460,7 +460,7 @@ EXPORT_SYMBOL_GPL(iommu_group_add_device); void iommu_group_remove_device(struct device *dev) { struct iommu_group *group = dev->iommu_group; - struct iommu_device *tmp_device, *device = NULL; + struct group_device *tmp_device, *device = NULL; pr_info("Removing device %s from group %d\n", dev_name(dev), group->id); @@ -495,7 +495,7 @@ EXPORT_SYMBOL_GPL(iommu_group_remove_device); static int iommu_group_device_count(struct iommu_group *group) { - struct iommu_device *entry; + struct group_device *entry; int ret = 0; list_for_each_entry(entry, &group->devices, list) @@ -518,7 +518,7 @@ static int iommu_group_device_count(struct iommu_group *group) static int __iommu_group_for_each_dev(struct iommu_group *group, void *data, int (*fn)(struct device *, void *)) { - struct iommu_device *device; + struct group_device *device; int ret = 0; list_for_each_entry(device, &group->devices, list) { -- cgit v1.1 From b0119e870837dcd15a207b4701542ebac5d19b45 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 1 Feb 2017 13:23:08 +0100 Subject: iommu: Introduce new 'struct iommu_device' This struct represents one hardware iommu in the iommu core code. For now it only has the iommu-ops associated with it, but that will be extended soon. The register/unregister interface is also added, as well as making use of it in the Intel and AMD IOMMU drivers. Signed-off-by: Joerg Roedel --- drivers/iommu/amd_iommu.c | 4 ++-- drivers/iommu/amd_iommu_init.c | 5 +++++ drivers/iommu/amd_iommu_types.h | 3 +++ drivers/iommu/dmar.c | 9 +++++++++ drivers/iommu/intel-iommu.c | 4 ++-- drivers/iommu/iommu.c | 19 +++++++++++++++++++ 6 files changed, 40 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 019e027..689d88f 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -112,7 +112,7 @@ static struct timer_list queue_timer; * Domain for untranslated devices - only allocated * if iommu=pt passed on kernel cmd line. */ -static const struct iommu_ops amd_iommu_ops; +const struct iommu_ops amd_iommu_ops; static ATOMIC_NOTIFIER_HEAD(ppr_notifier); int amd_iommu_max_glx_val = -1; @@ -3217,7 +3217,7 @@ static void amd_iommu_apply_dm_region(struct device *dev, WARN_ON_ONCE(reserve_iova(&dma_dom->iovad, start, end) == NULL); } -static const struct iommu_ops amd_iommu_ops = { +const struct iommu_ops amd_iommu_ops = { .capable = amd_iommu_capable, .domain_alloc = amd_iommu_domain_alloc, .domain_free = amd_iommu_domain_free, diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index 6799cf9..b7ccfb2 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -94,6 +94,8 @@ * out of it. */ +extern const struct iommu_ops amd_iommu_ops; + /* * structure describing one IOMMU in the ACPI table. Typically followed by one * or more ivhd_entrys. @@ -1639,6 +1641,9 @@ static int iommu_init_pci(struct amd_iommu *iommu) amd_iommu_groups, "ivhd%d", iommu->index); + iommu_device_set_ops(&iommu->iommu, &amd_iommu_ops); + iommu_device_register(&iommu->iommu); + return pci_enable_device(iommu->dev); } diff --git a/drivers/iommu/amd_iommu_types.h b/drivers/iommu/amd_iommu_types.h index 0d91785..0683505 100644 --- a/drivers/iommu/amd_iommu_types.h +++ b/drivers/iommu/amd_iommu_types.h @@ -538,6 +538,9 @@ struct amd_iommu { /* IOMMU sysfs device */ struct device *iommu_dev; + /* Handle for IOMMU core code */ + struct iommu_device iommu; + /* * We can't rely on the BIOS to restore all values on reinit, so we * need to stash them diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c index a88576d..83fee0e 100644 --- a/drivers/iommu/dmar.c +++ b/drivers/iommu/dmar.c @@ -74,6 +74,8 @@ static unsigned long dmar_seq_ids[BITS_TO_LONGS(DMAR_UNITS_SUPPORTED)]; static int alloc_iommu(struct dmar_drhd_unit *drhd); static void free_iommu(struct intel_iommu *iommu); +extern const struct iommu_ops intel_iommu_ops; + static void dmar_register_drhd_unit(struct dmar_drhd_unit *drhd) { /* @@ -1084,6 +1086,12 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd) err = PTR_ERR(iommu->iommu_dev); goto err_unmap; } + + iommu_device_set_ops(&iommu->iommu, &intel_iommu_ops); + + err = iommu_device_register(&iommu->iommu); + if (err) + goto err_unmap; } drhd->iommu = iommu; @@ -1102,6 +1110,7 @@ error: static void free_iommu(struct intel_iommu *iommu) { iommu_device_destroy(iommu->iommu_dev); + iommu_device_unregister(&iommu->iommu); if (iommu->irq) { if (iommu->pr_irq) { diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index c66c273..e6e8f5b 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -547,7 +547,7 @@ EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); static DEFINE_SPINLOCK(device_domain_lock); static LIST_HEAD(device_domain_list); -static const struct iommu_ops intel_iommu_ops; +const struct iommu_ops intel_iommu_ops; static bool translation_pre_enabled(struct intel_iommu *iommu) { @@ -5292,7 +5292,7 @@ struct intel_iommu *intel_svm_device_to_iommu(struct device *dev) } #endif /* CONFIG_INTEL_IOMMU_SVM */ -static const struct iommu_ops intel_iommu_ops = { +const struct iommu_ops intel_iommu_ops = { .capable = intel_iommu_capable, .domain_alloc = intel_iommu_domain_alloc, .domain_free = intel_iommu_domain_free, diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index cc569b1..1dfd70e 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -77,6 +77,25 @@ struct iommu_group_attribute iommu_group_attr_##_name = \ #define to_iommu_group(_kobj) \ container_of(_kobj, struct iommu_group, kobj) +static LIST_HEAD(iommu_device_list); +static DEFINE_SPINLOCK(iommu_device_lock); + +int iommu_device_register(struct iommu_device *iommu) +{ + spin_lock(&iommu_device_lock); + list_add_tail(&iommu->list, &iommu_device_list); + spin_unlock(&iommu_device_lock); + + return 0; +} + +void iommu_device_unregister(struct iommu_device *iommu) +{ + spin_lock(&iommu_device_lock); + list_del(&iommu->list); + spin_unlock(&iommu_device_lock); +} + static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus, unsigned type); static int __iommu_attach_device(struct iommu_domain *domain, -- cgit v1.1 From 39ab9555c24110671f8dc671311a26e5c985b592 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 1 Feb 2017 16:56:46 +0100 Subject: iommu: Add sysfs bindings for struct iommu_device There is currently support for iommu sysfs bindings, but those need to be implemented in the IOMMU drivers. Add a more generic version of this by adding a struct device to struct iommu_device and use that for the sysfs bindings. Also convert the AMD and Intel IOMMU driver to make use of it. Signed-off-by: Joerg Roedel --- drivers/iommu/amd_iommu.c | 14 ++++++++----- drivers/iommu/amd_iommu_init.c | 6 ++---- drivers/iommu/amd_iommu_types.h | 3 --- drivers/iommu/dmar.c | 13 +++++------- drivers/iommu/intel-iommu.c | 15 ++++++++------ drivers/iommu/iommu-sysfs.c | 45 +++++++++++++++++------------------------ 6 files changed, 43 insertions(+), 53 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 689d88f..4fee2fd 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -445,6 +445,7 @@ static void init_iommu_group(struct device *dev) static int iommu_init_device(struct device *dev) { struct iommu_dev_data *dev_data; + struct amd_iommu *iommu; int devid; if (dev->archdata.iommu) @@ -454,6 +455,8 @@ static int iommu_init_device(struct device *dev) if (devid < 0) return devid; + iommu = amd_iommu_rlookup_table[devid]; + dev_data = find_dev_data(devid); if (!dev_data) return -ENOMEM; @@ -469,8 +472,7 @@ static int iommu_init_device(struct device *dev) dev->archdata.iommu = dev_data; - iommu_device_link(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev, - dev); + iommu_device_link(&iommu->iommu.dev, dev); return 0; } @@ -495,13 +497,16 @@ static void iommu_ignore_device(struct device *dev) static void iommu_uninit_device(struct device *dev) { - int devid; struct iommu_dev_data *dev_data; + struct amd_iommu *iommu; + int devid; devid = get_device_id(dev); if (devid < 0) return; + iommu = amd_iommu_rlookup_table[devid]; + dev_data = search_dev_data(devid); if (!dev_data) return; @@ -509,8 +514,7 @@ static void iommu_uninit_device(struct device *dev) if (dev_data->domain) detach_device(dev); - iommu_device_unlink(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev, - dev); + iommu_device_unlink(&iommu->iommu.dev, dev); iommu_group_remove_device(dev); diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index b7ccfb2..6b9e661 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -1637,10 +1637,8 @@ static int iommu_init_pci(struct amd_iommu *iommu) amd_iommu_erratum_746_workaround(iommu); amd_iommu_ats_write_check_workaround(iommu); - iommu->iommu_dev = iommu_device_create(&iommu->dev->dev, iommu, - amd_iommu_groups, "ivhd%d", - iommu->index); - + iommu_device_sysfs_add(&iommu->iommu, &iommu->dev->dev, + amd_iommu_groups, "ivhd%d", iommu->index); iommu_device_set_ops(&iommu->iommu, &amd_iommu_ops); iommu_device_register(&iommu->iommu); diff --git a/drivers/iommu/amd_iommu_types.h b/drivers/iommu/amd_iommu_types.h index 0683505..af00f38 100644 --- a/drivers/iommu/amd_iommu_types.h +++ b/drivers/iommu/amd_iommu_types.h @@ -535,9 +535,6 @@ struct amd_iommu { /* if one, we need to send a completion wait command */ bool need_sync; - /* IOMMU sysfs device */ - struct device *iommu_dev; - /* Handle for IOMMU core code */ struct iommu_device iommu; diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c index 83fee0e..fc13146 100644 --- a/drivers/iommu/dmar.c +++ b/drivers/iommu/dmar.c @@ -1078,14 +1078,11 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd) raw_spin_lock_init(&iommu->register_lock); if (intel_iommu_enabled) { - iommu->iommu_dev = iommu_device_create(NULL, iommu, - intel_iommu_groups, - "%s", iommu->name); - - if (IS_ERR(iommu->iommu_dev)) { - err = PTR_ERR(iommu->iommu_dev); + err = iommu_device_sysfs_add(&iommu->iommu, NULL, + intel_iommu_groups, + "%s", iommu->name); + if (err) goto err_unmap; - } iommu_device_set_ops(&iommu->iommu, &intel_iommu_ops); @@ -1109,7 +1106,7 @@ error: static void free_iommu(struct intel_iommu *iommu) { - iommu_device_destroy(iommu->iommu_dev); + iommu_device_sysfs_remove(&iommu->iommu); iommu_device_unregister(&iommu->iommu); if (iommu->irq) { diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index e6e8f5b..316730c 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -4834,10 +4834,13 @@ int __init intel_iommu_init(void) init_iommu_pm_ops(); - for_each_active_iommu(iommu, drhd) - iommu->iommu_dev = iommu_device_create(NULL, iommu, - intel_iommu_groups, - "%s", iommu->name); + for_each_active_iommu(iommu, drhd) { + iommu_device_sysfs_add(&iommu->iommu, NULL, + intel_iommu_groups, + "%s", iommu->name); + iommu_device_set_ops(&iommu->iommu, &intel_iommu_ops); + iommu_device_register(&iommu->iommu); + } bus_set_iommu(&pci_bus_type, &intel_iommu_ops); bus_register_notifier(&pci_bus_type, &device_nb); @@ -5159,7 +5162,7 @@ static int intel_iommu_add_device(struct device *dev) if (!iommu) return -ENODEV; - iommu_device_link(iommu->iommu_dev, dev); + iommu_device_link(&iommu->iommu.dev, dev); group = iommu_group_get_for_dev(dev); @@ -5181,7 +5184,7 @@ static void intel_iommu_remove_device(struct device *dev) iommu_group_remove_device(dev); - iommu_device_unlink(iommu->iommu_dev, dev); + iommu_device_unlink(&iommu->iommu.dev, dev); } #ifdef CONFIG_INTEL_IOMMU_SVM diff --git a/drivers/iommu/iommu-sysfs.c b/drivers/iommu/iommu-sysfs.c index 39b2d91..bb87d35 100644 --- a/drivers/iommu/iommu-sysfs.c +++ b/drivers/iommu/iommu-sysfs.c @@ -50,54 +50,45 @@ static int __init iommu_dev_init(void) postcore_initcall(iommu_dev_init); /* - * Create an IOMMU device and return a pointer to it. IOMMU specific - * attributes can be provided as an attribute group, allowing a unique - * namespace per IOMMU type. + * Init the struct device for the IOMMU. IOMMU specific attributes can + * be provided as an attribute group, allowing a unique namespace per + * IOMMU type. */ -struct device *iommu_device_create(struct device *parent, void *drvdata, - const struct attribute_group **groups, - const char *fmt, ...) +int iommu_device_sysfs_add(struct iommu_device *iommu, + struct device *parent, + const struct attribute_group **groups, + const char *fmt, ...) { - struct device *dev; va_list vargs; int ret; - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return ERR_PTR(-ENOMEM); + device_initialize(&iommu->dev); - device_initialize(dev); - - dev->class = &iommu_class; - dev->parent = parent; - dev->groups = groups; - dev_set_drvdata(dev, drvdata); + iommu->dev.class = &iommu_class; + iommu->dev.parent = parent; + iommu->dev.groups = groups; va_start(vargs, fmt); - ret = kobject_set_name_vargs(&dev->kobj, fmt, vargs); + ret = kobject_set_name_vargs(&iommu->dev.kobj, fmt, vargs); va_end(vargs); if (ret) goto error; - ret = device_add(dev); + ret = device_add(&iommu->dev); if (ret) goto error; - return dev; + return 0; error: - put_device(dev); - return ERR_PTR(ret); + put_device(&iommu->dev); + return ret; } -void iommu_device_destroy(struct device *dev) +void iommu_device_sysfs_remove(struct iommu_device *iommu) { - if (!dev || IS_ERR(dev)) - return; - - device_unregister(dev); + device_unregister(&iommu->dev); } - /* * IOMMU drivers can indicate a device is managed by a given IOMMU using * this interface. A link to the device will be created in the "devices" -- cgit v1.1 From e3d10af1128b6bc394f21656ff13753130f3c107 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 1 Feb 2017 17:23:22 +0100 Subject: iommu: Make iommu_device_link/unlink take a struct iommu_device This makes the interface more consistent with iommu_device_sysfs_add/remove. Signed-off-by: Joerg Roedel --- drivers/iommu/amd_iommu.c | 4 ++-- drivers/iommu/intel-iommu.c | 4 ++-- drivers/iommu/iommu-sysfs.c | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 4fee2fd..ba708c5 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -472,7 +472,7 @@ static int iommu_init_device(struct device *dev) dev->archdata.iommu = dev_data; - iommu_device_link(&iommu->iommu.dev, dev); + iommu_device_link(&iommu->iommu, dev); return 0; } @@ -514,7 +514,7 @@ static void iommu_uninit_device(struct device *dev) if (dev_data->domain) detach_device(dev); - iommu_device_unlink(&iommu->iommu.dev, dev); + iommu_device_unlink(&iommu->iommu, dev); iommu_group_remove_device(dev); diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 316730c..cbe7c49 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -5162,7 +5162,7 @@ static int intel_iommu_add_device(struct device *dev) if (!iommu) return -ENODEV; - iommu_device_link(&iommu->iommu.dev, dev); + iommu_device_link(&iommu->iommu, dev); group = iommu_group_get_for_dev(dev); @@ -5184,7 +5184,7 @@ static void intel_iommu_remove_device(struct device *dev) iommu_group_remove_device(dev); - iommu_device_unlink(&iommu->iommu.dev, dev); + iommu_device_unlink(&iommu->iommu, dev); } #ifdef CONFIG_INTEL_IOMMU_SVM diff --git a/drivers/iommu/iommu-sysfs.c b/drivers/iommu/iommu-sysfs.c index bb87d35..c58351e 100644 --- a/drivers/iommu/iommu-sysfs.c +++ b/drivers/iommu/iommu-sysfs.c @@ -95,31 +95,31 @@ void iommu_device_sysfs_remove(struct iommu_device *iommu) * directory of the IOMMU device in sysfs and an "iommu" link will be * created under the linked device, pointing back at the IOMMU device. */ -int iommu_device_link(struct device *dev, struct device *link) +int iommu_device_link(struct iommu_device *iommu, struct device *link) { int ret; - if (!dev || IS_ERR(dev)) + if (!iommu || IS_ERR(iommu)) return -ENODEV; - ret = sysfs_add_link_to_group(&dev->kobj, "devices", + ret = sysfs_add_link_to_group(&iommu->dev.kobj, "devices", &link->kobj, dev_name(link)); if (ret) return ret; - ret = sysfs_create_link_nowarn(&link->kobj, &dev->kobj, "iommu"); + ret = sysfs_create_link_nowarn(&link->kobj, &iommu->dev.kobj, "iommu"); if (ret) - sysfs_remove_link_from_group(&dev->kobj, "devices", + sysfs_remove_link_from_group(&iommu->dev.kobj, "devices", dev_name(link)); return ret; } -void iommu_device_unlink(struct device *dev, struct device *link) +void iommu_device_unlink(struct iommu_device *iommu, struct device *link) { - if (!dev || IS_ERR(dev)) + if (!iommu || IS_ERR(iommu)) return; sysfs_remove_link(&link->kobj, "iommu"); - sysfs_remove_link_from_group(&dev->kobj, "devices", dev_name(link)); + sysfs_remove_link_from_group(&iommu->dev.kobj, "devices", dev_name(link)); } -- cgit v1.1 From 9648cbc9625b67c91ed1aaf4b8b77f3f0c537496 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 1 Feb 2017 18:11:36 +0100 Subject: iommu/arm-smmu: Make use of the iommu_register interface Also add the smmu devices to sysfs. Signed-off-by: Joerg Roedel --- drivers/iommu/arm-smmu-v3.c | 22 +++++++++++++++++++++- drivers/iommu/arm-smmu.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 4d6ec44..32133e2 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -616,6 +616,9 @@ struct arm_smmu_device { unsigned int sid_bits; struct arm_smmu_strtab_cfg strtab_cfg; + + /* IOMMU core code handle */ + struct iommu_device iommu; }; /* SMMU private data for each master */ @@ -1795,8 +1798,10 @@ static int arm_smmu_add_device(struct device *dev) } group = iommu_group_get_for_dev(dev); - if (!IS_ERR(group)) + if (!IS_ERR(group)) { iommu_group_put(group); + iommu_device_link(&smmu->iommu, dev); + } return PTR_ERR_OR_ZERO(group); } @@ -1805,14 +1810,17 @@ static void arm_smmu_remove_device(struct device *dev) { struct iommu_fwspec *fwspec = dev->iommu_fwspec; struct arm_smmu_master_data *master; + struct arm_smmu_device *smmu; if (!fwspec || fwspec->ops != &arm_smmu_ops) return; master = fwspec->iommu_priv; + smmu = master->smmu; if (master && master->ste.valid) arm_smmu_detach_dev(dev); iommu_group_remove_device(dev); + iommu_device_unlink(&smmu->iommu, dev); kfree(master); iommu_fwspec_free(dev); } @@ -2613,6 +2621,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) { int irq, ret; struct resource *res; + resource_size_t ioaddr; struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; bool bypass; @@ -2630,6 +2639,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) dev_err(dev, "MMIO region too small (%pr)\n", res); return -EINVAL; } + ioaddr = res->start; smmu->base = devm_ioremap_resource(dev, res); if (IS_ERR(smmu->base)) @@ -2682,6 +2692,16 @@ static int arm_smmu_device_probe(struct platform_device *pdev) return ret; /* And we're up. Go go go! */ + ret = iommu_device_sysfs_add(&smmu->iommu, dev, NULL, + "smmu3.%pa", &ioaddr); + if (ret) + return ret; + + iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops); + iommu_device_set_fwnode(&smmu->iommu, dev->fwnode); + + ret = iommu_device_register(&smmu->iommu); + iommu_register_instance(dev->fwnode, &arm_smmu_ops); #ifdef CONFIG_PCI diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a60cded..f4ce1e7 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -380,6 +380,9 @@ struct arm_smmu_device { unsigned int *irqs; u32 cavium_id_base; /* Specific to Cavium */ + + /* IOMMU core code handle */ + struct iommu_device iommu; }; enum arm_smmu_context_fmt { @@ -1444,6 +1447,8 @@ static int arm_smmu_add_device(struct device *dev) if (ret) goto out_free; + iommu_device_link(&smmu->iommu, dev); + return 0; out_free: @@ -1456,10 +1461,17 @@ out_free: static void arm_smmu_remove_device(struct device *dev) { struct iommu_fwspec *fwspec = dev->iommu_fwspec; + struct arm_smmu_master_cfg *cfg; + struct arm_smmu_device *smmu; + if (!fwspec || fwspec->ops != &arm_smmu_ops) return; + cfg = fwspec->iommu_priv; + smmu = cfg->smmu; + + iommu_device_unlink(&smmu->iommu, dev); arm_smmu_master_free_smes(fwspec); iommu_group_remove_device(dev); kfree(fwspec->iommu_priv); @@ -2011,6 +2023,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev, static int arm_smmu_device_probe(struct platform_device *pdev) { struct resource *res; + resource_size_t ioaddr; struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; int num_irqs, i, err; @@ -2031,6 +2044,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) return err; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ioaddr = res->start; smmu->base = devm_ioremap_resource(dev, res); if (IS_ERR(smmu->base)) return PTR_ERR(smmu->base); @@ -2091,6 +2105,22 @@ static int arm_smmu_device_probe(struct platform_device *pdev) } } + err = iommu_device_sysfs_add(&smmu->iommu, smmu->dev, NULL, + "smmu.%pa", &ioaddr); + if (err) { + dev_err(dev, "Failed to register iommu in sysfs\n"); + return err; + } + + iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops); + iommu_device_set_fwnode(&smmu->iommu, dev->fwnode); + + err = iommu_device_register(&smmu->iommu); + if (err) { + dev_err(dev, "Failed to register iommu\n"); + return err; + } + iommu_register_instance(dev->fwnode, &arm_smmu_ops); platform_set_drvdata(pdev, smmu); arm_smmu_device_reset(smmu); -- cgit v1.1 From 42df43b36163ed7d0ab13992e411093252903273 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 2 Feb 2017 18:52:34 +0100 Subject: iommu/msm: Make use of iommu_device_register interface Register the MSM IOMMUs to the iommu core and add sysfs entries for that driver. Signed-off-by: Joerg Roedel --- drivers/iommu/msm_iommu.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/iommu/msm_iommu.h | 3 ++ 2 files changed, 76 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c index b09692b..30795cb 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -371,6 +371,58 @@ static int msm_iommu_domain_config(struct msm_priv *priv) return 0; } +/* Must be called under msm_iommu_lock */ +static struct msm_iommu_dev *find_iommu_for_dev(struct device *dev) +{ + struct msm_iommu_dev *iommu, *ret = NULL; + struct msm_iommu_ctx_dev *master; + + list_for_each_entry(iommu, &qcom_iommu_devices, dev_node) { + master = list_first_entry(&iommu->ctx_list, + struct msm_iommu_ctx_dev, + list); + if (master->of_node == dev->of_node) { + ret = iommu; + break; + } + } + + return ret; +} + +static int msm_iommu_add_device(struct device *dev) +{ + struct msm_iommu_dev *iommu; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&msm_iommu_lock, flags); + + iommu = find_iommu_for_dev(dev); + if (iommu) + iommu_device_link(&iommu->iommu, dev); + else + ret = -ENODEV; + + spin_unlock_irqrestore(&msm_iommu_lock, flags); + + return ret; +} + +static void msm_iommu_remove_device(struct device *dev) +{ + struct msm_iommu_dev *iommu; + unsigned long flags; + + spin_lock_irqsave(&msm_iommu_lock, flags); + + iommu = find_iommu_for_dev(dev); + if (iommu) + iommu_device_unlink(&iommu->iommu, dev); + + spin_unlock_irqrestore(&msm_iommu_lock, flags); +} + static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) { int ret = 0; @@ -646,6 +698,8 @@ static struct iommu_ops msm_iommu_ops = { .unmap = msm_iommu_unmap, .map_sg = default_iommu_map_sg, .iova_to_phys = msm_iommu_iova_to_phys, + .add_device = msm_iommu_add_device, + .remove_device = msm_iommu_remove_device, .pgsize_bitmap = MSM_IOMMU_PGSIZES, .of_xlate = qcom_iommu_of_xlate, }; @@ -653,6 +707,7 @@ static struct iommu_ops msm_iommu_ops = { static int msm_iommu_probe(struct platform_device *pdev) { struct resource *r; + resource_size_t ioaddr; struct msm_iommu_dev *iommu; int ret, par, val; @@ -696,6 +751,7 @@ static int msm_iommu_probe(struct platform_device *pdev) ret = PTR_ERR(iommu->base); goto fail; } + ioaddr = r->start; iommu->irq = platform_get_irq(pdev, 0); if (iommu->irq < 0) { @@ -737,6 +793,23 @@ static int msm_iommu_probe(struct platform_device *pdev) } list_add(&iommu->dev_node, &qcom_iommu_devices); + + ret = iommu_device_sysfs_add(&iommu->iommu, iommu->dev, NULL, + "msm-smmu.%pa", &ioaddr); + if (ret) { + pr_err("Could not add msm-smmu at %pa to sysfs\n", &ioaddr); + goto fail; + } + + iommu_device_set_ops(&iommu->iommu, &msm_iommu_ops); + iommu_device_set_fwnode(&iommu->iommu, &pdev->dev.of_node->fwnode); + + ret = iommu_device_register(&iommu->iommu); + if (ret) { + pr_err("Could not register msm-smmu at %pa\n", &ioaddr); + goto fail; + } + of_iommu_set_ops(pdev->dev.of_node, &msm_iommu_ops); pr_info("device mapped at %p, irq %d with %d ctx banks\n", diff --git a/drivers/iommu/msm_iommu.h b/drivers/iommu/msm_iommu.h index 4ca25d5..ae92d27 100644 --- a/drivers/iommu/msm_iommu.h +++ b/drivers/iommu/msm_iommu.h @@ -19,6 +19,7 @@ #define MSM_IOMMU_H #include +#include #include /* Sharability attributes of MSM IOMMU mappings */ @@ -68,6 +69,8 @@ struct msm_iommu_dev { struct list_head dom_node; struct list_head ctx_list; DECLARE_BITMAP(context_map, IOMMU_MAX_CBS); + + struct iommu_device iommu; }; /** -- cgit v1.1 From b16c0170b53cfbf50db6cd60a427b52b0602b882 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Fri, 3 Feb 2017 12:57:32 +0100 Subject: iommu/mediatek: Make use of iommu_device_register interface Register individual Mediatek IOMMUs to the iommu core and add sysfs entries. Cc: Matthias Brugger Cc: linux-arm-kernel@lists.infradead.org Cc: linux-mediatek@lists.infradead.org Signed-off-by: Joerg Roedel --- drivers/iommu/mtk_iommu.c | 26 ++++++++++++++++++++++++++ drivers/iommu/mtk_iommu.h | 2 ++ 2 files changed, 28 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c index 1479c76..d484fa6 100644 --- a/drivers/iommu/mtk_iommu.c +++ b/drivers/iommu/mtk_iommu.c @@ -360,11 +360,15 @@ static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain, static int mtk_iommu_add_device(struct device *dev) { + struct mtk_iommu_data *data; struct iommu_group *group; if (!dev->iommu_fwspec || dev->iommu_fwspec->ops != &mtk_iommu_ops) return -ENODEV; /* Not a iommu client device */ + data = dev->iommu_fwspec->iommu_priv; + iommu_device_link(&data->iommu, dev); + group = iommu_group_get_for_dev(dev); if (IS_ERR(group)) return PTR_ERR(group); @@ -375,9 +379,14 @@ static int mtk_iommu_add_device(struct device *dev) static void mtk_iommu_remove_device(struct device *dev) { + struct mtk_iommu_data *data; + if (!dev->iommu_fwspec || dev->iommu_fwspec->ops != &mtk_iommu_ops) return; + data = dev->iommu_fwspec->iommu_priv; + iommu_device_unlink(&data->iommu, dev); + iommu_group_remove_device(dev); iommu_fwspec_free(dev); } @@ -497,6 +506,7 @@ static int mtk_iommu_probe(struct platform_device *pdev) struct mtk_iommu_data *data; struct device *dev = &pdev->dev; struct resource *res; + resource_size_t ioaddr; struct component_match *match = NULL; void *protect; int i, larb_nr, ret; @@ -519,6 +529,7 @@ static int mtk_iommu_probe(struct platform_device *pdev) data->base = devm_ioremap_resource(dev, res); if (IS_ERR(data->base)) return PTR_ERR(data->base); + ioaddr = res->start; data->irq = platform_get_irq(pdev, 0); if (data->irq < 0) @@ -567,6 +578,18 @@ static int mtk_iommu_probe(struct platform_device *pdev) if (ret) return ret; + ret = iommu_device_sysfs_add(&data->iommu, dev, NULL, + "mtk-iommu.%pa", &ioaddr); + if (ret) + return ret; + + iommu_device_set_ops(&data->iommu, &mtk_iommu_ops); + iommu_device_set_fwnode(&data->iommu, &pdev->dev.of_node->fwnode); + + ret = iommu_device_register(&data->iommu); + if (ret) + return ret; + if (!iommu_present(&platform_bus_type)) bus_set_iommu(&platform_bus_type, &mtk_iommu_ops); @@ -577,6 +600,9 @@ static int mtk_iommu_remove(struct platform_device *pdev) { struct mtk_iommu_data *data = platform_get_drvdata(pdev); + iommu_device_sysfs_remove(&data->iommu); + iommu_device_unregister(&data->iommu); + if (iommu_present(&platform_bus_type)) bus_set_iommu(&platform_bus_type, NULL); diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h index 50177f7..2a28ead 100644 --- a/drivers/iommu/mtk_iommu.h +++ b/drivers/iommu/mtk_iommu.h @@ -47,6 +47,8 @@ struct mtk_iommu_data { struct iommu_group *m4u_group; struct mtk_smi_iommu smi_imu; /* SMI larb iommu info */ bool enable_4GB; + + struct iommu_device iommu; }; static inline int compare_of(struct device *dev, void *data) -- cgit v1.1 From d2c302b6e8b1a5c0735b59d63075132bae9c52ac Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Fri, 3 Feb 2017 13:23:42 +0100 Subject: iommu/exynos: Make use of iommu_device_register interface Register Exynos IOMMUs to the IOMMU core and make them visible in sysfs. This patch does not add the links between IOMMUs and translated devices yet. Cc: Marek Szyprowski Cc: linux-arm-kernel@lists.infradead.org Cc: linux-samsung-soc@vger.kernel.org Acked-by: Marek Szyprowski Tested-by: Marek Szyprowski Signed-off-by: Joerg Roedel --- drivers/iommu/exynos-iommu.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 57ba0d3..64325d8 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -276,6 +276,8 @@ struct sysmmu_drvdata { struct list_head owner_node; /* node for owner controllers list */ phys_addr_t pgtable; /* assigned page table structure */ unsigned int version; /* our version */ + + struct iommu_device iommu; /* IOMMU core handle */ }; static struct exynos_iommu_domain *to_exynos_domain(struct iommu_domain *dom) @@ -611,6 +613,18 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev) data->sysmmu = dev; spin_lock_init(&data->lock); + ret = iommu_device_sysfs_add(&data->iommu, &pdev->dev, NULL, + dev_name(data->sysmmu)); + if (ret) + return ret; + + iommu_device_set_ops(&data->iommu, &exynos_iommu_ops); + iommu_device_set_fwnode(&data->iommu, &dev->of_node->fwnode); + + ret = iommu_device_register(&data->iommu); + if (ret) + return ret; + platform_set_drvdata(pdev, data); __sysmmu_get_version(data); -- cgit v1.1 From d0f6f5832603931b0a8da044fb9abe8289e201ee Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 2 Feb 2017 12:19:12 +0100 Subject: iommu: Remove iommu_register_instance interface And also move its remaining functionality to iommu_device_register() and 'struct iommu_device'. Cc: Rob Herring Cc: Frank Rowand Cc: Matthias Brugger Cc: Marek Szyprowski Cc: devicetree@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Signed-off-by: Joerg Roedel --- drivers/iommu/arm-smmu-v3.c | 2 -- drivers/iommu/arm-smmu.c | 1 - drivers/iommu/exynos-iommu.c | 2 -- drivers/iommu/iommu.c | 37 ++++++------------------------------- drivers/iommu/msm_iommu.c | 2 -- drivers/iommu/mtk_iommu.c | 1 - 6 files changed, 6 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index 32133e2..5375137 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -2702,8 +2702,6 @@ static int arm_smmu_device_probe(struct platform_device *pdev) ret = iommu_device_register(&smmu->iommu); - iommu_register_instance(dev->fwnode, &arm_smmu_ops); - #ifdef CONFIG_PCI if (pci_bus_type.iommu_ops != &arm_smmu_ops) { pci_request_acs(); diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index f4ce1e7..8fb4af2 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -2121,7 +2121,6 @@ static int arm_smmu_device_probe(struct platform_device *pdev) return err; } - iommu_register_instance(dev->fwnode, &arm_smmu_ops); platform_set_drvdata(pdev, smmu); arm_smmu_device_reset(smmu); diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 64325d8..778eccc 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -642,8 +642,6 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev) pm_runtime_enable(dev); - of_iommu_set_ops(dev->of_node, &exynos_iommu_ops); - return 0; } diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 1dfd70e..162d865 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1658,43 +1658,18 @@ out: return ret; } -struct iommu_instance { - struct list_head list; - struct fwnode_handle *fwnode; - const struct iommu_ops *ops; -}; -static LIST_HEAD(iommu_instance_list); -static DEFINE_SPINLOCK(iommu_instance_lock); - -void iommu_register_instance(struct fwnode_handle *fwnode, - const struct iommu_ops *ops) -{ - struct iommu_instance *iommu = kzalloc(sizeof(*iommu), GFP_KERNEL); - - if (WARN_ON(!iommu)) - return; - - of_node_get(to_of_node(fwnode)); - INIT_LIST_HEAD(&iommu->list); - iommu->fwnode = fwnode; - iommu->ops = ops; - spin_lock(&iommu_instance_lock); - list_add_tail(&iommu->list, &iommu_instance_list); - spin_unlock(&iommu_instance_lock); -} - const struct iommu_ops *iommu_ops_from_fwnode(struct fwnode_handle *fwnode) { - struct iommu_instance *instance; const struct iommu_ops *ops = NULL; + struct iommu_device *iommu; - spin_lock(&iommu_instance_lock); - list_for_each_entry(instance, &iommu_instance_list, list) - if (instance->fwnode == fwnode) { - ops = instance->ops; + spin_lock(&iommu_device_lock); + list_for_each_entry(iommu, &iommu_device_list, list) + if (iommu->fwnode == fwnode) { + ops = iommu->ops; break; } - spin_unlock(&iommu_instance_lock); + spin_unlock(&iommu_device_lock); return ops; } diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c index 30795cb..d044835 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -810,8 +810,6 @@ static int msm_iommu_probe(struct platform_device *pdev) goto fail; } - of_iommu_set_ops(pdev->dev.of_node, &msm_iommu_ops); - pr_info("device mapped at %p, irq %d with %d ctx banks\n", iommu->base, iommu->irq, iommu->ncb); diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c index d484fa6..5d14cd1 100644 --- a/drivers/iommu/mtk_iommu.c +++ b/drivers/iommu/mtk_iommu.c @@ -681,7 +681,6 @@ static int mtk_iommu_init_fn(struct device_node *np) return ret; } - of_iommu_set_ops(np, &mtk_iommu_ops); return 0; } -- cgit v1.1 From 2c9f1af528a4581e8ef8590108daa3c3df08dd5a Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 9 Feb 2017 16:01:58 +0000 Subject: vfio/type1: Fix error return code in vfio_iommu_type1_attach_group() Fix to return a negative error code from the error handling case instead of 0, as done elsewhere in this function. Fixes: 5d704992189f ("vfio/type1: Allow transparent MSI IOVA allocation") Signed-off-by: Wei Yongjun Reviewed-by: Eric Auger Acked-by: Alex Williamson Signed-off-by: Joerg Roedel --- drivers/vfio/vfio_iommu_type1.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 0f353f5..bd6f293 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -1332,8 +1332,11 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, if (ret) goto out_detach; - if (resv_msi && iommu_get_msi_cookie(domain->domain, resv_msi_base)) - goto out_detach; + if (resv_msi) { + ret = iommu_get_msi_cookie(domain->domain, resv_msi_base); + if (ret) + goto out_detach; + } list_add(&domain->next, &iommu->domain_list); -- cgit v1.1