Merge remote-tracking branch 'origin/dev' into release-candidate
This commit is contained in:
commit
a0f6a23062
3
.vscode/example/launch.json
vendored
3
.vscode/example/launch.json
vendored
@ -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",
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -15,6 +15,9 @@ typedef struct {
|
||||
DialogsApp* dialogs;
|
||||
Gui* gui;
|
||||
string_t fap_path;
|
||||
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Loading* loading;
|
||||
} FapLoader;
|
||||
|
||||
static bool
|
||||
@ -25,7 +28,7 @@ static bool
|
||||
FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface);
|
||||
|
||||
FlipperApplicationPreloadStatus preload_res =
|
||||
flipper_application_preload(app, string_get_cstr(path));
|
||||
flipper_application_preload_manifest(app, string_get_cstr(path));
|
||||
|
||||
bool load_success = false;
|
||||
|
||||
@ -144,12 +147,12 @@ int32_t fap_loader_app(void* p) {
|
||||
loader->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
loader->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
||||
Loading* loading = loading_alloc();
|
||||
loader->view_dispatcher = view_dispatcher_alloc();
|
||||
loader->loading = loading_alloc();
|
||||
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading));
|
||||
view_dispatcher_attach_to_gui(
|
||||
loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading));
|
||||
|
||||
if(p) {
|
||||
string_init_set(loader->fap_path, (const char*)p);
|
||||
@ -158,14 +161,14 @@ int32_t fap_loader_app(void* p) {
|
||||
string_init_set(loader->fap_path, EXT_PATH("apps"));
|
||||
|
||||
while(fap_loader_select_app(loader)) {
|
||||
view_dispatcher_switch_to_view(view_dispatcher, 0);
|
||||
view_dispatcher_switch_to_view(loader->view_dispatcher, 0);
|
||||
fap_loader_run_selected_app(loader);
|
||||
};
|
||||
}
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, 0);
|
||||
loading_free(loading);
|
||||
view_dispatcher_free(view_dispatcher);
|
||||
view_dispatcher_remove_view(loader->view_dispatcher, 0);
|
||||
loading_free(loader->loading);
|
||||
view_dispatcher_free(loader->view_dispatcher);
|
||||
|
||||
string_clear(loader->fap_path);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
@ -23,30 +23,36 @@ struct InfraredBruteForce {
|
||||
FlipperFormat* ff;
|
||||
const char* db_filename;
|
||||
string_t current_record_name;
|
||||
InfraredSignal* current_signal;
|
||||
InfraredBruteForceRecordDict_t records;
|
||||
bool is_started;
|
||||
};
|
||||
|
||||
InfraredBruteForce* infrared_brute_force_alloc() {
|
||||
InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce));
|
||||
brute_force->ff = NULL;
|
||||
brute_force->db_filename = NULL;
|
||||
brute_force->current_signal = NULL;
|
||||
brute_force->is_started = false;
|
||||
string_init(brute_force->current_record_name);
|
||||
InfraredBruteForceRecordDict_init(brute_force->records);
|
||||
return brute_force;
|
||||
}
|
||||
|
||||
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
|
||||
furi_assert(!brute_force->ff);
|
||||
furi_assert(!brute_force->is_started);
|
||||
InfraredBruteForceRecordDict_clear(brute_force->records);
|
||||
string_clear(brute_force->current_record_name);
|
||||
free(brute_force);
|
||||
}
|
||||
|
||||
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
|
||||
furi_assert(!brute_force->is_started);
|
||||
brute_force->db_filename = db_filename;
|
||||
}
|
||||
|
||||
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
|
||||
furi_assert(!brute_force->is_started);
|
||||
furi_assert(brute_force->db_filename);
|
||||
bool success = false;
|
||||
|
||||
@ -76,6 +82,7 @@ bool infrared_brute_force_start(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
uint32_t* record_count) {
|
||||
furi_assert(!brute_force->is_started);
|
||||
bool success = false;
|
||||
*record_count = 0;
|
||||
|
||||
@ -96,50 +103,37 @@ bool infrared_brute_force_start(
|
||||
if(*record_count) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
brute_force->ff = flipper_format_buffered_file_alloc(storage);
|
||||
brute_force->current_signal = infrared_signal_alloc();
|
||||
brute_force->is_started = true;
|
||||
success =
|
||||
flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename);
|
||||
if(!success) {
|
||||
flipper_format_free(brute_force->ff);
|
||||
brute_force->ff = NULL;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
if(!success) infrared_brute_force_stop(brute_force);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
|
||||
return brute_force->ff;
|
||||
return brute_force->is_started;
|
||||
}
|
||||
|
||||
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
furi_assert(string_size(brute_force->current_record_name));
|
||||
furi_assert(brute_force->ff);
|
||||
|
||||
furi_assert(brute_force->is_started);
|
||||
string_reset(brute_force->current_record_name);
|
||||
infrared_signal_free(brute_force->current_signal);
|
||||
flipper_format_free(brute_force->ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
brute_force->current_signal = NULL;
|
||||
brute_force->ff = NULL;
|
||||
brute_force->is_started = false;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
|
||||
furi_assert(string_size(brute_force->current_record_name));
|
||||
furi_assert(brute_force->ff);
|
||||
bool success = false;
|
||||
|
||||
string_t signal_name;
|
||||
string_init(signal_name);
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
|
||||
do {
|
||||
success = infrared_signal_read(signal, brute_force->ff, signal_name);
|
||||
} while(success && !string_equal_p(brute_force->current_record_name, signal_name));
|
||||
|
||||
furi_assert(brute_force->is_started);
|
||||
const bool success = infrared_signal_search_and_read(
|
||||
brute_force->current_signal, brute_force->ff, brute_force->current_record_name);
|
||||
if(success) {
|
||||
infrared_signal_transmit(signal);
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
}
|
||||
|
||||
infrared_signal_free(signal);
|
||||
string_clear(signal_name);
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -146,6 +146,26 @@ static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperForma
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) {
|
||||
string_t tmp;
|
||||
string_init(tmp);
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(ff, "type", tmp)) break;
|
||||
if(string_equal_p(tmp, "raw")) {
|
||||
success = infrared_signal_read_raw(signal, ff);
|
||||
} else if(string_equal_p(tmp, "parsed")) {
|
||||
success = infrared_signal_read_message(signal, ff);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unknown signal type");
|
||||
}
|
||||
} while(false);
|
||||
|
||||
string_clear(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
InfraredSignal* infrared_signal_alloc() {
|
||||
InfraredSignal* signal = malloc(sizeof(InfraredSignal));
|
||||
|
||||
@ -227,24 +247,41 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char*
|
||||
}
|
||||
|
||||
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) {
|
||||
string_t buf;
|
||||
string_init(buf);
|
||||
string_t tmp;
|
||||
string_init(tmp);
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(ff, "name", buf)) break;
|
||||
string_set(name, buf);
|
||||
if(!flipper_format_read_string(ff, "type", buf)) break;
|
||||
if(!string_cmp_str(buf, "raw")) {
|
||||
success = infrared_signal_read_raw(signal, ff);
|
||||
} else if(!string_cmp_str(buf, "parsed")) {
|
||||
success = infrared_signal_read_message(signal, ff);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) ");
|
||||
}
|
||||
if(!flipper_format_read_string(ff, "name", tmp)) break;
|
||||
string_set(name, tmp);
|
||||
if(!infrared_signal_read_body(signal, ff)) break;
|
||||
success = true;
|
||||
} while(0);
|
||||
|
||||
string_clear(buf);
|
||||
string_clear(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool infrared_signal_search_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
const string_t name) {
|
||||
bool success = false;
|
||||
string_t tmp;
|
||||
string_init(tmp);
|
||||
|
||||
do {
|
||||
bool is_name_found = false;
|
||||
while(flipper_format_read_string(ff, "name", tmp)) {
|
||||
is_name_found = string_equal_p(name, tmp);
|
||||
if(is_name_found) break;
|
||||
}
|
||||
if(!is_name_found) break;
|
||||
if(!infrared_signal_read_body(signal, ff)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
string_clear(tmp);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@ -37,5 +37,9 @@ InfraredMessage* infrared_signal_get_message(InfraredSignal* signal);
|
||||
|
||||
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
|
||||
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name);
|
||||
bool infrared_signal_search_and_read(
|
||||
InfraredSignal* signal,
|
||||
FlipperFormat* ff,
|
||||
const string_t name);
|
||||
|
||||
void infrared_signal_transmit(InfraredSignal* signal);
|
||||
|
||||
@ -223,6 +223,7 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even
|
||||
|
||||
void subghz_scene_receiver_config_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
variable_item_list_set_selected_item(subghz->variable_item_list, 0);
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet);
|
||||
|
||||
@ -45,7 +45,7 @@ void subghz_view_transmitter_add_data_to_show(
|
||||
}
|
||||
|
||||
static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) {
|
||||
const uint8_t button_height = 13;
|
||||
const uint8_t button_height = 12;
|
||||
const uint8_t vertical_offset = 3;
|
||||
const uint8_t horizontal_offset = 1;
|
||||
const uint8_t string_width = canvas_string_width(canvas, str);
|
||||
@ -69,7 +69,10 @@ static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str
|
||||
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_icon(
|
||||
canvas, x + horizontal_offset, y - button_height + vertical_offset, &I_ButtonCenter_7x7);
|
||||
canvas,
|
||||
x + horizontal_offset,
|
||||
y - button_height + vertical_offset - 1,
|
||||
&I_ButtonCenter_7x7);
|
||||
canvas_draw_str(
|
||||
canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str);
|
||||
canvas_invert_color(canvas);
|
||||
|
||||
12
applications/plugins/signal_generator/application.fam
Normal file
12
applications/plugins/signal_generator/application.fam
Normal 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",
|
||||
)
|
||||
@ -0,0 +1,30 @@
|
||||
#include "../signal_gen_app_i.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const signal_gen_scene_on_enter_handlers[])(void*) = {
|
||||
#include "signal_gen_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const signal_gen_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "signal_gen_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const signal_gen_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "signal_gen_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers signal_gen_scene_handlers = {
|
||||
.on_enter_handlers = signal_gen_scene_on_enter_handlers,
|
||||
.on_event_handlers = signal_gen_scene_on_event_handlers,
|
||||
.on_exit_handlers = signal_gen_scene_on_exit_handlers,
|
||||
.scene_num = SignalGenSceneNum,
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) SignalGenScene##id,
|
||||
typedef enum {
|
||||
#include "signal_gen_scene_config.h"
|
||||
SignalGenSceneNum,
|
||||
} SignalGenScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers signal_gen_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "signal_gen_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "signal_gen_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "signal_gen_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@ -0,0 +1,3 @@
|
||||
ADD_SCENE(signal_gen, start, Start)
|
||||
ADD_SCENE(signal_gen, pwm, Pwm)
|
||||
ADD_SCENE(signal_gen, mco, Mco)
|
||||
@ -0,0 +1,132 @@
|
||||
#include "../signal_gen_app_i.h"
|
||||
|
||||
typedef enum {
|
||||
LineIndexSource,
|
||||
LineIndexDivision,
|
||||
} LineIndex;
|
||||
|
||||
static const char* const mco_source_names[] = {
|
||||
"32768",
|
||||
"64MHz",
|
||||
"~100K",
|
||||
"~200K",
|
||||
"~400K",
|
||||
"~800K",
|
||||
"~1MHz",
|
||||
"~2MHz",
|
||||
"~4MHz",
|
||||
"~8MHz",
|
||||
"~16MHz",
|
||||
"~24MHz",
|
||||
"~32MHz",
|
||||
"~48MHz",
|
||||
};
|
||||
|
||||
static const FuriHalClockMcoSourceId mco_sources[] = {
|
||||
FuriHalClockMcoLse,
|
||||
FuriHalClockMcoSysclk,
|
||||
FuriHalClockMcoMsi100k,
|
||||
FuriHalClockMcoMsi200k,
|
||||
FuriHalClockMcoMsi400k,
|
||||
FuriHalClockMcoMsi800k,
|
||||
FuriHalClockMcoMsi1m,
|
||||
FuriHalClockMcoMsi2m,
|
||||
FuriHalClockMcoMsi4m,
|
||||
FuriHalClockMcoMsi8m,
|
||||
FuriHalClockMcoMsi16m,
|
||||
FuriHalClockMcoMsi24m,
|
||||
FuriHalClockMcoMsi32m,
|
||||
FuriHalClockMcoMsi48m,
|
||||
};
|
||||
|
||||
static const char* const mco_divisor_names[] = {
|
||||
"1",
|
||||
"2",
|
||||
"4",
|
||||
"8",
|
||||
"16",
|
||||
};
|
||||
|
||||
static const FuriHalClockMcoDivisorId mco_divisors[] = {
|
||||
FuriHalClockMcoDiv1,
|
||||
FuriHalClockMcoDiv2,
|
||||
FuriHalClockMcoDiv4,
|
||||
FuriHalClockMcoDiv8,
|
||||
FuriHalClockMcoDiv16,
|
||||
};
|
||||
|
||||
static void mco_source_list_change_callback(VariableItem* item) {
|
||||
SignalGenApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, mco_source_names[index]);
|
||||
|
||||
app->mco_src = mco_sources[index];
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate);
|
||||
}
|
||||
|
||||
static void mco_divisor_list_change_callback(VariableItem* item) {
|
||||
SignalGenApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, mco_divisor_names[index]);
|
||||
|
||||
app->mco_div = mco_divisors[index];
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate);
|
||||
}
|
||||
|
||||
void signal_gen_scene_mco_on_enter(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
VariableItemList* var_item_list = app->var_item_list;
|
||||
|
||||
VariableItem* item;
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "Source", COUNT_OF(mco_source_names), mco_source_list_change_callback, app);
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, mco_source_names[0]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list,
|
||||
"Division",
|
||||
COUNT_OF(mco_divisor_names),
|
||||
mco_divisor_list_change_callback,
|
||||
app);
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, mco_divisor_names[0]);
|
||||
|
||||
variable_item_list_set_selected_item(var_item_list, LineIndexSource);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList);
|
||||
|
||||
app->mco_src = FuriHalClockMcoLse;
|
||||
app->mco_div = FuriHalClockMcoDiv1;
|
||||
furi_hal_clock_mco_enable(app->mco_src, app->mco_div);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO);
|
||||
}
|
||||
|
||||
bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) {
|
||||
SignalGenApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SignalGenMcoEventUpdate) {
|
||||
consumed = true;
|
||||
furi_hal_clock_mco_enable(app->mco_src, app->mco_div);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void signal_gen_scene_mco_on_exit(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_usart_tx,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullUp,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn7USART1);
|
||||
furi_hal_clock_mco_disable();
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
#include "../signal_gen_app_i.h"
|
||||
|
||||
static const FuriHalPwmOutputId pwm_ch_id[] = {
|
||||
FuriHalPwmOutputIdTim1PA7,
|
||||
FuriHalPwmOutputIdLptim2PA4,
|
||||
};
|
||||
|
||||
#define DEFAULT_FREQ 1000
|
||||
#define DEFAULT_DUTY 50
|
||||
|
||||
static void
|
||||
signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) {
|
||||
SignalGenApp* app = context;
|
||||
|
||||
app->pwm_freq = freq;
|
||||
app->pwm_duty = duty;
|
||||
|
||||
if(app->pwm_ch != pwm_ch_id[channel_id]) {
|
||||
app->pwm_ch_prev = app->pwm_ch;
|
||||
app->pwm_ch = pwm_ch_id[channel_id];
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange);
|
||||
} else {
|
||||
app->pwm_ch = pwm_ch_id[channel_id];
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
void signal_gen_scene_pwm_on_enter(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm);
|
||||
|
||||
signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app);
|
||||
|
||||
signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY);
|
||||
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
|
||||
}
|
||||
|
||||
bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
|
||||
SignalGenApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SignalGenPwmEventUpdate) {
|
||||
consumed = true;
|
||||
furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
||||
} else if(event.event == SignalGenPwmEventChannelChange) {
|
||||
consumed = true;
|
||||
furi_hal_pwm_stop(app->pwm_ch_prev);
|
||||
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void signal_gen_scene_pwm_on_exit(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
furi_hal_pwm_stop(app->pwm_ch);
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
#include "../signal_gen_app_i.h"
|
||||
|
||||
typedef enum {
|
||||
SubmenuIndexPwm,
|
||||
SubmenuIndexClockOutput,
|
||||
} SubmenuIndex;
|
||||
|
||||
void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
SignalGenApp* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void signal_gen_scene_start_on_enter(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "PWM", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Clock Output",
|
||||
SubmenuIndexClockOutput,
|
||||
signal_gen_scene_start_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu);
|
||||
}
|
||||
|
||||
bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
SignalGenApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexPwm) {
|
||||
scene_manager_next_scene(app->scene_manager, SignalGenScenePwm);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexClockOutput) {
|
||||
scene_manager_next_scene(app->scene_manager, SignalGenSceneMco);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void signal_gen_scene_start_on_exit(void* context) {
|
||||
SignalGenApp* app = context;
|
||||
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
BIN
applications/plugins/signal_generator/signal_gen_10px.png
Normal file
BIN
applications/plugins/signal_generator/signal_gen_10px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
93
applications/plugins/signal_generator/signal_gen_app.c
Normal file
93
applications/plugins/signal_generator/signal_gen_app.c
Normal 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;
|
||||
}
|
||||
46
applications/plugins/signal_generator/signal_gen_app_i.h
Normal file
46
applications/plugins/signal_generator/signal_gen_app_i.h
Normal 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;
|
||||
301
applications/plugins/signal_generator/views/signal_gen_pwm.c
Normal file
301
applications/plugins/signal_generator/views/signal_gen_pwm.c
Normal 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);
|
||||
}
|
||||
21
applications/plugins/signal_generator/views/signal_gen_pwm.h
Normal file
21
applications/plugins/signal_generator/views/signal_gen_pwm.h
Normal 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);
|
||||
@ -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
|
||||
|
||||
@ -53,7 +53,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re
|
||||
* @param button_panel ButtonPanel instance
|
||||
* @param index value to pass to callback
|
||||
* @param matrix_place_x coordinates by x-axis on virtual grid, it
|
||||
* is only used for naviagation
|
||||
* is only used for navigation
|
||||
* @param matrix_place_y coordinates by y-axis on virtual grid, it
|
||||
* is only used for naviagation
|
||||
* @param x x-coordinate to draw icon on
|
||||
|
||||
@ -232,7 +232,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
if(text_length == 0 && char_is_lowercase(keys[column].text)) {
|
||||
if(model->clear_default_text ||
|
||||
(text_length == 0 && char_is_lowercase(keys[column].text))) {
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
@ -318,15 +319,17 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b
|
||||
}
|
||||
} else if(selected == BACKSPACE_KEY) {
|
||||
text_input_backspace_cb(model);
|
||||
} else if(text_length < (model->text_buffer_size - 1)) {
|
||||
} else {
|
||||
if(model->clear_default_text) {
|
||||
text_length = 0;
|
||||
}
|
||||
if(text_length == 0 && char_is_lowercase(selected)) {
|
||||
selected = char_to_uppercase(selected);
|
||||
if(text_length < (model->text_buffer_size - 1)) {
|
||||
if(text_length == 0 && char_is_lowercase(selected)) {
|
||||
selected = char_to_uppercase(selected);
|
||||
}
|
||||
model->text_buffer[text_length] = selected;
|
||||
model->text_buffer[text_length + 1] = 0;
|
||||
}
|
||||
model->text_buffer[text_length] = selected;
|
||||
model->text_buffer[text_length + 1] = 0;
|
||||
}
|
||||
model->clear_default_text = false;
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
#include "power_i.h"
|
||||
#include "views/power_off.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/view_port.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
#define POWER_OFF_TIMEOUT 90
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -109,10 +109,7 @@ static int storage_int_device_prog(
|
||||
|
||||
int ret = 0;
|
||||
while(size > 0) {
|
||||
if(!furi_hal_flash_write_dword(address, *(uint64_t*)buffer)) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
furi_hal_flash_write_dword(address, *(uint64_t*)buffer);
|
||||
address += c->prog_size;
|
||||
buffer += c->prog_size;
|
||||
size -= c->prog_size;
|
||||
@ -127,16 +124,13 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc
|
||||
|
||||
FURI_LOG_D(TAG, "Device erase: page %d, translated page: %x", block, page);
|
||||
|
||||
if(furi_hal_flash_erase(page)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
furi_hal_flash_erase(page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int storage_int_device_sync(const struct lfs_config* c) {
|
||||
UNUSED(c);
|
||||
FURI_LOG_D(TAG, "Device sync: skipping, cause ");
|
||||
FURI_LOG_D(TAG, "Device sync: skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ extern "C" {
|
||||
#include <stdbool.h>
|
||||
#include <m-string.h>
|
||||
|
||||
#define UPDATE_DELAY_OPERATION_OK 300
|
||||
#define UPDATE_DELAY_OPERATION_OK 10
|
||||
#define UPDATE_DELAY_OPERATION_ERROR INT_MAX
|
||||
|
||||
typedef enum {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include <update_util/dfu_file.h>
|
||||
#include <update_util/lfs_backup.h>
|
||||
#include <update_util/update_operation.h>
|
||||
#include <update_util/resources/manifest.h>
|
||||
#include <toolbox/tar/tar_archive.h>
|
||||
#include <toolbox/crc32_calc.h>
|
||||
|
||||
@ -50,10 +51,46 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory,
|
||||
update_task_set_progress(
|
||||
unpack_progress->update_task,
|
||||
UpdateTaskStageProgress,
|
||||
unpack_progress->processed_files * 100 / (unpack_progress->total_files + 1));
|
||||
/* For this stage, last 70% of progress = extraction */
|
||||
30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) {
|
||||
ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage);
|
||||
do {
|
||||
FURI_LOG_I(TAG, "Cleaning up old manifest");
|
||||
if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) {
|
||||
FURI_LOG_W(TAG, "No existing manifest");
|
||||
break;
|
||||
}
|
||||
|
||||
/* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */
|
||||
n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1;
|
||||
uint32_t n_processed_files = 0;
|
||||
|
||||
ResourceManifestEntry* entry_ptr = NULL;
|
||||
while((entry_ptr = resource_manifest_reader_next(manifest_reader))) {
|
||||
if(entry_ptr->type == ResourceManifestEntryTypeFile) {
|
||||
update_task_set_progress(
|
||||
update_task,
|
||||
UpdateTaskStageProgress,
|
||||
/* For this stage, first 30% of progress = cleanup */
|
||||
(n_processed_files++ * 30) / (n_approx_file_entries + 1));
|
||||
|
||||
string_t file_path;
|
||||
string_init(file_path);
|
||||
path_concat(STORAGE_EXT_PATH_PREFIX, string_get_cstr(entry_ptr->name), file_path);
|
||||
FURI_LOG_D(TAG, "Removing %s", string_get_cstr(file_path));
|
||||
storage_simply_remove(update_task->storage, string_get_cstr(file_path));
|
||||
string_clear(file_path);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
resource_manifest_reader_free(manifest_reader);
|
||||
}
|
||||
|
||||
static bool update_task_post_update(UpdateTask* update_task) {
|
||||
bool success = false;
|
||||
|
||||
@ -88,6 +125,8 @@ static bool update_task_post_update(UpdateTask* update_task) {
|
||||
|
||||
progress.total_files = tar_archive_get_entries_count(archive);
|
||||
if(progress.total_files > 0) {
|
||||
update_task_cleanup_resources(update_task, progress.total_files);
|
||||
|
||||
CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL));
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,11 +52,19 @@ static bool check_address_boundaries(const size_t address) {
|
||||
return ((address >= min_allowed_address) && (address < max_allowed_address));
|
||||
}
|
||||
|
||||
static bool update_task_flash_program_page(
|
||||
const uint8_t i_page,
|
||||
const uint8_t* update_block,
|
||||
uint16_t update_block_len) {
|
||||
furi_hal_flash_program_page(i_page, update_block, update_block_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool update_task_write_dfu(UpdateTask* update_task) {
|
||||
DfuUpdateTask page_task = {
|
||||
.address_cb = &check_address_boundaries,
|
||||
.progress_cb = &update_task_file_progress,
|
||||
.task_cb = &furi_hal_flash_program_page,
|
||||
.task_cb = &update_task_flash_program_page,
|
||||
.context = update_task,
|
||||
};
|
||||
|
||||
@ -117,7 +125,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) {
|
||||
furi_hal_flash_get_page_number(update_task->manifest->radio_address + element_offs);
|
||||
CHECK_RESULT(i_page >= 0);
|
||||
|
||||
CHECK_RESULT(furi_hal_flash_program_page(i_page, fw_block, bytes_read));
|
||||
furi_hal_flash_program_page(i_page, fw_block, bytes_read);
|
||||
|
||||
element_offs += bytes_read;
|
||||
update_task_set_progress(
|
||||
@ -300,7 +308,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_I(
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"OB MATCH: #%d: real %08X == %08X (exp.)",
|
||||
idx,
|
||||
|
||||
@ -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,
|
||||
|
||||
BIN
assets/icons/Interface/SmallArrowDown_4x7.png
Normal file
BIN
assets/icons/Interface/SmallArrowDown_4x7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
assets/icons/Interface/SmallArrowUp_4x7.png
Normal file
BIN
assets/icons/Interface/SmallArrowUp_4x7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@ -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
|
||||
|
||||
@ -1,48 +1,50 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 1
|
||||
IV: F2 D4 F5 5A B3 CC 3F 21 28 3A AF ED D1 EB 73 DF
|
||||
BBFA4D79A73C384D6E07E717F761F32A625F28AA1DB2261B8B19A18261E30AB6
|
||||
CE4004AB56111B0B3D486770705FAD8BD616A80957EA2C537BAF1FD09E552DA3
|
||||
F974561612C9C751237C64D978F706B41873FDBE38851306574F436CB02D9ECA
|
||||
E29CAB7C2C2D9853D0F4DF69F582562E8182234E78B355540F6FE3F78C73D518
|
||||
97ABE18993A700A607E37DC88E1434F84DDD1C2771693978C9D2FA4CE4F2AB7BBC7C3EB3E8545B37FBBE1C1F1CA03543
|
||||
E86ABD0AAE5A4B4A9414C9CB6112CA49B3A8EC29723B14DCA85902F41B05ADDC
|
||||
C1FBE921035F408C59DA6AD5E76E3887AC9BC90146619B3CAE445BED556E96AC
|
||||
232C9F86915B927888352797B45F159268FE78956CF09B8D241CDC393D3B0225
|
||||
3D9E2A3C701C9D4DD4D72038D4536CA6F515C547CAB0AD18BA71204BD2ABFB74
|
||||
4D69A4506D2C97EF8EC68F90CF1AD1065A1EB909793EEB3AF71B0D75E55B9E76
|
||||
5A7F4595DFA181C3E946EBEE4974DBD6DA85AF6FCAD0B3725FDD28667175A421D69A2122853E57927C38CCF368732476
|
||||
6A946FAEDE134155B5A88EC01AA535E7A778947D360218B560381A64CAF9ACE896079D04C14718D5AD5C0D4EE3005F52
|
||||
88AC0C723AAA875A1885C8392A616FA43B205119B0E8D299193979A1921FC8B3
|
||||
40588AADA5E1A8BE214B2CCF32D268B48C6B783AE0DD10D88BDF3FF88E921E09
|
||||
A7BE05D05DEC9B9A3AE1575D411BF7B12366AD78B726F3E3E843E7BF199961A4
|
||||
79F973A155A4367F0EAA078AA0857A2A2A82FC4C8A5AE9E567E7CBF62C2A5CE2
|
||||
C38296EEABDA1F95D0C401CC6DDC8656476DC19248588EEF1CB93773D94CDB02A40C902970C4FCB14FABEFFB4F8BC208
|
||||
B0B7699B3C3573EE4D88D8CE65FAF3532B5A741D1F20892C0F38BAA2BCE98F2D
|
||||
6E401D6BDB1B33A404DEB668F3FB353166475487BAADE4A348E3CFDEB3B1B54B
|
||||
0E44B87878617559783CC6A7C65BE9F99950FE8956ED4BB04894BC53085E3A09CA19915B1E8C143A68D1B7A97F5D1ECB
|
||||
AC19E55638429C65E6E567C0E96DA9648F8FB80215CF693D7FD5DD86FE7989AC7AC7BAE86BBD4FFF7161AFFB405FFA98
|
||||
BCE70C69D90AD639A737813FC8FD26F40F803137BD36E47651C266A671428D6F
|
||||
F053CF5255AD2E1875A5C38635F7BF203B1DAE1433B162C30AE8695AC8A5589D
|
||||
B7EFC77FFA98B173E429B3566A27842C4DC5E91B0BC01F07A6A98332C4E1F42A
|
||||
D7C7950FFB2C5E7D9BCDBC230BF5F1BFFC0FE6F1CF5C8C6013DD90E41AE403FE
|
||||
50667B2E5909FD5F9D6385788A81DE5F72E56512EAD6BF5EACCA959CB6AF0DEF
|
||||
6435E07E5E952124B0F80F76E0F68265B8289087387E35C6D51831B299335480
|
||||
D7DE1F7748FB8BF90561151CC6AEADC160CA883FE5228768A3737A89F358AF58
|
||||
FA206F860C6F981FD4A358FDEA5E1860353406D8416FF2A811D17EBA09C803EA
|
||||
F2F7B2C6705D1457315F2AAA859AB53592241D63B84C045BC742D220BA110144
|
||||
3F0E05E572D1DF5E2B0BBB20EF8F3EB4D198CDF2794F86089E1DB0EF975E9337
|
||||
7D54D088C22AA3BA9A97FAB64371B8D512CDEC2A4355116BE2B74BCEC7FEC852
|
||||
0FD951F13E19F0FC1A25655DA430640034BE34659C526238E62B6042691998CB
|
||||
FCA04B0BF98FA89AAEF41A78AE7141EF7783E0D0CBAAB1B6F00C0AD3EAA84A54759D46E1A9BEEDCCE68BA12902802111
|
||||
6AD801CE08D58A380B689574BD7FCACC5DF768BDD93AD7EE1AA514A2351EF13A
|
||||
0A820F47699AFC4A5E3285BF521771FC5B6C5FB7C6C08A1990DA3B3A6766E860
|
||||
A7AAC90972DB24D20B57DDD46DC2624FC6169D529426E64B0544AC383799BB2A
|
||||
AF6088873BC71ED672FA39D50B386523825218C43CDB35D691B0C5895B7EF5C2
|
||||
774DFAC8D285241368CB377DA947D7A94951A1520017DF77FE2E6A517D5C6A1FC768BB1E2398F5AF71B10D1806C04CCD
|
||||
AA788A707E64C40E2A0EB8154FE795EAC68B936FD6BAC5DEF7677A4D5FE344DD
|
||||
A193EF5D1B223B0FA3C231052EDBDD7A31B0C192BCD8E7E37E11D4D899476ACD
|
||||
F6986E08949122D46BFA7F218B089E8DB00DCFA6971C5F2468CDDD179E5BBC40
|
||||
EDC23A07689EF6229081D1AB9E249E68527BD33EB72C242BA97727E64AF15BCC
|
||||
70CC64359A2A5DE40D5A30E916DE6532BCC511E7489CD3A2E5DEC269D303FDBD83B7EA14BF13B40E3C960C6D3D12774B
|
||||
IV: 2A 34 F1 5A AF 6F F5 1A 83 A6 1E DA DE B7 3D F1
|
||||
06B63DF24AE073A2F2B19C55CA9E8364FBECD26E49C551990153F6513BDE5267
|
||||
6139C78C74C341EB7474085CF1D047BD6FB005F80A72AF3EF3F89D58EF5DF500
|
||||
D85F11689020ECA47FBE9C2B67EE41A81E1F06DE2A35AF958965E3ECE29EA701
|
||||
1AE9073A42FE0E439544FE6945F6B33CF15A7A4A279020B5E0B3BE33FD189A7E
|
||||
E161F007854BB33E0056FA09A2E2DEE66789B5C87C8D6D3DE2C8C1BD2B48983EB9D1C5697CA6E95996918F7C47B761B0
|
||||
59AE4644DCB3D720C38B5115F230DA58E7BE0A697907F6174BB05AB7886ACDB1
|
||||
634DF0BCC185C4C1F7E1B1594B4438D051ABAE092433078963063B51D961D08C
|
||||
1EBEBCB49E498B9BE977D53EC21B9A546155B627737BD0AA832D496035729346
|
||||
4DFA93E639197772D57E8ACE04512CEFC045B8CC965C175A25ED525B630CBB63
|
||||
C2D5235D1014A319B249EAE8A5EE350F18D5AB8A498EF222704BD4EB1435F388
|
||||
F66D1937160E1392197F463A52E87FCE938A92070892113443C348D7553327A5715CF615CE2F2C96284F47759E043419
|
||||
841D29E7CBE040188E2283BFBA9F26EF2F65CCB085B56C3515E8C46C3F20BD75BAA963550869435FDAF509CEEE66A2C4
|
||||
7D87E24487D307635E7A17B989B8547EE11F3BF3468D055F0B44633B631BA42C
|
||||
B4916043973501B95A82B329196D6EBA69FBBC3AF8FD914583104E0E18CE82F6
|
||||
E4649F9C2A5465D2EA6F3E9724DD06CD6962FE2BAEB14F1453C14D1559232AE1
|
||||
96E15D890DF7FD348441F5E429A875754C6BF0520A787F8E9D8C5415674783CC
|
||||
CB52005EDED47B57F795BC92FB0522EAB18D23EE028B8D10ED57828C250EB285BFEC6E4A4BE8DABCE0D57ECAA20D90C3
|
||||
8E5A50C7D5C374445E88752301D20F0B3D6E4988B61D90FD63779B0EDEF9C60D
|
||||
49D6CB276A0E5FF134A38062503F01351F44CD6455708B50B5F07D03FC477C33
|
||||
CB45B56613DF208E79E4E10A6510F07DC1AA49210C7B94E8BBAECD2C35EC6ABC99FB10FD7C96DD6BB6A6685E9FAD93FB
|
||||
0743F3CC51200F763C242F1956B4D775C092ADF1A5C19ACAE96EB60C2990CF214F8FEA8FC6749286F6BDAB67657C479A
|
||||
E5608B28A058787D64A145F0362DEFD98CAE0B5A0F22C6DA7C6D278C7B5F95E3
|
||||
D4C113D43E7FB6D2EFA9E87471AA76A61B26872607B4AF5B87F9D72113835CE6
|
||||
2DC502800BFD21B76126390CA64A08C5432A2254E822F214CDE1EA11430084C5
|
||||
CA22C73010B0F1CB8009601BE2AF0B3674D83D5880E4A26C2A3FF0EA0A098CEA
|
||||
E53B2B102FDB000E9BB747F957156976E5A0C0E3898AA844C13AE8A9CEE7013B
|
||||
95CF1A46FFC252BE92919531C92BF6A3AA1B16C170DF4461EC54BE07A55C2387
|
||||
2EC7E24090F6DFFF6F2F2D8874D2F36AA769995F31F29FBE3B0EA6A16C3EE833
|
||||
C1145B1D9AC70761EA902B86455C1BE1BB1153552A1F7327411DECABE538827B
|
||||
18D596CADD2EE544200A58716C7A4690B658E58CC2B97334740F70894A6C90FA
|
||||
6A2F8859DFF01E13AC6C5300AD4A2218810FC91A6FB64A560E99FE6C99226AD2
|
||||
48D2EB5A08E35AF89A3B7A1CFDEE829FC0C2DDD2E965F4E3D043B0B14CB7825E
|
||||
91039325D53CDD0236D1CD13047973A013C14B45A32DE0784A73BFABCEAFBCD1
|
||||
51B4EAC87C4DC49B007F40D38B8166C388A1AF25E8D2FF6598E8EDE8726E6E14AD88443114D2A0F5E7721E304F3870DA
|
||||
3A179DDF65B9868CD84C7C04931F40D5D204C97B20DCBF1A70C241E59BFD7F14
|
||||
AF538FD16104DCAF03F4DDF05026D6741898DFC247E48A8F72E652DDF2DFD289
|
||||
E67F16AEC9D84B6C06F77B806CA6FBC7618BFBECD0D7A04EC3AE1D1DD06BEC5B
|
||||
FA4D9F8920EBF2F4293C6D4E99083AA4A71A9DDFFDB07EEBDC552DACEC4DA24A
|
||||
5BF23E630AC81E2CD533803E225BCB3C481B8D650A9858CF2B5219BAE1CDA01A
|
||||
17B57E8C1032481E69247EA9A0C9EA41F6C0EA9B3F11170CA69C0842423F0455
|
||||
96EA848B8527A647DC9DACDB16C5D92B0081EB1CD77B99B47F56C2E249190BD3BE4306333F37487133DD3AD8E57F3092
|
||||
B0E9411274D799BE5989D52E74E00DE310CCA2BD47D7A8FA554D66BB04CD787A
|
||||
D0D28476E3D8832975653D93F545C35278EC1F0B7AD70CA2F36EB476CC207937
|
||||
933195E37014619F997B73F5CF4C0110865A822CA8CB0ED1D977D49A1B06A37F
|
||||
E790CAC2A26452BF941A9E1BABF0A85598EA1CC8F8CFED637C9B40D5E027B518
|
||||
49C1F179ABA5BD4F2C45257A33701730E9CC4728677EFF07808ABE31D3CE6FD5C805F43EA5ABB7261B220C82F0794092
|
||||
|
||||
@ -64,7 +64,7 @@ class AppState:
|
||||
|
||||
def is_loaded_in_gdb(self, gdb_app) -> bool:
|
||||
# Avoid constructing full app wrapper for comparison
|
||||
return self.entry_address == int(gdb_app["entry"])
|
||||
return self.entry_address == int(gdb_app["state"]["entry"])
|
||||
|
||||
@staticmethod
|
||||
def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]:
|
||||
@ -78,13 +78,13 @@ class AppState:
|
||||
@staticmethod
|
||||
def from_gdb(gdb_app: "AppState") -> "AppState":
|
||||
state = AppState(str(gdb_app["manifest"]["name"].string()))
|
||||
state.entry_address = int(gdb_app["entry"])
|
||||
state.entry_address = int(gdb_app["state"]["entry"])
|
||||
|
||||
app_state = gdb_app["state"]
|
||||
if debug_link_size := int(app_state["debug_link_size"]):
|
||||
if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]):
|
||||
debug_link_data = (
|
||||
gdb.selected_inferior()
|
||||
.read_memory(int(app_state["debug_link"]), debug_link_size)
|
||||
.read_memory(int(app_state["debug_link_info"]["debug_link"]), debug_link_size)
|
||||
.tobytes()
|
||||
)
|
||||
state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data(
|
||||
|
||||
@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config {
|
||||
# assignment
|
||||
mmw 0xE0042004 0x00000020 0
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event gdb-detach {
|
||||
resume
|
||||
}
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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}",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
|
@ -48,5 +48,7 @@ SECTIONS
|
||||
{
|
||||
*(.comment)
|
||||
*(.comment.*)
|
||||
*(.llvmbc)
|
||||
*(.llvmcmd)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
@ -236,3 +237,63 @@ void furi_hal_clock_suspend_tick() {
|
||||
void furi_hal_clock_resume_tick() {
|
||||
SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk);
|
||||
}
|
||||
|
||||
void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div) {
|
||||
if(source == FuriHalClockMcoLse) {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_LSE, div);
|
||||
} else if(source == FuriHalClockMcoSysclk) {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, div);
|
||||
} else {
|
||||
LL_RCC_MSI_Enable();
|
||||
while(LL_RCC_MSI_IsReady() != 1)
|
||||
;
|
||||
switch(source) {
|
||||
case FuriHalClockMcoMsi100k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0);
|
||||
break;
|
||||
case FuriHalClockMcoMsi200k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1);
|
||||
break;
|
||||
case FuriHalClockMcoMsi400k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_2);
|
||||
break;
|
||||
case FuriHalClockMcoMsi800k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_3);
|
||||
break;
|
||||
case FuriHalClockMcoMsi1m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4);
|
||||
break;
|
||||
case FuriHalClockMcoMsi2m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_5);
|
||||
break;
|
||||
case FuriHalClockMcoMsi4m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
|
||||
break;
|
||||
case FuriHalClockMcoMsi8m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_7);
|
||||
break;
|
||||
case FuriHalClockMcoMsi16m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_8);
|
||||
break;
|
||||
case FuriHalClockMcoMsi24m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_9);
|
||||
break;
|
||||
case FuriHalClockMcoMsi32m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_10);
|
||||
break;
|
||||
case FuriHalClockMcoMsi48m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_MSI, div);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_clock_mco_disable() {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, FuriHalClockMcoDiv1);
|
||||
LL_RCC_MSI_Disable();
|
||||
while(LL_RCC_MSI_IsReady() != 0)
|
||||
;
|
||||
}
|
||||
@ -4,6 +4,33 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
|
||||
typedef enum {
|
||||
FuriHalClockMcoLse,
|
||||
FuriHalClockMcoSysclk,
|
||||
FuriHalClockMcoMsi100k,
|
||||
FuriHalClockMcoMsi200k,
|
||||
FuriHalClockMcoMsi400k,
|
||||
FuriHalClockMcoMsi800k,
|
||||
FuriHalClockMcoMsi1m,
|
||||
FuriHalClockMcoMsi2m,
|
||||
FuriHalClockMcoMsi4m,
|
||||
FuriHalClockMcoMsi8m,
|
||||
FuriHalClockMcoMsi16m,
|
||||
FuriHalClockMcoMsi24m,
|
||||
FuriHalClockMcoMsi32m,
|
||||
FuriHalClockMcoMsi48m,
|
||||
} FuriHalClockMcoSourceId;
|
||||
|
||||
typedef enum {
|
||||
FuriHalClockMcoDiv1 = LL_RCC_MCO1_DIV_1,
|
||||
FuriHalClockMcoDiv2 = LL_RCC_MCO1_DIV_2,
|
||||
FuriHalClockMcoDiv4 = LL_RCC_MCO1_DIV_4,
|
||||
FuriHalClockMcoDiv8 = LL_RCC_MCO1_DIV_8,
|
||||
FuriHalClockMcoDiv16 = LL_RCC_MCO1_DIV_16,
|
||||
} FuriHalClockMcoDivisorId;
|
||||
|
||||
/** Early initialization */
|
||||
void furi_hal_clock_init_early();
|
||||
|
||||
@ -25,6 +52,16 @@ void furi_hal_clock_suspend_tick();
|
||||
/** Continue SysTick counter operation */
|
||||
void furi_hal_clock_resume_tick();
|
||||
|
||||
/** Enable clock output on MCO pin
|
||||
*
|
||||
* @param source MCO clock source
|
||||
* @param div MCO clock division
|
||||
*/
|
||||
void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div);
|
||||
|
||||
/** Disable clock output on MCO pin */
|
||||
void furi_hal_clock_mco_disable();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -6,6 +6,9 @@ void furi_hal_cortex_init_early() {
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
|
||||
/* Enable instruction prefetch */
|
||||
SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN);
|
||||
}
|
||||
|
||||
void furi_hal_cortex_delay_us(uint32_t microseconds) {
|
||||
|
||||
@ -21,7 +21,6 @@
|
||||
(FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \
|
||||
FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR)
|
||||
|
||||
//#define FURI_HAL_FLASH_OB_START_ADDRESS 0x1FFF8000
|
||||
#define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B
|
||||
#define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F
|
||||
#define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2))
|
||||
@ -80,9 +79,13 @@ size_t furi_hal_flash_get_free_page_count() {
|
||||
}
|
||||
|
||||
void furi_hal_flash_init() {
|
||||
// Errata 2.2.9, Flash OPTVERR flag is always set after system reset
|
||||
WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR);
|
||||
//__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
|
||||
/* Errata 2.2.9, Flash OPTVERR flag is always set after system reset */
|
||||
// WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR);
|
||||
/* Actually, reset all error flags on start */
|
||||
if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) {
|
||||
FURI_LOG_E(TAG, "FLASH->SR 0x%08X", FLASH->SR);
|
||||
WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_flash_unlock() {
|
||||
@ -91,6 +94,7 @@ static void furi_hal_flash_unlock() {
|
||||
|
||||
/* Authorize the FLASH Registers access */
|
||||
WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1);
|
||||
__ISB();
|
||||
WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2);
|
||||
|
||||
/* verify Flash is unlocked */
|
||||
@ -110,38 +114,38 @@ static void furi_hal_flash_lock(void) {
|
||||
}
|
||||
|
||||
static void furi_hal_flash_begin_with_core2(bool erase_flag) {
|
||||
// Take flash controller ownership
|
||||
/* Take flash controller ownership */
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID) != 0) {
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
// Unlock flash operation
|
||||
/* Unlock flash operation */
|
||||
furi_hal_flash_unlock();
|
||||
|
||||
// Erase activity notification
|
||||
/* Erase activity notification */
|
||||
if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);
|
||||
|
||||
// 64mHz 5us core2 flag protection
|
||||
/* 64mHz 5us core2 flag protection */
|
||||
for(volatile uint32_t i = 0; i < 35; i++)
|
||||
;
|
||||
|
||||
while(true) {
|
||||
// Wait till flash controller become usable
|
||||
/* Wait till flash controller become usable */
|
||||
while(LL_FLASH_IsActiveFlag_OperationSuspended()) {
|
||||
furi_thread_yield();
|
||||
};
|
||||
|
||||
// Just a little more love
|
||||
/* Just a little more love */
|
||||
taskENTER_CRITICAL();
|
||||
|
||||
// Actually we already have mutex for it, but specification is specification
|
||||
/* Actually we already have mutex for it, but specification is specification */
|
||||
if(LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) {
|
||||
taskEXIT_CRITICAL();
|
||||
furi_thread_yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Take sempahopre and prevent core2 from anything funky
|
||||
/* Take sempahopre and prevent core2 from anything funky */
|
||||
if(LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != 0) {
|
||||
taskEXIT_CRITICAL();
|
||||
furi_thread_yield();
|
||||
@ -153,10 +157,10 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) {
|
||||
}
|
||||
|
||||
static void furi_hal_flash_begin(bool erase_flag) {
|
||||
// Acquire dangerous ops mutex
|
||||
/* Acquire dangerous ops mutex */
|
||||
furi_hal_bt_lock_core2();
|
||||
|
||||
// If Core2 is running use IPC locking
|
||||
/* If Core2 is running use IPC locking */
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
furi_hal_flash_begin_with_core2(erase_flag);
|
||||
} else {
|
||||
@ -165,36 +169,36 @@ static void furi_hal_flash_begin(bool erase_flag) {
|
||||
}
|
||||
|
||||
static void furi_hal_flash_end_with_core2(bool erase_flag) {
|
||||
// Funky ops are ok at this point
|
||||
/* Funky ops are ok at this point */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0);
|
||||
|
||||
// Task switching is ok
|
||||
/* Task switching is ok */
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
// Doesn't make much sense, does it?
|
||||
/* Doesn't make much sense, does it? */
|
||||
while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) {
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
// Erase activity over, core2 can continue
|
||||
/* Erase activity over, core2 can continue */
|
||||
if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);
|
||||
|
||||
// Lock flash controller
|
||||
/* Lock flash controller */
|
||||
furi_hal_flash_lock();
|
||||
|
||||
// Release flash controller ownership
|
||||
/* Release flash controller ownership */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0);
|
||||
}
|
||||
|
||||
static void furi_hal_flash_end(bool erase_flag) {
|
||||
// If Core2 is running use IPC locking
|
||||
/* If Core2 is running - use IPC locking */
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
furi_hal_flash_end_with_core2(erase_flag);
|
||||
} else {
|
||||
furi_hal_flash_lock();
|
||||
}
|
||||
|
||||
// Release dangerous ops mutex
|
||||
/* Release dangerous ops mutex */
|
||||
furi_hal_bt_unlock_core2();
|
||||
}
|
||||
|
||||
@ -226,9 +230,9 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) {
|
||||
uint32_t error = 0;
|
||||
uint32_t countdown = 0;
|
||||
|
||||
// Wait for the FLASH operation to complete by polling on BUSY flag to be reset.
|
||||
// Even if the FLASH operation fails, the BUSY flag will be reset and an error
|
||||
// flag will be set
|
||||
/* Wait for the FLASH operation to complete by polling on BUSY flag to be reset.
|
||||
Even if the FLASH operation fails, the BUSY flag will be reset and an error
|
||||
flag will be set */
|
||||
countdown = timeout;
|
||||
while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) {
|
||||
if(LL_SYSTICK_IsActiveCounterFlag()) {
|
||||
@ -269,10 +273,10 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_flash_erase(uint8_t page) {
|
||||
void furi_hal_flash_erase(uint8_t page) {
|
||||
furi_hal_flash_begin(true);
|
||||
|
||||
// Ensure that controller state is valid
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
/* Verify that next operation can be proceed */
|
||||
@ -292,30 +296,31 @@ bool furi_hal_flash_erase(uint8_t page) {
|
||||
furi_hal_flush_cache();
|
||||
|
||||
furi_hal_flash_end(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) {
|
||||
static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) {
|
||||
/* Program first word */
|
||||
*(uint32_t*)address = (uint32_t)*data;
|
||||
|
||||
// Barrier to ensure programming is performed in 2 steps, in right order
|
||||
// (independently of compiler optimization behavior)
|
||||
/* Barrier to ensure programming is performed in 2 steps, in right order
|
||||
(independently of compiler optimization behavior) */
|
||||
__ISB();
|
||||
|
||||
/* Program second word */
|
||||
*(uint32_t*)(address + 4U) = (uint32_t)(*data >> 32U);
|
||||
}
|
||||
|
||||
static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) {
|
||||
furi_hal_flash_write_dword_internal_nowait(address, data);
|
||||
|
||||
/* Wait for last operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_flash_write_dword(size_t address, uint64_t data) {
|
||||
void furi_hal_flash_write_dword(size_t address, uint64_t data) {
|
||||
furi_hal_flash_begin(false);
|
||||
|
||||
// Ensure that controller state is valid
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
/* Check the parameters */
|
||||
@ -326,7 +331,7 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) {
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
|
||||
/* Do the thing */
|
||||
furi_check(furi_hal_flash_write_dword_internal(address, &data));
|
||||
furi_hal_flash_write_dword_internal(address, &data);
|
||||
|
||||
/* If the program operation is completed, disable the PG or FSTPG Bit */
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
@ -335,14 +340,13 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) {
|
||||
|
||||
/* Wait for last operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t furi_hal_flash_get_page_address(uint8_t page) {
|
||||
return furi_hal_flash_get_base() + page * FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
|
||||
bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) {
|
||||
void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) {
|
||||
uint16_t length = _length;
|
||||
furi_check(length <= FURI_HAL_FLASH_PAGE_SIZE);
|
||||
|
||||
@ -350,37 +354,63 @@ bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16
|
||||
|
||||
furi_hal_flash_begin(false);
|
||||
|
||||
// Ensure that controller state is valid
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
size_t page_start_address = furi_hal_flash_get_page_address(page);
|
||||
|
||||
/* Set PG bit */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
size_t i_dwords = 0;
|
||||
for(i_dwords = 0; i_dwords < (length / 8); ++i_dwords) {
|
||||
/* Do the thing */
|
||||
size_t data_offset = i_dwords * 8;
|
||||
furi_check(furi_hal_flash_write_dword_internal(
|
||||
page_start_address + data_offset, (uint64_t*)&data[data_offset]));
|
||||
size_t length_written = 0;
|
||||
|
||||
const uint16_t FAST_PROG_BLOCK_SIZE = 512;
|
||||
const uint8_t DWORD_PROG_BLOCK_SIZE = 8;
|
||||
|
||||
/* Write as much data as we can in fast mode */
|
||||
if(length >= FAST_PROG_BLOCK_SIZE) {
|
||||
taskENTER_CRITICAL();
|
||||
/* Enable fast flash programming mode */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_FSTPG);
|
||||
|
||||
while(length_written < (length / FAST_PROG_BLOCK_SIZE * FAST_PROG_BLOCK_SIZE)) {
|
||||
/* No context switch in the middle of the operation */
|
||||
furi_hal_flash_write_dword_internal_nowait(
|
||||
page_start_address + length_written, (uint64_t*)(data + length_written));
|
||||
length_written += DWORD_PROG_BLOCK_SIZE;
|
||||
|
||||
if((length_written % FAST_PROG_BLOCK_SIZE) == 0) {
|
||||
/* Wait for block operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
}
|
||||
}
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_FSTPG);
|
||||
taskEXIT_CRITICAL();
|
||||
}
|
||||
if((length % 8) != 0) {
|
||||
|
||||
/* Enable regular (dword) programming mode */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
if((length % FAST_PROG_BLOCK_SIZE) != 0) {
|
||||
/* Write tail in regular, dword mode */
|
||||
while(length_written < (length / DWORD_PROG_BLOCK_SIZE * DWORD_PROG_BLOCK_SIZE)) {
|
||||
furi_hal_flash_write_dword_internal(
|
||||
page_start_address + length_written, (uint64_t*)&data[length_written]);
|
||||
length_written += DWORD_PROG_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if((length % DWORD_PROG_BLOCK_SIZE) != 0) {
|
||||
/* there are more bytes, not fitting into dwords */
|
||||
uint64_t tail_data = 0;
|
||||
size_t data_offset = i_dwords * 8;
|
||||
for(int32_t tail_i = 0; tail_i < (length % 8); ++tail_i) {
|
||||
tail_data |= (((uint64_t)data[data_offset + tail_i]) << (tail_i * 8));
|
||||
for(int32_t tail_i = 0; tail_i < (length % DWORD_PROG_BLOCK_SIZE); ++tail_i) {
|
||||
tail_data |= (((uint64_t)data[length_written + tail_i]) << (tail_i * 8));
|
||||
}
|
||||
|
||||
furi_check(
|
||||
furi_hal_flash_write_dword_internal(page_start_address + data_offset, &tail_data));
|
||||
furi_hal_flash_write_dword_internal(page_start_address + length_written, &tail_data);
|
||||
}
|
||||
|
||||
/* If the program operation is completed, disable the PG or FSTPG Bit */
|
||||
/* Disable the PG Bit */
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
|
||||
furi_hal_flash_end(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t furi_hal_flash_get_page_number(size_t address) {
|
||||
@ -462,6 +492,7 @@ static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_T
|
||||
OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)),
|
||||
};
|
||||
#undef OB_REG_DEF
|
||||
|
||||
void furi_hal_flash_ob_apply() {
|
||||
furi_hal_flash_ob_unlock();
|
||||
|
||||
@ -90,10 +90,8 @@ size_t furi_hal_flash_get_free_page_count();
|
||||
* @warning locking operation with critical section, stalls execution
|
||||
*
|
||||
* @param page The page to erase
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool furi_hal_flash_erase(uint8_t page);
|
||||
void furi_hal_flash_erase(uint8_t page);
|
||||
|
||||
/** Write double word (64 bits)
|
||||
*
|
||||
@ -101,10 +99,8 @@ bool furi_hal_flash_erase(uint8_t page);
|
||||
*
|
||||
* @param address destination address, must be double word aligned.
|
||||
* @param data data to write
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool furi_hal_flash_write_dword(size_t address, uint64_t data);
|
||||
void furi_hal_flash_write_dword(size_t address, uint64_t data);
|
||||
|
||||
/** Write aligned page data (up to page size)
|
||||
*
|
||||
@ -113,10 +109,8 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data);
|
||||
* @param address destination address, must be page aligned.
|
||||
* @param data data to write
|
||||
* @param length data length
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length);
|
||||
void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length);
|
||||
|
||||
/** Get flash page number for address
|
||||
*
|
||||
|
||||
@ -266,6 +266,13 @@ bool furi_hal_power_is_charging() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_power_is_charging_done() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bool ret = bq25896_is_charging_done(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_shutdown() {
|
||||
furi_hal_power_insomnia_enter();
|
||||
|
||||
|
||||
138
firmware/targets/f7/furi_hal/furi_hal_pwm.c
Normal file
138
firmware/targets/f7/furi_hal/furi_hal_pwm.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
firmware/targets/f7/furi_hal/furi_hal_pwm.h
Normal file
42
firmware/targets/f7/furi_hal/furi_hal_pwm.h
Normal 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
|
||||
@ -85,6 +85,12 @@ uint8_t furi_hal_power_get_bat_health_pct();
|
||||
*/
|
||||
bool furi_hal_power_is_charging();
|
||||
|
||||
/** Get charge complete status
|
||||
*
|
||||
* @return true if done charging and connected to charger
|
||||
*/
|
||||
bool furi_hal_power_is_charging_done();
|
||||
|
||||
/** Switch MCU to SHUTDOWN */
|
||||
void furi_hal_power_shutdown();
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include <m-string.h>
|
||||
#include <m-dict.h>
|
||||
#include <toolbox/m_cstr_dup.h>
|
||||
|
||||
#define FURI_RECORD_FLAG_READY (0x1)
|
||||
|
||||
@ -15,7 +16,7 @@ typedef struct {
|
||||
size_t holders_count;
|
||||
} FuriRecordData;
|
||||
|
||||
DICT_DEF2(FuriRecordDataDict, string_t, STRING_OPLIST, FuriRecordData, M_POD_OPLIST)
|
||||
DICT_DEF2(FuriRecordDataDict, const char*, M_CSTR_DUP_OPLIST, FuriRecordData, M_POD_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
@ -24,6 +25,19 @@ typedef struct {
|
||||
|
||||
static FuriRecord* furi_record = NULL;
|
||||
|
||||
static FuriRecordData* furi_record_get(const char* name) {
|
||||
return FuriRecordDataDict_get(furi_record->records, name);
|
||||
}
|
||||
|
||||
static void furi_record_put(const char* name, FuriRecordData* record_data) {
|
||||
FuriRecordDataDict_set_at(furi_record->records, name, *record_data);
|
||||
}
|
||||
|
||||
static void furi_record_erase(const char* name, FuriRecordData* record_data) {
|
||||
furi_event_flag_free(record_data->flags);
|
||||
FuriRecordDataDict_erase(furi_record->records, name);
|
||||
}
|
||||
|
||||
void furi_record_init() {
|
||||
furi_record = malloc(sizeof(FuriRecord));
|
||||
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
@ -31,16 +45,16 @@ void furi_record_init() {
|
||||
FuriRecordDataDict_init(furi_record->records);
|
||||
}
|
||||
|
||||
static FuriRecordData* furi_record_data_get_or_create(string_t name_str) {
|
||||
static FuriRecordData* furi_record_data_get_or_create(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
if(!record_data) {
|
||||
FuriRecordData new_record;
|
||||
new_record.flags = furi_event_flag_alloc();
|
||||
new_record.data = NULL;
|
||||
new_record.holders_count = 0;
|
||||
FuriRecordDataDict_set_at(furi_record->records, name_str, new_record);
|
||||
record_data = FuriRecordDataDict_get(furi_record->records, name_str);
|
||||
furi_record_put(name, &new_record);
|
||||
record_data = furi_record_get(name);
|
||||
}
|
||||
return record_data;
|
||||
}
|
||||
@ -59,35 +73,25 @@ bool furi_record_exists(const char* name) {
|
||||
|
||||
bool ret = false;
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_record_lock();
|
||||
ret = (FuriRecordDataDict_get(furi_record->records, name_str) != NULL);
|
||||
ret = (furi_record_get(name) != NULL);
|
||||
furi_record_unlock();
|
||||
|
||||
string_clear(name_str);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_record_create(const char* name, void* data) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
// Get record data and fill it
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name_str);
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name);
|
||||
furi_assert(record_data->data == NULL);
|
||||
record_data->data = data;
|
||||
furi_event_flag_set(record_data->flags, FURI_RECORD_FLAG_READY);
|
||||
|
||||
furi_record_unlock();
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
||||
|
||||
bool furi_record_destroy(const char* name) {
|
||||
@ -95,35 +99,26 @@ bool furi_record_destroy(const char* name) {
|
||||
|
||||
bool ret = false;
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
furi_assert(record_data);
|
||||
if(record_data->holders_count == 0) {
|
||||
furi_event_flag_free(record_data->flags);
|
||||
FuriRecordDataDict_erase(furi_record->records, name_str);
|
||||
furi_record_erase(name, record_data);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
furi_record_unlock();
|
||||
|
||||
string_clear(name_str);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* furi_record_open(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name_str);
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name);
|
||||
record_data->holders_count++;
|
||||
|
||||
furi_record_unlock();
|
||||
@ -136,24 +131,17 @@ void* furi_record_open(const char* name) {
|
||||
FuriFlagWaitAny | FuriFlagNoClear,
|
||||
FuriWaitForever) == FURI_RECORD_FLAG_READY);
|
||||
|
||||
string_clear(name_str);
|
||||
|
||||
return record_data->data;
|
||||
}
|
||||
|
||||
void furi_record_close(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
furi_assert(record_data);
|
||||
record_data->holders_count--;
|
||||
|
||||
furi_record_unlock();
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
||||
|
||||
@ -85,7 +85,6 @@ static void furi_thread_body(void* context) {
|
||||
}
|
||||
|
||||
furi_assert(thread->state == FuriThreadStateRunning);
|
||||
furi_thread_set_state(thread, FuriThreadStateStopped);
|
||||
|
||||
if(thread->is_service) {
|
||||
FURI_LOG_E(
|
||||
@ -99,7 +98,10 @@ static void furi_thread_body(void* context) {
|
||||
furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL);
|
||||
vTaskSetThreadLocalStoragePointer(NULL, 0, NULL);
|
||||
|
||||
vTaskDelete(thread->task_handle);
|
||||
// from here we can't use thread pointer
|
||||
furi_thread_set_state(thread, FuriThreadStateStopped);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
furi_thread_catch();
|
||||
}
|
||||
|
||||
@ -205,11 +207,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) {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
#include "bq25896.h"
|
||||
#include "bq25896_reg.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@ -81,7 +80,7 @@ void bq25896_poweroff(FuriHalI2cBusHandle* handle) {
|
||||
handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT);
|
||||
}
|
||||
|
||||
bool bq25896_is_charging(FuriHalI2cBusHandle* handle) {
|
||||
ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) {
|
||||
furi_hal_i2c_read_mem(
|
||||
handle,
|
||||
BQ25896_ADDRESS,
|
||||
@ -91,7 +90,16 @@ bool bq25896_is_charging(FuriHalI2cBusHandle* handle) {
|
||||
BQ25896_I2C_TIMEOUT);
|
||||
furi_hal_i2c_read_reg_8(
|
||||
handle, BQ25896_ADDRESS, 0x0B, (uint8_t*)&bq25896_regs.r0B, BQ25896_I2C_TIMEOUT);
|
||||
return bq25896_regs.r0B.CHRG_STAT != ChrgStatNo;
|
||||
return bq25896_regs.r0B.CHRG_STAT;
|
||||
}
|
||||
|
||||
bool bq25896_is_charging(FuriHalI2cBusHandle* handle) {
|
||||
// Include precharge, fast charging, and charging termination done as "charging"
|
||||
return bq25896_get_charge_status(handle) != ChrgStatNo;
|
||||
}
|
||||
|
||||
bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) {
|
||||
return bq25896_get_charge_status(handle) == ChrgStatDone;
|
||||
}
|
||||
|
||||
void bq25896_enable_charging(FuriHalI2cBusHandle* handle) {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "bq25896_reg.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_i2c.h>
|
||||
@ -10,9 +12,15 @@ void bq25896_init(FuriHalI2cBusHandle* handle);
|
||||
/** Send device into shipping mode */
|
||||
void bq25896_poweroff(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Get charging status */
|
||||
ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Is currently charging */
|
||||
bool bq25896_is_charging(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Is charging completed while connected to charger */
|
||||
bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Enable charging */
|
||||
void bq25896_enable_charging(FuriHalI2cBusHandle* handle);
|
||||
|
||||
|
||||
21
lib/flipper_application/application_manifest.c
Normal file
21
lib/flipper_application/application_manifest.c
Normal 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;
|
||||
}
|
||||
@ -1,6 +1,12 @@
|
||||
/**
|
||||
* @file application_manifest.h
|
||||
* Flipper application manifest
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "elf/elf_api_interface.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -40,6 +46,25 @@ typedef FlipperApplicationManifestV1 FlipperApplicationManifest;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* @brief Check if manifest is valid
|
||||
*
|
||||
* @param manifest
|
||||
* @return bool
|
||||
*/
|
||||
bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest);
|
||||
|
||||
/**
|
||||
* @brief Check if manifest is compatible with current ELF API interface
|
||||
*
|
||||
* @param manifest
|
||||
* @param api_interface
|
||||
* @return bool
|
||||
*/
|
||||
bool flipper_application_manifest_is_compatible(
|
||||
const FlipperApplicationManifest* manifest,
|
||||
const ElfApiInterface* api_interface);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1116,6 +1116,8 @@ typedef struct {
|
||||
#define R_ARM_LDR_SBREL_11_0 35
|
||||
#define R_ARM_ALU_SBREL_19_12 36
|
||||
#define R_ARM_ALU_SBREL_27_20 37
|
||||
#define R_ARM_THM_MOVW_ABS_NC 47 /* Direct 16 bit (Thumb32 MOVW) */
|
||||
#define R_ARM_THM_MOVT_ABS 48 /* Direct high 16 bit */
|
||||
#define R_ARM_GNU_VTENTRY 100
|
||||
#define R_ARM_GNU_VTINHERIT 101
|
||||
#define R_ARM_THM_PC11 102 /* thumb unconditional branch */
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/elf/elf.h>
|
||||
#include <elf.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define ELF_INVALID_ADDRESS 0xFFFFFFFF
|
||||
|
||||
835
lib/flipper_application/elf/elf_file.c
Normal file
835
lib/flipper_application/elf/elf_file.c
Normal 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, §ion);
|
||||
section_p = elf_file_get_section(elf, name);
|
||||
}
|
||||
|
||||
if(lookup_sections[i].type == SectionTypeRelData) {
|
||||
section_p->rel_sec_idx = section_idx;
|
||||
} else {
|
||||
section_p->sec_idx = section_idx;
|
||||
}
|
||||
|
||||
return lookup_sections[i].type;
|
||||
}
|
||||
}
|
||||
|
||||
if(strcmp(name, ".symtab") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .symtab section");
|
||||
elf->symbol_table = section_header->sh_offset;
|
||||
elf->symbol_count = section_header->sh_size / sizeof(Elf32_Sym);
|
||||
return SectionTypeSymTab;
|
||||
} else if(strcmp(name, ".strtab") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .strtab section");
|
||||
elf->symbol_table_strings = section_header->sh_offset;
|
||||
return SectionTypeStrTab;
|
||||
} else if(strcmp(name, ".fapmeta") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .fapmeta section");
|
||||
if(elf_load_metadata(elf, section_header, manifest)) {
|
||||
return SectionTypeManifest;
|
||||
} else {
|
||||
return SectionTypeERROR;
|
||||
}
|
||||
} else if(strcmp(name, ".gnu_debuglink") == 0) {
|
||||
FURI_LOG_D(TAG, "Found .gnu_debuglink section");
|
||||
if(elf_load_debug_link(elf, section_header)) {
|
||||
return SectionTypeDebugLink;
|
||||
} else {
|
||||
return SectionTypeERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return SectionTypeUnused;
|
||||
}
|
||||
|
||||
static bool elf_load_section_data(ELFFile* elf, ELFSection* section) {
|
||||
Elf32_Shdr section_header;
|
||||
if(section->sec_idx == 0) {
|
||||
FURI_LOG_D(TAG, "Section is not present");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!elf_read_section_header(elf, section->sec_idx, §ion_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(section_header.sh_size == 0) {
|
||||
FURI_LOG_D(TAG, "No data for section");
|
||||
return true;
|
||||
}
|
||||
|
||||
section->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign);
|
||||
section->size = section_header.sh_size;
|
||||
|
||||
if(section_header.sh_type == SHT_NOBITS) {
|
||||
/* section is empty (.bss?) */
|
||||
/* no need to memset - allocator already did that */
|
||||
return true;
|
||||
}
|
||||
|
||||
if((!storage_file_seek(elf->fd, section_header.sh_offset, true)) ||
|
||||
(storage_file_read(elf->fd, section->data, section_header.sh_size) !=
|
||||
section_header.sh_size)) {
|
||||
FURI_LOG_E(TAG, " seek/read fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "0x%X", section->data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool elf_relocate_section(ELFFile* elf, ELFSection* section) {
|
||||
Elf32_Shdr section_header;
|
||||
if(section->rel_sec_idx) {
|
||||
FURI_LOG_D(TAG, "Relocating section");
|
||||
if(elf_read_section_header(elf, section->rel_sec_idx, §ion_header))
|
||||
return elf_relocate(elf, §ion_header, section);
|
||||
else {
|
||||
FURI_LOG_E(TAG, "Error reading section header");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "No relocation index"); /* Not an error */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void elf_file_call_section_list(ELFFile* elf, const char* name, bool reverse_order) {
|
||||
ELFSection* section = elf_file_get_section(elf, name);
|
||||
|
||||
if(section && section->size) {
|
||||
const uint32_t* start = section->data;
|
||||
const uint32_t* end = section->data + section->size;
|
||||
|
||||
if(reverse_order) {
|
||||
while(end > start) {
|
||||
end--;
|
||||
((void (*)(void))(*end))();
|
||||
}
|
||||
} else {
|
||||
while(start < end) {
|
||||
((void (*)(void))(*start))();
|
||||
start++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************************************/
|
||||
/********************************************* Public *********************************************/
|
||||
/**************************************************************************************************/
|
||||
|
||||
ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) {
|
||||
ELFFile* elf = malloc(sizeof(ELFFile));
|
||||
elf->fd = storage_file_alloc(storage);
|
||||
elf->api_interface = api_interface;
|
||||
ELFSectionDict_init(elf->sections);
|
||||
AddressCache_init(elf->trampoline_cache);
|
||||
return elf;
|
||||
}
|
||||
|
||||
void elf_file_free(ELFFile* elf) {
|
||||
// free sections data
|
||||
{
|
||||
ELFSectionDict_it_t it;
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
|
||||
ELFSectionDict_next(it)) {
|
||||
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
|
||||
if(itref->value.data) {
|
||||
aligned_free(itref->value.data);
|
||||
}
|
||||
free((void*)itref->key);
|
||||
}
|
||||
|
||||
ELFSectionDict_clear(elf->sections);
|
||||
}
|
||||
|
||||
// free trampoline data
|
||||
{
|
||||
AddressCache_it_t it;
|
||||
for(AddressCache_it(it, elf->trampoline_cache); !AddressCache_end_p(it);
|
||||
AddressCache_next(it)) {
|
||||
const AddressCache_itref_t* itref = AddressCache_cref(it);
|
||||
free((void*)itref->value);
|
||||
}
|
||||
|
||||
AddressCache_clear(elf->trampoline_cache);
|
||||
}
|
||||
|
||||
if(elf->debug_link_info.debug_link) {
|
||||
free(elf->debug_link_info.debug_link);
|
||||
}
|
||||
|
||||
storage_file_free(elf->fd);
|
||||
free(elf);
|
||||
}
|
||||
|
||||
bool elf_file_open(ELFFile* elf, const char* path) {
|
||||
Elf32_Ehdr h;
|
||||
Elf32_Shdr sH;
|
||||
|
||||
if(!storage_file_open(elf->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) ||
|
||||
!storage_file_seek(elf->fd, 0, true) ||
|
||||
storage_file_read(elf->fd, &h, sizeof(h)) != sizeof(h) ||
|
||||
!storage_file_seek(elf->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) ||
|
||||
storage_file_read(elf->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elf->entry = h.e_entry;
|
||||
elf->sections_count = h.e_shnum;
|
||||
elf->section_table = h.e_shoff;
|
||||
elf->section_table_strings = sH.sh_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) {
|
||||
bool result = false;
|
||||
string_t name;
|
||||
string_init(name);
|
||||
|
||||
FURI_LOG_D(TAG, "Looking for manifest section");
|
||||
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
|
||||
Elf32_Shdr section_header;
|
||||
|
||||
string_reset(name);
|
||||
if(!elf_read_section(elf, section_idx, §ion_header, name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp(name, ".fapmeta") == 0) {
|
||||
if(elf_load_metadata(elf, §ion_header, manifest)) {
|
||||
FURI_LOG_D(TAG, "Load manifest done");
|
||||
result = true;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) {
|
||||
SectionType loaded_sections = SectionTypeERROR;
|
||||
string_t name;
|
||||
string_init(name);
|
||||
|
||||
FURI_LOG_D(TAG, "Scan ELF indexs...");
|
||||
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
|
||||
Elf32_Shdr section_header;
|
||||
|
||||
string_reset(name);
|
||||
if(!elf_read_section(elf, section_idx, §ion_header, name)) {
|
||||
loaded_sections = SectionTypeERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Preloading data for section #%d %s", section_idx, string_get_cstr(name));
|
||||
SectionType section_type =
|
||||
elf_preload_section(elf, section_idx, §ion_header, name, manifest);
|
||||
loaded_sections |= section_type;
|
||||
|
||||
if(section_type == SectionTypeERROR) {
|
||||
loaded_sections = SectionTypeERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(name);
|
||||
FURI_LOG_D(TAG, "Load symbols done");
|
||||
|
||||
return IS_FLAGS_SET(loaded_sections, SectionTypeValid);
|
||||
}
|
||||
|
||||
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) {
|
||||
ELFFileLoadStatus status = ELFFileLoadStatusSuccess;
|
||||
ELFSectionDict_it_t it;
|
||||
|
||||
AddressCache_init(elf->relocation_cache);
|
||||
size_t start = furi_get_tick();
|
||||
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
|
||||
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
|
||||
FURI_LOG_D(TAG, "Loading section '%s'", itref->key);
|
||||
if(!elf_load_section_data(elf, &itref->value)) {
|
||||
FURI_LOG_E(TAG, "Error loading section '%s'", itref->key);
|
||||
status = ELFFileLoadStatusUnspecifiedError;
|
||||
}
|
||||
}
|
||||
|
||||
if(status == ELFFileLoadStatusSuccess) {
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
|
||||
ELFSectionDict_next(it)) {
|
||||
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
|
||||
FURI_LOG_D(TAG, "Relocating section '%s'", itref->key);
|
||||
if(!elf_relocate_section(elf, &itref->value)) {
|
||||
FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key);
|
||||
status = ELFFileLoadStatusMissingImports;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fixing up entry point */
|
||||
if(status == ELFFileLoadStatusSuccess) {
|
||||
ELFSection* text_section = elf_file_get_section(elf, ".text");
|
||||
|
||||
if(text_section == NULL) {
|
||||
FURI_LOG_E(TAG, "No .text section found");
|
||||
status = ELFFileLoadStatusUnspecifiedError;
|
||||
} else {
|
||||
elf->entry += (uint32_t)text_section->data;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Relocation cache size: %u", AddressCache_size(elf->relocation_cache));
|
||||
FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache));
|
||||
AddressCache_clear(elf->relocation_cache);
|
||||
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void elf_file_pre_run(ELFFile* elf) {
|
||||
elf_file_call_section_list(elf, ".preinit_array", false);
|
||||
elf_file_call_section_list(elf, ".init_array", false);
|
||||
}
|
||||
|
||||
int32_t elf_file_run(ELFFile* elf, void* args) {
|
||||
int32_t result;
|
||||
result = ((int32_t(*)(void*))elf->entry)(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
void elf_file_post_run(ELFFile* elf) {
|
||||
elf_file_call_section_list(elf, ".fini_array", true);
|
||||
}
|
||||
|
||||
const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) {
|
||||
return elf_file->api_interface;
|
||||
}
|
||||
|
||||
void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) {
|
||||
// set entry
|
||||
debug_info->entry = elf->entry;
|
||||
|
||||
// copy debug info
|
||||
memcpy(&debug_info->debug_link_info, &elf->debug_link_info, sizeof(ELFDebugLinkInfo));
|
||||
|
||||
// init mmap
|
||||
debug_info->mmap_entry_count = ELFSectionDict_size(elf->sections);
|
||||
debug_info->mmap_entries = malloc(sizeof(ELFMemoryMapEntry) * debug_info->mmap_entry_count);
|
||||
uint32_t mmap_entry_idx = 0;
|
||||
|
||||
ELFSectionDict_it_t it;
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
|
||||
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
|
||||
|
||||
const void* data_ptr = itref->value.data;
|
||||
if(data_ptr) {
|
||||
debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr;
|
||||
debug_info->mmap_entries[mmap_entry_idx].name = itref->key;
|
||||
mmap_entry_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elf_file_clear_debug_info(ELFDebugInfo* debug_info) {
|
||||
// clear debug info
|
||||
memset(&debug_info->debug_link_info, 0, sizeof(ELFDebugLinkInfo));
|
||||
|
||||
// clear mmap
|
||||
if(debug_info->mmap_entries) {
|
||||
free(debug_info->mmap_entries);
|
||||
debug_info->mmap_entries = NULL;
|
||||
}
|
||||
|
||||
debug_info->mmap_entry_count = 0;
|
||||
}
|
||||
127
lib/flipper_application/elf/elf_file.h
Normal file
127
lib/flipper_application/elf/elf_file.h
Normal 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
|
||||
46
lib/flipper_application/elf/elf_file_i.h
Normal file
46
lib/flipper_application/elf/elf_file_i.h
Normal 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
|
||||
@ -1,477 +0,0 @@
|
||||
#include "flipper_application_i.h"
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "fapp-i"
|
||||
|
||||
#define RESOLVER_THREAD_YIELD_STEP 30
|
||||
|
||||
#define IS_FLAGS_SET(v, m) ((v & m) == m)
|
||||
#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr))
|
||||
#define SYMBOL_OFFSET(e, n) (e->_table + n * sizeof(Elf32_Shdr))
|
||||
|
||||
bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path) {
|
||||
Elf32_Ehdr h;
|
||||
Elf32_Shdr sH;
|
||||
|
||||
if(!storage_file_open(e->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) ||
|
||||
!storage_file_seek(e->fd, 0, true) ||
|
||||
storage_file_read(e->fd, &h, sizeof(h)) != sizeof(h) ||
|
||||
!storage_file_seek(e->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) ||
|
||||
storage_file_read(e->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
e->entry = h.e_entry;
|
||||
e->sections = h.e_shnum;
|
||||
e->section_table = h.e_shoff;
|
||||
e->section_table_strings = sH.sh_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool flipper_application_load_metadata(FlipperApplication* e, Elf32_Shdr* sh) {
|
||||
if(sh->sh_size < sizeof(e->manifest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return storage_file_seek(e->fd, sh->sh_offset, true) &&
|
||||
storage_file_read(e->fd, &e->manifest, sh->sh_size) == sh->sh_size;
|
||||
}
|
||||
|
||||
static bool flipper_application_load_debug_link(FlipperApplication* e, Elf32_Shdr* sh) {
|
||||
e->state.debug_link_size = sh->sh_size;
|
||||
e->state.debug_link = malloc(sh->sh_size);
|
||||
|
||||
return storage_file_seek(e->fd, sh->sh_offset, true) &&
|
||||
storage_file_read(e->fd, e->state.debug_link, sh->sh_size) == sh->sh_size;
|
||||
}
|
||||
|
||||
static FindFlags_t flipper_application_preload_section(
|
||||
FlipperApplication* e,
|
||||
Elf32_Shdr* sh,
|
||||
const char* name,
|
||||
int n) {
|
||||
FURI_LOG_D(TAG, "Processing: %s", name);
|
||||
|
||||
const struct {
|
||||
const char* name;
|
||||
uint16_t* ptr_section_idx;
|
||||
FindFlags_t flags;
|
||||
} lookup_sections[] = {
|
||||
{".text", &e->text.sec_idx, FoundText},
|
||||
{".rodata", &e->rodata.sec_idx, FoundRodata},
|
||||
{".data", &e->data.sec_idx, FoundData},
|
||||
{".bss", &e->bss.sec_idx, FoundBss},
|
||||
{".rel.text", &e->text.rel_sec_idx, FoundRelText},
|
||||
{".rel.rodata", &e->rodata.rel_sec_idx, FoundRelRodata},
|
||||
{".rel.data", &e->data.rel_sec_idx, FoundRelData},
|
||||
};
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) {
|
||||
if(strcmp(name, lookup_sections[i].name) == 0) {
|
||||
*lookup_sections[i].ptr_section_idx = n;
|
||||
return lookup_sections[i].flags;
|
||||
}
|
||||
}
|
||||
|
||||
if(strcmp(name, ".symtab") == 0) {
|
||||
e->symbol_table = sh->sh_offset;
|
||||
e->symbol_count = sh->sh_size / sizeof(Elf32_Sym);
|
||||
return FoundSymTab;
|
||||
} else if(strcmp(name, ".strtab") == 0) {
|
||||
e->symbol_table_strings = sh->sh_offset;
|
||||
return FoundStrTab;
|
||||
} else if(strcmp(name, ".fapmeta") == 0) {
|
||||
// Load metadata immediately
|
||||
if(flipper_application_load_metadata(e, sh)) {
|
||||
return FoundFappManifest;
|
||||
}
|
||||
} else if(strcmp(name, ".gnu_debuglink") == 0) {
|
||||
if(flipper_application_load_debug_link(e, sh)) {
|
||||
return FoundDebugLink;
|
||||
}
|
||||
}
|
||||
return FoundERROR;
|
||||
}
|
||||
|
||||
static bool
|
||||
read_string_from_offset(FlipperApplication* e, off_t offset, char* buffer, size_t buffer_size) {
|
||||
bool success = false;
|
||||
|
||||
off_t old = storage_file_tell(e->fd);
|
||||
if(storage_file_seek(e->fd, offset, true) &&
|
||||
(storage_file_read(e->fd, buffer, buffer_size) == buffer_size)) {
|
||||
success = true;
|
||||
}
|
||||
storage_file_seek(e->fd, old, true);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool read_section_name(FlipperApplication* e, off_t off, char* buf, size_t max) {
|
||||
return read_string_from_offset(e, e->section_table_strings + off, buf, max);
|
||||
}
|
||||
|
||||
static bool read_symbol_name(FlipperApplication* e, off_t off, char* buf, size_t max) {
|
||||
return read_string_from_offset(e, e->symbol_table_strings + off, buf, max);
|
||||
}
|
||||
|
||||
static bool read_section_header(FlipperApplication* e, int n, Elf32_Shdr* h) {
|
||||
off_t offset = SECTION_OFFSET(e, n);
|
||||
return storage_file_seek(e->fd, offset, true) &&
|
||||
storage_file_read(e->fd, h, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr);
|
||||
}
|
||||
|
||||
static bool read_section(FlipperApplication* e, int n, Elf32_Shdr* h, char* name, size_t nlen) {
|
||||
if(!read_section_header(e, n, h)) {
|
||||
return false;
|
||||
}
|
||||
if(!h->sh_name) {
|
||||
return true;
|
||||
}
|
||||
return read_section_name(e, h->sh_name, name, nlen);
|
||||
}
|
||||
|
||||
bool flipper_application_load_section_table(FlipperApplication* e) {
|
||||
furi_check(e->state.mmap_entry_count == 0);
|
||||
|
||||
size_t n;
|
||||
FindFlags_t found = FoundERROR;
|
||||
FURI_LOG_D(TAG, "Scan ELF indexs...");
|
||||
for(n = 1; n < e->sections; n++) {
|
||||
Elf32_Shdr section_header;
|
||||
char name[33] = {0};
|
||||
if(!read_section_header(e, n, §ion_header)) {
|
||||
return false;
|
||||
}
|
||||
if(section_header.sh_name &&
|
||||
!read_section_name(e, section_header.sh_name, name, sizeof(name))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_T(TAG, "Examining section %d %s", n, name);
|
||||
FindFlags_t section_flags =
|
||||
flipper_application_preload_section(e, §ion_header, name, n);
|
||||
found |= section_flags;
|
||||
if((section_flags & FoundGdbSection) != 0) {
|
||||
e->state.mmap_entry_count++;
|
||||
}
|
||||
if(IS_FLAGS_SET(found, FoundAll)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Load symbols done");
|
||||
return IS_FLAGS_SET(found, FoundValid);
|
||||
}
|
||||
|
||||
static const char* type_to_str(int symt) {
|
||||
#define STRCASE(name) \
|
||||
case name: \
|
||||
return #name;
|
||||
switch(symt) {
|
||||
STRCASE(R_ARM_NONE)
|
||||
STRCASE(R_ARM_ABS32)
|
||||
STRCASE(R_ARM_THM_PC22)
|
||||
STRCASE(R_ARM_THM_JUMP24)
|
||||
default:
|
||||
return "R_<unknow>";
|
||||
}
|
||||
#undef STRCASE
|
||||
}
|
||||
|
||||
static void relocate_jmp_call(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
|
||||
UNUSED(type);
|
||||
uint16_t upper_insn = ((uint16_t*)relAddr)[0];
|
||||
uint16_t lower_insn = ((uint16_t*)relAddr)[1];
|
||||
uint32_t S = (upper_insn >> 10) & 1;
|
||||
uint32_t J1 = (lower_insn >> 13) & 1;
|
||||
uint32_t J2 = (lower_insn >> 11) & 1;
|
||||
|
||||
int32_t offset = (S << 24) | /* S -> offset[24] */
|
||||
((~(J1 ^ S) & 1) << 23) | /* J1 -> offset[23] */
|
||||
((~(J2 ^ S) & 1) << 22) | /* J2 -> offset[22] */
|
||||
((upper_insn & 0x03ff) << 12) | /* imm10 -> offset[12:21] */
|
||||
((lower_insn & 0x07ff) << 1); /* imm11 -> offset[1:11] */
|
||||
if(offset & 0x01000000) offset -= 0x02000000;
|
||||
|
||||
offset += symAddr - relAddr;
|
||||
|
||||
S = (offset >> 24) & 1;
|
||||
J1 = S ^ (~(offset >> 23) & 1);
|
||||
J2 = S ^ (~(offset >> 22) & 1);
|
||||
|
||||
upper_insn = ((upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff));
|
||||
((uint16_t*)relAddr)[0] = upper_insn;
|
||||
|
||||
lower_insn = ((lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | ((offset >> 1) & 0x07ff));
|
||||
((uint16_t*)relAddr)[1] = lower_insn;
|
||||
}
|
||||
|
||||
static bool relocate_symbol(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
|
||||
switch(type) {
|
||||
case R_ARM_ABS32:
|
||||
*((uint32_t*)relAddr) += symAddr;
|
||||
FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
|
||||
break;
|
||||
case R_ARM_THM_PC22:
|
||||
case R_ARM_THM_JUMP24:
|
||||
relocate_jmp_call(relAddr, type, symAddr);
|
||||
FURI_LOG_D(
|
||||
TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
|
||||
break;
|
||||
default:
|
||||
FURI_LOG_D(TAG, " Undefined relocation %d", type);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ELFSection_t* section_of(FlipperApplication* e, int index) {
|
||||
if(e->text.sec_idx == index) {
|
||||
return &e->text;
|
||||
} else if(e->data.sec_idx == index) {
|
||||
return &e->data;
|
||||
} else if(e->bss.sec_idx == index) {
|
||||
return &e->bss;
|
||||
} else if(e->rodata.sec_idx == index) {
|
||||
return &e->rodata;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Elf32_Addr address_of(FlipperApplication* e, Elf32_Sym* sym, const char* sName) {
|
||||
if(sym->st_shndx == SHN_UNDEF) {
|
||||
Elf32_Addr addr = 0;
|
||||
if(e->api_interface->resolver_callback(sName, &addr)) {
|
||||
return addr;
|
||||
}
|
||||
} else {
|
||||
ELFSection_t* symSec = section_of(e, sym->st_shndx);
|
||||
if(symSec) {
|
||||
return ((Elf32_Addr)symSec->data) + sym->st_value;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D(TAG, " Can not find address for symbol %s", sName);
|
||||
return ELF_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
static bool read_symbol(FlipperApplication* e, int n, Elf32_Sym* sym, char* name, size_t nlen) {
|
||||
bool success = false;
|
||||
off_t old = storage_file_tell(e->fd);
|
||||
off_t pos = e->symbol_table + n * sizeof(Elf32_Sym);
|
||||
if(storage_file_seek(e->fd, pos, true) &&
|
||||
storage_file_read(e->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) {
|
||||
if(sym->st_name)
|
||||
success = read_symbol_name(e, sym->st_name, name, nlen);
|
||||
else {
|
||||
Elf32_Shdr shdr;
|
||||
success = read_section(e, sym->st_shndx, &shdr, name, nlen);
|
||||
}
|
||||
}
|
||||
storage_file_seek(e->fd, old, true);
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool
|
||||
relocation_cache_get(RelocationAddressCache_t cache, int symEntry, Elf32_Addr* symAddr) {
|
||||
Elf32_Addr* addr = RelocationAddressCache_get(cache, symEntry);
|
||||
if(addr) {
|
||||
*symAddr = *addr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
relocation_cache_put(RelocationAddressCache_t cache, int symEntry, Elf32_Addr symAddr) {
|
||||
RelocationAddressCache_set_at(cache, symEntry, symAddr);
|
||||
}
|
||||
|
||||
#define MAX_SYMBOL_NAME_LEN 128u
|
||||
|
||||
static bool relocate(FlipperApplication* e, Elf32_Shdr* h, ELFSection_t* s) {
|
||||
if(s->data) {
|
||||
Elf32_Rel rel;
|
||||
size_t relEntries = h->sh_size / sizeof(rel);
|
||||
size_t relCount;
|
||||
(void)storage_file_seek(e->fd, h->sh_offset, true);
|
||||
FURI_LOG_D(TAG, " Offset Info Type Name");
|
||||
|
||||
int relocate_result = true;
|
||||
char symbol_name[MAX_SYMBOL_NAME_LEN + 1] = {0};
|
||||
|
||||
for(relCount = 0; relCount < relEntries; relCount++) {
|
||||
if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) {
|
||||
FURI_LOG_D(TAG, " reloc YIELD");
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
|
||||
if(storage_file_read(e->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) {
|
||||
FURI_LOG_E(TAG, " reloc read fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
Elf32_Addr symAddr;
|
||||
|
||||
int symEntry = ELF32_R_SYM(rel.r_info);
|
||||
int relType = ELF32_R_TYPE(rel.r_info);
|
||||
Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset;
|
||||
|
||||
if(!relocation_cache_get(e->relocation_cache, symEntry, &symAddr)) {
|
||||
Elf32_Sym sym;
|
||||
if(!read_symbol(e, symEntry, &sym, symbol_name, MAX_SYMBOL_NAME_LEN)) {
|
||||
FURI_LOG_E(TAG, " symbol read fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" %08X %08X %-16s %s",
|
||||
(unsigned int)rel.r_offset,
|
||||
(unsigned int)rel.r_info,
|
||||
type_to_str(relType),
|
||||
symbol_name);
|
||||
|
||||
symAddr = address_of(e, &sym, symbol_name);
|
||||
relocation_cache_put(e->relocation_cache, symEntry, symAddr);
|
||||
}
|
||||
|
||||
if(symAddr != ELF_INVALID_ADDRESS) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" symAddr=%08X relAddr=%08X",
|
||||
(unsigned int)symAddr,
|
||||
(unsigned int)relAddr);
|
||||
if(!relocate_symbol(relAddr, relType, symAddr)) {
|
||||
relocate_result = false;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, " No symbol address of %s", symbol_name);
|
||||
relocate_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return relocate_result;
|
||||
} else
|
||||
FURI_LOG_I(TAG, "Section not loaded");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool flipper_application_load_section_data(FlipperApplication* e, ELFSection_t* s) {
|
||||
Elf32_Shdr section_header;
|
||||
if(s->sec_idx == 0) {
|
||||
FURI_LOG_I(TAG, "Section is not present");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!read_section_header(e, s->sec_idx, §ion_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(section_header.sh_size == 0) {
|
||||
FURI_LOG_I(TAG, "No data for section");
|
||||
return true;
|
||||
}
|
||||
|
||||
s->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign);
|
||||
// e->state.mmap_entry_count++;
|
||||
|
||||
if(section_header.sh_type == SHT_NOBITS) {
|
||||
/* section is empty (.bss?) */
|
||||
/* no need to memset - allocator already did that */
|
||||
/* memset(s->data, 0, h->sh_size); */
|
||||
FURI_LOG_D(TAG, "0x%X", s->data);
|
||||
return true;
|
||||
}
|
||||
|
||||
if((!storage_file_seek(e->fd, section_header.sh_offset, true)) ||
|
||||
(storage_file_read(e->fd, s->data, section_header.sh_size) != section_header.sh_size)) {
|
||||
FURI_LOG_E(TAG, " seek/read fail");
|
||||
flipper_application_free_section(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "0x%X", s->data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool flipper_application_relocate_section(FlipperApplication* e, ELFSection_t* s) {
|
||||
Elf32_Shdr section_header;
|
||||
if(s->rel_sec_idx) {
|
||||
FURI_LOG_D(TAG, "Relocating section");
|
||||
if(read_section_header(e, s->rel_sec_idx, §ion_header))
|
||||
return relocate(e, §ion_header, s);
|
||||
else {
|
||||
FURI_LOG_E(TAG, "Error reading section header");
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
FURI_LOG_D(TAG, "No relocation index"); /* Not an error */
|
||||
return true;
|
||||
}
|
||||
|
||||
FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e) {
|
||||
FlipperApplicationLoadStatus status = FlipperApplicationLoadStatusSuccess;
|
||||
RelocationAddressCache_init(e->relocation_cache);
|
||||
size_t start = furi_get_tick();
|
||||
|
||||
struct {
|
||||
ELFSection_t* section;
|
||||
const char* name;
|
||||
} sections[] = {
|
||||
{&e->text, ".text"},
|
||||
{&e->rodata, ".rodata"},
|
||||
{&e->data, ".data"},
|
||||
{&e->bss, ".bss"},
|
||||
};
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(sections); i++) {
|
||||
if(!flipper_application_load_section_data(e, sections[i].section)) {
|
||||
FURI_LOG_E(TAG, "Error loading section '%s'", sections[i].name);
|
||||
status = FlipperApplicationLoadStatusUnspecifiedError;
|
||||
}
|
||||
}
|
||||
|
||||
if(status == FlipperApplicationLoadStatusSuccess) {
|
||||
for(size_t i = 0; i < COUNT_OF(sections); i++) {
|
||||
if(!flipper_application_relocate_section(e, sections[i].section)) {
|
||||
FURI_LOG_E(TAG, "Error relocating section '%s'", sections[i].name);
|
||||
status = FlipperApplicationLoadStatusMissingImports;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status == FlipperApplicationLoadStatusSuccess) {
|
||||
e->state.mmap_entries =
|
||||
malloc(sizeof(FlipperApplicationMemoryMapEntry) * e->state.mmap_entry_count);
|
||||
uint32_t mmap_entry_idx = 0;
|
||||
for(size_t i = 0; i < COUNT_OF(sections); i++) {
|
||||
const void* data_ptr = sections[i].section->data;
|
||||
if(data_ptr) {
|
||||
FURI_LOG_I(TAG, "0x%X %s", (uint32_t)data_ptr, sections[i].name);
|
||||
e->state.mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr;
|
||||
e->state.mmap_entries[mmap_entry_idx].name = sections[i].name;
|
||||
mmap_entry_idx++;
|
||||
}
|
||||
}
|
||||
furi_check(mmap_entry_idx == e->state.mmap_entry_count);
|
||||
|
||||
/* Fixing up entry point */
|
||||
e->entry += (uint32_t)e->text.data;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Relocation cache size: %u", RelocationAddressCache_size(e->relocation_cache));
|
||||
RelocationAddressCache_clear(e->relocation_cache);
|
||||
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void flipper_application_free_section(ELFSection_t* s) {
|
||||
if(s->data) {
|
||||
aligned_free(s->data);
|
||||
}
|
||||
s->data = NULL;
|
||||
}
|
||||
@ -1,16 +1,22 @@
|
||||
#include "flipper_application.h"
|
||||
#include "flipper_application_i.h"
|
||||
#include "elf/elf_file.h"
|
||||
|
||||
#define TAG "fapp"
|
||||
|
||||
struct FlipperApplication {
|
||||
ELFDebugInfo state;
|
||||
FlipperApplicationManifest manifest;
|
||||
ELFFile* elf;
|
||||
FuriThread* thread;
|
||||
};
|
||||
|
||||
/* For debugger access to app state */
|
||||
FlipperApplication* last_loaded_app = NULL;
|
||||
|
||||
FlipperApplication*
|
||||
flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) {
|
||||
FlipperApplication* app = malloc(sizeof(FlipperApplication));
|
||||
app->api_interface = api_interface;
|
||||
app->fd = storage_file_alloc(storage);
|
||||
app->elf = elf_file_alloc(storage, api_interface);
|
||||
app->thread = NULL;
|
||||
return app;
|
||||
}
|
||||
@ -25,56 +31,71 @@ void flipper_application_free(FlipperApplication* app) {
|
||||
|
||||
last_loaded_app = NULL;
|
||||
|
||||
if(app->state.debug_link_size) {
|
||||
free(app->state.debug_link);
|
||||
}
|
||||
|
||||
if(app->state.mmap_entries) {
|
||||
free(app->state.mmap_entries);
|
||||
}
|
||||
|
||||
ELFSection_t* sections[] = {&app->text, &app->rodata, &app->data, &app->bss};
|
||||
for(size_t i = 0; i < COUNT_OF(sections); i++) {
|
||||
flipper_application_free_section(sections[i]);
|
||||
}
|
||||
|
||||
storage_file_free(app->fd);
|
||||
|
||||
elf_file_clear_debug_info(&app->state);
|
||||
elf_file_free(app->elf);
|
||||
free(app);
|
||||
}
|
||||
|
||||
/* Parse headers, load manifest */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload(FlipperApplication* app, const char* path) {
|
||||
if(!flipper_application_load_elf_headers(app, path) ||
|
||||
!flipper_application_load_section_table(app)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) &&
|
||||
(app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) {
|
||||
static FlipperApplicationPreloadStatus
|
||||
flipper_application_validate_manifest(FlipperApplication* app) {
|
||||
if(!flipper_application_manifest_is_valid(&app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusInvalidManifest;
|
||||
}
|
||||
|
||||
if(app->manifest.base.api_version.major != app->api_interface->api_version_major /* ||
|
||||
app->manifest.base.api_version.minor > app->api_interface->api_version_minor */) {
|
||||
if(!flipper_application_manifest_is_compatible(
|
||||
&app->manifest, elf_file_get_api_interface(app->elf))) {
|
||||
return FlipperApplicationPreloadStatusApiMismatch;
|
||||
}
|
||||
|
||||
return FlipperApplicationPreloadStatusSuccess;
|
||||
}
|
||||
|
||||
/* Parse headers, load manifest */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
|
||||
if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
return flipper_application_validate_manifest(app);
|
||||
}
|
||||
|
||||
/* Parse headers, load full file */
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload(FlipperApplication* app, const char* path) {
|
||||
if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) {
|
||||
return FlipperApplicationPreloadStatusInvalidFile;
|
||||
}
|
||||
|
||||
return flipper_application_validate_manifest(app);
|
||||
}
|
||||
|
||||
const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
|
||||
return &app->manifest;
|
||||
}
|
||||
|
||||
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) {
|
||||
last_loaded_app = app;
|
||||
return flipper_application_load_sections(app);
|
||||
ELFFileLoadStatus status = elf_file_load_sections(app->elf);
|
||||
|
||||
switch(status) {
|
||||
case ELFFileLoadStatusSuccess:
|
||||
elf_file_init_debug_info(app->elf, &app->state);
|
||||
return FlipperApplicationLoadStatusSuccess;
|
||||
case ELFFileLoadStatusNoFreeMemory:
|
||||
return FlipperApplicationLoadStatusNoFreeMemory;
|
||||
case ELFFileLoadStatusMissingImports:
|
||||
return FlipperApplicationLoadStatusMissingImports;
|
||||
default:
|
||||
return FlipperApplicationLoadStatusUnspecifiedError;
|
||||
}
|
||||
}
|
||||
|
||||
const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app) {
|
||||
return &app->state;
|
||||
static int32_t flipper_application_thread(void* context) {
|
||||
elf_file_pre_run(last_loaded_app->elf);
|
||||
int32_t result = elf_file_run(last_loaded_app->elf, context);
|
||||
elf_file_post_run(last_loaded_app->elf);
|
||||
return result;
|
||||
}
|
||||
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
|
||||
@ -86,20 +107,12 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
|
||||
app->thread = furi_thread_alloc();
|
||||
furi_thread_set_stack_size(app->thread, manifest->stack_size);
|
||||
furi_thread_set_name(app->thread, manifest->name);
|
||||
furi_thread_set_callback(app->thread, (entry_t*)app->entry);
|
||||
furi_thread_set_callback(app->thread, flipper_application_thread);
|
||||
furi_thread_set_context(app->thread, args);
|
||||
|
||||
return app->thread;
|
||||
}
|
||||
|
||||
FuriThread* flipper_application_get_thread(FlipperApplication* app) {
|
||||
return app->thread;
|
||||
}
|
||||
|
||||
void const* flipper_application_get_entry_address(FlipperApplication* app) {
|
||||
return (void*)app->entry;
|
||||
}
|
||||
|
||||
static const char* preload_status_strings[] = {
|
||||
[FlipperApplicationPreloadStatusSuccess] = "Success",
|
||||
[FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error",
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file flipper_application.h
|
||||
* Flipper application
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "application_manifest.h"
|
||||
@ -79,6 +83,14 @@ void flipper_application_free(FlipperApplication* app);
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload(FlipperApplication* app, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Validate elf file and load application manifest
|
||||
* @param app Application pointer
|
||||
* @return Preload result code
|
||||
*/
|
||||
FlipperApplicationPreloadStatus
|
||||
flipper_application_preload_manifest(FlipperApplication* app, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Get pointer to application manifest for preloaded application
|
||||
* @param app Application pointer
|
||||
@ -93,13 +105,6 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic
|
||||
*/
|
||||
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Get state object for loaded application
|
||||
* @param app Application pointer
|
||||
* @return Pointer to state object
|
||||
*/
|
||||
const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Create application thread at entry point address, using app name and
|
||||
* stack size from metadata. Returned thread isn't started yet.
|
||||
@ -110,20 +115,6 @@ const FlipperApplicationState* flipper_application_get_state(FlipperApplication*
|
||||
*/
|
||||
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args);
|
||||
|
||||
/**
|
||||
* @brief Get previously spawned thread
|
||||
* @param app Application pointer
|
||||
* @return Created thread
|
||||
*/
|
||||
FuriThread* flipper_application_get_thread(FlipperApplication* app);
|
||||
|
||||
/**
|
||||
* @brief Return relocated and valid address of app's entry point
|
||||
* @param app Application pointer
|
||||
* @return Address of app's entry point
|
||||
*/
|
||||
void const* flipper_application_get_entry_address(FlipperApplication* app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,99 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "elf.h"
|
||||
#include "flipper_application.h"
|
||||
#include <m-dict.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DICT_DEF2(RelocationAddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST)
|
||||
|
||||
/**
|
||||
* Callable elf entry type
|
||||
*/
|
||||
typedef int32_t(entry_t)(void*);
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
uint16_t sec_idx;
|
||||
uint16_t rel_sec_idx;
|
||||
} ELFSection_t;
|
||||
|
||||
struct FlipperApplication {
|
||||
const ElfApiInterface* api_interface;
|
||||
File* fd;
|
||||
FlipperApplicationState state;
|
||||
FlipperApplicationManifest manifest;
|
||||
|
||||
size_t sections;
|
||||
off_t section_table;
|
||||
off_t section_table_strings;
|
||||
|
||||
size_t symbol_count;
|
||||
off_t symbol_table;
|
||||
off_t symbol_table_strings;
|
||||
off_t entry;
|
||||
|
||||
ELFSection_t text;
|
||||
ELFSection_t rodata;
|
||||
ELFSection_t data;
|
||||
ELFSection_t bss;
|
||||
|
||||
FuriThread* thread;
|
||||
RelocationAddressCache_t relocation_cache;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FoundERROR = 0,
|
||||
FoundSymTab = (1 << 0),
|
||||
FoundStrTab = (1 << 2),
|
||||
FoundText = (1 << 3),
|
||||
FoundRodata = (1 << 4),
|
||||
FoundData = (1 << 5),
|
||||
FoundBss = (1 << 6),
|
||||
FoundRelText = (1 << 7),
|
||||
FoundRelRodata = (1 << 8),
|
||||
FoundRelData = (1 << 9),
|
||||
FoundRelBss = (1 << 10),
|
||||
FoundFappManifest = (1 << 11),
|
||||
FoundDebugLink = (1 << 12),
|
||||
FoundValid = FoundSymTab | FoundStrTab | FoundFappManifest,
|
||||
FoundExec = FoundValid | FoundText,
|
||||
FoundGdbSection = FoundText | FoundRodata | FoundData | FoundBss,
|
||||
FoundAll = FoundSymTab | FoundStrTab | FoundText | FoundRodata | FoundData | FoundBss |
|
||||
FoundRelText | FoundRelRodata | FoundRelData | FoundRelBss | FoundDebugLink,
|
||||
} FindFlags_t;
|
||||
|
||||
/**
|
||||
* @brief Load and validate basic ELF file headers
|
||||
* @param e Application instance
|
||||
* @param path FS path to application file
|
||||
* @return true if ELF file is valid
|
||||
*/
|
||||
bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Iterate over all sections and save related indexes
|
||||
* @param e Application instance
|
||||
* @return true if all required sections are found
|
||||
*/
|
||||
bool flipper_application_load_section_table(FlipperApplication* e);
|
||||
|
||||
/**
|
||||
* @brief Load section data to memory and process relocations
|
||||
* @param e Application instance
|
||||
* @return Status code
|
||||
*/
|
||||
FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e);
|
||||
|
||||
/**
|
||||
* @brief Release section data
|
||||
* @param s section pointer
|
||||
*/
|
||||
void flipper_application_free_section(ELFSection_t* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
17
lib/toolbox/m_cstr_dup.h
Normal 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))
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
|
||||
115
lib/update_util/resources/manifest.c
Normal file
115
lib/update_util/resources/manifest.c
Normal 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;
|
||||
}
|
||||
58
lib/update_util/resources/manifest.h
Normal file
58
lib/update_util/resources/manifest.h
Normal 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
|
||||
@ -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")
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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__":
|
||||
|
||||
@ -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%"
|
||||
|
||||
|
||||
@ -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:-""}";
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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 = ""
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user