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