misc: tegra-profiler: backtracing for Thumb code
[linux-2.6.git] / drivers / misc / tegra-profiler / backtrace.c
1 /*
2  * drivers/misc/tegra-profiler/backtrace.c
3  *
4  * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms and conditions of the GNU General Public License,
8  * version 2, as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  */
16
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
19 #include <linux/module.h>
20 #include <asm-generic/uaccess.h>
21
22 #include <linux/tegra_profiler.h>
23
24 #include "backtrace.h"
25
26 #define QUADD_USER_SPACE_MIN_ADDR       0x8000
27
28 static inline void
29 quadd_callchain_store(struct quadd_callchain *callchain_data, u32 ip)
30 {
31         if (callchain_data->nr < QUADD_MAX_STACK_DEPTH) {
32                 /* pr_debug("[%d] Add entry: %#llx\n",
33                             callchain_data->nr, ip); */
34                 callchain_data->callchain[callchain_data->nr++] = ip;
35         }
36 }
37
38 static int
39 check_vma_address(unsigned long addr, struct vm_area_struct *vma)
40 {
41         unsigned long start, end, length;
42
43         if (vma) {
44                 start = vma->vm_start;
45                 end = vma->vm_end;
46                 length = end - start;
47                 if (length > sizeof(unsigned long) &&
48                     addr >= start && addr <= end - sizeof(unsigned long))
49                         return 0;
50         }
51         return -EINVAL;
52 }
53
54 static unsigned long __user *
55 user_backtrace(unsigned long __user *tail,
56                struct quadd_callchain *callchain_data,
57                struct vm_area_struct *stack_vma)
58 {
59         unsigned long value, value_lr = 0, value_fp = 0;
60         unsigned long __user *fp_prev = NULL;
61
62         if (check_vma_address((unsigned long)tail, stack_vma))
63                 return NULL;
64
65         if (__copy_from_user_inatomic(&value, tail, sizeof(unsigned long)))
66                 return NULL;
67
68         if (!check_vma_address(value, stack_vma)) {
69                 /* gcc thumb/clang frame */
70                 value_fp = value;
71
72                 if (check_vma_address((unsigned long)(tail + 1), stack_vma))
73                         return NULL;
74
75                 if (__copy_from_user_inatomic(&value_lr, tail + 1,
76                                               sizeof(unsigned long)))
77                         return NULL;
78         } else {
79                 /* gcc arm frame */
80                 if (__copy_from_user_inatomic(&value_fp, tail - 1,
81                                               sizeof(unsigned long)))
82                         return NULL;
83
84                 if (check_vma_address(value_fp, stack_vma))
85                         return NULL;
86
87                 value_lr = value;
88         }
89
90         fp_prev = (unsigned long __user *)value_fp;
91
92         if (value_lr < QUADD_USER_SPACE_MIN_ADDR)
93                 return NULL;
94
95         quadd_callchain_store(callchain_data, value_lr);
96
97         if (fp_prev <= tail)
98                 return NULL;
99
100         return fp_prev;
101 }
102
103 unsigned int
104 quadd_get_user_callchain(struct pt_regs *regs,
105                          struct quadd_callchain *callchain_data)
106 {
107         unsigned long fp, sp, pc, reg;
108         struct vm_area_struct *vma, *vma_pc;
109         unsigned long __user *tail = NULL;
110         struct mm_struct *mm = current->mm;
111
112         callchain_data->nr = 0;
113
114         if (!regs || !user_mode(regs) || !mm)
115                 return 0;
116
117         if (thumb_mode(regs))
118                 fp = regs->ARM_r7;
119         else
120                 fp = regs->ARM_fp;
121
122         sp = regs->ARM_sp;
123         pc = regs->ARM_pc;
124
125         if (fp == 0 || fp < sp || fp & 0x3)
126                 return 0;
127
128         vma = find_vma(mm, sp);
129         if (check_vma_address(fp, vma))
130                 return 0;
131
132         if (__copy_from_user_inatomic(&reg, (unsigned long __user *)fp,
133                                       sizeof(unsigned long)))
134                 return 0;
135
136         if (thumb_mode(regs)) {
137                 if (reg <= fp || check_vma_address(reg, vma))
138                         return 0;
139         } else if (reg > fp &&
140                   !check_vma_address(reg, vma)) {
141                 /* fp --> fp prev */
142                 unsigned long value;
143                 int read_lr = 0;
144
145                 if (!check_vma_address(fp + sizeof(unsigned long), vma)) {
146                         if (__copy_from_user_inatomic(
147                                         &value,
148                                         (unsigned long __user *)fp + 1,
149                                         sizeof(unsigned long)))
150                                 return 0;
151
152                         vma_pc = find_vma(mm, pc);
153                         read_lr = 1;
154                 }
155
156                 if (!read_lr || check_vma_address(value, vma_pc)) {
157                         /* gcc: fp --> short frame tail (fp) */
158
159                         if (regs->ARM_lr < QUADD_USER_SPACE_MIN_ADDR)
160                                 return 0;
161
162                         quadd_callchain_store(callchain_data, regs->ARM_lr);
163                         tail = (unsigned long __user *)reg;
164                 }
165         }
166
167         if (!tail)
168                 tail = (unsigned long __user *)fp;
169
170         while (tail && !((unsigned long)tail & 0x3))
171                 tail = user_backtrace(tail, callchain_data, vma);
172
173         return callchain_data->nr;
174 }