From ad94694fbd8aa35a72ea53d03420d56281a69f04 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 11:19:18 +0200 Subject: [PATCH 1/6] CLI: Fix long delay with quick connect/disconnect (#4251) * CLI: Fix long delay with quick connect/disconnect noticeable with qflipper, for some reason it sets DTR on/off/on again so flipper starts CLI, stops it, then starts again but cli shell is trying to print motd, and pipe is already broken so each character of motd times out for 100ms changing pipe timeout to 10ms works too, but is just a workaround it didnt always happen, i think that variable time of loading ext cmds made it so on ofw it usually manages to print motd before pipe is broken on cfw with more ext commands it always hung, on ofw only sometimes important part is bailing early in cli shell in cli vcp i made it cleanup cli shell on disconnect so it doesnt stay around after disconnecting usb, might free a little ram maybe * cli_shell: possibly more robust fix? * Fix use after free crash * cli_shell: waste even less time Co-Authored-By: WillyJL --------- Co-authored-by: Anna Antonenko Co-authored-by: hedger --- applications/services/cli/cli_vcp.c | 13 +++++-------- lib/toolbox/cli/shell/cli_shell.c | 16 +++++++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 1f9c77b08..9db1bec43 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -218,6 +218,11 @@ static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* c pipe_detach_from_event_loop(cli_vcp->own_pipe); pipe_free(cli_vcp->own_pipe); cli_vcp->own_pipe = NULL; + + // wait for shell to stop + cli_shell_join(cli_vcp->shell); + cli_shell_free(cli_vcp->shell); + pipe_free(cli_vcp->shell_pipe); break; } @@ -226,14 +231,6 @@ static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* c FURI_LOG_D(TAG, "Connected"); cli_vcp->is_connected = true; - // wait for previous shell to stop - furi_check(!cli_vcp->own_pipe); - if(cli_vcp->shell) { - cli_shell_join(cli_vcp->shell); - cli_shell_free(cli_vcp->shell); - pipe_free(cli_vcp->shell_pipe); - } - // start shell thread PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1); cli_vcp->own_pipe = bundle.alices_side; diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index 7a4c7ec1f..b2648d127 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -16,7 +16,8 @@ #define TAG "CliShell" -#define ANSI_TIMEOUT_MS 10 +#define ANSI_TIMEOUT_MS 10 +#define TRANSIENT_SESSION_WINDOW_MS 100 typedef enum { CliShellComponentCompletions, @@ -415,10 +416,15 @@ static void cli_shell_deinit(CliShell* shell) { static int32_t cli_shell_thread(void* context) { CliShell* shell = context; - // Sometimes, the other side closes the pipe even before our thread is started. Although the - // rest of the code will eventually find this out if this check is removed, there's no point in - // wasting time. - if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + // Sometimes, the other side (e.g. qFlipper) closes the pipe even before our thread is started. + // Although the rest of the code will eventually find this out if this check is removed, + // there's no point in wasting time. This gives qFlipper a chance to quickly close and re-open + // the session. + const size_t delay_step = 10; + for(size_t i = 0; i < TRANSIENT_SESSION_WINDOW_MS / delay_step; i++) { + furi_delay_ms(delay_step); + if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + } cli_shell_init(shell); FURI_LOG_D(TAG, "Started"); From dfd753703ad10177aa133154c8125d53720c8d2e Mon Sep 17 00:00:00 2001 From: dogtopus Date: Wed, 24 Sep 2025 19:08:40 +0900 Subject: [PATCH 2/6] FeliCa Emulation: Handle certain Polling commands in firmware (#4204) * FeliCa: Handle non-hardware Polling commands NFC TagInfo and possibly other readers rely on Polling commands with Request Code of 1 (default System Code request) or non-FFFF System Code to detect card type. Since the NFC controller doesn't seem to handle them in hardware and simply bubbles them up, and then the Flipper firmware will just ignore them and refuse to respond afterwards, this causes the reading operation to fail. This commit adds a simple handler for such Polling commands so that readers behaving like NFC TagInfo could read the emulated card without failing. * Only handle cases when System Code is not FFFF The NFC controller should handle Polling commands with the System Code set to FFFF, so it's not necessary for the firmware to handle it. * Remove system code logging * More cleanups * Remove the claim that we need a poller change We already have enough information to determine whether or not the card supports NDEF since SYS_OP register value is included in all current Lite-S card dumps. * Respond to 12FC polling command when needed * Handle Polling with NDEF and Lite-S Service Code This allows the reader to specifically select the service by naming the Service Code. * Introduce API for manual handling of Polling commands Introduce nfc_felica_listener_timer_anticol_start() and nfc_felica_listener_timer_anticol_stop(). These are for now just wrappers around the block_tx timer that can be used to delay the response until the desired Time Slot. Thanks to the loose timing constraints of FeliCa collision resolution protocol, no compensation seems to be necessary. Also enabled the block_tx timer for FeliCa listener, but with both compensation and fdt set to 0 to keep the original behavior of not using the timer during normal data exchange. This API is now being used for handling Polling commands that are not handled by the NFC controller on the hardware side. * Document target_time_slot * Implement changes suggested by @RebornedBrain * api: f18 version sync * nfc: added stubs for `nfc_felica_listener_timer_anticol` for unit tests --------- Co-authored-by: hedger Co-authored-by: hedger --- lib/nfc/nfc.c | 19 ++++ lib/nfc/nfc.h | 18 ++++ lib/nfc/nfc_mock.c | 10 +++ lib/nfc/protocols/felica/felica.h | 3 +- lib/nfc/protocols/felica/felica_listener.c | 93 +++++++++++++++++++- lib/nfc/protocols/felica/felica_listener_i.c | 13 --- lib/nfc/protocols/felica/felica_listener_i.h | 37 +++++++- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 4 +- targets/f7/furi_hal/furi_hal_nfc_felica.c | 3 +- 10 files changed, 182 insertions(+), 20 deletions(-) diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 90e65b282..e843ef70c 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -9,6 +9,9 @@ #define NFC_MAX_BUFFER_SIZE (256) +#define NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC (512 * 64) +#define NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC (256 * 64) + typedef enum { NfcStateIdle, NfcStateRunning, @@ -661,4 +664,20 @@ NfcError nfc_felica_listener_set_sensf_res_data( return nfc_process_hal_error(error); } +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) { + furi_check(instance); + + furi_hal_nfc_timer_block_tx_start( + NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC + + target_time_slot * NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC); +} + +void nfc_felica_listener_timer_anticol_stop(Nfc* instance) { + furi_check(instance); + + if(furi_hal_nfc_timer_block_tx_is_running()) { + furi_hal_nfc_timer_block_tx_stop(); + } +} + #endif // FW_CFG_unit_tests diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 8fbf90d1f..bbbaf306b 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,6 +380,24 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); +/** + * @brief Start the timer used for manual FeliCa collision resolution in listener mode. + * + * This blocks TX until the desired Time Slot, and should be called as soon as the listener + * determines that a collision resolution needs to be handled manually. + * + * @param[in, out] instance instance pointer to the instance to be configured. + * @param[in] target_time_slot Target Time Slot number. Should be a value within the range of 0-15 (double-inclusive). + */ +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot); + +/** + * @brief Cancel the timer used for manual FeliCa collision resolution in listener mode. + * + * @param[in, out] instance instance pointer to the instance to be configured. + */ +void nfc_felica_listener_timer_anticol_stop(Nfc* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/nfc_mock.c b/lib/nfc/nfc_mock.c index ee4bb09cc..717c93519 100644 --- a/lib/nfc/nfc_mock.c +++ b/lib/nfc/nfc_mock.c @@ -518,4 +518,14 @@ NfcError nfc_felica_listener_set_sensf_res_data( return NfcErrorNone; } +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) { + furi_check(instance); + + UNUSED(target_time_slot); +} + +void nfc_felica_listener_timer_anticol_stop(Nfc* instance) { + furi_check(instance); +} + #endif diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 7b9c78e07..b4e7af9d1 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -38,7 +38,7 @@ extern "C" { #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) -#define FELICA_FDT_LISTEN_FC (1172) +#define FELICA_FDT_LISTEN_FC (0) #define FELICA_SYSTEM_CODE_CODE (0xFFFFU) #define FELICA_TIME_SLOT_1 (0x00U) @@ -58,6 +58,7 @@ typedef enum { FelicaErrorWrongCrc, FelicaErrorProtocol, FelicaErrorTimeout, + FelicaErrorFeatureUnsupported, } FelicaError; typedef struct { diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c index 90954de97..3bf109366 100644 --- a/lib/nfc/protocols/felica/felica_listener.c +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -5,9 +5,20 @@ #include #define FELICA_LISTENER_MAX_BUFFER_SIZE (128) +#define FELICA_LISTENER_CMD_POLLING (0x00U) +#define FELICA_LISTENER_RESPONSE_POLLING (0x01U) #define FELICA_LISTENER_RESPONSE_CODE_READ (0x07) #define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09) +#define FELICA_LISTENER_REQUEST_NONE (0x00U) +#define FELICA_LISTENER_REQUEST_SYSTEM_CODE (0x01U) +#define FELICA_LISTENER_REQUEST_PERFORMANCE (0x02U) + +#define FELICA_LISTENER_SYSTEM_CODE_NDEF (__builtin_bswap16(0x12FCU)) +#define FELICA_LISTENER_SYSTEM_CODE_LITES (__builtin_bswap16(0x88B4U)) + +#define FELICA_LISTENER_PERFORMANCE_VALUE (__builtin_bswap16(0x0083U)) + #define TAG "FelicaListener" FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { @@ -151,6 +162,70 @@ static FelicaError felica_listener_process_request( } } +static void felica_listener_populate_polling_response_header( + FelicaListener* instance, + FelicaListenerPollingResponseHeader* resp) { + resp->idm = instance->data->idm; + resp->pmm = instance->data->pmm; + resp->response_code = FELICA_LISTENER_RESPONSE_POLLING; +} + +static bool felica_listener_check_system_code( + const FelicaListenerGenericRequest* const generic_request, + uint16_t code) { + return ( + generic_request->polling.system_code == code || + generic_request->polling.system_code == (code | 0x00FFU) || + generic_request->polling.system_code == (code | 0xFF00U)); +} + +static uint16_t felica_listener_get_response_system_code( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + uint16_t resp_system_code = FELICA_SYSTEM_CODE_CODE; + if(felica_listener_check_system_code(generic_request, FELICA_LISTENER_SYSTEM_CODE_NDEF) && + instance->data->data.fs.mc.data[FELICA_MC_SYS_OP] == 1) { + // NDEF + resp_system_code = FELICA_LISTENER_SYSTEM_CODE_NDEF; + } else if(felica_listener_check_system_code( + generic_request, FELICA_LISTENER_SYSTEM_CODE_LITES)) { + // Lite-S + resp_system_code = FELICA_LISTENER_SYSTEM_CODE_LITES; + } + return resp_system_code; +} + +static FelicaError felica_listener_process_system_code( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + FelicaError result = FelicaErrorFeatureUnsupported; + do { + uint16_t resp_system_code = + felica_listener_get_response_system_code(instance, generic_request); + if(resp_system_code == FELICA_SYSTEM_CODE_CODE) break; + + FelicaListenerPollingResponse* resp = malloc(sizeof(FelicaListenerPollingResponse)); + felica_listener_populate_polling_response_header(instance, &resp->header); + + resp->header.length = sizeof(FelicaListenerPollingResponse); + if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_SYSTEM_CODE) { + resp->optional_request_data = resp_system_code; + } else if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_PERFORMANCE) { + resp->optional_request_data = FELICA_LISTENER_PERFORMANCE_VALUE; + } else { + resp->header.length = sizeof(FelicaListenerPollingResponseHeader); + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length); + free(resp); + + result = felica_listener_frame_exchange(instance, instance->tx_buffer); + } while(false); + + return result; +} + NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -187,7 +262,23 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { break; } - if(!felica_listener_check_idm(instance, &request->header.idm)) { + if(request->header.code == FELICA_LISTENER_CMD_POLLING) { + // Will always respond at Time Slot 0 for now. + nfc_felica_listener_timer_anticol_start(instance->nfc, 0); + if(request->polling.system_code != FELICA_SYSTEM_CODE_CODE) { + FelicaError error = felica_listener_process_system_code(instance, request); + if(error == FelicaErrorFeatureUnsupported) { + command = NfcCommandReset; + } else if(error != FelicaErrorNone) { + FURI_LOG_E( + TAG, "Error when handling Polling with System Code: %2X", error); + } + break; + } else { + FURI_LOG_E(TAG, "Hardware Polling command leaking through"); + break; + } + } else if(!felica_listener_check_idm(instance, &request->header.idm)) { FURI_LOG_E(TAG, "Wrong IDm"); break; } diff --git a/lib/nfc/protocols/felica/felica_listener_i.c b/lib/nfc/protocols/felica/felica_listener_i.c index b3ae24635..d69c8c66a 100644 --- a/lib/nfc/protocols/felica/felica_listener_i.c +++ b/lib/nfc/protocols/felica/felica_listener_i.c @@ -7,19 +7,6 @@ #define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U) #define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU) -#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U) -#define FELICA_MC_ALL_BYTE (2U) -#define FELICA_MC_SYS_OP (3U) -#define FELICA_MC_RF_PRM (4U) -#define FELICA_MC_CKCKV_W_MAC_A (5U) -#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U) -#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U) -#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U) -#define FELICA_MC_STATE_W_MAC_A (12U) -#define FELICA_MC_RESERVED_13 (13U) -#define FELICA_MC_RESERVED_14 (14U) -#define FELICA_MC_RESERVED_15 (15U) - #define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte]) #define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00) #define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF) diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h index 0ea144249..f64a598cc 100644 --- a/lib/nfc/protocols/felica/felica_listener_i.h +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -7,15 +7,50 @@ #define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U) #define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U) +#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U) +#define FELICA_MC_ALL_BYTE (2U) +#define FELICA_MC_SYS_OP (3U) +#define FELICA_MC_RF_PRM (4U) +#define FELICA_MC_CKCKV_W_MAC_A (5U) +#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U) +#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U) +#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U) +#define FELICA_MC_STATE_W_MAC_A (12U) +#define FELICA_MC_RESERVED_13 (13U) +#define FELICA_MC_RESERVED_14 (14U) +#define FELICA_MC_RESERVED_15 (15U) + typedef enum { Felica_ListenerStateIdle, Felica_ListenerStateActivated, } FelicaListenerState; +typedef struct FURI_PACKED { + uint8_t code; + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; +} FelicaListenerPollingHeader; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + FelicaPMm pmm; +} FelicaListenerPollingResponseHeader; + +typedef struct FURI_PACKED { + FelicaListenerPollingResponseHeader header; + uint16_t optional_request_data; +} FelicaListenerPollingResponse; + /** Generic Felica request same for both read and write operations. */ typedef struct { uint8_t length; - FelicaCommandHeader header; + union { + FelicaCommandHeader header; + FelicaListenerPollingHeader polling; + }; } FelicaListenerGenericRequest; /** Generic request but with list of requested elements. */ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 23c9edb63..fbbb5d13f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.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 d142a6374..f2b13e4a9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.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,, @@ -2859,6 +2859,8 @@ Function,+,nfc_device_set_data,void,"NfcDevice*, NfcProtocol, const NfcDeviceDat Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_uid,_Bool,"NfcDevice*, const uint8_t*, size_t" Function,+,nfc_felica_listener_set_sensf_res_data,NfcError,"Nfc*, const uint8_t*, const uint8_t, const uint8_t*, const uint8_t, const uint16_t" +Function,+,nfc_felica_listener_timer_anticol_start,void,"Nfc*, uint8_t" +Function,+,nfc_felica_listener_timer_anticol_stop,void,Nfc* Function,+,nfc_free,void,Nfc* Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index 1267b7b13..49c59eb79 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -1,8 +1,7 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" -// Prevent FDT timer from starting -#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (INT32_MAX) +#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (0) #define FURI_HAL_FELICA_COMMUNICATION_PERFORMANCE (0x0083U) #define FURI_HAL_FELICA_RESPONSE_CODE (0x01) From 7f0e8f39d37f1ae858cbdae9e9f1b1c2a4e0d409 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 12:24:09 +0200 Subject: [PATCH 3/6] FuriHalSerial: Fix RXFNE interrupt hang (#4246) * Expansion: Wake thread on UART error flag * Expansion: Stop UART async rx early * FuriHalSerial: Fix RXFNE interrupt hang --------- Co-authored-by: hedger --- .../services/expansion/expansion_worker.c | 5 +- targets/f7/furi_hal/furi_hal_serial.c | 67 +++++++++++-------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index ac2a5935b..703a72607 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -35,7 +35,8 @@ typedef enum { ExpansionWorkerFlagError = 1 << 2, } ExpansionWorkerFlag; -#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) +#define EXPANSION_ALL_FLAGS \ + (ExpansionWorkerFlagData | ExpansionWorkerFlagStop | ExpansionWorkerFlagError) struct ExpansionWorker { FuriThread* thread; @@ -360,6 +361,8 @@ static int32_t expansion_worker(void* context) { expansion_worker_state_machine(instance); } + furi_hal_serial_async_rx_stop(instance->serial_handle); + if(instance->state == ExpansionWorkerStateRpcActive) { expansion_worker_rpc_session_close(instance); } diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 8ad9794a8..e5c30a278 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -817,6 +817,21 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_DisableIT_RXNE_RXFNE(USART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + } + } + // Handle must be configured before enabling RX interrupt // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = callback; @@ -824,27 +839,17 @@ static void furi_hal_serial_async_rx_configure( furi_hal_serial[handle->id].rx_dma_callback = NULL; furi_hal_serial[handle->id].context = context; - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { furi_hal_serial_usart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); LL_USART_EnableIT_RXNE_RXFNE(USART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); - furi_hal_serial_usart_deinit_dma_rx(); - LL_USART_DisableIT_RXNE_RXFNE(USART1); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { + } else if(handle->id == FuriHalSerialIdLpuart) { furi_hal_serial_lpuart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); - furi_hal_serial_lpuart_deinit_dma_rx(); - LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } } @@ -944,33 +949,39 @@ static void furi_hal_serial_dma_configure( FuriHalSerialHandle* handle, FuriHalSerialDmaRxCallback callback, void* context) { - furi_check(handle); - - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { - furi_hal_serial_usart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); - } else { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { LL_USART_DisableIT_RXNE_RXFNE(USART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); furi_hal_serial_usart_deinit_dma_rx(); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { - furi_hal_serial_lpuart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); - } else { + } else if(handle->id == FuriHalSerialIdLpuart) { LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); furi_hal_serial_lpuart_deinit_dma_rx(); } } + + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = NULL; furi_hal_serial[handle->id].handle = handle; furi_hal_serial[handle->id].rx_dma_callback = callback; furi_hal_serial[handle->id].context = context; + + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { + furi_hal_serial_usart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + } else if(handle->id == FuriHalSerialIdLpuart) { + furi_hal_serial_lpuart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + } + } } void furi_hal_serial_dma_rx_start( From 6a49131ae96d48fd4581e9851911c40269192f44 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 24 Sep 2025 14:34:28 +0400 Subject: [PATCH 4/6] js_gpio: stop pwm on exit (#4206) Co-authored-by: hedger --- applications/system/js_app/modules/js_gpio.c | 33 ++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 63de6900a..df7d494a6 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -23,6 +23,7 @@ typedef struct { } JsGpioPinInst; ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 +#define M_OPL_ManagedPinsArray_t() ARRAY_OPLIST(ManagedPinsArray) /** * Per-module instance control structure @@ -444,20 +445,26 @@ static void js_gpio_destroy(void* inst) { JsGpioInst* module = (JsGpioInst*)inst; // reset pins - ManagedPinsArray_it_t iterator; - for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); - ManagedPinsArray_next(iterator)) { - JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); - if(manager_data->had_interrupt) { - furi_hal_gpio_disable_int_callback(manager_data->pin); - furi_hal_gpio_remove_int_callback(manager_data->pin); + for + M_EACH(item, module->managed_pins, ManagedPinsArray_t) { + JsGpioPinInst* manager_data = *item; + + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); + } + if(manager_data->pwm_output != FuriHalPwmOutputIdNone) { + if(furi_hal_pwm_is_running(manager_data->pwm_output)) + furi_hal_pwm_stop(manager_data->pwm_output); + } + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + + free(manager_data->interrupt_contract); + free(manager_data); } - furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); - furi_semaphore_free(manager_data->interrupt_semaphore); - free(manager_data->interrupt_contract); - free(manager_data); - } // free buffers furi_hal_adc_release(module->adc_handle); From 7554b325382628eac47972e232397e461c7fe362 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:45:03 +0300 Subject: [PATCH 5/6] SubGHz: Linear and Dickert MAHS protocols fixes & improvements (#4267) * Linear and dickert mahs fixes * fix dip pattern * proper fix by WillyJL * Fix unit tests Linear Decoder now decodes more linear signals --------- Co-authored-by: hedger --- .../unit_tests/tests/subghz/subghz_test.c | 2 +- lib/subghz/protocols/dickert_mahs.c | 5 ++-- lib/subghz/protocols/linear.c | 29 ++++++++++--------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/applications/debug/unit_tests/tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c index ac14bce6a..a58875fe8 100644 --- a/applications/debug/unit_tests/tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -17,7 +17,7 @@ #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 329 +#define TEST_RANDOM_COUNT_PARSE 331 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index 4691e3423..3f614412b 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -288,8 +288,9 @@ void subghz_protocol_decoder_dickert_mahs_feed(void* context, bool level, uint32 instance->decoder.decode_count_bit = 0; } - if((!level) && (duration > 10 * subghz_protocol_dickert_mahs_const.te_short)) { - //Found header DICKERT_MAHS + if((!level) && (DURATION_DIFF(duration, subghz_protocol_dickert_mahs_const.te_long * 50) < + subghz_protocol_dickert_mahs_const.te_delta * 70)) { + //Found header DICKERT_MAHS 44k us instance->decoder.parser_step = DickertMAHSDecoderStepInitial; } break; diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index f9d7aaca8..7671dc428 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -18,7 +18,7 @@ static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, .te_long = 1500, - .te_delta = 150, + .te_delta = 350, .min_count_bit_for_found = 10, }; @@ -116,7 +116,7 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea if(bit_read(instance->generic.data, i - 1)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); } else { @@ -124,22 +124,22 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 3); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_long); } } //Send end bit if(bit_read(instance->generic.data, 0)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); - //Send PT_GUARD + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); + //Send gap instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); } else { //send bit 0 instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); - //Send PT_GUARD + //Send gap instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); } @@ -223,7 +223,7 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur switch(instance->decoder.parser_step) { case LinearDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) < - subghz_protocol_linear_const.te_delta * 20)) { + subghz_protocol_linear_const.te_delta * 15)) { //Found header Linear instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -244,7 +244,7 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur instance->decoder.parser_step = LinearDecoderStepReset; //checking that the duration matches the guardtime if(DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) > - subghz_protocol_linear_const.te_delta * 20) { + subghz_protocol_linear_const.te_delta * 15) { break; } if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < @@ -321,18 +321,21 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + // Protocol is actually implemented wrong way around, bits are inverted. + // Instead of fixing it and breaking old saved remotes, + // only the display here is inverted (~) to show correct values. + uint32_t code_found_lo = ~instance->generic.data & 0x00000000000003ff; uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( - instance->generic.data, instance->generic.data_count_bit); + ~instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000000003ff; furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n" + "Key:0x%03lX\r\n" + "Yek:0x%03lX\r\n" "DIP:" DIP_PATTERN "\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, From 7483069d1049d1208d624fed254b34123a59ee95 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Wed, 24 Sep 2025 16:52:06 +0200 Subject: [PATCH 6/6] hid_app mouse clicker: make mouse button selectable (#4270) * hid_app mouse clicker: make mouse button selectable * hid_app: fixed uninit var warning --------- Co-authored-by: hedger Co-authored-by: hedger --- .../system/hid_app/views/hid_mouse_clicker.c | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index 491f9962b..e9fd9170d 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -19,6 +19,7 @@ typedef struct { bool connected; bool running; int rate; + enum HidMouseButtons btn; } HidMouseClickerModel; static void hid_mouse_clicker_start_or_restart_timer(void* context) { @@ -61,6 +62,26 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { // Ok canvas_draw_icon(canvas, 58, 25, &I_Space_65x18); + canvas_draw_icon(canvas, 61, 50, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 117, 50, &I_ButtonRight_4x7); + + const char* btn_label; + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + btn_label = "Left"; + break; + case HID_MOUSE_BTN_WHEEL: + btn_label = "Middle"; + break; + case HID_MOUSE_BTN_RIGHT: + btn_label = "Right"; + break; + default: + furi_crash(); + } + + elements_multiline_text_aligned(canvas, 89, 57, AlignCenter, AlignBottom, btn_label); + if(model->running) { elements_slightly_rounded_box(canvas, 61, 27, 60, 13); canvas_set_color(canvas, ColorWhite); @@ -100,8 +121,8 @@ static void hid_mouse_clicker_timer_callback(void* context) { HidMouseClickerModel * model, { if(model->running) { - hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); - hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_mouse_clicker->hid, model->btn); + hid_hal_mouse_release(hid_mouse_clicker->hid, model->btn); } }, false); @@ -154,6 +175,34 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { case InputKeyBack: model->running = false; break; + case InputKeyLeft: + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + model->btn = HID_MOUSE_BTN_RIGHT; + break; + case HID_MOUSE_BTN_WHEEL: + model->btn = HID_MOUSE_BTN_LEFT; + break; + case HID_MOUSE_BTN_RIGHT: + model->btn = HID_MOUSE_BTN_WHEEL; + break; + } + consumed = true; + break; + case InputKeyRight: + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + model->btn = HID_MOUSE_BTN_WHEEL; + break; + case HID_MOUSE_BTN_WHEEL: + model->btn = HID_MOUSE_BTN_RIGHT; + break; + case HID_MOUSE_BTN_RIGHT: + model->btn = HID_MOUSE_BTN_LEFT; + break; + } + consumed = true; + break; default: consumed = true; break; @@ -188,7 +237,10 @@ HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { with_view_model( hid_mouse_clicker->view, HidMouseClickerModel * model, - { model->rate = DEFAULT_CLICK_RATE; }, + { + model->rate = DEFAULT_CLICK_RATE; + model->btn = HID_MOUSE_BTN_LEFT; + }, true); return hid_mouse_clicker;