diff --git a/subsys/shell/modules/CMakeLists.txt b/subsys/shell/modules/CMakeLists.txt index d002604c6ae..dccce335b1a 100644 --- a/subsys/shell/modules/CMakeLists.txt +++ b/subsys/shell/modules/CMakeLists.txt @@ -1,9 +1,9 @@ # SPDX-License-Identifier: Apache-2.0 -zephyr_sources_ifdef( - CONFIG_KERNEL_SHELL - kernel_service.c - ) +add_subdirectory_ifdef(CONFIG_KERNEL_SHELL + kernel_service +) + zephyr_sources_ifdef( CONFIG_DEVICE_SHELL device_service.c diff --git a/subsys/shell/modules/Kconfig b/subsys/shell/modules/Kconfig index 86e9ac49a9b..84e4a879f83 100644 --- a/subsys/shell/modules/Kconfig +++ b/subsys/shell/modules/Kconfig @@ -3,29 +3,6 @@ # Copyright (c) 2016 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -config KERNEL_SHELL - bool "Kernel shell" - default y if !SHELL_MINIMAL - imply INIT_STACKS - imply THREAD_MONITOR - imply THREAD_NAME - imply THREAD_STACK_INFO - help - This shell provides access to basic kernel data like version, uptime - and other useful information. - -config KERNEL_SHELL_REBOOT_DELAY - int "Delay between reception of shell reboot command and reboot (ms)" - depends on KERNEL_SHELL - depends on REBOOT - default 0 - help - This delay allows time for the shell to successfully echo the reboot - command input before the reboot abruptly terminates it. This can help - external systems that interact with the shell and require the reboot - command's echo to successfully complete to synchronise with the - device. - config DEVICE_SHELL bool "Device shell" default y if !SHELL_MINIMAL @@ -45,3 +22,5 @@ config DEVMEM_SHELL select POSIX_C_LIB_EXT help This shell command provides read/write access to physical memory. + +rsource "kernel_service/Kconfig" diff --git a/subsys/shell/modules/kernel_service.c b/subsys/shell/modules/kernel_service.c deleted file mode 100644 index 17a9586557c..00000000000 --- a/subsys/shell/modules/kernel_service.c +++ /dev/null @@ -1,698 +0,0 @@ -/* - * Copyright (c) 2018 Nordic Semiconductor ASA - * Copyright (c) 2016 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/* - * TODO(b/272518464): Work around coreboot GCC preprocessor bug. - * #line marks the *next* line, so it is off by one. - */ -#line 13 -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) && (K_HEAP_MEM_POOL_SIZE > 0) -#include -#endif -#if defined(CONFIG_LOG_RUNTIME_FILTERING) -#include -#endif -#include - -#if defined(CONFIG_THREAD_MAX_NAME_LEN) -#define THREAD_MAX_NAM_LEN CONFIG_THREAD_MAX_NAME_LEN -#else -#define THREAD_MAX_NAM_LEN 10 -#endif - -static int cmd_kernel_version(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - - shell_print(sh, "Zephyr version %s", KERNEL_VERSION_STRING); - return 0; -} - -#define MINUTES_FACTOR (MSEC_PER_SEC * SEC_PER_MIN) -#define HOURS_FACTOR (MINUTES_FACTOR * MIN_PER_HOUR) -#define DAYS_FACTOR (HOURS_FACTOR * HOUR_PER_DAY) - -static int cmd_kernel_uptime(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - - int64_t milliseconds = k_uptime_get(); - int64_t days; - int64_t hours; - int64_t minutes; - int64_t seconds; - - if (argc == 1) { - shell_print(sh, "Uptime: %llu ms", milliseconds); - return 0; - } - - /* No need to enable the getopt and getopt_long for just one option. */ - if (strcmp("-p", argv[1]) && strcmp("--pretty", argv[1]) != 0) { - shell_error(sh, "Unsupported option: %s", argv[1]); - return -EIO; - } - - days = milliseconds / DAYS_FACTOR; - milliseconds %= DAYS_FACTOR; - hours = milliseconds / HOURS_FACTOR; - milliseconds %= HOURS_FACTOR; - minutes = milliseconds / MINUTES_FACTOR; - milliseconds %= MINUTES_FACTOR; - seconds = milliseconds / MSEC_PER_SEC; - milliseconds = milliseconds % MSEC_PER_SEC; - - shell_print(sh, - "uptime: %llu days, %llu hours, %llu minutes, %llu seconds, %llu milliseconds", - days, hours, minutes, seconds, milliseconds); - - return 0; -} - -static int cmd_kernel_cycles(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - - shell_print(sh, "cycles: %u hw cycles", k_cycle_get_32()); - return 0; -} - -#if defined(CONFIG_INIT_STACKS) && defined(CONFIG_THREAD_STACK_INFO) && \ - defined(CONFIG_THREAD_MONITOR) -static void shell_tdata_dump(const struct k_thread *cthread, void *user_data) -{ - struct k_thread *thread = (struct k_thread *)cthread; - const struct shell *sh = (const struct shell *)user_data; - unsigned int pcnt; - size_t unused; - size_t size = thread->stack_info.size; - const char *tname; - int ret; - char state_str[32]; - -#ifdef CONFIG_THREAD_RUNTIME_STATS - k_thread_runtime_stats_t rt_stats_thread; - k_thread_runtime_stats_t rt_stats_all; -#endif - - tname = k_thread_name_get(thread); - - shell_print(sh, "%s%p %-10s", - (thread == k_current_get()) ? "*" : " ", - thread, - tname ? tname : "NA"); - /* Cannot use lld as it's less portable. */ - shell_print(sh, "\toptions: 0x%x, priority: %d timeout: %" PRId64, - thread->base.user_options, - thread->base.prio, - (int64_t)thread->base.timeout.dticks); - shell_print(sh, "\tstate: %s, entry: %p", - k_thread_state_str(thread, state_str, sizeof(state_str)), - thread->entry.pEntry); - -#ifdef CONFIG_SCHED_CPU_MASK - shell_print(sh, "\tcpu_mask: 0x%x", thread->base.cpu_mask); -#endif /* CONFIG_SCHED_CPU_MASK */ - -#ifdef CONFIG_THREAD_RUNTIME_STATS - ret = 0; - - if (k_thread_runtime_stats_get(thread, &rt_stats_thread) != 0) { - ret++; - } - - if (k_thread_runtime_stats_all_get(&rt_stats_all) != 0) { - ret++; - } - - if (ret == 0) { - pcnt = (rt_stats_thread.execution_cycles * 100U) / - rt_stats_all.execution_cycles; - - /* - * z_prf() does not support %llu by default unless - * CONFIG_MINIMAL_LIBC_LL_PRINTF=y. So do conditional - * compilation to avoid blindly enabling this kconfig - * so it won't increase RAM/ROM usage too much on 32-bit - * targets. - */ - shell_print(sh, "\tTotal execution cycles: %u (%u %%)", - (uint32_t)rt_stats_thread.execution_cycles, - pcnt); -#ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS - shell_print(sh, "\tCurrent execution cycles: %u", - (uint32_t)rt_stats_thread.current_cycles); - shell_print(sh, "\tPeak execution cycles: %u", - (uint32_t)rt_stats_thread.peak_cycles); - shell_print(sh, "\tAverage execution cycles: %u", - (uint32_t)rt_stats_thread.average_cycles); -#endif - } else { - shell_print(sh, "\tTotal execution cycles: ? (? %%)"); -#ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS - shell_print(sh, "\tCurrent execution cycles: ?"); - shell_print(sh, "\tPeak execution cycles: ?"); - shell_print(sh, "\tAverage execution cycles: ?"); -#endif - } -#endif - - ret = k_thread_stack_space_get(thread, &unused); - if (ret) { - shell_print(sh, - "Unable to determine unused stack size (%d)\n", - ret); - } else { - /* Calculate the real size reserved for the stack */ - pcnt = ((size - unused) * 100U) / size; - - shell_print(sh, - "\tstack size %zu, unused %zu, usage %zu / %zu (%u %%)\n", - size, unused, size - unused, size, pcnt); - } - -} - -static int cmd_kernel_thread_list(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - - shell_print(sh, "Scheduler: %u since last call", sys_clock_elapsed()); - shell_print(sh, "Threads:"); - - /* - * Use the unlocked version as the callback itself might call - * arch_irq_unlock. - */ - k_thread_foreach_unlocked(shell_tdata_dump, (void *)sh); - - return 0; -} - -static void shell_stack_dump(const struct k_thread *thread, void *user_data) -{ - const struct shell *sh = (const struct shell *)user_data; - unsigned int pcnt; - size_t unused; - size_t size = thread->stack_info.size; - const char *tname; - int ret; - - ret = k_thread_stack_space_get(thread, &unused); - if (ret) { - shell_print(sh, - "Unable to determine unused stack size (%d)\n", - ret); - return; - } - - tname = k_thread_name_get((struct k_thread *)thread); - - /* Calculate the real size reserved for the stack */ - pcnt = ((size - unused) * 100U) / size; - - shell_print( - (const struct shell *)user_data, "%p %-" STRINGIFY(THREAD_MAX_NAM_LEN) "s " - "(real size %4zu):\tunused %4zu\tusage %4zu / %4zu (%2u %%)", - thread, tname ? tname : "NA", size, unused, size - unused, size, pcnt); -} - -K_KERNEL_STACK_ARRAY_DECLARE(z_interrupt_stacks, CONFIG_MP_MAX_NUM_CPUS, - CONFIG_ISR_STACK_SIZE); - -static int cmd_kernel_thread_stacks(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - char pad[THREAD_MAX_NAM_LEN] = { 0 }; - - memset(pad, ' ', MAX((THREAD_MAX_NAM_LEN - strlen("IRQ 00")), 1)); - - /* - * Use the unlocked version as the callback itself might call - * arch_irq_unlock. - */ - k_thread_foreach_unlocked(shell_stack_dump, (void *)sh); - - /* Placeholder logic for interrupt stack until we have better - * kernel support, including dumping arch-specific exception-related - * stack buffers. - */ - unsigned int num_cpus = arch_num_cpus(); - - for (int i = 0; i < num_cpus; i++) { - size_t unused; - const uint8_t *buf = K_KERNEL_STACK_BUFFER(z_interrupt_stacks[i]); - size_t size = K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[i]); - int err = z_stack_space_get(buf, size, &unused); - - (void)err; - __ASSERT_NO_MSG(err == 0); - - shell_print(sh, - "%p IRQ %02d %s(real size %4zu):\tunused %4zu\tusage %4zu / %4zu (%2zu %%)", - &z_interrupt_stacks[i], i, pad, size, unused, size - unused, size, - ((size - unused) * 100U) / size); - } - - return 0; -} -#endif /* CONFIG_INIT_STACKS & CONFIG_THREAD_STACK_INFO & CONFIG_THREAD_MONITOR */ - -struct thread_entry { - const struct k_thread *const thread; - bool valid; -}; - -static void thread_valid_cb(const struct k_thread *cthread, void *user_data) -{ - struct thread_entry *entry = user_data; - - if (cthread == entry->thread) { - entry->valid = true; - } -} - -__maybe_unused -static bool thread_is_valid(const struct k_thread *thread) -{ - struct thread_entry entry = { - .thread = thread, - .valid = false, - }; - - k_thread_foreach(thread_valid_cb, &entry); - - return entry.valid; -} - -#if defined(CONFIG_ARCH_STACKWALK) - -static bool print_trace_address(void *arg, unsigned long ra) -{ - const struct shell *sh = arg; -#ifdef CONFIG_SYMTAB - uint32_t offset = 0; - const char *name = symtab_find_symbol_name(ra, &offset); - - shell_print(sh, "ra: %p [%s+0x%x]", (void *)ra, name, offset); -#else - shell_print(sh, "ra: %p", (void *)ra); -#endif - - return true; -} - -static int cmd_kernel_thread_unwind(const struct shell *sh, size_t argc, char **argv) -{ - struct k_thread *thread; - int err = 0; - - if (argc == 1) { - thread = _current; - } else { - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - } - shell_print(sh, "Unwinding %p %s", (void *)thread, thread->name); - - arch_stack_walk(print_trace_address, (void *)sh, thread, NULL); - - return 0; -} - -#endif /* CONFIG_ARCH_STACKWALK */ - -#ifdef CONFIG_SCHED_CPU_MASK -static int cmd_kernel_thread_mask_clear(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - - int rc, err = 0; - struct k_thread *thread; - - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - - rc = k_thread_cpu_mask_clear(thread); - if (rc != 0) { - shell_error(sh, "Failed - %d", rc); - } else { - shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, - thread->base.cpu_mask); - } - - return rc; -} - -static int cmd_kernel_thread_mask_enable_all(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - - int rc, err = 0; - struct k_thread *thread; - - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - - rc = k_thread_cpu_mask_enable_all(thread); - if (rc != 0) { - shell_error(sh, "Failed - %d", rc); - } else { - shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, - thread->base.cpu_mask); - } - - return rc; -} - -static int cmd_kernel_thread_mask_enable(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - - int rc, cpu, err = 0; - struct k_thread *thread; - - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - - cpu = (int)shell_strtol(argv[2], 10, &err); - if (err != 0) { - shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); - return err; - } - - rc = k_thread_cpu_mask_enable(thread, cpu); - if (rc != 0) { - shell_error(sh, "Failed - %d", rc); - } else { - shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, - thread->base.cpu_mask); - } - - return rc; -} - -static int cmd_kernel_thread_mask_disable(const struct shell *sh, size_t argc, char **argv) -{ - ARG_UNUSED(argc); - - int rc, cpu, err = 0; - struct k_thread *thread; - - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - - cpu = (int)shell_strtol(argv[2], 10, &err); - if (err != 0) { - shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); - return err; - } - - rc = k_thread_cpu_mask_disable(thread, cpu); - if (rc != 0) { - shell_error(sh, "Failed - %d", rc); - } else { - shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, - thread->base.cpu_mask); - } - - return rc; -} - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel_thread_mask, - SHELL_CMD_ARG(clear, NULL, - "Sets all CPU enable masks to zero.\n" - "Usage: kernel thread mask clear ", - cmd_kernel_thread_mask_clear, 2, 0), - SHELL_CMD_ARG(enable_all, NULL, - "Sets all CPU enable masks to one.\n" - "Usage: kernel thread mask enable_all ", - cmd_kernel_thread_mask_enable_all, 2, 0), - SHELL_CMD_ARG(enable, NULL, - "Enable thread to run on specified CPU.\n" - "Usage: kernel thread mask enable ", - cmd_kernel_thread_mask_enable, 3, 0), - SHELL_CMD_ARG(disable, NULL, - "Prevent thread to run on specified CPU.\n" - "Usage: kernel thread mask disable ", - cmd_kernel_thread_mask_disable, 3, 0), - SHELL_SUBCMD_SET_END /* Array terminated. */ -); - -static int cmd_kernel_thread_pin(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - - int cpu, err = 0; - struct k_thread *thread; - - thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); - if (err != 0) { - shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); - return err; - } - - if (!thread_is_valid(thread)) { - shell_error(sh, "Invalid thread id %p", (void *)thread); - return -EINVAL; - } - - cpu = shell_strtoul(argv[2], 10, &err); - if (err != 0) { - shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); - return err; - } - - shell_print(sh, "Pinning %p %s to CPU %d", (void *)thread, thread->name, cpu); - err = k_thread_cpu_pin(thread, cpu); - if (err != 0) { - shell_error(sh, "Failed - %d", err); - } else { - shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, - thread->base.cpu_mask); - } - - return err; -} -#endif /* CONFIG_SCHED_CPU_MASK */ - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel_thread, -#if defined(CONFIG_INIT_STACKS) && defined(CONFIG_THREAD_STACK_INFO) && \ - defined(CONFIG_THREAD_MONITOR) - SHELL_CMD(stacks, NULL, "List threads stack usage.", cmd_kernel_thread_stacks), - SHELL_CMD(list, NULL, "List kernel threads.", cmd_kernel_thread_list), -#endif -#if defined(CONFIG_ARCH_STACKWALK) - SHELL_CMD_ARG(unwind, NULL, "Unwind a thread.", cmd_kernel_thread_unwind, 1, 1), -#endif /* CONFIG_ARCH_STACKWALK */ -#if defined(CONFIG_SCHED_CPU_MASK) - SHELL_CMD_ARG(mask, &sub_kernel_thread_mask, "Configure thread CPU mask affinity.", NULL, 2, - 0), - SHELL_CMD_ARG(pin, NULL, - "Pin thread to a CPU.\n" - "Usage: kernel pin ", - cmd_kernel_thread_pin, 3, 0), -#endif /* CONFIG_SCHED_CPU_MASK */ - SHELL_SUBCMD_SET_END /* Array terminated. */ -); - -#if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) && (K_HEAP_MEM_POOL_SIZE > 0) -extern struct sys_heap _system_heap; - -static int cmd_kernel_heap(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - - int err; - struct sys_memory_stats stats; - - err = sys_heap_runtime_stats_get(&_system_heap, &stats); - if (err) { - shell_error(sh, "Failed to read kernel system heap statistics (err %d)", err); - return -ENOEXEC; - } - - shell_print(sh, "free: %zu", stats.free_bytes); - shell_print(sh, "allocated: %zu", stats.allocated_bytes); - shell_print(sh, "max. allocated: %zu", stats.max_allocated_bytes); - - return 0; -} -#endif - -static int cmd_kernel_sleep(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(sh); - ARG_UNUSED(argc); - - uint32_t ms; - int err = 0; - - ms = shell_strtoul(argv[1], 10, &err); - - if (!err) { - k_msleep(ms); - } else { - shell_error(sh, "Unable to parse input (err %d)", err); - return err; - } - - return 0; -} - -#if defined(CONFIG_LOG_RUNTIME_FILTERING) -static int cmd_kernel_log_level_set(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); - int err = 0; - - uint8_t severity = shell_strtoul(argv[2], 10, &err); - - if (err) { - shell_error(sh, "Unable to parse log severity (err %d)", err); - - return err; - } - - if (severity > LOG_LEVEL_DBG) { - shell_error(sh, "Invalid log level: %d", severity); - shell_help(sh); - return SHELL_CMD_HELP_PRINTED; - } - - int source_id = log_source_id_get(argv[1]); - - /* log_filter_set() takes an int16_t for the source ID */ - if (source_id < 0) { - shell_error(sh, "Unable to find log source: %s", argv[1]); - } - - log_filter_set(NULL, 0, (int16_t)source_id, severity); - - return 0; -} -#endif - -#if defined(CONFIG_REBOOT) -static int cmd_kernel_reboot_warm(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); -#if (CONFIG_KERNEL_SHELL_REBOOT_DELAY > 0) - k_sleep(K_MSEC(CONFIG_KERNEL_SHELL_REBOOT_DELAY)); -#endif - sys_reboot(SYS_REBOOT_WARM); - return 0; -} - -static int cmd_kernel_reboot_cold(const struct shell *sh, - size_t argc, char **argv) -{ - ARG_UNUSED(argc); - ARG_UNUSED(argv); -#if (CONFIG_KERNEL_SHELL_REBOOT_DELAY > 0) - k_sleep(K_MSEC(CONFIG_KERNEL_SHELL_REBOOT_DELAY)); -#endif - sys_reboot(SYS_REBOOT_COLD); - return 0; -} - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel_reboot, - SHELL_CMD(cold, NULL, "Cold reboot.", cmd_kernel_reboot_cold), - SHELL_CMD(warm, NULL, "Warm reboot.", cmd_kernel_reboot_warm), - SHELL_SUBCMD_SET_END /* Array terminated. */ -); -#endif - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel, - SHELL_CMD(cycles, NULL, "Kernel cycles.", cmd_kernel_cycles), -#if defined(CONFIG_REBOOT) - SHELL_CMD(reboot, &sub_kernel_reboot, "Reboot.", cmd_kernel_reboot_cold), -#endif - SHELL_CMD(thread, &sub_kernel_thread, "Kernel threads.", NULL), -#if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) && (K_HEAP_MEM_POOL_SIZE > 0) - SHELL_CMD(heap, NULL, "System heap usage statistics.", cmd_kernel_heap), -#endif - SHELL_CMD_ARG(uptime, NULL, "Kernel uptime. Can be called with the -p or --pretty options", - cmd_kernel_uptime, 1, 1), - SHELL_CMD(version, NULL, "Kernel version.", cmd_kernel_version), - SHELL_CMD_ARG(sleep, NULL, "ms", cmd_kernel_sleep, 2, 0), -#if defined(CONFIG_LOG_RUNTIME_FILTERING) - SHELL_CMD_ARG(log-level, NULL, " ", - cmd_kernel_log_level_set, 3, 0), -#endif - SHELL_SUBCMD_SET_END /* Array terminated. */ -); - -SHELL_CMD_REGISTER(kernel, &sub_kernel, "Kernel commands", NULL); diff --git a/subsys/shell/modules/kernel_service/CMakeLists.txt b/subsys/shell/modules/kernel_service/CMakeLists.txt new file mode 100644 index 00000000000..0bc582df690 --- /dev/null +++ b/subsys/shell/modules/kernel_service/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2024 Meta Platforms +# SPDX-License-Identifier: Apache-2.0 + +zephyr_sources( + # Main command + kernel_shell.c + # Subcommand starts here + cycles.c + sleep.c + uptime.c + version.c +) + +# Conditional subcommands +zephyr_sources_ifdef(CONFIG_SYS_HEAP_RUNTIME_STATS heap.c) + +zephyr_sources_ifdef(CONFIG_LOG_RUNTIME_FILTERING log-level.c) + +zephyr_sources_ifdef(CONFIG_REBOOT reboot.c) + +add_subdirectory_ifdef(CONFIG_KERNEL_THREAD_SHELL thread) diff --git a/subsys/shell/modules/kernel_service/Kconfig b/subsys/shell/modules/kernel_service/Kconfig new file mode 100644 index 00000000000..f0c27679699 --- /dev/null +++ b/subsys/shell/modules/kernel_service/Kconfig @@ -0,0 +1,32 @@ +# Kernel service configuration options + +# Copyright (c) 2016 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +config KERNEL_SHELL + bool "Kernel shell" + default y if !SHELL_MINIMAL + imply INIT_STACKS + imply THREAD_MONITOR + imply THREAD_NAME + imply THREAD_STACK_INFO + help + This shell provides access to basic kernel data like version, uptime + and other useful information. + +if KERNEL_SHELL + +config KERNEL_SHELL_REBOOT_DELAY + int "Delay between reception of shell reboot command and reboot (ms)" + depends on REBOOT + default 0 + help + This delay allows time for the shell to successfully echo the reboot + command input before the reboot abruptly terminates it. This can help + external systems that interact with the shell and require the reboot + command's echo to successfully complete to synchronise with the + device. + +rsource "thread/Kconfig" + +endif # KERNEL_SHELL diff --git a/subsys/shell/modules/kernel_service/cycles.c b/subsys/shell/modules/kernel_service/cycles.c new file mode 100644 index 00000000000..d6b9da83fa4 --- /dev/null +++ b/subsys/shell/modules/kernel_service/cycles.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_cycles(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "cycles: %u hw cycles", k_cycle_get_32()); + return 0; +} + +KERNEL_CMD_ADD(cycles, NULL, "Kernel cycles.", cmd_kernel_cycles); diff --git a/subsys/shell/modules/kernel_service/heap.c b/subsys/shell/modules/kernel_service/heap.c new file mode 100644 index 00000000000..f6c5a050ed6 --- /dev/null +++ b/subsys/shell/modules/kernel_service/heap.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if K_HEAP_MEM_POOL_SIZE > 0 +#include + +extern struct sys_heap _system_heap; + +static int cmd_kernel_heap(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + int err; + struct sys_memory_stats stats; + + err = sys_heap_runtime_stats_get(&_system_heap, &stats); + if (err) { + shell_error(sh, "Failed to read kernel system heap statistics (err %d)", err); + return -ENOEXEC; + } + + shell_print(sh, "free: %zu", stats.free_bytes); + shell_print(sh, "allocated: %zu", stats.allocated_bytes); + shell_print(sh, "max. allocated: %zu", stats.max_allocated_bytes); + + return 0; +} + +KERNEL_CMD_ADD(heap, NULL, "System heap usage statistics.", cmd_kernel_heap); + +#endif /* K_HEAP_MEM_POOL_SIZE > 0 */ diff --git a/subsys/shell/modules/kernel_service/kernel_shell.c b/subsys/shell/modules/kernel_service/kernel_shell.c new file mode 100644 index 00000000000..1ac045a266d --- /dev/null +++ b/subsys/shell/modules/kernel_service/kernel_shell.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +SHELL_SUBCMD_SET_CREATE(kernel_cmds, (kernel)); +SHELL_CMD_REGISTER(kernel, &kernel_cmds, "Kernel commands", NULL); diff --git a/subsys/shell/modules/kernel_service/kernel_shell.h b/subsys/shell/modules/kernel_service/kernel_shell.h new file mode 100644 index 00000000000..ca0523ad9ea --- /dev/null +++ b/subsys/shell/modules/kernel_service/kernel_shell.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_SHELL_MODULES_KERNEL_SERVICE_KERNEL_SHELL_H_ +#define ZEPHYR_SUBSYS_SHELL_MODULES_KERNEL_SERVICE_KERNEL_SHELL_H_ + +#include + +/* Add command to the set of kernel subcommands, see `SHELL_SUBCMD_ADD` */ +#define KERNEL_CMD_ARG_ADD(_syntax, _subcmd, _help, _handler, _mand, _opt) \ + SHELL_SUBCMD_ADD((kernel), _syntax, _subcmd, _help, _handler, _mand, _opt); + +#define KERNEL_CMD_ADD(_syntax, _subcmd, _help, _handler) \ + KERNEL_CMD_ARG_ADD(_syntax, _subcmd, _help, _handler, 0, 0); + +/* Add command to the set of `kernel thread` subcommands */ +#define KERNEL_THREAD_CMD_ARG_ADD(_syntax, _subcmd, _help, _handler, _mand, _opt) \ + SHELL_SUBCMD_ADD((thread), _syntax, _subcmd, _help, _handler, _mand, _opt); + +#define KERNEL_THREAD_CMD_ADD(_syntax, _subcmd, _help, _handler) \ + KERNEL_THREAD_CMD_ARG_ADD(_syntax, _subcmd, _help, _handler, 0, 0); + +/* Internal function to check if a thread pointer is valid */ +bool z_thread_is_valid(const struct k_thread *thread); + +#endif /* ZEPHYR_SUBSYS_SHELL_MODULES_KERNEL_SERVICE_KERNEL_SHELL_H_ */ diff --git a/subsys/shell/modules/kernel_service/log-level.c b/subsys/shell/modules/kernel_service/log-level.c new file mode 100644 index 00000000000..70a3142d00b --- /dev/null +++ b/subsys/shell/modules/kernel_service/log-level.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_log_level_set(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int err = 0; + + uint8_t severity = shell_strtoul(argv[2], 10, &err); + + if (err) { + shell_error(sh, "Unable to parse log severity (err %d)", err); + + return err; + } + + if (severity > LOG_LEVEL_DBG) { + shell_error(sh, "Invalid log level: %d", severity); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + int source_id = log_source_id_get(argv[1]); + + /* log_filter_set() takes an int16_t for the source ID */ + if (source_id < 0) { + shell_error(sh, "Unable to find log source: %s", argv[1]); + } + + log_filter_set(NULL, 0, (int16_t)source_id, severity); + + return 0; +} + +KERNEL_CMD_ARG_ADD(log_level, NULL, " ", cmd_kernel_log_level_set, 3, + 0); diff --git a/subsys/shell/modules/kernel_service/reboot.c b/subsys/shell/modules/kernel_service/reboot.c new file mode 100644 index 00000000000..b537ea716eb --- /dev/null +++ b/subsys/shell/modules/kernel_service/reboot.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_reboot_warm(const struct shell *sh, + size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); +#if (CONFIG_KERNEL_SHELL_REBOOT_DELAY > 0) + k_sleep(K_MSEC(CONFIG_KERNEL_SHELL_REBOOT_DELAY)); +#endif + sys_reboot(SYS_REBOOT_WARM); + return 0; +} + +static int cmd_kernel_reboot_cold(const struct shell *sh, + size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); +#if (CONFIG_KERNEL_SHELL_REBOOT_DELAY > 0) + k_sleep(K_MSEC(CONFIG_KERNEL_SHELL_REBOOT_DELAY)); +#endif + sys_reboot(SYS_REBOOT_COLD); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel_reboot, + SHELL_CMD(cold, NULL, "Cold reboot.", cmd_kernel_reboot_cold), + SHELL_CMD(warm, NULL, "Warm reboot.", cmd_kernel_reboot_warm), + SHELL_SUBCMD_SET_END /* Array terminated. */ +); + +KERNEL_CMD_ADD(reboot, &sub_kernel_reboot, "Reboot.", cmd_kernel_reboot_cold); diff --git a/subsys/shell/modules/kernel_service/sleep.c b/subsys/shell/modules/kernel_service/sleep.c new file mode 100644 index 00000000000..3931bb74944 --- /dev/null +++ b/subsys/shell/modules/kernel_service/sleep.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_sleep(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + uint32_t ms; + int err = 0; + + ms = shell_strtoul(argv[1], 10, &err); + + if (!err) { + k_msleep(ms); + } else { + shell_error(sh, "Unable to parse input (err %d)", err); + return err; + } + + return 0; +} + +KERNEL_CMD_ARG_ADD(sleep, NULL, "ms", cmd_kernel_sleep, 2, 0); diff --git a/subsys/shell/modules/kernel_service/thread/CMakeLists.txt b/subsys/shell/modules/kernel_service/thread/CMakeLists.txt new file mode 100644 index 00000000000..25690a550d0 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Meta Platforms +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(../) + +# Main command +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL thread.c) + +# Subcommands +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL_LIST list.c) + +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL_MASK mask.c) + +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL_MASK pin.c) + +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL_STACKS stacks.c) + +zephyr_sources_ifdef(CONFIG_KERNEL_THREAD_SHELL_UNWIND unwind.c) diff --git a/subsys/shell/modules/kernel_service/thread/Kconfig b/subsys/shell/modules/kernel_service/thread/Kconfig new file mode 100644 index 00000000000..4a6ccf10981 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/Kconfig @@ -0,0 +1,54 @@ +# Kernel thread configuration options + +# Copyright (c) 2024 Meta Platforms +# SPDX-License-Identifier: Apache-2.0 + +config KERNEL_THREAD_SHELL + bool + help + Internal helper macro to determine if the main `thread` command + should be compiled. + +config KERNEL_THREAD_SHELL_LIST + bool + default y + depends on INIT_STACKS + depends on THREAD_MONITOR + depends on THREAD_STACK_INFO + select KERNEL_THREAD_SHELL + help + Internal helper macro to compile the `list` subcommand + +config KERNEL_THREAD_SHELL_STACKS + bool + default y + depends on INIT_STACKS + depends on THREAD_MONITOR + depends on THREAD_STACK_INFO + select KERNEL_THREAD_SHELL + help + Internal helper macro to compile the `stacks` subcommand + +config KERNEL_THREAD_SHELL_MASK + bool + default y + depends on SCHED_CPU_MASK + select KERNEL_THREAD_SHELL + help + Internal helper macro to compile the `mask` subcommand + +config KERNEL_THREAD_SHELL_PIN + bool + default y + depends on SCHED_CPU_MASK + select KERNEL_THREAD_SHELL + help + Internal helper macro to compile the `pin` subcommand + +config KERNEL_THREAD_SHELL_UNWIND + bool + default y + depends on ARCH_STACKWALK + select KERNEL_THREAD_SHELL + help + Internal helper macro to compile the `unwind` subcommand diff --git a/subsys/shell/modules/kernel_service/thread/list.c b/subsys/shell/modules/kernel_service/thread/list.c new file mode 100644 index 00000000000..655881342e2 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/list.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include +#include + +static void shell_tdata_dump(const struct k_thread *cthread, void *user_data) +{ + struct k_thread *thread = (struct k_thread *)cthread; + const struct shell *sh = (const struct shell *)user_data; + unsigned int pcnt; + size_t unused; + size_t size = thread->stack_info.size; + const char *tname; + int ret; + char state_str[32]; + +#ifdef CONFIG_THREAD_RUNTIME_STATS + k_thread_runtime_stats_t rt_stats_thread; + k_thread_runtime_stats_t rt_stats_all; +#endif /* CONFIG_THREAD_RUNTIME_STATS */ + + tname = k_thread_name_get(thread); + + shell_print(sh, "%s%p %-10s", + (thread == k_current_get()) ? "*" : " ", + thread, + tname ? tname : "NA"); + /* Cannot use lld as it's less portable. */ + shell_print(sh, "\toptions: 0x%x, priority: %d timeout: %" PRId64, + thread->base.user_options, + thread->base.prio, + (int64_t)thread->base.timeout.dticks); + shell_print(sh, "\tstate: %s, entry: %p", + k_thread_state_str(thread, state_str, sizeof(state_str)), + thread->entry.pEntry); + +#ifdef CONFIG_SCHED_CPU_MASK + shell_print(sh, "\tcpu_mask: 0x%x", thread->base.cpu_mask); +#endif /* CONFIG_SCHED_CPU_MASK */ + +#ifdef CONFIG_THREAD_RUNTIME_STATS + ret = 0; + + if (k_thread_runtime_stats_get(thread, &rt_stats_thread) != 0) { + ret++; + } + + if (k_thread_runtime_stats_all_get(&rt_stats_all) != 0) { + ret++; + } + + if (ret == 0) { + pcnt = (rt_stats_thread.execution_cycles * 100U) / + rt_stats_all.execution_cycles; + + /* + * z_prf() does not support %llu by default unless + * CONFIG_MINIMAL_LIBC_LL_PRINTF=y. So do conditional + * compilation to avoid blindly enabling this kconfig + * so it won't increase RAM/ROM usage too much on 32-bit + * targets. + */ + shell_print(sh, "\tTotal execution cycles: %u (%u %%)", + (uint32_t)rt_stats_thread.execution_cycles, + pcnt); +#ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS + shell_print(sh, "\tCurrent execution cycles: %u", + (uint32_t)rt_stats_thread.current_cycles); + shell_print(sh, "\tPeak execution cycles: %u", + (uint32_t)rt_stats_thread.peak_cycles); + shell_print(sh, "\tAverage execution cycles: %u", + (uint32_t)rt_stats_thread.average_cycles); +#endif /* CONFIG_SCHED_THREAD_USAGE_ANALYSIS */ + } else { + shell_print(sh, "\tTotal execution cycles: ? (? %%)"); +#ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS + shell_print(sh, "\tCurrent execution cycles: ?"); + shell_print(sh, "\tPeak execution cycles: ?"); + shell_print(sh, "\tAverage execution cycles: ?"); +#endif /* CONFIG_SCHED_THREAD_USAGE_ANALYSIS */ + } +#endif /* CONFIG_THREAD_RUNTIME_STATS */ + + ret = k_thread_stack_space_get(thread, &unused); + if (ret) { + shell_print(sh, + "Unable to determine unused stack size (%d)\n", + ret); + } else { + /* Calculate the real size reserved for the stack */ + pcnt = ((size - unused) * 100U) / size; + + shell_print(sh, + "\tstack size %zu, unused %zu, usage %zu / %zu (%u %%)\n", + size, unused, size - unused, size, pcnt); + } + +} + +static int cmd_kernel_thread_list(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "Scheduler: %u since last call", sys_clock_elapsed()); + shell_print(sh, "Threads:"); + + /* + * Use the unlocked version as the callback itself might call + * arch_irq_unlock. + */ + k_thread_foreach_unlocked(shell_tdata_dump, (void *)sh); + + return 0; +} + +KERNEL_THREAD_CMD_ADD(list, NULL, "List kernel threads.", cmd_kernel_thread_list); diff --git a/subsys/shell/modules/kernel_service/thread/mask.c b/subsys/shell/modules/kernel_service/thread/mask.c new file mode 100644 index 00000000000..0da1ad39fa8 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/mask.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_thread_mask_clear(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int rc, err = 0; + struct k_thread *thread; + + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + + rc = k_thread_cpu_mask_clear(thread); + if (rc != 0) { + shell_error(sh, "Failed - %d", rc); + } else { + shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, + thread->base.cpu_mask); + } + + return rc; +} + +static int cmd_kernel_thread_mask_enable_all(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int rc, err = 0; + struct k_thread *thread; + + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + + rc = k_thread_cpu_mask_enable_all(thread); + if (rc != 0) { + shell_error(sh, "Failed - %d", rc); + } else { + shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, + thread->base.cpu_mask); + } + + return rc; +} + +static int cmd_kernel_thread_mask_enable(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int rc, cpu, err = 0; + struct k_thread *thread; + + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + + cpu = (int)shell_strtol(argv[2], 10, &err); + if (err != 0) { + shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); + return err; + } + + rc = k_thread_cpu_mask_enable(thread, cpu); + if (rc != 0) { + shell_error(sh, "Failed - %d", rc); + } else { + shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, + thread->base.cpu_mask); + } + + return rc; +} + +static int cmd_kernel_thread_mask_disable(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int rc, cpu, err = 0; + struct k_thread *thread; + + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + + cpu = (int)shell_strtol(argv[2], 10, &err); + if (err != 0) { + shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); + return err; + } + + rc = k_thread_cpu_mask_disable(thread, cpu); + if (rc != 0) { + shell_error(sh, "Failed - %d", rc); + } else { + shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, + thread->base.cpu_mask); + } + + return rc; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel_thread_mask, + SHELL_CMD_ARG(clear, NULL, + "Sets all CPU enable masks to zero.\n" + "Usage: kernel thread mask clear ", + cmd_kernel_thread_mask_clear, 2, 0), + SHELL_CMD_ARG(enable_all, NULL, + "Sets all CPU enable masks to one.\n" + "Usage: kernel thread mask enable_all ", + cmd_kernel_thread_mask_enable_all, 2, 0), + SHELL_CMD_ARG(enable, NULL, + "Enable thread to run on specified CPU.\n" + "Usage: kernel thread mask enable ", + cmd_kernel_thread_mask_enable, 3, 0), + SHELL_CMD_ARG(disable, NULL, + "Prevent thread to run on specified CPU.\n" + "Usage: kernel thread mask disable ", + cmd_kernel_thread_mask_disable, 3, 0), + SHELL_SUBCMD_SET_END /* Array terminated. */ +); + +KERNEL_THREAD_CMD_ARG_ADD(mask, &sub_kernel_thread_mask, "Configure thread CPU mask affinity.", + NULL, 2, 0); diff --git a/subsys/shell/modules/kernel_service/thread/pin.c b/subsys/shell/modules/kernel_service/thread/pin.c new file mode 100644 index 00000000000..de70b7b5822 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/pin.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_thread_pin(const struct shell *sh, + size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + int cpu, err = 0; + struct k_thread *thread; + + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + + cpu = shell_strtoul(argv[2], 10, &err); + if (err != 0) { + shell_error(sh, "Unable to parse CPU ID %s (err %d)", argv[2], err); + return err; + } + + shell_print(sh, "Pinning %p %s to CPU %d", (void *)thread, thread->name, cpu); + err = k_thread_cpu_pin(thread, cpu); + if (err != 0) { + shell_error(sh, "Failed - %d", err); + } else { + shell_print(sh, "%p %s cpu_mask: 0x%x", (void *)thread, thread->name, + thread->base.cpu_mask); + } + + return err; +} + +KERNEL_THREAD_CMD_ARG_ADD(pin, NULL, + "Pin thread to a CPU.\n" + "Usage: kernel pin ", + cmd_kernel_thread_pin, 3, 0); diff --git a/subsys/shell/modules/kernel_service/thread/stacks.c b/subsys/shell/modules/kernel_service/thread/stacks.c new file mode 100644 index 00000000000..dc884516a5a --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/stacks.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +#include + +#include + +#if defined(CONFIG_THREAD_MAX_NAME_LEN) +#define THREAD_MAX_NAM_LEN CONFIG_THREAD_MAX_NAME_LEN +#else +#define THREAD_MAX_NAM_LEN 10 +#endif + +static void shell_stack_dump(const struct k_thread *thread, void *user_data) +{ + const struct shell *sh = (const struct shell *)user_data; + unsigned int pcnt; + size_t unused; + size_t size = thread->stack_info.size; + const char *tname; + int ret; + + ret = k_thread_stack_space_get(thread, &unused); + if (ret) { + shell_print(sh, + "Unable to determine unused stack size (%d)\n", + ret); + return; + } + + tname = k_thread_name_get((struct k_thread *)thread); + + /* Calculate the real size reserved for the stack */ + pcnt = ((size - unused) * 100U) / size; + + shell_print( + (const struct shell *)user_data, "%p %-" STRINGIFY(THREAD_MAX_NAM_LEN) "s " + "(real size %4zu):\tunused %4zu\tusage %4zu / %4zu (%2u %%)", + thread, tname ? tname : "NA", size, unused, size - unused, size, pcnt); +} + +K_KERNEL_STACK_ARRAY_DECLARE(z_interrupt_stacks, CONFIG_MP_MAX_NUM_CPUS, + CONFIG_ISR_STACK_SIZE); + +static int cmd_kernel_thread_stacks(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + char pad[THREAD_MAX_NAM_LEN] = { 0 }; + + memset(pad, ' ', MAX((THREAD_MAX_NAM_LEN - strlen("IRQ 00")), 1)); + + /* + * Use the unlocked version as the callback itself might call + * arch_irq_unlock. + */ + k_thread_foreach_unlocked(shell_stack_dump, (void *)sh); + + /* Placeholder logic for interrupt stack until we have better + * kernel support, including dumping arch-specific exception-related + * stack buffers. + */ + unsigned int num_cpus = arch_num_cpus(); + + for (int i = 0; i < num_cpus; i++) { + size_t unused; + const uint8_t *buf = K_KERNEL_STACK_BUFFER(z_interrupt_stacks[i]); + size_t size = K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[i]); + int err = z_stack_space_get(buf, size, &unused); + + (void)err; + __ASSERT_NO_MSG(err == 0); + + shell_print(sh, + "%p IRQ %02d %s(real size %4zu):\tunused %4zu\tusage %4zu / %4zu (%2zu %%)", + &z_interrupt_stacks[i], i, pad, size, unused, size - unused, size, + ((size - unused) * 100U) / size); + } + + return 0; +} + +KERNEL_THREAD_CMD_ADD(stacks, NULL, "List threads stack usage.", cmd_kernel_thread_stacks); diff --git a/subsys/shell/modules/kernel_service/thread/thread.c b/subsys/shell/modules/kernel_service/thread/thread.c new file mode 100644 index 00000000000..dc9bf6ceec9 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/thread.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Meta Platforms + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +#include +#include +#include + +struct thread_entry { + const struct k_thread *const thread; + bool valid; +}; + +static void thread_valid_cb(const struct k_thread *cthread, void *user_data) +{ + struct thread_entry *entry = user_data; + + if (cthread == entry->thread) { + entry->valid = true; + } +} + +bool z_thread_is_valid(const struct k_thread *thread) +{ + struct thread_entry entry = { + .thread = thread, + .valid = false, + }; + + k_thread_foreach(thread_valid_cb, &entry); + + return entry.valid; +} + +SHELL_SUBCMD_SET_CREATE(sub_kernel_thread, (thread)); +KERNEL_CMD_ADD(thread, &sub_kernel_thread, "Kernel threads.", NULL); diff --git a/subsys/shell/modules/kernel_service/thread/unwind.c b/subsys/shell/modules/kernel_service/thread/unwind.c new file mode 100644 index 00000000000..e75df2d4864 --- /dev/null +++ b/subsys/shell/modules/kernel_service/thread/unwind.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Meta Platforms + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include +#include + +static bool print_trace_address(void *arg, unsigned long ra) +{ + const struct shell *sh = arg; +#ifdef CONFIG_SYMTAB + uint32_t offset = 0; + const char *name = symtab_find_symbol_name(ra, &offset); + + shell_print(sh, "ra: %p [%s+0x%x]", (void *)ra, name, offset); +#else + shell_print(sh, "ra: %p", (void *)ra); +#endif + + return true; +} + +static int cmd_kernel_thread_unwind(const struct shell *sh, size_t argc, char **argv) +{ + struct k_thread *thread; + int err = 0; + + if (argc == 1) { + thread = _current; + } else { + thread = UINT_TO_POINTER(shell_strtoull(argv[1], 16, &err)); + if (err != 0) { + shell_error(sh, "Unable to parse thread ID %s (err %d)", argv[1], err); + return err; + } + + if (!z_thread_is_valid(thread)) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + } + shell_print(sh, "Unwinding %p %s", (void *)thread, thread->name); + + arch_stack_walk(print_trace_address, (void *)sh, thread, NULL); + + return 0; +} + +KERNEL_THREAD_CMD_ARG_ADD(unwind, NULL, "Unwind a thread.", cmd_kernel_thread_unwind, 1, 1); diff --git a/subsys/shell/modules/kernel_service/uptime.c b/subsys/shell/modules/kernel_service/uptime.c new file mode 100644 index 00000000000..c00f4f16071 --- /dev/null +++ b/subsys/shell/modules/kernel_service/uptime.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +#define MINUTES_FACTOR (MSEC_PER_SEC * SEC_PER_MIN) +#define HOURS_FACTOR (MINUTES_FACTOR * MIN_PER_HOUR) +#define DAYS_FACTOR (HOURS_FACTOR * HOUR_PER_DAY) + +static int cmd_kernel_uptime(const struct shell *sh, size_t argc, char **argv) +{ + int64_t milliseconds = k_uptime_get(); + int64_t days; + int64_t hours; + int64_t minutes; + int64_t seconds; + + if (argc == 1) { + shell_print(sh, "Uptime: %llu ms", milliseconds); + return 0; + } + + /* No need to enable the getopt and getopt_long for just one option. */ + if (strcmp("-p", argv[1]) && strcmp("--pretty", argv[1]) != 0) { + shell_error(sh, "Unsupported option: %s", argv[1]); + return -EIO; + } + + days = milliseconds / DAYS_FACTOR; + milliseconds %= DAYS_FACTOR; + hours = milliseconds / HOURS_FACTOR; + milliseconds %= HOURS_FACTOR; + minutes = milliseconds / MINUTES_FACTOR; + milliseconds %= MINUTES_FACTOR; + seconds = milliseconds / MSEC_PER_SEC; + milliseconds = milliseconds % MSEC_PER_SEC; + + shell_print(sh, + "uptime: %llu days, %llu hours, %llu minutes, %llu seconds, %llu milliseconds", + days, hours, minutes, seconds, milliseconds); + + return 0; +} + +KERNEL_CMD_ARG_ADD(uptime, NULL, "Kernel uptime. Can be called with the -p or --pretty options", + cmd_kernel_uptime, 1, 1); diff --git a/subsys/shell/modules/kernel_service/version.c b/subsys/shell/modules/kernel_service/version.c new file mode 100644 index 00000000000..fd1903cbc66 --- /dev/null +++ b/subsys/shell/modules/kernel_service/version.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "kernel_shell.h" + +#include + +static int cmd_kernel_version(const struct shell *sh, + size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "Zephyr version %s", KERNEL_VERSION_STRING); + return 0; +} + +KERNEL_CMD_ADD(version, NULL, "Kernel version.", cmd_kernel_version);