454 lines
11 KiB
C
454 lines
11 KiB
C
/*
|
|
* Copyright (c) 2018 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <shell/shell_log_backend.h>
|
|
#include <shell/shell.h>
|
|
#include "shell_ops.h"
|
|
#include <logging/log_ctrl.h>
|
|
|
|
static bool process_msg2_from_buffer(const struct shell *shell);
|
|
|
|
int z_shell_log_backend_output_func(uint8_t *data, size_t length, void *ctx)
|
|
{
|
|
z_shell_print_stream(ctx, data, length);
|
|
return length;
|
|
}
|
|
|
|
static struct log_msg *msg_from_fifo(const struct shell_log_backend *backend)
|
|
{
|
|
struct shell_log_backend_msg msg;
|
|
int err;
|
|
|
|
err = k_msgq_get(backend->msgq, &msg, K_NO_WAIT);
|
|
|
|
return (err == 0) ? msg.msg : NULL;
|
|
}
|
|
|
|
/* Set fifo clean state (in case of deferred mode). */
|
|
static void fifo_reset(const struct shell_log_backend *backend)
|
|
{
|
|
if (IS_ENABLED(CONFIG_LOG2_MODE_DEFERRED)) {
|
|
mpsc_pbuf_init(backend->mpsc_buffer,
|
|
backend->mpsc_buffer_config);
|
|
return;
|
|
}
|
|
|
|
/* Flush pending log messages without processing. */
|
|
if (IS_ENABLED(CONFIG_LOG_MODE_DEFERRED)) {
|
|
struct log_msg *msg;
|
|
|
|
while ((msg = msg_from_fifo(backend)) != NULL) {
|
|
log_msg_put(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void z_shell_log_backend_enable(const struct shell_log_backend *backend,
|
|
void *ctx, uint32_t init_log_level)
|
|
{
|
|
int err = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_LOG_IMMEDIATE)) {
|
|
const struct shell *shell;
|
|
|
|
shell = (const struct shell *)ctx;
|
|
|
|
/* Reenable transport in blocking mode */
|
|
err = shell->iface->api->enable(shell->iface, true);
|
|
}
|
|
|
|
if (err == 0) {
|
|
fifo_reset(backend);
|
|
log_backend_enable(backend->backend, ctx, init_log_level);
|
|
log_output_ctx_set(backend->log_output, ctx);
|
|
backend->control_block->dropped_cnt = 0;
|
|
backend->control_block->state = SHELL_LOG_BACKEND_ENABLED;
|
|
}
|
|
}
|
|
|
|
static void flush_expired_messages(const struct shell *shell)
|
|
{
|
|
int err;
|
|
struct shell_log_backend_msg msg;
|
|
struct k_msgq *msgq = shell->log_backend->msgq;
|
|
uint32_t timeout = shell->log_backend->timeout;
|
|
uint32_t now = k_uptime_get_32();
|
|
|
|
while (1) {
|
|
err = k_msgq_peek(msgq, &msg);
|
|
|
|
if (err == 0 && ((now - msg.timestamp) > timeout)) {
|
|
(void)k_msgq_get(msgq, &msg, K_NO_WAIT);
|
|
log_msg_put(msg.msg);
|
|
|
|
if (IS_ENABLED(CONFIG_SHELL_STATS)) {
|
|
atomic_inc(&shell->stats->log_lost_cnt);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void msg_to_fifo(const struct shell *shell,
|
|
struct log_msg *msg)
|
|
{
|
|
int err;
|
|
bool cont;
|
|
struct shell_log_backend_msg t_msg = {
|
|
.msg = msg,
|
|
.timestamp = k_uptime_get_32()
|
|
};
|
|
|
|
do {
|
|
cont = false;
|
|
err = k_msgq_put(shell->log_backend->msgq, &t_msg,
|
|
K_MSEC(shell->log_backend->timeout));
|
|
|
|
switch (err) {
|
|
case 0:
|
|
break;
|
|
case -EAGAIN:
|
|
case -ENOMSG:
|
|
{
|
|
/* Attempt to drop old message. */
|
|
flush_expired_messages(shell);
|
|
|
|
/* Retry putting message. */
|
|
cont = true;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
/* Other errors are not expected. */
|
|
__ASSERT_NO_MSG(0);
|
|
break;
|
|
}
|
|
} while (cont);
|
|
}
|
|
|
|
void z_shell_log_backend_disable(const struct shell_log_backend *backend)
|
|
{
|
|
log_backend_disable(backend->backend);
|
|
backend->control_block->state = SHELL_LOG_BACKEND_DISABLED;
|
|
}
|
|
|
|
static void msg_process(const struct log_output *log_output,
|
|
struct log_msg *msg, bool colors)
|
|
{
|
|
uint32_t flags = LOG_OUTPUT_FLAG_LEVEL |
|
|
LOG_OUTPUT_FLAG_TIMESTAMP |
|
|
LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP;
|
|
|
|
if (colors) {
|
|
flags |= LOG_OUTPUT_FLAG_COLORS;
|
|
}
|
|
|
|
log_output_msg_process(log_output, msg, flags);
|
|
log_msg_put(msg);
|
|
}
|
|
|
|
bool z_shell_log_backend_process(const struct shell_log_backend *backend)
|
|
{
|
|
const struct shell *shell =
|
|
(const struct shell *)backend->backend->cb->ctx;
|
|
uint32_t dropped;
|
|
bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
|
|
shell->ctx->internal.flags.use_colors;
|
|
|
|
dropped = atomic_set(&backend->control_block->dropped_cnt, 0);
|
|
if (dropped) {
|
|
struct shell_vt100_colors col;
|
|
|
|
if (colors) {
|
|
z_shell_vt100_colors_store(shell, &col);
|
|
z_shell_vt100_color_set(shell, SHELL_VT100_COLOR_RED);
|
|
}
|
|
|
|
log_output_dropped_process(backend->log_output, dropped);
|
|
|
|
if (colors) {
|
|
z_shell_vt100_colors_restore(shell, &col);
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_LOG2_MODE_DEFERRED)) {
|
|
return process_msg2_from_buffer(shell);
|
|
}
|
|
|
|
struct log_msg *msg = msg_from_fifo(backend);
|
|
|
|
if (!msg) {
|
|
return false;
|
|
}
|
|
msg_process(shell->log_backend->log_output, msg, colors);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void put(const struct log_backend *const backend, struct log_msg *msg)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
|
|
shell->ctx->internal.flags.use_colors;
|
|
struct k_poll_signal *signal;
|
|
|
|
log_msg_get(msg);
|
|
|
|
switch (shell->log_backend->control_block->state) {
|
|
case SHELL_LOG_BACKEND_ENABLED:
|
|
msg_to_fifo(shell, msg);
|
|
|
|
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
|
|
signal = &shell->ctx->signals[SHELL_SIGNAL_LOG_MSG];
|
|
k_poll_signal_raise(signal, 0);
|
|
}
|
|
|
|
break;
|
|
case SHELL_LOG_BACKEND_PANIC:
|
|
z_shell_cmd_line_erase(shell);
|
|
msg_process(shell->log_backend->log_output, msg, colors);
|
|
|
|
break;
|
|
|
|
case SHELL_LOG_BACKEND_DISABLED:
|
|
__fallthrough;
|
|
default:
|
|
/* Discard message. */
|
|
log_msg_put(msg);
|
|
}
|
|
}
|
|
|
|
static void put_sync_string(const struct log_backend *const backend,
|
|
struct log_msg_ids src_level, uint32_t timestamp,
|
|
const char *fmt, va_list ap)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
uint32_t key;
|
|
uint32_t flags = LOG_OUTPUT_FLAG_LEVEL |
|
|
LOG_OUTPUT_FLAG_TIMESTAMP |
|
|
LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP;
|
|
|
|
if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
|
|
flags |= LOG_OUTPUT_FLAG_COLORS;
|
|
}
|
|
|
|
key = irq_lock();
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_cmd_line_erase(shell);
|
|
}
|
|
log_output_string(shell->log_backend->log_output, src_level, timestamp,
|
|
fmt, ap, flags);
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_print_prompt_and_cmd(shell);
|
|
}
|
|
irq_unlock(key);
|
|
}
|
|
|
|
static void put_sync_hexdump(const struct log_backend *const backend,
|
|
struct log_msg_ids src_level, uint32_t timestamp,
|
|
const char *metadata, const uint8_t *data,
|
|
uint32_t length)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
uint32_t key;
|
|
uint32_t flags = LOG_OUTPUT_FLAG_LEVEL |
|
|
LOG_OUTPUT_FLAG_TIMESTAMP |
|
|
LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP;
|
|
|
|
if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) {
|
|
flags |= LOG_OUTPUT_FLAG_COLORS;
|
|
}
|
|
|
|
key = irq_lock();
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_cmd_line_erase(shell);
|
|
}
|
|
log_output_hexdump(shell->log_backend->log_output, src_level,
|
|
timestamp, metadata, data, length, flags);
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_print_prompt_and_cmd(shell);
|
|
}
|
|
irq_unlock(key);
|
|
}
|
|
|
|
static void panic(const struct log_backend *const backend)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
int err;
|
|
|
|
if (IS_ENABLED(CONFIG_LOG_IMMEDIATE)) {
|
|
return;
|
|
}
|
|
|
|
err = shell->iface->api->enable(shell->iface, true);
|
|
|
|
if (err == 0) {
|
|
shell->log_backend->control_block->state =
|
|
SHELL_LOG_BACKEND_PANIC;
|
|
|
|
/* Move to the start of next line. */
|
|
z_shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons,
|
|
shell->ctx->cmd_buff_pos,
|
|
shell->ctx->cmd_buff_len);
|
|
z_shell_op_cursor_vert_move(shell, -1);
|
|
z_shell_op_cursor_horiz_move(shell,
|
|
-shell->ctx->vt100_ctx.cons.cur_x);
|
|
|
|
if (IS_ENABLED(CONFIG_LOG2_MODE_DEFERRED)) {
|
|
while (process_msg2_from_buffer(shell)) {
|
|
/* empty */
|
|
}
|
|
} else if (IS_ENABLED(CONFIG_LOG_MODE_DEFERRED)) {
|
|
while (z_shell_log_backend_process(
|
|
shell->log_backend)) {
|
|
/* empty */
|
|
}
|
|
}
|
|
} else {
|
|
z_shell_log_backend_disable(shell->log_backend);
|
|
}
|
|
}
|
|
|
|
static void dropped(const struct log_backend *const backend, uint32_t cnt)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
const struct shell_log_backend *log_backend = shell->log_backend;
|
|
|
|
atomic_add(&shell->stats->log_lost_cnt, cnt);
|
|
atomic_add(&log_backend->control_block->dropped_cnt, cnt);
|
|
}
|
|
|
|
static void copy_to_pbuffer(struct mpsc_pbuf_buffer *mpsc_buffer,
|
|
union log_msg2_generic *msg, uint32_t timeout)
|
|
{
|
|
size_t wlen;
|
|
union mpsc_pbuf_generic *dst;
|
|
|
|
wlen = log_msg2_generic_get_wlen((union mpsc_pbuf_generic *)msg);
|
|
dst = mpsc_pbuf_alloc(mpsc_buffer, wlen, K_MSEC(timeout));
|
|
if (!dst) {
|
|
/* No space to store the log */
|
|
return;
|
|
}
|
|
|
|
/* First word contains intenal mpsc packet flags and when copying
|
|
* those flags must be omitted.
|
|
*/
|
|
uint8_t *dst_data = (uint8_t *)dst + sizeof(struct mpsc_pbuf_hdr);
|
|
uint8_t *src_data = (uint8_t *)msg + sizeof(struct mpsc_pbuf_hdr);
|
|
size_t hdr_wlen = ceiling_fraction(sizeof(struct mpsc_pbuf_hdr),
|
|
sizeof(uint32_t));
|
|
|
|
dst->hdr.data = msg->buf.hdr.data;
|
|
memcpy(dst_data, src_data, (wlen - hdr_wlen) * sizeof(uint32_t));
|
|
|
|
mpsc_pbuf_commit(mpsc_buffer, dst);
|
|
}
|
|
|
|
static void process_log_msg2(const struct shell *shell,
|
|
const struct log_output *log_output,
|
|
union log_msg2_generic *msg,
|
|
bool locked, bool colors)
|
|
{
|
|
unsigned int key;
|
|
uint32_t flags = LOG_OUTPUT_FLAG_LEVEL |
|
|
LOG_OUTPUT_FLAG_TIMESTAMP |
|
|
LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP;
|
|
|
|
if (colors) {
|
|
flags |= LOG_OUTPUT_FLAG_COLORS;
|
|
}
|
|
|
|
if (locked) {
|
|
key = irq_lock();
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_cmd_line_erase(shell);
|
|
}
|
|
}
|
|
|
|
log_output_msg2_process(log_output, &msg->log, flags);
|
|
|
|
if (locked) {
|
|
if (!z_flag_cmd_ctx_get(shell)) {
|
|
z_shell_print_prompt_and_cmd(shell);
|
|
}
|
|
irq_unlock(key);
|
|
}
|
|
}
|
|
|
|
static bool process_msg2_from_buffer(const struct shell *shell)
|
|
{
|
|
const struct shell_log_backend *log_backend = shell->log_backend;
|
|
struct mpsc_pbuf_buffer *mpsc_buffer = log_backend->mpsc_buffer;
|
|
const struct log_output *log_output = log_backend->log_output;
|
|
union log_msg2_generic *msg;
|
|
bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
|
|
shell->ctx->internal.flags.use_colors;
|
|
|
|
msg = (union log_msg2_generic *)mpsc_pbuf_claim(mpsc_buffer);
|
|
if (!msg) {
|
|
return false;
|
|
}
|
|
|
|
process_log_msg2(shell, log_output, msg, false, colors);
|
|
|
|
mpsc_pbuf_free(mpsc_buffer, &msg->buf);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void log2_process(const struct log_backend *const backend,
|
|
union log_msg2_generic *msg)
|
|
{
|
|
const struct shell *shell = (const struct shell *)backend->cb->ctx;
|
|
const struct shell_log_backend *log_backend = shell->log_backend;
|
|
struct mpsc_pbuf_buffer *mpsc_buffer = log_backend->mpsc_buffer;
|
|
const struct log_output *log_output = log_backend->log_output;
|
|
bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
|
|
shell->ctx->internal.flags.use_colors;
|
|
struct k_poll_signal *signal;
|
|
|
|
switch (shell->log_backend->control_block->state) {
|
|
case SHELL_LOG_BACKEND_ENABLED:
|
|
if (IS_ENABLED(CONFIG_LOG_IMMEDIATE)) {
|
|
process_log_msg2(shell, log_output, msg, true, colors);
|
|
} else {
|
|
copy_to_pbuffer(mpsc_buffer, msg,
|
|
log_backend->timeout);
|
|
|
|
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
|
|
signal =
|
|
&shell->ctx->signals[SHELL_SIGNAL_LOG_MSG];
|
|
k_poll_signal_raise(signal, 0);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SHELL_LOG_BACKEND_PANIC:
|
|
z_shell_cmd_line_erase(shell);
|
|
process_log_msg2(shell, log_output, msg, true, colors);
|
|
|
|
break;
|
|
|
|
case SHELL_LOG_BACKEND_DISABLED:
|
|
__fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const struct log_backend_api log_backend_shell_api = {
|
|
.process = IS_ENABLED(CONFIG_LOG2) ? log2_process : NULL,
|
|
.put = IS_ENABLED(CONFIG_LOG_MODE_DEFERRED) ? put : NULL,
|
|
.put_sync_string = IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE) ?
|
|
put_sync_string : NULL,
|
|
.put_sync_hexdump = IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE) ?
|
|
put_sync_hexdump : NULL,
|
|
.dropped = dropped,
|
|
.panic = panic,
|
|
};
|