blob: 8393fb3cc99de5e480ef849d2fb7550365148355 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (C) 2019, Linaro Limited
*/
/*
* APIs defined in this file are required to use __noprof attribute to
* avoid any circular dependency during profiling. So this requirement
* prohibits these APIs to use standard library APIs as those can be
* profiled too.
*/
#include <assert.h>
#include <user_ta_header.h>
#if defined(__KERNEL__)
#include <arm.h>
#include <kernel/panic.h>
#include <kernel/tee_ta_manager.h>
#include <kernel/thread.h>
#include <mm/core_mmu.h>
#else
#include <arm_user_sysreg.h>
#include <setjmp.h>
#include <utee_syscalls.h>
#endif
#include "ftrace.h"
#define DURATION_MAX_LEN 16
static const char hex_str[] = "0123456789abcdef";
static __noprof struct ftrace_buf *get_fbuf(void)
{
#if defined(__KERNEL__)
int ct = thread_get_id_may_fail();
struct tee_ta_session *s = NULL;
struct thread_specific_data *tsd = NULL;
if (ct == -1)
return NULL;
if (!(core_mmu_user_va_range_is_defined() &&
core_mmu_user_mapping_is_active()))
return NULL;
tsd = thread_get_tsd();
s = TAILQ_FIRST(&tsd->sess_stack);
if (!s || tsd->ctx != s->ctx)
return NULL;
if (s->fbuf && s->fbuf->syscall_trace_enabled &&
!s->fbuf->syscall_trace_suspended)
return s->fbuf;
else
return NULL;
#else
return &__ftrace_buf_start;
#endif
}
/*
* This API shifts/moves ftrace buffer to create space for new dump
* in case the buffer size falls short of actual dump.
*/
static void __noprof fbuf_shift(struct ftrace_buf *fbuf, size_t size)
{
char *dst = (char *)fbuf + fbuf->buf_off;
const char *src = (char *)fbuf + fbuf->buf_off + size;
size_t n = 0;
fbuf->curr_size -= size;
for (n = 0; n < fbuf->curr_size; n++)
dst[n] = src[n];
}
static size_t __noprof to_func_enter_fmt(char *buf, uint32_t ret_idx,
unsigned long pc)
{
char *str = buf;
uint32_t addr_size = 2 * sizeof(unsigned long);
uint32_t i = 0;
for (i = 0; i < (DURATION_MAX_LEN + ret_idx); i++)
if (i == (DURATION_MAX_LEN - 2))
*str++ = '|';
else
*str++ = ' ';
*str++ = '0';
*str++ = 'x';
for (i = 0; i < addr_size; i++)
*str++ = hex_str[(pc >> 4 * (addr_size - i - 1)) & 0xf];
*str++ = '(';
*str++ = ')';
*str++ = ' ';
*str++ = '{';
*str++ = '\n';
*str = '\0';
return str - buf;
}
void __noprof ftrace_enter(unsigned long pc, unsigned long *lr)
{
struct ftrace_buf *fbuf = NULL;
size_t dump_size = 0;
fbuf = get_fbuf();
if (!fbuf || !fbuf->buf_off || !fbuf->max_size)
return;
dump_size = DURATION_MAX_LEN + fbuf->ret_idx +
(2 * sizeof(unsigned long)) + 8;
/*
* Check if we have enough space in ftrace buffer. If not then just
* remove oldest dump under the assumption that its the least
* interesting data.
*/
if ((fbuf->curr_size + dump_size) > fbuf->max_size)
fbuf_shift(fbuf, dump_size);
fbuf->curr_size += to_func_enter_fmt((char *)fbuf + fbuf->buf_off +
fbuf->curr_size, fbuf->ret_idx,
pc);
if (fbuf->ret_idx < FTRACE_RETFUNC_DEPTH) {
fbuf->ret_stack[fbuf->ret_idx] = *lr;
fbuf->begin_time[fbuf->ret_idx] = read_cntpct();
fbuf->ret_idx++;
} else {
/*
* This scenario isn't expected as function call depth
* shouldn't be more than FTRACE_RETFUNC_DEPTH.
*/
#if defined(__KERNEL__)
panic();
#else
utee_panic(0);
#endif
}
*lr = (unsigned long)&__ftrace_return;
}
static void __noprof ftrace_duration(char *buf, uint64_t start, uint64_t end)
{
uint32_t max_us = CFG_FTRACE_US_MS;
uint32_t cntfrq = read_cntfrq();
uint64_t ticks = end - start;
uint32_t ms = 0;
uint32_t us = 0;
uint32_t ns = 0;
uint32_t frac = 0;
uint32_t in = 0;
char unit = 'u';
int i = 0;
ticks = ticks * 1000000000 / cntfrq;
us = ticks / 1000;
ns = ticks % 1000;
if (max_us && us >= max_us) {
/* Display value in milliseconds */
unit = 'm';
ms = us / 1000;
us = us % 1000;
frac = us;
in = ms;
} else {
/* Display value in microseconds */
frac = ns;
in = us;
}
*buf-- = 's';
*buf-- = unit;
*buf-- = ' ';
COMPILE_TIME_ASSERT(DURATION_MAX_LEN == 16);
if (in > 999999) {
/* Not enough space to print the value */
for (i = 0; i < 10; i++)
*buf-- = '-';
return;
}
for (i = 0; i < 3; i++) {
*buf-- = hex_str[frac % 10];
frac /= 10;
}
*buf-- = '.';
while (in) {
*buf-- = hex_str[in % 10];
in /= 10;
}
}
unsigned long __noprof ftrace_return(void)
{
struct ftrace_buf *fbuf = NULL;
size_t dump_size = 0;
char *curr_buf = NULL;
char *dur_loc = NULL;
uint32_t i = 0;
fbuf = get_fbuf();
/* Check for valid return index */
if (fbuf && fbuf->ret_idx && fbuf->ret_idx <= FTRACE_RETFUNC_DEPTH)
fbuf->ret_idx--;
else
return 0;
curr_buf = (char *)fbuf + fbuf->buf_off + fbuf->curr_size;
/*
* Check for '{' symbol as it represents if it is an exit from current
* or nested function. If exit is from current function, than exit dump
* via ';' symbol else exit dump via '}' symbol.
*/
if (*(curr_buf - 2) == '{') {
*(curr_buf - 3) = ';';
*(curr_buf - 2) = '\n';
*(curr_buf - 1) = '\0';
fbuf->curr_size -= 1;
dur_loc = curr_buf - (fbuf->ret_idx +
(2 * sizeof(unsigned long)) + 11);
ftrace_duration(dur_loc, fbuf->begin_time[fbuf->ret_idx],
read_cntpct());
} else {
dump_size = DURATION_MAX_LEN + fbuf->ret_idx + 3;
if ((fbuf->curr_size + dump_size) > fbuf->max_size)
fbuf_shift(fbuf, dump_size);
curr_buf = (char *)fbuf + fbuf->buf_off + fbuf->curr_size;
for (i = 0; i < (DURATION_MAX_LEN + fbuf->ret_idx); i++)
if (i == (DURATION_MAX_LEN - 2))
*curr_buf++ = '|';
else
*curr_buf++ = ' ';
*curr_buf++ = '}';
*curr_buf++ = '\n';
*curr_buf = '\0';
fbuf->curr_size += dump_size - 1;
dur_loc = curr_buf - fbuf->ret_idx - 6;
ftrace_duration(dur_loc, fbuf->begin_time[fbuf->ret_idx],
read_cntpct());
}
return fbuf->ret_stack[fbuf->ret_idx];
}
#if !defined(__KERNEL__)
void __noprof ftrace_longjmp(unsigned int *ret_idx)
{
while (__ftrace_buf_start.ret_idx > *ret_idx)
ftrace_return();
}
void __noprof ftrace_setjmp(unsigned int *ret_idx)
{
*ret_idx = __ftrace_buf_start.ret_idx;
}
#endif