blob: 4c6dd93dfe738eabbdfa3e682623814f19e55d60 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log_frontend.h>
#include <zephyr/logging/log_frontend_stmesp.h>
#include <zephyr/logging/log_frontend_stmesp_demux.h>
#include <zephyr/logging/log_output_dict.h>
#include <zephyr/logging/log_ctrl.h>
#include <zephyr/logging/log_msg.h>
#include <zephyr/sys/cbprintf.h>
#ifdef CONFIG_NRF_ETR
#include <zephyr/drivers/misc/coresight/nrf_etr.h>
#endif
/* Only 32 bit platforms supported. */
BUILD_ASSERT(sizeof(void *) == sizeof(uint32_t));
#define LOG_FRONTEND_STM_NO_SOURCE 0xFFFF
#define EARLY_BUF_SIZE CONFIG_LOG_FRONTEND_STMESP_EARLY_BUF_SIZE
#define LEN_SZ sizeof(uint32_t)
#define STMESP_FLUSH_WORD 0xaabbccdd
#define STM_FLAG(reg) \
stmesp_flag(reg, 1, false, IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS))
#define STM_D8(reg, data, timestamp, marked) \
stmesp_data8(reg, data, timestamp, marked, \
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS))
#define STM_D16(reg, data, timestamp, marked) \
stmesp_data16(reg, data, timestamp, marked, \
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS))
#define STM_D32(reg, data, timestamp, marked) \
stmesp_data32(reg, data, timestamp, marked, \
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS))
/* Buffer for storing frontend data before STM/ETR is ready for usage.
* When notification about ETR readiness is received content of this buffer is
* written to the STM stimulus port.
*/
static atomic_t stmesp_chan_cnt;
static uint8_t early_buf[EARLY_BUF_SIZE] __aligned(sizeof(uint32_t));
static uint32_t early_buf_idx;
static struct k_spinlock lock;
/* Flag indicating that STM/ETR is ready for use. */
static bool etr_rdy;
/* Number of messages dropped due to too small early buffer. */
static uint32_t dropped;
/* Flag indicating that logging is in the panic mode. */
static bool in_panic;
static atomic_t new_data;
/* Enum used for type bit field in the message. */
enum stm_msg_type_log_dict {
/* Dictionary-based log message */
STM_MSG_TYPE_LOG_DICT = 0,
/* Reserved for future use. */
STM_MSG_TYPE_RESERVED = 1,
};
/* Descriptor of the dictionary based logging message. */
struct stm_log_dict_msg_desc {
/* Structure version. Current version 0. */
uint32_t ver: 2;
/* Type. Set 0 as this is a logging message descriptor. */
uint32_t type: 1;
/* Severity level. */
uint32_t level: 3;
/* Data length, non-zero fox hexdump message. */
uint32_t data_len: 12;
/* Source ID. Source index as ordered in the memory section. */
uint32_t source_id: 12;
/* Reserved for future use. */
uint32_t reserved: 2;
};
union stm_log_dict_hdr {
struct stm_log_dict_msg_desc hdr;
uint32_t raw;
};
/* Dictionary header initializer. */
#define DICT_HDR_INITIALIZER(_level, _source_id, _data_len) \
{ \
.hdr = {.ver = CONFIG_LOG_FRONTEND_STMESP_DICT_VER, \
.type = STM_MSG_TYPE_LOG_DICT, \
.level = _level, \
.data_len = _data_len, \
.source_id = _source_id, \
.reserved = 0 } \
}
/* Align early buffer index to a 32 bit word. */
static inline void early_buf_align_idx(void)
{
uint32_t rem = early_buf_idx & 0x3;
if (rem) {
early_buf_idx += (sizeof(uint32_t) - rem);
}
}
/* Get address where length is written. Location is used in the dictionary mode
* where length of the message is only known once whole message is written.
* Calculated length is written to the length field location.
*/
static inline uint32_t *early_buf_len_loc(void)
{
early_buf_align_idx();
uint32_t *loc = (uint32_t *)&early_buf[early_buf_idx];
early_buf_idx += LEN_SZ;
return loc;
}
/* Check if there is space for requested amount of data. */
static inline bool early_buf_has_space(uint32_t len)
{
return (EARLY_BUF_SIZE - early_buf_idx) >= len;
}
/* Calculate length of the message. It is calculated by taking current write
* location and length location (which is at the beginning of the current message.
* Used in dictionary mode.
*/
static inline uint32_t early_buf_get_len(uint32_t *len_loc)
{
if (early_buf_idx == EARLY_BUF_SIZE) {
return 0;
}
return (uint32_t)((uintptr_t)&early_buf[early_buf_idx] - (uintptr_t)len_loc - LEN_SZ);
}
/* Check if there is available space for the message. If yes, write length field
* and return true. If allocation fails it sets next length field to 0 to indicate
* that the buffer is full.
*/
static inline bool early_buf_alloc(uint32_t len)
{
early_buf_align_idx();
if (early_buf_has_space(len + LEN_SZ)) {
*(uint32_t *)&early_buf[early_buf_idx] = len;
early_buf_idx += LEN_SZ;
return true;
}
if (early_buf_has_space(LEN_SZ)) {
*(uint32_t *)&early_buf[early_buf_idx] = 0;
}
return false;
}
/* Switch to read mode. Start reading from the beginning. */
static inline void early_buf_read_mode(void)
{
early_buf_idx = 0;
}
/* Get message. Returns 0 if no messages. */
static inline uint32_t early_buf_get_data(void **buf)
{
early_buf_align_idx();
if (early_buf_has_space(LEN_SZ)) {
uint32_t len = *(uint32_t *)&early_buf[early_buf_idx];
*buf = &early_buf[early_buf_idx + LEN_SZ];
early_buf_idx += len + LEN_SZ;
return len;
}
return 0;
}
static void early_buf_put_data(const void *buf, size_t len)
{
if (early_buf_has_space(len)) {
memcpy(&early_buf[early_buf_idx], buf, len);
early_buf_idx += len;
} else {
early_buf_idx = EARLY_BUF_SIZE;
}
}
static int early_package_cb(const void *buf, size_t len, void *ctx)
{
ARG_UNUSED(ctx);
early_buf_put_data(buf, len);
return 0;
}
static inline void write_data(const void *data, size_t len, STMESP_Type *const stm_esp)
{
const uint8_t *p8 = data;
const uint32_t *p32;
uint32_t unaligned;
if (!len) {
return;
}
/* Start by writing using D8 or D16 until pointer is word aligned. */
unaligned = (uintptr_t)data & 0x00000003UL;
if (unaligned != 0) {
unaligned = 4 - unaligned;
unaligned = MIN(len, unaligned);
len -= unaligned;
switch (unaligned) {
case 3:
STM_D8(stm_esp, *p8++, false, false);
STM_D16(stm_esp, *(uint16_t *)p8, false, false);
p8 += sizeof(uint16_t);
break;
case 2:
if (len) {
STM_D16(stm_esp, *(uint16_t *)p8, false, false);
p8 += sizeof(uint16_t);
} else {
/* If len 0 then it means that even though 2 bytes are
* to be copied we can have address which is not aligned
* to 2 bytes.
*/
STM_D8(stm_esp, *p8++, false, false);
STM_D8(stm_esp, *p8++, false, false);
}
break;
default:
/* 1 byte to align. */
STM_D8(stm_esp, *p8++, false, false);
}
}
p32 = (const uint32_t *)p8;
/* Use D32 to write as much data as possible. */
while (len >= sizeof(uint32_t)) {
STM_D32(stm_esp, *p32++, false, false);
len -= sizeof(uint32_t);
}
/* Write tail using D16 or D8. Address is word aligned at that point. */
if (len) {
p8 = (const uint8_t *)p32;
switch (len) {
case 2:
STM_D16(stm_esp, *(uint16_t *)p8, false, false);
p8 += sizeof(uint16_t);
break;
case 3:
STM_D16(stm_esp, *(uint16_t *)p8, false, false);
p8 += sizeof(uint16_t);
/* fallthrough */
default:
/* 1 byte to align. */
STM_D8(stm_esp, *p8++, false, false);
break;
}
}
}
static int package_cb(const void *buf, size_t len, void *ctx)
{
write_data(buf, len, (STMESP_Type *const)ctx);
return len;
}
/* Get STM channel to use. Ensure that channel is unique for given priority level.
* This way we know that writing to that channel will not be interrupted by
* another log message writing to the same channel.
*/
static inline uint16_t get_channel(void)
{
return (atomic_inc(&stmesp_chan_cnt) & 0x7F) + 1;
}
/* Convert pointer to the source structure to the source ID. */
static inline int16_t get_source_id(const void *source)
{
if (source != NULL) {
return log_source_id(source);
}
return LOG_FRONTEND_STM_NO_SOURCE;
}
static void packet_end(STMESP_Type *stm_esp)
{
if (IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_MSG_END_TIMESTAMP)) {
STM_D8(stm_esp, 0, true, true);
} else {
STM_FLAG(stm_esp);
}
atomic_set(&new_data, 1);
}
/* Common function to end the message when STMESP is not ready. */
static inline void early_msg_end(uint32_t *len_loc)
{
*len_loc = early_buf_get_len(len_loc);
if (*len_loc == 0) {
dropped++;
}
}
#if CONFIG_LOG_FRONTEND_STMESP_FSC
void log_frontend_msg(const void *source, const struct log_msg_desc desc, uint8_t *package,
const void *data)
{
uint16_t strl[4];
union log_frontend_stmesp_demux_header hdr = {.log = {.level = desc.level}};
bool use_timestamp = desc.level != LOG_LEVEL_INTERNAL_RAW_STRING;
const char *sname;
static const char null_c = '\0';
size_t sname_len;
int package_len;
int total_len;
static const uint32_t flags =
CBPRINTF_PACKAGE_CONVERT_RW_STR | CBPRINTF_PACKAGE_CONVERT_RO_STR;
sname = log_source_name_get(0, get_source_id(source));
if (sname) {
sname_len = strlen(sname) + 1;
} else {
sname = &null_c;
sname_len = 1;
}
total_len = desc.data_len + sname_len /* null terminator */;
package_len = cbprintf_package_convert(package, desc.package_len, NULL, NULL, flags, strl,
ARRAY_SIZE(strl));
hdr.log.total_len = total_len + package_len;
hdr.log.package_len = package_len;
if ((EARLY_BUF_SIZE == 0) || etr_rdy) {
STMESP_Type *stm_esp;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return;
}
if (IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_MSG_END_TIMESTAMP)) {
STM_D32(stm_esp, hdr.raw, false, false);
} else {
STM_D32(stm_esp, hdr.raw, use_timestamp, true);
}
(void)cbprintf_package_convert(package, desc.package_len, package_cb, stm_esp,
flags, strl, ARRAY_SIZE(strl));
write_data(sname, sname_len, stm_esp);
if (data) {
write_data(data, desc.data_len, stm_esp);
}
packet_end(stm_esp);
} else {
k_spinlock_key_t key = k_spin_lock(&lock);
if ((EARLY_BUF_SIZE == 0) ||
(early_buf_alloc(hdr.log.total_len + sizeof(hdr)) == false)) {
dropped++;
k_spin_unlock(&lock, key);
return;
}
early_buf_put_data((const uint8_t *)&hdr, sizeof(hdr));
(void)cbprintf_package_convert(package, desc.package_len, early_package_cb, NULL,
flags, strl, ARRAY_SIZE(strl));
early_buf_put_data(sname, sname_len);
if (data) {
early_buf_put_data(data, desc.data_len);
}
k_spin_unlock(&lock, key);
}
}
#else /* CONFIG_LOG_FRONTEND_STMESP_FSC */
void log_frontend_msg(const void *source, const struct log_msg_desc desc, uint8_t *package,
const void *data)
{
static const uint32_t flags = CBPRINTF_PACKAGE_CONVERT_RW_STR;
union stm_log_dict_hdr dict_desc =
DICT_HDR_INITIALIZER(desc.level, get_source_id(source), 0);
if ((EARLY_BUF_SIZE == 0) || etr_rdy) {
STMESP_Type *stm_esp;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return;
}
if (IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_MSG_END_TIMESTAMP)) {
STM_D32(stm_esp, dict_desc.raw, false, false);
} else {
STM_D32(stm_esp, dict_desc.raw, true, true);
}
(void)cbprintf_package_convert(package, desc.package_len, package_cb, stm_esp,
flags, NULL, 0);
if (data) {
package_cb(data, desc.data_len, stm_esp);
}
packet_end(stm_esp);
} else {
k_spinlock_key_t key;
uint32_t *len_loc;
key = k_spin_lock(&lock);
len_loc = early_buf_len_loc();
early_package_cb(&dict_desc.raw, sizeof(dict_desc.raw), NULL);
(void)cbprintf_package_convert(package, desc.package_len, early_package_cb, NULL,
flags, NULL, 0);
if (data) {
early_package_cb(data, desc.data_len, NULL);
}
early_msg_end(len_loc);
k_spin_unlock(&lock, key);
}
}
/* Common function for optimized message (log with 0-2 arguments) which is used in
* case when STMESP is not yet ready.
*/
static inline uint32_t *early_msg_start(uint32_t level, const void *source, uint32_t package_hdr,
const char *fmt)
{
union stm_log_dict_hdr dict_desc = DICT_HDR_INITIALIZER(level, get_source_id(source), 0);
uint32_t fmt32 = (uint32_t)fmt;
uint32_t *len_loc = early_buf_len_loc();
early_package_cb(&dict_desc.raw, sizeof(dict_desc.raw), NULL);
early_package_cb(&package_hdr, sizeof(package_hdr), NULL);
early_package_cb(&fmt32, sizeof(fmt32), NULL);
return len_loc;
}
/* Common function for optimized message (log with 0-2 arguments) which writes to STMESP */
static inline void msg_start(STMESP_Type *stm_esp, uint32_t level, const void *source,
uint32_t package_hdr, const char *fmt)
{
union stm_log_dict_hdr dict_desc = DICT_HDR_INITIALIZER(level, get_source_id(source), 0);
if (IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_MSG_END_TIMESTAMP)) {
STM_D32(stm_esp, dict_desc.raw, false, false);
} else {
STM_D32(stm_esp, dict_desc.raw, true, true);
}
STM_D32(stm_esp, package_hdr, false, false);
STM_D32(stm_esp, (uint32_t)fmt, false, false);
}
void log_frontend_simple_0(const void *source, uint32_t level, const char *fmt)
{
static const union cbprintf_package_hdr package_hdr = {.desc = {.len = 2}};
if ((EARLY_BUF_SIZE == 0) || etr_rdy) {
STMESP_Type *stm_esp;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return;
}
msg_start(stm_esp, level, source, (uint32_t)package_hdr.raw, fmt);
packet_end(stm_esp);
return;
}
uint32_t *len_loc;
k_spinlock_key_t key;
key = k_spin_lock(&lock);
len_loc = early_msg_start(level, source, (uint32_t)package_hdr.raw, fmt);
early_msg_end(len_loc);
k_spin_unlock(&lock, key);
}
void log_frontend_simple_1(const void *source, uint32_t level, const char *fmt, uint32_t arg)
{
static const union cbprintf_package_hdr package_hdr = {.desc = {.len = 2 + 1}};
if ((EARLY_BUF_SIZE == 0) || etr_rdy) {
STMESP_Type *stm_esp;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return;
}
msg_start(stm_esp, level, source, (uint32_t)package_hdr.raw, fmt);
STM_D32(stm_esp, arg, false, false);
packet_end(stm_esp);
return;
}
uint32_t *len_loc;
k_spinlock_key_t key;
key = k_spin_lock(&lock);
len_loc = early_msg_start(level, source, (uint32_t)package_hdr.raw, fmt);
early_package_cb(&arg, sizeof(arg), NULL);
early_msg_end(len_loc);
k_spin_unlock(&lock, key);
}
void log_frontend_simple_2(const void *source, uint32_t level, const char *fmt, uint32_t arg0,
uint32_t arg1)
{
static const union cbprintf_package_hdr package_hdr = {.desc = {.len = 2 + 2}};
if ((EARLY_BUF_SIZE == 0) || etr_rdy) {
STMESP_Type *stm_esp;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return;
}
msg_start(stm_esp, level, source, (uint32_t)package_hdr.raw, fmt);
STM_D32(stm_esp, arg0, false, false);
STM_D32(stm_esp, arg1, false, false);
packet_end(stm_esp);
return;
}
uint32_t *len_loc;
k_spinlock_key_t key;
key = k_spin_lock(&lock);
len_loc = early_msg_start(level, source, (uint32_t)package_hdr.raw, fmt);
early_package_cb(&arg0, sizeof(arg0), NULL);
early_package_cb(&arg1, sizeof(arg1), NULL);
early_msg_end(len_loc);
k_spin_unlock(&lock, key);
}
#endif /* CONFIG_LOG_FRONTEND_STMESP_FSC */
void log_frontend_panic(void)
{
in_panic = true;
#ifdef CONFIG_NRF_ETR
nrf_etr_flush();
#endif
}
void log_frontend_init(void)
{
#if defined(CONFIG_LOG_FRONTEND_STPESP_TURBO_SOURCE_PORT_ID) && !defined(CONFIG_NRF_ETR)
/* Send location of section with constant source data. It is used by the
* application core to retrieve source names of log messages coming from
* coprocessors (FLPR and PPR).
*/
TYPE_SECTION_START_EXTERN(struct log_source_const_data, log_const);
STMESP_Type *stm_esp;
uintptr_t log_const_start;
(void)stmesp_get_port(CONFIG_LOG_FRONTEND_STPESP_TURBO_SOURCE_PORT_ID, &stm_esp);
log_const_start = (uintptr_t)TYPE_SECTION_START(log_const);
STM_D32(stm_esp, log_const_start, false, true);
#endif
}
void log_frontend_stmesp_dummy_write(void)
{
#define STMESP_DUMMY_WORD 0xaabbccdd
STMESP_Type *stm_esp;
(void)stmesp_get_port(CONFIG_LOG_FRONTEND_STMESP_FLUSH_PORT_ID, &stm_esp);
STM_D32(stm_esp, STMESP_DUMMY_WORD, false, false);
}
void log_frontend_stmesp_pre_sleep(void)
{
bool use_stm = etr_rdy || (EARLY_BUF_SIZE == 0);
if (!use_stm || new_data == 0) {
return;
}
for (uint32_t i = 0; i < CONFIG_LOG_FRONTEND_STMESP_FLUSH_COUNT; i++) {
log_frontend_stmesp_dummy_write();
}
atomic_set(&new_data, 0);
}
#if EARLY_BUF_SIZE > 0
int log_frontend_stmesp_etr_ready(void)
{
STMESP_Type *stm_esp;
uint16_t len;
uint32_t *buf = NULL;
int err;
err = stmesp_get_port(get_channel(), &stm_esp);
if (err < 0) {
return -EIO;
}
early_buf_read_mode();
while ((len = early_buf_get_data((void **)&buf)) > 0) {
if (IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_MSG_END_TIMESTAMP)) {
STM_D32(stm_esp, *buf, false, false);
} else {
/* Write first word with Marked and timestamp. */
STM_D32(stm_esp, *buf, true, true);
}
buf++;
len -= sizeof(uint32_t);
/* Write remaining data as raw data. */
write_data(buf, len, stm_esp);
/* Flag the end. */
packet_end(stm_esp);
}
etr_rdy = true;
return 0;
}
#endif /* EARLY_BUF_SIZE > 0 */
void log_frontend_stmesp_log0(const void *source, uint32_t x)
{
STMESP_Type *port;
int err = stmesp_get_port((uint32_t)x + 0x8000, &port);
uint16_t source_id = log_source_id(source);
__ASSERT_NO_MSG(err == 0);
if (err == 0) {
stmesp_data16(port, source_id, true, true,
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS));
}
}
void log_frontend_stmesp_log1(const void *source, uint32_t x, uint32_t arg)
{
STMESP_Type *port;
int err = stmesp_get_port((uint32_t)x + 0x8000, &port);
uint16_t source_id = log_source_id(source);
__ASSERT_NO_MSG(err == 0);
if (err == 0) {
stmesp_data16(port, source_id, false, true,
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS));
stmesp_data32(port, arg, true, true,
IS_ENABLED(CONFIG_LOG_FRONTEND_STMESP_GUARANTEED_ACCESS));
}
}