blob: cd277cab1d6a1e7e3bd69c9aa591388255b6ff41 [file]
/*
* Copyright (c) 2026 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "zephyr/sys/__assert.h"
#include <zephyr/logging/log.h>
#include <stdlib.h>
#include <zephyr/shell/shell_remote_common.h>
#include <zephyr/shell/shell_remote.h>
LOG_MODULE_REGISTER(shell_remote, CONFIG_SHELL_LOG_LEVEL);
/* Send a message to the remote shell host. If ack is true, wait for acknowledgment.
* Block if the endpoint is not ready.
*/
static int ipc_send(struct shell_remote_data *shremote, const void *data, size_t len, bool ack)
{
int err;
if (shremote->ready == false) {
err = k_sem_take(&shremote->sem, K_MSEC(CONFIG_SHELL_REMOTE_TIMEOUT_MS));
if (err < 0) {
LOG_ERR("Failed to take semaphore");
return err;
}
shremote->ready = true;
}
err = ipc_service_send(&shremote->ept, data, len);
if (err < 0) {
LOG_ERR("Failed to send message");
return err;
}
if (ack) {
err = k_sem_take(&shremote->sem, K_MSEC(CONFIG_SHELL_REMOTE_TIMEOUT_MS));
if (err < 0) {
LOG_ERR("Failed to take semaphore");
return err;
}
}
return 0;
}
/* Take a print message, make a copy on heap and add it to the list which is processed
* with the work queue.
*/
static void msg_print_handle(struct shell_remote_data *shremote, struct shell_remote_msg_print *msg,
size_t len)
{
__ASSERT_NO_MSG(shremote->msg == NULL);
shremote->msg = k_aligned_alloc(8, len);
if (shremote->msg == NULL) {
LOG_ERR("Failed to allocate message with remote shell printing");
return;
}
shremote->msg_len = len;
memcpy(shremote->msg, msg, len);
k_work_submit(&shremote->work);
}
static void msg_cmd_handle(struct shell_remote_data *shremote,
const struct shell_remote_msg_cmd *msg, size_t len)
{
#define TRUNCATED_MSG "..."
bool truncated = false;
shremote->result = 0;
shremote->current_cmd->rem_cmd = msg->entry;
shremote->current_cmd->cmd.subcmd = msg->subcmd;
shremote->current_cmd->cmd.handler = msg->handler;
shremote->current_cmd->cmd.args.mandatory = msg->args.mandatory;
shremote->current_cmd->cmd.args.optional = msg->args.optional;
shremote->current_cmd->cmd.args.remote_cmd = SHELL_CMD_FLAG_REMOTE_SUBCMD;
size_t strings_len = len - offsetof(struct shell_remote_msg_cmd, data);
size_t syntax_len = strlen(msg->data);
if (strings_len > CONFIG_SHELL_REMOTE_TMP_BUF_SIZE) {
truncated = true;
strings_len = CONFIG_SHELL_REMOTE_TMP_BUF_SIZE;
}
memcpy(shremote->current_cmd->tmp_buf, msg->data, strings_len);
shremote->current_cmd->cmd.syntax = shremote->current_cmd->tmp_buf;
if (truncated) {
/* Truncate help message */
LOG_DBG("Truncated help message");
memcpy(&shremote->current_cmd
->tmp_buf[CONFIG_SHELL_REMOTE_TMP_BUF_SIZE - sizeof(TRUNCATED_MSG)],
TRUNCATED_MSG, sizeof(TRUNCATED_MSG));
}
__ASSERT_NO_MSG(syntax_len <
CONFIG_SHELL_REMOTE_TMP_BUF_SIZE - (sizeof(TRUNCATED_MSG) + 2));
shremote->current_cmd->cmd.help = &shremote->current_cmd->tmp_buf[syntax_len + 1];
k_sem_give(&shremote->sem);
}
static void msg_result_handle(struct shell_remote_data *shremote,
struct shell_remote_msg_result *msg)
{
/* Store result and signal to shell thread. */
shremote->result = msg->result;
k_sem_give(&shremote->sem);
}
void shell_remote_ep_recv(const void *data, size_t len, void *priv)
{
struct shell_remote_data *shremote = priv;
union shell_remote_msg msg;
msg.generic = (struct shell_remote_msg_generic *)data;
switch (msg.generic->id) {
case SHELL_REMOTE_MSG_CMD:
msg_cmd_handle(shremote, msg.cmd, len);
break;
case SHELL_REMOTE_MSG_PRINT:
msg_print_handle(shremote, msg.print, len);
break;
case SHELL_REMOTE_MSG_RESULT:
msg_result_handle(shremote, msg.result);
break;
default:
__ASSERT(0, "Unexpected message:%d", msg.generic->id);
}
}
static const struct shell_remote *get_remote(size_t id)
{
const struct shell_remote *shremote;
size_t cnt;
STRUCT_SECTION_COUNT(shell_remote, &cnt);
if (id >= cnt) {
return NULL;
}
STRUCT_SECTION_GET(shell_remote, id, &shremote);
return shremote;
}
static struct shell_remote_data *get_remote_data(size_t id)
{
const struct shell_remote *shremote = get_remote(id);
return shremote ? (struct shell_remote_data *)shremote->ep_cfg.priv : NULL;
}
const struct shell_static_entry *z_shell_remote_cmd_get(const struct shell_static_entry *parent,
size_t idx, struct shell_static_entry *dloc)
{
uint32_t remote_id = parent->args.remote_id;
struct shell_remote_data *shremote = get_remote_data(remote_id);
const struct shell_static_entry *rem_parent;
int err;
LOG_DBG("shremote %p current: %p, cmd0:%p, cmd1:%p", shremote, shremote->current_cmd,
&shremote->cmds[0], &shremote->cmds[1]);
LOG_DBG("parent: %p, syntax: %s, shremote->cmds[0].cmd.syntax: %s, "
"shremote->cmds[1].cmd.syntax: %s",
(void *)parent, parent->syntax ? parent->syntax : "NULL",
shremote->cmds[0].cmd.syntax ? shremote->cmds[0].cmd.syntax : "NULL",
shremote->cmds[1].cmd.syntax ? shremote->cmds[1].cmd.syntax : "NULL");
if (parent->args.remote_cmd == SHELL_CMD_FLAG_REMOTE_ROOT) {
rem_parent = NULL;
shremote->current_cmd = &shremote->cmds[0];
} else if (strcmp(parent->syntax, shremote->cmds[0].cmd.syntax) == 0) {
rem_parent = shremote->cmds[0].rem_cmd;
shremote->current_cmd = &shremote->cmds[1];
} else if (strcmp(parent->syntax, shremote->cmds[1].cmd.syntax) == 0) {
rem_parent = shremote->cmds[1].rem_cmd;
shremote->current_cmd = &shremote->cmds[0];
} else {
LOG_ERR("Unexpected parent %p with syntax:%s", parent, parent->syntax);
return NULL;
}
/* Inherit remote id for the current command. */
shremote->current_cmd->cmd.args.remote_id = remote_id;
LOG_DBG("Command get parent:%s idx:%d", parent->syntax, idx);
struct shell_remote_msg_cmd_get cmd_get = {
.id = SHELL_REMOTE_MSG_CMD_GET, .parent = rem_parent, .idx = idx};
err = ipc_send(shremote, &cmd_get, sizeof(cmd_get), true);
if (err < 0) {
LOG_ERR("Failed to send command get");
return NULL;
}
if (shremote->result != 0) {
if (shremote->result == -ENODEV) {
return NULL;
}
if (shremote->result == -ENOEXEC) {
dloc->syntax = "";
dloc->subcmd = NULL;
dloc->handler = NULL;
dloc->help = NULL;
return dloc;
}
__ASSERT_NO_MSG(0);
return NULL;
}
return &shremote->current_cmd->cmd;
}
int z_shell_remote_cmd_exec(const struct shell *sh, const struct shell_static_entry *cmd,
uint8_t argc, const char **argv, size_t cmd_lvl)
{
__ASSERT_NO_MSG(argc <= CONFIG_SHELL_ARGC_MAX);
uint32_t remote_id = cmd->args.remote_id;
struct shell_remote_data *shremote = get_remote_data(remote_id);
struct shell_remote_msg_exec *msg;
uint8_t *tmp_buf = shremote->cmds[0].tmp_buf;
uint8_t skip_argc = IS_ENABLED(CONFIG_SHELL_REMOTE_MULTI_CLI) ? 2 : 1;
int err;
size_t msg_len;
uint8_t *arg_buf;
size_t slen[argc];
shremote->sh = sh;
msg_len = offsetof(struct shell_remote_msg_exec, data);
/* Skip first two commands (remote_shell root command and remote name) */
for (uint8_t i = skip_argc; i < argc; i++) {
slen[i] = strlen(argv[i]);
msg_len += slen[i] + 1;
}
if (msg_len > CONFIG_SHELL_REMOTE_TMP_BUF_SIZE) {
return -ENOMEM;
}
msg = (struct shell_remote_msg_exec *)tmp_buf;
arg_buf = msg->data;
msg->id = SHELL_REMOTE_MSG_EXEC;
msg->argc = argc - skip_argc;
msg->cmd_lvl = cmd_lvl - skip_argc;
msg->handler = cmd->handler;
for (uint8_t i = skip_argc; i < argc; i++) {
memcpy(arg_buf, argv[i], slen[i]);
arg_buf[slen[i]] = '\0';
arg_buf += slen[i] + 1;
}
err = ipc_send(shremote, tmp_buf, msg_len, true);
if (err < 0) {
LOG_ERR("Failed to send exec command");
return err;
}
return shremote->result;
}
static void ack_result(struct shell_remote_data *shremote)
{
static const struct shell_remote_msg_result rsp = {.id = SHELL_REMOTE_MSG_RESULT,
.result = 0};
int err;
err = ipc_send(shremote, &rsp, sizeof(rsp), false);
if (err < 0) {
LOG_ERR("Failed to send result");
}
}
static void print_process(struct k_work *work)
{
struct shell_remote_data *shremote = CONTAINER_OF(work, struct shell_remote_data, work);
struct shell_remote_msg_print *msg = (struct shell_remote_msg_print *)shremote->msg;
void *data;
__ASSERT_NO_MSG(shremote->msg != NULL);
data = (void *)ROUND_UP((uintptr_t)msg->data, sizeof(uint64_t));
shell_cbpprintf(shremote->sh, msg->color, data);
k_free(shremote->msg);
shremote->msg = NULL;
shremote->msg_len = 0;
ack_result(shremote);
}
void shell_remote_ep_bound(void *priv)
{
struct shell_remote_data *remote_data = priv;
k_sem_give(&remote_data->sem);
}
static int shell_remote_init(void)
{
int ret;
STRUCT_SECTION_FOREACH(shell_remote, remote) {
struct shell_remote_data *data = remote->ep_cfg.priv;
k_sem_init(&data->sem, 0, 1);
ret = ipc_service_open_instance(remote->ipc);
if ((ret < 0) && (ret != -EALREADY)) {
LOG_ERR("ipc_service_open_instance() failure (remote:%s)", remote->name);
return ret;
}
ret = ipc_service_register_endpoint(remote->ipc, &data->ept, &remote->ep_cfg);
if (ret < 0) {
LOG_ERR("ipc_service_register_endpoint() failure (remote: %s)",
remote->name);
return ret;
}
k_work_init(&data->work, print_process);
LOG_DBG("shell remote initialized (remote: %s dev:%s).", remote->name,
remote->ipc->name);
}
return 0;
}
SYS_INIT(shell_remote_init, APPLICATION, 0);
#ifdef CONFIG_SHELL_REMOTE_MULTI_CLI
static void dynamic_cmd_get(size_t idx, struct shell_static_entry *entry)
{
const struct shell_remote *remote = get_remote(idx);
if (remote == NULL) {
entry->syntax = NULL;
return;
}
entry->syntax = remote->name;
entry->handler = NULL;
entry->subcmd = NULL;
entry->help = NULL;
entry->args.remote_cmd = SHELL_CMD_FLAG_REMOTE_ROOT;
entry->args.remote_id = idx;
}
SHELL_DYNAMIC_CMD_CREATE(sub_remote_shell, dynamic_cmd_get);
SHELL_CMD_REGISTER(remote_shell, &sub_remote_shell, "Execute commands on remote shell", NULL);
#else
/* First check if the chosen node is available then if there is only a single IPC device
* available use that one.
*/
#if DT_HAS_CHOSEN(zephyr_ipc_shell)
#define IPC_NODE DT_CHOSEN(zephyr_ipc_shell)
#elif DT_NUM_INST_STATUS_OKAY(zephyr_ipc_icbmsg) == 1
#define IPC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_ipc_icbmsg)
#elif DT_NUM_INST_STATUS_OKAY(zephyr_ipc_icmsg) == 1
#define IPC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_ipc_icmsg)
#elif DT_NUM_INST_STATUS_OKAY(zephyr_ipc_openamp_static_vrings) == 1
#define IPC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_ipc_openamp_static_vrings)
#else
#error "No IPC node found"
#endif
SHELL_REMOTE_CONN(remote, IPC_NODE);
/* Manually register root command with remote_root flag. */
static const struct shell_static_entry remote_shell =
SHELL_EXPR_CMD_ARG(1, remote_shell, NULL, "Execute commands on remote shell", NULL, 0, 0,
SHELL_CMD_FLAG_REMOTE_ROOT, 0);
static const TYPE_SECTION_ITERABLE(union shell_cmd_entry, shell_cmd_remote_shell, shell_root_cmds,
shell_cmd_remote_shell) = {.entry = &remote_shell};
#endif