From patchwork Thu Jul 10 06:53:05 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Zhen Lei X-Patchwork-Id: 4521191 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 3F6CD9F1C4 for ; Thu, 10 Jul 2014 06:59:22 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id A7E41202F0 for ; Thu, 10 Jul 2014 06:59:20 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id EC6202026F for ; Thu, 10 Jul 2014 06:59:18 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1X58I1-0004go-RY; Thu, 10 Jul 2014 06:57:09 +0000 Received: from szxga02-in.huawei.com ([119.145.14.65]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1X58Ha-0003Lq-IX for linux-arm-kernel@lists.infradead.org; Thu, 10 Jul 2014 06:56:53 +0000 Received: from 172.24.2.119 (EHLO SZXEML454-HUB.china.huawei.com) ([172.24.2.119]) by szxrg02-dlp.huawei.com (MOS 4.3.7-GA FastPath queued) with ESMTP id BWM48372; Thu, 10 Jul 2014 14:55:49 +0800 (CST) Received: from localhost (10.177.27.142) by SZXEML454-HUB.china.huawei.com (10.82.67.197) with Microsoft SMTP Server id 14.3.158.1; Thu, 10 Jul 2014 14:54:22 +0800 From: Zhen Lei To: Catalin Marinas , Will Deacon , linux-arm-kernel Subject: [PATCH v3 12/13] iommu/hisilicon: Add support for Hisilicon Ltd. System MMU architecture Date: Thu, 10 Jul 2014 14:53:05 +0800 Message-ID: <1404975186-12032-13-git-send-email-thunder.leizhen@huawei.com> X-Mailer: git-send-email 1.8.4.msysgit.0 In-Reply-To: <1404975186-12032-1-git-send-email-thunder.leizhen@huawei.com> References: <1404975186-12032-1-git-send-email-thunder.leizhen@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.177.27.142] X-CFilter-Loop: Reflected X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140709_235651_947102_BF2F0049 X-CRM114-Status: GOOD ( 20.70 ) X-Spam-Score: -1.4 (-) Cc: Zhen Lei X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00,RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Some Hisilicon smmu features are list below: 1. StreamID is 16 bits, highest 8 bits is VMID, lowest 8 bits is ASID. StreamID match is not support, so direct use VMID and ASID to index context bank. First use VMID to index stage2 context bank, then use ASID to index stage1 context bank. In fact, max 256 stage2 context banks, each stage2 context bank relate to 256 stage1 context banks. |-----------------| |-----------------| |stage2 CB VMID0 |----------->|stage1 CB ASID0 | |-----------------| |-----------------| | ...... | | ...... | |-----------------| |-----------------| |stage2 CB VMID255|-----| |stage2 CB ASID255| |-----------------| | |-----------------| | | | |----->|-----------------| |stage1 CB ASID0 | |-----------------| | ...... | |-----------------| |stage2 CB ASID255| |-----------------| 2. The base address of stage2 context bank is stored in SMMU_CFG_S2CTBAR, and the base address of stage1 context bank is stored in S2_S1CTBAR(locate in stage2 context bank). 3. All context bank fault share 8 groups of context fault registers. That is, max record 8 context faults. Fault syndrome register recorded StreamID to help software determine which context bank issue fault. Signed-off-by: Zhen Lei --- drivers/iommu/Kconfig | 10 + drivers/iommu/Makefile | 1 + drivers/iommu/arm-smmu.h | 2 + drivers/iommu/hisi-smmu.c | 575 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 588 insertions(+) create mode 100644 drivers/iommu/hisi-smmu.c -- 1.8.0 diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index fad5e38..716b0ab 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -309,4 +309,14 @@ config ARM_SMMU Say Y here if your SoC includes an IOMMU device implementing the ARM SMMU architecture. +config HISI_SMMU + bool "Hisilicon Ltd. System MMU (SMMU) Support" + depends on ARM64 || (ARM_LPAE && OF) + select IOMMU_API + select ARM_SMMU_BASE + select ARM_DMA_USE_IOMMU if ARM + help + Say Y here if your SoC includes an IOMMU device implementing + the Hisilicon SMMU architecture. + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 717cfa3..ef932f2 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU_BASE) += arm-smmu-base.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o +obj-$(CONFIG_HISI_SMMU) += hisi-smmu.o obj-$(CONFIG_DMAR_TABLE) += dmar.o obj-$(CONFIG_INTEL_IOMMU) += iova.o intel-iommu.o obj-$(CONFIG_IPMMU_VMSA) += ipmmu-vmsa.o diff --git a/drivers/iommu/arm-smmu.h b/drivers/iommu/arm-smmu.h index 9847a33..b88b5de 100644 --- a/drivers/iommu/arm-smmu.h +++ b/drivers/iommu/arm-smmu.h @@ -151,6 +151,8 @@ struct arm_smmu_device { struct smmu_hwdep_ops *hwdep_ops; + void __iomem *s1cbt; + void __iomem *s2cbt; void __iomem *base; u32 size; u32 pagesize; diff --git a/drivers/iommu/hisi-smmu.c b/drivers/iommu/hisi-smmu.c new file mode 100644 index 0000000..7191d5c --- /dev/null +++ b/drivers/iommu/hisi-smmu.c @@ -0,0 +1,575 @@ +/* + * 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. + * + * Copyright (C) 2014 Hisilicon Limited + * + * Author: Zhen Lei + * + * Hisilicon smmu-v1 implemention + * + */ + +#define pr_fmt(fmt) "hisi-smmu: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm-smmu.h" + +/* Maximum number of context banks per VMID */ +#define HISI_SMMU_MAX_CBS 256 + +#define SMMU_OS_VMID 0 +#define SMMU_CB_NUMIRPT 8 +#define SMMU_S1CBT_SIZE 0x10000 +#define SMMU_S2CBT_SIZE 0x2000 +#define SMMU_S1CBT_SHIFT 16 +#define SMMU_S2CBT_SHIFT 12 + +/* SMMU global address space */ +#define SMMU_GR0(smmu) ((smmu)->base) + +#define SMMU_CTRL_CR0 0x0 +#define SMMU_CTRL_ACR 0x8 +#define SMMU_CFG_S2CTBAR 0xc +#define SMMU_IDR0 0x10 +#define SMMU_IDR1 0x14 +#define SMMU_IDR2 0x18 +#define SMMU_HIS_GFAR_LOW 0x20 +#define SMMU_HIS_GFAR_HIGH 0x24 +#define SMMU_RINT_GFSR 0x28 +#define SMMU_RINT_GFSYNR 0x2c +#define SMMU_CFG_GFIM 0x30 +#define SMMU_CFG_CBF 0x34 +#define SMMU_TLBIALL 0x40 +#define SMMU_TLBIVMID 0x44 +#define SMMU_TLBISID 0x48 +#define SMMU_TLBIVA_LOW 0x4c +#define SMMU_TLBIVA_HIGH 0x50 +#define SMMU_TLBGSYNC 0x54 +#define SMMU_TLBGSTATUS 0x58 +#define SMMU_CXTIALL 0x60 +#define SMMU_CXTIVMID 0x64 +#define SMMU_CXTISID 0x68 +#define SMMU_CXTGSYNC 0x6c +#define SMMU_CXTGSTATUS 0x70 +#define SMMU_RINT_CB_FSR(n) (0x100 + ((n) << 2)) +#define SMMU_RINT_CB_FSYNR(n) (0x120 + ((n) << 2)) +#define SMMU_HIS_CB_FAR_LOW(n) (0x140 + ((n) << 3)) +#define SMMU_HIS_CB_FAR_HIGH(n) (0x144 + ((n) << 3)) +#define SMMU_CTRL_CB_RESUME(n) (0x180 + ((n) << 2)) + +#define SMMU_CB_S2CR(n) (0x0 + ((n) << 5)) +#define SMMU_CB_CBAR(n) (0x4 + ((n) << 5)) +#define SMMU_CB_S1CTBAR(n) (0x18 + ((n) << 5)) + +/* SMMU stage1 context bank and StreamID */ +#define SMMU_CB_BASE(smmu) ((smmu)->s1cbt) +#define SMMU_CB(smmu, n) ((n) << 5) +#define SMMU_CB_SID(cfg) (((u16)SMMU_OS_VMID << 8) | \ + ((cfg)->cbndx)) + +#define SMMU_S1_MAIR0 0x0 +#define SMMU_S1_MAIR1 0x4 +#define SMMU_S1_TTBR0_L 0x8 +#define SMMU_S1_TTBR0_H 0xc +#define SMMU_S1_TTBR1_L 0x10 +#define SMMU_S1_TTBR1_H 0x14 +#define SMMU_S1_TTBCR 0x18 +#define SMMU_S1_SCTLR 0x1c + +#define CFG_CBF_S1_ORGN_WA (1 << 12) +#define CFG_CBF_S1_IRGN_WA (1 << 10) +#define CFG_CBF_S1_SHCFG_IS (3 << 8) +#define CFG_CBF_S2_ORGN_WA (1 << 4) +#define CFG_CBF_S2_IRGN_WA (1 << 2) +#define CFG_CBF_S2_SHCFG_IS (3 << 0) + +#if (PAGE_SIZE == SZ_4K) +#define sACR_WC_EN (7 << 0) +#elif (PAGE_SIZE == SZ_64K) +#define sACR_WC_EN (3 << 5) +#else +#define sACR_WC_EN 0 +#endif + +/* Configuration registers */ +#define sCR0_CLIENTPD (1 << 0) +#define sCR0_GFRE (1 << 1) +#define sCR0_GFIE (1 << 2) +#define sCR0_GCFGFRE (1 << 4) +#define sCR0_GCFGFIE (1 << 5) + +#define ID0_S1TS (1 << 30) +#define ID0_NTS (1 << 28) +#define ID0_CTTW (1 << 14) + +#define ID2_IAS_GET(id2) (((id2) << 0) & 0xff) +#define ID2_OAS_GET(id2) (((id2) << 8) & 0xff) +#define ID2_IPA_SIZE 48 + +#define CBAR_TYPE_S1_TRANS_S2_BYPASS (0x1 << 16) +#define CBAR_S1_BPSHCFG_NSH (0x3 << 8) +#define CBAR_S1_MEMATTR_WB (0xf << 12) +#define CBAR_MTSH_WEAKEST (CBAR_S1_BPSHCFG_NSH | \ + CBAR_S1_MEMATTR_WB) + +#define S2CR_TYPE_SHIFT 16 +#define S2CR_TYPE_TRANS (0 << S2CR_TYPE_SHIFT) +#define S2CR_TYPE_BYPASS (1 << S2CR_TYPE_SHIFT) +#define S2CR_SHCFG_NS (3 << 8) +#define S2CR_MTCFG (1 << 11) +#define S2CR_MEMATTR_OIWB (0xf << 12) +#define S2CR_MTSH_WEAKEST (S2CR_SHCFG_NS | \ + S2CR_MTCFG | S2CR_MEMATTR_OIWB) + +#define SCTLR_CFCFG (1 << 7) +#define SCTLR_CFIE (1 << 6) +#define SCTLR_CFRE (1 << 5) +#define SCTLR_E (1 << 4) +#define SCTLR_AFED (1 << 3) +#define SCTLR_M (1 << 0) + +#define sTLBGSTATUS_GSACTIVE (1 << 0) + +#define HISI_TTBCR_TG0_64K (3 << 14) + +#define FSR_MULTI (1 << 31) +#define FSR_EF (1 << 4) +#define FSR_PF (1 << 3) +#define FSR_AFF (1 << 2) +#define FSR_TF (1 << 1) +#define FSR_IGN (FSR_AFF) +#define FSR_FAULT (FSR_MULTI | FSR_EF | \ + FSR_PF | FSR_TF | FSR_IGN) + +#define FSYNR0_ASID(n) (0xff & ((n) >> 24)) +#define FSYNR0_VMID(n) (0xff & ((n) >> 16)) +#define FSYNR0_WNR (1 << 4) +#define FSYNR0_SS (1 << 2) +#define FSYNR0_CF (1 << 0) + +static int hisi_smmu_alloc_context(struct arm_smmu_device *smmu, + int start, int end, struct arm_smmu_master *master) +{ + if (!master) + return -ENOSPC; + + start = master->streamids[0]; + + return __arm_smmu_alloc_bitmap(smmu->context_map, start, start + 1); +} + +static int hisi_smmu_tlb_sync_finished(struct arm_smmu_device *smmu) +{ + u32 reg; + + reg = readl_relaxed(SMMU_GR0(smmu) + SMMU_TLBGSTATUS); + + return !(reg & sTLBGSTATUS_GSACTIVE); +} + +static void hisi_smmu_tlb_sync(struct arm_smmu_device *smmu) +{ + writel_relaxed(0, SMMU_GR0(smmu) + SMMU_TLBGSYNC); + arm_smmu_tlb_sync_wait(smmu); +} + +static void hisi_smmu_tlb_inv_context(struct arm_smmu_cfg *cfg) +{ + struct arm_smmu_device *smmu = cfg->smmu; + + writel_relaxed(SMMU_CB_SID(cfg), SMMU_GR0(smmu) + SMMU_CXTISID); + hisi_smmu_tlb_sync(smmu); +} + +static irqreturn_t hisi_smmu_context_fault(int irq, void *dev) +{ + int i, flags, ret = IRQ_NONE; + u32 fsr, far, fsynr, resume; + unsigned long iova; + struct iommu_domain *domain = dev; + struct arm_smmu_domain *smmu_domain = domain->priv; + struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg; + struct arm_smmu_device *smmu = root_cfg->smmu; + void __iomem *gr0_base = SMMU_GR0(smmu); + + for (i = 0; i < SMMU_CB_NUMIRPT; i++) { + fsynr = readl_relaxed(gr0_base + SMMU_RINT_CB_FSYNR(i)); + + if ((fsynr & FSYNR0_CF) && + (FSYNR0_VMID(fsynr) == SMMU_OS_VMID) && + (root_cfg->cbndx == FSYNR0_ASID(fsynr))) + break; + } + + if (i >= SMMU_CB_NUMIRPT) + return IRQ_NONE; + + fsr = readl_relaxed(gr0_base + SMMU_RINT_CB_FSR(i)); + if (fsr & FSR_IGN) + dev_err_ratelimited(smmu->dev, + "Unexpected context fault (fsr 0x%u)\n", + fsr); + + flags = fsynr & FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ; + + far = readl_relaxed(gr0_base + SMMU_HIS_CB_FAR_LOW(i)); + iova = far; +#ifdef CONFIG_64BIT + far = readl_relaxed(gr0_base + SMMU_HIS_CB_FAR_HIGH(i)); + iova |= ((unsigned long)far << 32); +#endif + + if (!report_iommu_fault(domain, smmu->dev, iova, flags)) { + ret = IRQ_HANDLED; + resume = RESUME_RETRY; + } else { + dev_err_ratelimited(smmu->dev, + "Unhandled context fault: iova=0x%08lx, fsynr=0x%x, cb=%d\n", + iova, fsynr, root_cfg->cbndx); + ret = IRQ_NONE; + resume = RESUME_TERMINATE; + } + + /* Clear the faulting FSR */ + writel(fsr, gr0_base + SMMU_RINT_CB_FSR(i)); + + /* Retry or terminate any stalled transactions */ + if (fsynr & FSYNR0_SS) + writel_relaxed(resume, gr0_base + SMMU_CTRL_CB_RESUME(i)); + + return ret; +} + +static irqreturn_t hisi_smmu_global_fault(int irq, void *dev) +{ + u32 gfsr, gfsynr0; + struct arm_smmu_device *smmu = dev; + void __iomem *gr0_base = SMMU_GR0(smmu); + + gfsr = readl_relaxed(gr0_base + SMMU_RINT_GFSR); + if (!gfsr) + return IRQ_NONE; + + gfsynr0 = readl_relaxed(gr0_base + SMMU_RINT_GFSYNR); + + dev_err_ratelimited(smmu->dev, + "Unexpected global fault, this could be serious\n"); + dev_err_ratelimited(smmu->dev, + "\tGFSR 0x%08x, GFSYNR0 0x%08x\n", gfsr, gfsynr0); + + writel(gfsr, gr0_base + SMMU_RINT_GFSR); + return IRQ_HANDLED; +} + +static void hisi_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) +{ + u32 reg; + struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg; + struct arm_smmu_device *smmu = root_cfg->smmu; + void __iomem *cb_base; + + cb_base = SMMU_CB_BASE(smmu) + SMMU_CB(smmu, root_cfg->cbndx); + + /* TTBR0 */ + arm_smmu_flush_pgtable(smmu, root_cfg->pgd, + PTRS_PER_PGD * sizeof(pgd_t)); + reg = __pa(root_cfg->pgd); + writel_relaxed(reg, cb_base + SMMU_S1_TTBR0_L); + reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32; + writel_relaxed(reg, cb_base + SMMU_S1_TTBR0_H); + + /* + * TTBCR + * We use long descriptor, with inner-shareable WBWA tables in TTBR0. + */ + if (PAGE_SIZE == SZ_4K) + reg = TTBCR_TG0_4K; + else + reg = HISI_TTBCR_TG0_64K; + + reg |= (64 - smmu->s1_output_size) << TTBCR_T0SZ_SHIFT; + + reg |= (TTBCR_SH_IS << TTBCR_SH0_SHIFT) | + (TTBCR_RGN_WBWA << TTBCR_ORGN0_SHIFT) | + (TTBCR_RGN_WBWA << TTBCR_IRGN0_SHIFT); + writel_relaxed(reg, cb_base + SMMU_S1_TTBCR); + + reg = MAIR0_STAGE1; + writel_relaxed(reg, cb_base + SMMU_S1_MAIR0); + + /* SCTLR */ + reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_AFED; +#ifdef __BIG_ENDIAN + reg |= SCTLR_E; +#endif + writel_relaxed(reg, cb_base + SMMU_S1_SCTLR); +} + +static void hisi_smmu_destroy_context_bank(struct arm_smmu_domain *smmu_domain) +{ + struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg; + struct arm_smmu_device *smmu = root_cfg->smmu; + void __iomem *cb_base; + + /* Disable the context bank and nuke the TLB before freeing it. */ + cb_base = SMMU_CB_BASE(smmu) + SMMU_CB(smmu, root_cfg->cbndx); + writel_relaxed(0, cb_base + SMMU_S1_SCTLR); + hisi_smmu_tlb_inv_context(root_cfg); +} + +static int hisi_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, + struct arm_smmu_master *master) +{ + if (SMMU_CB_SID(&smmu_domain->root_cfg) != master->streamids[0]) { + dev_err(smmu_domain->leaf_smmu->dev, "Too many sid attached\n"); + return -ENODEV; + } + + return 0; +} + +static void hisi_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, + struct arm_smmu_master *master) +{ +} + +static int hisi_smmu_device_reset(struct arm_smmu_device *smmu) +{ + void __iomem *gr0_base = SMMU_GR0(smmu); + void __iomem *cb_base; + struct page *cbt_page; + int i = 0; + u32 reg; + + /* Clear Global FSR */ + reg = readl_relaxed(gr0_base + SMMU_RINT_GFSR); + writel(reg, gr0_base + SMMU_RINT_GFSR); + + /* unmask all global interrupt */ + writel_relaxed(0, gr0_base + SMMU_CFG_GFIM); + + reg = CFG_CBF_S1_ORGN_WA | CFG_CBF_S1_IRGN_WA | CFG_CBF_S1_SHCFG_IS; + reg |= CFG_CBF_S2_ORGN_WA | CFG_CBF_S2_IRGN_WA | CFG_CBF_S2_SHCFG_IS; + writel_relaxed(reg, gr0_base + SMMU_CFG_CBF); + + /* stage 2 context banks table */ + reg = readl_relaxed(gr0_base + SMMU_CFG_S2CTBAR); + if (!reg) { + cbt_page = alloc_pages(GFP_DMA32, get_order(SMMU_S2CBT_SIZE)); + if (!cbt_page) { + pr_err("Failed to allocate stage2 CB table\n"); + return -ENOMEM; + } + + reg = (u32)(page_to_phys(cbt_page) >> SMMU_S2CBT_SHIFT); + writel_relaxed(reg, gr0_base + SMMU_CFG_S2CTBAR); + smmu->s2cbt = page_address(cbt_page); + + for (i = 0; i < HISI_SMMU_MAX_CBS; i++) { + writel_relaxed(0, smmu->s2cbt + SMMU_CB_S1CTBAR(i)); + writel_relaxed(S2CR_TYPE_BYPASS, + smmu->s2cbt + SMMU_CB_S2CR(i)); + } + + /* Invalidate all TLB */ + writel_relaxed(0, gr0_base + SMMU_TLBIALL); + hisi_smmu_tlb_sync(smmu); + } else { + smmu->s2cbt = ioremap_cache( + (phys_addr_t)reg << SMMU_S2CBT_SHIFT, SMMU_S2CBT_SIZE); + } + + /* stage 1 context banks table */ + cbt_page = alloc_pages(GFP_DMA32, get_order(SMMU_S1CBT_SIZE)); + if (!cbt_page) { + pr_err("Failed to allocate stage1 CB table\n"); + return -ENOMEM; + } + + reg = (u32)(page_to_phys(cbt_page) >> SMMU_S1CBT_SHIFT); + writel_relaxed(reg, smmu->s2cbt + SMMU_CB_S1CTBAR(SMMU_OS_VMID)); + smmu->s1cbt = page_address(cbt_page); + + /* Make sure all context banks are disabled */ + for (i = 0; i < smmu->num_context_banks; i++) { + cb_base = SMMU_CB_BASE(smmu) + SMMU_CB(smmu, i); + + writel_relaxed(0, cb_base + SMMU_S1_SCTLR); + } + + /* Clear CB_FSR */ + for (i = 0; i < SMMU_CB_NUMIRPT; i++) + writel_relaxed(FSR_FAULT, gr0_base + SMMU_RINT_CB_FSR(i)); + + /* + * Use the weakest attribute, so no impact stage 1 output attribute. + */ + reg = CBAR_TYPE_S1_TRANS_S2_BYPASS | CBAR_MTSH_WEAKEST; + writel_relaxed(reg, smmu->s2cbt + SMMU_CB_CBAR(SMMU_OS_VMID)); + + /* Bypass need use another S2CR */ + reg = S2CR_TYPE_BYPASS | S2CR_MTSH_WEAKEST; + writel_relaxed(reg, smmu->s2cbt + SMMU_CB_S2CR(0xff)); + + /* Mark S2CR as translation */ + reg = S2CR_TYPE_TRANS | S2CR_MTSH_WEAKEST; + writel_relaxed(reg, smmu->s2cbt + SMMU_CB_S2CR(SMMU_OS_VMID)); + + /* Invalidate host OS TLB */ + writel_relaxed(SMMU_OS_VMID, gr0_base + SMMU_TLBIVMID); + hisi_smmu_tlb_sync(smmu); + + writel_relaxed(sACR_WC_EN, gr0_base + SMMU_CTRL_ACR); + + /* Enable fault report */ + reg = readl_relaxed(SMMU_GR0(smmu) + SMMU_CTRL_CR0); + reg |= (sCR0_GFRE | sCR0_GFIE | sCR0_GCFGFRE | sCR0_GCFGFIE); + reg &= ~sCR0_CLIENTPD; + + writel_relaxed(reg, gr0_base + SMMU_CTRL_CR0); + + return 0; +} + +static u32 hisi_smmu_id_size_to_bits(u32 size) +{ + int i; + + for (i = 7; i >= 0; i--) + if ((size >> i) & 0x1) + break; + + return 32 + 4 * (i + 1); +} + +static int hisi_smmu_device_cfg_probe(struct arm_smmu_device *smmu) +{ + void __iomem *gr0_base = SMMU_GR0(smmu); + u32 id; + + dev_notice(smmu->dev, "probing hisi-smmu hardware configuration...\n"); + + smmu->version = 1; + + /* ID0 */ + id = readl_relaxed(gr0_base + SMMU_IDR0); + + if (id & ID0_NTS) { + smmu->features |= ARM_SMMU_FEAT_TRANS_NESTED; + smmu->features |= ARM_SMMU_FEAT_TRANS_S1; + smmu->features |= ARM_SMMU_FEAT_TRANS_S2; + dev_notice(smmu->dev, "\tnested translation\n"); + } else if (id & ID0_S1TS) { + smmu->features |= ARM_SMMU_FEAT_TRANS_S1; + dev_notice(smmu->dev, "\tstage 1 translation\n"); + } + + if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S1)) { + dev_err(smmu->dev, "\tstage 1 translation not support!\n"); + return -ENODEV; + } + + if (id & ID0_CTTW) { + smmu->features |= ARM_SMMU_FEAT_COHERENT_WALK; + dev_notice(smmu->dev, "\tcoherent table walk\n"); + } + + smmu->num_context_banks = HISI_SMMU_MAX_CBS; + + /* ID2 */ + id = readl_relaxed(gr0_base + SMMU_IDR2); + smmu->input_size = hisi_smmu_id_size_to_bits(ID2_IAS_GET(id)); + smmu->s1_output_size = ID2_IPA_SIZE; + smmu->s2_output_size = hisi_smmu_id_size_to_bits(ID2_OAS_GET(id)); + + return 0; +} + +static int hisi_smmu_device_remove(struct arm_smmu_device *smmu) +{ + u32 reg; + + /* + * Here, we only free s1cbt. + * The s2cbt may be shared with hypervisor or other smmu devices. + */ + free_pages((unsigned long)smmu->s1cbt, get_order(SMMU_S1CBT_SIZE)); + + /* Disable fault report */ + reg = readl_relaxed(SMMU_GR0(smmu) + SMMU_CTRL_CR0); + reg &= ~(sCR0_GFRE | sCR0_GFIE | sCR0_GCFGFRE | sCR0_GCFGFIE); + reg |= sCR0_CLIENTPD; + writel(reg, SMMU_GR0(smmu) + SMMU_CTRL_CR0); + + return 0; +} + +static struct smmu_hwdep_ops hisi_smmu_hwdep_ops = { + .alloc_context = hisi_smmu_alloc_context, + .tlb_sync_finished = hisi_smmu_tlb_sync_finished, + .tlb_inv_context = hisi_smmu_tlb_inv_context, + .context_fault = hisi_smmu_context_fault, + .global_fault = hisi_smmu_global_fault, + .init_context_bank = hisi_smmu_init_context_bank, + .destroy_context_bank = hisi_smmu_destroy_context_bank, + .domain_add_master = hisi_smmu_domain_add_master, + .domain_remove_master = hisi_smmu_domain_remove_master, + .device_reset = hisi_smmu_device_reset, + .device_cfg_probe = hisi_smmu_device_cfg_probe, + .device_remove = hisi_smmu_device_remove, +}; + +#ifdef CONFIG_OF +static struct of_device_id hisi_smmu_of_match[] = { + { .compatible = "hisilicon,smmu-v1", }, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_smmu_of_match); +#endif + +static int arm_smmu_device_probe(struct platform_device *pdev) +{ + return arm_smmu_device_dt_probe(pdev, &hisi_smmu_hwdep_ops); +} + +static struct platform_driver hisi_smmu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "hisi-smmu", + .of_match_table = of_match_ptr(hisi_smmu_of_match), + }, + .probe = arm_smmu_device_probe, + .remove = arm_smmu_device_remove, +}; + +static int __init hisi_smmu_init(void) +{ + return platform_driver_register(&hisi_smmu_driver); +} + +static void __exit hisi_smmu_exit(void) +{ + return platform_driver_unregister(&hisi_smmu_driver); +} + +subsys_initcall(hisi_smmu_init); +module_exit(hisi_smmu_exit); + +MODULE_DESCRIPTION("IOMMU API for Hisilicon architected SMMU implementations"); +MODULE_AUTHOR("Zhen Lei "); +MODULE_LICENSE("GPL v2");