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

[Xen-devel] [PATCH v03 04/10] arm: introduce remoteprocessor iommu module



Remoteprocessor iommu module is used to handle remote
(external) processors memory management units.
Remote processors are typically used for graphic
rendering (GPUs) and high quality video decoding (IPUs).
They are typically installed on such multimedia SoCs
as OMAP4 / OMAP5.

As soon as remoteprocessor MMU typically works with
pagetables filled by physical addresses, which are
allocated by domU kernel, it is almost impossible to
use them under Xen, intermediate physical addresses
allocated by kernel, need to be translated to machine
addresses.

This patch introduces a simple framework to perform
pfn -> mfn translation for external MMUs.
It introduces basic data structures and algorithms
needed for translation.

Typically, when MMU is configured, some it registers
are updated by new values. Introduced frameworks
uses traps as starting point of remoteproc MMUs
pagetables translation.

Signed-off-by: Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
---
 xen/arch/arm/Makefile                      |   1 +
 xen/arch/arm/Rules.mk                      |   1 +
 xen/arch/arm/remoteproc/Makefile           |   1 +
 xen/arch/arm/remoteproc/remoteproc_iommu.c | 426 +++++++++++++++++++++++++++++
 xen/include/asm-arm/remoteproc_iommu.h     |  82 ++++++
 5 files changed, 511 insertions(+)
 create mode 100644 xen/arch/arm/remoteproc/Makefile
 create mode 100644 xen/arch/arm/remoteproc/remoteproc_iommu.c
 create mode 100644 xen/include/asm-arm/remoteproc_iommu.h

diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile
index c13206f..300db2f 100644
--- a/xen/arch/arm/Makefile
+++ b/xen/arch/arm/Makefile
@@ -1,5 +1,6 @@
 subdir-$(arm32) += arm32
 subdir-$(arm64) += arm64
+subdir-$(HAS_REMOTEPROC) += remoteproc
 subdir-y += platforms
 
 obj-$(EARLY_PRINTK) += early_printk.o
diff --git a/xen/arch/arm/Rules.mk b/xen/arch/arm/Rules.mk
index 8658176..b178eab 100644
--- a/xen/arch/arm/Rules.mk
+++ b/xen/arch/arm/Rules.mk
@@ -110,6 +110,7 @@ CFLAGS-$(EARLY_PRINTK) += 
-DEARLY_PRINTK_INC=\"debug-$(EARLY_PRINTK_INC).inc\"
 CFLAGS-$(EARLY_PRINTK) += -DEARLY_PRINTK_BAUD=$(EARLY_PRINTK_BAUD)
 CFLAGS-$(EARLY_PRINTK) += -DEARLY_UART_BASE_ADDRESS=$(EARLY_UART_BASE_ADDRESS)
 CFLAGS-$(EARLY_PRINTK) += -DEARLY_UART_REG_SHIFT=$(EARLY_UART_REG_SHIFT)
+CFLAGS-$(HAS_REMOTEPROC)   += -DHAS_REMOTEPROC
 
 else # !debug
 
