[PATCH v4 12/21] iommu/riscv: Pre-map IMSIC MSI targets
Andrew Jones <[email protected]>
| Newsgroups | dev.linux.lists.iommu,org.infradead.lists.linux-riscv,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
IMSIC target addresses change with interrupt affinity, which may be updated in atomic context. Therefore, an interrupt-remapping domain cannot create mappings on demand while composing an MSI message. Pre-map the supervisor IMSIC page for each possible CPU when a paging domain first allocates remapped interrupts. Also prepare the mappings when attaching a paging domain to a device with allocated interrupts, so an incoming domain is ready before the hardware switches to it. Reject passthrough mapping results because interrupt remapping requires the IMSIC page IOVA and its fixed page shift. Store the resulting IOVAs in a domain-local table so message composition only requires a lookup. Use the IMSIC group and hart fields as the table index, matching the RISC-V IOMMU MSI address extraction scheme. These topology fields do not necessarily produce dense CPU indices, so size the table for their full encoded range and leave unused entries empty. Create the per-device interrupt-remapping domain during IOMMU probe, but publish it from probe_finalize() after the IOMMU core has assigned the device to its group. This ensures the allocation callback can safely lock the group. Select IRQ_MSI_IOMMU for the generic MSI mapping API. Signed-off-by: Andrew Jones <[email protected]> --- drivers/iommu/riscv/Kconfig | 1 + drivers/iommu/riscv/iommu-ir.c | 133 ++++++++++++++++++++++++++++++++- drivers/iommu/riscv/iommu.c | 12 ++- drivers/iommu/riscv/iommu.h | 7 ++ 4 files changed, 147 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/riscv/Kconfig b/drivers/iommu/riscv/Kconfig index b86e5ab94183..f557b55c2c14 100644 --- a/drivers/iommu/riscv/Kconfig +++ b/drivers/iommu/riscv/Kconfig @@ -10,6 +10,7 @@ config RISCV_IOMMU select GENERIC_PT select IOMMU_PT select IOMMU_PT_RISCV64 + select IRQ_MSI_IOMMU help Support for implementations of the RISC-V IOMMU architecture that complements the RISC-V MMU capabilities, providing similar address diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c index de5e22ecbd63..4dc104c6d4b1 100644 --- a/drivers/iommu/riscv/iommu-ir.c +++ b/drivers/iommu/riscv/iommu-ir.c @@ -6,13 +6,108 @@ */ #include <linux/acpi.h> #include <linux/cleanup.h> +#include <linux/irqchip/riscv-imsic.h> #include <linux/msi.h> #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/vmalloc.h> #include "iommu.h" +/* + * Compute the MSI index for an MSI physical address using the + * IOMMU "extract" function (RISC-V IOMMU spec section 2.3.3). + */ +static size_t riscv_iommu_ir_extract_msi_idx(phys_addr_t pa) +{ + const struct imsic_global_config *global = imsic_get_global_config(); + phys_addr_t mask, addr = pa >> 12; + size_t idx; + + mask = BIT(global->hart_index_bits + global->guest_index_bits) - 1; + idx = addr & mask; + + if (global->group_index_bits) { + phys_addr_t group_mask = BIT(global->group_index_bits) - 1; + phys_addr_t group_shift = global->group_index_shift - 12; + phys_addr_t group = (addr >> group_shift) & group_mask; + + idx |= group << fls64(mask); + } + + return idx; +} + +static size_t riscv_iommu_ir_msi_iova_idx(phys_addr_t pa) +{ + const struct imsic_global_config *global = imsic_get_global_config(); + + /* msi_iova[] is only used for the host imsics */ + return riscv_iommu_ir_extract_msi_idx(pa) >> global->guest_index_bits; +} + +static size_t riscv_iommu_ir_msi_iova_count(void) +{ + const struct imsic_global_config *global = imsic_get_global_config(); + + return BIT(global->group_index_bits + global->hart_index_bits); +} + +static int riscv_iommu_ir_build_msi_iova(struct riscv_iommu_domain *domain, struct device *dev) +{ + const struct imsic_global_config *global = imsic_get_global_config(); + struct iommu_domain *d = &domain->domain; + dma_addr_t *msi_iova; + unsigned int cpu; + int ret; + + guard(mutex)(&domain->mutex); + + if (domain->msi_iova) + return 0; + + switch (d->cookie_type) { + case IOMMU_COOKIE_DMA_IOVA: + case IOMMU_COOKIE_DMA_MSI: + case IOMMU_COOKIE_IOMMUFD: + break; + default: + return 0; + } + + msi_iova = vcalloc(riscv_iommu_ir_msi_iova_count(), sizeof(*msi_iova)); + if (!msi_iova) + return -ENOMEM; + + for_each_possible_cpu(cpu) { + const struct imsic_local_config *local = per_cpu_ptr(global->local, cpu); + phys_addr_t pa = local->msi_pa; + unsigned int shift; + size_t idx; + + if (!pa) + continue; + + idx = riscv_iommu_ir_msi_iova_idx(pa); + ret = iommu_dma_map_msi(d, dev, pa, IMSIC_MMIO_PAGE_SZ, &msi_iova[idx], &shift); + if (ret) + goto err_free; + if (shift != IMSIC_MMIO_PAGE_SHIFT) { + ret = -EBUSY; + goto err_free; + } + } + + domain->msi_iova = msi_iova; + + return 0; + +err_free: + vfree(msi_iova); + return ret; +} + static struct irq_chip riscv_iommu_ir_irq_chip = { .name = "IOMMU-IR", .irq_ack = irq_chip_ack_parent, @@ -25,8 +120,24 @@ static int riscv_iommu_ir_irq_domain_alloc_irqs(struct irq_domain *irqdomain, unsigned int irq_base, unsigned int nr_irqs, void *arg) { + struct riscv_iommu_info *info = irqdomain->host_data; int ret; + /* + * MSI IOVAs are domain-local, just like DMA IOVAs. The device must be + * quiesced, including MSI teardown, before switching away from or freeing + * the domain. iommu_dma_map_msi() requires the group mutex to be held; + * take it around the domain lookup too so info->domain can't change + * out from under the build. + */ + scoped_guard(iommu_group, info->dev) { + struct riscv_iommu_domain *domain = rcu_dereference_protected(info->domain, true); + + ret = domain ? riscv_iommu_ir_build_msi_iova(domain, info->dev) : 0; + } + if (ret) + return ret; + ret = irq_domain_alloc_irqs_parent(irqdomain, irq_base, nr_irqs, arg); if (ret) return ret; @@ -112,11 +223,22 @@ struct irq_domain *riscv_iommu_ir_irq_domain_create(struct device *dev, irqdomain->msi_parent_ops = &riscv_iommu_ir_msi_parent_ops; irq_domain_update_bus_token(irqdomain, DOMAIN_BUS_MSI_REMAP); - dev_set_msi_domain(dev, irqdomain); - + /* + * Publication is deferred to riscv_iommu_ir_irq_domain_publish(), + * called from probe_finalize() after the IOMMU core assigns + * dev->iommu_group, because the allocation callback locks the group. + */ return irqdomain; } +void riscv_iommu_ir_irq_domain_publish(struct device *dev) +{ + struct riscv_iommu_info *info = dev_iommu_priv_get(dev); + + if (info->irqdomain) + dev_set_msi_domain(dev, info->irqdomain); +} + void riscv_iommu_ir_irq_domain_remove(struct device *dev, struct riscv_iommu_info *info) { struct fwnode_handle *fn; @@ -124,7 +246,8 @@ void riscv_iommu_ir_irq_domain_remove(struct device *dev, struct riscv_iommu_inf if (!info->irqdomain) return; - dev_set_msi_domain(dev, info->irqdomain->parent); + if (dev_get_msi_domain(dev) == info->irqdomain) + dev_set_msi_domain(dev, info->irqdomain->parent); fn = info->irqdomain->fwnode; irq_domain_remove(info->irqdomain); info->irqdomain = NULL; @@ -139,6 +262,10 @@ int riscv_iommu_ir_attach_paging_domain(struct iommu_domain *iommu_domain, struc void riscv_iommu_ir_free_paging_domain(struct iommu_domain *iommu_domain) { + struct riscv_iommu_domain *domain = iommu_domain_to_riscv(iommu_domain); + + vfree(domain->msi_iova); + domain->msi_iova = NULL; } void riscv_iommu_ir_get_resv_regions(struct device *dev, struct list_head *head) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 049ca1852d7b..1a30bac88101 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -811,9 +811,6 @@ static int riscv_iommu_iodir_set_mode(struct riscv_iommu_device *iommu, return 0; } -#define iommu_domain_to_riscv(iommu_domain) \ - container_of(iommu_domain, struct riscv_iommu_domain, domain) - /* * Linkage between an iommu_domain and attached devices. * @@ -1341,6 +1338,8 @@ static struct iommu_domain *riscv_iommu_alloc_paging_domain(struct device *dev) if (!domain) return ERR_PTR(-ENOMEM); + mutex_init(&domain->mutex); + INIT_LIST_HEAD_RCU(&domain->bonds); spin_lock_init(&domain->lock); /* @@ -1467,6 +1466,7 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev) * any IMSICs or no MSI domain has been set up for the device. */ info->irqdomain = irqdomain; + info->dev = dev; /* * Allocate and pre-configure device context entries in @@ -1490,6 +1490,11 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev) return &iommu->iommu; } +static void riscv_iommu_probe_finalize(struct device *dev) +{ + riscv_iommu_ir_irq_domain_publish(dev); +} + static void riscv_iommu_release_device(struct device *dev) { struct riscv_iommu_info *info = dev_iommu_priv_get(dev); @@ -1512,6 +1517,7 @@ static const struct iommu_ops riscv_iommu_ops = { .domain_alloc_paging = riscv_iommu_alloc_paging_domain, .device_group = riscv_iommu_device_group, .probe_device = riscv_iommu_probe_device, + .probe_finalize = riscv_iommu_probe_finalize, .release_device = riscv_iommu_release_device, .get_resv_regions = riscv_iommu_get_resv_regions, }; diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index 16b3c9c4cf8c..59779aa9d380 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -32,14 +32,20 @@ struct riscv_iommu_domain { }; struct list_head bonds; spinlock_t lock; /* protect bonds list updates. */ + struct mutex mutex; /* serializes sleepable, domain-wide setups */ int pscid; + dma_addr_t *msi_iova; }; PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain); +#define iommu_domain_to_riscv(iommu_domain) \ + container_of(iommu_domain, struct riscv_iommu_domain, domain) + /* Private IOMMU data for managed devices, dev_iommu_priv_* */ struct riscv_iommu_info { struct riscv_iommu_domain __rcu *domain; struct irq_domain *irqdomain; + struct device *dev; }; struct riscv_iommu_device; @@ -93,6 +99,7 @@ void riscv_iommu_disable(struct riscv_iommu_device *iommu); struct irq_domain *riscv_iommu_ir_irq_domain_create(struct device *dev, struct riscv_iommu_info *info); +void riscv_iommu_ir_irq_domain_publish(struct device *dev); void riscv_iommu_ir_irq_domain_remove(struct device *dev, struct riscv_iommu_info *info); int riscv_iommu_ir_attach_paging_domain(struct iommu_domain *iommu_domain, struct device *dev, struct iommu_domain *old); -- 2.43.0