@@ -1454,6 +1454,7 @@ err:
static void __init setup_virt_paging_one(void *data)
{
unsigned long val = (unsigned long)data;
+ /* SMMUv3 S2 cfg vtcr reuses the following value */
WRITE_SYSREG32(val, VTCR_EL2);
isb();
}
@@ -15,4 +15,6 @@ source "drivers/video/Kconfig"
config HAS_VPCI
bool
+source "drivers/passthrough/arm/Kconfig"
+
endmenu
new file mode 100644
@@ -0,0 +1,8 @@
+
+config ARM_SMMU_v3
+ bool "ARM SMMUv3 Support"
+ depends on ARM_64
+ help
+ Support for implementations of the ARM System MMU architecture
+ version 3.
+
@@ -1,2 +1,3 @@
obj-y += iommu.o
obj-y += smmu.o
+obj-$(CONFIG_ARM_SMMU_v3) += smmu-v3.o
@@ -18,28 +18,414 @@
* Author: Will Deacon <will.deacon@arm.com>
*
* This driver is powered by bad coffee and bombay mix.
+ *
+ *
+ * Based on Linux drivers/iommu/arm-smmu-v3.c
+ * => commit 7aa8619a66aea52b145e04cbab4f8d6a4e5f3f3b
+ *
+ * Xen modifications:
+ * Sameer Goel <sameer.goel@linaro.org>
+ * Copyright (C) 2017, The Linux Foundation, All rights reserved.
+ *
*/
-#include <linux/acpi.h>
-#include <linux/acpi_iort.h>
-#include <linux/delay.h>
-#include <linux/dma-iommu.h>
-#include <linux/err.h>
-#include <linux/interrupt.h>
-#include <linux/iommu.h>
-#include <linux/iopoll.h>
-#include <linux/module.h>
-#include <linux/msi.h>
-#include <linux/of.h>
-#include <linux/of_address.h>
-#include <linux/of_iommu.h>
-#include <linux/of_platform.h>
-#include <linux/pci.h>
-#include <linux/platform_device.h>
-
-#include <linux/amba/bus.h>
-
-#include "io-pgtable.h"
+#include <xen/acpi.h>
+#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/linux-compat.h>
+#include <xen/list.h>
+#include <xen/mm.h>
+#include <xen/rbtree.h>
+#include <xen/sched.h>
+#include <xen/sizes.h>
+#include <xen/vmap.h>
+#include <asm/acpi/acpi_iort.h>
+#include <asm/atomic.h>
+#include <asm/device.h>
+#include <asm/io.h>
+#include <asm/platform.h>
+
+/* 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_property_read_u32(np, pname, out) (!dt_property_read_u32(np, pname, out))
+#define of_property_read_bool dt_property_read_bool
+#define of_parse_phandle_with_args dt_parse_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 device
+
+#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;
+ struct acpi_iort_node *iort_node;
+ struct acpi_iort_smmu_v3 *node_smmu_data;
+ int ret = 0;
+
+ res.type = type;
+
+ switch (type) {
+ case IORESOURCE_MEM:
+ if (pdev->type == DEV_ACPI) {
+ ret = 1;
+ iort_node = pdev->acpi_node;
+ node_smmu_data =
+ (struct acpi_iort_smmu_v3 *)iort_node->node_data;
+
+ if (node_smmu_data != NULL) {
+ res.addr = node_smmu_data->base_address;
+ res.size = SZ_128K;
+ ret = 0;
+ }
+ } else {
+ ret = dt_device_get_address(dev_to_dt(pdev), num,
+ &res.addr, &res.size);
+ }
+
+ return ((ret) ? NULL : &res);
+
+ case IORESOURCE_IRQ:
+ /* ACPI case not implemented as there is no use case for it */
+ ret = platform_get_irq(dev_to_dt(pdev), num);
+
+ if (ret < 0)
+ return NULL;
+
+ res.addr = ret;
+ res.size = 1;
+
+ return &res;
+
+ default:
+ return NULL;
+ }
+}
+
+static int platform_get_irq_byname(struct platform_device *pdev, const char *name)
+{
+ const struct dt_property *dtprop;
+ struct acpi_iort_node *iort_node;
+ struct acpi_iort_smmu_v3 *node_smmu_data;
+ int ret = 0;
+
+ if (pdev->type == DEV_ACPI) {
+ iort_node = pdev->acpi_node;
+ node_smmu_data = (struct acpi_iort_smmu_v3 *)iort_node->node_data;
+
+ if (node_smmu_data != NULL) {
+ if (!strcmp(name, "eventq"))
+ ret = node_smmu_data->event_gsiv;
+ else if (!strcmp(name, "priq"))
+ ret = node_smmu_data->pri_gsiv;
+ else if (!strcmp(name, "cmdq-sync"))
+ ret = node_smmu_data->sync_gsiv;
+ else if (!strcmp(name, "gerror"))
+ ret = node_smmu_data->gerr_gsiv;
+ else
+ ret = -EINVAL;
+ }
+ } else {
+ dtprop = dt_find_property(dev_to_dt(pdev), "interrupt-names", NULL);
+ if (!dtprop)
+ return -EINVAL;
+
+ if (!dtprop->value)
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+/*
+ * Xen: Helpers for DMA allocation. Just the function name is reused for
+ * porting code these allocation are not managed allocations
+ */
+
+static void *dmam_alloc_coherent(struct device *dev, size_t size,
+ dma_addr_t *dma_handle, gfp_t gfp)
+{
+ void *vaddr;
+ unsigned long alignment = size;
+
+ /*
+ * _xzalloc requires that the (align & (align -1)) = 0. Most of the
+ * allocations in SMMU code should send the right value for size. In
+ * case this is not true print a warning and align to the size of a
+ * (void *)
+ */
+ if (size & (size - 1)) {
+ dev_warn(dev, "Fixing alignment for the DMA buffer\n");
+ alignment = sizeof(void *);
+ }
+
+ vaddr = _xzalloc(size, alignment);
+ if (!vaddr) {
+ dev_err(dev, "DMA allocation failed\n");
+ return NULL;
+ }
+
+ *dma_handle = virt_to_maddr(vaddr);
+
+ return vaddr;
+}
+
+
+static void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,
+ dma_addr_t dma_handle)
+{
+ xfree(vaddr);
+}
+
+/* Xen: Stub out DMA domain related functions */
+#define iommu_get_dma_cookie(dom) 0
+#define iommu_put_dma_cookie(dom)
+
+/* Xen: Stub out module param related function */
+#define module_param_named(a, b, c, d)
+#define MODULE_PARM_DESC(a, b)
+
+#define dma_set_mask_and_coherent(d, b) 0
+
+#define of_dma_is_coherent(n) 0
+
+#define MODULE_DEVICE_TABLE(type, name)
+
+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: Compatibility define for iommu_domain_geometry.*/
+struct iommu_domain_geometry {
+ dma_addr_t aperture_start; /* First address that can be mapped */
+ dma_addr_t aperture_end; /* Last address that can be mapped */
+ bool force_aperture; /* DMA only allowed in mappable range? */
+};
+
+
+/* Xen: Type definitions for iommu_domain */
+#define IOMMU_DOMAIN_UNMANAGED 0
+#define IOMMU_DOMAIN_DMA 1
+#define IOMMU_DOMAIN_IDENTITY 2
+
+/* Xen: Dummy iommu_domain */
+struct iommu_domain {
+ /* Runtime SMMU configuration for this iommu_domain */
+ struct arm_smmu_domain *priv;
+ unsigned int type;
+
+ /* Dummy compatibility defines */
+ unsigned long pgsize_bitmap;
+ struct iommu_domain_geometry geometry;
+
+ atomic_t ref;
+ /*
+ * Used to link iommu_domain contexts for a same domain.
+ * There is at least one per-SMMU to used by the domain.
+ */
+ struct list_head list;
+};
+
+/* Xen: Describes information required for a Xen domain */
+struct arm_smmu_xen_domain {
+ spinlock_t lock;
+ /* List of iommu domains associated to this domain */
+ struct list_head contexts;
+};
+
+/*
+ * Xen: Information about each device stored in dev->archdata.iommu
+ *
+ * The dev->archdata.iommu stores the iommu_domain (runtime configuration of
+ * the SMMU).
+ */
+struct arm_smmu_xen_device {
+ struct iommu_domain *domain;
+};
+
+/*
+ * Xen: io_pgtable compatibility defines.
+ * Most of these are to port in the S1 translation code as is.
+ */
+struct io_pgtable_ops {
+};
+
+struct iommu_gather_ops {
+ void (*tlb_flush_all)(void *cookie);
+ void (*tlb_add_flush)(unsigned long iova, size_t size, size_t granule,
+ bool leaf, void *cookie);
+ void (*tlb_sync)(void *cookie);
+};
+
+struct io_pgtable_cfg {
+ /*
+ * IO_PGTABLE_QUIRK_ARM_NS: (ARM formats) Set NS and NSTABLE bits in
+ * stage 1 PTEs, for hardware which insists on validating them
+ * even in non-secure state where they should normally be ignored.
+ *
+ * IO_PGTABLE_QUIRK_NO_PERMS: Ignore the IOMMU_READ, IOMMU_WRITE and
+ * IOMMU_NOEXEC flags and map everything with full access, for
+ * hardware which does not implement the permissions of a given
+ * format, and/or requires some format-specific default value.
+ *
+ * IO_PGTABLE_QUIRK_TLBI_ON_MAP: If the format forbids caching invalid
+ * (unmapped) entries but the hardware might do so anyway, perform
+ * TLB maintenance when mapping as well as when unmapping.
+ *
+ * IO_PGTABLE_QUIRK_ARM_MTK_4GB: (ARM v7s format) Set bit 9 in all
+ * PTEs, for Mediatek IOMMUs which treat it as a 33rd address bit
+ * when the SoC is in "4GB mode" and they can only access the high
+ * remap of DRAM (0x1_00000000 to 0x1_ffffffff).
+ *
+ * IO_PGTABLE_QUIRK_NO_DMA: Guarantees that the tables will only ever
+ * be accessed by a fully cache-coherent IOMMU or CPU (e.g. for a
+ * software-emulated IOMMU), such that pagetable updates need not
+ * be treated as explicit DMA data.
+ */
+ #define IO_PGTABLE_QUIRK_ARM_NS BIT(0)
+ #define IO_PGTABLE_QUIRK_NO_PERMS BIT(1)
+ #define IO_PGTABLE_QUIRK_TLBI_ON_MAP BIT(2)
+ #define IO_PGTABLE_QUIRK_ARM_MTK_4GB BIT(3)
+ #define IO_PGTABLE_QUIRK_NO_DMA BIT(4)
+ unsigned long quirks;
+ unsigned long pgsize_bitmap;
+ unsigned int ias;
+ unsigned int oas;
+ const struct iommu_gather_ops *tlb;
+ struct device *iommu_dev;
+
+ /* Low-level data specific to the table format */
+ union {
+ struct {
+ u64 ttbr[2];
+ u64 tcr;
+ u64 mair[2];
+ } arm_lpae_s1_cfg;
+
+ struct {
+ u64 vttbr;
+ u64 vtcr;
+ } arm_lpae_s2_cfg;
+
+ struct {
+ u32 ttbr[2];
+ u32 tcr;
+ u32 nmrr;
+ u32 prrr;
+ } arm_v7s_cfg;
+ };
+};
+
+enum io_pgtable_fmt {
+ ARM_32_LPAE_S1,
+ ARM_32_LPAE_S2,
+ ARM_64_LPAE_S1,
+ ARM_64_LPAE_S2,
+ ARM_V7S,
+ IO_PGTABLE_NUM_FMTS,
+};
+
+/*
+ * Xen: The pgtable_ops are used by the S1 translations, so return the dummy
+ * address.
+ */
+#define alloc_io_pgtable_ops(f, c, o) ((struct io_pgtable_ops *)0x1)
+#define free_io_pgtable_ops(o)
+
+/* Xen: Define wrapper for requesting IRQs */
+#define IRQF_ONESHOT 0
+
+typedef void (*irq_handler_t)(int, void *, struct cpu_user_regs *);
+
+static inline int devm_request_irq(struct device *dev, unsigned int irq,
+ irq_handler_t handler, unsigned long irqflags,
+ const char *devname, void *dev_id)
+{
+ /*
+ * SMMUv3 implementation can support wired interrupt outputs that are
+ * edge-triggered. Set the irq type as per the spec.
+ */
+ irq_set_type(irq, IRQ_TYPE_EDGE_BOTH);
+ return request_irq(irq, irqflags, handler, devname, dev_id);
+}
+
+/*
+ * Xen does not have a concept of threaded irq, but we can use tasklets to
+ * achieve the desired functionality as needed.
+ */
+int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
+ irq_handler_t thread_fn, unsigned long irqflags,
+ const char *devname, void *dev_id)
+{
+ return devm_request_irq(dev, irq, thread_fn, irqflags, devname, dev_id);
+}
+
+/* Xen: The mutex is used only during initialization so the typecast is safe */
+#define mutex spinlock
+#define mutex_init spin_lock_init
+#define mutex_lock spin_lock
+#define mutex_unlock spin_unlock
+
+#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us) \
+({ \
+ s_time_t deadline = NOW() + MICROSECS(timeout_us); \
+ for (;;) { \
+ (val) = op(addr); \
+ if (cond) \
+ break; \
+ if (NOW() > deadline) { \
+ (val) = op(addr); \
+ break; \
+ } \
+ udelay(sleep_us); \
+ } \
+ (cond) ? 0 : -ETIMEDOUT; \
+})
+
+#define readl_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \
+ readx_poll_timeout(readl_relaxed, addr, val, cond, delay_us, timeout_us)
+
+#define VA_BITS 0 /* Only needed for S1 translations */
/* MMIO registers */
#define ARM_SMMU_IDR0 0x0
@@ -433,6 +819,7 @@ enum pri_resp {
PRI_RESP_SUCC,
};
+#if 0 /* Xen: No MSI support in this iteration */
enum arm_smmu_msi_index {
EVTQ_MSI_INDEX,
GERROR_MSI_INDEX,
@@ -457,6 +844,7 @@ static phys_addr_t arm_smmu_msi_cfg[ARM_SMMU_MAX_MSIS][3] = {
ARM_SMMU_PRIQ_IRQ_CFG2,
},
};
+#endif
struct arm_smmu_cmdq_ent {
/* Common fields */
@@ -561,6 +949,8 @@ struct arm_smmu_s2_cfg {
u16 vmid;
u64 vttbr;
u64 vtcr;
+ /* Xen: Domain associated to this configuration */
+ struct domain *domain;
};
struct arm_smmu_strtab_ent {
@@ -635,9 +1025,25 @@ struct arm_smmu_device {
struct arm_smmu_strtab_cfg strtab_cfg;
/* IOMMU core code handle */
+#if 0 /*Xen: Generic iommu_device ref not needed here */
struct iommu_device iommu;
+#endif
+ /* Xen: Need to keep a list of SMMU devices */
+ struct list_head devices;
+ /* Xen: Tasklets for handling evts/faults and pci page request IRQs*/
+ struct tasklet evtq_tasklet;
+ struct tasklet priq_tasklet;
+ struct tasklet combined_irq_tasklet;
};
+/* Xen: Keep a list of devices associated with this driver */
+static DEFINE_SPINLOCK(arm_smmu_devices_lock);
+static LIST_HEAD(arm_smmu_devices);
+/* Xen: Helper for finding a device using fwnode */
+static
+struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode);
+
+
/* SMMU private data for each master */
struct arm_smmu_master_data {
struct arm_smmu_device *smmu;
@@ -1232,7 +1638,7 @@ static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt)
dev_info(smmu->dev, "unexpected PRI request received:\n");
dev_info(smmu->dev,
- "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova 0x%016llx\n",
+ "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova %#" PRIx64 "\n",
sid, ssid, grpid, last ? "L" : "",
evt[0] & PRIQ_0_PERM_PRIV ? "" : "un",
evt[0] & PRIQ_0_PERM_READ ? "R" : "",
@@ -1342,10 +1748,20 @@ static irqreturn_t arm_smmu_combined_irq_thread(int irq, void *dev)
return IRQ_HANDLED;
}
+/* Xen: Forward define for combined_irq tasklet */
+static void arm_smmu_combined_irq_tasklet(unsigned long dev);
+
static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
{
+ /* Xen: Need an smmu reference to schedule the tasklet */
+ struct arm_smmu_device *smmu = (struct arm_smmu_device *)dev;
+
arm_smmu_gerror_handler(irq, dev);
arm_smmu_cmdq_sync_handler(irq, dev);
+
+ /*Xen: No threaded irq. So, schedule the right tasklet*/
+ tasklet_schedule(&(smmu->combined_irq_tasklet));
+
return IRQ_WAKE_THREAD;
}
@@ -1358,6 +1774,69 @@ static void __arm_smmu_tlb_sync(struct arm_smmu_device *smmu)
arm_smmu_cmdq_issue_cmd(smmu, &cmd);
}
+/*
+ * Xen: Define the IRQ handlers and tasklets for xen. The linux functions
+ * would be modified to use the functions defined in the following code.
+ */
+
+static void arm_smmu_evtq_tasklet(unsigned long dev)
+{
+ /* The IRQ number is not relevent for the evtq thread processing */
+ arm_smmu_evtq_thread(0, (void *)dev);
+}
+
+static void arm_smmu_priq_tasklet(unsigned long dev)
+{
+ /* The IRQ number is not relevent for the priq thread processing */
+ arm_smmu_priq_thread(0, (void *)dev);
+}
+
+static void arm_smmu_combined_irq_tasklet(unsigned long dev)
+{
+ /* The IRQ number is not relevent for the combined irq handler.*/
+ arm_smmu_combined_irq_thread(0, (void *)dev);
+}
+
+static void arm_smmu_evtq_thread_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ struct arm_smmu_device *smmu = (struct arm_smmu_device *)dev;
+
+ tasklet_schedule(&(smmu->evtq_tasklet));
+}
+
+static void arm_smmu_priq_thread_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ struct arm_smmu_device *smmu = (struct arm_smmu_device *)dev;
+
+ tasklet_schedule(&(smmu->priq_tasklet));
+}
+
+static void arm_smmu_cmdq_sync_handler_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ arm_smmu_cmdq_sync_handler(irq, dev);
+}
+
+static void arm_smmu_gerror_handler_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ arm_smmu_gerror_handler(irq, dev);
+}
+
+static void arm_smmu_combined_irq_handler_xen(int irq, void *dev,
+ struct cpu_user_regs *regs)
+{
+ arm_smmu_combined_irq_handler(irq, dev);
+}
+
+#define arm_smmu_evtq_thread arm_smmu_evtq_thread_xen
+#define arm_smmu_priq_thread arm_smmu_priq_thread_xen
+#define arm_smmu_cmdq_sync_handler arm_smmu_cmdq_sync_handler_xen
+#define arm_smmu_gerror_handler arm_smmu_gerror_handler_xen
+#define arm_smmu_combined_irq_handler arm_smmu_combined_irq_handler_xen
+
static void arm_smmu_tlb_sync(void *cookie)
{
struct arm_smmu_domain *smmu_domain = cookie;
@@ -1415,6 +1894,7 @@ static const struct iommu_gather_ops arm_smmu_gather_ops = {
.tlb_sync = arm_smmu_tlb_sync,
};
+#if 0 /*Xen: Unused functionality */
/* IOMMU API */
static bool arm_smmu_capable(enum iommu_cap cap)
{
@@ -1427,6 +1907,7 @@ static bool arm_smmu_capable(enum iommu_cap cap)
return false;
}
}
+#endif
static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
{
@@ -1546,9 +2027,16 @@ static int arm_smmu_domain_finalise_s2(struct arm_smmu_domain *smmu_domain,
if (vmid < 0)
return vmid;
- cfg->vmid = (u16)vmid;
- cfg->vttbr = pgtbl_cfg->arm_lpae_s2_cfg.vttbr;
- cfg->vtcr = pgtbl_cfg->arm_lpae_s2_cfg.vtcr;
+ /*
+ * Xen: Get the ttbr and vtcr values
+ * vttbr: This is a shared value with the domain page table
+ * vtcr: The TCR settings are the same as CPU since the page
+ * tables are shared
+ */
+
+ cfg->vmid = vmid;
+ cfg->vttbr = page_to_maddr(cfg->domain->arch.p2m.root);
+ cfg->vtcr = READ_SYSREG32(VTCR_EL2) & STRTAB_STE_2_VTCR_MASK;
return 0;
}
@@ -1604,6 +2092,7 @@ static int arm_smmu_domain_finalise(struct iommu_domain *domain)
if (smmu->features & ARM_SMMU_FEAT_COHERENCY)
pgtbl_cfg.quirks = IO_PGTABLE_QUIRK_NO_DMA;
+ /* Xen: pgtbl_ops gets an invalid address */
pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, smmu_domain);
if (!pgtbl_ops)
return -ENOMEM;
@@ -1721,6 +2210,7 @@ out_unlock:
return ret;
}
+#if 0 /* Xen: Unused functionality */
static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot)
{
@@ -1772,6 +2262,7 @@ struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode)
put_device(dev);
return dev ? dev_get_drvdata(dev) : NULL;
}
+#endif
static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
{
@@ -1783,7 +2274,14 @@ static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
return sid < limit;
}
+/* Xen: Unused */
+#if 0
static struct iommu_ops arm_smmu_ops;
+#endif
+
+/* Xen: Redefine arm_smmu_ops to what fwspec should evaluate */
+static const struct iommu_ops arm_smmu_iommu_ops;
+#define arm_smmu_ops arm_smmu_iommu_ops
static int arm_smmu_add_device(struct device *dev)
{
@@ -1791,8 +2289,11 @@ static int arm_smmu_add_device(struct device *dev)
struct arm_smmu_device *smmu;
struct arm_smmu_master_data *master;
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
+#if 0 /*Xen: iommu_group is not needed */
struct iommu_group *group;
+#endif
+ /* Xen: fwspec->ops are not needed */
if (!fwspec || fwspec->ops != &arm_smmu_ops)
return -ENODEV;
/*
@@ -1830,6 +2331,11 @@ static int arm_smmu_add_device(struct device *dev)
}
}
+/*
+ * Xen: Do not need an iommu group as the stream data is carried by the SMMU
+ * master device object
+ */
+#if 0
group = iommu_group_get_for_dev(dev);
if (!IS_ERR(group)) {
iommu_group_put(group);
@@ -1837,8 +2343,16 @@ static int arm_smmu_add_device(struct device *dev)
}
return PTR_ERR_OR_ZERO(group);
+#endif
+ return 0;
}
+/*
+ * Xen: We can potentially support this function and destroy a device. This
+ * will be relevant for PCI hotplug. So, will be implemented as needed after
+ * passthrough support is available.
+ */
+#if 0
static void arm_smmu_remove_device(struct device *dev)
{
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
@@ -1974,6 +2488,7 @@ static struct iommu_ops arm_smmu_ops = {
.put_resv_regions = arm_smmu_put_resv_regions,
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
+#endif
/* Probing and initialisation functions */
static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
@@ -2182,6 +2697,7 @@ static int arm_smmu_update_gbpa(struct arm_smmu_device *smmu, u32 set, u32 clr)
1, ARM_SMMU_POLL_TIMEOUT_US);
}
+#if 0 /* Xen: There is no MSI support as yet */
static void arm_smmu_free_msis(void *data)
{
struct device *dev = data;
@@ -2247,12 +2763,15 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
/* Add callback to free MSIs on teardown */
devm_add_action(dev, arm_smmu_free_msis, dev);
}
+#endif
static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
{
int irq, ret;
+#if 0 /*Xen: Cannot setup msis for now */
arm_smmu_setup_msis(smmu);
+#endif
/* Request interrupt lines */
irq = smmu->evtq.q.irq;
@@ -2316,9 +2835,13 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
* Cavium ThunderX2 implementation doesn't not support unique
* irq lines. Use single irq line for all the SMMUv3 interrupts.
*/
- ret = devm_request_threaded_irq(smmu->dev, irq,
+ /*
+ * Xen: Does not support threaded irqs, so serialise the setup.
+ * This is the same for pris and event interrupt lines on other
+ * systems
+ */
+ ret = devm_request_irq(smmu->dev, irq,
arm_smmu_combined_irq_handler,
- arm_smmu_combined_irq_thread,
IRQF_ONESHOT,
"arm-smmu-v3-combined-irq", smmu);
if (ret < 0)
@@ -2452,6 +2975,13 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
return ret;
}
+ /* Xen: Initialize tasklets */
+ tasklet_init(&smmu->evtq_tasklet, arm_smmu_evtq_tasklet,
+ (unsigned long)smmu);
+ tasklet_init(&smmu->priq_tasklet, arm_smmu_priq_tasklet,
+ (unsigned long)smmu);
+ tasklet_init(&smmu->combined_irq_tasklet, arm_smmu_combined_irq_tasklet,
+ (unsigned long)smmu);
/* Enable the SMMU interface, or ensure bypass */
if (!bypass || disable_bypass) {
@@ -2542,8 +3072,14 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
smmu->features |= ARM_SMMU_FEAT_STALLS;
}
+/*
+ * Xen: Block stage 1 translations. By doing this here we do not need to set the
+ * domain->stage explicitly.
+ */
+#if 0
if (reg & IDR0_S1P)
smmu->features |= ARM_SMMU_FEAT_TRANS_S1;
+#endif
if (reg & IDR0_S2P)
smmu->features |= ARM_SMMU_FEAT_TRANS_S2;
@@ -2616,10 +3152,12 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
if (reg & IDR5_GRAN4K)
smmu->pgsize_bitmap |= SZ_4K | SZ_2M | SZ_1G;
+#if 0 /* Xen: SMMU ops do not have a pgsize_bitmap member for Xen */
if (arm_smmu_ops.pgsize_bitmap == -1UL)
arm_smmu_ops.pgsize_bitmap = smmu->pgsize_bitmap;
else
arm_smmu_ops.pgsize_bitmap |= smmu->pgsize_bitmap;
+#endif
/* Output address size */
switch (reg & IDR5_OAS_MASK << IDR5_OAS_SHIFT) {
@@ -2680,7 +3218,8 @@ static int arm_smmu_device_acpi_probe(struct platform_device *pdev,
struct device *dev = smmu->dev;
struct acpi_iort_node *node;
- node = *(struct acpi_iort_node **)dev_get_platdata(dev);
+ /* Xen: Modification to get iort_node */
+ node = (struct acpi_iort_node *)dev->acpi_node;
/* Retrieve SMMUv3 specific data */
iort_smmu = (struct acpi_iort_smmu_v3 *)node->node_data;
@@ -2703,7 +3242,7 @@ static inline int arm_smmu_device_acpi_probe(struct platform_device *pdev,
static int arm_smmu_device_dt_probe(struct platform_device *pdev,
struct arm_smmu_device *smmu)
{
- struct device *dev = &pdev->dev;
+ struct device *dev = pdev;
u32 cells;
int ret = -EINVAL;
@@ -2716,6 +3255,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev,
parse_driver_options(smmu);
+ /* Xen: of_dma_is_coherent is a stub till dt support is introduced */
if (of_dma_is_coherent(dev->of_node))
smmu->features |= ARM_SMMU_FEAT_COHERENCY;
@@ -2734,9 +3274,11 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
{
int irq, ret;
struct resource *res;
+#if 0 /*Xen: Do not need to setup sysfs */
resource_size_t ioaddr;
+#endif
struct arm_smmu_device *smmu;
- struct device *dev = &pdev->dev;
+ struct device *dev = pdev;/* Xen: dev is ignored */
bool bypass;
smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL);
@@ -2763,7 +3305,9 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
dev_err(dev, "MMIO region too small (%pr)\n", res);
return -EINVAL;
}
+#if 0 /*Xen: Do not need to setup sysfs */
ioaddr = res->start;
+#endif
smmu->base = devm_ioremap_resource(dev, res);
if (IS_ERR(smmu->base))
@@ -2802,13 +3346,18 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
return ret;
/* Record our private device structure */
+ /* Xen: SMMU is not treated a a platform device*/
+#if 0
platform_set_drvdata(pdev, smmu);
+#endif
/* Reset the device */
ret = arm_smmu_device_reset(smmu, bypass);
if (ret)
return ret;
+/* Xen: Not creating an IOMMU device list for Xen */
+#if 0
/* And we're up. Go go go! */
ret = iommu_device_sysfs_add(&smmu->iommu, dev, NULL,
"smmu3.%pa", &ioaddr);
@@ -2844,9 +3393,20 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
if (ret)
return ret;
}
+#endif
+ /*
+ * Xen: Keep a list of all probed devices. This will be used to query
+ * the smmu devices based on the fwnode.
+ */
+ INIT_LIST_HEAD(&smmu->devices);
+ spin_lock(&arm_smmu_devices_lock);
+ list_add(&smmu->devices, &arm_smmu_devices);
+ spin_unlock(&arm_smmu_devices_lock);
return 0;
}
+/* Xen: Unused function */
+#if 0
static int arm_smmu_device_remove(struct platform_device *pdev)
{
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
@@ -2860,6 +3420,8 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
{
arm_smmu_device_remove(pdev);
}
+#endif
+
static const struct of_device_id arm_smmu_of_match[] = {
{ .compatible = "arm,smmu-v3", },
@@ -2867,6 +3429,7 @@ static const struct of_device_id arm_smmu_of_match[] = {
};
MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
+#if 0
static struct platform_driver arm_smmu_driver = {
.driver = {
.name = "arm-smmu-v3",
@@ -2883,3 +3446,316 @@ IOMMU_OF_DECLARE(arm_smmuv3, "arm,smmu-v3", NULL);
MODULE_DESCRIPTION("IOMMU API for ARM architected SMMUv3 implementations");
MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>");
MODULE_LICENSE("GPL v2");
+#endif
+
+/***** Start of Xen specific code *****/
+
+static int __must_check arm_smmu_iotlb_flush_all(struct domain *d)
+{
+ struct arm_smmu_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+ struct iommu_domain *io_domain;
+
+ spin_lock(&xen_domain->lock);
+ list_for_each_entry(io_domain, &xen_domain->contexts, list) {
+ /*
+ * Only invalidate the context when SMMU is present.
+ * This is because the context initialization is delayed
+ * until a master has been added.
+ */
+ if (unlikely(!ACCESS_ONCE(io_domain->priv->smmu)))
+ continue;
+ arm_smmu_tlb_inv_context(io_domain->priv);
+ }
+ spin_unlock(&xen_domain->lock);
+ return 0;
+}
+
+static int __must_check arm_smmu_iotlb_flush(struct domain *d,
+ unsigned long gfn,
+ unsigned int page_count)
+{
+ return arm_smmu_iotlb_flush_all(d);
+}
+
+static struct iommu_domain *arm_smmu_get_domain(struct domain *d,
+ struct device *dev)
+{
+ struct iommu_domain *io_domain;
+ struct arm_smmu_xen_domain *xen_domain;
+ struct arm_smmu_device *smmu;
+ struct arm_smmu_domain *smmu_domain;
+
+ xen_domain = dom_iommu(d)->arch.priv;
+
+ smmu = arm_smmu_get_by_fwnode(dev->iommu_fwspec->iommu_fwnode);
+ if (!smmu)
+ return NULL;
+
+ /*
+ * Loop through the &xen_domain->contexts to locate a context
+ * assigned to this SMMU
+ */
+ list_for_each_entry(io_domain, &xen_domain->contexts, list) {
+ smmu_domain = to_smmu_domain(io_domain);
+ if (smmu_domain->smmu == smmu)
+ return io_domain;
+ }
+
+ return NULL;
+}
+
+static void arm_smmu_destroy_iommu_domain(struct iommu_domain *io_domain)
+{
+ list_del(&io_domain->list);
+ arm_smmu_domain_free(io_domain);
+}
+
+static int arm_smmu_assign_dev(struct domain *d, u8 devfn,
+ struct device *dev, u32 flag)
+{
+ int ret = 0;
+ struct iommu_domain *io_domain;
+ struct arm_smmu_xen_domain *xen_domain;
+ struct arm_smmu_domain *smmu_domain;
+
+ xen_domain = dom_iommu(d)->arch.priv;
+
+ if (!dev->archdata.iommu) {
+ dev->archdata.iommu = xzalloc(struct arm_smmu_xen_device);
+ if (!dev->archdata.iommu)
+ return -ENOMEM;
+ }
+
+ ret = arm_smmu_add_device(dev);
+ if (ret)
+ return ret;
+
+ spin_lock(&xen_domain->lock);
+
+ /*
+ * Check to see if an iommu_domain already exists for this xen domain
+ * under the same SMMU
+ */
+ io_domain = arm_smmu_get_domain(d, dev);
+ if (!io_domain) {
+
+ io_domain = arm_smmu_domain_alloc(IOMMU_DOMAIN_DMA);
+ if (!io_domain) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ smmu_domain = to_smmu_domain(io_domain);
+ smmu_domain->s2_cfg.domain = d;
+
+ /* Chain the new context to the domain */
+ list_add(&io_domain->list, &xen_domain->contexts);
+
+ }
+
+ ret = arm_smmu_attach_dev(io_domain, dev);
+ if (ret) {
+ if (io_domain->ref.counter == 0)
+ arm_smmu_destroy_iommu_domain(io_domain);
+ } else {
+ atomic_inc(&io_domain->ref);
+ }
+
+out:
+ spin_unlock(&xen_domain->lock);
+ return ret;
+}
+
+static int arm_smmu_deassign_dev(struct domain *d, struct device *dev)
+{
+ struct iommu_domain *io_domain = arm_smmu_get_domain(d, dev);
+ struct arm_smmu_xen_domain *xen_domain;
+ struct arm_smmu_domain *arm_smmu = to_smmu_domain(io_domain);
+
+ xen_domain = dom_iommu(d)->arch.priv;
+
+ if (!arm_smmu || arm_smmu->s2_cfg.domain != d) {
+ dev_err(dev, " not attached to domain %d\n", d->domain_id);
+ return -ESRCH;
+ }
+
+ spin_lock(&xen_domain->lock);
+
+ arm_smmu_detach_dev(dev);
+ atomic_dec(&io_domain->ref);
+
+ if (io_domain->ref.counter == 0)
+ arm_smmu_destroy_iommu_domain(io_domain);
+
+ spin_unlock(&xen_domain->lock);
+
+ return 0;
+}
+
+static int arm_smmu_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 = arm_smmu_deassign_dev(s, dev);
+ if (ret)
+ return ret;
+
+ if (t) {
+ /* No flags are defined for ARM. */
+ ret = arm_smmu_assign_dev(t, devfn, dev, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int arm_smmu_iommu_xen_domain_init(struct domain *d)
+{
+ struct arm_smmu_xen_domain *xen_domain;
+
+ xen_domain = xzalloc(struct arm_smmu_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;
+
+ return 0;
+}
+
+static void __hwdom_init arm_smmu_iommu_hwdom_init(struct domain *d)
+{
+}
+
+static void arm_smmu_iommu_xen_domain_teardown(struct domain *d)
+{
+ struct arm_smmu_xen_domain *xen_domain = dom_iommu(d)->arch.priv;
+
+ ASSERT(list_empty(&xen_domain->contexts));
+ xfree(xen_domain);
+}
+
+static int __must_check arm_smmu_map_page(struct domain *d, unsigned long gfn,
+ unsigned long mfn, unsigned int flags)
+{
+ p2m_type_t t;
+
+ /*
+ * Grant mappings can be used for DMA requests. The dev_bus_addr
+ * returned by the hypercall is the MFN (not the IPA). For device
+ * protected by an IOMMU, Xen needs to add a 1:1 mapping in the domain
+ * p2m to allow DMA request to work.
+ * This is only valid when the domain is directed mapped. Hence this
+ * function should only be used by gnttab code with gfn == mfn.
+ */
+ BUG_ON(!is_domain_direct_mapped(d));
+ BUG_ON(mfn != gfn);
+
+ /* We only support readable and writable flags */
+ if (!(flags & (IOMMUF_readable | IOMMUF_writable)))
+ return -EINVAL;
+
+ t = (flags & IOMMUF_writable) ? p2m_iommu_map_rw : p2m_iommu_map_ro;
+
+ /*
+ * The function guest_physmap_add_entry replaces the current mapping
+ * if there is already one...
+ */
+ return guest_physmap_add_entry(d, _gfn(gfn), _mfn(mfn), 0, t);
+}
+
+static int __must_check arm_smmu_unmap_page(struct domain *d, unsigned long gfn)
+{
+ /*
+ * This function should only be used by gnttab code when the domain
+ * is direct mapped
+ */
+ if (!is_domain_direct_mapped(d))
+ return -EINVAL;
+
+ return guest_physmap_remove_page(d, _gfn(gfn), _mfn(gfn), 0);
+}
+
+static const struct iommu_ops arm_smmu_iommu_ops = {
+ .init = arm_smmu_iommu_xen_domain_init,
+ .hwdom_init = arm_smmu_iommu_hwdom_init,
+ .teardown = arm_smmu_iommu_xen_domain_teardown,
+ .iotlb_flush = arm_smmu_iotlb_flush,
+ .iotlb_flush_all = arm_smmu_iotlb_flush_all,
+ .assign_device = arm_smmu_assign_dev,
+ .reassign_device = arm_smmu_reassign_dev,
+ .map_page = arm_smmu_map_page,
+ .unmap_page = arm_smmu_unmap_page,
+};
+
+static
+struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode)
+{
+ struct arm_smmu_device *smmu = NULL;
+
+ spin_lock(&arm_smmu_devices_lock);
+ list_for_each_entry(smmu, &arm_smmu_devices, devices) {
+ if (smmu->dev->fwnode == fwnode)
+ break;
+ }
+ spin_unlock(&arm_smmu_devices_lock);
+
+ return smmu;
+}
+
+static __init int arm_smmu_dt_init(struct dt_device_node *dev,
+ const void *data)
+{
+ int rc;
+
+ /*
+ * Even if the device can't be initialized, we don't want to
+ * give the SMMU device to dom0.
+ */
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ rc = arm_smmu_device_probe(dt_to_dev(dev));
+ if (rc)
+ return rc;
+
+ iommu_set_ops(&arm_smmu_iommu_ops);
+
+ return 0;
+}
+
+DT_DEVICE_START(smmuv3, "ARM SMMU V3", DEVICE_IOMMU)
+ .dt_match = arm_smmu_of_match,
+ .init = arm_smmu_dt_init,
+DT_DEVICE_END
+
+#ifdef CONFIG_ACPI
+/* Set up the IOMMU */
+static int __init arm_smmu_acpi_init(const void *data)
+{
+ int rc;
+
+ rc = arm_smmu_device_probe((struct device *)data);
+ if (rc)
+ return rc;
+
+ iommu_set_ops(&arm_smmu_iommu_ops);
+ return 0;
+}
+
+ACPI_DEVICE_START(asmmuv3, "ARM SMMU V3", DEVICE_IOMMU)
+ .class_type = ACPI_IORT_NODE_SMMU_V3,
+ .init = arm_smmu_acpi_init,
+ACPI_DEVICE_END
+
+#endif
new file mode 100644
@@ -0,0 +1,84 @@
+/******************************************************************************
+ * include/xen/linux_compat.h
+ *
+ * Compatibility defines for porting code from Linux to Xen
+ *
+ * Copyright (c) 2017 Linaro Limited
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __XEN_LINUX_COMPAT_H__
+#define __XEN_LINUX_COMPAT_H__
+
+#include <asm/types.h>
+
+typedef paddr_t phys_addr_t;
+typedef paddr_t dma_addr_t;
+
+typedef unsigned int gfp_t;
+#define GFP_KERNEL 0
+#define __GFP_ZERO 0x01U
+
+/* Helpers for IRQ functions */
+#define free_irq release_irq
+
+enum irqreturn {
+ IRQ_NONE,
+ IRQ_HANDLED,
+ IRQ_WAKE_THREAD,
+};
+
+typedef enum irqreturn irqreturn_t;
+
+/* Device logger functions */
+#define dev_dbg(dev, fmt, ...) printk(XENLOG_DEBUG fmt, ## __VA_ARGS__)
+#define dev_notice(dev, fmt, ...) printk(XENLOG_INFO fmt, ## __VA_ARGS__)
+#define dev_warn(dev, fmt, ...) printk(XENLOG_WARNING fmt, ## __VA_ARGS__)
+#define dev_err(dev, fmt, ...) printk(XENLOG_ERR fmt, ## __VA_ARGS__)
+#define dev_info(dev, fmt, ...) printk(XENLOG_INFO fmt, ## __VA_ARGS__)
+
+#define dev_err_ratelimited(dev, fmt, ...) \
+ printk(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) ({\
+ void *__ret_alloc = NULL; \
+ if (flags & __GFP_ZERO) \
+ __ret_alloc = _xzalloc(size, sizeof(void *)); \
+ else \
+ __ret_alloc = _xmalloc(size, sizeof(void *)); \
+ __ret_alloc; \
+})
+#define kzalloc(size, flags) _xzalloc(size, sizeof(void *))
+#define devm_kzalloc(dev, size, flags) _xzalloc(size, sizeof(void *))
+#define kmalloc_array(size, n, flags) ({\
+ void *__ret_alloc = NULL; \
+ if (flags & __GFP_ZERO) \
+ __ret_alloc = _xzalloc_array(size, sizeof(void *), n); \
+ else \
+ __ret_alloc = _xmalloc_array(size, sizeof(void *), n); \
+ __ret_alloc; \
+})
+
+/* Alias to Xen time functions */
+#define ktime_t s_time_t
+#define ktime_get() (NOW())
+#define ktime_add_us(t,i) (t + MICROSECS(i))
+#define ktime_compare(t,i) (t > (i))
+
+#endif /* __XEN_LINUX_COMPAT_H__ */
This driver follows an approach similar to smmu driver. The intent here is to reuse as much Linux code as possible. - Glue code has been introduced to bridge the API calls. - Called Linux functions from the Xen IOMMU function calls. - Xen modifications are preceded by /*Xen: comment */ - xen/linux_compat: Add a Linux compat header For porting files directly from Linux it is useful to have a function mapping definitions from Linux to Xen. This file adds common API functions and other defines that are needed for porting arm SMMU drivers. Signed-off-by: Sameer Goel <sameer.goel@linaro.org> --- xen/arch/arm/p2m.c | 1 + xen/drivers/Kconfig | 2 + xen/drivers/passthrough/arm/Kconfig | 8 + xen/drivers/passthrough/arm/Makefile | 1 + xen/drivers/passthrough/arm/smmu-v3.c | 934 +++++++++++++++++++++++++- xen/include/xen/linux-compat.h | 84 +++ 6 files changed, 1001 insertions(+), 29 deletions(-) create mode 100644 xen/drivers/passthrough/arm/Kconfig create mode 100644 xen/include/xen/linux-compat.h