From 730026c87ad01523365d334f01dae0143c7d97ca Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Thu, 13 Jun 2024 21:38:24 +1000 Subject: [PATCH 1/4] [FL-3844] Loader: fix crash on "locked via cli loader" (#3706) --- applications/services/loader/loader.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index c02eefd04..d4a4d581a 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -428,13 +428,19 @@ static LoaderStatus loader_do_start_by_name( do { // check lock if(loader_do_is_locked(loader)) { - const char* current_thread_name = - furi_thread_get_name(furi_thread_get_id(loader->app.thread)); - status = loader_make_status_error( - LoaderStatusErrorAppStarted, - error_message, - "Loader is locked, please close the \"%s\" first", - current_thread_name); + if(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE) { + status = loader_make_status_error( + LoaderStatusErrorAppStarted, error_message, "Loader is locked"); + } else { + const char* current_thread_name = + furi_thread_get_name(furi_thread_get_id(loader->app.thread)); + + status = loader_make_status_error( + LoaderStatusErrorAppStarted, + error_message, + "Loader is locked, please close the \"%s\" first", + current_thread_name); + } break; } From e7d0afdc503a832b8cabe3362970b4bad947f455 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 13 Jun 2024 07:55:51 -0400 Subject: [PATCH 2/4] [LFRFID] Added Support for Securakey Protocol (#3697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create protocol_securakey.c * decode and render done * Support for Radio Key Securakey Support both 26- and 32-bit format Solves issue #2619 * debugs and improvements * Formatting in response to requested changes * fixed wiegand parity bit calculation * format * debug unnecessary assert * LfRfid: swap vendor and protocol names in securakey * fixed manually generated keys issues * fix bit length render error caused by bit length fix Co-authored-by: あく --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_securakey.c | 294 ++++++++++++++++++++++ lib/lfrfid/protocols/protocol_securakey.h | 4 + 4 files changed, 301 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_securakey.c create mode 100644 lib/lfrfid/protocols/protocol_securakey.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 175d64c2b..f0c28f675 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -18,6 +18,7 @@ #include "protocol_keri.h" #include "protocol_gallagher.h" #include "protocol_nexwatch.h" +#include "protocol_securakey.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -41,4 +42,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolKeri] = &protocol_keri, [LFRFIDProtocolGallagher] = &protocol_gallagher, [LFRFIDProtocolNexwatch] = &protocol_nexwatch, + [LFRFIDProtocolSecurakey] = &protocol_securakey, }; diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 89aa51c25..c90e842af 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -29,6 +29,7 @@ typedef enum { LFRFIDProtocolKeri, LFRFIDProtocolGallagher, LFRFIDProtocolNexwatch, + LFRFIDProtocolSecurakey, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_securakey.c b/lib/lfrfid/protocols/protocol_securakey.c new file mode 100644 index 000000000..55048a592 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_securakey.c @@ -0,0 +1,294 @@ +// The timing parameters and data structure used in this file +// are based on the knowledge found in Proxmark3's firmware: +// https://github.com/RfidResearchGroup/proxmark3/blob/1c52152d30f7744c0336633317ea6640dbcdc796/client/src/cmdlfsecurakey.c +// PM3's repo has mentioned the existence of non-26-or-32-bit formats. +// Those are not supported here for preventing false positives. +#include +#include +#include +#include +#include "lfrfid_protocols.h" +#include + +#define TAG "SECURAKEY" +#define SECURAKEY_ENCODED_SIZE_BITS (84) +#define SECURAKEY_PREAMBLE_SIZE_BITS (12) +#define SECURAKEY_ENCODED_FULL_SIZE_BITS \ + (SECURAKEY_ENCODED_SIZE_BITS + SECURAKEY_PREAMBLE_SIZE_BITS) +#define SECURAKEY_ENCODED_FULL_SIZE_BYTE (SECURAKEY_ENCODED_FULL_SIZE_BITS / 8) +#define SECURAKEY_DECODED_DATA_SIZE_BITS \ + (48) // 16-bit for facility code/number, 16-bit for card number, 16-bit for two checksum +#define SECURAKEY_DECODED_DATA_SIZE_BYTES (SECURAKEY_DECODED_DATA_SIZE_BITS / 8) +#define LFRFID_FREQUENCY (125000) +#define SECURAKEY_CLOCK_PER_BIT (40) // RF/40 +#define SECURAKEY_READ_LONG_TIME \ + (1000000 / (LFRFID_FREQUENCY / SECURAKEY_CLOCK_PER_BIT)) // 1000000 micro sec / sec +#define SECURAKEY_READ_SHORT_TIME (SECURAKEY_READ_LONG_TIME / 2) +#define SECURAKEY_READ_JITTER_TIME (SECURAKEY_READ_SHORT_TIME * 40 / 100) // 40% jitter tolerance +#define SECURAKEY_READ_SHORT_TIME_LOW \ + (SECURAKEY_READ_SHORT_TIME - \ + SECURAKEY_READ_JITTER_TIME) // these are used for manchester decoding +#define SECURAKEY_READ_SHORT_TIME_HIGH (SECURAKEY_READ_SHORT_TIME + SECURAKEY_READ_JITTER_TIME) +#define SECURAKEY_READ_LONG_TIME_LOW (SECURAKEY_READ_LONG_TIME - SECURAKEY_READ_JITTER_TIME) +#define SECURAKEY_READ_LONG_TIME_HIGH (SECURAKEY_READ_LONG_TIME + SECURAKEY_READ_JITTER_TIME) + +typedef struct { + uint8_t data[SECURAKEY_DECODED_DATA_SIZE_BYTES]; + uint8_t encoded_data[SECURAKEY_ENCODED_FULL_SIZE_BYTE]; + uint8_t encoded_data_index; + bool encoded_polarity; + ManchesterState decoder_manchester_state; + uint8_t bit_format; +} ProtocolSecurakey; + +ProtocolSecurakey* protocol_securakey_alloc(void) { + ProtocolSecurakey* protocol = malloc(sizeof(ProtocolSecurakey)); + return (void*)protocol; +}; + +void protocol_securakey_free(ProtocolSecurakey* protocol) { + free(protocol); +}; + +uint8_t* protocol_securakey_get_data(ProtocolSecurakey* protocol) { + return protocol->data; +}; + +static bool protocol_securakey_can_be_decoded(ProtocolSecurakey* protocol) { + // check 11 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 0, SECURAKEY_PREAMBLE_SIZE_BITS) == + 0b011111111100) { + if(bit_lib_get_bits(protocol->encoded_data, 13, 6) == 26 || + bit_lib_get_bits(protocol->encoded_data, 13, 6) == 32) { + return true; + } else { + return false; + } + } else { + return false; + } +}; + +static void protocol_securakey_decode(ProtocolSecurakey* protocol) { + memset(protocol->data, 0, SECURAKEY_DECODED_DATA_SIZE_BYTES); + // encoded_data looks like this (citation: pm3 repo): + // 26-bit format (1-bit even parity bit, 8-bit facility number, 16-bit card number, 1-bit odd parity bit) + // preamble ??bitlen reserved EPf fffffffc cccccccc cccccccOP CS? CS2? + // 0111111111 0 01011010 0 00000000 0 00000010 0 00110110 0 00111110 0 01100010 0 00001111 0 01100000 0 00000000 0 0000 + + // 32-bit format (1-bit even parity bit, 14-bit facility number, 16-bit card number, 1-bit odd parity bit) + // preamble ??bitlen reserved EPfffffff fffffffc cccccccc cccccccOP CS? CS2? + // 0111111111 0 01100000 0 00000000 0 10000100 0 11001010 0 01011011 0 01010110 0 00010110 0 11100000 0 00000000 0 0000 + + // left two 0 paddings in the beginning for easier parsing (00011010 = 011010) + // get facility number (f) + if(bit_lib_get_bits(protocol->encoded_data, 13, 6) == 26) { + FURI_LOG_D(TAG, "26-bit Securakey detected"); + protocol->bit_format = 26; + bit_lib_copy_bits(protocol->data, 8, 1, protocol->encoded_data, 36); + // have to skip one spacer + bit_lib_copy_bits(protocol->data, 9, 7, protocol->encoded_data, 38); + } else if(bit_lib_get_bits(protocol->encoded_data, 13, 6) == 32) { + FURI_LOG_D(TAG, "32-bit Securakey detected"); + protocol->bit_format = 32; + // same two 0 paddings here, otherwise should be bit_lib_copy_bits(protocol->data, 8, 7, protocol->encoded_data, 30); + bit_lib_copy_bits(protocol->data, 2, 7, protocol->encoded_data, 30); + // have to skip one spacer + bit_lib_copy_bits(protocol->data, 9, 7, protocol->encoded_data, 38); + } + + // get card number (c) + bit_lib_copy_bits(protocol->data, 16, 1, protocol->encoded_data, 45); + // same skips here + bit_lib_copy_bits(protocol->data, 17, 8, protocol->encoded_data, 47); + bit_lib_copy_bits(protocol->data, 25, 7, protocol->encoded_data, 56); + + // unsure about CS yet, might as well just save it + // CS1 + bit_lib_copy_bits(protocol->data, 32, 8, protocol->encoded_data, 65); + // CS2 + bit_lib_copy_bits(protocol->data, 40, 8, protocol->encoded_data, 74); + + // (decoded) data looks like this (pp are zero paddings): + // 26-bit format (1-bit EP, 8-bit facility number, 16-bit card number, 1-bit OP) + // pppppppp ffffffff cccccccc cccccccc CS1 CS2 + // 00000000 00011011 00011111 00110001 00001111 01100000 + + // 32-bit format (1-bit EP, 14-bit facility number, 16-bit card number, 1-bit OP) + // ppffffff ffffffff cccccccc cccccccc CS1 CS2 + // 00000010 01100101 00101101 10101011 00010110 11100000 +}; + +void protocol_securakey_decoder_start(ProtocolSecurakey* protocol) { + memset(protocol->encoded_data, 0, SECURAKEY_ENCODED_FULL_SIZE_BYTE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +}; + +bool protocol_securakey_decoder_feed(ProtocolSecurakey* protocol, bool level, uint32_t duration) { + bool result = false; + // this is where we do manchester demodulation on already ASK-demoded data + ManchesterEvent event = ManchesterEventReset; + if(duration > SECURAKEY_READ_SHORT_TIME_LOW && duration < SECURAKEY_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > SECURAKEY_READ_LONG_TIME_LOW && duration < SECURAKEY_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + // append a new bit to the encoded bit stream + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, SECURAKEY_ENCODED_FULL_SIZE_BYTE, data); + if(protocol_securakey_can_be_decoded(protocol)) { + protocol_securakey_decode(protocol); + result = true; + } + } + } + return result; +}; + +void protocol_securakey_render_data(ProtocolSecurakey* protocol, FuriString* result) { + if(bit_lib_get_bits(protocol->data, 0, 8) == 0) { + protocol->bit_format = 26; + } else { + protocol->bit_format = 32; + } + furi_string_printf( + result, + "%u-bit format\nFacility code: %u\nCard number: %u", + protocol->bit_format, + bit_lib_get_bits_16(protocol->data, 0, 16), + bit_lib_get_bits_16(protocol->data, 16, 16)); +}; + +bool protocol_securakey_encoder_start(ProtocolSecurakey* protocol) { + // set all of our encoded_data bits to zeros. + memset(protocol->encoded_data, 0, SECURAKEY_ENCODED_FULL_SIZE_BYTE); + + // write the preamble to the beginning of the encoded_data + bit_lib_set_bits(protocol->encoded_data, 0, 0b01111111, 8); + bit_lib_set_bits(protocol->encoded_data, 8, 0b11001, 5); + + if(bit_lib_get_bits(protocol->data, 0, 8) == 0) { + protocol->bit_format = 26; + // set bit length + bit_lib_set_bits(protocol->encoded_data, 13, protocol->bit_format, 6); + // set even parity & odd parity + if(!bit_lib_test_parity(protocol->data, 8, 12, BitLibParityOdd, 12)) { + bit_lib_set_bit(protocol->encoded_data, 35, 1); + } + if(bit_lib_test_parity(protocol->data, 20, 12, BitLibParityOdd, 12)) { + bit_lib_set_bit(protocol->encoded_data, 63, 1); + } + // write facility number (f) + bit_lib_copy_bits(protocol->encoded_data, 36, 1, protocol->data, 8); + // have to skip one spacer + bit_lib_copy_bits(protocol->encoded_data, 38, 7, protocol->data, 9); + + } else { + protocol->bit_format = 32; + // set bit length + bit_lib_set_bits(protocol->encoded_data, 13, protocol->bit_format, 6); + // set EP & OP + if(!bit_lib_test_parity(protocol->data, 2, 15, BitLibParityOdd, 15)) { + bit_lib_set_bit(protocol->encoded_data, 29, 1); + } + if(bit_lib_test_parity(protocol->data, 17, 15, BitLibParityOdd, 15)) { + bit_lib_set_bit(protocol->encoded_data, 63, 1); + } + // write facility number (f) + bit_lib_copy_bits(protocol->encoded_data, 30, 7, protocol->data, 2); + // have to skip one spacer + bit_lib_copy_bits(protocol->encoded_data, 38, 7, protocol->data, 3); + } + + // write card number (c) + bit_lib_copy_bits(protocol->encoded_data, 45, 1, protocol->data, 16); + // same skips here + bit_lib_copy_bits(protocol->encoded_data, 47, 8, protocol->data, 17); + bit_lib_copy_bits(protocol->encoded_data, 56, 7, protocol->data, 25); + + // unsure about CS yet might as well just copy it from saved + // CS1 + bit_lib_copy_bits(protocol->encoded_data, 65, 8, protocol->data, 32); + // CS2 + bit_lib_copy_bits(protocol->encoded_data, 74, 8, protocol->data, 40); + + // for sending we start at bit 0. + protocol->encoded_data_index = 0; + protocol->encoded_polarity = true; + return true; +}; + +LevelDuration protocol_securakey_encoder_yield(ProtocolSecurakey* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = SECURAKEY_CLOCK_PER_BIT / 2; + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, SECURAKEY_ENCODED_FULL_SIZE_BITS); + } + return level_duration_make(level, duration); +}; + +bool protocol_securakey_write_data(ProtocolSecurakey* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + // Correct protocol data by redecoding + protocol_securakey_encoder_start(protocol); + protocol_securakey_decode(protocol); + protocol_securakey_encoder_start(protocol); + // Write T5577 + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_40 | + (3 + << LFRFID_T5577_MAXBLOCK_SHIFT)); // we only need 3 32-bit blocks for our 96-bit encoded data + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +const ProtocolBase protocol_securakey = { + .name = "Radio Key", + .manufacturer = "Securakey", + .data_size = SECURAKEY_DECODED_DATA_SIZE_BYTES, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_securakey_alloc, + .free = (ProtocolFree)protocol_securakey_free, + .get_data = (ProtocolGetData)protocol_securakey_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_securakey_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_securakey_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_securakey_encoder_start, + .yield = (ProtocolEncoderYield)protocol_securakey_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_securakey_render_data, + .render_brief_data = (ProtocolRenderData)protocol_securakey_render_data, + .write_data = (ProtocolWriteData)protocol_securakey_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_securakey.h b/lib/lfrfid/protocols/protocol_securakey.h new file mode 100644 index 000000000..ad990f119 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_securakey.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_securakey; \ No newline at end of file From 5a8a13639b01ca131a3bba2cbe7c3929e6c2593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Jun 2024 17:47:43 +0100 Subject: [PATCH 3/4] [FL-3842] Desktop lockup fix, GUI improvements (#3710) * Gui: increase ViewDispatcher messages queues size, improves event processing with blocking operations. * Gui: fix log message text in view dispatcher, loosen some mutexes in view port --- applications/services/gui/view_dispatcher.c | 6 +++--- applications/services/gui/view_port.c | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 0d0437736..bf1cd2be6 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -52,7 +52,7 @@ void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) { view_dispatcher->event_loop = furi_event_loop_alloc(); - view_dispatcher->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + view_dispatcher->input_queue = furi_message_queue_alloc(16, sizeof(InputEvent)); furi_event_loop_message_queue_subscribe( view_dispatcher->event_loop, view_dispatcher->input_queue, @@ -60,7 +60,7 @@ void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) { view_dispatcher_run_input_callback, view_dispatcher); - view_dispatcher->event_queue = furi_message_queue_alloc(8, sizeof(uint32_t)); + view_dispatcher->event_queue = furi_message_queue_alloc(16, sizeof(uint32_t)); furi_event_loop_message_queue_subscribe( view_dispatcher->event_loop, view_dispatcher->event_queue, @@ -298,7 +298,7 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e } else if(view_dispatcher->ongoing_input_view && event->type == InputTypeRelease) { FURI_LOG_D( TAG, - "View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", + "View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view", view_dispatcher->ongoing_input_view, view_dispatcher->current_view, input_get_key_name(event->key), diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 7f1da3b70..39156411d 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -139,12 +139,17 @@ uint8_t view_port_get_height(const ViewPort* view_port) { void view_port_enabled_set(ViewPort* view_port, bool enabled) { furi_check(view_port); - furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } if(view_port->is_enabled != enabled) { view_port->is_enabled = enabled; if(view_port->gui) gui_update(view_port->gui); } - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_release(view_port->mutex); } bool view_port_is_enabled(const ViewPort* view_port) { @@ -236,8 +241,13 @@ void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientat } ViewPortOrientation view_port_get_orientation(const ViewPort* view_port) { - furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + furi_check(view_port); + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } ViewPortOrientation orientation = view_port->orientation; - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_release(view_port->mutex); return orientation; } From ca8517a1b00b5352fb023a67fe069bcc11f1625f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Jun 2024 18:07:13 +0100 Subject: [PATCH 4/4] Cli: top (#3707) * Cli: top command to replace ps. Furi: ThreadList and thread enumeration routine. * Sync API Symbols * Cli: cleanup top output, add memory section. Furi: thread enumeration code cleanup. Fix doxygen and make pvs happy. * Furi: iterator in thread_list instead of M_EACH, fix memory leak * Update documentation * Cli: customizable refres interval for top command * Furi: add consistentency into float declaration in thread list * FreeRTOSConfig: remove invalid comment Co-authored-by: Sergei Gavrilov --- applications/services/cli/cli_commands.c | 86 ++++++++++++------ documentation/AppManifests.md | 2 +- furi/core/thread.c | 77 ++++++++++++---- furi/core/thread.h | 15 ++-- furi/core/thread_list.c | 110 +++++++++++++++++++++++ furi/core/thread_list.h | 81 +++++++++++++++++ furi/furi.h | 1 + targets/f18/api_symbols.csv | 10 ++- targets/f7/api_symbols.csv | 10 ++- targets/f7/inc/FreeRTOSConfig.h | 4 - 10 files changed, 336 insertions(+), 60 deletions(-) create mode 100644 furi/core/thread_list.c create mode 100644 furi/core/thread_list.h diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 43f1c01c4..7bf86c90c 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -382,37 +382,69 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -void cli_command_ps(Cli* cli, FuriString* args, void* context) { +static void cli_command_top(Cli* cli, FuriString* args, void* context) { UNUSED(cli); - UNUSED(args); UNUSED(context); - const uint8_t threads_num_max = 32; - FuriThreadId threads_ids[threads_num_max]; - uint32_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); - printf( - "%-17s %-20s %-5s %-13s %-6s %-8s %s\r\n", - "AppID", - "Name", - "Prio", - "Stack start", - "Heap", - "Stack", - "Stack min free"); - for(uint8_t i = 0; i < thread_num; i++) { - TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; - size_t thread_heap = memmgr_heap_get_thread_memory(threads_ids[i]); + int interval = 1000; + args_read_int_and_trim(args, &interval); + + FuriThreadList* thread_list = furi_thread_list_alloc(); + while(!cli_cmd_interrupt_received(cli)) { + uint32_t tick = furi_get_tick(); + furi_thread_enumerate(thread_list); + + if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + + uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "%-17s %-20s %-5d 0x%-11lx %-6zu %-8lu %-8lu\r\n", - furi_thread_get_appid(threads_ids[i]), - furi_thread_get_name(threads_ids[i]), - furi_thread_get_priority(threads_ids[i]), - (uint32_t)tcb->pxStack, - thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap, - (uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t), - furi_thread_get_stack_space(threads_ids[i])); + "Threads: %zu, Uptime: %luh%lum%lus\r\n", + furi_thread_list_size(thread_list), + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + + printf( + "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + memmgr_get_total_heap(), + memmgr_get_free_heap(), + memmgr_get_minimum_free_heap(), + memmgr_heap_get_max_free_block()); + + printf( + "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "AppID", + "Name", + "State", + "Prio", + "Stack start", + "Stack", + "Stack Min", + "Heap", + "CPU"); + + for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { + const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); + printf( + "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + item->app_id, + item->name, + item->state, + item->priority, + item->stack_address, + item->stack_size, + item->stack_min_free, + item->heap, + (double)item->cpu); + } + + if(interval > 0) { + furi_delay_ms(interval); + } else { + break; + } } - printf("\r\nTotal: %lu", thread_num); + furi_thread_list_free(thread_list); } void cli_command_free(Cli* cli, FuriString* args, void* context) { @@ -472,7 +504,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); - cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL); + cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index b612df1b7..98a38ffd8 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -36,7 +36,7 @@ Only two parameters are mandatory: **appid** and **apptype**. Others are optiona - **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. - **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. -- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `ps` and `free` CLI commands to profile your app's memory usage._ +- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._ - **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. - **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ - **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. diff --git a/furi/core/thread.c b/furi/core/thread.c index 9d330b71b..4e9477712 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,4 +1,5 @@ #include "thread.h" +#include "thread_list.h" #include "kernel.h" #include "memmgr.h" #include "memmgr_heap.h" @@ -13,6 +14,8 @@ #include #include +#include + #define TAG "FuriThread" #define THREAD_NOTIFY_INDEX (1) // Index 0 is used for stream buffers @@ -547,33 +550,71 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo return rflags; } -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count) { - uint32_t i, count; - TaskStatus_t* task; +static const char* furi_thread_state_name(eTaskState state) { + switch(state) { + case eRunning: + return "Running"; + case eReady: + return "Ready"; + case eBlocked: + return "Blocked"; + case eSuspended: + return "Suspended"; + case eDeleted: + return "Deleted"; + case eInvalid: + return "Invalid"; + default: + return "?"; + } +} - if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_item_count == 0U)) { - count = 0U; - } else { - vTaskSuspendAll(); +bool furi_thread_enumerate(FuriThreadList* thread_list) { + furi_check(thread_list); + furi_check(!FURI_IS_IRQ_MODE()); + + bool result = false; + + vTaskSuspendAll(); + do { + uint32_t tick = furi_get_tick(); + uint32_t count = uxTaskGetNumberOfTasks(); + + TaskStatus_t* task = pvPortMalloc(count * sizeof(TaskStatus_t)); + + if(!task) break; - count = uxTaskGetNumberOfTasks(); - task = pvPortMalloc(count * sizeof(TaskStatus_t)); configRUN_TIME_COUNTER_TYPE total_run_time; + count = uxTaskGetSystemState(task, count, &total_run_time); + for(uint32_t i = 0U; i < count; i++) { + TaskControlBlock* tcb = (TaskControlBlock*)task[i].xHandle; - if(task != NULL) { - count = uxTaskGetSystemState(task, count, &total_run_time); + FuriThreadListItem* item = + furi_thread_list_get_or_insert(thread_list, (FuriThread*)task[i].xHandle); - for(i = 0U; (i < count) && (i < array_item_count); i++) { - thread_array[i] = (FuriThreadId)task[i].xHandle; - } - count = i; + item->thread = (FuriThreadId)task[i].xHandle; + item->app_id = furi_thread_get_appid(item->thread); + item->name = task[i].pcTaskName; + item->priority = task[i].uxCurrentPriority; + item->stack_address = (uint32_t)tcb->pxStack; + size_t thread_heap = memmgr_heap_get_thread_memory(item->thread); + item->heap = thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap; + item->stack_size = (tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t); + item->stack_min_free = furi_thread_get_stack_space(item->thread); + item->state = furi_thread_state_name(task[i].eCurrentState); + item->counter_previous = item->counter_current; + item->counter_current = task[i].ulRunTimeCounter; + item->tick = tick; } - (void)xTaskResumeAll(); vPortFree(task); - } + furi_thread_list_process(thread_list, total_run_time, tick); - return count; + result = true; + } while(false); + (void)xTaskResumeAll(); + + return result; } const char* furi_thread_get_name(FuriThreadId thread_id) { diff --git a/furi/core/thread.h b/furi/core/thread.h index d78272a4d..9c113bd49 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -46,6 +46,9 @@ typedef enum { */ typedef struct FuriThread FuriThread; +/** FuriThreadList type */ +typedef struct FuriThreadList FuriThreadList; + /** * @brief Unique thread identifier type (used by the OS kernel). */ @@ -379,13 +382,13 @@ uint32_t furi_thread_flags_get(void); uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout); /** - * @brief Enumerate all threads. - * - * @param[out] thread_array pointer to the output array (must be properly allocated) - * @param[in] array_item_count output array capacity in elements (NOT bytes) - * @return total thread count (array_item_count or less) + * @brief Enumerate all threads. + * + * @param[out] thread_list pointer to the FuriThreadList container + * + * @return true on success, false otherwise */ -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); +bool furi_thread_enumerate(FuriThreadList* thread_list); /** * @brief Get the name of a thread based on its unique identifier. diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c new file mode 100644 index 000000000..65ee11ad3 --- /dev/null +++ b/furi/core/thread_list.c @@ -0,0 +1,110 @@ +#include "thread_list.h" +#include "check.h" + +#include +#include + +ARRAY_DEF(FuriThreadListItemArray, FuriThreadListItem*, M_PTR_OPLIST) // NOLINT + +#define M_OPL_FuriThreadListItemArray_t() ARRAY_OPLIST(FuriThreadListItemArray, M_PTR_OPLIST) + +DICT_DEF2( + FuriThreadListItemDict, + uint32_t, + M_DEFAULT_OPLIST, + FuriThreadListItem*, + M_PTR_OPLIST) // NOLINT + +#define M_OPL_FuriThreadListItemDict_t() \ + DICT_OPLIST(FuriThreadListItemDict, M_DEFAULT_OPLIST, M_PTR_OPLIST) + +struct FuriThreadList { + FuriThreadListItemArray_t items; + FuriThreadListItemDict_t search; + uint32_t runtime_previous; + uint32_t runtime_current; +}; + +FuriThreadList* furi_thread_list_alloc(void) { + FuriThreadList* instance = malloc(sizeof(FuriThreadList)); + + FuriThreadListItemArray_init(instance->items); + FuriThreadListItemDict_init(instance->search); + + return instance; +} + +void furi_thread_list_free(FuriThreadList* instance) { + furi_check(instance); + + FuriThreadListItemArray_it_t it; + FuriThreadListItemArray_it(it, instance->items); + while(!FuriThreadListItemArray_end_p(it)) { + FuriThreadListItem* item = *FuriThreadListItemArray_cref(it); + free(item); + FuriThreadListItemArray_next(it); + } + + FuriThreadListItemDict_clear(instance->search); + FuriThreadListItemArray_clear(instance->items); + + free(instance); +} + +size_t furi_thread_list_size(FuriThreadList* instance) { + furi_check(instance); + return FuriThreadListItemArray_size(instance->items); +} + +FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t position) { + furi_check(instance); + furi_check(position < furi_thread_list_size(instance)); + + return *FuriThreadListItemArray_get(instance->items, position); +} + +FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread) { + furi_check(instance); + + FuriThreadListItem** item_ptr = FuriThreadListItemDict_get(instance->search, (uint32_t)thread); + if(item_ptr) { + return *item_ptr; + } + + FuriThreadListItem* item = malloc(sizeof(FuriThreadListItem)); + + FuriThreadListItemArray_push_back(instance->items, item); + FuriThreadListItemDict_set_at(instance->search, (uint32_t)thread, item); + + return item; +} + +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) { + furi_check(instance); + + instance->runtime_previous = instance->runtime_current; + instance->runtime_current = runtime; + + uint32_t runtime_counter = instance->runtime_current - instance->runtime_previous; + + FuriThreadListItemArray_it_t it; + FuriThreadListItemArray_it(it, instance->items); + while(!FuriThreadListItemArray_end_p(it)) { + FuriThreadListItem* item = *FuriThreadListItemArray_cref(it); + if(item->tick != tick) { + FuriThreadListItemArray_remove(instance->items, it); + (void)FuriThreadListItemDict_erase(instance->search, (uint32_t)item->thread); + free(item); + } else { + uint32_t item_counter = item->counter_current - item->counter_previous; + if(item_counter && item->counter_previous && item->counter_current) { + item->cpu = (float)item_counter / (float)runtime_counter * 100.0f; + if(item->cpu > 200.0f) item->cpu = 0.0f; + } else { + item->cpu = 0.0f; + } + + FuriThreadListItemArray_next(it); + } + } +} diff --git a/furi/core/thread_list.h b/furi/core/thread_list.h new file mode 100644 index 000000000..bf15e4032 --- /dev/null +++ b/furi/core/thread_list.h @@ -0,0 +1,81 @@ +#pragma once + +#include "base.h" +#include "common_defines.h" +#include "thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + FuriThread* thread; /**< Pointer to FuriThread, valid while it is running */ + const char* app_id; /**< Thread application id, valid while it is running */ + const char* name; /**< Thread name, valid while it is running */ + FuriThreadPriority priority; /**< Thread priority */ + uint32_t stack_address; /**< Thread stack address */ + size_t heap; /**< Thread heap size if tracking enabled, 0 - otherwise */ + uint32_t stack_size; /**< Thread stack size */ + uint32_t stack_min_free; /**< Thread minimum of the stack size ever reached */ + const char* + state; /**< Thread state, can be: "Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid" */ + float cpu; /**< Thread CPU usage time in percents (including interrupts happened while running) */ + + // Service variables + uint32_t counter_previous; /**< Thread previous runtime counter */ + uint32_t counter_current; /**< Thread current runtime counter */ + uint32_t tick; /**< Thread last seen tick */ +} FuriThreadListItem; + +/** Anonymous FuriThreadList type */ +typedef struct FuriThreadList FuriThreadList; + +/** Allocate FuriThreadList instance + * + * @return FuriThreadList instance + */ +FuriThreadList* furi_thread_list_alloc(void); + +/** Free FuriThreadList instance + * + * @param instance The FuriThreadList instance to free + */ +void furi_thread_list_free(FuriThreadList* instance); + +/** Get FuriThreadList instance size + * + * @param instance The instance + * + * @return Item count + */ +size_t furi_thread_list_size(FuriThreadList* instance); + +/** Get item at position + * + * @param instance The FuriThreadList instance + * @param[in] position The position of the item + * + * @return The FuriThreadListItem instance + */ +FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t position); + +/** Get item by thread FuriThread pointer + * + * @param instance The FuriThreadList instance + * @param thread The FuriThread pointer + * + * @return The FuriThreadListItem instance + */ +FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread); + +/** Process items in the FuriThreadList instance + * + * @param instance The instance + * @param[in] runtime The runtime of the system since start + * @param[in] tick The tick when processing happened + */ +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); + +#ifdef __cplusplus +} +#endif diff --git a/furi/furi.h b/furi/furi.h index 24e597acf..400cf1d64 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -16,6 +16,7 @@ #include "core/record.h" #include "core/semaphore.h" #include "core/thread.h" +#include "core/thread_list.h" #include "core/timer.h" #include "core/string.h" #include "core/stream_buffer.h" diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index fc5d86599..4b40107ce 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.3,, +Version,+,65.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1594,7 +1594,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_enumerate,_Bool,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1614,6 +1614,12 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_list_alloc,FuriThreadList*, +Function,+,furi_thread_list_free,void,FuriThreadList* +Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" +Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" +Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c2e7f59e3..d64571963 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.3,, +Version,+,65.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1808,7 +1808,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_enumerate,_Bool,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1828,6 +1828,12 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_list_alloc,FuriThreadList*, +Function,+,furi_thread_list_free,void,FuriThreadList* +Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" +Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" +Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index e6233f624..37aac1eb0 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -29,13 +29,9 @@ // #define configTOTAL_HEAP_SIZE ((size_t)0) #define configMAX_TASK_NAME_LEN (32) -/* Run-time stats - broken ATM, to be fixed */ -/* #define configGENERATE_RUN_TIME_STATS 1 -#define configRUN_TIME_COUNTER_TYPE uint64_t #define portGET_RUN_TIME_COUNTER_VALUE() (DWT->CYCCNT) #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() -*/ #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0