linux/arch/microblaze/kernel/unwind.c
<<
>>
Prefs
   1/*
   2 * Backtrace support for Microblaze
   3 *
   4 * Copyright (C) 2010  Digital Design Corporation
   5 *
   6 * Based on arch/sh/kernel/cpu/sh5/unwind.c code which is:
   7 * Copyright (C) 2004  Paul Mundt
   8 * Copyright (C) 2004  Richard Curnow
   9 *
  10 * This file is subject to the terms and conditions of the GNU General Public
  11 * License.  See the file "COPYING" in the main directory of this archive
  12 * for more details.
  13 */
  14
  15/* #define DEBUG 1 */
  16#include <linux/export.h>
  17#include <linux/kallsyms.h>
  18#include <linux/kernel.h>
  19#include <linux/sched.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>
  28
  29struct stack_trace;
  30
  31/*
  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 at
  35 * a consistent offset within each frame. To determine frame size, it is
  36 * necessary to search for the assembly instruction that creates or reclaims
  37 * the frame and extract the size from it.
  38 *
  39 * Microblaze stores the stack pointer in r1, and creates a frame via
  40 *
  41 *     addik r1, r1, -FRAME_SIZE
  42 *
  43 * The frame is reclaimed via
  44 *
  45 *     addik r1, r1, FRAME_SIZE
  46 *
  47 * Frame creation occurs at or near the top of a function.
  48 * Depending on the compiler, reclaim may occur at the end, or before
  49 * a mid-function return.
  50 *
  51 * A stack frame is usually not created in a leaf function.
  52 *
  53 */
  54
  55/**
  56 * get_frame_size - Extract the stack adjustment from an
  57 *                  "addik r1, r1, adjust" instruction
  58 * @instr : Microblaze instruction
  59 *
  60 * Return - Number of stack bytes the instruction reserves or reclaims
  61 */
  62static inline long get_frame_size(unsigned long instr)
  63{
  64        return abs((s16)(instr & 0xFFFF));
  65}
  66
  67/**
  68 * find_frame_creation - Search backward to find the instruction that creates
  69 *                       the stack frame (hopefully, for the same function the
  70 *                       initial PC is in).
  71 * @pc : Program counter at which to begin the search
  72 *
  73 * Return - PC at which stack frame creation occurs
  74 *          NULL if this cannot be found, i.e. a leaf function
  75 */
  76static unsigned long *find_frame_creation(unsigned long *pc)
  77{
  78        int i;
  79
  80        /* NOTE: Distance to search is arbitrary
  81         *       250 works well for most things,
  82         *       750 picks up things like tcp_recvmsg(),
  83         *      1000 needed for fat_fill_super()
  84         */
  85        for (i = 0; i < 1000; i++, pc--) {
  86                unsigned long instr;
  87                s16 frame_size;
  88
  89                if (!kernel_text_address((unsigned long) pc))
  90                        return NULL;
  91
  92                instr = *pc;
  93
  94                /* addik r1, r1, foo ? */
  95                if ((instr & 0xFFFF0000) != 0x30210000)
  96                        continue;       /* No */
  97
  98                frame_size = get_frame_size(instr);
  99                if ((frame_size < 8) || (frame_size & 3)) {
 100                        pr_debug("    Invalid frame size %d at 0x%p\n",
 101                                 frame_size, pc);
 102                        return NULL;
 103                }
 104
 105                pr_debug("    Found frame creation at 0x%p, size %d\n", pc,
 106                         frame_size);
 107                return pc;
 108        }
 109
 110        return NULL;
 111}
 112
 113/**
 114 * lookup_prev_stack_frame - Find the stack frame of the previous function.
 115 * @fp          : Frame (stack) pointer for current function
 116 * @pc          : Program counter within current function
 117 * @leaf_return : r15 value within current function. If the current function
 118 *                is a leaf, this is the caller's return address.
 119 * @pprev_fp    : On exit, set to frame (stack) pointer for previous function
 120 * @pprev_pc    : On exit, set to current function caller's return address
 121 *
 122 * Return - 0 on success, -EINVAL if the previous frame cannot be found
 123 */
 124static int lookup_prev_stack_frame(unsigned long fp, unsigned long pc,
 125                                   unsigned long leaf_return,
 126                                   unsigned long *pprev_fp,
 127                                   unsigned long *pprev_pc)
 128{
 129        unsigned long *prologue = NULL;
 130
 131        /* _switch_to is a special leaf function */
 132        if (pc != (unsigned long) &_switch_to)
 133                prologue = find_frame_creation((unsigned long *)pc);
 134
 135        if (prologue) {
 136                long frame_size = get_frame_size(*prologue);
 137
 138                *pprev_fp = fp + frame_size;
 139                *pprev_pc = *(unsigned long *)fp;
 140        } else {
 141                if (!leaf_return)
 142                        return -EINVAL;
 143                *pprev_pc = leaf_return;
 144                *pprev_fp = fp;
 145        }
 146
 147        /* NOTE: don't check kernel_text_address here, to allow display
 148         *       of userland return address
 149         */
 150        return (!*pprev_pc || (*pprev_pc & 3)) ? -EINVAL : 0;
 151}
 152
 153static void microblaze_unwind_inner(struct task_struct *task,
 154                                    unsigned long pc, unsigned long fp,
 155                                    unsigned long leaf_return,
 156                                    struct stack_trace *trace);
 157
 158/**
 159 * unwind_trap - Unwind through a system trap, that stored previous state
 160 *               on the stack.
 161 */
 162#ifdef CONFIG_MMU
 163static inline void unwind_trap(struct task_struct *task, unsigned long pc,
 164                                unsigned long fp, struct stack_trace *trace)
 165{
 166        /* To be implemented */
 167}
 168#else
 169static inline void unwind_trap(struct task_struct *task, unsigned long pc,
 170                                unsigned long fp, struct stack_trace *trace)
 171{
 172        const struct pt_regs *regs = (const struct pt_regs *) fp;
 173        microblaze_unwind_inner(task, regs->pc, regs->r1, regs->r15, trace);
 174}
 175#endif
 176
 177/**
 178 * microblaze_unwind_inner - Unwind the stack from the specified point
 179 * @task  : Task whose stack we are to unwind (may be NULL)
 180 * @pc    : Program counter from which we start unwinding
 181 * @fp    : Frame (stack) pointer from which we start unwinding
 182 * @leaf_return : Value of r15 at pc. If the function is a leaf, this is
 183 *                                the caller's return address.
 184 * @trace : Where to store stack backtrace (PC values).
 185 *          NULL == print backtrace to kernel log
 186 */
 187static void microblaze_unwind_inner(struct task_struct *task,
 188                             unsigned long pc, unsigned long fp,
 189                             unsigned long leaf_return,
 190                             struct stack_trace *trace)
 191{
 192        int ofs = 0;
 193
 194        pr_debug("    Unwinding with PC=%p, FP=%p\n", (void *)pc, (void *)fp);
 195        if (!pc || !fp || (pc & 3) || (fp & 3)) {
 196                pr_debug("    Invalid state for unwind, aborting\n");
 197                return;
 198        }
 199        for (; pc != 0;) {
 200                unsigned long next_fp, next_pc = 0;
 201                unsigned long return_to = pc +  2 * sizeof(unsigned long);
 202                const struct trap_handler_info *handler =
 203                        &microblaze_trap_handlers;
 204
 205                /* Is previous function the HW exception handler? */
 206                if ((return_to >= (unsigned long)&_hw_exception_handler)
 207                    &&(return_to < (unsigned long)&ex_handler_unhandled)) {
 208                        /*
 209                         * HW exception handler doesn't save all registers,
 210                         * so we open-code a special case of unwind_trap()
 211                         */
 212#ifndef CONFIG_MMU
 213                        const struct pt_regs *regs =
 214                                (const struct pt_regs *) fp;
 215#endif
 216                        pr_info("HW EXCEPTION\n");
 217#ifndef CONFIG_MMU
 218                        microblaze_unwind_inner(task, regs->r17 - 4,
 219                                                fp + EX_HANDLER_STACK_SIZ,
 220                                                regs->r15, trace);
 221#endif
 222                        return;
 223                }
 224
 225                /* Is previous function a trap handler? */
 226                for (; handler->start_addr; ++handler) {
 227                        if ((return_to >= handler->start_addr)
 228                            && (return_to <= handler->end_addr)) {
 229                                if (!trace)
 230                                        pr_info("%s\n", handler->trap_name);
 231                                unwind_trap(task, pc, fp, trace);
 232                                return;
 233                        }
 234                }
 235                pc -= ofs;
 236
 237                if (trace) {
 238#ifdef CONFIG_STACKTRACE
 239                        if (trace->skip > 0)
 240                                trace->skip--;
 241                        else
 242                                trace->entries[trace->nr_entries++] = pc;
 243
 244                        if (trace->nr_entries >= trace->max_entries)
 245                                break;
 246#endif
 247                } else {
 248                        /* Have we reached userland? */
 249                        if (unlikely(pc == task_pt_regs(task)->pc)) {
 250                                pr_info("[<%p>] PID %lu [%s]\n",
 251                                        (void *) pc,
 252                                        (unsigned long) task->pid,
 253                                        task->comm);
 254                                break;
 255                        } else
 256                                print_ip_sym(pc);
 257                }
 258
 259                /* Stop when we reach anything not part of the kernel */
 260                if (!kernel_text_address(pc))
 261                        break;
 262
 263                if (lookup_prev_stack_frame(fp, pc, leaf_return, &next_fp,
 264                                            &next_pc) == 0) {
 265                        ofs = sizeof(unsigned long);
 266                        pc = next_pc & ~3;
 267                        fp = next_fp;
 268                        leaf_return = 0;
 269                } else {
 270                        pr_debug("    Failed to find previous stack frame\n");
 271                        break;
 272                }
 273
 274                pr_debug("    Next PC=%p, next FP=%p\n",
 275                         (void *)next_pc, (void *)next_fp);
 276        }
 277}
 278
 279/**
 280 * microblaze_unwind - Stack unwinder for Microblaze (external entry point)
 281 * @task  : Task whose stack we are to unwind (NULL == current)
 282 * @trace : Where to store stack backtrace (PC values).
 283 *          NULL == print backtrace to kernel log
 284 */
 285void microblaze_unwind(struct task_struct *task, struct stack_trace *trace)
 286{
 287        if (task) {
 288                if (task == current) {
 289                        const struct pt_regs *regs = task_pt_regs(task);
 290                        microblaze_unwind_inner(task, regs->pc, regs->r1,
 291                                                regs->r15, trace);
 292                } else {
 293                        struct thread_info *thread_info =
 294                                (struct thread_info *)(task->stack);
 295                        const struct cpu_context *cpu_context =
 296                                &thread_info->cpu_context;
 297
 298                        microblaze_unwind_inner(task,
 299                                                (unsigned long) &_switch_to,
 300                                                cpu_context->r1,
 301                                                cpu_context->r15, trace);
 302                }
 303        } else {
 304                unsigned long pc, fp;
 305
 306                __asm__ __volatile__ ("or %0, r1, r0" : "=r" (fp));
 307
 308                __asm__ __volatile__ (
 309                        "brlid %0, 0f;"
 310                        "nop;"
 311                        "0:"
 312                        : "=r" (pc)
 313                );
 314
 315                /* Since we are not a leaf function, use leaf_return = 0 */
 316                microblaze_unwind_inner(current, pc, fp, 0, trace);
 317        }
 318}
 319
 320