2012-03-05 15:49:31 +04:00
/*
* VDSO implementation for AArch64 and vector page setup for AArch32 .
*
* Copyright ( C ) 2012 ARM Limited
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License 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 , see < http : //www.gnu.org/licenses/>.
*
* Author : Will Deacon < will . deacon @ arm . com >
*/
# include <linux/kernel.h>
# include <linux/clocksource.h>
# include <linux/elf.h>
# include <linux/err.h>
# include <linux/errno.h>
# include <linux/gfp.h>
# include <linux/mm.h>
# include <linux/sched.h>
# include <linux/signal.h>
# include <linux/slab.h>
2012-10-16 14:44:53 +04:00
# include <linux/timekeeper_internal.h>
2012-03-05 15:49:31 +04:00
# include <linux/vmalloc.h>
# include <asm/cacheflush.h>
# include <asm/signal32.h>
# include <asm/vdso.h>
# include <asm/vdso_datapage.h>
extern char vdso_start , vdso_end ;
static unsigned long vdso_pages ;
static struct page * * vdso_pagelist ;
/*
* 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 ;
# ifdef CONFIG_COMPAT
/*
* Create and map the vectors page for AArch32 tasks .
*/
static struct page * vectors_page [ 1 ] ;
static int alloc_vectors_page ( void )
{
extern char __kuser_helper_start [ ] , __kuser_helper_end [ ] ;
int kuser_sz = __kuser_helper_end - __kuser_helper_start ;
unsigned long vpage ;
vpage = get_zeroed_page ( GFP_ATOMIC ) ;
if ( ! vpage )
return - ENOMEM ;
/* kuser helpers */
memcpy ( ( void * ) vpage + 0x1000 - kuser_sz , __kuser_helper_start ,
kuser_sz ) ;
/* sigreturn code */
memcpy ( ( void * ) vpage + AARCH32_KERN_SIGRET_CODE_OFFSET ,
aarch32_sigret_code , sizeof ( aarch32_sigret_code ) ) ;
flush_icache_range ( vpage , vpage + PAGE_SIZE ) ;
vectors_page [ 0 ] = virt_to_page ( vpage ) ;
return 0 ;
}
arch_initcall ( alloc_vectors_page ) ;
int aarch32_setup_vectors_page ( struct linux_binprm * bprm , int uses_interp )
{
struct mm_struct * mm = current - > mm ;
unsigned long addr = AARCH32_VECTORS_BASE ;
int ret ;
down_write ( & mm - > mmap_sem ) ;
current - > mm - > context . vdso = ( void * ) addr ;
/* Map vectors page at the high address. */
ret = install_special_mapping ( mm , addr , PAGE_SIZE ,
VM_READ | VM_EXEC | VM_MAYREAD | VM_MAYEXEC ,
vectors_page ) ;
up_write ( & mm - > mmap_sem ) ;
return ret ;
}
# endif /* CONFIG_COMPAT */
static int __init vdso_init ( void )
{
struct page * pg ;
char * vbase ;
int i , ret = 0 ;
vdso_pages = ( & vdso_end - & vdso_start ) > > PAGE_SHIFT ;
pr_info ( " vdso: %ld pages (%ld code, %ld data) at base %p \n " ,
vdso_pages + 1 , vdso_pages , 1L , & vdso_start ) ;
/* Allocate the vDSO pagelist, plus a page for the data. */
vdso_pagelist = kzalloc ( sizeof ( struct page * ) * ( vdso_pages + 1 ) ,
GFP_KERNEL ) ;
if ( vdso_pagelist = = NULL ) {
pr_err ( " Failed to allocate vDSO pagelist! \n " ) ;
return - ENOMEM ;
}
/* Grab the vDSO code pages. */
for ( i = 0 ; i < vdso_pages ; i + + ) {
pg = virt_to_page ( & vdso_start + i * PAGE_SIZE ) ;
ClearPageReserved ( pg ) ;
get_page ( pg ) ;
vdso_pagelist [ i ] = pg ;
}
/* Sanity check the shared object header. */
vbase = vmap ( vdso_pagelist , 1 , 0 , PAGE_KERNEL ) ;
if ( vbase = = NULL ) {
pr_err ( " Failed to map vDSO pagelist! \n " ) ;
return - ENOMEM ;
} else if ( memcmp ( vbase , " \177 ELF " , 4 ) ) {
pr_err ( " vDSO is not a valid ELF object! \n " ) ;
ret = - EINVAL ;
goto unmap ;
}
/* Grab the vDSO data page. */
pg = virt_to_page ( vdso_data ) ;
get_page ( pg ) ;
vdso_pagelist [ i ] = pg ;
unmap :
vunmap ( vbase ) ;
return ret ;
}
arch_initcall ( vdso_init ) ;
int arch_setup_additional_pages ( struct linux_binprm * bprm ,
int uses_interp )
{
struct mm_struct * mm = current - > mm ;
unsigned long vdso_base , vdso_mapping_len ;
int ret ;
/* Be sure to map the data page */
vdso_mapping_len = ( vdso_pages + 1 ) < < PAGE_SHIFT ;
down_write ( & mm - > mmap_sem ) ;
vdso_base = get_unmapped_area ( NULL , 0 , vdso_mapping_len , 0 , 0 ) ;
if ( IS_ERR_VALUE ( vdso_base ) ) {
ret = vdso_base ;
goto up_fail ;
}
mm - > context . vdso = ( void * ) vdso_base ;
ret = install_special_mapping ( mm , vdso_base , vdso_mapping_len ,
VM_READ | VM_EXEC |
VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC ,
vdso_pagelist ) ;
if ( ret ) {
mm - > context . vdso = NULL ;
goto up_fail ;
}
up_fail :
up_write ( & mm - > mmap_sem ) ;
return ret ;
}
const char * arch_vma_name ( struct vm_area_struct * vma )
{
/*
* We can re - use the vdso pointer in mm_context_t for identifying
* the vectors page for compat applications . The vDSO will always
* sit above TASK_UNMAPPED_BASE and so we don ' t need to worry about
* it conflicting with the vectors base .
*/
if ( vma - > vm_mm & & vma - > vm_start = = ( long ) vma - > vm_mm - > context . vdso ) {
# ifdef CONFIG_COMPAT
if ( vma - > vm_start = = AARCH32_VECTORS_BASE )
return " [vectors] " ;
# endif
return " [vdso] " ;
}
return NULL ;
}
/*
* We define AT_SYSINFO_EHDR , so we need these function stubs to keep
* Linux happy .
*/
int in_gate_area_no_mm ( unsigned long addr )
{
return 0 ;
}
int in_gate_area ( struct mm_struct * mm , unsigned long addr )
{
return 0 ;
}
struct vm_area_struct * get_gate_vma ( struct mm_struct * mm )
{
return NULL ;
}
/*
* Update the vDSO data page to keep in sync with kernel timekeeping .
*/
2012-10-16 14:44:53 +04:00
void update_vsyscall ( struct timekeeper * tk )
2012-03-05 15:49:31 +04:00
{
struct timespec xtime_coarse ;
2012-10-16 14:44:53 +04:00
u32 use_syscall = strcmp ( tk - > clock - > name , " arch_sys_counter " ) ;
2012-03-05 15:49:31 +04:00
+ + vdso_data - > tb_seq_count ;
smp_wmb ( ) ;
xtime_coarse = __current_kernel_time ( ) ;
vdso_data - > use_syscall = use_syscall ;
vdso_data - > xtime_coarse_sec = xtime_coarse . tv_sec ;
vdso_data - > xtime_coarse_nsec = xtime_coarse . tv_nsec ;
if ( ! use_syscall ) {
2012-10-16 14:44:53 +04:00
vdso_data - > cs_cycle_last = tk - > clock - > cycle_last ;
vdso_data - > xtime_clock_sec = tk - > xtime_sec ;
2012-11-30 02:33:29 +04:00
vdso_data - > xtime_clock_nsec = tk - > xtime_nsec ;
2012-10-16 14:44:53 +04:00
vdso_data - > cs_mult = tk - > mult ;
vdso_data - > cs_shift = tk - > shift ;
vdso_data - > wtm_clock_sec = tk - > wall_to_monotonic . tv_sec ;
vdso_data - > wtm_clock_nsec = tk - > wall_to_monotonic . tv_nsec ;
2012-03-05 15:49:31 +04:00
}
smp_wmb ( ) ;
+ + vdso_data - > tb_seq_count ;
}
void update_vsyscall_tz ( void )
{
vdso_data - > tz_minuteswest = sys_tz . tz_minuteswest ;
vdso_data - > tz_dsttime = sys_tz . tz_dsttime ;
}