Commit d6a60fc1 authored by Joerg Roedel's avatar Joerg Roedel

Merge branches 'arm/exynos', 'ppc/pamu', 'arm/smmu', 'x86/amd' and 'iommu/fixes' into next

...@@ -28,6 +28,9 @@ struct dev_archdata { ...@@ -28,6 +28,9 @@ struct dev_archdata {
void *iommu_table_base; void *iommu_table_base;
} dma_data; } dma_data;
#ifdef CONFIG_IOMMU_API
void *iommu_domain;
#endif
#ifdef CONFIG_SWIOTLB #ifdef CONFIG_SWIOTLB
dma_addr_t max_direct_dma_addr; dma_addr_t max_direct_dma_addr;
#endif #endif
......
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
*/
#ifndef __FSL_PAMU_STASH_H
#define __FSL_PAMU_STASH_H
/* cache stash targets */
enum pamu_stash_target {
PAMU_ATTR_CACHE_L1 = 1,
PAMU_ATTR_CACHE_L2,
PAMU_ATTR_CACHE_L3,
};
/*
* This attribute allows configuring stashig specific parameters
* in the PAMU hardware.
*/
struct pamu_stash_attribute {
u32 cpu; /* cpu number */
u32 cache; /* cache to stash to: L1,L2,L3 */
};
#endif /* __FSL_PAMU_STASH_H */
...@@ -16,6 +16,11 @@ ...@@ -16,6 +16,11 @@
struct platform_device; struct platform_device;
/* FSL PCI controller BRR1 register */
#define PCI_FSL_BRR1 0xbf8
#define PCI_FSL_BRR1_VER 0xffff
#define PCIE_LTSSM 0x0404 /* PCIE Link Training and Status */ #define PCIE_LTSSM 0x0404 /* PCIE Link Training and Status */
#define PCIE_LTSSM_L0 0x16 /* L0 state */ #define PCIE_LTSSM_L0 0x16 /* L0 state */
#define PCIE_IP_REV_2_2 0x02080202 /* PCIE IP block version Rev2.2 */ #define PCIE_IP_REV_2_2 0x02080202 /* PCIE IP block version Rev2.2 */
......
...@@ -17,6 +17,16 @@ config OF_IOMMU ...@@ -17,6 +17,16 @@ config OF_IOMMU
def_bool y def_bool y
depends on OF depends on OF
config FSL_PAMU
bool "Freescale IOMMU support"
depends on PPC_E500MC
select IOMMU_API
select GENERIC_ALLOCATOR
help
Freescale PAMU support. PAMU is the IOMMU present on Freescale QorIQ platforms.
PAMU can authorize memory access, remap the memory address, and remap I/O
transaction types.
# MSM IOMMU support # MSM IOMMU support
config MSM_IOMMU config MSM_IOMMU
bool "MSM IOMMU Support" bool "MSM IOMMU Support"
......
...@@ -16,3 +16,4 @@ obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o ...@@ -16,3 +16,4 @@ obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o
obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
obj-$(CONFIG_SHMOBILE_IOMMU) += shmobile-iommu.o obj-$(CONFIG_SHMOBILE_IOMMU) += shmobile-iommu.o
obj-$(CONFIG_SHMOBILE_IPMMU) += shmobile-ipmmu.o obj-$(CONFIG_SHMOBILE_IPMMU) += shmobile-ipmmu.o
obj-$(CONFIG_FSL_PAMU) += fsl_pamu.o fsl_pamu_domain.o
...@@ -456,8 +456,10 @@ static int iommu_init_device(struct device *dev) ...@@ -456,8 +456,10 @@ static int iommu_init_device(struct device *dev)
} }
ret = init_iommu_group(dev); ret = init_iommu_group(dev);
if (ret) if (ret) {
free_dev_data(dev_data);
return ret; return ret;
}
if (pci_iommuv2_capable(pdev)) { if (pci_iommuv2_capable(pdev)) {
struct amd_iommu *iommu; struct amd_iommu *iommu;
......
...@@ -1384,7 +1384,7 @@ static int iommu_init_msi(struct amd_iommu *iommu) ...@@ -1384,7 +1384,7 @@ static int iommu_init_msi(struct amd_iommu *iommu)
if (iommu->int_enabled) if (iommu->int_enabled)
goto enable_faults; goto enable_faults;
if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSI)) if (iommu->dev->msi_cap)
ret = iommu_setup_msi(iommu); ret = iommu_setup_msi(iommu);
else else
ret = -ENODEV; ret = -ENODEV;
......
...@@ -56,9 +56,6 @@ ...@@ -56,9 +56,6 @@
/* Maximum number of mapping groups per SMMU */ /* Maximum number of mapping groups per SMMU */
#define ARM_SMMU_MAX_SMRS 128 #define ARM_SMMU_MAX_SMRS 128
/* Number of VMIDs per SMMU */
#define ARM_SMMU_NUM_VMIDS 256
/* SMMU global address space */ /* SMMU global address space */
#define ARM_SMMU_GR0(smmu) ((smmu)->base) #define ARM_SMMU_GR0(smmu) ((smmu)->base)
#define ARM_SMMU_GR1(smmu) ((smmu)->base + (smmu)->pagesize) #define ARM_SMMU_GR1(smmu) ((smmu)->base + (smmu)->pagesize)
...@@ -87,6 +84,7 @@ ...@@ -87,6 +84,7 @@
#define ARM_SMMU_PTE_AP_UNPRIV (((pteval_t)1) << 6) #define ARM_SMMU_PTE_AP_UNPRIV (((pteval_t)1) << 6)
#define ARM_SMMU_PTE_AP_RDONLY (((pteval_t)2) << 6) #define ARM_SMMU_PTE_AP_RDONLY (((pteval_t)2) << 6)
#define ARM_SMMU_PTE_ATTRINDX_SHIFT 2 #define ARM_SMMU_PTE_ATTRINDX_SHIFT 2
#define ARM_SMMU_PTE_nG (((pteval_t)1) << 11)
/* Stage-2 PTE */ /* Stage-2 PTE */
#define ARM_SMMU_PTE_HAP_FAULT (((pteval_t)0) << 6) #define ARM_SMMU_PTE_HAP_FAULT (((pteval_t)0) << 6)
...@@ -223,6 +221,7 @@ ...@@ -223,6 +221,7 @@
#define ARM_SMMU_CB_FAR_LO 0x60 #define ARM_SMMU_CB_FAR_LO 0x60
#define ARM_SMMU_CB_FAR_HI 0x64 #define ARM_SMMU_CB_FAR_HI 0x64
#define ARM_SMMU_CB_FSYNR0 0x68 #define ARM_SMMU_CB_FSYNR0 0x68
#define ARM_SMMU_CB_S1_TLBIASID 0x610
#define SCTLR_S1_ASIDPNE (1 << 12) #define SCTLR_S1_ASIDPNE (1 << 12)
#define SCTLR_CFCFG (1 << 7) #define SCTLR_CFCFG (1 << 7)
...@@ -282,6 +281,8 @@ ...@@ -282,6 +281,8 @@
#define TTBCR2_ADDR_44 4 #define TTBCR2_ADDR_44 4
#define TTBCR2_ADDR_48 5 #define TTBCR2_ADDR_48 5
#define TTBRn_HI_ASID_SHIFT 16
#define MAIR_ATTR_SHIFT(n) ((n) << 3) #define MAIR_ATTR_SHIFT(n) ((n) << 3)
#define MAIR_ATTR_MASK 0xff #define MAIR_ATTR_MASK 0xff
#define MAIR_ATTR_DEVICE 0x04 #define MAIR_ATTR_DEVICE 0x04
...@@ -305,7 +306,7 @@ ...@@ -305,7 +306,7 @@
#define FSR_IGN (FSR_AFF | FSR_ASF | FSR_TLBMCF | \ #define FSR_IGN (FSR_AFF | FSR_ASF | FSR_TLBMCF | \
FSR_TLBLKF) FSR_TLBLKF)
#define FSR_FAULT (FSR_MULTI | FSR_SS | FSR_UUT | \ #define FSR_FAULT (FSR_MULTI | FSR_SS | FSR_UUT | \
FSR_EF | FSR_PF | FSR_TF) FSR_EF | FSR_PF | FSR_TF | FSR_IGN)
#define FSYNR0_WNR (1 << 4) #define FSYNR0_WNR (1 << 4)
...@@ -365,21 +366,21 @@ struct arm_smmu_device { ...@@ -365,21 +366,21 @@ struct arm_smmu_device {
u32 num_context_irqs; u32 num_context_irqs;
unsigned int *irqs; unsigned int *irqs;
DECLARE_BITMAP(vmid_map, ARM_SMMU_NUM_VMIDS);
struct list_head list; struct list_head list;
struct rb_root masters; struct rb_root masters;
}; };
struct arm_smmu_cfg { struct arm_smmu_cfg {
struct arm_smmu_device *smmu; struct arm_smmu_device *smmu;
u8 vmid;
u8 cbndx; u8 cbndx;
u8 irptndx; u8 irptndx;
u32 cbar; u32 cbar;
pgd_t *pgd; pgd_t *pgd;
}; };
#define ARM_SMMU_CB_ASID(cfg) ((cfg)->cbndx)
#define ARM_SMMU_CB_VMID(cfg) ((cfg)->cbndx + 1)
struct arm_smmu_domain { struct arm_smmu_domain {
/* /*
* A domain can span across multiple, chained SMMUs and requires * A domain can span across multiple, chained SMMUs and requires
...@@ -533,6 +534,25 @@ static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu) ...@@ -533,6 +534,25 @@ static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu)
} }
} }
static void arm_smmu_tlb_inv_context(struct arm_smmu_cfg *cfg)
{
struct arm_smmu_device *smmu = cfg->smmu;
void __iomem *base = ARM_SMMU_GR0(smmu);
bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS;
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);
} else {
base = ARM_SMMU_GR0(smmu);
writel_relaxed(ARM_SMMU_CB_VMID(cfg),
base + ARM_SMMU_GR0_TLBIVMID);
}
arm_smmu_tlb_sync(smmu);
}
static irqreturn_t arm_smmu_context_fault(int irq, void *dev) static irqreturn_t arm_smmu_context_fault(int irq, void *dev)
{ {
int flags, ret; int flags, ret;
...@@ -590,6 +610,9 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev) ...@@ -590,6 +610,9 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev)
void __iomem *gr0_base = ARM_SMMU_GR0(smmu); void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
gfsr = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSR); gfsr = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSR);
if (!gfsr)
return IRQ_NONE;
gfsynr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR0); gfsynr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR0);
gfsynr1 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR1); gfsynr1 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR1);
gfsynr2 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR2); gfsynr2 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR2);
...@@ -601,7 +624,7 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev) ...@@ -601,7 +624,7 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev)
gfsr, gfsynr0, gfsynr1, gfsynr2); gfsr, gfsynr0, gfsynr1, gfsynr2);
writel(gfsr, gr0_base + ARM_SMMU_GR0_sGFSR); writel(gfsr, gr0_base + ARM_SMMU_GR0_sGFSR);
return IRQ_NONE; return IRQ_HANDLED;
} }
static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
...@@ -618,14 +641,15 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) ...@@ -618,14 +641,15 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, root_cfg->cbndx); cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, root_cfg->cbndx);
/* CBAR */ /* CBAR */
reg = root_cfg->cbar | reg = root_cfg->cbar;
(root_cfg->vmid << CBAR_VMID_SHIFT);
if (smmu->version == 1) if (smmu->version == 1)
reg |= root_cfg->irptndx << CBAR_IRPTNDX_SHIFT; reg |= root_cfg->irptndx << CBAR_IRPTNDX_SHIFT;
/* Use the weakest memory type, so it is overridden by the pte */ /* Use the weakest memory type, so it is overridden by the pte */
if (stage1) if (stage1)
reg |= (CBAR_S1_MEMATTR_WB << CBAR_S1_MEMATTR_SHIFT); reg |= (CBAR_S1_MEMATTR_WB << CBAR_S1_MEMATTR_SHIFT);
else
reg |= ARM_SMMU_CB_VMID(root_cfg) << CBAR_VMID_SHIFT;
writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(root_cfg->cbndx)); writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(root_cfg->cbndx));
if (smmu->version > 1) { if (smmu->version > 1) {
...@@ -687,15 +711,11 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) ...@@ -687,15 +711,11 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
/* TTBR0 */ /* TTBR0 */
reg = __pa(root_cfg->pgd); reg = __pa(root_cfg->pgd);
#ifndef __BIG_ENDIAN
writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO); writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO);
reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32; reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32;
if (stage1)
reg |= ARM_SMMU_CB_ASID(root_cfg) << TTBRn_HI_ASID_SHIFT;
writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI); writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI);
#else
writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI);
reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32;
writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO);
#endif
/* /*
* TTBCR * TTBCR
...@@ -750,10 +770,6 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) ...@@ -750,10 +770,6 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR0); writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR0);
} }
/* Nuke the TLB */
writel_relaxed(root_cfg->vmid, gr0_base + ARM_SMMU_GR0_TLBIVMID);
arm_smmu_tlb_sync(smmu);
/* SCTLR */ /* SCTLR */
reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP; reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP;
if (stage1) if (stage1)
...@@ -790,11 +806,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, ...@@ -790,11 +806,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
return -ENODEV; return -ENODEV;
} }
ret = __arm_smmu_alloc_bitmap(smmu->vmid_map, 0, ARM_SMMU_NUM_VMIDS);
if (IS_ERR_VALUE(ret))
return ret;
root_cfg->vmid = ret;
if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) { if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
/* /*
* We will likely want to change this if/when KVM gets * We will likely want to change this if/when KVM gets
...@@ -813,10 +824,9 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, ...@@ -813,10 +824,9 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
ret = __arm_smmu_alloc_bitmap(smmu->context_map, start, ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
smmu->num_context_banks); smmu->num_context_banks);
if (IS_ERR_VALUE(ret)) if (IS_ERR_VALUE(ret))
goto out_free_vmid; return ret;
root_cfg->cbndx = ret; root_cfg->cbndx = ret;
if (smmu->version == 1) { if (smmu->version == 1) {
root_cfg->irptndx = atomic_inc_return(&smmu->irptndx); root_cfg->irptndx = atomic_inc_return(&smmu->irptndx);
root_cfg->irptndx %= smmu->num_context_irqs; root_cfg->irptndx %= smmu->num_context_irqs;
...@@ -840,8 +850,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, ...@@ -840,8 +850,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
out_free_context: out_free_context:
__arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx); __arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx);
out_free_vmid:
__arm_smmu_free_bitmap(smmu->vmid_map, root_cfg->vmid);
return ret; return ret;
} }
...@@ -850,17 +858,22 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) ...@@ -850,17 +858,22 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
struct arm_smmu_domain *smmu_domain = domain->priv; struct arm_smmu_domain *smmu_domain = domain->priv;
struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg; struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg;
struct arm_smmu_device *smmu = root_cfg->smmu; struct arm_smmu_device *smmu = root_cfg->smmu;
void __iomem *cb_base;
int irq; int irq;
if (!smmu) if (!smmu)
return; return;
/* Disable the context bank and nuke the TLB before freeing it. */
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, root_cfg->cbndx);
writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR);
arm_smmu_tlb_inv_context(root_cfg);
if (root_cfg->irptndx != -1) { if (root_cfg->irptndx != -1) {
irq = smmu->irqs[smmu->num_global_irqs + root_cfg->irptndx]; irq = smmu->irqs[smmu->num_global_irqs + root_cfg->irptndx];
free_irq(irq, domain); free_irq(irq, domain);
} }
__arm_smmu_free_bitmap(smmu->vmid_map, root_cfg->vmid);
__arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx); __arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx);
} }
...@@ -959,6 +972,11 @@ static void arm_smmu_free_pgtables(struct arm_smmu_domain *smmu_domain) ...@@ -959,6 +972,11 @@ static void arm_smmu_free_pgtables(struct arm_smmu_domain *smmu_domain)
static void arm_smmu_domain_destroy(struct iommu_domain *domain) static void arm_smmu_domain_destroy(struct iommu_domain *domain)
{ {
struct arm_smmu_domain *smmu_domain = domain->priv; struct arm_smmu_domain *smmu_domain = domain->priv;
/*
* Free the domain resources. We assume that all devices have
* already been detached.
*/
arm_smmu_destroy_domain_context(domain); arm_smmu_destroy_domain_context(domain);
arm_smmu_free_pgtables(smmu_domain); arm_smmu_free_pgtables(smmu_domain);
kfree(smmu_domain); kfree(smmu_domain);
...@@ -1199,7 +1217,7 @@ static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd, ...@@ -1199,7 +1217,7 @@ static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd,
} }
if (stage == 1) { if (stage == 1) {
pteval |= ARM_SMMU_PTE_AP_UNPRIV; pteval |= ARM_SMMU_PTE_AP_UNPRIV | ARM_SMMU_PTE_nG;
if (!(flags & IOMMU_WRITE) && (flags & IOMMU_READ)) if (!(flags & IOMMU_WRITE) && (flags & IOMMU_READ))
pteval |= ARM_SMMU_PTE_AP_RDONLY; pteval |= ARM_SMMU_PTE_AP_RDONLY;
...@@ -1415,13 +1433,9 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova, ...@@ -1415,13 +1433,9 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
{ {
int ret; int ret;
struct arm_smmu_domain *smmu_domain = domain->priv; 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 = ARM_SMMU_GR0(smmu);
ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0); ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0);
writel_relaxed(root_cfg->vmid, gr0_base + ARM_SMMU_GR0_TLBIVMID); arm_smmu_tlb_inv_context(&smmu_domain->root_cfg);
arm_smmu_tlb_sync(smmu);
return ret ? ret : size; return ret ? ret : size;
} }
...@@ -1544,6 +1558,7 @@ static struct iommu_ops arm_smmu_ops = { ...@@ -1544,6 +1558,7 @@ static struct iommu_ops arm_smmu_ops = {
static void arm_smmu_device_reset(struct arm_smmu_device *smmu) static void arm_smmu_device_reset(struct arm_smmu_device *smmu)
{ {
void __iomem *gr0_base = ARM_SMMU_GR0(smmu); void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
void __iomem *sctlr_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB_SCTLR;
int i = 0; int i = 0;
u32 scr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sCR0); u32 scr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sCR0);
...@@ -1553,6 +1568,10 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) ...@@ -1553,6 +1568,10 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu)
writel_relaxed(S2CR_TYPE_BYPASS, gr0_base + ARM_SMMU_GR0_S2CR(i)); writel_relaxed(S2CR_TYPE_BYPASS, gr0_base + ARM_SMMU_GR0_S2CR(i));
} }
/* Make sure all context banks are disabled */
for (i = 0; i < smmu->num_context_banks; ++i)
writel_relaxed(0, sctlr_base + ARM_SMMU_CB(smmu, i));
/* Invalidate the TLB, just in case */ /* Invalidate the TLB, just in case */
writel_relaxed(0, gr0_base + ARM_SMMU_GR0_STLBIALL); writel_relaxed(0, gr0_base + ARM_SMMU_GR0_STLBIALL);
writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLH); writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLH);
...@@ -1906,7 +1925,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev) ...@@ -1906,7 +1925,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
of_node_put(master->of_node); of_node_put(master->of_node);
} }
if (!bitmap_empty(smmu->vmid_map, ARM_SMMU_NUM_VMIDS)) if (!bitmap_empty(smmu->context_map, ARM_SMMU_MAX_CBS))
dev_err(dev, "removing device with active domains!\n"); dev_err(dev, "removing device with active domains!\n");
for (i = 0; i < smmu->num_global_irqs; ++i) for (i = 0; i < smmu->num_global_irqs; ++i)
......
...@@ -247,50 +247,6 @@ static void __sysmmu_set_prefbuf(void __iomem *sfrbase, unsigned long base, ...@@ -247,50 +247,6 @@ static void __sysmmu_set_prefbuf(void __iomem *sfrbase, unsigned long base,
__raw_writel(size - 1 + base, sfrbase + REG_PB0_EADDR + idx * 8); __raw_writel(size - 1 + base, sfrbase + REG_PB0_EADDR + idx * 8);
} }
void exynos_sysmmu_set_prefbuf(struct device *dev,
unsigned long base0, unsigned long size0,
unsigned long base1, unsigned long size1)
{
struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
unsigned long flags;
int i;
BUG_ON((base0 + size0) <= base0);
BUG_ON((size1 > 0) && ((base1 + size1) <= base1));
read_lock_irqsave(&data->lock, flags);
if (!is_sysmmu_active(data))
goto finish;
for (i = 0; i < data->nsfrs; i++) {
if ((readl(data->sfrbases[i] + REG_MMU_VERSION) >> 28) == 3) {
if (!sysmmu_block(data->sfrbases[i]))
continue;
if (size1 == 0) {
if (size0 <= SZ_128K) {
base1 = base0;
size1 = size0;
} else {
size1 = size0 -
ALIGN(size0 / 2, SZ_64K);
size0 = size0 - size1;
base1 = base0 + size0;
}
}
__sysmmu_set_prefbuf(
data->sfrbases[i], base0, size0, 0);
__sysmmu_set_prefbuf(
data->sfrbases[i], base1, size1, 1);
sysmmu_unblock(data->sfrbases[i]);
}
}
finish:
read_unlock_irqrestore(&data->lock, flags);
}
static void __set_fault_handler(struct sysmmu_drvdata *data, static void __set_fault_handler(struct sysmmu_drvdata *data,
sysmmu_fault_handler_t handler) sysmmu_fault_handler_t handler)
{ {
......
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
*/
#define pr_fmt(fmt) "fsl-pamu: %s: " fmt, __func__
#include <linux/init.h>
#include <linux/iommu.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/of_platform.h>
#include <linux/bootmem.h>
#include <linux/genalloc.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/fsl_guts.h>
#include "fsl_pamu.h"
/* define indexes for each operation mapping scenario */
#define OMI_QMAN 0x00
#define OMI_FMAN 0x01
#define OMI_QMAN_PRIV 0x02
#define OMI_CAAM 0x03
#define make64(high, low) (((u64)(high) << 32) | (low))
struct pamu_isr_data {
void __iomem *pamu_reg_base; /* Base address of PAMU regs*/
unsigned int count; /* The number of PAMUs */
};
static struct paace *ppaact;
static struct paace *spaact;
static struct ome *omt;
/*
* Table for matching compatible strings, for device tree
* guts node, for QorIQ SOCs.
* "fsl,qoriq-device-config-2.0" corresponds to T4 & B4
* SOCs. For the older SOCs "fsl,qoriq-device-config-1.0"
* string would be used.
*/
static const struct of_device_id guts_device_ids[] = {
{ .compatible = "fsl,qoriq-device-config-1.0", },
{ .compatible = "fsl,qoriq-device-config-2.0", },
{}
};
/*
* Table for matching compatible strings, for device tree
* L3 cache controller node.
* "fsl,t4240-l3-cache-controller" corresponds to T4,
* "fsl,b4860-l3-cache-controller" corresponds to B4 &
* "fsl,p4080-l3-cache-controller" corresponds to other,
* SOCs.
*/
static const struct of_device_id l3_device_ids[] = {
{ .compatible = "fsl,t4240-l3-cache-controller", },
{ .compatible = "fsl,b4860-l3-cache-controller", },
{ .compatible = "fsl,p4080-l3-cache-controller", },
{}
};
/* maximum subwindows permitted per liodn */
static u32 max_subwindow_count;
/* Pool for fspi allocation */
struct gen_pool *spaace_pool;
/**
* pamu_get_max_subwin_cnt() - Return the maximum supported
* subwindow count per liodn.
*
*/
u32 pamu_get_max_subwin_cnt()
{
return max_subwindow_count;
}
/**
* pamu_get_ppaace() - Return the primary PACCE
* @liodn: liodn PAACT index for desired PAACE
*
* Returns the ppace pointer upon success else return
* null.
*/
static struct paace *pamu_get_ppaace(int liodn)
{
if (!ppaact || liodn >= PAACE_NUMBER_ENTRIES) {
pr_debug("PPAACT doesn't exist\n");
return NULL;
}
return &ppaact[liodn];
}
/**
* pamu_enable_liodn() - Set valid bit of PACCE
* @liodn: liodn PAACT index for desired PAACE
*
* Returns 0 upon success else error code < 0 returned
*/
int pamu_enable_liodn(int liodn)
{
struct paace *ppaace;
ppaace = pamu_get_ppaace(liodn);
if (!ppaace) {
pr_debug("Invalid primary paace entry\n");
return -ENOENT;
}
if (!get_bf(ppaace->addr_bitfields, PPAACE_AF_WSE)) {
pr_debug("liodn %d not configured\n", liodn);
return -EINVAL;
}
/* Ensure that all other stores to the ppaace complete first */
mb();
set_bf(ppaace->addr_bitfields, PAACE_AF_V, PAACE_V_VALID);
mb();
return 0;
}
/**
* pamu_disable_liodn() - Clears valid bit of PACCE
* @liodn: liodn PAACT index for desired PAACE
*
* Returns 0 upon success else error code < 0 returned
*/
int pamu_disable_liodn(int liodn)
{
struct paace *ppaace;
ppaace = pamu_get_ppaace(liodn);
if (!ppaace) {
pr_debug("Invalid primary paace entry\n");
return -ENOENT;
}
set_bf(ppaace->addr_bitfields, PAACE_AF_V, PAACE_V_INVALID);
mb();
return 0;
}
/* Derive the window size encoding for a particular PAACE entry */
static unsigned int map_addrspace_size_to_wse(phys_addr_t addrspace_size)
{
/* Bug if not a power of 2 */
BUG_ON(!is_power_of_2(addrspace_size));
/* window size is 2^(WSE+1) bytes */
return __ffs(addrspace_size) - 1;
}
/* Derive the PAACE window count encoding for the subwindow count */
static unsigned int map_subwindow_cnt_to_wce(u32 subwindow_cnt)
{
/* window count is 2^(WCE+1) bytes */
return __ffs(subwindow_cnt) - 1;
}
/*
* Set the PAACE type as primary and set the coherency required domain
* attribute
*/
static void pamu_init_ppaace(struct paace *ppaace)
{
set_bf(ppaace->addr_bitfields, PAACE_AF_PT, PAACE_PT_PRIMARY);
set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
PAACE_M_COHERENCE_REQ);
}
/*
* Set the PAACE type as secondary and set the coherency required domain
* attribute.
*/
static void pamu_init_spaace(struct paace *spaace)
{
set_bf(spaace->addr_bitfields, PAACE_AF_PT, PAACE_PT_SECONDARY);
set_bf(spaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
PAACE_M_COHERENCE_REQ);
}
/*
* Return the spaace (corresponding to the secondary window index)
* for a particular ppaace.
*/
static struct paace *pamu_get_spaace(struct paace *paace, u32 wnum)
{
u32 subwin_cnt;
struct paace *spaace = NULL;
subwin_cnt = 1UL << (get_bf(paace->impl_attr, PAACE_IA_WCE) + 1);
if (wnum < subwin_cnt)
spaace = &spaact[paace->fspi + wnum];
else
pr_debug("secondary paace out of bounds\n");
return spaace;
}
/**
* pamu_get_fspi_and_allocate() - Allocates fspi index and reserves subwindows
* required for primary PAACE in the secondary
* PAACE table.
* @subwin_cnt: Number of subwindows to be reserved.
*
* A PPAACE entry may have a number of associated subwindows. A subwindow
* corresponds to a SPAACE entry in the SPAACT table. Each PAACE entry stores
* the index (fspi) of the first SPAACE entry in the SPAACT table. This
* function returns the index of the first SPAACE entry. The remaining
* SPAACE entries are reserved contiguously from that index.
*
* Returns a valid fspi index in the range of 0 - SPAACE_NUMBER_ENTRIES on success.
* If no SPAACE entry is available or the allocator can not reserve the required
* number of contiguous entries function returns ULONG_MAX indicating a failure.
*
*/
static unsigned long pamu_get_fspi_and_allocate(u32 subwin_cnt)
{
unsigned long spaace_addr;
spaace_addr = gen_pool_alloc(spaace_pool, subwin_cnt * sizeof(struct paace));
if (!spaace_addr)
return ULONG_MAX;
return (spaace_addr - (unsigned long)spaact) / (sizeof(struct paace));
}
/* Release the subwindows reserved for a particular LIODN */
void pamu_free_subwins(int liodn)
{
struct paace *ppaace;
u32 subwin_cnt, size;
ppaace = pamu_get_ppaace(liodn);
if (!ppaace) {
pr_debug("Invalid liodn entry\n");
return;
}
if (get_bf(ppaace->addr_bitfields, PPAACE_AF_MW)) {
subwin_cnt = 1UL << (get_bf(ppaace->impl_attr, PAACE_IA_WCE) + 1);
size = (subwin_cnt - 1) * sizeof(struct paace);
gen_pool_free(spaace_pool, (unsigned long)&spaact[ppaace->fspi], size);
set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0);
}
}
/*
* Function used for updating stash destination for the coressponding
* LIODN.
*/
int pamu_update_paace_stash(int liodn, u32 subwin, u32 value)
{
struct paace *paace;
paace = pamu_get_ppaace(liodn);
if (!paace) {
pr_debug("Invalid liodn entry\n");
return -ENOENT;
}
if (subwin) {
paace = pamu_get_spaace(paace, subwin - 1);
if (!paace) {
return -ENOENT;
}
}
set_bf(paace->impl_attr, PAACE_IA_CID, value);
mb();
return 0;
}
/* Disable a subwindow corresponding to the LIODN */
int pamu_disable_spaace(int liodn, u32 subwin)
{
struct paace *paace;
paace = pamu_get_ppaace(liodn);
if (!paace) {
pr_debug("Invalid liodn entry\n");
return -ENOENT;
}
if (subwin) {
paace = pamu_get_spaace(paace, subwin - 1);
if (!paace) {
return -ENOENT;
}
set_bf(paace->addr_bitfields, PAACE_AF_V,
PAACE_V_INVALID);
} else {
set_bf(paace->addr_bitfields, PAACE_AF_AP,
PAACE_AP_PERMS_DENIED);
}
mb();
return 0;
}
/**
* pamu_config_paace() - Sets up PPAACE entry for specified liodn
*
* @liodn: Logical IO device number
* @win_addr: starting address of DSA window
* @win-size: size of DSA window
* @omi: Operation mapping index -- if ~omi == 0 then omi not defined
* @rpn: real (true physical) page number
* @stashid: cache stash id for associated cpu -- if ~stashid == 0 then
* stashid not defined
* @snoopid: snoop id for hardware coherency -- if ~snoopid == 0 then
* snoopid not defined
* @subwin_cnt: number of sub-windows
* @prot: window permissions
*
* Returns 0 upon success else error code < 0 returned
*/
int pamu_config_ppaace(int liodn, phys_addr_t win_addr, phys_addr_t win_size,
u32 omi, unsigned long rpn, u32 snoopid, u32 stashid,
u32 subwin_cnt, int prot)
{
struct paace *ppaace;
unsigned long fspi;
if (!is_power_of_2(win_size) || win_size < PAMU_PAGE_SIZE) {
pr_debug("window size too small or not a power of two %llx\n", win_size);
return -EINVAL;
}
if (win_addr & (win_size - 1)) {
pr_debug("window address is not aligned with window size\n");
return -EINVAL;
}
ppaace = pamu_get_ppaace(liodn);
if (!ppaace) {
return -ENOENT;
}
/* window size is 2^(WSE+1) bytes */
set_bf(ppaace->addr_bitfields, PPAACE_AF_WSE,
map_addrspace_size_to_wse(win_size));
pamu_init_ppaace(ppaace);
ppaace->wbah = win_addr >> (PAMU_PAGE_SHIFT + 20);
set_bf(ppaace->addr_bitfields, PPAACE_AF_WBAL,
(win_addr >> PAMU_PAGE_SHIFT));
/* set up operation mapping if it's configured */
if (omi < OME_NUMBER_ENTRIES) {
set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
ppaace->op_encode.index_ot.omi = omi;
} else if (~omi != 0) {
pr_debug("bad operation mapping index: %d\n", omi);
return -EINVAL;
}
/* configure stash id */
if (~stashid != 0)
set_bf(ppaace->impl_attr, PAACE_IA_CID, stashid);
/* configure snoop id */
if (~snoopid != 0)
ppaace->domain_attr.to_host.snpid = snoopid;
if (subwin_cnt) {
/* The first entry is in the primary PAACE instead */
fspi = pamu_get_fspi_and_allocate(subwin_cnt - 1);
if (fspi == ULONG_MAX) {
pr_debug("spaace indexes exhausted\n");
return -EINVAL;
}
/* window count is 2^(WCE+1) bytes */
set_bf(ppaace->impl_attr, PAACE_IA_WCE,
map_subwindow_cnt_to_wce(subwin_cnt));
set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0x1);
ppaace->fspi = fspi;
} else {
set_bf(ppaace->impl_attr, PAACE_IA_ATM, PAACE_ATM_WINDOW_XLATE);
ppaace->twbah = rpn >> 20;
set_bf(ppaace->win_bitfields, PAACE_WIN_TWBAL, rpn);
set_bf(ppaace->addr_bitfields, PAACE_AF_AP, prot);
set_bf(ppaace->impl_attr, PAACE_IA_WCE, 0);
set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0);
}
mb();
return 0;
}
/**
* pamu_config_spaace() - Sets up SPAACE entry for specified subwindow
*
* @liodn: Logical IO device number
* @subwin_cnt: number of sub-windows associated with dma-window
* @subwin: subwindow index
* @subwin_size: size of subwindow
* @omi: Operation mapping index
* @rpn: real (true physical) page number
* @snoopid: snoop id for hardware coherency -- if ~snoopid == 0 then
* snoopid not defined
* @stashid: cache stash id for associated cpu
* @enable: enable/disable subwindow after reconfiguration
* @prot: sub window permissions
*
* Returns 0 upon success else error code < 0 returned
*/
int pamu_config_spaace(int liodn, u32 subwin_cnt, u32 subwin,
phys_addr_t subwin_size, u32 omi, unsigned long rpn,
u32 snoopid, u32 stashid, int enable, int prot)
{
struct paace *paace;
/* setup sub-windows */
if (!subwin_cnt) {
pr_debug("Invalid subwindow count\n");
return -EINVAL;
}
paace = pamu_get_ppaace(liodn);
if (subwin > 0 && subwin < subwin_cnt && paace) {
paace = pamu_get_spaace(paace, subwin - 1);
if (paace && !(paace->addr_bitfields & PAACE_V_VALID)) {
pamu_init_spaace(paace);
set_bf(paace->addr_bitfields, SPAACE_AF_LIODN, liodn);
}
}
if (!paace) {
pr_debug("Invalid liodn entry\n");
return -ENOENT;
}
if (!is_power_of_2(subwin_size) || subwin_size < PAMU_PAGE_SIZE) {
pr_debug("subwindow size out of range, or not a power of 2\n");
return -EINVAL;
}
if (rpn == ULONG_MAX) {
pr_debug("real page number out of range\n");
return -EINVAL;
}
/* window size is 2^(WSE+1) bytes */
set_bf(paace->win_bitfields, PAACE_WIN_SWSE,
map_addrspace_size_to_wse(subwin_size));
set_bf(paace->impl_attr, PAACE_IA_ATM, PAACE_ATM_WINDOW_XLATE);
paace->twbah = rpn >> 20;
set_bf(paace->win_bitfields, PAACE_WIN_TWBAL, rpn);
set_bf(paace->addr_bitfields, PAACE_AF_AP, prot);
/* configure snoop id */
if (~snoopid != 0)
paace->domain_attr.to_host.snpid = snoopid;
/* set up operation mapping if it's configured */
if (omi < OME_NUMBER_ENTRIES) {
set_bf(paace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
paace->op_encode.index_ot.omi = omi;
} else if (~omi != 0) {
pr_debug("bad operation mapping index: %d\n", omi);
return -EINVAL;
}
if (~stashid != 0)
set_bf(paace->impl_attr, PAACE_IA_CID, stashid);
smp_wmb();
if (enable)
set_bf(paace->addr_bitfields, PAACE_AF_V, PAACE_V_VALID);
mb();
return 0;
}
/**
* get_ome_index() - Returns the index in the operation mapping table
* for device.
* @*omi_index: pointer for storing the index value
*
*/
void get_ome_index(u32 *omi_index, struct device *dev)
{
if (of_device_is_compatible(dev->of_node, "fsl,qman-portal"))
*omi_index = OMI_QMAN;
if (of_device_is_compatible(dev->of_node, "fsl,qman"))
*omi_index = OMI_QMAN_PRIV;
}
/**
* get_stash_id - Returns stash destination id corresponding to a
* cache type and vcpu.
* @stash_dest_hint: L1, L2 or L3
* @vcpu: vpcu target for a particular cache type.
*
* Returs stash on success or ~(u32)0 on failure.
*
*/
u32 get_stash_id(u32 stash_dest_hint, u32 vcpu)
{
const u32 *prop;
struct device_node *node;
u32 cache_level;
int len, found = 0;
int i;
/* Fastpath, exit early if L3/CPC cache is target for stashing */
if (stash_dest_hint == PAMU_ATTR_CACHE_L3) {
node = of_find_matching_node(NULL, l3_device_ids);
if (node) {
prop = of_get_property(node, "cache-stash-id", 0);
if (!prop) {
pr_debug("missing cache-stash-id at %s\n", node->full_name);
of_node_put(node);
return ~(u32)0;
}
of_node_put(node);
return be32_to_cpup(prop);
}
return ~(u32)0;
}
for_each_node_by_type(node, "cpu") {
prop = of_get_property(node, "reg", &len);
for (i = 0; i < len / sizeof(u32); i++) {
if (be32_to_cpup(&prop[i]) == vcpu) {
found = 1;
goto found_cpu_node;
}
}
}
found_cpu_node:
/* find the hwnode that represents the cache */
for (cache_level = PAMU_ATTR_CACHE_L1; (cache_level < PAMU_ATTR_CACHE_L3) && found; cache_level++) {
if (stash_dest_hint == cache_level) {
prop = of_get_property(node, "cache-stash-id", 0);
if (!prop) {
pr_debug("missing cache-stash-id at %s\n", node->full_name);
of_node_put(node);
return ~(u32)0;
}
of_node_put(node);
return be32_to_cpup(prop);
}
prop = of_get_property(node, "next-level-cache", 0);
if (!prop) {
pr_debug("can't find next-level-cache at %s\n",
node->full_name);
of_node_put(node);
return ~(u32)0; /* can't traverse any further */
}
of_node_put(node);
/* advance to next node in cache hierarchy */
node = of_find_node_by_phandle(*prop);
if (!node) {
pr_debug("Invalid node for cache hierarchy %s\n",
node->full_name);
return ~(u32)0;
}
}
pr_debug("stash dest not found for %d on vcpu %d\n",
stash_dest_hint, vcpu);
return ~(u32)0;
}
/* Identify if the PAACT table entry belongs to QMAN, BMAN or QMAN Portal */
#define QMAN_PAACE 1
#define QMAN_PORTAL_PAACE 2
#define BMAN_PAACE 3
/**
* Setup operation mapping and stash destinations for QMAN and QMAN portal.
* Memory accesses to QMAN and BMAN private memory need not be coherent, so
* clear the PAACE entry coherency attribute for them.
*/
static void setup_qbman_paace(struct paace *ppaace, int paace_type)
{
switch (paace_type) {
case QMAN_PAACE:
set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
ppaace->op_encode.index_ot.omi = OMI_QMAN_PRIV;
/* setup QMAN Private data stashing for the L3 cache */
set_bf(ppaace->impl_attr, PAACE_IA_CID, get_stash_id(PAMU_ATTR_CACHE_L3, 0));
set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
0);
break;
case QMAN_PORTAL_PAACE:
set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
ppaace->op_encode.index_ot.omi = OMI_QMAN;
/*Set DQRR and Frame stashing for the L3 cache */
set_bf(ppaace->impl_attr, PAACE_IA_CID, get_stash_id(PAMU_ATTR_CACHE_L3, 0));
break;
case BMAN_PAACE:
set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
0);
break;
}
}
/**
* Setup the operation mapping table for various devices. This is a static
* table where each table index corresponds to a particular device. PAMU uses
* this table to translate device transaction to appropriate corenet
* transaction.
*/
static void __init setup_omt(struct ome *omt)
{
struct ome *ome;
/* Configure OMI_QMAN */
ome = &omt[OMI_QMAN];
ome->moe[IOE_READ_IDX] = EOE_VALID | EOE_READ;
ome->moe[IOE_EREAD0_IDX] = EOE_VALID | EOE_RSA;
ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
ome->moe[IOE_EWRITE0_IDX] = EOE_VALID | EOE_WWSAO;
ome->moe[IOE_DIRECT0_IDX] = EOE_VALID | EOE_LDEC;
ome->moe[IOE_DIRECT1_IDX] = EOE_VALID | EOE_LDECPE;
/* Configure OMI_FMAN */
ome = &omt[OMI_FMAN];
ome->moe[IOE_READ_IDX] = EOE_VALID | EOE_READI;
ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
/* Configure OMI_QMAN private */
ome = &omt[OMI_QMAN_PRIV];
ome->moe[IOE_READ_IDX] = EOE_VALID | EOE_READ;
ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
ome->moe[IOE_EREAD0_IDX] = EOE_VALID | EOE_RSA;
ome->moe[IOE_EWRITE0_IDX] = EOE_VALID | EOE_WWSA;
/* Configure OMI_CAAM */
ome = &omt[OMI_CAAM];
ome->moe[IOE_READ_IDX] = EOE_VALID | EOE_READI;
ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
}
/*
* Get the maximum number of PAACT table entries
* and subwindows supported by PAMU
*/
static void get_pamu_cap_values(unsigned long pamu_reg_base)
{
u32 pc_val;
pc_val = in_be32((u32 *)(pamu_reg_base + PAMU_PC3));
/* Maximum number of subwindows per liodn */
max_subwindow_count = 1 << (1 + PAMU_PC3_MWCE(pc_val));
}
/* Setup PAMU registers pointing to PAACT, SPAACT and OMT */
int setup_one_pamu(unsigned long pamu_reg_base, unsigned long pamu_reg_size,
phys_addr_t ppaact_phys, phys_addr_t spaact_phys,
phys_addr_t omt_phys)
{
u32 *pc;
struct pamu_mmap_regs *pamu_regs;
pc = (u32 *) (pamu_reg_base + PAMU_PC);
pamu_regs = (struct pamu_mmap_regs *)
(pamu_reg_base + PAMU_MMAP_REGS_BASE);
/* set up pointers to corenet control blocks */
out_be32(&pamu_regs->ppbah, upper_32_bits(ppaact_phys));
out_be32(&pamu_regs->ppbal, lower_32_bits(ppaact_phys));
ppaact_phys = ppaact_phys + PAACT_SIZE;
out_be32(&pamu_regs->pplah, upper_32_bits(ppaact_phys));
out_be32(&pamu_regs->pplal, lower_32_bits(ppaact_phys));
out_be32(&pamu_regs->spbah, upper_32_bits(spaact_phys));
out_be32(&pamu_regs->spbal, lower_32_bits(spaact_phys));
spaact_phys = spaact_phys + SPAACT_SIZE;
out_be32(&pamu_regs->splah, upper_32_bits(spaact_phys));
out_be32(&pamu_regs->splal, lower_32_bits(spaact_phys));
out_be32(&pamu_regs->obah, upper_32_bits(omt_phys));
out_be32(&pamu_regs->obal, lower_32_bits(omt_phys));
omt_phys = omt_phys + OMT_SIZE;
out_be32(&pamu_regs->olah, upper_32_bits(omt_phys));
out_be32(&pamu_regs->olal, lower_32_bits(omt_phys));
/*
* set PAMU enable bit,
* allow ppaact & omt to be cached
* & enable PAMU access violation interrupts.
*/
out_be32((u32 *)(pamu_reg_base + PAMU_PICS),
PAMU_ACCESS_VIOLATION_ENABLE);
out_be32(pc, PAMU_PC_PE | PAMU_PC_OCE | PAMU_PC_SPCC | PAMU_PC_PPCC);
return 0;
}
/* Enable all device LIODNS */
static void __init setup_liodns(void)
{
int i, len;
struct paace *ppaace;
struct device_node *node = NULL;
const u32 *prop;
for_each_node_with_property(node, "fsl,liodn") {
prop = of_get_property(node, "fsl,liodn", &len);
for (i = 0; i < len / sizeof(u32); i++) {
int liodn;
liodn = be32_to_cpup(&prop[i]);
if (liodn >= PAACE_NUMBER_ENTRIES) {
pr_debug("Invalid LIODN value %d\n", liodn);
continue;
}
ppaace = pamu_get_ppaace(liodn);
pamu_init_ppaace(ppaace);
/* window size is 2^(WSE+1) bytes */
set_bf(ppaace->addr_bitfields, PPAACE_AF_WSE, 35);
ppaace->wbah = 0;
set_bf(ppaace->addr_bitfields, PPAACE_AF_WBAL, 0);
set_bf(ppaace->impl_attr, PAACE_IA_ATM,
PAACE_ATM_NO_XLATE);
set_bf(ppaace->addr_bitfields, PAACE_AF_AP,
PAACE_AP_PERMS_ALL);
if (of_device_is_compatible(node, "fsl,qman-portal"))
setup_qbman_paace(ppaace, QMAN_PORTAL_PAACE);
if (of_device_is_compatible(node, "fsl,qman"))
setup_qbman_paace(ppaace, QMAN_PAACE);
if (of_device_is_compatible(node, "fsl,bman"))
setup_qbman_paace(ppaace, BMAN_PAACE);
mb();
pamu_enable_liodn(liodn);
}
}
}
irqreturn_t pamu_av_isr(int irq, void *arg)
{
struct pamu_isr_data *data = arg;
phys_addr_t phys;
unsigned int i, j, ret;
pr_emerg("access violation interrupt\n");
for (i = 0; i < data->count; i++) {
void __iomem *p = data->pamu_reg_base + i * PAMU_OFFSET;
u32 pics = in_be32(p + PAMU_PICS);
if (pics & PAMU_ACCESS_VIOLATION_STAT) {
u32 avs1 = in_be32(p + PAMU_AVS1);
struct paace *paace;
pr_emerg("POES1=%08x\n", in_be32(p + PAMU_POES1));
pr_emerg("POES2=%08x\n", in_be32(p + PAMU_POES2));
pr_emerg("AVS1=%08x\n", avs1);
pr_emerg("AVS2=%08x\n", in_be32(p + PAMU_AVS2));
pr_emerg("AVA=%016llx\n", make64(in_be32(p + PAMU_AVAH),
in_be32(p + PAMU_AVAL)));
pr_emerg("UDAD=%08x\n", in_be32(p + PAMU_UDAD));
pr_emerg("POEA=%016llx\n", make64(in_be32(p + PAMU_POEAH),
in_be32(p + PAMU_POEAL)));
phys = make64(in_be32(p + PAMU_POEAH),
in_be32(p + PAMU_POEAL));
/* Assume that POEA points to a PAACE */
if (phys) {
u32 *paace = phys_to_virt(phys);
/* Only the first four words are relevant */
for (j = 0; j < 4; j++)
pr_emerg("PAACE[%u]=%08x\n", j, in_be32(paace + j));
}
/* clear access violation condition */
out_be32((p + PAMU_AVS1), avs1 & PAMU_AV_MASK);
paace = pamu_get_ppaace(avs1 >> PAMU_AVS1_LIODN_SHIFT);
BUG_ON(!paace);
/* check if we got a violation for a disabled LIODN */
if (!get_bf(paace->addr_bitfields, PAACE_AF_V)) {
/*
* As per hardware erratum A-003638, access
* violation can be reported for a disabled
* LIODN. If we hit that condition, disable
* access violation reporting.
*/
pics &= ~PAMU_ACCESS_VIOLATION_ENABLE;
} else {
/* Disable the LIODN */
ret = pamu_disable_liodn(avs1 >> PAMU_AVS1_LIODN_SHIFT);
BUG_ON(ret);
pr_emerg("Disabling liodn %x\n", avs1 >> PAMU_AVS1_LIODN_SHIFT);
}
out_be32((p + PAMU_PICS), pics);
}
}
return IRQ_HANDLED;
}
#define LAWAR_EN 0x80000000
#define LAWAR_TARGET_MASK 0x0FF00000
#define LAWAR_TARGET_SHIFT 20
#define LAWAR_SIZE_MASK 0x0000003F
#define LAWAR_CSDID_MASK 0x000FF000
#define LAWAR_CSDID_SHIFT 12
#define LAW_SIZE_4K 0xb
struct ccsr_law {
u32 lawbarh; /* LAWn base address high */
u32 lawbarl; /* LAWn base address low */
u32 lawar; /* LAWn attributes */
u32 reserved;
};
/*
* Create a coherence subdomain for a given memory block.
*/
static int __init create_csd(phys_addr_t phys, size_t size, u32 csd_port_id)
{
struct device_node *np;
const __be32 *iprop;
void __iomem *lac = NULL; /* Local Access Control registers */
struct ccsr_law __iomem *law;
void __iomem *ccm = NULL;
u32 __iomem *csdids;
unsigned int i, num_laws, num_csds;
u32 law_target = 0;
u32 csd_id = 0;
int ret = 0;
np = of_find_compatible_node(NULL, NULL, "fsl,corenet-law");
if (!np)
return -ENODEV;
iprop = of_get_property(np, "fsl,num-laws", NULL);
if (!iprop) {
ret = -ENODEV;
goto error;
}
num_laws = be32_to_cpup(iprop);
if (!num_laws) {
ret = -ENODEV;
goto error;
}
lac = of_iomap(np, 0);
if (!lac) {
ret = -ENODEV;
goto error;
}
/* LAW registers are at offset 0xC00 */
law = lac + 0xC00;
of_node_put(np);
np = of_find_compatible_node(NULL, NULL, "fsl,corenet-cf");
if (!np) {
ret = -ENODEV;
goto error;
}
iprop = of_get_property(np, "fsl,ccf-num-csdids", NULL);
if (!iprop) {
ret = -ENODEV;
goto error;
}
num_csds = be32_to_cpup(iprop);
if (!num_csds) {
ret = -ENODEV;
goto error;
}
ccm = of_iomap(np, 0);
if (!ccm) {
ret = -ENOMEM;
goto error;
}
/* The undocumented CSDID registers are at offset 0x600 */
csdids = ccm + 0x600;
of_node_put(np);
np = NULL;
/* Find an unused coherence subdomain ID */
for (csd_id = 0; csd_id < num_csds; csd_id++) {
if (!csdids[csd_id])
break;
}
/* Store the Port ID in the (undocumented) proper CIDMRxx register */
csdids[csd_id] = csd_port_id;
/* Find the DDR LAW that maps to our buffer. */
for (i = 0; i < num_laws; i++) {
if (law[i].lawar & LAWAR_EN) {
phys_addr_t law_start, law_end;
law_start = make64(law[i].lawbarh, law[i].lawbarl);
law_end = law_start +
(2ULL << (law[i].lawar & LAWAR_SIZE_MASK));
if (law_start <= phys && phys < law_end) {
law_target = law[i].lawar & LAWAR_TARGET_MASK;
break;
}
}
}
if (i == 0 || i == num_laws) {
/* This should never happen*/
ret = -ENOENT;
goto error;
}
/* Find a free LAW entry */
while (law[--i].lawar & LAWAR_EN) {
if (i == 0) {
/* No higher priority LAW slots available */
ret = -ENOENT;
goto error;
}
}
law[i].lawbarh = upper_32_bits(phys);
law[i].lawbarl = lower_32_bits(phys);
wmb();
law[i].lawar = LAWAR_EN | law_target | (csd_id << LAWAR_CSDID_SHIFT) |
(LAW_SIZE_4K + get_order(size));
wmb();
error:
if (ccm)
iounmap(ccm);
if (lac)
iounmap(lac);
if (np)
of_node_put(np);
return ret;
}
/*
* Table of SVRs and the corresponding PORT_ID values. Port ID corresponds to a
* bit map of snoopers for a given range of memory mapped by a LAW.
*
* All future CoreNet-enabled SOCs will have this erratum(A-004510) fixed, so this
* table should never need to be updated. SVRs are guaranteed to be unique, so
* there is no worry that a future SOC will inadvertently have one of these
* values.
*/
static const struct {
u32 svr;
u32 port_id;
} port_id_map[] = {
{0x82100010, 0xFF000000}, /* P2040 1.0 */
{0x82100011, 0xFF000000}, /* P2040 1.1 */
{0x82100110, 0xFF000000}, /* P2041 1.0 */
{0x82100111, 0xFF000000}, /* P2041 1.1 */
{0x82110310, 0xFF000000}, /* P3041 1.0 */
{0x82110311, 0xFF000000}, /* P3041 1.1 */
{0x82010020, 0xFFF80000}, /* P4040 2.0 */
{0x82000020, 0xFFF80000}, /* P4080 2.0 */
{0x82210010, 0xFC000000}, /* P5010 1.0 */
{0x82210020, 0xFC000000}, /* P5010 2.0 */
{0x82200010, 0xFC000000}, /* P5020 1.0 */
{0x82050010, 0xFF800000}, /* P5021 1.0 */
{0x82040010, 0xFF800000}, /* P5040 1.0 */
};
#define SVR_SECURITY 0x80000 /* The Security (E) bit */
static int __init fsl_pamu_probe(struct platform_device *pdev)
{
void __iomem *pamu_regs = NULL;
struct ccsr_guts __iomem *guts_regs = NULL;
u32 pamubypenr, pamu_counter;
unsigned long pamu_reg_off;
unsigned long pamu_reg_base;
struct pamu_isr_data *data = NULL;
struct device_node *guts_node;
u64 size;
struct page *p;
int ret = 0;
int irq;
phys_addr_t ppaact_phys;
phys_addr_t spaact_phys;
phys_addr_t omt_phys;
size_t mem_size = 0;
unsigned int order = 0;
u32 csd_port_id = 0;
unsigned i;
/*
* enumerate all PAMUs and allocate and setup PAMU tables
* for each of them,
* NOTE : All PAMUs share the same LIODN tables.
*/
pamu_regs = of_iomap(pdev->dev.of_node, 0);
if (!pamu_regs) {
dev_err(&pdev->dev, "ioremap of PAMU node failed\n");
return -ENOMEM;
}
of_get_address(pdev->dev.of_node, 0, &size, NULL);
irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (irq == NO_IRQ) {
dev_warn(&pdev->dev, "no interrupts listed in PAMU node\n");
goto error;
}
data = kzalloc(sizeof(struct pamu_isr_data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "PAMU isr data memory allocation failed\n");
ret = -ENOMEM;
goto error;
}
data->pamu_reg_base = pamu_regs;
data->count = size / PAMU_OFFSET;
/* The ISR needs access to the regs, so we won't iounmap them */
ret = request_irq(irq, pamu_av_isr, 0, "pamu", data);
if (ret < 0) {
dev_err(&pdev->dev, "error %i installing ISR for irq %i\n",
ret, irq);
goto error;
}
guts_node = of_find_matching_node(NULL, guts_device_ids);
if (!guts_node) {
dev_err(&pdev->dev, "could not find GUTS node %s\n",
pdev->dev.of_node->full_name);
ret = -ENODEV;
goto error;
}
guts_regs = of_iomap(guts_node, 0);
of_node_put(guts_node);
if (!guts_regs) {
dev_err(&pdev->dev, "ioremap of GUTS node failed\n");
ret = -ENODEV;
goto error;
}
/* read in the PAMU capability registers */
get_pamu_cap_values((unsigned long)pamu_regs);
/*
* To simplify the allocation of a coherency domain, we allocate the
* PAACT and the OMT in the same memory buffer. Unfortunately, this
* wastes more memory compared to allocating the buffers separately.
*/
/* Determine how much memory we need */
mem_size = (PAGE_SIZE << get_order(PAACT_SIZE)) +
(PAGE_SIZE << get_order(SPAACT_SIZE)) +
(PAGE_SIZE << get_order(OMT_SIZE));
order = get_order(mem_size);
p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
if (!p) {
dev_err(&pdev->dev, "unable to allocate PAACT/SPAACT/OMT block\n");
ret = -ENOMEM;
goto error;
}
ppaact = page_address(p);
ppaact_phys = page_to_phys(p);
/* Make sure the memory is naturally aligned */
if (ppaact_phys & ((PAGE_SIZE << order) - 1)) {
dev_err(&pdev->dev, "PAACT/OMT block is unaligned\n");
ret = -ENOMEM;
goto error;
}
spaact = (void *)ppaact + (PAGE_SIZE << get_order(PAACT_SIZE));
omt = (void *)spaact + (PAGE_SIZE << get_order(SPAACT_SIZE));
dev_dbg(&pdev->dev, "ppaact virt=%p phys=0x%llx\n", ppaact,
(unsigned long long) ppaact_phys);
/* Check to see if we need to implement the work-around on this SOC */
/* Determine the Port ID for our coherence subdomain */
for (i = 0; i < ARRAY_SIZE(port_id_map); i++) {
if (port_id_map[i].svr == (mfspr(SPRN_SVR) & ~SVR_SECURITY)) {
csd_port_id = port_id_map[i].port_id;
dev_dbg(&pdev->dev, "found matching SVR %08x\n",
port_id_map[i].svr);
break;
}
}
if (csd_port_id) {
dev_dbg(&pdev->dev, "creating coherency subdomain at address "
"0x%llx, size %zu, port id 0x%08x", ppaact_phys,
mem_size, csd_port_id);
ret = create_csd(ppaact_phys, mem_size, csd_port_id);
if (ret) {
dev_err(&pdev->dev, "could not create coherence "
"subdomain\n");
return ret;
}
}
spaact_phys = virt_to_phys(spaact);
omt_phys = virt_to_phys(omt);
spaace_pool = gen_pool_create(ilog2(sizeof(struct paace)), -1);
if (!spaace_pool) {
ret = -ENOMEM;
dev_err(&pdev->dev, "PAMU : failed to allocate spaace gen pool\n");
goto error;
}
ret = gen_pool_add(spaace_pool, (unsigned long)spaact, SPAACT_SIZE, -1);
if (ret)
goto error_genpool;
pamubypenr = in_be32(&guts_regs->pamubypenr);
for (pamu_reg_off = 0, pamu_counter = 0x80000000; pamu_reg_off < size;
pamu_reg_off += PAMU_OFFSET, pamu_counter >>= 1) {
pamu_reg_base = (unsigned long) pamu_regs + pamu_reg_off;
setup_one_pamu(pamu_reg_base, pamu_reg_off, ppaact_phys,
spaact_phys, omt_phys);
/* Disable PAMU bypass for this PAMU */
pamubypenr &= ~pamu_counter;
}
setup_omt(omt);
/* Enable all relevant PAMU(s) */
out_be32(&guts_regs->pamubypenr, pamubypenr);
iounmap(guts_regs);
/* Enable DMA for the LIODNs in the device tree*/
setup_liodns();
return 0;
error_genpool:
gen_pool_destroy(spaace_pool);
error:
if (irq != NO_IRQ)
free_irq(irq, data);
if (data) {
memset(data, 0, sizeof(struct pamu_isr_data));
kfree(data);
}
if (pamu_regs)
iounmap(pamu_regs);
if (guts_regs)
iounmap(guts_regs);
if (ppaact)
free_pages((unsigned long)ppaact, order);
ppaact = NULL;
return ret;
}
static const struct of_device_id fsl_of_pamu_ids[] = {
{
.compatible = "fsl,p4080-pamu",
},
{
.compatible = "fsl,pamu",
},
{},
};
static struct platform_driver fsl_of_pamu_driver = {
.driver = {
.name = "fsl-of-pamu",
.owner = THIS_MODULE,
},
.probe = fsl_pamu_probe,
};
static __init int fsl_pamu_init(void)
{
struct platform_device *pdev = NULL;
struct device_node *np;
int ret;
/*
* The normal OF process calls the probe function at some
* indeterminate later time, after most drivers have loaded. This is
* too late for us, because PAMU clients (like the Qman driver)
* depend on PAMU being initialized early.
*
* So instead, we "manually" call our probe function by creating the
* platform devices ourselves.
*/
/*
* We assume that there is only one PAMU node in the device tree. A
* single PAMU node represents all of the PAMU devices in the SOC
* already. Everything else already makes that assumption, and the
* binding for the PAMU nodes doesn't allow for any parent-child
* relationships anyway. In other words, support for more than one
* PAMU node would require significant changes to a lot of code.
*/
np = of_find_compatible_node(NULL, NULL, "fsl,pamu");
if (!np) {
pr_err("could not find a PAMU node\n");
return -ENODEV;
}
ret = platform_driver_register(&fsl_of_pamu_driver);
if (ret) {
pr_err("could not register driver (err=%i)\n", ret);
goto error_driver_register;
}
pdev = platform_device_alloc("fsl-of-pamu", 0);
if (!pdev) {
pr_err("could not allocate device %s\n",
np->full_name);
ret = -ENOMEM;
goto error_device_alloc;
}
pdev->dev.of_node = of_node_get(np);
ret = pamu_domain_init();
if (ret)
goto error_device_add;
ret = platform_device_add(pdev);
if (ret) {
pr_err("could not add device %s (err=%i)\n",
np->full_name, ret);
goto error_device_add;
}
return 0;
error_device_add:
of_node_put(pdev->dev.of_node);
pdev->dev.of_node = NULL;
platform_device_put(pdev);
error_device_alloc:
platform_driver_unregister(&fsl_of_pamu_driver);
error_driver_register:
of_node_put(np);
return ret;
}
arch_initcall(fsl_pamu_init);
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
*/
#ifndef __FSL_PAMU_H
#define __FSL_PAMU_H
#include <asm/fsl_pamu_stash.h>
/* Bit Field macros
* v = bit field variable; m = mask, m##_SHIFT = shift, x = value to load
*/
#define set_bf(v, m, x) (v = ((v) & ~(m)) | (((x) << (m##_SHIFT)) & (m)))
#define get_bf(v, m) (((v) & (m)) >> (m##_SHIFT))
/* PAMU CCSR space */
#define PAMU_PGC 0x00000000 /* Allows all peripheral accesses */
#define PAMU_PE 0x40000000 /* enable PAMU */
/* PAMU_OFFSET to the next pamu space in ccsr */
#define PAMU_OFFSET 0x1000
#define PAMU_MMAP_REGS_BASE 0
struct pamu_mmap_regs {
u32 ppbah;
u32 ppbal;
u32 pplah;
u32 pplal;
u32 spbah;
u32 spbal;
u32 splah;
u32 splal;
u32 obah;
u32 obal;
u32 olah;
u32 olal;
};
/* PAMU Error Registers */
#define PAMU_POES1 0x0040
#define PAMU_POES2 0x0044
#define PAMU_POEAH 0x0048
#define PAMU_POEAL 0x004C
#define PAMU_AVS1 0x0050
#define PAMU_AVS1_AV 0x1
#define PAMU_AVS1_OTV 0x6
#define PAMU_AVS1_APV 0x78
#define PAMU_AVS1_WAV 0x380
#define PAMU_AVS1_LAV 0x1c00
#define PAMU_AVS1_GCV 0x2000
#define PAMU_AVS1_PDV 0x4000
#define PAMU_AV_MASK (PAMU_AVS1_AV | PAMU_AVS1_OTV | PAMU_AVS1_APV | PAMU_AVS1_WAV \
| PAMU_AVS1_LAV | PAMU_AVS1_GCV | PAMU_AVS1_PDV)
#define PAMU_AVS1_LIODN_SHIFT 16
#define PAMU_LAV_LIODN_NOT_IN_PPAACT 0x400
#define PAMU_AVS2 0x0054
#define PAMU_AVAH 0x0058
#define PAMU_AVAL 0x005C
#define PAMU_EECTL 0x0060
#define PAMU_EEDIS 0x0064
#define PAMU_EEINTEN 0x0068
#define PAMU_EEDET 0x006C
#define PAMU_EEATTR 0x0070
#define PAMU_EEAHI 0x0074
#define PAMU_EEALO 0x0078
#define PAMU_EEDHI 0X007C
#define PAMU_EEDLO 0x0080
#define PAMU_EECC 0x0084
#define PAMU_UDAD 0x0090
/* PAMU Revision Registers */
#define PAMU_PR1 0x0BF8
#define PAMU_PR2 0x0BFC
/* PAMU version mask */
#define PAMU_PR1_MASK 0xffff
/* PAMU Capabilities Registers */
#define PAMU_PC1 0x0C00
#define PAMU_PC2 0x0C04
#define PAMU_PC3 0x0C08
#define PAMU_PC4 0x0C0C
/* PAMU Control Register */
#define PAMU_PC 0x0C10
/* PAMU control defs */
#define PAMU_CONTROL 0x0C10
#define PAMU_PC_PGC 0x80000000 /* PAMU gate closed bit */
#define PAMU_PC_PE 0x40000000 /* PAMU enable bit */
#define PAMU_PC_SPCC 0x00000010 /* sPAACE cache enable */
#define PAMU_PC_PPCC 0x00000001 /* pPAACE cache enable */
#define PAMU_PC_OCE 0x00001000 /* OMT cache enable */
#define PAMU_PFA1 0x0C14
#define PAMU_PFA2 0x0C18
#define PAMU_PC2_MLIODN(X) ((X) >> 16)
#define PAMU_PC3_MWCE(X) (((X) >> 21) & 0xf)
/* PAMU Interrupt control and Status Register */
#define PAMU_PICS 0x0C1C
#define PAMU_ACCESS_VIOLATION_STAT 0x8
#define PAMU_ACCESS_VIOLATION_ENABLE 0x4
/* PAMU Debug Registers */
#define PAMU_PD1 0x0F00
#define PAMU_PD2 0x0F04
#define PAMU_PD3 0x0F08
#define PAMU_PD4 0x0F0C
#define PAACE_AP_PERMS_DENIED 0x0
#define PAACE_AP_PERMS_QUERY 0x1
#define PAACE_AP_PERMS_UPDATE 0x2
#define PAACE_AP_PERMS_ALL 0x3
#define PAACE_DD_TO_HOST 0x0
#define PAACE_DD_TO_IO 0x1
#define PAACE_PT_PRIMARY 0x0
#define PAACE_PT_SECONDARY 0x1
#define PAACE_V_INVALID 0x0
#define PAACE_V_VALID 0x1
#define PAACE_MW_SUBWINDOWS 0x1
#define PAACE_WSE_4K 0xB
#define PAACE_WSE_8K 0xC
#define PAACE_WSE_16K 0xD
#define PAACE_WSE_32K 0xE
#define PAACE_WSE_64K 0xF
#define PAACE_WSE_128K 0x10
#define PAACE_WSE_256K 0x11
#define PAACE_WSE_512K 0x12
#define PAACE_WSE_1M 0x13
#define PAACE_WSE_2M 0x14
#define PAACE_WSE_4M 0x15
#define PAACE_WSE_8M 0x16
#define PAACE_WSE_16M 0x17
#define PAACE_WSE_32M 0x18
#define PAACE_WSE_64M 0x19
#define PAACE_WSE_128M 0x1A
#define PAACE_WSE_256M 0x1B
#define PAACE_WSE_512M 0x1C
#define PAACE_WSE_1G 0x1D
#define PAACE_WSE_2G 0x1E
#define PAACE_WSE_4G 0x1F
#define PAACE_DID_PCI_EXPRESS_1 0x00
#define PAACE_DID_PCI_EXPRESS_2 0x01
#define PAACE_DID_PCI_EXPRESS_3 0x02
#define PAACE_DID_PCI_EXPRESS_4 0x03
#define PAACE_DID_LOCAL_BUS 0x04
#define PAACE_DID_SRIO 0x0C
#define PAACE_DID_MEM_1 0x10
#define PAACE_DID_MEM_2 0x11
#define PAACE_DID_MEM_3 0x12
#define PAACE_DID_MEM_4 0x13
#define PAACE_DID_MEM_1_2 0x14
#define PAACE_DID_MEM_3_4 0x15
#define PAACE_DID_MEM_1_4 0x16
#define PAACE_DID_BM_SW_PORTAL 0x18
#define PAACE_DID_PAMU 0x1C
#define PAACE_DID_CAAM 0x21
#define PAACE_DID_QM_SW_PORTAL 0x3C
#define PAACE_DID_CORE0_INST 0x80
#define PAACE_DID_CORE0_DATA 0x81
#define PAACE_DID_CORE1_INST 0x82
#define PAACE_DID_CORE1_DATA 0x83
#define PAACE_DID_CORE2_INST 0x84
#define PAACE_DID_CORE2_DATA 0x85
#define PAACE_DID_CORE3_INST 0x86
#define PAACE_DID_CORE3_DATA 0x87
#define PAACE_DID_CORE4_INST 0x88
#define PAACE_DID_CORE4_DATA 0x89
#define PAACE_DID_CORE5_INST 0x8A
#define PAACE_DID_CORE5_DATA 0x8B
#define PAACE_DID_CORE6_INST 0x8C
#define PAACE_DID_CORE6_DATA 0x8D
#define PAACE_DID_CORE7_INST 0x8E
#define PAACE_DID_CORE7_DATA 0x8F
#define PAACE_DID_BROADCAST 0xFF
#define PAACE_ATM_NO_XLATE 0x00
#define PAACE_ATM_WINDOW_XLATE 0x01
#define PAACE_ATM_PAGE_XLATE 0x02
#define PAACE_ATM_WIN_PG_XLATE \
(PAACE_ATM_WINDOW_XLATE | PAACE_ATM_PAGE_XLATE)
#define PAACE_OTM_NO_XLATE 0x00
#define PAACE_OTM_IMMEDIATE 0x01
#define PAACE_OTM_INDEXED 0x02
#define PAACE_OTM_RESERVED 0x03
#define PAACE_M_COHERENCE_REQ 0x01
#define PAACE_PID_0 0x0
#define PAACE_PID_1 0x1
#define PAACE_PID_2 0x2
#define PAACE_PID_3 0x3
#define PAACE_PID_4 0x4
#define PAACE_PID_5 0x5
#define PAACE_PID_6 0x6
#define PAACE_PID_7 0x7
#define PAACE_TCEF_FORMAT0_8B 0x00
#define PAACE_TCEF_FORMAT1_RSVD 0x01
/*
* Hard coded value for the PAACT size to accomodate
* maximum LIODN value generated by u-boot.
*/
#define PAACE_NUMBER_ENTRIES 0x500
/* Hard coded value for the SPAACT size */
#define SPAACE_NUMBER_ENTRIES 0x800
#define OME_NUMBER_ENTRIES 16
/* PAACE Bit Field Defines */
#define PPAACE_AF_WBAL 0xfffff000
#define PPAACE_AF_WBAL_SHIFT 12
#define PPAACE_AF_WSE 0x00000fc0
#define PPAACE_AF_WSE_SHIFT 6
#define PPAACE_AF_MW 0x00000020
#define PPAACE_AF_MW_SHIFT 5
#define SPAACE_AF_LIODN 0xffff0000
#define SPAACE_AF_LIODN_SHIFT 16
#define PAACE_AF_AP 0x00000018
#define PAACE_AF_AP_SHIFT 3
#define PAACE_AF_DD 0x00000004
#define PAACE_AF_DD_SHIFT 2
#define PAACE_AF_PT 0x00000002
#define PAACE_AF_PT_SHIFT 1
#define PAACE_AF_V 0x00000001
#define PAACE_AF_V_SHIFT 0
#define PAACE_DA_HOST_CR 0x80
#define PAACE_DA_HOST_CR_SHIFT 7
#define PAACE_IA_CID 0x00FF0000
#define PAACE_IA_CID_SHIFT 16
#define PAACE_IA_WCE 0x000000F0
#define PAACE_IA_WCE_SHIFT 4
#define PAACE_IA_ATM 0x0000000C
#define PAACE_IA_ATM_SHIFT 2
#define PAACE_IA_OTM 0x00000003
#define PAACE_IA_OTM_SHIFT 0
#define PAACE_WIN_TWBAL 0xfffff000
#define PAACE_WIN_TWBAL_SHIFT 12
#define PAACE_WIN_SWSE 0x00000fc0
#define PAACE_WIN_SWSE_SHIFT 6
/* PAMU Data Structures */
/* primary / secondary paact structure */
struct paace {
/* PAACE Offset 0x00 */
u32 wbah; /* only valid for Primary PAACE */
u32 addr_bitfields; /* See P/S PAACE_AF_* */
/* PAACE Offset 0x08 */
/* Interpretation of first 32 bits dependent on DD above */
union {
struct {
/* Destination ID, see PAACE_DID_* defines */
u8 did;
/* Partition ID */
u8 pid;
/* Snoop ID */
u8 snpid;
/* coherency_required : 1 reserved : 7 */
u8 coherency_required; /* See PAACE_DA_* */
} to_host;
struct {
/* Destination ID, see PAACE_DID_* defines */
u8 did;
u8 reserved1;
u16 reserved2;
} to_io;
} domain_attr;
/* Implementation attributes + window count + address & operation translation modes */
u32 impl_attr; /* See PAACE_IA_* */
/* PAACE Offset 0x10 */
/* Translated window base address */
u32 twbah;
u32 win_bitfields; /* See PAACE_WIN_* */
/* PAACE Offset 0x18 */
/* first secondary paace entry */
u32 fspi; /* only valid for Primary PAACE */
union {
struct {
u8 ioea;
u8 moea;
u8 ioeb;
u8 moeb;
} immed_ot;
struct {
u16 reserved;
u16 omi;
} index_ot;
} op_encode;
/* PAACE Offsets 0x20-0x38 */
u32 reserved[8]; /* not currently implemented */
};
/* OME : Operation mapping entry
* MOE : Mapped Operation Encodings
* The operation mapping table is table containing operation mapping entries (OME).
* The index of a particular OME is programmed in the PAACE entry for translation
* in bound I/O operations corresponding to an LIODN. The OMT is used for translation
* specifically in case of the indexed translation mode. Each OME contains a 128
* byte mapped operation encoding (MOE), where each byte represents an MOE.
*/
#define NUM_MOE 128
struct ome {
u8 moe[NUM_MOE];
} __attribute__((packed));
#define PAACT_SIZE (sizeof(struct paace) * PAACE_NUMBER_ENTRIES)
#define SPAACT_SIZE (sizeof(struct paace) * SPAACE_NUMBER_ENTRIES)
#define OMT_SIZE (sizeof(struct ome) * OME_NUMBER_ENTRIES)
#define PAMU_PAGE_SHIFT 12
#define PAMU_PAGE_SIZE 4096ULL
#define IOE_READ 0x00
#define IOE_READ_IDX 0x00
#define IOE_WRITE 0x81
#define IOE_WRITE_IDX 0x01
#define IOE_EREAD0 0x82 /* Enhanced read type 0 */
#define IOE_EREAD0_IDX 0x02 /* Enhanced read type 0 */
#define IOE_EWRITE0 0x83 /* Enhanced write type 0 */
#define IOE_EWRITE0_IDX 0x03 /* Enhanced write type 0 */
#define IOE_DIRECT0 0x84 /* Directive type 0 */
#define IOE_DIRECT0_IDX 0x04 /* Directive type 0 */
#define IOE_EREAD1 0x85 /* Enhanced read type 1 */
#define IOE_EREAD1_IDX 0x05 /* Enhanced read type 1 */
#define IOE_EWRITE1 0x86 /* Enhanced write type 1 */
#define IOE_EWRITE1_IDX 0x06 /* Enhanced write type 1 */
#define IOE_DIRECT1 0x87 /* Directive type 1 */
#define IOE_DIRECT1_IDX 0x07 /* Directive type 1 */
#define IOE_RAC 0x8c /* Read with Atomic clear */
#define IOE_RAC_IDX 0x0c /* Read with Atomic clear */
#define IOE_RAS 0x8d /* Read with Atomic set */
#define IOE_RAS_IDX 0x0d /* Read with Atomic set */
#define IOE_RAD 0x8e /* Read with Atomic decrement */
#define IOE_RAD_IDX 0x0e /* Read with Atomic decrement */
#define IOE_RAI 0x8f /* Read with Atomic increment */
#define IOE_RAI_IDX 0x0f /* Read with Atomic increment */
#define EOE_READ 0x00
#define EOE_WRITE 0x01
#define EOE_RAC 0x0c /* Read with Atomic clear */
#define EOE_RAS 0x0d /* Read with Atomic set */
#define EOE_RAD 0x0e /* Read with Atomic decrement */
#define EOE_RAI 0x0f /* Read with Atomic increment */
#define EOE_LDEC 0x10 /* Load external cache */
#define EOE_LDECL 0x11 /* Load external cache with stash lock */
#define EOE_LDECPE 0x12 /* Load external cache with preferred exclusive */
#define EOE_LDECPEL 0x13 /* Load external cache with preferred exclusive and lock */
#define EOE_LDECFE 0x14 /* Load external cache with forced exclusive */
#define EOE_LDECFEL 0x15 /* Load external cache with forced exclusive and lock */
#define EOE_RSA 0x16 /* Read with stash allocate */
#define EOE_RSAU 0x17 /* Read with stash allocate and unlock */
#define EOE_READI 0x18 /* Read with invalidate */
#define EOE_RWNITC 0x19 /* Read with no intention to cache */
#define EOE_WCI 0x1a /* Write cache inhibited */
#define EOE_WWSA 0x1b /* Write with stash allocate */
#define EOE_WWSAL 0x1c /* Write with stash allocate and lock */
#define EOE_WWSAO 0x1d /* Write with stash allocate only */
#define EOE_WWSAOL 0x1e /* Write with stash allocate only and lock */
#define EOE_VALID 0x80
/* Function prototypes */
int pamu_domain_init(void);
int pamu_enable_liodn(int liodn);
int pamu_disable_liodn(int liodn);
void pamu_free_subwins(int liodn);
int pamu_config_ppaace(int liodn, phys_addr_t win_addr, phys_addr_t win_size,
u32 omi, unsigned long rpn, u32 snoopid, uint32_t stashid,
u32 subwin_cnt, int prot);
int pamu_config_spaace(int liodn, u32 subwin_cnt, u32 subwin_addr,
phys_addr_t subwin_size, u32 omi, unsigned long rpn,
uint32_t snoopid, u32 stashid, int enable, int prot);
u32 get_stash_id(u32 stash_dest_hint, u32 vcpu);
void get_ome_index(u32 *omi_index, struct device *dev);
int pamu_update_paace_stash(int liodn, u32 subwin, u32 value);
int pamu_disable_spaace(int liodn, u32 subwin);
u32 pamu_get_max_subwin_cnt(void);
#endif /* __FSL_PAMU_H */
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
* Author: Varun Sethi <varun.sethi@freescale.com>
*
*/
#define pr_fmt(fmt) "fsl-pamu-domain: %s: " fmt, __func__
#include <linux/init.h>
#include <linux/iommu.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/of_platform.h>
#include <linux/bootmem.h>
#include <linux/err.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/pci-bridge.h>
#include <sysdev/fsl_pci.h>
#include "fsl_pamu_domain.h"
#include "pci.h"
/*
* Global spinlock that needs to be held while
* configuring PAMU.
*/
static DEFINE_SPINLOCK(iommu_lock);
static struct kmem_cache *fsl_pamu_domain_cache;
static struct kmem_cache *iommu_devinfo_cache;
static DEFINE_SPINLOCK(device_domain_lock);
static int __init iommu_init_mempool(void)
{
fsl_pamu_domain_cache = kmem_cache_create("fsl_pamu_domain",
sizeof(struct fsl_dma_domain),
0,
SLAB_HWCACHE_ALIGN,
NULL);
if (!fsl_pamu_domain_cache) {
pr_debug("Couldn't create fsl iommu_domain cache\n");
return -ENOMEM;
}
iommu_devinfo_cache = kmem_cache_create("iommu_devinfo",
sizeof(struct device_domain_info),
0,
SLAB_HWCACHE_ALIGN,
NULL);
if (!iommu_devinfo_cache) {
pr_debug("Couldn't create devinfo cache\n");
kmem_cache_destroy(fsl_pamu_domain_cache);
return -ENOMEM;
}
return 0;
}
static phys_addr_t get_phys_addr(struct fsl_dma_domain *dma_domain, dma_addr_t iova)
{
u32 win_cnt = dma_domain->win_cnt;
struct dma_window *win_ptr =
&dma_domain->win_arr[0];
struct iommu_domain_geometry *geom;
geom = &dma_domain->iommu_domain->geometry;
if (!win_cnt || !dma_domain->geom_size) {
pr_debug("Number of windows/geometry not configured for the domain\n");
return 0;
}
if (win_cnt > 1) {
u64 subwin_size;
dma_addr_t subwin_iova;
u32 wnd;
subwin_size = dma_domain->geom_size >> ilog2(win_cnt);
subwin_iova = iova & ~(subwin_size - 1);
wnd = (subwin_iova - geom->aperture_start) >> ilog2(subwin_size);
win_ptr = &dma_domain->win_arr[wnd];
}
if (win_ptr->valid)
return (win_ptr->paddr + (iova & (win_ptr->size - 1)));
return 0;
}
static int map_subwins(int liodn, struct fsl_dma_domain *dma_domain)
{
struct dma_window *sub_win_ptr =
&dma_domain->win_arr[0];
int i, ret;
unsigned long rpn, flags;
for (i = 0; i < dma_domain->win_cnt; i++) {
if (sub_win_ptr[i].valid) {
rpn = sub_win_ptr[i].paddr >>
PAMU_PAGE_SHIFT;
spin_lock_irqsave(&iommu_lock, flags);
ret = pamu_config_spaace(liodn, dma_domain->win_cnt, i,
sub_win_ptr[i].size,
~(u32)0,
rpn,
dma_domain->snoop_id,
dma_domain->stash_id,
(i > 0) ? 1 : 0,
sub_win_ptr[i].prot);
spin_unlock_irqrestore(&iommu_lock, flags);
if (ret) {
pr_debug("PAMU SPAACE configuration failed for liodn %d\n",
liodn);
return ret;
}
}
}
return ret;
}
static int map_win(int liodn, struct fsl_dma_domain *dma_domain)
{
int ret;
struct dma_window *wnd = &dma_domain->win_arr[0];
phys_addr_t wnd_addr = dma_domain->iommu_domain->geometry.aperture_start;
unsigned long flags;
spin_lock_irqsave(&iommu_lock, flags);
ret = pamu_config_ppaace(liodn, wnd_addr,
wnd->size,
~(u32)0,
wnd->paddr >> PAMU_PAGE_SHIFT,
dma_domain->snoop_id, dma_domain->stash_id,
0, wnd->prot);
spin_unlock_irqrestore(&iommu_lock, flags);
if (ret)
pr_debug("PAMU PAACE configuration failed for liodn %d\n",
liodn);
return ret;
}
/* Map the DMA window corresponding to the LIODN */
static int map_liodn(int liodn, struct fsl_dma_domain *dma_domain)
{
if (dma_domain->win_cnt > 1)
return map_subwins(liodn, dma_domain);
else
return map_win(liodn, dma_domain);
}
/* Update window/subwindow mapping for the LIODN */
static int update_liodn(int liodn, struct fsl_dma_domain *dma_domain, u32 wnd_nr)
{
int ret;
struct dma_window *wnd = &dma_domain->win_arr[wnd_nr];
unsigned long flags;
spin_lock_irqsave(&iommu_lock, flags);
if (dma_domain->win_cnt > 1) {
ret = pamu_config_spaace(liodn, dma_domain->win_cnt, wnd_nr,
wnd->size,
~(u32)0,
wnd->paddr >> PAMU_PAGE_SHIFT,
dma_domain->snoop_id,
dma_domain->stash_id,
(wnd_nr > 0) ? 1 : 0,
wnd->prot);
if (ret)
pr_debug("Subwindow reconfiguration failed for liodn %d\n", liodn);
} else {
phys_addr_t wnd_addr;
wnd_addr = dma_domain->iommu_domain->geometry.aperture_start;
ret = pamu_config_ppaace(liodn, wnd_addr,
wnd->size,
~(u32)0,
wnd->paddr >> PAMU_PAGE_SHIFT,
dma_domain->snoop_id, dma_domain->stash_id,
0, wnd->prot);
if (ret)
pr_debug("Window reconfiguration failed for liodn %d\n", liodn);
}
spin_unlock_irqrestore(&iommu_lock, flags);
return ret;
}
static int update_liodn_stash(int liodn, struct fsl_dma_domain *dma_domain,
u32 val)
{
int ret = 0, i;
unsigned long flags;
spin_lock_irqsave(&iommu_lock, flags);
if (!dma_domain->win_arr) {
pr_debug("Windows not configured, stash destination update failed for liodn %d\n", liodn);
spin_unlock_irqrestore(&iommu_lock, flags);
return -EINVAL;
}
for (i = 0; i < dma_domain->win_cnt; i++) {
ret = pamu_update_paace_stash(liodn, i, val);
if (ret) {
pr_debug("Failed to update SPAACE %d field for liodn %d\n ", i, liodn);
spin_unlock_irqrestore(&iommu_lock, flags);
return ret;
}
}
spin_unlock_irqrestore(&iommu_lock, flags);
return ret;
}
/* Set the geometry parameters for a LIODN */
static int pamu_set_liodn(int liodn, struct device *dev,
struct fsl_dma_domain *dma_domain,
struct iommu_domain_geometry *geom_attr,
u32 win_cnt)
{
phys_addr_t window_addr, window_size;
phys_addr_t subwin_size;
int ret = 0, i;
u32 omi_index = ~(u32)0;
unsigned long flags;
/*
* Configure the omi_index at the geometry setup time.
* This is a static value which depends on the type of
* device and would not change thereafter.
*/
get_ome_index(&omi_index, dev);
window_addr = geom_attr->aperture_start;
window_size = dma_domain->geom_size;
spin_lock_irqsave(&iommu_lock, flags);
ret = pamu_disable_liodn(liodn);
if (!ret)
ret = pamu_config_ppaace(liodn, window_addr, window_size, omi_index,
0, dma_domain->snoop_id,
dma_domain->stash_id, win_cnt, 0);
spin_unlock_irqrestore(&iommu_lock, flags);
if (ret) {
pr_debug("PAMU PAACE configuration failed for liodn %d, win_cnt =%d\n", liodn, win_cnt);
return ret;
}
if (win_cnt > 1) {
subwin_size = window_size >> ilog2(win_cnt);
for (i = 0; i < win_cnt; i++) {
spin_lock_irqsave(&iommu_lock, flags);
ret = pamu_disable_spaace(liodn, i);
if (!ret)
ret = pamu_config_spaace(liodn, win_cnt, i,
subwin_size, omi_index,
0, dma_domain->snoop_id,
dma_domain->stash_id,
0, 0);
spin_unlock_irqrestore(&iommu_lock, flags);
if (ret) {
pr_debug("PAMU SPAACE configuration failed for liodn %d\n", liodn);
return ret;
}
}
}
return ret;
}
static int check_size(u64 size, dma_addr_t iova)
{
/*
* Size must be a power of two and at least be equal
* to PAMU page size.
*/
if (!is_power_of_2(size) || size < PAMU_PAGE_SIZE) {
pr_debug("%s: size too small or not a power of two\n", __func__);
return -EINVAL;
}
/* iova must be page size aligned*/
if (iova & (size - 1)) {
pr_debug("%s: address is not aligned with window size\n", __func__);
return -EINVAL;
}
return 0;
}
static struct fsl_dma_domain *iommu_alloc_dma_domain(void)
{
struct fsl_dma_domain *domain;
domain = kmem_cache_zalloc(fsl_pamu_domain_cache, GFP_KERNEL);
if (!domain)
return NULL;
domain->stash_id = ~(u32)0;
domain->snoop_id = ~(u32)0;
domain->win_cnt = pamu_get_max_subwin_cnt();
domain->geom_size = 0;
INIT_LIST_HEAD(&domain->devices);
spin_lock_init(&domain->domain_lock);
return domain;
}
static inline struct device_domain_info *find_domain(struct device *dev)
{
return dev->archdata.iommu_domain;
}
static void remove_device_ref(struct device_domain_info *info, u32 win_cnt)
{
unsigned long flags;
list_del(&info->link);
spin_lock_irqsave(&iommu_lock, flags);
if (win_cnt > 1)
pamu_free_subwins(info->liodn);
pamu_disable_liodn(info->liodn);
spin_unlock_irqrestore(&iommu_lock, flags);
spin_lock_irqsave(&device_domain_lock, flags);
info->dev->archdata.iommu_domain = NULL;
kmem_cache_free(iommu_devinfo_cache, info);
spin_unlock_irqrestore(&device_domain_lock, flags);
}
static void detach_device(struct device *dev, struct fsl_dma_domain *dma_domain)
{
struct device_domain_info *info, *tmp;
unsigned long flags;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
/* Remove the device from the domain device list */
list_for_each_entry_safe(info, tmp, &dma_domain->devices, link) {
if (!dev || (info->dev == dev))
remove_device_ref(info, dma_domain->win_cnt);
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
}
static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct device *dev)
{
struct device_domain_info *info, *old_domain_info;
unsigned long flags;
spin_lock_irqsave(&device_domain_lock, flags);
/*
* Check here if the device is already attached to domain or not.
* If the device is already attached to a domain detach it.
*/
old_domain_info = find_domain(dev);
if (old_domain_info && old_domain_info->domain != dma_domain) {
spin_unlock_irqrestore(&device_domain_lock, flags);
detach_device(dev, old_domain_info->domain);
spin_lock_irqsave(&device_domain_lock, flags);
}
info = kmem_cache_zalloc(iommu_devinfo_cache, GFP_ATOMIC);
info->dev = dev;
info->liodn = liodn;
info->domain = dma_domain;
list_add(&info->link, &dma_domain->devices);
/*
* In case of devices with multiple LIODNs just store
* the info for the first LIODN as all
* LIODNs share the same domain
*/
if (!old_domain_info)
dev->archdata.iommu_domain = info;
spin_unlock_irqrestore(&device_domain_lock, flags);
}
static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
dma_addr_t iova)
{
struct fsl_dma_domain *dma_domain = domain->priv;
if ((iova < domain->geometry.aperture_start) ||
iova > (domain->geometry.aperture_end))
return 0;
return get_phys_addr(dma_domain, iova);
}
static int fsl_pamu_domain_has_cap(struct iommu_domain *domain,
unsigned long cap)
{
return cap == IOMMU_CAP_CACHE_COHERENCY;
}
static void fsl_pamu_domain_destroy(struct iommu_domain *domain)
{
struct fsl_dma_domain *dma_domain = domain->priv;
domain->priv = NULL;
/* remove all the devices from the device list */
detach_device(NULL, dma_domain);
dma_domain->enabled = 0;
dma_domain->mapped = 0;
kmem_cache_free(fsl_pamu_domain_cache, dma_domain);
}
static int fsl_pamu_domain_init(struct iommu_domain *domain)
{
struct fsl_dma_domain *dma_domain;
dma_domain = iommu_alloc_dma_domain();
if (!dma_domain) {
pr_debug("dma_domain allocation failed\n");
return -ENOMEM;
}
domain->priv = dma_domain;
dma_domain->iommu_domain = domain;
/* defaul geometry 64 GB i.e. maximum system address */
domain->geometry.aperture_start = 0;
domain->geometry.aperture_end = (1ULL << 36) - 1;
domain->geometry.force_aperture = true;
return 0;
}
/* Configure geometry settings for all LIODNs associated with domain */
static int pamu_set_domain_geometry(struct fsl_dma_domain *dma_domain,
struct iommu_domain_geometry *geom_attr,
u32 win_cnt)
{
struct device_domain_info *info;
int ret = 0;
list_for_each_entry(info, &dma_domain->devices, link) {
ret = pamu_set_liodn(info->liodn, info->dev, dma_domain,
geom_attr, win_cnt);
if (ret)
break;
}
return ret;
}
/* Update stash destination for all LIODNs associated with the domain */
static int update_domain_stash(struct fsl_dma_domain *dma_domain, u32 val)
{
struct device_domain_info *info;
int ret = 0;
list_for_each_entry(info, &dma_domain->devices, link) {
ret = update_liodn_stash(info->liodn, dma_domain, val);
if (ret)
break;
}
return ret;
}
/* Update domain mappings for all LIODNs associated with the domain */
static int update_domain_mapping(struct fsl_dma_domain *dma_domain, u32 wnd_nr)
{
struct device_domain_info *info;
int ret = 0;
list_for_each_entry(info, &dma_domain->devices, link) {
ret = update_liodn(info->liodn, dma_domain, wnd_nr);
if (ret)
break;
}
return ret;
}
static int disable_domain_win(struct fsl_dma_domain *dma_domain, u32 wnd_nr)
{
struct device_domain_info *info;
int ret = 0;
list_for_each_entry(info, &dma_domain->devices, link) {
if (dma_domain->win_cnt == 1 && dma_domain->enabled) {
ret = pamu_disable_liodn(info->liodn);
if (!ret)
dma_domain->enabled = 0;
} else {
ret = pamu_disable_spaace(info->liodn, wnd_nr);
}
}
return ret;
}
static void fsl_pamu_window_disable(struct iommu_domain *domain, u32 wnd_nr)
{
struct fsl_dma_domain *dma_domain = domain->priv;
unsigned long flags;
int ret;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
if (!dma_domain->win_arr) {
pr_debug("Number of windows not configured\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return;
}
if (wnd_nr >= dma_domain->win_cnt) {
pr_debug("Invalid window index\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return;
}
if (dma_domain->win_arr[wnd_nr].valid) {
ret = disable_domain_win(dma_domain, wnd_nr);
if (!ret) {
dma_domain->win_arr[wnd_nr].valid = 0;
dma_domain->mapped--;
}
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
}
static int fsl_pamu_window_enable(struct iommu_domain *domain, u32 wnd_nr,
phys_addr_t paddr, u64 size, int prot)
{
struct fsl_dma_domain *dma_domain = domain->priv;
struct dma_window *wnd;
int pamu_prot = 0;
int ret;
unsigned long flags;
u64 win_size;
if (prot & IOMMU_READ)
pamu_prot |= PAACE_AP_PERMS_QUERY;
if (prot & IOMMU_WRITE)
pamu_prot |= PAACE_AP_PERMS_UPDATE;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
if (!dma_domain->win_arr) {
pr_debug("Number of windows not configured\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -ENODEV;
}
if (wnd_nr >= dma_domain->win_cnt) {
pr_debug("Invalid window index\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
win_size = dma_domain->geom_size >> ilog2(dma_domain->win_cnt);
if (size > win_size) {
pr_debug("Invalid window size \n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
if (dma_domain->win_cnt == 1) {
if (dma_domain->enabled) {
pr_debug("Disable the window before updating the mapping\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EBUSY;
}
ret = check_size(size, domain->geometry.aperture_start);
if (ret) {
pr_debug("Aperture start not aligned to the size\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
}
wnd = &dma_domain->win_arr[wnd_nr];
if (!wnd->valid) {
wnd->paddr = paddr;
wnd->size = size;
wnd->prot = pamu_prot;
ret = update_domain_mapping(dma_domain, wnd_nr);
if (!ret) {
wnd->valid = 1;
dma_domain->mapped++;
}
} else {
pr_debug("Disable the window before updating the mapping\n");
ret = -EBUSY;
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return ret;
}
/*
* Attach the LIODN to the DMA domain and configure the geometry
* and window mappings.
*/
static int handle_attach_device(struct fsl_dma_domain *dma_domain,
struct device *dev, const u32 *liodn,
int num)
{
unsigned long flags;
struct iommu_domain *domain = dma_domain->iommu_domain;
int ret = 0;
int i;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
for (i = 0; i < num; i++) {
/* Ensure that LIODN value is valid */
if (liodn[i] >= PAACE_NUMBER_ENTRIES) {
pr_debug("Invalid liodn %d, attach device failed for %s\n",
liodn[i], dev->of_node->full_name);
ret = -EINVAL;
break;
}
attach_device(dma_domain, liodn[i], dev);
/*
* Check if geometry has already been configured
* for the domain. If yes, set the geometry for
* the LIODN.
*/
if (dma_domain->win_arr) {
u32 win_cnt = dma_domain->win_cnt > 1 ? dma_domain->win_cnt : 0;
ret = pamu_set_liodn(liodn[i], dev, dma_domain,
&domain->geometry,
win_cnt);
if (ret)
break;
if (dma_domain->mapped) {
/*
* Create window/subwindow mapping for
* the LIODN.
*/
ret = map_liodn(liodn[i], dma_domain);
if (ret)
break;
}
}
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return ret;
}
static int fsl_pamu_attach_device(struct iommu_domain *domain,
struct device *dev)
{
struct fsl_dma_domain *dma_domain = domain->priv;
const u32 *liodn;
u32 liodn_cnt;
int len, ret = 0;
struct pci_dev *pdev = NULL;
struct pci_controller *pci_ctl;
/*
* Use LIODN of the PCI controller while attaching a
* PCI device.
*/
if (dev->bus == &pci_bus_type) {
pdev = to_pci_dev(dev);
pci_ctl = pci_bus_to_host(pdev->bus);
/*
* make dev point to pci controller device
* so we can get the LIODN programmed by
* u-boot.
*/
dev = pci_ctl->parent;
}
liodn = of_get_property(dev->of_node, "fsl,liodn", &len);
if (liodn) {
liodn_cnt = len / sizeof(u32);
ret = handle_attach_device(dma_domain, dev,
liodn, liodn_cnt);
} else {
pr_debug("missing fsl,liodn property at %s\n",
dev->of_node->full_name);
ret = -EINVAL;
}
return ret;
}
static void fsl_pamu_detach_device(struct iommu_domain *domain,
struct device *dev)
{
struct fsl_dma_domain *dma_domain = domain->priv;
const u32 *prop;
int len;
struct pci_dev *pdev = NULL;
struct pci_controller *pci_ctl;
/*
* Use LIODN of the PCI controller while detaching a
* PCI device.
*/
if (dev->bus == &pci_bus_type) {
pdev = to_pci_dev(dev);
pci_ctl = pci_bus_to_host(pdev->bus);
/*
* make dev point to pci controller device
* so we can get the LIODN programmed by
* u-boot.
*/
dev = pci_ctl->parent;
}
prop = of_get_property(dev->of_node, "fsl,liodn", &len);
if (prop)
detach_device(dev, dma_domain);
else
pr_debug("missing fsl,liodn property at %s\n",
dev->of_node->full_name);
}
static int configure_domain_geometry(struct iommu_domain *domain, void *data)
{
struct iommu_domain_geometry *geom_attr = data;
struct fsl_dma_domain *dma_domain = domain->priv;
dma_addr_t geom_size;
unsigned long flags;
geom_size = geom_attr->aperture_end - geom_attr->aperture_start + 1;
/*
* Sanity check the geometry size. Also, we do not support
* DMA outside of the geometry.
*/
if (check_size(geom_size, geom_attr->aperture_start) ||
!geom_attr->force_aperture) {
pr_debug("Invalid PAMU geometry attributes\n");
return -EINVAL;
}
spin_lock_irqsave(&dma_domain->domain_lock, flags);
if (dma_domain->enabled) {
pr_debug("Can't set geometry attributes as domain is active\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EBUSY;
}
/* Copy the domain geometry information */
memcpy(&domain->geometry, geom_attr,
sizeof(struct iommu_domain_geometry));
dma_domain->geom_size = geom_size;
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return 0;
}
/* Set the domain stash attribute */
static int configure_domain_stash(struct fsl_dma_domain *dma_domain, void *data)
{
struct pamu_stash_attribute *stash_attr = data;
unsigned long flags;
int ret;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
memcpy(&dma_domain->dma_stash, stash_attr,
sizeof(struct pamu_stash_attribute));
dma_domain->stash_id = get_stash_id(stash_attr->cache,
stash_attr->cpu);
if (dma_domain->stash_id == ~(u32)0) {
pr_debug("Invalid stash attributes\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
ret = update_domain_stash(dma_domain, dma_domain->stash_id);
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return ret;
}
/* Configure domain dma state i.e. enable/disable DMA*/
static int configure_domain_dma_state(struct fsl_dma_domain *dma_domain, bool enable)
{
struct device_domain_info *info;
unsigned long flags;
int ret;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
if (enable && !dma_domain->mapped) {
pr_debug("Can't enable DMA domain without valid mapping\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -ENODEV;
}
dma_domain->enabled = enable;
list_for_each_entry(info, &dma_domain->devices,
link) {
ret = (enable) ? pamu_enable_liodn(info->liodn) :
pamu_disable_liodn(info->liodn);
if (ret)
pr_debug("Unable to set dma state for liodn %d",
info->liodn);
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return 0;
}
static int fsl_pamu_set_domain_attr(struct iommu_domain *domain,
enum iommu_attr attr_type, void *data)
{
struct fsl_dma_domain *dma_domain = domain->priv;
int ret = 0;
switch (attr_type) {
case DOMAIN_ATTR_GEOMETRY:
ret = configure_domain_geometry(domain, data);
break;
case DOMAIN_ATTR_FSL_PAMU_STASH:
ret = configure_domain_stash(dma_domain, data);
break;
case DOMAIN_ATTR_FSL_PAMU_ENABLE:
ret = configure_domain_dma_state(dma_domain, *(int *)data);
break;
default:
pr_debug("Unsupported attribute type\n");
ret = -EINVAL;
break;
};
return ret;
}
static int fsl_pamu_get_domain_attr(struct iommu_domain *domain,
enum iommu_attr attr_type, void *data)
{
struct fsl_dma_domain *dma_domain = domain->priv;
int ret = 0;
switch (attr_type) {
case DOMAIN_ATTR_FSL_PAMU_STASH:
memcpy((struct pamu_stash_attribute *) data, &dma_domain->dma_stash,
sizeof(struct pamu_stash_attribute));
break;
case DOMAIN_ATTR_FSL_PAMU_ENABLE:
*(int *)data = dma_domain->enabled;
break;
case DOMAIN_ATTR_FSL_PAMUV1:
*(int *)data = DOMAIN_ATTR_FSL_PAMUV1;
break;
default:
pr_debug("Unsupported attribute type\n");
ret = -EINVAL;
break;
};
return ret;
}
#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
static struct iommu_group *get_device_iommu_group(struct device *dev)
{
struct iommu_group *group;
group = iommu_group_get(dev);
if (!group)
group = iommu_group_alloc();
return group;
}
static bool check_pci_ctl_endpt_part(struct pci_controller *pci_ctl)
{
u32 version;
/* Check the PCI controller version number by readding BRR1 register */
version = in_be32(pci_ctl->cfg_addr + (PCI_FSL_BRR1 >> 2));
version &= PCI_FSL_BRR1_VER;
/* If PCI controller version is >= 0x204 we can partition endpoints*/
if (version >= 0x204)
return 1;
return 0;
}
/* Get iommu group information from peer devices or devices on the parent bus */
static struct iommu_group *get_shared_pci_device_group(struct pci_dev *pdev)
{
struct pci_dev *tmp;
struct iommu_group *group;
struct pci_bus *bus = pdev->bus;
/*
* Traverese the pci bus device list to get
* the shared iommu group.
*/
while (bus) {
list_for_each_entry(tmp, &bus->devices, bus_list) {
if (tmp == pdev)
continue;
group = iommu_group_get(&tmp->dev);
if (group)
return group;
}
bus = bus->parent;
}
return NULL;
}
static struct iommu_group *get_pci_device_group(struct pci_dev *pdev)
{
struct pci_controller *pci_ctl;
bool pci_endpt_partioning;
struct iommu_group *group = NULL;
struct pci_dev *bridge, *dma_pdev = NULL;
pci_ctl = pci_bus_to_host(pdev->bus);
pci_endpt_partioning = check_pci_ctl_endpt_part(pci_ctl);
/* We can partition PCIe devices so assign device group to the device */
if (pci_endpt_partioning) {
bridge = pci_find_upstream_pcie_bridge(pdev);
if (bridge) {
if (pci_is_pcie(bridge))
dma_pdev = pci_get_domain_bus_and_slot(
pci_domain_nr(pdev->bus),
bridge->subordinate->number, 0);
if (!dma_pdev)
dma_pdev = pci_dev_get(bridge);
} else
dma_pdev = pci_dev_get(pdev);
/* Account for quirked devices */
swap_pci_ref(&dma_pdev, pci_get_dma_source(dma_pdev));
/*
* If it's a multifunction device that does not support our
* required ACS flags, add to the same group as lowest numbered
* function that also does not suport the required ACS flags.
*/
if (dma_pdev->multifunction &&
!pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)) {
u8 i, slot = PCI_SLOT(dma_pdev->devfn);
for (i = 0; i < 8; i++) {
struct pci_dev *tmp;
tmp = pci_get_slot(dma_pdev->bus, PCI_DEVFN(slot, i));
if (!tmp)
continue;
if (!pci_acs_enabled(tmp, REQ_ACS_FLAGS)) {
swap_pci_ref(&dma_pdev, tmp);
break;
}
pci_dev_put(tmp);
}
}
/*
* Devices on the root bus go through the iommu. If that's not us,
* find the next upstream device and test ACS up to the root bus.
* Finding the next device may require skipping virtual buses.
*/
while (!pci_is_root_bus(dma_pdev->bus)) {
struct pci_bus *bus = dma_pdev->bus;
while (!bus->self) {
if (!pci_is_root_bus(bus))
bus = bus->parent;
else
goto root_bus;
}
if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS))
break;
swap_pci_ref(&dma_pdev, pci_dev_get(bus->self));
}
root_bus:
group = get_device_iommu_group(&dma_pdev->dev);
pci_dev_put(dma_pdev);
/*
* PCIe controller is not a paritionable entity
* free the controller device iommu_group.
*/
if (pci_ctl->parent->iommu_group)
iommu_group_remove_device(pci_ctl->parent);
} else {
/*
* All devices connected to the controller will share the
* PCI controllers device group. If this is the first
* device to be probed for the pci controller, copy the
* device group information from the PCI controller device
* node and remove the PCI controller iommu group.
* For subsequent devices, the iommu group information can
* be obtained from sibling devices (i.e. from the bus_devices
* link list).
*/
if (pci_ctl->parent->iommu_group) {
group = get_device_iommu_group(pci_ctl->parent);
iommu_group_remove_device(pci_ctl->parent);
} else
group = get_shared_pci_device_group(pdev);
}
return group;
}
static int fsl_pamu_add_device(struct device *dev)
{
struct iommu_group *group = NULL;
struct pci_dev *pdev;
const u32 *prop;
int ret, len;
/*
* For platform devices we allocate a separate group for
* each of the devices.
*/
if (dev->bus == &pci_bus_type) {
pdev = to_pci_dev(dev);
/* Don't create device groups for virtual PCI bridges */
if (pdev->subordinate)
return 0;
group = get_pci_device_group(pdev);
} else {
prop = of_get_property(dev->of_node, "fsl,liodn", &len);
if (prop)
group = get_device_iommu_group(dev);
}
if (!group || IS_ERR(group))
return PTR_ERR(group);
ret = iommu_group_add_device(group, dev);
iommu_group_put(group);
return ret;
}
static void fsl_pamu_remove_device(struct device *dev)
{
iommu_group_remove_device(dev);
}
static int fsl_pamu_set_windows(struct iommu_domain *domain, u32 w_count)
{
struct fsl_dma_domain *dma_domain = domain->priv;
unsigned long flags;
int ret;
spin_lock_irqsave(&dma_domain->domain_lock, flags);
/* Ensure domain is inactive i.e. DMA should be disabled for the domain */
if (dma_domain->enabled) {
pr_debug("Can't set geometry attributes as domain is active\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EBUSY;
}
/* Ensure that the geometry has been set for the domain */
if (!dma_domain->geom_size) {
pr_debug("Please configure geometry before setting the number of windows\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
/*
* Ensure we have valid window count i.e. it should be less than
* maximum permissible limit and should be a power of two.
*/
if (w_count > pamu_get_max_subwin_cnt() || !is_power_of_2(w_count)) {
pr_debug("Invalid window count\n");
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -EINVAL;
}
ret = pamu_set_domain_geometry(dma_domain, &domain->geometry,
((w_count > 1) ? w_count : 0));
if (!ret) {
if (dma_domain->win_arr)
kfree(dma_domain->win_arr);
dma_domain->win_arr = kzalloc(sizeof(struct dma_window) *
w_count, GFP_ATOMIC);
if (!dma_domain->win_arr) {
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return -ENOMEM;
}
dma_domain->win_cnt = w_count;
}
spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
return ret;
}
static u32 fsl_pamu_get_windows(struct iommu_domain *domain)
{
struct fsl_dma_domain *dma_domain = domain->priv;
return dma_domain->win_cnt;
}
static struct iommu_ops fsl_pamu_ops = {
.domain_init = fsl_pamu_domain_init,
.domain_destroy = fsl_pamu_domain_destroy,
.attach_dev = fsl_pamu_attach_device,
.detach_dev = fsl_pamu_detach_device,
.domain_window_enable = fsl_pamu_window_enable,
.domain_window_disable = fsl_pamu_window_disable,
.domain_get_windows = fsl_pamu_get_windows,
.domain_set_windows = fsl_pamu_set_windows,
.iova_to_phys = fsl_pamu_iova_to_phys,
.domain_has_cap = fsl_pamu_domain_has_cap,
.domain_set_attr = fsl_pamu_set_domain_attr,
.domain_get_attr = fsl_pamu_get_domain_attr,
.add_device = fsl_pamu_add_device,
.remove_device = fsl_pamu_remove_device,
};
int pamu_domain_init()
{
int ret = 0;
ret = iommu_init_mempool();
if (ret)
return ret;
bus_set_iommu(&platform_bus_type, &fsl_pamu_ops);
bus_set_iommu(&pci_bus_type, &fsl_pamu_ops);
return ret;
}
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
*/
#ifndef __FSL_PAMU_DOMAIN_H
#define __FSL_PAMU_DOMAIN_H
#include "fsl_pamu.h"
struct dma_window {
phys_addr_t paddr;
u64 size;
int valid;
int prot;
};
struct fsl_dma_domain {
/*
* Indicates the geometry size for the domain.
* This would be set when the geometry is
* configured for the domain.
*/
dma_addr_t geom_size;
/*
* Number of windows assocaited with this domain.
* During domain initialization, it is set to the
* the maximum number of subwindows allowed for a LIODN.
* Minimum value for this is 1 indicating a single PAMU
* window, without any sub windows. Value can be set/
* queried by set_attr/get_attr API for DOMAIN_ATTR_WINDOWS.
* Value can only be set once the geometry has been configured.
*/
u32 win_cnt;
/*
* win_arr contains information of the configured
* windows for a domain. This is allocated only
* when the number of windows for the domain are
* set.
*/
struct dma_window *win_arr;
/* list of devices associated with the domain */
struct list_head devices;
/* dma_domain states:
* mapped - A particular mapping has been created
* within the configured geometry.
* enabled - DMA has been enabled for the given
* domain. This translates to setting of the
* valid bit for the primary PAACE in the PAMU
* PAACT table. Domain geometry should be set and
* it must have a valid mapping before DMA can be
* enabled for it.
*
*/
int mapped;
int enabled;
/* stash_id obtained from the stash attribute details */
u32 stash_id;
struct pamu_stash_attribute dma_stash;
u32 snoop_id;
struct iommu_domain *iommu_domain;
spinlock_t domain_lock;
};
/* domain-device relationship */
struct device_domain_info {
struct list_head link; /* link to domain siblings */
struct device *dev;
u32 liodn;
struct fsl_dma_domain *domain; /* pointer to domain */
};
#endif /* __FSL_PAMU_DOMAIN_H */
...@@ -890,56 +890,54 @@ static int dma_pte_clear_range(struct dmar_domain *domain, ...@@ -890,56 +890,54 @@ static int dma_pte_clear_range(struct dmar_domain *domain,
return order; return order;
} }
static void dma_pte_free_level(struct dmar_domain *domain, int level,
struct dma_pte *pte, unsigned long pfn,
unsigned long start_pfn, unsigned long last_pfn)
{
pfn = max(start_pfn, pfn);
pte = &pte[pfn_level_offset(pfn, level)];
do {
unsigned long level_pfn;
struct dma_pte *level_pte;
if (!dma_pte_present(pte) || dma_pte_superpage(pte))
goto next;
level_pfn = pfn & level_mask(level - 1);
level_pte = phys_to_virt(dma_pte_addr(pte));
if (level > 2)
dma_pte_free_level(domain, level - 1, level_pte,
level_pfn, start_pfn, last_pfn);
/* If range covers entire pagetable, free it */
if (!(start_pfn > level_pfn ||
last_pfn < level_pfn + level_size(level))) {
dma_clear_pte(pte);
domain_flush_cache(domain, pte, sizeof(*pte));
free_pgtable_page(level_pte);
}
next:
pfn += level_size(level);
} while (!first_pte_in_page(++pte) && pfn <= last_pfn);
}
/* free page table pages. last level pte should already be cleared */ /* free page table pages. last level pte should already be cleared */
static void dma_pte_free_pagetable(struct dmar_domain *domain, static void dma_pte_free_pagetable(struct dmar_domain *domain,
unsigned long start_pfn, unsigned long start_pfn,
unsigned long last_pfn) unsigned long last_pfn)
{ {
int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT; int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
struct dma_pte *first_pte, *pte;
int total = agaw_to_level(domain->agaw);
int level;
unsigned long tmp;
int large_page = 2;
BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width); BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width);
BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width); BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width);
BUG_ON(start_pfn > last_pfn); BUG_ON(start_pfn > last_pfn);
/* We don't need lock here; nobody else touches the iova range */ /* We don't need lock here; nobody else touches the iova range */
level = 2; dma_pte_free_level(domain, agaw_to_level(domain->agaw),
while (level <= total) { domain->pgd, 0, start_pfn, last_pfn);
tmp = align_to_level(start_pfn, level);
/* If we can't even clear one PTE at this level, we're done */
if (tmp + level_size(level) - 1 > last_pfn)
return;
do {
large_page = level;
first_pte = pte = dma_pfn_level_pte(domain, tmp, level, &large_page);
if (large_page > level)
level = large_page + 1;
if (!pte) {
tmp = align_to_level(tmp + 1, level + 1);
continue;
}
do {
if (dma_pte_present(pte)) {
free_pgtable_page(phys_to_virt(dma_pte_addr(pte)));
dma_clear_pte(pte);
}
pte++;
tmp += level_size(level);
} while (!first_pte_in_page(pte) &&
tmp + level_size(level) - 1 <= last_pfn);
domain_flush_cache(domain, first_pte,
(void *)pte - (void *)first_pte);
} while (tmp && tmp + level_size(level) - 1 <= last_pfn);
level++;
}
/* free pgd */ /* free pgd */
if (start_pfn == 0 && last_pfn == DOMAIN_MAX_PFN(domain->gaw)) { if (start_pfn == 0 && last_pfn == DOMAIN_MAX_PFN(domain->gaw)) {
free_pgtable_page(domain->pgd); free_pgtable_page(domain->pgd);
......
...@@ -58,10 +58,26 @@ struct iommu_domain { ...@@ -58,10 +58,26 @@ struct iommu_domain {
#define IOMMU_CAP_CACHE_COHERENCY 0x1 #define IOMMU_CAP_CACHE_COHERENCY 0x1
#define IOMMU_CAP_INTR_REMAP 0x2 /* isolates device intrs */ #define IOMMU_CAP_INTR_REMAP 0x2 /* isolates device intrs */
/*
* Following constraints are specifc to FSL_PAMUV1:
* -aperture must be power of 2, and naturally aligned
* -number of windows must be power of 2, and address space size
* of each window is determined by aperture size / # of windows
* -the actual size of the mapped region of a window must be power
* of 2 starting with 4KB and physical address must be naturally
* aligned.
* DOMAIN_ATTR_FSL_PAMUV1 corresponds to the above mentioned contraints.
* The caller can invoke iommu_domain_get_attr to check if the underlying
* iommu implementation supports these constraints.
*/
enum iommu_attr { enum iommu_attr {
DOMAIN_ATTR_GEOMETRY, DOMAIN_ATTR_GEOMETRY,
DOMAIN_ATTR_PAGING, DOMAIN_ATTR_PAGING,
DOMAIN_ATTR_WINDOWS, DOMAIN_ATTR_WINDOWS,
DOMAIN_ATTR_FSL_PAMU_STASH,
DOMAIN_ATTR_FSL_PAMU_ENABLE,
DOMAIN_ATTR_FSL_PAMUV1,
DOMAIN_ATTR_MAX, DOMAIN_ATTR_MAX,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment