[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [RFC v01 1/3] arm: omap: introduce iommu module



On Wed, 22 Jan 2014, Andrii Tseglytskyi wrote:
> omap IOMMU module is designed to handle access to external
> omap MMUs, connected to the L3 bus.
> 
> Change-Id: I96bbf2738e9dd2e21662e0986ca15c60183e669e
> Signed-off-by: Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
> ---
>  xen/arch/arm/Makefile     |    1 +
>  xen/arch/arm/io.c         |    1 +
>  xen/arch/arm/io.h         |    1 +
>  xen/arch/arm/omap_iommu.c |  415 
> +++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 418 insertions(+)
>  create mode 100644 xen/arch/arm/omap_iommu.c
> 
> diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile
> index 003ac84..cb0b385 100644
> --- a/xen/arch/arm/Makefile
> +++ b/xen/arch/arm/Makefile
> @@ -14,6 +14,7 @@ obj-y += io.o
>  obj-y += irq.o
>  obj-y += kernel.o
>  obj-y += mm.o
> +obj-y += omap_iommu.o
>  obj-y += p2m.o
>  obj-y += percpu.o
>  obj-y += guestcopy.o
> diff --git a/xen/arch/arm/io.c b/xen/arch/arm/io.c
> index a6db00b..3281b67 100644
> --- a/xen/arch/arm/io.c
> +++ b/xen/arch/arm/io.c
> @@ -26,6 +26,7 @@ static const struct mmio_handler *const mmio_handlers[] =
>  {
>      &vgic_distr_mmio_handler,
>      &vuart_mmio_handler,
> +     &mmu_mmio_handler,
>  };
>  #define MMIO_HANDLER_NR ARRAY_SIZE(mmio_handlers)

I think that omap_iommu should be a platform specific driver, and it
should hook into a set of platform specific mmio_handlers instead of
using the generic mmio_handler structure.


> diff --git a/xen/arch/arm/io.h b/xen/arch/arm/io.h
> index 8d252c0..acb5dff 100644
> --- a/xen/arch/arm/io.h
> +++ b/xen/arch/arm/io.h
> @@ -42,6 +42,7 @@ struct mmio_handler {
>  
>  extern const struct mmio_handler vgic_distr_mmio_handler;
>  extern const struct mmio_handler vuart_mmio_handler;
> +extern const struct mmio_handler mmu_mmio_handler;
>  
>  extern int handle_mmio(mmio_info_t *info);
>  
> diff --git a/xen/arch/arm/omap_iommu.c b/xen/arch/arm/omap_iommu.c
> new file mode 100644
> index 0000000..4dab30f
> --- /dev/null
> +++ b/xen/arch/arm/omap_iommu.c

It should probably live under xen/arch/arm/platforms.


> @@ -0,0 +1,415 @@
> +/*
> + * xen/arch/arm/omap_iommu.c
> + *
> + * Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
> + * Copyright (c) 2013 GlobalLogic
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <xen/config.h>
> +#include <xen/lib.h>
> +#include <xen/errno.h>
> +#include <xen/mm.h>
> +#include <xen/vmap.h>
> +#include <xen/init.h>
> +#include <xen/sched.h>
> +#include <xen/stdbool.h>
> +#include <asm/system.h>
> +#include <asm/current.h>
> +#include <asm/io.h>
> +#include <asm/p2m.h>
> +
> +#include "io.h"
> +
> +/* register where address of page table is stored */
> +#define MMU_TTB                      0x4c
> +
> +/*
> + * "L2 table" address mask and size definitions.
> + */
> +
> +/* 1st level translation */
> +#define IOPGD_SHIFT          20
> +#define IOPGD_SIZE           (1UL << IOPGD_SHIFT)
> +#define IOPGD_MASK           (~(IOPGD_SIZE - 1))
> +
> +/* "supersection" - 16 Mb */
> +#define IOSUPER_SHIFT                24
> +#define IOSUPER_SIZE         (1UL << IOSUPER_SHIFT)
> +#define IOSUPER_MASK         (~(IOSUPER_SIZE - 1))
> +
> +/* "section"  - 1 Mb */
> +#define IOSECTION_SHIFT              20
> +#define IOSECTION_SIZE               (1UL << IOSECTION_SHIFT)
> +#define IOSECTION_MASK               (~(IOSECTION_SIZE - 1))
> +
> +/* 4096 first level descriptors for "supersection" and "section" */
> +#define PTRS_PER_IOPGD               (1UL << (32 - IOPGD_SHIFT))
> +#define IOPGD_TABLE_SIZE     (PTRS_PER_IOPGD * sizeof(u32))
> +
> +/* 2nd level translation */
> +
> +/* "small page" - 4Kb */
> +#define IOPTE_SMALL_SHIFT            12
> +#define IOPTE_SMALL_SIZE             (1UL << IOPTE_SMALL_SHIFT)
> +#define IOPTE_SMALL_MASK             (~(IOPTE_SMALL_SIZE - 1))
> +
> +/* "large page" - 64 Kb */
> +#define IOPTE_LARGE_SHIFT            16
> +#define IOPTE_LARGE_SIZE             (1UL << IOPTE_LARGE_SHIFT)
> +#define IOPTE_LARGE_MASK             (~(IOPTE_LARGE_SIZE - 1))
> +
> +/* 256 second level descriptors for "small" and "large" pages */
> +#define PTRS_PER_IOPTE               (1UL << (IOPGD_SHIFT - 
> IOPTE_SMALL_SHIFT))
> +#define IOPTE_TABLE_SIZE     (PTRS_PER_IOPTE * sizeof(u32))
> +
> +/*
> + * some descriptor attributes.
> + */
> +#define IOPGD_TABLE          (1 << 0)
> +#define IOPGD_SECTION        (2 << 0)
> +#define IOPGD_SUPER          (1 << 18 | 2 << 0)
> +
> +#define iopgd_is_table(x)    (((x) & 3) == IOPGD_TABLE)
> +#define iopgd_is_section(x)  (((x) & (1 << 18 | 3)) == IOPGD_SECTION)
> +#define iopgd_is_super(x)    (((x) & (1 << 18 | 3)) == IOPGD_SUPER)
> +
> +#define IOPTE_SMALL          (2 << 0)
> +#define IOPTE_LARGE          (1 << 0)
> +
> +#define iopte_is_small(x)    (((x) & 2) == IOPTE_SMALL)
> +#define iopte_is_large(x)    (((x) & 3) == IOPTE_LARGE)
> +#define iopte_offset(x)              ((x) & IOPTE_SMALL_MASK)
> +
> +struct mmu_info {
> +     const char                      *name;
> +     paddr_t                         mem_start;
> +     u32                                     mem_size;
> +     u32                                     *pagetable;
> +     void __iomem            *mem_map;
> +};
> +
> +static struct mmu_info omap_ipu_mmu = {
> +     .name           = "IPU_L2_MMU",
> +     .mem_start      = 0x55082000,
> +     .mem_size       = 0x1000,
> +     .pagetable      = NULL,
> +};
> +
> +static struct mmu_info omap_dsp_mmu = {
> +     .name           = "DSP_L2_MMU",
> +     .mem_start      = 0x4a066000,
> +     .mem_size       = 0x1000,
> +     .pagetable      = NULL,
> +};
> +
> +static struct mmu_info *mmu_list[] = {
> +     &omap_ipu_mmu,
> +     &omap_dsp_mmu,
> +};
> +
> +#define mmu_for_each(pfunc, data)                                            
> \
> +({                                                                           
>                                 \
> +     u32 __i;                                                                
>                         \
> +     int __res = 0;                                                          
>                 \
> +                                                                             
>                                 \
> +     for (__i = 0; __i < ARRAY_SIZE(mmu_list); __i++) {      \
> +             __res |= pfunc(mmu_list[__i], data);                    \
> +     }                                                                       
>                                 \
> +     __res;                                                                  
>                         \
> +})
> +
> +static int mmu_check_mem_range(struct mmu_info *mmu, paddr_t addr)
> +{
> +     if ((addr >= mmu->mem_start) && (addr < (mmu->mem_start + 
> mmu->mem_size)))
> +             return 1;
> +
> +     return 0;
> +}
> +
> +static inline struct mmu_info *mmu_lookup(u32 addr)
> +{
> +     u32 i;
> +
> +     for (i = 0; i < ARRAY_SIZE(mmu_list); i++) {
> +             if (mmu_check_mem_range(mmu_list[i], addr))
> +                     return mmu_list[i];
> +     }
> +
> +     return NULL;
> +}
> +
> +static inline u32 mmu_virt_to_phys(u32 reg, u32 va, u32 mask)
> +{
> +     return (reg & mask) | (va & (~mask));
> +}
> +
> +static inline u32 mmu_phys_to_virt(u32 reg, u32 pa, u32 mask)
> +{
> +     return (reg & ~mask) | pa;
> +}
> +
> +static int mmu_mmio_check(struct vcpu *v, paddr_t addr)
> +{
> +     return mmu_for_each(mmu_check_mem_range, addr);
> +}
> +
> +static int mmu_copy_pagetable(struct mmu_info *mmu)
> +{
> +     void __iomem *pagetable = NULL;
> +     u32 pgaddr;
> +
> +     ASSERT(mmu);
> +
> +     /* read address where kernel MMU pagetable is stored */
> +     pgaddr = readl(mmu->mem_map + MMU_TTB);
> +     pagetable = ioremap(pgaddr, IOPGD_TABLE_SIZE);
> +     if (!pagetable) {

Xen uses a different coding style from Linux, see CODING_STYLE.


> +             printk("%s: %s failed to map pagetable\n",
> +                        __func__, mmu->name);
> +             return -EINVAL;
> +     }
> +
> +     /*
> +      * pagetable can be changed since last time
> +      * we accessed it therefore we need to copy it each time
> +      */
> +     memcpy(mmu->pagetable, pagetable, IOPGD_TABLE_SIZE);
> +
> +     iounmap(pagetable);

Do you need to flush the dcache here?


> +     return 0;
> +}
> +
> +#define mmu_dump_pdentry(da, iopgd, paddr, maddr, vaddr, mask)               
>                                                         \
> +{                                                                            
>                                                                               
>                                   \
> +     const char *sect_type = (iopgd_is_table(iopgd) || (mask == 
> IOPTE_SMALL_MASK) ||                         \
> +                                                     (mask == 
> IOPTE_LARGE_MASK)) ? "table"                                                  
>          \
> +                                                     : iopgd_is_super(iopgd) 
> ? "supersection"                                                        \
> +                                                     : 
> iopgd_is_section(iopgd) ? "section"                                           
>                 \
> +                                                     : "Unknown section";    
>                                                                               
>           \
> +     printk("[iopgd] %s da 0x%08x iopgd 0x%08x paddr 0x%08x maddr 0x%pS 
> vaddr 0x%08x mask 0x%08x\n",\
> +                sect_type, da, iopgd, paddr, _p(maddr), vaddr, mask);        
>                                                         \
> +}
> +
> +static u32 mmu_translate_pgentry(struct domain *dom, u32 iopgd, u32 da, u32 
> mask)
> +{
> +     u32 vaddr, paddr;
> +     paddr_t maddr;
> +
> +     paddr = mmu_virt_to_phys(iopgd, da, mask);
> +     maddr = p2m_lookup(dom, paddr);
> +     vaddr = mmu_phys_to_virt(iopgd, maddr, mask);
> +
> +     return vaddr;
> +}
> +
> +/*
> + * on boot table is empty
> + */
> +static int mmu_translate_pagetable(struct domain *dom, struct mmu_info *mmu)
> +{
> +     u32 i;
> +     int res;
> +     bool table_updated = false;
> +
> +     ASSERT(dom);
> +     ASSERT(mmu);
> +
> +     /* copy pagetable from  domain to xen */
> +     res = mmu_copy_pagetable(mmu);
> +     if (res) {
> +             printk("%s: %s failed to map pagetable memory\n",
> +                        __func__, mmu->name);
> +             return res;
> +     }
> +
> +     /* 1-st level translation */
> +     for (i = 0; i < PTRS_PER_IOPGD; i++) {
> +             u32 da;
> +             u32 iopgd = mmu->pagetable[i];
> +
> +             if (!iopgd)
> +                     continue;
> +
> +             table_updated = true;
> +
> +             /* "supersection" 16 Mb */
> +             if (iopgd_is_super(iopgd)) {
> +                     da = i << IOSECTION_SHIFT;
> +                     mmu->pagetable[i] = mmu_translate_pgentry(dom, iopgd, 
> da, IOSUPER_MASK);
> +
> +             /* "section" 1Mb */
> +             } else if (iopgd_is_section(iopgd)) {
> +                     da = i << IOSECTION_SHIFT;
> +                     mmu->pagetable[i] = mmu_translate_pgentry(dom, iopgd, 
> da, IOSECTION_MASK);
> +
> +             /* "table" */
> +             } else if (iopgd_is_table(iopgd)) {
> +                     u32 j, mask;
> +                     u32 iopte = iopte_offset(iopgd);
> +
> +                     /* 2-nd level translation */
> +                     for (j = 0; j < PTRS_PER_IOPTE; j++, iopte += 
> IOPTE_SMALL_SIZE) {
> +
> +                             /* "small table" 4Kb */
> +                             if (iopte_is_small(iopgd)) {
> +                                     da = (i << IOSECTION_SHIFT) + (j << 
> IOPTE_SMALL_SHIFT);
> +                                     mask = IOPTE_SMALL_MASK;
> +
> +                             /* "large table" 64Kb */
> +                             } else if (iopte_is_large(iopgd)) {
> +                                     da = (i << IOSECTION_SHIFT) + (j << 
> IOPTE_LARGE_SHIFT);
> +                                     mask = IOPTE_LARGE_MASK;
> +
> +                             /* error */
> +                             } else {
> +                                     printk("%s Unknown table type 
> 0x%08x\n", mmu->name, iopte);
> +                                     return -EINVAL;
> +                             }
> +
> +                             /* translate 2-nd level entry */
> +                             mmu->pagetable[i] = mmu_translate_pgentry(dom, 
> iopte, da, mask);
> +                     }
> +
> +                     continue;
> +
> +             /* error */
> +             } else {
> +                     printk("%s Unknown entry 0x%08x\n", mmu->name, iopgd);
> +                     return -EINVAL;
> +             }
> +     }
> +
> +     /* force omap IOMMU to use new pagetable */
> +     if (table_updated) {
> +             paddr_t maddr;
> +             flush_xen_dcache_va_range(mmu->pagetable, IOPGD_TABLE_SIZE);

So you are flushing the dcache all at once at the end, probably better
this way.

> +             maddr = __pa(mmu->pagetable);
> +             writel(maddr, mmu->mem_map + MMU_TTB);
> +             printk("%s update pagetable, maddr 0x%pS\n", mmu->name, 
> _p(maddr));
> +     }
> +
> +     return 0;
> +}
> +
> +static int mmu_trap_write_access(struct domain *dom,
> +                                                              struct 
> mmu_info *mmu, mmio_info_t *info)
> +{
> +     struct cpu_user_regs *regs = guest_cpu_user_regs();
> +     register_t *r = select_user_reg(regs, info->dabt.reg);
> +     int res = 0;
> +
> +     switch (info->gpa - mmu->mem_start) {
> +             case MMU_TTB:
> +                     printk("%s MMU_TTB write access 0x%pS <= 0x%08x\n",
> +                                mmu->name, _p(info->gpa), *r);
> +                     res = mmu_translate_pagetable(dom, mmu);
> +                     break;
> +             default:
> +                     break;
> +     }
> +
> +     return res;
> +}
> +
> +static int mmu_mmio_read(struct vcpu *v, mmio_info_t *info)
> +{
> +     struct mmu_info *mmu = NULL;
> +    struct cpu_user_regs *regs = guest_cpu_user_regs();
> +    register_t *r = select_user_reg(regs, info->dabt.reg);
> +
> +     mmu = mmu_lookup(info->gpa);
> +     if (!mmu) {
> +             printk("%s: can't get mmu for addr 0x%08x\n", __func__, 
> (u32)info->gpa);
> +             return -EINVAL;
> +     }
> +
> +    *r = readl(mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
> +
> +    return 1;
> +}
> +
> +static int mmu_mmio_write(struct vcpu *v, mmio_info_t *info)
> +{
> +     struct domain *dom = v->domain;
> +     struct mmu_info *mmu = NULL;
> +    struct cpu_user_regs *regs = guest_cpu_user_regs();
> +    register_t *r = select_user_reg(regs, info->dabt.reg);
> +     int res;
> +
> +     mmu = mmu_lookup(info->gpa);
> +     if (!mmu) {
> +             printk("%s: can't get mmu for addr 0x%08x\n", __func__, 
> (u32)info->gpa);
> +             return -EINVAL;
> +     }
> +
> +     /*
> +      * make sure that user register is written first in this function
> +      * following calls may expect valid data in it
> +      */
> +    writel(*r, mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
> +
> +     res = mmu_trap_write_access(dom, mmu, info);
> +     if (res)
> +             return res;
> +
> +    return 1;
> +}

I wonder if we actually need to trap guest accesses in all cases: if we
leave the GPU/IPU in Dom0, mapped 1:1, then we don't need any traps.
Maybe we can find a way to detect that so that we can avoid trapping and
translating in that case.


> +static int mmu_init(struct mmu_info *mmu, u32 data)
> +{
> +     ASSERT(mmu);
> +     ASSERT(!mmu->mem_map);
> +     ASSERT(!mmu->pagetable);
> +
> +    mmu->mem_map = ioremap(mmu->mem_start, mmu->mem_size);
> +     if (!mmu->mem_map) {
> +             printk("%s: %s failed to map memory\n",  __func__, mmu->name);
> +             return -EINVAL;
> +     }
> +
> +     printk("%s: %s ipu_map = 0x%pS\n", __func__, mmu->name, 
> _p(mmu->mem_map));
> +
> +     mmu->pagetable = xzalloc_bytes(IOPGD_TABLE_SIZE);
> +     if (!mmu->pagetable) {
> +             printk("%s: %s failed to alloc private pagetable\n",
> +                        __func__, mmu->name);
> +             return -ENOMEM;
> +     }
> +
> +     printk("%s: %s private pagetable %lu bytes\n",
> +                __func__, mmu->name, IOPGD_TABLE_SIZE);
> +
> +     return 0;
> +}
> +
> +static int mmu_init_all(void)
> +{
> +     int res;
> +
> +     res = mmu_for_each(mmu_init, 0);
> +     if (res) {
> +             printk("%s error during init %d\n", __func__, res);
> +             return res;
> +     }
> +
> +     return 0;
> +}
> +
> +const struct mmio_handler mmu_mmio_handler = {
> +     .check_handler = mmu_mmio_check,
> +     .read_handler  = mmu_mmio_read,
> +     .write_handler = mmu_mmio_write,
> +};
> +
> +__initcall(mmu_init_all);
> -- 
> 1.7.9.5
> 
> 
> _______________________________________________
> Xen-devel mailing list
> Xen-devel@xxxxxxxxxxxxx
> http://lists.xen.org/xen-devel
> 

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.