|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [RFC PATCH v1 2/7] iommu/arm: ipmmu-vmsa: Add Xen changes for main driver
From: Oleksandr Tyshchenko <oleksandr_tyshchenko@xxxxxxxx>
Modify the Linux IPMMU driver to be functional inside Xen.
All devices within a single Xen domain must use the same
IOMMU context no matter what IOMMU domains they are attached to.
This is the main difference between drivers in Linux
and Xen. Having 8 separate contexts allow us to passthrough
devices to 8 guest domain at the same time.
Also wrap following code in #if 0:
- All DMA related stuff
- Linux PM callbacks
- Driver remove callback
- iommu_group management
Maybe, it would be more correct to move different Linux2Xen wrappers,
define-s, helpers from IPMMU-VMSA and SMMU to some common file
before introducing IPMMU-VMSA patch series. And this common file
might be reused by possible future IOMMUs on ARM.
Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@xxxxxxxx>
CC: Julien Grall <julien.grall@xxxxxxx>
CC: Stefano Stabellini <sstabellini@xxxxxxxxxx>
---
xen/drivers/passthrough/arm/ipmmu-vmsa.c | 984 +++++++++++++++++++++++++++++--
1 file changed, 948 insertions(+), 36 deletions(-)
diff --git a/xen/drivers/passthrough/arm/ipmmu-vmsa.c
b/xen/drivers/passthrough/arm/ipmmu-vmsa.c
index 2b380ff..e54b507 100644
--- a/xen/drivers/passthrough/arm/ipmmu-vmsa.c
+++ b/xen/drivers/passthrough/arm/ipmmu-vmsa.c
@@ -6,31 +6,212 @@
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
+ *
+ * Based on Linux drivers/iommu/ipmmu-vmsa.c
+ * => commit f4747eba89c9b5d90fdf0a5458866283c47395d8
+ * (iommu/ipmmu-vmsa: Restrict IOMMU Domain Geometry to 32-bit address space)
+ *
+ * Xen modification:
+ * Oleksandr Tyshchenko <Oleksandr_Tyshchenko@xxxxxxxx>
+ * Copyright (C) 2016-2017 EPAM Systems Inc.
*/
-#include <linux/bitmap.h>
-#include <linux/delay.h>
-#include <linux/dma-iommu.h>
-#include <linux/dma-mapping.h>
-#include <linux/err.h>
-#include <linux/export.h>
-#include <linux/interrupt.h>
-#include <linux/io.h>
-#include <linux/iommu.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_iommu.h>
-#include <linux/platform_device.h>
-#include <linux/sizes.h>
-#include <linux/slab.h>
-
-#if defined(CONFIG_ARM) && !defined(CONFIG_IOMMU_DMA)
-#include <asm/dma-iommu.h>
-#include <asm/pgalloc.h>
-#endif
+#include <xen/config.h>
+#include <xen/delay.h>
+#include <xen/errno.h>
+#include <xen/err.h>
+#include <xen/irq.h>
+#include <xen/lib.h>
+#include <xen/list.h>
+#include <xen/mm.h>
+#include <xen/vmap.h>
+#include <xen/rbtree.h>
+#include <xen/sched.h>
+#include <xen/sizes.h>
+#include <asm/atomic.h>
+#include <asm/device.h>
+#include <asm/io.h>
+#include <asm/platform.h>
#include "io-pgtable.h"
+/* TODO:
+ * 1. Optimize xen_domain->lock usage.
+ * 2. Show domain_id in every printk which is per Xen domain.
+ *
+ */
+
+/***** Start of Xen specific code *****/
+
+#define IOMMU_READ (1 << 0)
+#define IOMMU_WRITE (1 << 1)
+#define IOMMU_CACHE (1 << 2) /* DMA cache coherency */
+#define IOMMU_NOEXEC (1 << 3)
+#define IOMMU_MMIO (1 << 4) /* e.g. things like MSI doorbells */
+
+#define __fls(x) (fls(x) - 1)
+#define __ffs(x) (ffs(x) - 1)
+
+#define IO_PGTABLE_QUIRK_ARM_NS BIT(0)
+
+#define ioread32 readl
+#define iowrite32 writel
+
+#define dev_info dev_notice
+
+#define devm_request_irq(unused, irq, func, flags, name, dev) \
+ request_irq(irq, flags, func, name, dev)
+
+/* Alias to Xen device tree helpers */
+#define device_node dt_device_node
+#define of_phandle_args dt_phandle_args
+#define of_device_id dt_device_match
+#define of_match_node dt_match_node
+#define of_parse_phandle_with_args dt_parse_phandle_with_args
+#define of_find_property dt_find_property
+#define of_count_phandle_with_args dt_count_phandle_with_args
+
+/* Xen: Helpers to get device MMIO and IRQs */
+struct resource
+{
+ u64 addr;
+ u64 size;
+ unsigned int type;
+};
+
+#define resource_size(res) (res)->size;
+
+#define platform_device dt_device_node
+
+#define IORESOURCE_MEM 0
+#define IORESOURCE_IRQ 1
+
+static struct resource *platform_get_resource(struct platform_device *pdev,
+ unsigned int type,
+ unsigned int num)
+{
+ /*
+ * The resource is only used between 2 calls of platform_get_resource.
+ * It's quite ugly but it's avoid to add too much code in the part
+ * imported from Linux
+ */
+ static struct resource res;
+ int ret = 0;
+
+ res.type = type;
+
+ switch (type) {
+ case IORESOURCE_MEM:
+ ret = dt_device_get_address(pdev, num, &res.addr, &res.size);
+
+ return ((ret) ? NULL : &res);
+
+ case IORESOURCE_IRQ:
+ ret = platform_get_irq(pdev, num);
+ if (ret < 0)
+ return NULL;
+
+ res.addr = ret;
+ res.size = 1;
+
+ return &res;
+
+ default:
+ return NULL;
+ }
+}
+
+enum irqreturn {
+ IRQ_NONE = (0 << 0),
+ IRQ_HANDLED = (1 << 0),
+};
+
+typedef enum irqreturn irqreturn_t;
+
+/* Device logger functions */
+#define dev_print(dev, lvl, fmt, ...)
\
+ printk(lvl "ipmmu: %s: " fmt, dt_node_full_name(dev_to_dt(dev)), ##
__VA_ARGS__)
+
+#define dev_dbg(dev, fmt, ...) dev_print(dev, XENLOG_DEBUG, fmt, ##
__VA_ARGS__)
+#define dev_notice(dev, fmt, ...) dev_print(dev, XENLOG_INFO, fmt, ##
__VA_ARGS__)
+#define dev_warn(dev, fmt, ...) dev_print(dev, XENLOG_WARNING, fmt, ##
__VA_ARGS__)
+#define dev_err(dev, fmt, ...) dev_print(dev, XENLOG_ERR, fmt, ## __VA_ARGS__)
+
+#define dev_err_ratelimited(dev, fmt, ...)
\
+ dev_print(dev, XENLOG_ERR, fmt, ## __VA_ARGS__)
+
+#define dev_name(dev) dt_node_full_name(dev_to_dt(dev))
+
+/* Alias to Xen allocation helpers */
+#define kfree xfree
+#define kmalloc(size, flags) _xmalloc(size, sizeof(void *))
+#define kzalloc(size, flags) _xzalloc(size, sizeof(void *))
+#define devm_kzalloc(dev, size, flags) _xzalloc(size, sizeof(void *))
+#define kmalloc_array(size, n, flags) _xmalloc_array(size, sizeof(void *), n)
+#define kcalloc(size, n, flags) _xzalloc_array(size,
sizeof(void *), n)
+
+static void __iomem *devm_ioremap_resource(struct device *dev,
+ struct resource *res)
+{
+ void __iomem *ptr;
+
+ if (!res || res->type != IORESOURCE_MEM) {
+ dev_err(dev, "Invalid resource\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ ptr = ioremap_nocache(res->addr, res->size);
+ if (!ptr) {
+ dev_err(dev,
+ "ioremap failed (addr 0x%"PRIx64" size 0x%"PRIx64")\n",
+ res->addr, res->size);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return ptr;
+}
+
+/* Xen doesn't handle IOMMU fault */
+#define report_iommu_fault(...) 1
+
+#define MODULE_DEVICE_TABLE(type, name)
+#define module_param_named(name, value, type, perm)
+#define MODULE_PARM_DESC(_parm, desc)
+
+/* Xen: Dummy iommu_domain */
+struct iommu_domain
+{
+ atomic_t ref;
+ /* Used to link iommu_domain contexts for a same domain.
+ * There is at least one per-IPMMU to used by the domain.
+ * */
+ struct list_head list;
+};
+
+/* Xen: Describes informations required for a Xen domain */
+struct ipmmu_vmsa_xen_domain {
+ spinlock_t lock;
+ /* List of context (i.e iommu_domain) associated to this domain */
+ struct list_head contexts;
+ struct iommu_domain *base_context;
+};
+
+/*
+ * Xen: Information about each device stored in dev->archdata.iommu
+ *
+ * On Linux the dev->archdata.iommu only stores the arch specific information,
+ * but, on Xen, we also have to store the iommu domain.
+ */
+struct ipmmu_vmsa_xen_device {
+ struct iommu_domain *domain;
+ struct ipmmu_vmsa_archdata *archdata;
+};
+
+#define dev_iommu(dev) ((struct ipmmu_vmsa_xen_device *)dev->archdata.iommu)
+#define dev_iommu_domain(dev) (dev_iommu(dev)->domain)
+
+/***** Start of Linux IPMMU code *****/
+
#define IPMMU_CTX_MAX 8
struct ipmmu_features {
@@ -64,7 +245,9 @@ struct ipmmu_vmsa_device {
struct hw_register *reg_backup[IPMMU_CTX_MAX];
#endif
+#if 0 /* Xen: Not needed */
struct dma_iommu_mapping *mapping;
+#endif
};
struct ipmmu_vmsa_domain {
@@ -77,6 +260,9 @@ struct ipmmu_vmsa_domain {
unsigned int context_id;
spinlock_t lock; /* Protects mappings */
+
+ /* Xen: Domain associated to this configuration */
+ struct domain *d;
};
struct ipmmu_vmsa_archdata {
@@ -94,14 +280,20 @@ struct ipmmu_vmsa_archdata {
static DEFINE_SPINLOCK(ipmmu_devices_lock);
static LIST_HEAD(ipmmu_devices);
+#if 0 /* Xen: Not needed */
static DEFINE_SPINLOCK(ipmmu_slave_devices_lock);
static LIST_HEAD(ipmmu_slave_devices);
+#endif
static struct ipmmu_vmsa_domain *to_vmsa_domain(struct iommu_domain *dom)
{
return container_of(dom, struct ipmmu_vmsa_domain, io_domain);
}
+/*
+ * Xen: Rewrite Linux helpers to manipulate with archdata on Xen.
+ */
+#if 0
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
static struct ipmmu_vmsa_archdata *to_archdata(struct device *dev)
{
@@ -120,6 +312,16 @@ static void set_archdata(struct device *dev, struct
ipmmu_vmsa_archdata *p)
{
}
#endif
+#else
+static struct ipmmu_vmsa_archdata *to_archdata(struct device *dev)
+{
+ return dev_iommu(dev)->archdata;
+}
+static void set_archdata(struct device *dev, struct ipmmu_vmsa_archdata *p)
+{
+ dev_iommu(dev)->archdata = p;
+}
+#endif
#define TLB_LOOP_TIMEOUT 100 /* 100us */
@@ -355,6 +557,10 @@ static struct hw_register *root_pgtable[IPMMU_CTX_MAX] = {
static bool ipmmu_is_root(struct ipmmu_vmsa_device *mmu)
{
+ /* Xen: Fix */
+ if (!mmu)
+ return false;
+
if (mmu->features->has_cache_leaf_nodes)
return mmu->is_leaf ? false : true;
else
@@ -405,14 +611,28 @@ static void ipmmu_ctx_write(struct ipmmu_vmsa_domain
*domain, unsigned int reg,
ipmmu_write(domain->root, domain->context_id * IM_CTX_SIZE + reg, data);
}
-static void ipmmu_ctx_write2(struct ipmmu_vmsa_domain *domain, unsigned int
reg,
+/* Xen: Write the context for cache IPMMU only. */
+static void ipmmu_ctx_write1(struct ipmmu_vmsa_domain *domain, unsigned int
reg,
u32 data)
{
if (domain->mmu != domain->root)
- ipmmu_write(domain->mmu,
- domain->context_id * IM_CTX_SIZE + reg, data);
+ ipmmu_write(domain->mmu, domain->context_id * IM_CTX_SIZE +
reg, data);
+}
- ipmmu_write(domain->root, domain->context_id * IM_CTX_SIZE + reg, data);
+/*
+ * Xen: Write the context for both root IPMMU and all cache IPMMUs
+ * that assigned to this Xen domain.
+ */
+static void ipmmu_ctx_write2(struct ipmmu_vmsa_domain *domain, unsigned int
reg,
+ u32 data)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain =
dom_iommu(domain->d)->arch.priv;
+ struct iommu_domain *io_domain;
+
+ list_for_each_entry(io_domain, &xen_domain->contexts, list)
+ ipmmu_ctx_write1(to_vmsa_domain(io_domain), reg, data);
+
+ ipmmu_ctx_write(domain, reg, data);
}
/*
-----------------------------------------------------------------------------
@@ -488,6 +708,10 @@ static void ipmmu_tlb_flush_all(void *cookie)
{
struct ipmmu_vmsa_domain *domain = cookie;
+ /* Xen: Just return if context_id has non-existent value */
+ if (domain->context_id >= domain->root->num_ctx)
+ return;
+
ipmmu_tlb_invalidate(domain);
}
@@ -549,8 +773,10 @@ 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;
+#if 0 /* Xen: Not needed */
domain->io_domain.geometry.aperture_end = DMA_BIT_MASK(32);
domain->io_domain.geometry.force_aperture = true;
+#endif
/*
* TODO: Add support for coherent walk through CCI with DVM and remove
* cache handling. For now, delegate it to the io-pgtable code.
@@ -562,6 +788,9 @@ static int ipmmu_domain_init_context(struct
ipmmu_vmsa_domain *domain)
if (!domain->iop)
return -EINVAL;
+ /* Xen: Initialize context_id with non-existent value */
+ domain->context_id = domain->root->num_ctx;
+
/*
* Find an unused context.
*/
@@ -578,6 +807,11 @@ static int ipmmu_domain_init_context(struct
ipmmu_vmsa_domain *domain)
/* TTBR0 */
ttbr = domain->cfg.arm_lpae_s1_cfg.ttbr[0];
+
+ /* Xen: */
+ dev_notice(domain->root->dev, "d%d: Set IPMMU context %u (pgd
0x%"PRIx64")\n",
+ domain->d->domain_id, domain->context_id, ttbr);
+
ipmmu_ctx_write(domain, IMTTLBR0, ttbr);
ipmmu_ctx_write(domain, IMTTUBR0, ttbr >> 32);
@@ -616,8 +850,9 @@ static int ipmmu_domain_init_context(struct
ipmmu_vmsa_domain *domain)
* translation table format doesn't use TEX remapping. Don't enable AF
* software management as we have no use for it. Flush the TLB as
* required when modifying the context registers.
+ * Xen: Enable the context for the root IPMMU only.
*/
- ipmmu_ctx_write2(domain, IMCTR,
+ ipmmu_ctx_write(domain, IMCTR,
IMCTR_INTEN | IMCTR_FLUSH | IMCTR_MMUEN);
return 0;
@@ -638,13 +873,18 @@ static void ipmmu_domain_free_context(struct
ipmmu_vmsa_device *mmu,
static void ipmmu_domain_destroy_context(struct ipmmu_vmsa_domain *domain)
{
+ /* Xen: Just return if context_id has non-existent value */
+ if (domain->context_id >= domain->root->num_ctx)
+ return;
+
/*
* Disable the context. Flush the TLB as required when modifying the
* context registers.
*
* TODO: Is TLB flush really needed ?
+ * Xen: Disable the context for the root IPMMU only.
*/
- ipmmu_ctx_write2(domain, IMCTR, IMCTR_FLUSH);
+ ipmmu_ctx_write(domain, IMCTR, IMCTR_FLUSH);
ipmmu_tlb_sync(domain);
#ifdef CONFIG_RCAR_DDR_BACKUP
@@ -652,12 +892,16 @@ static void ipmmu_domain_destroy_context(struct
ipmmu_vmsa_domain *domain)
#endif
ipmmu_domain_free_context(domain->root, domain->context_id);
+
+ /* Xen: Initialize context_id with non-existent value */
+ domain->context_id = domain->root->num_ctx;
}
/*
-----------------------------------------------------------------------------
* Fault Handling
*/
+/* Xen: Show domain_id in every printk */
static irqreturn_t ipmmu_domain_irq(struct ipmmu_vmsa_domain *domain)
{
const u32 err_mask = IMSTR_MHIT | IMSTR_ABORT | IMSTR_PF | IMSTR_TF;
@@ -681,11 +925,11 @@ static irqreturn_t ipmmu_domain_irq(struct
ipmmu_vmsa_domain *domain)
/* Log fatal errors. */
if (status & IMSTR_MHIT)
- dev_err_ratelimited(mmu->dev, "Multiple TLB hits @0x%08x\n",
- iova);
+ dev_err_ratelimited(mmu->dev, "d%d: Multiple TLB hits
@0x%08x\n",
+ domain->d->domain_id, iova);
if (status & IMSTR_ABORT)
- dev_err_ratelimited(mmu->dev, "Page Table Walk Abort @0x%08x\n",
- iova);
+ dev_err_ratelimited(mmu->dev, "d%d: Page Table Walk Abort
@0x%08x\n",
+ domain->d->domain_id, iova);
if (!(status & (IMSTR_PF | IMSTR_TF)))
return IRQ_NONE;
@@ -700,8 +944,8 @@ static irqreturn_t ipmmu_domain_irq(struct
ipmmu_vmsa_domain *domain)
return IRQ_HANDLED;
dev_err_ratelimited(mmu->dev,
- "Unhandled fault: status 0x%08x iova 0x%08x\n",
- status, iova);
+ "d%d: Unhandled fault: status 0x%08x iova 0x%08x\n",
+ domain->d->domain_id, status, iova);
return IRQ_HANDLED;
}
@@ -730,6 +974,16 @@ static irqreturn_t ipmmu_irq(int irq, void *dev)
return status;
}
+/* Xen: Interrupt handlers wrapper */
+static void ipmmu_irq_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ ipmmu_irq(irq, dev);
+}
+
+#define ipmmu_irq ipmmu_irq_xen
+
+#if 0 /* Xen: Not needed */
/*
-----------------------------------------------------------------------------
* IOMMU Operations
*/
@@ -759,6 +1013,7 @@ static void ipmmu_domain_free(struct iommu_domain
*io_domain)
free_io_pgtable_ops(domain->iop);
kfree(domain);
}
+#endif
static int ipmmu_attach_device(struct iommu_domain *io_domain,
struct device *dev)
@@ -787,7 +1042,20 @@ static int ipmmu_attach_device(struct iommu_domain
*io_domain,
/* The domain hasn't been used yet, initialize it. */
domain->mmu = mmu;
domain->root = root;
+
+/*
+ * Xen: We have already initialized and enabled context for root IPMMU
+ * for this Xen domain. Enable context for given cache IPMMU only.
+ * Flush the TLB as required when modifying the context registers.
+ */
+#if 0
ret = ipmmu_domain_init_context(domain);
+#endif
+ ipmmu_ctx_write1(domain, IMCTR,
+ ipmmu_ctx_read(domain, IMCTR) | IMCTR_FLUSH);
+
+ dev_info(dev, "Using IPMMU context %u\n", domain->context_id);
+#if 0 /* Xen: Not needed */
if (ret < 0) {
dev_err(dev, "Unable to initialize IPMMU context\n");
domain->mmu = NULL;
@@ -795,6 +1063,7 @@ static int ipmmu_attach_device(struct iommu_domain
*io_domain,
dev_info(dev, "Using IPMMU context %u\n",
domain->context_id);
}
+#endif
} else if (domain->mmu != mmu) {
/*
* Something is wrong, we can't attach two devices using
@@ -834,6 +1103,14 @@ static void ipmmu_detach_device(struct iommu_domain
*io_domain,
*/
}
+/*
+ * Xen: The current implementation of these callbacks is insufficient for us
+ * since they are intended to be called from Linux IOMMU core that
+ * has already done all required actions such as doing various checks,
+ * splitting into memory block the hardware supports and so on.
+ * So, overwrite them with more completely functions.
+ */
+#if 0
static int ipmmu_map(struct iommu_domain *io_domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot)
{
@@ -862,7 +1139,177 @@ static phys_addr_t ipmmu_iova_to_phys(struct
iommu_domain *io_domain,
return domain->iop->iova_to_phys(domain->iop, iova);
}
+#endif
+
+static size_t ipmmu_pgsize(struct iommu_domain *io_domain,
+ unsigned long addr_merge, size_t size)
+{
+ struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
+ unsigned int pgsize_idx;
+ size_t pgsize;
+
+ /* Max page size that still fits into 'size' */
+ pgsize_idx = __fls(size);
+
+ /* need to consider alignment requirements ? */
+ if (likely(addr_merge)) {
+ /* Max page size allowed by address */
+ unsigned int align_pgsize_idx = __ffs(addr_merge);
+ pgsize_idx = min(pgsize_idx, align_pgsize_idx);
+ }
+
+ /* build a mask of acceptable page sizes */
+ pgsize = (1UL << (pgsize_idx + 1)) - 1;
+
+ /* throw away page sizes not supported by the hardware */
+ pgsize &= domain->cfg.pgsize_bitmap;
+
+ /* make sure we're still sane */
+ BUG_ON(!pgsize);
+
+ /* pick the biggest page */
+ pgsize_idx = __fls(pgsize);
+ pgsize = 1UL << pgsize_idx;
+
+ return pgsize;
+}
+
+phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain, dma_addr_t iova)
+{
+ struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
+
+ if (unlikely(domain->iop->iova_to_phys == NULL))
+ return 0;
+
+ return domain->iop->iova_to_phys(domain->iop, iova);
+}
+
+size_t ipmmu_unmap(struct iommu_domain *io_domain, unsigned long iova, size_t
size)
+{
+ struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
+ size_t unmapped_page, unmapped = 0;
+ dma_addr_t max_iova;
+ unsigned int min_pagesz;
+
+ if (unlikely(domain->iop->unmap == NULL ||
+ domain->cfg.pgsize_bitmap == 0UL))
+ return -ENODEV;
+
+ /* find out the minimum page size supported */
+ min_pagesz = 1 << __ffs(domain->cfg.pgsize_bitmap);
+
+ /*
+ * The virtual address, as well as the size of the mapping, must be
+ * aligned (at least) to the size of the smallest page supported
+ * by the hardware
+ */
+ if (!IS_ALIGNED(iova | size, min_pagesz)) {
+ printk("unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n",
+ iova, size, min_pagesz);
+ return -EINVAL;
+ }
+ /*
+ * the sum of virtual address and size must be inside the IOVA space
+ * that hardware supports
+ */
+ max_iova = (1UL << domain->cfg.ias) - 1;
+ if ((dma_addr_t)iova + size > max_iova) {
+ printk("out-of-bound: iova 0x%lx + size 0x%zx > max_iova
0x%"PRIx64"\n",
+ iova, size, max_iova);
+ /* TODO Return -EINVAL instead */
+ return 0;
+ }
+
+ /*
+ * Keep iterating until we either unmap 'size' bytes (or more)
+ * or we hit an area that isn't mapped.
+ */
+ while (unmapped < size) {
+ size_t pgsize = ipmmu_pgsize(io_domain, iova, size - unmapped);
+
+ unmapped_page = domain->iop->unmap(domain->iop, iova, pgsize);
+ if (!unmapped_page)
+ break;
+
+ iova += unmapped_page;
+ unmapped += unmapped_page;
+ }
+
+ return unmapped;
+}
+
+int ipmmu_map(struct iommu_domain *io_domain, unsigned long iova,
+ phys_addr_t paddr, size_t size, int prot)
+{
+ struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
+ unsigned long orig_iova = iova;
+ dma_addr_t max_iova;
+ unsigned int min_pagesz;
+ size_t orig_size = size;
+ int ret = 0;
+
+ if (unlikely(domain->iop->map == NULL ||
+ domain->cfg.pgsize_bitmap == 0UL))
+ return -ENODEV;
+
+ /* find out the minimum page size supported */
+ min_pagesz = 1 << __ffs(domain->cfg.pgsize_bitmap);
+
+ /*
+ * both the virtual address and the physical one, as well as
+ * the size of the mapping, must be aligned (at least) to the
+ * size of the smallest page supported by the hardware
+ */
+ if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
+ printk("unaligned: iova 0x%lx pa 0x%"PRIx64" size 0x%zx
min_pagesz 0x%x\n",
+ iova, paddr, size, min_pagesz);
+ return -EINVAL;
+ }
+
+ /*
+ * the sum of virtual address and size must be inside the IOVA space
+ * that hardware supports
+ */
+ max_iova = (1UL << domain->cfg.ias) - 1;
+ if ((dma_addr_t)iova + size > max_iova) {
+ printk("out-of-bound: iova 0x%lx + size 0x%zx > max_iova
0x%"PRIx64"\n",
+ iova, size, max_iova);
+ /* TODO Return -EINVAL instead */
+ return 0;
+ }
+
+ while (size) {
+ size_t pgsize = ipmmu_pgsize(io_domain, iova | paddr, size);
+
+ ret = domain->iop->map(domain->iop, iova, paddr, pgsize, prot);
+ if (ret == -EEXIST) {
+ phys_addr_t exist_paddr = ipmmu_iova_to_phys(io_domain,
iova);
+ if (exist_paddr == paddr)
+ ret = 0;
+ else if (exist_paddr) {
+ printk("remap: iova 0x%lx pa 0x%"PRIx64" pgsize
0x%zx\n",
+ iova, paddr, pgsize);
+ ipmmu_unmap(io_domain, iova, pgsize);
+ ret = domain->iop->map(domain->iop, iova,
paddr, pgsize, prot);
+ }
+ }
+ if (ret)
+ break;
+
+ iova += pgsize;
+ paddr += pgsize;
+ size -= pgsize;
+ }
+
+ /* unroll mapping in case something went wrong */
+ if (ret && orig_size != size)
+ ipmmu_unmap(io_domain, orig_iova, orig_size - size);
+
+ return ret;
+}
+
+#if 0 /* Xen: Not needed */
static struct device *ipmmu_find_sibling_device(struct device *dev)
{
struct ipmmu_vmsa_archdata *archdata = dev->archdata.iommu;
@@ -898,6 +1345,7 @@ static struct iommu_group *ipmmu_find_group(struct device
*dev)
return group;
}
+#endif
static int ipmmu_find_utlbs(struct ipmmu_vmsa_device *mmu, struct device *dev,
unsigned int *utlbs, unsigned int num_utlbs)
@@ -913,7 +1361,9 @@ static int ipmmu_find_utlbs(struct ipmmu_vmsa_device *mmu,
struct device *dev,
if (ret < 0)
return ret;
+#if 0 /* Xen: Not needed */
of_node_put(args.np);
+#endif
if (args.np != mmu->dev->of_node || args.args_count != 1)
return -EINVAL;
@@ -924,6 +1374,19 @@ static int ipmmu_find_utlbs(struct ipmmu_vmsa_device
*mmu, struct device *dev,
return 0;
}
+/* Xen: To roll back actions that took place it init */
+static __maybe_unused void ipmmu_destroy_platform_device(struct device *dev)
+{
+ struct ipmmu_vmsa_archdata *archdata = to_archdata(dev);
+
+ if (!archdata)
+ return;
+
+ kfree(archdata->utlbs);
+ kfree(archdata);
+ set_archdata(dev, NULL);
+}
+
static int ipmmu_init_platform_device(struct device *dev)
{
struct ipmmu_vmsa_archdata *archdata;
@@ -996,6 +1459,11 @@ static int ipmmu_init_platform_device(struct device *dev)
archdata->num_utlbs = num_utlbs;
archdata->dev = dev;
set_archdata(dev, archdata);
+
+ /* Xen: */
+ dev_notice(dev, "initialized master device (IPMMU %s micro-TLBs %u)\n",
+ dev_name(mmu->dev), num_utlbs);
+
return 0;
error:
@@ -1003,6 +1471,7 @@ error:
return ret;
}
+#if 0 /* Xen: Not needed */
#if defined(CONFIG_ARM) && !defined(CONFIG_IOMMU_DMA)
static int ipmmu_add_device(struct device *dev)
@@ -1233,6 +1702,7 @@ static const struct iommu_ops ipmmu_ops = {
};
#endif /* CONFIG_IOMMU_DMA */
+#endif
/*
-----------------------------------------------------------------------------
* Probe/remove and init
@@ -1274,12 +1744,20 @@ static const struct of_device_id ipmmu_of_ids[] = {
.compatible = "renesas,ipmmu-r8a7796",
.data = &ipmmu_features_rcar_gen3,
}, {
+ /* Xen: It is not clear how to deal with it */
+ .compatible = "renesas,ipmmu-pmb-r8a7795",
+ .data = NULL,
+ }, {
/* Terminator */
},
};
MODULE_DEVICE_TABLE(of, ipmmu_of_ids);
+/*
+ * Xen: We don't have refcount for allocated memory so manually free memory
+ * when an error occured.
+ */
static int ipmmu_probe(struct platform_device *pdev)
{
struct ipmmu_vmsa_device *mmu;
@@ -1303,13 +1781,17 @@ static int ipmmu_probe(struct platform_device *pdev)
spin_lock_init(&mmu->lock);
bitmap_zero(mmu->ctx, IPMMU_CTX_MAX);
mmu->features = match->data;
+#if 0 /* Xen: Not needed */
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+#endif
/* Map I/O memory and request IRQ. */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mmu->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(mmu->base))
- return PTR_ERR(mmu->base);
+ if (IS_ERR(mmu->base)) {
+ ret = PTR_ERR(mmu->base);
+ goto out;
+ }
/*
* The IPMMU has two register banks, for secure and non-secure modes.
@@ -1351,14 +1833,15 @@ static int ipmmu_probe(struct platform_device *pdev)
if (ipmmu_is_root(mmu)) {
if (irq < 0) {
dev_err(&pdev->dev, "no IRQ found\n");
- return irq;
+ ret = irq;
+ goto out;
}
ret = devm_request_irq(&pdev->dev, irq, ipmmu_irq, 0,
dev_name(&pdev->dev), mmu);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
- return ret;
+ goto out;
}
ipmmu_device_reset(mmu);
@@ -1374,11 +1857,25 @@ static int ipmmu_probe(struct platform_device *pdev)
list_add(&mmu->list, &ipmmu_devices);
spin_unlock(&ipmmu_devices_lock);
+#if 0 /* Xen: Not needed */
platform_set_drvdata(pdev, mmu);
+#endif
+
+ /* Xen: */
+ dev_notice(&pdev->dev, "registered %s IPMMU\n",
+ ipmmu_is_root(mmu) ? "root" : "cache");
return 0;
+
+out:
+ if (!IS_ERR(mmu->base))
+ iounmap(mmu->base);
+ kfree(mmu);
+
+ return ret;
}
+#if 0 /* Xen: Not needed */
static int ipmmu_remove(struct platform_device *pdev)
{
struct ipmmu_vmsa_device *mmu = platform_get_drvdata(pdev);
@@ -1645,3 +2142,418 @@ IOMMU_OF_DECLARE(ipmmu_r8a7796_iommu_of,
"renesas,ipmmu-r8a7796",
MODULE_DESCRIPTION("IOMMU API for Renesas VMSA-compatible IPMMU");
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>");
MODULE_LICENSE("GPL v2");
+#endif
+
+/***** Start of Xen specific code *****/
+
+static int __must_check ipmmu_vmsa_iotlb_flush(struct domain *d,
+ unsigned long gfn, unsigned int page_count)
+{
+ return 0;
+}
+
+static struct iommu_domain *ipmmu_vmsa_get_domain(struct domain *d,
+ struct device *dev)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ struct iommu_domain *io_domain;
+ struct ipmmu_vmsa_device *mmu;
+
+ mmu = to_archdata(dev)->mmu;
+ if (!mmu)
+ return NULL;
+
+ /*
+ * Loop through the &xen_domain->contexts to locate a context
+ * assigned to this IPMMU
+ */
+ list_for_each_entry(io_domain, &xen_domain->contexts, list) {
+ if (to_vmsa_domain(io_domain)->mmu == mmu)
+ return io_domain;
+ }
+
+ return NULL;
+}
+
+static void ipmmu_vmsa_destroy_domain(struct iommu_domain *io_domain)
+{
+ struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
+
+ list_del(&io_domain->list);
+
+ if (domain->mmu != domain->root) {
+ /*
+ * Disable the context for cache IPMMU only. Flush the TLB as
required
+ * when modifying the context registers.
+ */
+ ipmmu_ctx_write1(domain, IMCTR, IMCTR_FLUSH);
+ } else {
+ /*
+ * Free main domain resources. We assume that all devices have
already
+ * been detached.
+ */
+ ipmmu_domain_destroy_context(domain);
+ free_io_pgtable_ops(domain->iop);
+ }
+
+ kfree(domain);
+}
+
+static int ipmmu_vmsa_assign_dev(struct domain *d, u8 devfn,
+ struct device *dev, u32 flag)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ struct iommu_domain *io_domain;
+ struct ipmmu_vmsa_domain *domain;
+ int ret = 0;
+
+ if (!xen_domain || !xen_domain->base_context)
+ return -EINVAL;
+
+ if (!dev->archdata.iommu) {
+ dev->archdata.iommu = xzalloc(struct ipmmu_vmsa_xen_device);
+ if (!dev->archdata.iommu)
+ return -ENOMEM;
+ }
+
+ if (!to_archdata(dev)) {
+ ret = ipmmu_init_platform_device(dev);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock(&xen_domain->lock);
+
+ if (dev_iommu_domain(dev)) {
+ dev_err(dev, "already attached to IPMMU domain\n");
+ ret = -EEXIST;
+ goto out;
+ }
+
+ /*
+ * Check to see if a context bank (iommu_domain) already exists for
+ * this Xen domain under the same IPMMU
+ */
+ io_domain = ipmmu_vmsa_get_domain(d, dev);
+ if (!io_domain) {
+ domain = xzalloc(struct ipmmu_vmsa_domain);
+ if (!domain) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ spin_lock_init(&domain->lock);
+
+ domain->d = d;
+ domain->context_id =
to_vmsa_domain(xen_domain->base_context)->context_id;
+
+ io_domain = &domain->io_domain;
+
+ /* Chain the new context to the Xen domain */
+ list_add(&io_domain->list, &xen_domain->contexts);
+ }
+
+ ret = ipmmu_attach_device(io_domain, dev);
+ if (ret) {
+ if (io_domain->ref.counter == 0)
+ ipmmu_vmsa_destroy_domain(io_domain);
+ } else {
+ atomic_inc(&io_domain->ref);
+ dev_iommu_domain(dev) = io_domain;
+ }
+
+out:
+ spin_unlock(&xen_domain->lock);
+
+ return ret;
+}
+
+static int ipmmu_vmsa_deassign_dev(struct domain *d, struct device *dev)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ struct iommu_domain *io_domain = dev_iommu_domain(dev);
+
+ if (!io_domain || to_vmsa_domain(io_domain)->d != d) {
+ dev_err(dev, " not attached to domain %d\n", d->domain_id);
+ return -ESRCH;
+ }
+
+ spin_lock(&xen_domain->lock);
+
+ ipmmu_detach_device(io_domain, dev);
+ dev_iommu_domain(dev) = NULL;
+ atomic_dec(&io_domain->ref);
+
+ if (io_domain->ref.counter == 0)
+ ipmmu_vmsa_destroy_domain(io_domain);
+
+ spin_unlock(&xen_domain->lock);
+
+ return 0;
+}
+
+static int ipmmu_vmsa_reassign_dev(struct domain *s, struct domain *t,
+ u8 devfn, struct device *dev)
+{
+ int ret = 0;
+
+ /* Don't allow remapping on other domain than hwdom */
+ if (t && t != hardware_domain)
+ return -EPERM;
+
+ if (t == s)
+ return 0;
+
+ ret = ipmmu_vmsa_deassign_dev(s, dev);
+ if (ret)
+ return ret;
+
+ if (t) {
+ /* No flags are defined for ARM. */
+ ret = ipmmu_vmsa_assign_dev(t, devfn, dev, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ipmmu_vmsa_alloc_page_table(struct domain *d)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ struct ipmmu_vmsa_domain *domain;
+ struct ipmmu_vmsa_device *root;
+ int ret;
+
+ if (xen_domain->base_context)
+ return 0;
+
+ root = ipmmu_find_root(NULL);
+ if (!root) {
+ printk("d%d: Unable to locate root IPMMU\n", d->domain_id);
+ return -EAGAIN;
+ }
+
+ domain = xzalloc(struct ipmmu_vmsa_domain);
+ if (!domain)
+ return -ENOMEM;
+
+ spin_lock_init(&domain->lock);
+ INIT_LIST_HEAD(&domain->io_domain.list);
+ domain->d = d;
+ domain->root = root;
+
+ spin_lock(&xen_domain->lock);
+ ret = ipmmu_domain_init_context(domain);
+ if (ret < 0) {
+ dev_err(root->dev, "d%d: Unable to initialize IPMMU context\n",
+ d->domain_id);
+ spin_unlock(&xen_domain->lock);
+ xfree(domain);
+ return ret;
+ }
+ xen_domain->base_context = &domain->io_domain;
+ spin_unlock(&xen_domain->lock);
+
+ return 0;
+}
+
+static int ipmmu_vmsa_domain_init(struct domain *d, bool use_iommu)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain;
+
+ xen_domain = xzalloc(struct ipmmu_vmsa_xen_domain);
+ if (!xen_domain)
+ return -ENOMEM;
+
+ spin_lock_init(&xen_domain->lock);
+ INIT_LIST_HEAD(&xen_domain->contexts);
+
+ dom_iommu(d)->arch.priv = xen_domain;
+
+ if (use_iommu) {
+ int ret = ipmmu_vmsa_alloc_page_table(d);
+
+ if (ret) {
+ xfree(xen_domain);
+ dom_iommu(d)->arch.priv = NULL;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void __hwdom_init ipmmu_vmsa_hwdom_init(struct domain *d)
+{
+}
+
+static void ipmmu_vmsa_domain_teardown(struct domain *d)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+
+ if (!xen_domain)
+ return;
+
+ spin_lock(&xen_domain->lock);
+ if (xen_domain->base_context) {
+ ipmmu_vmsa_destroy_domain(xen_domain->base_context);
+ xen_domain->base_context = NULL;
+ }
+ spin_unlock(&xen_domain->lock);
+
+ ASSERT(list_empty(&xen_domain->contexts));
+ xfree(xen_domain);
+ dom_iommu(d)->arch.priv = NULL;
+}
+
+static int __must_check ipmmu_vmsa_map_pages(struct domain *d,
+ unsigned long gfn, unsigned long mfn, unsigned int order,
+ unsigned int flags)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ size_t size = PAGE_SIZE * (1UL << order);
+ int ret, prot = 0;
+
+ if (!xen_domain || !xen_domain->base_context)
+ return -EINVAL;
+
+ if (flags & IOMMUF_writable)
+ prot |= IOMMU_WRITE;
+ if (flags & IOMMUF_readable)
+ prot |= IOMMU_READ;
+
+ spin_lock(&xen_domain->lock);
+ ret = ipmmu_map(xen_domain->base_context, pfn_to_paddr(gfn),
+ pfn_to_paddr(mfn), size, prot);
+ spin_unlock(&xen_domain->lock);
+
+ return ret;
+}
+
+static int __must_check ipmmu_vmsa_unmap_pages(struct domain *d,
+ unsigned long gfn, unsigned int order)
+{
+ struct ipmmu_vmsa_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ size_t ret, size = PAGE_SIZE * (1UL << order);
+
+ if (!xen_domain || !xen_domain->base_context)
+ return -EINVAL;
+
+ spin_lock(&xen_domain->lock);
+ ret = ipmmu_unmap(xen_domain->base_context, pfn_to_paddr(gfn), size);
+ spin_unlock(&xen_domain->lock);
+
+ /*
+ * We don't check how many bytes were actually unmapped. Otherwise we
+ * should have raised an error every time we hit an area that isn't
mapped.
+ * And the p2m's attempt to unmap the same page twice can lead to crash
or
+ * panic. We think it is better to have corresponding warns inside
+ * page table allocator for complaining about that rather than
+ * breaking the whole system.
+ */
+ return IS_ERR_VALUE(ret) ? ret : 0;
+}
+
+static void ipmmu_vmsa_dump_p2m_table(struct domain *d)
+{
+ /* TODO: This platform callback should be implemented. */
+}
+
+static const struct iommu_ops ipmmu_vmsa_iommu_ops = {
+ .init = ipmmu_vmsa_domain_init,
+ .hwdom_init = ipmmu_vmsa_hwdom_init,
+ .teardown = ipmmu_vmsa_domain_teardown,
+ .iotlb_flush = ipmmu_vmsa_iotlb_flush,
+ .assign_device = ipmmu_vmsa_assign_dev,
+ .reassign_device = ipmmu_vmsa_reassign_dev,
+ .map_pages = ipmmu_vmsa_map_pages,
+ .unmap_pages = ipmmu_vmsa_unmap_pages,
+ .dump_p2m_table = ipmmu_vmsa_dump_p2m_table,
+};
+
+static __init const struct ipmmu_vmsa_device *find_ipmmu(const struct device
*dev)
+{
+ struct ipmmu_vmsa_device *mmu;
+ bool found = false;
+
+ spin_lock(&ipmmu_devices_lock);
+ list_for_each_entry(mmu, &ipmmu_devices, list) {
+ if (mmu->dev == dev) {
+ found = true;
+ break;
+ }
+ }
+ spin_unlock(&ipmmu_devices_lock);
+
+ return (found) ? mmu : NULL;
+}
+
+static __init void populate_ipmmu_masters(const struct ipmmu_vmsa_device *mmu)
+{
+ struct dt_device_node *np;
+
+ dt_for_each_device_node(dt_host, np) {
+ if (mmu->dev->of_node != dt_parse_phandle(np, "iommus", 0))
+ continue;
+
+ /* Let Xen know that the device is protected by an IPMMU */
+ dt_device_set_protected(np);
+
+ dev_notice(mmu->dev, "found master device %s\n",
dt_node_full_name(np));
+ }
+}
+
+/* TODO: What to do if we failed to init cache/root IPMMU? */
+static __init int ipmmu_vmsa_init(struct dt_device_node *dev,
+ const void *data)
+{
+ int rc;
+ const struct ipmmu_vmsa_device *mmu;
+ static bool set_ops_done = false;
+
+ /*
+ * Even if the device can't be initialized, we don't want to
+ * give the IPMMU device to dom0.
+ */
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ rc = ipmmu_probe(dev);
+ if (rc) {
+ dev_err(&dev->dev, "failed to init IPMMU\n");
+ return rc;
+ }
+
+ /*
+ * Since IPMMU is composed of two parts (a number of cache IPMMUs and
+ * the root IPMMU) this function will be called more than once.
+ * Use the flag below to avoid setting IOMMU ops if they already set.
+ */
+ if (!set_ops_done) {
+ iommu_set_ops(&ipmmu_vmsa_iommu_ops);
+ set_ops_done = true;
+ }
+
+ /* Find the last IPMMU added. */
+ mmu = find_ipmmu(dt_to_dev(dev));
+ BUG_ON(mmu == NULL);
+
+ /* Mark all masters that connected to the last IPMMU as protected. */
+ populate_ipmmu_masters(mmu);
+
+ /*
+ * The IPMMU can't utilize P2M table since it doesn't use the same
+ * page-table format as the CPU.
+ */
+ if (iommu_hap_pt_share) {
+ iommu_hap_pt_share = false;
+ dev_notice(&dev->dev,
+ "disable sharing P2M table between the CPU and
IPMMU\n");
+ }
+
+ return 0;
+}
+
+DT_DEVICE_START(ipmmu, "Renesas IPMMU-VMSA", DEVICE_IOMMU)
+ .dt_match = ipmmu_of_ids,
+ .init = ipmmu_vmsa_init,
+DT_DEVICE_END
--
2.7.4
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |