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