| /** | |
| * @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. */ | |
| oprofile_add_trace(pc); | |
| 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); | |
| } |