Merge remote-tracking branch 'origin/release-candidate' into release

This commit is contained in:
Aleksandr Kutuzov 2022-10-03 00:45:53 +09:00
commit 5c52bb7621
94 changed files with 3067 additions and 1048 deletions

View File

@ -28,13 +28,14 @@
"servertype": "openocd",
"device": "stlink",
"svdFile": "./debug/STM32WB55_CM4.svd",
// If you're debugging early in the boot process, before OS scheduler is running,
// you have to comment out the following line.
"rtos": "FreeRTOS",
"configFiles": [
"interface/stlink.cfg",
"./debug/stm32wbx.cfg",
],
"postAttachCommands": [
// "attach 1",
// "compare-sections",
"source debug/flipperapps.py",
// "source debug/FreeRTOS/FreeRTOS.py",

View File

@ -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.

View File

@ -58,7 +58,7 @@ MU_TEST(storage_file_open_lock) {
storage_file_close(file);
// file_locker thread stop
mu_check(furi_thread_join(locker_thread) == FuriStatusOk);
mu_check(furi_thread_join(locker_thread));
furi_thread_free(locker_thread);
// clean data
@ -148,7 +148,7 @@ MU_TEST(storage_dir_open_lock) {
storage_dir_close(file);
// file_locker thread stop
mu_check(furi_thread_join(locker_thread) == FuriStatusOk);
mu_check(furi_thread_join(locker_thread));
furi_thread_free(locker_thread);
// clean data

View File

@ -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);

View File

@ -23,7 +23,7 @@ void ibutton_scene_delete_confirm_on_enter(void* context) {
ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name));
widget_add_text_box_element(
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false);
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true);
widget_add_button_element(
widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton);
widget_add_button_element(
@ -47,24 +47,24 @@ void ibutton_scene_delete_confirm_on_enter(void* context) {
key_data[6],
key_data[7]);
widget_add_string_element(
widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Dallas");
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas");
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
widget_add_string_element(
widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
widget_add_string_element(
widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Metakom");
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom");
break;
}
widget_add_string_element(
widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);

View File

@ -15,7 +15,7 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) {
void ibutton_scene_emulate_on_enter(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
Widget* widget = ibutton->widget;
iButtonKey* key = ibutton->key;
const uint8_t* key_data = ibutton_key_get_data_p(key);
@ -26,20 +26,18 @@ void ibutton_scene_emulate_on_enter(void* context) {
path_extract_filename(ibutton->file_path, key_name, true);
}
uint8_t line_count = 2;
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
// check that stored key has name
if(!string_empty_p(key_name)) {
ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name));
line_count = 2;
ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name));
} else {
// if not, show key data
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"emulating\n%02X %02X %02X %02X\n%02X %02X %02X %02X",
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
@ -48,40 +46,24 @@ void ibutton_scene_emulate_on_enter(void* context) {
key_data[5],
key_data[6],
key_data[7]);
line_count = 3;
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "emulating\n%02X %02X", key_data[0], key_data[1]);
line_count = 2;
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton,
"emulating\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3]);
line_count = 2;
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
break;
}
}
switch(line_count) {
case 3:
popup_set_header(popup, "iButton", 82, 18, AlignCenter, AlignBottom);
popup_set_text(popup, ibutton->text_store, 82, 22, AlignCenter, AlignTop);
break;
widget_add_string_multiline_element(
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
widget_add_text_box_element(
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
default:
popup_set_header(popup, "iButton", 82, 24, AlignCenter, AlignBottom);
popup_set_text(popup, ibutton->text_store, 82, 28, AlignCenter, AlignTop);
break;
}
popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
ibutton_worker_emulate_set_callback(
ibutton->key_worker, ibutton_scene_emulate_callback, ibutton);
@ -122,10 +104,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_emulate_on_exit(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
ibutton_worker_stop(ibutton->key_worker);
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL);
widget_reset(ibutton->widget);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
}

View File

@ -14,7 +14,7 @@ void ibutton_scene_info_on_enter(void* context) {
ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name));
widget_add_text_box_element(
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false);
widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true);
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
@ -29,26 +29,24 @@ void ibutton_scene_info_on_enter(void* context) {
key_data[5],
key_data[6],
key_data[7]);
widget_add_string_element(
widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Dallas");
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas");
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
widget_add_string_element(
widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Metakom");
widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom");
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
widget_add_string_element(
widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral");
break;
}
widget_add_string_element(
widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store);
widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);

View File

@ -29,10 +29,8 @@ bool ibutton_scene_save_success_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventBack) {
const uint32_t possible_scenes[] = {
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
scene_manager_search_and_switch_to_previous_scene_one_of(
ibutton->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
scene_manager_search_and_switch_to_another_scene(
ibutton->scene_manager, iButtonSceneSelectKey);
}
}

View File

@ -14,8 +14,8 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult
void ibutton_scene_write_on_enter(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
iButtonKey* key = ibutton->key;
Widget* widget = ibutton->widget;
iButtonWorker* worker = ibutton->key_worker;
const uint8_t* key_data = ibutton_key_get_data_p(key);
@ -26,19 +26,16 @@ void ibutton_scene_write_on_enter(void* context) {
path_extract_filename(ibutton->file_path, key_name, true);
}
uint8_t line_count = 2;
// check that stored key has name
if(!string_empty_p(key_name)) {
ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name));
line_count = 2;
ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name));
} else {
// if not, show key data
switch(ibutton_key_get_type(key)) {
case iButtonKeyDS1990:
ibutton_text_store_set(
ibutton,
"writing\n%02X %02X %02X %02X\n%02X %02X %02X %02X",
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
@ -47,40 +44,24 @@ void ibutton_scene_write_on_enter(void* context) {
key_data[5],
key_data[6],
key_data[7]);
line_count = 3;
break;
case iButtonKeyCyfral:
ibutton_text_store_set(ibutton, "writing\n%02X %02X", key_data[0], key_data[1]);
line_count = 2;
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
break;
case iButtonKeyMetakom:
ibutton_text_store_set(
ibutton,
"writing\n%02X %02X %02X %02X",
key_data[0],
key_data[1],
key_data[2],
key_data[3]);
line_count = 2;
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
break;
}
}
switch(line_count) {
case 3:
popup_set_header(popup, "iButton", 82, 18, AlignCenter, AlignBottom);
popup_set_text(popup, ibutton->text_store, 82, 22, AlignCenter, AlignTop);
break;
widget_add_string_multiline_element(
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting");
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
widget_add_text_box_element(
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
default:
popup_set_header(popup, "iButton", 82, 24, AlignCenter, AlignBottom);
popup_set_text(popup, ibutton->text_store, 82, 28, AlignCenter, AlignTop);
break;
}
popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
ibutton_worker_write_start(worker, key);
@ -114,11 +95,8 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
void ibutton_scene_write_on_exit(void* context) {
iButton* ibutton = context;
Popup* popup = ibutton->popup;
ibutton_worker_stop(ibutton->key_worker);
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL);
widget_reset(ibutton->widget);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
}

View File

@ -23,30 +23,36 @@ struct InfraredBruteForce {
FlipperFormat* ff;
const char* db_filename;
string_t current_record_name;
InfraredSignal* current_signal;
InfraredBruteForceRecordDict_t records;
bool is_started;
};
InfraredBruteForce* infrared_brute_force_alloc() {
InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce));
brute_force->ff = NULL;
brute_force->db_filename = NULL;
brute_force->current_signal = NULL;
brute_force->is_started = false;
string_init(brute_force->current_record_name);
InfraredBruteForceRecordDict_init(brute_force->records);
return brute_force;
}
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->ff);
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_clear(brute_force->records);
string_clear(brute_force->current_record_name);
free(brute_force);
}
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
furi_assert(!brute_force->is_started);
brute_force->db_filename = db_filename;
}
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->is_started);
furi_assert(brute_force->db_filename);
bool success = false;
@ -76,6 +82,7 @@ bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count) {
furi_assert(!brute_force->is_started);
bool success = false;
*record_count = 0;
@ -96,50 +103,37 @@ bool infrared_brute_force_start(
if(*record_count) {
Storage* storage = furi_record_open(RECORD_STORAGE);
brute_force->ff = flipper_format_buffered_file_alloc(storage);
brute_force->current_signal = infrared_signal_alloc();
brute_force->is_started = true;
success =
flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename);
if(!success) {
flipper_format_free(brute_force->ff);
brute_force->ff = NULL;
furi_record_close(RECORD_STORAGE);
}
if(!success) infrared_brute_force_stop(brute_force);
}
return success;
}
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
return brute_force->ff;
return brute_force->is_started;
}
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_assert(string_size(brute_force->current_record_name));
furi_assert(brute_force->ff);
furi_assert(brute_force->is_started);
string_reset(brute_force->current_record_name);
infrared_signal_free(brute_force->current_signal);
flipper_format_free(brute_force->ff);
furi_record_close(RECORD_STORAGE);
brute_force->current_signal = NULL;
brute_force->ff = NULL;
brute_force->is_started = false;
furi_record_close(RECORD_STORAGE);
}
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
furi_assert(string_size(brute_force->current_record_name));
furi_assert(brute_force->ff);
bool success = false;
string_t signal_name;
string_init(signal_name);
InfraredSignal* signal = infrared_signal_alloc();
do {
success = infrared_signal_read(signal, brute_force->ff, signal_name);
} while(success && !string_equal_p(brute_force->current_record_name, signal_name));
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_and_read(
brute_force->current_signal, brute_force->ff, brute_force->current_record_name);
if(success) {
infrared_signal_transmit(signal);
infrared_signal_transmit(brute_force->current_signal);
}
infrared_signal_free(signal);
string_clear(signal_name);
return success;
}
@ -155,5 +149,6 @@ void infrared_brute_force_add_record(
}
void infrared_brute_force_reset(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_reset(brute_force->records);
}

