shell: backends: Add RPMsg shell backend

Introduce a backend for the Shell subsystem using a RPMsg endpoint.

This is useful for tooling & testing a remote processor from linux.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
This commit is contained in:
Pieter De Gendt 2024-02-07 17:02:53 +01:00 committed by Alberto Escolar
commit df7750f63a
4 changed files with 379 additions and 0 deletions

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2024 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef SHELL_RPMSG_H__
#define SHELL_RPMSG_H__
#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <openamp/rpmsg.h>
#ifdef __cplusplus
extern "C" {
#endif
extern const struct shell_transport_api shell_rpmsg_transport_api;
/** RPMsg received message placeholder */
struct shell_rpmsg_rx {
/** Pointer to the data held by RPMsg endpoint */
void *data;
/** The length of the data */
size_t len;
};
/** RPMsg-based shell transport. */
struct shell_rpmsg {
/** Handler function registered by shell. */
shell_transport_handler_t shell_handler;
/** Context registered by shell. */
void *shell_context;
/** Indicator if we are ready to read/write */
bool ready;
/** Setting for blocking mode */
bool blocking;
/** RPMsg endpoint */
struct rpmsg_endpoint ept;
/** Queue for received data. */
struct k_msgq rx_q;
/** Buffer for received messages */
struct shell_rpmsg_rx rx_buf[CONFIG_SHELL_RPMSG_MAX_RX];
/** The current rx message */
struct shell_rpmsg_rx rx_cur;
/** The number of bytes consumed from rx_cur */
size_t rx_consumed;
};
#define SHELL_RPMSG_DEFINE(_name) \
static struct shell_rpmsg _name##_shell_rpmsg; \
struct shell_transport _name = { \
.api = &shell_rpmsg_transport_api, \
.ctx = (struct shell_rpmsg *)&_name##_shell_rpmsg, \
}
/**
* @brief Initialize the Shell backend using the provided @p rpmsg_dev device.
*
* @param rpmsg_dev A pointer to an RPMsg device
* @return 0 on success or a negative value on error
*/
int shell_backend_rpmsg_init_transport(struct rpmsg_device *rpmsg_dev);
/**
* @brief This function provides pointer to shell RPMsg backend instance.
*
* Function returns pointer to the shell RPMsg instance. This instance can be
* next used with shell_execute_cmd function in order to test commands behavior.
*
* @returns Pointer to the shell instance.
*/
const struct shell *shell_backend_rpmsg_get_ptr(void);
#ifdef __cplusplus
}
#endif
#endif /* SHELL_RPMSG_H__ */

View file

@ -24,3 +24,8 @@ zephyr_sources_ifdef(
CONFIG_SHELL_BACKEND_MQTT
shell_mqtt.c
)
zephyr_sources_ifdef(
CONFIG_SHELL_BACKEND_RPMSG
shell_rpmsg.c
)

View file

