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