xref: /optee_os/lib/libutils/ext/ftrace/ftrace.c (revision 43be6453dd3e98d39721c8bc6725416772f4205c)
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 
32 static __noprof struct ftrace_buf *get_fbuf(void)
33 {
34 #if defined(__KERNEL__)
35 	int ct = thread_get_id_may_fail();
36 	struct tee_ta_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 /*
63  * This API shifts/moves ftrace buffer to create space for new dump
64  * in case the buffer size falls short of actual dump.
65  */
66 static void __noprof fbuf_shift(struct ftrace_buf *fbuf, size_t size)
67 {
68 	char *dst = (char *)fbuf + fbuf->buf_off;
69 	const char *src = (char *)fbuf + fbuf->buf_off + size;
70 	size_t n = 0;
71 
72 	fbuf->curr_size -= size;
73 
74 	for (n = 0; n < fbuf->curr_size; n++)
75 		dst[n] = src[n];
76 }
77 
78 static size_t __noprof to_func_enter_fmt(char *buf, uint32_t ret_idx,
79 					 unsigned long pc)
80 {
81 	char *str = buf;
82 	uint32_t addr_size = 2 * sizeof(unsigned long);
83 	uint32_t i = 0;
84 
85 	for (i = 0; i < (DURATION_MAX_LEN + ret_idx); i++)
86 		if (i == (DURATION_MAX_LEN - 2))
87 			*str++ = '|';
88 		else
89 			*str++ = ' ';
90 
91 	*str++ = '0';
92 	*str++ = 'x';
93 
94 	for (i = 0; i < addr_size; i++)
95 		*str++ = hex_str[(pc >> 4 * (addr_size - i - 1)) & 0xf];
96 
97 	*str++ = '(';
98 	*str++ = ')';
99 	*str++ = ' ';
100 	*str++ = '{';
101 	*str++ = '\n';
102 	*str = '\0';
103 
104 	return str - buf;
105 }
106 
107 void __noprof ftrace_enter(unsigned long pc, unsigned long *lr)
108 {
109 	struct ftrace_buf *fbuf = NULL;
110 	size_t dump_size = 0;
111 
112 	fbuf = get_fbuf();
113 
114 	if (!fbuf || !fbuf->buf_off || !fbuf->max_size)
115 		return;
116 
117 	dump_size = DURATION_MAX_LEN + fbuf->ret_idx +
118 			(2 * sizeof(unsigned long)) + 8;
119 
120 	/*
121 	 * Check if we have enough space in ftrace buffer. If not then just
122 	 * remove oldest dump under the assumption that its the least
123 	 * interesting data.
124 	 */
125 	if ((fbuf->curr_size + dump_size) > fbuf->max_size)
126 		fbuf_shift(fbuf, dump_size);
127 
128 	fbuf->curr_size += to_func_enter_fmt((char *)fbuf + fbuf->buf_off +
129 					     fbuf->curr_size, fbuf->ret_idx,
130 					     pc);
131 
132 	if (fbuf->ret_idx < FTRACE_RETFUNC_DEPTH) {
133 		fbuf->ret_stack[fbuf->ret_idx] = *lr;
134 		fbuf->begin_time[fbuf->ret_idx] = read_cntpct();
135 		fbuf->ret_idx++;
136 	} else {
137 		/*
138 		 * This scenario isn't expected as function call depth
139 		 * shouldn't be more than FTRACE_RETFUNC_DEPTH.
140 		 */
141 #if defined(__KERNEL__)
142 		panic();
143 #else
144 		utee_panic(0);
145 #endif
146 	}
147 
148 	*lr = (unsigned long)&__ftrace_return;
149 }
150 
151 static void __noprof ftrace_duration(char *buf, uint64_t start, uint64_t end)
152 {
153 	uint32_t max_us = CFG_FTRACE_US_MS;
154 	uint32_t cntfrq = read_cntfrq();
155 	uint64_t ticks = end - start;
156 	uint32_t ms = 0;
157 	uint32_t us = 0;
158 	uint32_t ns = 0;
159 	uint32_t frac = 0;
160 	uint32_t in = 0;
161 	char unit = 'u';
162 	int i = 0;
163 
164 	ticks = ticks * 1000000000 / cntfrq;
165 	us = ticks / 1000;
166 	ns = ticks % 1000;
167 
168 	if (max_us && us >= max_us) {
169 		/* Display value in milliseconds */
170 		unit = 'm';
171 		ms = us / 1000;
172 		us = us % 1000;
173 		frac = us;
174 		in = ms;
175 	} else {
176 		/* Display value in microseconds */
177 		frac = ns;
178 		in = us;
179 	}
180 
181 	*buf-- = 's';
182 	*buf-- = unit;
183 	*buf-- = ' ';
184 
185 	COMPILE_TIME_ASSERT(DURATION_MAX_LEN == 16);
186 	if (in > 999999) {
187 		/* Not enough space to print the value */
188 		for (i = 0; i < 10; i++)
189 			*buf-- = '-';
190 		return;
191 	}
192 
193 	for (i = 0; i < 3; i++) {
194 		*buf-- = hex_str[frac % 10];
195 		frac /= 10;
196 	}
197 
198 	*buf-- = '.';
199 
200 	while (in) {
201 		*buf-- = hex_str[in % 10];
202 		in /= 10;
203 	}
204 }
205 
206 unsigned long __noprof ftrace_return(void)
207 {
208 	struct ftrace_buf *fbuf = NULL;
209 	size_t dump_size = 0;
210 	char *curr_buf = NULL;
211 	char *dur_loc = NULL;
212 	uint32_t i = 0;
213 
214 	fbuf = get_fbuf();
215 
216 	/* Check for valid return index */
217 	if (fbuf && fbuf->ret_idx && fbuf->ret_idx <= FTRACE_RETFUNC_DEPTH)
218 		fbuf->ret_idx--;
219 	else
220 		return 0;
221 
222 	curr_buf = (char *)fbuf + fbuf->buf_off + fbuf->curr_size;
223 
224 	/*
225 	 * Check for '{' symbol as it represents if it is an exit from current
226 	 * or nested function. If exit is from current function, than exit dump
227 	 * via ';' symbol else exit dump via '}' symbol.
228 	 */
229 	if (*(curr_buf - 2) == '{') {
230 		*(curr_buf - 3) = ';';
231 		*(curr_buf - 2) = '\n';
232 		*(curr_buf - 1) = '\0';
233 		fbuf->curr_size -= 1;
234 
235 		dur_loc = curr_buf - (fbuf->ret_idx +
236 				      (2 * sizeof(unsigned long)) + 11);
237 		ftrace_duration(dur_loc, fbuf->begin_time[fbuf->ret_idx],
238 				read_cntpct());
239 	} else {
240 		dump_size = DURATION_MAX_LEN + fbuf->ret_idx + 3;
241 		if ((fbuf->curr_size + dump_size) > fbuf->max_size)
242 			fbuf_shift(fbuf, dump_size);
243 
244 		curr_buf = (char *)fbuf + fbuf->buf_off + fbuf->curr_size;
245 
246 		for (i = 0; i < (DURATION_MAX_LEN + fbuf->ret_idx); i++)
247 			if (i == (DURATION_MAX_LEN - 2))
248 				*curr_buf++ = '|';
249 			else
250 				*curr_buf++ = ' ';
251 
252 		*curr_buf++ = '}';
253 		*curr_buf++ = '\n';
254 		*curr_buf = '\0';
255 
256 		fbuf->curr_size += dump_size - 1;
257 
258 		dur_loc = curr_buf - fbuf->ret_idx - 6;
259 		ftrace_duration(dur_loc, fbuf->begin_time[fbuf->ret_idx],
260 				read_cntpct());
261 	}
262 
263 	return fbuf->ret_stack[fbuf->ret_idx];
264 }
265 
266 #if !defined(__KERNEL__)
267 void __noprof ftrace_longjmp(unsigned int *ret_idx)
268 {
269 	while (__ftrace_buf_start.ret_idx > *ret_idx)
270 		ftrace_return();
271 }
272 
273 void __noprof ftrace_setjmp(unsigned int *ret_idx)
274 {
275 	*ret_idx = __ftrace_buf_start.ret_idx;
276 }
277 #endif
278