From 6d2b0a3b6c791082bd77a5c097c37c83ba09d329 Mon Sep 17 00:00:00 2001 From: Yoanndp <13591243+Yoanndp@users.noreply.github.com> Date: Sat, 24 Sep 2022 12:36:11 +0200 Subject: [PATCH 01/23] Update ReadMe.md (#1766) --- ReadMe.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index e848e18a4fe..c091d7e3f0f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -24,7 +24,7 @@ Check out details on [how to build firmware](documentation/fbt.md), [write appli Flipper Zero's firmware consists of two components: -- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it. +- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory, and you should never update it. - Core1 Firmware - HAL + OS + Drivers + Applications. They both must be flashed in the order described. @@ -52,7 +52,7 @@ Prerequisites: - [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) - openocd -One liner: `./fbt firmware_flash` +One-liner: `./fbt firmware_flash` ## With USB DFU @@ -128,7 +128,7 @@ Connect your device via ST-Link and run: - `debug` - Debug tool: GDB-plugins, SVD-file and etc - `documentation` - Documentation generation system configs and input files - `firmware` - Firmware source code -- `lib` - Our and 3rd party libraries, drivers and etc... +- `lib` - Our and 3rd party libraries, drivers, etc. - `scripts` - Supplementary scripts and python libraries home -Also pay attention to `ReadMe.md` files inside of those directories. +Also pay attention to `ReadMe.md` files inside those directories. From eadd7801afea681841ea1a2ba28e1e64d819977f Mon Sep 17 00:00:00 2001 From: ghettorce <799240+ghettorce@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:30:19 +0300 Subject: [PATCH 02/23] fbt: exclude user site-packages directory from sys.path (#1778) * fbt: exclude user site-packages directory from sys.path * fbt: python path fixes for *nix * fbt: fixed cli target on Windows Co-authored-by: hedger --- scripts/flipper/utils/cdc.py | 2 +- scripts/serial_cli.py | 14 +++++++++++++- scripts/toolchain/fbtenv.cmd | 1 + scripts/toolchain/fbtenv.sh | 13 +++++++++++++ site_scons/environ.scons | 9 ++++++++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index 081705cc257..7047db2a686 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -14,4 +14,4 @@ def resolve_port(logger, portname: str = "auto"): logger.error("Failed to find connected Flipper") elif len(flippers) > 1: logger.error("More than one Flipper is attached") - logger.error("Failed to guess which port to use. Specify --port") + logger.error("Failed to guess which port to use") diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index e07e6bfb4b8..441bc7cc8e1 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -1,13 +1,25 @@ import logging import subprocess from flipper.utils.cdc import resolve_port +import os +import sys def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): + logger.error("Is Flipper connected over USB and isn't in DFU mode?") return 1 - subprocess.call(["python3", "-m", "serial.tools.miniterm", "--raw", port, "230400"]) + subprocess.call( + [ + os.path.basename(sys.executable), + "-m", + "serial.tools.miniterm", + "--raw", + port, + "230400", + ] + ) if __name__ == "__main__": diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 1403837d940..5eaf16f9443 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -32,6 +32,7 @@ if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" +set "PYTHONNOUSERSITE=1" set "PATH=%FBT_TOOLCHAIN_ROOT%\python;%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\protoc\bin;%FBT_TOOLCHAIN_ROOT%\openocd\bin;%PATH%" set "PROMPT=(fbt) %PROMPT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index f68e4c0bad8..7c501803fbf 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -40,6 +40,13 @@ fbtenv_restore_env() elif [ -n "${PROMPT:-""}" ]; then PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; fi + + PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; + PYTHONPATH="$SAVED_PYTHONPATH"; + + unset SAVED_PYTHONNOUSERSITE; + unset SAVED_PYTHONPATH; + unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; unset FBT_TOOLCHAIN_PATH; @@ -276,6 +283,12 @@ fbtenv_main() PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; + + SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; + SAVED_PYTHONPATH="${PYTHONPATH:-""}"; + + PYTHONNOUSERSITE=1; + PYTHONPATH=; } fbtenv_main "${1:-""}"; diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 99d4cc0b562..c61f29616e5 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -12,7 +12,14 @@ forward_os_env = { "PATH": os.environ["PATH"], } # Proxying CI environment to child processes & scripts -for env_value_name in ("WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", "HOME", "APPDATA"): +for env_value_name in ( + "WORKFLOW_BRANCH_OR_TAG", + "DIST_SUFFIX", + "HOME", + "APPDATA", + "PYTHONHOME", + "PYTHONNOUSERSITE", +): if environ_value := os.environ.get(env_value_name, None): forward_os_env[env_value_name] = environ_value From 92e440c77dce7ede20b25d116ee740009f40c31e Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 25 Sep 2022 21:48:57 +1000 Subject: [PATCH 03/23] Core: simplify record container (#1776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/record.c | 62 ++++++++++++++++------------------------ lib/toolbox/m_cstr_dup.h | 17 +++++++++++ 2 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 lib/toolbox/m_cstr_dup.h diff --git a/furi/core/record.c b/furi/core/record.c index 666d50761a8..63dfdbe477d 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -6,6 +6,7 @@ #include #include +#include #define FURI_RECORD_FLAG_READY (0x1) @@ -15,7 +16,7 @@ typedef struct { size_t holders_count; } FuriRecordData; -DICT_DEF2(FuriRecordDataDict, string_t, STRING_OPLIST, FuriRecordData, M_POD_OPLIST) +DICT_DEF2(FuriRecordDataDict, const char*, M_CSTR_DUP_OPLIST, FuriRecordData, M_POD_OPLIST) typedef struct { FuriMutex* mutex; @@ -24,6 +25,19 @@ typedef struct { static FuriRecord* furi_record = NULL; +static FuriRecordData* furi_record_get(const char* name) { + return FuriRecordDataDict_get(furi_record->records, name); +} + +static void furi_record_put(const char* name, FuriRecordData* record_data) { + FuriRecordDataDict_set_at(furi_record->records, name, *record_data); +} + +static void furi_record_erase(const char* name, FuriRecordData* record_data) { + furi_event_flag_free(record_data->flags); + FuriRecordDataDict_erase(furi_record->records, name); +} + void furi_record_init() { furi_record = malloc(sizeof(FuriRecord)); furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -31,16 +45,16 @@ void furi_record_init() { FuriRecordDataDict_init(furi_record->records); } -static FuriRecordData* furi_record_data_get_or_create(string_t name_str) { +static FuriRecordData* furi_record_data_get_or_create(const char* name) { furi_assert(furi_record); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); if(!record_data) { FuriRecordData new_record; new_record.flags = furi_event_flag_alloc(); new_record.data = NULL; new_record.holders_count = 0; - FuriRecordDataDict_set_at(furi_record->records, name_str, new_record); - record_data = FuriRecordDataDict_get(furi_record->records, name_str); + furi_record_put(name, &new_record); + record_data = furi_record_get(name); } return record_data; } @@ -59,35 +73,25 @@ bool furi_record_exists(const char* name) { bool ret = false; - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - ret = (FuriRecordDataDict_get(furi_record->records, name_str) != NULL); + ret = (furi_record_get(name) != NULL); furi_record_unlock(); - string_clear(name_str); - return ret; } void furi_record_create(const char* name, void* data) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); // Get record data and fill it - FuriRecordData* record_data = furi_record_data_get_or_create(name_str); + FuriRecordData* record_data = furi_record_data_get_or_create(name); furi_assert(record_data->data == NULL); record_data->data = data; furi_event_flag_set(record_data->flags, FURI_RECORD_FLAG_READY); furi_record_unlock(); - - string_clear(name_str); } bool furi_record_destroy(const char* name) { @@ -95,35 +99,26 @@ bool furi_record_destroy(const char* name) { bool ret = false; - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); furi_assert(record_data); if(record_data->holders_count == 0) { - furi_event_flag_free(record_data->flags); - FuriRecordDataDict_erase(furi_record->records, name_str); + furi_record_erase(name, record_data); ret = true; } furi_record_unlock(); - string_clear(name_str); - return ret; } void* furi_record_open(const char* name) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = furi_record_data_get_or_create(name_str); + FuriRecordData* record_data = furi_record_data_get_or_create(name); record_data->holders_count++; furi_record_unlock(); @@ -136,24 +131,17 @@ void* furi_record_open(const char* name) { FuriFlagWaitAny | FuriFlagNoClear, FuriWaitForever) == FURI_RECORD_FLAG_READY); - string_clear(name_str); - return record_data->data; } void furi_record_close(const char* name) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); furi_assert(record_data); record_data->holders_count--; furi_record_unlock(); - - string_clear(name_str); } diff --git a/lib/toolbox/m_cstr_dup.h b/lib/toolbox/m_cstr_dup.h new file mode 100644 index 00000000000..2bc35c87774 --- /dev/null +++ b/lib/toolbox/m_cstr_dup.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#define M_INIT_DUP(a) ((a) = strdup("")) +#define M_SET_DUP(a, b) (M_CHECK_DEFAULT_TYPE(a), free((void*)a), (a) = strdup(b)) +#define M_CLEAR_DUP(a) (free((void*)a)) + +#define M_CSTR_DUP_OPLIST \ + (INIT(M_INIT_DUP), \ + INIT_SET(M_SET_DUP), \ + SET(M_SET_DUP), \ + CLEAR(M_CLEAR_DUP), \ + HASH(m_core_cstr_hash), \ + EQUAL(M_CSTR_EQUAL), \ + CMP(strcmp), \ + TYPE(const char*), \ + OUT_STR(M_CSTR_OUT_STR)) From 7e2008095e9151fd3d0883e742d158926a18db5f Mon Sep 17 00:00:00 2001 From: Jauder Ho Date: Sun, 25 Sep 2022 04:56:53 -0700 Subject: [PATCH 04/23] Bump protobuf from 3.20.1 to 3.20.2 in /scripts (#1774) --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 4c4b7279c74..35cac774211 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -3,5 +3,5 @@ heatshrink2==0.11.0 Pillow==9.1.1 grpcio==1.47.0 grpcio-tools==1.47.0 -protobuf==3.20.1 +protobuf==3.20.2 python3-protobuf==2.5.0 From e6d22ed1475a05db858b39b95ae9933bbd2252ca Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 26 Sep 2022 00:11:29 +1000 Subject: [PATCH 05/23] ELF-Loader: C++ plugin support, loader overhaul. (#1744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fap-loader: load all code and data sections * fap-loader: relocate all code and data sections * fap-loader: remove old elf loader * fap-loader: new jmp call relocation * openocd: resume on detach * fap-loader: trampoline for big jumps * fap-loader: rename cache * fap-loader: init_array support * fap-loader: untangled flipper_application into separate entities * fap-loader: fix debug * fap-loader: optimize section container * fap-loader: optimize key for section container * fap-loader: disable debug log * documentation * F7: bump api symbols version * Lib: cleanup elf_file.c Co-authored-by: あく --- applications/main/fap_loader/fap_loader_app.c | 2 +- debug/flipperapps.py | 8 +- debug/stm32wbx.cfg | 4 + firmware/targets/f7/api_symbols.csv | 8 +- .../application_manifest.c | 21 + .../application_manifest.h | 25 + .../elf/elf_api_interface.h | 2 +- lib/flipper_application/elf/elf_file.c | 794 ++++++++++++++++++ lib/flipper_application/elf/elf_file.h | 127 +++ lib/flipper_application/elf/elf_file_i.h | 46 + .../flipper_applicaiton_i.c | 477 ----------- lib/flipper_application/flipper_application.c | 87 +- lib/flipper_application/flipper_application.h | 33 +- .../flipper_application_i.h | 99 --- 14 files changed, 1089 insertions(+), 644 deletions(-) create mode 100644 lib/flipper_application/application_manifest.c create mode 100644 lib/flipper_application/elf/elf_file.c create mode 100644 lib/flipper_application/elf/elf_file.h create mode 100644 lib/flipper_application/elf/elf_file_i.h delete mode 100644 lib/flipper_application/flipper_applicaiton_i.c delete mode 100644 lib/flipper_application/flipper_application_i.h diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 14da2f32037..9050ddf7834 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -25,7 +25,7 @@ static bool FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); FlipperApplicationPreloadStatus preload_res = - flipper_application_preload(app, string_get_cstr(path)); + flipper_application_preload_manifest(app, string_get_cstr(path)); bool load_success = false; diff --git a/debug/flipperapps.py b/debug/flipperapps.py index c8d3fcdb998..8e1aa2daf90 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -64,7 +64,7 @@ def get_gdb_unload_command(self) -> str: def is_loaded_in_gdb(self, gdb_app) -> bool: # Avoid constructing full app wrapper for comparison - return self.entry_address == int(gdb_app["entry"]) + return self.entry_address == int(gdb_app["state"]["entry"]) @staticmethod def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @@ -78,13 +78,13 @@ def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @staticmethod def from_gdb(gdb_app: "AppState") -> "AppState": state = AppState(str(gdb_app["manifest"]["name"].string())) - state.entry_address = int(gdb_app["entry"]) + state.entry_address = int(gdb_app["state"]["entry"]) app_state = gdb_app["state"] - if debug_link_size := int(app_state["debug_link_size"]): + if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]): debug_link_data = ( gdb.selected_inferior() - .read_memory(int(app_state["debug_link"]), debug_link_size) + .read_memory(int(app_state["debug_link_info"]["debug_link"]), debug_link_size) .tobytes() ) state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data( diff --git a/debug/stm32wbx.cfg b/debug/stm32wbx.cfg index f100c3ccda1..ba383831bd4 100644 --- a/debug/stm32wbx.cfg +++ b/debug/stm32wbx.cfg @@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config { # assignment mmw 0xE0042004 0x00000020 0 } + +$_TARGETNAME configure -event gdb-detach { + resume +} \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ac4df046d57..39365c397fe 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.9,, +Version,+,1.10,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -779,13 +779,13 @@ Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* -Function,-,flipper_application_get_entry_address,const void*,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* -Function,-,flipper_application_get_state,const FlipperApplicationState*,FlipperApplication* -Function,-,flipper_application_get_thread,FuriThread*,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus +Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" +Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* diff --git a/lib/flipper_application/application_manifest.c b/lib/flipper_application/application_manifest.c new file mode 100644 index 00000000000..ab92e493046 --- /dev/null +++ b/lib/flipper_application/application_manifest.c @@ -0,0 +1,21 @@ +#include "application_manifest.h" + +bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest) { + if((manifest->base.manifest_magic != FAP_MANIFEST_MAGIC) || + (manifest->base.manifest_version != FAP_MANIFEST_SUPPORTED_VERSION)) { + return false; + } + + return true; +} + +bool flipper_application_manifest_is_compatible( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface) { + if(manifest->base.api_version.major != api_interface->api_version_major /* || + manifest->base.api_version.minor > app->api_interface->api_version_minor */) { + return false; + } + + return true; +} diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h index 6aa20e48187..f46d44fd7d0 100644 --- a/lib/flipper_application/application_manifest.h +++ b/lib/flipper_application/application_manifest.h @@ -1,6 +1,12 @@ +/** + * @file application_manifest.h + * Flipper application manifest + */ #pragma once #include +#include +#include "elf/elf_api_interface.h" #ifdef __cplusplus extern "C" { @@ -40,6 +46,25 @@ typedef FlipperApplicationManifestV1 FlipperApplicationManifest; #pragma pack(pop) +/** + * @brief Check if manifest is valid + * + * @param manifest + * @return bool + */ +bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest); + +/** + * @brief Check if manifest is compatible with current ELF API interface + * + * @param manifest + * @param api_interface + * @return bool + */ +bool flipper_application_manifest_is_compatible( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface); + #ifdef __cplusplus } #endif diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h index 505f4f71838..ca31fc4836a 100644 --- a/lib/flipper_application/elf/elf_api_interface.h +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #define ELF_INVALID_ADDRESS 0xFFFFFFFF diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c new file mode 100644 index 00000000000..202d0a8756e --- /dev/null +++ b/lib/flipper_application/elf/elf_file.c @@ -0,0 +1,794 @@ +#include +#include "elf_file.h" +#include "elf_file_i.h" +#include "elf_api_interface.h" + +#define TAG "elf" + +#define ELF_NAME_BUFFER_LEN 32 +#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) +#define IS_FLAGS_SET(v, m) ((v & m) == m) +#define RESOLVER_THREAD_YIELD_STEP 30 + +// #define ELF_DEBUG_LOG 1 + +#ifndef ELF_DEBUG_LOG +#undef FURI_LOG_D +#define FURI_LOG_D(...) +#endif + +#define TRAMPOLINE_CODE_SIZE 6 + +/** +ldr r12, [pc, #2] +bx r12 +*/ +const uint8_t trampoline_code_little_endian[TRAMPOLINE_CODE_SIZE] = + {0xdf, 0xf8, 0x02, 0xc0, 0x60, 0x47}; + +typedef struct { + uint8_t code[TRAMPOLINE_CODE_SIZE]; + uint32_t addr; +} __attribute__((packed)) JMPTrampoline; + +/**************************************************************************************************/ +/********************************************* Caches *********************************************/ +/**************************************************************************************************/ + +static bool address_cache_get(AddressCache_t cache, int symEntry, Elf32_Addr* symAddr) { + Elf32_Addr* addr = AddressCache_get(cache, symEntry); + if(addr) { + *symAddr = *addr; + return true; + } else { + return false; + } +} + +static void address_cache_put(AddressCache_t cache, int symEntry, Elf32_Addr symAddr) { + AddressCache_set_at(cache, symEntry, symAddr); +} + +/**************************************************************************************************/ +/********************************************** ELF ***********************************************/ +/**************************************************************************************************/ + +static ELFSection* elf_file_get_section(ELFFile* elf, const char* name) { + return ELFSectionDict_get(elf->sections, name); +} + +static void elf_file_put_section(ELFFile* elf, const char* name, ELFSection* section) { + ELFSectionDict_set_at(elf->sections, strdup(name), *section); +} + +static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t name) { + bool result = false; + + off_t old = storage_file_tell(elf->fd); + + do { + if(!storage_file_seek(elf->fd, offset, true)) break; + + char buffer[ELF_NAME_BUFFER_LEN + 1]; + buffer[ELF_NAME_BUFFER_LEN] = 0; + + while(true) { + uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); + string_cat_str(name, buffer); + if(strlen(buffer) < ELF_NAME_BUFFER_LEN) { + result = true; + break; + } + + if(storage_file_get_error(elf->fd) != FSE_OK || read == 0) break; + } + + } while(false); + storage_file_seek(elf->fd, old, true); + + return result; +} + +static bool elf_read_section_name(ELFFile* elf, off_t offset, string_t name) { + return elf_read_string_from_offset(elf, elf->section_table_strings + offset, name); +} + +static bool elf_read_symbol_name(ELFFile* elf, off_t offset, string_t name) { + return elf_read_string_from_offset(elf, elf->symbol_table_strings + offset, name); +} + +static bool elf_read_section_header(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header) { + off_t offset = SECTION_OFFSET(elf, section_idx); + return storage_file_seek(elf->fd, offset, true) && + storage_file_read(elf->fd, section_header, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); +} + +static bool + elf_read_section(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, string_t name) { + if(!elf_read_section_header(elf, section_idx, section_header)) { + return false; + } + + if(section_header->sh_name && !elf_read_section_name(elf, section_header->sh_name, name)) { + return false; + } + + return true; +} + +static bool elf_read_symbol(ELFFile* elf, int n, Elf32_Sym* sym, string_t name) { + bool success = false; + off_t old = storage_file_tell(elf->fd); + off_t pos = elf->symbol_table + n * sizeof(Elf32_Sym); + if(storage_file_seek(elf->fd, pos, true) && + storage_file_read(elf->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) { + if(sym->st_name) + success = elf_read_symbol_name(elf, sym->st_name, name); + else { + Elf32_Shdr shdr; + success = elf_read_section(elf, sym->st_shndx, &shdr, name); + } + } + storage_file_seek(elf->fd, old, true); + return success; +} + +static ELFSection* elf_section_of(ELFFile* elf, int index) { + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + if(itref->value.sec_idx == index) { + return &itref->value; + } + } + + return NULL; +} + +static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) { + if(sym->st_shndx == SHN_UNDEF) { + Elf32_Addr addr = 0; + if(elf->api_interface->resolver_callback(sName, &addr)) { + return addr; + } + } else { + ELFSection* symSec = elf_section_of(elf, sym->st_shndx); + if(symSec) { + return ((Elf32_Addr)symSec->data) + sym->st_value; + } + } + FURI_LOG_D(TAG, " Can not find address for symbol %s", sName); + return ELF_INVALID_ADDRESS; +} + +__attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) { +#define STRCASE(name) \ + case name: \ + return #name; + switch(symt) { + STRCASE(R_ARM_NONE) + STRCASE(R_ARM_TARGET1) + STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_THM_PC22) + STRCASE(R_ARM_THM_JUMP24) + default: + return "R_"; + } +#undef STRCASE +} + +static JMPTrampoline* elf_create_trampoline(Elf32_Addr addr) { + JMPTrampoline* trampoline = malloc(sizeof(JMPTrampoline)); + memcpy(trampoline->code, trampoline_code_little_endian, TRAMPOLINE_CODE_SIZE); + trampoline->addr = addr; + return trampoline; +} + +static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + int offset, hi, lo, s, j1, j2, i1, i2, imm10, imm11; + int to_thumb, is_call, blx_bit = 1 << 12; + + /* Get initial offset */ + hi = ((uint16_t*)relAddr)[0]; + lo = ((uint16_t*)relAddr)[1]; + s = (hi >> 10) & 1; + j1 = (lo >> 13) & 1; + j2 = (lo >> 11) & 1; + i1 = (j1 ^ s) ^ 1; + i2 = (j2 ^ s) ^ 1; + imm10 = hi & 0x3ff; + imm11 = lo & 0x7ff; + offset = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + if(offset & 0x01000000) offset -= 0x02000000; + + to_thumb = symAddr & 1; + is_call = (type == R_ARM_THM_PC22); + + /* Store offset */ + int offset_copy = offset; + + /* Compute final offset */ + offset += symAddr - relAddr; + if(!to_thumb && is_call) { + blx_bit = 0; /* bl -> blx */ + offset = (offset + 3) & -4; /* Compute offset from aligned PC */ + } + + /* Check that relocation is possible + * offset must not be out of range + * if target is to be entered in arm mode: + - bit 1 must not set + - instruction must be a call (bl) or a jump to PLT */ + if(!to_thumb || offset >= 0x1000000 || offset < -0x1000000) { + if(to_thumb || (symAddr & 2) || (!is_call)) { + FURI_LOG_D( + TAG, + "can't relocate value at %x, %s, doing trampoline", + relAddr, + elf_reloc_type_to_str(type)); + + Elf32_Addr addr; + if(!address_cache_get(elf->trampoline_cache, symAddr, &addr)) { + addr = (Elf32_Addr)elf_create_trampoline(symAddr); + address_cache_put(elf->trampoline_cache, symAddr, addr); + } + + offset = offset_copy; + offset += (int)addr - relAddr; + if(!to_thumb && is_call) { + blx_bit = 0; /* bl -> blx */ + offset = (offset + 3) & -4; /* Compute offset from aligned PC */ + } + } + } + + /* Compute and store final offset */ + s = (offset >> 24) & 1; + i1 = (offset >> 23) & 1; + i2 = (offset >> 22) & 1; + j1 = s ^ (i1 ^ 1); + j2 = s ^ (i2 ^ 1); + imm10 = (offset >> 12) & 0x3ff; + imm11 = (offset >> 1) & 0x7ff; + (*(uint16_t*)relAddr) = (uint16_t)((hi & 0xf800) | (s << 10) | imm10); + (*(uint16_t*)(relAddr + 2)) = + (uint16_t)((lo & 0xc000) | (j1 << 13) | blx_bit | (j2 << 11) | imm11); +} + +static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + switch(type) { + case R_ARM_TARGET1: + case R_ARM_ABS32: + *((uint32_t*)relAddr) += symAddr; + FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + case R_ARM_THM_PC22: + case R_ARM_THM_JUMP24: + elf_relocate_jmp_call(elf, relAddr, type, symAddr); + FURI_LOG_D( + TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + default: + FURI_LOG_E(TAG, " Undefined relocation %d", type); + return false; + } + return true; +} + +static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { + if(s->data) { + Elf32_Rel rel; + size_t relEntries = h->sh_size / sizeof(rel); + size_t relCount; + (void)storage_file_seek(elf->fd, h->sh_offset, true); + FURI_LOG_D(TAG, " Offset Info Type Name"); + + int relocate_result = true; + string_t symbol_name; + string_init(symbol_name); + + for(relCount = 0; relCount < relEntries; relCount++) { + if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { + FURI_LOG_D(TAG, " reloc YIELD"); + furi_delay_tick(1); + } + + if(storage_file_read(elf->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { + FURI_LOG_E(TAG, " reloc read fail"); + string_clear(symbol_name); + return false; + } + + Elf32_Addr symAddr; + + int symEntry = ELF32_R_SYM(rel.r_info); + int relType = ELF32_R_TYPE(rel.r_info); + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset; + + if(!address_cache_get(elf->relocation_cache, symEntry, &symAddr)) { + Elf32_Sym sym; + string_reset(symbol_name); + if(!elf_read_symbol(elf, symEntry, &sym, symbol_name)) { + FURI_LOG_E(TAG, " symbol read fail"); + string_clear(symbol_name); + return false; + } + + FURI_LOG_D( + TAG, + " %08X %08X %-16s %s", + (unsigned int)rel.r_offset, + (unsigned int)rel.r_info, + elf_reloc_type_to_str(relType), + string_get_cstr(symbol_name)); + + symAddr = elf_address_of(elf, &sym, string_get_cstr(symbol_name)); + address_cache_put(elf->relocation_cache, symEntry, symAddr); + } + + if(symAddr != ELF_INVALID_ADDRESS) { + FURI_LOG_D( + TAG, + " symAddr=%08X relAddr=%08X", + (unsigned int)symAddr, + (unsigned int)relAddr); + if(!elf_relocate_symbol(elf, relAddr, relType, symAddr)) { + relocate_result = false; + } + } else { + FURI_LOG_E(TAG, " No symbol address of %s", string_get_cstr(symbol_name)); + relocate_result = false; + } + } + string_clear(symbol_name); + + return relocate_result; + } else { + FURI_LOG_D(TAG, "Section not loaded"); + } + + return false; +} + +/**************************************************************************************************/ +/********************************************* MISC ***********************************************/ +/**************************************************************************************************/ + +static bool cstr_prefix(const char* prefix, const char* string) { + return strncmp(prefix, string, strlen(prefix)) == 0; +} + +/**************************************************************************************************/ +/************************************ Internal FAP interfaces *************************************/ +/**************************************************************************************************/ +typedef enum { + SectionTypeERROR = 0, + SectionTypeUnused = 1 << 0, + SectionTypeData = 1 << 1, + SectionTypeRelData = 1 << 2, + SectionTypeSymTab = 1 << 3, + SectionTypeStrTab = 1 << 4, + SectionTypeManifest = 1 << 5, + SectionTypeDebugLink = 1 << 6, + + SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest, +} SectionType; + +static bool elf_load_metadata( + ELFFile* elf, + Elf32_Shdr* section_header, + FlipperApplicationManifest* manifest) { + if(section_header->sh_size < sizeof(FlipperApplicationManifest)) { + return false; + } + + if(manifest == NULL) { + return true; + } + + return storage_file_seek(elf->fd, section_header->sh_offset, true) && + storage_file_read(elf->fd, manifest, section_header->sh_size) == + section_header->sh_size; +} + +static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) { + elf->debug_link_info.debug_link_size = section_header->sh_size; + elf->debug_link_info.debug_link = malloc(section_header->sh_size); + + return storage_file_seek(elf->fd, section_header->sh_offset, true) && + storage_file_read(elf->fd, elf->debug_link_info.debug_link, section_header->sh_size) == + section_header->sh_size; +} + +static SectionType elf_preload_section( + ELFFile* elf, + size_t section_idx, + Elf32_Shdr* section_header, + string_t name_string, + FlipperApplicationManifest* manifest) { + const char* name = string_get_cstr(name_string); + + const struct { + const char* prefix; + SectionType type; + } lookup_sections[] = { + {".text", SectionTypeData}, + {".rodata", SectionTypeData}, + {".data", SectionTypeData}, + {".bss", SectionTypeData}, + {".preinit_array", SectionTypeData}, + {".init_array", SectionTypeData}, + {".fini_array", SectionTypeData}, + {".rel.text", SectionTypeRelData}, + {".rel.rodata", SectionTypeRelData}, + {".rel.data", SectionTypeRelData}, + {".rel.preinit_array", SectionTypeRelData}, + {".rel.init_array", SectionTypeRelData}, + {".rel.fini_array", SectionTypeRelData}, + }; + + for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { + if(cstr_prefix(lookup_sections[i].prefix, name)) { + FURI_LOG_D(TAG, "Found section %s", lookup_sections[i].prefix); + + if(lookup_sections[i].type == SectionTypeRelData) { + name = name + strlen(".rel"); + } + + ELFSection* section_p = elf_file_get_section(elf, name); + if(!section_p) { + ELFSection section = { + .data = NULL, + .sec_idx = 0, + .rel_sec_idx = 0, + .size = 0, + }; + + elf_file_put_section(elf, name, §ion); + section_p = elf_file_get_section(elf, name); + } + + if(lookup_sections[i].type == SectionTypeRelData) { + section_p->rel_sec_idx = section_idx; + } else { + section_p->sec_idx = section_idx; + } + + return lookup_sections[i].type; + } + } + + if(strcmp(name, ".symtab") == 0) { + FURI_LOG_D(TAG, "Found .symtab section"); + elf->symbol_table = section_header->sh_offset; + elf->symbol_count = section_header->sh_size / sizeof(Elf32_Sym); + return SectionTypeSymTab; + } else if(strcmp(name, ".strtab") == 0) { + FURI_LOG_D(TAG, "Found .strtab section"); + elf->symbol_table_strings = section_header->sh_offset; + return SectionTypeStrTab; + } else if(strcmp(name, ".fapmeta") == 0) { + FURI_LOG_D(TAG, "Found .fapmeta section"); + if(elf_load_metadata(elf, section_header, manifest)) { + return SectionTypeManifest; + } else { + return SectionTypeERROR; + } + } else if(strcmp(name, ".gnu_debuglink") == 0) { + FURI_LOG_D(TAG, "Found .gnu_debuglink section"); + if(elf_load_debug_link(elf, section_header)) { + return SectionTypeDebugLink; + } else { + return SectionTypeERROR; + } + } + + return SectionTypeUnused; +} + +static bool elf_load_section_data(ELFFile* elf, ELFSection* section) { + Elf32_Shdr section_header; + if(section->sec_idx == 0) { + FURI_LOG_D(TAG, "Section is not present"); + return true; + } + + if(!elf_read_section_header(elf, section->sec_idx, §ion_header)) { + return false; + } + + if(section_header.sh_size == 0) { + FURI_LOG_D(TAG, "No data for section"); + return true; + } + + section->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); + section->size = section_header.sh_size; + + if(section_header.sh_type == SHT_NOBITS) { + /* section is empty (.bss?) */ + /* no need to memset - allocator already did that */ + return true; + } + + if((!storage_file_seek(elf->fd, section_header.sh_offset, true)) || + (storage_file_read(elf->fd, section->data, section_header.sh_size) != + section_header.sh_size)) { + FURI_LOG_E(TAG, " seek/read fail"); + return false; + } + + FURI_LOG_D(TAG, "0x%X", section->data); + return true; +} + +static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { + Elf32_Shdr section_header; + if(section->rel_sec_idx) { + FURI_LOG_D(TAG, "Relocating section"); + if(elf_read_section_header(elf, section->rel_sec_idx, §ion_header)) + return elf_relocate(elf, §ion_header, section); + else { + FURI_LOG_E(TAG, "Error reading section header"); + return false; + } + } else { + FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ + } + return true; +} + +static void elf_file_call_section_list(ELFFile* elf, const char* name, bool reverse_order) { + ELFSection* section = elf_file_get_section(elf, name); + + if(section && section->size) { + const uint32_t* start = section->data; + const uint32_t* end = section->data + section->size; + + if(reverse_order) { + while(end > start) { + end--; + ((void (*)(void))(*end))(); + } + } else { + while(start < end) { + ((void (*)(void))(*start))(); + start++; + } + } + } +} + +/**************************************************************************************************/ +/********************************************* Public *********************************************/ +/**************************************************************************************************/ + +ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) { + ELFFile* elf = malloc(sizeof(ELFFile)); + elf->fd = storage_file_alloc(storage); + elf->api_interface = api_interface; + ELFSectionDict_init(elf->sections); + AddressCache_init(elf->trampoline_cache); + return elf; +} + +void elf_file_free(ELFFile* elf) { + // free sections data + { + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); + if(itref->value.data) { + aligned_free(itref->value.data); + } + free((void*)itref->key); + } + + ELFSectionDict_clear(elf->sections); + } + + // free trampoline data + { + AddressCache_it_t it; + for(AddressCache_it(it, elf->trampoline_cache); !AddressCache_end_p(it); + AddressCache_next(it)) { + const AddressCache_itref_t* itref = AddressCache_cref(it); + free((void*)itref->value); + } + + AddressCache_clear(elf->trampoline_cache); + } + + if(elf->debug_link_info.debug_link) { + free(elf->debug_link_info.debug_link); + } + + storage_file_free(elf->fd); + free(elf); +} + +bool elf_file_open(ELFFile* elf, const char* path) { + Elf32_Ehdr h; + Elf32_Shdr sH; + + if(!storage_file_open(elf->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) || + !storage_file_seek(elf->fd, 0, true) || + storage_file_read(elf->fd, &h, sizeof(h)) != sizeof(h) || + !storage_file_seek(elf->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) || + storage_file_read(elf->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { + return false; + } + + elf->entry = h.e_entry; + elf->sections_count = h.e_shnum; + elf->section_table = h.e_shoff; + elf->section_table_strings = sH.sh_offset; + return true; +} + +bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) { + bool result = false; + string_t name; + string_init(name); + + FURI_LOG_D(TAG, "Looking for manifest section"); + for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { + Elf32_Shdr section_header; + + string_reset(name); + if(!elf_read_section(elf, section_idx, §ion_header, name)) { + break; + } + + if(string_cmp(name, ".fapmeta") == 0) { + if(elf_load_metadata(elf, §ion_header, manifest)) { + FURI_LOG_D(TAG, "Load manifest done"); + result = true; + break; + } else { + break; + } + } + } + + string_clear(name); + return result; +} + +bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) { + SectionType loaded_sections = SectionTypeERROR; + string_t name; + string_init(name); + + FURI_LOG_D(TAG, "Scan ELF indexs..."); + for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { + Elf32_Shdr section_header; + + string_reset(name); + if(!elf_read_section(elf, section_idx, §ion_header, name)) { + loaded_sections = SectionTypeERROR; + break; + } + + FURI_LOG_D(TAG, "Preloading data for section #%d %s", section_idx, string_get_cstr(name)); + SectionType section_type = + elf_preload_section(elf, section_idx, §ion_header, name, manifest); + loaded_sections |= section_type; + + if(section_type == SectionTypeERROR) { + loaded_sections = SectionTypeERROR; + break; + } + } + + string_clear(name); + FURI_LOG_D(TAG, "Load symbols done"); + + return IS_FLAGS_SET(loaded_sections, SectionTypeValid); +} + +ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { + ELFFileLoadStatus status = ELFFileLoadStatusSuccess; + ELFSectionDict_it_t it; + + AddressCache_init(elf->relocation_cache); + size_t start = furi_get_tick(); + + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + FURI_LOG_D(TAG, "Loading section '%s'", itref->key); + if(!elf_load_section_data(elf, &itref->value)) { + FURI_LOG_E(TAG, "Error loading section '%s'", itref->key); + status = ELFFileLoadStatusUnspecifiedError; + } + } + + if(status == ELFFileLoadStatusSuccess) { + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + FURI_LOG_D(TAG, "Relocating section '%s'", itref->key); + if(!elf_relocate_section(elf, &itref->value)) { + FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key); + status = ELFFileLoadStatusMissingImports; + } + } + } + + /* Fixing up entry point */ + if(status == ELFFileLoadStatusSuccess) { + ELFSection* text_section = elf_file_get_section(elf, ".text"); + + if(text_section == NULL) { + FURI_LOG_E(TAG, "No .text section found"); + status = ELFFileLoadStatusUnspecifiedError; + } else { + elf->entry += (uint32_t)text_section->data; + } + } + + FURI_LOG_D(TAG, "Relocation cache size: %u", AddressCache_size(elf->relocation_cache)); + FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache)); + AddressCache_clear(elf->relocation_cache); + FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); + + return status; +} + +void elf_file_pre_run(ELFFile* elf) { + elf_file_call_section_list(elf, ".preinit_array", false); + elf_file_call_section_list(elf, ".init_array", false); +} + +int32_t elf_file_run(ELFFile* elf, void* args) { + int32_t result; + result = ((int32_t(*)(void*))elf->entry)(args); + return result; +} + +void elf_file_post_run(ELFFile* elf) { + elf_file_call_section_list(elf, ".fini_array", true); +} + +const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) { + return elf_file->api_interface; +} + +void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) { + // set entry + debug_info->entry = elf->entry; + + // copy debug info + memcpy(&debug_info->debug_link_info, &elf->debug_link_info, sizeof(ELFDebugLinkInfo)); + + // init mmap + debug_info->mmap_entry_count = ELFSectionDict_size(elf->sections); + debug_info->mmap_entries = malloc(sizeof(ELFMemoryMapEntry) * debug_info->mmap_entry_count); + uint32_t mmap_entry_idx = 0; + + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); + + const void* data_ptr = itref->value.data; + if(data_ptr) { + debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; + debug_info->mmap_entries[mmap_entry_idx].name = itref->key; + mmap_entry_idx++; + } + } +} + +void elf_file_clear_debug_info(ELFDebugInfo* debug_info) { + // clear debug info + memset(&debug_info->debug_link_info, 0, sizeof(ELFDebugLinkInfo)); + + // clear mmap + if(debug_info->mmap_entries) { + free(debug_info->mmap_entries); + debug_info->mmap_entries = NULL; + } + + debug_info->mmap_entry_count = 0; +} diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h new file mode 100644 index 00000000000..673f165ccca --- /dev/null +++ b/lib/flipper_application/elf/elf_file.h @@ -0,0 +1,127 @@ +/** + * @file elf_file.h + * ELF file loader + */ +#pragma once +#include +#include "../application_manifest.h" +#include "elf_api_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ELFFile ELFFile; + +typedef struct { + const char* name; + uint32_t address; +} ELFMemoryMapEntry; + +typedef struct { + uint32_t debug_link_size; + uint8_t* debug_link; +} ELFDebugLinkInfo; + +typedef struct { + uint32_t mmap_entry_count; + ELFMemoryMapEntry* mmap_entries; + ELFDebugLinkInfo debug_link_info; + off_t entry; +} ELFDebugInfo; + +typedef enum { + ELFFileLoadStatusSuccess = 0, + ELFFileLoadStatusUnspecifiedError, + ELFFileLoadStatusNoFreeMemory, + ELFFileLoadStatusMissingImports, +} ELFFileLoadStatus; + +/** + * @brief Allocate ELFFile instance + * @param storage + * @param api_interface + * @return ELFFile* + */ +ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface); + +/** + * @brief Free ELFFile instance + * @param elf_file + */ +void elf_file_free(ELFFile* elf_file); + +/** + * @brief Open ELF file + * @param elf_file + * @param path + * @return bool + */ +bool elf_file_open(ELFFile* elf_file, const char* path); + +/** + * @brief Load ELF file manifest + * @param elf + * @param manifest + * @return bool + */ +bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest); + +/** + * @brief Load ELF file section table (load stage #1) + * @param elf_file + * @param manifest + * @return bool + */ +bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest); + +/** + * @brief Load and relocate ELF file sections (load stage #2) + * @param elf_file + * @return ELFFileLoadStatus + */ +ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file); + +/** + * @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3) + * @param elf + */ +void elf_file_pre_run(ELFFile* elf); + +/** + * @brief Run ELF file (load stage #4) + * @param elf_file + * @param args + * @return int32_t + */ +int32_t elf_file_run(ELFFile* elf_file, void* args); + +/** + * @brief Execute ELF file post-run stage, call static destructors for example (load stage #5) + * @param elf + */ +void elf_file_post_run(ELFFile* elf); + +/** + * @brief Get ELF file API interface + * @param elf_file + * @return const ElfApiInterface* + */ +const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file); + +/** + * @brief Get ELF file debug info + * @param elf_file + * @param debug_info + */ +void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info); + +/** + * @brief Clear ELF file debug info generated by elf_file_init_debug_info + * @param debug_info + */ +void elf_file_clear_debug_info(ELFDebugInfo* debug_info); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h new file mode 100644 index 00000000000..1df075f062b --- /dev/null +++ b/lib/flipper_application/elf/elf_file_i.h @@ -0,0 +1,46 @@ +#pragma once +#include "elf_file.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) + +/** + * Callable elf entry type + */ +typedef int32_t(entry_t)(void*); + +typedef struct { + void* data; + uint16_t sec_idx; + uint16_t rel_sec_idx; + Elf32_Word size; +} ELFSection; + +DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST) + +struct ELFFile { + size_t sections_count; + off_t section_table; + off_t section_table_strings; + + size_t symbol_count; + off_t symbol_table; + off_t symbol_table_strings; + off_t entry; + ELFSectionDict_t sections; + + AddressCache_t relocation_cache; + AddressCache_t trampoline_cache; + + File* fd; + const ElfApiInterface* api_interface; + ELFDebugLinkInfo debug_link_info; +}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_applicaiton_i.c b/lib/flipper_application/flipper_applicaiton_i.c deleted file mode 100644 index a2a069eebc5..00000000000 --- a/lib/flipper_application/flipper_applicaiton_i.c +++ /dev/null @@ -1,477 +0,0 @@ -#include "flipper_application_i.h" -#include - -#define TAG "fapp-i" - -#define RESOLVER_THREAD_YIELD_STEP 30 - -#define IS_FLAGS_SET(v, m) ((v & m) == m) -#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) -#define SYMBOL_OFFSET(e, n) (e->_table + n * sizeof(Elf32_Shdr)) - -bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path) { - Elf32_Ehdr h; - Elf32_Shdr sH; - - if(!storage_file_open(e->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) || - !storage_file_seek(e->fd, 0, true) || - storage_file_read(e->fd, &h, sizeof(h)) != sizeof(h) || - !storage_file_seek(e->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) || - storage_file_read(e->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { - return false; - } - - e->entry = h.e_entry; - e->sections = h.e_shnum; - e->section_table = h.e_shoff; - e->section_table_strings = sH.sh_offset; - return true; -} - -static bool flipper_application_load_metadata(FlipperApplication* e, Elf32_Shdr* sh) { - if(sh->sh_size < sizeof(e->manifest)) { - return false; - } - - return storage_file_seek(e->fd, sh->sh_offset, true) && - storage_file_read(e->fd, &e->manifest, sh->sh_size) == sh->sh_size; -} - -static bool flipper_application_load_debug_link(FlipperApplication* e, Elf32_Shdr* sh) { - e->state.debug_link_size = sh->sh_size; - e->state.debug_link = malloc(sh->sh_size); - - return storage_file_seek(e->fd, sh->sh_offset, true) && - storage_file_read(e->fd, e->state.debug_link, sh->sh_size) == sh->sh_size; -} - -static FindFlags_t flipper_application_preload_section( - FlipperApplication* e, - Elf32_Shdr* sh, - const char* name, - int n) { - FURI_LOG_D(TAG, "Processing: %s", name); - - const struct { - const char* name; - uint16_t* ptr_section_idx; - FindFlags_t flags; - } lookup_sections[] = { - {".text", &e->text.sec_idx, FoundText}, - {".rodata", &e->rodata.sec_idx, FoundRodata}, - {".data", &e->data.sec_idx, FoundData}, - {".bss", &e->bss.sec_idx, FoundBss}, - {".rel.text", &e->text.rel_sec_idx, FoundRelText}, - {".rel.rodata", &e->rodata.rel_sec_idx, FoundRelRodata}, - {".rel.data", &e->data.rel_sec_idx, FoundRelData}, - }; - - for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { - if(strcmp(name, lookup_sections[i].name) == 0) { - *lookup_sections[i].ptr_section_idx = n; - return lookup_sections[i].flags; - } - } - - if(strcmp(name, ".symtab") == 0) { - e->symbol_table = sh->sh_offset; - e->symbol_count = sh->sh_size / sizeof(Elf32_Sym); - return FoundSymTab; - } else if(strcmp(name, ".strtab") == 0) { - e->symbol_table_strings = sh->sh_offset; - return FoundStrTab; - } else if(strcmp(name, ".fapmeta") == 0) { - // Load metadata immediately - if(flipper_application_load_metadata(e, sh)) { - return FoundFappManifest; - } - } else if(strcmp(name, ".gnu_debuglink") == 0) { - if(flipper_application_load_debug_link(e, sh)) { - return FoundDebugLink; - } - } - return FoundERROR; -} - -static bool - read_string_from_offset(FlipperApplication* e, off_t offset, char* buffer, size_t buffer_size) { - bool success = false; - - off_t old = storage_file_tell(e->fd); - if(storage_file_seek(e->fd, offset, true) && - (storage_file_read(e->fd, buffer, buffer_size) == buffer_size)) { - success = true; - } - storage_file_seek(e->fd, old, true); - - return success; -} - -static bool read_section_name(FlipperApplication* e, off_t off, char* buf, size_t max) { - return read_string_from_offset(e, e->section_table_strings + off, buf, max); -} - -static bool read_symbol_name(FlipperApplication* e, off_t off, char* buf, size_t max) { - return read_string_from_offset(e, e->symbol_table_strings + off, buf, max); -} - -static bool read_section_header(FlipperApplication* e, int n, Elf32_Shdr* h) { - off_t offset = SECTION_OFFSET(e, n); - return storage_file_seek(e->fd, offset, true) && - storage_file_read(e->fd, h, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); -} - -static bool read_section(FlipperApplication* e, int n, Elf32_Shdr* h, char* name, size_t nlen) { - if(!read_section_header(e, n, h)) { - return false; - } - if(!h->sh_name) { - return true; - } - return read_section_name(e, h->sh_name, name, nlen); -} - -bool flipper_application_load_section_table(FlipperApplication* e) { - furi_check(e->state.mmap_entry_count == 0); - - size_t n; - FindFlags_t found = FoundERROR; - FURI_LOG_D(TAG, "Scan ELF indexs..."); - for(n = 1; n < e->sections; n++) { - Elf32_Shdr section_header; - char name[33] = {0}; - if(!read_section_header(e, n, §ion_header)) { - return false; - } - if(section_header.sh_name && - !read_section_name(e, section_header.sh_name, name, sizeof(name))) { - return false; - } - - FURI_LOG_T(TAG, "Examining section %d %s", n, name); - FindFlags_t section_flags = - flipper_application_preload_section(e, §ion_header, name, n); - found |= section_flags; - if((section_flags & FoundGdbSection) != 0) { - e->state.mmap_entry_count++; - } - if(IS_FLAGS_SET(found, FoundAll)) { - return true; - } - } - - FURI_LOG_D(TAG, "Load symbols done"); - return IS_FLAGS_SET(found, FoundValid); -} - -static const char* type_to_str(int symt) { -#define STRCASE(name) \ - case name: \ - return #name; - switch(symt) { - STRCASE(R_ARM_NONE) - STRCASE(R_ARM_ABS32) - STRCASE(R_ARM_THM_PC22) - STRCASE(R_ARM_THM_JUMP24) - default: - return "R_"; - } -#undef STRCASE -} - -static void relocate_jmp_call(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { - UNUSED(type); - uint16_t upper_insn = ((uint16_t*)relAddr)[0]; - uint16_t lower_insn = ((uint16_t*)relAddr)[1]; - uint32_t S = (upper_insn >> 10) & 1; - uint32_t J1 = (lower_insn >> 13) & 1; - uint32_t J2 = (lower_insn >> 11) & 1; - - int32_t offset = (S << 24) | /* S -> offset[24] */ - ((~(J1 ^ S) & 1) << 23) | /* J1 -> offset[23] */ - ((~(J2 ^ S) & 1) << 22) | /* J2 -> offset[22] */ - ((upper_insn & 0x03ff) << 12) | /* imm10 -> offset[12:21] */ - ((lower_insn & 0x07ff) << 1); /* imm11 -> offset[1:11] */ - if(offset & 0x01000000) offset -= 0x02000000; - - offset += symAddr - relAddr; - - S = (offset >> 24) & 1; - J1 = S ^ (~(offset >> 23) & 1); - J2 = S ^ (~(offset >> 22) & 1); - - upper_insn = ((upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff)); - ((uint16_t*)relAddr)[0] = upper_insn; - - lower_insn = ((lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | ((offset >> 1) & 0x07ff)); - ((uint16_t*)relAddr)[1] = lower_insn; -} - -static bool relocate_symbol(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { - switch(type) { - case R_ARM_ABS32: - *((uint32_t*)relAddr) += symAddr; - FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); - break; - case R_ARM_THM_PC22: - case R_ARM_THM_JUMP24: - relocate_jmp_call(relAddr, type, symAddr); - FURI_LOG_D( - TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); - break; - default: - FURI_LOG_D(TAG, " Undefined relocation %d", type); - return false; - } - return true; -} - -static ELFSection_t* section_of(FlipperApplication* e, int index) { - if(e->text.sec_idx == index) { - return &e->text; - } else if(e->data.sec_idx == index) { - return &e->data; - } else if(e->bss.sec_idx == index) { - return &e->bss; - } else if(e->rodata.sec_idx == index) { - return &e->rodata; - } - return NULL; -} - -static Elf32_Addr address_of(FlipperApplication* e, Elf32_Sym* sym, const char* sName) { - if(sym->st_shndx == SHN_UNDEF) { - Elf32_Addr addr = 0; - if(e->api_interface->resolver_callback(sName, &addr)) { - return addr; - } - } else { - ELFSection_t* symSec = section_of(e, sym->st_shndx); - if(symSec) { - return ((Elf32_Addr)symSec->data) + sym->st_value; - } - } - FURI_LOG_D(TAG, " Can not find address for symbol %s", sName); - return ELF_INVALID_ADDRESS; -} - -static bool read_symbol(FlipperApplication* e, int n, Elf32_Sym* sym, char* name, size_t nlen) { - bool success = false; - off_t old = storage_file_tell(e->fd); - off_t pos = e->symbol_table + n * sizeof(Elf32_Sym); - if(storage_file_seek(e->fd, pos, true) && - storage_file_read(e->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) { - if(sym->st_name) - success = read_symbol_name(e, sym->st_name, name, nlen); - else { - Elf32_Shdr shdr; - success = read_section(e, sym->st_shndx, &shdr, name, nlen); - } - } - storage_file_seek(e->fd, old, true); - return success; -} - -static bool - relocation_cache_get(RelocationAddressCache_t cache, int symEntry, Elf32_Addr* symAddr) { - Elf32_Addr* addr = RelocationAddressCache_get(cache, symEntry); - if(addr) { - *symAddr = *addr; - return true; - } else { - return false; - } -} - -static void - relocation_cache_put(RelocationAddressCache_t cache, int symEntry, Elf32_Addr symAddr) { - RelocationAddressCache_set_at(cache, symEntry, symAddr); -} - -#define MAX_SYMBOL_NAME_LEN 128u - -static bool relocate(FlipperApplication* e, Elf32_Shdr* h, ELFSection_t* s) { - if(s->data) { - Elf32_Rel rel; - size_t relEntries = h->sh_size / sizeof(rel); - size_t relCount; - (void)storage_file_seek(e->fd, h->sh_offset, true); - FURI_LOG_D(TAG, " Offset Info Type Name"); - - int relocate_result = true; - char symbol_name[MAX_SYMBOL_NAME_LEN + 1] = {0}; - - for(relCount = 0; relCount < relEntries; relCount++) { - if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { - FURI_LOG_D(TAG, " reloc YIELD"); - furi_delay_tick(1); - } - - if(storage_file_read(e->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { - FURI_LOG_E(TAG, " reloc read fail"); - return false; - } - - Elf32_Addr symAddr; - - int symEntry = ELF32_R_SYM(rel.r_info); - int relType = ELF32_R_TYPE(rel.r_info); - Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset; - - if(!relocation_cache_get(e->relocation_cache, symEntry, &symAddr)) { - Elf32_Sym sym; - if(!read_symbol(e, symEntry, &sym, symbol_name, MAX_SYMBOL_NAME_LEN)) { - FURI_LOG_E(TAG, " symbol read fail"); - return false; - } - - FURI_LOG_D( - TAG, - " %08X %08X %-16s %s", - (unsigned int)rel.r_offset, - (unsigned int)rel.r_info, - type_to_str(relType), - symbol_name); - - symAddr = address_of(e, &sym, symbol_name); - relocation_cache_put(e->relocation_cache, symEntry, symAddr); - } - - if(symAddr != ELF_INVALID_ADDRESS) { - FURI_LOG_D( - TAG, - " symAddr=%08X relAddr=%08X", - (unsigned int)symAddr, - (unsigned int)relAddr); - if(!relocate_symbol(relAddr, relType, symAddr)) { - relocate_result = false; - } - } else { - FURI_LOG_D(TAG, " No symbol address of %s", symbol_name); - relocate_result = false; - } - } - - return relocate_result; - } else - FURI_LOG_I(TAG, "Section not loaded"); - - return false; -} - -static bool flipper_application_load_section_data(FlipperApplication* e, ELFSection_t* s) { - Elf32_Shdr section_header; - if(s->sec_idx == 0) { - FURI_LOG_I(TAG, "Section is not present"); - return true; - } - - if(!read_section_header(e, s->sec_idx, §ion_header)) { - return false; - } - - if(section_header.sh_size == 0) { - FURI_LOG_I(TAG, "No data for section"); - return true; - } - - s->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); - // e->state.mmap_entry_count++; - - if(section_header.sh_type == SHT_NOBITS) { - /* section is empty (.bss?) */ - /* no need to memset - allocator already did that */ - /* memset(s->data, 0, h->sh_size); */ - FURI_LOG_D(TAG, "0x%X", s->data); - return true; - } - - if((!storage_file_seek(e->fd, section_header.sh_offset, true)) || - (storage_file_read(e->fd, s->data, section_header.sh_size) != section_header.sh_size)) { - FURI_LOG_E(TAG, " seek/read fail"); - flipper_application_free_section(s); - return false; - } - - FURI_LOG_D(TAG, "0x%X", s->data); - return true; -} - -static bool flipper_application_relocate_section(FlipperApplication* e, ELFSection_t* s) { - Elf32_Shdr section_header; - if(s->rel_sec_idx) { - FURI_LOG_D(TAG, "Relocating section"); - if(read_section_header(e, s->rel_sec_idx, §ion_header)) - return relocate(e, §ion_header, s); - else { - FURI_LOG_E(TAG, "Error reading section header"); - return false; - } - } else - FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ - return true; -} - -FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e) { - FlipperApplicationLoadStatus status = FlipperApplicationLoadStatusSuccess; - RelocationAddressCache_init(e->relocation_cache); - size_t start = furi_get_tick(); - - struct { - ELFSection_t* section; - const char* name; - } sections[] = { - {&e->text, ".text"}, - {&e->rodata, ".rodata"}, - {&e->data, ".data"}, - {&e->bss, ".bss"}, - }; - - for(size_t i = 0; i < COUNT_OF(sections); i++) { - if(!flipper_application_load_section_data(e, sections[i].section)) { - FURI_LOG_E(TAG, "Error loading section '%s'", sections[i].name); - status = FlipperApplicationLoadStatusUnspecifiedError; - } - } - - if(status == FlipperApplicationLoadStatusSuccess) { - for(size_t i = 0; i < COUNT_OF(sections); i++) { - if(!flipper_application_relocate_section(e, sections[i].section)) { - FURI_LOG_E(TAG, "Error relocating section '%s'", sections[i].name); - status = FlipperApplicationLoadStatusMissingImports; - } - } - } - - if(status == FlipperApplicationLoadStatusSuccess) { - e->state.mmap_entries = - malloc(sizeof(FlipperApplicationMemoryMapEntry) * e->state.mmap_entry_count); - uint32_t mmap_entry_idx = 0; - for(size_t i = 0; i < COUNT_OF(sections); i++) { - const void* data_ptr = sections[i].section->data; - if(data_ptr) { - FURI_LOG_I(TAG, "0x%X %s", (uint32_t)data_ptr, sections[i].name); - e->state.mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; - e->state.mmap_entries[mmap_entry_idx].name = sections[i].name; - mmap_entry_idx++; - } - } - furi_check(mmap_entry_idx == e->state.mmap_entry_count); - - /* Fixing up entry point */ - e->entry += (uint32_t)e->text.data; - } - - FURI_LOG_D(TAG, "Relocation cache size: %u", RelocationAddressCache_size(e->relocation_cache)); - RelocationAddressCache_clear(e->relocation_cache); - FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); - - return status; -} - -void flipper_application_free_section(ELFSection_t* s) { - if(s->data) { - aligned_free(s->data); - } - s->data = NULL; -} diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 6e84cce38a0..cf44eebb2ab 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -1,16 +1,22 @@ #include "flipper_application.h" -#include "flipper_application_i.h" +#include "elf/elf_file.h" #define TAG "fapp" +struct FlipperApplication { + ELFDebugInfo state; + FlipperApplicationManifest manifest; + ELFFile* elf; + FuriThread* thread; +}; + /* For debugger access to app state */ FlipperApplication* last_loaded_app = NULL; FlipperApplication* flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) { FlipperApplication* app = malloc(sizeof(FlipperApplication)); - app->api_interface = api_interface; - app->fd = storage_file_alloc(storage); + app->elf = elf_file_alloc(storage, api_interface); app->thread = NULL; return app; } @@ -25,43 +31,43 @@ void flipper_application_free(FlipperApplication* app) { last_loaded_app = NULL; - if(app->state.debug_link_size) { - free(app->state.debug_link); - } + elf_file_clear_debug_info(&app->state); + elf_file_free(app->elf); + free(app); +} - if(app->state.mmap_entries) { - free(app->state.mmap_entries); +static FlipperApplicationPreloadStatus + flipper_application_validate_manifest(FlipperApplication* app) { + if(!flipper_application_manifest_is_valid(&app->manifest)) { + return FlipperApplicationPreloadStatusInvalidManifest; } - ELFSection_t* sections[] = {&app->text, &app->rodata, &app->data, &app->bss}; - for(size_t i = 0; i < COUNT_OF(sections); i++) { - flipper_application_free_section(sections[i]); + if(!flipper_application_manifest_is_compatible( + &app->manifest, elf_file_get_api_interface(app->elf))) { + return FlipperApplicationPreloadStatusApiMismatch; } - storage_file_free(app->fd); - - free(app); + return FlipperApplicationPreloadStatusSuccess; } /* Parse headers, load manifest */ FlipperApplicationPreloadStatus - flipper_application_preload(FlipperApplication* app, const char* path) { - if(!flipper_application_load_elf_headers(app, path) || - !flipper_application_load_section_table(app)) { + flipper_application_preload_manifest(FlipperApplication* app, const char* path) { + if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) { return FlipperApplicationPreloadStatusInvalidFile; } - if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) && - (app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) { - return FlipperApplicationPreloadStatusInvalidManifest; - } + return flipper_application_validate_manifest(app); +} - if(app->manifest.base.api_version.major != app->api_interface->api_version_major /* || - app->manifest.base.api_version.minor > app->api_interface->api_version_minor */) { - return FlipperApplicationPreloadStatusApiMismatch; +/* Parse headers, load full file */ +FlipperApplicationPreloadStatus + flipper_application_preload(FlipperApplication* app, const char* path) { + if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) { + return FlipperApplicationPreloadStatusInvalidFile; } - return FlipperApplicationPreloadStatusSuccess; + return flipper_application_validate_manifest(app); } const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) { @@ -70,11 +76,26 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { last_loaded_app = app; - return flipper_application_load_sections(app); + ELFFileLoadStatus status = elf_file_load_sections(app->elf); + + switch(status) { + case ELFFileLoadStatusSuccess: + elf_file_init_debug_info(app->elf, &app->state); + return FlipperApplicationLoadStatusSuccess; + case ELFFileLoadStatusNoFreeMemory: + return FlipperApplicationLoadStatusNoFreeMemory; + case ELFFileLoadStatusMissingImports: + return FlipperApplicationLoadStatusMissingImports; + default: + return FlipperApplicationLoadStatusUnspecifiedError; + } } -const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app) { - return &app->state; +static int32_t flipper_application_thread(void* context) { + elf_file_pre_run(last_loaded_app->elf); + int32_t result = elf_file_run(last_loaded_app->elf, context); + elf_file_post_run(last_loaded_app->elf); + return result; } FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { @@ -86,20 +107,12 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { app->thread = furi_thread_alloc(); furi_thread_set_stack_size(app->thread, manifest->stack_size); furi_thread_set_name(app->thread, manifest->name); - furi_thread_set_callback(app->thread, (entry_t*)app->entry); + furi_thread_set_callback(app->thread, flipper_application_thread); furi_thread_set_context(app->thread, args); return app->thread; } -FuriThread* flipper_application_get_thread(FlipperApplication* app) { - return app->thread; -} - -void const* flipper_application_get_entry_address(FlipperApplication* app) { - return (void*)app->entry; -} - static const char* preload_status_strings[] = { [FlipperApplicationPreloadStatusSuccess] = "Success", [FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error", diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index 34de4038886..b3e5996bbd5 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -1,3 +1,7 @@ +/** + * @file flipper_application.h + * Flipper application + */ #pragma once #include "application_manifest.h" @@ -79,6 +83,14 @@ void flipper_application_free(FlipperApplication* app); FlipperApplicationPreloadStatus flipper_application_preload(FlipperApplication* app, const char* path); +/** + * @brief Validate elf file and load application manifest + * @param app Application pointer + * @return Preload result code + */ +FlipperApplicationPreloadStatus + flipper_application_preload_manifest(FlipperApplication* app, const char* path); + /** * @brief Get pointer to application manifest for preloaded application * @param app Application pointer @@ -93,13 +105,6 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic */ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); -/** - * @brief Get state object for loaded application - * @param app Application pointer - * @return Pointer to state object - */ -const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app); - /** * @brief Create application thread at entry point address, using app name and * stack size from metadata. Returned thread isn't started yet. @@ -110,20 +115,6 @@ const FlipperApplicationState* flipper_application_get_state(FlipperApplication* */ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); -/** - * @brief Get previously spawned thread - * @param app Application pointer - * @return Created thread - */ -FuriThread* flipper_application_get_thread(FlipperApplication* app); - -/** - * @brief Return relocated and valid address of app's entry point - * @param app Application pointer - * @return Address of app's entry point - */ -void const* flipper_application_get_entry_address(FlipperApplication* app); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_application_i.h b/lib/flipper_application/flipper_application_i.h deleted file mode 100644 index 8adf5c0d2d5..00000000000 --- a/lib/flipper_application/flipper_application_i.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "elf.h" -#include "flipper_application.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -DICT_DEF2(RelocationAddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) - -/** - * Callable elf entry type - */ -typedef int32_t(entry_t)(void*); - -typedef struct { - void* data; - uint16_t sec_idx; - uint16_t rel_sec_idx; -} ELFSection_t; - -struct FlipperApplication { - const ElfApiInterface* api_interface; - File* fd; - FlipperApplicationState state; - FlipperApplicationManifest manifest; - - size_t sections; - off_t section_table; - off_t section_table_strings; - - size_t symbol_count; - off_t symbol_table; - off_t symbol_table_strings; - off_t entry; - - ELFSection_t text; - ELFSection_t rodata; - ELFSection_t data; - ELFSection_t bss; - - FuriThread* thread; - RelocationAddressCache_t relocation_cache; -}; - -typedef enum { - FoundERROR = 0, - FoundSymTab = (1 << 0), - FoundStrTab = (1 << 2), - FoundText = (1 << 3), - FoundRodata = (1 << 4), - FoundData = (1 << 5), - FoundBss = (1 << 6), - FoundRelText = (1 << 7), - FoundRelRodata = (1 << 8), - FoundRelData = (1 << 9), - FoundRelBss = (1 << 10), - FoundFappManifest = (1 << 11), - FoundDebugLink = (1 << 12), - FoundValid = FoundSymTab | FoundStrTab | FoundFappManifest, - FoundExec = FoundValid | FoundText, - FoundGdbSection = FoundText | FoundRodata | FoundData | FoundBss, - FoundAll = FoundSymTab | FoundStrTab | FoundText | FoundRodata | FoundData | FoundBss | - FoundRelText | FoundRelRodata | FoundRelData | FoundRelBss | FoundDebugLink, -} FindFlags_t; - -/** - * @brief Load and validate basic ELF file headers - * @param e Application instance - * @param path FS path to application file - * @return true if ELF file is valid - */ -bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path); - -/** - * @brief Iterate over all sections and save related indexes - * @param e Application instance - * @return true if all required sections are found - */ -bool flipper_application_load_section_table(FlipperApplication* e); - -/** - * @brief Load section data to memory and process relocations - * @param e Application instance - * @return Status code - */ -FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e); - -/** - * @brief Release section data - * @param s section pointer - */ -void flipper_application_free_section(ELFSection_t* s); - -#ifdef __cplusplus -} -#endif \ No newline at end of file From bc777b2eff816613c6a703e46ab5e24c7d694127 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:34:52 +0400 Subject: [PATCH 06/23] SubGhz: fix config menu (#1748) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix config menu * SubGhz: fix gui Magellen protocol * SubGhz: fix gui Transmit SubGhz * SubGhz: keeloq, new gen manufacture code * SubGhz: Update keeloq_mfcodes Co-authored-by: あく --- .../scenes/subghz_scene_receiver_config.c | 1 + applications/main/subghz/views/transmitter.c | 7 +- assets/resources/subghz/assets/keeloq_mfcodes | 92 ++++++++++--------- lib/subghz/protocols/keeloq.c | 12 +++ lib/subghz/protocols/keeloq_common.c | 12 +++ lib/subghz/protocols/keeloq_common.h | 9 ++ lib/subghz/protocols/magellen.c | 2 +- 7 files changed, 87 insertions(+), 48 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index c59630f7ea4..541ec0e0d4b 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -223,6 +223,7 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; + variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 3cbcf098a85..dd2b6d321b1 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -45,7 +45,7 @@ void subghz_view_transmitter_add_data_to_show( } static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { - const uint8_t button_height = 13; + const uint8_t button_height = 12; const uint8_t vertical_offset = 3; const uint8_t horizontal_offset = 1; const uint8_t string_width = canvas_string_width(canvas, str); @@ -69,7 +69,10 @@ static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str canvas_invert_color(canvas); canvas_draw_icon( - canvas, x + horizontal_offset, y - button_height + vertical_offset, &I_ButtonCenter_7x7); + canvas, + x + horizontal_offset, + y - button_height + vertical_offset - 1, + &I_ButtonCenter_7x7); canvas_draw_str( canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); canvas_invert_color(canvas); diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index f9771285edc..b8fc36903f2 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,48 +1,50 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: F2 D4 F5 5A B3 CC 3F 21 28 3A AF ED D1 EB 73 DF -BBFA4D79A73C384D6E07E717F761F32A625F28AA1DB2261B8B19A18261E30AB6 -CE4004AB56111B0B3D486770705FAD8BD616A80957EA2C537BAF1FD09E552DA3 -F974561612C9C751237C64D978F706B41873FDBE38851306574F436CB02D9ECA -E29CAB7C2C2D9853D0F4DF69F582562E8182234E78B355540F6FE3F78C73D518 -97ABE18993A700A607E37DC88E1434F84DDD1C2771693978C9D2FA4CE4F2AB7BBC7C3EB3E8545B37FBBE1C1F1CA03543 -E86ABD0AAE5A4B4A9414C9CB6112CA49B3A8EC29723B14DCA85902F41B05ADDC -C1FBE921035F408C59DA6AD5E76E3887AC9BC90146619B3CAE445BED556E96AC -232C9F86915B927888352797B45F159268FE78956CF09B8D241CDC393D3B0225 -3D9E2A3C701C9D4DD4D72038D4536CA6F515C547CAB0AD18BA71204BD2ABFB74 -4D69A4506D2C97EF8EC68F90CF1AD1065A1EB909793EEB3AF71B0D75E55B9E76 -5A7F4595DFA181C3E946EBEE4974DBD6DA85AF6FCAD0B3725FDD28667175A421D69A2122853E57927C38CCF368732476 -6A946FAEDE134155B5A88EC01AA535E7A778947D360218B560381A64CAF9ACE896079D04C14718D5AD5C0D4EE3005F52 -88AC0C723AAA875A1885C8392A616FA43B205119B0E8D299193979A1921FC8B3 -40588AADA5E1A8BE214B2CCF32D268B48C6B783AE0DD10D88BDF3FF88E921E09 -A7BE05D05DEC9B9A3AE1575D411BF7B12366AD78B726F3E3E843E7BF199961A4 -79F973A155A4367F0EAA078AA0857A2A2A82FC4C8A5AE9E567E7CBF62C2A5CE2 -C38296EEABDA1F95D0C401CC6DDC8656476DC19248588EEF1CB93773D94CDB02A40C902970C4FCB14FABEFFB4F8BC208 -B0B7699B3C3573EE4D88D8CE65FAF3532B5A741D1F20892C0F38BAA2BCE98F2D -6E401D6BDB1B33A404DEB668F3FB353166475487BAADE4A348E3CFDEB3B1B54B -0E44B87878617559783CC6A7C65BE9F99950FE8956ED4BB04894BC53085E3A09CA19915B1E8C143A68D1B7A97F5D1ECB -AC19E55638429C65E6E567C0E96DA9648F8FB80215CF693D7FD5DD86FE7989AC7AC7BAE86BBD4FFF7161AFFB405FFA98 -BCE70C69D90AD639A737813FC8FD26F40F803137BD36E47651C266A671428D6F -F053CF5255AD2E1875A5C38635F7BF203B1DAE1433B162C30AE8695AC8A5589D -B7EFC77FFA98B173E429B3566A27842C4DC5E91B0BC01F07A6A98332C4E1F42A -D7C7950FFB2C5E7D9BCDBC230BF5F1BFFC0FE6F1CF5C8C6013DD90E41AE403FE -50667B2E5909FD5F9D6385788A81DE5F72E56512EAD6BF5EACCA959CB6AF0DEF -6435E07E5E952124B0F80F76E0F68265B8289087387E35C6D51831B299335480 -D7DE1F7748FB8BF90561151CC6AEADC160CA883FE5228768A3737A89F358AF58 -FA206F860C6F981FD4A358FDEA5E1860353406D8416FF2A811D17EBA09C803EA -F2F7B2C6705D1457315F2AAA859AB53592241D63B84C045BC742D220BA110144 -3F0E05E572D1DF5E2B0BBB20EF8F3EB4D198CDF2794F86089E1DB0EF975E9337 -7D54D088C22AA3BA9A97FAB64371B8D512CDEC2A4355116BE2B74BCEC7FEC852 -0FD951F13E19F0FC1A25655DA430640034BE34659C526238E62B6042691998CB -FCA04B0BF98FA89AAEF41A78AE7141EF7783E0D0CBAAB1B6F00C0AD3EAA84A54759D46E1A9BEEDCCE68BA12902802111 -6AD801CE08D58A380B689574BD7FCACC5DF768BDD93AD7EE1AA514A2351EF13A -0A820F47699AFC4A5E3285BF521771FC5B6C5FB7C6C08A1990DA3B3A6766E860 -A7AAC90972DB24D20B57DDD46DC2624FC6169D529426E64B0544AC383799BB2A -AF6088873BC71ED672FA39D50B386523825218C43CDB35D691B0C5895B7EF5C2 -774DFAC8D285241368CB377DA947D7A94951A1520017DF77FE2E6A517D5C6A1FC768BB1E2398F5AF71B10D1806C04CCD -AA788A707E64C40E2A0EB8154FE795EAC68B936FD6BAC5DEF7677A4D5FE344DD -A193EF5D1B223B0FA3C231052EDBDD7A31B0C192BCD8E7E37E11D4D899476ACD -F6986E08949122D46BFA7F218B089E8DB00DCFA6971C5F2468CDDD179E5BBC40 -EDC23A07689EF6229081D1AB9E249E68527BD33EB72C242BA97727E64AF15BCC -70CC64359A2A5DE40D5A30E916DE6532BCC511E7489CD3A2E5DEC269D303FDBD83B7EA14BF13B40E3C960C6D3D12774B +IV: 2A 34 F1 5A AF 6F F5 1A 83 A6 1E DA DE B7 3D F1 +06B63DF24AE073A2F2B19C55CA9E8364FBECD26E49C551990153F6513BDE5267 +6139C78C74C341EB7474085CF1D047BD6FB005F80A72AF3EF3F89D58EF5DF500 +D85F11689020ECA47FBE9C2B67EE41A81E1F06DE2A35AF958965E3ECE29EA701 +1AE9073A42FE0E439544FE6945F6B33CF15A7A4A279020B5E0B3BE33FD189A7E +E161F007854BB33E0056FA09A2E2DEE66789B5C87C8D6D3DE2C8C1BD2B48983EB9D1C5697CA6E95996918F7C47B761B0 +59AE4644DCB3D720C38B5115F230DA58E7BE0A697907F6174BB05AB7886ACDB1 +634DF0BCC185C4C1F7E1B1594B4438D051ABAE092433078963063B51D961D08C +1EBEBCB49E498B9BE977D53EC21B9A546155B627737BD0AA832D496035729346 +4DFA93E639197772D57E8ACE04512CEFC045B8CC965C175A25ED525B630CBB63 +C2D5235D1014A319B249EAE8A5EE350F18D5AB8A498EF222704BD4EB1435F388 +F66D1937160E1392197F463A52E87FCE938A92070892113443C348D7553327A5715CF615CE2F2C96284F47759E043419 +841D29E7CBE040188E2283BFBA9F26EF2F65CCB085B56C3515E8C46C3F20BD75BAA963550869435FDAF509CEEE66A2C4 +7D87E24487D307635E7A17B989B8547EE11F3BF3468D055F0B44633B631BA42C +B4916043973501B95A82B329196D6EBA69FBBC3AF8FD914583104E0E18CE82F6 +E4649F9C2A5465D2EA6F3E9724DD06CD6962FE2BAEB14F1453C14D1559232AE1 +96E15D890DF7FD348441F5E429A875754C6BF0520A787F8E9D8C5415674783CC +CB52005EDED47B57F795BC92FB0522EAB18D23EE028B8D10ED57828C250EB285BFEC6E4A4BE8DABCE0D57ECAA20D90C3 +8E5A50C7D5C374445E88752301D20F0B3D6E4988B61D90FD63779B0EDEF9C60D +49D6CB276A0E5FF134A38062503F01351F44CD6455708B50B5F07D03FC477C33 +CB45B56613DF208E79E4E10A6510F07DC1AA49210C7B94E8BBAECD2C35EC6ABC99FB10FD7C96DD6BB6A6685E9FAD93FB +0743F3CC51200F763C242F1956B4D775C092ADF1A5C19ACAE96EB60C2990CF214F8FEA8FC6749286F6BDAB67657C479A +E5608B28A058787D64A145F0362DEFD98CAE0B5A0F22C6DA7C6D278C7B5F95E3 +D4C113D43E7FB6D2EFA9E87471AA76A61B26872607B4AF5B87F9D72113835CE6 +2DC502800BFD21B76126390CA64A08C5432A2254E822F214CDE1EA11430084C5 +CA22C73010B0F1CB8009601BE2AF0B3674D83D5880E4A26C2A3FF0EA0A098CEA +E53B2B102FDB000E9BB747F957156976E5A0C0E3898AA844C13AE8A9CEE7013B +95CF1A46FFC252BE92919531C92BF6A3AA1B16C170DF4461EC54BE07A55C2387 +2EC7E24090F6DFFF6F2F2D8874D2F36AA769995F31F29FBE3B0EA6A16C3EE833 +C1145B1D9AC70761EA902B86455C1BE1BB1153552A1F7327411DECABE538827B +18D596CADD2EE544200A58716C7A4690B658E58CC2B97334740F70894A6C90FA +6A2F8859DFF01E13AC6C5300AD4A2218810FC91A6FB64A560E99FE6C99226AD2 +48D2EB5A08E35AF89A3B7A1CFDEE829FC0C2DDD2E965F4E3D043B0B14CB7825E +91039325D53CDD0236D1CD13047973A013C14B45A32DE0784A73BFABCEAFBCD1 +51B4EAC87C4DC49B007F40D38B8166C388A1AF25E8D2FF6598E8EDE8726E6E14AD88443114D2A0F5E7721E304F3870DA +3A179DDF65B9868CD84C7C04931F40D5D204C97B20DCBF1A70C241E59BFD7F14 +AF538FD16104DCAF03F4DDF05026D6741898DFC247E48A8F72E652DDF2DFD289 +E67F16AEC9D84B6C06F77B806CA6FBC7618BFBECD0D7A04EC3AE1D1DD06BEC5B +FA4D9F8920EBF2F4293C6D4E99083AA4A71A9DDFFDB07EEBDC552DACEC4DA24A +5BF23E630AC81E2CD533803E225BCB3C481B8D650A9858CF2B5219BAE1CDA01A +17B57E8C1032481E69247EA9A0C9EA41F6C0EA9B3F11170CA69C0842423F0455 +96EA848B8527A647DC9DACDB16C5D92B0081EB1CD77B99B47F56C2E249190BD3BE4306333F37487133DD3AD8E57F3092 +B0E9411274D799BE5989D52E74E00DE310CCA2BD47D7A8FA554D66BB04CD787A +D0D28476E3D8832975653D93F545C35278EC1F0B7AD70CA2F36EB476CC207937 +933195E37014619F997B73F5CF4C0110865A822CA8CB0ED1D977D49A1B06A37F +E790CAC2A26452BF941A9E1BABF0A85598EA1CC8F8CFED637C9B40D5E027B518 +49C1F179ABA5BD4F2C45257A33701730E9CC4728677EFF07808ABE31D3CE6FD5C805F43EA5ABB7261B220C82F0794092 diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 99b3c5fb342..cfb92fe8b07 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -521,6 +521,15 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 1; } break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; case KEELOQ_LEARNING_UNKNOWN: // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); @@ -528,6 +537,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = string_get_cstr(manufacture_code->name); return 1; } + // Check for mirrored man uint64_t man_rev = 0; uint64_t man_rev_byte = 0; @@ -535,11 +545,13 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man_rev_byte = (uint8_t)(manufacture_code->key >> i); man_rev = man_rev | man_rev_byte << (56 - i); } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = string_get_cstr(manufacture_code->name); return 1; } + //########################### // Normal Learning // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index 0f8c763db24..4c0c1d4ef1a 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -86,3 +86,15 @@ inline uint64_t data &= 0x0FFFFFFF; return (((uint64_t)data << 32) | data) ^ xor; } + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { + return man | ((uint64_t)data << 40) | + ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); +} diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index aa07a7f5858..448388f0a62 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -21,6 +21,7 @@ #define KEELOQ_LEARNING_NORMAL 2u #define KEELOQ_LEARNING_SECURE 3u #define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 5u /** * Simple Learning Encrypt @@ -63,3 +64,11 @@ uint64_t * @return manufacture for this serial number (64bit) */ uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index 52ef5a72470..6dcc83e5648 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -381,7 +381,7 @@ static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t "%s%s%s%s%s%s%s%s", ((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") : (event & 0x1 ? " Motion" : " Ok")), - ((event >> 1) & 0x1 ? ", Tamper On (Alarm)" : ""), + ((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""), ((event >> 2) & 0x1 ? ", ?" : ""), ((event >> 3) & 0x1 ? ", Power On" : ""), ((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""), From f86eada292462754001329c8a4158a7b0e785fe6 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Sun, 25 Sep 2022 16:39:06 +0200 Subject: [PATCH 07/23] Remove unused headers (#1751) --- applications/services/power/power_service/power.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 85d217f290f..757d7718ab9 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -1,10 +1,7 @@ #include "power_i.h" -#include "views/power_off.h" #include #include -#include -#include #define POWER_OFF_TIMEOUT 90 From 2a2078d9b5d07b4e689a6827705891427bd4bc57 Mon Sep 17 00:00:00 2001 From: Chris van Marle Date: Sun, 25 Sep 2022 18:17:09 +0200 Subject: [PATCH 08/23] Text input overwrite max size template (#1687) --- applications/services/gui/modules/text_input.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index b8098a3b9f2..58d7ecab0d0 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -318,15 +318,17 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b } } else if(selected == BACKSPACE_KEY) { text_input_backspace_cb(model); - } else if(text_length < (model->text_buffer_size - 1)) { + } else { if(model->clear_default_text) { text_length = 0; } - if(text_length == 0 && char_is_lowercase(selected)) { - selected = char_to_uppercase(selected); + if(text_length < (model->text_buffer_size - 1)) { + if(text_length == 0 && char_is_lowercase(selected)) { + selected = char_to_uppercase(selected); + } + model->text_buffer[text_length] = selected; + model->text_buffer[text_length + 1] = 0; } - model->text_buffer[text_length] = selected; - model->text_buffer[text_length + 1] = 0; } model->clear_default_text = false; } From a6b98ccbbe8b1aa7e3da4eefacca5987ad05b03e Mon Sep 17 00:00:00 2001 From: David Coles Date: Sun, 25 Sep 2022 14:06:46 -0700 Subject: [PATCH 09/23] Preliminary Rust support (#1781) * Add support for R_ARM_THM_MOVW_ABS_NC/THM_MOVT_ABS These are sometimes emitted by the Rust LLVM compiler. Ref: https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#56relocation * Discard LLVM bitcode from extension applications LLVM-based compilers may include uncompressed bitcode in object files to help with link-time optimization. However this can bloat binary sizes from KB to MB. * Expose alligned_malloc/free functions to applications This is required to implement a global allocator in Rust. --- firmware/targets/f7/api_symbols.csv | 4 +-- firmware/targets/f7/application-ext.ld | 2 ++ lib/flipper_application/elf/elf.h | 2 ++ lib/flipper_application/elf/elf_file.c | 41 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 39365c397fe..11b719ddb43 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -433,8 +433,8 @@ Function,-,acoshl,long double,long double Function,-,acosl,long double,long double Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" Function,-,aligned_alloc,void*,"size_t, size_t" -Function,-,aligned_free,void,void* -Function,-,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t diff --git a/firmware/targets/f7/application-ext.ld b/firmware/targets/f7/application-ext.ld index 8f79675be2e..01bb021b655 100644 --- a/firmware/targets/f7/application-ext.ld +++ b/firmware/targets/f7/application-ext.ld @@ -48,5 +48,7 @@ SECTIONS { *(.comment) *(.comment.*) + *(.llvmbc) + *(.llvmcmd) } } diff --git a/lib/flipper_application/elf/elf.h b/lib/flipper_application/elf/elf.h index f1697ba484c..a36622b52dc 100644 --- a/lib/flipper_application/elf/elf.h +++ b/lib/flipper_application/elf/elf.h @@ -1116,6 +1116,8 @@ typedef struct { #define R_ARM_LDR_SBREL_11_0 35 #define R_ARM_ALU_SBREL_19_12 36 #define R_ARM_ALU_SBREL_27_20 37 +#define R_ARM_THM_MOVW_ABS_NC 47 /* Direct 16 bit (Thumb32 MOVW) */ +#define R_ARM_THM_MOVT_ABS 48 /* Direct high 16 bit */ #define R_ARM_GNU_VTENTRY 100 #define R_ARM_GNU_VTINHERIT 101 #define R_ARM_THM_PC11 102 /* thumb unconditional branch */ diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 202d0a8756e..e2616549558 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -255,6 +255,42 @@ static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, El (uint16_t)((lo & 0xc000) | (j1 << 13) | blx_bit | (j2 << 11) | imm11); } +static void elf_relocate_mov(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + uint16_t upper_insn = ((uint16_t*)relAddr)[0]; + uint16_t lower_insn = ((uint16_t*)relAddr)[1]; + + /* MOV* ,# + * + * i = upper[10] + * imm4 = upper[3:0] + * imm3 = lower[14:12] + * imm8 = lower[7:0] + * + * imm16 = imm4:i:imm3:imm8 + */ + uint32_t i = (upper_insn >> 10) & 1; /* upper[10] */ + uint32_t imm4 = upper_insn & 0x000F; /* upper[3:0] */ + uint32_t imm3 = (lower_insn >> 12) & 0x7; /* lower[14:12] */ + uint32_t imm8 = lower_insn & 0x00FF; /* lower[7:0] */ + + int32_t addend = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8; /* imm16 */ + + uint32_t addr = (symAddr + addend); + if (type == R_ARM_THM_MOVT_ABS) { + addr >>= 16; /* upper 16 bits */ + } else { + addr &= 0x0000FFFF; /* lower 16 bits */ + } + + /* Re-encode */ + ((uint16_t*)relAddr)[0] = (upper_insn & 0xFBF0) + | (((addr >> 11) & 1) << 10) /* i */ + | ((addr >> 12) & 0x000F); /* imm4 */ + ((uint16_t*)relAddr)[1] = (lower_insn & 0x8F00) + | (((addr >> 8) & 0x7) << 12) /* imm3 */ + | (addr & 0x00FF); /* imm8 */ +} + static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { switch(type) { case R_ARM_TARGET1: @@ -268,6 +304,11 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 FURI_LOG_D( TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; + case R_ARM_THM_MOVW_ABS_NC: + case R_ARM_THM_MOVT_ABS: + elf_relocate_mov(relAddr, type, symAddr); + FURI_LOG_D(TAG, " R_ARM_THM_MOVW_ABS_NC/MOVT_ABS relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; default: FURI_LOG_E(TAG, " Undefined relocation %d", type); return false; From efb09380bd4fcc5e2702ddf66d67db89f9772443 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 26 Sep 2022 15:03:21 +0400 Subject: [PATCH 10/23] [FL-2836] Fast flash programming mode (#1782) * updater: lowered logging level for resources unpacking; hal: implemented fast flash write mode * hal: reworked fast flash programming; clearing most error flags on flash init; changed some flash functions return type from bool to void; scripts: fixed malformed CRC values in update bundles in certain cases; * hal: flash: larger critical section * hal: flash: enabling fast write inside critical section * api_symbols: bump minor version --- .../services/storage/storages/storage_int.c | 14 +- .../updater/util/update_task_worker_flasher.c | 12 +- firmware/targets/f7/api_symbols.csv | 8 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 141 +++++++++++------- firmware/targets/f7/furi_hal/furi_hal_flash.h | 12 +- lib/toolbox/tar/tar_archive.c | 2 +- scripts/update.py | 2 +- 7 files changed, 109 insertions(+), 82 deletions(-) diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index cae61f16e47..7583973543f 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -109,10 +109,7 @@ static int storage_int_device_prog( int ret = 0; while(size > 0) { - if(!furi_hal_flash_write_dword(address, *(uint64_t*)buffer)) { - ret = -1; - break; - } + furi_hal_flash_write_dword(address, *(uint64_t*)buffer); address += c->prog_size; buffer += c->prog_size; size -= c->prog_size; @@ -127,16 +124,13 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc FURI_LOG_D(TAG, "Device erase: page %d, translated page: %x", block, page); - if(furi_hal_flash_erase(page)) { - return 0; - } else { - return -1; - } + furi_hal_flash_erase(page); + return 0; } static int storage_int_device_sync(const struct lfs_config* c) { UNUSED(c); - FURI_LOG_D(TAG, "Device sync: skipping, cause "); + FURI_LOG_D(TAG, "Device sync: skipping"); return 0; } diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index d56b4ae0a9f..7b598c50b10 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -52,11 +52,19 @@ static bool check_address_boundaries(const size_t address) { return ((address >= min_allowed_address) && (address < max_allowed_address)); } +static bool update_task_flash_program_page( + const uint8_t i_page, + const uint8_t* update_block, + uint16_t update_block_len) { + furi_hal_flash_program_page(i_page, update_block, update_block_len); + return true; +} + static bool update_task_write_dfu(UpdateTask* update_task) { DfuUpdateTask page_task = { .address_cb = &check_address_boundaries, .progress_cb = &update_task_file_progress, - .task_cb = &furi_hal_flash_program_page, + .task_cb = &update_task_flash_program_page, .context = update_task, }; @@ -117,7 +125,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) { furi_hal_flash_get_page_number(update_task->manifest->radio_address + element_offs); CHECK_RESULT(i_page >= 0); - CHECK_RESULT(furi_hal_flash_program_page(i_page, fw_block, bytes_read)); + furi_hal_flash_program_page(i_page, fw_block, bytes_read); element_offs += bytes_read; update_task_set_progress( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 11b719ddb43..c8e6a6cf491 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.10,, +Version,+,1.11,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -986,7 +986,7 @@ Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,-,furi_hal_deinit_early,void, -Function,-,furi_hal_flash_erase,_Bool,uint8_t +Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, Function,-,furi_hal_flash_get_cycles_count,size_t, Function,-,furi_hal_flash_get_free_end_address,const void*, @@ -1001,8 +1001,8 @@ Function,-,furi_hal_flash_init,void, Function,-,furi_hal_flash_ob_apply,void, Function,-,furi_hal_flash_ob_get_raw_ptr,const FuriHalFlashRawOptionByteData*, Function,-,furi_hal_flash_ob_set_word,_Bool,"size_t, const uint32_t" -Function,-,furi_hal_flash_program_page,_Bool,"const uint8_t, const uint8_t*, uint16_t" -Function,-,furi_hal_flash_write_dword,_Bool,"size_t, uint64_t" +Function,-,furi_hal_flash_program_page,void,"const uint8_t, const uint8_t*, uint16_t" +Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index 9e05dc1238a..f99cf8c3da8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -21,7 +21,6 @@ (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR) -//#define FURI_HAL_FLASH_OB_START_ADDRESS 0x1FFF8000 #define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B #define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) @@ -80,9 +79,13 @@ size_t furi_hal_flash_get_free_page_count() { } void furi_hal_flash_init() { - // Errata 2.2.9, Flash OPTVERR flag is always set after system reset - WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); - //__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR); + /* Errata 2.2.9, Flash OPTVERR flag is always set after system reset */ + // WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); + /* Actually, reset all error flags on start */ + if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) { + FURI_LOG_E(TAG, "FLASH->SR 0x%08X", FLASH->SR); + WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS); + } } static void furi_hal_flash_unlock() { @@ -91,6 +94,7 @@ static void furi_hal_flash_unlock() { /* Authorize the FLASH Registers access */ WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1); + __ISB(); WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2); /* verify Flash is unlocked */ @@ -110,38 +114,38 @@ static void furi_hal_flash_lock(void) { } static void furi_hal_flash_begin_with_core2(bool erase_flag) { - // Take flash controller ownership + /* Take flash controller ownership */ while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID) != 0) { furi_thread_yield(); } - // Unlock flash operation + /* Unlock flash operation */ furi_hal_flash_unlock(); - // Erase activity notification + /* Erase activity notification */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - // 64mHz 5us core2 flag protection + /* 64mHz 5us core2 flag protection */ for(volatile uint32_t i = 0; i < 35; i++) ; while(true) { - // Wait till flash controller become usable + /* Wait till flash controller become usable */ while(LL_FLASH_IsActiveFlag_OperationSuspended()) { furi_thread_yield(); }; - // Just a little more love + /* Just a little more love */ taskENTER_CRITICAL(); - // Actually we already have mutex for it, but specification is specification + /* Actually we already have mutex for it, but specification is specification */ if(LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) { taskEXIT_CRITICAL(); furi_thread_yield(); continue; } - // Take sempahopre and prevent core2 from anything funky + /* Take sempahopre and prevent core2 from anything funky */ if(LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != 0) { taskEXIT_CRITICAL(); furi_thread_yield(); @@ -153,10 +157,10 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { } static void furi_hal_flash_begin(bool erase_flag) { - // Acquire dangerous ops mutex + /* Acquire dangerous ops mutex */ furi_hal_bt_lock_core2(); - // If Core2 is running use IPC locking + /* If Core2 is running use IPC locking */ if(furi_hal_bt_is_alive()) { furi_hal_flash_begin_with_core2(erase_flag); } else { @@ -165,36 +169,36 @@ static void furi_hal_flash_begin(bool erase_flag) { } static void furi_hal_flash_end_with_core2(bool erase_flag) { - // Funky ops are ok at this point + /* Funky ops are ok at this point */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0); - // Task switching is ok + /* Task switching is ok */ taskEXIT_CRITICAL(); - // Doesn't make much sense, does it? + /* Doesn't make much sense, does it? */ while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) { furi_thread_yield(); } - // Erase activity over, core2 can continue + /* Erase activity over, core2 can continue */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); - // Lock flash controller + /* Lock flash controller */ furi_hal_flash_lock(); - // Release flash controller ownership + /* Release flash controller ownership */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0); } static void furi_hal_flash_end(bool erase_flag) { - // If Core2 is running use IPC locking + /* If Core2 is running - use IPC locking */ if(furi_hal_bt_is_alive()) { furi_hal_flash_end_with_core2(erase_flag); } else { furi_hal_flash_lock(); } - // Release dangerous ops mutex + /* Release dangerous ops mutex */ furi_hal_bt_unlock_core2(); } @@ -226,9 +230,9 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { uint32_t error = 0; uint32_t countdown = 0; - // Wait for the FLASH operation to complete by polling on BUSY flag to be reset. - // Even if the FLASH operation fails, the BUSY flag will be reset and an error - // flag will be set + /* Wait for the FLASH operation to complete by polling on BUSY flag to be reset. + Even if the FLASH operation fails, the BUSY flag will be reset and an error + flag will be set */ countdown = timeout; while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) { if(LL_SYSTICK_IsActiveCounterFlag()) { @@ -269,10 +273,10 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { return true; } -bool furi_hal_flash_erase(uint8_t page) { +void furi_hal_flash_erase(uint8_t page) { furi_hal_flash_begin(true); - // Ensure that controller state is valid + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); /* Verify that next operation can be proceed */ @@ -292,30 +296,31 @@ bool furi_hal_flash_erase(uint8_t page) { furi_hal_flush_cache(); furi_hal_flash_end(true); - - return true; } -static inline bool furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) { +static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) { /* Program first word */ *(uint32_t*)address = (uint32_t)*data; - // Barrier to ensure programming is performed in 2 steps, in right order - // (independently of compiler optimization behavior) + /* Barrier to ensure programming is performed in 2 steps, in right order + (independently of compiler optimization behavior) */ __ISB(); /* Program second word */ *(uint32_t*)(address + 4U) = (uint32_t)(*data >> 32U); +} + +static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) { + furi_hal_flash_write_dword_internal_nowait(address, data); /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); - return true; } -bool furi_hal_flash_write_dword(size_t address, uint64_t data) { +void furi_hal_flash_write_dword(size_t address, uint64_t data) { furi_hal_flash_begin(false); - // Ensure that controller state is valid + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); /* Check the parameters */ @@ -326,7 +331,7 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) { SET_BIT(FLASH->CR, FLASH_CR_PG); /* Do the thing */ - furi_check(furi_hal_flash_write_dword_internal(address, &data)); + furi_hal_flash_write_dword_internal(address, &data); /* If the program operation is completed, disable the PG or FSTPG Bit */ CLEAR_BIT(FLASH->CR, FLASH_CR_PG); @@ -335,14 +340,13 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) { /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); - return true; } static size_t furi_hal_flash_get_page_address(uint8_t page) { return furi_hal_flash_get_base() + page * FURI_HAL_FLASH_PAGE_SIZE; } -bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) { +void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) { uint16_t length = _length; furi_check(length <= FURI_HAL_FLASH_PAGE_SIZE); @@ -350,37 +354,63 @@ bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 furi_hal_flash_begin(false); - // Ensure that controller state is valid + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); size_t page_start_address = furi_hal_flash_get_page_address(page); - /* Set PG bit */ + size_t length_written = 0; + + const uint16_t FAST_PROG_BLOCK_SIZE = 512; + const uint8_t DWORD_PROG_BLOCK_SIZE = 8; + + /* Write as much data as we can in fast mode */ + if(length >= FAST_PROG_BLOCK_SIZE) { + taskENTER_CRITICAL(); + /* Enable fast flash programming mode */ + SET_BIT(FLASH->CR, FLASH_CR_FSTPG); + + while(length_written < (length / FAST_PROG_BLOCK_SIZE * FAST_PROG_BLOCK_SIZE)) { + /* No context switch in the middle of the operation */ + furi_hal_flash_write_dword_internal_nowait( + page_start_address + length_written, (uint64_t*)(data + length_written)); + length_written += DWORD_PROG_BLOCK_SIZE; + + if((length_written % FAST_PROG_BLOCK_SIZE) == 0) { + /* Wait for block operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + } + } + CLEAR_BIT(FLASH->CR, FLASH_CR_FSTPG); + taskEXIT_CRITICAL(); + } + + /* Enable regular (dword) programming mode */ SET_BIT(FLASH->CR, FLASH_CR_PG); - size_t i_dwords = 0; - for(i_dwords = 0; i_dwords < (length / 8); ++i_dwords) { - /* Do the thing */ - size_t data_offset = i_dwords * 8; - furi_check(furi_hal_flash_write_dword_internal( - page_start_address + data_offset, (uint64_t*)&data[data_offset])); + if((length % FAST_PROG_BLOCK_SIZE) != 0) { + /* Write tail in regular, dword mode */ + while(length_written < (length / DWORD_PROG_BLOCK_SIZE * DWORD_PROG_BLOCK_SIZE)) { + furi_hal_flash_write_dword_internal( + page_start_address + length_written, (uint64_t*)&data[length_written]); + length_written += DWORD_PROG_BLOCK_SIZE; + } } - if((length % 8) != 0) { + + if((length % DWORD_PROG_BLOCK_SIZE) != 0) { /* there are more bytes, not fitting into dwords */ uint64_t tail_data = 0; - size_t data_offset = i_dwords * 8; - for(int32_t tail_i = 0; tail_i < (length % 8); ++tail_i) { - tail_data |= (((uint64_t)data[data_offset + tail_i]) << (tail_i * 8)); + for(int32_t tail_i = 0; tail_i < (length % DWORD_PROG_BLOCK_SIZE); ++tail_i) { + tail_data |= (((uint64_t)data[length_written + tail_i]) << (tail_i * 8)); } - furi_check( - furi_hal_flash_write_dword_internal(page_start_address + data_offset, &tail_data)); + furi_hal_flash_write_dword_internal(page_start_address + length_written, &tail_data); } - - /* If the program operation is completed, disable the PG or FSTPG Bit */ + /* Disable the PG Bit */ CLEAR_BIT(FLASH->CR, FLASH_CR_PG); furi_hal_flash_end(false); - return true; } int16_t furi_hal_flash_get_page_number(size_t address) { @@ -462,6 +492,7 @@ static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_T OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)), OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)), }; +#undef OB_REG_DEF void furi_hal_flash_ob_apply() { furi_hal_flash_ob_unlock(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/firmware/targets/f7/furi_hal/furi_hal_flash.h index 1ed4c039960..9fa8f94af9d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.h +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.h @@ -90,10 +90,8 @@ size_t furi_hal_flash_get_free_page_count(); * @warning locking operation with critical section, stalls execution * * @param page The page to erase - * - * @return true on success */ -bool furi_hal_flash_erase(uint8_t page); +void furi_hal_flash_erase(uint8_t page); /** Write double word (64 bits) * @@ -101,10 +99,8 @@ bool furi_hal_flash_erase(uint8_t page); * * @param address destination address, must be double word aligned. * @param data data to write - * - * @return true on success */ -bool furi_hal_flash_write_dword(size_t address, uint64_t data); +void furi_hal_flash_write_dword(size_t address, uint64_t data); /** Write aligned page data (up to page size) * @@ -113,10 +109,8 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data); * @param address destination address, must be page aligned. * @param data data to write * @param length data length - * - * @return true on success */ -bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length); +void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length); /** Get flash page number for address * diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 0d42d162c2a..f51d6231786 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -209,7 +209,7 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); string_clear(converted_fname); - FURI_LOG_I(TAG, "Extracting %d bytes to '%s'", header->size, header->name); + FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); File* out_file = storage_file_alloc(archive->storage); uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); diff --git a/scripts/update.py b/scripts/update.py index ee485f44d2a..52391965be8 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -229,7 +229,7 @@ def bytes2ffhex(value: bytes): @staticmethod def int2ffhex(value: int, n_hex_syms=8): if value: - n_hex_syms = math.ceil(math.ceil(math.log2(value)) / 8) * 2 + n_hex_syms = max(math.ceil(math.ceil(math.log2(value)) / 8) * 2, n_hex_syms) fmtstr = f"%0{n_hex_syms}X" hexstr = fmtstr % value return " ".join(list(Main.batch(hexstr, 2))[::-1]) From 3e3a167764132a426752cb5bed52afeaf3e93f3e Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 26 Sep 2022 16:49:18 +0300 Subject: [PATCH 11/23] [FL-2852] Update Universal Remote documentation (#1786) * Update Universal Remote documentation * Change formatting --- documentation/UniversalRemotes.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index ac98d451f4f..6ecf3b11b54 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -32,5 +32,7 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. Test the file against the actual device. Every signal must do what it's supposed to. -If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir) -and open a pull request. + +If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). +Keep the signals of the same type grouped together (e.g. an `Off` signal must follow a previous `Off` one). +When done, open a pull request containing the changed file. From 9f501034c3240c3d9506cf07270aa4b0818e5e02 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Mon, 26 Sep 2022 11:34:59 -0400 Subject: [PATCH 12/23] Power: Also ask charger if charge done (#1378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * power: Also ask charger if charge done * F7: bump API Symbols version * Lib: remove double include in bq25896.c Co-authored-by: あく --- applications/services/power/power_service/power.c | 2 +- firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_power.c | 7 +++++++ firmware/targets/furi_hal_include/furi_hal_power.h | 6 ++++++ lib/drivers/bq25896.c | 14 +++++++++++--- lib/drivers/bq25896.h | 8 ++++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 757d7718ab9..89886b0f85e 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -96,7 +96,7 @@ void power_free(Power* power) { static void power_check_charging_state(Power* power) { if(furi_hal_power_is_charging()) { - if(power->info.charge == 100) { + if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) { if(power->state != PowerStateCharged) { notification_internal_message(power->notification, &sequence_charged); power->state = PowerStateCharged; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c8e6a6cf491..c18780acbe5 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.11,, +Version,+,1.12,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1141,6 +1141,7 @@ Function,+,furi_hal_power_insomnia_enter,void, Function,+,furi_hal_power_insomnia_exit,void, Function,-,furi_hal_power_insomnia_level,uint16_t, Function,+,furi_hal_power_is_charging,_Bool, +Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 246383921fb..524fae0b1cc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -266,6 +266,13 @@ bool furi_hal_power_is_charging() { return ret; } +bool furi_hal_power_is_charging_done() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bool ret = bq25896_is_charging_done(&furi_hal_i2c_handle_power); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + void furi_hal_power_shutdown() { furi_hal_power_insomnia_enter(); diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 3ab30c42480..f8eaa5c3ab0 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -85,6 +85,12 @@ uint8_t furi_hal_power_get_bat_health_pct(); */ bool furi_hal_power_is_charging(); +/** Get charge complete status + * + * @return true if done charging and connected to charger + */ +bool furi_hal_power_is_charging_done(); + /** Switch MCU to SHUTDOWN */ void furi_hal_power_shutdown(); diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 73135d93a27..1fb9d53e71e 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -1,5 +1,4 @@ #include "bq25896.h" -#include "bq25896_reg.h" #include @@ -81,7 +80,7 @@ void bq25896_poweroff(FuriHalI2cBusHandle* handle) { handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT); } -bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { +ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, @@ -91,7 +90,16 @@ bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { BQ25896_I2C_TIMEOUT); furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0B, (uint8_t*)&bq25896_regs.r0B, BQ25896_I2C_TIMEOUT); - return bq25896_regs.r0B.CHRG_STAT != ChrgStatNo; + return bq25896_regs.r0B.CHRG_STAT; +} + +bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { + // Include precharge, fast charging, and charging termination done as "charging" + return bq25896_get_charge_status(handle) != ChrgStatNo; +} + +bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) { + return bq25896_get_charge_status(handle) == ChrgStatDone; } void bq25896_enable_charging(FuriHalI2cBusHandle* handle) { diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index 39d343c3385..c8da0a0643d 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -1,5 +1,7 @@ #pragma once +#include "bq25896_reg.h" + #include #include #include @@ -10,9 +12,15 @@ void bq25896_init(FuriHalI2cBusHandle* handle); /** Send device into shipping mode */ void bq25896_poweroff(FuriHalI2cBusHandle* handle); +/** Get charging status */ +ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle); + /** Is currently charging */ bool bq25896_is_charging(FuriHalI2cBusHandle* handle); +/** Is charging completed while connected to charger */ +bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle); + /** Enable charging */ void bq25896_enable_charging(FuriHalI2cBusHandle* handle); From f201062819d75035e4e5529e6cb33015e652f59e Mon Sep 17 00:00:00 2001 From: phreakocious Date: Mon, 26 Sep 2022 10:42:29 -0500 Subject: [PATCH 13/23] Add Hisense A/C IR signals.. (#1773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add Hisense A/C IR signals.. note that using any will toggle the power and apply the settings * re-order the entries to be grouped by function Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index b1075b2f36e..49586b555be 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -7,32 +7,68 @@ frequency: 38000 duty_cycle: 0.33 data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 # +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 +# name: Dh type: raw frequency: 38000 duty_cycle: 0.33 data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 # +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.33 data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 # +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.33 data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 # +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.33 data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 # +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 +# name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.33 data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8972 4491 592 1651 592 1655 598 532 599 535 597 542 600 541 601 544 598 1656 597 526 595 1652 591 1658 595 539 593 545 597 545 597 546 596 537 594 529 592 535 596 1653 600 534 597 541 601 539 592 552 600 533 598 525 596 530 591 538 593 539 592 1665 598 1662 591 1673 601 533 598 526 595 533 598 532 600 534 597 540 591 548 594 550 592 542 600 523 598 528 593 536 595 537 594 543 599 542 600 543 599 517 594 7937 593 531 601 526 595 535 597 537 594 542 600 541 601 543 599 1654 599 523 598 528 593 536 596 538 594 542 600 541 590 552 600 532 599 524 597 528 593 536 595 537 595 541 601 539 593 551 591 542 600 522 599 527 594 536 595 537 594 543 599 540 591 552 600 532 600 523 598 527 594 535 596 537 595 542 600 540 591 552 600 532 600 523 598 528 593 536 595 538 593 543 599 541 601 543 599 535 596 527 594 532 600 531 601 534 597 540 592 549 593 552 600 534 597 525 596 529 592 1655 598 534 597 1656 597 1661 592 1671 592 1644 599 7934 596 529 592 535 597 535 597 538 593 544 598 543 599 545 597 538 593 1650 593 535 596 534 597 536 595 540 591 547 595 547 595 536 595 526 595 529 592 536 595 535 596 539 593 546 596 547 595 538 593 528 593 531 601 529 592 541 601 536 596 545 597 548 594 540 592 532 600 526 595 535 596 1656 597 541 601 540 592 553 599 534 597 526 595 532 599 531 600 533 598 539 593 548 594 552 600 535 596 1647 596 531 590 538 593 1656 597 538 594 545 597 545 597 518 593 From 5bb7cabea6cb02ce2ad281484da70db4e83d2df1 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 27 Sep 2022 01:59:28 +1000 Subject: [PATCH 14/23] Applications loader: do not use view dispatcher queue #1788 --- applications/main/fap_loader/fap_loader_app.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 9050ddf7834..c0f60ceca98 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -15,6 +15,9 @@ typedef struct { DialogsApp* dialogs; Gui* gui; string_t fap_path; + + ViewDispatcher* view_dispatcher; + Loading* loading; } FapLoader; static bool @@ -144,12 +147,12 @@ int32_t fap_loader_app(void* p) { loader->dialogs = furi_record_open(RECORD_DIALOGS); loader->gui = furi_record_open(RECORD_GUI); - ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); - Loading* loading = loading_alloc(); + loader->view_dispatcher = view_dispatcher_alloc(); + loader->loading = loading_alloc(); - view_dispatcher_enable_queue(view_dispatcher); - view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + view_dispatcher_attach_to_gui( + loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); if(p) { string_init_set(loader->fap_path, (const char*)p); @@ -158,14 +161,14 @@ int32_t fap_loader_app(void* p) { string_init_set(loader->fap_path, EXT_PATH("apps")); while(fap_loader_select_app(loader)) { - view_dispatcher_switch_to_view(view_dispatcher, 0); + view_dispatcher_switch_to_view(loader->view_dispatcher, 0); fap_loader_run_selected_app(loader); }; } - view_dispatcher_remove_view(view_dispatcher, 0); - loading_free(loading); - view_dispatcher_free(view_dispatcher); + view_dispatcher_remove_view(loader->view_dispatcher, 0); + loading_free(loader->loading); + view_dispatcher_free(loader->view_dispatcher); string_clear(loader->fap_path); furi_record_close(RECORD_GUI); From e6e1e7fe15f14610ce83bbd25d94c06a41148102 Mon Sep 17 00:00:00 2001 From: Tom Samstag Date: Tue, 27 Sep 2022 10:00:50 -0700 Subject: [PATCH 15/23] Add formatting to DESfire data dump (#1784) Co-authored-by: gornekich --- lib/nfc/protocols/mifare_desfire.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index 1822d5c1ab6..f969cdde642 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -209,8 +209,24 @@ void mf_df_cat_file(MifareDesfireFile* file, string_t out) { uint8_t* data = file->contents; if(data) { for(int rec = 0; rec < num; rec++) { - for(int ch = 0; ch < size; ch++) { - string_cat_printf(out, "%02x", data[rec * size + ch]); + string_cat_printf(out, "record %d\n", rec); + for(int ch = 0; ch < size; ch += 4) { + string_cat_printf(out, "%03x|", ch); + for(int i = 0; i < 4; i++) { + if(ch + i < size) { + string_cat_printf(out, "%02x ", data[rec * size + ch + i]); + } else { + string_cat_printf(out, " "); + } + } + for(int i = 0; i < 4 && ch + i < size; i++) { + if(isprint(data[rec * size + ch + i])) { + string_cat_printf(out, "%c", data[rec * size + ch + i]); + } else { + string_cat_printf(out, "."); + } + } + string_cat_printf(out, "\n"); } string_cat_printf(out, " \n"); } From 12a6290e911f837d67383eb7d2e8dd1f568af396 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:11:28 +0300 Subject: [PATCH 16/23] [FL-2853] Reorganise Universal A/C library (#1792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reorganise A/C universal remote library file * Refactor infrared brute force code * Update UniversalRemotes.md Co-authored-by: あく --- .../main/infrared/infrared_brute_force.c | 49 +++++++-------- applications/main/infrared/infrared_signal.c | 63 +++++++++++++++---- applications/main/infrared/infrared_signal.h | 4 ++ assets/resources/infrared/assets/ac.ir | 54 ++++++++-------- documentation/UniversalRemotes.md | 6 +- 5 files changed, 108 insertions(+), 68 deletions(-) diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 575fa05ecd3..0edc5f7425b 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -23,30 +23,36 @@ struct InfraredBruteForce { FlipperFormat* ff; const char* db_filename; string_t current_record_name; + InfraredSignal* current_signal; InfraredBruteForceRecordDict_t records; + bool is_started; }; InfraredBruteForce* infrared_brute_force_alloc() { InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce)); brute_force->ff = NULL; brute_force->db_filename = NULL; + brute_force->current_signal = NULL; + brute_force->is_started = false; string_init(brute_force->current_record_name); InfraredBruteForceRecordDict_init(brute_force->records); return brute_force; } void infrared_brute_force_free(InfraredBruteForce* brute_force) { - furi_assert(!brute_force->ff); + furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_clear(brute_force->records); string_clear(brute_force->current_record_name); free(brute_force); } void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) { + furi_assert(!brute_force->is_started); brute_force->db_filename = db_filename; } bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { + furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); bool success = false; @@ -76,6 +82,7 @@ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count) { + furi_assert(!brute_force->is_started); bool success = false; *record_count = 0; @@ -96,50 +103,37 @@ bool infrared_brute_force_start( if(*record_count) { Storage* storage = furi_record_open(RECORD_STORAGE); brute_force->ff = flipper_format_buffered_file_alloc(storage); + brute_force->current_signal = infrared_signal_alloc(); + brute_force->is_started = true; success = flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename); - if(!success) { - flipper_format_free(brute_force->ff); - brute_force->ff = NULL; - furi_record_close(RECORD_STORAGE); - } + if(!success) infrared_brute_force_stop(brute_force); } return success; } bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { - return brute_force->ff; + return brute_force->is_started; } void infrared_brute_force_stop(InfraredBruteForce* brute_force) { - furi_assert(string_size(brute_force->current_record_name)); - furi_assert(brute_force->ff); - + furi_assert(brute_force->is_started); string_reset(brute_force->current_record_name); + infrared_signal_free(brute_force->current_signal); flipper_format_free(brute_force->ff); - furi_record_close(RECORD_STORAGE); + brute_force->current_signal = NULL; brute_force->ff = NULL; + brute_force->is_started = false; + furi_record_close(RECORD_STORAGE); } bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { - furi_assert(string_size(brute_force->current_record_name)); - furi_assert(brute_force->ff); - bool success = false; - - string_t signal_name; - string_init(signal_name); - InfraredSignal* signal = infrared_signal_alloc(); - - do { - success = infrared_signal_read(signal, brute_force->ff, signal_name); - } while(success && !string_equal_p(brute_force->current_record_name, signal_name)); - + furi_assert(brute_force->is_started); + const bool success = infrared_signal_search_and_read( + brute_force->current_signal, brute_force->ff, brute_force->current_record_name); if(success) { - infrared_signal_transmit(signal); + infrared_signal_transmit(brute_force->current_signal); } - - infrared_signal_free(signal); - string_clear(signal_name); return success; } @@ -155,5 +149,6 @@ void infrared_brute_force_add_record( } void infrared_brute_force_reset(InfraredBruteForce* brute_force) { + furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_reset(brute_force->records); } diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index b76717dd35e..f2e359c8a09 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -146,6 +146,26 @@ static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperForma return success; } +static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { + string_t tmp; + string_init(tmp); + bool success = false; + + do { + if(!flipper_format_read_string(ff, "type", tmp)) break; + if(string_equal_p(tmp, "raw")) { + success = infrared_signal_read_raw(signal, ff); + } else if(string_equal_p(tmp, "parsed")) { + success = infrared_signal_read_message(signal, ff); + } else { + FURI_LOG_E(TAG, "Unknown signal type"); + } + } while(false); + + string_clear(tmp); + return success; +} + InfraredSignal* infrared_signal_alloc() { InfraredSignal* signal = malloc(sizeof(InfraredSignal)); @@ -227,24 +247,41 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* } bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) { - string_t buf; - string_init(buf); + string_t tmp; + string_init(tmp); bool success = false; do { - if(!flipper_format_read_string(ff, "name", buf)) break; - string_set(name, buf); - if(!flipper_format_read_string(ff, "type", buf)) break; - if(!string_cmp_str(buf, "raw")) { - success = infrared_signal_read_raw(signal, ff); - } else if(!string_cmp_str(buf, "parsed")) { - success = infrared_signal_read_message(signal, ff); - } else { - FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) "); - } + if(!flipper_format_read_string(ff, "name", tmp)) break; + string_set(name, tmp); + if(!infrared_signal_read_body(signal, ff)) break; + success = true; } while(0); - string_clear(buf); + string_clear(tmp); + return success; +} + +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const string_t name) { + bool success = false; + string_t tmp; + string_init(tmp); + + do { + bool is_name_found = false; + while(flipper_format_read_string(ff, "name", tmp)) { + is_name_found = string_equal_p(name, tmp); + if(is_name_found) break; + } + if(!is_name_found) break; + if(!infrared_signal_read_body(signal, ff)) break; + success = true; + } while(false); + + string_clear(tmp); return success; } diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index 2dbaa75faab..ad2f5d57a4c 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -37,5 +37,9 @@ InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name); +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const string_t name); void infrared_signal_transmit(InfraredSignal* signal); diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 49586b555be..7866febc693 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,71 +1,73 @@ Filetype: IR library file Version: 1 # +# Model: Electrolux EACM-16 HP/N3 name: Off type: raw frequency: 38000 duty_cycle: 0.33 data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 # -name: Off -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 -# name: Dh type: raw frequency: 38000 duty_cycle: 0.33 data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 # -name: Dh +name: Cool_hi type: raw frequency: 38000 -duty_cycle: 0.330000 -data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 +duty_cycle: 0.33 +data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 # -name: Cool_hi +name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.33 -data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 +data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 # -name: Cool_hi +name: Heat_hi type: raw frequency: 38000 -duty_cycle: 0.330000 -data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 +duty_cycle: 0.33 +data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 # -name: Cool_lo +name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.33 -data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 +data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 # -name: Cool_lo +# Model: Hisense Generic +name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 # -name: Heat_hi +name: Dh type: raw frequency: 38000 -duty_cycle: 0.33 -data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 +duty_cycle: 0.330000 +data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 # -name: Heat_hi +name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 +data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 # -name: Heat_lo +name: Cool_lo type: raw frequency: 38000 -duty_cycle: 0.33 -data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 +duty_cycle: 0.330000 +data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 # name: Heat_lo type: raw diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 6ecf3b11b54..3dd82c6158d 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -33,6 +33,8 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. Test the file against the actual device. Every signal must do what it's supposed to. -If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). -Keep the signals of the same type grouped together (e.g. an `Off` signal must follow a previous `Off` one). +If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). + +The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. + When done, open a pull request containing the changed file. From 4241ad24a3e038cf2f60f83e4c6b9d1ae4bb7453 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 28 Sep 2022 19:37:24 +0300 Subject: [PATCH 17/23] [FL-2797] Signal Generator app (#1793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Signal Generator app * MCO pin initialization in app * furi_hal_pwm documentation Co-authored-by: あく --- .../plugins/signal_generator/application.fam | 12 + .../scenes/signal_gen_scene.c | 30 ++ .../scenes/signal_gen_scene.h | 29 ++ .../scenes/signal_gen_scene_config.h | 3 + .../scenes/signal_gen_scene_mco.c | 132 ++++++++ .../scenes/signal_gen_scene_pwm.c | 60 ++++ .../scenes/signal_gen_scene_start.c | 55 ++++ .../signal_generator/signal_gen_10px.png | Bin 0 -> 6082 bytes .../plugins/signal_generator/signal_gen_app.c | 93 ++++++ .../signal_generator/signal_gen_app_i.h | 46 +++ .../signal_generator/views/signal_gen_pwm.c | 301 ++++++++++++++++++ .../signal_generator/views/signal_gen_pwm.h | 21 ++ assets/icons/Interface/SmallArrowDown_4x7.png | Bin 0 -> 8340 bytes assets/icons/Interface/SmallArrowUp_4x7.png | Bin 0 -> 8552 bytes firmware/targets/f7/api_symbols.csv | 10 +- firmware/targets/f7/furi_hal/furi_hal_clock.c | 61 ++++ firmware/targets/f7/furi_hal/furi_hal_clock.h | 37 +++ firmware/targets/f7/furi_hal/furi_hal_pwm.c | 138 ++++++++ firmware/targets/f7/furi_hal/furi_hal_pwm.h | 42 +++ 19 files changed, 1069 insertions(+), 1 deletion(-) create mode 100644 applications/plugins/signal_generator/application.fam create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene.h create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_config.h create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_start.c create mode 100644 applications/plugins/signal_generator/signal_gen_10px.png create mode 100644 applications/plugins/signal_generator/signal_gen_app.c create mode 100644 applications/plugins/signal_generator/signal_gen_app_i.h create mode 100644 applications/plugins/signal_generator/views/signal_gen_pwm.c create mode 100644 applications/plugins/signal_generator/views/signal_gen_pwm.h create mode 100644 assets/icons/Interface/SmallArrowDown_4x7.png create mode 100644 assets/icons/Interface/SmallArrowUp_4x7.png create mode 100644 firmware/targets/f7/furi_hal/furi_hal_pwm.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_pwm.h diff --git a/applications/plugins/signal_generator/application.fam b/applications/plugins/signal_generator/application.fam new file mode 100644 index 00000000000..7794ee492c6 --- /dev/null +++ b/applications/plugins/signal_generator/application.fam @@ -0,0 +1,12 @@ +App( + appid="signal_generator", + name="Signal Generator", + apptype=FlipperAppType.PLUGIN, + entry_point="signal_gen_app", + cdefines=["APP_SIGNAL_GEN"], + requires=["gui"], + stack_size=1 * 1024, + order=50, + fap_icon="signal_gen_10px.png", + fap_category="Tools", +) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.c b/applications/plugins/signal_generator/scenes/signal_gen_scene.c new file mode 100644 index 00000000000..29b11ee3f47 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.c @@ -0,0 +1,30 @@ +#include "../signal_gen_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const signal_gen_scene_on_enter_handlers[])(void*) = { +#include "signal_gen_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const signal_gen_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "signal_gen_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const signal_gen_scene_on_exit_handlers[])(void* context) = { +#include "signal_gen_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers signal_gen_scene_handlers = { + .on_enter_handlers = signal_gen_scene_on_enter_handlers, + .on_event_handlers = signal_gen_scene_on_event_handlers, + .on_exit_handlers = signal_gen_scene_on_exit_handlers, + .scene_num = SignalGenSceneNum, +}; diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.h b/applications/plugins/signal_generator/scenes/signal_gen_scene.h new file mode 100644 index 00000000000..c139afa3bf4 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SignalGenScene##id, +typedef enum { +#include "signal_gen_scene_config.h" + SignalGenSceneNum, +} SignalGenScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers signal_gen_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "signal_gen_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "signal_gen_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "signal_gen_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h new file mode 100644 index 00000000000..b6c75025669 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(signal_gen, start, Start) +ADD_SCENE(signal_gen, pwm, Pwm) +ADD_SCENE(signal_gen, mco, Mco) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c new file mode 100644 index 00000000000..632b08c75e4 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c @@ -0,0 +1,132 @@ +#include "../signal_gen_app_i.h" + +typedef enum { + LineIndexSource, + LineIndexDivision, +} LineIndex; + +static const char* const mco_source_names[] = { + "32768", + "64MHz", + "~100K", + "~200K", + "~400K", + "~800K", + "~1MHz", + "~2MHz", + "~4MHz", + "~8MHz", + "~16MHz", + "~24MHz", + "~32MHz", + "~48MHz", +}; + +static const FuriHalClockMcoSourceId mco_sources[] = { + FuriHalClockMcoLse, + FuriHalClockMcoSysclk, + FuriHalClockMcoMsi100k, + FuriHalClockMcoMsi200k, + FuriHalClockMcoMsi400k, + FuriHalClockMcoMsi800k, + FuriHalClockMcoMsi1m, + FuriHalClockMcoMsi2m, + FuriHalClockMcoMsi4m, + FuriHalClockMcoMsi8m, + FuriHalClockMcoMsi16m, + FuriHalClockMcoMsi24m, + FuriHalClockMcoMsi32m, + FuriHalClockMcoMsi48m, +}; + +static const char* const mco_divisor_names[] = { + "1", + "2", + "4", + "8", + "16", +}; + +static const FuriHalClockMcoDivisorId mco_divisors[] = { + FuriHalClockMcoDiv1, + FuriHalClockMcoDiv2, + FuriHalClockMcoDiv4, + FuriHalClockMcoDiv8, + FuriHalClockMcoDiv16, +}; + +static void mco_source_list_change_callback(VariableItem* item) { + SignalGenApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mco_source_names[index]); + + app->mco_src = mco_sources[index]; + + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); +} + +static void mco_divisor_list_change_callback(VariableItem* item) { + SignalGenApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mco_divisor_names[index]); + + app->mco_div = mco_divisors[index]; + + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); +} + +void signal_gen_scene_mco_on_enter(void* context) { + SignalGenApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + + VariableItem* item; + + item = variable_item_list_add( + var_item_list, "Source", COUNT_OF(mco_source_names), mco_source_list_change_callback, app); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, mco_source_names[0]); + + item = variable_item_list_add( + var_item_list, + "Division", + COUNT_OF(mco_divisor_names), + mco_divisor_list_change_callback, + app); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, mco_divisor_names[0]); + + variable_item_list_set_selected_item(var_item_list, LineIndexSource); + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList); + + app->mco_src = FuriHalClockMcoLse; + app->mco_div = FuriHalClockMcoDiv1; + furi_hal_clock_mco_enable(app->mco_src, app->mco_div); + furi_hal_gpio_init_ex( + &gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO); +} + +bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SignalGenMcoEventUpdate) { + consumed = true; + furi_hal_clock_mco_enable(app->mco_src, app->mco_div); + } + } + return consumed; +} + +void signal_gen_scene_mco_on_exit(void* context) { + SignalGenApp* app = context; + variable_item_list_reset(app->var_item_list); + furi_hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + furi_hal_clock_mco_disable(); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c new file mode 100644 index 00000000000..f302c023228 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c @@ -0,0 +1,60 @@ +#include "../signal_gen_app_i.h" + +static const FuriHalPwmOutputId pwm_ch_id[] = { + FuriHalPwmOutputIdTim1PA7, + FuriHalPwmOutputIdLptim2PA4, +}; + +#define DEFAULT_FREQ 1000 +#define DEFAULT_DUTY 50 + +static void + signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) { + SignalGenApp* app = context; + + app->pwm_freq = freq; + app->pwm_duty = duty; + + if(app->pwm_ch != pwm_ch_id[channel_id]) { + app->pwm_ch_prev = app->pwm_ch; + app->pwm_ch = pwm_ch_id[channel_id]; + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange); + } else { + app->pwm_ch = pwm_ch_id[channel_id]; + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate); + } +} + +void signal_gen_scene_pwm_on_enter(void* context) { + SignalGenApp* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm); + + signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app); + + signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY); + furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); +} + +bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SignalGenPwmEventUpdate) { + consumed = true; + furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } else if(event.event == SignalGenPwmEventChannelChange) { + consumed = true; + furi_hal_pwm_stop(app->pwm_ch_prev); + furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } + } + return consumed; +} + +void signal_gen_scene_pwm_on_exit(void* context) { + SignalGenApp* app = context; + variable_item_list_reset(app->var_item_list); + furi_hal_pwm_stop(app->pwm_ch); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c new file mode 100644 index 00000000000..91f6081d84b --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c @@ -0,0 +1,55 @@ +#include "../signal_gen_app_i.h" + +typedef enum { + SubmenuIndexPwm, + SubmenuIndexClockOutput, +} SubmenuIndex; + +void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) { + SignalGenApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void signal_gen_scene_start_on_enter(void* context) { + SignalGenApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, "PWM", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); + submenu_add_item( + submenu, + "Clock Output", + SubmenuIndexClockOutput, + signal_gen_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu); +} + +bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexPwm) { + scene_manager_next_scene(app->scene_manager, SignalGenScenePwm); + consumed = true; + } else if(event.event == SubmenuIndexClockOutput) { + scene_manager_next_scene(app->scene_manager, SignalGenSceneMco); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event); + } + + return consumed; +} + +void signal_gen_scene_start_on_exit(void* context) { + SignalGenApp* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/plugins/signal_generator/signal_gen_10px.png b/applications/plugins/signal_generator/signal_gen_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..9f6dcc5d0d9a9c1fbc54b7459b46c50c16c7e863 GIT binary patch literal 6082 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/signal_generator/signal_gen_app.c b/applications/plugins/signal_generator/signal_gen_app.c new file mode 100644 index 00000000000..ca065d330d1 --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app.c @@ -0,0 +1,93 @@ +#include "signal_gen_app_i.h" + +#include +#include + +static bool signal_gen_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SignalGenApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool signal_gen_app_back_event_callback(void* context) { + furi_assert(context); + SignalGenApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void signal_gen_app_tick_event_callback(void* context) { + furi_assert(context); + SignalGenApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +SignalGenApp* signal_gen_app_alloc() { + SignalGenApp* app = malloc(sizeof(SignalGenApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&signal_gen_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, signal_gen_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, signal_gen_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, signal_gen_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SignalGenViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SignalGenViewSubmenu, submenu_get_view(app->submenu)); + + app->pwm_view = signal_gen_pwm_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SignalGenViewPwm, signal_gen_pwm_get_view(app->pwm_view)); + + scene_manager_next_scene(app->scene_manager, SignalGenSceneStart); + + return app; +} + +void signal_gen_app_free(SignalGenApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewPwm); + + submenu_free(app->submenu); + variable_item_list_free(app->var_item_list); + signal_gen_pwm_free(app->pwm_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t signal_gen_app(void* p) { + UNUSED(p); + SignalGenApp* signal_gen_app = signal_gen_app_alloc(); + + view_dispatcher_run(signal_gen_app->view_dispatcher); + + signal_gen_app_free(signal_gen_app); + + return 0; +} diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/plugins/signal_generator/signal_gen_app_i.h new file mode 100644 index 00000000000..47c266475b1 --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app_i.h @@ -0,0 +1,46 @@ +#pragma once + +#include "scenes/signal_gen_scene.h" + +#include "furi_hal_clock.h" +#include "furi_hal_pwm.h" + +#include +#include +#include +#include +#include +#include +#include "views/signal_gen_pwm.h" + +typedef struct SignalGenApp SignalGenApp; + +struct SignalGenApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + VariableItemList* var_item_list; + Submenu* submenu; + SignalGenPwm* pwm_view; + + FuriHalClockMcoSourceId mco_src; + FuriHalClockMcoDivisorId mco_div; + + FuriHalPwmOutputId pwm_ch_prev; + FuriHalPwmOutputId pwm_ch; + uint32_t pwm_freq; + uint8_t pwm_duty; +}; + +typedef enum { + SignalGenViewVarItemList, + SignalGenViewSubmenu, + SignalGenViewPwm, +} SignalGenAppView; + +typedef enum { + SignalGenMcoEventUpdate, + SignalGenPwmEventUpdate, + SignalGenPwmEventChannelChange, +} SignalGenCustomEvent; diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c new file mode 100644 index 00000000000..00b4ef2674b --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -0,0 +1,301 @@ +#include "../signal_gen_app_i.h" +#include "furi_hal.h" +#include + +typedef enum { + LineIndexChannel, + LineIndexFrequency, + LineIndexDuty, + LineIndexTotalCount +} LineIndex; + +static const char* const pwm_ch_names[] = {"TIM1(2)", "LPTIM2(4)"}; + +struct SignalGenPwm { + View* view; + SignalGenPwmViewCallback callback; + void* context; +}; + +typedef struct { + LineIndex line_sel; + bool edit_mode; + uint8_t edit_digit; + + uint8_t channel_id; + uint32_t freq; + uint8_t duty; + +} SignalGenPwmViewModel; + +#define ITEM_H 64 / 3 +#define ITEM_W 128 + +#define VALUE_X 95 +#define VALUE_W 55 + +#define FREQ_VALUE_X 62 +#define FREQ_MAX 1000000UL +#define FREQ_DIGITS_NB 7 + +static void pwm_set_config(SignalGenPwm* pwm) { + FuriHalPwmOutputId channel; + uint32_t freq; + uint8_t duty; + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + channel = model->channel_id; + freq = model->freq; + duty = model->duty; + return false; + }); + + furi_assert(pwm->callback); + pwm->callback(channel, freq, duty, pwm->context); +} + +static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) { + if(event->key == InputKeyLeft) { + if(model->channel_id > 0) { + model->channel_id--; + } + } else if(event->key == InputKeyRight) { + if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) { + model->channel_id++; + } + } +} + +static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) { + if(event->key == InputKeyLeft) { + if(model->duty > 0) { + model->duty--; + } + } else if(event->key == InputKeyRight) { + if(model->duty < 100) { + model->duty++; + } + } +} + +static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) { + bool consumed = false; + if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + if(event->key == InputKeyRight) { + if(model->edit_digit > 0) { + model->edit_digit--; + } + consumed = true; + } else if(event->key == InputKeyLeft) { + if(model->edit_digit < (FREQ_DIGITS_NB - 1)) { + model->edit_digit++; + } + consumed = true; + } else if(event->key == InputKeyUp) { + uint32_t step = 1; + for(uint8_t i = 0; i < model->edit_digit; i++) { + step *= 10; + } + if((model->freq + step) < FREQ_MAX) { + model->freq += step; + } else { + model->freq = FREQ_MAX; + } + consumed = true; + } else if(event->key == InputKeyDown) { + uint32_t step = 1; + for(uint8_t i = 0; i < model->edit_digit; i++) { + step *= 10; + } + if(model->freq > (step + 1)) { + model->freq -= step; + } else { + model->freq = 1; + } + consumed = true; + } + } + return consumed; +} + +static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { + SignalGenPwmViewModel* model = _model; + char* line_label = NULL; + char val_text[16]; + + for(uint8_t line = 0; line < LineIndexTotalCount; line++) { + if(line == LineIndexChannel) { + line_label = "PWM Channel"; + } else if(line == LineIndexFrequency) { + line_label = "Frequency"; + } else if(line == LineIndexDuty) { + line_label = "Duty Cycle"; + } + + canvas_set_color(canvas, ColorBlack); + if(line == model->line_sel) { + elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1); + canvas_set_color(canvas, ColorWhite); + } + + uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2; + + canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label); + + if(line == LineIndexChannel) { + snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]); + canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); + if(model->channel_id != 0) { + canvas_draw_str_aligned( + canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); + } + if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) { + canvas_draw_str_aligned( + canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); + } + } else if(line == LineIndexFrequency) { + snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq); + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str_aligned( + canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text); + canvas_set_font(canvas, FontSecondary); + + if(model->edit_mode) { + uint8_t icon_x = (FREQ_VALUE_X - 1) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; + canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_4x7); + canvas_draw_icon(canvas, icon_x, text_y + 4, &I_SmallArrowDown_4x7); + } + } else if(line == LineIndexDuty) { + snprintf(val_text, sizeof(val_text), "%d%%", model->duty); + canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); + if(model->duty != 0) { + canvas_draw_str_aligned( + canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); + } + if(model->duty != 100) { + canvas_draw_str_aligned( + canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); + } + } + } +} + +static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { + furi_assert(context); + SignalGenPwm* pwm = context; + bool consumed = false; + bool need_update = false; + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + if(model->edit_mode == false) { + if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + if(event->key == InputKeyUp) { + if(model->line_sel == 0) { + model->line_sel = LineIndexTotalCount - 1; + } else { + model->line_sel = + CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0); + } + consumed = true; + } else if(event->key == InputKeyDown) { + if(model->line_sel == LineIndexTotalCount - 1) { + model->line_sel = 0; + } else { + model->line_sel = + CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0); + } + consumed = true; + } else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) { + if(model->line_sel == LineIndexChannel) { + pwm_channel_change(model, event); + need_update = true; + } else if(model->line_sel == LineIndexDuty) { + pwm_duty_change(model, event); + need_update = true; + } else if(model->line_sel == LineIndexFrequency) { + model->edit_mode = true; + } + consumed = true; + } else if(event->key == InputKeyOk) { + if(model->line_sel == LineIndexFrequency) { + model->edit_mode = true; + } + consumed = true; + } + } + } else { + if((event->key == InputKeyOk) || (event->key == InputKeyBack)) { + if(event->type == InputTypeShort) { + model->edit_mode = false; + consumed = true; + } + } else { + if(model->line_sel == LineIndexFrequency) { + consumed = pwm_freq_edit(model, event); + need_update = consumed; + } + } + } + return true; + }); + + if(need_update) { + pwm_set_config(pwm); + } + + return consumed; +} + +SignalGenPwm* signal_gen_pwm_alloc() { + SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm)); + + pwm->view = view_alloc(); + view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel)); + view_set_context(pwm->view, pwm); + view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback); + view_set_input_callback(pwm->view, signal_gen_pwm_input_callback); + + return pwm; +} + +void signal_gen_pwm_free(SignalGenPwm* pwm) { + furi_assert(pwm); + view_free(pwm->view); + free(pwm); +} + +View* signal_gen_pwm_get_view(SignalGenPwm* pwm) { + furi_assert(pwm); + return pwm->view; +} + +void signal_gen_pwm_set_callback( + SignalGenPwm* pwm, + SignalGenPwmViewCallback callback, + void* context) { + furi_assert(pwm); + furi_assert(callback); + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + UNUSED(model); + pwm->callback = callback; + pwm->context = context; + return false; + }); +} + +void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) { + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + model->channel_id = channel_id; + model->freq = freq; + model->duty = duty; + return true; + }); + + furi_assert(pwm->callback); + pwm->callback(channel_id, freq, duty, pwm->context); +} diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.h b/applications/plugins/signal_generator/views/signal_gen_pwm.h new file mode 100644 index 00000000000..986794e7a28 --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../signal_gen_app_i.h" + +typedef struct SignalGenPwm SignalGenPwm; +typedef void ( + *SignalGenPwmViewCallback)(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context); + +SignalGenPwm* signal_gen_pwm_alloc(); + +void signal_gen_pwm_free(SignalGenPwm* pwm); + +View* signal_gen_pwm_get_view(SignalGenPwm* pwm); + +void signal_gen_pwm_set_callback( + SignalGenPwm* pwm, + SignalGenPwmViewCallback callback, + void* context); + +void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty); diff --git a/assets/icons/Interface/SmallArrowDown_4x7.png b/assets/icons/Interface/SmallArrowDown_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..5c5252b167d2f9f9a1ce5e7b9f9c99123879c1b4 GIT binary patch literal 8340 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 literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c18780acbe5..18550984d2b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.12,, +Version,+,1.13,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -42,6 +42,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, @@ -954,6 +955,8 @@ Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" Function,+,furi_hal_clock_deinit_early,void, Function,-,furi_hal_clock_init,void, Function,-,furi_hal_clock_init_early,void, +Function,+,furi_hal_clock_mco_disable,void, +Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, @@ -1150,6 +1153,9 @@ Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, Function,+,furi_hal_power_suppress_charge_enter,void, Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" Function,+,furi_hal_random_get,uint32_t, Function,+,furi_hal_region_get,const FuriHalRegion*, @@ -2665,6 +2671,8 @@ Variable,+,I_SDQuestion_35x43,const Icon, Variable,+,I_SDcardFail_11x8,const Icon, Variable,+,I_SDcardMounted_11x8,const Icon, Variable,+,I_Scanning_123x52,const Icon, +Variable,+,I_SmallArrowDown_4x7,const Icon, +Variable,+,I_SmallArrowUp_4x7,const Icon, Variable,+,I_Smile_18x18,const Icon, Variable,+,I_Space_65x18,const Icon, Variable,+,I_Tap_reader_36x38,const Icon, diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index a7c9b4d031d..cf19451ec7b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -236,3 +237,63 @@ void furi_hal_clock_suspend_tick() { void furi_hal_clock_resume_tick() { SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk); } + +void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div) { + if(source == FuriHalClockMcoLse) { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_LSE, div); + } else if(source == FuriHalClockMcoSysclk) { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, div); + } else { + LL_RCC_MSI_Enable(); + while(LL_RCC_MSI_IsReady() != 1) + ; + switch(source) { + case FuriHalClockMcoMsi100k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0); + break; + case FuriHalClockMcoMsi200k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1); + break; + case FuriHalClockMcoMsi400k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_2); + break; + case FuriHalClockMcoMsi800k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_3); + break; + case FuriHalClockMcoMsi1m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4); + break; + case FuriHalClockMcoMsi2m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_5); + break; + case FuriHalClockMcoMsi4m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6); + break; + case FuriHalClockMcoMsi8m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_7); + break; + case FuriHalClockMcoMsi16m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_8); + break; + case FuriHalClockMcoMsi24m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_9); + break; + case FuriHalClockMcoMsi32m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_10); + break; + case FuriHalClockMcoMsi48m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11); + break; + default: + break; + } + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_MSI, div); + } +} + +void furi_hal_clock_mco_disable() { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, FuriHalClockMcoDiv1); + LL_RCC_MSI_Disable(); + while(LL_RCC_MSI_IsReady() != 0) + ; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/firmware/targets/f7/furi_hal/furi_hal_clock.h index 6cebb20c626..5e651bbd359 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.h +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.h @@ -4,6 +4,33 @@ extern "C" { #endif +#include + +typedef enum { + FuriHalClockMcoLse, + FuriHalClockMcoSysclk, + FuriHalClockMcoMsi100k, + FuriHalClockMcoMsi200k, + FuriHalClockMcoMsi400k, + FuriHalClockMcoMsi800k, + FuriHalClockMcoMsi1m, + FuriHalClockMcoMsi2m, + FuriHalClockMcoMsi4m, + FuriHalClockMcoMsi8m, + FuriHalClockMcoMsi16m, + FuriHalClockMcoMsi24m, + FuriHalClockMcoMsi32m, + FuriHalClockMcoMsi48m, +} FuriHalClockMcoSourceId; + +typedef enum { + FuriHalClockMcoDiv1 = LL_RCC_MCO1_DIV_1, + FuriHalClockMcoDiv2 = LL_RCC_MCO1_DIV_2, + FuriHalClockMcoDiv4 = LL_RCC_MCO1_DIV_4, + FuriHalClockMcoDiv8 = LL_RCC_MCO1_DIV_8, + FuriHalClockMcoDiv16 = LL_RCC_MCO1_DIV_16, +} FuriHalClockMcoDivisorId; + /** Early initialization */ void furi_hal_clock_init_early(); @@ -25,6 +52,16 @@ void furi_hal_clock_suspend_tick(); /** Continue SysTick counter operation */ void furi_hal_clock_resume_tick(); +/** Enable clock output on MCO pin + * + * @param source MCO clock source + * @param div MCO clock division +*/ +void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div); + +/** Disable clock output on MCO pin */ +void furi_hal_clock_mco_disable(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c new file mode 100644 index 00000000000..e484808d5ab --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -0,0 +1,138 @@ +#include "furi_hal_pwm.h" +#include +#include + +#include +#include +#include +#include + +#include + +const uint32_t lptim_psc_table[] = { + LL_LPTIM_PRESCALER_DIV1, + LL_LPTIM_PRESCALER_DIV2, + LL_LPTIM_PRESCALER_DIV4, + LL_LPTIM_PRESCALER_DIV8, + LL_LPTIM_PRESCALER_DIV16, + LL_LPTIM_PRESCALER_DIV32, + LL_LPTIM_PRESCALER_DIV64, + LL_LPTIM_PRESCALER_DIV128, +}; + +void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { + if(channel == FuriHalPwmOutputIdTim1PA7) { + furi_hal_gpio_init_ex( + &gpio_ext_pa7, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn1TIM1); + + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(TIM1); + FURI_CRITICAL_EXIT(); + + LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetRepetitionCounter(TIM1, 0); + LL_TIM_SetClockDivision(TIM1, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_EnableARRPreload(TIM1); + + LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); + LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N); + + LL_TIM_EnableAllOutputs(TIM1); + + furi_hal_pwm_set_params(channel, freq, duty); + + LL_TIM_EnableCounter(TIM1); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + furi_hal_gpio_init_ex( + &gpio_ext_pa4, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn14LPTIM2); + + FURI_CRITICAL_ENTER(); + LL_LPTIM_DeInit(LPTIM2); + FURI_CRITICAL_EXIT(); + + LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_ENDOFPERIOD); + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); + LL_LPTIM_SetClockSource(LPTIM2, LL_LPTIM_CLK_SOURCE_INTERNAL); + LL_LPTIM_ConfigOutput( + LPTIM2, LL_LPTIM_OUTPUT_WAVEFORM_PWM, LL_LPTIM_OUTPUT_POLARITY_INVERSE); + LL_LPTIM_SetCounterMode(LPTIM2, LL_LPTIM_COUNTER_MODE_INTERNAL); + + LL_LPTIM_Enable(LPTIM2); + + furi_hal_pwm_set_params(channel, freq, duty); + + LL_LPTIM_StartCounter(LPTIM2, LL_LPTIM_OPERATING_MODE_CONTINUOUS); + } +} + +void furi_hal_pwm_stop(FuriHalPwmOutputId channel) { + if(channel == FuriHalPwmOutputIdTim1PA7) { + furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(TIM1); + FURI_CRITICAL_EXIT(); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog); + FURI_CRITICAL_ENTER(); + LL_LPTIM_DeInit(LPTIM2); + FURI_CRITICAL_EXIT(); + } +} + +void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { + furi_assert(freq > 0); + uint32_t freq_div = 64000000LU / freq; + + if(channel == FuriHalPwmOutputIdTim1PA7) { + uint32_t prescaler = freq_div / 0x10000LU; + uint32_t period = freq_div / (prescaler + 1); + uint32_t compare = period * duty / 100; + + LL_TIM_SetPrescaler(TIM1, prescaler); + LL_TIM_SetAutoReload(TIM1, period - 1); + LL_TIM_OC_SetCompareCH1(TIM1, compare); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + uint32_t prescaler = 0; + uint32_t period = 0; + + bool clock_lse = false; + + do { + period = freq_div / (1 << prescaler); + if(period <= 0xFFFF) { + break; + } + prescaler++; + if(prescaler > 7) { + prescaler = 0; + clock_lse = true; + period = 32768LU / freq; + break; + } + } while(1); + + uint32_t compare = period * duty / 100; + + LL_LPTIM_SetPrescaler(LPTIM2, lptim_psc_table[prescaler]); + LL_LPTIM_SetAutoReload(LPTIM2, period); + LL_LPTIM_SetCompare(LPTIM2, compare); + + if(clock_lse) { + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_LSE); + } else { + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); + } + } +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/firmware/targets/f7/furi_hal/furi_hal_pwm.h new file mode 100644 index 00000000000..a8682c5fbb1 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.h @@ -0,0 +1,42 @@ +/** + * @file furi_hal_pwm.h + * PWM contol HAL + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + FuriHalPwmOutputIdTim1PA7, + FuriHalPwmOutputIdLptim2PA4, +} FuriHalPwmOutputId; + +/** Enable PWM channel and set parameters + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) + * @param[in] freq Frequency in Hz + * @param[in] duty Duty cycle value in % +*/ +void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); + +/** Disable PWM channel + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) +*/ +void furi_hal_pwm_stop(FuriHalPwmOutputId channel); + +/** Set PWM channel parameters + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) + * @param[in] freq Frequency in Hz + * @param[in] duty Duty cycle value in % +*/ +void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); + +#ifdef __cplusplus +} +#endif From e25b4241881cabeb6dff957303179cc0ba254cf6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Wed, 28 Sep 2022 21:52:46 +0500 Subject: [PATCH 18/23] Typos fix in some strings/comments #1794 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/canvas.h | 6 +++--- applications/services/gui/modules/button_panel.h | 2 +- documentation/AppManifests.md | 2 +- scripts/assets.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 4923e2e6618..49bbd7d6875 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -61,7 +61,7 @@ typedef struct { uint8_t descender; } CanvasFontParameters; -/** Canvas anonymouse structure */ +/** Canvas anonymous structure */ typedef struct Canvas Canvas; /** Get Canvas width @@ -297,7 +297,7 @@ void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r); * @param y y coordinate of base and height intersection * @param base length of triangle side * @param height length of triangle height - * @param dir CanvasDirection triangle orientaion + * @param dir CanvasDirection triangle orientation */ void canvas_draw_triangle( Canvas* canvas, @@ -323,7 +323,7 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch); */ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha); -/** Draw rounded-corner frame of width, height at x,y, with round value raduis +/** Draw rounded-corner frame of width, height at x,y, with round value radius * * @param canvas Canvas instance * @param x x coordinate diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index 0c17e3a7c6a..f3b0bae703c 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -53,7 +53,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re * @param button_panel ButtonPanel instance * @param index value to pass to callback * @param matrix_place_x coordinates by x-axis on virtual grid, it - * is only used for naviagation + * is only used for navigation * @param matrix_place_y coordinates by y-axis on virtual grid, it * is only used for naviagation * @param x x-coordinate to draw icon on diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 7bc8d0a477b..5b14b7ddb67 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -48,7 +48,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. -* **fap_description**: string, may be empty. Short application descriotion. +* **fap_description**: string, may be empty. Short application description. * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. diff --git a/scripts/assets.py b/scripts/assets.py index 1f36a135b74..6f94101377f 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -111,7 +111,7 @@ def icons(self): if not filenames: continue if "frame_rate" in filenames: - self.logger.debug(f"Folder contatins animation") + self.logger.debug(f"Folder contains animation") icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_") width = height = None frame_count = 0 From f8b532f06317298566b730a4c6bc17dd6ee35f4e Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 28 Sep 2022 21:13:12 +0400 Subject: [PATCH 19/23] [FL-2831] Resources cleanup in updater (#1796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updater: remove files from existing resources Manifest file before deploying new resources * toolbox: tar: single file extraction API Co-authored-by: あく --- .../system/updater/util/update_task.h | 2 +- .../updater/util/update_task_worker_backup.c | 41 ++++++- .../updater/util/update_task_worker_flasher.c | 2 +- firmware/targets/f7/api_symbols.csv | 1 + .../targets/f7/furi_hal/furi_hal_cortex.c | 3 + lib/toolbox/tar/tar_archive.c | 98 ++++++++------- lib/toolbox/tar/tar_archive.h | 5 + lib/update_util/resources/manifest.c | 115 ++++++++++++++++++ lib/update_util/resources/manifest.h | 58 +++++++++ 9 files changed, 280 insertions(+), 45 deletions(-) create mode 100644 lib/update_util/resources/manifest.c create mode 100644 lib/update_util/resources/manifest.h diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index 1f291556852..ad8c50857d1 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -10,7 +10,7 @@ extern "C" { #include #include -#define UPDATE_DELAY_OPERATION_OK 300 +#define UPDATE_DELAY_OPERATION_OK 10 #define UPDATE_DELAY_OPERATION_ERROR INT_MAX typedef enum { diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 09e4595333c..046be372dc5 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -50,10 +51,46 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory, update_task_set_progress( unpack_progress->update_task, UpdateTaskStageProgress, - unpack_progress->processed_files * 100 / (unpack_progress->total_files + 1)); + /* For this stage, last 70% of progress = extraction */ + 30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1)); return true; } +static void + update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) { + ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage); + do { + FURI_LOG_I(TAG, "Cleaning up old manifest"); + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) { + FURI_LOG_W(TAG, "No existing manifest"); + break; + } + + /* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */ + n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1; + uint32_t n_processed_files = 0; + + ResourceManifestEntry* entry_ptr = NULL; + while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { + if(entry_ptr->type == ResourceManifestEntryTypeFile) { + update_task_set_progress( + update_task, + UpdateTaskStageProgress, + /* For this stage, first 30% of progress = cleanup */ + (n_processed_files++ * 30) / (n_approx_file_entries + 1)); + + string_t file_path; + string_init(file_path); + path_concat(STORAGE_EXT_PATH_PREFIX, string_get_cstr(entry_ptr->name), file_path); + FURI_LOG_D(TAG, "Removing %s", string_get_cstr(file_path)); + storage_simply_remove(update_task->storage, string_get_cstr(file_path)); + string_clear(file_path); + } + } + } while(false); + resource_manifest_reader_free(manifest_reader); +} + static bool update_task_post_update(UpdateTask* update_task) { bool success = false; @@ -88,6 +125,8 @@ static bool update_task_post_update(UpdateTask* update_task) { progress.total_files = tar_archive_get_entries_count(archive); if(progress.total_files > 0) { + update_task_cleanup_resources(update_task, progress.total_files); + CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL)); } } diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 7b598c50b10..b235d0018f8 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -308,7 +308,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { } } } else { - FURI_LOG_I( + FURI_LOG_D( TAG, "OB MATCH: #%d: real %08X == %08X (exp.)", idx, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 18550984d2b..2cbdae77c13 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2269,6 +2269,7 @@ Function,+,tar_archive_get_entries_count,int32_t,TarArchive* Function,+,tar_archive_open,_Bool,"TarArchive*, const char*, TarOpenMode" Function,+,tar_archive_set_file_callback,void,"TarArchive*, tar_unpack_file_cb, void*" Function,+,tar_archive_store_data,_Bool,"TarArchive*, const char*, const uint8_t*, const int32_t" +Function,+,tar_archive_unpack_file,_Bool,"TarArchive*, const char*, const char*" Function,+,tar_archive_unpack_to,_Bool,"TarArchive*, const char*, Storage_name_converter" Function,-,tempnam,char*,"const char*, const char*" Function,+,text_box_alloc,TextBox*, diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 2b4ea6e99b2..c9c8400a748 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -6,6 +6,9 @@ void furi_hal_cortex_init_early() { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0U; + + /* Enable instruction prefetch */ + SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN); } void furi_hal_cortex_delay_us(uint32_t microseconds) { diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index f51d6231786..1a542b2e0da 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -168,7 +168,44 @@ typedef struct { Storage_name_converter converter; } TarArchiveDirectoryOpParams; +static bool archive_extract_current_file(TarArchive* archive, const char* dst_path) { + mtar_t* tar = &archive->tar; + File* out_file = storage_file_alloc(archive->storage); + uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); + + bool success = true; + uint8_t n_tries = FILE_OPEN_NTRIES; + do { + while(n_tries-- > 0) { + if(storage_file_open(out_file, dst_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + break; + } + FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", dst_path, n_tries); + storage_file_close(out_file); + furi_delay_ms(FILE_OPEN_RETRY_DELAY); + } + + if(!storage_file_is_open(out_file)) { + success = false; + break; + } + + while(!mtar_eof_data(tar)) { + int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE); + if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) { + success = false; + break; + } + } + } while(false); + storage_file_free(out_file); + free(readbuf); + + return success; +} + static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, void* param) { + UNUSED(tar); TarArchiveDirectoryOpParams* op_params = param; TarArchive* archive = op_params->archive; @@ -199,58 +236,22 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, return 0; } - string_init(full_extracted_fname); + FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); string_t converted_fname; string_init_set(converted_fname, header->name); if(op_params->converter) { op_params->converter(converted_fname); } - path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); - string_clear(converted_fname); - FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); - File* out_file = storage_file_alloc(archive->storage); - uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); - - bool failed = false; - uint8_t n_tries = FILE_OPEN_NTRIES; - do { - while(n_tries-- > 0) { - if(storage_file_open( - out_file, - string_get_cstr(full_extracted_fname), - FSAM_WRITE, - FSOM_CREATE_ALWAYS)) { - break; - } - FURI_LOG_W( - TAG, - "Failed to open '%s', reties: %d", - string_get_cstr(full_extracted_fname), - n_tries); - storage_file_close(out_file); - furi_delay_ms(FILE_OPEN_RETRY_DELAY); - } - - if(!storage_file_is_open(out_file)) { - failed = true; - break; - } + string_init(full_extracted_fname); + path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); - while(!mtar_eof_data(tar)) { - int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE); - if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) { - failed = true; - break; - } - } - } while(false); + bool success = archive_extract_current_file(archive, string_get_cstr(full_extracted_fname)); - storage_file_free(out_file); - free(readbuf); + string_clear(converted_fname); string_clear(full_extracted_fname); - return failed ? -1 : 0; + return success ? 0 : -1; } bool tar_archive_unpack_to( @@ -369,3 +370,16 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch storage_file_free(directory); return success; } + +bool tar_archive_unpack_file( + TarArchive* archive, + const char* archive_fname, + const char* destination) { + furi_assert(archive); + furi_assert(archive_fname); + furi_assert(destination); + if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) { + return false; + } + return archive_extract_current_file(archive, destination); +} \ No newline at end of file diff --git a/lib/toolbox/tar/tar_archive.h b/lib/toolbox/tar/tar_archive.h index 88cb3dd4d96..ceaf82ee914 100644 --- a/lib/toolbox/tar/tar_archive.h +++ b/lib/toolbox/tar/tar_archive.h @@ -41,6 +41,11 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch int32_t tar_archive_get_entries_count(TarArchive* archive); +bool tar_archive_unpack_file( + TarArchive* archive, + const char* archive_fname, + const char* destination); + /* Optional per-entry callback on unpacking - return false to skip entry */ typedef bool (*tar_unpack_file_cb)(const char* name, bool is_directory, void* context); diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c new file mode 100644 index 00000000000..4f8a7d1abde --- /dev/null +++ b/lib/update_util/resources/manifest.c @@ -0,0 +1,115 @@ +#include "manifest.h" + +#include +#include + +struct ResourceManifestReader { + Storage* storage; + Stream* stream; + string_t linebuf; + ResourceManifestEntry entry; +}; + +ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) { + ResourceManifestReader* resource_manifest = + (ResourceManifestReader*)malloc(sizeof(ResourceManifestReader)); + resource_manifest->storage = storage; + resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage); + memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry)); + string_init(resource_manifest->entry.name); + string_init(resource_manifest->linebuf); + return resource_manifest; +} + +void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + string_clear(resource_manifest->linebuf); + string_clear(resource_manifest->entry.name); + buffered_file_stream_close(resource_manifest->stream); + stream_free(resource_manifest->stream); + free(resource_manifest); +} + +bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename) { + furi_assert(resource_manifest); + + return buffered_file_stream_open( + resource_manifest->stream, filename, FSAM_READ, FSOM_OPEN_EXISTING); +} + +/* Read entries in format of + * F::: + * D: + */ +ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + string_reset(resource_manifest->entry.name); + resource_manifest->entry.type = ResourceManifestEntryTypeUnknown; + resource_manifest->entry.size = 0; + memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash)); + + do { + if(!stream_read_line(resource_manifest->stream, resource_manifest->linebuf)) { + return NULL; + } + + /* Trim end of line */ + string_strim(resource_manifest->linebuf); + + char type_code = string_get_char(resource_manifest->linebuf, 0); + switch(type_code) { + case 'F': + resource_manifest->entry.type = ResourceManifestEntryTypeFile; + break; + case 'D': + resource_manifest->entry.type = ResourceManifestEntryTypeDirectory; + break; + default: /* Skip other entries - version, timestamp, etc */ + continue; + }; + + if(resource_manifest->entry.type == ResourceManifestEntryTypeFile) { + /* Parse file entry + F::: */ + + /* Remove entry type code */ + string_right(resource_manifest->linebuf, 2); + + if(string_search_char(resource_manifest->linebuf, ':') != + sizeof(resource_manifest->entry.hash) * 2) { + /* Invalid hash */ + continue; + } + + /* Read hash */ + hex_chars_to_uint8( + string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash); + + /* Remove hash */ + string_right( + resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); + + resource_manifest->entry.size = atoi(string_get_cstr(resource_manifest->linebuf)); + + /* Remove size */ + size_t offs = string_search_char(resource_manifest->linebuf, ':'); + string_right(resource_manifest->linebuf, offs + 1); + + string_set(resource_manifest->entry.name, resource_manifest->linebuf); + } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { + /* Parse directory entry + D: */ + + /* Remove entry type code */ + string_right(resource_manifest->linebuf, 2); + + string_set(resource_manifest->entry.name, resource_manifest->linebuf); + } + + return &resource_manifest->entry; + } while(true); + + return NULL; +} diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h new file mode 100644 index 00000000000..092b7badb04 --- /dev/null +++ b/lib/update_util/resources/manifest.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ResourceManifestEntryTypeUnknown = 0, + ResourceManifestEntryTypeDirectory, + ResourceManifestEntryTypeFile, +} ResourceManifestEntryType; + +typedef struct { + ResourceManifestEntryType type; + string_t name; + uint32_t size; + uint8_t hash[16]; +} ResourceManifestEntry; + +typedef struct ResourceManifestReader ResourceManifestReader; + +/** + * @brief Initialize resource manifest reader + * @param storage Storage API pointer + * @return allocated object + */ +ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage); + +/** + * @brief Release resource manifest reader + * @param resource_manifest allocated object + */ +void resource_manifest_reader_free(ResourceManifestReader* resource_manifest); + +/** + * @brief Initialize resource manifest reader iteration + * @param resource_manifest allocated object + * @param filename manifest file name + * @return true if file opened + */ +bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename); + +/** + * @brief Read next file/dir entry from manifest + * @param resource_manifest allocated object + * @return entry or NULL if end of file + */ +ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file From 5883e134d4c870a2b08f705eb48a2e56f8a24a23 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 29 Sep 2022 03:37:07 +1000 Subject: [PATCH 20/23] Furi Thread: don't use thread pointer after FuriThreadStateStopped callback (#1799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Furi Thread: correct furi_thread_join, do not use thread pointer after FuriThreadStateStopped callback * Furi: a little bit easier way to do harakiri * Furi: crash on thread self join attempt Co-authored-by: あく --- furi/core/thread.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/furi/core/thread.c b/furi/core/thread.c index 58cb9bc0991..538ae4d87db 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -85,7 +85,6 @@ static void furi_thread_body(void* context) { } furi_assert(thread->state == FuriThreadStateRunning); - furi_thread_set_state(thread, FuriThreadStateStopped); if(thread->is_service) { FURI_LOG_E( @@ -99,7 +98,10 @@ static void furi_thread_body(void* context) { furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); - vTaskDelete(thread->task_handle); + // from here we can't use thread pointer + furi_thread_set_state(thread, FuriThreadStateStopped); + + vTaskDelete(NULL); furi_thread_catch(); } @@ -205,7 +207,9 @@ void furi_thread_start(FuriThread* thread) { bool furi_thread_join(FuriThread* thread) { furi_assert(thread); - while(thread->state != FuriThreadStateStopped) { + furi_check(furi_thread_get_current() != thread); + + while(eTaskGetState(thread->task_handle) != eDeleted) { furi_delay_ms(10); } From bcfb12bf28ab8784732a76cb0400318de7a12585 Mon Sep 17 00:00:00 2001 From: Mewa Date: Wed, 28 Sep 2022 19:44:24 +0200 Subject: [PATCH 21/23] Keyboard: show Uppercase keys when replacing content (#1548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/modules/text_input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 58d7ecab0d0..b2aba03fc03 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -232,7 +232,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } - if(text_length == 0 && char_is_lowercase(keys[column].text)) { + if(model->clear_default_text || + (text_length == 0 && char_is_lowercase(keys[column].text))) { canvas_draw_glyph( canvas, keyboard_origin_x + keys[column].x, From aba20b6af8eb15e23ff544cf29dfa77207695aea Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 29 Sep 2022 20:42:15 +1000 Subject: [PATCH 22/23] Furi Thread: fixed furi_thread_join, check if thread has not been started (#1803) * furi thread: fixed furi_thread_join, check if thread has not been started * Furi: correct returns in furi_thread_join Co-authored-by: Aleksandr Kutuzov --- furi/core/thread.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/furi/core/thread.c b/furi/core/thread.c index 538ae4d87db..d326e389b33 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -209,11 +209,17 @@ bool furi_thread_join(FuriThread* thread) { furi_check(furi_thread_get_current() != thread); + // Check if thread was started + if(thread->task_handle == NULL) { + return false; + } + + // Wait for thread to stop while(eTaskGetState(thread->task_handle) != eDeleted) { furi_delay_ms(10); } - return FuriStatusOk; + return true; } FuriThreadId furi_thread_get_id(FuriThread* thread) { From 76d38e832e2da86457d0114beb20b88b6dc24221 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 29 Sep 2022 15:00:22 +0400 Subject: [PATCH 23/23] fbt: reproducible manifest builds & improvements (#1801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: reproducible manifest builds, less rebuild on small updates; scripts: assets: using timestamp from commandline af available * fbt: added app import validation for launch_app & single app build targets * fbt: COMSTR for app imports validation * docs: minor fixes * docs: markdown fix * vscode: comments for RTOS startup Co-authored-by: あく --- .vscode/example/launch.json | 3 ++- assets/SConscript | 6 +++++- documentation/AppManifests.md | 4 ++-- firmware.scons | 1 + scripts/assets.py | 9 ++++++++- scripts/flipper/assets/manifest.py | 4 ++-- site_scons/extapps.scons | 3 ++- site_scons/fbt/appmanifest.py | 2 +- site_scons/fbt/version.py | 5 +++++ site_scons/site_tools/fbt_extapps.py | 21 +++++++++++++++++---- 10 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index 7cb2542de3e..f4df9659275 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -28,13 +28,14 @@ "servertype": "openocd", "device": "stlink", "svdFile": "./debug/STM32WB55_CM4.svd", + // If you're debugging early in the boot process, before OS scheduler is running, + // you have to comment out the following line. "rtos": "FreeRTOS", "configFiles": [ "interface/stlink.cfg", "./debug/stm32wbx.cfg", ], "postAttachCommands": [ - // "attach 1", // "compare-sections", "source debug/flipperapps.py", // "source debug/FreeRTOS/FreeRTOS.py", diff --git a/assets/SConscript b/assets/SConscript index 47713d1a609..a0b3b13abdb 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,8 +1,11 @@ Import("env") +from fbt.version import get_git_commit_unix_timestamp + assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", + GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), ) assetsenv.ApplyLibFlags() @@ -90,10 +93,11 @@ if assetsenv["IS_BASE_FIRMWARE"]: "#/assets/resources/Manifest", assetsenv.GlobRecursive("*", "resources", exclude="Manifest"), action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}"', + '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', "${RESMANIFESTCOMSTR}", ), ) + assetsenv.Precious(resources) assetsenv.AlwaysBuild(resources) assetsenv.Clean( resources, diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 5b14b7ddb67..0a4f5e9b778 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -43,8 +43,8 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of file name masks, used for gathering sources within app folder. Default value of ["\*.c\*"] includes C and CPP source files. -* **fap_version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file. +* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and CPP source files. +* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meanig version "0.1". * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. diff --git a/firmware.scons b/firmware.scons index 530634ef261..0970541e825 100644 --- a/firmware.scons +++ b/firmware.scons @@ -96,6 +96,7 @@ if not env["VERBOSE"]: SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", APPMETA_COMSTR="\tAPPMETA\t${TARGET}", APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}", ) diff --git a/scripts/assets.py b/scripts/assets.py index 6f94101377f..9b4ee5b61f7 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -39,6 +39,13 @@ def init(self): "manifest", help="Create directory Manifest" ) self.parser_manifest.add_argument("local_path", help="local_path") + self.parser_manifest.add_argument( + "--timestamp", + help="timestamp value to embed", + default=0, + type=int, + required=False, + ) self.parser_manifest.set_defaults(func=self.manifest) self.parser_copro = self.subparsers.add_parser( @@ -213,7 +220,7 @@ def manifest(self): self.logger.info( f'Creating temporary Manifest for directory "{directory_path}"' ) - new_manifest = Manifest() + new_manifest = Manifest(self.args.timestamp) new_manifest.create(directory_path) self.logger.info(f"Comparing new manifest with existing") diff --git a/scripts/flipper/assets/manifest.py b/scripts/flipper/assets/manifest.py index 840761d729d..a8f6855a476 100644 --- a/scripts/flipper/assets/manifest.py +++ b/scripts/flipper/assets/manifest.py @@ -106,11 +106,11 @@ def toLine(self): class Manifest: - def __init__(self): + def __init__(self, timestamp_value=None): self.version = None self.records = [] self.records.append(ManifestRecordVersion(MANIFEST_VERSION)) - self.records.append(ManifestRecordTimestamp(timestamp())) + self.records.append(ManifestRecordTimestamp(timestamp_value or timestamp())) self.logger = logging.getLogger(self.__class__.__name__) def load(self, filename): diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 4cb5e35cfd4..66002915f01 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -86,12 +86,13 @@ if appenv["FORCE"]: Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps["compact"].values()) if appsrc := appenv.subst("$APPSRC"): - app_manifest, fap_file = appenv.GetExtAppFromPath(appsrc) + app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( "launch_app", '${PYTHON3} scripts/runfap.py ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', source=fap_file, FAP_CATEGORY=app_manifest.fap_category, ) + appenv.Alias("launch_app", app_validator) Return("extapps") diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py index a0ab5e7c6c4..a1132eeaba0 100644 --- a/site_scons/fbt/appmanifest.py +++ b/site_scons/fbt/appmanifest.py @@ -38,7 +38,7 @@ class FlipperApplication: sdk_headers: List[str] = field(default_factory=list) # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) - fap_version: Tuple[int] = field(default_factory=lambda: (0, 0)) + fap_version: Tuple[int] = field(default_factory=lambda: (0, 1)) fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" diff --git a/site_scons/fbt/version.py b/site_scons/fbt/version.py index 1b1c166f28b..e7fe2edaf64 100644 --- a/site_scons/fbt/version.py +++ b/site_scons/fbt/version.py @@ -3,6 +3,11 @@ from functools import cache +@cache +def get_git_commit_unix_timestamp(): + return int(subprocess.check_output(["git", "show", "-s", "--format=%ct"])) + + @cache def get_fast_git_version_id(): try: diff --git a/site_scons/site_tools/fbt_extapps.py b/site_scons/site_tools/fbt_extapps.py index 4b584530525..fec2407105d 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/site_scons/site_tools/fbt_extapps.py @@ -37,7 +37,15 @@ def BuildAppElf(env, app): APP=app, ) - env.Depends(app_elf_augmented, [env["SDK_DEFINITION"], env.Value(app)]) + manifest_vals = vars(app) + manifest_vals = { + k: v for k, v in manifest_vals.items() if k not in ("_appdir", "_apppath") + } + + env.Depends( + app_elf_augmented, + [env["SDK_DEFINITION"], env.Value(manifest_vals)], + ) if app.fap_icon: env.Depends( app_elf_augmented, @@ -47,6 +55,7 @@ def BuildAppElf(env, app): app_elf_import_validator = env.ValidateAppImports(app_elf_augmented) env.AlwaysBuild(app_elf_import_validator) + env.Alias(app_alias, app_elf_import_validator) return (app_elf_augmented, app_elf_raw, app_elf_import_validator) @@ -100,9 +109,13 @@ def GetExtAppFromPath(env, app_dir): app_elf = env["_extapps"]["compact"].get(app.appid, None) if not app_elf: - raise UserError(f"No external app found for {app.appid}") + raise UserError( + f"Application {app.appid} is not configured for building as external" + ) + + app_validator = env["_extapps"]["validators"].get(app.appid, None) - return (app, app_elf[0]) + return (app, app_elf[0], app_validator[0]) def generate(env, **kw): @@ -138,7 +151,7 @@ def generate(env, **kw): ), Action( validate_app_imports, - None, # "$APPCHECK_COMSTR", + "$APPCHECK_COMSTR", ), ], suffix=".impsyms",