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>
diff --git a/include/zephyr/shell/shell_rpmsg.h b/include/zephyr/shell/shell_rpmsg.h
new file mode 100644
index 0000000..7a79c74
--- /dev/null
+++ b/include/zephyr/shell/shell_rpmsg.h
@@ -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__ */
diff --git a/subsys/shell/backends/CMakeLists.txt b/subsys/shell/backends/CMakeLists.txt
index f6d87f2..7711996 100644
--- a/subsys/shell/backends/CMakeLists.txt
+++ b/subsys/shell/backends/CMakeLists.txt
@@ -24,3 +24,8 @@
CONFIG_SHELL_BACKEND_MQTT
shell_mqtt.c
)
+
+zephyr_sources_ifdef(
+ CONFIG_SHELL_BACKEND_RPMSG
+ shell_rpmsg.c
+)
diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends
index f4c245c..e47e3e0 100644
--- a/subsys/shell/backends/Kconfig.backends
+++ b/subsys/shell/backends/Kconfig.backends
@@ -354,6 +354,91 @@
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
diff --git a/subsys/shell/backends/shell_rpmsg.c b/subsys/shell/backends/shell_rpmsg.c
new file mode 100644
index 0000000..846b73a
--- /dev/null
+++ b/subsys/shell/backends/shell_rpmsg.c
@@ -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;
+}