View File

@ -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;
}

View File

@ -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);

View File

@ -38,7 +38,6 @@ void lfrfid_scene_write_on_enter(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
app->old_key_data = (uint8_t*)malloc(size);
protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size);
lfrfid_worker_start_thread(app->lfworker);
@ -92,5 +91,4 @@ void lfrfid_scene_write_on_exit(void* context) {
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
free(app->old_key_data);
}

View File

@ -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);

View File

@ -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);

View File

@ -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",
)

View File

@ -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,
};

View File

@ -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

View File

@ -0,0 +1,3 @@
ADD_SCENE(signal_gen, start, Start)
ADD_SCENE(signal_gen, pwm, Pwm)
ADD_SCENE(signal_gen, mco, Mco)

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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
@ -99,7 +96,7 @@ void power_free(Power* power) {
static void power_check_charging_state(Power* power) {
if(furi_hal_power_is_charging()) {
if(power->info.charge == 100) {
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
if(power->state != PowerStateCharged) {
notification_internal_message(power->notification, &sequence_charged);
power->state = PowerStateCharged;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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));
}
}

View File

@ -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,

View File

@ -1,8 +1,11 @@
Import("env")
from fbt.version import get_git_commit_unix_timestamp
assetsenv = env.Clone(
tools=["fbt_assets"],
FW_LIB_NAME="assets",
GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(),
)
assetsenv.ApplyLibFlags()
@ -90,10 +93,11 @@ if assetsenv["IS_BASE_FIRMWARE"]:
"#/assets/resources/Manifest",
assetsenv.GlobRecursive("*", "resources", exclude="Manifest"),
action=Action(
'${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}"',
'${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}',
"${RESMANIFESTCOMSTR}",
),
)
assetsenv.Precious(resources)
assetsenv.AlwaysBuild(resources)
assetsenv.Clean(
resources,

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,6 +1,7 @@
Filetype: IR library file
Version: 1
#
# Model: Electrolux EACM-16 HP/N3
name: Off
type: raw
frequency: 38000
@ -36,3 +37,40 @@ 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

View File

@ -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

View File

@ -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(

View File

@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config {
# assignment
mmw 0xE0042004 0x00000020 0
}
$_TARGETNAME configure -event gdb-detach {
resume
}

View File

@ -43,12 +43,12 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio
The following parameters are used only for [FAPs](./AppsOnSDCard.md):
* **sources**: list of file name masks, used for gathering sources within app folder. Default value of ["\*.c\*"] includes C and CPP source files.
* **fap_version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file.
* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and CPP source files.
* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meanig version "0.1".
* **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file.
* **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption.
* **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system.
* **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.

View File

@ -32,5 +32,9 @@ Finally, record the `Off` signal:
The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used.
Test the file against the actual device. Every signal must do what it's supposed to.
If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir)
and open a pull request.
If everything checks out, 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.

View File

@ -96,6 +96,7 @@ if not env["VERBOSE"]:
SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}",
APPMETA_COMSTR="\tAPPMETA\t${TARGET}",
APPMETAEMBED_COMSTR="\tFAP\t${TARGET}",
APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}",
)

