arm: msm: Add MSM IOMMU support.
Subsystems (3)
PATCH v4 1/1 arm: msm: Add MSM IOMMU support.
Add support for the IOMMUs found on the upcoming Qualcomm MSM8x60 chips. These IOMMUs allow virtualization of the address space used by most of the multimedia cores on these chips. The driver implements the kernel's IOMMU API.
--- arch/arm/mach-msm/include/mach/iommu.h | 102 ++ arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h | 1864 ++++++++++++++++++++++++ arch/arm/mach-msm/iommu.c | 598 ++++++++ 3 files changed, 2564 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-msm/include/mach/iommu.h create mode 100644 arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h create mode 100644 arch/arm/mach-msm/iommu.c
arch/arm/mach-msm/include/mach/iommu.h new file +102 -0
@@ -0,0 +1,102 @@ +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. +*/ + +#ifndef MSM_IOMMU_H +#define MSM_IOMMU_H + +#include <linux/interrupt.h> + +/* Maximum number of Machine IDs that we are allowing to be mapped to the same +* context bank. The number of MIDs mapped to the same CB does not affect +* performance, but there is a practical limit on how many distinct MIDs may +* be present. These mappings are typically determined at design time and are +* not expected to change at run time. +*/ +#define MAX_NUM_MIDS 16 + +/** +* struct msm_iommu_dev - a single IOMMU hardware instance +* name Human-readable name given to this IOMMU HW instance +* clk_rate Rate to set for this IOMMU's clock, if applicable to this +* particular IOMMU. 0 means don't set a rate. +* +*/ +struct msm_iommu_dev { +const char *name; +unsigned long clk_rate; +}; + +/** +* struct msm_iommu_ctx_dev - an IOMMU context bank instance +* name Human-readable name given to this context bank +* num Index of this context bank within the hardware +* mids List of Machine IDs that are to be mapped into this context +* bank, terminated by -1. The MID is a set of signals on the +* AXI bus that identifies the function associated with a specific +* memory request. (See ARM spec). +*/ +struct msm_iommu_ctx_dev { +const char *name; +int num; +int mids[MAX_NUM_MIDS ]; +}; + + +/** +* struct msm_iommu_drvdata - A single IOMMU hardware instance +* @base: IOMMU config port base address (VA) +* @irq: Interrupt number +* +* A msm_iommu_drvdata holds the global driver data about a single piece +* of an IOMMU hardware instance. +*/ +struct msm_iommu_drvdata { +void __iomem *base; +int irq; +}; + +/** +* struct msm_iommu_ctx_drvdata - an IOMMU context bank instance +* @num: Hardware context number of this context +* @pdev: Platform device associated wit this HW instance +* @attached_elm: List element for domains to track which devices are +* attached to them +* +* A msm_iommu_ctx_drvdata holds the driver data for a single context bank +* within each IOMMU hardware instance +*/ +struct msm_iommu_ctx_drvdata { +int num; +struct platform_device *pdev; +struct list_head attached_elm; +}; + +/* +* Look up an IOMMU context device by its context name. NULL if none found. +* Useful for testing and drivers that do not yet fully have IOMMU stuff in +* their platform devices. +*/ +struct device *msm_iommu_get_ctx (const char *ctx_name); + +/* +* Interrupt handler for the IOMMU context fault interrupt. Hooking the +* interrupt is not supported in the API yet, but this will print an error +* message and dump useful IOMMU registers. +*/ +irqreturn_t msm_iommu_fault_handler (int irq,void *dev_id); + +#endif
arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h new file +1864 -0
1,864 lines not on this page · raw message
arch/arm/mach-msm/iommu.c new file +598 -0
598 lines not on this page · raw message
--
1.7.0.2
Sent by an employee of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
Hi Stepan,
Quoted 4 lines · Stepan Moskovchenko
Add support for the IOMMUs found on the upcoming Qualcomm MSM8x60 chips. These IOMMUs allow virtualization of the address space used by most of the multimedia cores on these chips. The driver implements the kernel's IOMMU API.
Using the IOMMU-API seems to work out well for this at the first glance. I will give your patch a more deep review next week when I am back in office. I really appreciate that your work fits into the IOMMU-API now.
So long,
Joerg
I did a very brief look.
arch/arm/mach-msm/iommu.c line 58
Quoted 13 lines
arch/arm/mach-msm/iommu.c new file +13
-0+static void __flush_iotlb (struct iommu_domain *domain) +{ +struct msm_priv *priv = domain->priv; +struct msm_iommu_drvdata *iommu_drvdata; +struct msm_iommu_ctx_drvdata *ctx_drvdata; + +#ifndef CONFIG_IOMMU_PGTABLES_L2 +unsigned long *fl_table = priv->pgtable; +int i; + +dmac_flush_range (fl_table, fl_table +SZ_16K ); + +for (i =0 ; i <4096 ; i++)
Can you use a #define for the 4096? Will that value ever change?
arch/arm/mach-msm/iommu.c line 60
Quoted 2 lines
arch/arm/mach-msm/iommu.c new file +2
-0+if ((fl_table[i] &0x03 ) ==FL_TYPE_TABLE ) { +void *sl_table =__va (fl_table[i] &0xFFFFFC00 );
Does it make sense to define say:
#define MASK_XX (x) x & 0xFFFFFC00
and
#define MASK_XY (x) x & 0xFFFF0000
and use those instead of these hard-coded values?
Quoted 104 lines
arch/arm/mach-msm/iommu.c new file +104
-0+dmac_flush_range (sl_table, sl_table +SZ_4K ); + } +#endif + +list_for_each_entry (ctx_drvdata, &priv->list_attached, attached_elm) { +if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent) +BUG (); + + iommu_drvdata =dev_get_drvdata (ctx_drvdata->pdev->dev.parent); +SET_CTX_TLBIALL (iommu_drvdata->base, ctx_drvdata->num,0 ); + } +} + +static void __reset_context (void __iomem *base,int ctx) +{ +SET_BPRCOSH (base, ctx,0 ); +SET_BPRCISH (base, ctx,0 ); +SET_BPRCNSH (base, ctx,0 ); +SET_BPSHCFG (base, ctx,0 ); +SET_BPMTCFG (base, ctx,0 ); +SET_ACTLR (base, ctx,0 ); +SET_SCTLR (base, ctx,0 ); +SET_FSRRESTORE (base, ctx,0 ); +SET_TTBR0 (base, ctx,0 ); +SET_TTBR1 (base, ctx,0 ); +SET_TTBCR (base, ctx,0 ); +SET_BFBCR (base, ctx,0 ); +SET_PAR (base, ctx,0 ); +SET_FAR (base, ctx,0 ); +SET_CTX_TLBIALL (base, ctx,0 ); +SET_TLBFLPTER (base, ctx,0 ); +SET_TLBSLPTER (base, ctx,0 ); +SET_TLBLKCR (base, ctx,0 ); +SET_PRRR (base, ctx,0 ); +SET_NMRR (base, ctx,0 ); +SET_CONTEXTIDR (base, ctx,0 ); +} + +static void __program_context (void __iomem *base,int ctx,phys_addr_t pgtable) +{ +__reset_context (base, ctx); + +/* Set up HTW mode */ +/* TLB miss configuration: perform HTW on miss */ +SET_TLBMCFG (base, ctx,0x3 ); + +/* V2P configuration: HTW for access */ +SET_V2PCFG (base, ctx,0x3 ); + +SET_TTBCR (base, ctx,0 ); +SET_TTBR0_PA (base, ctx, (pgtable >>14 )); + +/* Invalidate the TLB for this context */ +SET_CTX_TLBIALL (base, ctx,0 ); + +/* Set interrupt number to "secure" interrupt */ +SET_IRPTNDX (base, ctx,0 ); + +/* Enable context fault interrupt */ +SET_CFEIE (base, ctx,1 ); + +/* Stall access on a context fault and let the handler deal with it */ +SET_CFCFG (base, ctx,1 ); + +/* Redirect all cacheable requests to L2 slave port. */ +SET_RCISH (base, ctx,1 ); +SET_RCOSH (base, ctx,1 ); +SET_RCNSH (base, ctx,1 ); + +/* Turn on TEX Remap */ +SET_TRE (base, ctx,1 ); + +/* Do not configure PRRR / NMRR on the IOMMU for now. We will assume +* TEX class 0 for everything until attributes are properly worked out +*/ +SET_PRRR (base, ctx,0 ); +SET_NMRR (base, ctx,0 ); + +/* Turn on BFB prefetch */ +SET_BFBDFE (base, ctx,1 ); + +#ifdef CONFIG_IOMMU_PGTABLES_L2 +/* Configure page tables as inner-cacheable and shareable to reduce +* the TLB miss penalty. +*/ +SET_TTBR0_SH (base, ctx,1 ); +SET_TTBR1_SH (base, ctx,1 ); + +SET_TTBR0_NOS (base, ctx,1 ); +SET_TTBR1_NOS (base, ctx,1 ); + +SET_TTBR0_IRGNH (base, ctx,0 );/* WB, WA */ +SET_TTBR0_IRGNL (base, ctx,1 ); + +SET_TTBR1_IRGNH (base, ctx,0 );/* WB, WA */ +SET_TTBR1_IRGNL (base, ctx,1 ); + +SET_TTBR0_ORGN (base, ctx,1 );/* WB, WA */ +SET_TTBR1_ORGN (base, ctx,1 );/* WB, WA */ +#endif + +/* Enable the MMU */ +SET_M (base, ctx,1 ); +}
arch/arm/mach-msm/iommu.c line 174
Quoted 10 lines
arch/arm/mach-msm/iommu.c new file +10
-0+ +static int msm_iommu_domain_init (struct iommu_domain *domain) +{ +struct msm_priv *priv =kzalloc (sizeof (*priv),GFP_KERNEL ); + +if (!priv) + goto fail_nomem; + +INIT_LIST_HEAD (&priv->list_attached); + priv->pgtable = (unsigned long *)__get_free_pages (GFP_KERNEL ,2 );
Ugh. Can you use get_order(SZ_16K) instead?
Quoted 18 lines
arch/arm/mach-msm/iommu.c new file +18
-0+ +if (!priv->pgtable) + goto fail_nomem; + +memset (priv->pgtable,0 ,SZ_16K ); + domain->priv = priv; +return 0 ; + +fail_nomem: +kfree (priv); +return -ENOMEM ; +} + +static void msm_iommu_domain_destroy (struct iommu_domain *domain) +{ +struct msm_priv *priv; +unsigned long flags; +unsigned long *fl_table;
arch/arm/mach-msm/iommu.c line 202
Quoted 10 lines
arch/arm/mach-msm/iommu.c new file +10
-0+int i; + +spin_lock_irqsave (&msm_iommu_lock, flags); + priv = domain->priv; + domain->priv =NULL ; + +if (priv) { + fl_table = priv->pgtable; + +for (i =0 ; i <4096 ; i++)
Ditto
arch/arm/mach-msm/iommu.c line 207
Quoted 5 lines
arch/arm/mach-msm/iommu.c new file +5
-0+if ((fl_table[i] &0x03 ) ==FL_TYPE_TABLE ) +free_page ((unsigned long )__va (((fl_table[i]) & +0xFFFFFC00 ))); + +free_pages ((unsigned long )priv->pgtable,2 );
And here as well.
Quoted 128 lines
arch/arm/mach-msm/iommu.c new file +128
-0+ priv->pgtable =NULL ; + } + +kfree (priv); +spin_unlock_irqrestore (&msm_iommu_lock, flags); +} + +static int msm_iommu_attach_dev (struct iommu_domain *domain,struct device *dev) +{ +struct msm_priv *priv; +struct msm_iommu_ctx_dev *ctx_dev; +struct msm_iommu_drvdata *iommu_drvdata; +struct msm_iommu_ctx_drvdata *ctx_drvdata; +struct msm_iommu_ctx_drvdata *tmp_drvdata; +int ret =0 ; +unsigned long flags; + +spin_lock_irqsave (&msm_iommu_lock, flags); + + priv = domain->priv; + +if (!priv || !dev) { + ret = -EINVAL ; + goto fail; + } + + iommu_drvdata =dev_get_drvdata (dev->parent); + ctx_drvdata =dev_get_drvdata (dev); + ctx_dev = dev->platform_data; + +if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) { + ret = -EINVAL ; + goto fail; + } + +list_for_each_entry (tmp_drvdata, &priv->list_attached, attached_elm) +if (tmp_drvdata == ctx_drvdata) { + ret = -EBUSY ; + goto fail; + } + +__program_context (iommu_drvdata->base, ctx_dev->num, +__pa (priv->pgtable)); + +list_add (&(ctx_drvdata->attached_elm), &priv->list_attached); +__flush_iotlb (domain); + +fail: +spin_unlock_irqrestore (&msm_iommu_lock, flags); +return ret; +} + +static void msm_iommu_detach_dev (struct iommu_domain *domain, +struct device *dev) +{ +struct msm_priv *priv; +struct msm_iommu_ctx_dev *ctx_dev; +struct msm_iommu_drvdata *iommu_drvdata; +struct msm_iommu_ctx_drvdata *ctx_drvdata; +unsigned long flags; + +spin_lock_irqsave (&msm_iommu_lock, flags); + priv = domain->priv; + +if (!priv || !dev) + goto fail; + + iommu_drvdata =dev_get_drvdata (dev->parent); + ctx_drvdata =dev_get_drvdata (dev); + ctx_dev = dev->platform_data; + +if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) + goto fail; + +__flush_iotlb (domain); +__reset_context (iommu_drvdata->base, ctx_dev->num); +list_del_init (&ctx_drvdata->attached_elm); + +fail: +spin_unlock_irqrestore (&msm_iommu_lock, flags); +} + +static int msm_iommu_map (struct iommu_domain *domain,unsigned long va, +phys_addr_t pa,int order,int prot) +{ +struct msm_priv *priv; +unsigned long flags; +unsigned long *fl_table; +unsigned long *fl_pte; +unsigned long fl_offset; +unsigned long *sl_table; +unsigned long *sl_pte; +unsigned long sl_offset; +size_t len =0x1000UL << order; +int ret =0 ; + +spin_lock_irqsave (&msm_iommu_lock, flags); + priv = domain->priv; + +if (!priv) { + ret = -EINVAL ; + goto fail; + } + + fl_table = priv->pgtable; + +if (len !=SZ_16M && len !=SZ_1M && + len !=SZ_64K && len !=SZ_4K ) { +pr_debug ("Bad size: %d\n" , len); + ret = -EINVAL ; + goto fail; + } + +if (!fl_table) { +pr_debug ("Null page table\n" ); + ret = -EINVAL ; + goto fail; + } + + fl_offset =FL_OFFSET (va);/* Upper 12 bits */ + fl_pte = fl_table + fl_offset;/* int pointers, 4 bytes */ + +if (len ==SZ_16M ) { +int i =0 ; +for (i =0 ; i <16 ; i++) + *(fl_pte+i) = (pa &0xFF000000 ) |FL_SUPERSECTION | +FL_AP_READ |FL_AP_WRITE |FL_TYPE_SECT | +FL_SHARED ;
arch/arm/mach-msm/iommu.c line 345
Quoted 10 lines
arch/arm/mach-msm/iommu.c new file +10
-0+ } + +if (len ==SZ_1M ) + *fl_pte = (pa &0xFFF00000 ) |FL_AP_READ |FL_AP_WRITE | +FL_TYPE_SECT |FL_SHARED ; + +/* Need a 2nd level table */ +if ((len ==SZ_4K || len ==SZ_64K ) && (*fl_pte) ==0 ) { +unsigned long *sl; + sl = (unsigned long *)__get_free_pages (GFP_KERNEL ,0 );
get_order(SZ_4K)
Quoted 137 lines
arch/arm/mach-msm/iommu.c new file +137
-0+ +if (!sl) { +pr_debug ("Could not allocate second level table\n" ); + ret = -ENOMEM ; + goto fail; + } + +memset (sl,0 ,SZ_4K ); + *fl_pte = ((((int )__pa (sl)) &0xFFFFFC00 ) |FL_TYPE_TABLE ); + } + + sl_table = (unsigned long *)__va (((*fl_pte) &0xFFFFFC00 )); + sl_offset =SL_OFFSET (va); + sl_pte = sl_table + sl_offset; + + +if (len ==SZ_4K ) + *sl_pte = (pa &0xFFFFF000 ) |SL_AP0 |SL_AP1 |SL_SHARED | +SL_TYPE_SMALL ; + +if (len ==SZ_64K ) { +int i; + +for (i =0 ; i <16 ; i++) + *(sl_pte+i) = (pa &0xFFFF0000 ) |SL_AP0 |SL_AP1 | +SL_SHARED |SL_TYPE_LARGE ; + } + +__flush_iotlb (domain); +fail: +spin_unlock_irqrestore (&msm_iommu_lock, flags); +return ret; +} + +static int msm_iommu_unmap (struct iommu_domain *domain,unsigned long va, +int order) +{ +struct msm_priv *priv; +unsigned long flags; +unsigned long *fl_table; +unsigned long *fl_pte; +unsigned long fl_offset; +unsigned long *sl_table; +unsigned long *sl_pte; +unsigned long sl_offset; +size_t len =0x1000UL << order; +int i, ret =0 ; + +spin_lock_irqsave (&msm_iommu_lock, flags); + + priv = domain->priv; + +if (!priv) { + ret = -ENODEV ; + goto fail; + } + + fl_table = priv->pgtable; + +if (len !=SZ_16M && len !=SZ_1M && + len !=SZ_64K && len !=SZ_4K ) { +pr_debug ("Bad length: %d\n" , len); + ret = -EINVAL ; + goto fail; + } + +if (!fl_table) { +pr_debug ("Null page table\n" ); + ret = -EINVAL ; + goto fail; + } + + fl_offset =FL_OFFSET (va);/* Upper 12 bits */ + fl_pte = fl_table + fl_offset;/* int pointers, 4 bytes */ + +if (*fl_pte ==0 ) { +pr_debug ("First level PTE is 0\n" ); + ret = -ENODEV ; + goto fail; + } + +/* Unmap supersection */ +if (len ==SZ_16M ) +for (i =0 ; i <16 ; i++) + *(fl_pte+i) =0 ; + +if (len ==SZ_1M ) + *fl_pte =0 ; + + sl_table = (unsigned long *)__va (((*fl_pte) &0xFFFFFC00 )); + sl_offset =SL_OFFSET (va); + sl_pte = sl_table + sl_offset; + +if (len ==SZ_64K ) { +for (i =0 ; i <16 ; i++) + *(sl_pte+i) =0 ; + } + +if (len ==SZ_4K ) + *sl_pte =0 ; + +if (len ==SZ_4K || len ==SZ_64K ) { +int used =0 ; + +for (i =0 ; i <256 ; i++) +if (sl_table[i]) + used =1 ; +if (!used) { +free_page ((unsigned long )sl_table); + *fl_pte =0 ; + } + } + +__flush_iotlb (domain); +fail: +spin_unlock_irqrestore (&msm_iommu_lock, flags); +return ret; +} + +static phys_addr_t msm_iommu_iova_to_phys (struct iommu_domain *domain, +unsigned long va) +{ +struct msm_priv *priv; +struct msm_iommu_drvdata *iommu_drvdata; +struct msm_iommu_ctx_drvdata *ctx_drvdata; +unsigned int par; +unsigned long flags; +void __iomem *base; +phys_addr_t ret =0 ; +int ctx; + +spin_lock_irqsave (&msm_iommu_lock, flags); + + priv = domain->priv; +if (list_empty (&priv->list_attached)) + goto fail; +
arch/arm/mach-msm/iommu.c line 492
Quoted 10 lines
arch/arm/mach-msm/iommu.c new file +10
-0+ ctx_drvdata =list_entry (priv->list_attached.next, + struct msm_iommu_ctx_drvdata, attached_elm); + iommu_drvdata =dev_get_drvdata (ctx_drvdata->pdev->dev.parent); + + base = iommu_drvdata->base; + ctx = ctx_drvdata->num; + +/* Invalidate context TLB */ +SET_CTX_TLBIALL (base, ctx,0 ); +SET_V2PPR_VA (base, ctx, va >>12 );
Hmm, PAGE_SHIFT can't be used? Or some other #define?
Quoted 6 lines · Konrad Rzeszutek Wilk
I did a very brief look.
arch/arm/mach-msm/iommu.c
Quoted 3 lines
arch/arm/mach-msm/iommu.c new file +3
-0+dmac_flush_range (fl_table, fl_table +SZ_16K ); + +for (i =0 ; i<4096 ; i++)Can you use a #define for the 4096? Will that value ever change?
I suppose a #define can be used, but this is a HW-specific number that will never ever change.
Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ if ((fl_table[i]& 0x03) == FL_TYPE_TABLE) { + void *sl_table = __va(fl_table[i]& 0xFFFFFC00);Does it make sense to define say:
#defineMASK_XX(x) x&0xFFFFFC00
and
#defineMASK_XY(x) x&0xFFFF0000and use those instead of these hard-coded values?
I guess so.. again, HW specific.
Quoted 7 lines
arch/arm/mach-msm/iommu.c line 174
Quoted 1 line
arch/arm/mach-msm/iommu.c new file +1
-0+ priv->pgtable = (unsigned long *)__get_free_pages (GFP_KERNEL ,2 );Ugh. Can you use
get_order(SZ_16K)instead?arch/arm/mach-msm/iommu.c
Quoted 3 lines
arch/arm/mach-msm/iommu.c new file +3
-0+/* Invalidate context TLB */ +SET_CTX_TLBIALL (base, ctx,0 ); +SET_V2PPR_VA (base, ctx, va>>12 );Hmm,
PAGE_SHIFTcan't be used? Or some other #define?
I guess I can use the IOMMU PA shift. PAGE_SHIFT is not appropriate here because that is MMU-specific.
I can put out a v5 early next week.
Thanks
Steve