@ -354,6 +354,91 @@ source "subsys/logging/Kconfig.template.log_config"
endif # SHELL_BACKEND_MQTT
config SHELL_BACKEND_RPMSG
bool "RPMsg backend."
depends on OPENAMP
help
Enable RPMsg backend.
if SHELL_BACKEND_RPMSG
config SHELL_PROMPT_RPMSG
string "Displayed prompt name"
default "ipc:~$ "
help
Displayed prompt name for RPMsg backend. If prompt is set, the shell will
send two newlines during initialization.
config SHELL_RPMSG_SERVICE_NAME
string "Service name"
default "rpmsg-tty"
help
The service name associated with the RPMsg endpoint.
config SHELL_RPMSG_SRC_ADDR
hex "Local address"
default 0xffffffff # The ANY address
help
Local address of the RPMsg endpoint.
config SHELL_RPMSG_DST_ADDR
hex "Remote address"
default 0xffffffff # The ANY address
help
Target address of the RPMsg endpoint.
config SHELL_RPMSG_MAX_RX
int "Receive buffer size"
default 10
help
The maximum number of received messages to be queued.
module = SHELL_BACKEND_RPMSG
default-timeout = 100
source "subsys/shell/Kconfig.template.shell_log_queue_timeout"
default-size = 512
source "subsys/shell/Kconfig.template.shell_log_queue_size"
choice
prompt "Initial log level limit"
default SHELL_RPMSG_INIT_LOG_LEVEL_DEFAULT
config SHELL_RPMSG_INIT_LOG_LEVEL_DEFAULT
bool "System limit (LOG_MAX_LEVEL)"
config SHELL_RPMSG_INIT_LOG_LEVEL_DBG
bool "Debug"
config SHELL_RPMSG_INIT_LOG_LEVEL_INF
bool "Info"
config SHELL_RPMSG_INIT_LOG_LEVEL_WRN
bool "Warning"
config SHELL_RPMSG_INIT_LOG_LEVEL_ERR
bool "Error"
config SHELL_RPMSG_INIT_LOG_LEVEL_NONE
bool "None"
endchoice # SHELL_RPMSG_INIT_LOG_LEVEL
config SHELL_RPMSG_INIT_LOG_LEVEL
int
default 0 if SHELL_RPMSG_INIT_LOG_LEVEL_NONE
default 1 if SHELL_RPMSG_INIT_LOG_LEVEL_ERR
default 2 if SHELL_RPMSG_INIT_LOG_LEVEL_WRN
default 3 if SHELL_RPMSG_INIT_LOG_LEVEL_INF
default 4 if SHELL_RPMSG_INIT_LOG_LEVEL_DBG
default 5 if SHELL_RPMSG_INIT_LOG_LEVEL_DEFAULT
module = SHELL_RPMSG
module-str = RPMsg shell backend
source "subsys/logging/Kconfig.template.log_config"
endif # SHELL_BACKEND_RPMSG
config SHELL_BACKEND_TELNET
bool "TELNET backend."
depends on NET_TCP

View file