View File

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,1.9,,
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,,
@ -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*,
@ -2262,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*,
@ -2664,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,

1 entry status name type params
2 Version + 1.9 1.13
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
42 Header + firmware/targets/f7/furi_hal/furi_hal_idle_timer.h
43 Header + firmware/targets/f7/furi_hal/furi_hal_interrupt.h
44 Header + firmware/targets/f7/furi_hal/furi_hal_os.h
45 Header + firmware/targets/f7/furi_hal/furi_hal_pwm.h
46 Header + firmware/targets/f7/furi_hal/furi_hal_resources.h
47 Header + firmware/targets/f7/furi_hal/furi_hal_spi_config.h
48 Header + firmware/targets/f7/furi_hal/furi_hal_spi_types.h
434 Function - acosl long double long double
435 Function + acquire_mutex void* ValueMutex*, uint32_t
436 Function - aligned_alloc void* size_t, size_t
437 Function - + aligned_free void void*
438 Function - + aligned_malloc void* size_t, size_t
439 Function - arc4random __uint32_t
440 Function - arc4random_buf void void*, size_t
441 Function - arc4random_uniform __uint32_t __uint32_t
780 Function - fiscanf int FILE*, const char*, ...
781 Function + flipper_application_alloc FlipperApplication* Storage*, const ElfApiInterface*
782 Function + flipper_application_free void FlipperApplication*
Function - flipper_application_get_entry_address const void* FlipperApplication*
783 Function + flipper_application_get_manifest const FlipperApplicationManifest* FlipperApplication*
Function - flipper_application_get_state const FlipperApplicationState* FlipperApplication*
Function - flipper_application_get_thread FuriThread* FlipperApplication*
784 Function + flipper_application_load_status_to_string const char* FlipperApplicationLoadStatus
785 Function + flipper_application_manifest_is_compatible _Bool const FlipperApplicationManifest*, const ElfApiInterface*
786 Function + flipper_application_manifest_is_valid _Bool const FlipperApplicationManifest*
787 Function + flipper_application_map_to_memory FlipperApplicationLoadStatus FlipperApplication*
788 Function + flipper_application_preload FlipperApplicationPreloadStatus FlipperApplication*, const char*
789 Function + flipper_application_preload_manifest FlipperApplicationPreloadStatus FlipperApplication*, const char*
790 Function - flipper_application_preload_status_to_string const char* FlipperApplicationPreloadStatus
791 Function + flipper_application_spawn FuriThread* FlipperApplication*, void*
792 Function + flipper_format_buffered_file_alloc FlipperFormat* Storage*
955 Function + furi_hal_clock_deinit_early void
956 Function - furi_hal_clock_init void
957 Function - furi_hal_clock_init_early void
958 Function + furi_hal_clock_mco_disable void
959 Function + furi_hal_clock_mco_enable void FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId
960 Function - furi_hal_clock_resume_tick void
961 Function - furi_hal_clock_suspend_tick void
962 Function - furi_hal_clock_switch_to_hsi void
989 Function + furi_hal_debug_disable void
990 Function + furi_hal_debug_enable void
991 Function - furi_hal_deinit_early void
992 Function - furi_hal_flash_erase _Bool void uint8_t
993 Function - furi_hal_flash_get_base size_t
994 Function - furi_hal_flash_get_cycles_count size_t
995 Function - furi_hal_flash_get_free_end_address const void*
1004 Function - furi_hal_flash_ob_apply void
1005 Function - furi_hal_flash_ob_get_raw_ptr const FuriHalFlashRawOptionByteData*
1006 Function - furi_hal_flash_ob_set_word _Bool size_t, const uint32_t
1007 Function - furi_hal_flash_program_page _Bool void const uint8_t, const uint8_t*, uint16_t
1008 Function - furi_hal_flash_write_dword _Bool void size_t, uint64_t
1009 Function + furi_hal_gpio_add_int_callback void const GpioPin*, GpioExtiCallback, void*
1010 Function + furi_hal_gpio_disable_int_callback void const GpioPin*
1011 Function + furi_hal_gpio_enable_int_callback void const GpioPin*
1144 Function + furi_hal_power_insomnia_exit void
1145 Function - furi_hal_power_insomnia_level uint16_t
1146 Function + furi_hal_power_is_charging _Bool
1147 Function + furi_hal_power_is_charging_done _Bool
1148 Function + furi_hal_power_is_otg_enabled _Bool
1149 Function + furi_hal_power_off void
1150 Function + furi_hal_power_reset void
1153 Function + furi_hal_power_sleep_available _Bool
1154 Function + furi_hal_power_suppress_charge_enter void
1155 Function + furi_hal_power_suppress_charge_exit void
1156 Function + furi_hal_pwm_set_params void FuriHalPwmOutputId, uint32_t, uint8_t
1157 Function + furi_hal_pwm_start void FuriHalPwmOutputId, uint32_t, uint8_t
1158 Function + furi_hal_pwm_stop void FuriHalPwmOutputId
1159 Function + furi_hal_random_fill_buf void uint8_t*, uint32_t
1160 Function + furi_hal_random_get uint32_t
1161 Function + furi_hal_region_get const FuriHalRegion*
2269 Function + tar_archive_open _Bool TarArchive*, const char*, TarOpenMode
2270 Function + tar_archive_set_file_callback void TarArchive*, tar_unpack_file_cb, void*
2271 Function + tar_archive_store_data _Bool TarArchive*, const char*, const uint8_t*, const int32_t
2272 Function + tar_archive_unpack_file _Bool TarArchive*, const char*, const char*
2273 Function + tar_archive_unpack_to _Bool TarArchive*, const char*, Storage_name_converter
2274 Function - tempnam char* const char*, const char*
2275 Function + text_box_alloc TextBox*
2672 Variable + I_SDcardFail_11x8 const Icon
2673 Variable + I_SDcardMounted_11x8 const Icon
2674 Variable + I_Scanning_123x52 const Icon
2675 Variable + I_SmallArrowDown_4x7 const Icon
2676 Variable + I_SmallArrowUp_4x7 const Icon
2677 Variable + I_Smile_18x18 const Icon
2678 Variable + I_Space_65x18 const Icon
2679 Variable + I_Tap_reader_36x38 const Icon

