2005-04-16 15:20:36 -07: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 .
*
* Copyright ( C ) 1994 - 1999 , 2000 by Ralf Baechle and others .
* Copyright ( C ) 1999 , 2000 Silicon Graphics , Inc .
* Copyright ( C ) 2004 Thiemo Seufer
*/
# include <linux/config.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/stddef.h>
# include <linux/unistd.h>
# include <linux/ptrace.h>
# include <linux/slab.h>
# include <linux/mman.h>
# include <linux/personality.h>
# include <linux/sys.h>
# include <linux/user.h>
# include <linux/a.out.h>
# include <linux/init.h>
# include <linux/completion.h>
# include <asm/bootinfo.h>
# include <asm/cpu.h>
# include <asm/fpu.h>
# include <asm/pgtable.h>
# include <asm/system.h>
# include <asm/mipsregs.h>
# include <asm/processor.h>
# include <asm/uaccess.h>
# include <asm/io.h>
# include <asm/elf.h>
# include <asm/isadep.h>
# include <asm/inst.h>
/*
* The idle thread . There ' s no useful work to be done , so just try to conserve
* power and have a low exit latency ( ie sit in a loop waiting for somebody to
* say that they ' d like to reschedule )
*/
ATTRIB_NORET void cpu_idle ( void )
{
/* endless idle loop with no priority at all */
while ( 1 ) {
while ( ! need_resched ( ) )
if ( cpu_wait )
( * cpu_wait ) ( ) ;
schedule ( ) ;
}
}
asmlinkage void ret_from_fork ( void ) ;
void start_thread ( struct pt_regs * regs , unsigned long pc , unsigned long sp )
{
unsigned long status ;
/* New thread loses kernel privileges. */
status = regs - > cp0_status & ~ ( ST0_CU0 | ST0_CU1 | KU_MASK ) ;
2005-09-03 15:56:16 -07:00
# ifdef CONFIG_64BIT
2005-04-16 15:20:36 -07:00
status & = ~ ST0_FR ;
status | = ( current - > thread . mflags & MF_32BIT_REGS ) ? 0 : ST0_FR ;
# endif
status | = KU_USER ;
regs - > cp0_status = status ;
clear_used_math ( ) ;
lose_fpu ( ) ;
regs - > cp0_epc = pc ;
regs - > regs [ 29 ] = sp ;
current_thread_info ( ) - > addr_limit = USER_DS ;
}
void exit_thread ( void )
{
}
void flush_thread ( void )
{
}
int copy_thread ( int nr , unsigned long clone_flags , unsigned long usp ,
unsigned long unused , struct task_struct * p , struct pt_regs * regs )
{
struct thread_info * ti = p - > thread_info ;
struct pt_regs * childregs ;
long childksp ;
2005-04-13 17:43:59 +00:00
p - > set_child_tid = p - > clear_child_tid = NULL ;
2005-04-16 15:20:36 -07:00
childksp = ( unsigned long ) ti + THREAD_SIZE - 32 ;
preempt_disable ( ) ;
if ( is_fpu_owner ( ) ) {
save_fp ( p ) ;
}
preempt_enable ( ) ;
/* set up new TSS. */
childregs = ( struct pt_regs * ) childksp - 1 ;
* childregs = * regs ;
childregs - > regs [ 7 ] = 0 ; /* Clear error flag */
# if defined(CONFIG_BINFMT_IRIX)
if ( current - > personality ! = PER_LINUX ) {
/* Under IRIX things are a little different. */
childregs - > regs [ 3 ] = 1 ;
regs - > regs [ 3 ] = 0 ;
}
# endif
childregs - > regs [ 2 ] = 0 ; /* Child gets zero as return value */
regs - > regs [ 2 ] = p - > pid ;
if ( childregs - > cp0_status & ST0_CU0 ) {
childregs - > regs [ 28 ] = ( unsigned long ) ti ;
childregs - > regs [ 29 ] = childksp ;
ti - > addr_limit = KERNEL_DS ;
} else {
childregs - > regs [ 29 ] = usp ;
ti - > addr_limit = USER_DS ;
}
p - > thread . reg29 = ( unsigned long ) childregs ;
p - > thread . reg31 = ( unsigned long ) ret_from_fork ;
/*
* New tasks lose permission to use the fpu . This accelerates context
* switching for most programs since they don ' t use the fpu .
*/
p - > thread . cp0_status = read_c0_status ( ) & ~ ( ST0_CU2 | ST0_CU1 ) ;
childregs - > cp0_status & = ~ ( ST0_CU2 | ST0_CU1 ) ;
clear_tsk_thread_flag ( p , TIF_USEDFPU ) ;
2005-04-13 17:43:59 +00:00
if ( clone_flags & CLONE_SETTLS )
ti - > tp_value = regs - > regs [ 7 ] ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
/* Fill in the fpu structure for a core dump.. */
int dump_fpu ( struct pt_regs * regs , elf_fpregset_t * r )
{
memcpy ( r , & current - > thread . fpu , sizeof ( current - > thread . fpu ) ) ;
return 1 ;
}
void dump_regs ( elf_greg_t * gp , struct pt_regs * regs )
{
int i ;
for ( i = 0 ; i < EF_R0 ; i + + )
gp [ i ] = 0 ;
gp [ EF_R0 ] = 0 ;
for ( i = 1 ; i < = 31 ; i + + )
gp [ EF_R0 + i ] = regs - > regs [ i ] ;
gp [ EF_R26 ] = 0 ;
gp [ EF_R27 ] = 0 ;
gp [ EF_LO ] = regs - > lo ;
gp [ EF_HI ] = regs - > hi ;
gp [ EF_CP0_EPC ] = regs - > cp0_epc ;
gp [ EF_CP0_BADVADDR ] = regs - > cp0_badvaddr ;
gp [ EF_CP0_STATUS ] = regs - > cp0_status ;
gp [ EF_CP0_CAUSE ] = regs - > cp0_cause ;
# ifdef EF_UNUSED0
gp [ EF_UNUSED0 ] = 0 ;
# endif
}
2005-03-14 10:16:59 +00:00
int dump_task_regs ( struct task_struct * tsk , elf_gregset_t * regs )
{
struct thread_info * ti = tsk - > thread_info ;
long ksp = ( unsigned long ) ti + THREAD_SIZE - 32 ;
dump_regs ( & ( * regs ) [ 0 ] , ( struct pt_regs * ) ksp - 1 ) ;
return 1 ;
}
2005-04-16 15:20:36 -07:00
int dump_task_fpu ( struct task_struct * t , elf_fpregset_t * fpr )
{
memcpy ( fpr , & t - > thread . fpu , sizeof ( current - > thread . fpu ) ) ;
return 1 ;
}
/*
* Create a kernel thread
*/
ATTRIB_NORET void kernel_thread_helper ( void * arg , int ( * fn ) ( void * ) )
{
do_exit ( fn ( arg ) ) ;
}
long kernel_thread ( int ( * fn ) ( void * ) , void * arg , unsigned long flags )
{
struct pt_regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . regs [ 4 ] = ( unsigned long ) arg ;
regs . regs [ 5 ] = ( unsigned long ) fn ;
regs . cp0_epc = ( unsigned long ) kernel_thread_helper ;
regs . cp0_status = read_c0_status ( ) ;
# if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
regs . cp0_status & = ~ ( ST0_KUP | ST0_IEC ) ;
regs . cp0_status | = ST0_IEP ;
# else
regs . cp0_status | = ST0_EXL ;
# endif
/* Ok, create the new process.. */
return do_fork ( flags | CLONE_VM | CLONE_UNTRACED , 0 , & regs , 0 , NULL , NULL ) ;
}
2005-02-21 10:55:16 +00:00
static struct mips_frame_info {
void * func ;
int omit_fp ; /* compiled without fno-omit-frame-pointer */
2005-04-16 15:20:36 -07:00
int frame_offset ;
int pc_offset ;
2005-02-21 10:55:16 +00:00
} schedule_frame , mfinfo [ ] = {
{ schedule , 0 } , /* must be first */
/* arch/mips/kernel/semaphore.c */
{ __down , 1 } ,
{ __down_interruptible , 1 } ,
/* kernel/sched.c */
# ifdef CONFIG_PREEMPT
{ preempt_schedule , 0 } ,
# endif
{ wait_for_completion , 0 } ,
{ interruptible_sleep_on , 0 } ,
{ interruptible_sleep_on_timeout , 0 } ,
{ sleep_on , 0 } ,
{ sleep_on_timeout , 0 } ,
{ yield , 0 } ,
{ io_schedule , 0 } ,
{ io_schedule_timeout , 0 } ,
# if defined(CONFIG_SMP) && defined(CONFIG_PREEMPT)
{ __preempt_spin_lock , 0 } ,
{ __preempt_write_lock , 0 } ,
# endif
/* kernel/timer.c */
{ schedule_timeout , 1 } ,
/* { nanosleep_restart, 1 }, */
/* lib/rwsem-spinlock.c */
{ __down_read , 1 } ,
{ __down_write , 1 } ,
2005-04-16 15:20:36 -07:00
} ;
2005-02-21 10:55:16 +00:00
2005-04-16 15:20:36 -07:00
static int mips_frame_info_initialized ;
2005-02-21 10:55:16 +00:00
static int __init get_frame_info ( struct mips_frame_info * info )
2005-04-16 15:20:36 -07:00
{
int i ;
2005-02-21 10:55:16 +00:00
void * func = info - > func ;
2005-04-16 15:20:36 -07:00
union mips_instruction * ip = ( union mips_instruction * ) func ;
info - > pc_offset = - 1 ;
2005-02-21 10:55:16 +00:00
info - > frame_offset = info - > omit_fp ? 0 : - 1 ;
2005-04-16 15:20:36 -07:00
for ( i = 0 ; i < 128 ; i + + , ip + + ) {
/* if jal, jalr, jr, stop. */
if ( ip - > j_format . opcode = = jal_op | |
( ip - > r_format . opcode = = spec_op & &
( ip - > r_format . func = = jalr_op | |
ip - > r_format . func = = jr_op ) ) )
break ;
if (
2005-09-03 15:56:16 -07:00
# ifdef CONFIG_32BIT
2005-04-16 15:20:36 -07:00
ip - > i_format . opcode = = sw_op & &
# endif
2005-09-03 15:56:16 -07:00
# ifdef CONFIG_64BIT
2005-04-16 15:20:36 -07:00
ip - > i_format . opcode = = sd_op & &
# endif
ip - > i_format . rs = = 29 )
{
/* sw / sd $ra, offset($sp) */
if ( ip - > i_format . rt = = 31 ) {
if ( info - > pc_offset ! = - 1 )
2005-02-21 10:55:16 +00:00
continue ;
2005-04-16 15:20:36 -07:00
info - > pc_offset =
ip - > i_format . simmediate / sizeof ( long ) ;
}
/* sw / sd $s8, offset($sp) */
if ( ip - > i_format . rt = = 30 ) {
2005-02-21 10:55:16 +00:00
//#if 0 /* gcc 3.4 does aggressive optimization... */
2005-04-16 15:20:36 -07:00
if ( info - > frame_offset ! = - 1 )
2005-02-21 10:55:16 +00:00
continue ;
//#endif
2005-04-16 15:20:36 -07:00
info - > frame_offset =
ip - > i_format . simmediate / sizeof ( long ) ;
}
}
}
if ( info - > pc_offset = = - 1 | | info - > frame_offset = = - 1 ) {
printk ( " Can't analyze prologue code at %p \n " , func ) ;
info - > pc_offset = - 1 ;
info - > frame_offset = - 1 ;
return - 1 ;
}
return 0 ;
}
static int __init frame_info_init ( void )
{
2005-02-21 10:55:16 +00:00
int i , found ;
for ( i = 0 ; i < ARRAY_SIZE ( mfinfo ) ; i + + )
if ( get_frame_info ( & mfinfo [ i ] ) )
return - 1 ;
schedule_frame = mfinfo [ 0 ] ;
/* bubble sort */
do {
struct mips_frame_info tmp ;
found = 0 ;
for ( i = 1 ; i < ARRAY_SIZE ( mfinfo ) ; i + + ) {
if ( mfinfo [ i - 1 ] . func > mfinfo [ i ] . func ) {
tmp = mfinfo [ i ] ;
mfinfo [ i ] = mfinfo [ i - 1 ] ;
mfinfo [ i - 1 ] = tmp ;
found = 1 ;
}
}
} while ( found ) ;
mips_frame_info_initialized = 1 ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
arch_initcall ( frame_info_init ) ;
/*
* Return saved PC of a blocked thread .
*/
unsigned long thread_saved_pc ( struct task_struct * tsk )
{
struct thread_struct * t = & tsk - > thread ;
/* New born processes are a special case */
if ( t - > reg31 = = ( unsigned long ) ret_from_fork )
return t - > reg31 ;
if ( schedule_frame . pc_offset < 0 )
return 0 ;
return ( ( unsigned long * ) t - > reg29 ) [ schedule_frame . pc_offset ] ;
}
/* get_wchan - a maintenance nightmare^W^Wpain in the ass ... */
unsigned long get_wchan ( struct task_struct * p )
{
2005-02-21 10:55:16 +00:00
unsigned long stack_page ;
2005-04-16 15:20:36 -07:00
unsigned long frame , pc ;
if ( ! p | | p = = current | | p - > state = = TASK_RUNNING )
return 0 ;
2005-02-21 10:55:16 +00:00
stack_page = ( unsigned long ) p - > thread_info ;
if ( ! stack_page | | ! mips_frame_info_initialized )
2005-04-16 15:20:36 -07:00
return 0 ;
2005-02-21 10:55:16 +00:00
2005-04-16 15:20:36 -07:00
pc = thread_saved_pc ( p ) ;
if ( ! in_sched_functions ( pc ) )
2005-02-21 10:55:16 +00:00
return pc ;
2005-04-16 15:20:36 -07:00
frame = ( ( unsigned long * ) p - > thread . reg30 ) [ schedule_frame . frame_offset ] ;
2005-02-21 10:55:16 +00:00
do {
int i ;
2005-04-16 15:20:36 -07:00
2005-02-21 10:55:16 +00:00
if ( frame < stack_page | | frame > stack_page + THREAD_SIZE - 32 )
return 0 ;
2005-04-16 15:20:36 -07:00
2005-02-21 10:55:16 +00:00
for ( i = ARRAY_SIZE ( mfinfo ) - 1 ; i > = 0 ; i - - ) {
if ( pc > = ( unsigned long ) mfinfo [ i ] . func )
break ;
}
if ( i < 0 )
break ;
2005-04-16 15:20:36 -07:00
2005-02-21 10:55:16 +00:00
if ( mfinfo [ i ] . omit_fp )
break ;
pc = ( ( unsigned long * ) frame ) [ mfinfo [ i ] . pc_offset ] ;
frame = ( ( unsigned long * ) frame ) [ mfinfo [ i ] . frame_offset ] ;
} while ( in_sched_functions ( pc ) ) ;
2005-04-16 15:20:36 -07:00
return pc ;
}
EXPORT_SYMBOL ( get_wchan ) ;