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