diff options
Diffstat (limited to 'drivers/iommu')
-rw-r--r-- | drivers/iommu/Kconfig | 85 | ||||
-rw-r--r-- | drivers/iommu/Makefile | 5 | ||||
-rw-r--r-- | drivers/iommu/arm-smmu.c | 3086 | ||||
-rw-r--r-- | drivers/iommu/dma-iommu.c | 10 | ||||
-rw-r--r-- | drivers/iommu/dma-mapping-fast.c | 860 | ||||
-rw-r--r-- | drivers/iommu/io-pgtable-arm.c | 832 | ||||
-rw-r--r-- | drivers/iommu/io-pgtable-fast.c | 751 | ||||
-rw-r--r-- | drivers/iommu/io-pgtable-msm-secure.c | 243 | ||||
-rw-r--r-- | drivers/iommu/io-pgtable.c | 75 | ||||
-rw-r--r-- | drivers/iommu/io-pgtable.h | 69 | ||||
-rw-r--r-- | drivers/iommu/iommu-debug.c | 2386 | ||||
-rw-r--r-- | drivers/iommu/iommu-debug.h | 34 | ||||
-rw-r--r-- | drivers/iommu/iommu.c | 165 | ||||
-rw-r--r-- | drivers/iommu/msm_dma_iommu_mapping.c | 423 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu.c | 735 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu.h | 120 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu_dev.c | 392 | ||||
-rw-r--r-- | drivers/iommu/msm_iommu_hw-8xxx.h | 1865 |
18 files changed, 8626 insertions, 3510 deletions
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index b9094e9da537..7a504b1ad94d 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -39,6 +39,42 @@ config IOMMU_IO_PGTABLE_LPAE_SELFTEST If unsure, say N here. +config IOMMU_IO_PGTABLE_FAST + bool "Fast ARMv7/v8 Long Descriptor Format" + depends on ARM64_DMA_USE_IOMMU + help + Enable support for a subset of the ARM long descriptor pagetable + format. This allocator achieves fast performance by + pre-allocating and pre-populating page table memory up front. + only supports a 32 bit virtual address space. + + This implementation is mainly optimized for use cases where the + buffers are small (<= 64K) since it only supports 4K page sizes. + +config IOMMU_IO_PGTABLE_FAST_SELFTEST + bool "Fast IO pgtable selftests" + depends on IOMMU_IO_PGTABLE_FAST + help + Enable self-tests for "fast" page table allocator. This performs + a series of page-table consistency checks during boot. + + If unsure, say N here. + +config IOMMU_IO_PGTABLE_FAST_PROVE_TLB + bool "Prove correctness of TLB maintenance in the Fast DMA mapper" + depends on IOMMU_IO_PGTABLE_FAST + help + Enables some debug features that help prove correctness of TLB + maintenance routines in the Fast DMA mapper. This option will + slow things down considerably, so should only be used in a debug + configuration. This relies on the ability to set bits in an + invalid page table entry, which is disallowed on some hardware + due to errata. If you're running on such a platform then this + option can only be used with unit tests. It will break real use + cases. + + If unsure, say N here. + endmenu config IOMMU_IOVA @@ -66,24 +102,6 @@ config FSL_PAMU PAMU can authorize memory access, remap the memory address, and remap I/O transaction types. -# MSM IOMMU support -config MSM_IOMMU - bool "MSM IOMMU Support" - depends on ARM - depends on ARCH_MSM8X60 || ARCH_MSM8960 || COMPILE_TEST - depends on BROKEN - select IOMMU_API - help - Support for the IOMMUs found on certain Qualcomm SOCs. - These IOMMUs allow virtualization of the address space used by most - cores within the multimedia subsystem. - - If unsure, say N here. - -config IOMMU_PGTABLES_L2 - def_bool y - depends on MSM_IOMMU && MMU && SMP && CPU_DCACHE_DISABLE=n - # AMD IOMMU support config AMD_IOMMU bool "AMD IOMMU support" @@ -366,6 +384,7 @@ config ARM_SMMU select IOMMU_API select IOMMU_IO_PGTABLE_LPAE select ARM_DMA_USE_IOMMU if ARM + select ARM64_DMA_USE_IOMMU if ARM64 help Support for implementations of the ARM System MMU architecture versions 1 and 2. @@ -393,4 +412,34 @@ config S390_IOMMU help Support for the IOMMU API for s390 PCI devices. +menuconfig IOMMU_DEBUG + bool "IOMMU Profiling and Debugging" + help + Makes available some additional IOMMU profiling and debugging + options. + +if IOMMU_DEBUG + +config IOMMU_DEBUG_TRACKING + bool "Track key IOMMU events" + select IOMMU_API + help + Enables additional debug tracking in the IOMMU framework code. + Tracking information and tests can be accessed through various + debugfs files. + + Say Y here if you need to debug IOMMU issues and are okay with + the performance penalty of the tracking. + +config IOMMU_TESTS + bool "Interactive IOMMU performance/functional tests" + select IOMMU_API + help + Enables a suite of IOMMU unit tests. The tests are runnable + through debugfs. Unlike the IOMMU_DEBUG_TRACKING option, the + impact of enabling this option to overal system performance + should be minimal. + +endif # IOMMU_DEBUG + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 68faca02225d..fe78a84c845e 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -2,11 +2,14 @@ obj-$(CONFIG_IOMMU_API) += iommu.o obj-$(CONFIG_IOMMU_API) += iommu-traces.o obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o obj-$(CONFIG_IOMMU_DMA) += dma-iommu.o +obj-$(CONFIG_IOMMU_API) += msm_dma_iommu_mapping.o obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o obj-$(CONFIG_IOMMU_IOVA) += iova.o +obj-$(CONFIG_MSM_TZ_SMMU) += io-pgtable-msm-secure.o +obj-$(CONFIG_IOMMU_IO_PGTABLE_FAST) += io-pgtable-fast.o dma-mapping-fast.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o -obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o +obj-$(CONFIG_IOMMU_DEBUG) += iommu-debug.o obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 47dc7a793f5c..90306a0e2164 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -42,13 +42,21 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/spinlock.h> +#include <linux/notifier.h> #include <linux/amba/bus.h> +#include <soc/qcom/msm_tz_smmu.h> +#include <soc/qcom/scm.h> +#include <soc/qcom/secure_buffer.h> +#include <asm/cacheflush.h> +#include <linux/msm-bus.h> +#include <dt-bindings/msm/msm-bus-ids.h> +#include <linux/msm_pcie.h> #include "io-pgtable.h" /* Maximum number of stream IDs assigned to a single device */ -#define MAX_MASTER_STREAMIDS MAX_PHANDLE_ARGS +#define MAX_MASTER_STREAMIDS 45 /* Maximum number of context banks per SMMU */ #define ARM_SMMU_MAX_CBS 128 @@ -148,7 +156,7 @@ #define ARM_SMMU_GR0_sTLBGSYNC 0x70 #define ARM_SMMU_GR0_sTLBGSTATUS 0x74 #define sTLBGSTATUS_GSACTIVE (1 << 0) -#define TLB_LOOP_TIMEOUT 1000000 /* 1s! */ +#define TLB_LOOP_TIMEOUT 500000 /* 500ms */ /* Stream mapping registers */ #define ARM_SMMU_GR0_SMR(n) (0x800 + ((n) << 2)) @@ -195,29 +203,38 @@ #define ARM_SMMU_CB(smmu, n) ((n) * (1 << (smmu)->pgshift)) #define ARM_SMMU_CB_SCTLR 0x0 +#define ARM_SMMU_CB_ACTLR 0x4 #define ARM_SMMU_CB_RESUME 0x8 #define ARM_SMMU_CB_TTBCR2 0x10 #define ARM_SMMU_CB_TTBR0 0x20 #define ARM_SMMU_CB_TTBR1 0x28 #define ARM_SMMU_CB_TTBCR 0x30 +#define ARM_SMMU_CB_CONTEXTIDR 0x34 #define ARM_SMMU_CB_S1_MAIR0 0x38 #define ARM_SMMU_CB_S1_MAIR1 0x3c #define ARM_SMMU_CB_PAR_LO 0x50 #define ARM_SMMU_CB_PAR_HI 0x54 #define ARM_SMMU_CB_FSR 0x58 +#define ARM_SMMU_CB_FSRRESTORE 0x5c #define ARM_SMMU_CB_FAR_LO 0x60 #define ARM_SMMU_CB_FAR_HI 0x64 #define ARM_SMMU_CB_FSYNR0 0x68 #define ARM_SMMU_CB_S1_TLBIVA 0x600 #define ARM_SMMU_CB_S1_TLBIASID 0x610 +#define ARM_SMMU_CB_S1_TLBIALL 0x618 #define ARM_SMMU_CB_S1_TLBIVAL 0x620 #define ARM_SMMU_CB_S2_TLBIIPAS2 0x630 #define ARM_SMMU_CB_S2_TLBIIPAS2L 0x638 +#define ARM_SMMU_CB_TLBSYNC 0x7f0 +#define ARM_SMMU_CB_TLBSTATUS 0x7f4 +#define TLBSTATUS_SACTIVE (1 << 0) #define ARM_SMMU_CB_ATS1PR 0x800 #define ARM_SMMU_CB_ATSR 0x8f0 +#define ARM_SMMU_GR1_CBFRSYNRA(n) (0x400 + ((n) << 2)) #define SCTLR_S1_ASIDPNE (1 << 12) #define SCTLR_CFCFG (1 << 7) +#define SCTLR_HUPCF (1 << 8) #define SCTLR_CFIE (1 << 6) #define SCTLR_CFRE (1 << 5) #define SCTLR_E (1 << 4) @@ -233,9 +250,6 @@ #define RESUME_RETRY (0 << 0) #define RESUME_TERMINATE (1 << 0) -#define TTBCR2_SEP_SHIFT 15 -#define TTBCR2_SEP_UPSTREAM (0x7 << TTBCR2_SEP_SHIFT) - #define TTBRn_ASID_SHIFT 48 #define FSR_MULTI (1 << 31) @@ -249,12 +263,46 @@ #define FSR_AFF (1 << 2) #define FSR_TF (1 << 1) +/* Definitions for implementation-defined registers */ +#define ACTLR_QCOM_OSH_SHIFT 28 +#define ACTLR_QCOM_OSH 1 + +#define ACTLR_QCOM_ISH_SHIFT 29 +#define ACTLR_QCOM_ISH 1 + +#define ACTLR_QCOM_NSH_SHIFT 30 +#define ACTLR_QCOM_NSH 1 + +#define ARM_SMMU_IMPL_DEF0(smmu) \ + ((smmu)->base + (2 * (1 << (smmu)->pgshift))) +#define ARM_SMMU_IMPL_DEF1(smmu) \ + ((smmu)->base + (6 * (1 << (smmu)->pgshift))) +#define IMPL_DEF1_MICRO_MMU_CTRL 0 +#define MICRO_MMU_CTRL_LOCAL_HALT_REQ (1 << 2) +#define MICRO_MMU_CTRL_IDLE (1 << 3) + #define FSR_IGN (FSR_AFF | FSR_ASF | \ FSR_TLBMCF | FSR_TLBLKF) #define FSR_FAULT (FSR_MULTI | FSR_SS | FSR_UUT | \ FSR_EF | FSR_PF | FSR_TF | FSR_IGN) #define FSYNR0_WNR (1 << 4) +#define MAX_GLOBAL_REG_SAVE_ENTRIES (2 * ARM_SMMU_MAX_SMRS + 1) + +enum arm_smmu_save_ctx { + SAVE_ARM_SMMU_CB_SCTLR, + SAVE_ARM_SMMU_CB_ACTLR, + SAVE_ARM_SMMU_CB_TTBCR2, + SAVE_ARM_SMMU_CB_TTBR0, + SAVE_ARM_SMMU_CB_TTBR1, + SAVE_ARM_SMMU_CB_TTBCR, + SAVE_ARM_SMMU_CB_CONTEXTIDR, + SAVE_ARM_SMMU_CB_S1_MAIR0, + SAVE_ARM_SMMU_CB_S1_MAIR1, + SAVE_ARM_SMMU_GR1_CBA2R, + SAVE_ARM_SMMU_GR1_CBAR, + SAVE_ARM_SMMU_MAX_CNT, +}; static int force_stage; module_param_named(force_stage, force_stage, int, S_IRUGO); @@ -284,11 +332,24 @@ struct arm_smmu_master { struct arm_smmu_master_cfg cfg; }; +enum smmu_model_id { + SMMU_MODEL_DEFAULT, + SMMU_MODEL_QCOM_V2, +}; + +struct arm_smmu_impl_def_reg { + u32 offset; + u32 value; +}; + struct arm_smmu_device { struct device *dev; + enum smmu_model_id model; + void __iomem *base; unsigned long size; + phys_addr_t phys_addr; unsigned long pgshift; #define ARM_SMMU_FEAT_COHERENT_WALK (1 << 0) @@ -300,6 +361,17 @@ struct arm_smmu_device { u32 features; #define ARM_SMMU_OPT_SECURE_CFG_ACCESS (1 << 0) +#define ARM_SMMU_OPT_INVALIDATE_ON_MAP (1 << 1) +#define ARM_SMMU_OPT_HALT_AND_TLB_ON_ATOS (1 << 2) +#define ARM_SMMU_OPT_REGISTER_SAVE (1 << 3) +#define ARM_SMMU_OPT_SKIP_INIT (1 << 4) +#define ARM_SMMU_OPT_ERRATA_CTX_FAULT_HANG (1 << 5) +#define ARM_SMMU_OPT_FATAL_ASF (1 << 6) +#define ARM_SMMU_OPT_ERRATA_TZ_ATOS (1 << 7) +#define ARM_SMMU_OPT_NO_SMR_CHECK (1 << 9) +#define ARM_SMMU_OPT_DYNAMIC (1 << 10) +#define ARM_SMMU_OPT_HALT (1 << 11) +#define ARM_SMMU_OPT_STATIC_CB (1 << 12) u32 options; enum arm_smmu_arch_version version; @@ -311,6 +383,8 @@ struct arm_smmu_device { u32 num_mapping_groups; DECLARE_BITMAP(smr_map, ARM_SMMU_MAX_SMRS); + u32 ubs; + unsigned long va_size; unsigned long ipa_size; unsigned long pa_size; @@ -320,18 +394,59 @@ struct arm_smmu_device { unsigned int *irqs; struct list_head list; + struct list_head static_cbndx_list; struct rb_root masters; + + int num_clocks; + struct clk **clocks; + + struct regulator *gdsc; + struct notifier_block regulator_nb; + + /* Protects against domains attaching to the same SMMU concurrently */ + struct mutex attach_lock; + unsigned int attach_count; + struct idr asid_idr; + + struct arm_smmu_impl_def_reg *impl_def_attach_registers; + unsigned int num_impl_def_attach_registers; + + spinlock_t atos_lock; + unsigned int clock_refs_count; + spinlock_t clock_refs_lock; + + struct mutex power_lock; + unsigned int power_count; + + u32 bus_client; + struct msm_bus_scale_pdata *bus_pdata; + + enum tz_smmu_device_id sec_id; + int regulator_defer; + u64 regs[ARM_SMMU_MAX_CBS*(SAVE_ARM_SMMU_MAX_CNT)]; + u64 reg_global[MAX_GLOBAL_REG_SAVE_ENTRIES]; }; struct arm_smmu_cfg { u8 cbndx; u8 irptndx; u32 cbar; + u32 procid; + u16 asid; + u8 vmid; }; #define INVALID_IRPTNDX 0xff +#define INVALID_CBNDX 0xff +#define INVALID_ASID 0xffff +#define INVALID_VMID 0xff +/* + * In V7L and V8L with TTBCR2.AS == 0, ASID is 8 bits. + * V8L 16 with TTBCR2.AS == 1 (16 bit ASID) isn't supported yet. + */ +#define MAX_ASID 0xff -#define ARM_SMMU_CB_ASID(cfg) ((cfg)->cbndx) -#define ARM_SMMU_CB_VMID(cfg) ((cfg)->cbndx + 1) +#define ARM_SMMU_CB_ASID(cfg) ((cfg)->asid) +#define ARM_SMMU_CB_VMID(cfg) ((cfg)->vmid) enum arm_smmu_domain_stage { ARM_SMMU_DOMAIN_S1 = 0, @@ -339,13 +454,29 @@ enum arm_smmu_domain_stage { ARM_SMMU_DOMAIN_NESTED, }; +struct arm_smmu_pte_info { + void *virt_addr; + size_t size; + struct list_head entry; +}; + struct arm_smmu_domain { struct arm_smmu_device *smmu; struct io_pgtable_ops *pgtbl_ops; - spinlock_t pgtbl_lock; + struct io_pgtable_cfg pgtbl_cfg; + spinlock_t pgtbl_spin_lock; + struct mutex pgtbl_mutex_lock; struct arm_smmu_cfg cfg; enum arm_smmu_domain_stage stage; struct mutex init_mutex; /* Protects smmu pointer */ + u32 attributes; + bool slave_side_secure; + u32 secure_vmid; + struct list_head pte_info_list; + struct list_head unassign_list; + struct mutex assign_lock; + struct list_head secure_pool_list; + bool non_fatal_faults; struct iommu_domain domain; }; @@ -361,9 +492,75 @@ struct arm_smmu_option_prop { static struct arm_smmu_option_prop arm_smmu_options[] = { { ARM_SMMU_OPT_SECURE_CFG_ACCESS, "calxeda,smmu-secure-config-access" }, + { ARM_SMMU_OPT_INVALIDATE_ON_MAP, "qcom,smmu-invalidate-on-map" }, + { ARM_SMMU_OPT_HALT_AND_TLB_ON_ATOS, "qcom,halt-and-tlb-on-atos" }, + { ARM_SMMU_OPT_REGISTER_SAVE, "qcom,register-save" }, + { ARM_SMMU_OPT_SKIP_INIT, "qcom,skip-init" }, + { ARM_SMMU_OPT_ERRATA_CTX_FAULT_HANG, "qcom,errata-ctx-fault-hang" }, + { ARM_SMMU_OPT_FATAL_ASF, "qcom,fatal-asf" }, + { ARM_SMMU_OPT_ERRATA_TZ_ATOS, "qcom,errata-tz-atos" }, + { ARM_SMMU_OPT_NO_SMR_CHECK, "qcom,no-smr-check" }, + { ARM_SMMU_OPT_DYNAMIC, "qcom,dynamic" }, + { ARM_SMMU_OPT_HALT, "qcom,enable-smmu-halt"}, + { ARM_SMMU_OPT_STATIC_CB, "qcom,enable-static-cb"}, { 0, NULL}, }; +#define TYPE_TRANS (S2CR_TYPE_TRANS >> S2CR_TYPE_SHIFT) +#define TYPE_BYPASS (S2CR_TYPE_BYPASS >> S2CR_TYPE_SHIFT) +#define TYPE_FAULT (S2CR_TYPE_FAULT >> S2CR_TYPE_SHIFT) + +struct static_cbndx_entry { + struct list_head list; + u8 cbndx; + u8 smr_idx; + u16 sid; + u8 type; +}; + +struct arm_iommus_node { + struct device_node *master; + struct list_head list; + struct list_head iommuspec_list; +}; + +struct arm_iommus_spec { + struct of_phandle_args iommu_spec; + struct list_head list; +}; + +static LIST_HEAD(iommus_nodes); + +static int arm_smmu_enable_clocks_atomic(struct arm_smmu_device *smmu); +static void arm_smmu_disable_clocks_atomic(struct arm_smmu_device *smmu); +static void arm_smmu_prepare_pgtable(void *addr, void *cookie); +static void arm_smmu_unprepare_pgtable(void *cookie, void *addr, size_t size); +static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, + dma_addr_t iova); +static phys_addr_t arm_smmu_iova_to_phys_hard_no_halt( + struct iommu_domain *domain, dma_addr_t iova); +static int arm_smmu_wait_for_halt(struct arm_smmu_device *smmu); +static int arm_smmu_halt_nowait(struct arm_smmu_device *smmu); +static void arm_smmu_resume(struct arm_smmu_device *smmu); +static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova); +static int arm_smmu_assign_table(struct arm_smmu_domain *smmu_domain); +static void arm_smmu_unassign_table(struct arm_smmu_domain *smmu_domain); +static int arm_smmu_halt(struct arm_smmu_device *smmu); +static void arm_smmu_device_reset(struct arm_smmu_device *smmu); +static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t size); +static bool arm_smmu_is_master_side_secure(struct arm_smmu_domain *smmu_domain); +static bool arm_smmu_is_static_cb(struct arm_smmu_device *smmu); +static bool arm_smmu_is_slave_side_secure(struct arm_smmu_domain *smmu_domain); +static bool arm_smmu_has_secure_vmid(struct arm_smmu_domain *smmu_domain); +static bool arm_smmu_is_iova_coherent(struct iommu_domain *domain, + dma_addr_t iova); +static uint64_t arm_smmu_iova_to_pte(struct iommu_domain *domain, + dma_addr_t iova); + +static int arm_smmu_enable_s1_translations(struct arm_smmu_domain *smmu_domain); + static struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom) { return container_of(dom, struct arm_smmu_domain, domain); @@ -377,7 +574,7 @@ static void parse_driver_options(struct arm_smmu_device *smmu) if (of_property_read_bool(smmu->dev->of_node, arm_smmu_options[i].prop)) { smmu->options |= arm_smmu_options[i].opt; - dev_notice(smmu->dev, "option %s\n", + dev_dbg(smmu->dev, "option %s\n", arm_smmu_options[i].prop); } } while (arm_smmu_options[++i].opt); @@ -417,6 +614,28 @@ static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, return NULL; } +static struct arm_smmu_master *find_smmu_master_by_sid( + struct arm_smmu_device *smmu, u32 sid) +{ + struct rb_node *next; + struct arm_smmu_master *master; + struct arm_smmu_master_cfg *cfg; + int i; + + next = rb_first(&smmu->masters); + for (; next; next = rb_next(next)) { + master = container_of(next, struct arm_smmu_master, node); + cfg = &master->cfg; + + for (i = 0; i < cfg->num_streamids; i++) { + if (cfg->streamids[i] == sid) + return master; + } + } + + return NULL; +} + static struct arm_smmu_master_cfg * find_smmu_master_cfg(struct device *dev) { @@ -456,25 +675,32 @@ static int insert_smmu_master(struct arm_smmu_device *smmu, return 0; } +struct iommus_entry { + struct list_head list; + struct device_node *node; + u16 streamids[MAX_MASTER_STREAMIDS]; + int num_sids; +}; + static int register_smmu_master(struct arm_smmu_device *smmu, - struct device *dev, - struct of_phandle_args *masterspec) + struct iommus_entry *entry) { int i; struct arm_smmu_master *master; + struct device *dev = smmu->dev; - master = find_smmu_master(smmu, masterspec->np); + master = find_smmu_master(smmu, entry->node); if (master) { dev_err(dev, "rejecting multiple registrations for master device %s\n", - masterspec->np->name); + entry->node->name); return -EBUSY; } - if (masterspec->args_count > MAX_MASTER_STREAMIDS) { + if (entry->num_sids > MAX_MASTER_STREAMIDS) { dev_err(dev, "reached maximum number (%d) of stream IDs for master device %s\n", - MAX_MASTER_STREAMIDS, masterspec->np->name); + MAX_MASTER_STREAMIDS, entry->node->name); return -ENOSPC; } @@ -482,22 +708,84 @@ static int register_smmu_master(struct arm_smmu_device *smmu, if (!master) return -ENOMEM; - master->of_node = masterspec->np; - master->cfg.num_streamids = masterspec->args_count; + master->of_node = entry->node; + master->cfg.num_streamids = entry->num_sids; - for (i = 0; i < master->cfg.num_streamids; ++i) { - u16 streamid = masterspec->args[i]; + for (i = 0; i < master->cfg.num_streamids; ++i) + master->cfg.streamids[i] = entry->streamids[i]; - if (!(smmu->features & ARM_SMMU_FEAT_STREAM_MATCH) && - (streamid >= smmu->num_mapping_groups)) { - dev_err(dev, - "stream ID for master device %s greater than maximum allowed (%d)\n", - masterspec->np->name, smmu->num_mapping_groups); - return -ERANGE; + return insert_smmu_master(smmu, master); +} + +static int arm_smmu_parse_iommus_properties(struct arm_smmu_device *smmu) +{ + struct arm_iommus_node *node, *nex; + + list_for_each_entry_safe(node, nex, &iommus_nodes, list) { + struct iommus_entry *entry, *next; + struct arm_iommus_spec *iommuspec_node, *n; + LIST_HEAD(iommus); + int node_found = 0; + + list_for_each_entry_safe(iommuspec_node, n, + &node->iommuspec_list, list) { + if (iommuspec_node->iommu_spec.np != smmu->dev->of_node) + continue; + + /* + * Since each master node will have iommu spec(s) of the + * same device, we can delete this master node after + * the devices are registered. + */ + node_found = 1; + + list_for_each_entry(entry, &iommus, list) + if (entry->node == node->master) + break; + if (&entry->list == &iommus) { + entry = devm_kzalloc(smmu->dev, sizeof(*entry), + GFP_KERNEL); + if (!entry) + return -ENOMEM; + entry->node = node->master; + list_add(&entry->list, &iommus); + } + switch (iommuspec_node->iommu_spec.args_count) { + case 0: + /* + * For pci-e devices the SIDs are provided + * at device attach time. + */ + break; + case 1: + entry->num_sids++; + entry->streamids[entry->num_sids - 1] + = iommuspec_node->iommu_spec.args[0]; + break; + default: + BUG(); + } + list_del(&iommuspec_node->list); + kfree(iommuspec_node); + } + + list_for_each_entry_safe(entry, next, &iommus, list) { + int rc = register_smmu_master(smmu, entry); + + if (rc) + dev_err(smmu->dev, "Couldn't register %s\n", + entry->node->name); + list_del(&entry->list); + devm_kfree(smmu->dev, entry); + } + + if (node_found) { + list_del(&node->list); + kfree(node); } - master->cfg.streamids[i] = streamid; } - return insert_smmu_master(smmu, master); + + return 0; } static struct arm_smmu_device *find_smmu_for_device(struct device *dev) @@ -530,11 +818,272 @@ static int __arm_smmu_alloc_bitmap(unsigned long *map, int start, int end) return idx; } +static int __arm_smmu_set_bitmap(unsigned long *map, int idx) +{ + return test_and_set_bit(idx, map); +} + +static struct static_cbndx_entry *arm_smmu_get_static_entry_from_sid( + struct arm_smmu_device *smmu, int sid) +{ + struct static_cbndx_entry *entry; + + list_for_each_entry(entry, &smmu->static_cbndx_list, list) { + if (entry->sid == sid) + return entry; + } + + return NULL; +} + +static struct static_cbndx_entry *arm_smmu_get_static_entry_from_context( + struct arm_smmu_device *smmu, int idx) +{ + struct static_cbndx_entry *entry; + + list_for_each_entry(entry, &smmu->static_cbndx_list, list) { + if (entry->type == TYPE_TRANS && entry->cbndx == idx) + return entry; + } + + return NULL; +} + +static struct static_cbndx_entry *arm_smmu_get_static_entry_from_smr( + struct arm_smmu_device *smmu, int idx) +{ + struct static_cbndx_entry *entry; + + list_for_each_entry(entry, &smmu->static_cbndx_list, list) { + if (entry->smr_idx == idx) + return entry; + } + + return NULL; +} + +static int arm_smmu_alloc_smr_idx(struct arm_smmu_device *smmu, int start, + int end, int sid) +{ + struct static_cbndx_entry *entry = arm_smmu_get_static_entry_from_sid( + smmu, sid); + + if (entry) + return entry->smr_idx; + else + return __arm_smmu_alloc_bitmap(smmu->smr_map, start, end); +} + +static int arm_smmu_alloc_context_idx(struct arm_smmu_device *smmu, int start, + int end, u16 *streamids, int num_streamids) +{ + struct static_cbndx_entry *entry = NULL; + int i; + + for (i = 0; i < num_streamids; ++i) { + entry = arm_smmu_get_static_entry_from_sid(smmu, streamids[i]); + if (entry && entry->type == TYPE_TRANS) + break; + } + + if (entry && entry->type == TYPE_TRANS) + return entry->cbndx; + else + return __arm_smmu_alloc_bitmap(smmu->context_map, start, end); +} + static void __arm_smmu_free_bitmap(unsigned long *map, int idx) { clear_bit(idx, map); } +static void arm_smmu_free_smr_idx(struct arm_smmu_device *smmu, int idx) +{ + struct static_cbndx_entry *entry = arm_smmu_get_static_entry_from_smr( + smmu, idx); + + if (!entry) + __arm_smmu_free_bitmap(smmu->smr_map, idx); +} + +static void arm_smmu_free_context_idx(struct arm_smmu_device *smmu, int idx) +{ + struct static_cbndx_entry *entry = + arm_smmu_get_static_entry_from_context(smmu, idx); + + if (!entry) + __arm_smmu_free_bitmap(smmu->context_map, idx); +} + +static void arm_smmu_unprepare_clocks(struct arm_smmu_device *smmu) +{ + int i; + + for (i = smmu->num_clocks; i; --i) + clk_unprepare(smmu->clocks[i - 1]); +} + +static int arm_smmu_prepare_clocks(struct arm_smmu_device *smmu) +{ + int i, ret = 0; + + for (i = 0; i < smmu->num_clocks; ++i) { + ret = clk_prepare(smmu->clocks[i]); + if (ret) { + dev_err(smmu->dev, "Couldn't prepare clock #%d\n", i); + while (i--) + clk_unprepare(smmu->clocks[i]); + break; + } + } + return ret; +} + +static int arm_smmu_request_bus(struct arm_smmu_device *smmu) +{ + if (!smmu->bus_client) + return 0; + return msm_bus_scale_client_update_request(smmu->bus_client, 1); +} + +static int arm_smmu_unrequest_bus(struct arm_smmu_device *smmu) +{ + if (!smmu->bus_client) + return 0; + return msm_bus_scale_client_update_request(smmu->bus_client, 0); +} + +static int arm_smmu_disable_regulators(struct arm_smmu_device *smmu) +{ + int ret = 0; + + mutex_lock(&smmu->power_lock); + if (smmu->power_count == 0) { + WARN(1, "%s: Mismatched power count\n", dev_name(smmu->dev)); + mutex_unlock(&smmu->power_lock); + return -EINVAL; + } else if (smmu->power_count > 1) { + smmu->power_count -= 1; + mutex_unlock(&smmu->power_lock); + return 0; + } + + arm_smmu_unprepare_clocks(smmu); + arm_smmu_unrequest_bus(smmu); + if (smmu->gdsc) { + ret = regulator_disable_deferred(smmu->gdsc, + smmu->regulator_defer); + WARN(ret, "%s: Regulator disable failed\n", + dev_name(smmu->dev)); + } + + smmu->power_count = 0; + mutex_unlock(&smmu->power_lock); + return ret; +} + +static int arm_smmu_enable_regulators(struct arm_smmu_device *smmu) +{ + int ret; + + mutex_lock(&smmu->power_lock); + if (smmu->power_count) { + smmu->power_count++; + mutex_unlock(&smmu->power_lock); + return 0; + } + + if (smmu->gdsc) { + ret = regulator_enable(smmu->gdsc); + if (WARN_ON_ONCE(ret)) + goto out; + } + + ret = arm_smmu_request_bus(smmu); + if (WARN_ON_ONCE(ret)) + goto out_reg; + + ret = arm_smmu_prepare_clocks(smmu); + if (WARN_ON_ONCE(ret)) + goto out_bus; + + smmu->power_count = 1; + mutex_unlock(&smmu->power_lock); + return ret; + +out_bus: + arm_smmu_unrequest_bus(smmu); +out_reg: + if (smmu->gdsc) + regulator_disable(smmu->gdsc); +out: + mutex_unlock(&smmu->power_lock); + return ret; +} + +static int arm_smmu_enable_clocks(struct arm_smmu_device *smmu) +{ + int ret = 0; + + ret = arm_smmu_enable_regulators(smmu); + if (unlikely(ret)) + return ret; + ret = arm_smmu_enable_clocks_atomic(smmu); + if (unlikely(ret)) + arm_smmu_disable_regulators(smmu); + + return ret; +} + +static void arm_smmu_disable_clocks(struct arm_smmu_device *smmu) +{ + arm_smmu_disable_clocks_atomic(smmu); + arm_smmu_disable_regulators(smmu); +} + +/* Clocks must be prepared before this (arm_smmu_prepare_clocks) */ +static int arm_smmu_enable_clocks_atomic(struct arm_smmu_device *smmu) +{ + int i, ret = 0; + unsigned long flags; + + spin_lock_irqsave(&smmu->clock_refs_lock, flags); + if (smmu->clock_refs_count++ > 0) { + spin_unlock_irqrestore(&smmu->clock_refs_lock, flags); + return 0; + } + + for (i = 0; i < smmu->num_clocks; ++i) { + ret = clk_enable(smmu->clocks[i]); + if (WARN_ON_ONCE(ret)) { + dev_err(smmu->dev, "Couldn't enable clock #%d\n", i); + while (i--) + clk_disable(smmu->clocks[i]); + smmu->clock_refs_count--; + break; + } + } + spin_unlock_irqrestore(&smmu->clock_refs_lock, flags); + return ret; +} + +/* Clocks should be unprepared after this (arm_smmu_unprepare_clocks) */ +static void arm_smmu_disable_clocks_atomic(struct arm_smmu_device *smmu) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&smmu->clock_refs_lock, flags); + if (smmu->clock_refs_count-- > 1) { + spin_unlock_irqrestore(&smmu->clock_refs_lock, flags); + return; + } + + for (i = smmu->num_clocks; i; --i) + clk_disable(smmu->clocks[i - 1]); + spin_unlock_irqrestore(&smmu->clock_refs_lock, flags); +} + /* Wait for any pending TLB invalidations to complete */ static void __arm_smmu_tlb_sync(struct arm_smmu_device *smmu) { @@ -554,12 +1103,30 @@ static void __arm_smmu_tlb_sync(struct arm_smmu_device *smmu) } } +static void arm_smmu_tlb_sync_cb(struct arm_smmu_device *smmu, + int cbndx) +{ + void __iomem *base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cbndx); + u32 val; + + writel_relaxed(0, base + ARM_SMMU_CB_TLBSYNC); + if (readl_poll_timeout_atomic(base + ARM_SMMU_CB_TLBSTATUS, val, + !(val & TLBSTATUS_SACTIVE), + 0, TLB_LOOP_TIMEOUT)) + dev_err(smmu->dev, "TLBSYNC timeout!\n"); +} + static void arm_smmu_tlb_sync(void *cookie) { struct arm_smmu_domain *smmu_domain = cookie; - __arm_smmu_tlb_sync(smmu_domain->smmu); + + if (smmu_domain->smmu == NULL) + return; + + arm_smmu_tlb_sync_cb(smmu_domain->smmu, smmu_domain->cfg.cbndx); } +/* Must be called with clocks/regulators enabled */ static void arm_smmu_tlb_inv_context(void *cookie) { struct arm_smmu_domain *smmu_domain = cookie; @@ -568,19 +1135,23 @@ static void arm_smmu_tlb_inv_context(void *cookie) bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS; void __iomem *base; + if (!smmu) + return; + if (stage1) { base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); writel_relaxed(ARM_SMMU_CB_ASID(cfg), base + ARM_SMMU_CB_S1_TLBIASID); + arm_smmu_tlb_sync_cb(smmu, cfg->cbndx); } else { base = ARM_SMMU_GR0(smmu); writel_relaxed(ARM_SMMU_CB_VMID(cfg), base + ARM_SMMU_GR0_TLBIVMID); + __arm_smmu_tlb_sync(smmu); } - - __arm_smmu_tlb_sync(smmu); } +/* Must be called with clocks/regulators enabled */ static void arm_smmu_tlb_inv_range_nosync(unsigned long iova, size_t size, bool leaf, void *cookie) { @@ -589,6 +1160,11 @@ static void arm_smmu_tlb_inv_range_nosync(unsigned long iova, size_t size, struct arm_smmu_device *smmu = smmu_domain->smmu; bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS; void __iomem *reg; + int atomic_ctx = smmu_domain->attributes & (1 << DOMAIN_ATTR_ATOMIC); + + BUG_ON(atomic_ctx && !smmu); + if (!smmu) + return; if (stage1) { reg = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); @@ -618,61 +1194,331 @@ static void arm_smmu_tlb_inv_range_nosync(unsigned long iova, size_t size, } } +static void arm_smmu_tlbi_domain(struct iommu_domain *domain) +{ + arm_smmu_tlb_inv_context(to_smmu_domain(domain)); +} + +static int arm_smmu_enable_config_clocks(struct iommu_domain *domain) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + + return arm_smmu_enable_clocks(smmu_domain->smmu); +} + +static void arm_smmu_disable_config_clocks(struct iommu_domain *domain) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + + arm_smmu_disable_clocks(smmu_domain->smmu); +} + +struct arm_smmu_secure_pool_chunk { + void *addr; + size_t size; + struct list_head list; +}; + +static void *arm_smmu_secure_pool_remove(struct arm_smmu_domain *smmu_domain, + size_t size) +{ + struct arm_smmu_secure_pool_chunk *it; + + list_for_each_entry(it, &smmu_domain->secure_pool_list, list) { + if (it->size == size) { + void *addr = it->addr; + + list_del(&it->list); + kfree(it); + return addr; + } + } + + return NULL; +} + +static int arm_smmu_secure_pool_add(struct arm_smmu_domain *smmu_domain, + void *addr, size_t size) +{ + struct arm_smmu_secure_pool_chunk *chunk; + + chunk = kmalloc(sizeof(*chunk), GFP_ATOMIC); + if (!chunk) + return -ENOMEM; + + chunk->addr = addr; + chunk->size = size; + memset(addr, 0, size); + list_add(&chunk->list, &smmu_domain->secure_pool_list); + + return 0; +} + +static void arm_smmu_secure_pool_destroy(struct arm_smmu_domain *smmu_domain) +{ + struct arm_smmu_secure_pool_chunk *it, *i; + + list_for_each_entry_safe(it, i, &smmu_domain->secure_pool_list, list) { + arm_smmu_unprepare_pgtable(smmu_domain, it->addr, it->size); + /* pages will be freed later (after being unassigned) */ + list_del(&it->list); + kfree(it); + } +} + +static void *arm_smmu_alloc_pages_exact(void *cookie, + size_t size, gfp_t gfp_mask) +{ + void *ret; + struct arm_smmu_domain *smmu_domain = cookie; + + if (!arm_smmu_is_master_side_secure(smmu_domain)) + return alloc_pages_exact(size, gfp_mask); + + ret = arm_smmu_secure_pool_remove(smmu_domain, size); + if (ret) + return ret; + + ret = alloc_pages_exact(size, gfp_mask); + if (ret) + arm_smmu_prepare_pgtable(ret, cookie); + + return ret; +} + +static void arm_smmu_free_pages_exact(void *cookie, void *virt, size_t size) +{ + struct arm_smmu_domain *smmu_domain = cookie; + + if (!arm_smmu_is_master_side_secure(smmu_domain)) { + free_pages_exact(virt, size); + return; + } + + if (arm_smmu_secure_pool_add(smmu_domain, virt, size)) + arm_smmu_unprepare_pgtable(smmu_domain, virt, size); +} + static struct iommu_gather_ops arm_smmu_gather_ops = { .tlb_flush_all = arm_smmu_tlb_inv_context, .tlb_add_flush = arm_smmu_tlb_inv_range_nosync, .tlb_sync = arm_smmu_tlb_sync, + .alloc_pages_exact = arm_smmu_alloc_pages_exact, + .free_pages_exact = arm_smmu_free_pages_exact, }; +static phys_addr_t arm_smmu_verify_fault(struct iommu_domain *domain, + dma_addr_t iova, u32 fsr) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + struct arm_smmu_device *smmu; + void __iomem *cb_base; + u64 sctlr, sctlr_orig; + phys_addr_t phys; + + smmu = smmu_domain->smmu; + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + + arm_smmu_halt_nowait(smmu); + + writel_relaxed(RESUME_TERMINATE, cb_base + ARM_SMMU_CB_RESUME); + + arm_smmu_wait_for_halt(smmu); + + /* clear FSR to allow ATOS to log any faults */ + writel_relaxed(fsr, cb_base + ARM_SMMU_CB_FSR); + + /* disable stall mode momentarily */ + sctlr_orig = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR); + sctlr = sctlr_orig & ~SCTLR_CFCFG; + writel_relaxed(sctlr, cb_base + ARM_SMMU_CB_SCTLR); + + phys = arm_smmu_iova_to_phys_hard_no_halt(domain, iova); + + if (!phys) { + dev_err(smmu->dev, + "ATOS failed. Will issue a TLBIALL and try again...\n"); + arm_smmu_tlb_inv_context(smmu_domain); + phys = arm_smmu_iova_to_phys_hard_no_halt(domain, iova); + if (phys) + dev_err(smmu->dev, + "ATOS succeeded this time. Maybe we missed a TLB invalidation while messing with page tables earlier??\n"); + else + dev_err(smmu->dev, + "ATOS still failed. If the page tables look good (check the software table walk) then hardware might be misbehaving.\n"); + } + + /* restore SCTLR */ + writel_relaxed(sctlr_orig, cb_base + ARM_SMMU_CB_SCTLR); + + arm_smmu_resume(smmu); + + return phys; +} + static irqreturn_t arm_smmu_context_fault(int irq, void *dev) { - int flags, ret; - u32 fsr, far, fsynr, resume; - unsigned long iova; + int flags, ret, tmp; + u32 fsr, fsynr, resume; + unsigned long iova, far; struct iommu_domain *domain = dev; struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct arm_smmu_cfg *cfg = &smmu_domain->cfg; - struct arm_smmu_device *smmu = smmu_domain->smmu; + struct arm_smmu_device *smmu; void __iomem *cb_base; + bool ctx_hang_errata; + bool fatal_asf; + void __iomem *gr1_base; + phys_addr_t phys_soft; + u32 sid; + bool non_fatal_fault = smmu_domain->non_fatal_faults; + struct arm_smmu_master *master; + + static DEFINE_RATELIMIT_STATE(_rs, + DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + + mutex_lock(&smmu_domain->init_mutex); + smmu = smmu_domain->smmu; + if (!smmu) { + ret = IRQ_HANDLED; + pr_err("took a fault on a detached domain (%p)\n", domain); + goto out_unlock; + } + ctx_hang_errata = smmu->options & ARM_SMMU_OPT_ERRATA_CTX_FAULT_HANG; + fatal_asf = smmu->options & ARM_SMMU_OPT_FATAL_ASF; + if (arm_smmu_enable_clocks(smmu)) { + ret = IRQ_NONE; + goto out_unlock; + } + + gr1_base = ARM_SMMU_GR1(smmu); cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); fsr = readl_relaxed(cb_base + ARM_SMMU_CB_FSR); - if (!(fsr & FSR_FAULT)) - return IRQ_NONE; + if (!(fsr & FSR_FAULT)) { + arm_smmu_disable_clocks(smmu); + ret = IRQ_NONE; + goto out_unlock; + } - if (fsr & FSR_IGN) - dev_err_ratelimited(smmu->dev, - "Unexpected context fault (fsr 0x%x)\n", - fsr); + if (fatal_asf && (fsr & FSR_ASF)) { + dev_err(smmu->dev, + "Took an address size fault. Refusing to recover.\n"); + BUG(); + } fsynr = readl_relaxed(cb_base + ARM_SMMU_CB_FSYNR0); flags = fsynr & FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ; + if (fsr & FSR_TF) + flags |= IOMMU_FAULT_TRANSLATION; + if (fsr & FSR_PF) + flags |= IOMMU_FAULT_PERMISSION; + if (fsr & FSR_EF) + flags |= IOMMU_FAULT_EXTERNAL; + if (fsr & FSR_SS) + flags |= IOMMU_FAULT_TRANSACTION_STALLED; far = readl_relaxed(cb_base + ARM_SMMU_CB_FAR_LO); - iova = far; #ifdef CONFIG_64BIT - far = readl_relaxed(cb_base + ARM_SMMU_CB_FAR_HI); - iova |= ((unsigned long)far << 32); + far |= ((u64)readl_relaxed(cb_base + ARM_SMMU_CB_FAR_HI)) << 32; #endif + iova = far; - if (!report_iommu_fault(domain, smmu->dev, iova, flags)) { + phys_soft = arm_smmu_iova_to_phys(domain, iova); + sid = readl_relaxed(gr1_base + ARM_SMMU_GR1_CBFRSYNRA(cfg->cbndx)); + sid &= 0xffff; + master = find_smmu_master_by_sid(smmu, sid); + tmp = report_iommu_fault(domain, smmu->dev, iova, flags); + if (!tmp || (tmp == -EBUSY)) { + dev_dbg(smmu->dev, + "Context fault handled by client: iova=0x%08lx, fsr=0x%x, fsynr=0x%x, cb=%d\n", + iova, fsr, fsynr, cfg->cbndx); + dev_dbg(smmu->dev, + "soft iova-to-phys=%pa\n", &phys_soft); ret = IRQ_HANDLED; - resume = RESUME_RETRY; + resume = RESUME_TERMINATE; } else { - dev_err_ratelimited(smmu->dev, - "Unhandled context fault: iova=0x%08lx, fsynr=0x%x, cb=%d\n", - iova, fsynr, cfg->cbndx); + phys_addr_t phys_atos = arm_smmu_verify_fault(domain, iova, + fsr); + + if (__ratelimit(&_rs)) { + dev_err(smmu->dev, "Context Fault for %s\n", + master ? master->of_node->name : "Unknown SID"); + + dev_err(smmu->dev, + "Unhandled context fault: iova=0x%08lx, fsr=0x%x, fsynr=0x%x, cb=%d\n", + iova, fsr, fsynr, cfg->cbndx); + dev_err(smmu->dev, "FAR = %016lx\n", + (unsigned long)far); + dev_err(smmu->dev, + "FSR = %08x [%s%s%s%s%s%s%s%s%s]\n", + fsr, + (fsr & 0x02) ? "TF " : "", + (fsr & 0x04) ? "AFF " : "", + (fsr & 0x08) ? "PF " : "", + (fsr & 0x10) ? "EF " : "", + (fsr & 0x20) ? "TLBMCF " : "", + (fsr & 0x40) ? "TLBLKF " : "", + (fsr & 0x80) ? "MHF " : "", + (fsr & 0x40000000) ? "SS " : "", + (fsr & 0x80000000) ? "MULTI " : ""); + dev_err(smmu->dev, + "soft iova-to-phys=%pa\n", &phys_soft); + if (!phys_soft) + dev_err(smmu->dev, + "SOFTWARE TABLE WALK FAILED! Looks like %s accessed an unmapped address!\n", + dev_name(smmu->dev)); + dev_err(smmu->dev, + "hard iova-to-phys (ATOS)=%pa\n", &phys_atos); + dev_err(smmu->dev, "SID=0x%x\n", sid); + } ret = IRQ_NONE; resume = RESUME_TERMINATE; + if (!non_fatal_fault) { + dev_err(smmu->dev, + "Unhandled context faults are fatal on this domain. Going down now...\n"); + BUG(); + } } - /* Clear the faulting FSR */ - writel(fsr, cb_base + ARM_SMMU_CB_FSR); + /* + * If the client returns -EBUSY, do not clear FSR and do not RESUME + * if stalled. This is required to keep the IOMMU client stalled on + * the outstanding fault. This gives the client a chance to take any + * debug action and then terminate the stalled transaction. + * So, the sequence in case of stall on fault should be: + * 1) Do not clear FSR or write to RESUME here + * 2) Client takes any debug action + * 3) Client terminates the stalled transaction and resumes the IOMMU + * 4) Client clears FSR. The FSR should only be cleared after 3) and + * not before so that the fault remains outstanding. This ensures + * SCTLR.HUPCF has the desired effect if subsequent transactions also + * need to be terminated. + */ + if (tmp != -EBUSY) { + /* Clear the faulting FSR */ + writel_relaxed(fsr, cb_base + ARM_SMMU_CB_FSR); - /* Retry or terminate any stalled transactions */ - if (fsr & FSR_SS) - writel_relaxed(resume, cb_base + ARM_SMMU_CB_RESUME); + /* + * Barrier required to ensure that the FSR is cleared + * before resuming SMMU operation + */ + wmb(); + + /* Retry or terminate any stalled transactions */ + if (fsr & FSR_SS) { + if (ctx_hang_errata) + arm_smmu_tlb_sync_cb(smmu, cfg->cbndx); + writel_relaxed(resume, cb_base + ARM_SMMU_CB_RESUME); + } + } + + arm_smmu_disable_clocks(smmu); +out_unlock: + mutex_unlock(&smmu_domain->init_mutex); return ret; } @@ -683,13 +1529,18 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev) struct arm_smmu_device *smmu = dev; void __iomem *gr0_base = ARM_SMMU_GR0_NS(smmu); + if (arm_smmu_enable_clocks(smmu)) + return IRQ_NONE; + gfsr = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSR); gfsynr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR0); gfsynr1 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR1); gfsynr2 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR2); - if (!gfsr) + if (!gfsr) { + arm_smmu_disable_clocks(smmu); return IRQ_NONE; + } dev_err_ratelimited(smmu->dev, "Unexpected global fault, this could be serious\n"); @@ -698,9 +1549,36 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev) gfsr, gfsynr0, gfsynr1, gfsynr2); writel(gfsr, gr0_base + ARM_SMMU_GR0_sGFSR); + arm_smmu_disable_clocks(smmu); return IRQ_HANDLED; } +static void arm_smmu_trigger_fault(struct iommu_domain *domain, + unsigned long flags) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + struct arm_smmu_device *smmu; + void __iomem *cb_base; + + if (!smmu_domain->smmu) { + pr_err("Can't trigger faults on non-attached domains\n"); + return; + } + + smmu = smmu_domain->smmu; + + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + if (arm_smmu_enable_clocks(smmu)) + return; + dev_err(smmu->dev, "Writing 0x%lx to FSRRESTORE on cb %d\n", + flags, cfg->cbndx); + writel_relaxed(flags, cb_base + ARM_SMMU_CB_FSRRESTORE); + /* give the interrupt time to fire... */ + msleep(1000); + arm_smmu_disable_clocks(smmu); +} + static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, struct io_pgtable_cfg *pgtbl_cfg) { @@ -723,6 +1601,9 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, */ #ifdef CONFIG_64BIT reg = CBA2R_RW64_64BIT; + if (!arm_smmu_has_secure_vmid(smmu_domain) && + arm_smmu_is_static_cb(smmu)) + msm_tz_set_cb_format(smmu->sec_id, cfg->cbndx); #else reg = CBA2R_RW64_32BIT; #endif @@ -741,9 +1622,8 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, if (stage1) { reg |= (CBAR_S1_BPSHCFG_NSH << CBAR_S1_BPSHCFG_SHIFT) | (CBAR_S1_MEMATTR_WB << CBAR_S1_MEMATTR_SHIFT); - } else { - reg |= ARM_SMMU_CB_VMID(cfg) << CBAR_VMID_SHIFT; } + reg |= ARM_SMMU_CB_VMID(cfg) << CBAR_VMID_SHIFT; writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(cfg->cbndx)); /* TTBRs */ @@ -767,7 +1647,6 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR); if (smmu->version > ARM_SMMU_V1) { reg = pgtbl_cfg->arm_lpae_s1_cfg.tcr >> 32; - reg |= TTBCR2_SEP_UPSTREAM; writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBCR2); } } else { @@ -783,8 +1662,25 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR1); } + if (smmu->model == SMMU_MODEL_QCOM_V2) { + reg = ACTLR_QCOM_ISH << ACTLR_QCOM_ISH_SHIFT | + ACTLR_QCOM_OSH << ACTLR_QCOM_OSH_SHIFT | + ACTLR_QCOM_NSH << ACTLR_QCOM_NSH_SHIFT; + writel_relaxed(reg, cb_base + ARM_SMMU_CB_ACTLR); + } + /* SCTLR */ - reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP; + reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_EAE_SBOP; + + if (smmu_domain->attributes & (1 << DOMAIN_ATTR_CB_STALL_DISABLE)) { + reg &= ~SCTLR_CFCFG; + reg |= SCTLR_HUPCF; + } + + if ((!(smmu_domain->attributes & (1 << DOMAIN_ATTR_S1_BYPASS)) && + !(smmu_domain->attributes & (1 << DOMAIN_ATTR_EARLY_MAP))) || + !stage1) + reg |= SCTLR_M; if (stage1) reg |= SCTLR_S1_ASIDPNE; #ifdef __BIG_ENDIAN @@ -793,20 +1689,107 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain, writel_relaxed(reg, cb_base + ARM_SMMU_CB_SCTLR); } +static bool arm_smmu_is_static_cb(struct arm_smmu_device *smmu) +{ + return smmu->options & ARM_SMMU_OPT_STATIC_CB; +} + +static bool arm_smmu_has_secure_vmid(struct arm_smmu_domain *smmu_domain) +{ + return smmu_domain->secure_vmid != VMID_INVAL; +} + +static bool arm_smmu_is_slave_side_secure(struct arm_smmu_domain *smmu_domain) +{ + return arm_smmu_has_secure_vmid(smmu_domain) + && smmu_domain->slave_side_secure; +} + +static bool arm_smmu_is_master_side_secure(struct arm_smmu_domain *smmu_domain) +{ + return arm_smmu_has_secure_vmid(smmu_domain) + && !smmu_domain->slave_side_secure; +} + +static void arm_smmu_secure_domain_lock(struct arm_smmu_domain *smmu_domain) +{ + if (arm_smmu_is_master_side_secure(smmu_domain)) + mutex_lock(&smmu_domain->assign_lock); +} + +static void arm_smmu_secure_domain_unlock(struct arm_smmu_domain *smmu_domain) +{ + if (arm_smmu_is_master_side_secure(smmu_domain)) + mutex_unlock(&smmu_domain->assign_lock); +} + +static unsigned long arm_smmu_pgtbl_lock(struct arm_smmu_domain *smmu_domain) +{ + unsigned long flags = 0; + + if (arm_smmu_is_slave_side_secure(smmu_domain)) + mutex_lock(&smmu_domain->pgtbl_mutex_lock); + else + spin_lock_irqsave(&smmu_domain->pgtbl_spin_lock, flags); + + return flags; +} + +static void arm_smmu_pgtbl_unlock(struct arm_smmu_domain *smmu_domain, + unsigned long flags) +{ + if (arm_smmu_is_slave_side_secure(smmu_domain)) + mutex_unlock(&smmu_domain->pgtbl_mutex_lock); + else + spin_unlock_irqrestore(&smmu_domain->pgtbl_spin_lock, flags); +} + +static int arm_smmu_restore_sec_cfg(struct arm_smmu_device *smmu) +{ + int ret; + u64 scm_ret = 0; + + if (!arm_smmu_is_static_cb(smmu)) + return 0; + + ret = scm_restore_sec_cfg(smmu->sec_id, 0x0, &scm_ret); + if (ret || scm_ret) { + pr_err("scm call IOMMU_SECURE_CFG failed\n"); + return -EINVAL; + } + + return 0; +} + +static bool is_iommu_pt_coherent(struct arm_smmu_domain *smmu_domain) +{ + if (smmu_domain->attributes & + (1 << DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT)) + return true; + else if (smmu_domain->smmu && smmu_domain->smmu->dev) + return smmu_domain->smmu->dev->archdata.dma_coherent; + else + return false; +} + static int arm_smmu_init_domain_context(struct iommu_domain *domain, - struct arm_smmu_device *smmu) + struct arm_smmu_device *smmu, + struct arm_smmu_master_cfg *master_cfg) { int irq, start, ret = 0; unsigned long ias, oas; + int sep = 0; struct io_pgtable_ops *pgtbl_ops; - struct io_pgtable_cfg pgtbl_cfg; enum io_pgtable_fmt fmt; struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + bool is_fast = smmu_domain->attributes & (1 << DOMAIN_ATTR_FAST); + unsigned long quirks = + smmu_domain->attributes & (1 << DOMAIN_ATTR_ENABLE_TTBR1) ? + IO_PGTABLE_QUIRK_ARM_TTBR1 : 0; - mutex_lock(&smmu_domain->init_mutex); if (smmu_domain->smmu) - goto out_unlock; + goto out; /* * Mapping the requested stage onto what we support is surprisingly @@ -837,9 +1820,27 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, start = smmu->num_s2_context_banks; ias = smmu->va_size; oas = smmu->ipa_size; - if (IS_ENABLED(CONFIG_64BIT)) + if (IS_ENABLED(CONFIG_64BIT)) { fmt = ARM_64_LPAE_S1; - else + + if (quirks & IO_PGTABLE_QUIRK_ARM_TTBR1) { + + /* + * When the UBS id is 5 we know that the bus + * size is 49 bits and that bit 48 is the fixed + * sign extension bit. For any other bus size + * we need to specify the sign extension bit + * and adjust the input size accordingly + */ + + if (smmu->ubs == 5) { + sep = 48; + } else { + sep = ias - 1; + ias--; + } + } + } else fmt = ARM_32_LPAE_S1; break; case ARM_SMMU_DOMAIN_NESTED: @@ -859,15 +1860,18 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, break; default: ret = -EINVAL; - goto out_unlock; + goto out; } - ret = __arm_smmu_alloc_bitmap(smmu->context_map, start, - smmu->num_context_banks); - if (IS_ERR_VALUE(ret)) - goto out_unlock; + if (cfg->cbndx == INVALID_CBNDX) { + ret = arm_smmu_alloc_context_idx(smmu, start, + smmu->num_context_banks, master_cfg->streamids, + master_cfg->num_streamids); + if (IS_ERR_VALUE(ret)) + goto out; + cfg->cbndx = ret; + } - cfg->cbndx = ret; if (smmu->version == ARM_SMMU_V1) { cfg->irptndx = atomic_inc_return(&smmu->irptndx); cfg->irptndx %= smmu->num_context_irqs; @@ -875,50 +1879,82 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, cfg->irptndx = cfg->cbndx; } - pgtbl_cfg = (struct io_pgtable_cfg) { - .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap, - .ias = ias, - .oas = oas, - .tlb = &arm_smmu_gather_ops, - .iommu_dev = smmu->dev, - }; - smmu_domain->smmu = smmu; - pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, smmu_domain); + + if (is_iommu_pt_coherent(smmu_domain)) + quirks |= IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT; + + if (arm_smmu_is_slave_side_secure(smmu_domain)) { + smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) { + .quirks = quirks, + .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap, + .arm_msm_secure_cfg = { + .sec_id = smmu->sec_id, + .cbndx = cfg->cbndx, + }, + .iommu_dev = smmu->dev, + }; + fmt = ARM_MSM_SECURE; + } else { + + smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) { + .quirks = quirks, + .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap, + .ias = ias, + .oas = oas, + .sep = sep, + .tlb = &arm_smmu_gather_ops, + .iommu_dev = smmu->dev, + .iova_base = domain->geometry.aperture_start, + .iova_end = domain->geometry.aperture_end, + }; + } + + if (is_fast) + fmt = ARM_V8L_FAST; + + cfg->asid = cfg->cbndx + 1; + cfg->vmid = cfg->cbndx + 2; + pgtbl_ops = alloc_io_pgtable_ops(fmt, &smmu_domain->pgtbl_cfg, + smmu_domain); if (!pgtbl_ops) { ret = -ENOMEM; goto out_clear_smmu; } - - /* Update our support page sizes to reflect the page table format */ - arm_smmu_ops.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; + /* + * assign any page table memory that might have been allocated + * during alloc_io_pgtable_ops + */ + if (arm_smmu_is_master_side_secure(smmu_domain)) { + arm_smmu_secure_domain_lock(smmu_domain); + arm_smmu_assign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + } /* Initialise the context bank with our page table cfg */ - arm_smmu_init_context_bank(smmu_domain, &pgtbl_cfg); + arm_smmu_init_context_bank(smmu_domain, &smmu_domain->pgtbl_cfg); /* * Request context fault interrupt. Do this last to avoid the * handler seeing a half-initialised domain state. */ irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx]; - ret = request_irq(irq, arm_smmu_context_fault, IRQF_SHARED, - "arm-smmu-context-fault", domain); + ret = request_threaded_irq(irq, NULL, arm_smmu_context_fault, + IRQF_ONESHOT | IRQF_SHARED, + "arm-smmu-context-fault", domain); if (IS_ERR_VALUE(ret)) { dev_err(smmu->dev, "failed to request context IRQ %d (%u)\n", cfg->irptndx, irq); cfg->irptndx = INVALID_IRPTNDX; } - mutex_unlock(&smmu_domain->init_mutex); - /* Publish page table ops for map/unmap */ smmu_domain->pgtbl_ops = pgtbl_ops; return 0; out_clear_smmu: smmu_domain->smmu = NULL; -out_unlock: - mutex_unlock(&smmu_domain->init_mutex); +out: return ret; } @@ -930,9 +1966,8 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) void __iomem *cb_base; int irq; - if (!smmu) - return; - + if (arm_smmu_enable_clocks(smmu_domain->smmu)) + goto free_irqs; /* * Disable the context bank and free the page tables before freeing * it. @@ -940,15 +1975,32 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR); + arm_smmu_disable_clocks(smmu_domain->smmu); + + if (smmu_domain->pgtbl_ops) { + free_io_pgtable_ops(smmu_domain->pgtbl_ops); + /* unassign any freed page table memory */ + if (arm_smmu_is_master_side_secure(smmu_domain)) { + arm_smmu_secure_domain_lock(smmu_domain); + arm_smmu_secure_pool_destroy(smmu_domain); + arm_smmu_unassign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + } + smmu_domain->pgtbl_ops = NULL; + } + +free_irqs: if (cfg->irptndx != INVALID_IRPTNDX) { irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx]; free_irq(irq, domain); } - if (smmu_domain->pgtbl_ops) - free_io_pgtable_ops(smmu_domain->pgtbl_ops); - - __arm_smmu_free_bitmap(smmu->context_map, cfg->cbndx); + arm_smmu_free_context_idx(smmu, cfg->cbndx); + smmu_domain->smmu = NULL; + cfg->cbndx = INVALID_CBNDX; + cfg->irptndx = INVALID_IRPTNDX; + cfg->asid = INVALID_ASID; + cfg->vmid = INVALID_VMID; } static struct iommu_domain *arm_smmu_domain_alloc(unsigned type) @@ -966,8 +2018,19 @@ static struct iommu_domain *arm_smmu_domain_alloc(unsigned type) if (!smmu_domain) return NULL; + smmu_domain->secure_vmid = VMID_INVAL; + INIT_LIST_HEAD(&smmu_domain->pte_info_list); + INIT_LIST_HEAD(&smmu_domain->unassign_list); + INIT_LIST_HEAD(&smmu_domain->secure_pool_list); + smmu_domain->cfg.cbndx = INVALID_CBNDX; + smmu_domain->cfg.irptndx = INVALID_IRPTNDX; + smmu_domain->cfg.asid = INVALID_ASID; + smmu_domain->cfg.vmid = INVALID_VMID; + mutex_init(&smmu_domain->init_mutex); - spin_lock_init(&smmu_domain->pgtbl_lock); + spin_lock_init(&smmu_domain->pgtbl_spin_lock); + mutex_init(&smmu_domain->assign_lock); + mutex_init(&smmu_domain->pgtbl_mutex_lock); return &smmu_domain->domain; } @@ -980,7 +2043,18 @@ static void arm_smmu_domain_free(struct iommu_domain *domain) * Free the domain resources. We assume that all devices have * already been detached. */ - arm_smmu_destroy_domain_context(domain); + if (smmu_domain->pgtbl_ops) { + free_io_pgtable_ops(smmu_domain->pgtbl_ops); + /* unassign any freed page table memory */ + if (arm_smmu_is_master_side_secure(smmu_domain)) { + arm_smmu_secure_domain_lock(smmu_domain); + arm_smmu_secure_pool_destroy(smmu_domain); + arm_smmu_unassign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + } + smmu_domain->pgtbl_ops = NULL; + } + kfree(smmu_domain); } @@ -1006,8 +2080,8 @@ static int arm_smmu_master_configure_smrs(struct arm_smmu_device *smmu, /* Allocate the SMRs on the SMMU */ for (i = 0; i < cfg->num_streamids; ++i) { - int idx = __arm_smmu_alloc_bitmap(smmu->smr_map, 0, - smmu->num_mapping_groups); + int idx = arm_smmu_alloc_smr_idx(smmu, 0, + smmu->num_mapping_groups, cfg->streamids[i]); if (IS_ERR_VALUE(idx)) { dev_err(smmu->dev, "failed to allocate free SMR\n"); goto err_free_smrs; @@ -1032,7 +2106,7 @@ static int arm_smmu_master_configure_smrs(struct arm_smmu_device *smmu, err_free_smrs: while (--i >= 0) - __arm_smmu_free_bitmap(smmu->smr_map, smrs[i].idx); + arm_smmu_free_smr_idx(smmu, smrs[i].idx); kfree(smrs); return -ENOSPC; } @@ -1052,7 +2126,7 @@ static void arm_smmu_master_free_smrs(struct arm_smmu_device *smmu, u8 idx = smrs[i].idx; writel_relaxed(~SMR_VALID, gr0_base + ARM_SMMU_GR0_SMR(idx)); - __arm_smmu_free_bitmap(smmu->smr_map, idx); + arm_smmu_free_smr_idx(smmu, idx); } cfg->smrs = NULL; @@ -1098,6 +2172,8 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, * We *must* clear the S2CR first, because freeing the SMR means * that it can be re-allocated immediately. */ + if (arm_smmu_enable_clocks(smmu)) + return; for (i = 0; i < cfg->num_streamids; ++i) { u32 idx = cfg->smrs ? cfg->smrs[i].idx : cfg->streamids[i]; @@ -1106,6 +2182,132 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, } arm_smmu_master_free_smrs(smmu, cfg); + arm_smmu_disable_clocks(smmu); +} + +static void arm_smmu_impl_def_programming(struct arm_smmu_device *smmu) +{ + int i; + struct arm_smmu_impl_def_reg *regs = smmu->impl_def_attach_registers; + + arm_smmu_halt(smmu); + for (i = 0; i < smmu->num_impl_def_attach_registers; ++i) + writel_relaxed(regs[i].value, + ARM_SMMU_GR0(smmu) + regs[i].offset); + arm_smmu_resume(smmu); +} + +static int arm_smmu_attach_dynamic(struct iommu_domain *domain, + struct arm_smmu_device *smmu) +{ + int ret; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + enum io_pgtable_fmt fmt; + struct io_pgtable_ops *pgtbl_ops = NULL; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + + if (!(smmu->options & ARM_SMMU_OPT_DYNAMIC)) { + dev_err(smmu->dev, "dynamic domains not supported\n"); + return -EPERM; + } + + if (smmu_domain->smmu != NULL) { + dev_err(smmu->dev, "domain is already attached\n"); + return -EBUSY; + } + + if (smmu_domain->cfg.cbndx >= smmu->num_context_banks) { + dev_err(smmu->dev, "invalid context bank\n"); + return -ENODEV; + } + + if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) { + smmu_domain->cfg.cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS; + } else if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) { + smmu_domain->cfg.cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS; + } else { + /* dynamic only makes sense for S1. */ + return -EINVAL; + } + + smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) { + .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap, + .ias = smmu->va_size, + .oas = smmu->ipa_size, + .tlb = &arm_smmu_gather_ops, + .iommu_dev = smmu->dev, + }; + + fmt = IS_ENABLED(CONFIG_64BIT) ? ARM_64_LPAE_S1 : ARM_32_LPAE_S1; + + pgtbl_ops = alloc_io_pgtable_ops(fmt, &smmu_domain->pgtbl_cfg, + smmu_domain); + if (!pgtbl_ops) + return -ENOMEM; + + /* + * assign any page table memory that might have been allocated + * during alloc_io_pgtable_ops + */ + if (arm_smmu_is_master_side_secure(smmu_domain)) { + arm_smmu_secure_domain_lock(smmu_domain); + arm_smmu_assign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + } + + cfg->vmid = cfg->cbndx + 2; + smmu_domain->smmu = smmu; + + mutex_lock(&smmu->attach_lock); + /* try to avoid reusing an old ASID right away */ + ret = idr_alloc_cyclic(&smmu->asid_idr, domain, + smmu->num_context_banks + 2, + MAX_ASID + 1, GFP_KERNEL); + if (ret < 0) { + dev_err_ratelimited(smmu->dev, + "dynamic ASID allocation failed: %d\n", ret); + goto out; + } + + smmu_domain->cfg.asid = ret; + smmu_domain->smmu = smmu; + smmu_domain->pgtbl_ops = pgtbl_ops; + ret = 0; +out: + if (ret) { + free_io_pgtable_ops(pgtbl_ops); + /* unassign any freed page table memory */ + if (arm_smmu_is_master_side_secure(smmu_domain)) { + arm_smmu_secure_domain_lock(smmu_domain); + arm_smmu_secure_pool_destroy(smmu_domain); + arm_smmu_unassign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + } + smmu_domain->pgtbl_ops = NULL; + } + mutex_unlock(&smmu->attach_lock); + + return ret; +} + +static int arm_smmu_populate_cb(struct arm_smmu_device *smmu, + struct arm_smmu_domain *smmu_domain, struct device *dev) +{ + struct arm_smmu_master_cfg *cfg; + struct arm_smmu_cfg *smmu_cfg = &smmu_domain->cfg; + struct static_cbndx_entry *entry; + + cfg = find_smmu_master_cfg(dev); + if (!cfg) + return -ENODEV; + + entry = arm_smmu_get_static_entry_from_sid(smmu, cfg->streamids[0]); + if (entry && entry->type == TYPE_TRANS) { + smmu_cfg->cbndx = entry->cbndx; + return 0; + } + + return -EINVAL; } static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) @@ -1114,22 +2316,83 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct arm_smmu_device *smmu; struct arm_smmu_master_cfg *cfg; + int atomic_ctx = smmu_domain->attributes & (1 << DOMAIN_ATTR_ATOMIC); + mutex_lock(&smmu_domain->init_mutex); smmu = find_smmu_for_device(dev); if (!smmu) { dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n"); + mutex_unlock(&smmu_domain->init_mutex); return -ENXIO; } + if (smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC)) { + ret = arm_smmu_attach_dynamic(domain, smmu); + mutex_unlock(&smmu_domain->init_mutex); + return ret; + } + + mutex_lock(&smmu->attach_lock); + if (dev->archdata.iommu) { dev_err(dev, "already attached to IOMMU domain\n"); - return -EEXIST; + ret = -EEXIST; + goto err_unlock; + } + + if (!smmu->attach_count) { + /* + * We need an extra power vote if we can't retain register + * settings across a power collapse, or if this is an + * atomic domain (since atomic domains can't sleep during + * unmap, so regulators already need to be on to enable tlb + * invalidation). The result (due to regulator + * refcounting) is that we never disable regulators while a + * client is attached in these cases. + */ + if (!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE)) { + ret = arm_smmu_enable_regulators(smmu); + if (ret) + goto err_unlock; + } + ret = arm_smmu_enable_clocks(smmu); + if (ret) + goto err_disable_regulators; + arm_smmu_device_reset(smmu); + arm_smmu_impl_def_programming(smmu); + } else { + ret = arm_smmu_enable_clocks(smmu); + if (ret) + goto err_unlock; + } + smmu->attach_count++; + + if (atomic_ctx) { + ret = arm_smmu_enable_regulators(smmu); + if (ret) + goto err_disable_clocks; + } + + if (arm_smmu_is_static_cb(smmu)) { + ret = arm_smmu_populate_cb(smmu, smmu_domain, dev); + + if (ret) { + dev_err(dev, "Failed to get valid context bank\n"); + goto err_atomic_ctx; + } + smmu_domain->slave_side_secure = true; + } + + cfg = find_smmu_master_cfg(dev); + if (!cfg) { + ret = -ENODEV; + goto err_atomic_ctx; } /* Ensure that the domain is finalised */ - ret = arm_smmu_init_domain_context(domain, smmu); + ret = arm_smmu_init_domain_context(domain, smmu, cfg); if (IS_ERR_VALUE(ret)) - return ret; + goto err_atomic_ctx; /* * Sanity check the domain. We don't support domains across @@ -1139,31 +2402,196 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) dev_err(dev, "cannot attach to SMMU %s whilst already attached to domain on SMMU %s\n", dev_name(smmu_domain->smmu->dev), dev_name(smmu->dev)); - return -EINVAL; + ret = -EINVAL; + goto err_destroy_domain_context; } /* Looks ok, so add the device to the domain */ - cfg = find_smmu_master_cfg(dev); - if (!cfg) - return -ENODEV; - ret = arm_smmu_domain_add_master(smmu_domain, cfg); - if (!ret) - dev->archdata.iommu = domain; + if (ret) + goto err_destroy_domain_context; + dev->archdata.iommu = domain; + arm_smmu_disable_clocks(smmu); + mutex_unlock(&smmu->attach_lock); + mutex_unlock(&smmu_domain->init_mutex); + return ret; + +err_destroy_domain_context: + arm_smmu_destroy_domain_context(domain); +err_atomic_ctx: + if (atomic_ctx) + arm_smmu_disable_regulators(smmu); +err_disable_clocks: + arm_smmu_disable_clocks(smmu); + --smmu->attach_count; +err_disable_regulators: + if (!smmu->attach_count && + (!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE))) + arm_smmu_disable_regulators(smmu); +err_unlock: + mutex_unlock(&smmu->attach_lock); + mutex_unlock(&smmu_domain->init_mutex); return ret; } +static void arm_smmu_power_off(struct arm_smmu_device *smmu) +{ + /* Turn the thing off */ + if (arm_smmu_enable_clocks(smmu)) + return; + writel_relaxed(sCR0_CLIENTPD, + ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0); + arm_smmu_disable_clocks(smmu); + if (!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE)) + arm_smmu_disable_regulators(smmu); +} + +static void arm_smmu_detach_dynamic(struct iommu_domain *domain, + struct arm_smmu_device *smmu) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + + mutex_lock(&smmu->attach_lock); + if (smmu->attach_count > 0) { + if (arm_smmu_enable_clocks(smmu_domain->smmu)) + goto idr_remove; + arm_smmu_tlb_inv_context(smmu_domain); + arm_smmu_disable_clocks(smmu_domain->smmu); + } +idr_remove: + idr_remove(&smmu->asid_idr, smmu_domain->cfg.asid); + smmu_domain->cfg.asid = INVALID_ASID; + smmu_domain->smmu = NULL; + mutex_unlock(&smmu->attach_lock); +} + static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) { struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct arm_smmu_master_cfg *cfg; + struct arm_smmu_device *smmu; + int atomic_ctx = smmu_domain->attributes & (1 << DOMAIN_ATTR_ATOMIC); + + mutex_lock(&smmu_domain->init_mutex); + smmu = smmu_domain->smmu; + if (!smmu) { + dev_err(dev, "Domain already detached!\n"); + mutex_unlock(&smmu_domain->init_mutex); + return; + } + + + if (smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC)) { + arm_smmu_detach_dynamic(domain, smmu); + mutex_unlock(&smmu_domain->init_mutex); + if (atomic_ctx) + arm_smmu_disable_regulators(smmu); + return; + } + + mutex_lock(&smmu->attach_lock); cfg = find_smmu_master_cfg(dev); if (!cfg) - return; + goto unlock; dev->archdata.iommu = NULL; arm_smmu_domain_remove_master(smmu_domain, cfg); + arm_smmu_destroy_domain_context(domain); + if (!--smmu->attach_count) + arm_smmu_power_off(smmu); + if (atomic_ctx) + arm_smmu_disable_regulators(smmu); +unlock: + mutex_unlock(&smmu->attach_lock); + mutex_unlock(&smmu_domain->init_mutex); +} + + +static int arm_smmu_assign_table(struct arm_smmu_domain *smmu_domain) +{ + int ret = 0; + int dest_vmids[2] = {VMID_HLOS, smmu_domain->secure_vmid}; + int dest_perms[2] = {PERM_READ | PERM_WRITE, PERM_READ}; + int source_vmid = VMID_HLOS; + struct arm_smmu_pte_info *pte_info, *temp; + + if (!arm_smmu_is_master_side_secure(smmu_domain)) + return ret; + + list_for_each_entry(pte_info, &smmu_domain->pte_info_list, entry) { + ret = hyp_assign_phys(virt_to_phys(pte_info->virt_addr), + PAGE_SIZE, &source_vmid, 1, + dest_vmids, dest_perms, 2); + if (WARN_ON(ret)) + break; + } + + list_for_each_entry_safe(pte_info, temp, &smmu_domain->pte_info_list, + entry) { + list_del(&pte_info->entry); + kfree(pte_info); + } + return ret; +} + +static void arm_smmu_unassign_table(struct arm_smmu_domain *smmu_domain) +{ + int ret; + int dest_vmids = VMID_HLOS; + int dest_perms = PERM_READ | PERM_WRITE | PERM_EXEC; + int source_vmlist[2] = {smmu_domain->secure_vmid, VMID_HLOS}; + struct arm_smmu_pte_info *pte_info, *temp; + + if (!arm_smmu_is_master_side_secure(smmu_domain)) + return; + + list_for_each_entry(pte_info, &smmu_domain->unassign_list, entry) { + ret = hyp_assign_phys(virt_to_phys(pte_info->virt_addr), + PAGE_SIZE, source_vmlist, 2, + &dest_vmids, &dest_perms, 1); + if (WARN_ON(ret)) + break; + + free_pages_exact(pte_info->virt_addr, pte_info->size); + } + + list_for_each_entry_safe(pte_info, temp, &smmu_domain->unassign_list, + entry) { + list_del(&pte_info->entry); + kfree(pte_info); + } + return; +} + +static void arm_smmu_unprepare_pgtable(void *cookie, void *addr, size_t size) +{ + struct arm_smmu_domain *smmu_domain = cookie; + struct arm_smmu_pte_info *pte_info; + + BUG_ON(!arm_smmu_is_master_side_secure(smmu_domain)); + + pte_info = kzalloc(sizeof(struct arm_smmu_pte_info), GFP_ATOMIC); + if (!pte_info) + return; + + pte_info->virt_addr = addr; + pte_info->size = size; + list_add_tail(&pte_info->entry, &smmu_domain->unassign_list); +} + +static void arm_smmu_prepare_pgtable(void *addr, void *cookie) +{ + struct arm_smmu_domain *smmu_domain = cookie; + struct arm_smmu_pte_info *pte_info; + + BUG_ON(!arm_smmu_is_master_side_secure(smmu_domain)); + + pte_info = kzalloc(sizeof(struct arm_smmu_pte_info), GFP_ATOMIC); + if (!pte_info) + return; + pte_info->virt_addr = addr; + list_add_tail(&pte_info->entry, &smmu_domain->pte_info_list); } static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova, @@ -1177,9 +2605,87 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova, if (!ops) return -ENODEV; - spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); + arm_smmu_secure_domain_lock(smmu_domain); + + flags = arm_smmu_pgtbl_lock(smmu_domain); ret = ops->map(ops, iova, paddr, size, prot); - spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + + if (!ret) + ret = arm_smmu_assign_table(smmu_domain); + + arm_smmu_secure_domain_unlock(smmu_domain); + + return ret; +} + +static uint64_t arm_smmu_iova_to_pte(struct iommu_domain *domain, + dma_addr_t iova) +{ + uint64_t ret; + unsigned long flags; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops; + + if (!ops) + return 0; + + flags = arm_smmu_pgtbl_lock(smmu_domain); + ret = ops->iova_to_pte(ops, iova); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + return ret; +} + +static size_t arm_smmu_map_sg(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, unsigned int nents, int prot) +{ + int ret; + size_t size; + unsigned long flags; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops; + struct arm_smmu_device *smmu = smmu_domain->smmu; + int atomic_ctx = smmu_domain->attributes & (1 << DOMAIN_ATTR_ATOMIC); + + if (!ops) + return -ENODEV; + + if (arm_smmu_is_slave_side_secure(smmu_domain) && atomic_ctx) { + dev_err(smmu->dev, "Slave side atomic context not supported\n"); + return 0; + } + + if (arm_smmu_is_slave_side_secure(smmu_domain)) { + mutex_lock(&smmu_domain->init_mutex); + + if (arm_smmu_enable_clocks(smmu)) { + mutex_unlock(&smmu_domain->init_mutex); + return 0; + } + } + + arm_smmu_secure_domain_lock(smmu_domain); + + flags = arm_smmu_pgtbl_lock(smmu_domain); + ret = ops->map_sg(ops, iova, sg, nents, prot, &size); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + + if (ret) { + if (arm_smmu_assign_table(smmu_domain)) { + ret = 0; + goto out; + } + } else { + arm_smmu_secure_domain_unlock(smmu_domain); + arm_smmu_unmap(domain, iova, size); + } + +out: + arm_smmu_secure_domain_unlock(smmu_domain); + if (arm_smmu_is_slave_side_secure(smmu_domain)) { + arm_smmu_disable_clocks(smmu_domain->smmu); + mutex_unlock(&smmu_domain->init_mutex); + } return ret; } @@ -1190,31 +2696,206 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova, unsigned long flags; struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + int atomic_ctx = smmu_domain->attributes & (1 << DOMAIN_ATTR_ATOMIC); if (!ops) return 0; - spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); + if (arm_smmu_is_slave_side_secure(smmu_domain) && atomic_ctx) { + dev_err(smmu_domain->smmu->dev, + "Slave side atomic context not supported\n"); + return 0; + } + + /* + * The contract here is that if you set DOMAIN_ATTR_ATOMIC your + * domain *must* must be attached an SMMU during unmap. This + * function calls other functions that try to use smmu_domain->smmu + * if it's not NULL (like the tlb invalidation routines). So if + * the client sets DOMAIN_ATTR_ATOMIC and detaches in the middle of + * the unmap the smmu instance could go away and we could + * dereference NULL. This little BUG_ON should catch most gross + * offenders but if atomic clients violate this contract then this + * code is racy. + */ + BUG_ON(atomic_ctx && !smmu_domain->smmu); + + if (atomic_ctx) { + if (arm_smmu_enable_clocks_atomic(smmu_domain->smmu)) + return 0; + } else { + mutex_lock(&smmu_domain->init_mutex); + arm_smmu_secure_domain_lock(smmu_domain); + if (smmu_domain->smmu && + arm_smmu_enable_clocks(smmu_domain->smmu)) { + arm_smmu_secure_domain_unlock(smmu_domain); + mutex_unlock(&smmu_domain->init_mutex); + return 0; + } + } + + flags = arm_smmu_pgtbl_lock(smmu_domain); ret = ops->unmap(ops, iova, size); - spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + + /* + * While splitting up block mappings, we might allocate page table + * memory during unmap, so the vmids needs to be assigned to the + * memory here as well. + */ + if (arm_smmu_assign_table(smmu_domain)) { + arm_smmu_unassign_table(smmu_domain); + arm_smmu_secure_domain_unlock(smmu_domain); + mutex_unlock(&smmu_domain->init_mutex); + return 0; + } + + /* Also unassign any pages that were free'd during unmap */ + arm_smmu_unassign_table(smmu_domain); + + if (atomic_ctx) { + arm_smmu_disable_clocks_atomic(smmu_domain->smmu); + } else { + if (smmu_domain->smmu) + arm_smmu_disable_clocks(smmu_domain->smmu); + arm_smmu_secure_domain_unlock(smmu_domain); + mutex_unlock(&smmu_domain->init_mutex); + } + return ret; } -static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, - dma_addr_t iova) +static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + phys_addr_t ret; + unsigned long flags; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + + if (!ops) + return 0; + + flags = arm_smmu_pgtbl_lock(smmu_domain); + ret = ops->iova_to_phys(ops, iova); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + return ret; +} + +static bool arm_smmu_is_iova_coherent(struct iommu_domain *domain, + dma_addr_t iova) +{ + bool ret; + unsigned long flags; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops; + + if (!ops) + return false; + + flags = arm_smmu_pgtbl_lock(smmu_domain); + ret = ops->is_iova_coherent(ops, iova); + arm_smmu_pgtbl_unlock(smmu_domain, flags); + return ret; +} + +static int arm_smmu_wait_for_halt(struct arm_smmu_device *smmu) +{ + void __iomem *impl_def1_base = ARM_SMMU_IMPL_DEF1(smmu); + u32 tmp; + + if (readl_poll_timeout_atomic(impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL, + tmp, (tmp & MICRO_MMU_CTRL_IDLE), + 0, 30000)) { + dev_err(smmu->dev, "Couldn't halt SMMU!\n"); + return -EBUSY; + } + + return 0; +} + +static int __arm_smmu_halt(struct arm_smmu_device *smmu, bool wait) +{ + u32 reg; + void __iomem *impl_def1_base = ARM_SMMU_IMPL_DEF1(smmu); + + reg = readl_relaxed(impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL); + reg |= MICRO_MMU_CTRL_LOCAL_HALT_REQ; + + if (arm_smmu_is_static_cb(smmu)) { + phys_addr_t impl_def1_base_phys = impl_def1_base - smmu->base + + smmu->phys_addr; + + if (scm_io_write(impl_def1_base_phys + + IMPL_DEF1_MICRO_MMU_CTRL, reg)) { + dev_err(smmu->dev, + "scm_io_write fail. SMMU might not be halted"); + return -EINVAL; + } + } else { + writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL); + } + + return wait ? arm_smmu_wait_for_halt(smmu) : 0; +} + +static int arm_smmu_halt(struct arm_smmu_device *smmu) +{ + return __arm_smmu_halt(smmu, true); +} + +static int arm_smmu_halt_nowait(struct arm_smmu_device *smmu) +{ + return __arm_smmu_halt(smmu, false); +} + +static void arm_smmu_resume(struct arm_smmu_device *smmu) +{ + void __iomem *impl_def1_base = ARM_SMMU_IMPL_DEF1(smmu); + u32 reg; + + if (arm_smmu_restore_sec_cfg(smmu)) + return; + + reg = readl_relaxed(impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL); + reg &= ~MICRO_MMU_CTRL_LOCAL_HALT_REQ; + + if (arm_smmu_is_static_cb(smmu)) { + phys_addr_t impl_def1_base_phys = impl_def1_base - smmu->base + + smmu->phys_addr; + + if (scm_io_write(impl_def1_base_phys + + IMPL_DEF1_MICRO_MMU_CTRL, reg)) + dev_err(smmu->dev, + "scm_io_write fail. SMMU might not be resumed"); + } else { + writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL); + } +} + +static phys_addr_t __arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, + dma_addr_t iova, bool do_halt) { struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); struct arm_smmu_device *smmu = smmu_domain->smmu; struct arm_smmu_cfg *cfg = &smmu_domain->cfg; - struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; struct device *dev = smmu->dev; void __iomem *cb_base; u32 tmp; u64 phys; unsigned long va; + unsigned long flags; + + if (arm_smmu_enable_clocks(smmu)) + return 0; cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + spin_lock_irqsave(&smmu->atos_lock, flags); + + if (do_halt && arm_smmu_halt(smmu)) + goto err_unlock; + /* ATS1 registers can only be written atomically */ va = iova & ~0xfffUL; if (smmu->version == ARM_SMMU_V2) @@ -1224,46 +2905,115 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, if (readl_poll_timeout_atomic(cb_base + ARM_SMMU_CB_ATSR, tmp, !(tmp & ATSR_ACTIVE), 5, 50)) { - dev_err(dev, - "iova to phys timed out on %pad. Falling back to software table walk.\n", - &iova); - return ops->iova_to_phys(ops, iova); + dev_err(dev, "iova to phys timed out\n"); + goto err_resume; } phys = readl_relaxed(cb_base + ARM_SMMU_CB_PAR_LO); - phys |= ((u64)readl_relaxed(cb_base + ARM_SMMU_CB_PAR_HI)) << 32; + phys |= ((u64) readl_relaxed(cb_base + ARM_SMMU_CB_PAR_HI)) << 32; + + if (do_halt) + arm_smmu_resume(smmu); + spin_unlock_irqrestore(&smmu->atos_lock, flags); if (phys & CB_PAR_F) { - dev_err(dev, "translation fault!\n"); + dev_err(dev, "translation fault on %s!\n", dev_name(dev)); dev_err(dev, "PAR = 0x%llx\n", phys); - return 0; + phys = 0; + } else { + phys = (phys & (PHYS_MASK & ~0xfffULL)) | (iova & 0xfff); } - return (phys & GENMASK_ULL(39, 12)) | (iova & 0xfff); + arm_smmu_disable_clocks(smmu); + return phys; + +err_resume: + if (do_halt) + arm_smmu_resume(smmu); +err_unlock: + spin_unlock_irqrestore(&smmu->atos_lock, flags); + arm_smmu_disable_clocks(smmu); + phys = arm_smmu_iova_to_phys(domain, iova); + dev_err(dev, + "iova to phys failed 0x%pa. software table walk result=%pa.\n", + &iova, &phys); + return 0; } -static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, - dma_addr_t iova) +static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain, + dma_addr_t iova) +{ + return __arm_smmu_iova_to_phys_hard(domain, iova, true); +} + +static phys_addr_t arm_smmu_iova_to_phys_hard_no_halt( + struct iommu_domain *domain, dma_addr_t iova) +{ + return __arm_smmu_iova_to_phys_hard(domain, iova, false); +} + +static unsigned long arm_smmu_reg_read(struct iommu_domain *domain, + unsigned long offset) { - phys_addr_t ret; - unsigned long flags; struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); - struct io_pgtable_ops *ops= smmu_domain->pgtbl_ops; + struct arm_smmu_device *smmu; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + void __iomem *cb_base; + unsigned long val; - if (!ops) + if (offset >= SZ_4K) { + pr_err("Invalid offset: 0x%lx\n", offset); return 0; + } - spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); - if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS && - smmu_domain->stage == ARM_SMMU_DOMAIN_S1) { - ret = arm_smmu_iova_to_phys_hard(domain, iova); - } else { - ret = ops->iova_to_phys(ops, iova); + mutex_lock(&smmu_domain->init_mutex); + smmu = smmu_domain->smmu; + if (!smmu) { + WARN(1, "Can't read registers of a detached domain\n"); + val = 0; + goto unlock; } - spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + if (arm_smmu_enable_clocks(smmu)) { + val = 0; + goto unlock; + } + val = readl_relaxed(cb_base + offset); + arm_smmu_disable_clocks(smmu); - return ret; +unlock: + mutex_unlock(&smmu_domain->init_mutex); + return val; +} + +static void arm_smmu_reg_write(struct iommu_domain *domain, + unsigned long offset, unsigned long val) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + struct arm_smmu_device *smmu; + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + void __iomem *cb_base; + + if (offset >= SZ_4K) { + pr_err("Invalid offset: 0x%lx\n", offset); + return; + } + + mutex_lock(&smmu_domain->init_mutex); + smmu = smmu_domain->smmu; + if (!smmu) { + WARN(1, "Can't read registers of a detached domain\n"); + goto unlock; + } + + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + if (arm_smmu_enable_clocks(smmu)) + goto unlock; + writel_relaxed(val, cb_base + offset); + arm_smmu_disable_clocks(smmu); +unlock: + mutex_unlock(&smmu_domain->init_mutex); } static bool arm_smmu_capable(enum iommu_cap cap) @@ -1284,12 +3034,6 @@ static bool arm_smmu_capable(enum iommu_cap cap) } } -static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data) -{ - *((u16 *)data) = alias; - return 0; /* Continue walking */ -} - static void __arm_smmu_release_pci_iommudata(void *data) { kfree(data); @@ -1299,8 +3043,9 @@ static int arm_smmu_init_pci_device(struct pci_dev *pdev, struct iommu_group *group) { struct arm_smmu_master_cfg *cfg; - u16 sid; - int i; + u32 sid; + int tmp, ret; + struct device *dev = &pdev->dev; cfg = iommu_group_get_iommudata(group); if (!cfg) { @@ -1315,18 +3060,14 @@ static int arm_smmu_init_pci_device(struct pci_dev *pdev, if (cfg->num_streamids >= MAX_MASTER_STREAMIDS) return -ENOSPC; - /* - * Assume Stream ID == Requester ID for now. - * We need a way to describe the ID mappings in FDT. - */ - pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, &sid); - for (i = 0; i < cfg->num_streamids; ++i) - if (cfg->streamids[i] == sid) - break; - - /* Avoid duplicate SIDs, as this can lead to SMR conflicts */ - if (i == cfg->num_streamids) - cfg->streamids[cfg->num_streamids++] = sid; + ret = msm_pcie_configure_sid(dev, &sid, &tmp); + if (ret) { + dev_err(dev, + "Couldn't configure SID through PCI-e driver: %d\n", + ret); + return ret; + } + cfg->streamids[cfg->num_streamids++] = sid; return 0; } @@ -1370,12 +3111,18 @@ static struct iommu_group *arm_smmu_device_group(struct device *dev) struct iommu_group *group; int ret; - if (dev_is_pci(dev)) - group = pci_device_group(dev); - else - group = generic_device_group(dev); + /* + * We used to call pci_device_group here for dev_is_pci(dev) + * devices. However, that causes the root complex device to be + * placed in the same group as endpoint devices (and probably puts + * all endpoint devices in the same group as well), which makes + * things tricky in the DMA layer since we don't actually want to + * attach *everybody* in the group when one client calls attach. + * Instead, we'll just allocate a new group for everybody here. + */ + group = generic_device_group(dev); - if (IS_ERR(group)) + if (IS_ERR_OR_NULL(group)) return group; if (dev_is_pci(dev)) @@ -1394,15 +3141,120 @@ static struct iommu_group *arm_smmu_device_group(struct device *dev) static int arm_smmu_domain_get_attr(struct iommu_domain *domain, enum iommu_attr attr, void *data) { + int ret; struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + mutex_lock(&smmu_domain->init_mutex); switch (attr) { case DOMAIN_ATTR_NESTING: *(int *)data = (smmu_domain->stage == ARM_SMMU_DOMAIN_NESTED); - return 0; + ret = 0; + break; + case DOMAIN_ATTR_SECURE_VMID: + *((int *)data) = smmu_domain->secure_vmid; + ret = 0; + break; + case DOMAIN_ATTR_PT_BASE_ADDR: + *((phys_addr_t *)data) = + smmu_domain->pgtbl_cfg.arm_lpae_s1_cfg.ttbr[0]; + ret = 0; + break; + case DOMAIN_ATTR_CONTEXT_BANK: + /* context bank index isn't valid until we are attached */ + if (smmu_domain->smmu == NULL) + return -ENODEV; + + *((unsigned int *) data) = smmu_domain->cfg.cbndx; + ret = 0; + break; + case DOMAIN_ATTR_TTBR0: { + u64 val; + /* not valid until we are attached */ + if (smmu_domain->smmu == NULL) + return -ENODEV; + + val = smmu_domain->pgtbl_cfg.arm_lpae_s1_cfg.ttbr[0]; + if (smmu_domain->cfg.cbar != CBAR_TYPE_S2_TRANS) + val |= (u64)ARM_SMMU_CB_ASID(&smmu_domain->cfg) + << TTBRn_ASID_SHIFT; + *((u64 *)data) = val; + ret = 0; + break; + } + case DOMAIN_ATTR_CONTEXTIDR: + /* not valid until attached */ + if (smmu_domain->smmu == NULL) + return -ENODEV; + *((u32 *)data) = smmu_domain->cfg.procid; + ret = 0; + break; + case DOMAIN_ATTR_PROCID: + *((u32 *)data) = smmu_domain->cfg.procid; + ret = 0; + break; + case DOMAIN_ATTR_DYNAMIC: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_DYNAMIC)); + ret = 0; + break; + case DOMAIN_ATTR_NON_FATAL_FAULTS: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_NON_FATAL_FAULTS)); + ret = 0; + break; + case DOMAIN_ATTR_S1_BYPASS: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_S1_BYPASS)); + ret = 0; + break; + case DOMAIN_ATTR_FAST: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_FAST)); + ret = 0; + break; + case DOMAIN_ATTR_PGTBL_INFO: { + struct iommu_pgtbl_info *info = data; + + if (!(smmu_domain->attributes & (1 << DOMAIN_ATTR_FAST))) { + ret = -ENODEV; + break; + } + info->pmds = smmu_domain->pgtbl_cfg.av8l_fast_cfg.pmds; + ret = 0; + break; + } + case DOMAIN_ATTR_EARLY_MAP: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_EARLY_MAP)); + ret = 0; + break; + case DOMAIN_ATTR_PAGE_TABLE_IS_COHERENT: + if (!smmu_domain->smmu) + return -ENODEV; + *((int *)data) = is_iommu_pt_coherent(smmu_domain); + ret = 0; + break; + case DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT)); + ret = 0; + break; + case DOMAIN_ATTR_ENABLE_TTBR1: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_ENABLE_TTBR1)); + ret = 0; + break; + case DOMAIN_ATTR_CB_STALL_DISABLE: + *((int *)data) = !!(smmu_domain->attributes + & (1 << DOMAIN_ATTR_CB_STALL_DISABLE)); + ret = 0; + break; default: - return -ENODEV; + ret = -ENODEV; + break; } + mutex_unlock(&smmu_domain->init_mutex); + return ret; } static int arm_smmu_domain_set_attr(struct iommu_domain *domain, @@ -1424,10 +3276,174 @@ static int arm_smmu_domain_set_attr(struct iommu_domain *domain, smmu_domain->stage = ARM_SMMU_DOMAIN_NESTED; else smmu_domain->stage = ARM_SMMU_DOMAIN_S1; + break; + case DOMAIN_ATTR_SECURE_VMID: + BUG_ON(smmu_domain->secure_vmid != VMID_INVAL); + smmu_domain->secure_vmid = *((int *)data); + break; + case DOMAIN_ATTR_ATOMIC: + { + int atomic_ctx = *((int *)data); + if (atomic_ctx) + smmu_domain->attributes |= (1 << DOMAIN_ATTR_ATOMIC); + else + smmu_domain->attributes &= ~(1 << DOMAIN_ATTR_ATOMIC); + break; + } + case DOMAIN_ATTR_PROCID: + if (smmu_domain->smmu != NULL) { + dev_err(smmu_domain->smmu->dev, + "cannot change procid attribute while attached\n"); + ret = -EBUSY; + break; + } + smmu_domain->cfg.procid = *((u32 *)data); + ret = 0; + break; + case DOMAIN_ATTR_DYNAMIC: { + int dynamic = *((int *)data); + + if (smmu_domain->smmu != NULL) { + dev_err(smmu_domain->smmu->dev, + "cannot change dynamic attribute while attached\n"); + ret = -EBUSY; + break; + } + + if (dynamic) + smmu_domain->attributes |= 1 << DOMAIN_ATTR_DYNAMIC; + else + smmu_domain->attributes &= ~(1 << DOMAIN_ATTR_DYNAMIC); + ret = 0; + break; + } + case DOMAIN_ATTR_CONTEXT_BANK: + /* context bank can't be set while attached */ + if (smmu_domain->smmu != NULL) { + ret = -EBUSY; + break; + } + /* ... and it can only be set for dynamic contexts. */ + if (!(smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC))) { + ret = -EINVAL; + break; + } + + /* this will be validated during attach */ + smmu_domain->cfg.cbndx = *((unsigned int *)data); + ret = 0; + break; + case DOMAIN_ATTR_NON_FATAL_FAULTS: + smmu_domain->non_fatal_faults = *((int *)data); + ret = 0; + break; + case DOMAIN_ATTR_S1_BYPASS: { + int bypass = *((int *)data); + + if (bypass) + smmu_domain->attributes |= 1 << DOMAIN_ATTR_S1_BYPASS; + else + smmu_domain->attributes &= + ~(1 << DOMAIN_ATTR_S1_BYPASS); + + ret = 0; + break; + } + case DOMAIN_ATTR_FAST: + if (*((int *)data)) + smmu_domain->attributes |= 1 << DOMAIN_ATTR_FAST; + ret = 0; + break; + case DOMAIN_ATTR_EARLY_MAP: { + int early_map = *((int *)data); + + ret = 0; + if (early_map) { + smmu_domain->attributes |= + 1 << DOMAIN_ATTR_EARLY_MAP; + } else { + if (smmu_domain->smmu) + ret = arm_smmu_enable_s1_translations( + smmu_domain); + + if (!ret) + smmu_domain->attributes &= + ~(1 << DOMAIN_ATTR_EARLY_MAP); + } + break; + } + case DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT: { + int force_coherent = *((int *)data); + + if (smmu_domain->smmu != NULL) { + dev_err(smmu_domain->smmu->dev, + "cannot change force coherent attribute while attached\n"); + ret = -EBUSY; + break; + } + + if (force_coherent) + smmu_domain->attributes |= + 1 << DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT; + else + smmu_domain->attributes &= + ~(1 << DOMAIN_ATTR_PAGE_TABLE_FORCE_COHERENT); + ret = 0; + break; + } + case DOMAIN_ATTR_ENABLE_TTBR1: + if (*((int *)data)) + smmu_domain->attributes |= + 1 << DOMAIN_ATTR_ENABLE_TTBR1; + ret = 0; + break; + case DOMAIN_ATTR_GEOMETRY: { + struct iommu_domain_geometry *geometry = + (struct iommu_domain_geometry *)data; + + if (smmu_domain->smmu != NULL) { + dev_err(smmu_domain->smmu->dev, + "cannot set geometry attribute while attached\n"); + ret = -EBUSY; + break; + } + + if (geometry->aperture_start >= SZ_1G * 4ULL || + geometry->aperture_end >= SZ_1G * 4ULL) { + pr_err("fastmap does not support IOVAs >= 4GB\n"); + ret = -EINVAL; + break; + } + if (smmu_domain->attributes + & (1 << DOMAIN_ATTR_GEOMETRY)) { + if (geometry->aperture_start + < domain->geometry.aperture_start) + domain->geometry.aperture_start = + geometry->aperture_start; + + if (geometry->aperture_end + > domain->geometry.aperture_end) + domain->geometry.aperture_end = + geometry->aperture_end; + } else { + smmu_domain->attributes |= 1 << DOMAIN_ATTR_GEOMETRY; + domain->geometry.aperture_start = + geometry->aperture_start; + domain->geometry.aperture_end = geometry->aperture_end; + } + ret = 0; + break; + } + case DOMAIN_ATTR_CB_STALL_DISABLE: + if (*((int *)data)) + smmu_domain->attributes |= + 1 << DOMAIN_ATTR_CB_STALL_DISABLE; + ret = 0; break; default: ret = -ENODEV; + break; } out_unlock: @@ -1435,6 +3451,66 @@ out_unlock: return ret; } + +static int arm_smmu_enable_s1_translations(struct arm_smmu_domain *smmu_domain) +{ + struct arm_smmu_cfg *cfg = &smmu_domain->cfg; + struct arm_smmu_device *smmu = smmu_domain->smmu; + void __iomem *cb_base; + u32 reg; + int ret; + + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx); + ret = arm_smmu_enable_clocks(smmu); + if (ret) + return ret; + + reg = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR); + reg |= SCTLR_M; + + writel_relaxed(reg, cb_base + ARM_SMMU_CB_SCTLR); + arm_smmu_disable_clocks(smmu); + return ret; +} + +static int arm_smmu_dma_supported(struct iommu_domain *domain, + struct device *dev, u64 mask) +{ + struct arm_smmu_device *smmu; + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + int ret; + + mutex_lock(&smmu_domain->init_mutex); + smmu = smmu_domain->smmu; + if (!smmu) { + dev_err(dev, + "Can't call dma_supported on an unattached domain\n"); + mutex_unlock(&smmu_domain->init_mutex); + return 0; + } + + ret = ((1ULL << smmu->va_size) - 1) <= mask ? 0 : 1; + mutex_unlock(&smmu_domain->init_mutex); + return ret; +} + +static unsigned long arm_smmu_get_pgsize_bitmap(struct iommu_domain *domain) +{ + struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); + + /* + * if someone is calling map before attach just return the + * supported page sizes for the hardware itself. + */ + if (!smmu_domain->pgtbl_cfg.pgsize_bitmap) + return arm_smmu_ops.pgsize_bitmap; + /* + * otherwise return the page sizes supported by this specific page + * table configuration + */ + return smmu_domain->pgtbl_cfg.pgsize_bitmap; +} + static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc, @@ -1443,14 +3519,25 @@ static struct iommu_ops arm_smmu_ops = { .detach_dev = arm_smmu_detach_dev, .map = arm_smmu_map, .unmap = arm_smmu_unmap, - .map_sg = default_iommu_map_sg, + .map_sg = arm_smmu_map_sg, .iova_to_phys = arm_smmu_iova_to_phys, + .iova_to_phys_hard = arm_smmu_iova_to_phys_hard, .add_device = arm_smmu_add_device, .remove_device = arm_smmu_remove_device, .device_group = arm_smmu_device_group, .domain_get_attr = arm_smmu_domain_get_attr, .domain_set_attr = arm_smmu_domain_set_attr, .pgsize_bitmap = -1UL, /* Restricted during device attach */ + .get_pgsize_bitmap = arm_smmu_get_pgsize_bitmap, + .dma_supported = arm_smmu_dma_supported, + .trigger_fault = arm_smmu_trigger_fault, + .reg_read = arm_smmu_reg_read, + .reg_write = arm_smmu_reg_write, + .tlbi_domain = arm_smmu_tlbi_domain, + .enable_config_clocks = arm_smmu_enable_config_clocks, + .disable_config_clocks = arm_smmu_disable_config_clocks, + .is_iova_coherent = arm_smmu_is_iova_coherent, + .iova_to_pte = arm_smmu_iova_to_pte, }; static void arm_smmu_device_reset(struct arm_smmu_device *smmu) @@ -1464,18 +3551,21 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) reg = readl_relaxed(ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sGFSR); writel(reg, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sGFSR); - /* Mark all SMRn as invalid and all S2CRn as bypass */ - for (i = 0; i < smmu->num_mapping_groups; ++i) { - writel_relaxed(0, gr0_base + ARM_SMMU_GR0_SMR(i)); - writel_relaxed(S2CR_TYPE_BYPASS, - gr0_base + ARM_SMMU_GR0_S2CR(i)); - } + if (!(smmu->options & ARM_SMMU_OPT_SKIP_INIT)) { + /* Mark all SMRn as invalid and all S2CRn as bypass */ + for (i = 0; i < smmu->num_mapping_groups; ++i) { + writel_relaxed(0, + gr0_base + ARM_SMMU_GR0_SMR(i)); + writel_relaxed(S2CR_TYPE_BYPASS, + gr0_base + ARM_SMMU_GR0_S2CR(i)); + } - /* Make sure all context banks are disabled and clear CB_FSR */ - for (i = 0; i < smmu->num_context_banks; ++i) { - cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, i); - writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR); - writel_relaxed(FSR_FAULT, cb_base + ARM_SMMU_CB_FSR); + /* Make sure all context banks are disabled and clear CB_FSR */ + for (i = 0; i < smmu->num_context_banks; ++i) { + cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, i); + writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR); + writel_relaxed(FSR_FAULT, cb_base + ARM_SMMU_CB_FSR); + } } /* Invalidate the TLB, just in case */ @@ -1490,8 +3580,11 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) /* Disable TLB broadcasting. */ reg |= (sCR0_VMIDPNE | sCR0_PTM); - /* Enable client access, but bypass when no mapping is found */ - reg &= ~(sCR0_CLIENTPD | sCR0_USFCFG); + /* Enable client access */ + reg &= ~sCR0_CLIENTPD; + + /* Raise an unidentified stream fault on unmapped access */ + reg |= sCR0_USFCFG; /* Disable forced broadcasting */ reg &= ~sCR0_FB; @@ -1523,6 +3616,203 @@ static int arm_smmu_id_size_to_bits(int size) } } +static int regulator_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + int ret = 0; + struct arm_smmu_device *smmu = container_of(nb, + struct arm_smmu_device, regulator_nb); + + /* Ignore EVENT DISABLE as no clocks could be turned on + * at this notification. + */ + if (event != REGULATOR_EVENT_PRE_DISABLE && + event != REGULATOR_EVENT_ENABLE) + return NOTIFY_OK; + + ret = arm_smmu_prepare_clocks(smmu); + if (ret) + goto out; + + ret = arm_smmu_enable_clocks_atomic(smmu); + if (ret) + goto unprepare_clock; + + if (event == REGULATOR_EVENT_PRE_DISABLE) + arm_smmu_halt(smmu); + else if (event == REGULATOR_EVENT_ENABLE) + arm_smmu_resume(smmu); + + arm_smmu_disable_clocks_atomic(smmu); +unprepare_clock: + arm_smmu_unprepare_clocks(smmu); +out: + return NOTIFY_OK; +} + +static int register_regulator_notifier(struct arm_smmu_device *smmu) +{ + struct device *dev = smmu->dev; + int ret = 0; + + if (smmu->options & ARM_SMMU_OPT_HALT) { + smmu->regulator_nb.notifier_call = regulator_notifier; + ret = regulator_register_notifier(smmu->gdsc, + &smmu->regulator_nb); + + if (ret) + dev_err(dev, "Regulator notifier request failed\n"); + } + return ret; +} + +static int arm_smmu_init_regulators(struct arm_smmu_device *smmu) +{ + struct device *dev = smmu->dev; + + if (!of_get_property(dev->of_node, "vdd-supply", NULL)) + return 0; + + if (!of_property_read_u32(dev->of_node, + "qcom,deferred-regulator-disable-delay", + &(smmu->regulator_defer))) + dev_info(dev, "regulator defer delay %d\n", + smmu->regulator_defer); + + smmu->gdsc = devm_regulator_get(dev, "vdd"); + if (IS_ERR(smmu->gdsc)) + return PTR_ERR(smmu->gdsc); + + return 0; +} + +static int arm_smmu_init_clocks(struct arm_smmu_device *smmu) +{ + const char *cname; + struct property *prop; + int i; + struct device *dev = smmu->dev; + + smmu->num_clocks = + of_property_count_strings(dev->of_node, "clock-names"); + + if (smmu->num_clocks < 1) { + smmu->num_clocks = 0; + return 0; + } + + smmu->clocks = devm_kzalloc( + dev, sizeof(*smmu->clocks) * smmu->num_clocks, + GFP_KERNEL); + + if (!smmu->clocks) { + dev_err(dev, + "Failed to allocate memory for clocks\n"); + return -ENODEV; + } + + i = 0; + of_property_for_each_string(dev->of_node, "clock-names", + prop, cname) { + struct clk *c = devm_clk_get(dev, cname); + if (IS_ERR(c)) { + dev_err(dev, "Couldn't get clock: %s", + cname); + return PTR_ERR(c); + } + + if (clk_get_rate(c) == 0) { + long rate = clk_round_rate(c, 1000); + clk_set_rate(c, rate); + } + + smmu->clocks[i] = c; + + ++i; + } + return 0; +} + +static int arm_smmu_init_bus_scaling(struct platform_device *pdev, + struct arm_smmu_device *smmu) +{ + if (!of_find_property(pdev->dev.of_node, "qcom,msm-bus,name", NULL)) { + dev_dbg(&pdev->dev, "No bus scaling info\n"); + return 0; + } + + smmu->bus_pdata = msm_bus_cl_get_pdata(pdev); + if (!smmu->bus_pdata) { + dev_err(&pdev->dev, "Unable to read bus-scaling from DT\n"); + return -EINVAL; + } + + smmu->bus_client = msm_bus_scale_register_client(smmu->bus_pdata); + if (!smmu->bus_client) { + dev_err(&pdev->dev, "Bus client registration failed\n"); + return -EINVAL; + } + + return 0; +} + +static void arm_smmu_exit_bus_scaling(struct arm_smmu_device *smmu) +{ + if (smmu->bus_client) + msm_bus_scale_unregister_client(smmu->bus_client); + if (smmu->bus_pdata) + msm_bus_cl_clear_pdata(smmu->bus_pdata); + + smmu->bus_client = 0; + smmu->bus_pdata = NULL; +} + +static int arm_smmu_parse_impl_def_registers(struct arm_smmu_device *smmu) +{ + struct device *dev = smmu->dev; + int i, ntuples, ret; + u32 *tuples; + struct arm_smmu_impl_def_reg *regs, *regit; + + if (!of_find_property(dev->of_node, "attach-impl-defs", &ntuples)) + return 0; + + ntuples /= sizeof(u32); + if (ntuples % 2) { + dev_err(dev, + "Invalid number of attach-impl-defs registers: %d\n", + ntuples); + return -EINVAL; + } + + regs = devm_kmalloc( + dev, sizeof(*smmu->impl_def_attach_registers) * ntuples, + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + tuples = devm_kmalloc(dev, sizeof(u32) * ntuples * 2, GFP_KERNEL); + if (!tuples) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, "attach-impl-defs", + tuples, ntuples); + if (ret) + return ret; + + for (i = 0, regit = regs; i < ntuples; i += 2, ++regit) { + regit->offset = tuples[i]; + regit->value = tuples[i + 1]; + } + + devm_kfree(dev, tuples); + + smmu->impl_def_attach_registers = regs; + smmu->num_impl_def_attach_registers = ntuples / 2; + + return 0; +} + static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) { unsigned long size; @@ -1530,8 +3820,11 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) u32 id; bool cttw_dt, cttw_reg; - dev_notice(smmu->dev, "probing hardware configuration...\n"); - dev_notice(smmu->dev, "SMMUv%d with:\n", smmu->version); + if (arm_smmu_restore_sec_cfg(smmu)) + return -ENODEV; + + dev_dbg(smmu->dev, "probing hardware configuration...\n"); + dev_dbg(smmu->dev, "SMMUv%d with:\n", smmu->version); /* ID0 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID0); @@ -1544,28 +3837,28 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) if (id & ID0_S1TS) { smmu->features |= ARM_SMMU_FEAT_TRANS_S1; - dev_notice(smmu->dev, "\tstage 1 translation\n"); + dev_dbg(smmu->dev, "\tstage 1 translation\n"); } if (id & ID0_S2TS) { smmu->features |= ARM_SMMU_FEAT_TRANS_S2; - dev_notice(smmu->dev, "\tstage 2 translation\n"); + dev_dbg(smmu->dev, "\tstage 2 translation\n"); } if (id & ID0_NTS) { smmu->features |= ARM_SMMU_FEAT_TRANS_NESTED; - dev_notice(smmu->dev, "\tnested translation\n"); + dev_dbg(smmu->dev, "\tnested translation\n"); } if (!(smmu->features & (ARM_SMMU_FEAT_TRANS_S1 | ARM_SMMU_FEAT_TRANS_S2))) { - dev_err(smmu->dev, "\tno translation support!\n"); + dev_err(smmu->dev, "\tno translation support (id0=%x)!\n", id); return -ENODEV; } if ((id & ID0_S1TS) && ((smmu->version == 1) || !(id & ID0_ATOSNS))) { smmu->features |= ARM_SMMU_FEAT_TRANS_OPS; - dev_notice(smmu->dev, "\taddress translation ops\n"); + dev_dbg(smmu->dev, "\taddress translation ops\n"); } /* @@ -1579,14 +3872,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) if (cttw_dt) smmu->features |= ARM_SMMU_FEAT_COHERENT_WALK; if (cttw_dt || cttw_reg) - dev_notice(smmu->dev, "\t%scoherent table walk\n", + dev_dbg(smmu->dev, "\t%scoherent table walk\n", cttw_dt ? "" : "non-"); if (cttw_dt != cttw_reg) - dev_notice(smmu->dev, + dev_dbg(smmu->dev, "\t(IDR0.CTTW overridden by dma-coherent property)\n"); if (id & ID0_SMS) { - u32 smr, sid, mask; + u32 smr, sid, mask = 0; smmu->features |= ARM_SMMU_FEAT_STREAM_MATCH; smmu->num_mapping_groups = (id >> ID0_NUMSMRG_SHIFT) & @@ -1597,23 +3890,25 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENODEV; } - smr = SMR_MASK_MASK << SMR_MASK_SHIFT; - smr |= (SMR_ID_MASK << SMR_ID_SHIFT); - writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); - smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); - - mask = (smr >> SMR_MASK_SHIFT) & SMR_MASK_MASK; - sid = (smr >> SMR_ID_SHIFT) & SMR_ID_MASK; - if ((mask & sid) != sid) { - dev_err(smmu->dev, - "SMR mask bits (0x%x) insufficient for ID field (0x%x)\n", - mask, sid); - return -ENODEV; + if (!(smmu->options & ARM_SMMU_OPT_NO_SMR_CHECK)) { + smr = SMR_MASK_MASK << SMR_MASK_SHIFT; + smr |= (SMR_ID_MASK << SMR_ID_SHIFT); + writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(0)); + smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(0)); + + mask = (smr >> SMR_MASK_SHIFT) & SMR_MASK_MASK; + sid = (smr >> SMR_ID_SHIFT) & SMR_ID_MASK; + if ((mask & sid) != sid) { + dev_err(smmu->dev, + "SMR mask bits (0x%x) insufficient for ID field (0x%x)\n", + mask, sid); + return -ENODEV; + } } - dev_notice(smmu->dev, - "\tstream matching with %u register groups, mask 0x%x", - smmu->num_mapping_groups, mask); + dev_dbg(smmu->dev, + "\tstream matching with %u register groups, mask 0x%x", + smmu->num_mapping_groups, mask); } else { smmu->num_mapping_groups = (id >> ID0_NUMSIDB_SHIFT) & ID0_NUMSIDB_MASK; @@ -1637,8 +3932,8 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) dev_err(smmu->dev, "impossible number of S2 context banks!\n"); return -ENODEV; } - dev_notice(smmu->dev, "\t%u context banks (%u stage-2 only)\n", - smmu->num_context_banks, smmu->num_s2_context_banks); + dev_dbg(smmu->dev, "\t%u context banks (%u stage-2 only)\n", + smmu->num_context_banks, smmu->num_s2_context_banks); /* ID2 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID2); @@ -1662,11 +3957,13 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) smmu->va_size = smmu->ipa_size; size = SZ_4K | SZ_2M | SZ_1G; } else { - size = (id >> ID2_UBS_SHIFT) & ID2_UBS_MASK; - smmu->va_size = arm_smmu_id_size_to_bits(size); + smmu->ubs = (id >> ID2_UBS_SHIFT) & ID2_UBS_MASK; + + smmu->va_size = arm_smmu_id_size_to_bits(smmu->ubs); #ifndef CONFIG_64BIT smmu->va_size = min(32UL, smmu->va_size); #endif + smmu->va_size = min(39UL, smmu->va_size); size = 0; if (id & ID2_PTFS_4K) size |= SZ_4K | SZ_2M | SZ_1G; @@ -1677,25 +3974,82 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) } arm_smmu_ops.pgsize_bitmap &= size; - dev_notice(smmu->dev, "\tSupported page sizes: 0x%08lx\n", size); + dev_dbg(smmu->dev, "\tSupported page sizes: 0x%08lx\n", size); if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) - dev_notice(smmu->dev, "\tStage-1: %lu-bit VA -> %lu-bit IPA\n", - smmu->va_size, smmu->ipa_size); + dev_dbg(smmu->dev, "\tStage-1: %lu-bit VA -> %lu-bit IPA\n", + smmu->va_size, smmu->ipa_size); if (smmu->features & ARM_SMMU_FEAT_TRANS_S2) - dev_notice(smmu->dev, "\tStage-2: %lu-bit IPA -> %lu-bit PA\n", - smmu->ipa_size, smmu->pa_size); + dev_dbg(smmu->dev, "\tStage-2: %lu-bit IPA -> %lu-bit PA\n", + smmu->ipa_size, smmu->pa_size); return 0; } +static int arm_smmu_add_static_cbndx(struct arm_smmu_device *smmu, int sid, + int smr_idx) +{ + void __iomem *gr0_base; + u32 s2cr_reg; + struct static_cbndx_entry *entry; + + entry = devm_kzalloc(smmu->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + gr0_base = ARM_SMMU_GR0(smmu); + s2cr_reg = readl_relaxed(gr0_base + ARM_SMMU_GR0_S2CR(smr_idx)); + entry->type = (s2cr_reg >> S2CR_TYPE_SHIFT) & S2CR_TYPE_MASK; + entry->smr_idx = smr_idx; + entry->sid = sid; + + if (entry->type == TYPE_TRANS) { + entry->cbndx = (s2cr_reg >> S2CR_CBNDX_SHIFT) & + S2CR_CBNDX_MASK; + __arm_smmu_set_bitmap(smmu->context_map, entry->cbndx); + pr_debug("Static context bank: smr:%d, sid:%d, cbndx:%d\n", + smr_idx, sid, entry->cbndx); + } + __arm_smmu_set_bitmap(smmu->smr_map, smr_idx); + list_add(&entry->list, &smmu->static_cbndx_list); + + return 0; +} + +static int arm_smmu_init_static_cbndx_list(struct arm_smmu_device *smmu) +{ + int i, ret = 0; + void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + + for (i = 0; i < smmu->num_mapping_groups; ++i) { + u32 smr_reg, sid; + + smr_reg = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(i)); + if (smr_reg & SMR_VALID) { + u32 smr_mask = (smr_reg >> SMR_MASK_SHIFT) & + SMR_MASK_MASK; + + if (smr_mask != 0) + dev_warn(smmu->dev, + "Static smr mask not supported\n"); + sid = ((smr_reg >> SMR_ID_SHIFT) & SMR_ID_MASK); + ret = arm_smmu_add_static_cbndx(smmu, sid, i); + if (ret) + break; + } + } + + return ret; +} + static const struct of_device_id arm_smmu_of_match[] = { { .compatible = "arm,smmu-v1", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,smmu-v2", .data = (void *)ARM_SMMU_V2 }, { .compatible = "arm,mmu-400", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,mmu-401", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,mmu-500", .data = (void *)ARM_SMMU_V2 }, + { .compatible = "qcom,smmu-v2", .data = (void *)ARM_SMMU_V2 }, { }, }; MODULE_DEVICE_TABLE(of, arm_smmu_of_match); @@ -1707,7 +4061,6 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; struct rb_node *node; - struct of_phandle_args masterspec; int num_irqs, i, err; smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); @@ -1716,11 +4069,19 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) return -ENOMEM; } smmu->dev = dev; + mutex_init(&smmu->attach_lock); + mutex_init(&smmu->power_lock); + spin_lock_init(&smmu->atos_lock); + spin_lock_init(&smmu->clock_refs_lock); + INIT_LIST_HEAD(&smmu->static_cbndx_list); of_id = of_match_node(arm_smmu_of_match, dev->of_node); + if (!of_id) + return -ENODEV; smmu->version = (enum arm_smmu_arch_version)of_id->data; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + smmu->phys_addr = res->start; smmu->base = devm_ioremap_resource(dev, res); if (IS_ERR(smmu->base)) return PTR_ERR(smmu->base); @@ -1762,43 +4123,62 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) smmu->irqs[i] = irq; } - err = arm_smmu_device_cfg_probe(smmu); + i = 0; + + err = arm_smmu_parse_impl_def_registers(smmu); if (err) - return err; + goto out; - i = 0; - smmu->masters = RB_ROOT; - while (!of_parse_phandle_with_args(dev->of_node, "mmu-masters", - "#stream-id-cells", i, - &masterspec)) { - err = register_smmu_master(smmu, dev, &masterspec); - if (err) { - dev_err(dev, "failed to add master %s\n", - masterspec.np->name); - goto out_put_masters; - } + err = arm_smmu_init_regulators(smmu); + if (err) + goto out; - i++; - } - dev_notice(dev, "registered %d master devices\n", i); + err = arm_smmu_init_clocks(smmu); + if (err) + goto out; + + err = arm_smmu_init_bus_scaling(pdev, smmu); + if (err) + goto out; parse_driver_options(smmu); + err = arm_smmu_enable_clocks(smmu); + if (err) + goto out; + + /* No probe deferral occurred! Proceed with iommu property parsing. */ + smmu->masters = RB_ROOT; + err = arm_smmu_parse_iommus_properties(smmu); + if (err) + goto out_put_masters; + + smmu->sec_id = msm_dev_to_device_id(dev); + err = arm_smmu_device_cfg_probe(smmu); + if (!err) + err = arm_smmu_init_static_cbndx_list(smmu); + + arm_smmu_disable_clocks(smmu); + if (err) + goto out_put_masters; + + if (of_device_is_compatible(dev->of_node, "qcom,smmu-v2")) + smmu->model = SMMU_MODEL_QCOM_V2; + if (smmu->version > ARM_SMMU_V1 && smmu->num_context_banks != smmu->num_context_irqs) { dev_err(dev, - "found only %d context interrupt(s) but %d required\n", - smmu->num_context_irqs, smmu->num_context_banks); - err = -ENODEV; - goto out_put_masters; + "found %d context interrupt(s) but have %d context banks. assuming %d context interrupts.\n", + smmu->num_context_irqs, smmu->num_context_banks, + smmu->num_context_banks); + smmu->num_context_irqs = smmu->num_context_banks; } for (i = 0; i < smmu->num_global_irqs; ++i) { - err = request_irq(smmu->irqs[i], - arm_smmu_global_fault, - IRQF_SHARED, - "arm-smmu global fault", - smmu); + err = request_threaded_irq(smmu->irqs[i], + NULL, arm_smmu_global_fault, + IRQF_ONESHOT | IRQF_SHARED, + "arm-smmu global fault", smmu); if (err) { dev_err(dev, "failed to request global IRQ %d (%u)\n", i, smmu->irqs[i]); @@ -1806,12 +4186,19 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) } } + idr_init(&smmu->asid_idr); + + platform_set_drvdata(pdev, smmu); + + err = register_regulator_notifier(smmu); + if (err) + goto out_free_irqs; + INIT_LIST_HEAD(&smmu->list); spin_lock(&arm_smmu_devices_lock); list_add(&smmu->list, &arm_smmu_devices); spin_unlock(&arm_smmu_devices_lock); - arm_smmu_device_reset(smmu); return 0; out_free_irqs: @@ -1819,12 +4206,13 @@ out_free_irqs: free_irq(smmu->irqs[i], smmu); out_put_masters: + arm_smmu_exit_bus_scaling(smmu); for (node = rb_first(&smmu->masters); node; node = rb_next(node)) { struct arm_smmu_master *master = container_of(node, struct arm_smmu_master, node); of_node_put(master->of_node); } - +out: return err; } @@ -1860,15 +4248,212 @@ static int arm_smmu_device_remove(struct platform_device *pdev) for (i = 0; i < smmu->num_global_irqs; ++i) free_irq(smmu->irqs[i], smmu); - /* Turn the thing off */ - writel(sCR0_CLIENTPD, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0); + mutex_lock(&smmu->attach_lock); + idr_destroy(&smmu->asid_idr); + /* + * If all devices weren't detached for some reason, we're + * still powered on. Power off now. + */ + if (smmu->attach_count) + arm_smmu_power_off(smmu); + mutex_unlock(&smmu->attach_lock); + + arm_smmu_exit_bus_scaling(smmu); + + return 0; +} + +static void arm_smmu_free_master_nodes(void) +{ + struct arm_iommus_node *node, *nex; + struct arm_iommus_spec *entry, *n; + + list_for_each_entry_safe(node, nex, &iommus_nodes, list) { + list_for_each_entry_safe(entry, n, + &node->iommuspec_list, list) { + list_del(&entry->list); + kfree(entry); + } + list_del(&node->list); + kfree(node); + } +} + +static int arm_smmu_get_master_nodes(void) +{ + struct arm_iommus_node *node; + struct device_node *master; + struct of_phandle_args iommuspec; + struct arm_iommus_spec *entry; + + for_each_node_with_property(master, "iommus") { + int arg_ind = 0; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + goto release_memory; + node->master = master; + list_add(&node->list, &iommus_nodes); + + INIT_LIST_HEAD(&node->iommuspec_list); + + while (!of_parse_phandle_with_args(master, "iommus", + "#iommu-cells", arg_ind, &iommuspec)) { + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto release_memory; + entry->iommu_spec = iommuspec; + list_add(&entry->list, &node->iommuspec_list); + arg_ind++; + } + } + return 0; + +release_memory: + arm_smmu_free_master_nodes(); + return -ENOMEM; } +#if CONFIG_PM +static int arm_smmu_pm_suspend(struct device *dev) +{ + struct arm_smmu_device *smmu = dev_get_drvdata(dev); + u64 *regs, *reg_global; + int j, k = 0; + u32 cb_count = 0; + void __iomem *base, *gr0_base, *gr1_base; + + if (!smmu) + return -ENODEV; + + if (!smmu->attach_count) + return 0; + + if (arm_smmu_enable_clocks(smmu)) { + dev_err(smmu->dev, "failed to enable clocks for smmu"); + return -EINVAL; + } + + regs = &smmu->regs[0]; + reg_global = &smmu->reg_global[0]; + cb_count = smmu->num_context_banks; + + gr0_base = ARM_SMMU_GR0(smmu); + gr1_base = ARM_SMMU_GR1(smmu); + + for (j = 0; j < cb_count; j++) { + base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, j); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_SCTLR); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_ACTLR); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_TTBCR2); + regs[k++] = readq_relaxed(base + ARM_SMMU_CB_TTBR0); + regs[k++] = readq_relaxed(base + ARM_SMMU_CB_TTBR1); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_TTBCR); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_CONTEXTIDR); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_S1_MAIR0); + regs[k++] = readl_relaxed(base + ARM_SMMU_CB_S1_MAIR1); + regs[k++] = readl_relaxed(gr1_base + ARM_SMMU_GR1_CBA2R(j)); + regs[k++] = readl_relaxed(gr1_base + ARM_SMMU_GR1_CBAR(j)); + } + + for (j = 0, k = 0; j < smmu->num_mapping_groups; j++) { + reg_global[k++] = readl_relaxed( + gr0_base + ARM_SMMU_GR0_S2CR(j)); + reg_global[k++] = readl_relaxed( + gr0_base + ARM_SMMU_GR0_SMR(j)); + } + reg_global[k++] = readl_relaxed(ARM_SMMU_GR0_NS(smmu) + + ARM_SMMU_GR0_sCR0); + + arm_smmu_disable_clocks(smmu); + + return 0; +} +static int arm_smmu_pm_resume(struct device *dev) +{ + struct arm_smmu_device *smmu = dev_get_drvdata(dev); + u64 *regs, *reg_global; + int j, k = 0; + u32 cb_count = 0; + void __iomem *base, *gr0_base, *gr1_base; + + if (!smmu) + return -ENODEV; + + if (!smmu->attach_count) + return 0; + + if (arm_smmu_enable_clocks(smmu)) { + dev_err(smmu->dev, "failed to enable clocks for smmu"); + return -EINVAL; + } + + regs = &smmu->regs[0]; + reg_global = &smmu->reg_global[0]; + cb_count = smmu->num_context_banks; + + gr0_base = ARM_SMMU_GR0(smmu); + gr1_base = ARM_SMMU_GR1(smmu); + + for (j = 0; j < cb_count; j++) { + base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, j); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_SCTLR); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_ACTLR); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_TTBCR2); + writeq_relaxed(regs[k++], base + ARM_SMMU_CB_TTBR0); + writeq_relaxed(regs[k++], base + ARM_SMMU_CB_TTBR1); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_TTBCR); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_CONTEXTIDR); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_S1_MAIR0); + writel_relaxed(regs[k++], base + ARM_SMMU_CB_S1_MAIR1); + writel_relaxed(regs[k++], gr1_base + ARM_SMMU_GR1_CBA2R(j)); + writel_relaxed(regs[k++], gr1_base + ARM_SMMU_GR1_CBAR(j)); + } + + for (j = 0, k = 0; j < smmu->num_mapping_groups; j++) { + writel_relaxed(reg_global[k++], + gr0_base + ARM_SMMU_GR0_S2CR(j)); + writel_relaxed(reg_global[k++], + gr0_base + ARM_SMMU_GR0_SMR(j)); + } + writel_relaxed(reg_global[k++], + ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0); + + /* Do a tlb flush */ + writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLH); + writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLNSNH); + __arm_smmu_tlb_sync(smmu); + + arm_smmu_disable_clocks(smmu); + + return 0; +} +#else +static inline int arm_smmu_pm_suspend(struct device *dev) +{ + return 0; +} + +static inline int arm_smmu_pm_resume(struct device *dev) +{ + return 0; +} +#endif + +static const struct dev_pm_ops arm_smmu_pm_ops = { +#ifdef CONFIG_PM + .freeze_late = arm_smmu_pm_suspend, + .thaw_early = arm_smmu_pm_resume, + .restore_early = arm_smmu_pm_resume, +#endif +}; + static struct platform_driver arm_smmu_driver = { .driver = { .name = "arm-smmu", .of_match_table = of_match_ptr(arm_smmu_of_match), + .pm = &arm_smmu_pm_ops, }, .probe = arm_smmu_device_dt_probe, .remove = arm_smmu_device_remove, @@ -1890,10 +4475,15 @@ static int __init arm_smmu_init(void) of_node_put(np); - ret = platform_driver_register(&arm_smmu_driver); + ret = arm_smmu_get_master_nodes(); if (ret) return ret; + ret = platform_driver_register(&arm_smmu_driver); + if (ret) { + arm_smmu_free_master_nodes(); + return ret; + } /* Oh, for a proper bus abstraction */ if (!iommu_present(&platform_bus_type)) bus_set_iommu(&platform_bus_type, &arm_smmu_ops); diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 087a092a6e6e..899cee22f535 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -517,16 +517,6 @@ void iommu_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, __iommu_dma_unmap(iommu_get_domain_for_dev(dev), sg_dma_address(sg)); } -int iommu_dma_supported(struct device *dev, u64 mask) -{ - /* - * 'Special' IOMMUs which don't have the same addressing capability - * as the CPU will have to wait until we have some way to query that - * before they'll be able to use this framework. - */ - return 1; -} - int iommu_dma_mapping_error(struct device *dev, dma_addr_t dma_addr) { return dma_addr == DMA_ERROR_CODE; diff --git a/drivers/iommu/dma-mapping-fast.c b/drivers/iommu/dma-mapping-fast.c new file mode 100644 index 000000000000..66c2abd358f8 --- /dev/null +++ b/drivers/iommu/dma-mapping-fast.c @@ -0,0 +1,860 @@ +/* Copyright (c) 2016-2017,2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/dma-contiguous.h> +#include <linux/dma-mapping.h> +#include <linux/dma-mapping-fast.h> +#include <linux/io-pgtable-fast.h> +#include <linux/vmalloc.h> +#include <asm/cacheflush.h> +#include <asm/dma-iommu.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +/* some redundant definitions... :( TODO: move to io-pgtable-fast.h */ +#define FAST_PAGE_SHIFT 12 +#define FAST_PAGE_SIZE (1UL << FAST_PAGE_SHIFT) +#define FAST_PAGE_MASK (~(PAGE_SIZE - 1)) +#define FAST_PTE_ADDR_MASK ((av8l_fast_iopte)0xfffffffff000) +#define FAST_MAIR_ATTR_IDX_CACHE 1 +#define FAST_PTE_ATTRINDX_SHIFT 2 +#define FAST_PTE_ATTRINDX_MASK 0x7 +#define FAST_PTE_SH_SHIFT 8 +#define FAST_PTE_SH_MASK (((av8l_fast_iopte)0x3) << FAST_PTE_SH_SHIFT) +#define FAST_PTE_SH_OS (((av8l_fast_iopte)2) << FAST_PTE_SH_SHIFT) +#define FAST_PTE_SH_IS (((av8l_fast_iopte)3) << FAST_PTE_SH_SHIFT) + +static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot, + bool coherent) +{ + if (dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs)) + return pgprot_noncached(prot); + else if (!coherent || dma_get_attr(DMA_ATTR_WRITE_COMBINE, attrs)) + return pgprot_writecombine(prot); + return prot; +} + +static int __get_iommu_pgprot(struct dma_attrs *attrs, int prot, + bool coherent) +{ + if (!dma_get_attr(DMA_ATTR_EXEC_MAPPING, attrs)) + prot |= IOMMU_NOEXEC; + if (dma_get_attr(DMA_ATTR_STRONGLY_ORDERED, attrs)) + prot |= IOMMU_DEVICE; + if (coherent) + prot |= IOMMU_CACHE; + + return prot; +} + +static void fast_dmac_clean_range(struct dma_fast_smmu_mapping *mapping, + void *start, void *end) +{ + if (!mapping->is_smmu_pt_coherent) + dmac_clean_range(start, end); +} + +static bool __fast_is_pte_coherent(av8l_fast_iopte *ptep) +{ + int attr_idx = (*ptep & (FAST_PTE_ATTRINDX_MASK << + FAST_PTE_ATTRINDX_SHIFT)) >> + FAST_PTE_ATTRINDX_SHIFT; + + if ((attr_idx == FAST_MAIR_ATTR_IDX_CACHE) && + (((*ptep & FAST_PTE_SH_MASK) == FAST_PTE_SH_IS) || + (*ptep & FAST_PTE_SH_MASK) == FAST_PTE_SH_OS)) + return true; + + return false; +} + +static bool is_dma_coherent(struct device *dev, struct dma_attrs *attrs) +{ + bool is_coherent; + + if (dma_get_attr(DMA_ATTR_FORCE_COHERENT, attrs)) + is_coherent = true; + else if (dma_get_attr(DMA_ATTR_FORCE_NON_COHERENT, attrs)) + is_coherent = false; + else if (is_device_dma_coherent(dev)) + is_coherent = true; + else + is_coherent = false; + + return is_coherent; +} + +/* + * Checks if the allocated range (ending at @end) covered the upcoming + * stale bit. We don't need to know exactly where the range starts since + * we already know where the candidate search range started. If, starting + * from the beginning of the candidate search range, we had to step over + * (or landed directly on top of) the upcoming stale bit, then we return + * true. + * + * Due to wrapping, there are two scenarios we'll need to check: (1) if the + * range [search_start, upcoming_stale] spans 0 (i.e. search_start > + * upcoming_stale), and, (2) if the range: [search_start, upcoming_stale] + * does *not* span 0 (i.e. search_start <= upcoming_stale). And for each + * of those two scenarios we need to handle three cases: (1) the bit was + * found before wrapping or + */ +static bool __bit_covered_stale(unsigned long upcoming_stale, + unsigned long search_start, + unsigned long end) +{ + if (search_start > upcoming_stale) { + if (end >= search_start) { + /* + * We started searching above upcoming_stale and we + * didn't wrap, so we couldn't have crossed + * upcoming_stale. + */ + return false; + } + /* + * We wrapped. Did we cross (or land on top of) + * upcoming_stale? + */ + return end >= upcoming_stale; + } + + if (search_start <= upcoming_stale) { + if (end >= search_start) { + /* + * We didn't wrap. Did we cross (or land on top + * of) upcoming_stale? + */ + return end >= upcoming_stale; + } + /* + * We wrapped. So we must have crossed upcoming_stale + * (since we started searching below it). + */ + return true; + } + + /* we should have covered all logical combinations... */ + WARN_ON(1); + return true; +} + +static dma_addr_t __fast_smmu_alloc_iova(struct dma_fast_smmu_mapping *mapping, + struct dma_attrs *attrs, + size_t size) +{ + unsigned long bit, prev_search_start, nbits = size >> FAST_PAGE_SHIFT; + unsigned long align = (1 << get_order(size)) - 1; + + bit = bitmap_find_next_zero_area( + mapping->bitmap, mapping->num_4k_pages, mapping->next_start, + nbits, align); + if (unlikely(bit > mapping->num_4k_pages)) { + /* try wrapping */ + mapping->next_start = 0; /* TODO: SHOULD I REALLY DO THIS?!? */ + bit = bitmap_find_next_zero_area( + mapping->bitmap, mapping->num_4k_pages, 0, nbits, + align); + if (unlikely(bit > mapping->num_4k_pages)) + return DMA_ERROR_CODE; + } + + bitmap_set(mapping->bitmap, bit, nbits); + prev_search_start = mapping->next_start; + mapping->next_start = bit + nbits; + if (unlikely(mapping->next_start >= mapping->num_4k_pages)) + mapping->next_start = 0; + + /* + * If we just re-allocated a VA whose TLB hasn't been invalidated + * since it was last used and unmapped, we need to invalidate it + * here. We actually invalidate the entire TLB so that we don't + * have to invalidate the TLB again until we wrap back around. + */ + if (mapping->have_stale_tlbs && + __bit_covered_stale(mapping->upcoming_stale_bit, + prev_search_start, + bit + nbits - 1)) { + bool skip_sync = dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs); + + iommu_tlbiall(mapping->domain); + mapping->have_stale_tlbs = false; + av8l_fast_clear_stale_ptes(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + mapping->base, + mapping->base + mapping->size - 1, + skip_sync); + } + + return (bit << FAST_PAGE_SHIFT) + mapping->base; +} + +/* + * Checks whether the candidate bit will be allocated sooner than the + * current upcoming stale bit. We can say candidate will be upcoming + * sooner than the current upcoming stale bit if it lies between the + * starting bit of the next search range and the upcoming stale bit + * (allowing for wrap-around). + * + * Stated differently, we're checking the relative ordering of three + * unsigned numbers. So we need to check all 6 (i.e. 3!) permutations, + * namely: + * + * 0 |---A---B---C---| TOP (Case 1) + * 0 |---A---C---B---| TOP (Case 2) + * 0 |---B---A---C---| TOP (Case 3) + * 0 |---B---C---A---| TOP (Case 4) + * 0 |---C---A---B---| TOP (Case 5) + * 0 |---C---B---A---| TOP (Case 6) + * + * Note that since we're allowing numbers to wrap, the following three + * scenarios are all equivalent for Case 1: + * + * 0 |---A---B---C---| TOP + * 0 |---C---A---B---| TOP (C has wrapped. This is Case 5.) + * 0 |---B---C---A---| TOP (C and B have wrapped. This is Case 4.) + * + * In any of these cases, if we start searching from A, we will find B + * before we find C. + * + * We can also find two equivalent cases for Case 2: + * + * 0 |---A---C---B---| TOP + * 0 |---B---A---C---| TOP (B has wrapped. This is Case 3.) + * 0 |---C---B---A---| TOP (B and C have wrapped. This is Case 6.) + * + * In any of these cases, if we start searching from A, we will find C + * before we find B. + */ +static bool __bit_is_sooner(unsigned long candidate, + struct dma_fast_smmu_mapping *mapping) +{ + unsigned long A = mapping->next_start; + unsigned long B = candidate; + unsigned long C = mapping->upcoming_stale_bit; + + if ((A < B && B < C) || /* Case 1 */ + (C < A && A < B) || /* Case 5 */ + (B < C && C < A)) /* Case 4 */ + return true; + + if ((A < C && C < B) || /* Case 2 */ + (B < A && A < C) || /* Case 3 */ + (C < B && B < A)) /* Case 6 */ + return false; + + /* + * For simplicity, we've been ignoring the possibility of any of + * our three numbers being equal. Handle those cases here (they + * shouldn't happen very often, (I think?)). + */ + + /* + * If candidate is the next bit to be searched then it's definitely + * sooner. + */ + if (A == B) + return true; + + /* + * If candidate is the next upcoming stale bit we'll return false + * to avoid doing `upcoming = candidate' in the caller (which would + * be useless since they're already equal) + */ + if (B == C) + return false; + + /* + * If next start is the upcoming stale bit then candidate can't + * possibly be sooner. The "soonest" bit is already selected. + */ + if (A == C) + return false; + + /* We should have covered all logical combinations. */ + WARN(1, "Well, that's awkward. A=%ld, B=%ld, C=%ld\n", A, B, C); + return true; +} + +static void __fast_smmu_free_iova(struct dma_fast_smmu_mapping *mapping, + dma_addr_t iova, size_t size) +{ + unsigned long start_bit = (iova - mapping->base) >> FAST_PAGE_SHIFT; + unsigned long nbits = size >> FAST_PAGE_SHIFT; + + /* + * We don't invalidate TLBs on unmap. We invalidate TLBs on map + * when we're about to re-allocate a VA that was previously + * unmapped but hasn't yet been invalidated. So we need to keep + * track of which bit is the closest to being re-allocated here. + */ + if (__bit_is_sooner(start_bit, mapping)) + mapping->upcoming_stale_bit = start_bit; + + bitmap_clear(mapping->bitmap, start_bit, nbits); + mapping->have_stale_tlbs = true; +} + + +static void __fast_dma_page_cpu_to_dev(struct page *page, unsigned long off, + size_t size, enum dma_data_direction dir) +{ + __dma_map_area(page_address(page) + off, size, dir); +} + +static void __fast_dma_page_dev_to_cpu(struct page *page, unsigned long off, + size_t size, enum dma_data_direction dir) +{ + __dma_unmap_area(page_address(page) + off, size, dir); + + /* TODO: WHAT IS THIS? */ + /* + * Mark the D-cache clean for this page to avoid extra flushing. + */ + if (dir != DMA_TO_DEVICE && off == 0 && size >= PAGE_SIZE) + set_bit(PG_dcache_clean, &page->flags); +} + +static int __fast_dma_direction_to_prot(enum dma_data_direction dir) +{ + switch (dir) { + case DMA_BIDIRECTIONAL: + return IOMMU_READ | IOMMU_WRITE; + case DMA_TO_DEVICE: + return IOMMU_READ; + case DMA_FROM_DEVICE: + return IOMMU_WRITE; + default: + return 0; + } +} + +static dma_addr_t fast_smmu_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + dma_addr_t iova; + unsigned long flags; + av8l_fast_iopte *pmd; + phys_addr_t phys_plus_off = page_to_phys(page) + offset; + phys_addr_t phys_to_map = round_down(phys_plus_off, FAST_PAGE_SIZE); + unsigned long offset_from_phys_to_map = phys_plus_off & ~FAST_PAGE_MASK; + size_t len = ALIGN(size + offset_from_phys_to_map, FAST_PAGE_SIZE); + int nptes = len >> FAST_PAGE_SHIFT; + bool skip_sync = dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs); + int prot = __fast_dma_direction_to_prot(dir); + bool is_coherent = is_dma_coherent(dev, attrs); + + prot = __get_iommu_pgprot(attrs, prot, is_coherent); + + if (!skip_sync && !is_coherent) + __fast_dma_page_cpu_to_dev(phys_to_page(phys_to_map), + offset_from_phys_to_map, size, dir); + + spin_lock_irqsave(&mapping->lock, flags); + + iova = __fast_smmu_alloc_iova(mapping, attrs, len); + + if (unlikely(iova == DMA_ERROR_CODE)) + goto fail; + + pmd = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, iova); + + if (unlikely(av8l_fast_map_public(pmd, phys_to_map, len, prot))) + goto fail_free_iova; + + fast_dmac_clean_range(mapping, pmd, pmd + nptes); + + spin_unlock_irqrestore(&mapping->lock, flags); + return iova + offset_from_phys_to_map; + +fail_free_iova: + __fast_smmu_free_iova(mapping, iova, size); +fail: + spin_unlock_irqrestore(&mapping->lock, flags); + return DMA_ERROR_CODE; +} + +static void fast_smmu_unmap_page(struct device *dev, dma_addr_t iova, + size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + unsigned long flags; + av8l_fast_iopte *pmd = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + iova); + unsigned long offset = iova & ~FAST_PAGE_MASK; + size_t len = ALIGN(size + offset, FAST_PAGE_SIZE); + int nptes = len >> FAST_PAGE_SHIFT; + struct page *page = phys_to_page((*pmd & FAST_PTE_ADDR_MASK)); + bool skip_sync = dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs); + bool is_coherent = is_dma_coherent(dev, attrs); + + if (!skip_sync && !is_coherent) + __fast_dma_page_dev_to_cpu(page, offset, size, dir); + + spin_lock_irqsave(&mapping->lock, flags); + av8l_fast_unmap_public(pmd, len); + fast_dmac_clean_range(mapping, pmd, pmd + nptes); + __fast_smmu_free_iova(mapping, iova, len); + spin_unlock_irqrestore(&mapping->lock, flags); +} + +static void fast_smmu_sync_single_for_cpu(struct device *dev, + dma_addr_t iova, size_t size, enum dma_data_direction dir) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + av8l_fast_iopte *pmd = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + iova); + unsigned long offset = iova & ~FAST_PAGE_MASK; + struct page *page = phys_to_page((*pmd & FAST_PTE_ADDR_MASK)); + + if (!__fast_is_pte_coherent(pmd)) + __fast_dma_page_dev_to_cpu(page, offset, size, dir); +} + +static void fast_smmu_sync_single_for_device(struct device *dev, + dma_addr_t iova, size_t size, enum dma_data_direction dir) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + av8l_fast_iopte *pmd = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + iova); + unsigned long offset = iova & ~FAST_PAGE_MASK; + struct page *page = phys_to_page((*pmd & FAST_PTE_ADDR_MASK)); + + if (!__fast_is_pte_coherent(pmd)) + __fast_dma_page_cpu_to_dev(page, offset, size, dir); +} + +static int fast_smmu_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + return -EINVAL; +} + +static void fast_smmu_unmap_sg(struct device *dev, + struct scatterlist *sg, int nents, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + WARN_ON_ONCE(1); +} + +static void fast_smmu_sync_sg_for_cpu(struct device *dev, + struct scatterlist *sg, int nents, enum dma_data_direction dir) +{ + WARN_ON_ONCE(1); +} + +static void fast_smmu_sync_sg_for_device(struct device *dev, + struct scatterlist *sg, int nents, enum dma_data_direction dir) +{ + WARN_ON_ONCE(1); +} + +static void __fast_smmu_free_pages(struct page **pages, int count) +{ + while (count--) + __free_page(pages[count]); + kvfree(pages); +} + +static struct page **__fast_smmu_alloc_pages(unsigned int count, gfp_t gfp) +{ + struct page **pages; + unsigned int i = 0, array_size = count * sizeof(*pages); + + if (array_size <= PAGE_SIZE) + pages = kzalloc(array_size, GFP_KERNEL); + else + pages = vzalloc(array_size); + if (!pages) + return NULL; + + /* IOMMU can map any pages, so himem can also be used here */ + gfp |= __GFP_NOWARN | __GFP_HIGHMEM; + + for (i = 0; i < count; ++i) { + struct page *page = alloc_page(gfp); + + if (!page) { + __fast_smmu_free_pages(pages, i - 1); + return NULL; + } + pages[i] = page; + } + return pages; +} + +static void *fast_smmu_alloc(struct device *dev, size_t size, + dma_addr_t *handle, gfp_t gfp, + struct dma_attrs *attrs) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + struct sg_table sgt; + dma_addr_t dma_addr, iova_iter; + void *addr; + av8l_fast_iopte *ptep; + unsigned long flags; + struct sg_mapping_iter miter; + size_t count = ALIGN(size, SZ_4K) >> PAGE_SHIFT; + int prot = IOMMU_READ | IOMMU_WRITE; /* TODO: extract from attrs */ + bool is_coherent = is_dma_coherent(dev, attrs); + pgprot_t remap_prot = __get_dma_pgprot(attrs, PAGE_KERNEL, is_coherent); + struct page **pages; + + /* + * sg_alloc_table_from_pages accepts unsigned int value for count + * so check count doesn't exceed UINT_MAX. + */ + + if (count > UINT_MAX) { + dev_err(dev, "count: %zx exceeds UNIT_MAX\n", count); + return NULL; + } + + prot = __get_iommu_pgprot(attrs, prot, is_coherent); + + *handle = DMA_ERROR_CODE; + + pages = __fast_smmu_alloc_pages(count, gfp); + if (!pages) { + dev_err(dev, "no pages\n"); + return NULL; + } + + size = ALIGN(size, SZ_4K); + if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, gfp)) { + dev_err(dev, "no sg tablen\n"); + goto out_free_pages; + } + + if (!is_coherent) { + /* + * The CPU-centric flushing implied by SG_MITER_TO_SG isn't + * sufficient here, so skip it by using the "wrong" direction. + */ + sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, + SG_MITER_FROM_SG); + while (sg_miter_next(&miter)) + __dma_flush_range(miter.addr, + miter.addr + miter.length); + sg_miter_stop(&miter); + } + + spin_lock_irqsave(&mapping->lock, flags); + dma_addr = __fast_smmu_alloc_iova(mapping, attrs, size); + if (dma_addr == DMA_ERROR_CODE) { + dev_err(dev, "no iova\n"); + spin_unlock_irqrestore(&mapping->lock, flags); + goto out_free_sg; + } + iova_iter = dma_addr; + sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, + SG_MITER_FROM_SG | SG_MITER_ATOMIC); + while (sg_miter_next(&miter)) { + int nptes = miter.length >> FAST_PAGE_SHIFT; + + ptep = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + iova_iter); + if (unlikely(av8l_fast_map_public( + ptep, page_to_phys(miter.page), + miter.length, prot))) { + dev_err(dev, "no map public\n"); + /* TODO: unwind previously successful mappings */ + goto out_free_iova; + } + fast_dmac_clean_range(mapping, ptep, ptep + nptes); + iova_iter += miter.length; + } + sg_miter_stop(&miter); + spin_unlock_irqrestore(&mapping->lock, flags); + + addr = dma_common_pages_remap(pages, size, VM_USERMAP, remap_prot, + __builtin_return_address(0)); + if (!addr) { + dev_err(dev, "no common pages\n"); + goto out_unmap; + } + + *handle = dma_addr; + sg_free_table(&sgt); + return addr; + +out_unmap: + /* need to take the lock again for page tables and iova */ + spin_lock_irqsave(&mapping->lock, flags); + ptep = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, + dma_addr); + av8l_fast_unmap_public(ptep, size); + fast_dmac_clean_range(mapping, ptep, ptep + count); +out_free_iova: + __fast_smmu_free_iova(mapping, dma_addr, size); + spin_unlock_irqrestore(&mapping->lock, flags); +out_free_sg: + sg_free_table(&sgt); +out_free_pages: + __fast_smmu_free_pages(pages, count); + return NULL; +} + +static void fast_smmu_free(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle, + struct dma_attrs *attrs) +{ + struct dma_fast_smmu_mapping *mapping = dev->archdata.mapping->fast; + struct vm_struct *area; + struct page **pages; + size_t count = ALIGN(size, SZ_4K) >> FAST_PAGE_SHIFT; + av8l_fast_iopte *ptep; + unsigned long flags; + + size = ALIGN(size, SZ_4K); + + area = find_vm_area(vaddr); + if (WARN_ON_ONCE(!area)) + return; + + pages = area->pages; + dma_common_free_remap(vaddr, size, VM_USERMAP, false); + ptep = iopte_pmd_offset(mapping->pgtbl_pmds, + mapping->domain->geometry.aperture_start, dma_handle); + spin_lock_irqsave(&mapping->lock, flags); + av8l_fast_unmap_public(ptep, size); + fast_dmac_clean_range(mapping, ptep, ptep + count); + __fast_smmu_free_iova(mapping, dma_handle, size); + spin_unlock_irqrestore(&mapping->lock, flags); + __fast_smmu_free_pages(pages, count); +} + +static int fast_smmu_mmap_attrs(struct device *dev, struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t dma_addr, + size_t size, struct dma_attrs *attrs) +{ + struct vm_struct *area; + unsigned long uaddr = vma->vm_start; + struct page **pages; + int i, nr_pages, ret = 0; + bool coherent = is_dma_coherent(dev, attrs); + + vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot, + coherent); + area = find_vm_area(cpu_addr); + if (!area) + return -EINVAL; + + pages = area->pages; + nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + for (i = vma->vm_pgoff; i < nr_pages && uaddr < vma->vm_end; i++) { + ret = vm_insert_page(vma, uaddr, pages[i]); + if (ret) + break; + uaddr += PAGE_SIZE; + } + + return ret; +} + +static int fast_smmu_dma_supported(struct device *dev, u64 mask) +{ + return mask <= 0xffffffff; +} + +static int fast_smmu_mapping_error(struct device *dev, + dma_addr_t dma_addr) +{ + return dma_addr == DMA_ERROR_CODE; +} + +static void __fast_smmu_mapped_over_stale(struct dma_fast_smmu_mapping *fast, + void *data) +{ + av8l_fast_iopte *ptep = data; + dma_addr_t iova; + unsigned long bitmap_idx; + + bitmap_idx = (unsigned long)(ptep - fast->pgtbl_pmds); + iova = bitmap_idx << FAST_PAGE_SHIFT; + dev_err(fast->dev, "Mapped over stale tlb at %pa\n", &iova); + dev_err(fast->dev, "bitmap (failure at idx %lu):\n", bitmap_idx); + dev_err(fast->dev, "ptep: %p pmds: %p diff: %lu\n", ptep, + fast->pgtbl_pmds, bitmap_idx); + print_hex_dump(KERN_ERR, "bmap: ", DUMP_PREFIX_ADDRESS, + 32, 8, fast->bitmap, fast->bitmap_size, false); +} + +static int fast_smmu_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct dma_fast_smmu_mapping *fast = container_of( + self, struct dma_fast_smmu_mapping, notifier); + + switch (action) { + case MAPPED_OVER_STALE_TLB: + __fast_smmu_mapped_over_stale(fast, data); + return NOTIFY_OK; + default: + WARN(1, "Unhandled notifier action"); + return NOTIFY_DONE; + } +} + +static const struct dma_map_ops fast_smmu_dma_ops = { + .alloc = fast_smmu_alloc, + .free = fast_smmu_free, + .mmap = fast_smmu_mmap_attrs, + .map_page = fast_smmu_map_page, + .unmap_page = fast_smmu_unmap_page, + .sync_single_for_cpu = fast_smmu_sync_single_for_cpu, + .sync_single_for_device = fast_smmu_sync_single_for_device, + .map_sg = fast_smmu_map_sg, + .unmap_sg = fast_smmu_unmap_sg, + .sync_sg_for_cpu = fast_smmu_sync_sg_for_cpu, + .sync_sg_for_device = fast_smmu_sync_sg_for_device, + .dma_supported = fast_smmu_dma_supported, + .mapping_error = fast_smmu_mapping_error, +}; + +/** + * __fast_smmu_create_mapping_sized + * @base: bottom of the VA range + * @size: size of the VA range in bytes + * + * Creates a mapping structure which holds information about used/unused IO + * address ranges, which is required to perform mapping with IOMMU aware + * functions. The only VA range supported is [0, 4GB]. + * + * The client device need to be attached to the mapping with + * fast_smmu_attach_device function. + */ +static struct dma_fast_smmu_mapping *__fast_smmu_create_mapping_sized( + dma_addr_t base, u64 size) +{ + struct dma_fast_smmu_mapping *fast; + + fast = kzalloc(sizeof(struct dma_fast_smmu_mapping), GFP_KERNEL); + if (!fast) + goto err; + + fast->base = base; + fast->size = size; + fast->num_4k_pages = size >> FAST_PAGE_SHIFT; + fast->bitmap_size = BITS_TO_LONGS(fast->num_4k_pages) * sizeof(long); + + fast->bitmap = kzalloc(fast->bitmap_size, GFP_KERNEL | __GFP_NOWARN | + __GFP_NORETRY); + if (!fast->bitmap) + fast->bitmap = vzalloc(fast->bitmap_size); + + if (!fast->bitmap) + goto err2; + + spin_lock_init(&fast->lock); + + return fast; +err2: + kfree(fast); +err: + return ERR_PTR(-ENOMEM); +} + +/** + * fast_smmu_attach_device + * @dev: valid struct device pointer + * @mapping: io address space mapping structure (returned from + * fast_smmu_create_mapping) + * + * Attaches specified io address space mapping to the provided device, + * this replaces the dma operations (dma_map_ops pointer) with the + * IOMMU aware version. More than one client might be attached to + * the same io address space mapping. + */ +int fast_smmu_attach_device(struct device *dev, + struct dma_iommu_mapping *mapping) +{ + int atomic_domain = 1; + struct iommu_domain *domain = mapping->domain; + struct iommu_pgtbl_info info; + u64 size = (u64)mapping->bits << PAGE_SHIFT; + struct iommu_domain_geometry geometry; + + if (mapping->base + size > (SZ_1G * 4ULL)) + return -EINVAL; + + if (iommu_domain_set_attr(domain, DOMAIN_ATTR_ATOMIC, + &atomic_domain)) + return -EINVAL; + + mapping->fast = __fast_smmu_create_mapping_sized(mapping->base, size); + if (IS_ERR(mapping->fast)) + return -ENOMEM; + mapping->fast->domain = domain; + mapping->fast->dev = dev; + + geometry.aperture_start = mapping->base; + geometry.aperture_end = mapping->base + size - 1; + if (iommu_domain_set_attr(domain, DOMAIN_ATTR_GEOMETRY, + &geometry)) + return -EINVAL; + + if (iommu_attach_device(domain, dev)) + return -EINVAL; + + if (iommu_domain_get_attr(domain, DOMAIN_ATTR_PGTBL_INFO, + &info)) { + dev_err(dev, "Couldn't get page table info\n"); + fast_smmu_detach_device(dev, mapping); + return -EINVAL; + } + mapping->fast->pgtbl_pmds = info.pmds; + + if (iommu_domain_get_attr(domain, DOMAIN_ATTR_PAGE_TABLE_IS_COHERENT, + &mapping->fast->is_smmu_pt_coherent)) + return -EINVAL; + + mapping->fast->notifier.notifier_call = fast_smmu_notify; + av8l_register_notify(&mapping->fast->notifier); + + dev->archdata.mapping = mapping; + set_dma_ops(dev, &fast_smmu_dma_ops); + + return 0; +} +EXPORT_SYMBOL(fast_smmu_attach_device); + +/** + * fast_smmu_detach_device + * @dev: valid struct device pointer + * + * Detaches the provided device from a previously attached map. + * This voids the dma operations (dma_map_ops pointer) + */ +void fast_smmu_detach_device(struct device *dev, + struct dma_iommu_mapping *mapping) +{ + iommu_detach_device(mapping->domain, dev); + dev->archdata.mapping = NULL; + set_dma_ops(dev, NULL); + + kvfree(mapping->fast->bitmap); + kfree(mapping->fast); +} +EXPORT_SYMBOL(fast_smmu_detach_device); diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c index 18751b1dfd3d..3f1617ca2fc0 100644 --- a/drivers/iommu/io-pgtable-arm.c +++ b/drivers/iommu/io-pgtable-arm.c @@ -22,6 +22,7 @@ #include <linux/iommu.h> #include <linux/kernel.h> +#include <linux/scatterlist.h> #include <linux/sizes.h> #include <linux/slab.h> #include <linux/types.h> @@ -68,9 +69,12 @@ #define ARM_LPAE_PGD_IDX(l,d) \ ((l) == ARM_LPAE_START_LVL(d) ? ilog2(ARM_LPAE_PAGES_PER_PGD(d)) : 0) +#define ARM_LPAE_LVL_MASK(l, d) \ + ((l) == ARM_LPAE_START_LVL(d) ? (1 << (d)->pgd_bits) - 1 : \ + (1 << (d)->bits_per_level) - 1) #define ARM_LPAE_LVL_IDX(a,l,d) \ (((u64)(a) >> ARM_LPAE_LVL_SHIFT(l,d)) & \ - ((1 << ((d)->bits_per_level + ARM_LPAE_PGD_IDX(l,d))) - 1)) + ARM_LPAE_LVL_MASK(l, d)) /* Calculate the block/page mapping size at level l for pagetable in d. */ #define ARM_LPAE_BLOCK_SIZE(l,d) \ @@ -85,6 +89,7 @@ #define ARM_LPAE_PTE_TYPE_TABLE 3 #define ARM_LPAE_PTE_TYPE_PAGE 3 +#define ARM_LPAE_PTE_SH_MASK (((arm_lpae_iopte)0x3) << 8) #define ARM_LPAE_PTE_NSTABLE (((arm_lpae_iopte)1) << 63) #define ARM_LPAE_PTE_XN (((arm_lpae_iopte)3) << 53) #define ARM_LPAE_PTE_AF (((arm_lpae_iopte)1) << 10) @@ -101,8 +106,11 @@ ARM_LPAE_PTE_ATTR_HI_MASK) /* Stage-1 PTE */ -#define ARM_LPAE_PTE_AP_UNPRIV (((arm_lpae_iopte)1) << 6) -#define ARM_LPAE_PTE_AP_RDONLY (((arm_lpae_iopte)2) << 6) +#define ARM_LPAE_PTE_AP_PRIV_RW (((arm_lpae_iopte)0) << 6) +#define ARM_LPAE_PTE_AP_RW (((arm_lpae_iopte)1) << 6) +#define ARM_LPAE_PTE_AP_PRIV_RO (((arm_lpae_iopte)2) << 6) +#define ARM_LPAE_PTE_AP_RO (((arm_lpae_iopte)3) << 6) +#define ARM_LPAE_PTE_ATTRINDX_MASK 0x7 #define ARM_LPAE_PTE_ATTRINDX_SHIFT 2 #define ARM_LPAE_PTE_nG (((arm_lpae_iopte)1) << 11) @@ -124,14 +132,21 @@ #define ARM_LPAE_TCR_TG0_64K (1 << 14) #define ARM_LPAE_TCR_TG0_16K (2 << 14) +#define ARM_LPAE_TCR_TG1_16K 1ULL +#define ARM_LPAE_TCR_TG1_4K 2ULL +#define ARM_LPAE_TCR_TG1_64K 3ULL + #define ARM_LPAE_TCR_SH0_SHIFT 12 #define ARM_LPAE_TCR_SH0_MASK 0x3 +#define ARM_LPAE_TCR_SH1_SHIFT 28 #define ARM_LPAE_TCR_SH_NS 0 #define ARM_LPAE_TCR_SH_OS 2 #define ARM_LPAE_TCR_SH_IS 3 #define ARM_LPAE_TCR_ORGN0_SHIFT 10 +#define ARM_LPAE_TCR_ORGN1_SHIFT 26 #define ARM_LPAE_TCR_IRGN0_SHIFT 8 +#define ARM_LPAE_TCR_IRGN1_SHIFT 24 #define ARM_LPAE_TCR_RGN_MASK 0x3 #define ARM_LPAE_TCR_RGN_NC 0 #define ARM_LPAE_TCR_RGN_WBWA 1 @@ -144,6 +159,9 @@ #define ARM_LPAE_TCR_T0SZ_SHIFT 0 #define ARM_LPAE_TCR_SZ_MASK 0xf +#define ARM_LPAE_TCR_T1SZ_SHIFT 16 +#define ARM_LPAE_TCR_T1SZ_MASK 0x3f + #define ARM_LPAE_TCR_PS_SHIFT 16 #define ARM_LPAE_TCR_PS_MASK 0x7 @@ -157,6 +175,19 @@ #define ARM_LPAE_TCR_PS_44_BIT 0x4ULL #define ARM_LPAE_TCR_PS_48_BIT 0x5ULL +#define ARM_LPAE_TCR_EPD1_SHIFT 23 +#define ARM_LPAE_TCR_EPD1_FAULT 1 + +#define ARM_LPAE_TCR_SEP_SHIFT (15 + 32) + +#define ARM_LPAE_TCR_SEP_31 0ULL +#define ARM_LPAE_TCR_SEP_35 1ULL +#define ARM_LPAE_TCR_SEP_39 2ULL +#define ARM_LPAE_TCR_SEP_41 3ULL +#define ARM_LPAE_TCR_SEP_43 4ULL +#define ARM_LPAE_TCR_SEP_47 5ULL +#define ARM_LPAE_TCR_SEP_UPSTREAM 7ULL + #define ARM_LPAE_MAIR_ATTR_SHIFT(n) ((n) << 3) #define ARM_LPAE_MAIR_ATTR_MASK 0xff #define ARM_LPAE_MAIR_ATTR_DEVICE 0x04 @@ -167,8 +198,8 @@ #define ARM_LPAE_MAIR_ATTR_IDX_DEV 2 /* IOPTE accessors */ -#define iopte_deref(pte,d) \ - (__va((pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1) \ +#define iopte_deref(pte, d) \ + (__va(iopte_val(pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1) \ & ~((1ULL << (d)->pg_shift) - 1))) #define iopte_type(pte,l) \ @@ -191,28 +222,113 @@ struct arm_lpae_io_pgtable { struct io_pgtable iop; int levels; + unsigned int pgd_bits; size_t pgd_size; unsigned long pg_shift; unsigned long bits_per_level; - void *pgd; + void *pgd[2]; }; typedef u64 arm_lpae_iopte; static bool selftest_running = false; +/* + * We'll use some ignored bits in table entries to keep track of the number + * of page mappings beneath the table. The maximum number of entries + * beneath any table mapping in armv8 is 8192 (which is possible at the + * 2nd- and 3rd-level when using a 64K granule size). The bits at our + * disposal are: + * + * 4k granule: [58..52], [11..2] + * 64k granule: [58..52], [15..2] + * + * [58..52], [11..2] is enough bits for tracking table mappings at any + * level for any granule, so we'll use those. + */ +#define BOTTOM_IGNORED_MASK 0x3ff +#define BOTTOM_IGNORED_SHIFT 2 +#define BOTTOM_IGNORED_NUM_BITS 10 +#define TOP_IGNORED_MASK 0x7fULL +#define TOP_IGNORED_SHIFT 52 +#define IOPTE_RESERVED_MASK ((BOTTOM_IGNORED_MASK << BOTTOM_IGNORED_SHIFT) | \ + (TOP_IGNORED_MASK << TOP_IGNORED_SHIFT)) + +static arm_lpae_iopte iopte_val(arm_lpae_iopte table_pte) +{ + return table_pte & ~IOPTE_RESERVED_MASK; +} + +static arm_lpae_iopte _iopte_bottom_ignored_val(arm_lpae_iopte table_pte) +{ + return (table_pte & (BOTTOM_IGNORED_MASK << BOTTOM_IGNORED_SHIFT)) + >> BOTTOM_IGNORED_SHIFT; +} + +static arm_lpae_iopte _iopte_top_ignored_val(arm_lpae_iopte table_pte) +{ + return (table_pte & (TOP_IGNORED_MASK << TOP_IGNORED_SHIFT)) + >> TOP_IGNORED_SHIFT; +} + +static int iopte_tblcnt(arm_lpae_iopte table_pte) +{ + return (_iopte_bottom_ignored_val(table_pte) | + (_iopte_top_ignored_val(table_pte) << BOTTOM_IGNORED_NUM_BITS)); +} + +static void iopte_tblcnt_set(arm_lpae_iopte *table_pte, int val) +{ + arm_lpae_iopte pte = iopte_val(*table_pte); + + pte |= ((val & BOTTOM_IGNORED_MASK) << BOTTOM_IGNORED_SHIFT) | + (((val & (TOP_IGNORED_MASK << BOTTOM_IGNORED_NUM_BITS)) + >> BOTTOM_IGNORED_NUM_BITS) << TOP_IGNORED_SHIFT); + *table_pte = pte; +} + +static void iopte_tblcnt_sub(arm_lpae_iopte *table_ptep, int cnt) +{ + arm_lpae_iopte current_cnt = iopte_tblcnt(*table_ptep); + + current_cnt -= cnt; + iopte_tblcnt_set(table_ptep, current_cnt); +} + +static void iopte_tblcnt_add(arm_lpae_iopte *table_ptep, int cnt) +{ + arm_lpae_iopte current_cnt = iopte_tblcnt(*table_ptep); + + current_cnt += cnt; + iopte_tblcnt_set(table_ptep, current_cnt); +} + +static bool suppress_map_failures; + static dma_addr_t __arm_lpae_dma_addr(void *pages) { return (dma_addr_t)virt_to_phys(pages); } +static inline void pgtable_dma_sync_single_for_device( + struct io_pgtable_cfg *cfg, + dma_addr_t addr, size_t size, + enum dma_data_direction dir) +{ + if (!(cfg->quirks & IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT)) + dma_sync_single_for_device(cfg->iommu_dev, addr, size, + dir); +} + static void *__arm_lpae_alloc_pages(size_t size, gfp_t gfp, - struct io_pgtable_cfg *cfg) + struct io_pgtable_cfg *cfg, + void *cookie) { struct device *dev = cfg->iommu_dev; dma_addr_t dma; - void *pages = alloc_pages_exact(size, gfp | __GFP_ZERO); + void *pages = io_pgtable_alloc_pages_exact(cfg, cookie, + size, gfp | __GFP_ZERO); if (!pages) return NULL; @@ -236,17 +352,17 @@ out_unmap: dev_err(dev, "Cannot accommodate DMA translation for IOMMU page tables\n"); dma_unmap_single(dev, dma, size, DMA_TO_DEVICE); out_free: - free_pages_exact(pages, size); + io_pgtable_free_pages_exact(cfg, cookie, pages, size); return NULL; } static void __arm_lpae_free_pages(void *pages, size_t size, - struct io_pgtable_cfg *cfg) + struct io_pgtable_cfg *cfg, void *cookie) { if (!selftest_running) dma_unmap_single(cfg->iommu_dev, __arm_lpae_dma_addr(pages), size, DMA_TO_DEVICE); - free_pages_exact(pages, size); + io_pgtable_free_pages_exact(cfg, cookie, pages, size); } static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, @@ -255,38 +371,24 @@ static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, *ptep = pte; if (!selftest_running) - dma_sync_single_for_device(cfg->iommu_dev, + pgtable_dma_sync_single_for_device(cfg, __arm_lpae_dma_addr(ptep), sizeof(pte), DMA_TO_DEVICE); } -static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, - unsigned long iova, size_t size, int lvl, - arm_lpae_iopte *ptep); - static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, unsigned long iova, phys_addr_t paddr, arm_lpae_iopte prot, int lvl, - arm_lpae_iopte *ptep) + arm_lpae_iopte *ptep, arm_lpae_iopte *prev_ptep, + bool flush) { arm_lpae_iopte pte = prot; struct io_pgtable_cfg *cfg = &data->iop.cfg; - if (iopte_leaf(*ptep, lvl)) { - /* We require an unmap first */ - WARN_ON(!selftest_running); + /* We require an unmap first */ + if (*ptep & ARM_LPAE_PTE_VALID) { + BUG_ON(!suppress_map_failures); return -EEXIST; - } else if (iopte_type(*ptep, lvl) == ARM_LPAE_PTE_TYPE_TABLE) { - /* - * We need to unmap and free the old table before - * overwriting it with a block entry. - */ - arm_lpae_iopte *tblp; - size_t sz = ARM_LPAE_BLOCK_SIZE(lvl, data); - - tblp = ptep - ARM_LPAE_LVL_IDX(iova, lvl, data); - if (WARN_ON(__arm_lpae_unmap(data, iova, sz, lvl, tblp) != sz)) - return -EINVAL; } if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS) @@ -297,27 +399,82 @@ static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, else pte |= ARM_LPAE_PTE_TYPE_BLOCK; - pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_IS; + pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_OS; pte |= pfn_to_iopte(paddr >> data->pg_shift, data); - __arm_lpae_set_pte(ptep, pte, cfg); + *ptep = pte; + + if (flush) + __arm_lpae_set_pte(ptep, pte, cfg); + + if (prev_ptep) + iopte_tblcnt_add(prev_ptep, 1); + return 0; } +struct map_state { + unsigned long iova_end; + unsigned int pgsize; + arm_lpae_iopte *pgtable; + arm_lpae_iopte *prev_pgtable; + arm_lpae_iopte *pte_start; + unsigned int num_pte; +}; +/* map state optimization works at level 3 (the 2nd-to-last level) */ +#define MAP_STATE_LVL 3 + static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, phys_addr_t paddr, size_t size, arm_lpae_iopte prot, - int lvl, arm_lpae_iopte *ptep) + int lvl, arm_lpae_iopte *ptep, + arm_lpae_iopte *prev_ptep, struct map_state *ms) { arm_lpae_iopte *cptep, pte; + void *cookie = data->iop.cookie; size_t block_size = ARM_LPAE_BLOCK_SIZE(lvl, data); + arm_lpae_iopte *pgtable = ptep; struct io_pgtable_cfg *cfg = &data->iop.cfg; /* Find our entry at the current level */ ptep += ARM_LPAE_LVL_IDX(iova, lvl, data); /* If we can install a leaf entry at this level, then do so */ - if (size == block_size && (size & cfg->pgsize_bitmap)) - return arm_lpae_init_pte(data, iova, paddr, prot, lvl, ptep); + if (size == block_size && (size & cfg->pgsize_bitmap)) { + if (!ms) + return arm_lpae_init_pte(data, iova, paddr, prot, lvl, + ptep, prev_ptep, true); + + if (lvl == MAP_STATE_LVL) { + if (ms->pgtable) + pgtable_dma_sync_single_for_device(cfg, + __arm_lpae_dma_addr(ms->pte_start), + ms->num_pte * sizeof(*ptep), + DMA_TO_DEVICE); + + ms->iova_end = round_down(iova, SZ_2M) + SZ_2M; + ms->pgtable = pgtable; + ms->prev_pgtable = prev_ptep; + ms->pgsize = size; + ms->pte_start = ptep; + ms->num_pte = 1; + } else { + /* + * We have some map state from previous page + * mappings, but we're about to set up a block + * mapping. Flush out the previous page mappings. + */ + if (ms->pgtable) + pgtable_dma_sync_single_for_device(cfg, + __arm_lpae_dma_addr(ms->pte_start), + ms->num_pte * sizeof(*ptep), + DMA_TO_DEVICE); + memset(ms, 0, sizeof(*ms)); + ms = NULL; + } + + return arm_lpae_init_pte(data, iova, paddr, prot, lvl, ptep, + prev_ptep, ms == NULL); + } /* We can't allocate tables at the final level */ if (WARN_ON(lvl >= ARM_LPAE_MAX_LEVELS - 1)) @@ -327,7 +484,7 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, pte = *ptep; if (!pte) { cptep = __arm_lpae_alloc_pages(1UL << data->pg_shift, - GFP_ATOMIC, cfg); + GFP_ATOMIC, cfg, cookie); if (!cptep) return -ENOMEM; @@ -344,7 +501,8 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, } /* Rinse, repeat */ - return __arm_lpae_map(data, iova, paddr, size, prot, lvl + 1, cptep); + return __arm_lpae_map(data, iova, paddr, size, prot, lvl + 1, cptep, + ptep, ms); } static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, @@ -354,14 +512,22 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, if (data->iop.fmt == ARM_64_LPAE_S1 || data->iop.fmt == ARM_32_LPAE_S1) { - pte = ARM_LPAE_PTE_AP_UNPRIV | ARM_LPAE_PTE_nG; + pte = ARM_LPAE_PTE_nG; - if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ)) - pte |= ARM_LPAE_PTE_AP_RDONLY; + if (prot & IOMMU_WRITE) + pte |= (prot & IOMMU_PRIV) ? ARM_LPAE_PTE_AP_PRIV_RW + : ARM_LPAE_PTE_AP_RW; + else + pte |= (prot & IOMMU_PRIV) ? ARM_LPAE_PTE_AP_PRIV_RO + : ARM_LPAE_PTE_AP_RO; if (prot & IOMMU_CACHE) pte |= (ARM_LPAE_MAIR_ATTR_IDX_CACHE << ARM_LPAE_PTE_ATTRINDX_SHIFT); + + if (prot & IOMMU_DEVICE) + pte |= (ARM_LPAE_MAIR_ATTR_IDX_DEV << + ARM_LPAE_PTE_ATTRINDX_SHIFT); } else { pte = ARM_LPAE_PTE_HAP_FAULT; if (prot & IOMMU_READ) @@ -372,6 +538,9 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, pte |= ARM_LPAE_PTE_MEMATTR_OIWB; else pte |= ARM_LPAE_PTE_MEMATTR_NC; + + if (prot & IOMMU_DEVICE) + pte |= ARM_LPAE_PTE_MEMATTR_DEV; } if (prot & IOMMU_NOEXEC) @@ -380,20 +549,42 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, return pte; } +static inline arm_lpae_iopte *arm_lpae_get_table( + struct arm_lpae_io_pgtable *data, unsigned long iova) +{ + struct io_pgtable_cfg *cfg = &data->iop.cfg; + + /* + * iovas for TTBR1 will have all the bits set between the input address + * region and the sign extension bit + */ + if (unlikely(cfg->quirks & IO_PGTABLE_QUIRK_ARM_TTBR1)) { + unsigned long mask = GENMASK(cfg->sep, cfg->ias); + + if ((iova & mask) == mask) + return data->pgd[1]; + } + + return data->pgd[0]; +} + static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, phys_addr_t paddr, size_t size, int iommu_prot) { struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); - arm_lpae_iopte *ptep = data->pgd; + arm_lpae_iopte *ptep; int ret, lvl = ARM_LPAE_START_LVL(data); arm_lpae_iopte prot; + ptep = arm_lpae_get_table(data, iova); + /* If no access, then nothing to do */ if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE))) return 0; prot = arm_lpae_prot_to_pte(data, iommu_prot); - ret = __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep); + ret = __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep, NULL, + NULL); /* * Synchronise all PTE updates for the new mapping before there's * a chance for anything to kick off a table walk for the new iova. @@ -403,6 +594,91 @@ static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, return ret; } +static int arm_lpae_map_sg(struct io_pgtable_ops *ops, unsigned long iova, + struct scatterlist *sg, unsigned int nents, + int iommu_prot, size_t *size) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + arm_lpae_iopte *ptep; + int lvl = ARM_LPAE_START_LVL(data); + arm_lpae_iopte prot; + struct scatterlist *s; + size_t mapped = 0; + int i, ret; + unsigned int min_pagesz; + struct map_state ms; + + ptep = arm_lpae_get_table(data, iova); + + /* If no access, then nothing to do */ + if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE))) + goto out_err; + + prot = arm_lpae_prot_to_pte(data, iommu_prot); + + min_pagesz = 1 << __ffs(data->iop.cfg.pgsize_bitmap); + + memset(&ms, 0, sizeof(ms)); + + for_each_sg(sg, s, nents, i) { + phys_addr_t phys = page_to_phys(sg_page(s)) + s->offset; + size_t size = s->length; + + /* + * We are mapping on IOMMU page boundaries, so offset within + * the page must be 0. However, the IOMMU may support pages + * smaller than PAGE_SIZE, so s->offset may still represent + * an offset of that boundary within the CPU page. + */ + if (!IS_ALIGNED(s->offset, min_pagesz)) + goto out_err; + + while (size) { + size_t pgsize = iommu_pgsize( + data->iop.cfg.pgsize_bitmap, iova | phys, size); + + if (ms.pgtable && (iova < ms.iova_end)) { + arm_lpae_iopte *ptep = ms.pgtable + + ARM_LPAE_LVL_IDX(iova, MAP_STATE_LVL, + data); + arm_lpae_init_pte( + data, iova, phys, prot, MAP_STATE_LVL, + ptep, ms.prev_pgtable, false); + ms.num_pte++; + } else { + ret = __arm_lpae_map(data, iova, phys, pgsize, + prot, lvl, ptep, NULL, &ms); + if (ret) + goto out_err; + } + + iova += pgsize; + mapped += pgsize; + phys += pgsize; + size -= pgsize; + } + } + + if (ms.pgtable) + pgtable_dma_sync_single_for_device(cfg, + __arm_lpae_dma_addr(ms.pte_start), + ms.num_pte * sizeof(*ms.pte_start), + DMA_TO_DEVICE); + /* + * Synchronise all PTE updates for the new mapping before there's + * a chance for anything to kick off a table walk for the new iova. + */ + wmb(); + + return mapped; + +out_err: + /* Return the size of the partial mapping so that they can be undone */ + *size = mapped; + return 0; +} + static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, arm_lpae_iopte *ptep) { @@ -422,6 +698,10 @@ static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, else end = (void *)ptep + table_size; + /* Only leaf entries at the last level */ + if (lvl == ARM_LPAE_MAX_LEVELS - 1) + goto end; + while (ptep != end) { arm_lpae_iopte pte = *ptep++; @@ -431,21 +711,27 @@ static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); } - __arm_lpae_free_pages(start, table_size, &data->iop.cfg); +end: + __arm_lpae_free_pages(start, table_size, &data->iop.cfg, + data->iop.cookie); } static void arm_lpae_free_pgtable(struct io_pgtable *iop) { struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop); - __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd); + __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd[0]); + if (data->pgd[1]) + __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), + data->pgd[1]); kfree(data); } static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, unsigned long iova, size_t size, arm_lpae_iopte prot, int lvl, - arm_lpae_iopte *ptep, size_t blk_size) + arm_lpae_iopte *ptep, + arm_lpae_iopte *prev_ptep, size_t blk_size) { unsigned long blk_start, blk_end; phys_addr_t blk_paddr; @@ -455,6 +741,7 @@ static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, blk_start = iova & ~(blk_size - 1); blk_end = blk_start + blk_size; blk_paddr = iopte_to_pfn(*ptep, data) << data->pg_shift; + size = ARM_LPAE_BLOCK_SIZE(lvl + 1, data); for (; blk_start < blk_end; blk_start += size, blk_paddr += size) { arm_lpae_iopte *tablep; @@ -466,7 +753,7 @@ static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, /* __arm_lpae_map expects a pointer to the start of the table */ tablep = &table - ARM_LPAE_LVL_IDX(blk_start, lvl, data); if (__arm_lpae_map(data, blk_start, blk_paddr, size, prot, lvl, - tablep) < 0) { + tablep, prev_ptep, NULL) < 0) { if (table) { /* Free the table we allocated */ tablep = iopte_deref(table, data); @@ -477,17 +764,15 @@ static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, } __arm_lpae_set_pte(ptep, table, cfg); - iova &= ~(blk_size - 1); - cfg->tlb->tlb_add_flush(iova, blk_size, true, data->iop.cookie); return size; } static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, unsigned long iova, size_t size, int lvl, - arm_lpae_iopte *ptep) + arm_lpae_iopte *ptep, arm_lpae_iopte *prev_ptep) { arm_lpae_iopte pte; - const struct iommu_gather_ops *tlb = data->iop.cfg.tlb; + struct io_pgtable_cfg *cfg = &data->iop.cfg; void *cookie = data->iop.cookie; size_t blk_size = ARM_LPAE_BLOCK_SIZE(lvl, data); @@ -504,15 +789,45 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, if (!iopte_leaf(pte, lvl)) { /* Also flush any partial walks */ - tlb->tlb_add_flush(iova, size, false, cookie); - tlb->tlb_sync(cookie); ptep = iopte_deref(pte, data); __arm_lpae_free_pgtable(data, lvl + 1, ptep); - } else { - tlb->tlb_add_flush(iova, size, true, cookie); } return size; + } else if ((lvl == ARM_LPAE_MAX_LEVELS - 2) && !iopte_leaf(pte, lvl)) { + arm_lpae_iopte *table = iopte_deref(pte, data); + arm_lpae_iopte *table_base = table; + int tl_offset = ARM_LPAE_LVL_IDX(iova, lvl + 1, data); + int entry_size = (1 << data->pg_shift); + int max_entries = ARM_LPAE_BLOCK_SIZE(lvl, data) / entry_size; + int entries = min_t(int, size / entry_size, + max_entries - tl_offset); + int table_len = entries * sizeof(*table); + + /* + * This isn't a block mapping so it must be a table mapping + * and since it's the 2nd-to-last level the next level has + * to be all page mappings. Zero them all out in one fell + * swoop. + */ + + table += tl_offset; + + memset(table, 0, table_len); + pgtable_dma_sync_single_for_device(cfg, + __arm_lpae_dma_addr(table), + table_len, DMA_TO_DEVICE); + + iopte_tblcnt_sub(ptep, entries); + if (!iopte_tblcnt(*ptep)) { + /* no valid mappings left under this table. free it. */ + __arm_lpae_set_pte(ptep, 0, cfg); + io_pgtable_free_pages_exact( + &data->iop.cfg, cookie, table_base, + max_entries * sizeof(*table_base)); + } + + return entries * entry_size; } else if (iopte_leaf(pte, lvl)) { /* * Insert a table at the next level to map the old region, @@ -520,63 +835,150 @@ static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, */ return arm_lpae_split_blk_unmap(data, iova, size, iopte_prot(pte), lvl, ptep, + prev_ptep, blk_size); } /* Keep on walkin' */ + prev_ptep = ptep; ptep = iopte_deref(pte, data); - return __arm_lpae_unmap(data, iova, size, lvl + 1, ptep); + return __arm_lpae_unmap(data, iova, size, lvl + 1, ptep, prev_ptep); } -static int arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova, +static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova, size_t size) { - size_t unmapped; + size_t unmapped = 0; struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); struct io_pgtable *iop = &data->iop; - arm_lpae_iopte *ptep = data->pgd; + arm_lpae_iopte *ptep; int lvl = ARM_LPAE_START_LVL(data); - unmapped = __arm_lpae_unmap(data, iova, size, lvl, ptep); + ptep = arm_lpae_get_table(data, iova); + + while (unmapped < size) { + size_t ret, size_to_unmap, remaining; + + remaining = (size - unmapped); + size_to_unmap = remaining < SZ_2M + ? remaining + : iommu_pgsize(data->iop.cfg.pgsize_bitmap, iova, + remaining); + ret = __arm_lpae_unmap(data, iova, size_to_unmap, lvl, ptep, + NULL); + if (ret == 0) + break; + unmapped += ret; + iova += ret; + } if (unmapped) - iop->cfg.tlb->tlb_sync(iop->cookie); + iop->cfg.tlb->tlb_flush_all(iop->cookie); return unmapped; } -static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops, - unsigned long iova) +static int arm_lpae_iova_to_pte(struct arm_lpae_io_pgtable *data, + unsigned long iova, int *plvl_ret, + arm_lpae_iopte *ptep_ret) { - struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); - arm_lpae_iopte pte, *ptep = data->pgd; - int lvl = ARM_LPAE_START_LVL(data); + arm_lpae_iopte pte, *ptep; + + ptep = arm_lpae_get_table(data, iova); + + *plvl_ret = ARM_LPAE_START_LVL(data); + *ptep_ret = 0; do { /* Valid IOPTE pointer? */ if (!ptep) - return 0; + return -EINVAL; /* Grab the IOPTE we're interested in */ - pte = *(ptep + ARM_LPAE_LVL_IDX(iova, lvl, data)); + pte = *(ptep + ARM_LPAE_LVL_IDX(iova, *plvl_ret, data)); /* Valid entry? */ if (!pte) - return 0; + return -EINVAL; /* Leaf entry? */ - if (iopte_leaf(pte,lvl)) + if (iopte_leaf(pte, *plvl_ret)) goto found_translation; /* Take it to the next level */ ptep = iopte_deref(pte, data); - } while (++lvl < ARM_LPAE_MAX_LEVELS); + } while (++(*plvl_ret) < ARM_LPAE_MAX_LEVELS); /* Ran out of page tables to walk */ - return 0; + return -EINVAL; found_translation: - iova &= ((1 << data->pg_shift) - 1); - return ((phys_addr_t)iopte_to_pfn(pte,data) << data->pg_shift) | iova; + *ptep_ret = pte; + return 0; +} + +static uint64_t arm_lpae_iova_get_pte(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + arm_lpae_iopte pte; + int lvl; + + if (!arm_lpae_iova_to_pte(data, iova, &lvl, &pte)) + return pte; + + return 0; +} + +static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + arm_lpae_iopte pte; + int lvl; + phys_addr_t phys = 0; + + if (!arm_lpae_iova_to_pte(data, iova, &lvl, &pte)) { + iova &= ((1 << ARM_LPAE_LVL_SHIFT(lvl, data)) - 1); + phys = ((phys_addr_t)iopte_to_pfn(pte, data) + << data->pg_shift) | iova; + } + + return phys; +} + +static bool __arm_lpae_is_iova_coherent(struct arm_lpae_io_pgtable *data, + arm_lpae_iopte *ptep) +{ + if (data->iop.fmt == ARM_64_LPAE_S1 || + data->iop.fmt == ARM_32_LPAE_S1) { + int attr_idx = (*ptep & (ARM_LPAE_PTE_ATTRINDX_MASK << + ARM_LPAE_PTE_ATTRINDX_SHIFT)) >> + ARM_LPAE_PTE_ATTRINDX_SHIFT; + if ((attr_idx == ARM_LPAE_MAIR_ATTR_IDX_CACHE) && + (((*ptep & ARM_LPAE_PTE_SH_MASK) == ARM_LPAE_PTE_SH_IS) + || + (*ptep & ARM_LPAE_PTE_SH_MASK) == ARM_LPAE_PTE_SH_OS)) + return true; + } else { + if (*ptep & ARM_LPAE_PTE_MEMATTR_OIWB) + return true; + } + + return false; +} + +static bool arm_lpae_is_iova_coherent(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + arm_lpae_iopte pte; + int lvl; + bool ret = false; + + if (!arm_lpae_iova_to_pte(data, iova, &lvl, &pte)) + ret = __arm_lpae_is_iova_coherent(data, &pte); + + return ret; } static void arm_lpae_restrict_pgsizes(struct io_pgtable_cfg *cfg) @@ -648,17 +1050,86 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg) /* Calculate the actual size of our pgd (without concatenation) */ pgd_bits = va_bits - (data->bits_per_level * (data->levels - 1)); + data->pgd_bits = pgd_bits; data->pgd_size = 1UL << (pgd_bits + ilog2(sizeof(arm_lpae_iopte))); data->iop.ops = (struct io_pgtable_ops) { .map = arm_lpae_map, + .map_sg = arm_lpae_map_sg, .unmap = arm_lpae_unmap, .iova_to_phys = arm_lpae_iova_to_phys, + .is_iova_coherent = arm_lpae_is_iova_coherent, + .iova_to_pte = arm_lpae_iova_get_pte, }; return data; } +static u64 arm64_lpae_setup_ttbr1(struct io_pgtable_cfg *cfg, + struct arm_lpae_io_pgtable *data) + +{ + u64 reg; + + /* If TTBR1 is disabled, disable speculative walks through the TTBR1 */ + if (!(cfg->quirks & IO_PGTABLE_QUIRK_ARM_TTBR1)) { + reg = ARM_LPAE_TCR_EPD1; + reg |= (ARM_LPAE_TCR_SEP_UPSTREAM << ARM_LPAE_TCR_SEP_SHIFT); + return reg; + } + + if (cfg->iommu_dev && cfg->iommu_dev->archdata.dma_coherent) + reg = (ARM_LPAE_TCR_SH_OS << ARM_LPAE_TCR_SH1_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN1_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN1_SHIFT); + else + reg = (ARM_LPAE_TCR_SH_OS << ARM_LPAE_TCR_SH1_SHIFT) | + (ARM_LPAE_TCR_RGN_NC << ARM_LPAE_TCR_IRGN1_SHIFT) | + (ARM_LPAE_TCR_RGN_NC << ARM_LPAE_TCR_ORGN1_SHIFT); + + switch (1 << data->pg_shift) { + case SZ_4K: + reg |= (ARM_LPAE_TCR_TG1_4K << 30); + break; + case SZ_16K: + reg |= (ARM_LPAE_TCR_TG1_16K << 30); + break; + case SZ_64K: + reg |= (ARM_LPAE_TCR_TG1_64K << 30); + break; + } + + /* Set T1SZ */ + reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T1SZ_SHIFT; + + switch (cfg->sep) { + case 31: + reg |= (ARM_LPAE_TCR_SEP_31 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 35: + reg |= (ARM_LPAE_TCR_SEP_35 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 39: + reg |= (ARM_LPAE_TCR_SEP_39 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 41: + reg |= (ARM_LPAE_TCR_SEP_41 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 43: + reg |= (ARM_LPAE_TCR_SEP_43 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 47: + reg |= (ARM_LPAE_TCR_SEP_47 << ARM_LPAE_TCR_SEP_SHIFT); + break; + case 48: + default: + reg |= (ARM_LPAE_TCR_SEP_UPSTREAM << ARM_LPAE_TCR_SEP_SHIFT); + break; + } + + return reg; +} + static struct io_pgtable * arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) { @@ -669,9 +1140,14 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) return NULL; /* TCR */ - reg = (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH0_SHIFT) | - (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | - (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); + if (cfg->quirks & IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT) + reg = (ARM_LPAE_TCR_SH_OS << ARM_LPAE_TCR_SH0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); + else + reg = (ARM_LPAE_TCR_SH_OS << ARM_LPAE_TCR_SH0_SHIFT) | + (ARM_LPAE_TCR_RGN_NC << ARM_LPAE_TCR_IRGN0_SHIFT) | + (ARM_LPAE_TCR_RGN_NC << ARM_LPAE_TCR_ORGN0_SHIFT); switch (1 << data->pg_shift) { case SZ_4K: @@ -710,8 +1186,9 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT; - /* Disable speculative walks through TTBR1 */ - reg |= ARM_LPAE_TCR_EPD1; + /* Bring in the TTBR1 configuration */ + reg |= arm64_lpae_setup_ttbr1(cfg, data); + cfg->arm_lpae_s1_cfg.tcr = reg; /* MAIRs */ @@ -726,16 +1203,33 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) cfg->arm_lpae_s1_cfg.mair[1] = 0; /* Looking good; allocate a pgd */ - data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg); - if (!data->pgd) + data->pgd[0] = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg, + cookie); + if (!data->pgd[0]) goto out_free_data; + + if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_TTBR1) { + data->pgd[1] = __arm_lpae_alloc_pages(data->pgd_size, + GFP_KERNEL, cfg, cookie); + if (!data->pgd[1]) { + __arm_lpae_free_pages(data->pgd[0], data->pgd_size, cfg, + cookie); + goto out_free_data; + } + } else { + data->pgd[1] = NULL; + } + /* Ensure the empty pgd is visible before any actual TTBR write */ wmb(); /* TTBRs */ - cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd); - cfg->arm_lpae_s1_cfg.ttbr[1] = 0; + cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd[0]); + + if (data->pgd[1]) + cfg->arm_lpae_s1_cfg.ttbr[1] = virt_to_phys(data->pgd[1]); + return &data->iop; out_free_data: @@ -815,15 +1309,16 @@ arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie) cfg->arm_lpae_s2_cfg.vtcr = reg; /* Allocate pgd pages */ - data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg); - if (!data->pgd) + data->pgd[0] = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg, + cookie); + if (!data->pgd[0]) goto out_free_data; /* Ensure the empty pgd is visible before any actual TTBR write */ wmb(); /* VTTBR */ - cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd); + cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd[0]); return &data->iop; out_free_data: @@ -921,16 +1416,54 @@ static void __init arm_lpae_dump_ops(struct io_pgtable_ops *ops) cfg->pgsize_bitmap, cfg->ias); pr_err("data: %d levels, 0x%zx pgd_size, %lu pg_shift, %lu bits_per_level, pgd @ %p\n", data->levels, data->pgd_size, data->pg_shift, - data->bits_per_level, data->pgd); + data->bits_per_level, data->pgd[0]); } #define __FAIL(ops, i) ({ \ WARN(1, "selftest: test failed for fmt idx %d\n", (i)); \ arm_lpae_dump_ops(ops); \ - selftest_running = false; \ + selftest_running = false; \ + suppress_map_failures = false; \ -EFAULT; \ }) +/* + * Returns true if there's any mapping in the given iova range in ops. + */ +static bool arm_lpae_range_has_mapping(struct io_pgtable_ops *ops, + unsigned long iova_start, size_t size) +{ + unsigned long iova = iova_start; + + while (iova < (iova_start + size)) { + if (ops->iova_to_phys(ops, iova + 42)) + return true; + iova += SZ_4K; + } + return false; +} + +/* + * Returns true if the iova range is successfully mapped to the contiguous + * phys range in ops. + */ +static bool arm_lpae_range_has_specific_mapping(struct io_pgtable_ops *ops, + const unsigned long iova_start, + const phys_addr_t phys_start, + const size_t size) +{ + unsigned long iova = iova_start; + phys_addr_t phys = phys_start; + + while (iova < (iova_start + size)) { + if (ops->iova_to_phys(ops, iova + 42) != (phys + 42)) + return false; + iova += SZ_4K; + phys += SZ_4K; + } + return true; +} + static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) { static const enum io_pgtable_fmt fmts[] = { @@ -938,7 +1471,7 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) ARM_64_LPAE_S2, }; - int i, j; + int i, j, k; unsigned long iova; size_t size; struct io_pgtable_ops *ops; @@ -946,6 +1479,9 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) selftest_running = true; for (i = 0; i < ARRAY_SIZE(fmts); ++i) { + unsigned long test_sg_sizes[] = { SZ_4K, SZ_64K, SZ_2M, + SZ_1M * 12, SZ_1M * 20 }; + cfg_cookie = cfg; ops = alloc_io_pgtable_ops(fmts[i], cfg, cfg); if (!ops) { @@ -954,16 +1490,11 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) } /* - * Initial sanity checks. - * Empty page tables shouldn't provide any translations. + * Initial sanity checks. Empty page tables shouldn't + * provide any translations. TODO: check entire supported + * range for these ops rather than first 2G */ - if (ops->iova_to_phys(ops, 42)) - return __FAIL(ops, i); - - if (ops->iova_to_phys(ops, SZ_1G + 42)) - return __FAIL(ops, i); - - if (ops->iova_to_phys(ops, SZ_2G + 42)) + if (arm_lpae_range_has_mapping(ops, 0, SZ_2G)) return __FAIL(ops, i); /* @@ -980,12 +1511,15 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) IOMMU_CACHE)) return __FAIL(ops, i); + suppress_map_failures = true; /* Overlapping mappings */ if (!ops->map(ops, iova, iova + size, size, IOMMU_READ | IOMMU_NOEXEC)) return __FAIL(ops, i); + suppress_map_failures = false; - if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) + if (!arm_lpae_range_has_specific_mapping(ops, iova, + iova, size)) return __FAIL(ops, i); iova += SZ_1G; @@ -998,11 +1532,15 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) if (ops->unmap(ops, SZ_1G + size, size) != size) return __FAIL(ops, i); + if (arm_lpae_range_has_mapping(ops, SZ_1G + size, size)) + return __FAIL(ops, i); + /* Remap of partial unmap */ if (ops->map(ops, SZ_1G + size, size, size, IOMMU_READ)) return __FAIL(ops, i); - if (ops->iova_to_phys(ops, SZ_1G + size + 42) != (size + 42)) + if (!arm_lpae_range_has_specific_mapping(ops, SZ_1G + size, + size, size)) return __FAIL(ops, i); /* Full unmap */ @@ -1024,15 +1562,107 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg) if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) return __FAIL(ops, i); + if (ops->unmap(ops, iova, size) != size) + return __FAIL(ops, i); + iova += SZ_1G; j++; j = find_next_bit(&cfg->pgsize_bitmap, BITS_PER_LONG, j); } + if (arm_lpae_range_has_mapping(ops, 0, SZ_2G)) + return __FAIL(ops, i); + + if ((cfg->pgsize_bitmap & SZ_2M) && + (cfg->pgsize_bitmap & SZ_4K)) { + /* mixed block + page mappings */ + iova = 0; + if (ops->map(ops, iova, iova, SZ_2M, IOMMU_READ)) + return __FAIL(ops, i); + + if (ops->map(ops, iova + SZ_2M, iova + SZ_2M, SZ_4K, + IOMMU_READ)) + return __FAIL(ops, i); + + if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) + return __FAIL(ops, i); + + if (ops->iova_to_phys(ops, iova + SZ_2M + 42) != + (iova + SZ_2M + 42)) + return __FAIL(ops, i); + + /* unmap both mappings at once */ + if (ops->unmap(ops, iova, SZ_2M + SZ_4K) != + (SZ_2M + SZ_4K)) + return __FAIL(ops, i); + + if (arm_lpae_range_has_mapping(ops, 0, SZ_2G)) + return __FAIL(ops, i); + } + + /* map_sg */ + for (j = 0; j < ARRAY_SIZE(test_sg_sizes); ++j) { + size_t mapped; + size_t unused; + struct page *page; + phys_addr_t page_phys; + struct sg_table table; + struct scatterlist *sg; + unsigned long total_size = test_sg_sizes[j]; + int chunk_size = 1UL << find_first_bit( + &cfg->pgsize_bitmap, BITS_PER_LONG); + int nents = total_size / chunk_size; + + if (total_size < chunk_size) + continue; + + page = alloc_pages(GFP_KERNEL, get_order(chunk_size)); + page_phys = page_to_phys(page); + + iova = 0; + BUG_ON(sg_alloc_table(&table, nents, GFP_KERNEL)); + BUG_ON(!page); + for_each_sg(table.sgl, sg, table.nents, k) + sg_set_page(sg, page, chunk_size, 0); + + mapped = ops->map_sg(ops, iova, table.sgl, table.nents, + IOMMU_READ | IOMMU_WRITE, &unused); + + if (mapped != total_size) + return __FAIL(ops, i); + + if (!arm_lpae_range_has_mapping(ops, iova, total_size)) + return __FAIL(ops, i); + + if (arm_lpae_range_has_mapping(ops, iova + total_size, + SZ_2G - (iova + total_size))) + return __FAIL(ops, i); + + for_each_sg(table.sgl, sg, table.nents, k) { + dma_addr_t newphys = + ops->iova_to_phys(ops, iova + 42); + if (newphys != (page_phys + 42)) + return __FAIL(ops, i); + iova += chunk_size; + } + + if (ops->unmap(ops, 0, total_size) != total_size) + return __FAIL(ops, i); + + if (arm_lpae_range_has_mapping(ops, 0, SZ_2G)) + return __FAIL(ops, i); + + sg_free_table(&table); + __free_pages(page, get_order(chunk_size)); + } + + if (arm_lpae_range_has_mapping(ops, 0, SZ_2G)) + return __FAIL(ops, i); + free_io_pgtable_ops(ops); } - selftest_running = false; + suppress_map_failures = false; return 0; } diff --git a/drivers/iommu/io-pgtable-fast.c b/drivers/iommu/io-pgtable-fast.c new file mode 100644 index 000000000000..5378e95c4627 --- /dev/null +++ b/drivers/iommu/io-pgtable-fast.c @@ -0,0 +1,751 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "io-pgtable-fast: " fmt + +#include <linux/iommu.h> +#include <linux/kernel.h> +#include <linux/scatterlist.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/io-pgtable-fast.h> +#include <linux/mm.h> +#include <asm/cacheflush.h> +#include <linux/vmalloc.h> + +#include "io-pgtable.h" + +#define AV8L_FAST_MAX_ADDR_BITS 48 + +/* Struct accessors */ +#define iof_pgtable_to_data(x) \ + container_of((x), struct av8l_fast_io_pgtable, iop) + +#define iof_pgtable_ops_to_pgtable(x) \ + container_of((x), struct io_pgtable, ops) + +#define iof_pgtable_ops_to_data(x) \ + iof_pgtable_to_data(iof_pgtable_ops_to_pgtable(x)) + +struct av8l_fast_io_pgtable { + struct io_pgtable iop; + av8l_fast_iopte *pgd; + av8l_fast_iopte *puds[4]; + av8l_fast_iopte *pmds; + struct page **pages; /* page table memory */ + int nr_pages; + dma_addr_t base; + dma_addr_t end; +}; + +/* Page table bits */ +#define AV8L_FAST_PTE_TYPE_SHIFT 0 +#define AV8L_FAST_PTE_TYPE_MASK 0x3 + +#define AV8L_FAST_PTE_TYPE_BLOCK 1 +#define AV8L_FAST_PTE_TYPE_TABLE 3 +#define AV8L_FAST_PTE_TYPE_PAGE 3 + +#define AV8L_FAST_PTE_NSTABLE (((av8l_fast_iopte)1) << 63) +#define AV8L_FAST_PTE_XN (((av8l_fast_iopte)3) << 53) +#define AV8L_FAST_PTE_AF (((av8l_fast_iopte)1) << 10) +#define AV8L_FAST_PTE_SH_NS (((av8l_fast_iopte)0) << 8) +#define AV8L_FAST_PTE_SH_OS (((av8l_fast_iopte)2) << 8) +#define AV8L_FAST_PTE_SH_IS (((av8l_fast_iopte)3) << 8) +#define AV8L_FAST_PTE_NS (((av8l_fast_iopte)1) << 5) +#define AV8L_FAST_PTE_VALID (((av8l_fast_iopte)1) << 0) + +#define AV8L_FAST_PTE_ATTR_LO_MASK (((av8l_fast_iopte)0x3ff) << 2) +/* Ignore the contiguous bit for block splitting */ +#define AV8L_FAST_PTE_ATTR_HI_MASK (((av8l_fast_iopte)6) << 52) +#define AV8L_FAST_PTE_ATTR_MASK (AV8L_FAST_PTE_ATTR_LO_MASK | \ + AV8L_FAST_PTE_ATTR_HI_MASK) +#define AV8L_FAST_PTE_ADDR_MASK ((av8l_fast_iopte)0xfffffffff000) + + +/* Stage-1 PTE */ +#define AV8L_FAST_PTE_AP_PRIV_RW (((av8l_fast_iopte)0) << 6) +#define AV8L_FAST_PTE_AP_RW (((av8l_fast_iopte)1) << 6) +#define AV8L_FAST_PTE_AP_PRIV_RO (((av8l_fast_iopte)2) << 6) +#define AV8L_FAST_PTE_AP_RO (((av8l_fast_iopte)3) << 6) +#define AV8L_FAST_PTE_ATTRINDX_SHIFT 2 +#define AV8L_FAST_PTE_nG (((av8l_fast_iopte)1) << 11) + +/* Stage-2 PTE */ +#define AV8L_FAST_PTE_HAP_FAULT (((av8l_fast_iopte)0) << 6) +#define AV8L_FAST_PTE_HAP_READ (((av8l_fast_iopte)1) << 6) +#define AV8L_FAST_PTE_HAP_WRITE (((av8l_fast_iopte)2) << 6) +#define AV8L_FAST_PTE_MEMATTR_OIWB (((av8l_fast_iopte)0xf) << 2) +#define AV8L_FAST_PTE_MEMATTR_NC (((av8l_fast_iopte)0x5) << 2) +#define AV8L_FAST_PTE_MEMATTR_DEV (((av8l_fast_iopte)0x1) << 2) + +/* Register bits */ +#define ARM_32_LPAE_TCR_EAE (1 << 31) +#define ARM_64_LPAE_S2_TCR_RES1 (1 << 31) + +#define AV8L_FAST_TCR_TG0_4K (0 << 14) +#define AV8L_FAST_TCR_TG0_64K (1 << 14) +#define AV8L_FAST_TCR_TG0_16K (2 << 14) + +#define AV8L_FAST_TCR_SH0_SHIFT 12 +#define AV8L_FAST_TCR_SH0_MASK 0x3 +#define AV8L_FAST_TCR_SH_NS 0 +#define AV8L_FAST_TCR_SH_OS 2 +#define AV8L_FAST_TCR_SH_IS 3 + +#define AV8L_FAST_TCR_ORGN0_SHIFT 10 +#define AV8L_FAST_TCR_IRGN0_SHIFT 8 +#define AV8L_FAST_TCR_RGN_MASK 0x3 +#define AV8L_FAST_TCR_RGN_NC 0 +#define AV8L_FAST_TCR_RGN_WBWA 1 +#define AV8L_FAST_TCR_RGN_WT 2 +#define AV8L_FAST_TCR_RGN_WB 3 + +#define AV8L_FAST_TCR_SL0_SHIFT 6 +#define AV8L_FAST_TCR_SL0_MASK 0x3 + +#define AV8L_FAST_TCR_T0SZ_SHIFT 0 +#define AV8L_FAST_TCR_SZ_MASK 0xf + +#define AV8L_FAST_TCR_PS_SHIFT 16 +#define AV8L_FAST_TCR_PS_MASK 0x7 + +#define AV8L_FAST_TCR_IPS_SHIFT 32 +#define AV8L_FAST_TCR_IPS_MASK 0x7 + +#define AV8L_FAST_TCR_PS_32_BIT 0x0ULL +#define AV8L_FAST_TCR_PS_36_BIT 0x1ULL +#define AV8L_FAST_TCR_PS_40_BIT 0x2ULL +#define AV8L_FAST_TCR_PS_42_BIT 0x3ULL +#define AV8L_FAST_TCR_PS_44_BIT 0x4ULL +#define AV8L_FAST_TCR_PS_48_BIT 0x5ULL + +#define AV8L_FAST_TCR_EPD1_SHIFT 23 +#define AV8L_FAST_TCR_EPD1_FAULT 1 + +#define AV8L_FAST_TCR_SEP_SHIFT (15 + 32) +#define AV8L_FAST_TCR_SEP_UPSTREAM 7ULL + +#define AV8L_FAST_MAIR_ATTR_SHIFT(n) ((n) << 3) +#define AV8L_FAST_MAIR_ATTR_MASK 0xff +#define AV8L_FAST_MAIR_ATTR_DEVICE 0x04 +#define AV8L_FAST_MAIR_ATTR_NC 0x44 +#define AV8L_FAST_MAIR_ATTR_WBRWA 0xff +#define AV8L_FAST_MAIR_ATTR_IDX_NC 0 +#define AV8L_FAST_MAIR_ATTR_IDX_CACHE 1 +#define AV8L_FAST_MAIR_ATTR_IDX_DEV 2 + +#define AV8L_FAST_PAGE_SHIFT 12 + + +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST_PROVE_TLB + +#include <asm/cacheflush.h> +#include <linux/notifier.h> + +static ATOMIC_NOTIFIER_HEAD(av8l_notifier_list); + +void av8l_register_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_register(&av8l_notifier_list, nb); +} +EXPORT_SYMBOL(av8l_register_notify); + +static void __av8l_check_for_stale_tlb(av8l_fast_iopte *ptep) +{ + if (unlikely(*ptep)) { + atomic_notifier_call_chain( + &av8l_notifier_list, MAPPED_OVER_STALE_TLB, + (void *) ptep); + pr_err("Tried to map over a non-vacant pte: 0x%llx @ %p\n", + *ptep, ptep); + pr_err("Nearby memory:\n"); + print_hex_dump(KERN_ERR, "pgtbl: ", DUMP_PREFIX_ADDRESS, + 32, 8, ptep - 16, 32 * sizeof(*ptep), false); + } +} + +void av8l_fast_clear_stale_ptes(av8l_fast_iopte *pmds, u64 base, + u64 start, u64 end, bool skip_sync) +{ + int i; + av8l_fast_iopte *pmdp = iopte_pmd_offset(pmds, base, start); + + for (i = start >> AV8L_FAST_PAGE_SHIFT; + i <= (end >> AV8L_FAST_PAGE_SHIFT); ++i) { + if (!(*pmdp & AV8L_FAST_PTE_VALID)) { + *pmdp = 0; + if (!skip_sync) + dmac_clean_range(pmdp, pmdp + 1); + } + pmdp++; + } +} +#else +static void __av8l_check_for_stale_tlb(av8l_fast_iopte *ptep) +{ +} +#endif + +/* caller must take care of cache maintenance on *ptep */ +int av8l_fast_map_public(av8l_fast_iopte *ptep, phys_addr_t paddr, size_t size, + int prot) +{ + int i, nptes = size >> AV8L_FAST_PAGE_SHIFT; + av8l_fast_iopte pte = AV8L_FAST_PTE_XN + | AV8L_FAST_PTE_TYPE_PAGE + | AV8L_FAST_PTE_AF + | AV8L_FAST_PTE_nG + | AV8L_FAST_PTE_SH_OS; + + if (prot & IOMMU_DEVICE) + pte |= (AV8L_FAST_MAIR_ATTR_IDX_DEV + << AV8L_FAST_PTE_ATTRINDX_SHIFT); + else if (prot & IOMMU_CACHE) + pte |= (AV8L_FAST_MAIR_ATTR_IDX_CACHE + << AV8L_FAST_PTE_ATTRINDX_SHIFT); + + if (!(prot & IOMMU_WRITE)) + pte |= AV8L_FAST_PTE_AP_RO; + else + pte |= AV8L_FAST_PTE_AP_RW; + + paddr &= AV8L_FAST_PTE_ADDR_MASK; + for (i = 0; i < nptes; i++, paddr += SZ_4K) { + __av8l_check_for_stale_tlb(ptep + i); + *(ptep + i) = pte | paddr; + } + + return 0; +} + +static int av8l_fast_map(struct io_pgtable_ops *ops, unsigned long iova, + phys_addr_t paddr, size_t size, int prot) +{ + struct av8l_fast_io_pgtable *data = iof_pgtable_ops_to_data(ops); + av8l_fast_iopte *ptep = iopte_pmd_offset(data->pmds, data->base, iova); + unsigned long nptes = size >> AV8L_FAST_PAGE_SHIFT; + + av8l_fast_map_public(ptep, paddr, size, prot); + dmac_clean_range(ptep, ptep + nptes); + + return 0; +} + +static void __av8l_fast_unmap(av8l_fast_iopte *ptep, size_t size, + bool need_stale_tlb_tracking) +{ + unsigned long nptes = size >> AV8L_FAST_PAGE_SHIFT; + int val = need_stale_tlb_tracking + ? AV8L_FAST_PTE_UNMAPPED_NEED_TLBI + : 0; + + memset(ptep, val, sizeof(*ptep) * nptes); +} + +/* caller must take care of cache maintenance on *ptep */ +void av8l_fast_unmap_public(av8l_fast_iopte *ptep, size_t size) +{ + __av8l_fast_unmap(ptep, size, true); +} + +static size_t av8l_fast_unmap(struct io_pgtable_ops *ops, unsigned long iova, + size_t size) +{ + struct av8l_fast_io_pgtable *data = iof_pgtable_ops_to_data(ops); + struct io_pgtable *iop = &data->iop; + av8l_fast_iopte *ptep = iopte_pmd_offset(data->pmds, data->base, iova); + unsigned long nptes = size >> AV8L_FAST_PAGE_SHIFT; + + __av8l_fast_unmap(ptep, size, false); + dmac_clean_range(ptep, ptep + nptes); + iop->cfg.tlb->tlb_flush_all(iop->cookie); + + return size; +} + +#if defined(CONFIG_ARM64) +#define FAST_PGDNDX(va) (((va) & 0x7fc0000000) >> 27) +#elif defined(CONFIG_ARM) +#define FAST_PGDNDX(va) (((va) & 0xc0000000) >> 27) +#endif + +static phys_addr_t av8l_fast_iova_to_phys(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct av8l_fast_io_pgtable *data = iof_pgtable_ops_to_data(ops); + av8l_fast_iopte pte, *pgdp, *pudp, *pmdp; + unsigned long pgd; + phys_addr_t phys; + const unsigned long pts = AV8L_FAST_PTE_TYPE_SHIFT; + const unsigned long ptm = AV8L_FAST_PTE_TYPE_MASK; + const unsigned long ptt = AV8L_FAST_PTE_TYPE_TABLE; + const unsigned long ptp = AV8L_FAST_PTE_TYPE_PAGE; + const av8l_fast_iopte am = AV8L_FAST_PTE_ADDR_MASK; + + /* TODO: clean up some of these magic numbers... */ + + pgd = (unsigned long)data->pgd | FAST_PGDNDX(iova); + pgdp = (av8l_fast_iopte *)pgd; + + pte = *pgdp; + if (((pte >> pts) & ptm) != ptt) + return 0; + pudp = phys_to_virt((pte & am) | ((iova & 0x3fe00000) >> 18)); + + pte = *pudp; + if (((pte >> pts) & ptm) != ptt) + return 0; + pmdp = phys_to_virt((pte & am) | ((iova & 0x1ff000) >> 9)); + + pte = *pmdp; + if (((pte >> pts) & ptm) != ptp) + return 0; + phys = pte & am; + + return phys | (iova & 0xfff); +} + +static int av8l_fast_map_sg(struct io_pgtable_ops *ops, unsigned long iova, + struct scatterlist *sg, unsigned int nents, + int prot, size_t *size) +{ + return -ENODEV; +} + +static struct av8l_fast_io_pgtable * +av8l_fast_alloc_pgtable_data(struct io_pgtable_cfg *cfg) +{ + struct av8l_fast_io_pgtable *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->iop.ops = (struct io_pgtable_ops) { + .map = av8l_fast_map, + .map_sg = av8l_fast_map_sg, + .unmap = av8l_fast_unmap, + .iova_to_phys = av8l_fast_iova_to_phys, + }; + + return data; +} + +/* + * We need max 1 page for the pgd, 4 pages for puds (1GB VA per pud page) and + * 2048 pages for pmds (each pud page contains 512 table entries, each + * pointing to a pmd). + */ +#define NUM_PGD_PAGES 1 +#define NUM_PUD_PAGES 4 +#define NUM_PMD_PAGES 2048 +#define NUM_PGTBL_PAGES (NUM_PGD_PAGES + NUM_PUD_PAGES + NUM_PMD_PAGES) + +/* undefine arch specific definitions which depends on page table format */ +#undef pud_index +#undef pud_mask +#undef pud_next +#undef pmd_index +#undef pmd_mask +#undef pmd_next + +#define pud_index(addr) (((addr) >> 30) & 0x3) +#define pud_mask(addr) ((addr) & ~((1UL << 30) - 1)) +#define pud_next(addr, end) \ +({ unsigned long __boundary = pud_mask(addr + (1UL << 30));\ + (__boundary - 1 < (end) - 1) ? __boundary : (end); \ +}) + +#define pmd_index(addr) (((addr) >> 21) & 0x1ff) +#define pmd_mask(addr) ((addr) & ~((1UL << 21) - 1)) +#define pmd_next(addr, end) \ +({ unsigned long __boundary = pmd_mask(addr + (1UL << 21));\ + (__boundary - 1 < (end) - 1) ? __boundary : (end); \ +}) + +static int +av8l_fast_prepopulate_pgtables(struct av8l_fast_io_pgtable *data, + struct io_pgtable_cfg *cfg, void *cookie) +{ + int i, j, pg = 0; + struct page **pages, *page; + dma_addr_t base = cfg->iova_base; + dma_addr_t end = cfg->iova_end; + dma_addr_t pud, pmd; + int pmd_pg_index; + + pages = kmalloc(sizeof(*pages) * NUM_PGTBL_PAGES, __GFP_NOWARN | + __GFP_NORETRY); + + if (!pages) + pages = vmalloc(sizeof(*pages) * NUM_PGTBL_PAGES); + + if (!pages) + return -ENOMEM; + + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + goto err_free_pages_arr; + pages[pg++] = page; + data->pgd = page_address(page); + + /* + * We need max 2048 entries at level 2 to map 4GB of VA space. A page + * can hold 512 entries, so we need max 4 pages. + */ + for (i = pud_index(base), pud = base; pud < end; + ++i, pud = pud_next(pud, end)) { + av8l_fast_iopte pte, *ptep; + + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + goto err_free_pages; + pages[pg++] = page; + data->puds[i] = page_address(page); + pte = page_to_phys(page) | AV8L_FAST_PTE_TYPE_TABLE; + ptep = ((av8l_fast_iopte *)data->pgd) + i; + *ptep = pte; + } + dmac_clean_range(data->pgd, data->pgd + 4); + + /* + * We have max 4 puds, each of which can point to 512 pmds, so we'll + * have max 2048 pmds, each of which can hold 512 ptes, for a grand + * total of 2048*512=1048576 PTEs. + */ + pmd_pg_index = pg; + for (i = pud_index(base), pud = base; pud < end; + ++i, pud = pud_next(pud, end)) { + for (j = pmd_index(pud), pmd = pud; pmd < pud_next(pud, end); + ++j, pmd = pmd_next(pmd, end)) { + av8l_fast_iopte pte, *pudp; + void *addr; + + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + goto err_free_pages; + pages[pg++] = page; + + addr = page_address(page); + dmac_clean_range(addr, addr + SZ_4K); + + pte = page_to_phys(page) | AV8L_FAST_PTE_TYPE_TABLE; + pudp = data->puds[i] + j; + *pudp = pte; + } + dmac_clean_range(data->puds[i], data->puds[i] + 512); + } + + /* + * We map the pmds into a virtually contiguous space so that we + * don't have to traverse the first two levels of the page tables + * to find the appropriate pud. Instead, it will be a simple + * offset from the virtual base of the pmds. + */ + data->pmds = vmap(&pages[pmd_pg_index], pg - pmd_pg_index, + VM_IOREMAP, PAGE_KERNEL); + if (!data->pmds) + goto err_free_pages; + + data->pages = pages; + data->nr_pages = pg; + data->base = base; + data->end = end; + return 0; + +err_free_pages: + for (i = 0; i < pg; ++i) + __free_page(pages[i]); +err_free_pages_arr: + kvfree(pages); + return -ENOMEM; +} + +static struct io_pgtable * +av8l_fast_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) +{ + u64 reg; + struct av8l_fast_io_pgtable *data = + av8l_fast_alloc_pgtable_data(cfg); + + if (!data) + return NULL; + + /* restrict according to the fast map requirements */ + cfg->ias = 32; + cfg->pgsize_bitmap = SZ_4K; + + /* TCR */ + if (cfg->quirks & IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT) + reg = (AV8L_FAST_TCR_SH_OS << AV8L_FAST_TCR_SH0_SHIFT) | + (AV8L_FAST_TCR_RGN_WBWA << AV8L_FAST_TCR_IRGN0_SHIFT) | + (AV8L_FAST_TCR_RGN_WBWA << AV8L_FAST_TCR_ORGN0_SHIFT); + else + reg = (AV8L_FAST_TCR_SH_OS << AV8L_FAST_TCR_SH0_SHIFT) | + (AV8L_FAST_TCR_RGN_NC << AV8L_FAST_TCR_IRGN0_SHIFT) | + (AV8L_FAST_TCR_RGN_NC << AV8L_FAST_TCR_ORGN0_SHIFT); + + reg |= AV8L_FAST_TCR_TG0_4K; + + switch (cfg->oas) { + case 32: + reg |= (AV8L_FAST_TCR_PS_32_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + case 36: + reg |= (AV8L_FAST_TCR_PS_36_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + case 40: + reg |= (AV8L_FAST_TCR_PS_40_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + case 42: + reg |= (AV8L_FAST_TCR_PS_42_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + case 44: + reg |= (AV8L_FAST_TCR_PS_44_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + case 48: + reg |= (AV8L_FAST_TCR_PS_48_BIT << AV8L_FAST_TCR_IPS_SHIFT); + break; + default: + goto out_free_data; + } + + reg |= (64ULL - cfg->ias) << AV8L_FAST_TCR_T0SZ_SHIFT; + reg |= AV8L_FAST_TCR_EPD1_FAULT << AV8L_FAST_TCR_EPD1_SHIFT; +#if defined(CONFIG_ARM) + reg |= ARM_32_LPAE_TCR_EAE; +#endif + reg |= AV8L_FAST_TCR_SEP_UPSTREAM << AV8L_FAST_TCR_SEP_SHIFT; + cfg->av8l_fast_cfg.tcr = reg; + + /* MAIRs */ + reg = (AV8L_FAST_MAIR_ATTR_NC + << AV8L_FAST_MAIR_ATTR_SHIFT(AV8L_FAST_MAIR_ATTR_IDX_NC)) | + (AV8L_FAST_MAIR_ATTR_WBRWA + << AV8L_FAST_MAIR_ATTR_SHIFT(AV8L_FAST_MAIR_ATTR_IDX_CACHE)) | + (AV8L_FAST_MAIR_ATTR_DEVICE + << AV8L_FAST_MAIR_ATTR_SHIFT(AV8L_FAST_MAIR_ATTR_IDX_DEV)); + + cfg->av8l_fast_cfg.mair[0] = reg; + cfg->av8l_fast_cfg.mair[1] = 0; + + /* Allocate all page table memory! */ + if (av8l_fast_prepopulate_pgtables(data, cfg, cookie)) + goto out_free_data; + + cfg->av8l_fast_cfg.pmds = data->pmds; + + /* TTBRs */ + cfg->av8l_fast_cfg.ttbr[0] = virt_to_phys(data->pgd); + cfg->av8l_fast_cfg.ttbr[1] = 0; + return &data->iop; + +out_free_data: + kfree(data); + return NULL; +} + +static void av8l_fast_free_pgtable(struct io_pgtable *iop) +{ + int i; + struct av8l_fast_io_pgtable *data = iof_pgtable_to_data(iop); + + vunmap(data->pmds); + for (i = 0; i < data->nr_pages; ++i) + __free_page(data->pages[i]); + kvfree(data->pages); + kfree(data); +} + +struct io_pgtable_init_fns io_pgtable_av8l_fast_init_fns = { + .alloc = av8l_fast_alloc_pgtable, + .free = av8l_fast_free_pgtable, +}; + + +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST + +#include <linux/dma-contiguous.h> + +static struct io_pgtable_cfg *cfg_cookie; + +static void dummy_tlb_flush_all(void *cookie) +{ + WARN_ON(cookie != cfg_cookie); +} + +static void dummy_tlb_add_flush(unsigned long iova, size_t size, bool leaf, + void *cookie) +{ + WARN_ON(cookie != cfg_cookie); + WARN_ON(!(size & cfg_cookie->pgsize_bitmap)); +} + +static void dummy_tlb_sync(void *cookie) +{ + WARN_ON(cookie != cfg_cookie); +} + +static struct iommu_gather_ops dummy_tlb_ops __initdata = { + .tlb_flush_all = dummy_tlb_flush_all, + .tlb_add_flush = dummy_tlb_add_flush, + .tlb_sync = dummy_tlb_sync, +}; + +/* + * Returns true if the iova range is successfully mapped to the contiguous + * phys range in ops. + */ +static bool av8l_fast_range_has_specific_mapping(struct io_pgtable_ops *ops, + const unsigned long iova_start, + const phys_addr_t phys_start, + const size_t size) +{ + u64 iova = iova_start; + phys_addr_t phys = phys_start; + + while (iova < (iova_start + size)) { + /* + 42 just to make sure offsetting is working */ + if (ops->iova_to_phys(ops, iova + 42) != (phys + 42)) + return false; + iova += SZ_4K; + phys += SZ_4K; + } + return true; +} + +static int __init av8l_fast_positive_testing(void) +{ + int failed = 0; + u64 iova; + struct io_pgtable_ops *ops; + struct io_pgtable_cfg cfg; + struct av8l_fast_io_pgtable *data; + av8l_fast_iopte *pmds; + u64 max = SZ_1G * 4ULL - 1; + u64 base = 0; + + cfg = (struct io_pgtable_cfg) { + .quirks = 0, + .tlb = &dummy_tlb_ops, + .ias = 32, + .oas = 32, + .pgsize_bitmap = SZ_4K, + .iova_base = base, + .iova_end = max, + }; + + cfg_cookie = &cfg; + ops = alloc_io_pgtable_ops(ARM_V8L_FAST, &cfg, &cfg); + + if (WARN_ON(!ops)) + return 1; + + data = iof_pgtable_ops_to_data(ops); + pmds = data->pmds; + + /* map the entire 4GB VA space with 4K map calls */ + for (iova = base; iova < max; iova += SZ_4K) { + if (WARN_ON(ops->map(ops, iova, iova, SZ_4K, IOMMU_READ))) { + failed++; + continue; + } + } + if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, base, + base, max - base))) + failed++; + + /* unmap it all */ + for (iova = base; iova < max; iova += SZ_4K) { + if (WARN_ON(ops->unmap(ops, iova, SZ_4K) != SZ_4K)) + failed++; + } + + /* sweep up TLB proving PTEs */ + av8l_fast_clear_stale_ptes(pmds, base, base, max, false); + + /* map the entire 4GB VA space with 8K map calls */ + for (iova = base; iova < max; iova += SZ_8K) { + if (WARN_ON(ops->map(ops, iova, iova, SZ_8K, IOMMU_READ))) { + failed++; + continue; + } + } + + if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, base, + base, max - base))) + failed++; + + /* unmap it all with 8K unmap calls */ + for (iova = base; iova < max; iova += SZ_8K) { + if (WARN_ON(ops->unmap(ops, iova, SZ_8K) != SZ_8K)) + failed++; + } + + /* sweep up TLB proving PTEs */ + av8l_fast_clear_stale_ptes(pmds, base, base, max, false); + + /* map the entire 4GB VA space with 16K map calls */ + for (iova = base; iova < max; iova += SZ_16K) { + if (WARN_ON(ops->map(ops, iova, iova, SZ_16K, IOMMU_READ))) { + failed++; + continue; + } + } + + if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, base, + base, max - base))) + failed++; + + /* unmap it all */ + for (iova = base; iova < max; iova += SZ_16K) { + if (WARN_ON(ops->unmap(ops, iova, SZ_16K) != SZ_16K)) + failed++; + } + + /* sweep up TLB proving PTEs */ + av8l_fast_clear_stale_ptes(pmds, base, base, max, false); + + /* map the entire 4GB VA space with 64K map calls */ + for (iova = base; iova < max; iova += SZ_64K) { + if (WARN_ON(ops->map(ops, iova, iova, SZ_64K, IOMMU_READ))) { + failed++; + continue; + } + } + + if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, base, + base, max - base))) + failed++; + + /* unmap it all at once */ + if (WARN_ON(ops->unmap(ops, base, max - base) != (max - base))) + failed++; + + free_io_pgtable_ops(ops); + return failed; +} + +static int __init av8l_fast_do_selftests(void) +{ + int failed = 0; + + failed += av8l_fast_positive_testing(); + + pr_err("selftest: completed with %d failures\n", failed); + + return 0; +} +subsys_initcall(av8l_fast_do_selftests); +#endif diff --git a/drivers/iommu/io-pgtable-msm-secure.c b/drivers/iommu/io-pgtable-msm-secure.c new file mode 100644 index 000000000000..336a43a1ed1f --- /dev/null +++ b/drivers/iommu/io-pgtable-msm-secure.c @@ -0,0 +1,243 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "io-pgtable-msm-secure: " fmt + +#include <linux/iommu.h> +#include <linux/kernel.h> +#include <linux/scatterlist.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <soc/qcom/scm.h> +#include <asm/cacheflush.h> + +#include "io-pgtable.h" + +#define IOMMU_SECURE_MAP2_FLAT 0x12 +#define IOMMU_SECURE_UNMAP2_FLAT 0x13 +#define IOMMU_TLBINVAL_FLAG 0x00000001 + +#define io_pgtable_to_data(x) \ + container_of((x), struct msm_secure_io_pgtable, iop) + +#define io_pgtable_ops_to_pgtable(x) \ + container_of((x), struct io_pgtable, ops) + +#define io_pgtable_ops_to_data(x) \ + io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) + +struct msm_secure_io_pgtable { + struct io_pgtable iop; +}; + +static int msm_secure_map(struct io_pgtable_ops *ops, unsigned long iova, + phys_addr_t paddr, size_t size, int iommu_prot) +{ + return -EINVAL; +} + +static dma_addr_t msm_secure_get_phys_addr(struct scatterlist *sg) +{ + /* + * Try sg_dma_address first so that we can + * map carveout regions that do not have a + * struct page associated with them. + */ + dma_addr_t pa = sg_dma_address(sg); + + if (pa == 0) + pa = sg_phys(sg); + return pa; +} + +static int msm_secure_map_sg(struct io_pgtable_ops *ops, unsigned long iova, + struct scatterlist *sg, unsigned int nents, + int iommu_prot, size_t *size) +{ + struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + int ret = -EINVAL; + struct scatterlist *tmp, *sgiter; + dma_addr_t *pa_list = 0; + unsigned int cnt, offset = 0, chunk_offset = 0; + dma_addr_t pa; + void *flush_va, *flush_va_end; + unsigned long len = 0; + struct scm_desc desc = {0}; + int i; + u32 resp; + + for_each_sg(sg, tmp, nents, i) + len += tmp->length; + + if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M)) + return -EINVAL; + + if (sg->length == len) { + cnt = 1; + pa = msm_secure_get_phys_addr(sg); + if (!IS_ALIGNED(pa, SZ_1M)) + return -EINVAL; + + desc.args[0] = virt_to_phys(&pa); + desc.args[1] = cnt; + desc.args[2] = len; + flush_va = &pa; + } else { + sgiter = sg; + if (!IS_ALIGNED(sgiter->length, SZ_1M)) + return -EINVAL; + cnt = sg->length / SZ_1M; + while ((sgiter = sg_next(sgiter))) { + if (!IS_ALIGNED(sgiter->length, SZ_1M)) + return -EINVAL; + cnt += sgiter->length / SZ_1M; + } + + pa_list = kmalloc_array(cnt, sizeof(*pa_list), GFP_KERNEL); + if (!pa_list) + return -ENOMEM; + + sgiter = sg; + cnt = 0; + pa = msm_secure_get_phys_addr(sgiter); + while (offset < len) { + + if (!IS_ALIGNED(pa, SZ_1M)) { + kfree(pa_list); + return -EINVAL; + } + + pa_list[cnt] = pa + chunk_offset; + chunk_offset += SZ_1M; + offset += SZ_1M; + cnt++; + + if (chunk_offset >= sgiter->length && offset < len) { + chunk_offset = 0; + sgiter = sg_next(sgiter); + pa = msm_secure_get_phys_addr(sgiter); + } + } + + desc.args[0] = virt_to_phys(pa_list); + desc.args[1] = cnt; + desc.args[2] = SZ_1M; + flush_va = pa_list; + } + + desc.args[3] = cfg->arm_msm_secure_cfg.sec_id; + desc.args[4] = cfg->arm_msm_secure_cfg.cbndx; + desc.args[5] = iova; + desc.args[6] = len; + desc.args[7] = 0; + + desc.arginfo = SCM_ARGS(8, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL, + SCM_VAL, SCM_VAL, SCM_VAL); + + /* + * Ensure that the buffer is in RAM by the time it gets to TZ + */ + + flush_va_end = (void *) (((unsigned long) flush_va) + + (cnt * sizeof(*pa_list))); + dmac_clean_range(flush_va, flush_va_end); + + if (is_scm_armv8()) { + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP, + IOMMU_SECURE_MAP2_FLAT), &desc); + resp = desc.ret[0]; + + if (ret || resp) + ret = -EINVAL; + else + ret = len; + } + + kfree(pa_list); + return ret; +} + +static size_t msm_secure_unmap(struct io_pgtable_ops *ops, unsigned long iova, + size_t len) +{ + struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + int ret = -EINVAL; + struct scm_desc desc = {0}; + + if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M)) + return ret; + + desc.args[0] = cfg->arm_msm_secure_cfg.sec_id; + desc.args[1] = cfg->arm_msm_secure_cfg.cbndx; + desc.args[2] = iova; + desc.args[3] = len; + desc.args[4] = IOMMU_TLBINVAL_FLAG; + desc.arginfo = SCM_ARGS(5); + + if (is_scm_armv8()) { + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP, + IOMMU_SECURE_UNMAP2_FLAT), &desc); + + if (!ret) + ret = len; + } + return ret; +} + +static phys_addr_t msm_secure_iova_to_phys(struct io_pgtable_ops *ops, + unsigned long iova) +{ + return -EINVAL; +} + +static struct msm_secure_io_pgtable * +msm_secure_alloc_pgtable_data(struct io_pgtable_cfg *cfg) +{ + struct msm_secure_io_pgtable *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->iop.ops = (struct io_pgtable_ops) { + .map = msm_secure_map, + .map_sg = msm_secure_map_sg, + .unmap = msm_secure_unmap, + .iova_to_phys = msm_secure_iova_to_phys, + }; + + return data; +} + +static struct io_pgtable * +msm_secure_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) +{ + struct msm_secure_io_pgtable *data = + msm_secure_alloc_pgtable_data(cfg); + + return &data->iop; +} + +static void msm_secure_free_pgtable(struct io_pgtable *iop) +{ + struct msm_secure_io_pgtable *data = io_pgtable_to_data(iop); + + kfree(data); +} + +struct io_pgtable_init_fns io_pgtable_arm_msm_secure_init_fns = { + .alloc = msm_secure_alloc_pgtable, + .free = msm_secure_free_pgtable, +}; diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c index 6f2e319d4f04..be11f5714750 100644 --- a/drivers/iommu/io-pgtable.c +++ b/drivers/iommu/io-pgtable.c @@ -18,12 +18,25 @@ * Author: Will Deacon <will.deacon@arm.com> */ +#define pr_fmt(fmt) "io-pgtable: " fmt + #include <linux/bug.h> #include <linux/kernel.h> #include <linux/types.h> +#include <linux/iommu.h> +#include <linux/debugfs.h> +#include <linux/atomic.h> +#include <linux/module.h> #include "io-pgtable.h" +extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_msm_secure_init_fns; +extern struct io_pgtable_init_fns io_pgtable_av8l_fast_init_fns; + static const struct io_pgtable_init_fns * io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = { @@ -33,8 +46,16 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = [ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns, [ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns, #endif +#ifdef CONFIG_MSM_TZ_SMMU + [ARM_MSM_SECURE] = &io_pgtable_arm_msm_secure_init_fns, +#endif +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST + [ARM_V8L_FAST] = &io_pgtable_av8l_fast_init_fns, +#endif }; +static struct dentry *io_pgtable_top; + struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, struct io_pgtable_cfg *cfg, void *cookie) @@ -72,6 +93,58 @@ void free_io_pgtable_ops(struct io_pgtable_ops *ops) return; iop = container_of(ops, struct io_pgtable, ops); - iop->cfg.tlb->tlb_flush_all(iop->cookie); io_pgtable_init_table[iop->fmt]->free(iop); } + +static atomic_t pages_allocated; + +void *io_pgtable_alloc_pages_exact(struct io_pgtable_cfg *cfg, void *cookie, + size_t size, gfp_t gfp_mask) +{ + void *ret; + + if (cfg->tlb->alloc_pages_exact) + ret = cfg->tlb->alloc_pages_exact(cookie, size, gfp_mask); + else + ret = alloc_pages_exact(size, gfp_mask); + + if (likely(ret)) + atomic_add(1 << get_order(size), &pages_allocated); + + return ret; +} + +void io_pgtable_free_pages_exact(struct io_pgtable_cfg *cfg, void *cookie, + void *virt, size_t size) +{ + if (cfg->tlb->free_pages_exact) + cfg->tlb->free_pages_exact(cookie, virt, size); + else + free_pages_exact(virt, size); + + atomic_sub(1 << get_order(size), &pages_allocated); +} + +static int io_pgtable_init(void) +{ + io_pgtable_top = debugfs_create_dir("io-pgtable", iommu_debugfs_top); + + if (!io_pgtable_top) + return -ENODEV; + + if (!debugfs_create_atomic_t("pages", 0600, + io_pgtable_top, &pages_allocated)) { + debugfs_remove_recursive(io_pgtable_top); + return -ENODEV; + } + + return 0; +} + +static void io_pgtable_exit(void) +{ + debugfs_remove_recursive(io_pgtable_top); +} + +module_init(io_pgtable_init); +module_exit(io_pgtable_exit); diff --git a/drivers/iommu/io-pgtable.h b/drivers/iommu/io-pgtable.h index ac9e2341a633..0326bb6a4afa 100644 --- a/drivers/iommu/io-pgtable.h +++ b/drivers/iommu/io-pgtable.h @@ -1,6 +1,9 @@ #ifndef __IO_PGTABLE_H #define __IO_PGTABLE_H +#include <linux/scatterlist.h> +#include <soc/qcom/msm_tz_smmu.h> + /* * Public API for use by IOMMU drivers */ @@ -9,6 +12,8 @@ enum io_pgtable_fmt { ARM_32_LPAE_S2, ARM_64_LPAE_S1, ARM_64_LPAE_S2, + ARM_MSM_SECURE, + ARM_V8L_FAST, IO_PGTABLE_NUM_FMTS, }; @@ -20,6 +25,10 @@ enum io_pgtable_fmt { * @tlb_sync: Ensure any queued TLB invalidation has taken effect, and * any corresponding page table updates are visible to the * IOMMU. + * @alloc_pages_exact: Allocate page table memory (optional, defaults to + * alloc_pages_exact) + * @free_pages_exact: Free page table memory (optional, defaults to + * free_pages_exact) * * Note that these can all be called in atomic context and must therefore * not block. @@ -29,6 +38,8 @@ struct iommu_gather_ops { void (*tlb_add_flush)(unsigned long iova, size_t size, bool leaf, void *cookie); void (*tlb_sync)(void *cookie); + void *(*alloc_pages_exact)(void *cookie, size_t size, gfp_t gfp_mask); + void (*free_pages_exact)(void *cookie, void *virt, size_t size); }; /** @@ -45,13 +56,22 @@ struct iommu_gather_ops { * page table walker. */ struct io_pgtable_cfg { + /* + * IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT: Set the page table as + * coherent. + */ #define IO_PGTABLE_QUIRK_ARM_NS (1 << 0) /* Set NS bit in PTEs */ + #define IO_PGTABLE_QUIRK_PAGE_TABLE_COHERENT (1 << 1) + #define IO_PGTABLE_QUIRK_ARM_TTBR1 (1 << 2) /* Allocate TTBR1 PT */ int quirks; unsigned long pgsize_bitmap; unsigned int ias; unsigned int oas; + int sep; const struct iommu_gather_ops *tlb; struct device *iommu_dev; + dma_addr_t iova_base; + dma_addr_t iova_end; /* Low-level data specific to the table format */ union { @@ -65,6 +85,18 @@ struct io_pgtable_cfg { u64 vttbr; u64 vtcr; } arm_lpae_s2_cfg; + + struct { + enum tz_smmu_device_id sec_id; + int cbndx; + } arm_msm_secure_cfg; + + struct { + u64 ttbr[2]; + u64 tcr; + u64 mair[2]; + void *pmds; + } av8l_fast_cfg; }; }; @@ -72,6 +104,9 @@ struct io_pgtable_cfg { * struct io_pgtable_ops - Page table manipulation API for IOMMU drivers. * * @map: Map a physically contiguous memory region. + * @map_sg: Map a scatterlist. Returns the number of bytes mapped, + * or 0 on failure. The size parameter contains the size + * of the partial mapping in case of failure. * @unmap: Unmap a physically contiguous memory region. * @iova_to_phys: Translate iova to physical address. * @@ -81,10 +116,18 @@ struct io_pgtable_cfg { struct io_pgtable_ops { int (*map)(struct io_pgtable_ops *ops, unsigned long iova, phys_addr_t paddr, size_t size, int prot); - int (*unmap)(struct io_pgtable_ops *ops, unsigned long iova, - size_t size); + int (*map_sg)(struct io_pgtable_ops *ops, unsigned long iova, + struct scatterlist *sg, unsigned int nents, + int prot, size_t *size); + size_t (*unmap)(struct io_pgtable_ops *ops, unsigned long iova, + size_t size); phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops, unsigned long iova); + bool (*is_iova_coherent)(struct io_pgtable_ops *ops, + unsigned long iova); + uint64_t (*iova_to_pte)(struct io_pgtable_ops *ops, + unsigned long iova); + }; /** @@ -143,6 +186,28 @@ struct io_pgtable_init_fns { void (*free)(struct io_pgtable *iop); }; +/** + * io_pgtable_alloc_pages_exact - allocate an exact number physically-contiguous pages. + * @size: the number of bytes to allocate + * @gfp_mask: GFP flags for the allocation + * + * Like alloc_pages_exact(), but with some additional accounting for debug + * purposes. + */ +void *io_pgtable_alloc_pages_exact(struct io_pgtable_cfg *cfg, void *cookie, + size_t size, gfp_t gfp_mask); + +/** + * io_pgtable_free_pages_exact - release memory allocated via io_pgtable_alloc_pages_exact() + * @virt: the value returned by alloc_pages_exact. + * @size: size of allocation, same value as passed to alloc_pages_exact(). + * + * Like free_pages_exact(), but with some additional accounting for debug + * purposes. + */ +void io_pgtable_free_pages_exact(struct io_pgtable_cfg *cfg, void *cookie, + void *virt, size_t size); + extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns; extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns; diff --git a/drivers/iommu/iommu-debug.c b/drivers/iommu/iommu-debug.c new file mode 100644 index 000000000000..108976f795b5 --- /dev/null +++ b/drivers/iommu/iommu-debug.c @@ -0,0 +1,2386 @@ +/* + * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#define pr_fmt(fmt) "iommu-debug: %s: " fmt, __func__ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/iommu.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/dma-contiguous.h> +#include <soc/qcom/secure_buffer.h> +#include <linux/qcom_iommu.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <asm/cacheflush.h> +#include <asm/dma-iommu.h> +#include "iommu-debug.h" + +#if defined(CONFIG_IOMMU_TESTS) + +static const char *iommu_debug_attr_to_string(enum iommu_attr attr) +{ + switch (attr) { + case DOMAIN_ATTR_GEOMETRY: + return "DOMAIN_ATTR_GEOMETRY"; + case DOMAIN_ATTR_PAGING: + return "DOMAIN_ATTR_PAGING"; + case DOMAIN_ATTR_WINDOWS: + return "DOMAIN_ATTR_WINDOWS"; + case DOMAIN_ATTR_FSL_PAMU_STASH: + return "DOMAIN_ATTR_FSL_PAMU_STASH"; + case DOMAIN_ATTR_FSL_PAMU_ENABLE: + return "DOMAIN_ATTR_FSL_PAMU_ENABLE"; + case DOMAIN_ATTR_FSL_PAMUV1: + return "DOMAIN_ATTR_FSL_PAMUV1"; + case DOMAIN_ATTR_NESTING: + return "DOMAIN_ATTR_NESTING"; + case DOMAIN_ATTR_PT_BASE_ADDR: + return "DOMAIN_ATTR_PT_BASE_ADDR"; + case DOMAIN_ATTR_SECURE_VMID: + return "DOMAIN_ATTR_SECURE_VMID"; + case DOMAIN_ATTR_ATOMIC: + return "DOMAIN_ATTR_ATOMIC"; + case DOMAIN_ATTR_CONTEXT_BANK: + return "DOMAIN_ATTR_CONTEXT_BANK"; + case DOMAIN_ATTR_TTBR0: + return "DOMAIN_ATTR_TTBR0"; + case DOMAIN_ATTR_CONTEXTIDR: + return "DOMAIN_ATTR_CONTEXTIDR"; + case DOMAIN_ATTR_PROCID: + return "DOMAIN_ATTR_PROCID"; + case DOMAIN_ATTR_DYNAMIC: + return "DOMAIN_ATTR_DYNAMIC"; + case DOMAIN_ATTR_NON_FATAL_FAULTS: + return "DOMAIN_ATTR_NON_FATAL_FAULTS"; + case DOMAIN_ATTR_S1_BYPASS: + return "DOMAIN_ATTR_S1_BYPASS"; + case DOMAIN_ATTR_FAST: + return "DOMAIN_ATTR_FAST"; + case DOMAIN_ATTR_EARLY_MAP: + return "DOMAIN_ATTR_EARLY_MAP"; + case DOMAIN_ATTR_CB_STALL_DISABLE: + return "DOMAIN_ATTR_CB_STALL_DISABLE"; + default: + return "Unknown attr!"; + } +} +#endif + +#ifdef CONFIG_IOMMU_DEBUG_TRACKING + +static DEFINE_MUTEX(iommu_debug_attachments_lock); +static LIST_HEAD(iommu_debug_attachments); + +/* + * Each group may have more than one domain; but each domain may + * only have one group. + * Used by debug tools to display the name of the device(s) associated + * with a particular domain. + */ +struct iommu_debug_attachment { + struct iommu_domain *domain; + struct iommu_group *group; + struct list_head list; +}; + +void iommu_debug_attach_device(struct iommu_domain *domain, + struct device *dev) +{ + struct iommu_debug_attachment *attach; + struct iommu_group *group; + + group = iommu_group_get(dev); + if (!group) + return; + + attach = kzalloc(sizeof(*attach), GFP_KERNEL); + if (!attach) + return; + + attach->domain = domain; + attach->group = group; + INIT_LIST_HEAD(&attach->list); + + mutex_lock(&iommu_debug_attachments_lock); + list_add(&attach->list, &iommu_debug_attachments); + mutex_unlock(&iommu_debug_attachments_lock); +} + +void iommu_debug_domain_remove(struct iommu_domain *domain) +{ + struct iommu_debug_attachment *it, *tmp; + + mutex_lock(&iommu_debug_attachments_lock); + list_for_each_entry_safe(it, tmp, &iommu_debug_attachments, list) { + if (it->domain != domain) + continue; + list_del(&it->list); + iommu_group_put(it->group); + kfree(it); + } + + mutex_unlock(&iommu_debug_attachments_lock); +} + +#endif + +#ifdef CONFIG_IOMMU_TESTS + +#ifdef CONFIG_64BIT + +#define kstrtoux kstrtou64 +#define kstrtox_from_user kstrtoull_from_user +#define kstrtosize_t kstrtoul + +#else + +#define kstrtoux kstrtou32 +#define kstrtox_from_user kstrtouint_from_user +#define kstrtosize_t kstrtouint + +#endif + +static LIST_HEAD(iommu_debug_devices); +static struct dentry *debugfs_tests_dir; +static u32 iters_per_op = 1; +static void *virt_addr; + +struct iommu_debug_device { + struct device *dev; + struct iommu_domain *domain; + u64 iova; + u64 phys; + size_t len; + struct list_head list; + struct mutex clk_lock; + struct mutex dev_lock; + unsigned int clk_count; +}; + +static int iommu_debug_build_phoney_sg_table(struct device *dev, + struct sg_table *table, + unsigned long total_size, + unsigned long chunk_size) +{ + unsigned long nents = total_size / chunk_size; + struct scatterlist *sg; + int i; + struct page *page; + + BUG_ON(!IS_ALIGNED(total_size, PAGE_SIZE)); + BUG_ON(!IS_ALIGNED(total_size, chunk_size)); + BUG_ON(sg_alloc_table(table, nents, GFP_KERNEL)); + page = alloc_pages(GFP_KERNEL, get_order(chunk_size)); + if (!page) + goto free_table; + + /* all the same page... why not. */ + for_each_sg(table->sgl, sg, table->nents, i) + sg_set_page(sg, page, chunk_size, 0); + + return 0; + +free_table: + sg_free_table(table); + return -ENOMEM; +} + +static void iommu_debug_destroy_phoney_sg_table(struct device *dev, + struct sg_table *table, + unsigned long chunk_size) +{ + __free_pages(sg_page(table->sgl), get_order(chunk_size)); + sg_free_table(table); +} + +static const char * const _size_to_string(unsigned long size) +{ + switch (size) { + case SZ_4K: + return "4K"; + case SZ_8K: + return "8K"; + case SZ_16K: + return "16K"; + case SZ_64K: + return "64K"; + case SZ_2M: + return "2M"; + case SZ_1M * 12: + return "12M"; + case SZ_1M * 20: + return "20M"; + } + return "unknown size, please add to _size_to_string"; +} + +static int nr_iters_set(void *data, u64 val) +{ + if (!val) + val = 1; + if (val > 10000) + val = 10000; + *(u32 *)data = val; + return 0; +} + +static int nr_iters_get(void *data, u64 *val) +{ + *val = *(u32 *)data; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(iommu_debug_nr_iters_ops, + nr_iters_get, nr_iters_set, "%llu\n"); + +static void iommu_debug_device_profiling(struct seq_file *s, struct device *dev, + enum iommu_attr attrs[], + void *attr_values[], int nattrs, + const size_t sizes[]) +{ + int i; + const size_t *sz; + struct iommu_domain *domain; + struct bus_type *bus; + unsigned long iova = 0x10000; + phys_addr_t paddr = 0xa000; + + bus = msm_iommu_get_bus(dev); + if (!bus) + return; + + domain = iommu_domain_alloc(bus); + if (!domain) { + seq_puts(s, "Couldn't allocate domain\n"); + return; + } + + seq_puts(s, "Domain attributes: [ "); + for (i = 0; i < nattrs; ++i) { + /* not all attrs are ints, but this will get us by for now */ + seq_printf(s, "%s=%d%s", iommu_debug_attr_to_string(attrs[i]), + *((int *)attr_values[i]), + i < nattrs ? " " : ""); + } + seq_puts(s, "]\n"); + for (i = 0; i < nattrs; ++i) { + if (iommu_domain_set_attr(domain, attrs[i], attr_values[i])) { + seq_printf(s, "Couldn't set %d to the value at %p\n", + attrs[i], attr_values[i]); + goto out_domain_free; + } + } + + if (iommu_attach_device(domain, dev)) { + seq_puts(s, + "Couldn't attach new domain to device. Is it already attached?\n"); + goto out_domain_free; + } + + seq_printf(s, "(average over %d iterations)\n", iters_per_op); + seq_printf(s, "%8s %19s %16s\n", "size", "iommu_map", "iommu_unmap"); + for (sz = sizes; *sz; ++sz) { + size_t size = *sz; + size_t unmapped; + u64 map_elapsed_ns = 0, unmap_elapsed_ns = 0; + u64 map_elapsed_us = 0, unmap_elapsed_us = 0; + u32 map_elapsed_rem = 0, unmap_elapsed_rem = 0; + struct timespec tbefore, tafter, diff; + int i; + + for (i = 0; i < iters_per_op; ++i) { + getnstimeofday(&tbefore); + if (iommu_map(domain, iova, paddr, size, + IOMMU_READ | IOMMU_WRITE)) { + seq_puts(s, "Failed to map\n"); + continue; + } + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + map_elapsed_ns += timespec_to_ns(&diff); + + getnstimeofday(&tbefore); + unmapped = iommu_unmap(domain, iova, size); + if (unmapped != size) { + seq_printf(s, + "Only unmapped %zx instead of %zx\n", + unmapped, size); + continue; + } + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + unmap_elapsed_ns += timespec_to_ns(&diff); + } + + map_elapsed_ns = div_u64_rem(map_elapsed_ns, iters_per_op, + &map_elapsed_rem); + unmap_elapsed_ns = div_u64_rem(unmap_elapsed_ns, iters_per_op, + &unmap_elapsed_rem); + + map_elapsed_us = div_u64_rem(map_elapsed_ns, 1000, + &map_elapsed_rem); + unmap_elapsed_us = div_u64_rem(unmap_elapsed_ns, 1000, + &unmap_elapsed_rem); + + seq_printf(s, "%8s %12lld.%03d us %9lld.%03d us\n", + _size_to_string(size), + map_elapsed_us, map_elapsed_rem, + unmap_elapsed_us, unmap_elapsed_rem); + } + + seq_putc(s, '\n'); + seq_printf(s, "%8s %19s %16s\n", "size", "iommu_map_sg", "iommu_unmap"); + for (sz = sizes; *sz; ++sz) { + size_t size = *sz; + size_t unmapped; + u64 map_elapsed_ns = 0, unmap_elapsed_ns = 0; + u64 map_elapsed_us = 0, unmap_elapsed_us = 0; + u32 map_elapsed_rem = 0, unmap_elapsed_rem = 0; + struct timespec tbefore, tafter, diff; + struct sg_table table; + unsigned long chunk_size = SZ_4K; + int i; + + if (iommu_debug_build_phoney_sg_table(dev, &table, size, + chunk_size)) { + seq_puts(s, + "couldn't build phoney sg table! bailing...\n"); + goto out_detach; + } + + for (i = 0; i < iters_per_op; ++i) { + getnstimeofday(&tbefore); + if (iommu_map_sg(domain, iova, table.sgl, table.nents, + IOMMU_READ | IOMMU_WRITE) != size) { + seq_puts(s, "Failed to map_sg\n"); + goto next; + } + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + map_elapsed_ns += timespec_to_ns(&diff); + + getnstimeofday(&tbefore); + unmapped = iommu_unmap(domain, iova, size); + if (unmapped != size) { + seq_printf(s, + "Only unmapped %zx instead of %zx\n", + unmapped, size); + goto next; + } + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + unmap_elapsed_ns += timespec_to_ns(&diff); + } + + map_elapsed_ns = div_u64_rem(map_elapsed_ns, iters_per_op, + &map_elapsed_rem); + unmap_elapsed_ns = div_u64_rem(unmap_elapsed_ns, iters_per_op, + &unmap_elapsed_rem); + + map_elapsed_us = div_u64_rem(map_elapsed_ns, 1000, + &map_elapsed_rem); + unmap_elapsed_us = div_u64_rem(unmap_elapsed_ns, 1000, + &unmap_elapsed_rem); + + seq_printf(s, "%8s %12lld.%03d us %9lld.%03d us\n", + _size_to_string(size), + map_elapsed_us, map_elapsed_rem, + unmap_elapsed_us, unmap_elapsed_rem); + +next: + iommu_debug_destroy_phoney_sg_table(dev, &table, chunk_size); + } + +out_detach: + iommu_detach_device(domain, dev); +out_domain_free: + iommu_domain_free(domain); +} + +static int iommu_debug_profiling_show(struct seq_file *s, void *ignored) +{ + struct iommu_debug_device *ddev = s->private; + const size_t sizes[] = { SZ_4K, SZ_64K, SZ_2M, SZ_1M * 12, + SZ_1M * 20, 0 }; + enum iommu_attr attrs[] = { + DOMAIN_ATTR_ATOMIC, + }; + int htw_disable = 1, atomic = 1; + void *attr_values[] = { &htw_disable, &atomic }; + + iommu_debug_device_profiling(s, ddev->dev, attrs, attr_values, + ARRAY_SIZE(attrs), sizes); + + return 0; +} + +static int iommu_debug_profiling_open(struct inode *inode, struct file *file) +{ + return single_open(file, iommu_debug_profiling_show, inode->i_private); +} + +static const struct file_operations iommu_debug_profiling_fops = { + .open = iommu_debug_profiling_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int iommu_debug_secure_profiling_show(struct seq_file *s, void *ignored) +{ + struct iommu_debug_device *ddev = s->private; + const size_t sizes[] = { SZ_4K, SZ_64K, SZ_2M, SZ_1M * 12, + SZ_1M * 20, 0 }; + + enum iommu_attr attrs[] = { + DOMAIN_ATTR_ATOMIC, + DOMAIN_ATTR_SECURE_VMID, + }; + int one = 1, secure_vmid = VMID_CP_PIXEL; + void *attr_values[] = { &one, &secure_vmid }; + + iommu_debug_device_profiling(s, ddev->dev, attrs, attr_values, + ARRAY_SIZE(attrs), sizes); + + return 0; +} + +static int iommu_debug_secure_profiling_open(struct inode *inode, + struct file *file) +{ + return single_open(file, iommu_debug_secure_profiling_show, + inode->i_private); +} + +static const struct file_operations iommu_debug_secure_profiling_fops = { + .open = iommu_debug_secure_profiling_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int iommu_debug_profiling_fast_show(struct seq_file *s, void *ignored) +{ + struct iommu_debug_device *ddev = s->private; + size_t sizes[] = {SZ_4K, SZ_8K, SZ_16K, SZ_64K, 0}; + enum iommu_attr attrs[] = { + DOMAIN_ATTR_FAST, + DOMAIN_ATTR_ATOMIC, + DOMAIN_ATTR_GEOMETRY, + }; + int one = 1; + struct iommu_domain_geometry geometry = {0, 0, 0}; + void *attr_values[] = { &one, &one, &geometry}; + + geometry.aperture_end = (dma_addr_t)(SZ_1G * 4ULL - 1); + + iommu_debug_device_profiling(s, ddev->dev, attrs, attr_values, + ARRAY_SIZE(attrs), sizes); + + return 0; +} + +static int iommu_debug_profiling_fast_open(struct inode *inode, + struct file *file) +{ + return single_open(file, iommu_debug_profiling_fast_show, + inode->i_private); +} + +static const struct file_operations iommu_debug_profiling_fast_fops = { + .open = iommu_debug_profiling_fast_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int iommu_debug_profiling_fast_dma_api_show(struct seq_file *s, + void *ignored) +{ + int i, experiment; + struct iommu_debug_device *ddev = s->private; + struct device *dev = ddev->dev; + u64 map_elapsed_ns[10], unmap_elapsed_ns[10]; + struct dma_iommu_mapping *mapping; + dma_addr_t dma_addr; + void *virt; + int fast = 1; + const char * const extra_labels[] = { + "not coherent", + "coherent", + }; + struct dma_attrs coherent_attrs; + struct dma_attrs *extra_attrs[] = { + NULL, + &coherent_attrs, + }; + + init_dma_attrs(&coherent_attrs); + dma_set_attr(DMA_ATTR_SKIP_CPU_SYNC, &coherent_attrs); + + virt = kmalloc(1518, GFP_KERNEL); + if (!virt) + goto out; + + mapping = arm_iommu_create_mapping(&platform_bus_type, 0, SZ_1G * 4ULL); + if (!mapping) { + seq_puts(s, "fast_smmu_create_mapping failed\n"); + goto out_kfree; + } + + if (iommu_domain_set_attr(mapping->domain, DOMAIN_ATTR_FAST, &fast)) { + seq_puts(s, "iommu_domain_set_attr failed\n"); + goto out_release_mapping; + } + + if (arm_iommu_attach_device(dev, mapping)) { + seq_puts(s, "fast_smmu_attach_device failed\n"); + goto out_release_mapping; + } + + if (iommu_enable_config_clocks(mapping->domain)) { + seq_puts(s, "Couldn't enable clocks\n"); + goto out_detach; + } + for (experiment = 0; experiment < 2; ++experiment) { + size_t map_avg = 0, unmap_avg = 0; + + for (i = 0; i < 10; ++i) { + struct timespec tbefore, tafter, diff; + u64 ns; + + getnstimeofday(&tbefore); + dma_addr = dma_map_single_attrs( + dev, virt, SZ_4K, DMA_TO_DEVICE, + extra_attrs[experiment]); + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + ns = timespec_to_ns(&diff); + if (dma_mapping_error(dev, dma_addr)) { + seq_puts(s, "dma_map_single failed\n"); + goto out_disable_config_clocks; + } + map_elapsed_ns[i] = ns; + + getnstimeofday(&tbefore); + dma_unmap_single_attrs( + dev, dma_addr, SZ_4K, DMA_TO_DEVICE, + extra_attrs[experiment]); + getnstimeofday(&tafter); + diff = timespec_sub(tafter, tbefore); + ns = timespec_to_ns(&diff); + unmap_elapsed_ns[i] = ns; + } + + seq_printf(s, "%13s %24s (ns): [", extra_labels[experiment], + "dma_map_single_attrs"); + for (i = 0; i < 10; ++i) { + map_avg += map_elapsed_ns[i]; + seq_printf(s, "%5llu%s", map_elapsed_ns[i], + i < 9 ? ", " : ""); + } + map_avg /= 10; + seq_printf(s, "] (avg: %zu)\n", map_avg); + + seq_printf(s, "%13s %24s (ns): [", extra_labels[experiment], + "dma_unmap_single_attrs"); + for (i = 0; i < 10; ++i) { + unmap_avg += unmap_elapsed_ns[i]; + seq_printf(s, "%5llu%s", unmap_elapsed_ns[i], + i < 9 ? ", " : ""); + } + unmap_avg /= 10; + seq_printf(s, "] (avg: %zu)\n", unmap_avg); + } + +out_disable_config_clocks: + iommu_disable_config_clocks(mapping->domain); +out_detach: + arm_iommu_detach_device(dev); +out_release_mapping: + arm_iommu_release_mapping(mapping); +out_kfree: + kfree(virt); +out: + return 0; +} + +static int iommu_debug_profiling_fast_dma_api_open(struct inode *inode, + struct file *file) +{ + return single_open(file, iommu_debug_profiling_fast_dma_api_show, + inode->i_private); +} + +static const struct file_operations iommu_debug_profiling_fast_dma_api_fops = { + .open = iommu_debug_profiling_fast_dma_api_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __tlb_stress_sweep(struct device *dev, struct seq_file *s) +{ + int i, ret = 0; + u64 iova; + const u64 max = SZ_1G * 4ULL - 1; + void *virt; + phys_addr_t phys; + dma_addr_t dma_addr; + + /* + * we'll be doing 4K and 8K mappings. Need to own an entire 8K + * chunk that we can work with. + */ + virt = (void *)__get_free_pages(GFP_KERNEL, get_order(SZ_8K)); + phys = virt_to_phys(virt); + + /* fill the whole 4GB space */ + for (iova = 0, i = 0; iova < max; iova += SZ_8K, ++i) { + dma_addr = dma_map_single(dev, virt, SZ_8K, DMA_TO_DEVICE); + if (dma_addr == DMA_ERROR_CODE) { + dev_err(dev, "Failed map on iter %d\n", i); + ret = -EINVAL; + goto out; + } + } + + if (dma_map_single(dev, virt, SZ_4K, DMA_TO_DEVICE) != DMA_ERROR_CODE) { + dev_err(dev, + "dma_map_single unexpectedly (VA should have been exhausted)\n"); + ret = -EINVAL; + goto out; + } + + /* + * free up 4K at the very beginning, then leave one 4K mapping, + * then free up 8K. This will result in the next 8K map to skip + * over the 4K hole and take the 8K one. + */ + dma_unmap_single(dev, 0, SZ_4K, DMA_TO_DEVICE); + dma_unmap_single(dev, SZ_8K, SZ_4K, DMA_TO_DEVICE); + dma_unmap_single(dev, SZ_8K + SZ_4K, SZ_4K, DMA_TO_DEVICE); + + /* remap 8K */ + dma_addr = dma_map_single(dev, virt, SZ_8K, DMA_TO_DEVICE); + if (dma_addr != SZ_8K) { + dma_addr_t expected = SZ_8K; + + dev_err(dev, "Unexpected dma_addr. got: %pa expected: %pa\n", + &dma_addr, &expected); + ret = -EINVAL; + goto out; + } + + /* + * now remap 4K. We should get the first 4K chunk that was skipped + * over during the previous 8K map. If we missed a TLB invalidate + * at that point this should explode. + */ + dma_addr = dma_map_single(dev, virt, SZ_4K, DMA_TO_DEVICE); + if (dma_addr != 0) { + dma_addr_t expected = 0; + + dev_err(dev, "Unexpected dma_addr. got: %pa expected: %pa\n", + &dma_addr, &expected); + ret = -EINVAL; + goto out; + } + + if (dma_map_single(dev, virt, SZ_4K, DMA_TO_DEVICE) != DMA_ERROR_CODE) { + dev_err(dev, + "dma_map_single unexpectedly after remaps (VA should have been exhausted)\n"); + ret = -EINVAL; + goto out; + } + + /* we're all full again. unmap everything. */ + for (iova = 0; iova < max; iova += SZ_8K) + dma_unmap_single(dev, (dma_addr_t)iova, SZ_8K, DMA_TO_DEVICE); + +out: + free_pages((unsigned long)virt, get_order(SZ_8K)); + return ret; +} + +struct fib_state { + unsigned long cur; + unsigned long prev; +}; + +static void fib_init(struct fib_state *f) +{ + f->cur = f->prev = 1; +} + +static unsigned long get_next_fib(struct fib_state *f) +{ + int next = f->cur + f->prev; + + f->prev = f->cur; + f->cur = next; + return next; +} + +/* + * Not actually random. Just testing the fibs (and max - the fibs). + */ +static int __rand_va_sweep(struct device *dev, struct seq_file *s, + const size_t size) +{ + u64 iova; + const u64 max = SZ_1G * 4ULL - 1; + int i, remapped, unmapped, ret = 0; + void *virt; + dma_addr_t dma_addr, dma_addr2; + struct fib_state fib; + + virt = (void *)__get_free_pages(GFP_KERNEL, get_order(size)); + if (!virt) { + if (size > SZ_8K) { + dev_err(dev, + "Failed to allocate %s of memory, which is a lot. Skipping test for this size\n", + _size_to_string(size)); + return 0; + } + return -ENOMEM; + } + + /* fill the whole 4GB space */ + for (iova = 0, i = 0; iova < max; iova += size, ++i) { + dma_addr = dma_map_single(dev, virt, size, DMA_TO_DEVICE); + if (dma_addr == DMA_ERROR_CODE) { + dev_err(dev, "Failed map on iter %d\n", i); + ret = -EINVAL; + goto out; + } + } + + /* now unmap "random" iovas */ + unmapped = 0; + fib_init(&fib); + for (iova = get_next_fib(&fib) * size; + iova < max - size; + iova = (u64)get_next_fib(&fib) * size) { + dma_addr = (dma_addr_t)(iova); + dma_addr2 = (dma_addr_t)((max + 1) - size - iova); + if (dma_addr == dma_addr2) { + WARN(1, + "%s test needs update! The random number sequence is folding in on itself and should be changed.\n", + __func__); + return -EINVAL; + } + dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE); + dma_unmap_single(dev, dma_addr2, size, DMA_TO_DEVICE); + unmapped += 2; + } + + /* and map until everything fills back up */ + for (remapped = 0; ; ++remapped) { + dma_addr = dma_map_single(dev, virt, size, DMA_TO_DEVICE); + if (dma_addr == DMA_ERROR_CODE) + break; + } + + if (unmapped != remapped) { + dev_err(dev, + "Unexpected random remap count! Unmapped %d but remapped %d\n", + unmapped, remapped); + ret = -EINVAL; + } + + for (iova = 0; iova < max; iova += size) + dma_unmap_single(dev, (dma_addr_t)iova, size, DMA_TO_DEVICE); + +out: + free_pages((unsigned long)virt, get_order(size)); + return ret; +} + +static int __check_mapping(struct device *dev, struct iommu_domain *domain, + dma_addr_t iova, phys_addr_t expected) +{ + phys_addr_t res = iommu_iova_to_phys_hard(domain, iova); + phys_addr_t res2 = iommu_iova_to_phys(domain, iova); + + WARN(res != res2, "hard/soft iova_to_phys fns don't agree..."); + + if (res != expected) { + dev_err_ratelimited(dev, + "Bad translation for %pa! Expected: %pa Got: %pa\n", + &iova, &expected, &res); + return -EINVAL; + } + + return 0; +} + +static int __full_va_sweep(struct device *dev, struct seq_file *s, + const size_t size, struct iommu_domain *domain) +{ + u64 iova; + dma_addr_t dma_addr; + void *virt; + phys_addr_t phys; + const u64 max = SZ_1G * 4ULL - 1; + int ret = 0, i; + + virt = (void *)__get_free_pages(GFP_KERNEL, get_order(size)); + if (!virt) { + if (size > SZ_8K) { + dev_err(dev, + "Failed to allocate %s of memory, which is a lot. Skipping test for this size\n", + _size_to_string(size)); + return 0; + } + return -ENOMEM; + } + phys = virt_to_phys(virt); + + for (iova = 0, i = 0; iova < max; iova += size, ++i) { + unsigned long expected = iova; + + dma_addr = dma_map_single(dev, virt, size, DMA_TO_DEVICE); + if (dma_addr != expected) { + dev_err_ratelimited(dev, + "Unexpected iova on iter %d (expected: 0x%lx got: 0x%lx)\n", + i, expected, + (unsigned long)dma_addr); + ret = -EINVAL; + goto out; + } + } + + if (domain) { + /* check every mapping from 0..6M */ + for (iova = 0, i = 0; iova < SZ_2M * 3; iova += size, ++i) { + phys_addr_t expected = phys; + + if (__check_mapping(dev, domain, iova, expected)) { + dev_err(dev, "iter: %d\n", i); + ret = -EINVAL; + goto out; + } + } + /* and from 4G..4G-6M */ + for (iova = 0, i = 0; iova < SZ_2M * 3; iova += size, ++i) { + phys_addr_t expected = phys; + unsigned long theiova = ((SZ_1G * 4ULL) - size) - iova; + + if (__check_mapping(dev, domain, theiova, expected)) { + dev_err(dev, "iter: %d\n", i); + ret = -EINVAL; + goto out; + } + } + } + + /* at this point, our VA space should be full */ + dma_addr = dma_map_single(dev, virt, size, DMA_TO_DEVICE); + if (dma_addr != DMA_ERROR_CODE) { + dev_err_ratelimited(dev, + "dma_map_single succeeded when it should have failed. Got iova: 0x%lx\n", + (unsigned long)dma_addr); + ret = -EINVAL; + } + +out: + for (iova = 0; iova < max; iova += size) + dma_unmap_single(dev, (dma_addr_t)iova, size, DMA_TO_DEVICE); + + free_pages((unsigned long)virt, get_order(size)); + return ret; +} + +#define ds_printf(d, s, fmt, ...) ({ \ + dev_err(d, fmt, ##__VA_ARGS__); \ + seq_printf(s, fmt, ##__VA_ARGS__); \ + }) + +static int __functional_dma_api_va_test(struct device *dev, struct seq_file *s, + struct iommu_domain *domain, void *priv) +{ + int i, j, ret = 0; + size_t *sz, *sizes = priv; + + for (j = 0; j < 1; ++j) { + for (sz = sizes; *sz; ++sz) { + for (i = 0; i < 2; ++i) { + ds_printf(dev, s, "Full VA sweep @%s %d", + _size_to_string(*sz), i); + if (__full_va_sweep(dev, s, *sz, domain)) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + ds_printf(dev, s, " -> SUCCEEDED\n"); + } + } + } + } + + ds_printf(dev, s, "bonus map:"); + if (__full_va_sweep(dev, s, SZ_4K, domain)) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + ds_printf(dev, s, " -> SUCCEEDED\n"); + } + + for (sz = sizes; *sz; ++sz) { + for (i = 0; i < 2; ++i) { + ds_printf(dev, s, "Rand VA sweep @%s %d", + _size_to_string(*sz), i); + if (__rand_va_sweep(dev, s, *sz)) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + ds_printf(dev, s, " -> SUCCEEDED\n"); + } + } + } + + ds_printf(dev, s, "TLB stress sweep"); + if (__tlb_stress_sweep(dev, s)) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + ds_printf(dev, s, " -> SUCCEEDED\n"); + } + + ds_printf(dev, s, "second bonus map:"); + if (__full_va_sweep(dev, s, SZ_4K, domain)) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + ds_printf(dev, s, " -> SUCCEEDED\n"); + } + + return ret; +} + +static int __functional_dma_api_alloc_test(struct device *dev, + struct seq_file *s, + struct iommu_domain *domain, + void *ignored) +{ + size_t size = SZ_1K * 742; + int ret = 0; + u8 *data; + dma_addr_t iova; + + /* Make sure we can allocate and use a buffer */ + ds_printf(dev, s, "Allocating coherent buffer"); + data = dma_alloc_coherent(dev, size, &iova, GFP_KERNEL); + if (!data) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + } else { + int i; + + ds_printf(dev, s, " -> SUCCEEDED\n"); + ds_printf(dev, s, "Using coherent buffer"); + for (i = 0; i < 742; ++i) { + int ind = SZ_1K * i; + u8 *p = data + ind; + u8 val = i % 255; + + memset(data, 0xa5, size); + *p = val; + (*p)++; + if ((*p) != val + 1) { + ds_printf(dev, s, + " -> FAILED on iter %d since %d != %d\n", + i, *p, val + 1); + ret = -EINVAL; + } + } + if (!ret) + ds_printf(dev, s, " -> SUCCEEDED\n"); + dma_free_coherent(dev, size, data, iova); + } + + return ret; +} + +static int __functional_dma_api_basic_test(struct device *dev, + struct seq_file *s, + struct iommu_domain *domain, + void *ignored) +{ + size_t size = 1518; + int i, j, ret = 0; + u8 *data; + dma_addr_t iova; + phys_addr_t pa, pa2; + + ds_printf(dev, s, "Basic DMA API test"); + /* Make sure we can allocate and use a buffer */ + for (i = 0; i < 1000; ++i) { + data = kmalloc(size, GFP_KERNEL); + if (!data) { + ds_printf(dev, s, " -> FAILED\n"); + ret = -EINVAL; + goto out; + } + memset(data, 0xa5, size); + iova = dma_map_single(dev, data, size, DMA_TO_DEVICE); + pa = iommu_iova_to_phys(domain, iova); + pa2 = iommu_iova_to_phys_hard(domain, iova); + if (pa != pa2) { + dev_err(dev, + "iova_to_phys doesn't match iova_to_phys_hard: %pa != %pa\n", + &pa, &pa2); + ret = -EINVAL; + goto out; + } + pa2 = virt_to_phys(data); + if (pa != pa2) { + dev_err(dev, + "iova_to_phys doesn't match virt_to_phys: %pa != %pa\n", + &pa, &pa2); + ret = -EINVAL; + goto out; + } + dma_unmap_single(dev, iova, size, DMA_TO_DEVICE); + for (j = 0; j < size; ++j) { + if (data[j] != 0xa5) { + dev_err(dev, "data[%d] != 0xa5\n", data[j]); + ret = -EINVAL; + goto out; + } + } + kfree(data); + } + +out: + if (ret) + ds_printf(dev, s, " -> FAILED\n"); + else + ds_printf(dev, s, " -> SUCCEEDED\n"); + + return ret; +} + +/* Creates a fresh fast mapping and applies @fn to it */ +static int __apply_to_new_mapping(struct seq_file *s, + int (*fn)(struct device *dev, + struct seq_file *s, + struct iommu_domain *domain, + void *priv), + void *priv) +{ + struct dma_iommu_mapping *mapping; + struct iommu_debug_device *ddev = s->private; + struct device *dev = ddev->dev; + int ret = -EINVAL, fast = 1; + phys_addr_t pt_phys; + + mapping = arm_iommu_create_mapping(&platform_bus_type, 0, + (SZ_1G * 4ULL)); + if (!mapping) + goto out; + + if (iommu_domain_set_attr(mapping->domain, DOMAIN_ATTR_FAST, &fast)) { + seq_puts(s, "iommu_domain_set_attr failed\n"); + goto out_release_mapping; + } + + if (arm_iommu_attach_device(dev, mapping)) + goto out_release_mapping; + + if (iommu_domain_get_attr(mapping->domain, DOMAIN_ATTR_PT_BASE_ADDR, + &pt_phys)) { + ds_printf(dev, s, "Couldn't get page table base address\n"); + goto out_release_mapping; + } + + dev_err(dev, "testing with pgtables at %pa\n", &pt_phys); + if (iommu_enable_config_clocks(mapping->domain)) { + ds_printf(dev, s, "Couldn't enable clocks\n"); + goto out_release_mapping; + } + ret = fn(dev, s, mapping->domain, priv); + iommu_disable_config_clocks(mapping->domain); + + arm_iommu_detach_device(dev); +out_release_mapping: + arm_iommu_release_mapping(mapping); +out: + seq_printf(s, "%s\n", ret ? "FAIL" : "SUCCESS"); + return 0; +} + +static int iommu_debug_functional_fast_dma_api_show(struct seq_file *s, + void *ignored) +{ + size_t sizes[] = {SZ_4K, SZ_8K, SZ_16K, SZ_64K, 0}; + int ret = 0; + + ret |= __apply_to_new_mapping(s, __functional_dma_api_alloc_test, NULL); + ret |= __apply_to_new_mapping(s, __functional_dma_api_basic_test, NULL); + ret |= __apply_to_new_mapping(s, __functional_dma_api_va_test, sizes); + return ret; +} + +static int iommu_debug_functional_fast_dma_api_open(struct inode *inode, + struct file *file) +{ + return single_open(file, iommu_debug_functional_fast_dma_api_show, + inode->i_private); +} + +static const struct file_operations iommu_debug_functional_fast_dma_api_fops = { + .open = iommu_debug_functional_fast_dma_api_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int iommu_debug_functional_arm_dma_api_show(struct seq_file *s, + void *ignored) +{ + struct dma_iommu_mapping *mapping; + struct iommu_debug_device *ddev = s->private; + struct device *dev = ddev->dev; + size_t sizes[] = {SZ_4K, SZ_64K, SZ_2M, SZ_1M * 12, 0}; + int ret = -EINVAL; + + /* Make the size equal to MAX_ULONG */ + mapping = arm_iommu_create_mapping(&platform_bus_type, 0, + (SZ_1G * 4ULL - 1)); + if (!mapping) + goto out; + + if (arm_iommu_attach_device(dev, mapping)) + goto out_release_mapping; + + ret = __functional_dma_api_alloc_test(dev, s, mapping->domain, sizes); + ret |= __functional_dma_api_basic_test(dev, s, mapping->domain, sizes); + + arm_iommu_detach_device(dev); +out_release_mapping: + arm_iommu_release_mapping(mapping); +out: + seq_printf(s, "%s\n", ret ? "FAIL" : "SUCCESS"); + return 0; +} + +static int iommu_debug_functional_arm_dma_api_open(struct inode *inode, + struct file *file) +{ + return single_open(file, iommu_debug_functional_arm_dma_api_show, + inode->i_private); +} + +static const struct file_operations iommu_debug_functional_arm_dma_api_fops = { + .open = iommu_debug_functional_arm_dma_api_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int iommu_debug_attach_do_attach(struct iommu_debug_device *ddev, + int val, bool is_secure) +{ + struct bus_type *bus; + + bus = msm_iommu_get_bus(ddev->dev); + if (!bus) + return -EINVAL; + + ddev->domain = iommu_domain_alloc(bus); + if (!ddev->domain) { + pr_err("Couldn't allocate domain\n"); + return -ENOMEM; + } + + val = VMID_CP_CAMERA; + if (is_secure && iommu_domain_set_attr(ddev->domain, + DOMAIN_ATTR_SECURE_VMID, + &val)) { + pr_err("Couldn't set secure vmid to %d\n", val); + goto out_domain_free; + } + + if (iommu_attach_device(ddev->domain, ddev->dev)) { + pr_err("Couldn't attach new domain to device. Is it already attached?\n"); + goto out_domain_free; + } + + return 0; + +out_domain_free: + iommu_domain_free(ddev->domain); + ddev->domain = NULL; + return -EIO; +} + +static ssize_t __iommu_debug_dma_attach_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + struct dma_iommu_mapping *dma_mapping; + ssize_t retval = -EINVAL; + int val; + + mutex_lock(&ddev->dev_lock); + if (kstrtoint_from_user(ubuf, count, 0, &val)) { + pr_err("Invalid format. Expected a hex or decimal integer"); + retval = -EFAULT; + goto out; + } + + if (val) { + if (dev->archdata.mapping) + if (dev->archdata.mapping->domain) { + pr_err("Already attached.\n"); + retval = -EINVAL; + goto out; + } + if (WARN(dev->archdata.iommu, + "Attachment tracking out of sync with device\n")) { + retval = -EINVAL; + goto out; + } + + dma_mapping = arm_iommu_create_mapping(&platform_bus_type, 0, + (SZ_1G * 4ULL)); + + if (!dma_mapping) + goto out; + + if (arm_iommu_attach_device(dev, dma_mapping)) + goto out_release_mapping; + pr_err("Attached\n"); + } else { + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(dev->archdata.mapping); + pr_err("Detached\n"); + } + mutex_unlock(&ddev->dev_lock); + retval = count; + return retval; + +out_release_mapping: + arm_iommu_release_mapping(dma_mapping); +out: + mutex_unlock(&ddev->dev_lock); + return retval; +} + +static ssize_t __iommu_debug_attach_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset, + bool is_secure) +{ + struct iommu_debug_device *ddev = file->private_data; + ssize_t retval; + int val; + + mutex_lock(&ddev->dev_lock); + if (kstrtoint_from_user(ubuf, count, 0, &val)) { + pr_err("Invalid format. Expected a hex or decimal integer"); + retval = -EFAULT; + goto out; + } + + if (val) { + if (ddev->domain) { + pr_err("Already attached.\n"); + retval = -EINVAL; + goto out; + } + if (WARN(ddev->dev->archdata.iommu, + "Attachment tracking out of sync with device\n")) { + retval = -EINVAL; + goto out; + } + if (iommu_debug_attach_do_attach(ddev, val, is_secure)) { + retval = -EIO; + goto out; + } + pr_err("Attached\n"); + } else { + if (!ddev->domain) { + pr_err("No domain. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + iommu_detach_device(ddev->domain, ddev->dev); + iommu_domain_free(ddev->domain); + ddev->domain = NULL; + pr_err("Detached\n"); + } + + retval = count; +out: + mutex_unlock(&ddev->dev_lock); + return retval; +} + +static ssize_t iommu_debug_dma_attach_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + return __iommu_debug_dma_attach_write(file, ubuf, count, offset); + +} + +static ssize_t iommu_debug_dma_attach_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + char c[2]; + size_t buflen = sizeof(c); + + if (*offset) + return 0; + + if (!dev->archdata.mapping) + c[0] = '0'; + else + c[0] = dev->archdata.mapping->domain ? '1' : '0'; + + c[1] = '\n'; + buflen = min(count, buflen); + if (copy_to_user(ubuf, &c, buflen)) { + pr_err("copy_to_user failed\n"); + return -EFAULT; + } + *offset = 1; /* non-zero means we're done */ + + return buflen; +} + +static const struct file_operations iommu_debug_dma_attach_fops = { + .open = simple_open, + .write = iommu_debug_dma_attach_write, + .read = iommu_debug_dma_attach_read, +}; + +static ssize_t iommu_debug_virt_addr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[100]; + ssize_t retval; + size_t buflen; + + if (*offset) + return 0; + + memset(buf, 0, 100); + + if (!virt_addr) + strlcpy(buf, "FAIL\n", 100); + else + snprintf(buf, 100, "0x%pK\n", virt_addr); + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; /* non-zero means we're done */ + retval = buflen; + } + + return retval; +} + +static const struct file_operations iommu_debug_virt_addr_fops = { + .open = simple_open, + .read = iommu_debug_virt_addr_read, +}; + +static ssize_t iommu_debug_attach_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + return __iommu_debug_attach_write(file, ubuf, count, offset, + false); + +} + +static ssize_t iommu_debug_attach_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + char c[2]; + size_t buflen = sizeof(c); + + if (*offset) + return 0; + + c[0] = ddev->domain ? '1' : '0'; + c[1] = '\n'; + buflen = min(count, buflen); + if (copy_to_user(ubuf, &c, buflen)) { + pr_err("copy_to_user failed\n"); + return -EFAULT; + } + *offset = 1; /* non-zero means we're done */ + + return buflen; +} + +static const struct file_operations iommu_debug_attach_fops = { + .open = simple_open, + .write = iommu_debug_attach_write, + .read = iommu_debug_attach_read, +}; + +static ssize_t iommu_debug_attach_write_secure(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + return __iommu_debug_attach_write(file, ubuf, count, offset, + true); + +} + +static const struct file_operations iommu_debug_secure_attach_fops = { + .open = simple_open, + .write = iommu_debug_attach_write_secure, + .read = iommu_debug_attach_read, +}; + +static ssize_t iommu_debug_pte_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + dma_addr_t iova; + + if (kstrtox_from_user(ubuf, count, 0, &iova)) { + pr_err("Invalid format for iova\n"); + ddev->iova = 0; + return -EINVAL; + } + + ddev->iova = iova; + pr_err("Saved iova=%pa for future PTE commands\n", &iova); + return count; +} + + +static ssize_t iommu_debug_pte_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + uint64_t pte; + char buf[100]; + ssize_t retval; + size_t buflen; + + if (kptr_restrict != 0) { + pr_err("kptr_restrict needs to be disabled.\n"); + return -EPERM; + } + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + return -EINVAL; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + if (*offset) + return 0; + + memset(buf, 0, 100); + + pte = iommu_iova_to_pte(dev->archdata.mapping->domain, + ddev->iova); + + if (!pte) + strlcpy(buf, "FAIL\n", 100); + else + snprintf(buf, 100, "pte=%016llx\n", pte); + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; /* non-zero means we're done */ + retval = buflen; + } + + return retval; +} + +static const struct file_operations iommu_debug_pte_fops = { + .open = simple_open, + .write = iommu_debug_pte_write, + .read = iommu_debug_pte_read, +}; + +static ssize_t iommu_debug_atos_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + dma_addr_t iova; + + if (kstrtox_from_user(ubuf, count, 0, &iova)) { + pr_err("Invalid format for iova\n"); + ddev->iova = 0; + return -EINVAL; + } + + ddev->iova = iova; + pr_err("Saved iova=%pa for future ATOS commands\n", &iova); + return count; +} + +static ssize_t iommu_debug_atos_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + phys_addr_t phys; + char buf[100]; + ssize_t retval; + size_t buflen; + + if (kptr_restrict != 0) { + pr_err("kptr_restrict needs to be disabled.\n"); + return -EPERM; + } + if (!ddev->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + if (*offset) + return 0; + + memset(buf, 0, 100); + + phys = iommu_iova_to_phys_hard(ddev->domain, ddev->iova); + if (!phys) { + strlcpy(buf, "FAIL\n", 100); + phys = iommu_iova_to_phys(ddev->domain, ddev->iova); + dev_err(ddev->dev, "ATOS for %pa failed. Software walk returned: %pa\n", + &ddev->iova, &phys); + } else { + snprintf(buf, 100, "%pa\n", &phys); + } + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; /* non-zero means we're done */ + retval = buflen; + } + + return retval; +} + +static const struct file_operations iommu_debug_atos_fops = { + .open = simple_open, + .write = iommu_debug_atos_write, + .read = iommu_debug_atos_read, +}; + +static ssize_t iommu_debug_dma_atos_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + phys_addr_t phys; + char buf[100]; + ssize_t retval; + size_t buflen; + + if (kptr_restrict != 0) { + pr_err("kptr_restrict needs to be disabled.\n"); + return -EPERM; + } + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + return -EINVAL; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + if (*offset) + return 0; + + memset(buf, 0, 100); + + phys = iommu_iova_to_phys_hard(dev->archdata.mapping->domain, + ddev->iova); + if (!phys) + strlcpy(buf, "FAIL\n", 100); + else + snprintf(buf, 100, "%pa\n", &phys); + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; /* non-zero means we're done */ + retval = buflen; + } + + return retval; +} + +static const struct file_operations iommu_debug_dma_atos_fops = { + .open = simple_open, + .write = iommu_debug_atos_write, + .read = iommu_debug_dma_atos_read, +}; + +static ssize_t iommu_debug_map_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *offset) +{ + ssize_t retval = -EINVAL; + int ret; + char *comma1, *comma2, *comma3; + char buf[100]; + dma_addr_t iova; + phys_addr_t phys; + size_t size; + int prot; + struct iommu_debug_device *ddev = file->private_data; + + if (count >= 100) { + pr_err("Value too large\n"); + return -EINVAL; + } + + if (!ddev->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + memset(buf, 0, 100); + + if (copy_from_user(buf, ubuf, count)) { + pr_err("Couldn't copy from user\n"); + retval = -EFAULT; + } + + comma1 = strnchr(buf, count, ','); + if (!comma1) + goto invalid_format; + + comma2 = strnchr(comma1 + 1, count, ','); + if (!comma2) + goto invalid_format; + + comma3 = strnchr(comma2 + 1, count, ','); + if (!comma3) + goto invalid_format; + + /* split up the words */ + *comma1 = *comma2 = *comma3 = '\0'; + + if (kstrtoux(buf, 0, &iova)) + goto invalid_format; + + if (kstrtoux(comma1 + 1, 0, &phys)) + goto invalid_format; + + if (kstrtosize_t(comma2 + 1, 0, &size)) + goto invalid_format; + + if (kstrtoint(comma3 + 1, 0, &prot)) + goto invalid_format; + + mutex_lock(&ddev->dev_lock); + ret = iommu_map(ddev->domain, iova, phys, size, prot); + if (ret) { + pr_err("iommu_map failed with %d\n", ret); + retval = -EIO; + goto out; + } + + retval = count; + pr_err("Mapped %pa to %pa (len=0x%zx, prot=0x%x)\n", + &iova, &phys, size, prot); +out: + mutex_unlock(&ddev->dev_lock); + return retval; + +invalid_format: + pr_err("Invalid format. Expected: iova,phys,len,prot where `prot' is the bitwise OR of IOMMU_READ, IOMMU_WRITE, etc.\n"); + return retval; +} + +static const struct file_operations iommu_debug_map_fops = { + .open = simple_open, + .write = iommu_debug_map_write, +}; + +static ssize_t iommu_debug_dma_map_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *offset) +{ + ssize_t retval = -EINVAL; + int ret; + char *comma1, *comma2; + char buf[100]; + unsigned long addr; + void *v_addr; + dma_addr_t iova; + size_t size; + unsigned int attr; + struct dma_attrs coherent_attr; + struct dma_attrs *dma_attrs = &coherent_attr; + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + + init_dma_attrs(dma_attrs); + + if (count >= 100) { + pr_err("Value too large\n"); + return -EINVAL; + } + + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + + memset(buf, 0, 100); + + if (copy_from_user(buf, ubuf, count)) { + pr_err("Couldn't copy from user\n"); + retval = -EFAULT; + goto out; + } + + comma1 = strnchr(buf, count, ','); + if (!comma1) + goto invalid_format; + + comma2 = strnchr(comma1 + 1, count, ','); + if (!comma2) + goto invalid_format; + + *comma1 = *comma2 = '\0'; + + if (kstrtoul(buf, 0, &addr)) + goto invalid_format; + v_addr = (void *)addr; + + if (kstrtosize_t(comma1 + 1, 0, &size)) + goto invalid_format; + + if (kstrtouint(comma2 + 1, 0, &attr)) + goto invalid_format; + + if (v_addr < virt_addr || v_addr > (virt_addr + SZ_1M - 1)) + goto invalid_addr; + + if (attr == 0) + dma_attrs = NULL; + else if (attr == 1) + dma_set_attr(DMA_ATTR_FORCE_COHERENT, dma_attrs); + else if (attr == 2) + dma_set_attr(DMA_ATTR_FORCE_NON_COHERENT, dma_attrs); + else + goto invalid_format; + + mutex_lock(&ddev->dev_lock); + iova = dma_map_single_attrs(dev, v_addr, size, + DMA_TO_DEVICE, dma_attrs); + + if (dma_mapping_error(dev, iova)) { + pr_err("Failed to perform dma_map_single\n"); + ret = -EINVAL; + mutex_unlock(&ddev->dev_lock); + goto out; + } + mutex_unlock(&ddev->dev_lock); + + retval = count; + pr_err("Mapped 0x%p to %pa (len=0x%zx)\n", + v_addr, &iova, size); + ddev->iova = iova; + pr_err("Saved iova=%pa for future PTE commands\n", &iova); +out: + return retval; + +invalid_format: + pr_err("Invalid format. Expected: addr,len,dma attr where 'dma attr' is\n0: normal mapping\n1: force coherent\n2: force non-cohernet\n"); + return retval; + +invalid_addr: + pr_err("Invalid addr given! Address should be within 1MB size from start addr returned by doing 'cat virt_addr'.\n"); + return retval; +} + +static ssize_t iommu_debug_dma_map_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + char buf[100]; + ssize_t retval; + size_t buflen; + dma_addr_t iova; + + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + return -EINVAL; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + if (*offset) + return 0; + + memset(buf, 0, 100); + + iova = ddev->iova; + snprintf(buf, 100, "%pa\n", &iova); + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + pr_err("Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; /* non-zero means we're done */ + retval = buflen; + } + + return retval; +} + +static const struct file_operations iommu_debug_dma_map_fops = { + .open = simple_open, + .write = iommu_debug_dma_map_write, + .read = iommu_debug_dma_map_read, +}; + +static ssize_t iommu_debug_unmap_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + ssize_t retval = 0; + char *comma1; + char buf[100]; + dma_addr_t iova; + size_t size; + size_t unmapped; + struct iommu_debug_device *ddev = file->private_data; + + if (count >= 100) { + pr_err("Value too large\n"); + return -EINVAL; + } + + if (!ddev->domain) { + pr_err("No domain. Did you already attach?\n"); + return -EINVAL; + } + + memset(buf, 0, 100); + + if (copy_from_user(buf, ubuf, count)) { + pr_err("Couldn't copy from user\n"); + retval = -EFAULT; + goto out; + } + + comma1 = strnchr(buf, count, ','); + if (!comma1) + goto invalid_format; + + /* split up the words */ + *comma1 = '\0'; + + if (kstrtoux(buf, 0, &iova)) + goto invalid_format; + + if (kstrtosize_t(comma1 + 1, 0, &size)) + goto invalid_format; + + mutex_lock(&ddev->dev_lock); + unmapped = iommu_unmap(ddev->domain, iova, size); + if (unmapped != size) { + pr_err("iommu_unmap failed. Expected to unmap: 0x%zx, unmapped: 0x%zx", + size, unmapped); + mutex_unlock(&ddev->dev_lock); + return -EIO; + } + mutex_unlock(&ddev->dev_lock); + + retval = count; + pr_err("Unmapped %pa (len=0x%zx)\n", &iova, size); +out: + return retval; + +invalid_format: + pr_err("Invalid format. Expected: iova,len\n"); + return retval; +} + +static const struct file_operations iommu_debug_unmap_fops = { + .open = simple_open, + .write = iommu_debug_unmap_write, +}; + +static ssize_t iommu_debug_dma_unmap_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + ssize_t retval = 0; + char *comma1, *comma2; + char buf[100]; + size_t size; + unsigned int attr; + dma_addr_t iova; + struct dma_attrs coherent_attr; + struct dma_attrs *dma_attrs = &coherent_attr; + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + + init_dma_attrs(dma_attrs); + + if (count >= 100) { + pr_err("Value too large\n"); + return -EINVAL; + } + + if (!dev->archdata.mapping) { + pr_err("No mapping. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + if (!dev->archdata.mapping->domain) { + pr_err("No domain. Did you already attach?\n"); + retval = -EINVAL; + goto out; + } + + memset(buf, 0, 100); + + if (copy_from_user(buf, ubuf, count)) { + pr_err("Couldn't copy from user\n"); + retval = -EFAULT; + goto out; + } + + comma1 = strnchr(buf, count, ','); + if (!comma1) + goto invalid_format; + + comma2 = strnchr(comma1 + 1, count, ','); + if (!comma2) + goto invalid_format; + + *comma1 = *comma2 = '\0'; + + if (kstrtoux(buf, 0, &iova)) + goto invalid_format; + + if (kstrtosize_t(comma1 + 1, 0, &size)) + goto invalid_format; + + if (kstrtouint(comma2 + 1, 0, &attr)) + goto invalid_format; + + if (attr == 0) + dma_attrs = NULL; + else if (attr == 1) + dma_set_attr(DMA_ATTR_FORCE_COHERENT, dma_attrs); + else if (attr == 2) + dma_set_attr(DMA_ATTR_FORCE_NON_COHERENT, dma_attrs); + else + goto invalid_format; + + mutex_lock(&ddev->dev_lock); + dma_unmap_single_attrs(dev, iova, size, DMA_TO_DEVICE, dma_attrs); + mutex_unlock(&ddev->dev_lock); + + retval = count; + pr_err("Unmapped %pa (len=0x%zx)\n", &iova, size); +out: + return retval; + +invalid_format: + pr_err("Invalid format. Expected: iova,len, dma attr\n"); + return retval; +} + +static const struct file_operations iommu_debug_dma_unmap_fops = { + .open = simple_open, + .write = iommu_debug_dma_unmap_write, +}; + +static ssize_t iommu_debug_config_clocks_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf; + struct iommu_debug_device *ddev = file->private_data; + struct device *dev = ddev->dev; + + /* we're expecting a single character plus (optionally) a newline */ + if (count > 2) { + dev_err(dev, "Invalid value\n"); + return -EINVAL; + } + + if (!ddev->domain) { + dev_err(dev, "No domain. Did you already attach?\n"); + return -EINVAL; + } + + if (copy_from_user(&buf, ubuf, 1)) { + dev_err(dev, "Couldn't copy from user\n"); + return -EFAULT; + } + + mutex_lock(&ddev->clk_lock); + switch (buf) { + case '0': + if (ddev->clk_count == 0) { + dev_err(dev, "Config clocks already disabled\n"); + break; + } + + if (--ddev->clk_count > 0) + break; + + dev_err(dev, "Disabling config clocks\n"); + iommu_disable_config_clocks(ddev->domain); + break; + case '1': + if (ddev->clk_count++ > 0) + break; + + dev_err(dev, "Enabling config clocks\n"); + if (iommu_enable_config_clocks(ddev->domain)) + dev_err(dev, "Failed!\n"); + break; + default: + dev_err(dev, "Invalid value. Should be 0 or 1.\n"); + mutex_unlock(&ddev->clk_lock); + return -EINVAL; + } + mutex_unlock(&ddev->clk_lock); + + return count; +} + +static const struct file_operations iommu_debug_config_clocks_fops = { + .open = simple_open, + .write = iommu_debug_config_clocks_write, +}; + +/* + * The following will only work for drivers that implement the generic + * device tree bindings described in + * Documentation/devicetree/bindings/iommu/iommu.txt + */ +static int snarf_iommu_devices(struct device *dev, const char *name) +{ + struct iommu_debug_device *ddev; + struct dentry *dir; + + if (IS_ERR_OR_NULL(dev)) + return -EINVAL; + + ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); + if (!ddev) + return -ENODEV; + mutex_init(&ddev->clk_lock); + mutex_init(&ddev->dev_lock); + ddev->dev = dev; + dir = debugfs_create_dir(name, debugfs_tests_dir); + if (!dir) { + pr_err("Couldn't create iommu/devices/%s debugfs dir\n", + name); + goto err; + } + + if (!debugfs_create_file("nr_iters", S_IRUSR, dir, &iters_per_op, + &iommu_debug_nr_iters_ops)) { + pr_err("Couldn't create iommu/devices/%s/nr_iters debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("virt_addr", S_IRUSR, dir, ddev, + &iommu_debug_virt_addr_fops)) { + pr_err("Couldn't create iommu/devices/%s/virt_addr debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("profiling", S_IRUSR, dir, ddev, + &iommu_debug_profiling_fops)) { + pr_err("Couldn't create iommu/devices/%s/profiling debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("secure_profiling", S_IRUSR, dir, ddev, + &iommu_debug_secure_profiling_fops)) { + pr_err("Couldn't create iommu/devices/%s/secure_profiling debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("profiling_fast", S_IRUSR, dir, ddev, + &iommu_debug_profiling_fast_fops)) { + pr_err("Couldn't create iommu/devices/%s/profiling_fast debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("profiling_fast_dma_api", S_IRUSR, dir, ddev, + &iommu_debug_profiling_fast_dma_api_fops)) { + pr_err("Couldn't create iommu/devices/%s/profiling_fast_dma_api debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("functional_fast_dma_api", S_IRUSR, dir, ddev, + &iommu_debug_functional_fast_dma_api_fops)) { + pr_err("Couldn't create iommu/devices/%s/functional_fast_dma_api debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("functional_arm_dma_api", S_IRUSR, dir, ddev, + &iommu_debug_functional_arm_dma_api_fops)) { + pr_err("Couldn't create iommu/devices/%s/functional_arm_dma_api debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("dma_attach", S_IRUSR, dir, ddev, + &iommu_debug_dma_attach_fops)) { + pr_err("Couldn't create iommu/devices/%s/dma_attach debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("attach", S_IRUSR, dir, ddev, + &iommu_debug_attach_fops)) { + pr_err("Couldn't create iommu/devices/%s/attach debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("secure_attach", S_IRUSR, dir, ddev, + &iommu_debug_secure_attach_fops)) { + pr_err("Couldn't create iommu/devices/%s/secure_attach debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("atos", S_IWUSR, dir, ddev, + &iommu_debug_atos_fops)) { + pr_err("Couldn't create iommu/devices/%s/atos debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("dma_atos", S_IWUSR, dir, ddev, + &iommu_debug_dma_atos_fops)) { + pr_err("Couldn't create iommu/devices/%s/dma_atos debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("map", S_IWUSR, dir, ddev, + &iommu_debug_map_fops)) { + pr_err("Couldn't create iommu/devices/%s/map debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("dma_map", S_IWUSR, dir, ddev, + &iommu_debug_dma_map_fops)) { + pr_err("Couldn't create iommu/devices/%s/dma_map debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("unmap", S_IWUSR, dir, ddev, + &iommu_debug_unmap_fops)) { + pr_err("Couldn't create iommu/devices/%s/unmap debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("dma_unmap", S_IWUSR, dir, ddev, + &iommu_debug_dma_unmap_fops)) { + pr_err("Couldn't create iommu/devices/%s/dma_unmap debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("pte", S_IWUSR, dir, ddev, + &iommu_debug_pte_fops)) { + pr_err("Couldn't create iommu/devices/%s/pte debugfs file\n", + name); + goto err_rmdir; + } + + if (!debugfs_create_file("config_clocks", S_IWUSR, dir, ddev, + &iommu_debug_config_clocks_fops)) { + pr_err("Couldn't create iommu/devices/%s/config_clocks debugfs file\n", + name); + goto err_rmdir; + } + + list_add(&ddev->list, &iommu_debug_devices); + return 0; + +err_rmdir: + debugfs_remove_recursive(dir); +err: + kfree(ddev); + return 0; +} + +static int pass_iommu_devices(struct device *dev, void *ignored) +{ + if (!of_device_is_compatible(dev->of_node, "iommu-debug-test")) + return 0; + + if (!of_find_property(dev->of_node, "iommus", NULL)) + return 0; + + return snarf_iommu_devices(dev, dev_name(dev)); +} + +static int iommu_debug_populate_devices(void) +{ + int ret; + struct device_node *np; + const char *cb_name; + + for_each_compatible_node(np, NULL, "qcom,msm-smmu-v2-ctx") { + if (!of_device_is_compatible(np, "iommu-debug-test")) + continue; + + ret = of_property_read_string(np, "label", &cb_name); + if (ret) + return ret; + + ret = snarf_iommu_devices(msm_iommu_get_ctx(cb_name), cb_name); + if (ret) + return ret; + } + + return bus_for_each_dev(&platform_bus_type, NULL, NULL, + pass_iommu_devices); +} + +static int iommu_debug_init_tests(void) +{ + debugfs_tests_dir = debugfs_create_dir("tests", + iommu_debugfs_top); + if (!debugfs_tests_dir) { + pr_err("Couldn't create iommu/tests debugfs directory\n"); + return -ENODEV; + } + + virt_addr = kzalloc(SZ_1M, GFP_KERNEL); + + if (!virt_addr) + return -ENOMEM; + + return iommu_debug_populate_devices(); +} + +static void iommu_debug_destroy_tests(void) +{ + debugfs_remove_recursive(debugfs_tests_dir); +} +#else +static inline int iommu_debug_init_tests(void) { return 0; } +static inline void iommu_debug_destroy_tests(void) { } +#endif + +/* + * This isn't really a "driver", we just need something in the device tree + * so that our tests can run without any client drivers, and our tests rely + * on parsing the device tree for nodes with the `iommus' property. + */ +static int iommu_debug_pass(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id iommu_debug_of_match[] = { + { .compatible = "iommu-debug-test" }, + { }, +}; + +static struct platform_driver iommu_debug_driver = { + .probe = iommu_debug_pass, + .remove = iommu_debug_pass, + .driver = { + .name = "iommu-debug", + .of_match_table = iommu_debug_of_match, + }, +}; + +static int iommu_debug_init(void) +{ + if (iommu_debug_init_tests()) + return -ENODEV; + + return platform_driver_register(&iommu_debug_driver); +} + +static void iommu_debug_exit(void) +{ + platform_driver_unregister(&iommu_debug_driver); + iommu_debug_destroy_tests(); +} + +module_init(iommu_debug_init); +module_exit(iommu_debug_exit); diff --git a/drivers/iommu/iommu-debug.h b/drivers/iommu/iommu-debug.h new file mode 100644 index 000000000000..91c418d9e37f --- /dev/null +++ b/drivers/iommu/iommu-debug.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef IOMMU_DEBUG_H +#define IOMMU_DEBUG_H + +#ifdef CONFIG_IOMMU_DEBUG_TRACKING + +void iommu_debug_attach_device(struct iommu_domain *domain, struct device *dev); +void iommu_debug_domain_remove(struct iommu_domain *domain); + +#else /* !CONFIG_IOMMU_DEBUG_TRACKING */ + +static inline void iommu_debug_attach_device(struct iommu_domain *domain, + struct device *dev) +{ +} + +static inline void iommu_debug_domain_remove(struct iommu_domain *domain) +{ +} + +#endif /* CONFIG_IOMMU_DEBUG_TRACKING */ + +#endif /* IOMMU_DEBUG_H */ diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index a1e7a73930fa..413c038b13ba 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -31,8 +31,11 @@ #include <linux/err.h> #include <linux/pci.h> #include <linux/bitops.h> +#include <linux/debugfs.h> #include <trace/events/iommu.h> +#include "iommu-debug.h" + static struct kset *iommu_group_kset; static struct ida iommu_group_ida; static struct mutex iommu_group_mutex; @@ -1035,6 +1038,8 @@ EXPORT_SYMBOL_GPL(bus_set_iommu); bool iommu_present(struct bus_type *bus) { + if (!bus) + return false; return bus->iommu_ops != NULL; } EXPORT_SYMBOL_GPL(iommu_present); @@ -1071,6 +1076,45 @@ void iommu_set_fault_handler(struct iommu_domain *domain, } EXPORT_SYMBOL_GPL(iommu_set_fault_handler); +/** + * iommu_trigger_fault() - trigger an IOMMU fault + * @domain: iommu domain + * + * Triggers a fault on the device to which this domain is attached. + * + * This function should only be used for debugging purposes, for obvious + * reasons. + */ +void iommu_trigger_fault(struct iommu_domain *domain, unsigned long flags) +{ + if (domain->ops->trigger_fault) + domain->ops->trigger_fault(domain, flags); +} + +/** + * iommu_reg_read() - read an IOMMU register + * + * Reads the IOMMU register at the given offset. + */ +unsigned long iommu_reg_read(struct iommu_domain *domain, unsigned long offset) +{ + if (domain->ops->reg_read) + return domain->ops->reg_read(domain, offset); + return 0; +} + +/** + * iommu_reg_write() - write an IOMMU register + * + * Writes the given value to the IOMMU register at the given offset. + */ +void iommu_reg_write(struct iommu_domain *domain, unsigned long offset, + unsigned long val) +{ + if (domain->ops->reg_write) + domain->ops->reg_write(domain, offset, val); +} + static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus, unsigned type) { @@ -1097,6 +1141,7 @@ EXPORT_SYMBOL_GPL(iommu_domain_alloc); void iommu_domain_free(struct iommu_domain *domain) { + iommu_debug_domain_remove(domain); domain->ops->domain_free(domain); } EXPORT_SYMBOL_GPL(iommu_domain_free); @@ -1109,8 +1154,10 @@ static int __iommu_attach_device(struct iommu_domain *domain, return -ENODEV; ret = domain->ops->attach_dev(domain, dev); - if (!ret) + if (!ret) { trace_attach_device_to_domain(dev); + iommu_debug_attach_device(domain, dev); + } return ret; } @@ -1289,8 +1336,40 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) } EXPORT_SYMBOL_GPL(iommu_iova_to_phys); -static size_t iommu_pgsize(struct iommu_domain *domain, - unsigned long addr_merge, size_t size) +phys_addr_t iommu_iova_to_phys_hard(struct iommu_domain *domain, + dma_addr_t iova) +{ + if (unlikely(domain->ops->iova_to_phys_hard == NULL)) + return 0; + + return domain->ops->iova_to_phys_hard(domain, iova); +} + +uint64_t iommu_iova_to_pte(struct iommu_domain *domain, + dma_addr_t iova) +{ + if (unlikely(domain->ops->iova_to_pte == NULL)) + return 0; + + return domain->ops->iova_to_pte(domain, iova); +} + +bool iommu_is_iova_coherent(struct iommu_domain *domain, dma_addr_t iova) +{ + if (unlikely(domain->ops->is_iova_coherent == NULL)) + return 0; + + return domain->ops->is_iova_coherent(domain, iova); +} +static unsigned long iommu_get_pgsize_bitmap(struct iommu_domain *domain) +{ + if (domain->ops->get_pgsize_bitmap) + return domain->ops->get_pgsize_bitmap(domain); + return domain->ops->pgsize_bitmap; +} + +size_t iommu_pgsize(unsigned long pgsize_bitmap, + unsigned long addr_merge, size_t size) { unsigned int pgsize_idx; size_t pgsize; @@ -1309,10 +1388,14 @@ static size_t iommu_pgsize(struct iommu_domain *domain, pgsize = (1UL << (pgsize_idx + 1)) - 1; /* throw away page sizes not supported by the hardware */ - pgsize &= domain->ops->pgsize_bitmap; + pgsize &= pgsize_bitmap; /* make sure we're still sane */ - BUG_ON(!pgsize); + if (!pgsize) { + pr_err("invalid pgsize/addr/size! 0x%lx 0x%lx 0x%zx\n", + pgsize_bitmap, addr_merge, size); + BUG(); + } /* pick the biggest page */ pgsize_idx = __fls(pgsize); @@ -1324,20 +1407,25 @@ static size_t iommu_pgsize(struct iommu_domain *domain, int iommu_map(struct iommu_domain *domain, unsigned long iova, phys_addr_t paddr, size_t size, int prot) { - unsigned long orig_iova = iova; + unsigned long orig_iova = iova, pgsize_bitmap; unsigned int min_pagesz; size_t orig_size = size; int ret = 0; + trace_map_start(iova, paddr, size); if (unlikely(domain->ops->map == NULL || - domain->ops->pgsize_bitmap == 0UL)) + (domain->ops->pgsize_bitmap == 0UL && + !domain->ops->get_pgsize_bitmap))) { + trace_map_end(iova, paddr, size); return -ENODEV; + } if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING))) return -EINVAL; + pgsize_bitmap = iommu_get_pgsize_bitmap(domain); /* find out the minimum page size supported */ - min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); + min_pagesz = 1 << __ffs(pgsize_bitmap); /* * both the virtual address and the physical one, as well as @@ -1347,13 +1435,14 @@ int iommu_map(struct iommu_domain *domain, unsigned long iova, if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) { pr_err("unaligned: iova 0x%lx pa %pa size 0x%zx min_pagesz 0x%x\n", iova, &paddr, size, min_pagesz); + trace_map_end(iova, paddr, size); return -EINVAL; } pr_debug("map: iova 0x%lx pa %pa size 0x%zx\n", iova, &paddr, size); while (size) { - size_t pgsize = iommu_pgsize(domain, iova | paddr, size); + size_t pgsize = iommu_pgsize(pgsize_bitmap, iova | paddr, size); pr_debug("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n", iova, &paddr, pgsize); @@ -1373,6 +1462,7 @@ int iommu_map(struct iommu_domain *domain, unsigned long iova, else trace_map(orig_iova, paddr, orig_size); + trace_map_end(iova, paddr, size); return ret; } EXPORT_SYMBOL_GPL(iommu_map); @@ -1383,15 +1473,21 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) unsigned int min_pagesz; unsigned long orig_iova = iova; + trace_unmap_start(iova, 0, size); if (unlikely(domain->ops->unmap == NULL || - domain->ops->pgsize_bitmap == 0UL)) + (domain->ops->pgsize_bitmap == 0UL && + !domain->ops->get_pgsize_bitmap))) { + trace_unmap_end(iova, 0, size); return -ENODEV; + } - if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING))) + if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING))) { + trace_unmap_end(iova, 0, size); return -EINVAL; + } /* find out the minimum page size supported */ - min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); + min_pagesz = 1 << __ffs(iommu_get_pgsize_bitmap(domain)); /* * The virtual address, as well as the size of the mapping, must be @@ -1401,6 +1497,7 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) if (!IS_ALIGNED(iova | size, min_pagesz)) { pr_err("unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n", iova, size, min_pagesz); + trace_unmap_end(iova, 0, size); return -EINVAL; } @@ -1411,9 +1508,9 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) * or we hit an area that isn't mapped. */ while (unmapped < size) { - size_t pgsize = iommu_pgsize(domain, iova, size - unmapped); + size_t left = size - unmapped; - unmapped_page = domain->ops->unmap(domain, iova, pgsize); + unmapped_page = domain->ops->unmap(domain, iova, left); if (!unmapped_page) break; @@ -1425,6 +1522,7 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) } trace_unmap(orig_iova, size, unmapped); + trace_unmap_end(orig_iova, 0, size); return unmapped; } EXPORT_SYMBOL_GPL(iommu_unmap); @@ -1436,11 +1534,14 @@ size_t default_iommu_map_sg(struct iommu_domain *domain, unsigned long iova, size_t mapped = 0; unsigned int i, min_pagesz; int ret; + unsigned long pgsize_bitmap; - if (unlikely(domain->ops->pgsize_bitmap == 0UL)) + if (unlikely(domain->ops->pgsize_bitmap == 0UL && + !domain->ops->get_pgsize_bitmap)) return 0; - min_pagesz = 1 << __ffs(domain->ops->pgsize_bitmap); + pgsize_bitmap = iommu_get_pgsize_bitmap(domain); + min_pagesz = 1 << __ffs(pgsize_bitmap); for_each_sg(sg, s, nents, i) { phys_addr_t phys = page_to_phys(sg_page(s)) + s->offset; @@ -1472,6 +1573,20 @@ out_err: } EXPORT_SYMBOL_GPL(default_iommu_map_sg); +/* DEPRECATED */ +int iommu_map_range(struct iommu_domain *domain, unsigned int iova, + struct scatterlist *sg, unsigned int len, int opt) +{ + return -ENODEV; +} + +/* DEPRECATED */ +int iommu_unmap_range(struct iommu_domain *domain, unsigned int iova, + unsigned int len) +{ + return -ENODEV; +} + int iommu_domain_window_enable(struct iommu_domain *domain, u32 wnd_nr, phys_addr_t paddr, u64 size, int prot) { @@ -1492,6 +1607,8 @@ void iommu_domain_window_disable(struct iommu_domain *domain, u32 wnd_nr) } EXPORT_SYMBOL_GPL(iommu_domain_window_disable); +struct dentry *iommu_debugfs_top; + static int __init iommu_init(void) { iommu_group_kset = kset_create_and_add("iommu_groups", @@ -1501,6 +1618,12 @@ static int __init iommu_init(void) BUG_ON(!iommu_group_kset); + iommu_debugfs_top = debugfs_create_dir("iommu", NULL); + if (!iommu_debugfs_top) { + pr_err("Couldn't create iommu debugfs directory\n"); + return -ENODEV; + } + return 0; } core_initcall(iommu_init); @@ -1521,7 +1644,7 @@ int iommu_domain_get_attr(struct iommu_domain *domain, break; case DOMAIN_ATTR_PAGING: paging = data; - *paging = (domain->ops->pgsize_bitmap != 0UL); + *paging = (iommu_get_pgsize_bitmap(domain) != 0UL); break; case DOMAIN_ATTR_WINDOWS: count = data; @@ -1570,6 +1693,14 @@ int iommu_domain_set_attr(struct iommu_domain *domain, } EXPORT_SYMBOL_GPL(iommu_domain_set_attr); +int iommu_dma_supported(struct iommu_domain *domain, struct device *dev, + u64 mask) +{ + if (domain->ops->dma_supported) + return domain->ops->dma_supported(domain, dev, mask); + return 0; +} + void iommu_get_dm_regions(struct device *dev, struct list_head *list) { const struct iommu_ops *ops = dev->bus->iommu_ops; diff --git a/drivers/iommu/msm_dma_iommu_mapping.c b/drivers/iommu/msm_dma_iommu_mapping.c new file mode 100644 index 000000000000..25fe36ab6339 --- /dev/null +++ b/drivers/iommu/msm_dma_iommu_mapping.c @@ -0,0 +1,423 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/slab.h> +#include <linux/rbtree.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <asm/barrier.h> + +#include <linux/msm_dma_iommu_mapping.h> + +/** + * struct msm_iommu_map - represents a mapping of an ion buffer to an iommu + * @lnode - list node to exist in the buffer's list of iommu mappings + * @dev - Device this is mapped to. Used as key + * @sgl - The scatterlist for this mapping + * @nents - Number of entries in sgl + * @dir - The direction for the unmap. + * @meta - Backpointer to the meta this guy belongs to. + * @ref - for reference counting this mapping + * + * Represents a mapping of one dma_buf buffer to a particular device + * and address range. There may exist other mappings of this buffer in + * different devices. All mappings will have the same cacheability and security. + */ +struct msm_iommu_map { + struct list_head lnode; + struct rb_node node; + struct device *dev; + struct scatterlist sgl; + unsigned int nents; + enum dma_data_direction dir; + struct msm_iommu_meta *meta; + struct kref ref; +}; + +struct msm_iommu_meta { + struct rb_node node; + struct list_head iommu_maps; + struct kref ref; + struct mutex lock; + void *buffer; +}; + +static struct rb_root iommu_root; +static DEFINE_MUTEX(msm_iommu_map_mutex); + +static void msm_iommu_meta_add(struct msm_iommu_meta *meta) +{ + struct rb_root *root = &iommu_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct msm_iommu_meta *entry; + + while (*p) { + parent = *p; + entry = rb_entry(parent, struct msm_iommu_meta, node); + + if (meta->buffer < entry->buffer) { + p = &(*p)->rb_left; + } else if (meta->buffer > entry->buffer) { + p = &(*p)->rb_right; + } else { + pr_err("%s: dma_buf %p already exists\n", __func__, + entry->buffer); + BUG(); + } + } + + rb_link_node(&meta->node, parent, p); + rb_insert_color(&meta->node, root); +} + +static struct msm_iommu_meta *msm_iommu_meta_lookup(void *buffer) +{ + struct rb_root *root = &iommu_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct msm_iommu_meta *entry = NULL; + + while (*p) { + parent = *p; + entry = rb_entry(parent, struct msm_iommu_meta, node); + + if (buffer < entry->buffer) + p = &(*p)->rb_left; + else if (buffer > entry->buffer) + p = &(*p)->rb_right; + else + return entry; + } + + return NULL; +} + +static void msm_iommu_add(struct msm_iommu_meta *meta, + struct msm_iommu_map *iommu) +{ + struct msm_iommu_map *entry; + + list_for_each_entry(entry, &meta->iommu_maps, lnode) { + if (entry->dev == iommu->dev) { + pr_err("%s: dma_buf %p already has mapping to device %p\n", + __func__, meta->buffer, iommu->dev); + BUG(); + } + } + INIT_LIST_HEAD(&iommu->lnode); + list_add(&iommu->lnode, &meta->iommu_maps); +} + + +static struct msm_iommu_map *msm_iommu_lookup(struct msm_iommu_meta *meta, + struct device *dev) +{ + struct msm_iommu_map *entry; + + list_for_each_entry(entry, &meta->iommu_maps, lnode) { + if (entry->dev == dev) + return entry; + } + + return NULL; +} + +static struct msm_iommu_meta *msm_iommu_meta_create(struct dma_buf *dma_buf) +{ + struct msm_iommu_meta *meta; + + meta = kzalloc(sizeof(*meta), GFP_KERNEL); + + if (!meta) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&meta->iommu_maps); + meta->buffer = dma_buf->priv; + kref_init(&meta->ref); + mutex_init(&meta->lock); + msm_iommu_meta_add(meta); + + return meta; +} + +static void msm_iommu_meta_put(struct msm_iommu_meta *meta); + +static inline int __msm_dma_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, + struct dma_buf *dma_buf, + struct dma_attrs *attrs) +{ + struct msm_iommu_map *iommu_map; + struct msm_iommu_meta *iommu_meta = NULL; + int ret = 0; + bool extra_meta_ref_taken = false; + int late_unmap = !dma_get_attr(DMA_ATTR_NO_DELAYED_UNMAP, attrs); + + mutex_lock(&msm_iommu_map_mutex); + iommu_meta = msm_iommu_meta_lookup(dma_buf->priv); + + if (!iommu_meta) { + iommu_meta = msm_iommu_meta_create(dma_buf); + + if (IS_ERR(iommu_meta)) { + mutex_unlock(&msm_iommu_map_mutex); + ret = PTR_ERR(iommu_meta); + goto out; + } + if (late_unmap) { + kref_get(&iommu_meta->ref); + extra_meta_ref_taken = true; + } + } else { + kref_get(&iommu_meta->ref); + } + + mutex_unlock(&msm_iommu_map_mutex); + + mutex_lock(&iommu_meta->lock); + iommu_map = msm_iommu_lookup(iommu_meta, dev); + if (!iommu_map) { + iommu_map = kmalloc(sizeof(*iommu_map), GFP_ATOMIC); + + if (!iommu_map) { + ret = -ENOMEM; + goto out_unlock; + } + + ret = dma_map_sg_attrs(dev, sg, nents, dir, attrs); + if (ret != nents) { + kfree(iommu_map); + goto out_unlock; + } + + kref_init(&iommu_map->ref); + if (late_unmap) + kref_get(&iommu_map->ref); + iommu_map->meta = iommu_meta; + iommu_map->sgl.dma_address = sg->dma_address; + iommu_map->sgl.dma_length = sg->dma_length; + iommu_map->dev = dev; + msm_iommu_add(iommu_meta, iommu_map); + + } else { + sg->dma_address = iommu_map->sgl.dma_address; + sg->dma_length = iommu_map->sgl.dma_length; + + kref_get(&iommu_map->ref); + if (is_device_dma_coherent(dev)) + /* + * Ensure all outstanding changes for coherent + * buffers are applied to the cache before any + * DMA occurs. + */ + dmb(ish); + ret = nents; + } + mutex_unlock(&iommu_meta->lock); + return ret; + +out_unlock: + mutex_unlock(&iommu_meta->lock); +out: + if (!IS_ERR(iommu_meta)) { + if (extra_meta_ref_taken) + msm_iommu_meta_put(iommu_meta); + msm_iommu_meta_put(iommu_meta); + } + return ret; + +} + +/* + * We are not taking a reference to the dma_buf here. It is expected that + * clients hold reference to the dma_buf until they are done with mapping and + * unmapping. + */ +int msm_dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, struct dma_buf *dma_buf, + struct dma_attrs *attrs) +{ + int ret; + + if (IS_ERR_OR_NULL(dev)) { + pr_err("%s: dev pointer is invalid\n", __func__); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(sg)) { + pr_err("%s: sg table pointer is invalid\n", __func__); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(dma_buf)) { + pr_err("%s: dma_buf pointer is invalid\n", __func__); + return -EINVAL; + } + + ret = __msm_dma_map_sg(dev, sg, nents, dir, dma_buf, attrs); + + return ret; +} +EXPORT_SYMBOL(msm_dma_map_sg_attrs); + +static void msm_iommu_meta_destroy(struct kref *kref) +{ + struct msm_iommu_meta *meta = container_of(kref, struct msm_iommu_meta, + ref); + + if (!list_empty(&meta->iommu_maps)) { + WARN(1, "%s: DMA Buffer %p being destroyed with outstanding iommu mappins!\n", __func__, + meta->buffer); + } + rb_erase(&meta->node, &iommu_root); + kfree(meta); +} + +static void msm_iommu_meta_put(struct msm_iommu_meta *meta) +{ + /* + * Need to lock here to prevent race against map/unmap + */ + mutex_lock(&msm_iommu_map_mutex); + kref_put(&meta->ref, msm_iommu_meta_destroy); + mutex_unlock(&msm_iommu_map_mutex); +} + +static void msm_iommu_map_release(struct kref *kref) +{ + struct msm_iommu_map *map = container_of(kref, struct msm_iommu_map, + ref); + + list_del(&map->lnode); + dma_unmap_sg(map->dev, &map->sgl, map->nents, map->dir); + kfree(map); +} + +void msm_dma_unmap_sg(struct device *dev, struct scatterlist *sgl, int nents, + enum dma_data_direction dir, struct dma_buf *dma_buf) +{ + struct msm_iommu_map *iommu_map; + struct msm_iommu_meta *meta; + + mutex_lock(&msm_iommu_map_mutex); + meta = msm_iommu_meta_lookup(dma_buf->priv); + if (!meta) { + WARN(1, "%s: (%p) was never mapped\n", __func__, dma_buf); + mutex_unlock(&msm_iommu_map_mutex); + goto out; + + } + mutex_unlock(&msm_iommu_map_mutex); + + mutex_lock(&meta->lock); + iommu_map = msm_iommu_lookup(meta, dev); + + if (!iommu_map) { + WARN(1, "%s: (%p) was never mapped for device %p\n", __func__, + dma_buf, dev); + mutex_unlock(&meta->lock); + goto out; + } + + /* + * Save direction for later use when we actually unmap. + * Not used right now but in the future if we go to coherent mapping + * API we might want to call the appropriate API when client asks + * to unmap + */ + iommu_map->dir = dir; + + kref_put(&iommu_map->ref, msm_iommu_map_release); + mutex_unlock(&meta->lock); + + msm_iommu_meta_put(meta); + +out: + return; +} +EXPORT_SYMBOL(msm_dma_unmap_sg); + +int msm_dma_unmap_all_for_dev(struct device *dev) +{ + int ret = 0; + struct msm_iommu_meta *meta; + struct rb_root *root; + struct rb_node *meta_node; + + mutex_lock(&msm_iommu_map_mutex); + root = &iommu_root; + meta_node = rb_first(root); + while (meta_node) { + struct msm_iommu_map *iommu_map; + struct msm_iommu_map *iommu_map_next; + + meta = rb_entry(meta_node, struct msm_iommu_meta, node); + mutex_lock(&meta->lock); + list_for_each_entry_safe(iommu_map, iommu_map_next, + &meta->iommu_maps, lnode) + if (iommu_map->dev == dev) + if (!kref_put(&iommu_map->ref, + msm_iommu_map_release)) + ret = -EINVAL; + + mutex_unlock(&meta->lock); + meta_node = rb_next(meta_node); + } + mutex_unlock(&msm_iommu_map_mutex); + + return ret; +} +EXPORT_SYMBOL(msm_dma_unmap_all_for_dev); + +/* + * Only to be called by ION code when a buffer is freed + */ +void msm_dma_buf_freed(void *buffer) +{ + struct msm_iommu_map *iommu_map; + struct msm_iommu_map *iommu_map_next; + struct msm_iommu_meta *meta; + + mutex_lock(&msm_iommu_map_mutex); + meta = msm_iommu_meta_lookup(buffer); + if (!meta) { + /* Already unmapped (assuming no late unmapping) */ + mutex_unlock(&msm_iommu_map_mutex); + goto out; + + } + mutex_unlock(&msm_iommu_map_mutex); + + mutex_lock(&meta->lock); + + list_for_each_entry_safe(iommu_map, iommu_map_next, &meta->iommu_maps, + lnode) + kref_put(&iommu_map->ref, msm_iommu_map_release); + + if (!list_empty(&meta->iommu_maps)) { + WARN(1, "%s: DMA Buffer %p being destroyed with outstanding iommu mappins!\n", __func__, + meta->buffer); + } + + INIT_LIST_HEAD(&meta->iommu_maps); + mutex_unlock(&meta->lock); + + msm_iommu_meta_put(meta); +out: + return; + +} + diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c deleted file mode 100644 index e321fa517a45..000000000000 --- a/drivers/iommu/msm_iommu.c +++ /dev/null @@ -1,735 +0,0 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/errno.h> -#include <linux/io.h> -#include <linux/interrupt.h> -#include <linux/list.h> -#include <linux/spinlock.h> -#include <linux/slab.h> -#include <linux/iommu.h> -#include <linux/clk.h> - -#include <asm/cacheflush.h> -#include <asm/sizes.h> - -#include "msm_iommu_hw-8xxx.h" -#include "msm_iommu.h" - -#define MRC(reg, processor, op1, crn, crm, op2) \ -__asm__ __volatile__ ( \ -" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \ -: "=r" (reg)) - -#define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0) -#define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1) - -/* bitmap of the page sizes currently supported */ -#define MSM_IOMMU_PGSIZES (SZ_4K | SZ_64K | SZ_1M | SZ_16M) - -static int msm_iommu_tex_class[4]; - -DEFINE_SPINLOCK(msm_iommu_lock); - -struct msm_priv { - unsigned long *pgtable; - struct list_head list_attached; - struct iommu_domain domain; -}; - -static struct msm_priv *to_msm_priv(struct iommu_domain *dom) -{ - return container_of(dom, struct msm_priv, domain); -} - -static int __enable_clocks(struct msm_iommu_drvdata *drvdata) -{ - int ret; - - ret = clk_enable(drvdata->pclk); - if (ret) - goto fail; - - if (drvdata->clk) { - ret = clk_enable(drvdata->clk); - if (ret) - clk_disable(drvdata->pclk); - } -fail: - return ret; -} - -static void __disable_clocks(struct msm_iommu_drvdata *drvdata) -{ - clk_disable(drvdata->clk); - clk_disable(drvdata->pclk); -} - -static int __flush_iotlb(struct iommu_domain *domain) -{ - struct msm_priv *priv = to_msm_priv(domain); - struct msm_iommu_drvdata *iommu_drvdata; - struct msm_iommu_ctx_drvdata *ctx_drvdata; - int ret = 0; -#ifndef CONFIG_IOMMU_PGTABLES_L2 - unsigned long *fl_table = priv->pgtable; - int i; - - if (!list_empty(&priv->list_attached)) { - dmac_flush_range(fl_table, fl_table + SZ_16K); - - for (i = 0; i < NUM_FL_PTE; i++) - if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) { - void *sl_table = __va(fl_table[i] & - FL_BASE_MASK); - dmac_flush_range(sl_table, sl_table + SZ_4K); - } - } -#endif - - list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { - - BUG_ON(!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent); - - iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); - BUG_ON(!iommu_drvdata); - - ret = __enable_clocks(iommu_drvdata); - if (ret) - goto fail; - - SET_CTX_TLBIALL(iommu_drvdata->base, ctx_drvdata->num, 0); - __disable_clocks(iommu_drvdata); - } -fail: - return ret; -} - -static void __reset_context(void __iomem *base, int ctx) -{ - SET_BPRCOSH(base, ctx, 0); - SET_BPRCISH(base, ctx, 0); - SET_BPRCNSH(base, ctx, 0); - SET_BPSHCFG(base, ctx, 0); - SET_BPMTCFG(base, ctx, 0); - SET_ACTLR(base, ctx, 0); - SET_SCTLR(base, ctx, 0); - SET_FSRRESTORE(base, ctx, 0); - SET_TTBR0(base, ctx, 0); - SET_TTBR1(base, ctx, 0); - SET_TTBCR(base, ctx, 0); - SET_BFBCR(base, ctx, 0); - SET_PAR(base, ctx, 0); - SET_FAR(base, ctx, 0); - SET_CTX_TLBIALL(base, ctx, 0); - SET_TLBFLPTER(base, ctx, 0); - SET_TLBSLPTER(base, ctx, 0); - SET_TLBLKCR(base, ctx, 0); - SET_PRRR(base, ctx, 0); - SET_NMRR(base, ctx, 0); -} - -static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) -{ - unsigned int prrr, nmrr; - __reset_context(base, ctx); - - /* Set up HTW mode */ - /* TLB miss configuration: perform HTW on miss */ - SET_TLBMCFG(base, ctx, 0x3); - - /* V2P configuration: HTW for access */ - SET_V2PCFG(base, ctx, 0x3); - - SET_TTBCR(base, ctx, 0); - SET_TTBR0_PA(base, ctx, (pgtable >> 14)); - - /* Invalidate the TLB for this context */ - SET_CTX_TLBIALL(base, ctx, 0); - - /* Set interrupt number to "secure" interrupt */ - SET_IRPTNDX(base, ctx, 0); - - /* Enable context fault interrupt */ - SET_CFEIE(base, ctx, 1); - - /* Stall access on a context fault and let the handler deal with it */ - SET_CFCFG(base, ctx, 1); - - /* Redirect all cacheable requests to L2 slave port. */ - SET_RCISH(base, ctx, 1); - SET_RCOSH(base, ctx, 1); - SET_RCNSH(base, ctx, 1); - - /* Turn on TEX Remap */ - SET_TRE(base, ctx, 1); - - /* Set TEX remap attributes */ - RCP15_PRRR(prrr); - RCP15_NMRR(nmrr); - SET_PRRR(base, ctx, prrr); - SET_NMRR(base, ctx, nmrr); - - /* Turn on BFB prefetch */ - SET_BFBDFE(base, ctx, 1); - -#ifdef CONFIG_IOMMU_PGTABLES_L2 - /* Configure page tables as inner-cacheable and shareable to reduce - * the TLB miss penalty. - */ - SET_TTBR0_SH(base, ctx, 1); - SET_TTBR1_SH(base, ctx, 1); - - SET_TTBR0_NOS(base, ctx, 1); - SET_TTBR1_NOS(base, ctx, 1); - - SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */ - SET_TTBR0_IRGNL(base, ctx, 1); - - SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */ - SET_TTBR1_IRGNL(base, ctx, 1); - - SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */ - SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */ -#endif - - /* Enable the MMU */ - SET_M(base, ctx, 1); -} - -static struct iommu_domain *msm_iommu_domain_alloc(unsigned type) -{ - struct msm_priv *priv; - - if (type != IOMMU_DOMAIN_UNMANAGED) - return NULL; - - priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - goto fail_nomem; - - INIT_LIST_HEAD(&priv->list_attached); - priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL, - get_order(SZ_16K)); - - if (!priv->pgtable) - goto fail_nomem; - - memset(priv->pgtable, 0, SZ_16K); - - priv->domain.geometry.aperture_start = 0; - priv->domain.geometry.aperture_end = (1ULL << 32) - 1; - priv->domain.geometry.force_aperture = true; - - return &priv->domain; - -fail_nomem: - kfree(priv); - return NULL; -} - -static void msm_iommu_domain_free(struct iommu_domain *domain) -{ - struct msm_priv *priv; - unsigned long flags; - unsigned long *fl_table; - int i; - - spin_lock_irqsave(&msm_iommu_lock, flags); - priv = to_msm_priv(domain); - - fl_table = priv->pgtable; - - for (i = 0; i < NUM_FL_PTE; i++) - if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) - free_page((unsigned long) __va(((fl_table[i]) & - FL_BASE_MASK))); - - free_pages((unsigned long)priv->pgtable, get_order(SZ_16K)); - priv->pgtable = NULL; - - kfree(priv); - spin_unlock_irqrestore(&msm_iommu_lock, flags); -} - -static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) -{ - struct msm_priv *priv; - struct msm_iommu_ctx_dev *ctx_dev; - struct msm_iommu_drvdata *iommu_drvdata; - struct msm_iommu_ctx_drvdata *ctx_drvdata; - struct msm_iommu_ctx_drvdata *tmp_drvdata; - int ret = 0; - unsigned long flags; - - spin_lock_irqsave(&msm_iommu_lock, flags); - - priv = to_msm_priv(domain); - - if (!dev) { - ret = -EINVAL; - goto fail; - } - - iommu_drvdata = dev_get_drvdata(dev->parent); - ctx_drvdata = dev_get_drvdata(dev); - ctx_dev = dev->platform_data; - - if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) { - ret = -EINVAL; - goto fail; - } - - if (!list_empty(&ctx_drvdata->attached_elm)) { - ret = -EBUSY; - goto fail; - } - - list_for_each_entry(tmp_drvdata, &priv->list_attached, attached_elm) - if (tmp_drvdata == ctx_drvdata) { - ret = -EBUSY; - goto fail; - } - - ret = __enable_clocks(iommu_drvdata); - if (ret) - goto fail; - - __program_context(iommu_drvdata->base, ctx_dev->num, - __pa(priv->pgtable)); - - __disable_clocks(iommu_drvdata); - list_add(&(ctx_drvdata->attached_elm), &priv->list_attached); - ret = __flush_iotlb(domain); - -fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); - return ret; -} - -static void msm_iommu_detach_dev(struct iommu_domain *domain, - struct device *dev) -{ - struct msm_priv *priv; - struct msm_iommu_ctx_dev *ctx_dev; - struct msm_iommu_drvdata *iommu_drvdata; - struct msm_iommu_ctx_drvdata *ctx_drvdata; - unsigned long flags; - int ret; - - spin_lock_irqsave(&msm_iommu_lock, flags); - priv = to_msm_priv(domain); - - if (!dev) - goto fail; - - iommu_drvdata = dev_get_drvdata(dev->parent); - ctx_drvdata = dev_get_drvdata(dev); - ctx_dev = dev->platform_data; - - if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) - goto fail; - - ret = __flush_iotlb(domain); - if (ret) - goto fail; - - ret = __enable_clocks(iommu_drvdata); - if (ret) - goto fail; - - __reset_context(iommu_drvdata->base, ctx_dev->num); - __disable_clocks(iommu_drvdata); - list_del_init(&ctx_drvdata->attached_elm); - -fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); -} - -static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, - phys_addr_t pa, size_t len, int prot) -{ - struct msm_priv *priv; - unsigned long flags; - unsigned long *fl_table; - unsigned long *fl_pte; - unsigned long fl_offset; - unsigned long *sl_table; - unsigned long *sl_pte; - unsigned long sl_offset; - unsigned int pgprot; - int ret = 0, tex, sh; - - spin_lock_irqsave(&msm_iommu_lock, flags); - - sh = (prot & MSM_IOMMU_ATTR_SH) ? 1 : 0; - tex = msm_iommu_tex_class[prot & MSM_IOMMU_CP_MASK]; - - if (tex < 0 || tex > NUM_TEX_CLASS - 1) { - ret = -EINVAL; - goto fail; - } - - priv = to_msm_priv(domain); - - fl_table = priv->pgtable; - - if (len != SZ_16M && len != SZ_1M && - len != SZ_64K && len != SZ_4K) { - pr_debug("Bad size: %d\n", len); - ret = -EINVAL; - goto fail; - } - - if (!fl_table) { - pr_debug("Null page table\n"); - ret = -EINVAL; - goto fail; - } - - if (len == SZ_16M || len == SZ_1M) { - pgprot = sh ? FL_SHARED : 0; - pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0; - pgprot |= tex & 0x02 ? FL_CACHEABLE : 0; - pgprot |= tex & 0x04 ? FL_TEX0 : 0; - } else { - pgprot = sh ? SL_SHARED : 0; - pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0; - pgprot |= tex & 0x02 ? SL_CACHEABLE : 0; - pgprot |= tex & 0x04 ? SL_TEX0 : 0; - } - - fl_offset = FL_OFFSET(va); /* Upper 12 bits */ - fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ - - if (len == SZ_16M) { - int i = 0; - for (i = 0; i < 16; i++) - *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION | - FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT | - FL_SHARED | FL_NG | pgprot; - } - - if (len == SZ_1M) - *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG | - FL_TYPE_SECT | FL_SHARED | pgprot; - - /* Need a 2nd level table */ - if ((len == SZ_4K || len == SZ_64K) && (*fl_pte) == 0) { - unsigned long *sl; - sl = (unsigned long *) __get_free_pages(GFP_ATOMIC, - get_order(SZ_4K)); - - if (!sl) { - pr_debug("Could not allocate second level table\n"); - ret = -ENOMEM; - goto fail; - } - - memset(sl, 0, SZ_4K); - *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | FL_TYPE_TABLE); - } - - sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); - sl_offset = SL_OFFSET(va); - sl_pte = sl_table + sl_offset; - - - if (len == SZ_4K) - *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_AP0 | SL_AP1 | SL_NG | - SL_SHARED | SL_TYPE_SMALL | pgprot; - - if (len == SZ_64K) { - int i; - - for (i = 0; i < 16; i++) - *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_AP0 | - SL_NG | SL_AP1 | SL_SHARED | SL_TYPE_LARGE | pgprot; - } - - ret = __flush_iotlb(domain); -fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); - return ret; -} - -static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, - size_t len) -{ - struct msm_priv *priv; - unsigned long flags; - unsigned long *fl_table; - unsigned long *fl_pte; - unsigned long fl_offset; - unsigned long *sl_table; - unsigned long *sl_pte; - unsigned long sl_offset; - int i, ret = 0; - - spin_lock_irqsave(&msm_iommu_lock, flags); - - priv = to_msm_priv(domain); - - fl_table = priv->pgtable; - - if (len != SZ_16M && len != SZ_1M && - len != SZ_64K && len != SZ_4K) { - pr_debug("Bad length: %d\n", len); - goto fail; - } - - if (!fl_table) { - pr_debug("Null page table\n"); - goto fail; - } - - fl_offset = FL_OFFSET(va); /* Upper 12 bits */ - fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ - - if (*fl_pte == 0) { - pr_debug("First level PTE is 0\n"); - goto fail; - } - - /* Unmap supersection */ - if (len == SZ_16M) - for (i = 0; i < 16; i++) - *(fl_pte+i) = 0; - - if (len == SZ_1M) - *fl_pte = 0; - - sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); - sl_offset = SL_OFFSET(va); - sl_pte = sl_table + sl_offset; - - if (len == SZ_64K) { - for (i = 0; i < 16; i++) - *(sl_pte+i) = 0; - } - - if (len == SZ_4K) - *sl_pte = 0; - - if (len == SZ_4K || len == SZ_64K) { - int used = 0; - - for (i = 0; i < NUM_SL_PTE; i++) - if (sl_table[i]) - used = 1; - if (!used) { - free_page((unsigned long)sl_table); - *fl_pte = 0; - } - } - - ret = __flush_iotlb(domain); - -fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); - - /* the IOMMU API requires us to return how many bytes were unmapped */ - len = ret ? 0 : len; - return len; -} - -static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, - dma_addr_t va) -{ - struct msm_priv *priv; - struct msm_iommu_drvdata *iommu_drvdata; - struct msm_iommu_ctx_drvdata *ctx_drvdata; - unsigned int par; - unsigned long flags; - void __iomem *base; - phys_addr_t ret = 0; - int ctx; - - spin_lock_irqsave(&msm_iommu_lock, flags); - - priv = to_msm_priv(domain); - if (list_empty(&priv->list_attached)) - goto fail; - - ctx_drvdata = list_entry(priv->list_attached.next, - struct msm_iommu_ctx_drvdata, attached_elm); - iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); - - base = iommu_drvdata->base; - ctx = ctx_drvdata->num; - - ret = __enable_clocks(iommu_drvdata); - if (ret) - goto fail; - - /* Invalidate context TLB */ - SET_CTX_TLBIALL(base, ctx, 0); - SET_V2PPR(base, ctx, va & V2Pxx_VA); - - par = GET_PAR(base, ctx); - - /* We are dealing with a supersection */ - if (GET_NOFAULT_SS(base, ctx)) - ret = (par & 0xFF000000) | (va & 0x00FFFFFF); - else /* Upper 20 bits from PAR, lower 12 from VA */ - ret = (par & 0xFFFFF000) | (va & 0x00000FFF); - - if (GET_FAULT(base, ctx)) - ret = 0; - - __disable_clocks(iommu_drvdata); -fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); - return ret; -} - -static bool msm_iommu_capable(enum iommu_cap cap) -{ - return false; -} - -static void print_ctx_regs(void __iomem *base, int ctx) -{ - unsigned int fsr = GET_FSR(base, ctx); - pr_err("FAR = %08x PAR = %08x\n", - GET_FAR(base, ctx), GET_PAR(base, ctx)); - pr_err("FSR = %08x [%s%s%s%s%s%s%s%s%s%s]\n", fsr, - (fsr & 0x02) ? "TF " : "", - (fsr & 0x04) ? "AFF " : "", - (fsr & 0x08) ? "APF " : "", - (fsr & 0x10) ? "TLBMF " : "", - (fsr & 0x20) ? "HTWDEEF " : "", - (fsr & 0x40) ? "HTWSEEF " : "", - (fsr & 0x80) ? "MHF " : "", - (fsr & 0x10000) ? "SL " : "", - (fsr & 0x40000000) ? "SS " : "", - (fsr & 0x80000000) ? "MULTI " : ""); - - pr_err("FSYNR0 = %08x FSYNR1 = %08x\n", - GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx)); - pr_err("TTBR0 = %08x TTBR1 = %08x\n", - GET_TTBR0(base, ctx), GET_TTBR1(base, ctx)); - pr_err("SCTLR = %08x ACTLR = %08x\n", - GET_SCTLR(base, ctx), GET_ACTLR(base, ctx)); - pr_err("PRRR = %08x NMRR = %08x\n", - GET_PRRR(base, ctx), GET_NMRR(base, ctx)); -} - -irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id) -{ - struct msm_iommu_drvdata *drvdata = dev_id; - void __iomem *base; - unsigned int fsr; - int i, ret; - - spin_lock(&msm_iommu_lock); - - if (!drvdata) { - pr_err("Invalid device ID in context interrupt handler\n"); - goto fail; - } - - base = drvdata->base; - - pr_err("Unexpected IOMMU page fault!\n"); - pr_err("base = %08x\n", (unsigned int) base); - - ret = __enable_clocks(drvdata); - if (ret) - goto fail; - - for (i = 0; i < drvdata->ncb; i++) { - fsr = GET_FSR(base, i); - if (fsr) { - pr_err("Fault occurred in context %d.\n", i); - pr_err("Interesting registers:\n"); - print_ctx_regs(base, i); - SET_FSR(base, i, 0x4000000F); - } - } - __disable_clocks(drvdata); -fail: - spin_unlock(&msm_iommu_lock); - return 0; -} - -static const struct iommu_ops msm_iommu_ops = { - .capable = msm_iommu_capable, - .domain_alloc = msm_iommu_domain_alloc, - .domain_free = msm_iommu_domain_free, - .attach_dev = msm_iommu_attach_dev, - .detach_dev = msm_iommu_detach_dev, - .map = msm_iommu_map, - .unmap = msm_iommu_unmap, - .map_sg = default_iommu_map_sg, - .iova_to_phys = msm_iommu_iova_to_phys, - .pgsize_bitmap = MSM_IOMMU_PGSIZES, -}; - -static int __init get_tex_class(int icp, int ocp, int mt, int nos) -{ - int i = 0; - unsigned int prrr = 0; - unsigned int nmrr = 0; - int c_icp, c_ocp, c_mt, c_nos; - - RCP15_PRRR(prrr); - RCP15_NMRR(nmrr); - - for (i = 0; i < NUM_TEX_CLASS; i++) { - c_nos = PRRR_NOS(prrr, i); - c_mt = PRRR_MT(prrr, i); - c_icp = NMRR_ICP(nmrr, i); - c_ocp = NMRR_OCP(nmrr, i); - - if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos) - return i; - } - - return -ENODEV; -} - -static void __init setup_iommu_tex_classes(void) -{ - msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED] = - get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1); - - msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_WA] = - get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1); - - msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_NWA] = - get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1); - - msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WT] = - get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1); -} - -static int __init msm_iommu_init(void) -{ - setup_iommu_tex_classes(); - bus_set_iommu(&platform_bus_type, &msm_iommu_ops); - return 0; -} - -subsys_initcall(msm_iommu_init); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>"); diff --git a/drivers/iommu/msm_iommu.h b/drivers/iommu/msm_iommu.h deleted file mode 100644 index 5c7c955e6d25..000000000000 --- a/drivers/iommu/msm_iommu.h +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#ifndef MSM_IOMMU_H -#define MSM_IOMMU_H - -#include <linux/interrupt.h> -#include <linux/clk.h> - -/* Sharability attributes of MSM IOMMU mappings */ -#define MSM_IOMMU_ATTR_NON_SH 0x0 -#define MSM_IOMMU_ATTR_SH 0x4 - -/* Cacheability attributes of MSM IOMMU mappings */ -#define MSM_IOMMU_ATTR_NONCACHED 0x0 -#define MSM_IOMMU_ATTR_CACHED_WB_WA 0x1 -#define MSM_IOMMU_ATTR_CACHED_WB_NWA 0x2 -#define MSM_IOMMU_ATTR_CACHED_WT 0x3 - -/* Mask for the cache policy attribute */ -#define MSM_IOMMU_CP_MASK 0x03 - -/* Maximum number of Machine IDs that we are allowing to be mapped to the same - * context bank. The number of MIDs mapped to the same CB does not affect - * performance, but there is a practical limit on how many distinct MIDs may - * be present. These mappings are typically determined at design time and are - * not expected to change at run time. - */ -#define MAX_NUM_MIDS 32 - -/** - * struct msm_iommu_dev - a single IOMMU hardware instance - * name Human-readable name given to this IOMMU HW instance - * ncb Number of context banks present on this IOMMU HW instance - */ -struct msm_iommu_dev { - const char *name; - int ncb; -}; - -/** - * struct msm_iommu_ctx_dev - an IOMMU context bank instance - * name Human-readable name given to this context bank - * num Index of this context bank within the hardware - * mids List of Machine IDs that are to be mapped into this context - * bank, terminated by -1. The MID is a set of signals on the - * AXI bus that identifies the function associated with a specific - * memory request. (See ARM spec). - */ -struct msm_iommu_ctx_dev { - const char *name; - int num; - int mids[MAX_NUM_MIDS]; -}; - - -/** - * struct msm_iommu_drvdata - A single IOMMU hardware instance - * @base: IOMMU config port base address (VA) - * @ncb The number of contexts on this IOMMU - * @irq: Interrupt number - * @clk: The bus clock for this IOMMU hardware instance - * @pclk: The clock for the IOMMU bus interconnect - * - * A msm_iommu_drvdata holds the global driver data about a single piece - * of an IOMMU hardware instance. - */ -struct msm_iommu_drvdata { - void __iomem *base; - int irq; - int ncb; - struct clk *clk; - struct clk *pclk; -}; - -/** - * struct msm_iommu_ctx_drvdata - an IOMMU context bank instance - * @num: Hardware context number of this context - * @pdev: Platform device associated wit this HW instance - * @attached_elm: List element for domains to track which devices are - * attached to them - * - * A msm_iommu_ctx_drvdata holds the driver data for a single context bank - * within each IOMMU hardware instance - */ -struct msm_iommu_ctx_drvdata { - int num; - struct platform_device *pdev; - struct list_head attached_elm; -}; - -/* - * Look up an IOMMU context device by its context name. NULL if none found. - * Useful for testing and drivers that do not yet fully have IOMMU stuff in - * their platform devices. - */ -struct device *msm_iommu_get_ctx(const char *ctx_name); - -/* - * Interrupt handler for the IOMMU context fault interrupt. Hooking the - * interrupt is not supported in the API yet, but this will print an error - * message and dump useful IOMMU registers. - */ -irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id); - -#endif diff --git a/drivers/iommu/msm_iommu_dev.c b/drivers/iommu/msm_iommu_dev.c deleted file mode 100644 index b6d01f97e537..000000000000 --- a/drivers/iommu/msm_iommu_dev.c +++ /dev/null @@ -1,392 +0,0 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/io.h> -#include <linux/clk.h> -#include <linux/iommu.h> -#include <linux/interrupt.h> -#include <linux/err.h> -#include <linux/slab.h> - -#include "msm_iommu_hw-8xxx.h" -#include "msm_iommu.h" - -struct iommu_ctx_iter_data { - /* input */ - const char *name; - - /* output */ - struct device *dev; -}; - -static struct platform_device *msm_iommu_root_dev; - -static int each_iommu_ctx(struct device *dev, void *data) -{ - struct iommu_ctx_iter_data *res = data; - struct msm_iommu_ctx_dev *c = dev->platform_data; - - if (!res || !c || !c->name || !res->name) - return -EINVAL; - - if (!strcmp(res->name, c->name)) { - res->dev = dev; - return 1; - } - return 0; -} - -static int each_iommu(struct device *dev, void *data) -{ - return device_for_each_child(dev, data, each_iommu_ctx); -} - -struct device *msm_iommu_get_ctx(const char *ctx_name) -{ - struct iommu_ctx_iter_data r; - int found; - - if (!msm_iommu_root_dev) { - pr_err("No root IOMMU device.\n"); - goto fail; - } - - r.name = ctx_name; - found = device_for_each_child(&msm_iommu_root_dev->dev, &r, each_iommu); - - if (!found) { - pr_err("Could not find context <%s>\n", ctx_name); - goto fail; - } - - return r.dev; -fail: - return NULL; -} -EXPORT_SYMBOL(msm_iommu_get_ctx); - -static void msm_iommu_reset(void __iomem *base, int ncb) -{ - int ctx; - - SET_RPUE(base, 0); - SET_RPUEIE(base, 0); - SET_ESRRESTORE(base, 0); - SET_TBE(base, 0); - SET_CR(base, 0); - SET_SPDMBE(base, 0); - SET_TESTBUSCR(base, 0); - SET_TLBRSW(base, 0); - SET_GLOBAL_TLBIALL(base, 0); - SET_RPU_ACR(base, 0); - SET_TLBLKCRWE(base, 1); - - for (ctx = 0; ctx < ncb; ctx++) { - SET_BPRCOSH(base, ctx, 0); - SET_BPRCISH(base, ctx, 0); - SET_BPRCNSH(base, ctx, 0); - SET_BPSHCFG(base, ctx, 0); - SET_BPMTCFG(base, ctx, 0); - SET_ACTLR(base, ctx, 0); - SET_SCTLR(base, ctx, 0); - SET_FSRRESTORE(base, ctx, 0); - SET_TTBR0(base, ctx, 0); - SET_TTBR1(base, ctx, 0); - SET_TTBCR(base, ctx, 0); - SET_BFBCR(base, ctx, 0); - SET_PAR(base, ctx, 0); - SET_FAR(base, ctx, 0); - SET_CTX_TLBIALL(base, ctx, 0); - SET_TLBFLPTER(base, ctx, 0); - SET_TLBSLPTER(base, ctx, 0); - SET_TLBLKCR(base, ctx, 0); - SET_PRRR(base, ctx, 0); - SET_NMRR(base, ctx, 0); - SET_CONTEXTIDR(base, ctx, 0); - } -} - -static int msm_iommu_probe(struct platform_device *pdev) -{ - struct resource *r; - struct clk *iommu_clk; - struct clk *iommu_pclk; - struct msm_iommu_drvdata *drvdata; - struct msm_iommu_dev *iommu_dev = dev_get_platdata(&pdev->dev); - void __iomem *regs_base; - int ret, irq, par; - - if (pdev->id == -1) { - msm_iommu_root_dev = pdev; - return 0; - } - - drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); - - if (!drvdata) { - ret = -ENOMEM; - goto fail; - } - - if (!iommu_dev) { - ret = -ENODEV; - goto fail; - } - - iommu_pclk = clk_get(NULL, "smmu_pclk"); - if (IS_ERR(iommu_pclk)) { - ret = -ENODEV; - goto fail; - } - - ret = clk_prepare_enable(iommu_pclk); - if (ret) - goto fail_enable; - - iommu_clk = clk_get(&pdev->dev, "iommu_clk"); - - if (!IS_ERR(iommu_clk)) { - if (clk_get_rate(iommu_clk) == 0) - clk_set_rate(iommu_clk, 1); - - ret = clk_prepare_enable(iommu_clk); - if (ret) { - clk_put(iommu_clk); - goto fail_pclk; - } - } else - iommu_clk = NULL; - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "physbase"); - regs_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(regs_base)) { - ret = PTR_ERR(regs_base); - goto fail_clk; - } - - irq = platform_get_irq_byname(pdev, "secure_irq"); - if (irq < 0) { - ret = -ENODEV; - goto fail_clk; - } - - msm_iommu_reset(regs_base, iommu_dev->ncb); - - SET_M(regs_base, 0, 1); - SET_PAR(regs_base, 0, 0); - SET_V2PCFG(regs_base, 0, 1); - SET_V2PPR(regs_base, 0, 0); - par = GET_PAR(regs_base, 0); - SET_V2PCFG(regs_base, 0, 0); - SET_M(regs_base, 0, 0); - - if (!par) { - pr_err("%s: Invalid PAR value detected\n", iommu_dev->name); - ret = -ENODEV; - goto fail_clk; - } - - ret = request_irq(irq, msm_iommu_fault_handler, 0, - "msm_iommu_secure_irpt_handler", drvdata); - if (ret) { - pr_err("Request IRQ %d failed with ret=%d\n", irq, ret); - goto fail_clk; - } - - - drvdata->pclk = iommu_pclk; - drvdata->clk = iommu_clk; - drvdata->base = regs_base; - drvdata->irq = irq; - drvdata->ncb = iommu_dev->ncb; - - pr_info("device %s mapped at %p, irq %d with %d ctx banks\n", - iommu_dev->name, regs_base, irq, iommu_dev->ncb); - - platform_set_drvdata(pdev, drvdata); - - clk_disable(iommu_clk); - - clk_disable(iommu_pclk); - - return 0; -fail_clk: - if (iommu_clk) { - clk_disable(iommu_clk); - clk_put(iommu_clk); - } -fail_pclk: - clk_disable_unprepare(iommu_pclk); -fail_enable: - clk_put(iommu_pclk); -fail: - kfree(drvdata); - return ret; -} - -static int msm_iommu_remove(struct platform_device *pdev) -{ - struct msm_iommu_drvdata *drv = NULL; - - drv = platform_get_drvdata(pdev); - if (drv) { - if (drv->clk) { - clk_unprepare(drv->clk); - clk_put(drv->clk); - } - clk_unprepare(drv->pclk); - clk_put(drv->pclk); - memset(drv, 0, sizeof(*drv)); - kfree(drv); - } - return 0; -} - -static int msm_iommu_ctx_probe(struct platform_device *pdev) -{ - struct msm_iommu_ctx_dev *c = dev_get_platdata(&pdev->dev); - struct msm_iommu_drvdata *drvdata; - struct msm_iommu_ctx_drvdata *ctx_drvdata; - int i, ret; - - if (!c || !pdev->dev.parent) - return -EINVAL; - - drvdata = dev_get_drvdata(pdev->dev.parent); - if (!drvdata) - return -ENODEV; - - ctx_drvdata = kzalloc(sizeof(*ctx_drvdata), GFP_KERNEL); - if (!ctx_drvdata) - return -ENOMEM; - - ctx_drvdata->num = c->num; - ctx_drvdata->pdev = pdev; - - INIT_LIST_HEAD(&ctx_drvdata->attached_elm); - platform_set_drvdata(pdev, ctx_drvdata); - - ret = clk_prepare_enable(drvdata->pclk); - if (ret) - goto fail; - - if (drvdata->clk) { - ret = clk_prepare_enable(drvdata->clk); - if (ret) { - clk_disable_unprepare(drvdata->pclk); - goto fail; - } - } - - /* Program the M2V tables for this context */ - for (i = 0; i < MAX_NUM_MIDS; i++) { - int mid = c->mids[i]; - if (mid == -1) - break; - - SET_M2VCBR_N(drvdata->base, mid, 0); - SET_CBACR_N(drvdata->base, c->num, 0); - - /* Set VMID = 0 */ - SET_VMID(drvdata->base, mid, 0); - - /* Set the context number for that MID to this context */ - SET_CBNDX(drvdata->base, mid, c->num); - - /* Set MID associated with this context bank to 0*/ - SET_CBVMID(drvdata->base, c->num, 0); - - /* Set the ASID for TLB tagging for this context */ - SET_CONTEXTIDR_ASID(drvdata->base, c->num, c->num); - - /* Set security bit override to be Non-secure */ - SET_NSCFG(drvdata->base, mid, 3); - } - - clk_disable(drvdata->clk); - clk_disable(drvdata->pclk); - - dev_info(&pdev->dev, "context %s using bank %d\n", c->name, c->num); - return 0; -fail: - kfree(ctx_drvdata); - return ret; -} - -static int msm_iommu_ctx_remove(struct platform_device *pdev) -{ - struct msm_iommu_ctx_drvdata *drv = NULL; - drv = platform_get_drvdata(pdev); - if (drv) { - memset(drv, 0, sizeof(struct msm_iommu_ctx_drvdata)); - kfree(drv); - } - return 0; -} - -static struct platform_driver msm_iommu_driver = { - .driver = { - .name = "msm_iommu", - }, - .probe = msm_iommu_probe, - .remove = msm_iommu_remove, -}; - -static struct platform_driver msm_iommu_ctx_driver = { - .driver = { - .name = "msm_iommu_ctx", - }, - .probe = msm_iommu_ctx_probe, - .remove = msm_iommu_ctx_remove, -}; - -static int __init msm_iommu_driver_init(void) -{ - int ret; - ret = platform_driver_register(&msm_iommu_driver); - if (ret != 0) { - pr_err("Failed to register IOMMU driver\n"); - goto error; - } - - ret = platform_driver_register(&msm_iommu_ctx_driver); - if (ret != 0) { - platform_driver_unregister(&msm_iommu_driver); - pr_err("Failed to register IOMMU context driver\n"); - goto error; - } - -error: - return ret; -} - -static void __exit msm_iommu_driver_exit(void) -{ - platform_driver_unregister(&msm_iommu_ctx_driver); - platform_driver_unregister(&msm_iommu_driver); -} - -subsys_initcall(msm_iommu_driver_init); -module_exit(msm_iommu_driver_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>"); diff --git a/drivers/iommu/msm_iommu_hw-8xxx.h b/drivers/iommu/msm_iommu_hw-8xxx.h deleted file mode 100644 index fc160101dead..000000000000 --- a/drivers/iommu/msm_iommu_hw-8xxx.h +++ /dev/null @@ -1,1865 +0,0 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#ifndef __ARCH_ARM_MACH_MSM_IOMMU_HW_8XXX_H -#define __ARCH_ARM_MACH_MSM_IOMMU_HW_8XXX_H - -#define CTX_SHIFT 12 - -#define GET_GLOBAL_REG(reg, base) (readl((base) + (reg))) -#define GET_CTX_REG(reg, base, ctx) \ - (readl((base) + (reg) + ((ctx) << CTX_SHIFT))) - -#define SET_GLOBAL_REG(reg, base, val) writel((val), ((base) + (reg))) - -#define SET_CTX_REG(reg, base, ctx, val) \ - writel((val), ((base) + (reg) + ((ctx) << CTX_SHIFT))) - -/* Wrappers for numbered registers */ -#define SET_GLOBAL_REG_N(b, n, r, v) SET_GLOBAL_REG(b, ((r) + (n << 2)), (v)) -#define GET_GLOBAL_REG_N(b, n, r) GET_GLOBAL_REG(b, ((r) + (n << 2))) - -/* Field wrappers */ -#define GET_GLOBAL_FIELD(b, r, F) GET_FIELD(((b) + (r)), F##_MASK, F##_SHIFT) -#define GET_CONTEXT_FIELD(b, c, r, F) \ - GET_FIELD(((b) + (r) + ((c) << CTX_SHIFT)), F##_MASK, F##_SHIFT) - -#define SET_GLOBAL_FIELD(b, r, F, v) \ - SET_FIELD(((b) + (r)), F##_MASK, F##_SHIFT, (v)) -#define SET_CONTEXT_FIELD(b, c, r, F, v) \ - SET_FIELD(((b) + (r) + ((c) << CTX_SHIFT)), F##_MASK, F##_SHIFT, (v)) - -#define GET_FIELD(addr, mask, shift) ((readl(addr) >> (shift)) & (mask)) - -#define SET_FIELD(addr, mask, shift, v) \ -do { \ - int t = readl(addr); \ - writel((t & ~((mask) << (shift))) + (((v) & (mask)) << (shift)), addr);\ -} while (0) - - -#define NUM_FL_PTE 4096 -#define NUM_SL_PTE 256 -#define NUM_TEX_CLASS 8 - -/* First-level page table bits */ -#define FL_BASE_MASK 0xFFFFFC00 -#define FL_TYPE_TABLE (1 << 0) -#define FL_TYPE_SECT (2 << 0) -#define FL_SUPERSECTION (1 << 18) -#define FL_AP_WRITE (1 << 10) -#define FL_AP_READ (1 << 11) -#define FL_SHARED (1 << 16) -#define FL_BUFFERABLE (1 << 2) -#define FL_CACHEABLE (1 << 3) -#define FL_TEX0 (1 << 12) -#define FL_OFFSET(va) (((va) & 0xFFF00000) >> 20) -#define FL_NG (1 << 17) - -/* Second-level page table bits */ -#define SL_BASE_MASK_LARGE 0xFFFF0000 -#define SL_BASE_MASK_SMALL 0xFFFFF000 -#define SL_TYPE_LARGE (1 << 0) -#define SL_TYPE_SMALL (2 << 0) -#define SL_AP0 (1 << 4) -#define SL_AP1 (2 << 4) -#define SL_SHARED (1 << 10) -#define SL_BUFFERABLE (1 << 2) -#define SL_CACHEABLE (1 << 3) -#define SL_TEX0 (1 << 6) -#define SL_OFFSET(va) (((va) & 0xFF000) >> 12) -#define SL_NG (1 << 11) - -/* Memory type and cache policy attributes */ -#define MT_SO 0 -#define MT_DEV 1 -#define MT_NORMAL 2 -#define CP_NONCACHED 0 -#define CP_WB_WA 1 -#define CP_WT 2 -#define CP_WB_NWA 3 - -/* Global register setters / getters */ -#define SET_M2VCBR_N(b, N, v) SET_GLOBAL_REG_N(M2VCBR_N, N, (b), (v)) -#define SET_CBACR_N(b, N, v) SET_GLOBAL_REG_N(CBACR_N, N, (b), (v)) -#define SET_TLBRSW(b, v) SET_GLOBAL_REG(TLBRSW, (b), (v)) -#define SET_TLBTR0(b, v) SET_GLOBAL_REG(TLBTR0, (b), (v)) -#define SET_TLBTR1(b, v) SET_GLOBAL_REG(TLBTR1, (b), (v)) -#define SET_TLBTR2(b, v) SET_GLOBAL_REG(TLBTR2, (b), (v)) -#define SET_TESTBUSCR(b, v) SET_GLOBAL_REG(TESTBUSCR, (b), (v)) -#define SET_GLOBAL_TLBIALL(b, v) SET_GLOBAL_REG(GLOBAL_TLBIALL, (b), (v)) -#define SET_TLBIVMID(b, v) SET_GLOBAL_REG(TLBIVMID, (b), (v)) -#define SET_CR(b, v) SET_GLOBAL_REG(CR, (b), (v)) -#define SET_EAR(b, v) SET_GLOBAL_REG(EAR, (b), (v)) -#define SET_ESR(b, v) SET_GLOBAL_REG(ESR, (b), (v)) -#define SET_ESRRESTORE(b, v) SET_GLOBAL_REG(ESRRESTORE, (b), (v)) -#define SET_ESYNR0(b, v) SET_GLOBAL_REG(ESYNR0, (b), (v)) -#define SET_ESYNR1(b, v) SET_GLOBAL_REG(ESYNR1, (b), (v)) -#define SET_RPU_ACR(b, v) SET_GLOBAL_REG(RPU_ACR, (b), (v)) - -#define GET_M2VCBR_N(b, N) GET_GLOBAL_REG_N(M2VCBR_N, N, (b)) -#define GET_CBACR_N(b, N) GET_GLOBAL_REG_N(CBACR_N, N, (b)) -#define GET_TLBTR0(b) GET_GLOBAL_REG(TLBTR0, (b)) -#define GET_TLBTR1(b) GET_GLOBAL_REG(TLBTR1, (b)) -#define GET_TLBTR2(b) GET_GLOBAL_REG(TLBTR2, (b)) -#define GET_TESTBUSCR(b) GET_GLOBAL_REG(TESTBUSCR, (b)) -#define GET_GLOBAL_TLBIALL(b) GET_GLOBAL_REG(GLOBAL_TLBIALL, (b)) -#define GET_TLBIVMID(b) GET_GLOBAL_REG(TLBIVMID, (b)) -#define GET_CR(b) GET_GLOBAL_REG(CR, (b)) -#define GET_EAR(b) GET_GLOBAL_REG(EAR, (b)) -#define GET_ESR(b) GET_GLOBAL_REG(ESR, (b)) -#define GET_ESRRESTORE(b) GET_GLOBAL_REG(ESRRESTORE, (b)) -#define GET_ESYNR0(b) GET_GLOBAL_REG(ESYNR0, (b)) -#define GET_ESYNR1(b) GET_GLOBAL_REG(ESYNR1, (b)) -#define GET_REV(b) GET_GLOBAL_REG(REV, (b)) -#define GET_IDR(b) GET_GLOBAL_REG(IDR, (b)) -#define GET_RPU_ACR(b) GET_GLOBAL_REG(RPU_ACR, (b)) - - -/* Context register setters/getters */ -#define SET_SCTLR(b, c, v) SET_CTX_REG(SCTLR, (b), (c), (v)) -#define SET_ACTLR(b, c, v) SET_CTX_REG(ACTLR, (b), (c), (v)) -#define SET_CONTEXTIDR(b, c, v) SET_CTX_REG(CONTEXTIDR, (b), (c), (v)) -#define SET_TTBR0(b, c, v) SET_CTX_REG(TTBR0, (b), (c), (v)) -#define SET_TTBR1(b, c, v) SET_CTX_REG(TTBR1, (b), (c), (v)) -#define SET_TTBCR(b, c, v) SET_CTX_REG(TTBCR, (b), (c), (v)) -#define SET_PAR(b, c, v) SET_CTX_REG(PAR, (b), (c), (v)) -#define SET_FSR(b, c, v) SET_CTX_REG(FSR, (b), (c), (v)) -#define SET_FSRRESTORE(b, c, v) SET_CTX_REG(FSRRESTORE, (b), (c), (v)) -#define SET_FAR(b, c, v) SET_CTX_REG(FAR, (b), (c), (v)) -#define SET_FSYNR0(b, c, v) SET_CTX_REG(FSYNR0, (b), (c), (v)) -#define SET_FSYNR1(b, c, v) SET_CTX_REG(FSYNR1, (b), (c), (v)) -#define SET_PRRR(b, c, v) SET_CTX_REG(PRRR, (b), (c), (v)) -#define SET_NMRR(b, c, v) SET_CTX_REG(NMRR, (b), (c), (v)) -#define SET_TLBLKCR(b, c, v) SET_CTX_REG(TLBLCKR, (b), (c), (v)) -#define SET_V2PSR(b, c, v) SET_CTX_REG(V2PSR, (b), (c), (v)) -#define SET_TLBFLPTER(b, c, v) SET_CTX_REG(TLBFLPTER, (b), (c), (v)) -#define SET_TLBSLPTER(b, c, v) SET_CTX_REG(TLBSLPTER, (b), (c), (v)) -#define SET_BFBCR(b, c, v) SET_CTX_REG(BFBCR, (b), (c), (v)) -#define SET_CTX_TLBIALL(b, c, v) SET_CTX_REG(CTX_TLBIALL, (b), (c), (v)) -#define SET_TLBIASID(b, c, v) SET_CTX_REG(TLBIASID, (b), (c), (v)) -#define SET_TLBIVA(b, c, v) SET_CTX_REG(TLBIVA, (b), (c), (v)) -#define SET_TLBIVAA(b, c, v) SET_CTX_REG(TLBIVAA, (b), (c), (v)) -#define SET_V2PPR(b, c, v) SET_CTX_REG(V2PPR, (b), (c), (v)) -#define SET_V2PPW(b, c, v) SET_CTX_REG(V2PPW, (b), (c), (v)) -#define SET_V2PUR(b, c, v) SET_CTX_REG(V2PUR, (b), (c), (v)) -#define SET_V2PUW(b, c, v) SET_CTX_REG(V2PUW, (b), (c), (v)) -#define SET_RESUME(b, c, v) SET_CTX_REG(RESUME, (b), (c), (v)) - -#define GET_SCTLR(b, c) GET_CTX_REG(SCTLR, (b), (c)) -#define GET_ACTLR(b, c) GET_CTX_REG(ACTLR, (b), (c)) -#define GET_CONTEXTIDR(b, c) GET_CTX_REG(CONTEXTIDR, (b), (c)) -#define GET_TTBR0(b, c) GET_CTX_REG(TTBR0, (b), (c)) -#define GET_TTBR1(b, c) GET_CTX_REG(TTBR1, (b), (c)) -#define GET_TTBCR(b, c) GET_CTX_REG(TTBCR, (b), (c)) -#define GET_PAR(b, c) GET_CTX_REG(PAR, (b), (c)) -#define GET_FSR(b, c) GET_CTX_REG(FSR, (b), (c)) -#define GET_FSRRESTORE(b, c) GET_CTX_REG(FSRRESTORE, (b), (c)) -#define GET_FAR(b, c) GET_CTX_REG(FAR, (b), (c)) -#define GET_FSYNR0(b, c) GET_CTX_REG(FSYNR0, (b), (c)) -#define GET_FSYNR1(b, c) GET_CTX_REG(FSYNR1, (b), (c)) -#define GET_PRRR(b, c) GET_CTX_REG(PRRR, (b), (c)) -#define GET_NMRR(b, c) GET_CTX_REG(NMRR, (b), (c)) -#define GET_TLBLCKR(b, c) GET_CTX_REG(TLBLCKR, (b), (c)) -#define GET_V2PSR(b, c) GET_CTX_REG(V2PSR, (b), (c)) -#define GET_TLBFLPTER(b, c) GET_CTX_REG(TLBFLPTER, (b), (c)) -#define GET_TLBSLPTER(b, c) GET_CTX_REG(TLBSLPTER, (b), (c)) -#define GET_BFBCR(b, c) GET_CTX_REG(BFBCR, (b), (c)) -#define GET_CTX_TLBIALL(b, c) GET_CTX_REG(CTX_TLBIALL, (b), (c)) -#define GET_TLBIASID(b, c) GET_CTX_REG(TLBIASID, (b), (c)) -#define GET_TLBIVA(b, c) GET_CTX_REG(TLBIVA, (b), (c)) -#define GET_TLBIVAA(b, c) GET_CTX_REG(TLBIVAA, (b), (c)) -#define GET_V2PPR(b, c) GET_CTX_REG(V2PPR, (b), (c)) -#define GET_V2PPW(b, c) GET_CTX_REG(V2PPW, (b), (c)) -#define GET_V2PUR(b, c) GET_CTX_REG(V2PUR, (b), (c)) -#define GET_V2PUW(b, c) GET_CTX_REG(V2PUW, (b), (c)) -#define GET_RESUME(b, c) GET_CTX_REG(RESUME, (b), (c)) - - -/* Global field setters / getters */ -/* Global Field Setters: */ -/* CBACR_N */ -#define SET_RWVMID(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWVMID, v) -#define SET_RWE(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWE, v) -#define SET_RWGE(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWGE, v) -#define SET_CBVMID(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), CBVMID, v) -#define SET_IRPTNDX(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), IRPTNDX, v) - - -/* M2VCBR_N */ -#define SET_VMID(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), VMID, v) -#define SET_CBNDX(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), CBNDX, v) -#define SET_BYPASSD(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BYPASSD, v) -#define SET_BPRCOSH(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCOSH, v) -#define SET_BPRCISH(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCISH, v) -#define SET_BPRCNSH(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCNSH, v) -#define SET_BPSHCFG(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPSHCFG, v) -#define SET_NSCFG(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), NSCFG, v) -#define SET_BPMTCFG(b, n, v) SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPMTCFG, v) -#define SET_BPMEMTYPE(b, n, v) \ - SET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPMEMTYPE, v) - - -/* CR */ -#define SET_RPUE(b, v) SET_GLOBAL_FIELD(b, CR, RPUE, v) -#define SET_RPUERE(b, v) SET_GLOBAL_FIELD(b, CR, RPUERE, v) -#define SET_RPUEIE(b, v) SET_GLOBAL_FIELD(b, CR, RPUEIE, v) -#define SET_DCDEE(b, v) SET_GLOBAL_FIELD(b, CR, DCDEE, v) -#define SET_CLIENTPD(b, v) SET_GLOBAL_FIELD(b, CR, CLIENTPD, v) -#define SET_STALLD(b, v) SET_GLOBAL_FIELD(b, CR, STALLD, v) -#define SET_TLBLKCRWE(b, v) SET_GLOBAL_FIELD(b, CR, TLBLKCRWE, v) -#define SET_CR_TLBIALLCFG(b, v) SET_GLOBAL_FIELD(b, CR, CR_TLBIALLCFG, v) -#define SET_TLBIVMIDCFG(b, v) SET_GLOBAL_FIELD(b, CR, TLBIVMIDCFG, v) -#define SET_CR_HUME(b, v) SET_GLOBAL_FIELD(b, CR, CR_HUME, v) - - -/* ESR */ -#define SET_CFG(b, v) SET_GLOBAL_FIELD(b, ESR, CFG, v) -#define SET_BYPASS(b, v) SET_GLOBAL_FIELD(b, ESR, BYPASS, v) -#define SET_ESR_MULTI(b, v) SET_GLOBAL_FIELD(b, ESR, ESR_MULTI, v) - - -/* ESYNR0 */ -#define SET_ESYNR0_AMID(b, v) SET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_AMID, v) -#define SET_ESYNR0_APID(b, v) SET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_APID, v) -#define SET_ESYNR0_ABID(b, v) SET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_ABID, v) -#define SET_ESYNR0_AVMID(b, v) SET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_AVMID, v) -#define SET_ESYNR0_ATID(b, v) SET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_ATID, v) - - -/* ESYNR1 */ -#define SET_ESYNR1_AMEMTYPE(b, v) \ - SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AMEMTYPE, v) -#define SET_ESYNR1_ASHARED(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ASHARED, v) -#define SET_ESYNR1_AINNERSHARED(b, v) \ - SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AINNERSHARED, v) -#define SET_ESYNR1_APRIV(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_APRIV, v) -#define SET_ESYNR1_APROTNS(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_APROTNS, v) -#define SET_ESYNR1_AINST(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AINST, v) -#define SET_ESYNR1_AWRITE(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AWRITE, v) -#define SET_ESYNR1_ABURST(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ABURST, v) -#define SET_ESYNR1_ALEN(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ALEN, v) -#define SET_ESYNR1_ASIZE(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ASIZE, v) -#define SET_ESYNR1_ALOCK(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ALOCK, v) -#define SET_ESYNR1_AOOO(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AOOO, v) -#define SET_ESYNR1_AFULL(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AFULL, v) -#define SET_ESYNR1_AC(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AC, v) -#define SET_ESYNR1_DCD(b, v) SET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_DCD, v) - - -/* TESTBUSCR */ -#define SET_TBE(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, TBE, v) -#define SET_SPDMBE(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, SPDMBE, v) -#define SET_WGSEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, WGSEL, v) -#define SET_TBLSEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, TBLSEL, v) -#define SET_TBHSEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, TBHSEL, v) -#define SET_SPDM0SEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, SPDM0SEL, v) -#define SET_SPDM1SEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, SPDM1SEL, v) -#define SET_SPDM2SEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, SPDM2SEL, v) -#define SET_SPDM3SEL(b, v) SET_GLOBAL_FIELD(b, TESTBUSCR, SPDM3SEL, v) - - -/* TLBIVMID */ -#define SET_TLBIVMID_VMID(b, v) SET_GLOBAL_FIELD(b, TLBIVMID, TLBIVMID_VMID, v) - - -/* TLBRSW */ -#define SET_TLBRSW_INDEX(b, v) SET_GLOBAL_FIELD(b, TLBRSW, TLBRSW_INDEX, v) -#define SET_TLBBFBS(b, v) SET_GLOBAL_FIELD(b, TLBRSW, TLBBFBS, v) - - -/* TLBTR0 */ -#define SET_PR(b, v) SET_GLOBAL_FIELD(b, TLBTR0, PR, v) -#define SET_PW(b, v) SET_GLOBAL_FIELD(b, TLBTR0, PW, v) -#define SET_UR(b, v) SET_GLOBAL_FIELD(b, TLBTR0, UR, v) -#define SET_UW(b, v) SET_GLOBAL_FIELD(b, TLBTR0, UW, v) -#define SET_XN(b, v) SET_GLOBAL_FIELD(b, TLBTR0, XN, v) -#define SET_NSDESC(b, v) SET_GLOBAL_FIELD(b, TLBTR0, NSDESC, v) -#define SET_ISH(b, v) SET_GLOBAL_FIELD(b, TLBTR0, ISH, v) -#define SET_SH(b, v) SET_GLOBAL_FIELD(b, TLBTR0, SH, v) -#define SET_MT(b, v) SET_GLOBAL_FIELD(b, TLBTR0, MT, v) -#define SET_DPSIZR(b, v) SET_GLOBAL_FIELD(b, TLBTR0, DPSIZR, v) -#define SET_DPSIZC(b, v) SET_GLOBAL_FIELD(b, TLBTR0, DPSIZC, v) - - -/* TLBTR1 */ -#define SET_TLBTR1_VMID(b, v) SET_GLOBAL_FIELD(b, TLBTR1, TLBTR1_VMID, v) -#define SET_TLBTR1_PA(b, v) SET_GLOBAL_FIELD(b, TLBTR1, TLBTR1_PA, v) - - -/* TLBTR2 */ -#define SET_TLBTR2_ASID(b, v) SET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_ASID, v) -#define SET_TLBTR2_V(b, v) SET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_V, v) -#define SET_TLBTR2_NSTID(b, v) SET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_NSTID, v) -#define SET_TLBTR2_NV(b, v) SET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_NV, v) -#define SET_TLBTR2_VA(b, v) SET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_VA, v) - - -/* Global Field Getters */ -/* CBACR_N */ -#define GET_RWVMID(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWVMID) -#define GET_RWE(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWE) -#define GET_RWGE(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), RWGE) -#define GET_CBVMID(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), CBVMID) -#define GET_IRPTNDX(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(CBACR_N), IRPTNDX) - - -/* M2VCBR_N */ -#define GET_VMID(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), VMID) -#define GET_CBNDX(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), CBNDX) -#define GET_BYPASSD(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BYPASSD) -#define GET_BPRCOSH(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCOSH) -#define GET_BPRCISH(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCISH) -#define GET_BPRCNSH(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPRCNSH) -#define GET_BPSHCFG(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPSHCFG) -#define GET_NSCFG(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), NSCFG) -#define GET_BPMTCFG(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPMTCFG) -#define GET_BPMEMTYPE(b, n) GET_GLOBAL_FIELD(b, (n<<2)|(M2VCBR_N), BPMEMTYPE) - - -/* CR */ -#define GET_RPUE(b) GET_GLOBAL_FIELD(b, CR, RPUE) -#define GET_RPUERE(b) GET_GLOBAL_FIELD(b, CR, RPUERE) -#define GET_RPUEIE(b) GET_GLOBAL_FIELD(b, CR, RPUEIE) -#define GET_DCDEE(b) GET_GLOBAL_FIELD(b, CR, DCDEE) -#define GET_CLIENTPD(b) GET_GLOBAL_FIELD(b, CR, CLIENTPD) -#define GET_STALLD(b) GET_GLOBAL_FIELD(b, CR, STALLD) -#define GET_TLBLKCRWE(b) GET_GLOBAL_FIELD(b, CR, TLBLKCRWE) -#define GET_CR_TLBIALLCFG(b) GET_GLOBAL_FIELD(b, CR, CR_TLBIALLCFG) -#define GET_TLBIVMIDCFG(b) GET_GLOBAL_FIELD(b, CR, TLBIVMIDCFG) -#define GET_CR_HUME(b) GET_GLOBAL_FIELD(b, CR, CR_HUME) - - -/* ESR */ -#define GET_CFG(b) GET_GLOBAL_FIELD(b, ESR, CFG) -#define GET_BYPASS(b) GET_GLOBAL_FIELD(b, ESR, BYPASS) -#define GET_ESR_MULTI(b) GET_GLOBAL_FIELD(b, ESR, ESR_MULTI) - - -/* ESYNR0 */ -#define GET_ESYNR0_AMID(b) GET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_AMID) -#define GET_ESYNR0_APID(b) GET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_APID) -#define GET_ESYNR0_ABID(b) GET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_ABID) -#define GET_ESYNR0_AVMID(b) GET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_AVMID) -#define GET_ESYNR0_ATID(b) GET_GLOBAL_FIELD(b, ESYNR0, ESYNR0_ATID) - - -/* ESYNR1 */ -#define GET_ESYNR1_AMEMTYPE(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AMEMTYPE) -#define GET_ESYNR1_ASHARED(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ASHARED) -#define GET_ESYNR1_AINNERSHARED(b) \ - GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AINNERSHARED) -#define GET_ESYNR1_APRIV(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_APRIV) -#define GET_ESYNR1_APROTNS(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_APROTNS) -#define GET_ESYNR1_AINST(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AINST) -#define GET_ESYNR1_AWRITE(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AWRITE) -#define GET_ESYNR1_ABURST(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ABURST) -#define GET_ESYNR1_ALEN(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ALEN) -#define GET_ESYNR1_ASIZE(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ASIZE) -#define GET_ESYNR1_ALOCK(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_ALOCK) -#define GET_ESYNR1_AOOO(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AOOO) -#define GET_ESYNR1_AFULL(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AFULL) -#define GET_ESYNR1_AC(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_AC) -#define GET_ESYNR1_DCD(b) GET_GLOBAL_FIELD(b, ESYNR1, ESYNR1_DCD) - - -/* IDR */ -#define GET_NM2VCBMT(b) GET_GLOBAL_FIELD(b, IDR, NM2VCBMT) -#define GET_HTW(b) GET_GLOBAL_FIELD(b, IDR, HTW) -#define GET_HUM(b) GET_GLOBAL_FIELD(b, IDR, HUM) -#define GET_TLBSIZE(b) GET_GLOBAL_FIELD(b, IDR, TLBSIZE) -#define GET_NCB(b) GET_GLOBAL_FIELD(b, IDR, NCB) -#define GET_NIRPT(b) GET_GLOBAL_FIELD(b, IDR, NIRPT) - - -/* REV */ -#define GET_MAJOR(b) GET_GLOBAL_FIELD(b, REV, MAJOR) -#define GET_MINOR(b) GET_GLOBAL_FIELD(b, REV, MINOR) - - -/* TESTBUSCR */ -#define GET_TBE(b) GET_GLOBAL_FIELD(b, TESTBUSCR, TBE) -#define GET_SPDMBE(b) GET_GLOBAL_FIELD(b, TESTBUSCR, SPDMBE) -#define GET_WGSEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, WGSEL) -#define GET_TBLSEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, TBLSEL) -#define GET_TBHSEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, TBHSEL) -#define GET_SPDM0SEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, SPDM0SEL) -#define GET_SPDM1SEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, SPDM1SEL) -#define GET_SPDM2SEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, SPDM2SEL) -#define GET_SPDM3SEL(b) GET_GLOBAL_FIELD(b, TESTBUSCR, SPDM3SEL) - - -/* TLBIVMID */ -#define GET_TLBIVMID_VMID(b) GET_GLOBAL_FIELD(b, TLBIVMID, TLBIVMID_VMID) - - -/* TLBTR0 */ -#define GET_PR(b) GET_GLOBAL_FIELD(b, TLBTR0, PR) -#define GET_PW(b) GET_GLOBAL_FIELD(b, TLBTR0, PW) -#define GET_UR(b) GET_GLOBAL_FIELD(b, TLBTR0, UR) -#define GET_UW(b) GET_GLOBAL_FIELD(b, TLBTR0, UW) -#define GET_XN(b) GET_GLOBAL_FIELD(b, TLBTR0, XN) -#define GET_NSDESC(b) GET_GLOBAL_FIELD(b, TLBTR0, NSDESC) -#define GET_ISH(b) GET_GLOBAL_FIELD(b, TLBTR0, ISH) -#define GET_SH(b) GET_GLOBAL_FIELD(b, TLBTR0, SH) -#define GET_MT(b) GET_GLOBAL_FIELD(b, TLBTR0, MT) -#define GET_DPSIZR(b) GET_GLOBAL_FIELD(b, TLBTR0, DPSIZR) -#define GET_DPSIZC(b) GET_GLOBAL_FIELD(b, TLBTR0, DPSIZC) - - -/* TLBTR1 */ -#define GET_TLBTR1_VMID(b) GET_GLOBAL_FIELD(b, TLBTR1, TLBTR1_VMID) -#define GET_TLBTR1_PA(b) GET_GLOBAL_FIELD(b, TLBTR1, TLBTR1_PA) - - -/* TLBTR2 */ -#define GET_TLBTR2_ASID(b) GET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_ASID) -#define GET_TLBTR2_V(b) GET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_V) -#define GET_TLBTR2_NSTID(b) GET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_NSTID) -#define GET_TLBTR2_NV(b) GET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_NV) -#define GET_TLBTR2_VA(b) GET_GLOBAL_FIELD(b, TLBTR2, TLBTR2_VA) - - -/* Context Register setters / getters */ -/* Context Register setters */ -/* ACTLR */ -#define SET_CFERE(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, CFERE, v) -#define SET_CFEIE(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, CFEIE, v) -#define SET_PTSHCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, PTSHCFG, v) -#define SET_RCOSH(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, RCOSH, v) -#define SET_RCISH(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, RCISH, v) -#define SET_RCNSH(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, RCNSH, v) -#define SET_PRIVCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, PRIVCFG, v) -#define SET_DNA(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, DNA, v) -#define SET_DNLV2PA(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, DNLV2PA, v) -#define SET_TLBMCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, TLBMCFG, v) -#define SET_CFCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, CFCFG, v) -#define SET_TIPCF(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, TIPCF, v) -#define SET_V2PCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, V2PCFG, v) -#define SET_HUME(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, HUME, v) -#define SET_PTMTCFG(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, PTMTCFG, v) -#define SET_PTMEMTYPE(b, c, v) SET_CONTEXT_FIELD(b, c, ACTLR, PTMEMTYPE, v) - - -/* BFBCR */ -#define SET_BFBDFE(b, c, v) SET_CONTEXT_FIELD(b, c, BFBCR, BFBDFE, v) -#define SET_BFBSFE(b, c, v) SET_CONTEXT_FIELD(b, c, BFBCR, BFBSFE, v) -#define SET_SFVS(b, c, v) SET_CONTEXT_FIELD(b, c, BFBCR, SFVS, v) -#define SET_FLVIC(b, c, v) SET_CONTEXT_FIELD(b, c, BFBCR, FLVIC, v) -#define SET_SLVIC(b, c, v) SET_CONTEXT_FIELD(b, c, BFBCR, SLVIC, v) - - -/* CONTEXTIDR */ -#define SET_CONTEXTIDR_ASID(b, c, v) \ - SET_CONTEXT_FIELD(b, c, CONTEXTIDR, CONTEXTIDR_ASID, v) -#define SET_CONTEXTIDR_PROCID(b, c, v) \ - SET_CONTEXT_FIELD(b, c, CONTEXTIDR, PROCID, v) - - -/* FSR */ -#define SET_TF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, TF, v) -#define SET_AFF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, AFF, v) -#define SET_APF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, APF, v) -#define SET_TLBMF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, TLBMF, v) -#define SET_HTWDEEF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, HTWDEEF, v) -#define SET_HTWSEEF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, HTWSEEF, v) -#define SET_MHF(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, MHF, v) -#define SET_SL(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, SL, v) -#define SET_SS(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, SS, v) -#define SET_MULTI(b, c, v) SET_CONTEXT_FIELD(b, c, FSR, MULTI, v) - - -/* FSYNR0 */ -#define SET_AMID(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR0, AMID, v) -#define SET_APID(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR0, APID, v) -#define SET_ABID(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR0, ABID, v) -#define SET_ATID(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR0, ATID, v) - - -/* FSYNR1 */ -#define SET_AMEMTYPE(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, AMEMTYPE, v) -#define SET_ASHARED(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, ASHARED, v) -#define SET_AINNERSHARED(b, c, v) \ - SET_CONTEXT_FIELD(b, c, FSYNR1, AINNERSHARED, v) -#define SET_APRIV(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, APRIV, v) -#define SET_APROTNS(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, APROTNS, v) -#define SET_AINST(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, AINST, v) -#define SET_AWRITE(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, AWRITE, v) -#define SET_ABURST(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, ABURST, v) -#define SET_ALEN(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, ALEN, v) -#define SET_FSYNR1_ASIZE(b, c, v) \ - SET_CONTEXT_FIELD(b, c, FSYNR1, FSYNR1_ASIZE, v) -#define SET_ALOCK(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, ALOCK, v) -#define SET_AFULL(b, c, v) SET_CONTEXT_FIELD(b, c, FSYNR1, AFULL, v) - - -/* NMRR */ -#define SET_ICPC0(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC0, v) -#define SET_ICPC1(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC1, v) -#define SET_ICPC2(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC2, v) -#define SET_ICPC3(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC3, v) -#define SET_ICPC4(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC4, v) -#define SET_ICPC5(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC5, v) -#define SET_ICPC6(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC6, v) -#define SET_ICPC7(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, ICPC7, v) -#define SET_OCPC0(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC0, v) -#define SET_OCPC1(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC1, v) -#define SET_OCPC2(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC2, v) -#define SET_OCPC3(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC3, v) -#define SET_OCPC4(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC4, v) -#define SET_OCPC5(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC5, v) -#define SET_OCPC6(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC6, v) -#define SET_OCPC7(b, c, v) SET_CONTEXT_FIELD(b, c, NMRR, OCPC7, v) - - -/* PAR */ -#define SET_FAULT(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT, v) - -#define SET_FAULT_TF(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_TF, v) -#define SET_FAULT_AFF(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_AFF, v) -#define SET_FAULT_APF(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_APF, v) -#define SET_FAULT_TLBMF(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_TLBMF, v) -#define SET_FAULT_HTWDEEF(b, c, v) \ - SET_CONTEXT_FIELD(b, c, PAR, FAULT_HTWDEEF, v) -#define SET_FAULT_HTWSEEF(b, c, v) \ - SET_CONTEXT_FIELD(b, c, PAR, FAULT_HTWSEEF, v) -#define SET_FAULT_MHF(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_MHF, v) -#define SET_FAULT_SL(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_SL, v) -#define SET_FAULT_SS(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, FAULT_SS, v) - -#define SET_NOFAULT_SS(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NOFAULT_SS, v) -#define SET_NOFAULT_MT(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NOFAULT_MT, v) -#define SET_NOFAULT_SH(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NOFAULT_SH, v) -#define SET_NOFAULT_NS(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NOFAULT_NS, v) -#define SET_NOFAULT_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NOFAULT_NOS, v) -#define SET_NPFAULT_PA(b, c, v) SET_CONTEXT_FIELD(b, c, PAR, NPFAULT_PA, v) - - -/* PRRR */ -#define SET_MTC0(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC0, v) -#define SET_MTC1(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC1, v) -#define SET_MTC2(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC2, v) -#define SET_MTC3(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC3, v) -#define SET_MTC4(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC4, v) -#define SET_MTC5(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC5, v) -#define SET_MTC6(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC6, v) -#define SET_MTC7(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, MTC7, v) -#define SET_SHDSH0(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, SHDSH0, v) -#define SET_SHDSH1(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, SHDSH1, v) -#define SET_SHNMSH0(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, SHNMSH0, v) -#define SET_SHNMSH1(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, SHNMSH1, v) -#define SET_NOS0(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS0, v) -#define SET_NOS1(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS1, v) -#define SET_NOS2(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS2, v) -#define SET_NOS3(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS3, v) -#define SET_NOS4(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS4, v) -#define SET_NOS5(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS5, v) -#define SET_NOS6(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS6, v) -#define SET_NOS7(b, c, v) SET_CONTEXT_FIELD(b, c, PRRR, NOS7, v) - - -/* RESUME */ -#define SET_TNR(b, c, v) SET_CONTEXT_FIELD(b, c, RESUME, TNR, v) - - -/* SCTLR */ -#define SET_M(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, M, v) -#define SET_TRE(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, TRE, v) -#define SET_AFE(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, AFE, v) -#define SET_HAF(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, HAF, v) -#define SET_BE(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, BE, v) -#define SET_AFFD(b, c, v) SET_CONTEXT_FIELD(b, c, SCTLR, AFFD, v) - - -/* TLBLKCR */ -#define SET_LKE(b, c, v) SET_CONTEXT_FIELD(b, c, TLBLKCR, LKE, v) -#define SET_TLBLKCR_TLBIALLCFG(b, c, v) \ - SET_CONTEXT_FIELD(b, c, TLBLKCR, TLBLCKR_TLBIALLCFG, v) -#define SET_TLBIASIDCFG(b, c, v) \ - SET_CONTEXT_FIELD(b, c, TLBLKCR, TLBIASIDCFG, v) -#define SET_TLBIVAACFG(b, c, v) SET_CONTEXT_FIELD(b, c, TLBLKCR, TLBIVAACFG, v) -#define SET_FLOOR(b, c, v) SET_CONTEXT_FIELD(b, c, TLBLKCR, FLOOR, v) -#define SET_VICTIM(b, c, v) SET_CONTEXT_FIELD(b, c, TLBLKCR, VICTIM, v) - - -/* TTBCR */ -#define SET_N(b, c, v) SET_CONTEXT_FIELD(b, c, TTBCR, N, v) -#define SET_PD0(b, c, v) SET_CONTEXT_FIELD(b, c, TTBCR, PD0, v) -#define SET_PD1(b, c, v) SET_CONTEXT_FIELD(b, c, TTBCR, PD1, v) - - -/* TTBR0 */ -#define SET_TTBR0_IRGNH(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_IRGNH, v) -#define SET_TTBR0_SH(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_SH, v) -#define SET_TTBR0_ORGN(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_ORGN, v) -#define SET_TTBR0_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_NOS, v) -#define SET_TTBR0_IRGNL(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_IRGNL, v) -#define SET_TTBR0_PA(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_PA, v) - - -/* TTBR1 */ -#define SET_TTBR1_IRGNH(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_IRGNH, v) -#define SET_TTBR1_SH(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_SH, v) -#define SET_TTBR1_ORGN(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_ORGN, v) -#define SET_TTBR1_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_NOS, v) -#define SET_TTBR1_IRGNL(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_IRGNL, v) -#define SET_TTBR1_PA(b, c, v) SET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_PA, v) - - -/* V2PSR */ -#define SET_HIT(b, c, v) SET_CONTEXT_FIELD(b, c, V2PSR, HIT, v) -#define SET_INDEX(b, c, v) SET_CONTEXT_FIELD(b, c, V2PSR, INDEX, v) - - -/* Context Register getters */ -/* ACTLR */ -#define GET_CFERE(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, CFERE) -#define GET_CFEIE(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, CFEIE) -#define GET_PTSHCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, PTSHCFG) -#define GET_RCOSH(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, RCOSH) -#define GET_RCISH(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, RCISH) -#define GET_RCNSH(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, RCNSH) -#define GET_PRIVCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, PRIVCFG) -#define GET_DNA(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, DNA) -#define GET_DNLV2PA(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, DNLV2PA) -#define GET_TLBMCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, TLBMCFG) -#define GET_CFCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, CFCFG) -#define GET_TIPCF(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, TIPCF) -#define GET_V2PCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, V2PCFG) -#define GET_HUME(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, HUME) -#define GET_PTMTCFG(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, PTMTCFG) -#define GET_PTMEMTYPE(b, c) GET_CONTEXT_FIELD(b, c, ACTLR, PTMEMTYPE) - -/* BFBCR */ -#define GET_BFBDFE(b, c) GET_CONTEXT_FIELD(b, c, BFBCR, BFBDFE) -#define GET_BFBSFE(b, c) GET_CONTEXT_FIELD(b, c, BFBCR, BFBSFE) -#define GET_SFVS(b, c) GET_CONTEXT_FIELD(b, c, BFBCR, SFVS) -#define GET_FLVIC(b, c) GET_CONTEXT_FIELD(b, c, BFBCR, FLVIC) -#define GET_SLVIC(b, c) GET_CONTEXT_FIELD(b, c, BFBCR, SLVIC) - - -/* CONTEXTIDR */ -#define GET_CONTEXTIDR_ASID(b, c) \ - GET_CONTEXT_FIELD(b, c, CONTEXTIDR, CONTEXTIDR_ASID) -#define GET_CONTEXTIDR_PROCID(b, c) GET_CONTEXT_FIELD(b, c, CONTEXTIDR, PROCID) - - -/* FSR */ -#define GET_TF(b, c) GET_CONTEXT_FIELD(b, c, FSR, TF) -#define GET_AFF(b, c) GET_CONTEXT_FIELD(b, c, FSR, AFF) -#define GET_APF(b, c) GET_CONTEXT_FIELD(b, c, FSR, APF) -#define GET_TLBMF(b, c) GET_CONTEXT_FIELD(b, c, FSR, TLBMF) -#define GET_HTWDEEF(b, c) GET_CONTEXT_FIELD(b, c, FSR, HTWDEEF) -#define GET_HTWSEEF(b, c) GET_CONTEXT_FIELD(b, c, FSR, HTWSEEF) -#define GET_MHF(b, c) GET_CONTEXT_FIELD(b, c, FSR, MHF) -#define GET_SL(b, c) GET_CONTEXT_FIELD(b, c, FSR, SL) -#define GET_SS(b, c) GET_CONTEXT_FIELD(b, c, FSR, SS) -#define GET_MULTI(b, c) GET_CONTEXT_FIELD(b, c, FSR, MULTI) - - -/* FSYNR0 */ -#define GET_AMID(b, c) GET_CONTEXT_FIELD(b, c, FSYNR0, AMID) -#define GET_APID(b, c) GET_CONTEXT_FIELD(b, c, FSYNR0, APID) -#define GET_ABID(b, c) GET_CONTEXT_FIELD(b, c, FSYNR0, ABID) -#define GET_ATID(b, c) GET_CONTEXT_FIELD(b, c, FSYNR0, ATID) - - -/* FSYNR1 */ -#define GET_AMEMTYPE(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, AMEMTYPE) -#define GET_ASHARED(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, ASHARED) -#define GET_AINNERSHARED(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, AINNERSHARED) -#define GET_APRIV(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, APRIV) -#define GET_APROTNS(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, APROTNS) -#define GET_AINST(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, AINST) -#define GET_AWRITE(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, AWRITE) -#define GET_ABURST(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, ABURST) -#define GET_ALEN(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, ALEN) -#define GET_FSYNR1_ASIZE(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, FSYNR1_ASIZE) -#define GET_ALOCK(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, ALOCK) -#define GET_AFULL(b, c) GET_CONTEXT_FIELD(b, c, FSYNR1, AFULL) - - -/* NMRR */ -#define GET_ICPC0(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC0) -#define GET_ICPC1(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC1) -#define GET_ICPC2(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC2) -#define GET_ICPC3(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC3) -#define GET_ICPC4(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC4) -#define GET_ICPC5(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC5) -#define GET_ICPC6(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC6) -#define GET_ICPC7(b, c) GET_CONTEXT_FIELD(b, c, NMRR, ICPC7) -#define GET_OCPC0(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC0) -#define GET_OCPC1(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC1) -#define GET_OCPC2(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC2) -#define GET_OCPC3(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC3) -#define GET_OCPC4(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC4) -#define GET_OCPC5(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC5) -#define GET_OCPC6(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC6) -#define GET_OCPC7(b, c) GET_CONTEXT_FIELD(b, c, NMRR, OCPC7) -#define NMRR_ICP(nmrr, n) (((nmrr) & (3 << ((n) * 2))) >> ((n) * 2)) -#define NMRR_OCP(nmrr, n) (((nmrr) & (3 << ((n) * 2 + 16))) >> \ - ((n) * 2 + 16)) - -/* PAR */ -#define GET_FAULT(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT) - -#define GET_FAULT_TF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_TF) -#define GET_FAULT_AFF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_AFF) -#define GET_FAULT_APF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_APF) -#define GET_FAULT_TLBMF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_TLBMF) -#define GET_FAULT_HTWDEEF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_HTWDEEF) -#define GET_FAULT_HTWSEEF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_HTWSEEF) -#define GET_FAULT_MHF(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_MHF) -#define GET_FAULT_SL(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_SL) -#define GET_FAULT_SS(b, c) GET_CONTEXT_FIELD(b, c, PAR, FAULT_SS) - -#define GET_NOFAULT_SS(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NOFAULT_SS) -#define GET_NOFAULT_MT(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NOFAULT_MT) -#define GET_NOFAULT_SH(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NOFAULT_SH) -#define GET_NOFAULT_NS(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NOFAULT_NS) -#define GET_NOFAULT_NOS(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NOFAULT_NOS) -#define GET_NPFAULT_PA(b, c) GET_CONTEXT_FIELD(b, c, PAR, PAR_NPFAULT_PA) - - -/* PRRR */ -#define GET_MTC0(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC0) -#define GET_MTC1(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC1) -#define GET_MTC2(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC2) -#define GET_MTC3(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC3) -#define GET_MTC4(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC4) -#define GET_MTC5(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC5) -#define GET_MTC6(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC6) -#define GET_MTC7(b, c) GET_CONTEXT_FIELD(b, c, PRRR, MTC7) -#define GET_SHDSH0(b, c) GET_CONTEXT_FIELD(b, c, PRRR, SHDSH0) -#define GET_SHDSH1(b, c) GET_CONTEXT_FIELD(b, c, PRRR, SHDSH1) -#define GET_SHNMSH0(b, c) GET_CONTEXT_FIELD(b, c, PRRR, SHNMSH0) -#define GET_SHNMSH1(b, c) GET_CONTEXT_FIELD(b, c, PRRR, SHNMSH1) -#define GET_NOS0(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS0) -#define GET_NOS1(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS1) -#define GET_NOS2(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS2) -#define GET_NOS3(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS3) -#define GET_NOS4(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS4) -#define GET_NOS5(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS5) -#define GET_NOS6(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS6) -#define GET_NOS7(b, c) GET_CONTEXT_FIELD(b, c, PRRR, NOS7) -#define PRRR_NOS(prrr, n) ((prrr) & (1 << ((n) + 24)) ? 1 : 0) -#define PRRR_MT(prrr, n) ((((prrr) & (3 << ((n) * 2))) >> ((n) * 2))) - - -/* RESUME */ -#define GET_TNR(b, c) GET_CONTEXT_FIELD(b, c, RESUME, TNR) - - -/* SCTLR */ -#define GET_M(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, M) -#define GET_TRE(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, TRE) -#define GET_AFE(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, AFE) -#define GET_HAF(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, HAF) -#define GET_BE(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, BE) -#define GET_AFFD(b, c) GET_CONTEXT_FIELD(b, c, SCTLR, AFFD) - - -/* TLBLKCR */ -#define GET_LKE(b, c) GET_CONTEXT_FIELD(b, c, TLBLKCR, LKE) -#define GET_TLBLCKR_TLBIALLCFG(b, c) \ - GET_CONTEXT_FIELD(b, c, TLBLKCR, TLBLCKR_TLBIALLCFG) -#define GET_TLBIASIDCFG(b, c) GET_CONTEXT_FIELD(b, c, TLBLKCR, TLBIASIDCFG) -#define GET_TLBIVAACFG(b, c) GET_CONTEXT_FIELD(b, c, TLBLKCR, TLBIVAACFG) -#define GET_FLOOR(b, c) GET_CONTEXT_FIELD(b, c, TLBLKCR, FLOOR) -#define GET_VICTIM(b, c) GET_CONTEXT_FIELD(b, c, TLBLKCR, VICTIM) - - -/* TTBCR */ -#define GET_N(b, c) GET_CONTEXT_FIELD(b, c, TTBCR, N) -#define GET_PD0(b, c) GET_CONTEXT_FIELD(b, c, TTBCR, PD0) -#define GET_PD1(b, c) GET_CONTEXT_FIELD(b, c, TTBCR, PD1) - - -/* TTBR0 */ -#define GET_TTBR0_IRGNH(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_IRGNH) -#define GET_TTBR0_SH(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_SH) -#define GET_TTBR0_ORGN(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_ORGN) -#define GET_TTBR0_NOS(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_NOS) -#define GET_TTBR0_IRGNL(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_IRGNL) -#define GET_TTBR0_PA(b, c) GET_CONTEXT_FIELD(b, c, TTBR0, TTBR0_PA) - - -/* TTBR1 */ -#define GET_TTBR1_IRGNH(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_IRGNH) -#define GET_TTBR1_SH(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_SH) -#define GET_TTBR1_ORGN(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_ORGN) -#define GET_TTBR1_NOS(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_NOS) -#define GET_TTBR1_IRGNL(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_IRGNL) -#define GET_TTBR1_PA(b, c) GET_CONTEXT_FIELD(b, c, TTBR1, TTBR1_PA) - - -/* V2PSR */ -#define GET_HIT(b, c) GET_CONTEXT_FIELD(b, c, V2PSR, HIT) -#define GET_INDEX(b, c) GET_CONTEXT_FIELD(b, c, V2PSR, INDEX) - - -/* Global Registers */ -#define M2VCBR_N (0xFF000) -#define CBACR_N (0xFF800) -#define TLBRSW (0xFFE00) -#define TLBTR0 (0xFFE80) -#define TLBTR1 (0xFFE84) -#define TLBTR2 (0xFFE88) -#define TESTBUSCR (0xFFE8C) -#define GLOBAL_TLBIALL (0xFFF00) -#define TLBIVMID (0xFFF04) -#define CR (0xFFF80) -#define EAR (0xFFF84) -#define ESR (0xFFF88) -#define ESRRESTORE (0xFFF8C) -#define ESYNR0 (0xFFF90) -#define ESYNR1 (0xFFF94) -#define REV (0xFFFF4) -#define IDR (0xFFFF8) -#define RPU_ACR (0xFFFFC) - - -/* Context Bank Registers */ -#define SCTLR (0x000) -#define ACTLR (0x004) -#define CONTEXTIDR (0x008) -#define TTBR0 (0x010) -#define TTBR1 (0x014) -#define TTBCR (0x018) -#define PAR (0x01C) -#define FSR (0x020) -#define FSRRESTORE (0x024) -#define FAR (0x028) -#define FSYNR0 (0x02C) -#define FSYNR1 (0x030) -#define PRRR (0x034) -#define NMRR (0x038) -#define TLBLCKR (0x03C) -#define V2PSR (0x040) -#define TLBFLPTER (0x044) -#define TLBSLPTER (0x048) -#define BFBCR (0x04C) -#define CTX_TLBIALL (0x800) -#define TLBIASID (0x804) -#define TLBIVA (0x808) -#define TLBIVAA (0x80C) -#define V2PPR (0x810) -#define V2PPW (0x814) -#define V2PUR (0x818) -#define V2PUW (0x81C) -#define RESUME (0x820) - - -/* Global Register Fields */ -/* CBACRn */ -#define RWVMID (RWVMID_MASK << RWVMID_SHIFT) -#define RWE (RWE_MASK << RWE_SHIFT) -#define RWGE (RWGE_MASK << RWGE_SHIFT) -#define CBVMID (CBVMID_MASK << CBVMID_SHIFT) -#define IRPTNDX (IRPTNDX_MASK << IRPTNDX_SHIFT) - - -/* CR */ -#define RPUE (RPUE_MASK << RPUE_SHIFT) -#define RPUERE (RPUERE_MASK << RPUERE_SHIFT) -#define RPUEIE (RPUEIE_MASK << RPUEIE_SHIFT) -#define DCDEE (DCDEE_MASK << DCDEE_SHIFT) -#define CLIENTPD (CLIENTPD_MASK << CLIENTPD_SHIFT) -#define STALLD (STALLD_MASK << STALLD_SHIFT) -#define TLBLKCRWE (TLBLKCRWE_MASK << TLBLKCRWE_SHIFT) -#define CR_TLBIALLCFG (CR_TLBIALLCFG_MASK << CR_TLBIALLCFG_SHIFT) -#define TLBIVMIDCFG (TLBIVMIDCFG_MASK << TLBIVMIDCFG_SHIFT) -#define CR_HUME (CR_HUME_MASK << CR_HUME_SHIFT) - - -/* ESR */ -#define CFG (CFG_MASK << CFG_SHIFT) -#define BYPASS (BYPASS_MASK << BYPASS_SHIFT) -#define ESR_MULTI (ESR_MULTI_MASK << ESR_MULTI_SHIFT) - - -/* ESYNR0 */ -#define ESYNR0_AMID (ESYNR0_AMID_MASK << ESYNR0_AMID_SHIFT) -#define ESYNR0_APID (ESYNR0_APID_MASK << ESYNR0_APID_SHIFT) -#define ESYNR0_ABID (ESYNR0_ABID_MASK << ESYNR0_ABID_SHIFT) -#define ESYNR0_AVMID (ESYNR0_AVMID_MASK << ESYNR0_AVMID_SHIFT) -#define ESYNR0_ATID (ESYNR0_ATID_MASK << ESYNR0_ATID_SHIFT) - - -/* ESYNR1 */ -#define ESYNR1_AMEMTYPE (ESYNR1_AMEMTYPE_MASK << ESYNR1_AMEMTYPE_SHIFT) -#define ESYNR1_ASHARED (ESYNR1_ASHARED_MASK << ESYNR1_ASHARED_SHIFT) -#define ESYNR1_AINNERSHARED (ESYNR1_AINNERSHARED_MASK<< \ - ESYNR1_AINNERSHARED_SHIFT) -#define ESYNR1_APRIV (ESYNR1_APRIV_MASK << ESYNR1_APRIV_SHIFT) -#define ESYNR1_APROTNS (ESYNR1_APROTNS_MASK << ESYNR1_APROTNS_SHIFT) -#define ESYNR1_AINST (ESYNR1_AINST_MASK << ESYNR1_AINST_SHIFT) -#define ESYNR1_AWRITE (ESYNR1_AWRITE_MASK << ESYNR1_AWRITE_SHIFT) -#define ESYNR1_ABURST (ESYNR1_ABURST_MASK << ESYNR1_ABURST_SHIFT) -#define ESYNR1_ALEN (ESYNR1_ALEN_MASK << ESYNR1_ALEN_SHIFT) -#define ESYNR1_ASIZE (ESYNR1_ASIZE_MASK << ESYNR1_ASIZE_SHIFT) -#define ESYNR1_ALOCK (ESYNR1_ALOCK_MASK << ESYNR1_ALOCK_SHIFT) -#define ESYNR1_AOOO (ESYNR1_AOOO_MASK << ESYNR1_AOOO_SHIFT) -#define ESYNR1_AFULL (ESYNR1_AFULL_MASK << ESYNR1_AFULL_SHIFT) -#define ESYNR1_AC (ESYNR1_AC_MASK << ESYNR1_AC_SHIFT) -#define ESYNR1_DCD (ESYNR1_DCD_MASK << ESYNR1_DCD_SHIFT) - - -/* IDR */ -#define NM2VCBMT (NM2VCBMT_MASK << NM2VCBMT_SHIFT) -#define HTW (HTW_MASK << HTW_SHIFT) -#define HUM (HUM_MASK << HUM_SHIFT) -#define TLBSIZE (TLBSIZE_MASK << TLBSIZE_SHIFT) -#define NCB (NCB_MASK << NCB_SHIFT) -#define NIRPT (NIRPT_MASK << NIRPT_SHIFT) - - -/* M2VCBRn */ -#define VMID (VMID_MASK << VMID_SHIFT) -#define CBNDX (CBNDX_MASK << CBNDX_SHIFT) -#define BYPASSD (BYPASSD_MASK << BYPASSD_SHIFT) -#define BPRCOSH (BPRCOSH_MASK << BPRCOSH_SHIFT) -#define BPRCISH (BPRCISH_MASK << BPRCISH_SHIFT) -#define BPRCNSH (BPRCNSH_MASK << BPRCNSH_SHIFT) -#define BPSHCFG (BPSHCFG_MASK << BPSHCFG_SHIFT) -#define NSCFG (NSCFG_MASK << NSCFG_SHIFT) -#define BPMTCFG (BPMTCFG_MASK << BPMTCFG_SHIFT) -#define BPMEMTYPE (BPMEMTYPE_MASK << BPMEMTYPE_SHIFT) - - -/* REV */ -#define IDR_MINOR (MINOR_MASK << MINOR_SHIFT) -#define IDR_MAJOR (MAJOR_MASK << MAJOR_SHIFT) - - -/* TESTBUSCR */ -#define TBE (TBE_MASK << TBE_SHIFT) -#define SPDMBE (SPDMBE_MASK << SPDMBE_SHIFT) -#define WGSEL (WGSEL_MASK << WGSEL_SHIFT) -#define TBLSEL (TBLSEL_MASK << TBLSEL_SHIFT) -#define TBHSEL (TBHSEL_MASK << TBHSEL_SHIFT) -#define SPDM0SEL (SPDM0SEL_MASK << SPDM0SEL_SHIFT) -#define SPDM1SEL (SPDM1SEL_MASK << SPDM1SEL_SHIFT) -#define SPDM2SEL (SPDM2SEL_MASK << SPDM2SEL_SHIFT) -#define SPDM3SEL (SPDM3SEL_MASK << SPDM3SEL_SHIFT) - - -/* TLBIVMID */ -#define TLBIVMID_VMID (TLBIVMID_VMID_MASK << TLBIVMID_VMID_SHIFT) - - -/* TLBRSW */ -#define TLBRSW_INDEX (TLBRSW_INDEX_MASK << TLBRSW_INDEX_SHIFT) -#define TLBBFBS (TLBBFBS_MASK << TLBBFBS_SHIFT) - - -/* TLBTR0 */ -#define PR (PR_MASK << PR_SHIFT) -#define PW (PW_MASK << PW_SHIFT) -#define UR (UR_MASK << UR_SHIFT) -#define UW (UW_MASK << UW_SHIFT) -#define XN (XN_MASK << XN_SHIFT) -#define NSDESC (NSDESC_MASK << NSDESC_SHIFT) -#define ISH (ISH_MASK << ISH_SHIFT) -#define SH (SH_MASK << SH_SHIFT) -#define MT (MT_MASK << MT_SHIFT) -#define DPSIZR (DPSIZR_MASK << DPSIZR_SHIFT) -#define DPSIZC (DPSIZC_MASK << DPSIZC_SHIFT) - - -/* TLBTR1 */ -#define TLBTR1_VMID (TLBTR1_VMID_MASK << TLBTR1_VMID_SHIFT) -#define TLBTR1_PA (TLBTR1_PA_MASK << TLBTR1_PA_SHIFT) - - -/* TLBTR2 */ -#define TLBTR2_ASID (TLBTR2_ASID_MASK << TLBTR2_ASID_SHIFT) -#define TLBTR2_V (TLBTR2_V_MASK << TLBTR2_V_SHIFT) -#define TLBTR2_NSTID (TLBTR2_NSTID_MASK << TLBTR2_NSTID_SHIFT) -#define TLBTR2_NV (TLBTR2_NV_MASK << TLBTR2_NV_SHIFT) -#define TLBTR2_VA (TLBTR2_VA_MASK << TLBTR2_VA_SHIFT) - - -/* Context Register Fields */ -/* ACTLR */ -#define CFERE (CFERE_MASK << CFERE_SHIFT) -#define CFEIE (CFEIE_MASK << CFEIE_SHIFT) -#define PTSHCFG (PTSHCFG_MASK << PTSHCFG_SHIFT) -#define RCOSH (RCOSH_MASK << RCOSH_SHIFT) -#define RCISH (RCISH_MASK << RCISH_SHIFT) -#define RCNSH (RCNSH_MASK << RCNSH_SHIFT) -#define PRIVCFG (PRIVCFG_MASK << PRIVCFG_SHIFT) -#define DNA (DNA_MASK << DNA_SHIFT) -#define DNLV2PA (DNLV2PA_MASK << DNLV2PA_SHIFT) -#define TLBMCFG (TLBMCFG_MASK << TLBMCFG_SHIFT) -#define CFCFG (CFCFG_MASK << CFCFG_SHIFT) -#define TIPCF (TIPCF_MASK << TIPCF_SHIFT) -#define V2PCFG (V2PCFG_MASK << V2PCFG_SHIFT) -#define HUME (HUME_MASK << HUME_SHIFT) -#define PTMTCFG (PTMTCFG_MASK << PTMTCFG_SHIFT) -#define PTMEMTYPE (PTMEMTYPE_MASK << PTMEMTYPE_SHIFT) - - -/* BFBCR */ -#define BFBDFE (BFBDFE_MASK << BFBDFE_SHIFT) -#define BFBSFE (BFBSFE_MASK << BFBSFE_SHIFT) -#define SFVS (SFVS_MASK << SFVS_SHIFT) -#define FLVIC (FLVIC_MASK << FLVIC_SHIFT) -#define SLVIC (SLVIC_MASK << SLVIC_SHIFT) - - -/* CONTEXTIDR */ -#define CONTEXTIDR_ASID (CONTEXTIDR_ASID_MASK << CONTEXTIDR_ASID_SHIFT) -#define PROCID (PROCID_MASK << PROCID_SHIFT) - - -/* FSR */ -#define TF (TF_MASK << TF_SHIFT) -#define AFF (AFF_MASK << AFF_SHIFT) -#define APF (APF_MASK << APF_SHIFT) -#define TLBMF (TLBMF_MASK << TLBMF_SHIFT) -#define HTWDEEF (HTWDEEF_MASK << HTWDEEF_SHIFT) -#define HTWSEEF (HTWSEEF_MASK << HTWSEEF_SHIFT) -#define MHF (MHF_MASK << MHF_SHIFT) -#define SL (SL_MASK << SL_SHIFT) -#define SS (SS_MASK << SS_SHIFT) -#define MULTI (MULTI_MASK << MULTI_SHIFT) - - -/* FSYNR0 */ -#define AMID (AMID_MASK << AMID_SHIFT) -#define APID (APID_MASK << APID_SHIFT) -#define ABID (ABID_MASK << ABID_SHIFT) -#define ATID (ATID_MASK << ATID_SHIFT) - - -/* FSYNR1 */ -#define AMEMTYPE (AMEMTYPE_MASK << AMEMTYPE_SHIFT) -#define ASHARED (ASHARED_MASK << ASHARED_SHIFT) -#define AINNERSHARED (AINNERSHARED_MASK << AINNERSHARED_SHIFT) -#define APRIV (APRIV_MASK << APRIV_SHIFT) -#define APROTNS (APROTNS_MASK << APROTNS_SHIFT) -#define AINST (AINST_MASK << AINST_SHIFT) -#define AWRITE (AWRITE_MASK << AWRITE_SHIFT) -#define ABURST (ABURST_MASK << ABURST_SHIFT) -#define ALEN (ALEN_MASK << ALEN_SHIFT) -#define FSYNR1_ASIZE (FSYNR1_ASIZE_MASK << FSYNR1_ASIZE_SHIFT) -#define ALOCK (ALOCK_MASK << ALOCK_SHIFT) -#define AFULL (AFULL_MASK << AFULL_SHIFT) - - -/* NMRR */ -#define ICPC0 (ICPC0_MASK << ICPC0_SHIFT) -#define ICPC1 (ICPC1_MASK << ICPC1_SHIFT) -#define ICPC2 (ICPC2_MASK << ICPC2_SHIFT) -#define ICPC3 (ICPC3_MASK << ICPC3_SHIFT) -#define ICPC4 (ICPC4_MASK << ICPC4_SHIFT) -#define ICPC5 (ICPC5_MASK << ICPC5_SHIFT) -#define ICPC6 (ICPC6_MASK << ICPC6_SHIFT) -#define ICPC7 (ICPC7_MASK << ICPC7_SHIFT) -#define OCPC0 (OCPC0_MASK << OCPC0_SHIFT) -#define OCPC1 (OCPC1_MASK << OCPC1_SHIFT) -#define OCPC2 (OCPC2_MASK << OCPC2_SHIFT) -#define OCPC3 (OCPC3_MASK << OCPC3_SHIFT) -#define OCPC4 (OCPC4_MASK << OCPC4_SHIFT) -#define OCPC5 (OCPC5_MASK << OCPC5_SHIFT) -#define OCPC6 (OCPC6_MASK << OCPC6_SHIFT) -#define OCPC7 (OCPC7_MASK << OCPC7_SHIFT) - - -/* PAR */ -#define FAULT (FAULT_MASK << FAULT_SHIFT) -/* If a fault is present, these are the -same as the fault fields in the FAR */ -#define FAULT_TF (FAULT_TF_MASK << FAULT_TF_SHIFT) -#define FAULT_AFF (FAULT_AFF_MASK << FAULT_AFF_SHIFT) -#define FAULT_APF (FAULT_APF_MASK << FAULT_APF_SHIFT) -#define FAULT_TLBMF (FAULT_TLBMF_MASK << FAULT_TLBMF_SHIFT) -#define FAULT_HTWDEEF (FAULT_HTWDEEF_MASK << FAULT_HTWDEEF_SHIFT) -#define FAULT_HTWSEEF (FAULT_HTWSEEF_MASK << FAULT_HTWSEEF_SHIFT) -#define FAULT_MHF (FAULT_MHF_MASK << FAULT_MHF_SHIFT) -#define FAULT_SL (FAULT_SL_MASK << FAULT_SL_SHIFT) -#define FAULT_SS (FAULT_SS_MASK << FAULT_SS_SHIFT) - -/* If NO fault is present, the following fields are in effect */ -/* (FAULT remains as before) */ -#define PAR_NOFAULT_SS (PAR_NOFAULT_SS_MASK << PAR_NOFAULT_SS_SHIFT) -#define PAR_NOFAULT_MT (PAR_NOFAULT_MT_MASK << PAR_NOFAULT_MT_SHIFT) -#define PAR_NOFAULT_SH (PAR_NOFAULT_SH_MASK << PAR_NOFAULT_SH_SHIFT) -#define PAR_NOFAULT_NS (PAR_NOFAULT_NS_MASK << PAR_NOFAULT_NS_SHIFT) -#define PAR_NOFAULT_NOS (PAR_NOFAULT_NOS_MASK << PAR_NOFAULT_NOS_SHIFT) -#define PAR_NPFAULT_PA (PAR_NPFAULT_PA_MASK << PAR_NPFAULT_PA_SHIFT) - - -/* PRRR */ -#define MTC0 (MTC0_MASK << MTC0_SHIFT) -#define MTC1 (MTC1_MASK << MTC1_SHIFT) -#define MTC2 (MTC2_MASK << MTC2_SHIFT) -#define MTC3 (MTC3_MASK << MTC3_SHIFT) -#define MTC4 (MTC4_MASK << MTC4_SHIFT) -#define MTC5 (MTC5_MASK << MTC5_SHIFT) -#define MTC6 (MTC6_MASK << MTC6_SHIFT) -#define MTC7 (MTC7_MASK << MTC7_SHIFT) -#define SHDSH0 (SHDSH0_MASK << SHDSH0_SHIFT) -#define SHDSH1 (SHDSH1_MASK << SHDSH1_SHIFT) -#define SHNMSH0 (SHNMSH0_MASK << SHNMSH0_SHIFT) -#define SHNMSH1 (SHNMSH1_MASK << SHNMSH1_SHIFT) -#define NOS0 (NOS0_MASK << NOS0_SHIFT) -#define NOS1 (NOS1_MASK << NOS1_SHIFT) -#define NOS2 (NOS2_MASK << NOS2_SHIFT) -#define NOS3 (NOS3_MASK << NOS3_SHIFT) -#define NOS4 (NOS4_MASK << NOS4_SHIFT) -#define NOS5 (NOS5_MASK << NOS5_SHIFT) -#define NOS6 (NOS6_MASK << NOS6_SHIFT) -#define NOS7 (NOS7_MASK << NOS7_SHIFT) - - -/* RESUME */ -#define TNR (TNR_MASK << TNR_SHIFT) - - -/* SCTLR */ -#define M (M_MASK << M_SHIFT) -#define TRE (TRE_MASK << TRE_SHIFT) -#define AFE (AFE_MASK << AFE_SHIFT) -#define HAF (HAF_MASK << HAF_SHIFT) -#define BE (BE_MASK << BE_SHIFT) -#define AFFD (AFFD_MASK << AFFD_SHIFT) - - -/* TLBIASID */ -#define TLBIASID_ASID (TLBIASID_ASID_MASK << TLBIASID_ASID_SHIFT) - - -/* TLBIVA */ -#define TLBIVA_ASID (TLBIVA_ASID_MASK << TLBIVA_ASID_SHIFT) -#define TLBIVA_VA (TLBIVA_VA_MASK << TLBIVA_VA_SHIFT) - - -/* TLBIVAA */ -#define TLBIVAA_VA (TLBIVAA_VA_MASK << TLBIVAA_VA_SHIFT) - - -/* TLBLCKR */ -#define LKE (LKE_MASK << LKE_SHIFT) -#define TLBLCKR_TLBIALLCFG (TLBLCKR_TLBIALLCFG_MASK<<TLBLCKR_TLBIALLCFG_SHIFT) -#define TLBIASIDCFG (TLBIASIDCFG_MASK << TLBIASIDCFG_SHIFT) -#define TLBIVAACFG (TLBIVAACFG_MASK << TLBIVAACFG_SHIFT) -#define FLOOR (FLOOR_MASK << FLOOR_SHIFT) -#define VICTIM (VICTIM_MASK << VICTIM_SHIFT) - - -/* TTBCR */ -#define N (N_MASK << N_SHIFT) -#define PD0 (PD0_MASK << PD0_SHIFT) -#define PD1 (PD1_MASK << PD1_SHIFT) - - -/* TTBR0 */ -#define TTBR0_IRGNH (TTBR0_IRGNH_MASK << TTBR0_IRGNH_SHIFT) -#define TTBR0_SH (TTBR0_SH_MASK << TTBR0_SH_SHIFT) -#define TTBR0_ORGN (TTBR0_ORGN_MASK << TTBR0_ORGN_SHIFT) -#define TTBR0_NOS (TTBR0_NOS_MASK << TTBR0_NOS_SHIFT) -#define TTBR0_IRGNL (TTBR0_IRGNL_MASK << TTBR0_IRGNL_SHIFT) -#define TTBR0_PA (TTBR0_PA_MASK << TTBR0_PA_SHIFT) - - -/* TTBR1 */ -#define TTBR1_IRGNH (TTBR1_IRGNH_MASK << TTBR1_IRGNH_SHIFT) -#define TTBR1_SH (TTBR1_SH_MASK << TTBR1_SH_SHIFT) -#define TTBR1_ORGN (TTBR1_ORGN_MASK << TTBR1_ORGN_SHIFT) -#define TTBR1_NOS (TTBR1_NOS_MASK << TTBR1_NOS_SHIFT) -#define TTBR1_IRGNL (TTBR1_IRGNL_MASK << TTBR1_IRGNL_SHIFT) -#define TTBR1_PA (TTBR1_PA_MASK << TTBR1_PA_SHIFT) - - -/* V2PSR */ -#define HIT (HIT_MASK << HIT_SHIFT) -#define INDEX (INDEX_MASK << INDEX_SHIFT) - - -/* V2Pxx */ -#define V2Pxx_INDEX (V2Pxx_INDEX_MASK << V2Pxx_INDEX_SHIFT) -#define V2Pxx_VA (V2Pxx_VA_MASK << V2Pxx_VA_SHIFT) - - -/* Global Register Masks */ -/* CBACRn */ -#define RWVMID_MASK 0x1F -#define RWE_MASK 0x01 -#define RWGE_MASK 0x01 -#define CBVMID_MASK 0x1F -#define IRPTNDX_MASK 0xFF - - -/* CR */ -#define RPUE_MASK 0x01 -#define RPUERE_MASK 0x01 -#define RPUEIE_MASK 0x01 -#define DCDEE_MASK 0x01 -#define CLIENTPD_MASK 0x01 -#define STALLD_MASK 0x01 -#define TLBLKCRWE_MASK 0x01 -#define CR_TLBIALLCFG_MASK 0x01 -#define TLBIVMIDCFG_MASK 0x01 -#define CR_HUME_MASK 0x01 - - -/* ESR */ -#define CFG_MASK 0x01 -#define BYPASS_MASK 0x01 -#define ESR_MULTI_MASK 0x01 - - -/* ESYNR0 */ -#define ESYNR0_AMID_MASK 0xFF -#define ESYNR0_APID_MASK 0x1F -#define ESYNR0_ABID_MASK 0x07 -#define ESYNR0_AVMID_MASK 0x1F -#define ESYNR0_ATID_MASK 0xFF - - -/* ESYNR1 */ -#define ESYNR1_AMEMTYPE_MASK 0x07 -#define ESYNR1_ASHARED_MASK 0x01 -#define ESYNR1_AINNERSHARED_MASK 0x01 -#define ESYNR1_APRIV_MASK 0x01 -#define ESYNR1_APROTNS_MASK 0x01 -#define ESYNR1_AINST_MASK 0x01 -#define ESYNR1_AWRITE_MASK 0x01 -#define ESYNR1_ABURST_MASK 0x01 -#define ESYNR1_ALEN_MASK 0x0F -#define ESYNR1_ASIZE_MASK 0x01 -#define ESYNR1_ALOCK_MASK 0x03 -#define ESYNR1_AOOO_MASK 0x01 -#define ESYNR1_AFULL_MASK 0x01 -#define ESYNR1_AC_MASK 0x01 -#define ESYNR1_DCD_MASK 0x01 - - -/* IDR */ -#define NM2VCBMT_MASK 0x1FF -#define HTW_MASK 0x01 -#define HUM_MASK 0x01 -#define TLBSIZE_MASK 0x0F -#define NCB_MASK 0xFF -#define NIRPT_MASK 0xFF - - -/* M2VCBRn */ -#define VMID_MASK 0x1F -#define CBNDX_MASK 0xFF -#define BYPASSD_MASK 0x01 -#define BPRCOSH_MASK 0x01 -#define BPRCISH_MASK 0x01 -#define BPRCNSH_MASK 0x01 -#define BPSHCFG_MASK 0x03 -#define NSCFG_MASK 0x03 -#define BPMTCFG_MASK 0x01 -#define BPMEMTYPE_MASK 0x07 - - -/* REV */ -#define MINOR_MASK 0x0F -#define MAJOR_MASK 0x0F - - -/* TESTBUSCR */ -#define TBE_MASK 0x01 -#define SPDMBE_MASK 0x01 -#define WGSEL_MASK 0x03 -#define TBLSEL_MASK 0x03 -#define TBHSEL_MASK 0x03 -#define SPDM0SEL_MASK 0x0F -#define SPDM1SEL_MASK 0x0F -#define SPDM2SEL_MASK 0x0F -#define SPDM3SEL_MASK 0x0F - - -/* TLBIMID */ -#define TLBIVMID_VMID_MASK 0x1F - - -/* TLBRSW */ -#define TLBRSW_INDEX_MASK 0xFF -#define TLBBFBS_MASK 0x03 - - -/* TLBTR0 */ -#define PR_MASK 0x01 -#define PW_MASK 0x01 -#define UR_MASK 0x01 -#define UW_MASK 0x01 -#define XN_MASK 0x01 -#define NSDESC_MASK 0x01 -#define ISH_MASK 0x01 -#define SH_MASK 0x01 -#define MT_MASK 0x07 -#define DPSIZR_MASK 0x07 -#define DPSIZC_MASK 0x07 - - -/* TLBTR1 */ -#define TLBTR1_VMID_MASK 0x1F -#define TLBTR1_PA_MASK 0x000FFFFF - - -/* TLBTR2 */ -#define TLBTR2_ASID_MASK 0xFF -#define TLBTR2_V_MASK 0x01 -#define TLBTR2_NSTID_MASK 0x01 -#define TLBTR2_NV_MASK 0x01 -#define TLBTR2_VA_MASK 0x000FFFFF - - -/* Global Register Shifts */ -/* CBACRn */ -#define RWVMID_SHIFT 0 -#define RWE_SHIFT 8 -#define RWGE_SHIFT 9 -#define CBVMID_SHIFT 16 -#define IRPTNDX_SHIFT 24 - - -/* CR */ -#define RPUE_SHIFT 0 -#define RPUERE_SHIFT 1 -#define RPUEIE_SHIFT 2 -#define DCDEE_SHIFT 3 -#define CLIENTPD_SHIFT 4 -#define STALLD_SHIFT 5 -#define TLBLKCRWE_SHIFT 6 -#define CR_TLBIALLCFG_SHIFT 7 -#define TLBIVMIDCFG_SHIFT 8 -#define CR_HUME_SHIFT 9 - - -/* ESR */ -#define CFG_SHIFT 0 -#define BYPASS_SHIFT 1 -#define ESR_MULTI_SHIFT 31 - - -/* ESYNR0 */ -#define ESYNR0_AMID_SHIFT 0 -#define ESYNR0_APID_SHIFT 8 -#define ESYNR0_ABID_SHIFT 13 -#define ESYNR0_AVMID_SHIFT 16 -#define ESYNR0_ATID_SHIFT 24 - - -/* ESYNR1 */ -#define ESYNR1_AMEMTYPE_SHIFT 0 -#define ESYNR1_ASHARED_SHIFT 3 -#define ESYNR1_AINNERSHARED_SHIFT 4 -#define ESYNR1_APRIV_SHIFT 5 -#define ESYNR1_APROTNS_SHIFT 6 -#define ESYNR1_AINST_SHIFT 7 -#define ESYNR1_AWRITE_SHIFT 8 -#define ESYNR1_ABURST_SHIFT 10 -#define ESYNR1_ALEN_SHIFT 12 -#define ESYNR1_ASIZE_SHIFT 16 -#define ESYNR1_ALOCK_SHIFT 20 -#define ESYNR1_AOOO_SHIFT 22 -#define ESYNR1_AFULL_SHIFT 24 -#define ESYNR1_AC_SHIFT 30 -#define ESYNR1_DCD_SHIFT 31 - - -/* IDR */ -#define NM2VCBMT_SHIFT 0 -#define HTW_SHIFT 9 -#define HUM_SHIFT 10 -#define TLBSIZE_SHIFT 12 -#define NCB_SHIFT 16 -#define NIRPT_SHIFT 24 - - -/* M2VCBRn */ -#define VMID_SHIFT 0 -#define CBNDX_SHIFT 8 -#define BYPASSD_SHIFT 16 -#define BPRCOSH_SHIFT 17 -#define BPRCISH_SHIFT 18 -#define BPRCNSH_SHIFT 19 -#define BPSHCFG_SHIFT 20 -#define NSCFG_SHIFT 22 -#define BPMTCFG_SHIFT 24 -#define BPMEMTYPE_SHIFT 25 - - -/* REV */ -#define MINOR_SHIFT 0 -#define MAJOR_SHIFT 4 - - -/* TESTBUSCR */ -#define TBE_SHIFT 0 -#define SPDMBE_SHIFT 1 -#define WGSEL_SHIFT 8 -#define TBLSEL_SHIFT 12 -#define TBHSEL_SHIFT 14 -#define SPDM0SEL_SHIFT 16 -#define SPDM1SEL_SHIFT 20 -#define SPDM2SEL_SHIFT 24 -#define SPDM3SEL_SHIFT 28 - - -/* TLBIMID */ -#define TLBIVMID_VMID_SHIFT 0 - - -/* TLBRSW */ -#define TLBRSW_INDEX_SHIFT 0 -#define TLBBFBS_SHIFT 8 - - -/* TLBTR0 */ -#define PR_SHIFT 0 -#define PW_SHIFT 1 -#define UR_SHIFT 2 -#define UW_SHIFT 3 -#define XN_SHIFT 4 -#define NSDESC_SHIFT 6 -#define ISH_SHIFT 7 -#define SH_SHIFT 8 -#define MT_SHIFT 9 -#define DPSIZR_SHIFT 16 -#define DPSIZC_SHIFT 20 - - -/* TLBTR1 */ -#define TLBTR1_VMID_SHIFT 0 -#define TLBTR1_PA_SHIFT 12 - - -/* TLBTR2 */ -#define TLBTR2_ASID_SHIFT 0 -#define TLBTR2_V_SHIFT 8 -#define TLBTR2_NSTID_SHIFT 9 -#define TLBTR2_NV_SHIFT 10 -#define TLBTR2_VA_SHIFT 12 - - -/* Context Register Masks */ -/* ACTLR */ -#define CFERE_MASK 0x01 -#define CFEIE_MASK 0x01 -#define PTSHCFG_MASK 0x03 -#define RCOSH_MASK 0x01 -#define RCISH_MASK 0x01 -#define RCNSH_MASK 0x01 -#define PRIVCFG_MASK 0x03 -#define DNA_MASK 0x01 -#define DNLV2PA_MASK 0x01 -#define TLBMCFG_MASK 0x03 -#define CFCFG_MASK 0x01 -#define TIPCF_MASK 0x01 -#define V2PCFG_MASK 0x03 -#define HUME_MASK 0x01 -#define PTMTCFG_MASK 0x01 -#define PTMEMTYPE_MASK 0x07 - - -/* BFBCR */ -#define BFBDFE_MASK 0x01 -#define BFBSFE_MASK 0x01 -#define SFVS_MASK 0x01 -#define FLVIC_MASK 0x0F -#define SLVIC_MASK 0x0F - - -/* CONTEXTIDR */ -#define CONTEXTIDR_ASID_MASK 0xFF -#define PROCID_MASK 0x00FFFFFF - - -/* FSR */ -#define TF_MASK 0x01 -#define AFF_MASK 0x01 -#define APF_MASK 0x01 -#define TLBMF_MASK 0x01 -#define HTWDEEF_MASK 0x01 -#define HTWSEEF_MASK 0x01 -#define MHF_MASK 0x01 -#define SL_MASK 0x01 -#define SS_MASK 0x01 -#define MULTI_MASK 0x01 - - -/* FSYNR0 */ -#define AMID_MASK 0xFF -#define APID_MASK 0x1F -#define ABID_MASK 0x07 -#define ATID_MASK 0xFF - - -/* FSYNR1 */ -#define AMEMTYPE_MASK 0x07 -#define ASHARED_MASK 0x01 -#define AINNERSHARED_MASK 0x01 -#define APRIV_MASK 0x01 -#define APROTNS_MASK 0x01 -#define AINST_MASK 0x01 -#define AWRITE_MASK 0x01 -#define ABURST_MASK 0x01 -#define ALEN_MASK 0x0F -#define FSYNR1_ASIZE_MASK 0x07 -#define ALOCK_MASK 0x03 -#define AFULL_MASK 0x01 - - -/* NMRR */ -#define ICPC0_MASK 0x03 -#define ICPC1_MASK 0x03 -#define ICPC2_MASK 0x03 -#define ICPC3_MASK 0x03 -#define ICPC4_MASK 0x03 -#define ICPC5_MASK 0x03 -#define ICPC6_MASK 0x03 -#define ICPC7_MASK 0x03 -#define OCPC0_MASK 0x03 -#define OCPC1_MASK 0x03 -#define OCPC2_MASK 0x03 -#define OCPC3_MASK 0x03 -#define OCPC4_MASK 0x03 -#define OCPC5_MASK 0x03 -#define OCPC6_MASK 0x03 -#define OCPC7_MASK 0x03 - - -/* PAR */ -#define FAULT_MASK 0x01 -/* If a fault is present, these are the -same as the fault fields in the FAR */ -#define FAULT_TF_MASK 0x01 -#define FAULT_AFF_MASK 0x01 -#define FAULT_APF_MASK 0x01 -#define FAULT_TLBMF_MASK 0x01 -#define FAULT_HTWDEEF_MASK 0x01 -#define FAULT_HTWSEEF_MASK 0x01 -#define FAULT_MHF_MASK 0x01 -#define FAULT_SL_MASK 0x01 -#define FAULT_SS_MASK 0x01 - -/* If NO fault is present, the following - * fields are in effect - * (FAULT remains as before) */ -#define PAR_NOFAULT_SS_MASK 0x01 -#define PAR_NOFAULT_MT_MASK 0x07 -#define PAR_NOFAULT_SH_MASK 0x01 -#define PAR_NOFAULT_NS_MASK 0x01 -#define PAR_NOFAULT_NOS_MASK 0x01 -#define PAR_NPFAULT_PA_MASK 0x000FFFFF - - -/* PRRR */ -#define MTC0_MASK 0x03 -#define MTC1_MASK 0x03 -#define MTC2_MASK 0x03 -#define MTC3_MASK 0x03 -#define MTC4_MASK 0x03 -#define MTC5_MASK 0x03 -#define MTC6_MASK 0x03 -#define MTC7_MASK 0x03 -#define SHDSH0_MASK 0x01 -#define SHDSH1_MASK 0x01 -#define SHNMSH0_MASK 0x01 -#define SHNMSH1_MASK 0x01 -#define NOS0_MASK 0x01 -#define NOS1_MASK 0x01 -#define NOS2_MASK 0x01 -#define NOS3_MASK 0x01 -#define NOS4_MASK 0x01 -#define NOS5_MASK 0x01 -#define NOS6_MASK 0x01 -#define NOS7_MASK 0x01 - - -/* RESUME */ -#define TNR_MASK 0x01 - - -/* SCTLR */ -#define M_MASK 0x01 -#define TRE_MASK 0x01 -#define AFE_MASK 0x01 -#define HAF_MASK 0x01 -#define BE_MASK 0x01 -#define AFFD_MASK 0x01 - - -/* TLBIASID */ -#define TLBIASID_ASID_MASK 0xFF - - -/* TLBIVA */ -#define TLBIVA_ASID_MASK 0xFF -#define TLBIVA_VA_MASK 0x000FFFFF - - -/* TLBIVAA */ -#define TLBIVAA_VA_MASK 0x000FFFFF - - -/* TLBLCKR */ -#define LKE_MASK 0x01 -#define TLBLCKR_TLBIALLCFG_MASK 0x01 -#define TLBIASIDCFG_MASK 0x01 -#define TLBIVAACFG_MASK 0x01 -#define FLOOR_MASK 0xFF -#define VICTIM_MASK 0xFF - - -/* TTBCR */ -#define N_MASK 0x07 -#define PD0_MASK 0x01 -#define PD1_MASK 0x01 - - -/* TTBR0 */ -#define TTBR0_IRGNH_MASK 0x01 -#define TTBR0_SH_MASK 0x01 -#define TTBR0_ORGN_MASK 0x03 -#define TTBR0_NOS_MASK 0x01 -#define TTBR0_IRGNL_MASK 0x01 -#define TTBR0_PA_MASK 0x0003FFFF - - -/* TTBR1 */ -#define TTBR1_IRGNH_MASK 0x01 -#define TTBR1_SH_MASK 0x01 -#define TTBR1_ORGN_MASK 0x03 -#define TTBR1_NOS_MASK 0x01 -#define TTBR1_IRGNL_MASK 0x01 -#define TTBR1_PA_MASK 0x0003FFFF - - -/* V2PSR */ -#define HIT_MASK 0x01 -#define INDEX_MASK 0xFF - - -/* V2Pxx */ -#define V2Pxx_INDEX_MASK 0xFF -#define V2Pxx_VA_MASK 0x000FFFFF - - -/* Context Register Shifts */ -/* ACTLR */ -#define CFERE_SHIFT 0 -#define CFEIE_SHIFT 1 -#define PTSHCFG_SHIFT 2 -#define RCOSH_SHIFT 4 -#define RCISH_SHIFT 5 -#define RCNSH_SHIFT 6 -#define PRIVCFG_SHIFT 8 -#define DNA_SHIFT 10 -#define DNLV2PA_SHIFT 11 -#define TLBMCFG_SHIFT 12 -#define CFCFG_SHIFT 14 -#define TIPCF_SHIFT 15 -#define V2PCFG_SHIFT 16 -#define HUME_SHIFT 18 -#define PTMTCFG_SHIFT 20 -#define PTMEMTYPE_SHIFT 21 - - -/* BFBCR */ -#define BFBDFE_SHIFT 0 -#define BFBSFE_SHIFT 1 -#define SFVS_SHIFT 2 -#define FLVIC_SHIFT 4 -#define SLVIC_SHIFT 8 - - -/* CONTEXTIDR */ -#define CONTEXTIDR_ASID_SHIFT 0 -#define PROCID_SHIFT 8 - - -/* FSR */ -#define TF_SHIFT 1 -#define AFF_SHIFT 2 -#define APF_SHIFT 3 -#define TLBMF_SHIFT 4 -#define HTWDEEF_SHIFT 5 -#define HTWSEEF_SHIFT 6 -#define MHF_SHIFT 7 -#define SL_SHIFT 16 -#define SS_SHIFT 30 -#define MULTI_SHIFT 31 - - -/* FSYNR0 */ -#define AMID_SHIFT 0 -#define APID_SHIFT 8 -#define ABID_SHIFT 13 -#define ATID_SHIFT 24 - - -/* FSYNR1 */ -#define AMEMTYPE_SHIFT 0 -#define ASHARED_SHIFT 3 -#define AINNERSHARED_SHIFT 4 -#define APRIV_SHIFT 5 -#define APROTNS_SHIFT 6 -#define AINST_SHIFT 7 -#define AWRITE_SHIFT 8 -#define ABURST_SHIFT 10 -#define ALEN_SHIFT 12 -#define FSYNR1_ASIZE_SHIFT 16 -#define ALOCK_SHIFT 20 -#define AFULL_SHIFT 24 - - -/* NMRR */ -#define ICPC0_SHIFT 0 -#define ICPC1_SHIFT 2 -#define ICPC2_SHIFT 4 -#define ICPC3_SHIFT 6 -#define ICPC4_SHIFT 8 -#define ICPC5_SHIFT 10 -#define ICPC6_SHIFT 12 -#define ICPC7_SHIFT 14 -#define OCPC0_SHIFT 16 -#define OCPC1_SHIFT 18 -#define OCPC2_SHIFT 20 -#define OCPC3_SHIFT 22 -#define OCPC4_SHIFT 24 -#define OCPC5_SHIFT 26 -#define OCPC6_SHIFT 28 -#define OCPC7_SHIFT 30 - - -/* PAR */ -#define FAULT_SHIFT 0 -/* If a fault is present, these are the -same as the fault fields in the FAR */ -#define FAULT_TF_SHIFT 1 -#define FAULT_AFF_SHIFT 2 -#define FAULT_APF_SHIFT 3 -#define FAULT_TLBMF_SHIFT 4 -#define FAULT_HTWDEEF_SHIFT 5 -#define FAULT_HTWSEEF_SHIFT 6 -#define FAULT_MHF_SHIFT 7 -#define FAULT_SL_SHIFT 16 -#define FAULT_SS_SHIFT 30 - -/* If NO fault is present, the following - * fields are in effect - * (FAULT remains as before) */ -#define PAR_NOFAULT_SS_SHIFT 1 -#define PAR_NOFAULT_MT_SHIFT 4 -#define PAR_NOFAULT_SH_SHIFT 7 -#define PAR_NOFAULT_NS_SHIFT 9 -#define PAR_NOFAULT_NOS_SHIFT 10 -#define PAR_NPFAULT_PA_SHIFT 12 - - -/* PRRR */ -#define MTC0_SHIFT 0 -#define MTC1_SHIFT 2 -#define MTC2_SHIFT 4 -#define MTC3_SHIFT 6 -#define MTC4_SHIFT 8 -#define MTC5_SHIFT 10 -#define MTC6_SHIFT 12 -#define MTC7_SHIFT 14 -#define SHDSH0_SHIFT 16 -#define SHDSH1_SHIFT 17 -#define SHNMSH0_SHIFT 18 -#define SHNMSH1_SHIFT 19 -#define NOS0_SHIFT 24 -#define NOS1_SHIFT 25 -#define NOS2_SHIFT 26 -#define NOS3_SHIFT 27 -#define NOS4_SHIFT 28 -#define NOS5_SHIFT 29 -#define NOS6_SHIFT 30 -#define NOS7_SHIFT 31 - - -/* RESUME */ -#define TNR_SHIFT 0 - - -/* SCTLR */ -#define M_SHIFT 0 -#define TRE_SHIFT 1 -#define AFE_SHIFT 2 -#define HAF_SHIFT 3 -#define BE_SHIFT 4 -#define AFFD_SHIFT 5 - - -/* TLBIASID */ -#define TLBIASID_ASID_SHIFT 0 - - -/* TLBIVA */ -#define TLBIVA_ASID_SHIFT 0 -#define TLBIVA_VA_SHIFT 12 - - -/* TLBIVAA */ -#define TLBIVAA_VA_SHIFT 12 - - -/* TLBLCKR */ -#define LKE_SHIFT 0 -#define TLBLCKR_TLBIALLCFG_SHIFT 1 -#define TLBIASIDCFG_SHIFT 2 -#define TLBIVAACFG_SHIFT 3 -#define FLOOR_SHIFT 8 -#define VICTIM_SHIFT 8 - - -/* TTBCR */ -#define N_SHIFT 3 -#define PD0_SHIFT 4 -#define PD1_SHIFT 5 - - -/* TTBR0 */ -#define TTBR0_IRGNH_SHIFT 0 -#define TTBR0_SH_SHIFT 1 -#define TTBR0_ORGN_SHIFT 3 -#define TTBR0_NOS_SHIFT 5 -#define TTBR0_IRGNL_SHIFT 6 -#define TTBR0_PA_SHIFT 14 - - -/* TTBR1 */ -#define TTBR1_IRGNH_SHIFT 0 -#define TTBR1_SH_SHIFT 1 -#define TTBR1_ORGN_SHIFT 3 -#define TTBR1_NOS_SHIFT 5 -#define TTBR1_IRGNL_SHIFT 6 -#define TTBR1_PA_SHIFT 14 - - -/* V2PSR */ -#define HIT_SHIFT 0 -#define INDEX_SHIFT 8 - - -/* V2Pxx */ -#define V2Pxx_INDEX_SHIFT 0 -#define V2Pxx_VA_SHIFT 12 - -#endif |