From 096c088bf18b280774291e88ba00fd539c5adb2b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 11 Apr 2025 14:53:10 +0400 Subject: [PATCH 1/8] vcp, cli: Handle Tx/Rx events before Connect/Disconnect + extra fixes (#4181) * cli_vcp: handle tx/rx before connext/disconnect * cli_vcp: disable trace * cli_perf: advanced error reporting * cli_vcp: reset tx flag directly in event handler * fix formatting * cli_vcp: make tx flag volatile * storage_settings: fix scene ids * cli_shell: add safety check to set_prompt * cli_registry: move from bptree to dict, fix memory leak * cli_vcp: go back to message queue for event signaling * loader: move BeforeLoad event even earlier * fix formatting --- .gitignore | 4 ++ applications/services/cli/cli_vcp.c | 65 ++++++++++--------- applications/services/loader/loader.c | 12 ++-- .../storage_settings/storage_settings.c | 4 +- lib/toolbox/cli/cli_registry.c | 32 +++++---- lib/toolbox/cli/cli_registry_i.h | 18 ++--- lib/toolbox/cli/shell/cli_shell.c | 15 +++-- lib/toolbox/cli/shell/cli_shell_completions.c | 6 +- scripts/serial_cli_perf.py | 18 ++++- 9 files changed, 92 insertions(+), 82 deletions(-) diff --git a/.gitignore b/.gitignore index 84b8e8319..79f2a8058 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ PVS-Studio.log # JS packages node_modules/ + +# cli_perf script output in case of errors +/block.bin +/return_block.bin diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index f4b539e26..def1949e2 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -30,27 +30,23 @@ typedef struct { } CliVcpMessage; typedef enum { - CliVcpInternalEventConnected = (1 << 0), - CliVcpInternalEventDisconnected = (1 << 1), - CliVcpInternalEventTxDone = (1 << 2), - CliVcpInternalEventRx = (1 << 3), + CliVcpInternalEventConnected, + CliVcpInternalEventDisconnected, + CliVcpInternalEventTxDone, + CliVcpInternalEventRx, } CliVcpInternalEvent; -#define CliVcpInternalEventAll \ - (CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \ - CliVcpInternalEventRx) - struct CliVcp { FuriEventLoop* event_loop; FuriMessageQueue* message_queue; // thread_id, event); + furi_check(furi_message_queue_put(cli_vcp->internal_evt_queue, &event, 0) == FuriStatusOk); } static void cli_vcp_cdc_tx_done(void* context) { CliVcp* cli_vcp = context; + cli_vcp->is_currently_transmitting = false; cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone); } @@ -190,12 +187,25 @@ static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) /** * Processes messages arriving from CDC event callbacks */ -static void cli_vcp_internal_event_happened(void* context) { +static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* context) { CliVcp* cli_vcp = context; - CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0); - furi_check(!(event & FuriFlagError)); + CliVcpInternalEvent event; + furi_check(furi_message_queue_get(object, &event, 0) == FuriStatusOk); - if(event & CliVcpInternalEventDisconnected) { + switch(event) { + case CliVcpInternalEventRx: { + VCP_TRACE(TAG, "Rx"); + cli_vcp_maybe_receive_data(cli_vcp); + break; + } + + case CliVcpInternalEventTxDone: { + VCP_TRACE(TAG, "TxDone"); + cli_vcp_maybe_send_data(cli_vcp); + break; + } + + case CliVcpInternalEventDisconnected: { if(!cli_vcp->is_connected) return; FURI_LOG_D(TAG, "Disconnected"); cli_vcp->is_connected = false; @@ -204,9 +214,10 @@ static void cli_vcp_internal_event_happened(void* context) { pipe_detach_from_event_loop(cli_vcp->own_pipe); pipe_free(cli_vcp->own_pipe); cli_vcp->own_pipe = NULL; + break; } - if(event & CliVcpInternalEventConnected) { + case CliVcpInternalEventConnected: { if(cli_vcp->is_connected) return; FURI_LOG_D(TAG, "Connected"); cli_vcp->is_connected = true; @@ -233,17 +244,8 @@ static void cli_vcp_internal_event_happened(void* context) { cli_vcp->shell = cli_shell_alloc( cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config); cli_shell_start(cli_vcp->shell); + break; } - - if(event & CliVcpInternalEventRx) { - VCP_TRACE(TAG, "Rx"); - cli_vcp_maybe_receive_data(cli_vcp); - } - - if(event & CliVcpInternalEventTxDone) { - VCP_TRACE(TAG, "TxDone"); - cli_vcp->is_currently_transmitting = false; - cli_vcp_maybe_send_data(cli_vcp); } } @@ -253,7 +255,6 @@ static void cli_vcp_internal_event_happened(void* context) { static CliVcp* cli_vcp_alloc(void) { CliVcp* cli_vcp = malloc(sizeof(CliVcp)); - cli_vcp->thread_id = furi_thread_get_current_id(); cli_vcp->event_loop = furi_event_loop_alloc(); @@ -265,8 +266,14 @@ static CliVcp* cli_vcp_alloc(void) { cli_vcp_message_received, cli_vcp); - furi_event_loop_subscribe_thread_flags( - cli_vcp->event_loop, cli_vcp_internal_event_happened, cli_vcp); + cli_vcp->internal_evt_queue = + furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpInternalEvent)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->internal_evt_queue, + FuriEventLoopEventIn, + cli_vcp_internal_event_happened, + cli_vcp); cli_vcp->main_registry = furi_record_open(RECORD_CLI); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index d3cd0022e..136b3c20b 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -418,9 +418,6 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -508,10 +505,6 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( result.value = loader_make_success_status(error_message); result.error = LoaderStatusErrorUnknown; - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); - do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); size_t start = furi_get_tick(); @@ -566,6 +559,7 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( if(result.value != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + LoaderEvent event; event.type = LoaderEventTypeApplicationLoadFailed; furi_pubsub_publish(loader->pubsub, &event); } @@ -615,6 +609,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( status.value = loader_make_success_status(error_message); status.error = LoaderStatusErrorUnknown; + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); + do { // check lock if(loader_do_is_locked(loader)) { diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index b4cde7b6b..07656431c 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -5,8 +5,8 @@ const SubmenuSettingsHelperDescriptor descriptor_template = { .options_cnt = 6, .options = { - {.name = "About Internal Storage", .scene_id = StorageSettingsSDInfo}, - {.name = "About SD Card", .scene_id = StorageSettingsInternalInfo}, + {.name = "About Internal Storage", .scene_id = StorageSettingsInternalInfo}, + {.name = "About SD Card", .scene_id = StorageSettingsSDInfo}, {.name = "Unmount SD Card", .scene_id = StorageSettingsUnmountConfirm}, {.name = "Format SD Card", .scene_id = StorageSettingsFormatConfirm}, {.name = "Benchmark SD Card", .scene_id = StorageSettingsBenchmarkConfirm}, diff --git a/lib/toolbox/cli/cli_registry.c b/lib/toolbox/cli/cli_registry.c index 35bed19f2..91f7c4046 100644 --- a/lib/toolbox/cli/cli_registry.c +++ b/lib/toolbox/cli/cli_registry.c @@ -3,16 +3,16 @@ #include #include -#define TAG "cli" +#define TAG "CliRegistry" struct CliRegistry { - CliCommandTree_t commands; + CliCommandDict_t commands; FuriMutex* mutex; }; CliRegistry* cli_registry_alloc(void) { CliRegistry* registry = malloc(sizeof(CliRegistry)); - CliCommandTree_init(registry->commands); + CliCommandDict_init(registry->commands); registry->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); return registry; } @@ -20,7 +20,7 @@ CliRegistry* cli_registry_alloc(void) { void cli_registry_free(CliRegistry* registry) { furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); furi_mutex_free(registry->mutex); - CliCommandTree_clear(registry->commands); + CliCommandDict_clear(registry->commands); free(registry); } @@ -61,7 +61,7 @@ void cli_registry_add_command_ex( }; furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_set_at(registry->commands, name_str, command); + CliCommandDict_set_at(registry->commands, name_str, command); furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); furi_string_free(name_str); @@ -79,7 +79,7 @@ void cli_registry_delete_command(CliRegistry* registry, const char* name) { } while(name_replace != FURI_STRING_FAILURE); furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_erase(registry->commands, name_str); + CliCommandDict_erase(registry->commands, name_str); furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); furi_string_free(name_str); @@ -91,7 +91,7 @@ bool cli_registry_get_command( CliRegistryCommand* result) { furi_assert(registry); furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); - CliRegistryCommand* data = CliCommandTree_get(registry->commands, command); + CliRegistryCommand* data = CliCommandDict_get(registry->commands, command); if(data) *result = *data; furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); @@ -103,16 +103,14 @@ void cli_registry_remove_external_commands(CliRegistry* registry) { furi_check(registry); furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); - // FIXME FL-3977: memory leak somewhere within this function - - CliCommandTree_t internal_cmds; - CliCommandTree_init(internal_cmds); + CliCommandDict_t internal_cmds; + CliCommandDict_init(internal_cmds); for - M_EACH(item, registry->commands, CliCommandTree_t) { - if(!(item->value_ptr->flags & CliCommandFlagExternal)) - CliCommandTree_set_at(internal_cmds, *item->key_ptr, *item->value_ptr); + M_EACH(item, registry->commands, CliCommandDict_t) { + if(!(item->value.flags & CliCommandFlagExternal)) + CliCommandDict_set_at(internal_cmds, item->key, item->value); } - CliCommandTree_move(registry->commands, internal_cmds); + CliCommandDict_move(registry->commands, internal_cmds); furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); } @@ -148,7 +146,7 @@ void cli_registry_reload_external_commands( .execute_callback = NULL, .flags = CliCommandFlagExternal, }; - CliCommandTree_set_at(registry->commands, plugin_name, command); + CliCommandDict_set_at(registry->commands, plugin_name, command); } furi_string_free(plugin_name); @@ -172,7 +170,7 @@ void cli_registry_unlock(CliRegistry* registry) { furi_mutex_release(registry->mutex); } -CliCommandTree_t* cli_registry_get_commands(CliRegistry* registry) { +CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry) { furi_assert(registry); return ®istry->commands; } diff --git a/lib/toolbox/cli/cli_registry_i.h b/lib/toolbox/cli/cli_registry_i.h index 95b7c55da..31995832f 100644 --- a/lib/toolbox/cli/cli_registry_i.h +++ b/lib/toolbox/cli/cli_registry_i.h @@ -6,7 +6,7 @@ #pragma once #include -#include +#include #include "cli_registry.h" #ifdef __cplusplus @@ -22,19 +22,9 @@ typedef struct { size_t stack_depth; } CliRegistryCommand; -#define CLI_COMMANDS_TREE_RANK 4 +DICT_DEF2(CliCommandDict, FuriString*, FURI_STRING_OPLIST, CliRegistryCommand, M_POD_OPLIST); -// -V:BPTREE_DEF2:1103 -// -V:BPTREE_DEF2:524 -BPTREE_DEF2( - CliCommandTree, - CLI_COMMANDS_TREE_RANK, - FuriString*, - FURI_STRING_OPLIST, - CliRegistryCommand, - M_POD_OPLIST); - -#define M_OPL_CliCommandTree_t() BPTREE_OPLIST2(CliCommandTree, FURI_STRING_OPLIST, M_POD_OPLIST) +#define M_OPL_CliCommandDict_t() DICT_OPLIST(CliCommandDict, FURI_STRING_OPLIST, M_POD_OPLIST) bool cli_registry_get_command( CliRegistry* registry, @@ -48,7 +38,7 @@ void cli_registry_unlock(CliRegistry* registry); /** * @warning Surround calls to this function with `cli_registry_[un]lock` */ -CliCommandTree_t* cli_registry_get_commands(CliRegistry* registry); +CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry); #ifdef __cplusplus } diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index daf5065ec..8aa7c387a 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -103,15 +103,15 @@ void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { printf("Available commands:\r\n" ANSI_FG_GREEN); cli_registry_lock(registry); - CliCommandTree_t* commands = cli_registry_get_commands(registry); - size_t commands_count = CliCommandTree_size(*commands); + CliCommandDict_t* commands = cli_registry_get_commands(registry); + size_t commands_count = CliCommandDict_size(*commands); - CliCommandTree_it_t iterator; - CliCommandTree_it(iterator, *commands); + CliCommandDict_it_t iterator; + CliCommandDict_it(iterator, *commands); for(size_t i = 0; i < commands_count; i++) { - const CliCommandTree_itref_t* item = CliCommandTree_cref(iterator); - printf("%-30s", furi_string_get_cstr(*item->key_ptr)); - CliCommandTree_next(iterator); + const CliCommandDict_itref_t* item = CliCommandDict_cref(iterator); + printf("%-30s", furi_string_get_cstr(item->key)); + CliCommandDict_next(iterator); if(i % columns == columns - 1) printf("\r\n"); } @@ -474,5 +474,6 @@ void cli_shell_join(CliShell* shell) { void cli_shell_set_prompt(CliShell* shell, const char* prompt) { furi_check(shell); + furi_check(furi_thread_get_state(shell->thread) == FuriThreadStateStopped); shell->prompt = prompt; } diff --git a/lib/toolbox/cli/shell/cli_shell_completions.c b/lib/toolbox/cli/shell/cli_shell_completions.c index 823f91fb9..7a178705d 100644 --- a/lib/toolbox/cli/shell/cli_shell_completions.c +++ b/lib/toolbox/cli/shell/cli_shell_completions.c @@ -111,10 +111,10 @@ void cli_shell_completions_fill_variants(CliShellCompletions* completions) { if(segment.type == CliShellCompletionSegmentTypeCommand) { CliRegistry* registry = completions->registry; cli_registry_lock(registry); - CliCommandTree_t* commands = cli_registry_get_commands(registry); + CliCommandDict_t* commands = cli_registry_get_commands(registry); for - M_EACH(registered_command, *commands, CliCommandTree_t) { - FuriString* command_name = *registered_command->key_ptr; + M_EACH(registered_command, *commands, CliCommandDict_t) { + FuriString* command_name = registered_command->key; if(furi_string_start_with(command_name, input)) { CommandCompletions_push_back(completions->variants, command_name); } diff --git a/scripts/serial_cli_perf.py b/scripts/serial_cli_perf.py index 0fed7c393..3d612e6de 100644 --- a/scripts/serial_cli_perf.py +++ b/scripts/serial_cli_perf.py @@ -32,16 +32,27 @@ def main(): bytes_to_send = args.length block_size = 1024 + success = True while bytes_to_send: actual_size = min(block_size, bytes_to_send) # can't use 0x03 because that's ASCII ETX, or Ctrl+C - block = bytes([randint(4, 255) for _ in range(actual_size)]) + # block = bytes([randint(4, 255) for _ in range(actual_size)]) + block = bytes([4 + (i // 64) for i in range(actual_size)]) port.write(block) return_block = port.read(actual_size) if return_block != block: - logger.error("Incorrect block received") + with open("block.bin", "wb") as f: + f.write(block) + with open("return_block.bin", "wb") as f: + f.write(return_block) + + logger.error( + "Incorrect block received. Saved to `block.bin' and `return_block.bin'." + ) + logger.error(f"{bytes_to_send} bytes left. Aborting.") + success = False break bytes_to_send -= actual_size @@ -49,7 +60,8 @@ def main(): end_time = time() delta = end_time - start_time speed = args.length / delta - print(f"Speed: {speed/1024:.2f} KiB/s") + if success: + print(f"Speed: {speed/1024:.2f} KiB/s") port.write(b"\x03") # Ctrl+C port.close() From e1bccf66b32918c5324109b44c9a4e899ef36801 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Sat, 12 Apr 2025 02:38:28 +0400 Subject: [PATCH 2/8] Fix NULL dereference in CLI completions (#4184) * cli_completions: fix null dereference * cli: mark free_blocks as parallel safe * codeowners: add me to co-owners of cli --- .github/CODEOWNERS | 4 +++- applications/services/cli/cli_main_commands.c | 2 +- lib/toolbox/cli/shell/cli_shell_completions.c | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 675679080..5af8ceedb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ /applications/main/u2f/ @DrZlo13 @hedger @gsurkov @nminaylov /applications/services/bt/ @DrZlo13 @hedger @gsurkov @gornekich -/applications/services/cli/ @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/cli/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3 /applications/services/crypto/ @DrZlo13 @hedger @gsurkov @nminaylov /applications/services/desktop/ @DrZlo13 @hedger @gsurkov @nminaylov /applications/services/dolphin/ @DrZlo13 @hedger @gsurkov @nminaylov @@ -64,6 +64,8 @@ /lib/nfc/ @DrZlo13 @hedger @gsurkov @gornekich /lib/one_wire/ @DrZlo13 @hedger @gsurkov /lib/subghz/ @DrZlo13 @hedger @gsurkov @Skorpionm +/lib/toolbox/ @DrZlo13 @hedger @gsurkov +/lib/toolbox/cli @DrZlo13 @hedger @gsurkov @portasynthinca3 # CI/CD /.github/workflows/ @DrZlo13 @hedger @gsurkov diff --git a/applications/services/cli/cli_main_commands.c b/applications/services/cli/cli_main_commands.c index f84c03d8a..508a650de 100644 --- a/applications/services/cli/cli_main_commands.c +++ b/applications/services/cli/cli_main_commands.c @@ -506,7 +506,7 @@ void cli_main_commands_init(CliRegistry* registry) { cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); cli_registry_add_command( - registry, "free_blocks", CliCommandFlagDefault, cli_command_free_blocks, NULL); + registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL); cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); diff --git a/lib/toolbox/cli/shell/cli_shell_completions.c b/lib/toolbox/cli/shell/cli_shell_completions.c index 7a178705d..6b6634dbb 100644 --- a/lib/toolbox/cli/shell/cli_shell_completions.c +++ b/lib/toolbox/cli/shell/cli_shell_completions.c @@ -265,6 +265,7 @@ void cli_shell_completions_render( } } else if(action == CliShellCompletionsActionSelectNoClose) { + if(!CommandCompletions_size(completions->variants)) return; // insert selection into prompt CliShellCompletionSegment segment = cli_shell_completions_segment(completions); FuriString* input = cli_shell_line_get_selected(completions->line); From 868eb1038162305b5e1e3193f6150173e9e95f2e Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 12 Apr 2025 04:21:39 +0100 Subject: [PATCH 3/8] SDK: Fix missing RECORD_CLI define (#4185) * SDK: Fix missing RECORD_CLI define * sdk: added compatibility `cli.h` header * cli: updated porting comments --------- Co-authored-by: hedger --- applications/services/cli/application.fam | 2 +- applications/services/cli/cli.h | 13 +++++++++++++ applications/services/cli/cli_main_commands.h | 4 ++-- targets/f18/api_symbols.csv | 1 + targets/f7/api_symbols.csv | 1 + 5 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 applications/services/cli/cli.h diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 6e0a28164..b305fb6b0 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -22,7 +22,7 @@ App( entry_point="cli_vcp_srv", stack_size=1024, order=10, - sdk_headers=["cli_vcp.h"], + sdk_headers=["cli_vcp.h", "cli.h"], sources=["cli_vcp.c"], ) diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h new file mode 100644 index 000000000..ca787d3db --- /dev/null +++ b/applications/services/cli/cli.h @@ -0,0 +1,13 @@ +#pragma once + +/* + * Compatibility header for ease of porting existing apps. + * In short: + * Cli* is replaced with with CliRegistry* + * cli_* functions are replaced with cli_registry_* functions + * (i.e., cli_add_command() is now cli_registry_add_command()) +*/ + +#include + +#define RECORD_CLI "cli" diff --git a/applications/services/cli/cli_main_commands.h b/applications/services/cli/cli_main_commands.h index d8058f467..ebee4ba1e 100644 --- a/applications/services/cli/cli_main_commands.h +++ b/applications/services/cli/cli_main_commands.h @@ -1,9 +1,9 @@ #pragma once +#include "cli.h" #include #include -#define RECORD_CLI "cli" -#define CLI_APPID "cli" +#define CLI_APPID "cli" void cli_main_commands_init(CliRegistry* registry); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d4cc2bec5..f1ebda528 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2,6 +2,7 @@ entry,status,name,type,params Version,+,85.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, +Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f5f18bc43..2e23dc56b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3,6 +3,7 @@ Version,+,85.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, +Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, From ddf77f58e383bc688d8df6aa179c91d18c128ce1 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 12 Apr 2025 12:27:58 +0100 Subject: [PATCH 4/8] sdk: bump API to force re-upload for the catalog (#4186) --- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f1ebda528..23c9edb63 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,85.0,, +Version,+,86.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2e23dc56b..d142a6374 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,85.0,, +Version,+,86.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, From 1eb57ba4626c11e432ec44b6c9df470bc8b795d7 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 12 Apr 2025 12:35:19 +0100 Subject: [PATCH 5/8] FBT: Fix for Python 3.13 (#4187) Co-authored-by: hedger --- scripts/fbt/appmanifest.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index b5b5d6e12..4da23b3dd 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -158,7 +158,7 @@ class AppManager: f"App {kw.get('appid')} cannot have fal_embedded set" ) - if apptype in AppBuildset.dist_app_types: + if apptype in AppBuildset.DIST_APP_TYPES: # For distributing .fap's resources, there's "fap_file_assets" for app_property in ("resources",): if kw.get(app_property): @@ -258,14 +258,12 @@ class AppBuildset: FlipperAppType.DEBUG: True, FlipperAppType.MENUEXTERNAL: False, } - - @classmethod - @property - def dist_app_types(cls): - """Applications that are installed on SD card""" - return list( - entry[0] for entry in cls.EXTERNAL_APP_TYPES_MAP.items() if entry[1] - ) + DIST_APP_TYPES = list( + # Applications that are installed on SD card + entry[0] + for entry in EXTERNAL_APP_TYPES_MAP.items() + if entry[1] + ) @staticmethod def print_writer(message): From 34a3222ec4082786392bc60f0d4ea1069077d5b4 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 16 Apr 2025 07:20:31 +0400 Subject: [PATCH 6/8] [FL-3979] USB-UART bridge fix (#4189) * cli_vcp: make enable/disable requests synchronous * usb_uart_bridge: open and close vcp record once --- applications/main/gpio/usb_uart_bridge.c | 20 +++++++++----------- applications/services/cli/cli_vcp.c | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 77cd02f63..3e1cefb93 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -60,6 +60,8 @@ struct UsbUartBridge { FuriApiLock cfg_lock; + CliVcp* cli_vcp; + uint8_t rx_buf[USB_CDC_PKT_LEN]; }; @@ -105,15 +107,11 @@ static void usb_uart_on_irq_rx_dma_cb( static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { furi_hal_usb_unlock(); if(vcp_ch == 0) { - CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); - cli_vcp_disable(cli_vcp); - furi_record_close(RECORD_CLI_VCP); + cli_vcp_disable(usb_uart->cli_vcp); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); } else { furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); - CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); - cli_vcp_enable(cli_vcp); - furi_record_close(RECORD_CLI_VCP); + cli_vcp_enable(usb_uart->cli_vcp); } furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); } @@ -122,9 +120,7 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { UNUSED(usb_uart); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); if(vcp_ch != 0) { - CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); - cli_vcp_disable(cli_vcp); - furi_record_close(RECORD_CLI_VCP); + cli_vcp_disable(usb_uart->cli_vcp); } } @@ -176,6 +172,8 @@ static int32_t usb_uart_worker(void* context) { memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig)); + usb_uart->cli_vcp = furi_record_open(RECORD_CLI_VCP); + usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1); usb_uart->tx_sem = furi_semaphore_alloc(1, 1); @@ -308,8 +306,8 @@ static int32_t usb_uart_worker(void* context) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); - CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); - cli_vcp_enable(cli_vcp); + cli_vcp_enable(usb_uart->cli_vcp); + furi_record_close(RECORD_CLI_VCP); return 0; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index def1949e2..cea4de248 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "cli_main_shell.h" #include "cli_main_commands.h" @@ -26,6 +27,7 @@ typedef struct { CliVcpMessageTypeEnable, CliVcpMessageTypeDisable, } type; + FuriApiLock api_lock; union {}; } CliVcpMessage; @@ -182,6 +184,8 @@ static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) furi_hal_usb_set_config(cli_vcp->previous_interface, NULL); break; } + + api_lock_unlock(message.api_lock); } /** @@ -300,16 +304,24 @@ int32_t cli_vcp_srv(void* p) { // Public API // ========== +static void cli_vcp_synchronous_request(CliVcp* cli_vcp, CliVcpMessage* message) { + message->api_lock = api_lock_alloc_locked(); + furi_message_queue_put(cli_vcp->message_queue, message, FuriWaitForever); + api_lock_wait_unlock_and_free(message->api_lock); +} + void cli_vcp_enable(CliVcp* cli_vcp) { + furi_check(cli_vcp); CliVcpMessage message = { .type = CliVcpMessageTypeEnable, }; - furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); + cli_vcp_synchronous_request(cli_vcp, &message); } void cli_vcp_disable(CliVcp* cli_vcp) { + furi_check(cli_vcp); CliVcpMessage message = { .type = CliVcpMessageTypeDisable, }; - furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); + cli_vcp_synchronous_request(cli_vcp, &message); } From c420cbb1a57d72c5f8802c0212abfe250cd87123 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 18 Apr 2025 05:52:15 +0100 Subject: [PATCH 7/8] Desktop: Fix freeze on boot if PIN set (#4193) --- applications/services/cli/cli_vcp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index cea4de248..1f9c77b08 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -164,7 +164,7 @@ static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) switch(message.type) { case CliVcpMessageTypeEnable: - if(cli_vcp->is_enabled) return; + if(cli_vcp->is_enabled) break; FURI_LOG_D(TAG, "Enabling"); cli_vcp->is_enabled = true; @@ -175,7 +175,7 @@ static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) break; case CliVcpMessageTypeDisable: - if(!cli_vcp->is_enabled) return; + if(!cli_vcp->is_enabled) break; FURI_LOG_D(TAG, "Disabling"); cli_vcp->is_enabled = false; From 5b911f5405f104ad3e3051aa70e2a50a5b2a6d16 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 18 Apr 2025 16:41:13 +0400 Subject: [PATCH 8/8] RC fixes (#4192) * cli_shell: fix FL-3983 * fix formatting and submodules * loader: fix FL-3986 * fix submodules --------- Co-authored-by: hedger --- applications/services/loader/loader.c | 1 + lib/toolbox/cli/shell/cli_shell.c | 62 +++++++++++++++------------ 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 136b3c20b..7b37f2510 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -694,6 +694,7 @@ static void loader_do_unlock(Loader* loader) { } static void loader_do_emit_queue_empty_event(Loader* loader) { + if(loader_do_is_locked(loader)) return; FURI_LOG_I(TAG, "Launch queue empty"); LoaderEvent event; event.type = LoaderEventTypeNoMoreAppsInQueue; diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index 8aa7c387a..7a4c7ec1f 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -31,10 +31,18 @@ CliShellKeyComboSet* component_key_combo_sets[] = { static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets)); typedef enum { - CliShellStorageEventMount, - CliShellStorageEventUnmount, + CliShellStorageEventMount = (1 << 0), + CliShellStorageEventUnmount = (1 << 1), } CliShellStorageEvent; +#define CliShellStorageEventAll (CliShellStorageEventMount | CliShellStorageEventUnmount) + +typedef struct { + Storage* storage; + FuriPubSubSubscription* subscription; + FuriEventFlag* event_flag; +} CliShellStorage; + struct CliShell { // Set and freed by external thread CliShellMotd motd; @@ -49,11 +57,7 @@ struct CliShell { FuriEventLoop* event_loop; CliAnsiParser* ansi_parser; FuriEventLoopTimer* ansi_parsing_timer; - - Storage* storage; - FuriPubSubSubscription* storage_subscription; - FuriMessageQueue* storage_event_queue; - + CliShellStorage storage; void* components[CliShellComponentMAX]; }; @@ -256,31 +260,32 @@ const char* cli_shell_get_prompt(CliShell* cli_shell) { // Event handlers // ============== +static void cli_shell_signal_storage_event(CliShell* cli_shell, CliShellStorageEvent event) { + furi_check(!(furi_event_flag_set(cli_shell->storage.event_flag, event) & FuriFlagError)); +} + static void cli_shell_storage_event(const void* message, void* context) { CliShell* cli_shell = context; const StorageEvent* event = message; if(event->type == StorageEventTypeCardMount) { - CliShellStorageEvent cli_event = CliShellStorageEventMount; - furi_check( - furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk); + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventMount); } else if(event->type == StorageEventTypeCardUnmount) { - CliShellStorageEvent cli_event = CliShellStorageEventUnmount; - furi_check( - furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk); + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventUnmount); } } static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) { CliShell* cli_shell = context; - FuriMessageQueue* queue = object; - CliShellStorageEvent event; - furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + FuriEventFlag* event_flag = object; + CliShellStorageEvent event = + furi_event_flag_wait(event_flag, FuriFlagWaitAll, FuriFlagWaitAny, 0); + furi_check(!(event & FuriFlagError)); - if(event == CliShellStorageEventMount) { - cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config); - } else if(event == CliShellStorageEventUnmount) { + if(event & CliShellStorageEventUnmount) { cli_registry_remove_external_commands(cli_shell->registry); + } else if(event & CliShellStorageEventMount) { + cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config); } else { furi_crash(); } @@ -377,25 +382,26 @@ static void cli_shell_init(CliShell* shell) { shell->ansi_parsing_timer = furi_event_loop_timer_alloc( shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, shell); - shell->storage_event_queue = furi_message_queue_alloc(1, sizeof(CliShellStorageEvent)); - furi_event_loop_subscribe_message_queue( + shell->storage.event_flag = furi_event_flag_alloc(); + furi_event_loop_subscribe_event_flag( shell->event_loop, - shell->storage_event_queue, + shell->storage.event_flag, FuriEventLoopEventIn, cli_shell_storage_internal_event, shell); - shell->storage = furi_record_open(RECORD_STORAGE); - shell->storage_subscription = - furi_pubsub_subscribe(storage_get_pubsub(shell->storage), cli_shell_storage_event, shell); + shell->storage.storage = furi_record_open(RECORD_STORAGE); + shell->storage.subscription = furi_pubsub_subscribe( + storage_get_pubsub(shell->storage.storage), cli_shell_storage_event, shell); cli_shell_install_pipe(shell); } static void cli_shell_deinit(CliShell* shell) { - furi_pubsub_unsubscribe(storage_get_pubsub(shell->storage), shell->storage_subscription); + furi_pubsub_unsubscribe( + storage_get_pubsub(shell->storage.storage), shell->storage.subscription); furi_record_close(RECORD_STORAGE); - furi_event_loop_unsubscribe(shell->event_loop, shell->storage_event_queue); - furi_message_queue_free(shell->storage_event_queue); + furi_event_loop_unsubscribe(shell->event_loop, shell->storage.event_flag); + furi_event_flag_free(shell->storage.event_flag); cli_shell_completions_free(shell->components[CliShellComponentCompletions]); cli_shell_line_free(shell->components[CliShellComponentLine]);