| /* |
| * 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 |