zephyr/subsys/shell/shell_log_backend.c
Krzysztof Chruscinski 2e6892c9b0 shell: Add handling of CONFIG_LOG_INPLACE_PROCESS option
Extended shell to be able to process logs in place
(in the context of a log call). In order to achieve that,
shell was extended to  support for TX blocking operations. If
CONFIG_LOG_INPLACE_PROCESS is enabled then shell instance
attempts to be initialized in blocking TX mode. If fails to
do so, shell log backend is disabled. If successfully enabled
logs are processed and printed in the context of the call.

Due to that change, user may expirience interleaved output as
shell has no means to multiplex shell output with logger output.
In extreme, huge amount of log messages may prevent shell thread
execution and shell may become unresponsive.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
2019-02-05 09:55:39 +01:00

311 lines
7.5 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>
int shell_log_backend_output_func(u8_t *data, size_t length, void *ctx)
{
shell_print_stream(ctx, data, length);
return length;
}
void shell_log_backend_enable(const struct shell_log_backend *backend,
void *ctx, u32_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) {
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 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;
}
static void fifo_flush(const struct shell_log_backend *backend)
{
struct log_msg *msg;
/* Flush log messages. */
while ((msg = msg_from_fifo(backend)) != NULL) {
log_msg_put(msg);
}
}
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;
u32_t timeout = shell->log_backend->timeout;
u32_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)) {
shell->stats->log_lost_cnt++;
}
} else {
break;
}
}
}
static void msg_to_fifo(const struct shell *shell,
struct log_msg *msg)
{
int err;
struct shell_log_backend_msg t_msg = {
.msg = msg,
.timestamp = k_uptime_get_32()
};
err = k_msgq_put(shell->log_backend->msgq, &t_msg,
shell->log_backend->timeout);
switch (err) {
case 0:
break;
case -EAGAIN:
case -ENOMSG:
{
flush_expired_messages(shell);
err = k_msgq_put(shell->log_backend->msgq, &msg, K_NO_WAIT);
if (err) {
/* Unexpected case as we just freed one element and
* there is no other context that puts into the msgq.
*/
__ASSERT_NO_MSG(0);
}
break;
}
default:
/* Other errors are not expected. */
__ASSERT_NO_MSG(0);
break;
}
}
void shell_log_backend_disable(const struct shell_log_backend *backend)
{
fifo_flush(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)
{
u32_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 shell_log_backend_process(const struct shell_log_backend *backend)
{
u32_t dropped;
const struct shell *shell =
(const struct shell *)backend->backend->cb->ctx;
bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
shell->ctx->internal.flags.use_colors;
struct log_msg *msg = msg_from_fifo(backend);
if (!msg) {
return false;
}
dropped = atomic_set(&backend->control_block->dropped_cnt, 0);
if (dropped) {
struct shell_vt100_colors col;
if (colors) {
shell_vt100_colors_store(shell, &col);
shell_vt100_color_set(shell, SHELL_VT100_COLOR_RED);
}
log_output_dropped_process(backend->log_output, dropped);
if (colors) {
shell_vt100_colors_restore(shell, &col);
}
}
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:
shell_cmd_line_erase(shell);
msg_process(shell->log_backend->log_output, msg, colors);
break;
case SHELL_LOG_BACKEND_DISABLED:
/* fall through */
/* no break */
default:
/* Discard message. */
log_msg_put(msg);
}
}
static void put_sync_string(const struct log_backend *const backend,
struct log_msg_ids src_level, u32_t timestamp,
const char *fmt, va_list ap)
{
const struct shell *shell = (const struct shell *)backend->cb->ctx;
u32_t key;
u32_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();
shell_cmd_line_erase(shell);
log_output_string(shell->log_backend->log_output, src_level, timestamp,
fmt, ap, flags);
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, u32_t timestamp,
const char *metadata, const u8_t *data, u32_t length)
{
const struct shell *shell = (const struct shell *)backend->cb->ctx;
struct k_poll_signal *signal;
u32_t key;
u32_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();
shell_cmd_line_erase(shell);
log_output_hexdump(shell->log_backend->log_output, src_level, timestamp,
metadata, data, length, flags);
irq_unlock(key);
/* Even though log message is handled notify shell thread which
* will print command buffer after the log message.
*/
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
signal = &shell->ctx->signals[SHELL_SIGNAL_LOG_MSG];
k_poll_signal_raise(signal, 0);
}
}
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. */
shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons,
shell->ctx->cmd_buff_pos,
shell->ctx->cmd_buff_len);
shell_op_cursor_vert_move(shell, -1);
shell_op_cursor_horiz_move(shell,
-shell->ctx->vt100_ctx.cons.cur_x);
while (shell_log_backend_process(shell->log_backend)) {
/* empty */
}
} else {
shell_log_backend_disable(shell->log_backend);
}
}
static void dropped(const struct log_backend *const backend, u32_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);
}
const struct log_backend_api log_backend_shell_api = {
.put = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ? NULL : put,
.put_sync_string = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ?
put_sync_string : NULL,
.put_sync_hexdump = IS_ENABLED(CONFIG_LOG_IMMEDIATE) ?
put_sync_hexdump : NULL,
.dropped = dropped,
.panic = panic,
};