[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [v3 4/6] xen/iommu: smmu-v3: Add Xen specific code to enable the ported driver
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@xxxxxxxxxx> --- 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 diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c index d43c3aa896..38aa9f00c1 100644 --- a/xen/arch/arm/p2m.c +++ b/xen/arch/arm/p2m.c @@ -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(); } diff --git a/xen/drivers/Kconfig b/xen/drivers/Kconfig index db94393f47..59ca00f850 100644 --- a/xen/drivers/Kconfig +++ b/xen/drivers/Kconfig @@ -15,4 +15,6 @@ source "drivers/video/Kconfig" config HAS_VPCI bool +source "drivers/passthrough/arm/Kconfig" + endmenu diff --git a/xen/drivers/passthrough/arm/Kconfig b/xen/drivers/passthrough/arm/Kconfig new file mode 100644 index 0000000000..cda899f608 --- /dev/null +++ b/xen/drivers/passthrough/arm/Kconfig @@ -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. + diff --git a/xen/drivers/passthrough/arm/Makefile b/xen/drivers/passthrough/arm/Makefile index f4cd26e15d..e14732b55c 100644 --- a/xen/drivers/passthrough/arm/Makefile +++ b/xen/drivers/passthrough/arm/Makefile @@ -1,2 +1,3 @@ obj-y += iommu.o obj-y += smmu.o +obj-$(CONFIG_ARM_SMMU_v3) += smmu-v3.o diff --git a/xen/drivers/passthrough/arm/smmu-v3.c b/xen/drivers/passthrough/arm/smmu-v3.c index e67ba6c40f..75c3411ad9 100644 --- a/xen/drivers/passthrough/arm/smmu-v3.c +++ b/xen/drivers/passthrough/arm/smmu-v3.c @@ -18,28 +18,414 @@ * Author: Will Deacon <will.deacon@xxxxxxx> * * 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@xxxxxxxxxx> + * 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@xxxxxxx>"); 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 diff --git a/xen/include/xen/linux-compat.h b/xen/include/xen/linux-compat.h new file mode 100644 index 0000000000..8037be0a3e --- /dev/null +++ b/xen/include/xen/linux-compat.h @@ -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__ */ -- 2.17.1 _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxxx https://lists.xenproject.org/mailman/listinfo/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |