linux/arch/tile/kernel/vdso.c
Chris Metcalf 4a556f4f56 tile: implement gettimeofday() via vDSO
This change creates the framework for vDSO calls, makes the existing
rt_sigreturn() mechanism use it, and adds a fast gettimeofday().
Now that we need to expose the vDSO address to userspace, we add
AT_SYSINFO_EHDR to the set of aux entries provided to userspace.
(You can disable any extra vDSO support by booting with vdso=0,
but the rt_sigreturn vDSO page will still be provided.)

Note that glibc has supported the tile vDSO since release 2.17.

Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
2013-08-13 16:26:21 -04:00

213 lines
5.0 KiB
C

/*
* Copyright 2012 Tilera Corporation. 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
* as published by the Free Software Foundation, version 2.
*
* 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, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/binfmts.h>
#include <linux/compat.h>
#include <linux/elf.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <asm/vdso.h>
#include <asm/mman.h>
#include <asm/sections.h>
#include <arch/sim.h>
/* The alignment of the vDSO. */
#define VDSO_ALIGNMENT PAGE_SIZE
static unsigned int vdso_pages;
static struct page **vdso_pagelist;
#ifdef CONFIG_COMPAT
static unsigned int vdso32_pages;
static struct page **vdso32_pagelist;
#endif
static int vdso_ready;
/*
* The vdso data page.
*/
static union {
struct vdso_data data;
u8 page[PAGE_SIZE];
} vdso_data_store __page_aligned_data;
struct vdso_data *vdso_data = &vdso_data_store.data;
static unsigned int __read_mostly vdso_enabled = 1;
static struct page **vdso_setup(void *vdso_kbase, unsigned int pages)
{
int i;
struct page **pagelist;
pagelist = kzalloc(sizeof(struct page *) * (pages + 1), GFP_KERNEL);
BUG_ON(pagelist == NULL);
for (i = 0; i < pages - 1; i++) {
struct page *pg = virt_to_page(vdso_kbase + i*PAGE_SIZE);
ClearPageReserved(pg);
pagelist[i] = pg;
}
pagelist[pages - 1] = virt_to_page(vdso_data);
pagelist[pages] = NULL;
return pagelist;
}
static int __init vdso_init(void)
{
int data_pages = sizeof(vdso_data_store) >> PAGE_SHIFT;
/*
* We can disable vDSO support generally, but we need to retain
* one page to support the two-bundle (16-byte) rt_sigreturn path.
*/
if (!vdso_enabled) {
size_t offset = (unsigned long)&__vdso_rt_sigreturn;
static struct page *sigret_page;
sigret_page = alloc_page(GFP_KERNEL | __GFP_ZERO);
BUG_ON(sigret_page == NULL);
vdso_pagelist = &sigret_page;
vdso_pages = 1;
BUG_ON(offset >= PAGE_SIZE);
memcpy(page_address(sigret_page) + offset,
vdso_start + offset, 16);
#ifdef CONFIG_COMPAT
vdso32_pages = vdso_pages;
vdso32_pagelist = vdso_pagelist;
#endif
vdso_ready = 1;
return 0;
}
vdso_pages = (vdso_end - vdso_start) >> PAGE_SHIFT;
vdso_pages += data_pages;
vdso_pagelist = vdso_setup(vdso_start, vdso_pages);
#ifdef CONFIG_COMPAT
vdso32_pages = (vdso32_end - vdso32_start) >> PAGE_SHIFT;
vdso32_pages += data_pages;
vdso32_pagelist = vdso_setup(vdso32_start, vdso32_pages);
#endif
smp_wmb();
vdso_ready = 1;
return 0;
}
arch_initcall(vdso_init);
const char *arch_vma_name(struct vm_area_struct *vma)
{
if (vma->vm_mm && vma->vm_start == VDSO_BASE)
return "[vdso]";
#ifndef __tilegx__
if (vma->vm_start == MEM_USER_INTRPT)
return "[intrpt]";
#endif
return NULL;
}
struct vm_area_struct *get_gate_vma(struct mm_struct *mm)
{
return NULL;
}
int in_gate_area(struct mm_struct *mm, unsigned long address)
{
return 0;
}
int in_gate_area_no_mm(unsigned long address)
{
return 0;
}
int setup_vdso_pages(void)
{
struct page **pagelist;
unsigned long pages;
struct mm_struct *mm = current->mm;
unsigned long vdso_base = 0;
int retval = 0;
if (!vdso_ready)
return 0;
mm->context.vdso_base = 0;
pagelist = vdso_pagelist;
pages = vdso_pages;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
pagelist = vdso32_pagelist;
pages = vdso32_pages;
}
#endif
/*
* vDSO has a problem and was disabled, just don't "enable" it for the
* process.
*/
if (pages == 0)
return 0;
vdso_base = get_unmapped_area(NULL, vdso_base,
(pages << PAGE_SHIFT) +
((VDSO_ALIGNMENT - 1) & PAGE_MASK),
0, 0);
if (IS_ERR_VALUE(vdso_base)) {
retval = vdso_base;
return retval;
}
/* Add required alignment. */
vdso_base = ALIGN(vdso_base, VDSO_ALIGNMENT);
/*
* Put vDSO base into mm struct. We need to do this before calling
* install_special_mapping or the perf counter mmap tracking code
* will fail to recognise it as a vDSO (since arch_vma_name fails).
*/
mm->context.vdso_base = vdso_base;
/*
* our vma flags don't have VM_WRITE so by default, the process isn't
* allowed to write those pages.
* gdb can break that with ptrace interface, and thus trigger COW on
* those pages but it's then your responsibility to never do that on
* the "data" page of the vDSO or you'll stop getting kernel updates
* and your nice userland gettimeofday will be totally dead.
* It's fine to use that for setting breakpoints in the vDSO code
* pages though
*/
retval = install_special_mapping(mm, vdso_base,
pages << PAGE_SHIFT,
VM_READ|VM_EXEC |
VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC,
pagelist);
if (retval)
mm->context.vdso_base = 0;
return retval;
}
static __init int vdso_func(char *s)
{
return kstrtouint(s, 0, &vdso_enabled);
}
__setup("vdso=", vdso_func);