1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3 * Copyright (C) 2019, Linaro Limited
4 */
5
6 /*
7 * APIs defined in this file are required to use __noprof attribute to
8 * avoid any circular dependency during profiling. So this requirement
9 * prohibits these APIs to use standard library APIs as those can be
10 * profiled too.
11 */
12
13 #include <assert.h>
14 #include <user_ta_header.h>
15 #if defined(__KERNEL__)
16 #include <arm.h>
17 #include <kernel/panic.h>
18 #include <kernel/tee_ta_manager.h>
19 #include <kernel/thread.h>
20 #include <mm/core_mmu.h>
21 #else
22 #include <arm_user_sysreg.h>
23 #include <setjmp.h>
24 #include <utee_syscalls.h>
25 #endif
26 #include "ftrace.h"
27
28 #define DURATION_MAX_LEN 16
29 #define ENTRY_SIZE(idx) (DURATION_MAX_LEN + (idx) + \
30 (2 * sizeof(unsigned long)) + 8)
31 #define EXIT_SIZE(idx) (DURATION_MAX_LEN + (idx) + 3)
32
33 static const char hex_str[] = "0123456789abcdef";
34
get_fbuf(void)35 static __noprof struct ftrace_buf *get_fbuf(void)
36 {
37 #if defined(__KERNEL__)
38 short int ct = thread_get_id_may_fail();
39 struct ts_session *s = NULL;
40 struct thread_specific_data *tsd = NULL;
41
42 if (ct == -1)
43 return NULL;
44
45 if (!(core_mmu_user_va_range_is_defined() &&
46 core_mmu_user_mapping_is_active()))
47 return NULL;
48
49 tsd = thread_get_tsd();
50 s = TAILQ_FIRST(&tsd->sess_stack);
51
52 if (!s || tsd->ctx != s->ctx)
53 return NULL;
54
55 if (s->fbuf && s->fbuf->syscall_trace_enabled &&
56 !s->fbuf->syscall_trace_suspended)
57 return s->fbuf;
58 else
59 return NULL;
60 #else
61 return &__ftrace_buf_start;
62 #endif
63 }
64
65 #if defined(_CFG_FTRACE_BUF_WHEN_FULL_shift)
66
67 /*
68 * This API shifts/moves ftrace buffer to create space for new dump
69 * in case the buffer size falls short of actual dump.
70 */
fbuf_make_room(struct ftrace_buf * fbuf,size_t size)71 static bool __noprof fbuf_make_room(struct ftrace_buf *fbuf, size_t size)
72 {
73 char *dst = (char *)fbuf + fbuf->buf_off;
74 const char *src = (char *)fbuf + fbuf->buf_off + size;
75 size_t n = 0;
76
77 fbuf->curr_size -= size;
78
79 for (n = 0; n < fbuf->curr_size; n++)
80 dst[n] = src[n];
81
82 return true;
83 }
84
85 #elif defined(_CFG_FTRACE_BUF_WHEN_FULL_wrap)
86
87 /* Makes room in the trace buffer by discarding the previously recorded data. */
fbuf_make_room(struct ftrace_buf * fbuf,size_t size)88 static bool __noprof fbuf_make_room(struct ftrace_buf *fbuf,
89 size_t size)
90 {
91 if (fbuf->buf_off + size > fbuf->max_size)
92 return false;
93
94 fbuf->curr_size = 0;
95
96 return true;
97 }
98
99 #elif defined(_CFG_FTRACE_BUF_WHEN_FULL_stop)
100
fbuf_make_room(struct ftrace_buf * fbuf __unused,size_t size __unused)101 static bool __noprof fbuf_make_room(struct ftrace_buf *fbuf __unused,
102 size_t size __unused)
103 {
104 return false;
105 }
106
107 #else
108 #error CFG_FTRACE_BUF_WHEN_FULL value not supported
109 #endif
110
to_func_enter_fmt(char * buf,uint32_t ret_idx,unsigned long pc)111 static size_t __noprof to_func_enter_fmt(char *buf, uint32_t ret_idx,
112 unsigned long pc)
113 {
114 char *str = buf;
115 uint32_t addr_size = 2 * sizeof(unsigned long);
116 uint32_t i = 0;
117
118 for (i = 0; i < (DURATION_MAX_LEN + ret_idx); i++)
119 if (i == (DURATION_MAX_LEN - 2))
120 *str++ = '|';
121 else
122 *str++ = ' ';
123
124 *str++ = '0';
125 *str++ = 'x';
126
127 for (i = 0; i < addr_size; i++)
128 *str++ = hex_str[(pc >> 4 * (addr_size - i - 1)) & 0xf];
129
130 *str++ = '(';
131 *str++ = ')';
132 *str++ = ' ';
133 *str++ = '{';
134 *str++ = '\n';
135 *str = '\0';
136
137 return str - buf;
138 }
139
ftrace_enter(unsigned long pc,unsigned long * lr)140 void __noprof ftrace_enter(unsigned long pc, unsigned long *lr)
141 {
142 struct ftrace_buf *fbuf = NULL;
143 size_t line_size = 0;
144 bool full = false;
145
146 fbuf = get_fbuf();
147
148 if (!fbuf || !fbuf->buf_off || !fbuf->max_size)
149 return;
150
151 line_size = ENTRY_SIZE(fbuf->ret_idx);
152
153 /*
154 * Check if we have enough space in ftrace buffer. If not then try to
155 * make room.
156 */
157 full = (fbuf->curr_size + line_size) > fbuf->max_size;
158 if (full)
159 full = !fbuf_make_room(fbuf, line_size);
160
161 if (!full)
162 fbuf->curr_size += to_func_enter_fmt((char *)fbuf +
163 fbuf->buf_off +
164 fbuf->curr_size,
165 fbuf->ret_idx,
166 pc);
167
168 if (fbuf->ret_idx < FTRACE_RETFUNC_DEPTH) {
169 fbuf->ret_stack[fbuf->ret_idx] = *lr;
170 fbuf->begin_time[fbuf->ret_idx] = barrier_read_counter_timer();
171 fbuf->ret_idx++;
172 } else {
173 /*
174 * This scenario isn't expected as function call depth
175 * shouldn't be more than FTRACE_RETFUNC_DEPTH.
176 */
177 #if defined(__KERNEL__)
178 panic();
179 #else
180 _utee_panic(0);
181 #endif
182 }
183
184 *lr = (unsigned long)&__ftrace_return;
185 }
186
ftrace_duration(char * buf,uint64_t start,uint64_t end)187 static void __noprof ftrace_duration(char *buf, uint64_t start, uint64_t end)
188 {
189 uint32_t max_us = CFG_FTRACE_US_MS;
190 uint32_t cntfrq = read_cntfrq();
191 uint64_t ticks = end - start;
192 uint32_t ms = 0;
193 uint32_t us = 0;
194 uint32_t ns = 0;
195 uint32_t frac = 0;
196 uint32_t in = 0;
197 char unit = 'u';
198 int i = 0;
199
200 ticks = ticks * 1000000000 / cntfrq;
201 us = ticks / 1000;
202 ns = ticks % 1000;
203
204 if (max_us && us >= max_us) {
205 /* Display value in milliseconds */
206 unit = 'm';
207 ms = us / 1000;
208 us = us % 1000;
209 frac = us;
210 in = ms;
211 } else {
212 /* Display value in microseconds */
213 frac = ns;
214 in = us;
215 }
216
217 *buf-- = 's';
218 *buf-- = unit;
219 *buf-- = ' ';
220
221 COMPILE_TIME_ASSERT(DURATION_MAX_LEN == 16);
222 if (in > 999999) {
223 /* Not enough space to print the value */
224 for (i = 0; i < 10; i++)
225 *buf-- = '-';
226 return;
227 }
228
229 for (i = 0; i < 3; i++) {
230 *buf-- = hex_str[frac % 10];
231 frac /= 10;
232 }
233
234 *buf-- = '.';
235
236 while (in) {
237 *buf-- = hex_str[in % 10];
238 in /= 10;
239 }
240 }
241
ftrace_return(void)242 unsigned long __noprof ftrace_return(void)
243 {
244 struct ftrace_buf *fbuf = NULL;
245 char *line = NULL;
246 size_t line_size = 0;
247 char *dur_loc = NULL;
248 uint32_t i = 0;
249
250 fbuf = get_fbuf();
251
252 /* Check for valid return index */
253 if (fbuf && fbuf->ret_idx && fbuf->ret_idx <= FTRACE_RETFUNC_DEPTH)
254 fbuf->ret_idx--;
255 else
256 return 0;
257
258 /*
259 * Check if we have a minimum ftrace buffer current size. If we have
260 * somehow corrupted ftrace buffer formatting, the function call chain
261 * shouldn't get broken since we have a valid fbuf->ret_idx at this
262 * point which can retrieve correct return pointer from fbuf->ret_stack.
263 */
264 line_size = ENTRY_SIZE(fbuf->ret_idx);
265 if (fbuf->curr_size < line_size)
266 goto out;
267
268 line = (char *)fbuf + fbuf->buf_off + fbuf->curr_size - line_size;
269
270 /*
271 * Check for '{' symbol as it represents if it is an exit from current
272 * or nested function. If exit is from current function, than exit dump
273 * via ';' symbol else exit dump via '}' symbol.
274 */
275 if (line[line_size - 2] == '{') {
276 line[line_size - 3] = ';';
277 line[line_size - 2] = '\n';
278 line[line_size - 1] = '\0';
279 fbuf->curr_size -= 1;
280
281 dur_loc = &line[DURATION_MAX_LEN - 3];
282 ftrace_duration(dur_loc, fbuf->begin_time[fbuf->ret_idx],
283 barrier_read_counter_timer());
284 } else {
285 bool full = false;
286
287 line_size = EXIT_SIZE(fbuf->ret_idx);
288 full = (fbuf->curr_size + line_size) > fbuf->max_size;
289 if (full)
290 full = !fbuf_make_room(fbuf, line_size);
291
292 if (!full) {
293 line = (char *)fbuf + fbuf->buf_off + fbuf->curr_size;
294
295 for (i = 0; i < DURATION_MAX_LEN + fbuf->ret_idx; i++) {
296 if (i == (DURATION_MAX_LEN - 2))
297 line[i] = '|';
298 else
299 line[i] = ' ';
300 }
301
302 line[i] = '}';
303 line[i + 1] = '\n';
304 line[i + 2] = '\0';
305
306 fbuf->curr_size += line_size - 1;
307
308 dur_loc = &line[DURATION_MAX_LEN - 4];
309 ftrace_duration(dur_loc,
310 fbuf->begin_time[fbuf->ret_idx],
311 barrier_read_counter_timer());
312 }
313 }
314 out:
315 return fbuf->ret_stack[fbuf->ret_idx];
316 }
317
318 #if !defined(__KERNEL__)
ftrace_longjmp(unsigned int * ret_idx)319 void __noprof ftrace_longjmp(unsigned int *ret_idx)
320 {
321 while (__ftrace_buf_start.ret_idx > *ret_idx)
322 ftrace_return();
323 }
324
ftrace_setjmp(unsigned int * ret_idx)325 void __noprof ftrace_setjmp(unsigned int *ret_idx)
326 {
327 *ret_idx = __ftrace_buf_start.ret_idx;
328 }
329 #endif
330