diff --git a/doc/kernel/services/threads/nothread.rst b/doc/kernel/services/threads/nothread.rst index 463ebefbd843a..64ced95fd67fc 100644 --- a/doc/kernel/services/threads/nothread.rst +++ b/doc/kernel/services/threads/nothread.rst @@ -119,3 +119,18 @@ peripheral drivers. *List/table of supported drivers to go here, including which API options are supported* + +Shell +===== + +The :ref:`shell_api` along with a subset of backends are expected to work +given the application calls :c:func:`shell_start` to start the shell +backend, and intermittently calls :c:func:`shell_process` to check for and +process new input. + +The following backends are supported: + +* Serial shell backend :kconfig:option:`CONFIG_SHELL_BACKEND_SERIAL` + configured to use either + :kconfig:option:`CONFIG_SHELL_BACKEND_SERIAL_API_INTERRUPT_DRIVEN` or + :kconfig:option:`CONFIG_SHELL_BACKEND_SERIAL_API_ASYNC` diff --git a/include/zephyr/shell/shell.h b/include/zephyr/shell/shell.h index ac0f9eaae3a0e..736d9497f6cd4 100644 --- a/include/zephyr/shell/shell.h +++ b/include/zephyr/shell/shell.h @@ -900,11 +900,10 @@ union shell_backend_ctx { }; enum shell_signal { - SHELL_SIGNAL_RXRDY, - SHELL_SIGNAL_LOG_MSG, - SHELL_SIGNAL_KILL, - SHELL_SIGNAL_TXDONE, /* TXDONE must be last one before SHELL_SIGNALS */ - SHELL_SIGNALS + SHELL_SIGNAL_RXRDY = BIT(0), + SHELL_SIGNAL_LOG_MSG = BIT(1), + SHELL_SIGNAL_KILL = BIT(2), + SHELL_SIGNAL_TXDONE = BIT(3), }; /** @@ -962,15 +961,11 @@ struct shell_ctx { volatile union shell_backend_cfg cfg; volatile union shell_backend_ctx ctx; - struct k_poll_signal signals[SHELL_SIGNALS]; - - /** Events that should be used only internally by shell thread. - * Event for SHELL_SIGNAL_TXDONE is initialized but unused. - */ - struct k_poll_event events[SHELL_SIGNALS]; - - struct k_mutex wr_mtx; +#if CONFIG_MULTITHREADING + struct k_event signal_event; + struct k_sem lock_sem; k_tid_t tid; +#endif int ret_val; }; @@ -1006,13 +1001,29 @@ struct shell { LOG_INSTANCE_PTR_DECLARE(log); const char *name; + +#if CONFIG_MULTITHREADING struct k_thread *thread; k_thread_stack_t *stack; +#endif }; extern void z_shell_print_stream(const void *user_ctx, const char *data, size_t data_len); +#if CONFIG_MULTITHREADING +#define Z_SHELL_THREAD_DEFINE(_name) \ + static K_KERNEL_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE); \ + static struct k_thread _name##_thread + +#define Z_SHELL_THREAD_INIT(_name) \ + .thread = &_name##_thread, \ + .stack = _name##_stack, +#else +#define Z_SHELL_THREAD_DEFINE(_name) +#define Z_SHELL_THREAD_INIT(_name) +#endif + /** @brief Internal macro for defining a shell instance. * * As it does not create the default shell logging backend it allows to use @@ -1033,8 +1044,7 @@ extern void z_shell_print_stream(const void *user_ctx, const char *data, IS_ENABLED(CONFIG_SHELL_PRINTF_AUTOFLUSH), z_shell_print_stream); \ LOG_INSTANCE_REGISTER(shell, _name, CONFIG_SHELL_LOG_LEVEL); \ Z_SHELL_STATS_DEFINE(_name); \ - static K_KERNEL_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE); \ - static struct k_thread _name##_thread; \ + Z_SHELL_THREAD_DEFINE(_name); \ static const STRUCT_SECTION_ITERABLE(shell, _name) = { \ .default_prompt = _prompt, \ .iface = _transport_iface, \ @@ -1044,8 +1054,9 @@ extern void z_shell_print_stream(const void *user_ctx, const char *data, .fprintf_ctx = &_name##_fprintf, \ .stats = Z_SHELL_STATS_PTR(_name), \ .log_backend = _log_backend, \ - LOG_INSTANCE_PTR_INIT(log, shell, _name).name = \ - STRINGIFY(_name), .thread = &_name##_thread, .stack = _name##_stack} + LOG_INSTANCE_PTR_INIT(log, shell, _name).name = STRINGIFY(_name), \ + Z_SHELL_THREAD_INIT(_name) \ + } /** * @brief Macro for defining a shell instance. diff --git a/samples/subsys/shell/threadless/CMakeLists.txt b/samples/subsys/shell/threadless/CMakeLists.txt new file mode 100644 index 0000000000000..38c121b79b29c --- /dev/null +++ b/samples/subsys/shell/threadless/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(shell_threadless) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/shell/threadless/README.rst b/samples/subsys/shell/threadless/README.rst new file mode 100644 index 0000000000000..9ecdbfea559a9 --- /dev/null +++ b/samples/subsys/shell/threadless/README.rst @@ -0,0 +1,43 @@ +.. zephyr:code-sample:: threadless_shell + :name: Threadless shell + + Run shell in single threaded build + +Overview +******** + +Demonstrate how to configure and use a shell in a single threaded build +(:kconfig:option:`CONFIG_MULTITHREADING` disabled). The sample uses the +UART shell backend using either the async +(:kconfig:option:`CONFIG_UART_ASYNC_API`) or interrupt driven +(:kconfig:option:`CONFIG_UART_INTERRUPT_DRIVEN`) APIs. The sample +implements a single command, which simply echoes back the single +argument passed to it, along with the built-in "help" command. + +.. code-block:: console + + uart:~$ sample foobar + foobar + uart:~$ + +Building and Running +******************** + +This application can be built and executed as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/shell/threadless + :host-os: unix + :board: nrf54l15dk/nrf54l15/cpuapp + :goals: run + :compact: + +To build for another board, change "nrf54l15dk/nrf54l15/cpuapp" above +to that board's name. + +Sample Output +************* + +.. code-block:: console + + uart:~$ diff --git a/samples/subsys/shell/threadless/prj.conf b/samples/subsys/shell/threadless/prj.conf new file mode 100644 index 0000000000000..f97ee14e527a1 --- /dev/null +++ b/samples/subsys/shell/threadless/prj.conf @@ -0,0 +1,3 @@ +CONFIG_MULTITHREADING=n +CONFIG_SHELL=y +CONFIG_SHELL_MINIMAL=y diff --git a/samples/subsys/shell/threadless/sample.yaml b/samples/subsys/shell/threadless/sample.yaml new file mode 100644 index 0000000000000..3438c1ddaf5b1 --- /dev/null +++ b/samples/subsys/shell/threadless/sample.yaml @@ -0,0 +1,18 @@ +sample: + description: Threadless shell using uart backend + name: Threadless shell +tests: + sample.subsys.shell.threadless: + tags: threadless + min_ram: 2 + min_flash: 32 + filter: CONFIG_ARCH_HAS_SINGLE_THREAD_SUPPORT and + dt_chosen_enabled("zephyr,shell-uart") + harness: shell + harness_config: + shell_commands: + - command: "sample foobar" + expected: "foobar" + platform_exclude: + - qemu_cortex_a53 + - qemu_cortex_a53/qemu_cortex_a53/smp diff --git a/samples/subsys/shell/threadless/src/main.c b/samples/subsys/shell/threadless/src/main.c new file mode 100644 index 0000000000000..0bc9d056adc26 --- /dev/null +++ b/samples/subsys/shell/threadless/src/main.c @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static int sample_sh_echo(const struct shell *sh, size_t argc, char **argv) +{ + shell_print(sh, "%s", argv[1]); + return 0; +} + +SHELL_CMD_ARG_REGISTER(sample, NULL, "Echo", sample_sh_echo, 2, 2); + +int main(void) +{ + /* + * Start the shell. This prints the shell prompt and enables + * input reception. + */ + shell_start(shell_backend_uart_get_ptr()); + + while (1) { + /* Check for new input and process it if present */ + shell_process(shell_backend_uart_get_ptr()); + } + + return 0; +} diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index bf2cd43264c36..6e93489c0c410 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -8,7 +8,7 @@ menuconfig SHELL bool "Shell" imply LOG_RUNTIME_FILTERING - select POLL + select EVENTS if MULTITHREADING if SHELL diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index eb38d5d01981e..e98a96d2cb100 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -562,11 +562,11 @@ static int exec_cmd(const struct shell *sh, size_t argc, const char **argv, /* Unlock thread mutex in case command would like to borrow * shell context to other thread to avoid mutex deadlock. */ - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); ret_val = sh->ctx->active_cmd.handler(sh, argc, (char **)argv); /* Bring back mutex to shell thread. */ - k_mutex_lock(&sh->ctx->wr_mtx, K_FOREVER); + z_shell_lock(sh); z_flag_cmd_ctx_set(sh, false); } @@ -1171,19 +1171,24 @@ static void state_collect(const struct shell *sh) static void transport_evt_handler(enum shell_transport_evt evt_type, void *ctx) { struct shell *sh = (struct shell *)ctx; - struct k_poll_signal *signal; - signal = (evt_type == SHELL_TRANSPORT_EVT_RX_RDY) ? - &sh->ctx->signals[SHELL_SIGNAL_RXRDY] : - &sh->ctx->signals[SHELL_SIGNAL_TXDONE]; - k_poll_signal_raise(signal, 0); +#if CONFIG_MULTITHREADING + enum shell_signal sig = evt_type == SHELL_TRANSPORT_EVT_RX_RDY + ? SHELL_SIGNAL_RXRDY + : SHELL_SIGNAL_TXDONE; + + k_event_post(&sh->ctx->signal_event, sig); +#endif + + if (evt_type == SHELL_TRANSPORT_EVT_TX_RDY) { + z_flag_tx_rdy_set(sh, true); + } } +#if CONFIG_MULTITHREADING static void shell_log_process(const struct shell *sh) { bool processed = false; - int signaled = 0; - int result; do { if (!IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE)) { @@ -1193,9 +1198,6 @@ static void shell_log_process(const struct shell *sh) sh->log_backend); } - struct k_poll_signal *signal = - &sh->ctx->signals[SHELL_SIGNAL_RXRDY]; - z_shell_print_prompt_and_cmd(sh); /* Arbitrary delay added to ensure that prompt is @@ -1205,10 +1207,9 @@ static void shell_log_process(const struct shell *sh) k_sleep(K_MSEC(15)); } - k_poll_signal_check(signal, &signaled, &result); - - } while (processed && !signaled); + } while (processed && !k_event_test(&sh->ctx->signal_event, SHELL_SIGNAL_RXRDY)); } +#endif static int instance_init(const struct shell *sh, const void *transport_config, @@ -1224,15 +1225,10 @@ static int instance_init(const struct shell *sh, history_init(sh); - k_mutex_init(&sh->ctx->wr_mtx); - - for (int i = 0; i < SHELL_SIGNALS; i++) { - k_poll_signal_init(&sh->ctx->signals[i]); - k_poll_event_init(&sh->ctx->events[i], - K_POLL_TYPE_SIGNAL, - K_POLL_MODE_NOTIFY_ONLY, - &sh->ctx->signals[i]); - } +#if CONFIG_MULTITHREADING + k_event_init(&sh->ctx->signal_event); + k_sem_init(&sh->ctx->lock_sem, 1, 1); +#endif if (IS_ENABLED(CONFIG_SHELL_STATS)) { sh->stats->log_lost_cnt = 0; @@ -1299,22 +1295,19 @@ static int instance_uninit(const struct shell *sh) return 0; } +#if CONFIG_MULTITHREADING typedef void (*shell_signal_handler_t)(const struct shell *sh); static void shell_signal_handle(const struct shell *sh, - enum shell_signal sig_idx, + enum shell_signal sig, shell_signal_handler_t handler) { - struct k_poll_signal *sig = &sh->ctx->signals[sig_idx]; - int set; - int res; - - k_poll_signal_check(sig, &set, &res); - - if (set) { - k_poll_signal_reset(sig); - handler(sh); + if (!k_event_test(&sh->ctx->signal_event, sig)) { + return; } + + k_event_clear(&sh->ctx->signal_event, sig); + handler(sh); } static void kill_handler(const struct shell *sh) @@ -1354,21 +1347,14 @@ void shell_thread(void *shell_handle, void *arg_log_backend, } while (true) { - /* waiting for all signals except SHELL_SIGNAL_TXDONE */ - err = k_poll(sh->ctx->events, SHELL_SIGNAL_TXDONE, + k_event_wait(&sh->ctx->signal_event, + SHELL_SIGNAL_RXRDY | + SHELL_SIGNAL_LOG_MSG | + SHELL_SIGNAL_KILL, + false, K_FOREVER); - if (err != 0) { - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { - return; - } - z_shell_fprintf(sh, SHELL_ERROR, - "Shell thread error: %d", err); - k_mutex_unlock(&sh->ctx->wr_mtx); - return; - } - - k_mutex_lock(&sh->ctx->wr_mtx, K_FOREVER); + z_shell_lock(sh); shell_signal_handle(sh, SHELL_SIGNAL_KILL, kill_handler); shell_signal_handle(sh, SHELL_SIGNAL_RXRDY, shell_process); @@ -1381,9 +1367,10 @@ void shell_thread(void *shell_handle, void *arg_log_backend, sh->iface->api->update(sh->iface); } - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); } } +#endif int shell_init(const struct shell *sh, const void *transport_config, struct shell_backend_config_flags cfg_flags, @@ -1392,9 +1379,11 @@ int shell_init(const struct shell *sh, const void *transport_config, __ASSERT_NO_MSG(sh); __ASSERT_NO_MSG(sh->ctx && sh->iface && sh->default_prompt); +#if CONFIG_MULTITHREADING if (sh->ctx->tid) { return -EALREADY; } +#endif int err = instance_init(sh, transport_config, cfg_flags); @@ -1402,6 +1391,7 @@ int shell_init(const struct shell *sh, const void *transport_config, return err; } +#if CONFIG_MULTITHREADING k_tid_t tid = k_thread_create(sh->thread, sh->stack, CONFIG_SHELL_STACK_SIZE, shell_thread, (void *)sh, (void *)log_backend, @@ -1410,6 +1400,7 @@ int shell_init(const struct shell *sh, const void *transport_config, sh->ctx->tid = tid; k_thread_name_set(tid, sh->name); +#endif return 0; } @@ -1418,16 +1409,9 @@ void shell_uninit(const struct shell *sh, shell_uninit_cb_t cb) { __ASSERT_NO_MSG(sh); - if (IS_ENABLED(CONFIG_MULTITHREADING)) { - struct k_poll_signal *signal = - &sh->ctx->signals[SHELL_SIGNAL_KILL]; - - sh->ctx->uninit_cb = cb; - /* signal kill message */ - (void)k_poll_signal_raise(signal, 0); - - return; - } +#if CONFIG_MULTITHREADING + k_event_post(&sh->ctx->signal_event, SHELL_SIGNAL_KILL); +#else int err = instance_uninit(sh); @@ -1436,6 +1420,7 @@ void shell_uninit(const struct shell *sh, shell_uninit_cb_t cb) } else { __ASSERT_NO_MSG(0); } +#endif } int shell_start(const struct shell *sh) @@ -1452,7 +1437,7 @@ int shell_start(const struct shell *sh) z_shell_log_backend_enable(sh->log_backend, (void *)sh, sh->ctx->log_level); } - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { + if (!z_shell_trylock(sh, SHELL_TX_MTX_TIMEOUT)) { return -EBUSY; } @@ -1474,7 +1459,7 @@ int shell_start(const struct shell *sh) */ z_shell_backend_rx_buffer_flush(sh); - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); return 0; } @@ -1556,7 +1541,7 @@ void shell_vfprintf(const struct shell *sh, enum shell_vt100_color color, return; } - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { + if (!z_shell_trylock(sh, SHELL_TX_MTX_TIMEOUT)) { return; } @@ -1569,7 +1554,7 @@ void shell_vfprintf(const struct shell *sh, enum shell_vt100_color color, } z_transport_buffer_flush(sh); - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); } /* These functions mustn't be used from shell context to avoid deadlock: @@ -1696,12 +1681,12 @@ int shell_prompt_change(const struct shell *sh, const char *prompt) size_t prompt_length = z_shell_strlen(prompt); - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { + if (!z_shell_trylock(sh, SHELL_TX_MTX_TIMEOUT)) { return -EBUSY; } if ((prompt_length + 1 > CONFIG_SHELL_PROMPT_BUFF_SIZE) || (prompt_length == 0)) { - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); return -EINVAL; } @@ -1709,7 +1694,7 @@ int shell_prompt_change(const struct shell *sh, const char *prompt) sh->ctx->vt100_ctx.cons.name_len = prompt_length; - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); return 0; #else @@ -1719,11 +1704,11 @@ int shell_prompt_change(const struct shell *sh, const char *prompt) void shell_help(const struct shell *sh) { - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { + if (!z_shell_trylock(sh, SHELL_TX_MTX_TIMEOUT)) { return; } shell_internal_help_print(sh); - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); } int shell_execute_cmd(const struct shell *sh, const char *cmd) @@ -1754,11 +1739,11 @@ int shell_execute_cmd(const struct shell *sh, const char *cmd) sh->ctx->cmd_buff_len = cmd_len; sh->ctx->cmd_buff_pos = cmd_len; - if (k_mutex_lock(&sh->ctx->wr_mtx, SHELL_TX_MTX_TIMEOUT) != 0) { + if (!z_shell_trylock(sh, SHELL_TX_MTX_TIMEOUT)) { return -ENOEXEC; } ret_val = execute(sh); - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); cmd_buffer_clear(sh); diff --git a/subsys/shell/shell_log_backend.c b/subsys/shell/shell_log_backend.c index 7e2a357da6e56..473bd50bc56f8 100644 --- a/subsys/shell/shell_log_backend.c +++ b/subsys/shell/shell_log_backend.c @@ -181,7 +181,7 @@ static void process_log_msg(const struct shell *sh, if (k_is_in_isr()) { key = irq_lock(); } else { - k_mutex_lock(&sh->ctx->wr_mtx, K_FOREVER); + z_shell_lock(sh); } if (!z_flag_cmd_ctx_get(sh)) { z_shell_cmd_line_erase(sh); @@ -197,7 +197,7 @@ static void process_log_msg(const struct shell *sh, if (k_is_in_isr()) { irq_unlock(key); } else { - k_mutex_unlock(&sh->ctx->wr_mtx); + z_shell_unlock(sh); } } } @@ -232,7 +232,6 @@ static void process(const struct log_backend *const backend, const struct log_output *log_output = log_backend->log_output; bool colors = IS_ENABLED(CONFIG_SHELL_VT100_COLORS) && z_flag_use_colors_get(sh); - struct k_poll_signal *signal; switch (sh->log_backend->control_block->state) { case SHELL_LOG_BACKEND_ENABLED: @@ -241,8 +240,8 @@ static void process(const struct log_backend *const backend, } else { if (copy_to_pbuffer(mpsc_buffer, msg, log_backend->timeout)) { if (IS_ENABLED(CONFIG_MULTITHREADING)) { - signal = &sh->ctx->signals[SHELL_SIGNAL_LOG_MSG]; - k_poll_signal_raise(signal, 0); + k_event_post(&sh->ctx->signal_event, + SHELL_SIGNAL_LOG_MSG); } } else { dropped(backend, 1); diff --git a/subsys/shell/shell_ops.c b/subsys/shell/shell_ops.c index af164d09f79d4..246d088f1a7fe 100644 --- a/subsys/shell/shell_ops.c +++ b/subsys/shell/shell_ops.c @@ -430,17 +430,14 @@ void z_shell_print_prompt_and_cmd(const struct shell *sh) static void shell_pend_on_txdone(const struct shell *sh) { - if (IS_ENABLED(CONFIG_MULTITHREADING) && - (sh->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) { - struct k_poll_event event; - - k_poll_event_init(&event, - K_POLL_TYPE_SIGNAL, - K_POLL_MODE_NOTIFY_ONLY, - &sh->ctx->signals[SHELL_SIGNAL_TXDONE]); - k_poll(&event, 1, K_FOREVER); - k_poll_signal_reset(&sh->ctx->signals[SHELL_SIGNAL_TXDONE]); +#if CONFIG_MULTITHREADING + if (sh->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE) { + k_event_wait(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE, false, K_FOREVER); + k_event_clear(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE); } else { +#else + { +#endif /* Blocking wait in case of bare metal. */ while (!z_flag_tx_rdy_get(sh)) { } diff --git a/subsys/shell/shell_ops.h b/subsys/shell/shell_ops.h index c673c974331dd..feec96172ead4 100644 --- a/subsys/shell/shell_ops.h +++ b/subsys/shell/shell_ops.h @@ -381,6 +381,40 @@ void z_shell_vfprintf(const struct shell *sh, enum shell_vt100_color color, */ void z_shell_backend_rx_buffer_flush(const struct shell *sh); +#if CONFIG_MULTITHREADING +static inline bool z_shell_trylock(const struct shell *sh, k_timeout_t timeout) +{ + return k_sem_take(&sh->ctx->lock_sem, timeout) == 0; +} + +static inline void z_shell_lock(const struct shell *sh) +{ + (void)k_sem_take(&sh->ctx->lock_sem, K_FOREVER); +} + +static inline void z_shell_unlock(const struct shell *sh) +{ + k_sem_give(&sh->ctx->lock_sem); +} +#else +static inline bool z_shell_trylock(const struct shell *sh, k_timeout_t timeout) +{ + ARG_UNUSED(sh); + ARG_UNUSED(timeout); + return true; +} + +static inline void z_shell_lock(const struct shell *sh) +{ + ARG_UNUSED(sh); +} + +static inline void z_shell_unlock(const struct shell *sh) +{ + ARG_UNUSED(sh); +} +#endif + #ifdef __cplusplus } #endif