diff --git a/xen/arch/arm/remoteproc/Makefile b/xen/arch/arm/remoteproc/Makefile
new file mode 100644
index 0000000..0b0ee0e
--- /dev/null
+++ b/xen/arch/arm/remoteproc/Makefile
@@ -0,0 +1 @@
+obj-y += remoteproc_iommu.o
diff --git a/xen/arch/arm/remoteproc/remoteproc_iommu.c 
b/xen/arch/arm/remoteproc/remoteproc_iommu.c
new file mode 100644
index 0000000..e73711a
--- /dev/null
+++ b/xen/arch/arm/remoteproc/remoteproc_iommu.c
@@ -0,0 +1,426 @@
+/*
+ * xen/arch/arm/remoteproc_iommu.c
+ *
+ * Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
+ * Copyright (c) 2014 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/domain_page.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 <xsm/xsm.h>
+
+#include <asm/remoteproc_iommu.h>
+
+static struct mmu_info *mmu_list[] = {
+};
+
+#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);             \
+        if ( __res )                                    \
+            break;                                      \
+    }                                                   \
+    __res;                                              \
+})
+
+static bool mmu_check_mem_range(struct mmu_info *mmu, paddr_t addr)
+{
+    if ( (addr >= mmu->mem_start) && (addr < (mmu->mem_start + mmu->mem_size)) 
)
+        return true;
+
+    return false;
+}
+
+static inline struct mmu_info *mmu_lookup(paddr_t addr)
+{
+    u32 i;
+
+    /* enumerate all registered MMU's and check is address in range */
+    for ( i = 0; i < ARRAY_SIZE(mmu_list); i++ )
+    {
+        /* check is in address belongs to the MMU */
+        if ( !mmu_check_mem_range(mmu_list[i], addr) )
+            continue;
+
+        /* check is MMU is properly initialized */
+        if ( !mmu_list[i]->mem_map )
+            continue;
+
+        return mmu_list[i];
+    }
+
+    return NULL;
+}
+
+struct mmu_pagetable *mmu_pagetable_lookup(struct mmu_info *mmu, paddr_t addr, 
bool is_maddr)
+{
+    struct mmu_pagetable *pgt;
+    paddr_t pgt_addr;
+
+    list_for_each_entry(pgt, &mmu->pagetables_list, link_node)
+    {
+        if ( is_maddr )
+            pgt_addr = pgt->maddr;
+        else
+            pgt_addr = pgt->paddr;
+
+        if ( pgt_addr == addr )
+            return pgt;
+    }
+
+    return NULL;
+}
+
+static struct mmu_pagetable *mmu_alloc_pagetable(struct mmu_info *mmu, paddr_t 
paddr)
+{
+    struct mmu_pagetable *pgt;
+    u32 pgt_size = MMU_PGD_TABLE_SIZE(mmu);
+
+    pgt = xzalloc_bytes(sizeof(struct mmu_pagetable));
+    if ( !pgt )
+    {
+        pr_mmu(mmu, "failed to alloc pagetable structure");
+        return NULL;
+    }
+
+    /* allocate pagetable managed by hypervisor */
+    pgt->hyp_pagetable = xzalloc_bytes(pgt_size);
+    if ( !pgt->hyp_pagetable )
+    {
+        pr_mmu(mmu, "failed to alloc private hyp_pagetable");
+        return NULL;
+    }
+
+    /* alocate pagetable for ipa storing */
+    pgt->kern_pagetable = xzalloc_bytes(pgt_size);
+    if ( !pgt->kern_pagetable )
+    {
+        pr_mmu(mmu, "failed to alloc private kern_pagetable");
+        return NULL;
+    }
+
+    pr_mmu(mmu, "private pagetables for paddr 0x%"PRIpaddr" size %u bytes 
(main 0x%"PRIpaddr", temp 0x%"PRIpaddr")",
+           paddr, pgt_size, __pa(pgt->hyp_pagetable), 
__pa(pgt->kern_pagetable));
+
+    pgt->paddr = paddr;
+
+    list_add(&pgt->link_node, &mmu->pagetables_list);
+
+    return pgt;
+}
+
+static paddr_t mmu_translate_pagetable(struct mmu_info *mmu, paddr_t paddr)
+{
+    struct mmu_pagetable *pgt;
+    int res;
+
+    /* sanity check */
+    if ( !mmu->copy_pagetable_pfunc || !mmu->translate_pfunc )
+    {
+        pr_mmu(mmu, "translation callbacks are not defined");
+        return INVALID_PADDR;
+    }
+
+    /* lookup using machine address first */
+    pgt = mmu_pagetable_lookup(mmu, paddr, true);
+    if ( !pgt )
+    {
+        /* lookup using kernel physical address */
+        pgt = mmu_pagetable_lookup(mmu, paddr, false);
+        if ( !pgt )
+        {
+            /* if pagetable doesn't exists in lookup list - allocate it */
+            pgt = mmu_alloc_pagetable(mmu, paddr);
+            if ( !pgt )
+                return INVALID_PADDR;
+        }
+    }
+
+    pgt->maddr = INVALID_PADDR;
+
+    /* copy pagetable from domain to hypervisor */
+    res = mmu->copy_pagetable_pfunc(mmu, pgt);
+       if ( res )
+        return res;
+
+    /* translate pagetable */
+    pgt->maddr = mmu->translate_pfunc(mmu, pgt);
+    return pgt->maddr;
+}
+
+static paddr_t mmu_trap_translate_pagetable(struct mmu_info *mmu, mmio_info_t 
*info)
+{
+    register_t *reg;
+    bool valid_trap = false;
+    paddr_t paddr;
+    u32 i;
+
+    reg = select_user_reg(guest_cpu_user_regs(), info->dabt.reg);
+
+    paddr = *reg;
+    if ( !paddr )
+        return INVALID_PADDR;
+
+    /* check is the register is a valid TTB register */
+    for ( i = 0; i < mmu->num_traps; i++ )
+    {
+        if ( mmu->trap_offsets[i] == (info->gpa - mmu->mem_start) )
+        {
+            valid_trap = true;
+            break;
+        }
+    }
+
+    if ( !valid_trap )
+        return INVALID_PADDR;
+
+    return mmu_translate_pagetable(mmu, paddr);
+}
+
+paddr_t remoteproc_iommu_translate_second_level(struct mmu_info *mmu,
+                                                struct mmu_pagetable *pgt,
+                                                paddr_t maddr, paddr_t 
hyp_addr)
+{
+    u32 *pte_table = NULL, *hyp_pte_table = NULL;
+    u32 i;
+
+    /* map second level translation table */
+    pte_table = map_domain_page(maddr>>PAGE_SHIFT);
+    if ( !pte_table )
+    {
+        pr_mmu(mmu, "failed to map pte table");
+        return INVALID_PADDR;
+    }
+
+    clean_and_invalidate_xen_dcache_va_range(pte_table, PAGE_SIZE);
+    /* allocate new second level pagetable once */
+    if ( 0 == hyp_addr )
+    {
+        hyp_pte_table = xzalloc_bytes(PAGE_SIZE);
+        if ( !hyp_pte_table )
+        {
+            pr_mmu(mmu, "failed to alloc new pte table");
+            return INVALID_PADDR;
+        }
+    }
+    else
+    {
+        hyp_pte_table = __va(hyp_addr & PAGE_MASK);
+    }
+
+    /* 2-nd level translation */
+    for ( i = 0; i < MMU_PTRS_PER_PTE(mmu); i++ )
+    {
+        paddr_t pt_maddr, pt_paddr, pt_flags;
+        u32 pt_mask = MMU_SECTION_MASK(mmu->pg_data->pte_shift);
+        int res;
+
+        if ( !pte_table[i] )
+        {
+            /* handle the case when page was removed */
+            if ( unlikely(hyp_pte_table[i]) )
+            {
+                guest_physmap_unpin_range(current->domain,
+                            (hyp_pte_table[i] & pt_mask) >> PAGE_SHIFT, 0);
+                hyp_pte_table[i] = 0;
+            }
+
+            continue;
+        }
+
+        pt_paddr = pte_table[i] & pt_mask;
+        pt_flags = pte_table[i] & ~pt_mask;
+        pt_maddr = p2m_lookup(current->domain, pt_paddr, NULL);
+
+        if ( INVALID_PADDR == pt_maddr )
+        {
+            pr_mmu(mmu, "can't translate pfn 0x%"PRIpaddr"", pt_paddr);
+            unmap_domain_page(pte_table);
+            return INVALID_PADDR;
+        }
+
+        if ( !guest_physmap_pinned_range(current->domain, pt_maddr >> 
PAGE_SHIFT, 0) )
+        {
+            res = guest_physmap_pin_range(current->domain, pt_maddr >> 
PAGE_SHIFT, 0);
+            if ( res )
+            {
+                pr_mmu(mmu, "can't pin page pfn 0x%"PRIpaddr" mfn 
0x%"PRIpaddr" res %d",
+                       pt_paddr, pt_maddr, res);
+                unmap_domain_page(pte_table);
+                return INVALID_PADDR;
+            }
+        }
+
+        hyp_pte_table[i] = pt_maddr | pt_flags;
+        pgt->page_counter++;
+    }
+
+    unmap_domain_page(pte_table);
+
+    clean_and_invalidate_xen_dcache_va_range(hyp_pte_table, 
MMU_PTE_TABLE_SIZE(mmu));
+    return __pa(hyp_pte_table);
+}
+
+static int mmu_init(struct mmu_info *mmu, u32 data)
+{
+    ASSERT(mmu);
+    ASSERT(!mmu->mem_map);
+
+    INIT_LIST_HEAD(&mmu->pagetables_list);
+
+    /* map MMU memory */
+    mmu->mem_map = ioremap_nocache(mmu->mem_start, mmu->mem_size);
+    if ( !mmu->mem_map )
+    {
+        pr_mmu(mmu, "failed to map memory");
+        return -EINVAL;
+    }
+
+    pr_mmu(mmu, "memory map = 0x%p", mmu->mem_map);
+
+    spin_lock_init(&mmu->lock);
+
+    return 0;
+}
+
+static int mmu_mmio_read(struct vcpu *v, mmio_info_t *info)
+{
+    struct mmu_info *mmu = NULL;
+    unsigned long flags;
+    register_t *reg;
+
+    reg = select_user_reg(guest_cpu_user_regs(), info->dabt.reg);
+
+    mmu = mmu_lookup(info->gpa);
+    if ( !mmu )
+    {
+        pr_mmu(mmu, "can't get mmu for addr 0x%"PRIpaddr"", info->gpa);
+        return -EINVAL;
+    }
+
+    spin_lock_irqsave(&mmu->lock, flags);
+    *reg = readl(mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
+    spin_unlock_irqrestore(&mmu->lock, flags);
+
+    return 1;
+}
+
+static int mmu_mmio_write(struct vcpu *v, mmio_info_t *info)
+{
+    struct mmu_info *mmu = NULL;
+    unsigned long flags;
+    register_t *reg;
+    paddr_t new_addr, val;
+
+    reg = select_user_reg(guest_cpu_user_regs(), info->dabt.reg);
+
+    /* find corresponding MMU */
+    mmu = mmu_lookup(info->gpa);
+    if ( !mmu )
+    {
+        pr_mmu(mmu, "can't get mmu for addr 0x%"PRIpaddr"", info->gpa);
+        return -EINVAL;
+    }
+
+    spin_lock_irqsave(&mmu->lock, flags);
+
+    /* get new address of translated pagetable */
+    new_addr = mmu_trap_translate_pagetable(mmu, info);
+    if ( INVALID_PADDR != new_addr )
+        val = new_addr;
+    else
+        val = *reg;
+
+    writel(val, mmu->mem_map + ((u32)(info->gpa) - mmu->mem_start));
+    spin_unlock_irqrestore(&mmu->lock, flags);
+
+    return 1;
+}
+
+static const struct mmio_handler_ops remoteproc_mmio_handler_ops = {
+    .read_handler  = mmu_mmio_read,
+    .write_handler = mmu_mmio_write,
+};
+
+static int mmu_register_mmio_handler(struct mmu_info *mmu, u32 data)
+{
+    struct domain *dom = (struct domain *) data;
+
+    register_mmio_handler(dom, &remoteproc_mmio_handler_ops,
+                          mmu->mem_start,
+                          mmu->mem_size);
+
+    pr_mmu(mmu, "register mmio handler dom %u base 0x%"PRIpaddr", size 
0x%"PRIpaddr"",
+           dom->domain_id, mmu->mem_start, mmu->mem_size);
+
+    return 0;
+}
+
+int remoteproc_iommu_register_mmio_handlers(struct domain *dom)
+{
+    int res;
+
+    if ( is_idle_domain(dom) )
+        return -EPERM;
+
+    /* check is domain allowed to access remoteproc MMU */
+    res = xsm_domctl(XSM_HOOK, dom, XEN_DOMCTL_access_remote_pagetable);
+    if ( res )
+    {
+        printk(XENLOG_ERR "dom %u is not allowed to access remoteproc MMU res 
(%d)",
+               dom->domain_id, res);
+        return -EPERM;
+    }
+
+    return mmu_for_each(mmu_register_mmio_handler, (u32)dom);
+}
+
+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;
+}
+
+__initcall(mmu_init_all);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/include/asm-arm/remoteproc_iommu.h 
b/xen/include/asm-arm/remoteproc_iommu.h
new file mode 100644
index 0000000..6fa78ee
--- /dev/null
+++ b/xen/include/asm-arm/remoteproc_iommu.h
@@ -0,0 +1,82 @@
+/*
+ * xen/include/xen/remoteproc_iommu.h
+ *
+ * Andrii Tseglytskyi <andrii.tseglytskyi@xxxxxxxxxxxxxxx>
+ * Copyright (c) 2014 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.
+ */
+
+#ifndef _REMOTEPROC_IOMMU_H_
+#define _REMOTEPROC_IOMMU_H_
+
+#include <asm/types.h>
+
+#define MMU_SECTION_SIZE(shift)     (1UL << (shift))
+#define MMU_SECTION_MASK(shift)     (~(MMU_SECTION_SIZE(shift) - 1))
+
+/* 4096 first level descriptors for "supersection" and "section" */
+#define MMU_PTRS_PER_PGD(mmu)       (1UL << (32 - (mmu->pg_data->pgd_shift)))
+#define MMU_PGD_TABLE_SIZE(mmu)     (MMU_PTRS_PER_PGD(mmu) * sizeof(u32))
+
+/* 256 second level descriptors for "small" and "large" pages */
+#define MMU_PTRS_PER_PTE(mmu)       (1UL << ((mmu->pg_data->pgd_shift) - 
(mmu->pg_data->pte_shift)))
+#define MMU_PTE_TABLE_SIZE(mmu)     (MMU_PTRS_PER_PTE(mmu) * sizeof(u32))
+
+/* 16 sections in supersection */
+#define MMU_SECTION_PER_SUPER(mmu)  (1UL << ((mmu->pg_data->super_shift) - 
(mmu->pg_data->section_shift)))
+
+#define pr_mmu(mmu, fmt, ...) \
+    printk(XENLOG_ERR"%s(%d): %s: "fmt"\n", __func__, __LINE__,\
+    ((mmu) ? (mmu)->name : ""), ##__VA_ARGS__)
+
+struct pagetable_data {
+    /* 1st level translation */
+    u32 pgd_shift;
+    u32 pte_shift;
+    u32 super_shift;
+    u32 section_shift;
+    /* 2nd level translation */
+    u32 pte_large_shift;
+};
+
+struct mmu_pagetable {
+    void                *hyp_pagetable;
+    void                *kern_pagetable;
+    paddr_t             paddr;
+    paddr_t             maddr;
+    struct list_head    link_node;
+    u32                 page_counter;
+};
+
+struct mmu_info {
+    const char  *name;
+    const struct pagetable_data *pg_data;
+    /* register where phys pointer to pagetable is stored */
+    u32                 *trap_offsets;
+    paddr_t             mem_start;
+    paddr_t             mem_size;
+    spinlock_t          lock;
+    struct list_head    pagetables_list;
+    u32                 num_traps;
+    void __iomem               *mem_map;
+    paddr_t    (*translate_pfunc)(struct mmu_info *, struct mmu_pagetable *);
+    int (*copy_pagetable_pfunc)(struct mmu_info *mmu, struct mmu_pagetable 
*pgt);
+    void (*print_pagetable_pfunc)(struct mmu_info *);
+};
+
+int remoteproc_iommu_register_mmio_handlers(struct domain *dom);
+
+paddr_t remoteproc_iommu_translate_second_level(struct mmu_info *mmu,
+                                                 struct mmu_pagetable *pgt,
+                                                 paddr_t maddr, paddr_t 
hyp_addr);
+
+#endif /* _REMOTEPROC_IOMMU_H_ */
-- 
1.9.1


_______________________________________________
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®.