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