Merge branch 'dev' into shutdown_idle
@@ -9,6 +9,10 @@
|
||||
"type": "command",
|
||||
"command": "shellCommand.execute",
|
||||
"args": {
|
||||
"useSingleResult": true,
|
||||
"env": {
|
||||
"PATH": "${workspaceFolder};${env:PATH}"
|
||||
},
|
||||
"command": "./fbt get_blackmagic",
|
||||
"description": "Get Blackmagic device",
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -44,6 +44,8 @@ distenv = coreenv.Clone(
|
||||
"target extended-remote ${GDBREMOTE}",
|
||||
"-ex",
|
||||
"set confirm off",
|
||||
"-ex",
|
||||
"set pagination off",
|
||||
],
|
||||
GDBOPTS_BLACKMAGIC=[
|
||||
"-ex",
|
||||
@@ -234,10 +236,19 @@ distenv.PhonyTarget(
|
||||
distenv.PhonyTarget(
|
||||
"debug_other",
|
||||
"${GDBPYCOM}",
|
||||
GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
|
||||
GDBOPTS="${GDBOPTS_BASE}",
|
||||
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
||||
GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
|
||||
)
|
||||
|
||||
distenv.PhonyTarget(
|
||||
"debug_other_blackmagic",
|
||||
"${GDBPYCOM}",
|
||||
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||
GDBREMOTE="$${BLACKMAGIC_ADDR}",
|
||||
)
|
||||
|
||||
|
||||
# Just start OpenOCD
|
||||
distenv.PhonyTarget(
|
||||
"openocd",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include <lib/nfc/protocols/nfca.h>
|
||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
@@ -170,10 +171,59 @@ MU_TEST(nfc_digital_signal_test) {
|
||||
"NFC long digital signal test failed\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(mf_classic_dict_test) {
|
||||
MfClassicDict* instance = NULL;
|
||||
uint64_t key = 0;
|
||||
string_t temp_str;
|
||||
string_init(temp_str);
|
||||
|
||||
instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest);
|
||||
mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n");
|
||||
|
||||
mu_assert(
|
||||
mf_classic_dict_get_total_keys(instance) == 0,
|
||||
"mf_classic_dict_get_total_keys == 0 assert failed\r\n");
|
||||
|
||||
string_set(temp_str, "2196FAD8115B");
|
||||
mu_assert(
|
||||
mf_classic_dict_add_key_str(instance, temp_str),
|
||||
"mf_classic_dict_add_key == true assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
mf_classic_dict_get_total_keys(instance) == 1,
|
||||
"mf_classic_dict_get_total_keys == 1 assert failed\r\n");
|
||||
|
||||
mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
mf_classic_dict_get_key_at_index_str(instance, temp_str, 0),
|
||||
"mf_classic_dict_get_key_at_index_str == true assert failed\r\n");
|
||||
mu_assert(
|
||||
string_cmp(temp_str, "2196FAD8115B") == 0,
|
||||
"string_cmp(temp_str, \"2196FAD8115B\") == 0 assert failed\r\n");
|
||||
|
||||
mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
mf_classic_dict_get_key_at_index(instance, &key, 0),
|
||||
"mf_classic_dict_get_key_at_index == true assert failed\r\n");
|
||||
mu_assert(key == 0x2196FAD8115B, "key == 0x2196FAD8115B assert failed\r\n");
|
||||
|
||||
mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
mf_classic_dict_delete_index(instance, 0),
|
||||
"mf_classic_dict_delete_index == true assert failed\r\n");
|
||||
|
||||
mf_classic_dict_free(instance);
|
||||
string_clear(temp_str);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(nfc) {
|
||||
nfc_test_alloc();
|
||||
|
||||
MU_RUN_TEST(nfc_digital_signal_test);
|
||||
MU_RUN_TEST(mf_classic_dict_test);
|
||||
|
||||
nfc_test_free();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ typedef struct {
|
||||
DialogsApp* dialogs;
|
||||
Gui* gui;
|
||||
string_t fap_path;
|
||||
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Loading* loading;
|
||||
} FapLoader;
|
||||
|
||||
static bool
|
||||
@@ -25,7 +28,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -153,3 +147,8 @@ void infrared_brute_force_add_record(
|
||||
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
|
||||
string_clear(key);
|
||||
}
|
||||
|
||||
void infrared_brute_force_reset(InfraredBruteForce* brute_force) {
|
||||
furi_assert(!brute_force->is_started);
|
||||
InfraredBruteForceRecordDict_reset(brute_force->records);
|
||||
}
|
||||
|
||||
@@ -20,3 +20,4 @@ void infrared_brute_force_add_record(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
const char* name);
|
||||
void infrared_brute_force_reset(InfraredBruteForce* brute_force);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -87,5 +87,6 @@ void infrared_scene_universal_common_on_exit(void* context) {
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel));
|
||||
infrared_brute_force_reset(infrared->brute_force);
|
||||
button_panel_reset(button_panel);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ ADD_SCENE(infrared, remote, Remote)
|
||||
ADD_SCENE(infrared, remote_list, RemoteList)
|
||||
ADD_SCENE(infrared, universal, Universal)
|
||||
ADD_SCENE(infrared, universal_tv, UniversalTV)
|
||||
ADD_SCENE(infrared, universal_ac, UniversalAC)
|
||||
ADD_SCENE(infrared, debug, Debug)
|
||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||
ADD_SCENE(infrared, rpc, Rpc)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
typedef enum {
|
||||
SubmenuIndexUniversalTV,
|
||||
SubmenuIndexUniversalAC,
|
||||
SubmenuIndexUniversalAudio,
|
||||
SubmenuIndexUniversalAirConditioner,
|
||||
} SubmenuIndex;
|
||||
|
||||
static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -21,6 +21,12 @@ void infrared_scene_universal_on_enter(void* context) {
|
||||
SubmenuIndexUniversalTV,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Air Conditioners",
|
||||
SubmenuIndexUniversalAC,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
submenu_set_selected_item(submenu, 0);
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
|
||||
@@ -35,12 +41,12 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == SubmenuIndexUniversalTV) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAC) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAudio) {
|
||||
//TODO Implement Audio universal remote
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAirConditioner) {
|
||||
//TODO Implement A/C universal remote
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "../infrared_i.h"
|
||||
|
||||
#include "common/infrared_scene_universal_common.h"
|
||||
|
||||
void infrared_scene_universal_ac_on_enter(void* context) {
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/ac.ir"));
|
||||
|
||||
button_panel_reserve(button_panel, 2, 3);
|
||||
uint32_t i = 0;
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
22,
|
||||
&I_Off_25x27,
|
||||
&I_Off_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Off");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
0,
|
||||
36,
|
||||
22,
|
||||
&I_Dehumidify_25x27,
|
||||
&I_Dehumidify_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Dh");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
59,
|
||||
&I_CoolHi_25x27,
|
||||
&I_CoolHi_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Cool_hi");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
1,
|
||||
36,
|
||||
59,
|
||||
&I_HeatHi_25x27,
|
||||
&I_HeatHi_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Heat_hi");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
91,
|
||||
&I_CoolLo_25x27,
|
||||
&I_CoolLo_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Cool_lo");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
2,
|
||||
36,
|
||||
91,
|
||||
&I_HeatLo_25x27,
|
||||
&I_HeatLo_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Heat_lo");
|
||||
|
||||
button_panel_add_label(button_panel, 6, 10, FontPrimary, "AC remote");
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
bool success = infrared_brute_force_calculate_messages(brute_force);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
|
||||
if(!success) {
|
||||
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) {
|
||||
return infrared_scene_universal_common_on_event(context, event);
|
||||
}
|
||||
|
||||
void infrared_scene_universal_ac_on_exit(void* context) {
|
||||
infrared_scene_universal_common_on_exit(context);
|
||||
}
|
||||
@@ -1,48 +1,87 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
#define NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX (100)
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_popup_callback(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) {
|
||||
Submenu* submenu = nfc->submenu;
|
||||
uint32_t index = 0;
|
||||
string_t temp_key;
|
||||
string_init(temp_key);
|
||||
|
||||
submenu_set_header(submenu, "Select key to delete:");
|
||||
while(mf_classic_dict_get_next_key_str(dict, temp_key)) {
|
||||
char* current_key = (char*)malloc(sizeof(char) * 13);
|
||||
strncpy(current_key, string_get_cstr(temp_key), 12);
|
||||
MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key);
|
||||
FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key);
|
||||
submenu_add_item(
|
||||
submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc);
|
||||
}
|
||||
string_clear(temp_key);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
|
||||
uint32_t index = 0;
|
||||
string_t temp_key;
|
||||
MfClassicUserKeys_init(nfc->mfc_key_strs);
|
||||
string_init(temp_key);
|
||||
if(dict) {
|
||||
mf_classic_dict_rewind(dict);
|
||||
while(mf_classic_dict_get_next_key_str(dict, temp_key)) {
|
||||
char* current_key = (char*)malloc(sizeof(char) * 13);
|
||||
strncpy(current_key, string_get_cstr(temp_key), 12);
|
||||
MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key);
|
||||
FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
current_key,
|
||||
index++,
|
||||
nfc_scene_mf_classic_keys_list_submenu_callback,
|
||||
nfc);
|
||||
uint32_t total_user_keys = mf_classic_dict_get_total_keys(dict);
|
||||
if(total_user_keys < NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX) {
|
||||
nfc_scene_mf_classic_keys_list_prepare(nfc, dict);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
} else {
|
||||
popup_set_header(nfc->popup, "Too many keys!", 64, 0, AlignCenter, AlignTop);
|
||||
popup_set_text(
|
||||
nfc->popup,
|
||||
"Edit user dictionary\nwith file browser",
|
||||
64,
|
||||
12,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback);
|
||||
popup_set_context(nfc->popup, nfc);
|
||||
popup_set_timeout(nfc->popup, 3000);
|
||||
popup_enable_timeout(nfc->popup);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
mf_classic_dict_free(dict);
|
||||
} else {
|
||||
popup_set_header(
|
||||
nfc->popup, "Failed to load dictionary", 64, 32, AlignCenter, AlignCenter);
|
||||
popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback);
|
||||
popup_set_context(nfc->popup, nfc);
|
||||
popup_set_timeout(nfc->popup, 3000);
|
||||
popup_enable_timeout(nfc->popup);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
submenu_set_header(submenu, "Select key to delete:");
|
||||
mf_classic_dict_free(dict);
|
||||
string_clear(temp_key);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete);
|
||||
consumed = true;
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
} else {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
@@ -57,4 +96,5 @@ void nfc_scene_mf_classic_keys_list_on_exit(void* context) {
|
||||
}
|
||||
MfClassicUserKeys_clear(nfc->mfc_key_strs);
|
||||
submenu_reset(nfc->submenu);
|
||||
popup_reset(nfc->popup);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,13 @@ enum SubmenuIndex {
|
||||
SubmenuIndexDynamic, // dynamic indexes start here
|
||||
};
|
||||
|
||||
void nfc_scene_mf_desfire_popup_callback(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
MifareDesfireApplication* nfc_scene_mf_desfire_app_get_app(Nfc* nfc) {
|
||||
uint32_t app_idx = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >>
|
||||
1;
|
||||
@@ -25,46 +32,45 @@ void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) {
|
||||
|
||||
void nfc_scene_mf_desfire_app_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc);
|
||||
if(!app) {
|
||||
popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42);
|
||||
popup_set_header(nfc->popup, "Internal Error!", 55, 12, AlignLeft, AlignBottom);
|
||||
popup_set_text(
|
||||
nfc->popup,
|
||||
"No app selected.\nThis should\nnever happen,\nplease file a bug.",
|
||||
55,
|
||||
15,
|
||||
AlignLeft,
|
||||
AlignTop);
|
||||
popup_set_header(nfc->popup, "Empty card!", 55, 12, AlignLeft, AlignBottom);
|
||||
popup_set_callback(nfc->popup, nfc_scene_mf_desfire_popup_callback);
|
||||
popup_set_context(nfc->popup, nfc);
|
||||
popup_set_timeout(nfc->popup, 3000);
|
||||
popup_enable_timeout(nfc->popup);
|
||||
popup_set_text(nfc->popup, "No application\nfound.", 55, 15, AlignLeft, AlignTop);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
FURI_LOG_E(TAG, "Bad state. No app selected?");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
text_box_set_font(nfc->text_box, TextBoxFontHex);
|
||||
submenu_add_item(
|
||||
nfc->submenu,
|
||||
"App info",
|
||||
SubmenuIndexAppInfo,
|
||||
nfc_scene_mf_desfire_app_submenu_callback,
|
||||
nfc);
|
||||
|
||||
text_box_set_font(nfc->text_box, TextBoxFontHex);
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "App info", SubmenuIndexAppInfo, nfc_scene_mf_desfire_app_submenu_callback, nfc);
|
||||
|
||||
uint16_t cap = NFC_TEXT_STORE_SIZE;
|
||||
char* buf = nfc->text_store;
|
||||
int idx = SubmenuIndexDynamic;
|
||||
for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
|
||||
int size = snprintf(buf, cap, "File %d", file->id);
|
||||
if(size < 0 || size >= cap) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated");
|
||||
break;
|
||||
uint16_t cap = NFC_TEXT_STORE_SIZE;
|
||||
char* buf = nfc->text_store;
|
||||
int idx = SubmenuIndexDynamic;
|
||||
for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
|
||||
int size = snprintf(buf, cap, "File %d", file->id);
|
||||
if(size < 0 || size >= cap) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated");
|
||||
break;
|
||||
}
|
||||
char* label = buf;
|
||||
cap -= size + 1;
|
||||
buf += size + 1;
|
||||
submenu_add_item(
|
||||
nfc->submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc);
|
||||
}
|
||||
char* label = buf;
|
||||
cap -= size + 1;
|
||||
buf += size + 1;
|
||||
submenu_add_item(submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -73,26 +79,30 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) {
|
||||
uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp);
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc);
|
||||
TextBox* text_box = nfc->text_box;
|
||||
string_reset(nfc->text_box_store);
|
||||
if(event.event == SubmenuIndexAppInfo) {
|
||||
mf_df_cat_application_info(app, nfc->text_box_store);
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
} else {
|
||||
uint16_t index = event.event - SubmenuIndexDynamic;
|
||||
MifareDesfireFile* file = app->file_head;
|
||||
for(int i = 0; file && i < index; i++) {
|
||||
file = file->next;
|
||||
MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc);
|
||||
TextBox* text_box = nfc->text_box;
|
||||
string_reset(nfc->text_box_store);
|
||||
if(event.event == SubmenuIndexAppInfo) {
|
||||
mf_df_cat_application_info(app, nfc->text_box_store);
|
||||
} else {
|
||||
uint16_t index = event.event - SubmenuIndexDynamic;
|
||||
MifareDesfireFile* file = app->file_head;
|
||||
for(int i = 0; file && i < index; i++) {
|
||||
file = file->next;
|
||||
}
|
||||
if(!file) {
|
||||
return false;
|
||||
}
|
||||
mf_df_cat_file(file, nfc->text_box_store);
|
||||
}
|
||||
if(!file) {
|
||||
return false;
|
||||
}
|
||||
mf_df_cat_file(file, nfc->text_box_store);
|
||||
text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
|
||||
consumed = true;
|
||||
}
|
||||
text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
if(state & 1) {
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
@@ -108,6 +118,7 @@ void nfc_scene_mf_desfire_app_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear views
|
||||
popup_reset(nfc->popup);
|
||||
text_box_reset(nfc->text_box);
|
||||
string_reset(nfc->text_box_store);
|
||||
submenu_reset(nfc->submenu);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,3 @@
|
||||
ADD_SCENE(signal_gen, start, Start)
|
||||
ADD_SCENE(signal_gen, pwm, Pwm)
|
||||
ADD_SCENE(signal_gen, mco, Mco)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,93 @@
|
||||
#include "signal_gen_app_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "scenes/signal_gen_scene.h"
|
||||
|
||||
#include "furi_hal_clock.h"
|
||||
#include "furi_hal_pwm.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#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;
|
||||
@@ -0,0 +1,301 @@
|
||||
#include "../signal_gen_app_i.h"
|
||||
#include "furi_hal.h"
|
||||
#include <gui/elements.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#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);
|
||||
@@ -22,15 +22,11 @@ void desktop_scene_slideshow_on_enter(void* context) {
|
||||
bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
Storage* storage = NULL;
|
||||
Power* power = NULL;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopSlideshowCompleted:
|
||||
storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_remove(storage, SLIDESHOW_FS_PATH);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
@@ -50,4 +46,8 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void desktop_scene_slideshow_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_remove(storage, SLIDESHOW_FS_PATH);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -133,6 +133,8 @@ void button_panel_reset(ButtonPanel* button_panel) {
|
||||
}
|
||||
model->reserve_x = 0;
|
||||
model->reserve_y = 0;
|
||||
model->selected_item_x = 0;
|
||||
model->selected_item_y = 0;
|
||||
LabelList_reset(model->labels);
|
||||
ButtonMatrix_reset(model->button_matrix);
|
||||
return true;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -318,15 +319,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;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include "power_i.h"
|
||||
#include "views/power_off.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/view_port.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
#define POWER_OFF_TIMEOUT 90
|
||||
|
||||
@@ -172,7 +169,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ extern "C" {
|
||||
#include <stdbool.h>
|
||||
#include <m-string.h>
|
||||
|
||||
#define UPDATE_DELAY_OPERATION_OK 300
|
||||
#define UPDATE_DELAY_OPERATION_OK 10
|
||||
#define UPDATE_DELAY_OPERATION_ERROR INT_MAX
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <update_util/dfu_file.h>
|
||||
#include <update_util/lfs_backup.h>
|
||||
#include <update_util/update_operation.h>
|
||||
#include <update_util/resources/manifest.h>
|
||||
#include <toolbox/tar/tar_archive.h>
|
||||
#include <toolbox/crc32_calc.h>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
@@ -300,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,
|
||||
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,76 @@
|
||||
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: 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: 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_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: 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_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
|
||||
#
|
||||
# Model: Hisense Generic
|
||||
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.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.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.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
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -64,7 +64,7 @@ class AppState:
|
||||
|
||||
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 @@ class AppState:
|
||||
@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(
|
||||
|
||||
@@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config {
|
||||
# assignment
|
||||
mmw 0xE0042004 0x00000020 0
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event gdb-detach {
|
||||
resume
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Universal Remotes
|
||||
## Air Conditioners
|
||||
### Recording signals
|
||||
Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote.
|
||||
The majority of A/C remotes have a small display which shows current mode, temperature and other settings.
|
||||
When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole.
|
||||
|
||||
In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, `Heat_lo`.
|
||||
Each signal (except `Off`) is recorded using the following algorithm:
|
||||
|
||||
1. Get the remote and press the **Power Button** so that the display shows that A/C is ON.
|
||||
2. Set the A/C to the corresponding mode (see table below), while leaving other parameters such as fan speed or vane on **AUTO** (if applicable).
|
||||
3. Press the **POWER** button to switch the A/C off.
|
||||
4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise.
|
||||
5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again.
|
||||
6. Save the resulting signal under the specified name.
|
||||
7. Repeat the steps 2-6 for each signal from the table below.
|
||||
|
||||
| Signal | Mode | Temperature | Note |
|
||||
| :-----: | :--------: | :---------: | ----------------------------------- |
|
||||
| Dh | Dehumidify | N/A | |
|
||||
| Cool_hi | Cooling | See note | Lowest temperature in cooling mode |
|
||||
| Cool_lo | Cooling | 23°C | |
|
||||
| Heat_hi | Heating | See note | Highest temperature in heating mode |
|
||||
| Heat_lo | Heating | 23°C | |
|
||||
|
||||
Finally, record the `Off` signal:
|
||||
1. Make sure the display shows that A/C is ON.
|
||||
2. Start learning a new signal on Flipper and point the remote towards the IR receiver.
|
||||
3. Press the **POWER** button so that the remote shows the OFF state.
|
||||
4. Save the resulting signal under the name `Off`.
|
||||
|
||||
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, 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: <Your model name>` in order to keep the library organised.
|
||||
|
||||
When done, open a pull request containing the changed file.
|
||||
@@ -49,7 +49,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option.
|
||||
- `flash` - flash attached device with OpenOCD over ST-Link
|
||||
- `flash_usb`, `flash_usb_full` - build, upload and install update package to device over USB. See details on `updater_package`, `updater_minpackage`
|
||||
- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded
|
||||
- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb
|
||||
- `debug_other`, `debug_other_blackmagic` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb
|
||||
- `updater_debug` - attach gdb with updater's .elf loaded
|
||||
- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board)
|
||||
- `openocd` - just start OpenOCD
|
||||
|
||||
@@ -76,19 +76,8 @@ to exclude the API function. */
|
||||
#define INCLUDE_xTaskGetSchedulerState 1
|
||||
#define INCLUDE_xTimerPendFunctionCall 1
|
||||
|
||||
/* CMSIS-RTOS V2 flags */
|
||||
#define configUSE_OS2_THREAD_SUSPEND_RESUME 1
|
||||
#define configUSE_OS2_THREAD_ENUMERATE 1
|
||||
#define configUSE_OS2_THREAD_FLAGS 1
|
||||
#define configUSE_OS2_TIMER 1
|
||||
#define configUSE_OS2_MUTEX 1
|
||||
|
||||
// NEVER TO BE USED, because of their hard realtime nature
|
||||
// #define configUSE_OS2_EVENTFLAGS_FROM_ISR 1
|
||||
|
||||
/* CMSIS-RTOS */
|
||||
/* Furi-specific */
|
||||
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
|
||||
#define CMSIS_TASK_NOTIFY_INDEX 1
|
||||
|
||||
extern __attribute__((__noreturn__)) void furi_thread_catch();
|
||||
#define configTASK_RETURN_ADDRESS (furi_thread_catch + 2)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,1.6,,
|
||||
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,,
|
||||
@@ -157,29 +158,29 @@ Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*"
|
||||
Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef*
|
||||
Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef*
|
||||
Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef*
|
||||
Function,-,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*"
|
||||
Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*"
|
||||
Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef*
|
||||
Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef*
|
||||
Function,-,LL_CRS_DeInit,ErrorStatus,
|
||||
Function,-,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t"
|
||||
Function,-,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*"
|
||||
Function,+,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t"
|
||||
Function,+,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*"
|
||||
Function,-,LL_DMA_StructInit,void,LL_DMA_InitTypeDef*
|
||||
Function,-,LL_EXTI_DeInit,ErrorStatus,
|
||||
Function,-,LL_EXTI_Init,ErrorStatus,LL_EXTI_InitTypeDef*
|
||||
Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef*
|
||||
Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef*
|
||||
Function,-,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*"
|
||||
Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*"
|
||||
Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef*
|
||||
Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef*
|
||||
Function,-,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*"
|
||||
Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*"
|
||||
Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef*
|
||||
Function,-,LL_Init1msTick,void,uint32_t
|
||||
Function,-,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef*
|
||||
Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef*
|
||||
Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef*
|
||||
Function,-,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*"
|
||||
Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*"
|
||||
Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef*
|
||||
Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef*
|
||||
Function,-,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*"
|
||||
Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*"
|
||||
Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef*
|
||||
Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef*
|
||||
Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*"
|
||||
@@ -193,14 +194,14 @@ Function,-,LL_RCC_GetADCClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetCLK48ClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetI2CClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetLPTIMClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t
|
||||
Function,+,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetRFWKPClockFreq,uint32_t,
|
||||
Function,-,LL_RCC_GetRNGClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetRTCClockFreq,uint32_t,
|
||||
Function,-,LL_RCC_GetSAIClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetSMPSClockFreq,uint32_t,
|
||||
Function,-,LL_RCC_GetSystemClocksFreq,void,LL_RCC_ClocksTypeDef*
|
||||
Function,-,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t
|
||||
Function,+,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RCC_GetUSBClockFreq,uint32_t,uint32_t
|
||||
Function,-,LL_RNG_DeInit,ErrorStatus,RNG_TypeDef*
|
||||
Function,-,LL_RNG_Init,ErrorStatus,"RNG_TypeDef*, LL_RNG_InitTypeDef*"
|
||||
@@ -212,21 +213,21 @@ Function,-,LL_RTC_ALMB_StructInit,void,LL_RTC_AlarmTypeDef*
|
||||
Function,-,LL_RTC_DATE_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_DateTypeDef*"
|
||||
Function,-,LL_RTC_DATE_StructInit,void,LL_RTC_DateTypeDef*
|
||||
Function,-,LL_RTC_DeInit,ErrorStatus,RTC_TypeDef*
|
||||
Function,-,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef*
|
||||
Function,+,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef*
|
||||
Function,-,LL_RTC_ExitInitMode,ErrorStatus,RTC_TypeDef*
|
||||
Function,-,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*"
|
||||
Function,+,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*"
|
||||
Function,-,LL_RTC_StructInit,void,LL_RTC_InitTypeDef*
|
||||
Function,-,LL_RTC_TIME_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_TimeTypeDef*"
|
||||
Function,-,LL_RTC_TIME_StructInit,void,LL_RTC_TimeTypeDef*
|
||||
Function,-,LL_RTC_WaitForSynchro,ErrorStatus,RTC_TypeDef*
|
||||
Function,-,LL_SPI_DeInit,ErrorStatus,SPI_TypeDef*
|
||||
Function,-,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*"
|
||||
Function,+,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*"
|
||||
Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef*
|
||||
Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t
|
||||
Function,-,LL_SetSystemCoreClock,void,uint32_t
|
||||
Function,+,LL_SetSystemCoreClock,void,uint32_t
|
||||
Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*"
|
||||
Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef*
|
||||
Function,-,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef*
|
||||
Function,+,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef*
|
||||
Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*"
|
||||
Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef*
|
||||
Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*"
|
||||
@@ -240,7 +241,7 @@ Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef*
|
||||
Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*"
|
||||
Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef*
|
||||
Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef*
|
||||
Function,-,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*"
|
||||
Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*"
|
||||
Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef*
|
||||
Function,-,LL_mDelay,void,uint32_t
|
||||
Function,-,SystemCoreClockUpdate,void,
|
||||
@@ -433,8 +434,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
|
||||
@@ -779,13 +780,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*
|
||||
@@ -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,
|
||||
@@ -986,7 +989,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 +1004,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*
|
||||
@@ -1141,6 +1144,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,
|
||||
@@ -1149,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*,
|
||||
@@ -1353,8 +1360,10 @@ Function,+,furi_thread_get_name,const char*,FuriThreadId
|
||||
Function,+,furi_thread_get_return_code,int32_t,FuriThread*
|
||||
Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId
|
||||
Function,+,furi_thread_get_state,FuriThreadState,FuriThread*
|
||||
Function,+,furi_thread_is_suspended,_Bool,FuriThreadId
|
||||
Function,+,furi_thread_join,_Bool,FuriThread*
|
||||
Function,+,furi_thread_mark_as_service,void,FuriThread*
|
||||
Function,+,furi_thread_resume,void,FuriThreadId
|
||||
Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback"
|
||||
Function,+,furi_thread_set_context,void,"FuriThread*, void*"
|
||||
Function,+,furi_thread_set_name,void,"FuriThread*, const char*"
|
||||
@@ -1366,6 +1375,7 @@ Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback
|
||||
Function,+,furi_thread_start,void,FuriThread*
|
||||
Function,+,furi_thread_stdout_flush,int32_t,
|
||||
Function,+,furi_thread_stdout_write,size_t,"const char*, size_t"
|
||||
Function,+,furi_thread_suspend,void,FuriThreadId
|
||||
Function,+,furi_thread_yield,void,
|
||||
Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*"
|
||||
Function,+,furi_timer_free,void,FuriTimer*
|
||||
@@ -2259,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*,
|
||||
@@ -2580,8 +2591,14 @@ Variable,+,I_Circles_47x47,const Icon,
|
||||
Variable,+,I_Clock_18x18,const Icon,
|
||||
Variable,+,I_Connect_me_62x31,const Icon,
|
||||
Variable,+,I_Connected_62x31,const Icon,
|
||||
Variable,+,I_CoolHi_25x27,const Icon,
|
||||
Variable,+,I_CoolHi_hvr_25x27,const Icon,
|
||||
Variable,+,I_CoolLo_25x27,const Icon,
|
||||
Variable,+,I_CoolLo_hvr_25x27,const Icon,
|
||||
Variable,+,I_Cry_dolph_55x52,const Icon,
|
||||
Variable,+,I_DFU_128x50,const Icon,
|
||||
Variable,+,I_Dehumidify_25x27,const Icon,
|
||||
Variable,+,I_Dehumidify_hvr_25x27,const Icon,
|
||||
Variable,+,I_Detailed_chip_17x13,const Icon,
|
||||
Variable,+,I_DolphinCommon_56x48,const Icon,
|
||||
Variable,+,I_DolphinMafia_115x62,const Icon,
|
||||
@@ -2605,6 +2622,10 @@ Variable,+,I_FaceNopower_29x14,const Icon,
|
||||
Variable,+,I_FaceNormal_29x14,const Icon,
|
||||
Variable,+,I_GameMode_11x8,const Icon,
|
||||
Variable,+,I_Health_16x16,const Icon,
|
||||
Variable,+,I_HeatHi_25x27,const Icon,
|
||||
Variable,+,I_HeatHi_hvr_25x27,const Icon,
|
||||
Variable,+,I_HeatLo_25x27,const Icon,
|
||||
Variable,+,I_HeatLo_hvr_25x27,const Icon,
|
||||
Variable,+,I_InfraredArrowDown_4x8,const Icon,
|
||||
Variable,+,I_InfraredArrowUp_4x8,const Icon,
|
||||
Variable,+,I_InfraredLearnShort_128x31,const Icon,
|
||||
@@ -2622,6 +2643,8 @@ Variable,+,I_Mute_25x27,const Icon,
|
||||
Variable,+,I_Mute_hvr_25x27,const Icon,
|
||||
Variable,+,I_NFC_manual_60x50,const Icon,
|
||||
Variable,+,I_Nfc_10px,const Icon,
|
||||
Variable,+,I_Off_25x27,const Icon,
|
||||
Variable,+,I_Off_hvr_25x27,const Icon,
|
||||
Variable,+,I_Ok_btn_9x9,const Icon,
|
||||
Variable,+,I_Ok_btn_pressed_13x13,const Icon,
|
||||
Variable,+,I_Percent_10x14,const Icon,
|
||||
@@ -2649,6 +2672,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,
|
||||
|
||||
|
@@ -48,5 +48,7 @@ SECTIONS
|
||||
{
|
||||
*(.comment)
|
||||
*(.comment.*)
|
||||
*(.llvmbc)
|
||||
*(.llvmcmd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
@@ -4,6 +4,33 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
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]));
|
||||
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();
|
||||
}
|
||||
if((length % 8) != 0) {
|
||||
|
||||
/* Enable regular (dword) programming mode */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
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 % 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();
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "furi_hal_pwm.h"
|
||||
#include <core/check.h>
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_lptim.h>
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file furi_hal_pwm.h
|
||||
* PWM contol HAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <m-string.h>
|
||||
#include <m-dict.h>
|
||||
#include <toolbox/m_cstr_dup.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -85,11 +85,12 @@ 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(
|
||||
"Service", "%s thread exited. Thread memory cannot be reclaimed.", thread->name);
|
||||
"Service",
|
||||
"%s thread exited. Thread memory cannot be reclaimed.",
|
||||
thread->name ? thread->name : "<unknown service>");
|
||||
}
|
||||
|
||||
// clear thread local storage
|
||||
@@ -97,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();
|
||||
}
|
||||
|
||||
@@ -203,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);
|
||||
}
|
||||
|
||||
@@ -515,4 +521,23 @@ size_t furi_thread_stdout_write(const char* data, size_t size) {
|
||||
|
||||
int32_t furi_thread_stdout_flush() {
|
||||
return __furi_thread_stdout_flush(furi_thread_get_current());
|
||||
}
|
||||
}
|
||||
|
||||
void furi_thread_suspend(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
vTaskSuspend(hTask);
|
||||
}
|
||||
|
||||
void furi_thread_resume(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
xTaskResumeFromISR(hTask);
|
||||
} else {
|
||||
vTaskResume(hTask);
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_thread_is_suspended(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
return eTaskGetState(hTask) == eSuspended;
|
||||
}
|
||||
|
||||
@@ -236,6 +236,25 @@ size_t furi_thread_stdout_write(const char* data, size_t size);
|
||||
*/
|
||||
int32_t furi_thread_stdout_flush();
|
||||
|
||||
/** Suspend thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void furi_thread_suspend(FuriThreadId thread_id);
|
||||
|
||||
/** Resume thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void furi_thread_resume(FuriThreadId thread_id);
|
||||
|
||||
/** Get thread suspended state
|
||||
*
|
||||
* @param thread_id thread id
|
||||
* @return true if thread is suspended
|
||||
*/
|
||||
bool furi_thread_is_suspended(FuriThreadId thread_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "bq25896.h"
|
||||
#include "bq25896_reg.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "bq25896_reg.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_i2c.h>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* @file application_manifest.h
|
||||
* Flipper application manifest
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/elf/elf.h>
|
||||
#include <elf.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define ELF_INVALID_ADDRESS 0xFFFFFFFF
|
||||
|
||||
@@ -0,0 +1,835 @@
|
||||
#include <elf.h>
|
||||
#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_<unknow>";
|
||||
}
|
||||
#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 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*<C> <Rd>,#<imm16>
|
||||
*
|
||||
* 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:
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file elf_file.h
|
||||
* ELF file loader
|
||||
*/
|
||||
#pragma once
|
||||
#include <storage/storage.h>
|
||||
#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
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "elf_file.h"
|
||||
#include <m-dict.h>
|
||||
|
||||
#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
|
||||
@@ -1,477 +0,0 @@
|
||||
#include "flipper_application_i.h"
|
||||
#include <furi.h>
|
||||
|
||||
#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_<unknow>";
|
||||
}
|
||||
#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;
|
||||
}
|
||||
@@ -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,56 +31,71 @@ void flipper_application_free(FlipperApplication* app) {
|
||||
|
||||
last_loaded_app = NULL;
|
||||
|
||||
if(app->state.debug_link_size) {
|
||||
free(app->state.debug_link);
|
||||
}
|
||||
|
||||
if(app->state.mmap_entries) {
|
||||
free(app->state.mmap_entries);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
storage_file_free(app->fd);
|
||||
|
||||
elf_file_clear_debug_info(&app->state);
|
||||
elf_file_free(app->elf);
|
||||
free(app);
|
||||
}
|
||||
|
||||
/* 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)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) &&
|
||||
(app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) {
|
||||
static FlipperApplicationPreloadStatus
|
||||
flipper_application_validate_manifest(FlipperApplication* app) {
|
||||
if(!flipper_application_manifest_is_valid(&app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusInvalidManifest;
|
||||
}
|
||||
|
||||
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 */) {
|
||||
if(!flipper_application_manifest_is_compatible(
|
||||
&app->manifest, elf_file_get_api_interface(app->elf))) {
|
||||
return FlipperApplicationPreloadStatusApiMismatch;
|
||||
}
|
||||
|
||||
return FlipperApplicationPreloadStatusSuccess;
|
||||
}
|
||||
|
||||
/* Parse headers, load manifest */
|
||||
FlipperApplicationPreloadStatus
|
||||
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;
|
||||
}
|
||||
|
||||
return flipper_application_validate_manifest(app);
|
||||
}
|
||||
|
||||
/* 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 flipper_application_validate_manifest(app);
|
||||
}
|
||||
|
||||
const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
|
||||
return &app->manifest;
|
||||
}
|
||||
|
||||
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",
|
||||
|
||||
@@ -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
|
||||
@@ -1,99 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "elf.h"
|
||||
#include "flipper_application.h"
|
||||
#include <m-dict.h>
|
||||
|
||||
#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
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
|
||||
#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
|
||||
#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc")
|
||||
|
||||
#define TAG "MfClassicDict"
|
||||
|
||||
@@ -23,6 +24,9 @@ bool mf_classic_dict_check_presence(MfClassicDictType dict_type) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
|
||||
} else if(dict_type == MfClassicDictTypeUser) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
|
||||
} else if(dict_type == MfClassicDictTypeUnitTest) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) ==
|
||||
FSE_OK;
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
@@ -50,6 +54,15 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
} else if(dict_type == MfClassicDictTypeUnitTest) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream,
|
||||
MF_CLASSIC_DICT_UNIT_TEST_PATH,
|
||||
FSAM_READ_WRITE,
|
||||
FSOM_CREATE_ALWAYS)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read total amount of keys
|
||||
@@ -100,7 +113,7 @@ static void mf_classic_dict_str_to_int(string_t key_str, uint64_t* key_int) {
|
||||
for(uint8_t i = 0; i < 12; i += 2) {
|
||||
args_char_to_hex(
|
||||
string_get_char(key_str, i), string_get_char(key_str, i + 1), &key_byte_tmp);
|
||||
*key_int |= (uint8_t)key_byte_tmp << 8 * (5 - i / 2);
|
||||
*key_int |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,41 @@
|
||||
typedef enum {
|
||||
MfClassicDictTypeUser,
|
||||
MfClassicDictTypeFlipper,
|
||||
MfClassicDictTypeUnitTest,
|
||||
} MfClassicDictType;
|
||||
|
||||
typedef struct MfClassicDict MfClassicDict;
|
||||
|
||||
bool mf_classic_dict_check_presence(MfClassicDictType dict_type);
|
||||
|
||||
/** Allocate MfClassicDict instance
|
||||
*
|
||||
* @param[in] dict_type The dictionary type
|
||||
*
|
||||
* @return MfClassicDict instance
|
||||
*/
|
||||
MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type);
|
||||
|
||||
/** Free MfClassicDict instance
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*/
|
||||
void mf_classic_dict_free(MfClassicDict* dict);
|
||||
|
||||
/** Get total keys count
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*
|
||||
* @return total keys count
|
||||
*/
|
||||
uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict);
|
||||
|
||||
/** Rewind to the beginning
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_rewind(MfClassicDict* dict);
|
||||
|
||||
bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key);
|
||||
@@ -31,16 +54,46 @@ bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
|
||||
|
||||
bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key);
|
||||
|
||||
/** Get key at target offset as uint64_t
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[out] key Pointer to the uint64_t key
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target);
|
||||
|
||||
/** Get key at target offset as string_t
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[out] key Found key destination buffer
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target);
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key);
|
||||
|
||||
/** Add string representation of the key
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[in] key String representation of the key
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key);
|
||||
|
||||
bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target);
|
||||
|
||||
bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target);
|
||||
|
||||
/** Delete key at target offset
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flip
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_bett_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_bett_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -162,7 +162,7 @@ bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flip
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_came_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_came_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -155,7 +155,7 @@ static bool
|
||||
break;
|
||||
|
||||
default:
|
||||
furi_crash(TAG " unknown protocol.");
|
||||
FURI_LOG_E(TAG, "Invalid bits count");
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
@@ -224,7 +224,7 @@ bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_chamb_code_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -173,7 +173,7 @@ bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* fl
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_clemsa_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_clemsa_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -154,7 +154,7 @@ bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat*
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_doitrand_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_doitrand_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -147,7 +147,7 @@ bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* f
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_gate_tx_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -160,7 +160,7 @@ bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* fl
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_holtek_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_holtek_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -162,7 +162,7 @@ bool subghz_protocol_encoder_honeywell_wdb_deserialize(
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_honeywell_wdb_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -163,7 +163,7 @@ bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* f
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_hormann_get_upload(instance);
|
||||
if(!subghz_protocol_encoder_hormann_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
|
||||