From 2817666eb94187adf64146636a99922dd330c549 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:37:52 +0900 Subject: [PATCH 01/11] [FL-3774] Fix 5V on GPIO (#4103) * Move OTG controls to the power service * Accessor: add missing power service import * Power: add is_otg_enabled to info and properly handle OTG enable with VBUS voltage present * Power: method naming * Power: add backward compatibility with old-style use of furi_hal_power * Scripts: lower MIN_GAP_PAGES to 1 * SubGhz: fix incorrect logging tag * SubGhz: delegate OTG management to power service * Power: fix condition race, various improvements Co-authored-by: Aleksandr Kutuzov --- applications/debug/accessor/accessor_app.cpp | 7 +++- applications/debug/accessor/accessor_app.h | 2 + .../examples/example_thermo/example_thermo.c | 10 +++-- applications/main/gpio/gpio_app.c | 3 ++ applications/main/gpio/gpio_app_i.h | 2 + .../main/gpio/scenes/gpio_scene_start.c | 6 +-- applications/main/infrared/infrared_app.c | 12 +++--- applications/main/onewire/onewire_cli.c | 9 ++++- .../main/subghz/helpers/subghz_txrx.c | 21 ++++------ applications/main/subghz/subghz_cli.c | 19 +++------ applications/main/subghz/subghz_i.h | 2 + .../services/expansion/expansion_worker.c | 10 ++++- applications/services/power/power_cli.c | 7 +++- .../services/power/power_service/power.c | 40 +++++++++++++++++-- .../services/power/power_service/power.h | 14 +++++++ .../services/power/power_service/power_api.c | 19 +++++++++ .../services/power/power_service/power_i.h | 2 + applications/services/rpc/rpc_gpio.c | 9 ++++- lib/ibutton/ibutton_worker_modes.c | 19 ++++++--- lib/subghz/protocols/secplus_v1.c | 2 +- scripts/update.py | 2 +- targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- targets/furi_hal_include/furi_hal_power.h | 4 ++ 24 files changed, 169 insertions(+), 60 deletions(-) diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 8d43acc13..59f5d6cc7 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -2,6 +2,7 @@ #include #include #include +#include void AccessorApp::run(void) { AccessorEvent event; @@ -35,16 +36,18 @@ AccessorApp::AccessorApp() : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); expansion = static_cast(furi_record_open(RECORD_EXPANSION)); + power = static_cast(furi_record_open(RECORD_POWER)); onewire_host = onewire_host_alloc(&gpio_ibutton); expansion_disable(expansion); - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } AccessorApp::~AccessorApp() { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); expansion_enable(expansion); furi_record_close(RECORD_EXPANSION); furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_POWER); onewire_host_free(onewire_host); } diff --git a/applications/debug/accessor/accessor_app.h b/applications/debug/accessor/accessor_app.h index 890552f5f..1961f9cbf 100644 --- a/applications/debug/accessor/accessor_app.h +++ b/applications/debug/accessor/accessor_app.h @@ -7,6 +7,7 @@ #include #include #include +#include class AccessorApp { public: @@ -53,4 +54,5 @@ private: NotificationApp* notification; Expansion* expansion; + Power* power; }; diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index e5af819e9..4b225b70c 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -22,7 +22,7 @@ #include #include -#include +#include #define UPDATE_PERIOD_MS 1000UL #define TEXT_STORE_SIZE 64U @@ -76,6 +76,7 @@ typedef struct { FuriThread* reader_thread; FuriMessageQueue* event_queue; OneWireHost* onewire; + Power* power; float temp_celsius; bool has_device; } ExampleThermoContext; @@ -273,7 +274,7 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) { /* Starts the reader thread and handles the input */ static void example_thermo_run(ExampleThermoContext* context) { /* Enable power on external pins */ - furi_hal_power_enable_otg(); + power_enable_otg(context->power, true); /* Configure the hardware in host mode */ onewire_host_start(context->onewire); @@ -309,7 +310,7 @@ static void example_thermo_run(ExampleThermoContext* context) { onewire_host_stop(context->onewire); /* Disable power on external pins */ - furi_hal_power_disable_otg(); + power_enable_otg(context->power, false); } /******************** Initialisation & startup *****************************/ @@ -334,6 +335,8 @@ static ExampleThermoContext* example_thermo_context_alloc(void) { context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN); + context->power = furi_record_open(RECORD_POWER); + return context; } @@ -348,6 +351,7 @@ static void example_thermo_context_free(ExampleThermoContext* context) { view_port_free(context->view_port); furi_record_close(RECORD_GUI); + furi_record_close(RECORD_POWER); } /* The application's entry point. Execution starts from here. */ diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 234cc793a..a21813955 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -30,6 +30,8 @@ GpioApp* gpio_app_alloc(void) { app->gui = furi_record_open(RECORD_GUI); app->gpio_items = gpio_items_alloc(); + app->power = furi_record_open(RECORD_POWER); + app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); @@ -100,6 +102,7 @@ void gpio_app_free(GpioApp* app) { // Close records furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_POWER); expansion_enable(app->expansion); furi_record_close(RECORD_EXPANSION); diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index ce4cb6f55..4fbe25ad8 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -5,6 +5,7 @@ #include "scenes/gpio_scene.h" #include "gpio_custom_event.h" #include "usb_uart_bridge.h" +#include #include #include @@ -27,6 +28,7 @@ struct GpioApp { SceneManager* scene_manager; Widget* widget; DialogEx* dialog; + Power* power; VariableItemList* var_item_list; VariableItem* var_item_flow; diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 421936488..0f37d77d8 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -60,7 +60,7 @@ void gpio_scene_start_on_enter(void* context) { GpioOtgSettingsNum, gpio_scene_start_var_list_change_callback, app); - if(furi_hal_power_is_otg_enabled()) { + if(power_is_otg_enabled(app->power)) { variable_item_set_current_value_index(item, GpioOtgOn); variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]); } else { @@ -80,9 +80,9 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == GpioStartEventOtgOn) { - furi_hal_power_enable_otg(); + power_enable_otg(app->power, true); } else if(event.event == GpioStartEventOtgOff) { - furi_hal_power_disable_otg(); + power_enable_otg(app->power, false); } else if(event.event == GpioStartEventManualControl) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); scene_manager_next_scene(app->scene_manager, GpioSceneTest); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index c50039760..4a03a8220 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -1,6 +1,6 @@ #include "infrared_app_i.h" -#include +#include #include #include @@ -501,12 +501,12 @@ void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin) { } void infrared_enable_otg(InfraredApp* infrared, bool enable) { - if(enable) { - furi_hal_power_enable_otg(); - } else { - furi_hal_power_disable_otg(); - } + Power* power = furi_record_open(RECORD_POWER); + + power_enable_otg(power, enable); infrared->app_state.is_otg_enabled = enable; + + furi_record_close(RECORD_POWER); } static void infrared_load_settings(InfraredApp* infrared) { diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index af3d4e803..74ca6bc1f 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -1,6 +1,8 @@ #include #include +#include + #include #include @@ -26,13 +28,14 @@ static void onewire_cli_print_usage(void) { static void onewire_cli_search(Cli* cli) { UNUSED(cli); OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); + Power* power = furi_record_open(RECORD_POWER); uint8_t address[8]; bool done = false; printf("Search started\r\n"); onewire_host_start(onewire); - furi_hal_power_enable_otg(); + power_enable_otg(power, true); while(!done) { if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { @@ -49,8 +52,10 @@ static void onewire_cli_search(Cli* cli) { furi_delay_ms(100); } - furi_hal_power_disable_otg(); + power_enable_otg(power, false); + onewire_host_free(onewire); + furi_record_close(RECORD_POWER); } void onewire_cli(Cli* cli, FuriString* args, void* context) { diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index e3a0c6057..eaa56c549 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -4,27 +4,22 @@ #include #include +#include + #define TAG "SubGhz" static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { UNUSED(instance); - uint8_t attempts = 5; - while(--attempts > 0) { - if(furi_hal_power_enable_otg()) break; - } - if(attempts == 0) { - if(furi_hal_power_get_usb_voltage() < 4.5f) { - FURI_LOG_E( - TAG, - "Error power otg enable. BQ2589 check otg fault = %d", - furi_hal_power_check_otg_fault() ? 1 : 0); - } - } + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { UNUSED(instance); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } SubGhzTxRx* subghz_txrx_alloc(void) { diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 88f9bfa38..54e02196d 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -28,22 +28,15 @@ #define TAG "SubGhzCli" static void subghz_cli_radio_device_power_on(void) { - uint8_t attempts = 5; - while(--attempts > 0) { - if(furi_hal_power_enable_otg()) break; - } - if(attempts == 0) { - if(furi_hal_power_get_usb_voltage() < 4.5f) { - FURI_LOG_E( - "TAG", - "Error power otg enable. BQ2589 check otg fault = %d", - furi_hal_power_check_otg_fault() ? 1 : 0); - } - } + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } static void subghz_cli_radio_device_power_off(void) { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } static SubGhzEnvironment* subghz_cli_environment_init(void) { diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 08687a4f7..b210dd22b 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -28,6 +28,8 @@ #include "rpc/rpc_app.h" +#include + #include "helpers/subghz_threshold_rssi.h" #include "helpers/subghz_txrx.h" diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index c05b9cc85..ac2a5935b 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -1,6 +1,8 @@ #include "expansion_worker.h" +#include #include + #include #include @@ -250,9 +252,13 @@ static bool expansion_worker_handle_state_connected( if(!expansion_worker_rpc_session_open(instance)) break; instance->state = ExpansionWorkerStateRpcActive; } else if(command == ExpansionFrameControlCommandEnableOtg) { - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } else if(command == ExpansionFrameControlCommandDisableOtg) { - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } else { break; } diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 93d0f232a..121552768 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -30,13 +30,16 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) { void power_cli_5v(Cli* cli, FuriString* args) { UNUSED(cli); + Power* power = furi_record_open(RECORD_POWER); if(!furi_string_cmp(args, "0")) { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else if(!furi_string_cmp(args, "1")) { - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } else { cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args)); } + + furi_record_close(RECORD_POWER); } void power_cli_3v3(Cli* cli, FuriString* args) { diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index b73c4a1dd..fa86328df 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -64,6 +64,7 @@ static bool power_update_info(Power* power) { .is_charging = furi_hal_power_is_charging(), .gauge_is_ok = furi_hal_power_gauge_is_ok(), .is_shutdown_requested = furi_hal_power_is_shutdown_requested(), + .is_otg_enabled = furi_hal_power_is_otg_enabled(), .charge = furi_hal_power_get_pct(), .health = furi_hal_power_get_bat_health_pct(), .capacity_remaining = furi_hal_power_get_battery_remaining_capacity(), @@ -216,6 +217,30 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) { case PowerMessageTypeShowBatteryLowWarning: power->show_battery_low_warning = *msg.bool_param; break; + case PowerMessageTypeSwitchOTG: + power->is_otg_requested = *msg.bool_param; + if(power->is_otg_requested) { + // Only try to enable if VBUS voltage is low, otherwise charger will refuse + if(power->info.voltage_vbus < 4.5f) { + size_t retries = 5; + while(retries-- > 0) { + if(furi_hal_power_enable_otg()) { + break; + } + } + if(!retries) { + FURI_LOG_W(TAG, "Failed to enable OTG, will try later"); + } + } else { + FURI_LOG_W( + TAG, + "Postponing OTG enable: VBUS(%0.1f) >= 4.5v", + (double)power->info.voltage_vbus); + } + } else { + furi_hal_power_disable_otg(); + } + break; default: furi_crash(); } @@ -241,9 +266,18 @@ static void power_tick_callback(void* context) { if(need_refresh) { view_port_update(power->battery_view_port); } - // Check OTG status and disable it in case of fault - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_check_otg_status(); + // Check OTG status, disable in case of a fault + if(furi_hal_power_check_otg_fault()) { + FURI_LOG_E(TAG, "OTG fault detected, disabling OTG"); + furi_hal_power_disable_otg(); + power->is_otg_requested = false; + } + + // Change OTG state if needed (i.e. after disconnecting USB power) + if(power->is_otg_requested && + (!power->info.is_otg_enabled && power->info.voltage_vbus < 4.5f)) { + FURI_LOG_D(TAG, "OTG requested but not enabled, enabling OTG"); + furi_hal_power_enable_otg(); } } diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 0168a8656..0e1de6449 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -39,6 +39,7 @@ typedef struct { bool gauge_is_ok; bool is_charging; bool is_shutdown_requested; + bool is_otg_enabled; float current_charger; float current_gauge; @@ -96,6 +97,19 @@ bool power_is_battery_healthy(Power* power); */ void power_enable_low_battery_level_notification(Power* power, bool enable); +/** Enable or disable OTG + * + * @param power Power instance + * @param enable true - enable, false - disable + */ +void power_enable_otg(Power* power, bool enable); + +/** Check OTG status + * + * @return true if OTG is requested + */ +bool power_is_otg_enabled(Power* power); + #ifdef __cplusplus } #endif diff --git a/applications/services/power/power_service/power_api.c b/applications/services/power/power_service/power_api.c index 6f7515f5e..f634f15e3 100644 --- a/applications/services/power/power_service/power_api.c +++ b/applications/services/power/power_service/power_api.c @@ -70,3 +70,22 @@ void power_enable_low_battery_level_notification(Power* power, bool enable) { furi_check( furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); } + +void power_enable_otg(Power* power, bool enable) { + furi_check(power); + + PowerMessage msg = { + .type = PowerMessageTypeSwitchOTG, + .bool_param = &enable, + .lock = api_lock_alloc_locked(), + }; + + furi_check( + furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); + api_lock_wait_unlock_and_free(msg.lock); +} + +bool power_is_otg_enabled(Power* power) { + furi_check(power); + return power->is_otg_requested; +} diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h index a0c02623a..f08fd8f88 100644 --- a/applications/services/power/power_service/power_i.h +++ b/applications/services/power/power_service/power_i.h @@ -33,6 +33,7 @@ struct Power { bool battery_low; bool show_battery_low_warning; + bool is_otg_requested; uint8_t battery_level; uint8_t power_off_timeout; }; @@ -48,6 +49,7 @@ typedef enum { PowerMessageTypeGetInfo, PowerMessageTypeIsBatteryHealthy, PowerMessageTypeShowBatteryLowWarning, + PowerMessageTypeSwitchOTG, } PowerMessageType; typedef struct { diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 40fc898a0..d05783afc 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -4,6 +4,7 @@ #include #include #include +#include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { switch(rpc_pin) { @@ -218,12 +219,16 @@ void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + Power* power = furi_record_open(RECORD_POWER); + if(mode == PB_Gpio_GpioOtgMode_OFF) { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else { - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } + furi_record_close(RECORD_POWER); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } diff --git a/lib/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c index 5900b10a2..ff76e784d 100644 --- a/lib/ibutton/ibutton_worker_modes.c +++ b/lib/ibutton/ibutton_worker_modes.c @@ -1,9 +1,10 @@ #include "ibutton_worker_i.h" #include +#include #include -#include +#include #include "ibutton_protocols.h" @@ -75,7 +76,9 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { void ibutton_worker_mode_read_start(iButtonWorker* worker) { UNUSED(worker); - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_read_tick(iButtonWorker* worker) { @@ -90,7 +93,9 @@ void ibutton_worker_mode_read_tick(iButtonWorker* worker) { void ibutton_worker_mode_read_stop(iButtonWorker* worker) { UNUSED(worker); - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } /*********************** EMULATE ***********************/ @@ -120,7 +125,9 @@ void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) { void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524 UNUSED(worker); - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_write_id_tick(iButtonWorker* worker) { @@ -149,5 +156,7 @@ void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) { void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524 UNUSED(worker); - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index f878ecb01..47cc100b3 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -228,7 +228,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i rolling = 0xE6000000; } if(fixed > 0xCFD41B90) { - FURI_LOG_E("TAG", "Encode wrong fixed data"); + FURI_LOG_E(TAG, "Encode wrong fixed data"); return false; } diff --git a/scripts/update.py b/scripts/update.py index 2b6620744..992f75603 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -34,7 +34,7 @@ class Main(App): FLASH_BASE = 0x8000000 FLASH_PAGE_SIZE = 4 * 1024 - MIN_GAP_PAGES = 2 + MIN_GAP_PAGES = 1 # Update stage file larger than that is not loadable without fix # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57eaf6ab3..5a750f24c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.2,, +Version,+,80.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -2362,8 +2362,10 @@ Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 900539019..217d90a2b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.2,, +Version,+,80.3,, 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,, @@ -2999,8 +2999,10 @@ Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index 06598df86..f5b6ac71b 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -105,10 +105,14 @@ void furi_hal_power_off(void); FURI_NORETURN void furi_hal_power_reset(void); /** OTG enable + * + * @warning this is low level control, use power service instead */ bool furi_hal_power_enable_otg(void); /** OTG disable + * + * @warning this is low level control, use power service instead */ void furi_hal_power_disable_otg(void); From 290a6dc1eb787cea472c742f3294c46f3ce1e090 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Thu, 20 Feb 2025 13:42:31 -0500 Subject: [PATCH 02/11] gpio: clear irq status before calling user handler (#4118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gpio: clear irq status before calling user handler * Format sources Co-authored-by: あく --- targets/f7/furi_hal/furi_hal_gpio.c | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index a020e9d7c..0104dd898 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -258,85 +258,85 @@ FURI_ALWAYS_INLINE static void furi_hal_gpio_int_call(uint16_t pin_num) { /* Interrupt handlers */ void EXTI0_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)) { - furi_hal_gpio_int_call(0); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); + furi_hal_gpio_int_call(0); } } void EXTI1_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_1)) { - furi_hal_gpio_int_call(1); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1); + furi_hal_gpio_int_call(1); } } void EXTI2_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_2)) { - furi_hal_gpio_int_call(2); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); + furi_hal_gpio_int_call(2); } } void EXTI3_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) { - furi_hal_gpio_int_call(3); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); + furi_hal_gpio_int_call(3); } } void EXTI4_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_4)) { - furi_hal_gpio_int_call(4); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4); + furi_hal_gpio_int_call(4); } } void EXTI9_5_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_5)) { - furi_hal_gpio_int_call(5); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5); + furi_hal_gpio_int_call(5); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_6)) { - furi_hal_gpio_int_call(6); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); + furi_hal_gpio_int_call(6); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_7)) { - furi_hal_gpio_int_call(7); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); + furi_hal_gpio_int_call(7); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8)) { - furi_hal_gpio_int_call(8); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8); + furi_hal_gpio_int_call(8); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9)) { - furi_hal_gpio_int_call(9); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9); + furi_hal_gpio_int_call(9); } } void EXTI15_10_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_10)) { - furi_hal_gpio_int_call(10); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10); + furi_hal_gpio_int_call(10); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_11)) { - furi_hal_gpio_int_call(11); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11); + furi_hal_gpio_int_call(11); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_12)) { - furi_hal_gpio_int_call(12); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12); + furi_hal_gpio_int_call(12); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13)) { - furi_hal_gpio_int_call(13); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13); + furi_hal_gpio_int_call(13); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_14)) { - furi_hal_gpio_int_call(14); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14); + furi_hal_gpio_int_call(14); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_15)) { - furi_hal_gpio_int_call(15); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15); + furi_hal_gpio_int_call(15); } } From 7c5c5d4749c7aabe407e61d9b854ba0f49890245 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 20 Feb 2025 23:54:38 +0400 Subject: [PATCH 03/11] [FL-3734] UART framing mode selection (#4121) * HAL: feat: uart framing * JS: feat: uart framing * fix formatting * fix pvs warning * HAL: flash impact reduction attempt 1 * HAL: flash impact reduction attempt 2 * fix compile error * HAL: finalize flash impact reduction * HAL: remove user-facing magic numbers Co-authored-by: Aleksandr Kutuzov --- applications/debug/uart_echo/uart_echo.c | 98 +++++++++++++-- .../resources/unit_tests/js/basic.js | 2 +- .../examples/apps/Scripts/uart_echo_8e1.js | 15 +++ applications/system/js_app/js_modules.c | 1 + applications/system/js_app/js_modules.h | 112 ++++++++++++------ .../system/js_app/modules/js_serial.c | 97 +++++++-------- .../create-fz-app/template/package.json | 2 +- .../system/js_app/packages/fz-sdk/global.d.ts | 6 +- .../js_app/packages/fz-sdk/package.json | 2 +- .../js_app/packages/fz-sdk/serial/index.d.ts | 23 +++- targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- targets/f7/furi_hal/furi_hal_serial.c | 90 +++++++++++++- targets/f7/furi_hal/furi_hal_serial.h | 23 +++- targets/f7/furi_hal/furi_hal_serial_types.h | 35 ++++++ 15 files changed, 404 insertions(+), 108 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 4298dc33d..7150b830b 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -16,6 +16,9 @@ #define LINES_ON_SCREEN 6 #define COLUMNS_ON_SCREEN 21 #define DEFAULT_BAUD_RATE 230400 +#define DEFAULT_DATA_BITS FuriHalSerialDataBits8 +#define DEFAULT_PARITY FuriHalSerialParityNone +#define DEFAULT_STOP_BITS FuriHalSerialStopBits1 typedef struct UartDumpModel UartDumpModel; @@ -49,11 +52,12 @@ typedef enum { WorkerEventRxOverrunError = (1 << 4), WorkerEventRxFramingError = (1 << 5), WorkerEventRxNoiseError = (1 << 6), + WorkerEventRxParityError = (1 << 7), } WorkerEventFlags; #define WORKER_EVENTS_MASK \ (WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \ - WorkerEventRxFramingError | WorkerEventRxNoiseError) + WorkerEventRxFramingError | WorkerEventRxNoiseError | WorkerEventRxParityError) const NotificationSequence sequence_notification = { &message_display_backlight_on, @@ -62,6 +66,13 @@ const NotificationSequence sequence_notification = { NULL, }; +const NotificationSequence sequence_error = { + &message_display_backlight_on, + &message_red_255, + &message_delay_10, + NULL, +}; + static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) { UartDumpModel* model = _model; @@ -133,6 +144,9 @@ static void if(event & FuriHalSerialRxEventOverrunError) { flag |= WorkerEventRxOverrunError; } + if(event & FuriHalSerialRxEventParityError) { + flag |= WorkerEventRxParityError; + } furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag); } @@ -227,13 +241,21 @@ static int32_t uart_echo_worker(void* context) { if(events & WorkerEventRxNoiseError) { furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13); } + if(events & WorkerEventRxParityError) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect PE\r\n", 13); + } + notification_message(app->notification, &sequence_error); } } return 0; } -static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { +static UartEchoApp* uart_echo_app_alloc( + uint32_t baudrate, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { UartEchoApp* app = malloc(sizeof(UartEchoApp)); app->rx_stream = furi_stream_buffer_alloc(2048, 1); @@ -275,6 +297,7 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart); furi_check(app->serial_handle); furi_hal_serial_init(app->serial_handle, baudrate); + furi_hal_serial_configure_framing(app->serial_handle, data_bits, parity, stop_bits); furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true); @@ -318,19 +341,76 @@ static void uart_echo_app_free(UartEchoApp* app) { free(app); } +// silences "same-assignment" false positives in the arg parser below +// -V::1048 + int32_t uart_echo_app(void* p) { uint32_t baudrate = DEFAULT_BAUD_RATE; + FuriHalSerialDataBits data_bits = DEFAULT_DATA_BITS; + FuriHalSerialParity parity = DEFAULT_PARITY; + FuriHalSerialStopBits stop_bits = DEFAULT_STOP_BITS; + if(p) { - const char* baudrate_str = p; - if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) { - FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); - baudrate = DEFAULT_BAUD_RATE; + // parse argument + char* parse_ptr = p; + bool parse_success = false; + + do { + if(strint_to_uint32(parse_ptr, &parse_ptr, &baudrate, 10) != StrintParseNoError) break; + + if(*(parse_ptr++) != '_') break; + + uint16_t data_bits_int; + if(strint_to_uint16(parse_ptr, &parse_ptr, &data_bits_int, 10) != StrintParseNoError) + break; + if(data_bits_int == 6) + data_bits = FuriHalSerialDataBits6; + else if(data_bits_int == 7) + data_bits = FuriHalSerialDataBits7; + else if(data_bits_int == 8) + data_bits = FuriHalSerialDataBits8; + else if(data_bits_int == 9) + data_bits = FuriHalSerialDataBits9; + else + break; + + char parity_char = *(parse_ptr++); + if(parity_char == 'N') + parity = FuriHalSerialParityNone; + else if(parity_char == 'E') + parity = FuriHalSerialParityEven; + else if(parity_char == 'O') + parity = FuriHalSerialParityOdd; + else + break; + + uint16_t stop_bits_int; + if(strint_to_uint16(parse_ptr, &parse_ptr, &stop_bits_int, 10) != StrintParseNoError) + break; + if(stop_bits_int == 5) + stop_bits = FuriHalSerialStopBits0_5; + else if(stop_bits_int == 1) + stop_bits = FuriHalSerialStopBits1; + else if(stop_bits_int == 15) + stop_bits = FuriHalSerialStopBits1_5; + else if(stop_bits_int == 2) + stop_bits = FuriHalSerialStopBits2; + else + break; + + parse_success = true; + } while(0); + + if(!parse_success) { + FURI_LOG_I( + TAG, + "Couldn't parse baud rate and framing (%s). Applying defaults (%d_8N1)", + (const char*)p, + DEFAULT_BAUD_RATE); } } - FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate); - - UartEchoApp* app = uart_echo_app_alloc(baudrate); + UartEchoApp* app = uart_echo_app_alloc(baudrate, data_bits, parity, stop_bits); view_dispatcher_run(app->view_dispatcher); uart_echo_app_free(app); return 0; diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js index 26f3f68f5..0ef904ecb 100644 --- a/applications/debug/unit_tests/resources/unit_tests/js/basic.js +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -12,4 +12,4 @@ tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"])); tests.assert_eq("flipperdevices", flipper.firmwareVendor); tests.assert_eq(0, flipper.jsSdkVersion[0]); -tests.assert_eq(2, flipper.jsSdkVersion[1]); +tests.assert_eq(3, flipper.jsSdkVersion[1]); diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js new file mode 100644 index 000000000..171bb4637 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js @@ -0,0 +1,15 @@ +// This script is like uart_echo, except it uses 8E1 framing (8 data bits, even +// parity, 1 stop bit) as opposed to the default 8N1 (8 data bits, no parity, +// 1 stop bit) + +let serial = require("serial"); +serial.setup("usart", 230400, { dataBits: "8", parity: "even", stopBits: "1" }); + +while (1) { + let rx_data = serial.readBytes(1, 1000); + if (rx_data !== undefined) { + serial.write(rx_data); + let data_view = Uint8Array(rx_data); + print("0x" + data_view[0].toString(16)); + } +} diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 47bdd516c..57997b09f 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -269,6 +269,7 @@ static const char* extra_features[] = { "baseline", // dummy "feature" "gpio-pwm", "gui-widget", + "serial-framing", }; /** diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 29de72642..2babe231e 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -11,7 +11,7 @@ #define JS_SDK_VENDOR "flipperdevices" #define JS_SDK_MAJOR 0 -#define JS_SDK_MINOR 2 +#define JS_SDK_MINOR 3 /** * @brief Returns the foreign pointer in `obj["_"]` @@ -81,6 +81,11 @@ typedef enum { */ #define JS_AT_LEAST >= +typedef struct { + const char* name; + size_t value; +} JsEnumMapping; + #define JS_ENUM_MAP(var_name, ...) \ static const JsEnumMapping var_name##_mapping[] = { \ {NULL, sizeof(var_name)}, \ @@ -90,8 +95,14 @@ typedef enum { typedef struct { const char* name; - size_t value; -} JsEnumMapping; + size_t offset; +} JsObjectMapping; + +#define JS_OBJ_MAP(var_name, ...) \ + static const JsObjectMapping var_name##_mapping[] = { \ + __VA_ARGS__, \ + {NULL, 0}, \ + }; typedef struct { void* out; @@ -199,6 +210,54 @@ static inline void _js_validate_enum, \ var_name##_mapping}) +static inline bool _js_validate_object(struct mjs* mjs, mjs_val_t val, const void* extra) { + for(const JsObjectMapping* mapping = (JsObjectMapping*)extra; mapping->name; mapping++) + if(mjs_get(mjs, val, mapping->name, ~0) == MJS_UNDEFINED) return false; + return true; +} +static inline void + _js_convert_object(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { + const JsObjectMapping* mapping = (JsObjectMapping*)extra; + for(; mapping->name; mapping++) { + mjs_val_t field_val = mjs_get(mjs, *val, mapping->name, ~0); + *(mjs_val_t*)((uint8_t*)out + mapping->offset) = field_val; + } +} +#define JS_ARG_OBJECT(var_name, name) \ + ((_js_arg_decl){ \ + &var_name, \ + mjs_is_object, \ + _js_convert_object, \ + name " object", \ + _js_validate_object, \ + var_name##_mapping}) + +/** + * @brief Validates and converts a JS value with a declarative interface + * + * Example: `int32_t my_value; JS_CONVERT_OR_RETURN(mjs, &mjs_val, JS_ARG_INT32(&my_value), "value source");` + * + * @warning This macro executes `return;` by design in case of a validation failure + */ +#define JS_CONVERT_OR_RETURN(mjs, value, decl, source, ...) \ + if(decl.validator) \ + if(!decl.validator(*value)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + source ": expected %s", \ + ##__VA_ARGS__, \ + decl.expected_type); \ + if(decl.extended_validator) \ + if(!decl.extended_validator(mjs, *value, decl.extra_data)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + source ": expected %s", \ + ##__VA_ARGS__, \ + decl.expected_type); \ + decl.converter(mjs, value, decl.out, decl.extra_data); + //-V:JS_FETCH_ARGS_OR_RETURN:1008 /** * @brief Fetches and validates the arguments passed to a JS function @@ -208,38 +267,21 @@ static inline void * @warning This macro executes `return;` by design in case of an argument count * mismatch or a validation failure */ -#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ - _js_arg_decl _js_args[] = {__VA_ARGS__}; \ - int _js_arg_cnt = COUNT_OF(_js_args); \ - mjs_val_t _js_arg_vals[_js_arg_cnt]; \ - if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "expected %s%d arguments, got %d", \ - #arg_operator, \ - _js_arg_cnt, \ - mjs_nargs(mjs)); \ - for(int _i = 0; _i < _js_arg_cnt; _i++) { \ - _js_arg_vals[_i] = mjs_arg(mjs, _i); \ - if(_js_args[_i].validator) \ - if(!_js_args[_i].validator(_js_arg_vals[_i])) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - if(_js_args[_i].extended_validator) \ - if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - _js_args[_i].converter( \ - mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ +#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ + _js_arg_decl _js_args[] = {__VA_ARGS__}; \ + int _js_arg_cnt = COUNT_OF(_js_args); \ + mjs_val_t _js_arg_vals[_js_arg_cnt]; \ + if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "expected %s%d arguments, got %d", \ + #arg_operator, \ + _js_arg_cnt, \ + mjs_nargs(mjs)); \ + for(int _i = 0; _i < _js_arg_cnt; _i++) { \ + _js_arg_vals[_i] = mjs_arg(mjs, _i); \ + JS_CONVERT_OR_RETURN(mjs, &_js_arg_vals[_i], _js_args[_i], "argument %d", _i); \ } /** diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index b1e578fbc..20b18a4f1 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -20,14 +20,6 @@ typedef struct { char* data; } PatternArrayItem; -static const struct { - const char* name; - const FuriHalSerialId value; -} serial_channels[] = { - {"usart", FuriHalSerialIdUsart}, - {"lpuart", FuriHalSerialIdLpuart}, -}; - ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); static void @@ -43,9 +35,54 @@ static void } static void js_serial_setup(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); - furi_assert(serial); + FuriHalSerialId serial_id; + int32_t baudrate; + JS_ENUM_MAP(serial_id, {"lpuart", FuriHalSerialIdLpuart}, {"usart", FuriHalSerialIdUsart}); + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_AT_LEAST, JS_ARG_ENUM(serial_id, "SerialId"), JS_ARG_INT32(&baudrate)); + + FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; + FuriHalSerialParity parity = FuriHalSerialParityNone; + FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1; + if(mjs_nargs(mjs) > 2) { + struct framing { + mjs_val_t data_bits; + mjs_val_t parity; + mjs_val_t stop_bits; + } framing; + JS_OBJ_MAP( + framing, + {"dataBits", offsetof(struct framing, data_bits)}, + {"parity", offsetof(struct framing, parity)}, + {"stopBits", offsetof(struct framing, stop_bits)}); + JS_ENUM_MAP( + data_bits, + {"6", FuriHalSerialDataBits6}, + {"7", FuriHalSerialDataBits7}, + {"8", FuriHalSerialDataBits8}, + {"9", FuriHalSerialDataBits9}); + JS_ENUM_MAP( + parity, + {"none", FuriHalSerialParityNone}, + {"even", FuriHalSerialParityEven}, + {"odd", FuriHalSerialParityOdd}); + JS_ENUM_MAP( + stop_bits, + {"0.5", FuriHalSerialStopBits0_5}, + {"1", FuriHalSerialStopBits1}, + {"1.5", FuriHalSerialStopBits1_5}, + {"2", FuriHalSerialStopBits2}); + mjs_val_t framing_obj = mjs_arg(mjs, 2); + JS_CONVERT_OR_RETURN(mjs, &framing_obj, JS_ARG_OBJECT(framing, "Framing"), "argument 2"); + JS_CONVERT_OR_RETURN( + mjs, &framing.data_bits, JS_ARG_ENUM(data_bits, "DataBits"), "argument 2: dataBits"); + JS_CONVERT_OR_RETURN( + mjs, &framing.parity, JS_ARG_ENUM(parity, "Parity"), "argument 2: parity"); + JS_CONVERT_OR_RETURN( + mjs, &framing.stop_bits, JS_ARG_ENUM(stop_bits, "StopBits"), "argument 2: stopBits"); + } + + JsSerialInst* serial = JS_GET_CONTEXT(mjs); if(serial->setup_done) { mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); @@ -53,43 +90,6 @@ static void js_serial_setup(struct mjs* mjs) { return; } - bool args_correct = false; - FuriHalSerialId serial_id = FuriHalSerialIdMax; - uint32_t baudrate = 0; - - do { - if(mjs_nargs(mjs) != 2) break; - - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_string(arg)) break; - - size_t str_len = 0; - const char* arg_str = mjs_get_string(mjs, &arg, &str_len); - for(size_t i = 0; i < COUNT_OF(serial_channels); i++) { - size_t name_len = strlen(serial_channels[i].name); - if(str_len != name_len) continue; - if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) { - serial_id = serial_channels[i].value; - break; - } - } - if(serial_id == FuriHalSerialIdMax) { - break; - } - - arg = mjs_arg(mjs, 1); - if(!mjs_is_number(arg)) break; - baudrate = mjs_get_int32(mjs, arg); - - args_correct = true; - } while(0); - - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - expansion_disable(furi_record_open(RECORD_EXPANSION)); furi_record_close(RECORD_EXPANSION); @@ -97,6 +97,7 @@ static void js_serial_setup(struct mjs* mjs) { if(serial->serial_handle) { serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); furi_hal_serial_init(serial->serial_handle, baudrate); + furi_hal_serial_configure_framing(serial->serial_handle, data_bits, parity, stop_bits); furi_hal_serial_async_rx_start( serial->serial_handle, js_serial_on_async_rx, serial, false); serial->setup_done = true; diff --git a/applications/system/js_app/packages/create-fz-app/template/package.json b/applications/system/js_app/packages/create-fz-app/template/package.json index 7acdeccaa..322a8b58b 100644 --- a/applications/system/js_app/packages/create-fz-app/template/package.json +++ b/applications/system/js_app/packages/create-fz-app/template/package.json @@ -6,7 +6,7 @@ "start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload" }, "devDependencies": { - "@flipperdevices/fz-sdk": "^0.1", + "@flipperdevices/fz-sdk": "^0.3", "typescript": "^5.6.3" } } \ No newline at end of file diff --git a/applications/system/js_app/packages/fz-sdk/global.d.ts b/applications/system/js_app/packages/fz-sdk/global.d.ts index ba6996f27..4c7f217d0 100644 --- a/applications/system/js_app/packages/fz-sdk/global.d.ts +++ b/applications/system/js_app/packages/fz-sdk/global.d.ts @@ -72,7 +72,7 @@ * @brief Checks compatibility between the script and the JS SDK that the * firmware provides * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -92,7 +92,7 @@ declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: nu * @brief Checks compatibility between the script and the JS SDK that the * firmware provides in a boolean fashion * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -105,7 +105,7 @@ declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): * @brief Asks the user whether to continue executing the script if the versions * are not compatible. Does nothing if they are. * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 523845738..3ab108e48 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/fz-sdk", - "version": "0.2.0", + "version": "0.3.0", "description": "Type declarations and documentation for native JS modules available on Flipper Zero", "keywords": [ "flipper", diff --git a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts index 3c249352e..5064c4213 100644 --- a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts @@ -4,16 +4,33 @@ * @module */ +export interface Framing { + /** + * @note 6 data bits can only be selected when parity is enabled (even or + * odd) + * @note 9 data bits can only be selected when parity is disabled (none) + */ + dataBits: "6" | "7" | "8" | "9"; + parity: "none" | "even" | "odd"; + /** + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not + * 0.5 and 1.5) + */ + stopBits: "0.5" | "1" | "1.5" | "2"; +} + /** * @brief Initializes the serial port * * Automatically disables Expansion module service to prevent interference. * - * @param port The port to initialize (`"lpuart"` or `"start"`) - * @param baudRate + * @param port The port to initialize (`"lpuart"` or `"usart"`) + * @param baudRate Baud rate + * @param framing See `Framing` type * @version Added in JS SDK 0.1 + * @version Added `framing` parameter in JS SDK 0.3, extra feature `"serial-framing"` */ -export declare function setup(port: "lpuart" | "usart", baudRate: number): void; +export declare function setup(port: "lpuart" | "usart", baudRate: number, framing?: Framing): void; /** * @brief Writes data to the serial port diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5a750f24c..b93d6eb52 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.3,, +Version,+,80.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1437,6 +1437,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 217d90a2b..0fef0cb36 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.3,, +Version,+,80.4,, 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,, @@ -1627,6 +1627,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 5ddb0785f..8ad9794a8 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -120,7 +120,7 @@ static void furi_hal_serial_usart_irq_callback(void* context) { } if(USART1->ISR & USART_ISR_PE) { USART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr == NULL) { @@ -321,7 +321,7 @@ static void furi_hal_serial_lpuart_irq_callback(void* context) { } if(LPUART1->ISR & USART_ISR_PE) { LPUART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr == NULL) { @@ -605,6 +605,92 @@ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud) { } } +// Avoid duplicating look-up tables between USART and LPUART +static_assert(LL_LPUART_DATAWIDTH_7B == LL_USART_DATAWIDTH_7B); +static_assert(LL_LPUART_DATAWIDTH_8B == LL_USART_DATAWIDTH_8B); +static_assert(LL_LPUART_DATAWIDTH_9B == LL_USART_DATAWIDTH_9B); +static_assert(LL_LPUART_PARITY_NONE == LL_USART_PARITY_NONE); +static_assert(LL_LPUART_PARITY_EVEN == LL_USART_PARITY_EVEN); +static_assert(LL_LPUART_PARITY_ODD == LL_USART_PARITY_ODD); +static_assert(LL_LPUART_STOPBITS_1 == LL_USART_STOPBITS_1); +static_assert(LL_LPUART_STOPBITS_2 == LL_USART_STOPBITS_2); + +static const uint32_t serial_data_bits_lut[] = { + [FuriHalSerialDataBits7] = LL_USART_DATAWIDTH_7B, + [FuriHalSerialDataBits8] = LL_USART_DATAWIDTH_8B, + [FuriHalSerialDataBits9] = LL_USART_DATAWIDTH_9B, +}; + +static const uint32_t serial_parity_lut[] = { + [FuriHalSerialParityNone] = LL_USART_PARITY_NONE, + [FuriHalSerialParityEven] = LL_USART_PARITY_EVEN, + [FuriHalSerialParityOdd] = LL_USART_PARITY_ODD, +}; + +static const uint32_t serial_stop_bits_lut[] = { + [FuriHalSerialStopBits0_5] = LL_USART_STOPBITS_0_5, + [FuriHalSerialStopBits1] = LL_USART_STOPBITS_1, + [FuriHalSerialStopBits1_5] = LL_USART_STOPBITS_1_5, + [FuriHalSerialStopBits2] = LL_USART_STOPBITS_2, +}; + +static void furi_hal_serial_usart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_USART_SetDataWidth(USART1, serial_data_bits_lut[data_bits]); + LL_USART_SetParity(USART1, serial_parity_lut[parity]); + LL_USART_SetStopBitsLength(USART1, serial_stop_bits_lut[stop_bits]); +} + +static void furi_hal_serial_lpuart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_LPUART_SetDataWidth(LPUART1, serial_data_bits_lut[data_bits]); + LL_LPUART_SetParity(LPUART1, serial_parity_lut[parity]); + // Unsupported non-whole stop bit numbers have been furi_check'ed away + LL_LPUART_SetStopBitsLength(LPUART1, serial_stop_bits_lut[stop_bits]); +} + +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + furi_check(handle); + + // Unsupported combinations + if(data_bits == FuriHalSerialDataBits9) furi_check(parity == FuriHalSerialParityNone); + if(data_bits == FuriHalSerialDataBits6) furi_check(parity != FuriHalSerialParityNone); + + // Extend data word to account for parity bit + if(parity != FuriHalSerialParityNone) data_bits++; + + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while(!LL_USART_IsActiveFlag_TC(USART1)) + ; + LL_USART_Disable(USART1); + furi_hal_serial_usart_configure_framing(data_bits, parity, stop_bits); + LL_USART_Enable(USART1); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + // Unsupported configurations + furi_check(stop_bits == FuriHalSerialStopBits1 || stop_bits == FuriHalSerialStopBits2); + + if(LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) + ; + LL_LPUART_Disable(LPUART1); + furi_hal_serial_lpuart_configure_framing(data_bits, parity, stop_bits); + LL_LPUART_Enable(LPUART1); + } + } +} + void furi_hal_serial_deinit(FuriHalSerialHandle* handle) { furi_check(handle); furi_hal_serial_async_rx_configure(handle, NULL, NULL); diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 00010d83c..ca8860a60 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -16,7 +16,9 @@ extern "C" { /** Initialize Serial * - * Configures GPIO, configures and enables transceiver. + * Configures GPIO, configures and enables transceiver. Default framing settings + * are used: 8 data bits, no parity, 1 stop bit. Override them with + * `furi_hal_serial_configure_framing`. * * @param handle Serial handle * @param baud baud rate @@ -64,6 +66,20 @@ bool furi_hal_serial_is_baud_rate_supported(FuriHalSerialHandle* handle, uint32_ */ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud); +/** + * @brief Configures framing of a serial interface + * + * @param handle Serial handle + * @param data_bits Data bits + * @param parity Parity + * @param stop_bits Stop bits + */ +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits); + /** Transmits data in semi-blocking mode * * Fills transmission pipe with data, returns as soon as all bytes from buffer @@ -93,6 +109,7 @@ typedef enum { FuriHalSerialRxEventFrameError = (1 << 2), /**< Framing Error: incorrect frame detected */ FuriHalSerialRxEventNoiseError = (1 << 3), /**< Noise Error: noise on the line detected */ FuriHalSerialRxEventOverrunError = (1 << 4), /**< Overrun Error: no space for received data */ + FuriHalSerialRxEventParityError = (1 << 5), /**< Parity Error: incorrect parity bit received */ } FuriHalSerialRxEvent; /** Receive callback @@ -172,7 +189,7 @@ typedef void (*FuriHalSerialDmaRxCallback)( void* context); /** - * @brief Enable an input/output directon + * @brief Enable an input/output direction * * Takes over the respective pin by reconfiguring it to * the appropriate alternative function. @@ -185,7 +202,7 @@ void furi_hal_serial_enable_direction( FuriHalSerialDirection direction); /** - * @brief Disable an input/output directon + * @brief Disable an input/output direction * * Releases the respective pin by reconfiguring it to * initial state, making possible its use for other purposes. diff --git a/targets/f7/furi_hal/furi_hal_serial_types.h b/targets/f7/furi_hal/furi_hal_serial_types.h index 9f10102e1..a427765dd 100644 --- a/targets/f7/furi_hal/furi_hal_serial_types.h +++ b/targets/f7/furi_hal/furi_hal_serial_types.h @@ -19,4 +19,39 @@ typedef enum { FuriHalSerialDirectionMax, } FuriHalSerialDirection; +/** + * @brief Actual data bits, i.e. not including start/stop and parity bits + * @note 6 data bits are only permitted when parity is enabled + * @note 9 data bits are only permitted when parity is disabled + */ +typedef enum { + FuriHalSerialDataBits6, + FuriHalSerialDataBits7, + FuriHalSerialDataBits8, + FuriHalSerialDataBits9, + + FuriHalSerialDataBitsMax, +} FuriHalSerialDataBits; + +typedef enum { + FuriHalSerialParityNone, + FuriHalSerialParityEven, + FuriHalSerialParityOdd, + + FuriHalSerialParityMax, +} FuriHalSerialParity; + +/** + * @brief Stop bit length + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2, but not 0.5 and 1.5) + */ +typedef enum { + FuriHalSerialStopBits0_5, + FuriHalSerialStopBits1, + FuriHalSerialStopBits1_5, + FuriHalSerialStopBits2, + + FuriHalSerialStopBits2Max, +} FuriHalSerialStopBits; + typedef struct FuriHalSerialHandle FuriHalSerialHandle; From 4e9aa3883b4ac9ecee688cbcd70079a5d1a94f67 Mon Sep 17 00:00:00 2001 From: Akiva-Cohen <150308530+Akiva-Cohen@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:57:28 -0500 Subject: [PATCH 04/11] Updated Button Panel (#4119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated button panel * fixed mismateched .c and .h files * Gui: extra events for ok button handling in button_panel * Gui: extra events for other buttons handling in button_panel Co-authored-by: あく --- .../common/infrared_scene_universal_common.c | 8 ++++--- .../common/infrared_scene_universal_common.h | 2 +- .../services/gui/modules/button_panel.c | 23 +++++++++++-------- .../services/gui/modules/button_panel.h | 2 +- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 62b7350e7..4e5a965a7 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -13,10 +13,12 @@ typedef union { } InfraredSceneState; #pragma pack(pop) -void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { +void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type) { InfraredApp* infrared = context; - uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); - view_dispatcher_send_custom_event(infrared->view_dispatcher, event); + if(type == InputTypeShort) { + uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); + view_dispatcher_send_custom_event(infrared->view_dispatcher, event); + } } static void infrared_scene_universal_common_progress_input_callback( diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h index a6c697d77..277598e42 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h @@ -5,4 +5,4 @@ void infrared_scene_universal_common_on_enter(void* context); bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event); void infrared_scene_universal_common_on_exit(void* context); -void infrared_scene_universal_common_item_callback(void* context, uint32_t index); +void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type); diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 9301870ef..efc512bc5 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -46,6 +47,7 @@ ARRAY_DEF(ButtonMatrix, ButtonArray_t); struct ButtonPanel { View* view; + bool freeze_input; }; typedef struct { @@ -63,7 +65,7 @@ static void button_panel_process_up(ButtonPanel* button_panel); static void button_panel_process_down(ButtonPanel* button_panel); static void button_panel_process_left(ButtonPanel* button_panel); static void button_panel_process_right(ButtonPanel* button_panel); -static void button_panel_process_ok(ButtonPanel* button_panel); +static void button_panel_process_ok(ButtonPanel* button_panel, InputType input); static void button_panel_view_draw_callback(Canvas* canvas, void* _model); static bool button_panel_view_input_callback(InputEvent* event, void* context); @@ -347,7 +349,7 @@ static void button_panel_process_right(ButtonPanel* button_panel) { true); } -void button_panel_process_ok(ButtonPanel* button_panel) { +void button_panel_process_ok(ButtonPanel* button_panel, InputType type) { ButtonItem* button_item = NULL; with_view_model( @@ -360,7 +362,7 @@ void button_panel_process_ok(ButtonPanel* button_panel) { true); if(button_item && button_item->callback) { - button_item->callback(button_item->callback_context, button_item->index); + button_item->callback(button_item->callback_context, button_item->index, type); } } @@ -368,8 +370,15 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { ButtonPanel* button_panel = context; furi_assert(button_panel); bool consumed = false; - - if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + if((event->type == InputTypePress) || (event->type == InputTypeRelease)) { + button_panel->freeze_input = (event->type == InputTypePress); + } + consumed = true; + button_panel_process_ok(button_panel, event->type); + } + if(!button_panel->freeze_input && + (!(event->type == InputTypePress) && !(event->type == InputTypeRelease))) { switch(event->key) { case InputKeyUp: consumed = true; @@ -387,10 +396,6 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { consumed = true; button_panel_process_right(button_panel); break; - case InputKeyOk: - consumed = true; - button_panel_process_ok(button_panel); - break; default: break; } diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index 1218222b8..159e14336 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -15,7 +15,7 @@ extern "C" { typedef struct ButtonPanel ButtonPanel; /** Callback type to call for handling selecting button_panel items */ -typedef void (*ButtonItemCallback)(void* context, uint32_t index); +typedef void (*ButtonItemCallback)(void* context, uint32_t index, InputType type); /** Allocate new button_panel module. * From 16d18a79a958fd78ca41b61778136b6957efac77 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 21 Feb 2025 05:04:02 +0400 Subject: [PATCH 05/11] [FL-3900] Update heap implementation (#4123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * furi: update heap4 to latest * debug: heap under/overflow testing app * fix formatting * silence pvs warnings * Linker: symbols without type * Infrared: fix crash in universal remotes on long back press * Infrared: properly fix incorrect input handling behavior and crash in universal remote. Fix same issue in hid_app. * FreeRTOSConfig: explicit cast to uint in configTOTAL_HEAP_SIZE * Format sources * C and C++ compatible version of stm32wb55_linker.h Co-authored-by: あく --- applications/debug/crash_test/crash_test.c | 59 ++ .../infrared/views/infrared_progress_view.c | 6 +- .../system/hid_app/views/hid_mouse_clicker.c | 2 +- furi/core/check.c | 6 +- furi/core/memmgr.c | 3 +- furi/core/memmgr_heap.c | 593 ++++++++++-------- furi/flipper.c | 6 + targets/f7/inc/FreeRTOSConfig.h | 9 +- targets/f7/inc/stm32wb55_linker.h | 27 +- 9 files changed, 432 insertions(+), 279 deletions(-) diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c index 2b2be13d6..4c5a53ceb 100644 --- a/applications/debug/crash_test/crash_test.c +++ b/applications/debug/crash_test/crash_test.c @@ -24,8 +24,49 @@ typedef enum { CrashTestSubmenuAssertMessage, CrashTestSubmenuCrash, CrashTestSubmenuHalt, + CrashTestSubmenuHeapUnderflow, + CrashTestSubmenuHeapOverflow, } CrashTestSubmenu; +static void crash_test_corrupt_heap_underflow(void) { + const size_t block_size = 1000; + const size_t underflow_size = 123; + uint8_t* block = malloc(block_size); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want! + memset(block - underflow_size, 0xDD, underflow_size); // -V769 +#pragma GCC diagnostic pop + + free(block); // should crash here (if compiled with DEBUG=1) + + // If we got here, the heap wasn't able to detect our corruption and crash + furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error"); +} + +static void crash_test_corrupt_heap_overflow(void) { + const size_t block_size = 1000; + const size_t overflow_size = 123; + uint8_t* block1 = malloc(block_size); + uint8_t* block2 = malloc(block_size); + memset(block2, 12, 34); // simulate use to avoid optimization // -V597 // -V1086 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want! + memset(block1 + block_size, 0xDD, overflow_size); // -V769 // -V512 +#pragma GCC diagnostic pop + + uint8_t* block3 = malloc(block_size); + memset(block3, 12, 34); // simulate use to avoid optimization // -V597 // -V1086 + + free(block3); // should crash here (if compiled with DEBUG=1) + free(block2); + free(block1); + + // If we got here, the heap wasn't able to detect our corruption and crash + furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error"); +} + static void crash_test_submenu_callback(void* context, uint32_t index) { CrashTest* instance = (CrashTest*)context; UNUSED(instance); @@ -49,6 +90,12 @@ static void crash_test_submenu_callback(void* context, uint32_t index) { case CrashTestSubmenuHalt: furi_halt("Crash test: furi_halt"); break; + case CrashTestSubmenuHeapUnderflow: + crash_test_corrupt_heap_underflow(); + break; + case CrashTestSubmenuHeapOverflow: + crash_test_corrupt_heap_overflow(); + break; default: furi_crash(); } @@ -94,6 +141,18 @@ CrashTest* crash_test_alloc(void) { instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance); submenu_add_item( instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance); + submenu_add_item( + instance->submenu, + "Heap underflow", + CrashTestSubmenuHeapUnderflow, + crash_test_submenu_callback, + instance); + submenu_add_item( + instance->submenu, + "Heap overflow", + CrashTestSubmenuHeapOverflow, + crash_test_submenu_callback, + instance); return instance; } diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 716027297..76454b5bd 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -107,7 +107,11 @@ void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_p bool infrared_progress_view_input_callback(InputEvent* event, void* context) { InfraredProgressView* instance = context; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) return false; + + if(event->type == InputTypePress || event->type == InputTypeRelease) { + return false; + } + if(!instance->input_callback) return false; with_view_model( diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index e289b7179..491f9962b 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -124,7 +124,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { bool consumed = false; bool rate_changed = false; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { + if(event->type == InputTypePress || event->type == InputTypeRelease) { return false; } diff --git a/furi/core/check.c b/furi/core/check.c index ba05a675f..ee8ee4af0 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -102,11 +102,13 @@ static void __furi_print_bt_stack_info(void) { static void __furi_print_heap_info(void) { furi_log_puts("\r\n\t heap total: "); - __furi_put_uint32_as_text(xPortGetTotalHeapSize()); + __furi_put_uint32_as_text(configTOTAL_HEAP_SIZE); furi_log_puts("\r\n\t heap free: "); __furi_put_uint32_as_text(xPortGetFreeHeapSize()); + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); furi_log_puts("\r\n\t heap watermark: "); - __furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize()); + __furi_put_uint32_as_text(heap_stats.xMinimumEverFreeBytesRemaining); } static void __furi_print_name(bool isr) { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 8ee0d1723..a3bbf4556 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -1,6 +1,7 @@ #include "memmgr.h" #include #include +#include extern void* pvPortMalloc(size_t xSize); extern void vPortFree(void* pv); @@ -51,7 +52,7 @@ size_t memmgr_get_free_heap(void) { } size_t memmgr_get_total_heap(void) { - return xPortGetTotalHeapSize(); + return configTOTAL_HEAP_SIZE; } size_t memmgr_get_minimum_free_heap(void) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 359d0e3db..c8a72bc8c 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,6 +1,8 @@ /* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,10 +21,9 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS * - * 1 tab == 4 spaces! */ /* @@ -31,21 +32,25 @@ * limits memory fragmentation. * * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. + * memory management pages of https://www.FreeRTOS.org for more information. */ #include "memmgr_heap.h" #include "check.h" #include +#include #include #include #include #include #include +// -V::562 +// -V::650 + /* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ + * all the API functions to use the MPU wrappers. That should only be done when + * task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE #include @@ -53,8 +58,12 @@ task.h is included from an application file. */ #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT +#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) +#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 +#endif + +#ifndef configHEAP_CLEAR_MEMORY_ON_FREE +#define configHEAP_CLEAR_MEMORY_ON_FREE 0 #endif /* Block sizes must not get too small. */ @@ -63,16 +72,75 @@ task.h is included from an application file. */ /* Assumes 8bit bytes! */ #define heapBITS_PER_BYTE ((size_t)8) +/* Max value that fits in a size_t type. */ +#define heapSIZE_MAX (~((size_t)0)) + +/* Check if multiplying a and b will result in overflow. */ +#define heapMULTIPLY_WILL_OVERFLOW(a, b) (((a) > 0) && ((b) > (heapSIZE_MAX / (a)))) + +/* Check if adding a and b will result in overflow. */ +#define heapADD_WILL_OVERFLOW(a, b) ((a) > (heapSIZE_MAX - (b))) + +/* Check if the subtraction operation ( a - b ) will result in underflow. */ +#define heapSUBTRACT_WILL_UNDERFLOW(a, b) ((a) < (b)) + +/* MSB of the xBlockSize member of an BlockLink_t structure is used to track + * the allocation status of a block. When MSB of the xBlockSize member of + * an BlockLink_t structure is set then the block belongs to the application. + * When the bit is free the block is still part of the free heap space. */ +#define heapBLOCK_ALLOCATED_BITMASK (((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1)) +#define heapBLOCK_SIZE_IS_VALID(xBlockSize) (((xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) == 0) +#define heapBLOCK_IS_ALLOCATED(pxBlock) \ + (((pxBlock->xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) != 0) +#define heapALLOCATE_BLOCK(pxBlock) ((pxBlock->xBlockSize) |= heapBLOCK_ALLOCATED_BITMASK) +#define heapFREE_BLOCK(pxBlock) ((pxBlock->xBlockSize) &= ~heapBLOCK_ALLOCATED_BITMASK) + +/*-----------------------------------------------------------*/ + /* Heap start end symbols provided by linker */ uint8_t* ucHeap = (uint8_t*)&__heap_start__; /* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ + * of their memory address. */ typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ + struct A_BLOCK_LINK* pxNextFreeBlock; /**< The next free block in the list. */ + size_t xBlockSize; /**< The size of the free block. */ } BlockLink_t; +/* Setting configENABLE_HEAP_PROTECTOR to 1 enables heap block pointers + * protection using an application supplied canary value to catch heap + * corruption should a heap buffer overflow occur. + */ +#if(configENABLE_HEAP_PROTECTOR == 1) + +/** + * @brief Application provided function to get a random value to be used as canary. + * + * @param pxHeapCanary [out] Output parameter to return the canary value. + */ +extern void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary); + +/* Canary value for protecting internal heap pointers. */ +PRIVILEGED_DATA static portPOINTER_SIZE_TYPE xHeapCanary; + +/* Macro to load/store BlockLink_t pointers to memory. By XORing the + * pointers with a random canary value, heap overflows will result + * in randomly unpredictable pointer values which will be caught by + * heapVALIDATE_BLOCK_POINTER assert. */ +#define heapPROTECT_BLOCK_POINTER(pxBlock) \ + ((BlockLink_t*)(((portPOINTER_SIZE_TYPE)(pxBlock)) ^ xHeapCanary)) +#else + +#define heapPROTECT_BLOCK_POINTER(pxBlock) (pxBlock) + +#endif /* configENABLE_HEAP_PROTECTOR */ + +/* Assert that a heap block pointer is within the heap bounds. */ +#define heapVALIDATE_BLOCK_POINTER(pxBlock) \ + configASSERT( \ + ((uint8_t*)(pxBlock) >= &(ucHeap[0])) && \ + ((uint8_t*)(pxBlock) <= &(ucHeap[configTOTAL_HEAP_SIZE - 1]))) + /*-----------------------------------------------------------*/ /* @@ -81,34 +149,31 @@ typedef struct A_BLOCK_LINK { * the block in front it and/or the block behind it if the memory blocks are * adjacent to each other. */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) PRIVILEGED_FUNCTION; /* * Called automatically to setup the required heap structures the first time * pvPortMalloc() is called. */ -static void prvHeapInit(void); +static void prvHeapInit(void) PRIVILEGED_FUNCTION; /*-----------------------------------------------------------*/ /* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ + * block must by correctly byte aligned. */ static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & ~((size_t)portBYTE_ALIGNMENT_MASK); /* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; +PRIVILEGED_DATA static BlockLink_t xStart; +PRIVILEGED_DATA static BlockLink_t* pxEnd = NULL; -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; +/* Keeps track of the number of calls to allocate and free memory as well as the + * number of free bytes remaining, but says nothing about fragmentation. */ +PRIVILEGED_DATA static size_t xFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xMinimumEverFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulAllocations = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulFrees = (size_t)0U; /* Furi heap extension */ #include @@ -175,7 +240,7 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { puc -= xHeapStructSize; BlockLink_t* pxLink = (void*)puc; - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && + if((pxLink->xBlockSize & heapBLOCK_ALLOCATED_BITMASK) && pxLink->pxNextFreeBlock == NULL) { leftovers += data->value; } @@ -220,20 +285,9 @@ static inline void traceFREE(void* pointer, size_t size) { } size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); - - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; - } - - xTaskResumeAll(); - return max_free_size; + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); + return heap_stats.xSizeOfLargestFreeBlockInBytes; } void memmgr_heap_printf_free_blocks(void) { @@ -250,186 +304,115 @@ void memmgr_heap_printf_free_blocks(void) { //xTaskResumeAll(); } -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. - - return str; -} - -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif /*-----------------------------------------------------------*/ void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; + BlockLink_t* pxBlock; + BlockLink_t* pxPreviousBlock; + BlockLink_t* pxNewBlockLink; void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + size_t xToWipe = xWantedSize; + size_t xAdditionalRequiredSize; + size_t xAllocatedBlockSize = 0; if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif + if(xWantedSize > 0) { + /* The wanted size must be increased so it can contain a BlockLink_t + * structure in addition to the requested amount of bytes. */ + if(heapADD_WILL_OVERFLOW(xWantedSize, xHeapStructSize) == 0) { + xWantedSize += xHeapStructSize; - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif + /* Ensure that blocks are always aligned to the required number + * of bytes. */ + if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { + /* Byte alignment required. */ + xAdditionalRequiredSize = + portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK); - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + if(heapADD_WILL_OVERFLOW(xWantedSize, xAdditionalRequiredSize) == 0) { + xWantedSize += xAdditionalRequiredSize; + } else { + xWantedSize = 0; + } + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + xWantedSize = 0; } - (void)xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } vTaskSuspendAll(); { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + /* If this is the first call to malloc then the heap will require + * initialisation to setup the list of free blocks. */ + if(pxEnd == NULL) { + prvHeapInit(); + memmgr_heap_init(); + } else { + mtCOVERAGE_TEST_MARKER(); + } + /* Check the block size we are trying to allocate is not so large that the + * top bit is set. The top bit of the block size member of the BlockLink_t + * structure is used to determine who owns the block - the application or + * the kernel, so it must be free. */ + if(heapBLOCK_SIZE_IS_VALID(xWantedSize) != 0) { if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ + * one of adequate size is found. */ pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); + + while((pxBlock->xBlockSize < xWantedSize) && + (pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL))) { pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); } /* If the end marker was reached then a block of adequate size - was not found. */ + * was not found. */ if(pxBlock != pxEnd) { /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); + * BlockLink_t structure at its start. */ + pvReturn = (void*)(((uint8_t*)heapPROTECT_BLOCK_POINTER( + pxPreviousBlock->pxNextFreeBlock)) + + xHeapStructSize); + heapVALIDATE_BLOCK_POINTER(pvReturn); /* This block is being returned for use so must be taken out - of the list of free blocks. */ + * of the list of free blocks. */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into - two. */ + * two. */ + configASSERT( + heapSUBTRACT_WILL_UNDERFLOW(pxBlock->xBlockSize, xWantedSize) == 0); + if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ + * block following the number of bytes requested. The void + * cast is used to prevent byte alignment warnings from the + * compiler. */ pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); /* Calculate the sizes of two blocks split from the - single block. */ + * single block. */ pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); + pxNewBlockLink->pxNextFreeBlock = pxPreviousBlock->pxNextFreeBlock; + pxPreviousBlock->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxNewBlockLink); } else { mtCOVERAGE_TEST_MARKER(); } @@ -442,14 +425,13 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; + xAllocatedBlockSize = pxBlock->xBlockSize; -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif + /* The block is being returned - it is allocated and owned + * by the application and has no "next" block. */ + heapALLOCATE_BLOCK(pxBlock); + pxBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); + xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } @@ -460,29 +442,27 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - traceMALLOC(pvReturn, xWantedSize); + traceMALLOC(pvReturn, xAllocatedBlockSize); + + /* Prevent compiler warnings when trace macros are not used. */ + (void)xAllocatedBlockSize; } (void)xTaskResumeAll(); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif - #if(configUSE_MALLOC_FAILED_HOOK == 1) { if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); vApplicationMallocFailedHook(); } else { mtCOVERAGE_TEST_MARKER(); } } -#endif +#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */ configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); + pvReturn = memset(pvReturn, 0, xToWipe); return pvReturn; } /*-----------------------------------------------------------*/ @@ -497,24 +477,30 @@ void vPortFree(void* pv) { if(pv != NULL) { /* The memory being freed will have an BlockLink_t structure immediately - before it. */ + * before it. */ puc -= xHeapStructSize; /* This casting is to keep the compiler from issuing warnings. */ pxLink = (void*)puc; - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); + heapVALIDATE_BLOCK_POINTER(pxLink); + configASSERT(heapBLOCK_IS_ALLOCATED(pxLink) != 0); + configASSERT(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)); - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { + if(heapBLOCK_IS_ALLOCATED(pxLink) != 0) { + if(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)) { /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); + * allocated. */ + heapFREE_BLOCK(pxLink); +#if(configHEAP_CLEAR_MEMORY_ON_FREE == 1) + { + /* Check for underflow as this can occur if xBlockSize is + * overwritten in a heap block. */ + if(heapSUBTRACT_WILL_UNDERFLOW(pxLink->xBlockSize, xHeapStructSize) == 0) { + (void)memset( + puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize); + } + } #endif vTaskSuspendAll(); @@ -527,8 +513,8 @@ void vPortFree(void* pv) { /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList((BlockLink_t*)pxLink); + prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); + xNumberOfSuccessfulFrees++; } (void)xTaskResumeAll(); } else { @@ -537,19 +523,10 @@ void vPortFree(void* pv) { } else { mtCOVERAGE_TEST_MARKER(); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } } /*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ - size_t xPortGetFreeHeapSize(void) { return xFreeBytesRemaining; } @@ -560,71 +537,98 @@ size_t xPortGetMinimumEverFreeHeapSize(void) { } /*-----------------------------------------------------------*/ +void xPortResetHeapMinimumEverFreeHeapSize(void) { + xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; +} +/*-----------------------------------------------------------*/ + void vPortInitialiseBlocks(void) { /* This just exists to keep the linker quiet. */ } /*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +void* pvPortCalloc(size_t xNum, size_t xSize) { + void* pv = NULL; - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + if(heapMULTIPLY_WILL_OVERFLOW(xNum, xSize) == 0) { + pv = pvPortMalloc(xNum * xSize); - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + if(pv != NULL) { + (void)memset(pv, 0, xNum * xSize); + } } - pucAlignedHeap = (uint8_t*)uxAddress; + return pv; +} +/*-----------------------------------------------------------*/ + +static void prvHeapInit(void) /* PRIVILEGED_FUNCTION */ +{ + BlockLink_t* pxFirstFreeBlock; + portPOINTER_SIZE_TYPE uxStartAddress, uxEndAddress; + size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; + + /* Ensure the heap starts on a correctly aligned boundary. */ + uxStartAddress = (portPOINTER_SIZE_TYPE)ucHeap; + + if((uxStartAddress & portBYTE_ALIGNMENT_MASK) != 0) { + uxStartAddress += (portBYTE_ALIGNMENT - 1); + uxStartAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + xTotalHeapSize -= (size_t)(uxStartAddress - (portPOINTER_SIZE_TYPE)ucHeap); + } + +#if(configENABLE_HEAP_PROTECTOR == 1) + { vApplicationGetRandomHeapCanary(&(xHeapCanary)); } +#endif /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; + * blocks. The void cast is used to prevent compiler warnings. */ + xStart.pxNextFreeBlock = (void*)heapPROTECT_BLOCK_POINTER(uxStartAddress); xStart.xBlockSize = (size_t)0; /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; + * at the end of the heap space. */ + uxEndAddress = uxStartAddress + (portPOINTER_SIZE_TYPE)xTotalHeapSize; + uxEndAddress -= (portPOINTER_SIZE_TYPE)xHeapStructSize; + uxEndAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + pxEnd = (BlockLink_t*)uxEndAddress; pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; + pxEnd->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; + * entire heap space, minus the space taken by pxEnd. */ + pxFirstFreeBlock = (BlockLink_t*)uxStartAddress; + pxFirstFreeBlock->xBlockSize = + (size_t)(uxEndAddress - (portPOINTER_SIZE_TYPE)pxFirstFreeBlock); + pxFirstFreeBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); /* Only one block exists - and it covers the entire usable heap space. */ xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); } /*-----------------------------------------------------------*/ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) /* PRIVILEGED_FUNCTION */ +{ BlockLink_t* pxIterator; uint8_t* puc; /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { + * than the block being inserted. */ + for(pxIterator = &xStart; + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) < pxBlockToInsert; + pxIterator = heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { /* Nothing to do here, just iterate to the right position. */ } + if(pxIterator != &xStart) { + heapVALIDATE_BLOCK_POINTER(pxIterator); + } + /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxIterator; + if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; @@ -633,27 +637,98 @@ static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { } /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { + + if((puc + pxBlockToInsert->xBlockSize) == + (uint8_t*)heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { + if(heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) != pxEnd) { /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; + pxBlockToInsert->xBlockSize += + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->xBlockSize; + pxBlockToInsert->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->pxNextFreeBlock; } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; + pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ + /* If the block being inserted plugged a gap, so was merged with the block + * before and the block after, then it's pxNextFreeBlock pointer will have + * already been set, and should not be set here as that would make it point + * to itself. */ if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; + pxIterator->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxBlockToInsert); } else { mtCOVERAGE_TEST_MARKER(); } } +/*-----------------------------------------------------------*/ + +void vPortGetHeapStats(HeapStats_t* pxHeapStats) { + BlockLink_t* pxBlock; + size_t + xBlocks = 0, + xMaxSize = 0, + xMinSize = + portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */ + + vTaskSuspendAll(); + { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + + /* pxBlock will be NULL if the heap has not been initialised. The heap + * is initialised automatically when the first allocation is made. */ + if(pxBlock != NULL) { + while(pxBlock != pxEnd) { + /* Increment the number of blocks and record the largest block seen + * so far. */ + xBlocks++; + + if(pxBlock->xBlockSize > xMaxSize) { + xMaxSize = pxBlock->xBlockSize; + } + + if(pxBlock->xBlockSize < xMinSize) { + xMinSize = pxBlock->xBlockSize; + } + + /* Move to the next block in the chain until the last block is + * reached. */ + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + } + } + } + (void)xTaskResumeAll(); + + pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize; + pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize; + pxHeapStats->xNumberOfFreeBlocks = xBlocks; + + taskENTER_CRITICAL(); + { + pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining; + pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations; + pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees; + pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining; + } + taskEXIT_CRITICAL(); +} +/*-----------------------------------------------------------*/ + +/* + * Reset the state in this file. This state is normally initialized at start up. + * This function must be called by the application before restarting the + * scheduler. + */ +void vPortHeapResetState(void) { + pxEnd = NULL; + + xFreeBytesRemaining = (size_t)0U; + xMinimumEverFreeBytesRemaining = (size_t)0U; + xNumberOfSuccessfulAllocations = (size_t)0U; + xNumberOfSuccessfulFrees = (size_t)0U; +} +/*-----------------------------------------------------------*/ diff --git a/furi/flipper.c b/furi/flipper.c index 6d6215a9d..bafe2ed0f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -9,6 +9,8 @@ #define TAG "Flipper" +#define HEAP_CANARY_VALUE 0x8BADF00D + static void flipper_print_version(const char* target, const Version* version) { if(version) { FURI_LOG_I( @@ -67,3 +69,7 @@ void vApplicationGetTimerTaskMemory( *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; } + +void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary) { + *pxHeapCanary = HEAP_CANARY_VALUE; +} diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 357019ea2..8d34925ec 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -11,13 +11,16 @@ #endif /* CMSIS_device_header */ #include CMSIS_device_header +#include #define configENABLE_FPU 1 #define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 1 -#define configSUPPORT_DYNAMIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configENABLE_HEAP_PROTECTOR 1 +#define configHEAP_CLEAR_MEMORY_ON_FREE 1 #define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 @@ -30,7 +33,7 @@ #define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ -// #define configTOTAL_HEAP_SIZE ((size_t)0) +#define configTOTAL_HEAP_SIZE ((uint32_t) & __heap_end__ - (uint32_t) & __heap_start__) #define configMAX_TASK_NAME_LEN (32) #define configGENERATE_RUN_TIME_STATS 1 @@ -146,7 +149,7 @@ standard names. */ /* Normal assert() semantics without relying on the provision of an assert.h header file. */ -#ifdef DEBUG +#ifdef FURI_DEBUG #define configASSERT(x) \ if((x) == 0) { \ furi_crash("FreeRTOS Assert"); \ diff --git a/targets/f7/inc/stm32wb55_linker.h b/targets/f7/inc/stm32wb55_linker.h index 4b56a11be..e978dff35 100644 --- a/targets/f7/inc/stm32wb55_linker.h +++ b/targets/f7/inc/stm32wb55_linker.h @@ -9,25 +9,28 @@ #ifdef __cplusplus extern "C" { +typedef const char linker_symbol_t; +#else +typedef const void linker_symbol_t; #endif -extern const void _stack_end; /**< end of stack */ -extern const void _stack_size; /**< stack size */ +extern linker_symbol_t _stack_end; /**< end of stack */ +extern linker_symbol_t _stack_size; /**< stack size */ -extern const void _sidata; /**< data initial value start */ -extern const void _sdata; /**< data start */ -extern const void _edata; /**< data end */ +extern linker_symbol_t _sidata; /**< data initial value start */ +extern linker_symbol_t _sdata; /**< data start */ +extern linker_symbol_t _edata; /**< data end */ -extern const void _sbss; /**< bss start */ -extern const void _ebss; /**< bss end */ +extern linker_symbol_t _sbss; /**< bss start */ +extern linker_symbol_t _ebss; /**< bss end */ -extern const void _sMB_MEM2; /**< RAM2a start */ -extern const void _eMB_MEM2; /**< RAM2a end */ +extern linker_symbol_t _sMB_MEM2; /**< RAM2a start */ +extern linker_symbol_t _eMB_MEM2; /**< RAM2a end */ -extern const void __heap_start__; /**< RAM1 Heap start */ -extern const void __heap_end__; /**< RAM1 Heap end */ +extern linker_symbol_t __heap_start__; /**< RAM1 Heap start */ +extern linker_symbol_t __heap_end__; /**< RAM1 Heap end */ -extern const void __free_flash_start__; /**< Free Flash space start */ +extern linker_symbol_t __free_flash_start__; /**< Free Flash space start */ #ifdef __cplusplus } From 404764b660160faef0e9aa21f21c806820628fa2 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 21 Feb 2025 01:47:56 +0000 Subject: [PATCH 06/11] GUI: Widget view extra options for JS (#4120) * Fill option for widget frame * Add widget circle element * Add widget line element * Fix missing include for InputType * Fix missing comment * Update api symbols * Load .fxbm from file * Fix copy pasta * Add fill param to example * Fix some comments * Bump JS SDK 0.3 * Fix free * Rename widget frame to rect * Gui: add widget_add_frame_element backward compatibility macros Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/modules/widget.c | 21 ++++- applications/services/gui/modules/widget.h | 46 ++++++++-- .../modules/widget_elements/widget_element.h | 2 + .../widget_elements/widget_element_circle.c | 45 ++++++++++ .../widget_elements/widget_element_frame.c | 48 ---------- .../widget_elements/widget_element_i.h | 14 ++- .../widget_elements/widget_element_line.c | 41 +++++++++ .../widget_elements/widget_element_rect.c | 55 ++++++++++++ .../js_app/examples/apps/Scripts/gui.js | 4 +- applications/system/js_app/js_modules.c | 1 + .../system/js_app/modules/js_gui/icon.c | 88 ++++++++++++++++++- .../system/js_app/modules/js_gui/widget.c | 40 ++++++++- .../js_app/packages/fz-sdk/gui/icon.d.ts | 7 ++ .../js_app/packages/fz-sdk/gui/widget.d.ts | 8 +- targets/f18/api_symbols.csv | 6 +- targets/f7/api_symbols.csv | 6 +- 16 files changed, 360 insertions(+), 72 deletions(-) create mode 100644 applications/services/gui/modules/widget_elements/widget_element_circle.c delete mode 100644 applications/services/gui/modules/widget_elements/widget_element_frame.c create mode 100644 applications/services/gui/modules/widget_elements/widget_element_line.c create mode 100644 applications/services/gui/modules/widget_elements/widget_element_rect.c diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index ea3315d8d..f8d2fc346 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -195,14 +195,27 @@ void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* i widget_add_element(widget, icon_element); } -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius) { + uint8_t radius, + bool fill) { furi_check(widget); - WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius); - widget_add_element(widget, frame_element); + WidgetElement* rect_element = widget_element_rect_create(x, y, width, height, radius, fill); + widget_add_element(widget, rect_element); +} + +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill) { + furi_check(widget); + WidgetElement* circle_element = widget_element_circle_create(x, y, radius, fill); + widget_add_element(widget, circle_element); +} + +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + furi_check(widget); + WidgetElement* line_element = widget_element_line_create(x1, y1, x2, y2); + widget_add_element(widget, line_element); } diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index b31370296..bda444963 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -152,21 +152,57 @@ void widget_add_button_element( void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon); /** Add Frame Element + * + * @param widget Widget instance + * @param x top left x coordinate + * @param y top left y coordinate + * @param width frame width + * @param height frame height + * @param radius frame radius + * + * @warning deprecated, use widget_add_rect_element instead + */ +#define widget_add_frame_element(widget, x, y, width, height, radius) \ + widget_add_rect_element((widget), (x), (y), (width), (height), (radius), false) + +/** Add Rect Element * * @param widget Widget instance * @param x top left x coordinate * @param y top left y coordinate - * @param width frame width - * @param height frame height - * @param radius frame radius + * @param width rect width + * @param height rect height + * @param radius corner radius + * @param fill whether to fill the box or not */ -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); + +/** Add Circle Element + * + * @param widget Widget instance + * @param x center x coordinate + * @param y center y coordinate + * @param radius circle radius + * @param fill whether to fill the circle or not + */ +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Add Line Element + * + * @param widget Widget instance + * @param x1 first x coordinate + * @param y1 first y coordinate + * @param x2 second x coordinate + * @param y2 second y coordinate + */ +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); #ifdef __cplusplus } diff --git a/applications/services/gui/modules/widget_elements/widget_element.h b/applications/services/gui/modules/widget_elements/widget_element.h index 473fabd04..8f14053f4 100644 --- a/applications/services/gui/modules/widget_elements/widget_element.h +++ b/applications/services/gui/modules/widget_elements/widget_element.h @@ -5,6 +5,8 @@ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_circle.c b/applications/services/gui/modules/widget_elements/widget_element_circle.c new file mode 100644 index 000000000..47a952429 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_circle.c @@ -0,0 +1,45 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t radius; + bool fill; +} GuiCircleModel; + +static void gui_circle_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiCircleModel* model = element->model; + if(model->fill) { + canvas_draw_disc(canvas, model->x, model->y, model->radius); + } else { + canvas_draw_circle(canvas, model->x, model->y, model->radius); + } +} + +static void gui_circle_free(WidgetElement* gui_circle) { + furi_assert(gui_circle); + + free(gui_circle->model); + free(gui_circle); +} + +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill) { + // Allocate and init model + GuiCircleModel* model = malloc(sizeof(GuiCircleModel)); + model->x = x; + model->y = y; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_circle = malloc(sizeof(WidgetElement)); + gui_circle->parent = NULL; + gui_circle->input = NULL; + gui_circle->draw = gui_circle_draw; + gui_circle->free = gui_circle_free; + gui_circle->model = model; + + return gui_circle; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_frame.c b/applications/services/gui/modules/widget_elements/widget_element_frame.c deleted file mode 100644 index a7265348e..000000000 --- a/applications/services/gui/modules/widget_elements/widget_element_frame.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "widget_element_i.h" - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t width; - uint8_t height; - uint8_t radius; -} GuiFrameModel; - -static void gui_frame_draw(Canvas* canvas, WidgetElement* element) { - furi_assert(canvas); - furi_assert(element); - GuiFrameModel* model = element->model; - canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); -} - -static void gui_frame_free(WidgetElement* gui_frame) { - furi_assert(gui_frame); - - free(gui_frame->model); - free(gui_frame); -} - -WidgetElement* widget_element_frame_create( - uint8_t x, - uint8_t y, - uint8_t width, - uint8_t height, - uint8_t radius) { - // Allocate and init model - GuiFrameModel* model = malloc(sizeof(GuiFrameModel)); - model->x = x; - model->y = y; - model->width = width; - model->height = height; - model->radius = radius; - - // Allocate and init Element - WidgetElement* gui_frame = malloc(sizeof(WidgetElement)); - gui_frame->parent = NULL; - gui_frame->input = NULL; - gui_frame->draw = gui_frame_draw; - gui_frame->free = gui_frame_free; - gui_frame->model = model; - - return gui_frame; -} diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 2bced5576..adaf09168 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -73,14 +73,16 @@ WidgetElement* widget_element_button_create( /** Create icon element */ WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon); -/** Create frame element */ -WidgetElement* widget_element_frame_create( +/** Create rect element */ +WidgetElement* widget_element_rect_create( uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); +/** Create text scroll element */ WidgetElement* widget_element_text_scroll_create( uint8_t x, uint8_t y, @@ -88,6 +90,12 @@ WidgetElement* widget_element_text_scroll_create( uint8_t height, const char* text); +/** Create circle element */ +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Create line element */ +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); + #ifdef __cplusplus } #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_line.c b/applications/services/gui/modules/widget_elements/widget_element_line.c new file mode 100644 index 000000000..4cccdc976 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_line.c @@ -0,0 +1,41 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x1; + uint8_t y1; + uint8_t x2; + uint8_t y2; +} GuiLineModel; + +static void gui_line_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiLineModel* model = element->model; + canvas_draw_line(canvas, model->x1, model->y1, model->x2, model->y2); +} + +static void gui_line_free(WidgetElement* gui_line) { + furi_assert(gui_line); + + free(gui_line->model); + free(gui_line); +} + +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + // Allocate and init model + GuiLineModel* model = malloc(sizeof(GuiLineModel)); + model->x1 = x1; + model->y1 = y1; + model->x2 = x2; + model->y2 = y2; + + // Allocate and init Element + WidgetElement* gui_line = malloc(sizeof(WidgetElement)); + gui_line->parent = NULL; + gui_line->input = NULL; + gui_line->draw = gui_line_draw; + gui_line->free = gui_line_free; + gui_line->model = model; + + return gui_line; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_rect.c b/applications/services/gui/modules/widget_elements/widget_element_rect.c new file mode 100644 index 000000000..6539f09a8 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_rect.c @@ -0,0 +1,55 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t width; + uint8_t height; + uint8_t radius; + bool fill; +} GuiRectModel; + +static void gui_rect_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiRectModel* model = element->model; + if(model->fill) { + canvas_draw_rbox(canvas, model->x, model->y, model->width, model->height, model->radius); + } else { + canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); + } +} + +static void gui_rect_free(WidgetElement* gui_rect) { + furi_assert(gui_rect); + + free(gui_rect->model); + free(gui_rect); +} + +WidgetElement* widget_element_rect_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius, + bool fill) { + // Allocate and init model + GuiRectModel* model = malloc(sizeof(GuiRectModel)); + model->x = x; + model->y = y; + model->width = width; + model->height = height; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_rect = malloc(sizeof(WidgetElement)); + gui_rect->parent = NULL; + gui_rect->input = NULL; + gui_rect->draw = gui_rect_draw; + gui_rect->free = gui_rect_free; + gui_rect->model = model; + + return gui_rect; +} diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js index a1c104cf1..16673524a 100644 --- a/applications/system/js_app/examples/apps/Scripts/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -19,8 +19,8 @@ let jsLogo = icon.getBuiltin("js_script_10px"); let stopwatchWidgetElements = [ { element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" }, { element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" }, - { element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3 }, - { element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3 }, + { element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false }, + { element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false }, { element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch }, { element: "icon", x: 64, y: 13, iconData: jsLogo }, { element: "button", button: "right", text: "Back" }, diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 57997b09f..a8480e6a2 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -270,6 +270,7 @@ static const char* extra_features[] = { "gpio-pwm", "gui-widget", "serial-framing", + "gui-widget-extras", }; /** diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c index 4b7926ba1..3d8a67a8b 100644 --- a/applications/system/js_app/modules/js_gui/icon.c +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -1,5 +1,8 @@ #include "../../js_modules.h" #include +#include +#include +#include typedef struct { const char* name; @@ -16,6 +19,26 @@ static const IconDefinition builtin_icons[] = { ICON_DEF(js_script_10px), }; +// Firmware's Icon struct needs a frames array, and uses a small CompressHeader +// Here we use a variable size allocation to add the uncompressed data in same allocation +// Also use a one-long array pointing to later in the same struct as the frames array +// CompressHeader includes a first is_compressed byte so we don't need to compress (.fxbm is uncompressed) +typedef struct FURI_PACKED { + Icon icon; + uint8_t* frames[1]; + struct { + uint8_t is_compressed; + uint8_t uncompressed_data[]; + } frame; +} FxbmIconWrapper; + +LIST_DEF(FxbmIconWrapperList, FxbmIconWrapper*, M_PTR_OPLIST); // NOLINT +#define M_OPL_FxbmIconWrapperList_t() LIST_OPLIST(FxbmIconWrapperList) + +typedef struct { + FxbmIconWrapperList_t fxbm_list; +} JsGuiIconInst; + static void js_gui_icon_get_builtin(struct mjs* mjs) { const char* icon_name; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name)); @@ -30,17 +53,78 @@ static void js_gui_icon_get_builtin(struct mjs* mjs) { JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon"); } +static void js_gui_icon_load_fxbm(struct mjs* mjs) { + const char* fxbm_path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fxbm_path)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + FxbmIconWrapper* fxbm = NULL; + + do { + if(!storage_file_open(file, fxbm_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } + + struct { + uint32_t size; // Total following size including width and height values + uint32_t width; + uint32_t height; + } fxbm_header; + if(storage_file_read(file, &fxbm_header, sizeof(fxbm_header)) != sizeof(fxbm_header)) { + break; + } + + size_t frame_size = fxbm_header.size - sizeof(uint32_t) * 2; + fxbm = malloc(sizeof(FxbmIconWrapper) + frame_size); + if(storage_file_read(file, fxbm->frame.uncompressed_data, frame_size) != frame_size) { + free(fxbm); + fxbm = NULL; + break; + } + + FURI_CONST_ASSIGN(fxbm->icon.width, fxbm_header.width); + FURI_CONST_ASSIGN(fxbm->icon.height, fxbm_header.height); + FURI_CONST_ASSIGN(fxbm->icon.frame_count, 1); + FURI_CONST_ASSIGN(fxbm->icon.frame_rate, 1); + FURI_CONST_ASSIGN_PTR(fxbm->icon.frames, fxbm->frames); + fxbm->frames[0] = (void*)&fxbm->frame; + fxbm->frame.is_compressed = false; + } while(false); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + if(!fxbm) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "could not load .fxbm icon"); + } + + JsGuiIconInst* js_icon = JS_GET_CONTEXT(mjs); + FxbmIconWrapperList_push_back(js_icon->fxbm_list, fxbm); + mjs_return(mjs, mjs_mk_foreign(mjs, (void*)&fxbm->icon)); +} + static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); + JsGuiIconInst* js_icon = malloc(sizeof(JsGuiIconInst)); + FxbmIconWrapperList_init(js_icon->fxbm_list); *object = mjs_mk_object(mjs); JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_icon)); JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin)); + JS_FIELD("loadFxbm", MJS_MK_FN(js_gui_icon_load_fxbm)); } - return NULL; + return js_icon; } static void js_gui_icon_destroy(void* inst) { - UNUSED(inst); + JsGuiIconInst* js_icon = inst; + for + M_EACH(fxbm, js_icon->fxbm_list, FxbmIconWrapperList_t) { + free(*fxbm); + } + FxbmIconWrapperList_clear(js_icon->fxbm_list); + free(js_icon); } static const JsModuleDescriptor js_gui_icon_desc = { diff --git a/applications/system/js_app/modules/js_gui/widget.c b/applications/system/js_app/modules/js_gui/widget.c index 5f9a222cc..bb2898030 100644 --- a/applications/system/js_app/modules/js_gui/widget.c +++ b/applications/system/js_app/modules/js_gui/widget.c @@ -202,7 +202,7 @@ static bool js_widget_add_child( const Icon* icon = mjs_get_ptr(mjs, icon_data_in); widget_add_icon_element(widget, x, y, icon); - } else if(strcmp(element_type, "frame") == 0) { + } else if(strcmp(element_type, "rect") == 0) { int32_t x, y, w, h; DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); @@ -211,7 +211,43 @@ static bool js_widget_add_child( JS_ERROR_AND_RETURN_VAL( mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); int32_t radius = mjs_get_int32(mjs, radius_in); - widget_add_frame_element(widget, x, y, w, h, radius); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_rect_element(widget, x, y, w, h, radius, fill); + + } else if(strcmp(element_type, "circle") == 0) { + int32_t x, y; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0); + if(!mjs_is_number(radius_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); + int32_t radius = mjs_get_int32(mjs, radius_in); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_circle_element(widget, x, y, radius, fill); + + } else if(strcmp(element_type, "line") == 0) { + int32_t x1, y1, x2, y2; + mjs_val_t x1_in = mjs_get(mjs, child_obj, "x1", ~0); + mjs_val_t y1_in = mjs_get(mjs, child_obj, "y1", ~0); + mjs_val_t x2_in = mjs_get(mjs, child_obj, "x2", ~0); + mjs_val_t y2_in = mjs_get(mjs, child_obj, "y2", ~0); + if(!mjs_is_number(x1_in) || !mjs_is_number(y1_in) || !mjs_is_number(x2_in) || + !mjs_is_number(y2_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element positions"); + x1 = mjs_get_int32(mjs, x1_in); + y1 = mjs_get_int32(mjs, y1_in); + x2 = mjs_get_int32(mjs, x2_in); + y2 = mjs_get_int32(mjs, y2_in); + widget_add_line_element(widget, x1, y1, x2, y2); } return true; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts index 577e1e6a9..8d2b68bce 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -9,3 +9,10 @@ export type IconData = symbol & { "__tag__": "icon" }; * @version Added in JS SDK 0.2, extra feature `"gui-widget"` */ export declare function getBuiltin(icon: BuiltinIcon): IconData; + +/** + * Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI + * @param path Path to the .fxbm file + * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + */ +export declare function loadFxbm(path: string): IconData; diff --git a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts index b37af8e3c..bf4aab22b 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts @@ -42,7 +42,9 @@ type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & type TextScrollElement = { element: "text_scroll" } & Position & Size & Text; type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text; type IconElement = { element: "icon", iconData: IconData } & Position; -type FrameElement = { element: "frame", radius: number } & Position & Size; +type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ type Element = StringMultilineElement | StringElement @@ -50,7 +52,9 @@ type Element = StringMultilineElement | TextScrollElement | ButtonElement | IconElement - | FrameElement; + | RectElement + | CircleElement + | LineElement; type Props = {}; type Child = Element; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b93d6eb52..4aeef3c9d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.4,, +Version,+,81.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -2899,8 +2899,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0fef0cb36..14eaa3142 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.4,, +Version,+,81.1,, 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,, @@ -3750,8 +3750,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" From ef024e8086baed68112bab4cbd84d55d1e090b77 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Sat, 22 Feb 2025 03:11:27 +0400 Subject: [PATCH 07/11] [FL-3958] Stdio API improvements (#4110) * improve thread stdio callback signatures * pipe stdout timeout * update api symbols Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/tests/furi/furi_stdio_test.c | 15 +++++++++------ furi/core/thread.c | 14 ++++++++++---- furi/core/thread.h | 10 ++++++---- lib/toolbox/pipe.c | 11 ++++++++++- lib/toolbox/pipe.h | 10 ++++++++++ targets/f18/api_symbols.csv | 7 ++++--- targets/f7/api_symbols.csv | 7 ++++--- 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c index 94e2f613b..d80bd7bd5 100644 --- a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -30,7 +30,9 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context } void test_stdin(void) { - FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); + FuriThreadStdinReadCallback in_cb; + void* in_ctx; + furi_thread_get_stdin_callback(&in_cb, &in_ctx); furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); char buf[256]; @@ -63,13 +65,14 @@ void test_stdin(void) { fgets(buf, sizeof(buf), stdin); mu_assert_string_eq(" World!\n", buf); - furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); + furi_thread_set_stdin_callback(in_cb, in_ctx); } // stdout static FuriString* mock_out; -FuriThreadStdoutWriteCallback original_out_cb; +static FuriThreadStdoutWriteCallback original_out_cb; +static void* original_out_ctx; static void mock_out_cb(const char* data, size_t size, void* context) { furi_check(context == CONTEXT_MAGIC); @@ -83,7 +86,7 @@ static void assert_and_clear_mock_out(const char* expected) { // return the original stdout callback for the duration of the check // if the check fails, we don't want the error to end up in our buffer, // we want to be able to see it! - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); @@ -91,7 +94,7 @@ static void assert_and_clear_mock_out(const char* expected) { } void test_stdout(void) { - original_out_cb = furi_thread_get_stdout_callback(); + furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); mock_out = furi_string_alloc(); @@ -104,5 +107,5 @@ void test_stdout(void) { assert_and_clear_mock_out("Hello!"); furi_string_free(mock_out); - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); } diff --git a/furi/core/thread.c b/furi/core/thread.c index f6cfa1d36..d6132cdc2 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -758,16 +758,22 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + furi_check(callback); + furi_check(context); + *callback = thread->output.write_callback; + *context = thread->output.context; } -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->input.read_callback; + furi_check(callback); + furi_check(context); + *callback = thread->input.read_callback; + *context = thread->input.context; } void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { diff --git a/furi/core/thread.h b/furi/core/thread.h index 9abfde5cd..c1f615d16 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -479,16 +479,18 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** * @brief Get the standard output callback for the current thead. * - * @return pointer to the standard out callback function + * @param[out] callback where to store the stdout callback + * @param[out] context where to store the context */ -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context); /** * @brief Get the standard input callback for the current thead. * - * @return pointer to the standard in callback function + * @param[out] callback where to store the stdin callback + * @param[out] context where to store the context */ -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context); /** Set standard output callback for the current thread. * diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c index 9dc1d368e..c6a45abf1 100644 --- a/lib/toolbox/pipe.c +++ b/lib/toolbox/pipe.c @@ -23,6 +23,7 @@ struct PipeSide { PipeSideDataArrivedCallback on_data_arrived; PipeSideSpaceFreedCallback on_space_freed; PipeSideBrokenCallback on_pipe_broken; + FuriWait stdout_timeout; }; PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) { @@ -52,12 +53,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti .shared = shared, .sending = alice_to_bob, .receiving = bob_to_alice, + .stdout_timeout = FuriWaitForever, }; *bobs_side = (PipeSide){ .role = PipeRoleBob, .shared = shared, .sending = bob_to_alice, .receiving = alice_to_bob, + .stdout_timeout = FuriWaitForever, }; return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side}; @@ -100,7 +103,8 @@ static void _pipe_stdout_cb(const char* data, size_t size, void* context) { furi_assert(context); PipeSide* pipe = context; while(size) { - size_t sent = pipe_send(pipe, data, size, FuriWaitForever); + size_t sent = pipe_send(pipe, data, size, pipe->stdout_timeout); + if(!sent) break; data += sent; size -= sent; } @@ -118,6 +122,11 @@ void pipe_install_as_stdio(PipeSide* pipe) { furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); } +void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout) { + furi_check(pipe); + pipe->stdout_timeout = timeout; +} + size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h index df75f4c48..c05e60d0c 100644 --- a/lib/toolbox/pipe.h +++ b/lib/toolbox/pipe.h @@ -147,6 +147,16 @@ void pipe_free(PipeSide* pipe); */ void pipe_install_as_stdio(PipeSide* pipe); +/** + * @brief Sets the timeout for `stdout` write operations + * + * @note This value is set to `FuriWaitForever` when the pipe is created + * + * @param [in] pipe Pipe side to set the timeout of + * @param [in] timeout Timeout value in ticks + */ +void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout); + /** * @brief Receives data from the pipe. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 4aeef3c9d..3da638149 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,81.1,, +Version,+,82.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,, @@ -1666,8 +1666,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -2333,6 +2333,7 @@ Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, Fur Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait" Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 14eaa3142..9a87f08d4 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,81.1,, +Version,+,82.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,, @@ -1886,8 +1886,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -2970,6 +2970,7 @@ Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, Fur Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait" Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" From 9f5e93bed80366d2c769dda966a2255613639add Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:59:05 -0500 Subject: [PATCH 08/11] LFRFID: Fix Detection Conflict Between Securakey and Noralsy Format (#4125) * Securakey added parity check * Format Sources Co-authored-by: Aleksandr Kutuzov --- lib/lfrfid/protocols/protocol_securakey.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_securakey.c b/lib/lfrfid/protocols/protocol_securakey.c index a4bfeca60..947b68e72 100644 --- a/lib/lfrfid/protocols/protocol_securakey.c +++ b/lib/lfrfid/protocols/protocol_securakey.c @@ -61,17 +61,22 @@ uint8_t* protocol_securakey_get_data(ProtocolSecurakey* protocol) { static bool protocol_securakey_can_be_decoded(ProtocolSecurakey* protocol) { // check 19 bits preamble + format flag if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111000000000) { - protocol->bit_format = 0; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 54, BitLibParityAlways0, 9)) { + protocol->bit_format = 0; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001011010) { - protocol->bit_format = 26; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 26; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001100000) { - protocol->bit_format = 32; - return true; - } else { - return false; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 32; + return true; + } } + return false; } static void protocol_securakey_decode(ProtocolSecurakey* protocol) { From 145184f0f20f8bea0bdd57339205c8701442d262 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:07:45 -0500 Subject: [PATCH 09/11] NFC: FeliCa Protocol Expose Read Block API and Allow Specifying Service (#4074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add one parameter to the rdbl and expose * Bump api version and format sources Co-authored-by: あく --- lib/nfc/protocols/felica/felica_poller.c | 8 +++++--- lib/nfc/protocols/felica/felica_poller.h | 17 +++++++++++++++++ lib/nfc/protocols/felica/felica_poller_i.c | 3 ++- lib/nfc/protocols/felica/felica_poller_i.h | 15 --------------- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 3 ++- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 720daf238..cf00cbc09 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -118,7 +118,8 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { blocks[1] = FELICA_BLOCK_INDEX_WCNT; blocks[2] = FELICA_BLOCK_INDEX_MAC_A; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + error = felica_poller_read_blocks( + instance, sizeof(blocks), blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; if(felica_check_mac( @@ -174,7 +175,7 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + error = felica_poller_read_blocks(instance, 1, blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; instance->data->data.fs.state.SF1 = 0; @@ -203,7 +204,8 @@ NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { } FelicaPollerReadCommandResponse* response; - FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + FelicaError error = felica_poller_read_blocks( + instance, block_count, block_list, FELICA_SERVICE_RO_ACCESS, &response); if(error == FelicaErrorNone) { block_count = (response->SF1 == 0) ? response->block_count : block_count; uint8_t* data_ptr = diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b386f4b4b..541442df2 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -56,6 +56,23 @@ typedef struct { */ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] service_code Service code for the read operation + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + uint16_t service_code, + FelicaPollerReadCommandResponse** const response_ptr); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 8ec4b2889..49112debd 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -134,6 +134,7 @@ FelicaError felica_poller_read_blocks( FelicaPoller* instance, const uint8_t block_count, const uint8_t* const block_numbers, + uint16_t service_code, FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); furi_assert(block_count <= 4); @@ -143,7 +144,7 @@ FelicaError felica_poller_read_blocks( felica_poller_prepare_tx_buffer( instance, FELICA_CMD_READ_WITHOUT_ENCRYPTION, - FELICA_SERVICE_RO_ACCESS, + service_code, block_count, block_numbers, 0, diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index df205ba67..df4990a4e 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -70,21 +70,6 @@ FelicaError felica_poller_polling( const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); -/** - * @brief Performs felica read operation for blocks provided as parameters - * - * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[in] block_count Amount of blocks involved in reading procedure - * @param[in] block_numbers Array with block indexes according to felica docs - * @param[out] response_ptr Pointer to the response structure - * @return FelicaErrorNone on success, an error code on failure. -*/ -FelicaError felica_poller_read_blocks( - FelicaPoller* instance, - const uint8_t block_count, - const uint8_t* const block_numbers, - FelicaPollerReadCommandResponse** const response_ptr); - /** * @brief Performs felica write operation with data provided as parameters * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3da638149..2074fa131 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.0,, +Version,+,82.1,, 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 9a87f08d4..1168a6eea 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,82.0,, +Version,+,82.1,, 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,, @@ -1049,6 +1049,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_poller_read_blocks,FelicaError,"FelicaPoller*, const uint8_t, const uint8_t*, uint16_t, FelicaPollerReadCommandResponse**" Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" From 0d53cb2c48380dff521e9fcbe157524111bbac57 Mon Sep 17 00:00:00 2001 From: Evgeny E <10674163+ssecsd@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:00:51 +0000 Subject: [PATCH 10/11] test: fix timeout for flashing step (#4127) --- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 309dd7ebd..37df8e099 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -21,7 +21,7 @@ jobs: - name: 'Flash unit tests firmware' id: flashing if: success() - timeout-minutes: 20 + timeout-minutes: 10 run: | source scripts/toolchain/fbtenv.sh ./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 59cc6e716..df62daf58 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -20,7 +20,7 @@ jobs: - name: 'Flashing target firmware' id: first_full_flash - timeout-minutes: 20 + timeout-minutes: 10 run: | source scripts/toolchain/fbtenv.sh python3 scripts/testops.py -t=180 await_flipper From 0d99e54a17860469f98a36f749362cb9470648d1 Mon Sep 17 00:00:00 2001 From: Tyler Crumpton Date: Wed, 26 Feb 2025 10:34:54 -0500 Subject: [PATCH 11/11] Fix PWM-supported logic on pwmStop() (#4129) --- applications/system/js_app/modules/js_gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 23884a6d4..2a559570f 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -308,7 +308,7 @@ static void js_gpio_is_pwm_running(struct mjs* mjs) { */ static void js_gpio_pwm_stop(struct mjs* mjs) { JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); - if(manager_data->pwm_output != FuriHalPwmOutputIdNone) { + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); }