2005-07-14 15:57:16 +00:00
/*
* Copyright ( C ) 2004 , 2005 MIPS Technologies , Inc . All rights reserved .
*
* This program is free software ; you can distribute 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 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 , write to the Free Software Foundation , Inc . ,
* 59 Temple Place - Suite 330 , Boston MA 02111 - 1307 , USA .
*/
/*
* VPE support module
*
* Provides support for loading a MIPS SP program on VPE1 .
* The SP enviroment is rather simple , no tlb ' s . It needs to be relocatable
* ( or partially linked ) . You should initialise your stack in the startup
* code . This loader looks for the symbol __start and sets up
* execution to resume from there . The MIPS SDE kit contains suitable examples .
*
* To load and run , simply cat a SP ' program file ' to / dev / vpe1 .
* i . e cat spapp > / dev / vpe1 .
*/
# include <linux/kernel.h>
2007-02-07 13:48:59 +00:00
# include <linux/device.h>
2005-07-14 15:57:16 +00:00
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <asm/uaccess.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/vmalloc.h>
# include <linux/elf.h>
# include <linux/seq_file.h>
2008-05-15 09:10:50 -06:00
# include <linux/smp_lock.h>
2005-07-14 15:57:16 +00:00
# include <linux/syscalls.h>
# include <linux/moduleloader.h>
# include <linux/interrupt.h>
# include <linux/poll.h>
# include <linux/bootmem.h>
# include <asm/mipsregs.h>
2005-08-17 17:44:08 +00:00
# include <asm/mipsmtregs.h>
2005-07-14 15:57:16 +00:00
# include <asm/cacheflush.h>
# include <asm/atomic.h>
# include <asm/cpu.h>
2007-02-07 13:48:59 +00:00
# include <asm/mips_mt.h>
2005-07-14 15:57:16 +00:00
# include <asm/processor.h>
# include <asm/system.h>
2006-04-05 09:45:45 +01:00
# include <asm/vpe.h>
# include <asm/kspd.h>
2005-07-14 15:57:16 +00:00
typedef void * vpe_handle ;
# ifndef ARCH_SHF_SMALL
# define ARCH_SHF_SMALL 0
# endif
/* If this is set, the section belongs in the init part of the module */
# define INIT_OFFSET_MASK (1UL << (BITS_PER_LONG-1))
2007-07-27 19:33:18 +01:00
/*
* The number of TCs and VPEs physically available on the core
*/
static int hw_tcs , hw_vpes ;
2005-07-14 15:57:16 +00:00
static char module_name [ ] = " vpe " ;
2005-10-31 23:34:52 +00:00
static int major ;
2007-02-07 13:48:59 +00:00
static const int minor = 1 ; /* fixed for now */
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
# ifdef CONFIG_MIPS_APSP_KSPD
static struct kspd_notifications kspd_events ;
static int kspd_events_reqd = 0 ;
# endif
2005-07-14 15:57:16 +00:00
/* grab the likely amount of memory we will need. */
# ifdef CONFIG_MIPS_VPE_LOADER_TOM
# define P_SIZE (2 * 1024 * 1024)
# else
/* add an overhead to the max kmalloc size for non-striped symbols/etc */
# define P_SIZE (256 * 1024)
# endif
2006-04-05 09:45:45 +01:00
extern unsigned long physical_memsize ;
2005-07-14 15:57:16 +00:00
# define MAX_VPES 16
2006-04-05 09:45:45 +01:00
# define VPE_PATH_MAX 256
2005-07-14 15:57:16 +00:00
enum vpe_state {
VPE_STATE_UNUSED = 0 ,
VPE_STATE_INUSE ,
VPE_STATE_RUNNING
} ;
enum tc_state {
TC_STATE_UNUSED = 0 ,
TC_STATE_INUSE ,
TC_STATE_RUNNING ,
TC_STATE_DYNAMIC
} ;
2005-10-31 23:34:52 +00:00
struct vpe {
2005-07-14 15:57:16 +00:00
enum vpe_state state ;
/* (device) minor associated with this vpe */
int minor ;
/* elfloader stuff */
void * load_addr ;
2005-12-08 00:32:23 +00:00
unsigned long len ;
2005-07-14 15:57:16 +00:00
char * pbuffer ;
2005-12-08 00:32:23 +00:00
unsigned long plen ;
2006-04-05 09:45:45 +01:00
unsigned int uid , gid ;
char cwd [ VPE_PATH_MAX ] ;
2005-07-14 15:57:16 +00:00
unsigned long __start ;
/* tc's associated with this vpe */
struct list_head tc ;
/* The list of vpe's */
struct list_head list ;
/* shared symbol address */
void * shared_ptr ;
2006-04-05 09:45:45 +01:00
/* the list of who wants to know when something major happens */
struct list_head notify ;
2007-07-27 19:33:18 +01:00
unsigned int ntcs ;
2005-10-31 23:34:52 +00:00
} ;
struct tc {
enum tc_state state ;
int index ;
2007-07-27 19:31:10 +01:00
struct vpe * pvpe ; /* parent VPE */
struct list_head tc ; /* The list of TC's with this VPE */
struct list_head list ; /* The global list of tc's */
2005-10-31 23:34:52 +00:00
} ;
2005-07-14 15:57:16 +00:00
2007-01-24 19:13:08 +00:00
struct {
2005-07-14 15:57:16 +00:00
/* Virtual processing elements */
struct list_head vpe_list ;
/* Thread contexts */
struct list_head tc_list ;
2007-01-24 19:13:08 +00:00
} vpecontrol = {
. vpe_list = LIST_HEAD_INIT ( vpecontrol . vpe_list ) ,
. tc_list = LIST_HEAD_INIT ( vpecontrol . tc_list )
} ;
2005-07-14 15:57:16 +00:00
static void release_progmem ( void * ptr ) ;
extern void save_gp_address ( unsigned int secbase , unsigned int rel ) ;
/* get the vpe associated with this minor */
struct vpe * get_vpe ( int minor )
{
struct vpe * v ;
2006-04-05 09:45:45 +01:00
if ( ! cpu_has_mipsmt )
return NULL ;
2005-07-14 15:57:16 +00:00
list_for_each_entry ( v , & vpecontrol . vpe_list , list ) {
if ( v - > minor = = minor )
return v ;
}
return NULL ;
}
/* get the vpe associated with this minor */
struct tc * get_tc ( int index )
{
struct tc * t ;
list_for_each_entry ( t , & vpecontrol . tc_list , list ) {
if ( t - > index = = index )
return t ;
}
return NULL ;
}
struct tc * get_tc_unused ( void )
{
struct tc * t ;
list_for_each_entry ( t , & vpecontrol . tc_list , list ) {
if ( t - > state = = TC_STATE_UNUSED )
return t ;
}
return NULL ;
}
/* allocate a vpe and associate it with this minor (or index) */
struct vpe * alloc_vpe ( int minor )
{
struct vpe * v ;
2005-10-31 23:34:52 +00:00
if ( ( v = kzalloc ( sizeof ( struct vpe ) , GFP_KERNEL ) ) = = NULL ) {
2005-07-14 15:57:16 +00:00
return NULL ;
}
INIT_LIST_HEAD ( & v - > tc ) ;
list_add_tail ( & v - > list , & vpecontrol . vpe_list ) ;
2006-04-05 09:45:45 +01:00
INIT_LIST_HEAD ( & v - > notify ) ;
2005-07-14 15:57:16 +00:00
v - > minor = minor ;
return v ;
}
/* allocate a tc. At startup only tc0 is running, all other can be halted. */
struct tc * alloc_tc ( int index )
{
2007-07-27 19:31:10 +01:00
struct tc * tc ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
if ( ( tc = kzalloc ( sizeof ( struct tc ) , GFP_KERNEL ) ) = = NULL )
goto out ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
INIT_LIST_HEAD ( & tc - > tc ) ;
tc - > index = index ;
list_add_tail ( & tc - > list , & vpecontrol . tc_list ) ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
out :
return tc ;
2005-07-14 15:57:16 +00:00
}
/* clean up and free everything */
void release_vpe ( struct vpe * v )
{
list_del ( & v - > list ) ;
if ( v - > load_addr )
release_progmem ( v ) ;
kfree ( v ) ;
}
void dump_mtregs ( void )
{
unsigned long val ;
val = read_c0_config3 ( ) ;
printk ( " config3 0x%lx MT %ld \n " , val ,
( val & CONFIG3_MT ) > > CONFIG3_MT_SHIFT ) ;
val = read_c0_mvpcontrol ( ) ;
printk ( " MVPControl 0x%lx, STLB %ld VPC %ld EVP %ld \n " , val ,
( val & MVPCONTROL_STLB ) > > MVPCONTROL_STLB_SHIFT ,
( val & MVPCONTROL_VPC ) > > MVPCONTROL_VPC_SHIFT ,
( val & MVPCONTROL_EVP ) ) ;
2006-04-05 09:45:45 +01:00
val = read_c0_mvpconf0 ( ) ;
printk ( " mvpconf0 0x%lx, PVPE %ld PTC %ld M %ld \n " , val ,
( val & MVPCONF0_PVPE ) > > MVPCONF0_PVPE_SHIFT ,
val & MVPCONF0_PTC , ( val & MVPCONF0_M ) > > MVPCONF0_M_SHIFT ) ;
2005-07-14 15:57:16 +00:00
}
/* Find some VPE program space */
2005-12-08 00:32:23 +00:00
static void * alloc_progmem ( unsigned long len )
2005-07-14 15:57:16 +00:00
{
2008-03-13 15:16:53 +00:00
void * addr ;
2005-07-14 15:57:16 +00:00
# ifdef CONFIG_MIPS_VPE_LOADER_TOM
2008-03-13 15:16:53 +00:00
/*
* This means you must tell Linux to use less memory than you
* physically have , for example by passing a mem = boot argument .
*/
2008-04-17 13:42:50 +01:00
addr = pfn_to_kaddr ( max_low_pfn ) ;
2008-03-13 15:16:53 +00:00
memset ( addr , 0 , len ) ;
2005-07-14 15:57:16 +00:00
# else
2008-03-13 15:16:53 +00:00
/* simple grab some mem for now */
addr = kzalloc ( len , GFP_KERNEL ) ;
2005-07-14 15:57:16 +00:00
# endif
2008-03-13 15:16:53 +00:00
return addr ;
2005-07-14 15:57:16 +00:00
}
static void release_progmem ( void * ptr )
{
# ifndef CONFIG_MIPS_VPE_LOADER_TOM
kfree ( ptr ) ;
# endif
}
/* Update size with this section: return offset. */
static long get_offset ( unsigned long * size , Elf_Shdr * sechdr )
{
long ret ;
ret = ALIGN ( * size , sechdr - > sh_addralign ? : 1 ) ;
* size = ret + sechdr - > sh_size ;
return ret ;
}
/* Lay out the SHF_ALLOC sections in a way not dissimilar to how ld
might - - code , read - only data , read - write data , small data . Tally
sizes , and place the offsets into sh_entsize fields : high bit means it
belongs in init . */
static void layout_sections ( struct module * mod , const Elf_Ehdr * hdr ,
Elf_Shdr * sechdrs , const char * secstrings )
{
static unsigned long const masks [ ] [ 2 ] = {
/* NOTE: all executable code must be the first section
* in this array ; otherwise modify the text_size
* finder in the two loops below */
{ SHF_EXECINSTR | SHF_ALLOC , ARCH_SHF_SMALL } ,
{ SHF_ALLOC , SHF_WRITE | ARCH_SHF_SMALL } ,
{ SHF_WRITE | SHF_ALLOC , ARCH_SHF_SMALL } ,
{ ARCH_SHF_SMALL | SHF_ALLOC , 0 }
} ;
unsigned int m , i ;
for ( i = 0 ; i < hdr - > e_shnum ; i + + )
sechdrs [ i ] . sh_entsize = ~ 0UL ;
for ( m = 0 ; m < ARRAY_SIZE ( masks ) ; + + m ) {
for ( i = 0 ; i < hdr - > e_shnum ; + + i ) {
Elf_Shdr * s = & sechdrs [ i ] ;
// || strncmp(secstrings + s->sh_name, ".init", 5) == 0)
if ( ( s - > sh_flags & masks [ m ] [ 0 ] ) ! = masks [ m ] [ 0 ]
| | ( s - > sh_flags & masks [ m ] [ 1 ] )
| | s - > sh_entsize ! = ~ 0UL )
continue ;
s - > sh_entsize = get_offset ( & mod - > core_size , s ) ;
}
if ( m = = 0 )
mod - > core_text_size = mod - > core_size ;
}
}
/* from module-elf32.c, but subverted a little */
struct mips_hi16 {
struct mips_hi16 * next ;
Elf32_Addr * addr ;
Elf32_Addr value ;
} ;
static struct mips_hi16 * mips_hi16_list ;
static unsigned int gp_offs , gp_addr ;
static int apply_r_mips_none ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
return 0 ;
}
static int apply_r_mips_gprel16 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
int rel ;
if ( ! ( * location & 0xffff ) ) {
rel = ( int ) v - gp_addr ;
}
else {
/* .sbss + gp(relative) + offset */
/* kludge! */
rel = ( int ) ( short ) ( ( int ) v + gp_offs +
( int ) ( short ) ( * location & 0xffff ) - gp_addr ) ;
}
if ( ( rel > 32768 ) | | ( rel < - 32768 ) ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_DEBUG " VPE loader: apply_r_mips_gprel16: "
" relative address 0x%x out of range of gp register \n " ,
rel ) ;
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
* location = ( * location & 0xffff0000 ) | ( rel & 0xffff ) ;
return 0 ;
}
static int apply_r_mips_pc16 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
int rel ;
rel = ( ( ( unsigned int ) v - ( unsigned int ) location ) ) ;
rel > > = 2 ; // because the offset is in _instructions_ not bytes.
rel - = 1 ; // and one instruction less due to the branch delay slot.
if ( ( rel > 32768 ) | | ( rel < - 32768 ) ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_DEBUG " VPE loader: "
" apply_r_mips_pc16: relative address out of range 0x%x \n " , rel ) ;
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
* location = ( * location & 0xffff0000 ) | ( rel & 0xffff ) ;
return 0 ;
}
static int apply_r_mips_32 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
* location + = v ;
return 0 ;
}
static int apply_r_mips_26 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
if ( v % 4 ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_DEBUG " VPE loader: apply_r_mips_26 "
" unaligned relocation \n " ) ;
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
2005-10-31 23:34:52 +00:00
/*
* Not desperately convinced this is a good check of an overflow condition
* anyway . But it gets in the way of handling undefined weak symbols which
* we want to set to zero .
* if ( ( v & 0xf0000000 ) ! = ( ( ( unsigned long ) location + 4 ) & 0xf0000000 ) ) {
* printk ( KERN_ERR
* " module %s: relocation overflow \n " ,
* me - > name ) ;
* return - ENOEXEC ;
* }
*/
2005-07-14 15:57:16 +00:00
* location = ( * location & ~ 0x03ffffff ) |
( ( * location + ( v > > 2 ) ) & 0x03ffffff ) ;
return 0 ;
}
static int apply_r_mips_hi16 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
struct mips_hi16 * n ;
/*
* We cannot relocate this one now because we don ' t know the value of
* the carry we need to add . Save the information , and let LO16 do the
* actual relocation .
*/
n = kmalloc ( sizeof * n , GFP_KERNEL ) ;
if ( ! n )
return - ENOMEM ;
n - > addr = location ;
n - > value = v ;
n - > next = mips_hi16_list ;
mips_hi16_list = n ;
return 0 ;
}
static int apply_r_mips_lo16 ( struct module * me , uint32_t * location ,
Elf32_Addr v )
{
unsigned long insnlo = * location ;
Elf32_Addr val , vallo ;
/* Sign extend the addend we extract from the lo insn. */
vallo = ( ( insnlo & 0xffff ) ^ 0x8000 ) - 0x8000 ;
if ( mips_hi16_list ! = NULL ) {
struct mips_hi16 * l ;
l = mips_hi16_list ;
while ( l ! = NULL ) {
struct mips_hi16 * next ;
unsigned long insn ;
/*
* The value for the HI16 had best be the same .
*/
2006-04-05 09:45:45 +01:00
if ( v ! = l - > value ) {
printk ( KERN_DEBUG " VPE loader: "
2007-11-19 17:47:54 -08:00
" apply_r_mips_lo16/hi16: \t "
2006-04-05 09:45:45 +01:00
" inconsistent value information \n " ) ;
return - ENOEXEC ;
2005-07-14 15:57:16 +00:00
}
/*
* Do the HI16 relocation . Note that we actually don ' t
* need to know anything about the LO16 itself , except
* where to find the low 16 bits of the addend needed
* by the LO16 .
*/
insn = * l - > addr ;
val = ( ( insn & 0xffff ) < < 16 ) + vallo ;
val + = v ;
/*
* Account for the sign extension that will happen in
* the low bits .
*/
val = ( ( val > > 16 ) + ( ( val & 0x8000 ) ! = 0 ) ) & 0xffff ;
insn = ( insn & ~ 0xffff ) | val ;
* l - > addr = insn ;
next = l - > next ;
kfree ( l ) ;
l = next ;
}
mips_hi16_list = NULL ;
}
/*
* Ok , we ' re done with the HI16 relocs . Now deal with the LO16 .
*/
val = v + vallo ;
insnlo = ( insnlo & ~ 0xffff ) | ( val & 0xffff ) ;
* location = insnlo ;
return 0 ;
}
static int ( * reloc_handlers [ ] ) ( struct module * me , uint32_t * location ,
Elf32_Addr v ) = {
[ R_MIPS_NONE ] = apply_r_mips_none ,
[ R_MIPS_32 ] = apply_r_mips_32 ,
[ R_MIPS_26 ] = apply_r_mips_26 ,
[ R_MIPS_HI16 ] = apply_r_mips_hi16 ,
[ R_MIPS_LO16 ] = apply_r_mips_lo16 ,
[ R_MIPS_GPREL16 ] = apply_r_mips_gprel16 ,
[ R_MIPS_PC16 ] = apply_r_mips_pc16
} ;
2006-04-05 09:45:45 +01:00
static char * rstrs [ ] = {
2007-02-05 00:10:11 +00:00
[ R_MIPS_NONE ] = " MIPS_NONE " ,
2006-04-05 09:45:45 +01:00
[ R_MIPS_32 ] = " MIPS_32 " ,
[ R_MIPS_26 ] = " MIPS_26 " ,
[ R_MIPS_HI16 ] = " MIPS_HI16 " ,
[ R_MIPS_LO16 ] = " MIPS_LO16 " ,
[ R_MIPS_GPREL16 ] = " MIPS_GPREL16 " ,
[ R_MIPS_PC16 ] = " MIPS_PC16 "
} ;
2005-07-14 15:57:16 +00:00
int apply_relocations ( Elf32_Shdr * sechdrs ,
const char * strtab ,
unsigned int symindex ,
unsigned int relsec ,
struct module * me )
{
Elf32_Rel * rel = ( void * ) sechdrs [ relsec ] . sh_addr ;
Elf32_Sym * sym ;
uint32_t * location ;
unsigned int i ;
Elf32_Addr v ;
int res ;
for ( i = 0 ; i < sechdrs [ relsec ] . sh_size / sizeof ( * rel ) ; i + + ) {
Elf32_Word r_info = rel [ i ] . r_info ;
/* This is where to make the change */
location = ( void * ) sechdrs [ sechdrs [ relsec ] . sh_info ] . sh_addr
+ rel [ i ] . r_offset ;
/* This is the symbol it is referring to */
sym = ( Elf32_Sym * ) sechdrs [ symindex ] . sh_addr
+ ELF32_R_SYM ( r_info ) ;
if ( ! sym - > st_value ) {
printk ( KERN_DEBUG " %s: undefined weak symbol %s \n " ,
me - > name , strtab + sym - > st_name ) ;
/* just print the warning, dont barf */
}
v = sym - > st_value ;
res = reloc_handlers [ ELF32_R_TYPE ( r_info ) ] ( me , location , v ) ;
if ( res ) {
2006-04-05 09:45:45 +01:00
char * r = rstrs [ ELF32_R_TYPE ( r_info ) ] ;
printk ( KERN_WARNING " VPE loader: .text+0x%x "
" relocation type %s for symbol \" %s \" failed \n " ,
rel [ i ] . r_offset , r ? r : " UNKNOWN " ,
strtab + sym - > st_name ) ;
2005-07-14 15:57:16 +00:00
return res ;
2006-04-05 09:45:45 +01:00
}
2005-07-14 15:57:16 +00:00
}
return 0 ;
}
void save_gp_address ( unsigned int secbase , unsigned int rel )
{
gp_addr = secbase + rel ;
gp_offs = gp_addr - ( secbase & 0xffff0000 ) ;
}
/* end module-elf32.c */
/* Change all symbols so that sh_value encodes the pointer directly. */
2006-04-05 09:45:45 +01:00
static void simplify_symbols ( Elf_Shdr * sechdrs ,
2005-07-14 15:57:16 +00:00
unsigned int symindex ,
const char * strtab ,
const char * secstrings ,
unsigned int nsecs , struct module * mod )
{
Elf_Sym * sym = ( void * ) sechdrs [ symindex ] . sh_addr ;
unsigned long secbase , bssbase = 0 ;
unsigned int i , n = sechdrs [ symindex ] . sh_size / sizeof ( Elf_Sym ) ;
2006-04-05 09:45:45 +01:00
int size ;
2005-07-14 15:57:16 +00:00
/* find the .bss section for COMMON symbols */
for ( i = 0 ; i < nsecs ; i + + ) {
2006-04-05 09:45:45 +01:00
if ( strncmp ( secstrings + sechdrs [ i ] . sh_name , " .bss " , 4 ) = = 0 ) {
2005-07-14 15:57:16 +00:00
bssbase = sechdrs [ i ] . sh_addr ;
2006-04-05 09:45:45 +01:00
break ;
}
2005-07-14 15:57:16 +00:00
}
for ( i = 1 ; i < n ; i + + ) {
switch ( sym [ i ] . st_shndx ) {
case SHN_COMMON :
2006-04-05 09:45:45 +01:00
/* Allocate space for the symbol in the .bss section.
st_value is currently size .
2005-07-14 15:57:16 +00:00
We want it to have the address of the symbol . */
size = sym [ i ] . st_value ;
sym [ i ] . st_value = bssbase ;
bssbase + = size ;
break ;
case SHN_ABS :
/* Don't need to do anything */
break ;
case SHN_UNDEF :
/* ret = -ENOENT; */
break ;
case SHN_MIPS_SCOMMON :
2007-11-19 17:47:54 -08:00
printk ( KERN_DEBUG " simplify_symbols: ignoring SHN_MIPS_SCOMMON "
2006-04-05 09:45:45 +01:00
" symbol <%s> st_shndx %d \n " , strtab + sym [ i ] . st_name ,
sym [ i ] . st_shndx ) ;
2005-07-14 15:57:16 +00:00
// .sbss section
break ;
default :
secbase = sechdrs [ sym [ i ] . st_shndx ] . sh_addr ;
if ( strncmp ( strtab + sym [ i ] . st_name , " _gp " , 3 ) = = 0 ) {
save_gp_address ( secbase , sym [ i ] . st_value ) ;
}
sym [ i ] . st_value + = secbase ;
break ;
}
}
}
# ifdef DEBUG_ELFLOADER
static void dump_elfsymbols ( Elf_Shdr * sechdrs , unsigned int symindex ,
const char * strtab , struct module * mod )
{
Elf_Sym * sym = ( void * ) sechdrs [ symindex ] . sh_addr ;
unsigned int i , n = sechdrs [ symindex ] . sh_size / sizeof ( Elf_Sym ) ;
printk ( KERN_DEBUG " dump_elfsymbols: n %d \n " , n ) ;
for ( i = 1 ; i < n ; i + + ) {
printk ( KERN_DEBUG " i %d name <%s> 0x%x \n " , i ,
strtab + sym [ i ] . st_name , sym [ i ] . st_value ) ;
}
}
# endif
/* We are prepared so configure and start the VPE... */
2007-02-06 16:53:17 +00:00
static int vpe_run ( struct vpe * v )
2005-07-14 15:57:16 +00:00
{
2007-07-27 19:31:10 +01:00
unsigned long flags , val , dmt_flag ;
2006-04-05 09:45:45 +01:00
struct vpe_notifications * n ;
2007-07-27 19:31:10 +01:00
unsigned int vpeflags ;
2005-07-14 15:57:16 +00:00
struct tc * t ;
/* check we are the Master VPE */
2007-07-27 19:31:10 +01:00
local_irq_save ( flags ) ;
2005-07-14 15:57:16 +00:00
val = read_c0_vpeconf0 ( ) ;
if ( ! ( val & VPECONF0_MVP ) ) {
printk ( KERN_WARNING
2006-04-05 09:45:45 +01:00
" VPE loader: only Master VPE's are allowed to configure MT \n " ) ;
2007-07-27 19:31:10 +01:00
local_irq_restore ( flags ) ;
2005-07-14 15:57:16 +00:00
return - 1 ;
}
2007-07-27 19:31:10 +01:00
dmt_flag = dmt ( ) ;
vpeflags = dvpe ( ) ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
if ( ! list_empty ( & v - > tc ) ) {
2007-02-05 00:10:11 +00:00
if ( ( t = list_entry ( v - > tc . next , struct tc , tc ) ) = = NULL ) {
2007-07-27 19:31:10 +01:00
evpe ( vpeflags ) ;
emt ( dmt_flag ) ;
local_irq_restore ( flags ) ;
printk ( KERN_WARNING
" VPE loader: TC %d is already in use. \n " ,
t - > index ) ;
2007-02-05 00:10:11 +00:00
return - ENOEXEC ;
}
} else {
2007-07-27 19:31:10 +01:00
evpe ( vpeflags ) ;
emt ( dmt_flag ) ;
local_irq_restore ( flags ) ;
printk ( KERN_WARNING
" VPE loader: No TC's associated with VPE %d \n " ,
2007-02-05 00:10:11 +00:00
v - > minor ) ;
2007-07-27 19:31:10 +01:00
2007-02-05 00:10:11 +00:00
return - ENOEXEC ;
}
2006-04-05 09:45:45 +01:00
2005-07-14 15:57:16 +00:00
/* Put MVPE's into 'configuration state' */
2005-08-17 17:44:08 +00:00
set_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
settc ( t - > index ) ;
/* should check it is halted, and not activated */
if ( ( read_tc_c0_tcstatus ( ) & TCSTATUS_A ) | | ! ( read_tc_c0_tchalt ( ) & TCHALT_H ) ) {
2007-07-27 19:31:10 +01:00
evpe ( vpeflags ) ;
emt ( dmt_flag ) ;
local_irq_restore ( flags ) ;
printk ( KERN_WARNING " VPE loader: TC %d is already active! \n " ,
2005-07-14 15:57:16 +00:00
t - > index ) ;
2007-07-27 19:31:10 +01:00
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
/* Write the address we want it to start running from in the TCPC register. */
write_tc_c0_tcrestart ( ( unsigned long ) v - > __start ) ;
write_tc_c0_tccontext ( ( unsigned long ) 0 ) ;
2007-07-27 19:31:10 +01:00
2006-04-05 09:45:45 +01:00
/*
* Mark the TC as activated , not interrupt exempt and not dynamically
* allocatable
*/
2005-07-14 15:57:16 +00:00
val = read_tc_c0_tcstatus ( ) ;
val = ( val & ~ ( TCSTATUS_DA | TCSTATUS_IXMT ) ) | TCSTATUS_A ;
write_tc_c0_tcstatus ( val ) ;
write_tc_c0_tchalt ( read_tc_c0_tchalt ( ) & ~ TCHALT_H ) ;
/*
* The sde - kit passes ' memsize ' to __start in $ a3 , so set something
2006-04-05 09:45:45 +01:00
* here . . . Or set $ a3 to zero and define DFLT_STACK_SIZE and
2005-07-14 15:57:16 +00:00
* DFLT_HEAP_SIZE when you compile your program
*/
2007-07-27 19:33:18 +01:00
mttgpr ( 6 , v - > ntcs ) ;
2007-07-27 19:31:10 +01:00
mttgpr ( 7 , physical_memsize ) ;
2006-04-05 09:45:45 +01:00
/* set up VPE1 */
/*
* bind the TC to VPE 1 as late as possible so we only have the final
* VPE registers to set up , and so an EJTAG probe can trigger on it
*/
2007-07-27 19:31:10 +01:00
write_tc_c0_tcbind ( ( read_tc_c0_tcbind ( ) & ~ TCBIND_CURVPE ) | 1 ) ;
2005-07-14 15:57:16 +00:00
2006-08-17 12:39:21 +01:00
write_vpe_c0_vpeconf0 ( read_vpe_c0_vpeconf0 ( ) & ~ ( VPECONF0_VPA ) ) ;
back_to_back_c0_hazard ( ) ;
2007-02-05 00:10:11 +00:00
/* Set up the XTC bit in vpeconf0 to point at our tc */
write_vpe_c0_vpeconf0 ( ( read_vpe_c0_vpeconf0 ( ) & ~ ( VPECONF0_XTC ) )
| ( t - > index < < VPECONF0_XTC_SHIFT ) ) ;
2005-07-14 15:57:16 +00:00
2006-08-17 12:39:21 +01:00
back_to_back_c0_hazard ( ) ;
2007-02-05 00:10:11 +00:00
/* enable this VPE */
write_vpe_c0_vpeconf0 ( read_vpe_c0_vpeconf0 ( ) | VPECONF0_VPA ) ;
2005-07-14 15:57:16 +00:00
/* clear out any left overs from a previous program */
2006-04-05 09:45:45 +01:00
write_vpe_c0_status ( 0 ) ;
2005-07-14 15:57:16 +00:00
write_vpe_c0_cause ( 0 ) ;
/* take system out of configuration state */
2005-08-17 17:44:08 +00:00
clear_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
2008-04-16 15:32:22 +02:00
/*
* SMTC / SMVP kernels manage VPE enable independently ,
* but uniprocessor kernels need to turn it on , even
* if that wasn ' t the pre - dvpe ( ) state .
*/
2007-07-27 19:31:10 +01:00
# ifdef CONFIG_SMP
evpe ( vpeflags ) ;
2008-04-16 15:32:22 +02:00
# else
evpe ( EVPE_ENABLE ) ;
2007-07-27 19:31:10 +01:00
# endif
emt ( dmt_flag ) ;
local_irq_restore ( flags ) ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
list_for_each_entry ( n , & v - > notify , list )
n - > start ( minor ) ;
2006-04-05 09:45:45 +01:00
2005-07-14 15:57:16 +00:00
return 0 ;
}
2006-04-05 09:45:45 +01:00
static int find_vpe_symbols ( struct vpe * v , Elf_Shdr * sechdrs ,
2005-07-14 15:57:16 +00:00
unsigned int symindex , const char * strtab ,
struct module * mod )
{
Elf_Sym * sym = ( void * ) sechdrs [ symindex ] . sh_addr ;
unsigned int i , n = sechdrs [ symindex ] . sh_size / sizeof ( Elf_Sym ) ;
for ( i = 1 ; i < n ; i + + ) {
if ( strcmp ( strtab + sym [ i ] . st_name , " __start " ) = = 0 ) {
v - > __start = sym [ i ] . st_value ;
}
if ( strcmp ( strtab + sym [ i ] . st_name , " vpe_shared " ) = = 0 ) {
v - > shared_ptr = ( void * ) sym [ i ] . st_value ;
}
}
2006-04-05 09:45:45 +01:00
if ( ( v - > __start = = 0 ) | | ( v - > shared_ptr = = NULL ) )
return - 1 ;
2005-07-14 15:57:16 +00:00
return 0 ;
}
2005-10-31 23:34:52 +00:00
/*
2006-04-05 09:45:45 +01:00
* Allocates a VPE with some program code space ( the load address ) , copies the
* contents of the program ( p ) buffer performing relocatations / etc , free ' s it
* when finished .
*/
2007-02-06 16:53:17 +00:00
static int vpe_elfload ( struct vpe * v )
2005-07-14 15:57:16 +00:00
{
Elf_Ehdr * hdr ;
Elf_Shdr * sechdrs ;
long err = 0 ;
char * secstrings , * strtab = NULL ;
2006-04-05 09:45:45 +01:00
unsigned int len , i , symindex = 0 , strindex = 0 , relocate = 0 ;
2005-07-14 15:57:16 +00:00
struct module mod ; // so we can re-use the relocations code
memset ( & mod , 0 , sizeof ( struct module ) ) ;
2006-04-05 09:45:45 +01:00
strcpy ( mod . name , " VPE loader " ) ;
2005-07-14 15:57:16 +00:00
hdr = ( Elf_Ehdr * ) v - > pbuffer ;
len = v - > plen ;
/* Sanity checks against insmoding binaries or wrong arch,
weird elf version */
2008-05-04 17:50:02 +01:00
if ( memcmp ( hdr - > e_ident , ELFMAG , SELFMAG ) ! = 0
2006-04-05 09:45:45 +01:00
| | ( hdr - > e_type ! = ET_REL & & hdr - > e_type ! = ET_EXEC )
| | ! elf_check_arch ( hdr )
2005-07-14 15:57:16 +00:00
| | hdr - > e_shentsize ! = sizeof ( * sechdrs ) ) {
printk ( KERN_WARNING
2006-04-05 09:45:45 +01:00
" VPE loader: program wrong arch or weird elf version \n " ) ;
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
2006-04-05 09:45:45 +01:00
if ( hdr - > e_type = = ET_REL )
relocate = 1 ;
2005-07-14 15:57:16 +00:00
if ( len < hdr - > e_shoff + hdr - > e_shnum * sizeof ( Elf_Shdr ) ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_ERR " VPE loader: program length %u truncated \n " ,
len ) ;
2005-07-14 15:57:16 +00:00
return - ENOEXEC ;
}
/* Convenience variables */
sechdrs = ( void * ) hdr + hdr - > e_shoff ;
secstrings = ( void * ) hdr + sechdrs [ hdr - > e_shstrndx ] . sh_offset ;
sechdrs [ 0 ] . sh_addr = 0 ;
/* And these should exist, but gcc whinges if we don't init them */
symindex = strindex = 0 ;
2006-04-05 09:45:45 +01:00
if ( relocate ) {
for ( i = 1 ; i < hdr - > e_shnum ; i + + ) {
if ( sechdrs [ i ] . sh_type ! = SHT_NOBITS
& & len < sechdrs [ i ] . sh_offset + sechdrs [ i ] . sh_size ) {
printk ( KERN_ERR " VPE program length %u truncated \n " ,
len ) ;
return - ENOEXEC ;
}
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
/* Mark all sections sh_addr with their address in the
temporary image . */
sechdrs [ i ] . sh_addr = ( size_t ) hdr + sechdrs [ i ] . sh_offset ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
/* Internal symbols and strings. */
if ( sechdrs [ i ] . sh_type = = SHT_SYMTAB ) {
symindex = i ;
strindex = sechdrs [ i ] . sh_link ;
strtab = ( char * ) hdr + sechdrs [ strindex ] . sh_offset ;
}
2005-07-14 15:57:16 +00:00
}
2006-04-05 09:45:45 +01:00
layout_sections ( & mod , hdr , sechdrs , secstrings ) ;
2005-07-14 15:57:16 +00:00
}
v - > load_addr = alloc_progmem ( mod . core_size ) ;
2008-03-13 15:16:53 +00:00
if ( ! v - > load_addr )
return - ENOMEM ;
2005-07-14 15:57:16 +00:00
2008-03-13 15:16:53 +00:00
pr_info ( " VPE loader: loading to %p \n " , v - > load_addr ) ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
if ( relocate ) {
for ( i = 0 ; i < hdr - > e_shnum ; i + + ) {
void * dest ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
if ( ! ( sechdrs [ i ] . sh_flags & SHF_ALLOC ) )
continue ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
dest = v - > load_addr + sechdrs [ i ] . sh_entsize ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
if ( sechdrs [ i ] . sh_type ! = SHT_NOBITS )
memcpy ( dest , ( void * ) sechdrs [ i ] . sh_addr ,
sechdrs [ i ] . sh_size ) ;
/* Update sh_addr to point to copy in image. */
sechdrs [ i ] . sh_addr = ( unsigned long ) dest ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
printk ( KERN_DEBUG " section sh_name %s sh_addr 0x%x \n " ,
secstrings + sechdrs [ i ] . sh_name , sechdrs [ i ] . sh_addr ) ;
}
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
/* Fix up syms, so that st_value is a pointer to location. */
simplify_symbols ( sechdrs , symindex , strtab , secstrings ,
hdr - > e_shnum , & mod ) ;
/* Now do relocations. */
for ( i = 1 ; i < hdr - > e_shnum ; i + + ) {
const char * strtab = ( char * ) sechdrs [ strindex ] . sh_addr ;
unsigned int info = sechdrs [ i ] . sh_info ;
/* Not a valid relocation section? */
if ( info > = hdr - > e_shnum )
continue ;
/* Don't bother with non-allocated sections */
if ( ! ( sechdrs [ info ] . sh_flags & SHF_ALLOC ) )
continue ;
if ( sechdrs [ i ] . sh_type = = SHT_REL )
err = apply_relocations ( sechdrs , strtab , symindex , i ,
& mod ) ;
else if ( sechdrs [ i ] . sh_type = = SHT_RELA )
err = apply_relocate_add ( sechdrs , strtab , symindex , i ,
& mod ) ;
if ( err < 0 )
return err ;
}
} else {
2007-10-10 13:33:03 +01:00
struct elf_phdr * phdr = ( struct elf_phdr * ) ( ( char * ) hdr + hdr - > e_phoff ) ;
2006-04-05 09:45:45 +01:00
2007-10-10 13:33:03 +01:00
for ( i = 0 ; i < hdr - > e_phnum ; i + + ) {
2008-04-16 15:32:22 +02:00
if ( phdr - > p_type = = PT_LOAD ) {
memcpy ( ( void * ) phdr - > p_paddr ,
( char * ) hdr + phdr - > p_offset ,
phdr - > p_filesz ) ;
memset ( ( void * ) phdr - > p_paddr + phdr - > p_filesz ,
0 , phdr - > p_memsz - phdr - > p_filesz ) ;
}
phdr + + ;
2007-10-10 13:33:03 +01:00
}
for ( i = 0 ; i < hdr - > e_shnum ; i + + ) {
2006-04-05 09:45:45 +01:00
/* Internal symbols and strings. */
if ( sechdrs [ i ] . sh_type = = SHT_SYMTAB ) {
symindex = i ;
strindex = sechdrs [ i ] . sh_link ;
strtab = ( char * ) hdr + sechdrs [ strindex ] . sh_offset ;
/* mark the symtab's address for when we try to find the
magic symbols */
sechdrs [ i ] . sh_addr = ( size_t ) hdr + sechdrs [ i ] . sh_offset ;
}
2005-07-14 15:57:16 +00:00
}
}
/* make sure it's physically written out */
flush_icache_range ( ( unsigned long ) v - > load_addr ,
( unsigned long ) v - > load_addr + v - > len ) ;
if ( ( find_vpe_symbols ( v , sechdrs , symindex , strtab , & mod ) ) < 0 ) {
2006-04-05 09:45:45 +01:00
if ( v - > __start = = 0 ) {
printk ( KERN_WARNING " VPE loader: program does not contain "
" a __start symbol \n " ) ;
return - ENOEXEC ;
}
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
if ( v - > shared_ptr = = NULL )
printk ( KERN_WARNING " VPE loader: "
" program does not contain vpe_shared symbol. \n "
" Unable to use AMVP (AP/SP) facilities. \n " ) ;
2005-07-14 15:57:16 +00:00
}
printk ( " elf loaded \n " ) ;
2006-04-05 09:45:45 +01:00
return 0 ;
2005-07-14 15:57:16 +00:00
}
2006-04-05 09:45:45 +01:00
static void cleanup_tc ( struct tc * tc )
{
2007-07-27 19:31:10 +01:00
unsigned long flags ;
unsigned int mtflags , vpflags ;
2006-04-05 09:45:45 +01:00
int tmp ;
2007-07-27 19:31:10 +01:00
local_irq_save ( flags ) ;
mtflags = dmt ( ) ;
vpflags = dvpe ( ) ;
2006-04-05 09:45:45 +01:00
/* Put MVPE's into 'configuration state' */
set_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
settc ( tc - > index ) ;
tmp = read_tc_c0_tcstatus ( ) ;
/* mark not allocated and not dynamically allocatable */
tmp & = ~ ( TCSTATUS_A | TCSTATUS_DA ) ;
tmp | = TCSTATUS_IXMT ; /* interrupt exempt */
write_tc_c0_tcstatus ( tmp ) ;
write_tc_c0_tchalt ( TCHALT_H ) ;
2007-11-08 13:25:51 +00:00
mips_ihb ( ) ;
2006-04-05 09:45:45 +01:00
/* bind it to anything other than VPE1 */
2007-07-27 19:31:10 +01:00
// write_tc_c0_tcbind(read_tc_c0_tcbind() & ~TCBIND_CURVPE); // | TCBIND_CURVPE
2006-04-05 09:45:45 +01:00
clear_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2007-07-27 19:31:10 +01:00
evpe ( vpflags ) ;
emt ( mtflags ) ;
local_irq_restore ( flags ) ;
2006-04-05 09:45:45 +01:00
}
static int getcwd ( char * buff , int size )
{
mm_segment_t old_fs ;
int ret ;
old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
2007-10-11 23:46:15 +01:00
ret = sys_getcwd ( buff , size ) ;
2006-04-05 09:45:45 +01:00
set_fs ( old_fs ) ;
return ret ;
}
/* checks VPE is unused and gets ready to load program */
2005-07-14 15:57:16 +00:00
static int vpe_open ( struct inode * inode , struct file * filp )
{
2007-02-23 13:40:45 +00:00
enum vpe_state state ;
2006-04-05 09:45:45 +01:00
struct vpe_notifications * not ;
2007-07-27 19:31:10 +01:00
struct vpe * v ;
2008-05-15 09:10:50 -06:00
int ret , err = 0 ;
2005-07-14 15:57:16 +00:00
2008-05-15 09:10:50 -06:00
lock_kernel ( ) ;
2007-07-27 19:31:10 +01:00
if ( minor ! = iminor ( inode ) ) {
/* assume only 1 device at the moment. */
2006-04-05 09:45:45 +01:00
printk ( KERN_WARNING " VPE loader: only vpe1 is supported \n " ) ;
2008-05-15 09:10:50 -06:00
err = - ENODEV ;
goto out ;
2005-07-14 15:57:16 +00:00
}
2007-07-27 19:31:10 +01:00
if ( ( v = get_vpe ( tclimit ) ) = = NULL ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_WARNING " VPE loader: unable to get vpe \n " ) ;
2008-05-15 09:10:50 -06:00
err = - ENODEV ;
goto out ;
2005-07-14 15:57:16 +00:00
}
2007-02-23 13:40:45 +00:00
state = xchg ( & v - > state , VPE_STATE_INUSE ) ;
if ( state ! = VPE_STATE_UNUSED ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_DEBUG " VPE loader: tc in use dumping regs \n " ) ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
list_for_each_entry ( not , & v - > notify , list ) {
2007-07-27 19:31:10 +01:00
not - > stop ( tclimit ) ;
2006-04-05 09:45:45 +01:00
}
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
release_progmem ( v - > load_addr ) ;
2007-07-27 19:31:10 +01:00
cleanup_tc ( get_tc ( tclimit ) ) ;
2005-07-14 15:57:16 +00:00
}
/* this of-course trashes what was there before... */
v - > pbuffer = vmalloc ( P_SIZE ) ;
v - > plen = P_SIZE ;
v - > load_addr = NULL ;
v - > len = 0 ;
2008-11-14 10:39:25 +11:00
v - > uid = filp - > f_cred - > fsuid ;
v - > gid = filp - > f_cred - > fsgid ;
2006-04-05 09:45:45 +01:00
# ifdef CONFIG_MIPS_APSP_KSPD
/* get kspd to tell us when a syscall_exit happens */
if ( ! kspd_events_reqd ) {
kspd_notify ( & kspd_events ) ;
kspd_events_reqd + + ;
}
# endif
v - > cwd [ 0 ] = 0 ;
ret = getcwd ( v - > cwd , VPE_PATH_MAX ) ;
if ( ret < 0 )
printk ( KERN_WARNING " VPE loader: open, getcwd returned %d \n " , ret ) ;
v - > shared_ptr = NULL ;
v - > __start = 0 ;
2007-07-27 19:31:10 +01:00
2008-05-15 09:10:50 -06:00
out :
unlock_kernel ( ) ;
2005-07-14 15:57:16 +00:00
return 0 ;
}
static int vpe_release ( struct inode * inode , struct file * filp )
{
2005-10-31 23:34:52 +00:00
struct vpe * v ;
2005-07-14 15:57:16 +00:00
Elf_Ehdr * hdr ;
2007-07-27 19:31:10 +01:00
int ret = 0 ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
v = get_vpe ( tclimit ) ;
if ( v = = NULL )
2005-07-14 15:57:16 +00:00
return - ENODEV ;
hdr = ( Elf_Ehdr * ) v - > pbuffer ;
2008-05-04 17:50:02 +01:00
if ( memcmp ( hdr - > e_ident , ELFMAG , SELFMAG ) = = 0 ) {
2007-07-27 19:31:10 +01:00
if ( vpe_elfload ( v ) > = 0 ) {
2005-07-14 15:57:16 +00:00
vpe_run ( v ) ;
2007-07-27 19:31:10 +01:00
} else {
2006-04-05 09:45:45 +01:00
printk ( KERN_WARNING " VPE loader: ELF load failed. \n " ) ;
2005-07-14 15:57:16 +00:00
ret = - ENOEXEC ;
}
} else {
2006-04-05 09:45:45 +01:00
printk ( KERN_WARNING " VPE loader: only elf files are supported \n " ) ;
2005-07-14 15:57:16 +00:00
ret = - ENOEXEC ;
}
2006-04-05 09:45:45 +01:00
/* It's good to be able to run the SP and if it chokes have a look at
the / dev / rt ? . But if we reset the pointer to the shared struct we
2008-12-05 11:36:54 +11:00
lose what has happened . So perhaps if garbage is sent to the vpe
2006-04-05 09:45:45 +01:00
device , use it as a trigger for the reset . Hopefully a nice
executable will be along shortly . */
if ( ret < 0 )
v - > shared_ptr = NULL ;
2005-07-14 15:57:16 +00:00
// cleanup any temp buffers
if ( v - > pbuffer )
vfree ( v - > pbuffer ) ;
v - > plen = 0 ;
return ret ;
}
static ssize_t vpe_write ( struct file * file , const char __user * buffer ,
size_t count , loff_t * ppos )
{
size_t ret = count ;
2005-10-31 23:34:52 +00:00
struct vpe * v ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
if ( iminor ( file - > f_path . dentry - > d_inode ) ! = minor )
return - ENODEV ;
v = get_vpe ( tclimit ) ;
if ( v = = NULL )
2005-07-14 15:57:16 +00:00
return - ENODEV ;
if ( v - > pbuffer = = NULL ) {
2006-04-05 09:45:45 +01:00
printk ( KERN_ERR " VPE loader: no buffer for program \n " ) ;
2005-07-14 15:57:16 +00:00
return - ENOMEM ;
}
if ( ( count + v - > len ) > v - > plen ) {
printk ( KERN_WARNING
2006-04-05 09:45:45 +01:00
" VPE loader: elf size too big. Perhaps strip uneeded symbols \n " ) ;
2005-07-14 15:57:16 +00:00
return - ENOMEM ;
}
count - = copy_from_user ( v - > pbuffer + v - > len , buffer , count ) ;
2006-04-05 09:45:45 +01:00
if ( ! count )
2005-07-14 15:57:16 +00:00
return - EFAULT ;
v - > len + = count ;
return ret ;
}
2007-02-12 00:55:31 -08:00
static const struct file_operations vpe_fops = {
2005-07-14 15:57:16 +00:00
. owner = THIS_MODULE ,
. open = vpe_open ,
. release = vpe_release ,
. write = vpe_write
} ;
/* module wrapper entry points */
/* give me a vpe */
vpe_handle vpe_alloc ( void )
{
int i ;
struct vpe * v ;
/* find a vpe */
for ( i = 1 ; i < MAX_VPES ; i + + ) {
if ( ( v = get_vpe ( i ) ) ! = NULL ) {
v - > state = VPE_STATE_INUSE ;
return v ;
}
}
return NULL ;
}
EXPORT_SYMBOL ( vpe_alloc ) ;
/* start running from here */
int vpe_start ( vpe_handle vpe , unsigned long start )
{
struct vpe * v = vpe ;
v - > __start = start ;
return vpe_run ( v ) ;
}
EXPORT_SYMBOL ( vpe_start ) ;
/* halt it for now */
int vpe_stop ( vpe_handle vpe )
{
struct vpe * v = vpe ;
struct tc * t ;
unsigned int evpe_flags ;
evpe_flags = dvpe ( ) ;
if ( ( t = list_entry ( v - > tc . next , struct tc , tc ) ) ! = NULL ) {
settc ( t - > index ) ;
write_vpe_c0_vpeconf0 ( read_vpe_c0_vpeconf0 ( ) & ~ VPECONF0_VPA ) ;
}
evpe ( evpe_flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( vpe_stop ) ;
/* I've done with it thank you */
int vpe_free ( vpe_handle vpe )
{
struct vpe * v = vpe ;
struct tc * t ;
unsigned int evpe_flags ;
if ( ( t = list_entry ( v - > tc . next , struct tc , tc ) ) = = NULL ) {
return - ENOEXEC ;
}
evpe_flags = dvpe ( ) ;
/* Put MVPE's into 'configuration state' */
2005-08-17 17:44:08 +00:00
set_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
settc ( t - > index ) ;
write_vpe_c0_vpeconf0 ( read_vpe_c0_vpeconf0 ( ) & ~ VPECONF0_VPA ) ;
2007-11-08 13:25:51 +00:00
/* halt the TC */
2005-07-14 15:57:16 +00:00
write_tc_c0_tchalt ( TCHALT_H ) ;
2007-11-08 13:25:51 +00:00
mips_ihb ( ) ;
/* mark the TC unallocated */
write_tc_c0_tcstatus ( read_tc_c0_tcstatus ( ) & ~ TCSTATUS_A ) ;
2005-07-14 15:57:16 +00:00
v - > state = VPE_STATE_UNUSED ;
2005-08-17 17:44:08 +00:00
clear_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
evpe ( evpe_flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( vpe_free ) ;
void * vpe_get_shared ( int index )
{
struct vpe * v ;
2006-04-05 09:45:45 +01:00
if ( ( v = get_vpe ( index ) ) = = NULL )
2005-07-14 15:57:16 +00:00
return NULL ;
return v - > shared_ptr ;
}
EXPORT_SYMBOL ( vpe_get_shared ) ;
2006-04-05 09:45:45 +01:00
int vpe_getuid ( int index )
{
struct vpe * v ;
if ( ( v = get_vpe ( index ) ) = = NULL )
return - 1 ;
return v - > uid ;
}
EXPORT_SYMBOL ( vpe_getuid ) ;
int vpe_getgid ( int index )
{
struct vpe * v ;
if ( ( v = get_vpe ( index ) ) = = NULL )
return - 1 ;
return v - > gid ;
}
EXPORT_SYMBOL ( vpe_getgid ) ;
int vpe_notify ( int index , struct vpe_notifications * notify )
{
struct vpe * v ;
if ( ( v = get_vpe ( index ) ) = = NULL )
return - 1 ;
list_add ( & notify - > list , & v - > notify ) ;
return 0 ;
}
EXPORT_SYMBOL ( vpe_notify ) ;
char * vpe_getcwd ( int index )
{
struct vpe * v ;
if ( ( v = get_vpe ( index ) ) = = NULL )
return NULL ;
return v - > cwd ;
}
EXPORT_SYMBOL ( vpe_getcwd ) ;
# ifdef CONFIG_MIPS_APSP_KSPD
static void kspd_sp_exit ( int sp_id )
{
cleanup_tc ( get_tc ( sp_id ) ) ;
}
# endif
2007-10-15 13:42:35 +02:00
static ssize_t store_kill ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t len )
2007-07-27 19:37:51 +01:00
{
struct vpe * vpe = get_vpe ( tclimit ) ;
struct vpe_notifications * not ;
list_for_each_entry ( not , & vpe - > notify , list ) {
not - > stop ( tclimit ) ;
}
release_progmem ( vpe - > load_addr ) ;
cleanup_tc ( get_tc ( tclimit ) ) ;
vpe_stop ( vpe ) ;
vpe_free ( vpe ) ;
return len ;
}
2007-10-15 13:42:35 +02:00
static ssize_t show_ntcs ( struct device * cd , struct device_attribute * attr ,
char * buf )
2007-07-27 19:33:18 +01:00
{
struct vpe * vpe = get_vpe ( tclimit ) ;
return sprintf ( buf , " %d \n " , vpe - > ntcs ) ;
}
2007-10-15 13:42:35 +02:00
static ssize_t store_ntcs ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t len )
2007-07-27 19:33:18 +01:00
{
struct vpe * vpe = get_vpe ( tclimit ) ;
unsigned long new ;
char * endp ;
new = simple_strtoul ( buf , & endp , 0 ) ;
if ( endp = = buf )
goto out_einval ;
if ( new = = 0 | | new > ( hw_tcs - tclimit ) )
goto out_einval ;
vpe - > ntcs = new ;
return len ;
out_einval :
2009-06-28 09:26:09 -07:00
return - EINVAL ;
2007-07-27 19:33:18 +01:00
}
2007-10-15 13:42:35 +02:00
static struct device_attribute vpe_class_attributes [ ] = {
2007-07-27 19:37:51 +01:00
__ATTR ( kill , S_IWUSR , NULL , store_kill ) ,
2007-07-27 19:33:18 +01:00
__ATTR ( ntcs , S_IRUGO | S_IWUSR , show_ntcs , store_ntcs ) ,
{ }
} ;
2007-10-15 13:42:35 +02:00
static void vpe_device_release ( struct device * cd )
2007-07-27 19:33:18 +01:00
{
kfree ( cd ) ;
}
struct class vpe_class = {
. name = " vpe " ,
. owner = THIS_MODULE ,
2007-10-15 13:42:35 +02:00
. dev_release = vpe_device_release ,
. dev_attrs = vpe_class_attributes ,
2007-07-27 19:33:18 +01:00
} ;
2007-10-15 13:42:35 +02:00
struct device vpe_device ;
2007-02-07 13:48:59 +00:00
2005-07-14 15:57:16 +00:00
static int __init vpe_module_init ( void )
{
2007-07-27 19:31:10 +01:00
unsigned int mtflags , vpflags ;
unsigned long flags , val ;
2005-07-14 15:57:16 +00:00
struct vpe * v = NULL ;
struct tc * t ;
2007-07-27 19:33:18 +01:00
int tc , err ;
2005-07-14 15:57:16 +00:00
if ( ! cpu_has_mipsmt ) {
printk ( " VPE loader: not a MIPS MT capable processor \n " ) ;
return - ENODEV ;
}
2007-07-27 19:31:10 +01:00
if ( vpelimit = = 0 ) {
printk ( KERN_WARNING " No VPEs reserved for AP/SP, not "
" initializing VPE loader. \n Pass maxvpes=<n> argument as "
" kernel argument \n " ) ;
return - ENODEV ;
}
if ( tclimit = = 0 ) {
printk ( KERN_WARNING " No TCs reserved for AP/SP, not "
" initializing VPE loader. \n Pass maxtcs=<n> argument as "
" kernel argument \n " ) ;
return - ENODEV ;
}
2006-01-10 00:09:16 +03:00
major = register_chrdev ( 0 , module_name , & vpe_fops ) ;
if ( major < 0 ) {
2005-07-14 15:57:16 +00:00
printk ( " VPE loader: unable to register character device \n " ) ;
2005-10-31 23:34:52 +00:00
return major ;
2005-07-14 15:57:16 +00:00
}
2007-07-27 19:33:18 +01:00
err = class_register ( & vpe_class ) ;
if ( err ) {
printk ( KERN_ERR " vpe_class registration failed \n " ) ;
2007-02-07 13:48:59 +00:00
goto out_chrdev ;
}
2007-07-27 19:33:18 +01:00
2007-10-15 13:42:35 +02:00
device_initialize ( & vpe_device ) ;
2007-07-27 19:33:18 +01:00
vpe_device . class = & vpe_class ,
vpe_device . parent = NULL ,
2009-01-06 10:44:38 -08:00
dev_set_name ( & vpe_device , " vpe1 " ) ;
2007-07-27 19:33:18 +01:00
vpe_device . devt = MKDEV ( major , minor ) ;
2007-10-15 13:42:35 +02:00
err = device_add ( & vpe_device ) ;
2007-07-27 19:33:18 +01:00
if ( err ) {
printk ( KERN_ERR " Adding vpe_device failed \n " ) ;
goto out_class ;
}
2007-02-07 13:48:59 +00:00
2007-07-27 19:31:10 +01:00
local_irq_save ( flags ) ;
mtflags = dmt ( ) ;
vpflags = dvpe ( ) ;
2005-07-14 15:57:16 +00:00
/* Put MVPE's into 'configuration state' */
2005-08-17 17:44:08 +00:00
set_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
/* dump_mtregs(); */
val = read_c0_mvpconf0 ( ) ;
2007-07-27 19:31:10 +01:00
hw_tcs = ( val & MVPCONF0_PTC ) + 1 ;
hw_vpes = ( ( val & MVPCONF0_PVPE ) > > MVPCONF0_PVPE_SHIFT ) + 1 ;
for ( tc = tclimit ; tc < hw_tcs ; tc + + ) {
/*
* Must re - enable multithreading temporarily or in case we
* reschedule send IPIs or similar we might hang .
*/
clear_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
evpe ( vpflags ) ;
emt ( mtflags ) ;
local_irq_restore ( flags ) ;
t = alloc_tc ( tc ) ;
if ( ! t ) {
err = - ENOMEM ;
goto out ;
}
local_irq_save ( flags ) ;
mtflags = dmt ( ) ;
vpflags = dvpe ( ) ;
set_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
/* VPE's */
2007-07-27 19:31:10 +01:00
if ( tc < hw_tcs ) {
settc ( tc ) ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
if ( ( v = alloc_vpe ( tc ) ) = = NULL ) {
2005-07-14 15:57:16 +00:00
printk ( KERN_WARNING " VPE: unable to allocate VPE \n " ) ;
2007-07-27 19:31:10 +01:00
goto out_reenable ;
2005-07-14 15:57:16 +00:00
}
2007-07-27 19:33:18 +01:00
v - > ntcs = hw_tcs - tclimit ;
2006-04-05 09:45:45 +01:00
/* add the tc to the list of this vpe's tc's. */
list_add ( & t - > tc , & v - > tc ) ;
2005-07-14 15:57:16 +00:00
/* deactivate all but vpe0 */
2007-07-27 19:31:10 +01:00
if ( tc > = tclimit ) {
2005-07-14 15:57:16 +00:00
unsigned long tmp = read_vpe_c0_vpeconf0 ( ) ;
tmp & = ~ VPECONF0_VPA ;
/* master VPE */
tmp | = VPECONF0_MVP ;
write_vpe_c0_vpeconf0 ( tmp ) ;
}
/* disable multi-threading with TC's */
write_vpe_c0_vpecontrol ( read_vpe_c0_vpecontrol ( ) & ~ VPECONTROL_TE ) ;
2007-07-27 19:31:10 +01:00
if ( tc > = vpelimit ) {
2006-04-05 09:45:45 +01:00
/*
* Set config to be the same as vpe0 ,
* particularly kseg0 coherency alg
*/
2005-07-14 15:57:16 +00:00
write_vpe_c0_config ( read_c0_config ( ) ) ;
}
}
/* TC's */
t - > pvpe = v ; /* set the parent vpe */
2007-07-27 19:31:10 +01:00
if ( tc > = tclimit ) {
2005-07-14 15:57:16 +00:00
unsigned long tmp ;
2007-07-27 19:31:10 +01:00
settc ( tc ) ;
2005-07-14 15:57:16 +00:00
2006-04-05 09:45:45 +01:00
/* Any TC that is bound to VPE0 gets left as is - in case
we are running SMTC on VPE0 . A TC that is bound to any
other VPE gets bound to VPE0 , ideally I ' d like to make
it homeless but it doesn ' t appear to let me bind a TC
to a non - existent VPE . Which is perfectly reasonable .
The ( un ) bound state is visible to an EJTAG probe so may
notify GDB . . .
*/
if ( ( ( tmp = read_tc_c0_tcbind ( ) ) & TCBIND_CURVPE ) ) {
/* tc is bound >vpe0 */
write_tc_c0_tcbind ( tmp & ~ TCBIND_CURVPE ) ;
t - > pvpe = get_vpe ( 0 ) ; /* set the parent vpe */
}
2005-07-14 15:57:16 +00:00
2007-11-08 13:25:51 +00:00
/* halt the TC */
write_tc_c0_tchalt ( TCHALT_H ) ;
mips_ihb ( ) ;
2005-07-14 15:57:16 +00:00
tmp = read_tc_c0_tcstatus ( ) ;
2006-04-05 09:45:45 +01:00
/* mark not activated and not dynamically allocatable */
2005-07-14 15:57:16 +00:00
tmp & = ~ ( TCSTATUS_A | TCSTATUS_DA ) ;
tmp | = TCSTATUS_IXMT ; /* interrupt exempt */
write_tc_c0_tcstatus ( tmp ) ;
}
}
2007-07-27 19:31:10 +01:00
out_reenable :
2005-07-14 15:57:16 +00:00
/* release config state */
2005-08-17 17:44:08 +00:00
clear_c0_mvpcontrol ( MVPCONTROL_VPC ) ;
2005-07-14 15:57:16 +00:00
2007-07-27 19:31:10 +01:00
evpe ( vpflags ) ;
emt ( mtflags ) ;
local_irq_restore ( flags ) ;
2006-04-05 09:45:45 +01:00
# ifdef CONFIG_MIPS_APSP_KSPD
kspd_events . kspd_sp_exit = kspd_sp_exit ;
# endif
2005-07-14 15:57:16 +00:00
return 0 ;
2007-02-07 13:48:59 +00:00
2007-07-27 19:33:18 +01:00
out_class :
class_unregister ( & vpe_class ) ;
2007-02-07 13:48:59 +00:00
out_chrdev :
unregister_chrdev ( major , module_name ) ;
2007-07-27 19:31:10 +01:00
out :
2007-02-07 13:48:59 +00:00
return err ;
2005-07-14 15:57:16 +00:00
}
static void __exit vpe_module_exit ( void )
{
struct vpe * v , * n ;
list_for_each_entry_safe ( v , n , & vpecontrol . vpe_list , list ) {
if ( v - > state ! = VPE_STATE_UNUSED ) {
release_vpe ( v ) ;
}
}
2007-10-15 13:42:35 +02:00
device_del ( & vpe_device ) ;
2005-07-14 15:57:16 +00:00
unregister_chrdev ( major , module_name ) ;
}
module_init ( vpe_module_init ) ;
module_exit ( vpe_module_exit ) ;
MODULE_DESCRIPTION ( " MIPS VPE Loader " ) ;
2006-04-05 09:45:45 +01:00
MODULE_AUTHOR ( " Elizabeth Oldham, MIPS Technologies, Inc. " ) ;
2005-07-14 15:57:16 +00:00
MODULE_LICENSE ( " GPL " ) ;