kernel: ARM-specific implementation of user-accessible timers
Expose a read-only timer page to user-space to allow for a more
efficient implementation of gettimeofday in user-space.
Change-Id: Ibd6ce41f5f4ff9435e4bf4582ef5de721283d56b
Signed-off-by: Brent DeGraaf <bdegraaf@codeaurora.org>
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 39cf293..89c7417 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -271,6 +271,22 @@
Enables updating the kernel user helper area with the xtime struct
data for gettimeofday via kernel user helpers.
+config ARM_USE_USER_ACCESSIBLE_TIMERS
+ bool "Enables mapping a timer counter page to user space"
+ depends on USE_USER_ACCESSIBLE_TIMERS && GENERIC_TIME_VSYSCALL
+ help
+ Enables ARM-specific user-accessible timers via a shared
+ memory page containing the cycle counter.
+
+config ARM_USER_ACCESSIBLE_TIMER_BASE
+ hex "Base address of user-accessible timer counter page"
+ default 0xfffef000
+ depends on ARM_USE_USER_ACCESSIBLE_TIMERS
+ help
+ Specify the base user-space virtual address where the user-accessible
+ timer counter page should be mapped by the kernel. User-space apps
+ will read directly from the page at this address.
+
source "init/Kconfig"
source "kernel/Kconfig.freezer"
diff --git a/arch/arm/include/asm/mach/map.h b/arch/arm/include/asm/mach/map.h
index cd5be28..f705388 100644
--- a/arch/arm/include/asm/mach/map.h
+++ b/arch/arm/include/asm/mach/map.h
@@ -37,6 +37,7 @@
#define MT_MEMORY_RW 16
#define MT_MEMORY_RX 17
#define MT_MEMORY_DMA_READY 18
+#define MT_DEVICE_USER_ACCESSIBLE 19
#ifdef CONFIG_MMU
extern void iotable_init(struct map_desc *, int);
diff --git a/arch/arm/include/asm/user_accessible_timer.h b/arch/arm/include/asm/user_accessible_timer.h
new file mode 100644
index 0000000..c6d7bd4
--- /dev/null
+++ b/arch/arm/include/asm/user_accessible_timer.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2012, The Linux Foundation. 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.
+ */
+
+#ifndef _ARM_KERNEL_USER_ACCESSIBLE_TIMER_H_
+#define _ARM_KERNEL_USER_ACCESSIBLE_TIMER_H_
+
+#define ARM_USER_ACCESSIBLE_TIMERS_INVALID_PAGE -1
+
+extern unsigned long zero_pfn;
+
+#ifdef CONFIG_ARM_USE_USER_ACCESSIBLE_TIMERS
+#ifndef CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE
+#define CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE 0xfffef000
+#endif
+extern void setup_user_timer_offset(unsigned long addr);
+extern int get_timer_page_address(void);
+static inline int get_user_accessible_timers_base(void)
+{
+ return CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE;
+}
+extern void set_user_accessible_timer_flag(bool flag);
+#else
+#define CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE 0
+static inline void setup_user_timer_offset(unsigned long addr)
+{
+}
+static inline int get_timer_page_address(void)
+{
+ return ARM_USER_ACCESSIBLE_TIMERS_INVALID_PAGE;
+}
+static inline int get_user_accessible_timers_base(void)
+{
+ return 0;
+}
+static inline void set_user_accessible_timer_flag(bool flag)
+{
+}
+#endif
+
+#endif
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index cd1e2a0..fc00a23 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -74,6 +74,7 @@
obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o
AFLAGS_iwmmxt.o := -Wa,-mcpu=iwmmxt
obj-$(CONFIG_ARM_CPU_TOPOLOGY) += topology.o
+obj-$(CONFIG_ARM_USE_USER_ACCESSIBLE_TIMERS) += user_accessible_timer.o
ifneq ($(CONFIG_ARCH_EBSA110),y)
obj-y += io.o
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c
index af21496..ac17480 100644
--- a/arch/arm/kernel/process.c
+++ b/arch/arm/kernel/process.c
@@ -680,6 +680,11 @@
const char *arch_vma_name(struct vm_area_struct *vma)
{
- return (vma == &gate_vma) ? "[vectors]" : NULL;
+ if (vma == &gate_vma)
+ return "[vectors]";
+ else if (vma == get_user_timers_vma(NULL))
+ return "[timers]";
+ else
+ return NULL;
}
#endif
diff --git a/arch/arm/kernel/user_accessible_timer.c b/arch/arm/kernel/user_accessible_timer.c
new file mode 100644
index 0000000..c550c03
--- /dev/null
+++ b/arch/arm/kernel/user_accessible_timer.c
@@ -0,0 +1,132 @@
+/* Copyright (c) 2012, The Linux Foundation. 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.
+ */
+
+#include <linux/export.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <asm/user_accessible_timer.h>
+#include <asm/traps.h>
+
+#define USER_ACCESS_TIMER_OFFSET 0xf30
+#define USER_ACCESS_FEATURE_OFFSET 0xf34
+#define USER_ACCESS_FEATURE_FLAG 0xffff0f20
+
+static struct vm_area_struct user_timers_vma;
+static int __init user_timers_vma_init(void)
+{
+ user_timers_vma.vm_start = CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE;
+ user_timers_vma.vm_end = CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE
+ + PAGE_SIZE;
+ user_timers_vma.vm_page_prot = PAGE_READONLY;
+ user_timers_vma.vm_flags = VM_READ | VM_MAYREAD;
+ return 0;
+}
+arch_initcall(user_timers_vma_init);
+
+int in_user_timers_area(struct mm_struct *mm, unsigned long addr)
+{
+ return (addr >= user_timers_vma.vm_start) &&
+ (addr < user_timers_vma.vm_end);
+}
+EXPORT_SYMBOL(in_user_timers_area);
+
+struct vm_area_struct *get_user_timers_vma(struct mm_struct *mm)
+{
+ return &user_timers_vma;
+}
+EXPORT_SYMBOL(get_user_timers_vma);
+
+int get_user_timer_page(struct vm_area_struct *vma,
+ struct mm_struct *mm, unsigned long start, unsigned int gup_flags,
+ struct page **pages, int idx, int *goto_next_page)
+{
+ /* Replicates the earlier work done in mm/memory.c */
+ unsigned long pg = start & PAGE_MASK;
+ pgd_t *pgd;
+ pud_t *pud;
+ pmd_t *pmd;
+ pte_t *pte;
+
+ /* Unset this flag -- this only gets activated if the
+ * caller should go straight to the next_page label on
+ * return.
+ */
+ *goto_next_page = 0;
+
+ /* user gate pages are read-only */
+ if (gup_flags & FOLL_WRITE)
+ return idx ? : -EFAULT;
+ if (pg > TASK_SIZE)
+ pgd = pgd_offset_k(pg);
+ else
+ pgd = pgd_offset_gate(mm, pg);
+ BUG_ON(pgd_none(*pgd));
+ pud = pud_offset(pgd, pg);
+ BUG_ON(pud_none(*pud));
+ pmd = pmd_offset(pud, pg);
+ if (pmd_none(*pmd))
+ return idx ? : -EFAULT;
+ VM_BUG_ON(pmd_trans_huge(*pmd));
+ pte = pte_offset_map(pmd, pg);
+ if (pte_none(*pte)) {
+ pte_unmap(pte);
+ return idx ? : -EFAULT;
+ }
+ vma = get_user_timers_vma(mm);
+ if (pages) {
+ struct page *page;
+
+ page = vm_normal_page(vma, start, *pte);
+ if (!page) {
+ if (!(gup_flags & FOLL_DUMP) &&
+ zero_pfn == pte_pfn(*pte))
+ page = pte_page(*pte);
+ else {
+ pte_unmap(pte);
+ return idx ? : -EFAULT;
+ }
+ }
+ pages[idx] = page;
+ get_page(page);
+ }
+ pte_unmap(pte);
+ /* In this case, set the next page */
+ *goto_next_page = 1;
+ return 0;
+}
+EXPORT_SYMBOL(get_user_timer_page);
+
+void setup_user_timer_offset(unsigned long addr)
+{
+#if defined(CONFIG_CPU_USE_DOMAINS)
+ unsigned long vectors = CONFIG_VECTORS_BASE;
+#else
+ unsigned long vectors = (unsigned long)vectors_page;
+#endif
+ unsigned long *timer_offset = (unsigned long *)(vectors +
+ USER_ACCESS_TIMER_OFFSET);
+ *timer_offset = addr;
+}
+EXPORT_SYMBOL(setup_user_timer_offset);
+
+void set_user_accessible_timer_flag(bool flag)
+{
+#if defined(CONFIG_CPU_USE_DOMAINS)
+ unsigned long vectors = CONFIG_VECTORS_BASE;
+#else
+ unsigned long vectors = (unsigned long)vectors_page;
+#endif
+ unsigned long *timer_offset = (unsigned long *)(vectors +
+ USER_ACCESS_FEATURE_OFFSET);
+ *timer_offset = (flag ? USER_ACCESS_FEATURE_FLAG : 0);
+}
+EXPORT_SYMBOL(set_user_accessible_timer_flag);
diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
index 57f41ca..e2cd0120 100644
--- a/arch/arm/mm/init.c
+++ b/arch/arm/mm/init.c
@@ -771,6 +771,9 @@
printk(KERN_NOTICE "Virtual kernel memory layout:\n"
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
+#ifdef CONFIG_ARM_USE_USER_ACCESSIBLE_TIMERS
+ " timers : 0x%08lx - 0x%08lx (%4ld kB)\n"
+#endif
#ifdef CONFIG_HAVE_TCM
" DTCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
" ITCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
@@ -791,6 +794,11 @@
MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
(PAGE_SIZE)),
+#ifdef CONFIG_ARM_USE_USER_ACCESSIBLE_TIMERS
+ MLK(UL(CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE),
+ UL(CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE)
+ + (PAGE_SIZE)),
+#endif
#ifdef CONFIG_HAVE_TCM
MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
MLK(ITCM_OFFSET, (unsigned long) itcm_end),
diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c
index bae23b0..1cb6cba 100644
--- a/arch/arm/mm/mmu.c
+++ b/arch/arm/mm/mmu.c
@@ -33,6 +33,8 @@
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
+#include <asm/user_accessible_timer.h>
+
#include "mm.h"
/*
@@ -309,6 +311,13 @@
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_KERNEL,
},
+ [MT_DEVICE_USER_ACCESSIBLE] = {
+ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |
+ L_PTE_SHARED | L_PTE_USER | L_PTE_RDONLY,
+ .prot_l1 = PMD_TYPE_TABLE,
+ .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S,
+ .domain = DOMAIN_IO,
+ },
};
const struct mem_type *get_mem_type(unsigned int type)
@@ -764,7 +773,9 @@
const struct mem_type *type;
pgd_t *pgd;
- if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
+ if ((md->virtual != vectors_base() &&
+ md->virtual != get_user_accessible_timers_base()) &&
+ md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
" at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
@@ -1203,6 +1214,20 @@
mdesc->map_io();
fill_pmd_gaps();
+ if (use_user_accessible_timers()) {
+ /*
+ * Generate a mapping for the timer page.
+ */
+ int page_addr = get_timer_page_address();
+ if (page_addr != ARM_USER_ACCESSIBLE_TIMERS_INVALID_PAGE) {
+ map.pfn = __phys_to_pfn(page_addr);
+ map.virtual = CONFIG_ARM_USER_ACCESSIBLE_TIMER_BASE;
+ map.length = PAGE_SIZE;
+ map.type = MT_DEVICE_USER_ACCESSIBLE;
+ create_mapping(&map, false);
+ }
+ }
+
/*
* Finally flush the caches and tlb to ensure that we're in a
* consistent state wrt the writebuffer. This also ensures that