View File

@ -48,5 +48,7 @@ SECTIONS
{
*(.comment)
*(.comment.*)
*(.llvmbc)
*(.llvmcmd)
}
}

View File

@ -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)
;
}

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -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
*

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -85,7 +85,6 @@ static void furi_thread_body(void* context) {
}
furi_assert(thread->state == FuriThreadStateRunning);
furi_thread_set_state(thread, FuriThreadStateStopped);
if(thread->is_service) {
FURI_LOG_E(
@ -94,12 +93,17 @@ static void furi_thread_body(void* context) {
thread->name ? thread->name : "<unknown service>");
}
// clear thread local storage
// flush stdout
__furi_thread_stdout_flush(thread);
// from here we can't use thread pointer
furi_thread_set_state(thread, FuriThreadStateStopped);
// clear thread local storage
furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL);
vTaskSetThreadLocalStoragePointer(NULL, 0, NULL);
vTaskDelete(thread->task_handle);
vTaskDelete(NULL);
furi_thread_catch();
}
@ -205,11 +209,19 @@ 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);
// Check if thread was started
if(thread->task_handle == NULL) {
return false;
}
// Wait for thread to stop
while(eTaskGetState(thread->task_handle) != eDeleted) {
furi_delay_ms(10);
}
return FuriStatusOk;
return true;
}
FuriThreadId furi_thread_get_id(FuriThread* thread) {

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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 */

View File

@ -1,6 +1,6 @@
#pragma once
#include <flipper_application/elf/elf.h>
#include <elf.h>
#include <stdbool.h>
#define ELF_INVALID_ADDRESS 0xFFFFFFFF

View File

@ -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, &section);
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, &section_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, &section_header))
return elf_relocate(elf, &section_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, &section_header, name)) {
break;
}
if(string_cmp(name, ".fapmeta") == 0) {
if(elf_load_metadata(elf, &section_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, &section_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, &section_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;
}

View File

@ -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

View File

@ -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

View File

@ -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, &section_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, &section_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, &section_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, &section_header))
return relocate(e, &section_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;
}

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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");
}

