2008-02-08 04:19:31 -08:00
/* MN10300 Kernel module helper routines
*
2009-06-17 22:51:11 +01:00
* Copyright ( C ) 2007 , 2008 , 2009 Red Hat , Inc . All Rights Reserved .
2008-02-08 04:19:31 -08:00
* Written by Mark Salter ( msalter @ redhat . com )
* - Derived from arch / i386 / kernel / module . c
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public Licence as published by
* the Free Software Foundation ; either version 2 of the Licence , 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 Licence for more details .
*
* You should have received a copy of the GNU General Public Licence
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/moduleloader.h>
# include <linux/elf.h>
# include <linux/vmalloc.h>
# include <linux/fs.h>
# include <linux/string.h>
# include <linux/kernel.h>
2008-08-04 11:21:23 +01:00
# include <linux/bug.h>
2008-02-08 04:19:31 -08:00
#if 0
# define DEBUGP printk
# else
# define DEBUGP(fmt, ...)
# endif
static void reloc_put16 ( uint8_t * p , uint32_t val )
{
p [ 0 ] = val & 0xff ;
p [ 1 ] = ( val > > 8 ) & 0xff ;
}
static void reloc_put24 ( uint8_t * p , uint32_t val )
{
reloc_put16 ( p , val ) ;
p [ 2 ] = ( val > > 16 ) & 0xff ;
}
static void reloc_put32 ( uint8_t * p , uint32_t val )
{
reloc_put16 ( p , val ) ;
reloc_put16 ( p + 2 , val > > 16 ) ;
}
/*
* apply a RELA relocation
*/
int apply_relocate_add ( Elf32_Shdr * sechdrs ,
const char * strtab ,
unsigned int symindex ,
unsigned int relsec ,
struct module * me )
{
2009-06-17 22:51:11 +01:00
unsigned int i , sym_diff_seen = 0 ;
2008-02-08 04:19:31 -08:00
Elf32_Rela * rel = ( void * ) sechdrs [ relsec ] . sh_addr ;
Elf32_Sym * sym ;
2009-06-17 22:51:11 +01:00
Elf32_Addr relocation , sym_diff_val = 0 ;
2008-02-08 04:19:31 -08:00
uint8_t * location ;
uint32_t value ;
DEBUGP ( " Applying relocate section %u to %u \n " ,
relsec , sechdrs [ relsec ] . sh_info ) ;
for ( i = 0 ; i < sechdrs [ relsec ] . sh_size / sizeof ( * rel ) ; i + + ) {
/* this is where to make the change */
location = ( void * ) sechdrs [ sechdrs [ relsec ] . sh_info ] . sh_addr
+ rel [ i ] . r_offset ;
/* this is the symbol the relocation is referring to (note that
* all undefined symbols have been resolved by the caller ) */
sym = ( Elf32_Sym * ) sechdrs [ symindex ] . sh_addr
+ ELF32_R_SYM ( rel [ i ] . r_info ) ;
/* this is the adjustment to be made */
relocation = sym - > st_value + rel [ i ] . r_addend ;
2009-06-17 22:51:11 +01:00
if ( sym_diff_seen ) {
switch ( ELF32_R_TYPE ( rel [ i ] . r_info ) ) {
case R_MN10300_32 :
case R_MN10300_24 :
case R_MN10300_16 :
case R_MN10300_8 :
relocation - = sym_diff_val ;
sym_diff_seen = 0 ;
break ;
default :
printk ( KERN_ERR " module %s: Unexpected SYM_DIFF relocation: %u \n " ,
me - > name , ELF32_R_TYPE ( rel [ i ] . r_info ) ) ;
return - ENOEXEC ;
}
}
2008-02-08 04:19:31 -08:00
switch ( ELF32_R_TYPE ( rel [ i ] . r_info ) ) {
2008-12-02 14:38:09 +00:00
/* for the first four relocation types, we simply
* store the adjustment at the location given */
2008-02-08 04:19:31 -08:00
case R_MN10300_32 :
2008-12-02 14:38:09 +00:00
reloc_put32 ( location , relocation ) ;
2008-02-08 04:19:31 -08:00
break ;
case R_MN10300_24 :
2008-12-02 14:38:09 +00:00
reloc_put24 ( location , relocation ) ;
2008-02-08 04:19:31 -08:00
break ;
case R_MN10300_16 :
2008-12-02 14:38:09 +00:00
reloc_put16 ( location , relocation ) ;
2008-02-08 04:19:31 -08:00
break ;
case R_MN10300_8 :
2008-12-02 14:38:09 +00:00
* location = relocation ;
2008-02-08 04:19:31 -08:00
break ;
/* for the next three relocation types, we write the
* adjustment with the address subtracted over the
* value at the location given */
case R_MN10300_PCREL32 :
value = relocation - ( uint32_t ) location ;
reloc_put32 ( location , value ) ;
break ;
case R_MN10300_PCREL16 :
value = relocation - ( uint32_t ) location ;
reloc_put16 ( location , value ) ;
break ;
case R_MN10300_PCREL8 :
* location = relocation - ( uint32_t ) location ;
break ;
2009-06-17 22:51:11 +01:00
case R_MN10300_SYM_DIFF :
/* This is used to adjust the next reloc as required
* by relaxation . */
sym_diff_seen = 1 ;
sym_diff_val = sym - > st_value ;
break ;
case R_MN10300_ALIGN :
/* Just ignore the ALIGN relocs.
* Only interesting if kernel performed relaxation . */
continue ;
2008-02-08 04:19:31 -08:00
default :
printk ( KERN_ERR " module %s: Unknown relocation: %u \n " ,
me - > name , ELF32_R_TYPE ( rel [ i ] . r_info ) ) ;
return - ENOEXEC ;
}
}
2009-06-17 22:51:11 +01:00
if ( sym_diff_seen ) {
printk ( KERN_ERR " module %s: Nothing follows SYM_DIFF relocation: %u \n " ,
me - > name , ELF32_R_TYPE ( rel [ i ] . r_info ) ) ;
return - ENOEXEC ;
}
2008-02-08 04:19:31 -08:00
return 0 ;
}