From 4021d9e4d7ed08ab25d1515f4b77920f30195247 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Tue, 15 Jul 2025 16:47:53 +0200 Subject: [PATCH 1/5] shell: exchange k_poll for k_event The shell subsystem currently uses k_poll for signalling. However, k_poll is only used for simple event signals, results are not used. Replacing the k_poll with k_event greatly simplifies the code, and saves 4 struct k_poll_signal and 4 struct k_poll_event (one of which was entirely unused) while costing a single struct k_event, for every shell instance. It also allows us to not select POLL, as we are using the simpler EVENTS instead. A quick test build of the shell test suite on an nrf54l15 produces the following build info: using EVENTS: FLASH: 71592 B 1428 KB 4.90% RAM: 9872 B 188 KB 5.13% IDT_LIST: 0 GB 32 KB 0.00% using POLL FLASH: 75524 B 1428 KB 5.16% RAM: 11224 B 188 KB 5.83% IDT_LIST: 0 GB 32 KB 0.00% Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/shell/shell.h | 16 +++----- subsys/shell/Kconfig | 2 +- subsys/shell/shell.c | 68 +++++++++----------------------- subsys/shell/shell_log_backend.c | 5 +-- subsys/shell/shell_ops.c | 10 +---- 5 files changed, 28 insertions(+), 73 deletions(-) diff --git a/include/zephyr/shell/shell.h b/include/zephyr/shell/shell.h index ac0f9eaae3a0e..3d93dca841fef 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,12 +961,7 @@ 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_event signal_event; struct k_mutex wr_mtx; k_tid_t tid; diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index bf2cd43264c36..54066fa2a32c1 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 SHELL diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index eb38d5d01981e..63b3274e3bb37 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -1171,19 +1171,16 @@ 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; + enum shell_signal sig = evt_type == SHELL_TRANSPORT_EVT_RX_RDY + ? SHELL_SIGNAL_RXRDY + : SHELL_SIGNAL_TXDONE; - 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); + k_event_post(&sh->ctx->signal_event, sig); } 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 +1190,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,9 +1199,7 @@ 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)); } static int instance_init(const struct shell *sh, @@ -1224,16 +1216,9 @@ static int instance_init(const struct shell *sh, history_init(sh); + k_event_init(&sh->ctx->signal_event); 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 (IS_ENABLED(CONFIG_SHELL_STATS)) { sh->stats->log_lost_cnt = 0; } @@ -1302,19 +1287,15 @@ static int instance_uninit(const struct shell *sh) 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,20 +1335,13 @@ 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); shell_signal_handle(sh, SHELL_SIGNAL_KILL, kill_handler); @@ -1419,13 +1393,7 @@ 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); - + k_event_post(&sh->ctx->signal_event, SHELL_SIGNAL_KILL); return; } diff --git a/subsys/shell/shell_log_backend.c b/subsys/shell/shell_log_backend.c index 7e2a357da6e56..e46876a057c87 100644 --- a/subsys/shell/shell_log_backend.c +++ b/subsys/shell/shell_log_backend.c @@ -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..37715b7ec0e3e 100644 --- a/subsys/shell/shell_ops.c +++ b/subsys/shell/shell_ops.c @@ -432,14 +432,8 @@ 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]); + k_event_wait(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE, false, K_FOREVER); + k_event_clear(&sh->ctx->signal_event, SHELL_SIGNAL_TXDONE); } else { /* Blocking wait in case of bare metal. */ while (!z_flag_tx_rdy_get(sh)) { From 954f8b6501fc5c002c185b1be78a2ebf07ceb1f2 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Thu, 17 Jul 2025 00:09:54 +0200 Subject: [PATCH 2/5] shell: exchange k_mutex for k_sem The mutex is being used as a simple binary semaphore. It is not recursed so we don't need to track thread ownership nor lock count. Exchange the mutex for a binary semaphore to save resources and speed up shell. Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/shell/shell.h | 2 +- subsys/shell/shell.c | 32 ++++++++++++++++---------------- subsys/shell/shell_log_backend.c | 4 ++-- subsys/shell/shell_ops.h | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/include/zephyr/shell/shell.h b/include/zephyr/shell/shell.h index 3d93dca841fef..3b0d31b2a6db6 100644 --- a/include/zephyr/shell/shell.h +++ b/include/zephyr/shell/shell.h @@ -963,7 +963,7 @@ struct shell_ctx { struct k_event signal_event; - struct k_mutex wr_mtx; + struct k_sem lock_sem; k_tid_t tid; int ret_val; }; diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 63b3274e3bb37..fdfd0a92a240c 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); } @@ -1217,7 +1217,7 @@ static int instance_init(const struct shell *sh, history_init(sh); k_event_init(&sh->ctx->signal_event); - k_mutex_init(&sh->ctx->wr_mtx); + k_sem_init(&sh->ctx->lock_sem, 1, 1); if (IS_ENABLED(CONFIG_SHELL_STATS)) { sh->stats->log_lost_cnt = 0; @@ -1342,7 +1342,7 @@ void shell_thread(void *shell_handle, void *arg_log_backend, false, K_FOREVER); - 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); @@ -1355,7 +1355,7 @@ 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); } } @@ -1420,7 +1420,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; } @@ -1442,7 +1442,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; } @@ -1524,7 +1524,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; } @@ -1537,7 +1537,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: @@ -1664,12 +1664,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; } @@ -1677,7 +1677,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 @@ -1687,11 +1687,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) @@ -1722,11 +1722,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 e46876a057c87..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); } } } diff --git a/subsys/shell/shell_ops.h b/subsys/shell/shell_ops.h index c673c974331dd..a608c17daaea9 100644 --- a/subsys/shell/shell_ops.h +++ b/subsys/shell/shell_ops.h @@ -381,6 +381,21 @@ void z_shell_vfprintf(const struct shell *sh, enum shell_vt100_color color, */ void z_shell_backend_rx_buffer_flush(const struct shell *sh); +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); +} + #ifdef __cplusplus } #endif From 2e32820ef5a2973872fc2884a6639007b94cc309 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Fri, 18 Jul 2025 12:48:25 +0200 Subject: [PATCH 3/5] shell: fix shell: fix threadless option The shell at some point supported CONFIG_MULTITHREADING=n but this has not been maintained. Adjust the shell to support threadless operation similar to how logging works with no logging thread. With these adjustments, building with CONFIG_SHELL_MINIMAL and CONFIG_MULTITHREADING=n, shells can now be used like logging by calling shell_process() with non-polling backends, like async or irq driven UART based backends. Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/shell/shell.h | 27 ++++++++++++++++++++++----- subsys/shell/Kconfig | 2 +- subsys/shell/shell.c | 25 +++++++++++++++++++++---- subsys/shell/shell_ops.c | 7 +++++-- subsys/shell/shell_ops.h | 19 +++++++++++++++++++ 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/include/zephyr/shell/shell.h b/include/zephyr/shell/shell.h index 3b0d31b2a6db6..736d9497f6cd4 100644 --- a/include/zephyr/shell/shell.h +++ b/include/zephyr/shell/shell.h @@ -961,10 +961,11 @@ struct shell_ctx { volatile union shell_backend_cfg cfg; volatile union shell_backend_ctx ctx; +#if CONFIG_MULTITHREADING struct k_event signal_event; - struct k_sem lock_sem; k_tid_t tid; +#endif int ret_val; }; @@ -1000,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 @@ -1027,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, \ @@ -1038,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/subsys/shell/Kconfig b/subsys/shell/Kconfig index 54066fa2a32c1..6e93489c0c410 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -8,7 +8,7 @@ menuconfig SHELL bool "Shell" imply LOG_RUNTIME_FILTERING - select EVENTS + select EVENTS if MULTITHREADING if SHELL diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index fdfd0a92a240c..e98a96d2cb100 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -1171,13 +1171,21 @@ 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; + +#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; @@ -1201,6 +1209,7 @@ static void shell_log_process(const struct shell *sh) } 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, @@ -1216,8 +1225,10 @@ static int instance_init(const struct shell *sh, history_init(sh); +#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; @@ -1284,6 +1295,7 @@ 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, @@ -1358,6 +1370,7 @@ void shell_thread(void *shell_handle, void *arg_log_backend, z_shell_unlock(sh); } } +#endif int shell_init(const struct shell *sh, const void *transport_config, struct shell_backend_config_flags cfg_flags, @@ -1366,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); @@ -1376,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, @@ -1384,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; } @@ -1392,10 +1409,9 @@ void shell_uninit(const struct shell *sh, shell_uninit_cb_t cb) { __ASSERT_NO_MSG(sh); - if (IS_ENABLED(CONFIG_MULTITHREADING)) { - k_event_post(&sh->ctx->signal_event, SHELL_SIGNAL_KILL); - return; - } +#if CONFIG_MULTITHREADING + k_event_post(&sh->ctx->signal_event, SHELL_SIGNAL_KILL); +#else int err = instance_uninit(sh); @@ -1404,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) diff --git a/subsys/shell/shell_ops.c b/subsys/shell/shell_ops.c index 37715b7ec0e3e..246d088f1a7fe 100644 --- a/subsys/shell/shell_ops.c +++ b/subsys/shell/shell_ops.c @@ -430,11 +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)) { +#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 a608c17daaea9..feec96172ead4 100644 --- a/subsys/shell/shell_ops.h +++ b/subsys/shell/shell_ops.h @@ -381,6 +381,7 @@ 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; @@ -395,6 +396,24 @@ 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 } From 8fd2ecb90dcb9ed70af7c207edbb0e0c18693dd2 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Wed, 16 Jul 2025 10:22:09 +0200 Subject: [PATCH 4/5] samples: shell: introduce threadless shell sample Introduce threadless shell sample which shows how to build and use shells in threadless (single threaded) applications. Signed-off-by: Bjarki Arge Andreasen --- .../subsys/shell/threadless/CMakeLists.txt | 9 ++++ samples/subsys/shell/threadless/README.rst | 43 +++++++++++++++++++ samples/subsys/shell/threadless/prj.conf | 3 ++ samples/subsys/shell/threadless/sample.yaml | 18 ++++++++ samples/subsys/shell/threadless/src/main.c | 32 ++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 samples/subsys/shell/threadless/CMakeLists.txt create mode 100644 samples/subsys/shell/threadless/README.rst create mode 100644 samples/subsys/shell/threadless/prj.conf create mode 100644 samples/subsys/shell/threadless/sample.yaml create mode 100644 samples/subsys/shell/threadless/src/main.c 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; +} From e96fda6f9539b652c84011146d2a0a246cecda2f Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Wed, 16 Jul 2025 21:51:03 +0200 Subject: [PATCH 5/5] doc: kernel: services: threads: nothread: include shell support Extend nothread docs to list the shell subsystem using the serial shell backend as expected to work with CONFIG_MULTITHREADING=n Signed-off-by: Bjarki Arge Andreasen --- doc/kernel/services/threads/nothread.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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`