View File

@ -521,6 +521,15 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector(
return 1;
}
break;
case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1:
man = subghz_protocol_keeloq_common_magic_serial_type1_learning(
fix, manufacture_code->key);
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) {
*manufacture_name = string_get_cstr(manufacture_code->name);
return 1;
}
break;
case KEELOQ_LEARNING_UNKNOWN:
// Simple Learning
decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key);
@ -528,6 +537,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector(
*manufacture_name = string_get_cstr(manufacture_code->name);
return 1;
}
// Check for mirrored man
uint64_t man_rev = 0;
uint64_t man_rev_byte = 0;
@ -535,11 +545,13 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector(
man_rev_byte = (uint8_t)(manufacture_code->key >> i);
man_rev = man_rev | man_rev_byte << (56 - i);
}
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev);
if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) {
*manufacture_name = string_get_cstr(manufacture_code->name);
return 1;
}
//###########################
// Normal Learning
// https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37

View File

@ -86,3 +86,15 @@ inline uint64_t
data &= 0x0FFFFFFF;
return (((uint64_t)data << 32) | data) ^ xor;
}
/** Magic_serial_type1 Learning
* @param data - serial number (28bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t
subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) {
return man | ((uint64_t)data << 40) |
((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32);
}

View File

@ -21,6 +21,7 @@
#define KEELOQ_LEARNING_NORMAL 2u
#define KEELOQ_LEARNING_SECURE 3u
#define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 5u
/**
* Simple Learning Encrypt
@ -63,3 +64,11 @@ uint64_t
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor);
/** Magic_serial_type1 Learning
* @param data - serial number (28bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man);

View File

@ -381,7 +381,7 @@ static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t
"%s%s%s%s%s%s%s%s",
((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") :
(event & 0x1 ? " Motion" : " Ok")),
((event >> 1) & 0x1 ? ", Tamper On (Alarm)" : ""),
((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""),
((event >> 2) & 0x1 ? ", ?" : ""),
((event >> 3) & 0x1 ? ", Power On" : ""),
((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""),

17
lib/toolbox/m_cstr_dup.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <m-core.h>
#define M_INIT_DUP(a) ((a) = strdup(""))
#define M_SET_DUP(a, b) (M_CHECK_DEFAULT_TYPE(a), free((void*)a), (a) = strdup(b))
#define M_CLEAR_DUP(a) (free((void*)a))
#define M_CSTR_DUP_OPLIST \
(INIT(M_INIT_DUP), \
INIT_SET(M_SET_DUP), \
SET(M_SET_DUP), \
CLEAR(M_CLEAR_DUP), \
HASH(m_core_cstr_hash), \
EQUAL(M_CSTR_EQUAL), \
CMP(strcmp), \
TYPE(const char*), \
OUT_STR(M_CSTR_OUT_STR))

View File

@ -168,7 +168,44 @@ typedef struct {
Storage_name_converter converter;
} TarArchiveDirectoryOpParams;
static bool archive_extract_current_file(TarArchive* archive, const char* dst_path) {
mtar_t* tar = &archive->tar;
File* out_file = storage_file_alloc(archive->storage);
uint8_t* readbuf = malloc(FILE_BLOCK_SIZE);
bool success = true;
uint8_t n_tries = FILE_OPEN_NTRIES;
do {
while(n_tries-- > 0) {
if(storage_file_open(out_file, dst_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
break;
}
FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", dst_path, n_tries);
storage_file_close(out_file);
furi_delay_ms(FILE_OPEN_RETRY_DELAY);
}
if(!storage_file_is_open(out_file)) {
success = false;
break;
}
while(!mtar_eof_data(tar)) {
int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE);
if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) {
success = false;
break;
}
}
} while(false);
storage_file_free(out_file);
free(readbuf);
return success;
}
static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, void* param) {
UNUSED(tar);
TarArchiveDirectoryOpParams* op_params = param;
TarArchive* archive = op_params->archive;
@ -199,58 +236,22 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header,
return 0;
}
string_init(full_extracted_fname);
FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name);
string_t converted_fname;
string_init_set(converted_fname, header->name);
if(op_params->converter) {
op_params->converter(converted_fname);
}
string_init(full_extracted_fname);
path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname);
bool success = archive_extract_current_file(archive, string_get_cstr(full_extracted_fname));
string_clear(converted_fname);
FURI_LOG_I(TAG, "Extracting %d bytes to '%s'", header->size, header->name);
File* out_file = storage_file_alloc(archive->storage);
uint8_t* readbuf = malloc(FILE_BLOCK_SIZE);
bool failed = false;
uint8_t n_tries = FILE_OPEN_NTRIES;
do {
while(n_tries-- > 0) {
if(storage_file_open(
out_file,
string_get_cstr(full_extracted_fname),
FSAM_WRITE,
FSOM_CREATE_ALWAYS)) {
break;
}
FURI_LOG_W(
TAG,
"Failed to open '%s', reties: %d",
string_get_cstr(full_extracted_fname),
n_tries);
storage_file_close(out_file);
furi_delay_ms(FILE_OPEN_RETRY_DELAY);
}
if(!storage_file_is_open(out_file)) {
failed = true;
break;
}
while(!mtar_eof_data(tar)) {
int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE);
if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) {
failed = true;
break;
}
}
} while(false);
storage_file_free(out_file);
free(readbuf);
string_clear(full_extracted_fname);
return failed ? -1 : 0;
return success ? 0 : -1;
}
bool tar_archive_unpack_to(
@ -369,3 +370,16 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch
storage_file_free(directory);
return success;
}
bool tar_archive_unpack_file(
TarArchive* archive,
const char* archive_fname,
const char* destination) {
furi_assert(archive);
furi_assert(archive_fname);
furi_assert(destination);
if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) {
return false;
}
return archive_extract_current_file(archive, destination);
}

View File

@ -41,6 +41,11 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch
int32_t tar_archive_get_entries_count(TarArchive* archive);
bool tar_archive_unpack_file(
TarArchive* archive,
const char* archive_fname,
const char* destination);
/* Optional per-entry callback on unpacking - return false to skip entry */
typedef bool (*tar_unpack_file_cb)(const char* name, bool is_directory, void* context);

