2016-03-31 10:05:36 +01:00
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Support for Kernel relocation at boot time
*
* Copyright ( C ) 2015 , Imagination Technologies Ltd .
* Authors : Matt Redfearn ( matt . redfearn @ imgtec . com )
*/
2016-03-31 10:05:41 +01:00
# include <asm/bootinfo.h>
2016-03-31 10:05:36 +01:00
# include <asm/cacheflush.h>
2016-03-31 10:05:41 +01:00
# include <asm/fw/fw.h>
2016-03-31 10:05:36 +01:00
# include <asm/sections.h>
# include <asm/setup.h>
# include <asm/timex.h>
# include <linux/elf.h>
# include <linux/kernel.h>
2016-03-31 10:05:41 +01:00
# include <linux/libfdt.h>
# include <linux/of_fdt.h>
2017-03-31 11:51:33 +01:00
# include <linux/sched/task.h>
2016-03-31 10:05:36 +01:00
# include <linux/start_kernel.h>
# include <linux/string.h>
2016-03-31 10:05:41 +01:00
# include <linux/printk.h>
2016-03-31 10:05:36 +01:00
# define RELOCATED(x) ((void *)((long)x + offset))
extern u32 _relocation_start [ ] ; /* End kernel image / start relocation table */
extern u32 _relocation_end [ ] ; /* End relocation table */
extern long __start___ex_table ; /* Start exception table */
extern long __stop___ex_table ; /* End exception table */
2016-11-23 14:43:48 +01:00
extern void __weak plat_fdt_relocated ( void * new_location ) ;
2016-12-09 02:36:22 -06:00
/*
* This function may be defined for a platform to perform any post - relocation
* fixup necessary .
* Return non - zero to abort relocation
*/
int __weak plat_post_relocation ( long offset )
{
return 0 ;
}
2016-03-31 10:05:36 +01:00
static inline u32 __init get_synci_step ( void )
{
u32 res ;
__asm__ ( " rdhwr %0, $1 " : " =r " ( res ) ) ;
return res ;
}
static void __init sync_icache ( void * kbase , unsigned long kernel_length )
{
void * kend = kbase + kernel_length ;
u32 step = get_synci_step ( ) ;
do {
__asm__ __volatile__ (
" synci 0(%0) "
: /* no output */
: " r " ( kbase ) ) ;
kbase + = step ;
} while ( kbase < kend ) ;
/* Completion barrier */
__sync ( ) ;
}
static int __init apply_r_mips_64_rel ( u32 * loc_orig , u32 * loc_new , long offset )
{
* ( u64 * ) loc_new + = offset ;
return 0 ;
}
static int __init apply_r_mips_32_rel ( u32 * loc_orig , u32 * loc_new , long offset )
{
* loc_new + = offset ;
return 0 ;
}
static int __init apply_r_mips_26_rel ( u32 * loc_orig , u32 * loc_new , long offset )
{
unsigned long target_addr = ( * loc_orig ) & 0x03ffffff ;
if ( offset % 4 ) {
pr_err ( " Dangerous R_MIPS_26 REL relocation \n " ) ;
return - ENOEXEC ;
}
/* Original target address */
target_addr < < = 2 ;
target_addr + = ( unsigned long ) loc_orig & ~ 0x03ffffff ;
/* Get the new target address */
target_addr + = offset ;
if ( ( target_addr & 0xf0000000 ) ! = ( ( unsigned long ) loc_new & 0xf0000000 ) ) {
pr_err ( " R_MIPS_26 REL relocation overflow \n " ) ;
return - ENOEXEC ;
}
target_addr - = ( unsigned long ) loc_new & ~ 0x03ffffff ;
target_addr > > = 2 ;
* loc_new = ( * loc_new & ~ 0x03ffffff ) | ( target_addr & 0x03ffffff ) ;
return 0 ;
}
static int __init apply_r_mips_hi16_rel ( u32 * loc_orig , u32 * loc_new , long offset )
{
unsigned long insn = * loc_orig ;
unsigned long target = ( insn & 0xffff ) < < 16 ; /* high 16bits of target */
target + = offset ;
* loc_new = ( insn & ~ 0xffff ) | ( ( target > > 16 ) & 0xffff ) ;
return 0 ;
}
static int ( * reloc_handlers_rel [ ] ) ( u32 * , u32 * , long ) __initdata = {
[ R_MIPS_64 ] = apply_r_mips_64_rel ,
[ R_MIPS_32 ] = apply_r_mips_32_rel ,
[ R_MIPS_26 ] = apply_r_mips_26_rel ,
[ R_MIPS_HI16 ] = apply_r_mips_hi16_rel ,
} ;
int __init do_relocations ( void * kbase_old , void * kbase_new , long offset )
{
u32 * r ;
u32 * loc_orig ;
u32 * loc_new ;
int type ;
int res ;
for ( r = _relocation_start ; r < _relocation_end ; r + + ) {
/* Sentinel for last relocation */
if ( * r = = 0 )
break ;
type = ( * r > > 24 ) & 0xff ;
loc_orig = ( void * ) ( kbase_old + ( ( * r & 0x00ffffff ) < < 2 ) ) ;
loc_new = RELOCATED ( loc_orig ) ;
if ( reloc_handlers_rel [ type ] = = NULL ) {
/* Unsupported relocation */
pr_err ( " Unhandled relocation type %d at 0x%pK \n " ,
type , loc_orig ) ;
return - ENOEXEC ;
}
res = reloc_handlers_rel [ type ] ( loc_orig , loc_new , offset ) ;
if ( res )
return res ;
}
return 0 ;
}
/*
* The exception table is filled in by the relocs tool after vmlinux is linked .
* It must be relocated separately since there will not be any relocation
* information for it filled in by the linker .
*/
static int __init relocate_exception_table ( long offset )
{
unsigned long * etable_start , * etable_end , * e ;
etable_start = RELOCATED ( & __start___ex_table ) ;
etable_end = RELOCATED ( & __stop___ex_table ) ;
for ( e = etable_start ; e < etable_end ; e + + )
* e + = offset ;
return 0 ;
}
2016-03-31 10:05:41 +01:00
# ifdef CONFIG_RANDOMIZE_BASE
static inline __init unsigned long rotate_xor ( unsigned long hash ,
const void * area , size_t size )
{
size_t i ;
unsigned long * ptr = ( unsigned long * ) area ;
for ( i = 0 ; i < size / sizeof ( hash ) ; i + + ) {
/* Rotate by odd number of bits and XOR. */
hash = ( hash < < ( ( sizeof ( hash ) * 8 ) - 7 ) ) | ( hash > > 7 ) ;
hash ^ = ptr [ i ] ;
}
return hash ;
}
static inline __init unsigned long get_random_boot ( void )
{
unsigned long entropy = random_get_entropy ( ) ;
unsigned long hash = 0 ;
/* Attempt to create a simple but unpredictable starting entropy. */
hash = rotate_xor ( hash , linux_banner , strlen ( linux_banner ) ) ;
/* Add in any runtime entropy we can get */
hash = rotate_xor ( hash , & entropy , sizeof ( entropy ) ) ;
# if defined(CONFIG_USE_OF)
/* Get any additional entropy passed in device tree */
2016-10-17 17:21:46 +01:00
if ( initial_boot_params ) {
2016-03-31 10:05:41 +01:00
int node , len ;
u64 * prop ;
node = fdt_path_offset ( initial_boot_params , " /chosen " ) ;
if ( node > = 0 ) {
prop = fdt_getprop_w ( initial_boot_params , node ,
" kaslr-seed " , & len ) ;
if ( prop & & ( len = = sizeof ( u64 ) ) )
hash = rotate_xor ( hash , prop , sizeof ( * prop ) ) ;
}
}
# endif /* CONFIG_USE_OF */
return hash ;
}
static inline __init bool kaslr_disabled ( void )
{
char * str ;
# if defined(CONFIG_CMDLINE_BOOL)
const char * builtin_cmdline = CONFIG_CMDLINE ;
str = strstr ( builtin_cmdline , " nokaslr " ) ;
if ( str = = builtin_cmdline | |
( str > builtin_cmdline & & * ( str - 1 ) = = ' ' ) )
return true ;
# endif
str = strstr ( arcs_cmdline , " nokaslr " ) ;
if ( str = = arcs_cmdline | | ( str > arcs_cmdline & & * ( str - 1 ) = = ' ' ) )
return true ;
return false ;
}
static inline void __init * determine_relocation_address ( void )
{
/* Choose a new address for the kernel */
unsigned long kernel_length ;
void * dest = & _text ;
unsigned long offset ;
if ( kaslr_disabled ( ) )
return dest ;
kernel_length = ( long ) _end - ( long ) ( & _text ) ;
offset = get_random_boot ( ) < < 16 ;
offset & = ( CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1 ) ;
if ( offset < kernel_length )
offset + = ALIGN ( kernel_length , 0xffff ) ;
return RELOCATED ( dest ) ;
}
# else
2016-03-31 10:05:36 +01:00
static inline void __init * determine_relocation_address ( void )
{
/*
* Choose a new address for the kernel
* For now we ' ll hard code the destination
*/
return ( void * ) 0xffffffff81000000 ;
}
2016-03-31 10:05:41 +01:00
# endif
2016-03-31 10:05:36 +01:00
static inline int __init relocation_addr_valid ( void * loc_new )
{
if ( ( unsigned long ) loc_new & 0x0000ffff ) {
/* Inappropriately aligned new location */
return 0 ;
}
if ( ( unsigned long ) loc_new < ( unsigned long ) & _end ) {
/* New location overlaps original kernel */
return 0 ;
}
return 1 ;
}
void * __init relocate_kernel ( void )
{
void * loc_new ;
unsigned long kernel_length ;
unsigned long bss_length ;
long offset = 0 ;
int res = 1 ;
/* Default to original kernel entry point */
void * kernel_entry = start_kernel ;
2016-11-23 14:43:48 +01:00
void * fdt = NULL ;
2016-03-31 10:05:36 +01:00
2016-03-31 10:05:41 +01:00
/* Get the command line */
fw_init_cmdline ( ) ;
# if defined(CONFIG_USE_OF)
/* Deal with the device tree */
2016-11-23 14:43:48 +01:00
fdt = plat_get_fdt ( ) ;
early_init_dt_scan ( fdt ) ;
2016-03-31 10:05:41 +01:00
if ( boot_command_line [ 0 ] ) {
/* Boot command line was passed in device tree */
strlcpy ( arcs_cmdline , boot_command_line , COMMAND_LINE_SIZE ) ;
}
# endif /* CONFIG_USE_OF */
2016-03-31 10:05:36 +01:00
kernel_length = ( long ) ( & _relocation_start ) - ( long ) ( & _text ) ;
bss_length = ( long ) & __bss_stop - ( long ) & __bss_start ;
loc_new = determine_relocation_address ( ) ;
/* Sanity check relocation address */
if ( relocation_addr_valid ( loc_new ) )
offset = ( unsigned long ) loc_new - ( unsigned long ) ( & _text ) ;
2016-03-31 10:05:41 +01:00
/* Reset the command line now so we don't end up with a duplicate */
arcs_cmdline [ 0 ] = ' \0 ' ;
2016-03-31 10:05:36 +01:00
if ( offset ) {
2016-11-23 14:43:48 +01:00
void ( * fdt_relocated_ ) ( void * ) = NULL ;
# if defined(CONFIG_USE_OF)
unsigned long fdt_phys = virt_to_phys ( fdt ) ;
/*
* If built - in dtb is used then it will have been relocated
* during kernel _text relocation . If appended DTB is used
* then it will not be relocated , but it should remain
* intact in the original location . If dtb is loaded by
* the bootloader then it may need to be moved if it crosses
* the target memory area
*/
if ( fdt_phys > = virt_to_phys ( RELOCATED ( & _text ) ) & &
fdt_phys < = virt_to_phys ( RELOCATED ( & _end ) ) ) {
void * fdt_relocated =
RELOCATED ( ALIGN ( ( long ) & _end , PAGE_SIZE ) ) ;
memcpy ( fdt_relocated , fdt , fdt_totalsize ( fdt ) ) ;
fdt = fdt_relocated ;
fdt_relocated_ = RELOCATED ( & plat_fdt_relocated ) ;
}
# endif /* CONFIG_USE_OF */
2016-03-31 10:05:36 +01:00
/* Copy the kernel to it's new location */
memcpy ( loc_new , & _text , kernel_length ) ;
/* Perform relocations on the new kernel */
res = do_relocations ( & _text , loc_new , offset ) ;
if ( res < 0 )
goto out ;
/* Sync the caches ready for execution of new kernel */
sync_icache ( loc_new , kernel_length ) ;
res = relocate_exception_table ( offset ) ;
if ( res < 0 )
goto out ;
/*
* The original . bss has already been cleared , and
* some variables such as command line parameters
* stored to it so make a copy in the new location .
*/
memcpy ( RELOCATED ( & __bss_start ) , & __bss_start , bss_length ) ;
2016-11-23 14:43:48 +01:00
/*
* If fdt was stored outside of the kernel image and
* had to be moved then update platform ' s state data
* with the new fdt location
*/
if ( fdt_relocated_ )
fdt_relocated_ ( fdt ) ;
2016-12-09 02:36:22 -06:00
/*
* Last chance for the platform to abort relocation .
* This may also be used by the platform to perform any
* initialisation required now that the new kernel is
* resident in memory and ready to be executed .
*/
if ( plat_post_relocation ( offset ) )
goto out ;
2016-03-31 10:05:36 +01:00
/* The current thread is now within the relocated image */
__current_thread_info = RELOCATED ( & init_thread_union ) ;
/* Return the new kernel's entry point */
kernel_entry = RELOCATED ( start_kernel ) ;
}
out :
return kernel_entry ;
}
2016-03-31 10:05:41 +01:00
/*
* Show relocation information on panic .
*/
void show_kernel_relocation ( const char * level )
{
unsigned long offset ;
offset = __pa_symbol ( _text ) - __pa_symbol ( VMLINUX_LOAD_ADDRESS ) ;
if ( IS_ENABLED ( CONFIG_RELOCATABLE ) & & offset > 0 ) {
printk ( level ) ;
pr_cont ( " Kernel relocated by 0x%pK \n " , ( void * ) offset ) ;
pr_cont ( " .text @ 0x%pK \n " , _text ) ;
pr_cont ( " .data @ 0x%pK \n " , _sdata ) ;
pr_cont ( " .bss @ 0x%pK \n " , __bss_start ) ;
}
}
static int kernel_location_notifier_fn ( struct notifier_block * self ,
unsigned long v , void * p )
{
show_kernel_relocation ( KERN_EMERG ) ;
return NOTIFY_DONE ;
}
static struct notifier_block kernel_location_notifier = {
. notifier_call = kernel_location_notifier_fn
} ;
static int __init register_kernel_offset_dumper ( void )
{
atomic_notifier_chain_register ( & panic_notifier_list ,
& kernel_location_notifier ) ;
return 0 ;
}
__initcall ( register_kernel_offset_dumper ) ;