From: Yong Wu <yong.wu@xxxxxxxxxxxx> This patch adds support for mediatek m4u (MultiMedia Memory Management Unit). Currently this only supports m4u gen 2 with 2 levels of page table on mt8173. Signed-off-by: Yong Wu <yong.wu@xxxxxxxxxxxx> --- drivers/iommu/Kconfig | 11 + drivers/iommu/Makefile | 1 + drivers/iommu/mtk_iommu.c | 754 ++++++++++++++++++++++++++++++++++++ drivers/iommu/mtk_iommu.h | 73 ++++ drivers/iommu/mtk_iommu_pagetable.c | 439 +++++++++++++++++++++ drivers/iommu/mtk_iommu_pagetable.h | 49 +++ 6 files changed, 1327 insertions(+) create mode 100644 drivers/iommu/mtk_iommu.c create mode 100644 drivers/iommu/mtk_iommu.h create mode 100644 drivers/iommu/mtk_iommu_pagetable.c create mode 100644 drivers/iommu/mtk_iommu_pagetable.h diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 19027bb..e63f5b6 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -326,4 +326,15 @@ config ARM_SMMU Say Y here if your SoC includes an IOMMU device implementing the ARM SMMU architecture. +config MTK_IOMMU + bool "MTK IOMMU Support" + select IOMMU_API + select IOMMU_DMA + select MTK_SMI + help + Support for the IOMMUs on certain Mediatek SOCs. + These IOMMUs allow the multimedia hardware access discontinuous memory. + + If unsure, say N here. + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 37bfc4e..f2a8027 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o obj-$(CONFIG_IOMMU_DMA) += dma-iommu.o obj-$(CONFIG_IOMMU_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o +obj-$(CONFIG_MTK_IOMMU) += mtk_iommu.o mtk_iommu_pagetable.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c new file mode 100644 index 0000000..d62d4ab --- /dev/null +++ b/drivers/iommu/mtk_iommu.c @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2014-2015 MediaTek Inc. + * Author: Yong Wu <yong.wu@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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) "m4u:"fmt + +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/iommu.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/memblock.h> +#include <linux/dma-mapping.h> +#include <linux/dma-iommu.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/mtk-smi.h> +#include <asm/cacheflush.h> + +#include "mtk_iommu.h" + +#define REG_MMUG_PT_BASE 0x0 + +#define REG_MMU_INVLD 0x20 +#define F_MMU_INV_ALL 0x2 +#define F_MMU_INV_RANGE 0x1 + +#define REG_MMU_INVLD_SA 0x24 +#define REG_MMU_INVLD_EA 0x28 + +#define REG_MMU_INVLD_SEC 0x2c +#define F_MMU_INV_SEC_ALL 0x2 +#define F_MMU_INV_SEC_RANGE 0x1 + +#define REG_INVLID_SEL 0x38 +#define F_MMU_INV_EN_L1 BIT(0) +#define F_MMU_INV_EN_L2 BIT(1) + +#define REG_MMU_STANDARD_AXI_MODE 0x48 +#define REG_MMU_DCM_DIS 0x50 +#define REG_MMU_LEGACY_4KB_MODE 0x60 + +#define REG_MMU_CTRL_REG 0x110 +#define F_MMU_CTRL_REROUTE_PFQ_TO_MQ_EN BIT(4) +#define F_MMU_CTRL_TF_PROT_VAL(prot) (((prot) & 0x3)<<5) +#define F_MMU_CTRL_COHERE_EN BIT(8) + +#define REG_MMU_IVRP_PADDR 0x114 +#define F_MMU_IVRP_PA_SET(PA) (PA>>1) + +#define REG_MMU_INT_L2_CONTROL 0x120 +#define F_INT_L2_CLR_BIT BIT(12) + +#define REG_MMU_INT_MAIN_CONTROL 0x124 +#define F_INT_TRANSLATION_FAULT(MMU) (1<<(0+(((MMU)<<1)|((MMU)<<2)))) +#define F_INT_MAIN_MULTI_HIT_FAULT(MMU) (1<<(1+(((MMU)<<1)|((MMU)<<2)))) +#define F_INT_INVALID_PA_FAULT(MMU) (1<<(2+(((MMU)<<1)|((MMU)<<2)))) +#define F_INT_ENTRY_REPLACEMENT_FAULT(MMU) (1<<(3+(((MMU)<<1)|((MMU)<<2)))) +#define F_INT_TLB_MISS_FAULT(MMU) (1<<(4+(((MMU)<<1)|((MMU)<<2)))) +#define F_INT_PFH_FIFO_ERR(MMU) (1<<(6+(((MMU)<<1)|((MMU)<<2)))) + +#define REG_MMU_CPE_DONE 0x12C + +#define REG_MMU_MAIN_FAULT_ST 0x134 + +#define REG_MMU_FAULT_VA(mmu) (0x13c+((mmu)<<3)) +#define F_MMU_FAULT_VA_MSK ((~0x0)<<12) +#define F_MMU_FAULT_VA_WRITE_BIT BIT(1) +#define F_MMU_FAULT_VA_LAYER_BIT BIT(0) + +#define REG_MMU_INVLD_PA(mmu) (0x140+((mmu)<<3)) +#define REG_MMU_INT_ID(mmu) (0x150+((mmu)<<2)) +#define F_MMU0_INT_ID_TF_MSK (~0x3) /* only for MM iommu. */ + +#define MTK_TFID(larbid, portid) ((larbid << 7) | (portid << 2)) + +static const struct mtk_iommu_port mtk_iommu_mt8173_port[] = { + /* port name m4uid slaveid larbid portid tfid */ + /* larb0 */ + {"M4U_PORT_DISP_OVL0", 0, 0, 0, 0, MTK_TFID(0, 0)}, + {"M4U_PORT_DISP_RDMA0", 0, 0, 0, 1, MTK_TFID(0, 1)}, + {"M4U_PORT_DISP_WDMA0", 0, 0, 0, 2, MTK_TFID(0, 2)}, + {"M4U_PORT_DISP_OD_R", 0, 0, 0, 3, MTK_TFID(0, 3)}, + {"M4U_PORT_DISP_OD_W", 0, 0, 0, 4, MTK_TFID(0, 4)}, + {"M4U_PORT_MDP_RDMA0", 0, 0, 0, 5, MTK_TFID(0, 5)}, + {"M4U_PORT_MDP_WDMA", 0, 0, 0, 6, MTK_TFID(0, 6)}, + {"M4U_PORT_MDP_WROT0", 0, 0, 0, 7, MTK_TFID(0, 7)}, + + /* larb1 */ + {"M4U_PORT_HW_VDEC_MC_EXT", 0, 0, 1, 0, MTK_TFID(1, 0)}, + {"M4U_PORT_HW_VDEC_PP_EXT", 0, 0, 1, 1, MTK_TFID(1, 1)}, + {"M4U_PORT_HW_VDEC_UFO_EXT", 0, 0, 1, 2, MTK_TFID(1, 2)}, + {"M4U_PORT_HW_VDEC_VLD_EXT", 0, 0, 1, 3, MTK_TFID(1, 3)}, + {"M4U_PORT_HW_VDEC_VLD2_EXT", 0, 0, 1, 4, MTK_TFID(1, 4)}, + {"M4U_PORT_HW_VDEC_AVC_MV_EXT", 0, 0, 1, 5, MTK_TFID(1, 5)}, + {"M4U_PORT_HW_VDEC_PRED_RD_EXT", 0, 0, 1, 6, MTK_TFID(1, 6)}, + {"M4U_PORT_HW_VDEC_PRED_WR_EXT", 0, 0, 1, 7, MTK_TFID(1, 7)}, + {"M4U_PORT_HW_VDEC_PPWRAP_EXT", 0, 0, 1, 8, MTK_TFID(1, 8)}, + + /* larb2 */ + {"M4U_PORT_IMGO", 0, 0, 2, 0, MTK_TFID(2, 0)}, + {"M4U_PORT_RRZO", 0, 0, 2, 1, MTK_TFID(2, 1)}, + {"M4U_PORT_AAO", 0, 0, 2, 2, MTK_TFID(2, 2)}, + {"M4U_PORT_LCSO", 0, 0, 2, 3, MTK_TFID(2, 3)}, + {"M4U_PORT_ESFKO", 0, 0, 2, 4, MTK_TFID(2, 4)}, + {"M4U_PORT_IMGO_D", 0, 0, 2, 5, MTK_TFID(2, 5)}, + {"M4U_PORT_LSCI", 0, 0, 2, 6, MTK_TFID(2, 6)}, + {"M4U_PORT_LSCI_D", 0, 0, 2, 7, MTK_TFID(2, 7)}, + {"M4U_PORT_BPCI", 0, 0, 2, 8, MTK_TFID(2, 8)}, + {"M4U_PORT_BPCI_D", 0, 0, 2, 9, MTK_TFID(2, 9)}, + {"M4U_PORT_UFDI", 0, 0, 2, 10, MTK_TFID(2, 10)}, + {"M4U_PORT_IMGI", 0, 0, 2, 11, MTK_TFID(2, 11)}, + {"M4U_PORT_IMG2O", 0, 0, 2, 12, MTK_TFID(2, 12)}, + {"M4U_PORT_IMG3O", 0, 0, 2, 13, MTK_TFID(2, 13)}, + {"M4U_PORT_VIPI", 0, 0, 2, 14, MTK_TFID(2, 14)}, + {"M4U_PORT_VIP2I", 0, 0, 2, 15, MTK_TFID(2, 15)}, + {"M4U_PORT_VIP3I", 0, 0, 2, 16, MTK_TFID(2, 16)}, + {"M4U_PORT_LCEI", 0, 0, 2, 17, MTK_TFID(2, 17)}, + {"M4U_PORT_RB", 0, 0, 2, 18, MTK_TFID(2, 18)}, + {"M4U_PORT_RP", 0, 0, 2, 19, MTK_TFID(2, 19)}, + {"M4U_PORT_WR", 0, 0, 2, 20, MTK_TFID(2, 20)}, + + /* larb3 */ + {"M4U_PORT_VENC_RCPU", 0, 0, 3, 0, MTK_TFID(3, 0)}, + {"M4U_PORT_VENC_REC", 0, 0, 3, 1, MTK_TFID(3, 1)}, + {"M4U_PORT_VENC_BSDMA", 0, 0, 3, 2, MTK_TFID(3, 2)}, + {"M4U_PORT_VENC_SV_COMV", 0, 0, 3, 3, MTK_TFID(3, 3)}, + {"M4U_PORT_VENC_RD_COMV", 0, 0, 3, 4, MTK_TFID(3, 4)}, + {"M4U_PORT_JPGENC_RDMA", 0, 0, 3, 5, MTK_TFID(3, 5)}, + {"M4U_PORT_JPGENC_BSDMA", 0, 0, 3, 6, MTK_TFID(3, 6)}, + {"M4U_PORT_JPGDEC_WDMA", 0, 0, 3, 7, MTK_TFID(3, 7)}, + {"M4U_PORT_JPGDEC_BSDMA", 0, 0, 3, 8, MTK_TFID(3, 8)}, + {"M4U_PORT_VENC_CUR_LUMA", 0, 0, 3, 9, MTK_TFID(3, 9)}, + {"M4U_PORT_VENC_CUR_CHROMA", 0, 0, 3, 10, MTK_TFID(3, 10)}, + {"M4U_PORT_VENC_REF_LUMA", 0, 0, 3, 11, MTK_TFID(3, 11)}, + {"M4U_PORT_VENC_REF_CHROMA", 0, 0, 3, 12, MTK_TFID(3, 12)}, + {"M4U_PORT_VENC_NBM_RDMA", 0, 0, 3, 13, MTK_TFID(3, 13)}, + {"M4U_PORT_VENC_NBM_WDMA", 0, 0, 3, 14, MTK_TFID(3, 14)}, + + /* larb4 */ + {"M4U_PORT_DISP_OVL1", 0, 0, 4, 0, MTK_TFID(4, 0)}, + {"M4U_PORT_DISP_RDMA1", 0, 0, 4, 1, MTK_TFID(4, 1)}, + {"M4U_PORT_DISP_RDMA2", 0, 0, 4, 2, MTK_TFID(4, 2)}, + {"M4U_PORT_DISP_WDMA1", 0, 0, 4, 3, MTK_TFID(4, 3)}, + {"M4U_PORT_MDP_RDMA1", 0, 0, 4, 4, MTK_TFID(4, 4)}, + {"M4U_PORT_MDP_WROT1", 0, 0, 4, 5, MTK_TFID(4, 5)}, + + /* larb5 */ + {"M4U_PORT_VENC_RCPU_SET2", 0, 0, 5, 0, MTK_TFID(5, 0)}, + {"M4U_PORT_VENC_REC_FRM_SET2", 0, 0, 5, 1, MTK_TFID(5, 1)}, + {"M4U_PORT_VENC_REF_LUMA_SET2", 0, 0, 5, 2, MTK_TFID(5, 2)}, + {"M4U_PORT_VENC_REC_CHROMA_SET2", 0, 0, 5, 3, MTK_TFID(5, 3)}, + {"M4U_PORT_VENC_BSDMA_SET2", 0, 0, 5, 4, MTK_TFID(5, 4)}, + {"M4U_PORT_VENC_CUR_LUMA_SET2", 0, 0, 5, 5, MTK_TFID(5, 5)}, + {"M4U_PORT_VENC_CUR_CHROMA_SET2", 0, 0, 5, 6, MTK_TFID(5, 6)}, + {"M4U_PORT_VENC_RD_COMA_SET2", 0, 0, 5, 7, MTK_TFID(5, 7)}, + {"M4U_PORT_VENC_SV_COMA_SET2", 0, 0, 5, 8, MTK_TFID(5, 8)}, + + /* perisys iommu */ + {"M4U_PORT_RESERVE", 1, 0, 6, 0, 0xff}, + {"M4U_PORT_SPM", 1, 0, 6, 1, 0x50}, + {"M4U_PORT_MD32", 1, 0, 6, 2, 0x90}, + {"M4U_PORT_PTP_THERM", 1, 0, 6, 4, 0xd0}, + {"M4U_PORT_PWM", 1, 0, 6, 5, 0x1}, + {"M4U_PORT_MSDC1", 1, 0, 6, 6, 0x21}, + {"M4U_PORT_MSDC2", 1, 0, 6, 7, 0x41}, + {"M4U_PORT_NFI", 1, 0, 6, 8, 0x8}, + {"M4U_PORT_AUDIO", 1, 0, 6, 9, 0x48}, + {"M4U_PORT_RESERVED2", 1, 0, 6, 10, 0xfe}, + {"M4U_PORT_HSIC_XHCI", 1, 0, 6, 11, 0x9}, + + {"M4U_PORT_HSIC_MAS", 1, 0, 6, 12, 0x11}, + {"M4U_PORT_HSIC_DEV", 1, 0, 6, 13, 0x19}, + {"M4U_PORT_AP_DMA", 1, 0, 6, 14, 0x18}, + {"M4U_PORT_HSIC_DMA", 1, 0, 6, 15, 0xc8}, + {"M4U_PORT_MSDC0", 1, 0, 6, 16, 0x0}, + {"M4U_PORT_MSDC3", 1, 0, 6, 17, 0x20}, + {"M4U_PORT_UNKNOWN", 1, 0, 6, 18, 0xf}, +}; + +static const struct mtk_iommu_cfg mtk_iommu_mt8173_cfg = { + .larb_nr = 6, + .m4u_port_nr = ARRAY_SIZE(mtk_iommu_mt8173_port), + .pport = mtk_iommu_mt8173_port, +}; + +static const char *mtk_iommu_get_port_name(const struct mtk_iommu_info *piommu, + unsigned int portid) +{ + const struct mtk_iommu_port *pcurport = NULL; + + pcurport = piommu->imucfg->pport + portid; + if (portid < piommu->imucfg->m4u_port_nr && pcurport) + return pcurport->port_name; + else + return "UNKNOWN_PORT"; +} + +static int mtk_iommu_get_port_by_tfid(const struct mtk_iommu_info *pimu, + int tf_id) +{ + const struct mtk_iommu_cfg *pimucfg = pimu->imucfg; + int i; + unsigned int portid = pimucfg->m4u_port_nr; + + for (i = 0; i < pimucfg->m4u_port_nr; i++) { + if (pimucfg->pport[i].tf_id == tf_id) { + portid = i; + break; + } + } + if (i == pimucfg->m4u_port_nr) + dev_err(pimu->dev, "tf_id find fail, tfid %d\n", tf_id); + return portid; +} + +static irqreturn_t mtk_iommu_isr(int irq, void *dev_id) +{ + struct iommu_domain *domain = dev_id; + struct mtk_iommu_domain *mtkdomain = domain->priv; + struct mtk_iommu_info *piommu = mtkdomain->piommuinfo; + + if (irq == piommu->irq) + report_iommu_fault(domain, piommu->dev, 0, 0); + else + dev_err(piommu->dev, "irq number:%d\n", irq); + + return IRQ_HANDLED; +} + +static inline void mtk_iommu_clear_intr(void __iomem *m4u_base) +{ + u32 val; + + val = readl(m4u_base + REG_MMU_INT_L2_CONTROL); + val |= F_INT_L2_CLR_BIT; + writel(val, m4u_base + REG_MMU_INT_L2_CONTROL); +} + +static int mtk_iommu_invalidate_tlb(const struct mtk_iommu_info *piommu, + int isinvall, unsigned int iova_start, + unsigned int iova_end) +{ + void __iomem *m4u_base = piommu->m4u_base; + u32 val; + u64 start, end; + + start = sched_clock(); + + if (!isinvall) { + iova_start = round_down(iova_start, SZ_4K); + iova_end = round_up(iova_end, SZ_4K); + } + + val = F_MMU_INV_EN_L2 | F_MMU_INV_EN_L1; + + writel(val, m4u_base + REG_INVLID_SEL); + + if (isinvall) { + writel(F_MMU_INV_ALL, m4u_base + REG_MMU_INVLD); + } else { + writel(iova_start, m4u_base + REG_MMU_INVLD_SA); + writel(iova_end, m4u_base + REG_MMU_INVLD_EA); + writel(F_MMU_INV_RANGE, m4u_base + REG_MMU_INVLD); + + while (!readl(m4u_base + REG_MMU_CPE_DONE)) { + end = sched_clock(); + if (end - start >= 100000000ULL) { + dev_warn(piommu->dev, "invalid don't done\n"); + writel(F_MMU_INV_ALL, m4u_base + REG_MMU_INVLD); + } + }; + writel(0, m4u_base + REG_MMU_CPE_DONE); + } + + return 0; +} + +static int mtk_iommu_fault_handler(struct iommu_domain *imudomain, + struct device *dev, unsigned long iova, + int m4uindex, void *pimu) +{ + void __iomem *m4u_base; + u32 int_state, regval; + int m4u_slave_id = 0; + unsigned int layer, write, m4u_port; + unsigned int fault_mva, fault_pa; + struct mtk_iommu_info *piommu = pimu; + struct mtk_iommu_domain *mtkdomain = imudomain->priv; + + m4u_base = piommu->m4u_base; + int_state = readl(m4u_base + REG_MMU_MAIN_FAULT_ST); + + /* read error info from registers */ + fault_mva = readl(m4u_base + REG_MMU_FAULT_VA(m4u_slave_id)); + layer = !!(fault_mva & F_MMU_FAULT_VA_LAYER_BIT); + write = !!(fault_mva & F_MMU_FAULT_VA_WRITE_BIT); + fault_mva &= F_MMU_FAULT_VA_MSK; + fault_pa = readl(m4u_base + REG_MMU_INVLD_PA(m4u_slave_id)); + regval = readl(m4u_base + REG_MMU_INT_ID(m4u_slave_id)); + regval &= F_MMU0_INT_ID_TF_MSK; + m4u_port = mtk_iommu_get_port_by_tfid(piommu, regval); + + if (int_state & F_INT_TRANSLATION_FAULT(m4u_slave_id)) { + struct m4u_pte_info_t pte; + unsigned long flags; + + spin_lock_irqsave(&mtkdomain->pgtlock, flags); + m4u_get_pte_info(mtkdomain, fault_mva, &pte); + spin_unlock_irqrestore(&mtkdomain->pgtlock, flags); + + if (pte.size == MMU_SMALL_PAGE_SIZE || + pte.size == MMU_LARGE_PAGE_SIZE) { + dev_err_ratelimited( + dev, + "fault:port=%s iova=0x%x pa=0x%x layer=%d %s;" + "pgd(0x%x)->pte(0x%x)->pa(%pad)sz(0x%x)Valid(%d)\n", + mtk_iommu_get_port_name(piommu, m4u_port), + fault_mva, fault_pa, layer, + write ? "write" : "read", + imu_pgd_val(*pte.pgd), imu_pte_val(*pte.pte), + &pte.pa, pte.size, pte.valid); + } else { + dev_err_ratelimited( + dev, + "fault:port=%s iova=0x%x pa=0x%x layer=%d %s;" + "pgd(0x%x)->pa(%pad)sz(0x%x)Valid(%d)\n", + mtk_iommu_get_port_name(piommu, m4u_port), + fault_mva, fault_pa, layer, + write ? "write" : "read", + imu_pgd_val(*pte.pgd), + &pte.pa, pte.size, pte.valid); + } + } + + if (int_state & F_INT_MAIN_MULTI_HIT_FAULT(m4u_slave_id)) + dev_err_ratelimited(dev, "multi-hit!port=%s iova=0x%x\n", + mtk_iommu_get_port_name(piommu, m4u_port), + fault_mva); + + if (int_state & F_INT_INVALID_PA_FAULT(m4u_slave_id)) { + if (!(int_state & F_INT_TRANSLATION_FAULT(m4u_slave_id))) + dev_err_ratelimited(dev, "invalid pa!port=%s iova=0x%x\n", + mtk_iommu_get_port_name(piommu, + m4u_port), + fault_mva); + } + if (int_state & F_INT_ENTRY_REPLACEMENT_FAULT(m4u_slave_id)) + dev_err_ratelimited(dev, "replace-fault!port=%s iova=0x%x\n", + mtk_iommu_get_port_name(piommu, m4u_port), + fault_mva); + + if (int_state & F_INT_TLB_MISS_FAULT(m4u_slave_id)) + dev_err_ratelimited(dev, "tlb miss-fault!port=%s iova=0x%x\n", + mtk_iommu_get_port_name(piommu, m4u_port), + fault_mva); + + mtk_iommu_invalidate_tlb(piommu, 1, 0, 0); + + mtk_iommu_clear_intr(m4u_base); + + return 0; +} + +static int mtk_iommu_parse_dt(struct platform_device *pdev, + struct mtk_iommu_info *piommu) +{ + struct device *piommudev = &pdev->dev; + struct device_node *ofnode; + struct resource *res; + unsigned int mtk_iommu_cell = 0; + unsigned int i; + + ofnode = piommudev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + piommu->m4u_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(piommu->m4u_base)) { + dev_err(piommudev, "m4u_base %p err\n", piommu->m4u_base); + goto iommu_dts_err; + } + + piommu->irq = platform_get_irq(pdev, 0); + if (piommu->irq < 0) { + dev_err(piommudev, "irq err %d\n", piommu->irq); + goto iommu_dts_err; + } + + piommu->m4u_infra_clk = devm_clk_get(piommudev, "infra_m4u"); + if (IS_ERR(piommu->m4u_infra_clk)) { + dev_err(piommudev, "clk err %p\n", piommu->m4u_infra_clk); + goto iommu_dts_err; + } + + of_property_read_u32(ofnode, "#iommu-cells", &mtk_iommu_cell); + if (mtk_iommu_cell != 1) { + dev_err(piommudev, "iommu-cell fail:%d\n", mtk_iommu_cell); + goto iommu_dts_err; + } + + for (i = 0; i < piommu->imucfg->larb_nr; i++) { + struct device_node *larbnode; + + larbnode = of_parse_phandle(ofnode, "larb", i); + piommu->larbpdev[i] = of_find_device_by_node(larbnode); + of_node_put(larbnode); + if (!piommu->larbpdev[i]) { + dev_err(piommudev, "larb pdev fail@larb%d\n", i); + goto iommu_dts_err; + } + } + + return 0; + +iommu_dts_err: + return -EINVAL; +} + +static int mtk_iommu_hw_init(const struct mtk_iommu_domain *mtkdomain) +{ + struct mtk_iommu_info *piommu = mtkdomain->piommuinfo; + void __iomem *gm4ubaseaddr = piommu->m4u_base; + phys_addr_t protectpa; + u32 regval, protectreg; + int ret = 0; + + ret = clk_prepare_enable(piommu->m4u_infra_clk); + if (ret) { + dev_err(piommu->dev, "m4u clk enable error\n"); + return -ENODEV; + } + + writel((u32)mtkdomain->pgd_pa, gm4ubaseaddr + REG_MMUG_PT_BASE); + + regval = F_MMU_CTRL_REROUTE_PFQ_TO_MQ_EN | + F_MMU_CTRL_TF_PROT_VAL(2) | + F_MMU_CTRL_COHERE_EN; + writel(regval, gm4ubaseaddr + REG_MMU_CTRL_REG); + + writel(0x6f, gm4ubaseaddr + REG_MMU_INT_L2_CONTROL); + writel(0xffffffff, gm4ubaseaddr + REG_MMU_INT_MAIN_CONTROL); + + /* protect memory,HW will write here while translation fault */ + protectpa = __virt_to_phys(piommu->protect_va); + protectpa = ALIGN(protectpa, MTK_PROTECT_PA_ALIGN); + protectreg = (u32)F_MMU_IVRP_PA_SET(protectpa); + writel(protectreg, gm4ubaseaddr + REG_MMU_IVRP_PADDR); + + writel(0, gm4ubaseaddr + REG_MMU_DCM_DIS); + writel(0, gm4ubaseaddr + REG_MMU_STANDARD_AXI_MODE); + + return 0; +} + +static inline void mtk_iommu_config_port(struct mtk_iommu_info *piommu, + int portid) +{ + int larb, larb_port; + + larb = piommu->imucfg->pport[portid].larb_id; + larb_port = piommu->imucfg->pport[portid].port_id; + + mtk_smi_config_port(piommu->larbpdev[larb], larb_port); +} + +/* + * pimudev is a global var for dma_alloc_coherent. + * It is not accepatable, we will delete it if "domain_alloc" is enabled + */ +static struct device *pimudev; + +static int mtk_iommu_domain_init(struct iommu_domain *domain) +{ + struct mtk_iommu_domain *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pgd = dma_alloc_coherent(pimudev, M4U_PGD_SIZE, &priv->pgd_pa, + GFP_KERNEL); + if (!priv->pgd) { + pr_err("dma_alloc_coherent pagetable fail\n"); + goto err_pgtable; + } + + if (!IS_ALIGNED(priv->pgd_pa, M4U_PGD_SIZE)) { + pr_err("pagetable not aligned pa 0x%pad-0x%p align 0x%x\n", + &priv->pgd_pa, priv->pgd, M4U_PGD_SIZE); + goto err_pgtable; + } + + memset(priv->pgd, 0, M4U_PGD_SIZE); + + spin_lock_init(&priv->pgtlock); + spin_lock_init(&priv->portlock); + domain->priv = priv; + + domain->geometry.aperture_start = 0; + domain->geometry.aperture_end = (unsigned int)~0; + domain->geometry.force_aperture = true; + + return 0; + +err_pgtable: + if (priv->pgd) + dma_free_coherent(pimudev, M4U_PGD_SIZE, priv->pgd, + priv->pgd_pa); + kfree(priv); + return -ENOMEM; +} + +static void mtk_iommu_domain_destroy(struct iommu_domain *domain) +{ + struct mtk_iommu_domain *priv = domain->priv; + + dma_free_coherent(priv->piommuinfo->dev, M4U_PGD_SIZE, + priv->pgd, priv->pgd_pa); + kfree(domain->priv); + domain->priv = NULL; +} + +static int mtk_iommu_attach_device(struct iommu_domain *domain, + struct device *dev) +{ + unsigned long flags; + struct mtk_iommu_domain *priv = domain->priv; + struct mtk_iommu_info *piommu = priv->piommuinfo; + struct of_phandle_args out_args = {0}; + struct device *imudev; + unsigned int i = 0; + + if (!piommu) + goto imudev; + else + imudev = piommu->dev; + + spin_lock_irqsave(&priv->portlock, flags); + + while (!of_parse_phandle_with_args(dev->of_node, "iommus", + "#iommu-cells", i, &out_args)) { + if (1 == out_args.args_count) { + unsigned int portid = out_args.args[0]; + + dev_dbg(dev, "iommu add port:%d\n", portid); + + mtk_iommu_config_port(piommu, portid); + + if (i == 0) + dev->archdata.dma_ops = + piommu->dev->archdata.dma_ops; + } + i++; + } + + spin_unlock_irqrestore(&priv->portlock, flags); + +imudev: + return 0; +} + +static void mtk_iommu_detach_device(struct iommu_domain *domain, + struct device *dev) +{ +} + +static int mtk_iommu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t size, int prot) +{ + struct mtk_iommu_domain *priv = domain->priv; + unsigned long flags; + int ret; + + spin_lock_irqsave(&priv->pgtlock, flags); + ret = m4u_map(priv, (unsigned int)iova, paddr, size, prot); + mtk_iommu_invalidate_tlb(priv->piommuinfo, 0, + iova, iova + size - 1); + spin_unlock_irqrestore(&priv->pgtlock, flags); + + return ret; +} + +static size_t mtk_iommu_unmap(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + struct mtk_iommu_domain *priv = domain->priv; + unsigned long flags; + + spin_lock_irqsave(&priv->pgtlock, flags); + m4u_unmap(priv, (unsigned int)iova, size); + mtk_iommu_invalidate_tlb(priv->piommuinfo, 0, + iova, iova + size - 1); + spin_unlock_irqrestore(&priv->pgtlock, flags); + + return size; +} + +static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct mtk_iommu_domain *priv = domain->priv; + unsigned long flags; + struct m4u_pte_info_t pte; + + spin_lock_irqsave(&priv->pgtlock, flags); + m4u_get_pte_info(priv, (unsigned int)iova, &pte); + spin_unlock_irqrestore(&priv->pgtlock, flags); + + return pte.pa; +} + +static struct iommu_ops mtk_iommu_ops = { + .domain_init = mtk_iommu_domain_init, + .domain_destroy = mtk_iommu_domain_destroy, + .attach_dev = mtk_iommu_attach_device, + .detach_dev = mtk_iommu_detach_device, + .map = mtk_iommu_map, + .unmap = mtk_iommu_unmap, + .map_sg = default_iommu_map_sg, + .iova_to_phys = mtk_iommu_iova_to_phys, + .pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M, +}; + +static const struct of_device_id mtk_iommu_of_ids[] = { + { .compatible = "mediatek,mt8173-iommu", + .data = &mtk_iommu_mt8173_cfg, + }, + {} +}; + +static int mtk_iommu_probe(struct platform_device *pdev) +{ + int ret; + struct iommu_domain *domain; + struct mtk_iommu_domain *mtk_domain; + struct mtk_iommu_info *piommu; + struct iommu_dma_domain *dom; + const struct of_device_id *of_id; + + piommu = devm_kzalloc(&pdev->dev, sizeof(struct mtk_iommu_info), + GFP_KERNEL); + if (!piommu) + return -ENOMEM; + + pimudev = &pdev->dev; + piommu->dev = &pdev->dev; + + of_id = of_match_node(mtk_iommu_of_ids, pdev->dev.of_node); + if (!of_id) + return -ENODEV; + + piommu->protect_va = devm_kmalloc(piommu->dev, MTK_PROTECT_PA_ALIGN*2, + GFP_KERNEL); + if (!piommu->protect_va) + goto protect_err; + memset(piommu->protect_va, 0x55, MTK_PROTECT_PA_ALIGN*2); + + piommu->imucfg = (const struct mtk_iommu_cfg *)of_id->data; + + ret = mtk_iommu_parse_dt(pdev, piommu); + if (ret) { + dev_err(piommu->dev, "iommu dt parse fail\n"); + goto protect_err; + } + + /* alloc memcache for level-2 pgt */ + piommu->m4u_pte_kmem = kmem_cache_create("m4u_pte", IMU_BYTES_PER_PTE, + IMU_BYTES_PER_PTE, 0, NULL); + + if (IS_ERR_OR_NULL(piommu->m4u_pte_kmem)) { + dev_err(piommu->dev, "pte cached create fail %p\n", + piommu->m4u_pte_kmem); + goto protect_err; + } + + arch_setup_dma_ops(piommu->dev, 0, (1ULL<<32) - 1, &mtk_iommu_ops, 0); + + dom = get_dma_domain(piommu->dev); + domain = iommu_dma_raw_domain(dom); + + mtk_domain = domain->priv; + mtk_domain->piommuinfo = piommu; + + if (!domain) + goto pte_err; + + ret = mtk_iommu_hw_init(mtk_domain); + if (ret < 0) + goto hw_err; + + if (devm_request_irq(piommu->dev, piommu->irq, + mtk_iommu_isr, IRQF_TRIGGER_NONE, + "mtkiommu", (void *)domain)) { + dev_err(piommu->dev, "IRQ request %d failed\n", + piommu->irq); + goto hw_err; + } + + iommu_set_fault_handler(domain, mtk_iommu_fault_handler, piommu); + + dev_set_drvdata(piommu->dev, piommu); + + return 0; +hw_err: + arch_teardown_dma_ops(piommu->dev); +pte_err: + kmem_cache_destroy(piommu->m4u_pte_kmem); +protect_err: + dev_err(piommu->dev, "probe error\n"); + return 0; +} + +static int mtk_iommu_remove(struct platform_device *pdev) +{ + struct mtk_iommu_info *piommu = dev_get_drvdata(&pdev->dev); + + arch_teardown_dma_ops(piommu->dev); + kmem_cache_destroy(piommu->m4u_pte_kmem); + + return 0; +} + +static struct platform_driver mtk_iommu_driver = { + .probe = mtk_iommu_probe, + .remove = mtk_iommu_remove, + .driver = { + .name = "mtkiommu", + .of_match_table = mtk_iommu_of_ids, + } +}; + +static int __init mtk_iommu_init(void) +{ + return platform_driver_register(&mtk_iommu_driver); +} + +subsys_initcall(mtk_iommu_init); + diff --git a/drivers/iommu/mtk_iommu.h b/drivers/iommu/mtk_iommu.h new file mode 100644 index 0000000..239471f --- /dev/null +++ b/drivers/iommu/mtk_iommu.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014-2015 MediaTek Inc. + * Author: Yong Wu <yong.wu@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 MTK_IOMMU_PLATFORM_H +#define MTK_IOMMU_PLATFORM_H + +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/platform_device.h> + +#include "mtk_iommu_pagetable.h" + +#define M4U_PGD_SIZE SZ_16K /* pagetable size,mt8173 */ + +#define MTK_PROTECT_PA_ALIGN 128 + +#define MTK_IOMMU_LARB_MAX_NR 8 +#define MTK_IOMMU_PORT_MAX_NR 100 + +struct mtk_iommu_port { + const char *port_name; + unsigned int m4u_id:2; + unsigned int m4u_slave:2;/* main tlb index in mm iommu */ + unsigned int larb_id:4; + unsigned int port_id:8;/* port id in larb */ + unsigned int tf_id:16; /* translation fault id */ +}; + +struct mtk_iommu_cfg { + unsigned int larb_nr; + unsigned int m4u_port_nr; + const struct mtk_iommu_port *pport; +}; + +struct mtk_iommu_info { + void __iomem *m4u_base; + unsigned int irq; + struct platform_device *larbpdev[MTK_IOMMU_LARB_MAX_NR]; + struct clk *m4u_infra_clk; + void __iomem *protect_va; + struct device *dev; + struct kmem_cache *m4u_pte_kmem; + const struct mtk_iommu_cfg *imucfg; +}; + +struct mtk_iommu_domain { + struct imu_pgd_t *pgd; + dma_addr_t pgd_pa; + spinlock_t pgtlock; /* lock for modifying page table */ + spinlock_t portlock; /* lock for config port */ + struct mtk_iommu_info *piommuinfo; +}; + +int m4u_map(struct mtk_iommu_domain *m4u_domain, unsigned int iova, + phys_addr_t paddr, unsigned int size, unsigned int prot); +int m4u_unmap(struct mtk_iommu_domain *domain, unsigned int iova, + unsigned int size); +int m4u_get_pte_info(const struct mtk_iommu_domain *domain, + unsigned int iova, struct m4u_pte_info_t *pte_info); + +#endif diff --git a/drivers/iommu/mtk_iommu_pagetable.c b/drivers/iommu/mtk_iommu_pagetable.c new file mode 100644 index 0000000..5fe9640 --- /dev/null +++ b/drivers/iommu/mtk_iommu_pagetable.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2014-2015 MediaTek Inc. + * Author: Yong Wu <yong.wu@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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/err.h> +#include <linux/mm.h> +#include <linux/iommu.h> +#include <linux/errno.h> +#include "asm/cacheflush.h" + +#include "mtk_iommu.h" +#include "mtk_iommu_pagetable.h" + +/* 2 level pagetable: pgd -> pte */ +#define F_PTE_TYPE_GET(regval) (regval & 0x3) +#define F_PTE_TYPE_LARGE BIT(0) +#define F_PTE_TYPE_SMALL BIT(1) +#define F_PTE_B_BIT BIT(2) +#define F_PTE_C_BIT BIT(3) +#define F_PTE_BIT32_BIT BIT(9) +#define F_PTE_S_BIT BIT(10) +#define F_PTE_NG_BIT BIT(11) +#define F_PTE_PA_LARGE_MSK (~0UL << 16) +#define F_PTE_PA_LARGE_GET(regval) ((regval >> 16) & 0xffff) +#define F_PTE_PA_SMALL_MSK (~0UL << 12) +#define F_PTE_PA_SMALL_GET(regval) ((regval >> 12) & (~0)) +#define F_PTE_TYPE_IS_LARGE_PAGE(pte) ((imu_pte_val(pte) & 0x3) == \ + F_PTE_TYPE_LARGE) +#define F_PTE_TYPE_IS_SMALL_PAGE(pte) ((imu_pte_val(pte) & 0x3) == \ + F_PTE_TYPE_SMALL) + +#define F_PGD_TYPE_PAGE (0x1) +#define F_PGD_TYPE_PAGE_MSK (0x3) +#define F_PGD_TYPE_SECTION (0x2) +#define F_PGD_TYPE_SUPERSECTION (0x2 | (1 << 18)) +#define F_PGD_TYPE_SECTION_MSK (0x3 | (1 << 18)) +#define F_PGD_TYPE_IS_PAGE(pgd) ((imu_pgd_val(pgd)&3) == 1) +#define F_PGD_TYPE_IS_SECTION(pgd) \ + (F_PGD_TYPE_IS_PAGE(pgd) ? 0 : \ + ((imu_pgd_val(pgd) & F_PGD_TYPE_SECTION_MSK) == \ + F_PGD_TYPE_SECTION)) +#define F_PGD_TYPE_IS_SUPERSECTION(pgd) \ + (F_PGD_TYPE_IS_PAGE(pgd) ? 0 : \ + ((imu_pgd_val(pgd) & F_PGD_TYPE_SECTION_MSK) ==\ + F_PGD_TYPE_SUPERSECTION)) + +#define F_PGD_B_BIT BIT(2) +#define F_PGD_C_BIT BIT(3) +#define F_PGD_BIT32_BIT BIT(9) +#define F_PGD_S_BIT BIT(16) +#define F_PGD_NG_BIT BIT(17) +#define F_PGD_NS_BIT_PAGE(ns) (ns << 3) +#define F_PGD_NS_BIT_SECTION(ns) (ns << 19) +#define F_PGD_NS_BIT_SUPERSECTION(ns) (ns << 19) + +#define imu_pgd_index(addr) ((addr) >> IMU_PGDIR_SHIFT) +#define imu_pgd_offset(domain, addr) ((domain)->pgd + imu_pgd_index(addr)) + +#define imu_pte_index(addr) (((addr)>>IMU_PAGE_SHIFT)&(IMU_PTRS_PER_PTE - 1)) +#define imu_pte_offset_map(pgd, addr) (imu_pte_map(pgd) + imu_pte_index(addr)) + +#define F_PGD_PA_PAGETABLE_MSK (~0 << 10) +#define F_PGD_PA_SECTION_MSK (~0 << 20) +#define F_PGD_PA_SUPERSECTION_MSK (~0 << 24) + +static inline struct imu_pte_t *imu_pte_map(struct imu_pgd_t *pgd) +{ + unsigned int pte_pa = imu_pgd_val(*pgd); + + return (struct imu_pte_t *)(__va(pte_pa + & F_PGD_PA_PAGETABLE_MSK)); +} + +static inline struct imu_pgd_t *imu_supersection_start(struct imu_pgd_t *pgd) +{ + return (struct imu_pgd_t *)(round_down((unsigned long)pgd, (16 * 4))); +} + +static inline void m4u_set_pgd_val(struct imu_pgd_t *pgd, unsigned int val) +{ + imu_pgd_val(*pgd) = val; +} + +static inline unsigned int __m4u_get_pgd_attr(unsigned int prot, + bool super, bool imu4gmode) +{ + unsigned int pgprot; + + pgprot = F_PGD_NS_BIT_SECTION(1) | F_PGD_S_BIT; + pgprot |= super ? F_PGD_TYPE_SUPERSECTION : F_PGD_TYPE_SECTION; + pgprot |= (prot & IOMMU_CACHE) ? (F_PGD_C_BIT | F_PGD_B_BIT) : 0; + pgprot |= imu4gmode ? F_PGD_BIT32_BIT : 0; + + return pgprot; +} + +static inline unsigned int __m4u_get_pte_attr(unsigned int prot, + bool large, bool imu4gmode) +{ + unsigned int pgprot; + + pgprot = F_PTE_S_BIT; + pgprot |= large ? F_PTE_TYPE_LARGE : F_PTE_TYPE_SMALL; + pgprot |= (prot & IOMMU_CACHE) ? (F_PGD_C_BIT | F_PGD_B_BIT) : 0; + pgprot |= imu4gmode ? F_PTE_BIT32_BIT : 0; + + return pgprot; +} + +static inline void m4u_pgtable_flush(void *vastart, void *vaend) +{ + /* + * this function is not acceptable, we will use dma_map_single + * or use dma_pool_create for the level2 pagetable. + */ + __dma_flush_range(vastart, vaend); +} + +/* @return 0 -- pte is allocated + * 1 -- pte is not allocated, because it's allocated by others + * <0 -- error + */ +static int m4u_alloc_pte(struct mtk_iommu_domain *domain, struct imu_pgd_t *pgd, + unsigned int pgprot) +{ + void *pte_new_va; + phys_addr_t pte_new; + struct kmem_cache *pte_kmem = domain->piommuinfo->m4u_pte_kmem; + struct device *dev = domain->piommuinfo->dev; + unsigned int ret; + + pte_new_va = kmem_cache_zalloc(pte_kmem, GFP_KERNEL); + if (unlikely(!pte_new_va)) { + dev_err(dev, "%s:fail, no memory\n", __func__); + return -ENOMEM; + } + pte_new = __virt_to_phys(pte_new_va); + + /* check pte alignment -- must 1K align */ + if (unlikely(pte_new & (IMU_BYTES_PER_PTE - 1))) { + dev_err(dev, "%s:fail, not align pa=0x%pa, va=0x%p\n", + __func__, &pte_new, pte_new_va); + kmem_cache_free(pte_kmem, (void *)pte_new_va); + return -ENOMEM; + } + + /* because someone else may have allocated for this pgd first */ + if (likely(!imu_pgd_val(*pgd))) { + m4u_set_pgd_val(pgd, (unsigned int)(pte_new) | pgprot); + dev_dbg(dev, "%s:pgd:0x%p,pte_va:0x%p,pte_pa:%pa,value:0x%x\n", + __func__, pgd, pte_new_va, + &pte_new, (unsigned int)(pte_new) | pgprot); + ret = 0; + } else { + /* allocated by other thread */ + dev_dbg(dev, "m4u pte allocated by others: pgd=0x%p\n", pgd); + kmem_cache_free(pte_kmem, (void *)pte_new_va); + ret = 1; + } + return ret; +} + +static int m4u_free_pte(struct mtk_iommu_domain *domain, struct imu_pgd_t *pgd) +{ + struct imu_pte_t *pte_old; + struct kmem_cache *pte_kmem = domain->piommuinfo->m4u_pte_kmem; + + pte_old = imu_pte_map(pgd); + m4u_set_pgd_val(pgd, 0); + + kmem_cache_free(pte_kmem, pte_old); + + return 0; +} + +static int m4u_map_page(struct mtk_iommu_domain *m4u_domain, unsigned int iova, + phys_addr_t pa, unsigned int prot, bool largepage) +{ + int ret; + struct imu_pgd_t *pgd; + struct imu_pte_t *pte; + unsigned int pte_new, pgprot; + unsigned int padscpt; + struct device *dev = m4u_domain->piommuinfo->dev; + unsigned int mask = largepage ? + F_PTE_PA_LARGE_MSK : F_PTE_PA_SMALL_MSK; + unsigned int i, ptenum = largepage ? 16 : 1; + bool imu4gmode = (pa > 0xffffffffL) ? true : false; + + if ((iova & (~mask)) != ((unsigned int)pa & (~mask))) { + dev_err(dev, "error to mk_pte: iova=0x%x, pa=0x%pa, type=%s\n", + iova, &pa, largepage ? "large page" : "small page"); + return -EINVAL; + } + + iova &= mask; + padscpt = (unsigned int)pa & mask; + + pgprot = F_PGD_TYPE_PAGE | F_PGD_NS_BIT_PAGE(1); + pgd = imu_pgd_offset(m4u_domain, iova); + if (!imu_pgd_val(*pgd)) { + ret = m4u_alloc_pte(m4u_domain, pgd, pgprot); + if (ret < 0) + return ret; + else if (ret > 0) + pte_new = 0; + else + pte_new = 1; + } else { + if ((imu_pgd_val(*pgd) & (~F_PGD_PA_PAGETABLE_MSK)) != pgprot) { + dev_err(dev, "%s: iova=0x%x, pgd=0x%x, pgprot=0x%x\n", + __func__, iova, imu_pgd_val(*pgd), pgprot); + return -1; + } + pte_new = 0; + } + + pgprot = __m4u_get_pte_attr(prot, largepage, imu4gmode); + pte = imu_pte_offset_map(pgd, iova); + + dev_dbg(dev, "%s:iova:0x%x,pte:0x%p(0x%p+0x%x),pa:%pa,value:0x%x-%s\n", + __func__, iova, &imu_pte_val(*pte), imu_pte_map(pgd), + imu_pte_index(iova), &pa, padscpt | pgprot, + largepage ? "large page" : "small page"); + + for (i = 0; i < ptenum; i++) { + if (imu_pte_val(pte[i])) { + dev_err(dev, "%s: pte=0x%x, i=%d\n", __func__, + imu_pte_val(pte[i]), i); + goto err_out; + } + imu_pte_val(pte[i]) = padscpt | pgprot; + } + + m4u_pgtable_flush(pte, pte + ptenum); + + return 0; + + err_out: + for (i--; i >= 0; i--) + imu_pte_val(pte[i]) = 0; + return -EEXIST; +} + +static int m4u_map_section(struct mtk_iommu_domain *m4u_domain, + unsigned int iova, phys_addr_t pa, + unsigned int prot, bool supersection) +{ + int i; + struct imu_pgd_t *pgd; + unsigned int pgprot; + unsigned int padscpt; + struct device *dev = m4u_domain->piommuinfo->dev; + unsigned int mask = supersection ? + F_PGD_PA_SUPERSECTION_MSK : F_PGD_PA_SECTION_MSK; + unsigned int pgdnum = supersection ? 16 : 1; + bool imu4gmode = (pa > 0xffffffffL) ? true : false; + + if ((iova & (~mask)) != ((unsigned int)pa & (~mask))) { + dev_err(dev, "error to mk_pte: iova=0x%x, pa=0x%pa,type=%s\n", + iova, &pa, supersection ? "supersection" : "section"); + return -EINVAL; + } + + iova &= mask; + padscpt = (unsigned int)pa & mask; + + pgprot = __m4u_get_pgd_attr(prot, supersection, imu4gmode); + pgd = imu_pgd_offset(m4u_domain, iova); + + dev_dbg(dev, "%s:iova:0x%x,pgd:0x%p(0x%p+0x%x),pa:%pa,value:0x%x-%s\n", + __func__, iova, pgd, (m4u_domain)->pgd, imu_pgd_index(iova), + &pa, padscpt | pgprot, + supersection ? "supersection" : "section"); + + for (i = 0; i < pgdnum; i++) { + if (unlikely(imu_pgd_val(*pgd))) { + dev_err(dev, "%s:iova=0x%x, pgd=0x%x, i=%d\n", __func__, + iova, imu_pgd_val(*pgd), i); + goto err_out; + } + m4u_set_pgd_val(pgd, padscpt | pgprot); + pgd++; + } + return 0; + + err_out: + for (pgd--; i > 0; i--) { + m4u_set_pgd_val(pgd, 0); + pgd--; + } + return -EEXIST; +} + +int m4u_map(struct mtk_iommu_domain *m4u_domain, unsigned int iova, + phys_addr_t paddr, unsigned int size, unsigned int prot) +{ + if (size == SZ_4K) {/* most case */ + return m4u_map_page(m4u_domain, iova, paddr, prot, false); + } else if (size == SZ_64K) { + return m4u_map_page(m4u_domain, iova, paddr, prot, true); + } else if (size == SZ_1M) { + return m4u_map_section(m4u_domain, iova, paddr, prot, false); + } else if (size == SZ_16M) { + return m4u_map_section(m4u_domain, iova, paddr, prot, true); + } else { + return -EINVAL; + } +} + +static int m4u_check_free_pte(struct mtk_iommu_domain *domain, + struct imu_pgd_t *pgd) +{ + struct imu_pte_t *pte; + int i; + + pte = imu_pte_map(pgd); + for (i = 0; i < IMU_PTRS_PER_PTE; i++, pte++) { + if (imu_pte_val(*pte) != 0) + return 1; + } + + m4u_free_pte(domain, pgd); + return 0; +} + +int m4u_unmap(struct mtk_iommu_domain *domain, unsigned int iova, + unsigned int size) +{ + struct imu_pgd_t *pgd; + int i, ret; + unsigned long end_plus_1 = (unsigned long)iova + size; + + do { + pgd = imu_pgd_offset(domain, iova); + + if (F_PGD_TYPE_IS_PAGE(*pgd)) { + struct imu_pte_t *pte; + unsigned int pte_offset; + unsigned int num_to_clean; + + pte_offset = imu_pte_index(iova); + num_to_clean = + min((unsigned int)((end_plus_1 - iova) / PAGE_SIZE), + (unsigned int)(IMU_PTRS_PER_PTE - pte_offset)); + + pte = imu_pte_offset_map(pgd, iova); + + memset(pte, 0, num_to_clean << 2); + + ret = m4u_check_free_pte(domain, pgd); + if (ret == 1)/* pte is not freed, need to flush pte */ + m4u_pgtable_flush(pte, pte + num_to_clean); + + iova += num_to_clean << PAGE_SHIFT; + } else if (F_PGD_TYPE_IS_SECTION(*pgd)) { + m4u_set_pgd_val(pgd, 0); + iova += MMU_SECTION_SIZE; + } else if (F_PGD_TYPE_IS_SUPERSECTION(*pgd)) { + struct imu_pgd_t *start = imu_supersection_start(pgd); + + if (unlikely(start != pgd)) + dev_err(domain->piommuinfo->dev, + "%s:supper not align,iova=0x%x,pgd=0x%x\n", + __func__, iova, imu_pgd_val(*pgd)); + + for (i = 0; i < 16; i++) + m4u_set_pgd_val((start+i), 0); + + iova = (iova + MMU_SUPERSECTION_SIZE) & + (~(MMU_SUPERSECTION_SIZE - 1)); + } else { + iova += MMU_SECTION_SIZE; + } + } while (iova < end_plus_1 && iova); + + return 0; +} + +int m4u_get_pte_info(const struct mtk_iommu_domain *domain, unsigned int iova, + struct m4u_pte_info_t *pte_info) +{ + struct imu_pgd_t *pgd; + struct imu_pte_t *pte; + unsigned int pa = 0; + unsigned int size; + int valid = 1; + + pgd = imu_pgd_offset(domain, iova); + + if (F_PGD_TYPE_IS_PAGE(*pgd)) { + pte = imu_pte_offset_map(pgd, iova); + if (F_PTE_TYPE_GET(imu_pte_val(*pte)) == F_PTE_TYPE_LARGE) { + pa = imu_pte_val(*pte) & F_PTE_PA_LARGE_MSK; + pa |= iova & (~F_PTE_PA_LARGE_MSK); + size = MMU_LARGE_PAGE_SIZE; + } else if (F_PTE_TYPE_GET(imu_pte_val(*pte)) + == F_PTE_TYPE_SMALL) { + pa = imu_pte_val(*pte) & F_PTE_PA_SMALL_MSK; + pa |= iova & (~F_PTE_PA_SMALL_MSK); + size = MMU_SMALL_PAGE_SIZE; + } else { + valid = 0; + size = MMU_SMALL_PAGE_SIZE; + } + } else { + pte = NULL; + if (F_PGD_TYPE_IS_SECTION(*pgd)) { + pa = imu_pgd_val(*pgd) & F_PGD_PA_SECTION_MSK; + pa |= iova & (~F_PGD_PA_SECTION_MSK); + size = MMU_SECTION_SIZE; + } else if (F_PGD_TYPE_IS_SUPERSECTION(*pgd)) { + pa = imu_pgd_val(*pgd) & F_PGD_PA_SUPERSECTION_MSK; + pa |= iova & (~F_PGD_PA_SUPERSECTION_MSK); + size = MMU_SUPERSECTION_SIZE; + } else { + valid = 0; + size = MMU_SECTION_SIZE; + } + } + + pte_info->pgd = pgd; + pte_info->pte = pte; + pte_info->iova = iova; + pte_info->pa = pa; + pte_info->size = size; + pte_info->valid = valid; + return 0; +} + diff --git a/drivers/iommu/mtk_iommu_pagetable.h b/drivers/iommu/mtk_iommu_pagetable.h new file mode 100644 index 0000000..ebdfc6c --- /dev/null +++ b/drivers/iommu/mtk_iommu_pagetable.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014-2015 MediaTek Inc. + * Author: Yong Wu <yong.wu@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 MTK_IOMMU_PAGETABLE_H +#define MTK_IOMMU_PAGETABLE_H + +#define MMU_SMALL_PAGE_SIZE (SZ_4K) +#define MMU_LARGE_PAGE_SIZE (SZ_64K) +#define MMU_SECTION_SIZE (SZ_1M) +#define MMU_SUPERSECTION_SIZE (SZ_16M) + +#define IMU_PGDIR_SHIFT 20 +#define IMU_PAGE_SHIFT 12 +#define IMU_PTRS_PER_PGD 4096 +#define IMU_PTRS_PER_PTE 256 +#define IMU_BYTES_PER_PTE (IMU_PTRS_PER_PTE*sizeof(unsigned int)) + +struct imu_pte_t { + unsigned int imu_pte; +}; + +struct imu_pgd_t { + unsigned int imu_pgd; +}; + +#define imu_pte_val(x) ((x).imu_pte) +#define imu_pgd_val(x) ((x).imu_pgd) + +struct m4u_pte_info_t { + struct imu_pgd_t *pgd; + struct imu_pte_t *pte; + unsigned int iova; + phys_addr_t pa; + unsigned int size; + int valid; +}; + +#endif + -- 1.8.1.1.dirty -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html