zephyr/subsys/logging/frontends/log_frontend_stmesp.c
Krzysztof Chruściński aaeb015ec4 logging: frontends: stmesp: Fix sending string location
String location information should only be sent for core which
do not append strings to the log message (PPR, FLPR). Without
this, cpurad was also sending that information and that was
redundant.

Signed-off-by: Krzysztof Chruściński <krzysztof.chruscinski@nordicsemi.no>
2025-04-01 16:26:50 +02:00

693 lines
18 KiB
C

/*
* 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) && \
!defined(CONFIG_LOG_MSG_APPEND_RO_STRING_LOC)
/* 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;
uintptr_t log_str_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);
log_str_start = (uintptr_t)TYPE_SECTION_START(log_stmesp_ptr);
STM_D32(stm_esp, log_const_start, false, true);
STM_D32(stm_esp, log_str_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));
}
}