2005-06-23 22:01:16 -07:00
/*
* arch / xtensa / kernel / process . c
*
* Xtensa Processor version .
*
* 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 .
*
* Copyright ( C ) 2001 - 2005 Tensilica Inc .
*
* Joe Taylor < joe @ tensilica . com , joetylr @ yahoo . com >
* Chris Zankel < chris @ zankel . net >
* Marc Gauthier < marc @ tensilica . com , marc @ alumni . uwaterloo . ca >
* Kevin Chea
*/
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/stddef.h>
# include <linux/unistd.h>
# include <linux/ptrace.h>
# include <linux/slab.h>
# include <linux/elf.h>
# include <linux/init.h>
# include <linux/prctl.h>
# include <linux/init_task.h>
# include <linux/module.h>
# include <linux/mqueue.h>
# include <asm/pgtable.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <asm/io.h>
# include <asm/processor.h>
# include <asm/platform.h>
# include <asm/mmu.h>
# include <asm/irq.h>
# include <asm/atomic.h>
2005-09-09 20:57:26 +02:00
# include <asm/asm-offsets.h>
2006-12-10 02:18:48 -08:00
# include <asm/regs.h>
2005-06-23 22:01:16 -07:00
extern void ret_from_fork ( void ) ;
static struct fs_struct init_fs = INIT_FS ;
static struct files_struct init_files = INIT_FILES ;
static struct signal_struct init_signals = INIT_SIGNALS ( init_signals ) ;
static struct sighand_struct init_sighand = INIT_SIGHAND ( init_sighand ) ;
struct mm_struct init_mm = INIT_MM ( init_mm ) ;
EXPORT_SYMBOL ( init_mm ) ;
union thread_union init_thread_union
__attribute__ ( ( __section__ ( " .data.init_task " ) ) ) =
{ INIT_THREAD_INFO ( init_task ) } ;
struct task_struct init_task = INIT_TASK ( init_task ) ;
EXPORT_SYMBOL ( init_task ) ;
struct task_struct * current_set [ NR_CPUS ] = { & init_task , } ;
2006-03-06 15:42:47 -08:00
void ( * pm_power_off ) ( void ) = NULL ;
EXPORT_SYMBOL ( pm_power_off ) ;
2005-06-23 22:01:16 -07:00
/*
* Powermanagement idle function , if any is provided by the platform .
*/
void cpu_idle ( void )
{
local_irq_enable ( ) ;
/* endless idle loop with no priority at all */
while ( 1 ) {
while ( ! need_resched ( ) )
platform_idle ( ) ;
2005-11-08 21:39:01 -08:00
preempt_enable_no_resched ( ) ;
2005-06-23 22:01:16 -07:00
schedule ( ) ;
2005-11-08 21:39:01 -08:00
preempt_disable ( ) ;
2005-06-23 22:01:16 -07:00
}
}
/*
* Free current thread data structures etc . .
*/
void exit_thread ( void )
{
}
void flush_thread ( void )
{
}
/*
* Copy thread .
*
* The stack layout for the new thread looks like this :
*
* + - - - - - - - - - - - - - - - - - - - - - - - - + < - sp in childregs ( = tos )
* | childregs |
* + - - - - - - - - - - - - - - - - - - - - - - - - + < - thread . sp = sp in dummy - frame
* | dummy - frame | ( saved in dummy - frame spill - area )
* + - - - - - - - - - - - - - - - - - - - - - - - - +
*
* We create a dummy frame to return to ret_from_fork :
* a0 points to ret_from_fork ( simulating a call4 )
* sp points to itself ( thread . sp )
* a2 , a3 are unused .
*
* Note : This is a pristine frame , so we don ' t need any spill region on top of
* childregs .
*/
int copy_thread ( int nr , unsigned long clone_flags , unsigned long usp ,
unsigned long unused ,
struct task_struct * p , struct pt_regs * regs )
{
struct pt_regs * childregs ;
unsigned long tos ;
int user_mode = user_mode ( regs ) ;
/* Set up new TSS. */
2006-01-12 01:05:50 -08:00
tos = ( unsigned long ) task_stack_page ( p ) + THREAD_SIZE ;
2005-06-23 22:01:16 -07:00
if ( user_mode )
childregs = ( struct pt_regs * ) ( tos - PT_USER_SIZE ) ;
else
childregs = ( struct pt_regs * ) tos - 1 ;
* childregs = * regs ;
/* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */
* ( ( int * ) childregs - 3 ) = ( unsigned long ) childregs ;
* ( ( int * ) childregs - 4 ) = 0 ;
childregs - > areg [ 1 ] = tos ;
childregs - > areg [ 2 ] = 0 ;
p - > set_child_tid = p - > clear_child_tid = NULL ;
p - > thread . ra = MAKE_RA_FOR_CALL ( ( unsigned long ) ret_from_fork , 0x1 ) ;
p - > thread . sp = ( unsigned long ) childregs ;
if ( user_mode ( regs ) ) {
int len = childregs - > wmask & ~ 0xf ;
childregs - > areg [ 1 ] = usp ;
memcpy ( & childregs - > areg [ XCHAL_NUM_AREGS - len / 4 ] ,
& regs - > areg [ XCHAL_NUM_AREGS - len / 4 ] , len ) ;
if ( clone_flags & CLONE_SETTLS )
childregs - > areg [ 2 ] = childregs - > areg [ 6 ] ;
} else {
/* In kernel space, we start a new thread with a new stack. */
childregs - > wmask = 1 ;
}
return 0 ;
}
/*
* These bracket the sleeping functions . .
*/
unsigned long get_wchan ( struct task_struct * p )
{
unsigned long sp , pc ;
2006-01-12 01:05:50 -08:00
unsigned long stack_page = ( unsigned long ) task_stack_page ( p ) ;
2005-06-23 22:01:16 -07:00
int count = 0 ;
if ( ! p | | p = = current | | p - > state = = TASK_RUNNING )
return 0 ;
sp = p - > thread . sp ;
pc = MAKE_PC_FROM_RA ( p - > thread . ra , p - > thread . sp ) ;
do {
if ( sp < stack_page + sizeof ( struct task_struct ) | |
sp > = ( stack_page + THREAD_SIZE ) | |
pc = = 0 )
return 0 ;
if ( ! in_sched_functions ( pc ) )
return pc ;
/* Stack layout: sp-4: ra, sp-3: sp' */
pc = MAKE_PC_FROM_RA ( * ( unsigned long * ) sp - 4 , sp ) ;
sp = * ( unsigned long * ) sp - 3 ;
} while ( count + + < 16 ) ;
return 0 ;
}
/*
* do_copy_regs ( ) gathers information from ' struct pt_regs ' and
* ' current - > thread . areg [ ] ' to fill in the xtensa_gregset_t
* structure .
*
* xtensa_gregset_t and ' struct pt_regs ' are vastly different formats
* of processor registers . Besides different ordering ,
* xtensa_gregset_t contains non - live register information that
* ' struct pt_regs ' does not . Exception handling ( primarily ) uses
* ' struct pt_regs ' . Core files and ptrace use xtensa_gregset_t .
*
*/
void do_copy_regs ( xtensa_gregset_t * elfregs , struct pt_regs * regs ,
struct task_struct * tsk )
{
int i , n , wb_offset ;
elfregs - > xchal_config_id0 = XCHAL_HW_CONFIGID0 ;
elfregs - > xchal_config_id1 = XCHAL_HW_CONFIGID1 ;
__asm__ __volatile__ ( " rsr %0, 176 \n " : " =a " ( i ) ) ;
elfregs - > cpux = i ;
__asm__ __volatile__ ( " rsr %0, 208 \n " : " =a " ( i ) ) ;
elfregs - > cpuy = i ;
/* Note: PS.EXCM is not set while user task is running; its
* being set in regs - > ps is for exception handling convenience .
*/
elfregs - > pc = regs - > pc ;
2006-12-10 02:18:48 -08:00
elfregs - > ps = ( regs - > ps & ~ ( 1 < < PS_EXCM_BIT ) ) ;
2005-06-23 22:01:16 -07:00
elfregs - > exccause = regs - > exccause ;
elfregs - > excvaddr = regs - > excvaddr ;
elfregs - > windowbase = regs - > windowbase ;
elfregs - > windowstart = regs - > windowstart ;
elfregs - > lbeg = regs - > lbeg ;
elfregs - > lend = regs - > lend ;
elfregs - > lcount = regs - > lcount ;
elfregs - > sar = regs - > sar ;
elfregs - > syscall = regs - > syscall ;
/* Copy register file.
* The layout looks like this :
*
* | a0 . . . a15 | Z . . . Z | arX . . . arY |
* current window unused saved frames
*/
memset ( elfregs - > ar , 0 , sizeof ( elfregs - > ar ) ) ;
wb_offset = regs - > windowbase * 4 ;
n = ( regs - > wmask & 1 ) ? 4 : ( regs - > wmask & 2 ) ? 8 : ( regs - > wmask & 4 ) ? 12 : 16 ;
for ( i = 0 ; i < n ; i + + )
elfregs - > ar [ ( wb_offset + i ) % XCHAL_NUM_AREGS ] = regs - > areg [ i ] ;
n = ( regs - > wmask > > 4 ) * 4 ;
for ( i = XCHAL_NUM_AREGS - n ; n > 0 ; i + + , n - - )
elfregs - > ar [ ( wb_offset + i ) % XCHAL_NUM_AREGS ] = regs - > areg [ i ] ;
}
void xtensa_elf_core_copy_regs ( xtensa_gregset_t * elfregs , struct pt_regs * regs )
{
do_copy_regs ( ( xtensa_gregset_t * ) elfregs , regs , current ) ;
}
/* The inverse of do_copy_regs(). No error or sanity checking. */
void do_restore_regs ( xtensa_gregset_t * elfregs , struct pt_regs * regs ,
struct task_struct * tsk )
{
int i , n , wb_offset ;
/* Note: PS.EXCM is not set while user task is running; it
* needs to be set in regs - > ps is for exception handling convenience .
*/
regs - > pc = elfregs - > pc ;
2006-12-10 02:18:48 -08:00
regs - > ps = ( elfregs - > ps | ( 1 < < PS_EXCM_BIT ) ) ;
2005-06-23 22:01:16 -07:00
regs - > exccause = elfregs - > exccause ;
regs - > excvaddr = elfregs - > excvaddr ;
regs - > windowbase = elfregs - > windowbase ;
regs - > windowstart = elfregs - > windowstart ;
regs - > lbeg = elfregs - > lbeg ;
regs - > lend = elfregs - > lend ;
regs - > lcount = elfregs - > lcount ;
regs - > sar = elfregs - > sar ;
regs - > syscall = elfregs - > syscall ;
/* Clear everything. */
memset ( regs - > areg , 0 , sizeof ( regs - > areg ) ) ;
/* Copy regs from live window frame. */
wb_offset = regs - > windowbase * 4 ;
n = ( regs - > wmask & 1 ) ? 4 : ( regs - > wmask & 2 ) ? 8 : ( regs - > wmask & 4 ) ? 12 : 16 ;
for ( i = 0 ; i < n ; i + + )
regs - > areg [ ( wb_offset + i ) % XCHAL_NUM_AREGS ] = elfregs - > ar [ i ] ;
n = ( regs - > wmask > > 4 ) * 4 ;
for ( i = XCHAL_NUM_AREGS - n ; n > 0 ; i + + , n - - )
regs - > areg [ ( wb_offset + i ) % XCHAL_NUM_AREGS ] = elfregs - > ar [ i ] ;
}
/*
* do_save_fpregs ( ) gathers information from ' struct pt_regs ' and
* ' current - > thread ' to fill in the elf_fpregset_t structure .
*
* Core files and ptrace use elf_fpregset_t .
*/
void do_save_fpregs ( elf_fpregset_t * fpregs , struct pt_regs * regs ,
struct task_struct * tsk )
{
# if XCHAL_HAVE_CP
extern unsigned char _xtensa_reginfo_tables [ ] ;
extern unsigned _xtensa_reginfo_table_size ;
int i ;
unsigned long flags ;
/* Before dumping coprocessor state from memory,
* ensure any live coprocessor contents for this
* task are first saved to memory :
*/
local_irq_save ( flags ) ;
for ( i = 0 ; i < XCHAL_CP_MAX ; i + + ) {
if ( tsk = = coprocessor_info [ i ] . owner ) {
enable_coprocessor ( i ) ;
save_coprocessor_registers (
tsk - > thread . cp_save + coprocessor_info [ i ] . offset , i ) ;
disable_coprocessor ( i ) ;
}
}
local_irq_restore ( flags ) ;
/* Now dump coprocessor & extra state: */
memcpy ( ( unsigned char * ) fpregs ,
_xtensa_reginfo_tables , _xtensa_reginfo_table_size ) ;
memcpy ( ( unsigned char * ) fpregs + _xtensa_reginfo_table_size ,
tsk - > thread . cp_save , XTENSA_CP_EXTRA_SIZE ) ;
# endif
}
/*
* The inverse of do_save_fpregs ( ) .
* Copies coprocessor and extra state from fpregs into regs and tsk - > thread .
* Returns 0 on success , non - zero if layout doesn ' t match .
*/
int do_restore_fpregs ( elf_fpregset_t * fpregs , struct pt_regs * regs ,
struct task_struct * tsk )
{
# if XCHAL_HAVE_CP
extern unsigned char _xtensa_reginfo_tables [ ] ;
extern unsigned _xtensa_reginfo_table_size ;
int i ;
unsigned long flags ;
/* Make sure save area layouts match.
* FIXME : in the future we could allow restoring from
* a different layout of the same registers , by comparing
* fpregs ' table with _xtensa_reginfo_tables and matching
* entries and copying registers one at a time .
* Not too sure yet whether that ' s very useful .
*/
if ( memcmp ( ( unsigned char * ) fpregs ,
_xtensa_reginfo_tables , _xtensa_reginfo_table_size ) ) {
return - 1 ;
}
/* Before restoring coprocessor state from memory,
* ensure any live coprocessor contents for this
* task are first invalidated .
*/
local_irq_save ( flags ) ;
for ( i = 0 ; i < XCHAL_CP_MAX ; i + + ) {
if ( tsk = = coprocessor_info [ i ] . owner ) {
enable_coprocessor ( i ) ;
save_coprocessor_registers (
tsk - > thread . cp_save + coprocessor_info [ i ] . offset , i ) ;
coprocessor_info [ i ] . owner = 0 ;
disable_coprocessor ( i ) ;
}
}
local_irq_restore ( flags ) ;
/* Now restore coprocessor & extra state: */
memcpy ( tsk - > thread . cp_save ,
( unsigned char * ) fpregs + _xtensa_reginfo_table_size ,
XTENSA_CP_EXTRA_SIZE ) ;
# endif
return 0 ;
}
/*
* Fill in the CP structure for a core dump for a particular task .
*/
int
dump_task_fpu ( struct pt_regs * regs , struct task_struct * task , elf_fpregset_t * r )
{
return 0 ; /* no coprocessors active on this processor */
}
/*
* Fill in the CP structure for a core dump .
* This includes any FPU coprocessor .
* Here , we dump all coprocessors , and other ( " extra " ) custom state .
*
* This function is called by elf_core_dump ( ) in fs / binfmt_elf . c
* ( in which case ' regs ' comes from calls to do_coredump , see signals . c ) .
*/
int dump_fpu ( struct pt_regs * regs , elf_fpregset_t * r )
{
return dump_task_fpu ( regs , current , r ) ;
}
2006-12-10 02:18:52 -08:00
asmlinkage
long xtensa_clone ( unsigned long clone_flags , unsigned long newsp ,
void __user * parent_tid , void * child_tls ,
void __user * child_tid , long a5 ,
struct pt_regs * regs )
{
if ( ! newsp )
newsp = regs - > areg [ 1 ] ;
return do_fork ( clone_flags , newsp , regs , 0 , parent_tid , child_tid ) ;
}
/*
* * xtensa_execve ( ) executes a new program .
* */
asmlinkage
long xtensa_execve ( char __user * name , char __user * __user * argv ,
char __user * __user * envp ,
long a3 , long a4 , long a5 ,
struct pt_regs * regs )
{
long error ;
char * filename ;
filename = getname ( name ) ;
error = PTR_ERR ( filename ) ;
if ( IS_ERR ( filename ) )
goto out ;
// FIXME: release coprocessor??
error = do_execve ( filename , argv , envp , regs ) ;
if ( error = = 0 ) {
task_lock ( current ) ;
current - > ptrace & = ~ PT_DTRACE ;
task_unlock ( current ) ;
}
putname ( filename ) ;
out :
return error ;
}