2008-05-22 04:43:50 +04:00
/**
* @ file backtrace . c
*
* @ remark Copyright 2008 Tensilica Inc .
* @ remark Read the file COPYING
*
*/
# include <linux/oprofile.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <asm/ptrace.h>
# include <asm/uaccess.h>
# include <asm/traps.h>
/* Address of common_exception_return, used to check the
* transition from kernel to user space .
*/
extern int common_exception_return ;
/* A struct that maps to the part of the frame containing the a0 and
* a1 registers .
*/
struct frame_start {
unsigned long a0 ;
unsigned long a1 ;
} ;
static void xtensa_backtrace_user ( struct pt_regs * regs , unsigned int depth )
{
unsigned long windowstart = regs - > windowstart ;
unsigned long windowbase = regs - > windowbase ;
unsigned long a0 = regs - > areg [ 0 ] ;
unsigned long a1 = regs - > areg [ 1 ] ;
unsigned long pc = MAKE_PC_FROM_RA ( a0 , regs - > pc ) ;
int index ;
/* First add the current PC to the trace. */
if ( pc ! = 0 & & pc < = TASK_SIZE )
oprofile_add_trace ( pc ) ;
else
return ;
/* Two steps:
*
* 1. Look through the register window for the
* previous PCs in the call trace .
*
* 2. Look on the stack .
*/
/* Step 1. */
/* Rotate WINDOWSTART to move the bit corresponding to
* the current window to the bit # 0.
*/
windowstart = ( windowstart < < WSBITS | windowstart ) > > windowbase ;
/* Look for bits that are set, they correspond to
* valid windows .
*/
for ( index = WSBITS - 1 ; ( index > 0 ) & & depth ; depth - - , index - - )
if ( windowstart & ( 1 < < index ) ) {
/* Read a0 and a1 from the
* corresponding position in AREGs .
*/
a0 = regs - > areg [ index * 4 ] ;
a1 = regs - > areg [ index * 4 + 1 ] ;
/* Get the PC from a0 and a1. */
pc = MAKE_PC_FROM_RA ( a0 , pc ) ;
/* Add the PC to the trace. */
if ( pc ! = 0 & & pc < = TASK_SIZE )
oprofile_add_trace ( pc ) ;
else
return ;
}
/* Step 2. */
/* We are done with the register window, we need to
* look through the stack .
*/
if ( depth > 0 ) {
/* Start from the a1 register. */
/* a1 = regs->areg[1]; */
while ( a0 ! = 0 & & depth - - ) {
struct frame_start frame_start ;
/* Get the location for a1, a0 for the
* previous frame from the current a1 .
*/
unsigned long * psp = ( unsigned long * ) a1 ;
psp - = 4 ;
/* Check if the region is OK to access. */
if ( ! access_ok ( VERIFY_READ , psp , sizeof ( frame_start ) ) )
return ;
/* Copy a1, a0 from user space stack frame. */
if ( __copy_from_user_inatomic ( & frame_start , psp ,
sizeof ( frame_start ) ) )
return ;
a0 = frame_start . a0 ;
a1 = frame_start . a1 ;
pc = MAKE_PC_FROM_RA ( a0 , pc ) ;
if ( pc ! = 0 & & pc < = TASK_SIZE )
oprofile_add_trace ( pc ) ;
else
return ;
}
}
}
static void xtensa_backtrace_kernel ( struct pt_regs * regs , unsigned int depth )
{
unsigned long pc = regs - > pc ;
unsigned long * psp ;
unsigned long sp_start , sp_end ;
unsigned long a0 = regs - > areg [ 0 ] ;
unsigned long a1 = regs - > areg [ 1 ] ;
sp_start = a1 & ~ ( THREAD_SIZE - 1 ) ;
sp_end = sp_start + THREAD_SIZE ;
/* Spill the register window to the stack first. */
spill_registers ( ) ;
/* Read the stack frames one by one and create the PC
* from the a0 and a1 registers saved there .
*/
while ( a1 > sp_start & & a1 < sp_end & & depth - - ) {
pc = MAKE_PC_FROM_RA ( a0 , pc ) ;
/* Add the PC to the trace. */
2013-02-25 19:01:33 +04:00
oprofile_add_trace ( pc ) ;
2008-05-22 04:43:50 +04:00
if ( pc = = ( unsigned long ) & common_exception_return ) {
regs = ( struct pt_regs * ) a1 ;
if ( user_mode ( regs ) ) {
pc = regs - > pc ;
if ( pc ! = 0 & & pc < = TASK_SIZE )
oprofile_add_trace ( pc ) ;
else
return ;
return xtensa_backtrace_user ( regs , depth ) ;
}
a0 = regs - > areg [ 0 ] ;
a1 = regs - > areg [ 1 ] ;
continue ;
}
psp = ( unsigned long * ) a1 ;
a0 = * ( psp - 4 ) ;
a1 = * ( psp - 3 ) ;
if ( a1 < = ( unsigned long ) psp )
return ;
}
return ;
}
void xtensa_backtrace ( struct pt_regs * const regs , unsigned int depth )
{
if ( user_mode ( regs ) )
xtensa_backtrace_user ( regs , depth ) ;
else
xtensa_backtrace_kernel ( regs , depth ) ;
}