View File

@ -0,0 +1,115 @@
#include "manifest.h"
#include <toolbox/stream/buffered_file_stream.h>
#include <toolbox/hex.h>
struct ResourceManifestReader {
Storage* storage;
Stream* stream;
string_t linebuf;
ResourceManifestEntry entry;
};
ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) {
ResourceManifestReader* resource_manifest =
(ResourceManifestReader*)malloc(sizeof(ResourceManifestReader));
resource_manifest->storage = storage;
resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage);
memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry));
string_init(resource_manifest->entry.name);
string_init(resource_manifest->linebuf);
return resource_manifest;
}
void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) {
furi_assert(resource_manifest);
string_clear(resource_manifest->linebuf);
string_clear(resource_manifest->entry.name);
buffered_file_stream_close(resource_manifest->stream);
stream_free(resource_manifest->stream);
free(resource_manifest);
}
bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename) {
furi_assert(resource_manifest);
return buffered_file_stream_open(
resource_manifest->stream, filename, FSAM_READ, FSOM_OPEN_EXISTING);
}
/* Read entries in format of
* F:<hash>:<size>:<name>
* D:<name>
*/
ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) {
furi_assert(resource_manifest);
string_reset(resource_manifest->entry.name);
resource_manifest->entry.type = ResourceManifestEntryTypeUnknown;
resource_manifest->entry.size = 0;
memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash));
do {
if(!stream_read_line(resource_manifest->stream, resource_manifest->linebuf)) {
return NULL;
}
/* Trim end of line */
string_strim(resource_manifest->linebuf);
char type_code = string_get_char(resource_manifest->linebuf, 0);
switch(type_code) {
case 'F':
resource_manifest->entry.type = ResourceManifestEntryTypeFile;
break;
case 'D':
resource_manifest->entry.type = ResourceManifestEntryTypeDirectory;
break;
default: /* Skip other entries - version, timestamp, etc */
continue;
};
if(resource_manifest->entry.type == ResourceManifestEntryTypeFile) {
/* Parse file entry
F:<hash>:<size>:<name> */
/* Remove entry type code */
string_right(resource_manifest->linebuf, 2);
if(string_search_char(resource_manifest->linebuf, ':') !=
sizeof(resource_manifest->entry.hash) * 2) {
/* Invalid hash */
continue;
}
/* Read hash */
hex_chars_to_uint8(
string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash);
/* Remove hash */
string_right(
resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1);
resource_manifest->entry.size = atoi(string_get_cstr(resource_manifest->linebuf));
/* Remove size */
size_t offs = string_search_char(resource_manifest->linebuf, ':');
string_right(resource_manifest->linebuf, offs + 1);
string_set(resource_manifest->entry.name, resource_manifest->linebuf);
} else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) {
/* Parse directory entry
D:<name> */
/* Remove entry type code */
string_right(resource_manifest->linebuf, 2);
string_set(resource_manifest->entry.name, resource_manifest->linebuf);
}
return &resource_manifest->entry;
} while(true);
return NULL;
}

