/*1* Backtrace support for Microblaze2*3* Copyright (C) 2010 Digital Design Corporation4*5* Based on arch/sh/kernel/cpu/sh5/unwind.c code which is:6* Copyright (C) 2004 Paul Mundt7* Copyright (C) 2004 Richard Curnow8*9* This file is subject to the terms and conditions of the GNU General Public10* License. See the file "COPYING" in the main directory of this archive11* for more details.12*/1314/* #define DEBUG 1 */15#include <linux/export.h>16#include <linux/kallsyms.h>17#include <linux/kernel.h>18#include <linux/sched.h>19#include <linux/sched/task_stack.h>20#include <linux/stacktrace.h>21#include <linux/types.h>22#include <linux/errno.h>23#include <linux/io.h>24#include <asm/sections.h>25#include <asm/exceptions.h>26#include <asm/unwind.h>27#include <asm/switch_to.h>2829struct stack_trace;3031/*32* On Microblaze, finding the previous stack frame is a little tricky.33* At this writing (3/2010), Microblaze does not support CONFIG_FRAME_POINTERS,34* and even if it did, gcc (4.1.2) does not store the frame pointer at35* a consistent offset within each frame. To determine frame size, it is36* necessary to search for the assembly instruction that creates or reclaims37* the frame and extract the size from it.38*39* Microblaze stores the stack pointer in r1, and creates a frame via40*41* addik r1, r1, -FRAME_SIZE42*43* The frame is reclaimed via44*45* addik r1, r1, FRAME_SIZE46*47* Frame creation occurs at or near the top of a function.48* Depending on the compiler, reclaim may occur at the end, or before49* a mid-function return.50*51* A stack frame is usually not created in a leaf function.52*53*/5455/**56* get_frame_size - Extract the stack adjustment from an57* "addik r1, r1, adjust" instruction58* @instr : Microblaze instruction59*60* Return - Number of stack bytes the instruction reserves or reclaims61*/62static inline long get_frame_size(unsigned long instr)63{64return abs((s16)(instr & 0xFFFF));65}6667/**68* find_frame_creation - Search backward to find the instruction that creates69* the stack frame (hopefully, for the same function the70* initial PC is in).71* @pc : Program counter at which to begin the search72*73* Return - PC at which stack frame creation occurs74* NULL if this cannot be found, i.e. a leaf function75*/76static unsigned long *find_frame_creation(unsigned long *pc)77{78int i;7980/* NOTE: Distance to search is arbitrary81* 250 works well for most things,82* 750 picks up things like tcp_recvmsg(),83* 1000 needed for fat_fill_super()84*/85for (i = 0; i < 1000; i++, pc--) {86unsigned long instr;87s16 frame_size;8889if (!kernel_text_address((unsigned long) pc))90return NULL;9192instr = *pc;9394/* addik r1, r1, foo ? */95if ((instr & 0xFFFF0000) != 0x30210000)96continue; /* No */9798frame_size = get_frame_size(instr);99if ((frame_size < 8) || (frame_size & 3)) {100pr_debug(" Invalid frame size %d at 0x%p\n",101frame_size, pc);102return NULL;103}104105pr_debug(" Found frame creation at 0x%p, size %d\n", pc,106frame_size);107return pc;108}109110return NULL;111}112113/**114* lookup_prev_stack_frame - Find the stack frame of the previous function.115* @fp : Frame (stack) pointer for current function116* @pc : Program counter within current function117* @leaf_return : r15 value within current function. If the current function118* is a leaf, this is the caller's return address.119* @pprev_fp : On exit, set to frame (stack) pointer for previous function120* @pprev_pc : On exit, set to current function caller's return address121*122* Return - 0 on success, -EINVAL if the previous frame cannot be found123*/124static int lookup_prev_stack_frame(unsigned long fp, unsigned long pc,125unsigned long leaf_return,126unsigned long *pprev_fp,127unsigned long *pprev_pc)128{129unsigned long *prologue = NULL;130131/* _switch_to is a special leaf function */132if (pc != (unsigned long) &_switch_to)133prologue = find_frame_creation((unsigned long *)pc);134135if (prologue) {136long frame_size = get_frame_size(*prologue);137138*pprev_fp = fp + frame_size;139*pprev_pc = *(unsigned long *)fp;140} else {141if (!leaf_return)142return -EINVAL;143*pprev_pc = leaf_return;144*pprev_fp = fp;145}146147/* NOTE: don't check kernel_text_address here, to allow display148* of userland return address149*/150return (!*pprev_pc || (*pprev_pc & 3)) ? -EINVAL : 0;151}152153static void microblaze_unwind_inner(struct task_struct *task,154unsigned long pc, unsigned long fp,155unsigned long leaf_return,156struct stack_trace *trace,157const char *loglvl);158159/**160* unwind_trap - Unwind through a system trap, that stored previous state161* on the stack.162*/163static inline void unwind_trap(struct task_struct *task, unsigned long pc,164unsigned long fp, struct stack_trace *trace,165const char *loglvl)166{167/* To be implemented */168}169170/**171* microblaze_unwind_inner - Unwind the stack from the specified point172* @task : Task whose stack we are to unwind (may be NULL)173* @pc : Program counter from which we start unwinding174* @fp : Frame (stack) pointer from which we start unwinding175* @leaf_return : Value of r15 at pc. If the function is a leaf, this is176* the caller's return address.177* @trace : Where to store stack backtrace (PC values).178* NULL == print backtrace to kernel log179* @loglvl : Used for printk log level if (trace == NULL).180*/181static void microblaze_unwind_inner(struct task_struct *task,182unsigned long pc, unsigned long fp,183unsigned long leaf_return,184struct stack_trace *trace,185const char *loglvl)186{187int ofs = 0;188189pr_debug(" Unwinding with PC=%p, FP=%p\n", (void *)pc, (void *)fp);190if (!pc || !fp || (pc & 3) || (fp & 3)) {191pr_debug(" Invalid state for unwind, aborting\n");192return;193}194for (; pc != 0;) {195unsigned long next_fp, next_pc = 0;196unsigned long return_to = pc + 2 * sizeof(unsigned long);197const struct trap_handler_info *handler =198µblaze_trap_handlers;199200/* Is previous function the HW exception handler? */201if ((return_to >= (unsigned long)&_hw_exception_handler)202&&(return_to < (unsigned long)&ex_handler_unhandled)) {203/*204* HW exception handler doesn't save all registers,205* so we open-code a special case of unwind_trap()206*/207printk("%sHW EXCEPTION\n", loglvl);208return;209}210211/* Is previous function a trap handler? */212for (; handler->start_addr; ++handler) {213if ((return_to >= handler->start_addr)214&& (return_to <= handler->end_addr)) {215if (!trace)216printk("%s%s\n", loglvl, handler->trap_name);217unwind_trap(task, pc, fp, trace, loglvl);218return;219}220}221pc -= ofs;222223if (trace) {224#ifdef CONFIG_STACKTRACE225if (trace->skip > 0)226trace->skip--;227else228trace->entries[trace->nr_entries++] = pc;229230if (trace->nr_entries >= trace->max_entries)231break;232#endif233} else {234/* Have we reached userland? */235if (unlikely(pc == task_pt_regs(task)->pc)) {236printk("%s[<%p>] PID %lu [%s]\n",237loglvl, (void *) pc,238(unsigned long) task->pid,239task->comm);240break;241} else242print_ip_sym(loglvl, pc);243}244245/* Stop when we reach anything not part of the kernel */246if (!kernel_text_address(pc))247break;248249if (lookup_prev_stack_frame(fp, pc, leaf_return, &next_fp,250&next_pc) == 0) {251ofs = sizeof(unsigned long);252pc = next_pc & ~3;253fp = next_fp;254leaf_return = 0;255} else {256pr_debug(" Failed to find previous stack frame\n");257break;258}259260pr_debug(" Next PC=%p, next FP=%p\n",261(void *)next_pc, (void *)next_fp);262}263}264265/**266* microblaze_unwind - Stack unwinder for Microblaze (external entry point)267* @task : Task whose stack we are to unwind (NULL == current)268* @trace : Where to store stack backtrace (PC values).269* NULL == print backtrace to kernel log270* @loglvl : Used for printk log level if (trace == NULL).271*/272void microblaze_unwind(struct task_struct *task, struct stack_trace *trace,273const char *loglvl)274{275if (task) {276if (task == current) {277const struct pt_regs *regs = task_pt_regs(task);278microblaze_unwind_inner(task, regs->pc, regs->r1,279regs->r15, trace, loglvl);280} else {281struct thread_info *thread_info =282(struct thread_info *)(task->stack);283const struct cpu_context *cpu_context =284&thread_info->cpu_context;285286microblaze_unwind_inner(task,287(unsigned long) &_switch_to,288cpu_context->r1,289cpu_context->r15,290trace, loglvl);291}292} else {293unsigned long pc, fp;294295__asm__ __volatile__ ("or %0, r1, r0" : "=r" (fp));296297__asm__ __volatile__ (298"brlid %0, 0f;"299"nop;"300"0:"301: "=r" (pc)302);303304/* Since we are not a leaf function, use leaf_return = 0 */305microblaze_unwind_inner(current, pc, fp, 0, trace, loglvl);306}307}308309310311