@ -0,0 +1,202 @@
/*
* Copyright (c) 2024 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/shell/shell_rpmsg.h>
SHELL_RPMSG_DEFINE(shell_transport_rpmsg);
SHELL_DEFINE(shell_rpmsg, CONFIG_SHELL_PROMPT_RPMSG, &shell_transport_rpmsg,
CONFIG_SHELL_BACKEND_RPMSG_LOG_MESSAGE_QUEUE_SIZE,
CONFIG_SHELL_BACKEND_RPMSG_LOG_MESSAGE_QUEUE_TIMEOUT, SHELL_FLAG_OLF_CRLF);
static int rpmsg_shell_cb(struct rpmsg_endpoint *ept, void *data,
size_t len, uint32_t src, void *priv)
{
const struct shell_transport *transport = (const struct shell_transport *)priv;
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
struct shell_rpmsg_rx rx;
if (len == 0) {
return RPMSG_ERR_NO_BUFF;
}
rx.data = data;
rx.len = len;
if (k_msgq_put(&sh_rpmsg->rx_q, &rx, K_NO_WAIT) != 0) {
return RPMSG_ERR_NO_MEM;
}
rpmsg_hold_rx_buffer(ept, data);
sh_rpmsg->shell_handler(SHELL_TRANSPORT_EVT_RX_RDY, sh_rpmsg->shell_context);
return RPMSG_SUCCESS;
}
static int uninit(const struct shell_transport *transport)
{
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
if (!sh_rpmsg->ready) {
return -ENODEV;
}
rpmsg_destroy_ept(&sh_rpmsg->ept);
sh_rpmsg->ready = false;
return 0;
}
static int init(const struct shell_transport *transport,
const void *config,
shell_transport_handler_t evt_handler,
void *context)
{
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
struct rpmsg_device *rdev;
int ret;
if (sh_rpmsg->ready) {
return -EALREADY;
}
if (config == NULL) {
return -EINVAL;
}
rdev = (struct rpmsg_device *)config;
k_msgq_init(&sh_rpmsg->rx_q, (char *)sh_rpmsg->rx_buf, sizeof(struct shell_rpmsg_rx),
CONFIG_SHELL_RPMSG_MAX_RX);
ret = rpmsg_create_ept(&sh_rpmsg->ept, rdev, CONFIG_SHELL_RPMSG_SERVICE_NAME,
CONFIG_SHELL_RPMSG_SRC_ADDR, CONFIG_SHELL_RPMSG_DST_ADDR,
rpmsg_shell_cb, NULL);
if (ret < 0) {
return ret;
}
sh_rpmsg->ept.priv = (void *)transport;
sh_rpmsg->shell_handler = evt_handler;
sh_rpmsg->shell_context = context;
sh_rpmsg->ready = true;
return 0;
}
static int enable(const struct shell_transport *transport, bool blocking)
{
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
if (!sh_rpmsg->ready) {
return -ENODEV;
}
sh_rpmsg->blocking = blocking;
return 0;
}
static int write(const struct shell_transport *transport,
const void *data, size_t length, size_t *cnt)
{
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
int ret;
*cnt = 0;
if (!sh_rpmsg->ready) {
return -ENODEV;
}
if (sh_rpmsg->blocking) {
ret = rpmsg_send(&sh_rpmsg->ept, data, (int)length);
} else {
ret = rpmsg_trysend(&sh_rpmsg->ept, data, (int)length);
}
/* Set TX ready in any case, as we have no way to recover otherwise */
sh_rpmsg->shell_handler(SHELL_TRANSPORT_EVT_TX_RDY, sh_rpmsg->shell_context);
if (ret < 0) {
return ret;
}
*cnt = (size_t)ret;
return 0;
}
static int read(const struct shell_transport *transport,
void *data, size_t length, size_t *cnt)
{
struct shell_rpmsg *sh_rpmsg = (struct shell_rpmsg *)transport->ctx;
struct shell_rpmsg_rx *rx = &sh_rpmsg->rx_cur;
size_t read_len;
bool release = true;
if (!sh_rpmsg->ready) {
return -ENODEV;
}
/* Check if we still have pending data */
if (rx->data == NULL) {
int ret = k_msgq_get(&sh_rpmsg->rx_q, rx, K_NO_WAIT);
if (ret < 0) {
rx->data = NULL;
goto no_data;
}
__ASSERT_NO_MSG(rx->len > 0);
sh_rpmsg->rx_consumed = 0;
}
__ASSERT_NO_MSG(rx->len > sh_rpmsg->rx_consumed);
read_len = rx->len - sh_rpmsg->rx_consumed;
if (read_len > length) {
read_len = length;
release = false;
}
*cnt = read_len;
memcpy(data, &((char *)rx->data)[sh_rpmsg->rx_consumed], read_len);
if (release) {
rpmsg_release_rx_buffer(&sh_rpmsg->ept, rx->data);
rx->data = NULL;
} else {
sh_rpmsg->rx_consumed += read_len;
}
return 0;
no_data:
*cnt = 0;
return 0;
}
const struct shell_transport_api shell_rpmsg_transport_api = {
.init = init,
.uninit = uninit,
.enable = enable,
.read = read,
.write = write,
};
int shell_backend_rpmsg_init_transport(struct rpmsg_device *rpmsg_dev)
{
bool log_backend = CONFIG_SHELL_RPMSG_INIT_LOG_LEVEL > 0;
uint32_t level = (CONFIG_SHELL_RPMSG_INIT_LOG_LEVEL > LOG_LEVEL_DBG) ?
CONFIG_LOG_MAX_LEVEL : CONFIG_SHELL_RPMSG_INIT_LOG_LEVEL;
static const struct shell_backend_config_flags cfg_flags =
SHELL_DEFAULT_BACKEND_CONFIG_FLAGS;
return shell_init(&shell_rpmsg, rpmsg_dev, cfg_flags, log_backend, level);
}
const struct shell *shell_backend_rpmsg_get_ptr(void)
{
return &shell_rpmsg;
}