View File

@ -0,0 +1,58 @@
#pragma once
#include <storage/storage.h>
#include <m-string.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ResourceManifestEntryTypeUnknown = 0,
ResourceManifestEntryTypeDirectory,
ResourceManifestEntryTypeFile,
} ResourceManifestEntryType;
typedef struct {
ResourceManifestEntryType type;
string_t name;
uint32_t size;
uint8_t hash[16];
} ResourceManifestEntry;
typedef struct ResourceManifestReader ResourceManifestReader;
/**
* @brief Initialize resource manifest reader
* @param storage Storage API pointer
* @return allocated object
*/
ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage);
/**
* @brief Release resource manifest reader
* @param resource_manifest allocated object
*/
void resource_manifest_reader_free(ResourceManifestReader* resource_manifest);
/**
* @brief Initialize resource manifest reader iteration
* @param resource_manifest allocated object
* @param filename manifest file name
* @return true if file opened
*/
bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename);
/**
* @brief Read next file/dir entry from manifest
* @param resource_manifest allocated object
* @return entry or NULL if end of file
*/
ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -39,6 +39,13 @@ class Main(App):
"manifest", help="Create directory Manifest"
)
self.parser_manifest.add_argument("local_path", help="local_path")
self.parser_manifest.add_argument(
"--timestamp",
help="timestamp value to embed",
default=0,
type=int,
required=False,
)
self.parser_manifest.set_defaults(func=self.manifest)
self.parser_copro = self.subparsers.add_parser(
@ -111,7 +118,7 @@ class Main(App):
if not filenames:
continue
if "frame_rate" in filenames:
self.logger.debug(f"Folder contatins animation")
self.logger.debug(f"Folder contains animation")
icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_")
width = height = None
frame_count = 0
@ -213,7 +220,7 @@ class Main(App):
self.logger.info(
f'Creating temporary Manifest for directory "{directory_path}"'
)
new_manifest = Manifest()
new_manifest = Manifest(self.args.timestamp)
new_manifest.create(directory_path)
self.logger.info(f"Comparing new manifest with existing")

View File

@ -106,11 +106,11 @@ addManifestRecord(ManifestRecordFile)
class Manifest:
def __init__(self):
def __init__(self, timestamp_value=None):
self.version = None
self.records = []
self.records.append(ManifestRecordVersion(MANIFEST_VERSION))
self.records.append(ManifestRecordTimestamp(timestamp()))
self.records.append(ManifestRecordTimestamp(timestamp_value or timestamp()))
self.logger = logging.getLogger(self.__class__.__name__)
def load(self, filename):

View File

@ -14,4 +14,4 @@ def resolve_port(logger, portname: str = "auto"):
logger.error("Failed to find connected Flipper")
elif len(flippers) > 1:
logger.error("More than one Flipper is attached")
logger.error("Failed to guess which port to use. Specify --port")
logger.error("Failed to guess which port to use")

View File

@ -3,5 +3,5 @@ heatshrink2==0.11.0
Pillow==9.1.1
grpcio==1.47.0
grpcio-tools==1.47.0
protobuf==3.20.1
protobuf==3.20.2
python3-protobuf==2.5.0

View File

@ -1,13 +1,25 @@
import logging
import subprocess
from flipper.utils.cdc import resolve_port
import os
import sys
def main():
logger = logging.getLogger()
if not (port := resolve_port(logger, "auto")):
logger.error("Is Flipper connected over USB and isn't in DFU mode?")
return 1
subprocess.call(["python3", "-m", "serial.tools.miniterm", "--raw", port, "230400"])
subprocess.call(
[
os.path.basename(sys.executable),
"-m",
"serial.tools.miniterm",
"--raw",
port,
"230400",
]
)
if __name__ == "__main__":

View File

@ -32,6 +32,7 @@ if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" (
set "HOME=%USERPROFILE%"
set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python"
set "PYTHONPATH="
set "PYTHONNOUSERSITE=1"
set "PATH=%FBT_TOOLCHAIN_ROOT%\python;%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\protoc\bin;%FBT_TOOLCHAIN_ROOT%\openocd\bin;%PATH%"
set "PROMPT=(fbt) %PROMPT%"

View File

@ -40,6 +40,13 @@ fbtenv_restore_env()
elif [ -n "${PROMPT:-""}" ]; then
PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')";
fi
PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE";
PYTHONPATH="$SAVED_PYTHONPATH";
unset SAVED_PYTHONNOUSERSITE;
unset SAVED_PYTHONPATH;
unset SCRIPT_PATH;
unset FBT_TOOLCHAIN_VERSION;
unset FBT_TOOLCHAIN_PATH;
@ -276,6 +283,12 @@ fbtenv_main()
PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH";
PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH";
PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH";
SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}";
SAVED_PYTHONPATH="${PYTHONPATH:-""}";
PYTHONNOUSERSITE=1;
PYTHONPATH=;
}
fbtenv_main "${1:-""}";

View File

@ -229,7 +229,7 @@ class Main(App):
@staticmethod
def int2ffhex(value: int, n_hex_syms=8):
if value:
n_hex_syms = math.ceil(math.ceil(math.log2(value)) / 8) * 2
n_hex_syms = max(math.ceil(math.ceil(math.log2(value)) / 8) * 2, n_hex_syms)
fmtstr = f"%0{n_hex_syms}X"
hexstr = fmtstr % value
return " ".join(list(Main.batch(hexstr, 2))[::-1])

View File

@ -12,7 +12,14 @@ forward_os_env = {
"PATH": os.environ["PATH"],
}
# Proxying CI environment to child processes & scripts
for env_value_name in ("WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", "HOME", "APPDATA"):
for env_value_name in (
"WORKFLOW_BRANCH_OR_TAG",
"DIST_SUFFIX",
"HOME",
"APPDATA",
"PYTHONHOME",
"PYTHONNOUSERSITE",
):
if environ_value := os.environ.get(env_value_name, None):
forward_os_env[env_value_name] = environ_value

View File

@ -86,12 +86,13 @@ if appenv["FORCE"]:
Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps["compact"].values())
if appsrc := appenv.subst("$APPSRC"):
app_manifest, fap_file = appenv.GetExtAppFromPath(appsrc)
app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc)
appenv.PhonyTarget(
"launch_app",
'${PYTHON3} scripts/runfap.py ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"',
source=fap_file,
FAP_CATEGORY=app_manifest.fap_category,
)
appenv.Alias("launch_app", app_validator)
Return("extapps")

View File

@ -38,7 +38,7 @@ class FlipperApplication:
sdk_headers: List[str] = field(default_factory=list)
# .fap-specific
sources: List[str] = field(default_factory=lambda: ["*.c*"])
fap_version: Tuple[int] = field(default_factory=lambda: (0, 0))
fap_version: Tuple[int] = field(default_factory=lambda: (0, 1))
fap_icon: Optional[str] = None
fap_libs: List[str] = field(default_factory=list)
fap_category: str = ""

View File

@ -3,6 +3,11 @@ import datetime
from functools import cache
@cache
def get_git_commit_unix_timestamp():
return int(subprocess.check_output(["git", "show", "-s", "--format=%ct"]))
@cache
def get_fast_git_version_id():
try:

View File

@ -37,7 +37,15 @@ def BuildAppElf(env, app):
APP=app,
)
env.Depends(app_elf_augmented, [env["SDK_DEFINITION"], env.Value(app)])
manifest_vals = vars(app)
manifest_vals = {
k: v for k, v in manifest_vals.items() if k not in ("_appdir", "_apppath")
}
env.Depends(
app_elf_augmented,
[env["SDK_DEFINITION"], env.Value(manifest_vals)],
)
if app.fap_icon:
env.Depends(
app_elf_augmented,
@ -47,6 +55,7 @@ def BuildAppElf(env, app):
app_elf_import_validator = env.ValidateAppImports(app_elf_augmented)
env.AlwaysBuild(app_elf_import_validator)
env.Alias(app_alias, app_elf_import_validator)
return (app_elf_augmented, app_elf_raw, app_elf_import_validator)
@ -100,9 +109,13 @@ def GetExtAppFromPath(env, app_dir):
app_elf = env["_extapps"]["compact"].get(app.appid, None)
if not app_elf:
raise UserError(f"No external app found for {app.appid}")
raise UserError(
f"Application {app.appid} is not configured for building as external"
)
return (app, app_elf[0])
app_validator = env["_extapps"]["validators"].get(app.appid, None)
return (app, app_elf[0], app_validator[0])
def generate(env, **kw):
@ -138,7 +151,7 @@ def generate(env, **kw):
),
Action(
validate_app_imports,
None, # "$APPCHECK_COMSTR",
"$APPCHECK_COMSTR",
),
],
suffix=".impsyms",