From f46018b2046edc7e49546db4c5b7f375c828ab46 Mon Sep 17 00:00:00 2001 From: agarof <18119032+agarof@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:54:25 +0200 Subject: [PATCH 1/8] Add extended I2C HAL functions (#3037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add extended I2C HAL functions * Rename I2CEndClockStretch to I2CEndAwaitRestart * Address review comments * Update f18 api_symbols.csv * FuriHal: check input values in cortex timer * FuriHal: cleanup I2C documentation * Properly bump api symbols * FuriHal: fix incorrect cast in I2C write_reg methods, fix spelling and naming * FuriHal: cleanup const usage in I2C, sync declaration and implementation * Format Sources * FuriHal: more i2c docs * Add I2C Restart and Pause / Resume test * Add I2C auto-reload test * UnitTests: skip furi_hal_i2c_ext_eeprom if eeprom is not connected * UnitTest: cleanup subghz test output * FuriHal: classic timeouts in i2c Co-authored-by: hedger Co-authored-by: あく --- .../unit_tests/furi_hal/furi_hal_tests.c | 115 +++++ .../debug/unit_tests/subghz/subghz_test.c | 13 +- firmware/targets/f18/api_symbols.csv | 14 +- firmware/targets/f7/api_symbols.csv | 14 +- .../targets/f7/furi_hal/furi_hal_cortex.c | 5 + firmware/targets/f7/furi_hal/furi_hal_i2c.c | 431 ++++++++++-------- .../targets/furi_hal_include/furi_hal_i2c.h | 215 ++++++--- 7 files changed, 534 insertions(+), 273 deletions(-) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c index e942c5933b..2dbaa4d868 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -5,6 +5,11 @@ #include "../minunit.h" #define DATA_SIZE 4 +#define EEPROM_ADDRESS 0b10101000 +#define EEPROM_ADDRESS_HIGH (EEPROM_ADDRESS | 0b10) +#define EEPROM_SIZE 512 +#define EEPROM_PAGE_SIZE 16 +#define EEPROM_WRITE_DELAY_MS 6 static void furi_hal_i2c_int_setup() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -14,6 +19,14 @@ static void furi_hal_i2c_int_teardown() { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +static void furi_hal_i2c_ext_setup() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); +} + +static void furi_hal_i2c_ext_teardown() { + furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} + MU_TEST(furi_hal_i2c_int_1b) { bool ret = false; uint8_t data_one = 0; @@ -103,14 +116,116 @@ MU_TEST(furi_hal_i2c_int_1b_fail) { mu_assert(data_one != 0, "9 invalid data"); } +MU_TEST(furi_hal_i2c_int_ext_3b) { + bool ret = false; + uint8_t data_many[DATA_SIZE] = {0}; + + // 3 byte: read + data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER; + ret = furi_hal_i2c_tx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndAwaitRestart, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "3 tx failed"); + + // Send a RESTART condition, then read the 3 bytes one after the other + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 1, + 1, + FuriHalI2cBeginRestart, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "4 rx failed"); + mu_assert(data_many[1] != 0, "4 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 2, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "5 rx failed"); + mu_assert(data_many[2] != 0, "5 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 3, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "6 rx failed"); + mu_assert(data_many[3] != 0, "6 invalid data"); +} + +MU_TEST(furi_hal_i2c_ext_eeprom) { + if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 100)) { + printf("no device connected, skipping\r\n"); + return; + } + + bool ret = false; + uint8_t buffer[EEPROM_SIZE] = {0}; + + for(size_t page = 0; page < (EEPROM_SIZE / EEPROM_PAGE_SIZE); ++page) { + // Fill page buffer + for(size_t page_byte = 0; page_byte < EEPROM_PAGE_SIZE; ++page_byte) { + // Each byte is its position in the EEPROM modulo 256 + uint8_t byte = ((page * EEPROM_PAGE_SIZE) + page_byte) % 256; + + buffer[page_byte] = byte; + } + + uint8_t address = (page < 16) ? EEPROM_ADDRESS : EEPROM_ADDRESS_HIGH; + + ret = furi_hal_i2c_write_mem( + &furi_hal_i2c_handle_external, + address, + page * EEPROM_PAGE_SIZE, + buffer, + EEPROM_PAGE_SIZE, + 20); + + mu_assert(ret, "EEPROM write failed"); + furi_delay_ms(EEPROM_WRITE_DELAY_MS); + } + + ret = furi_hal_i2c_read_mem( + &furi_hal_i2c_handle_external, EEPROM_ADDRESS, 0, buffer, EEPROM_SIZE, 100); + + mu_assert(ret, "EEPROM read failed"); + + for(size_t pos = 0; pos < EEPROM_SIZE; ++pos) { + mu_assert_int_eq(pos % 256, buffer[pos]); + } +} + MU_TEST_SUITE(furi_hal_i2c_int_suite) { MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); MU_RUN_TEST(furi_hal_i2c_int_1b); MU_RUN_TEST(furi_hal_i2c_int_3b); + MU_RUN_TEST(furi_hal_i2c_int_ext_3b); MU_RUN_TEST(furi_hal_i2c_int_1b_fail); } +MU_TEST_SUITE(furi_hal_i2c_ext_suite) { + MU_SUITE_CONFIGURE(&furi_hal_i2c_ext_setup, &furi_hal_i2c_ext_teardown); + MU_RUN_TEST(furi_hal_i2c_ext_eeprom); +} + int run_minunit_test_furi_hal() { MU_RUN_SUITE(furi_hal_i2c_int_suite); + MU_RUN_SUITE(furi_hal_i2c_ext_suite); return MU_EXIT_CODE; } diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 1900f20455..64e0591dfa 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -98,9 +98,9 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder); + printf("Test decoder %s ERROR TimeOut\r\n", name_decoder); return false; } else { return subghz_test_decoder_count ? true : false; @@ -137,9 +137,9 @@ static bool subghz_decode_random_test(const char* path) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_D(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) { - printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n"); + printf("Random test ERROR TimeOut\r\n"); return false; } else if(subghz_test_decoder_count == TEST_RANDOM_COUNT_PARSE) { return true; @@ -200,10 +200,9 @@ static bool subghz_encoder_test(const char* path) { subghz_transmitter_free(transmitter); } flipper_format_free(fff_data_file); - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf( - "\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str)); + printf("Test encoder %s ERROR TimeOut\r\n", furi_string_get_cstr(temp_str)); subghz_test_decoder_count = 0; } furi_string_free(temp_str); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bc17ff2fa8..f4e990becb 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1105,14 +1105,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 26fad6b58f..d37d5f333d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1176,14 +1176,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 3fbe384e3c..6b5efc376c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -15,8 +15,11 @@ void furi_hal_cortex_init_early() { } void furi_hal_cortex_delay_us(uint32_t microseconds) { + furi_check(microseconds < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + uint32_t start = DWT->CYCCNT; uint32_t time_ticks = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * microseconds; + while((DWT->CYCCNT - start) < time_ticks) { }; } @@ -26,6 +29,8 @@ uint32_t furi_hal_cortex_instructions_per_microsecond() { } FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { + furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + FuriHalCortexTimer cortex_timer = {0}; cortex_timer.start = DWT->CYCCNT; cortex_timer.value = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * timeout_us; diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/firmware/targets/f7/furi_hal/furi_hal_i2c.c index bdfe0b0a3e..8c7b054e3e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c.c @@ -5,7 +5,6 @@ #include #include -#include #include #define TAG "FuriHalI2c" @@ -27,7 +26,7 @@ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_enter(); // Lock bus access handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); - // Ensuree that no active handle set + // Ensure that no active handle set furi_check(handle->bus->current_handle == NULL); // Set current handle handle->bus->current_handle = handle; @@ -51,177 +50,265 @@ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_exit(); } -bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, - uint8_t address, - const uint8_t* data, - uint8_t size, - uint32_t timeout) { - furi_check(handle->bus->current_handle == handle); - furi_assert(timeout > 0); +static bool + furi_hal_i2c_wait_for_idle(I2C_TypeDef* i2c, FuriHalI2cBegin begin, FuriHalCortexTimer timer) { + do { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } while(begin == FuriHalI2cBeginStart && LL_I2C_IsActiveFlag_BUSY(i2c)); + // Only check if the bus is busy if starting a new transaction, if not we already control the bus + + return true; +} + +static bool + furi_hal_i2c_wait_for_end(I2C_TypeDef* i2c, FuriHalI2cEnd end, FuriHalCortexTimer timer) { + // If ending the transaction with a stop condition, wait for it to be detected, otherwise wait for a transfer complete flag + bool wait_for_stop = end == FuriHalI2cEndStop; + uint32_t end_mask = (wait_for_stop) ? I2C_ISR_STOPF : (I2C_ISR_TC | I2C_ISR_TCR); + + while((i2c->ISR & end_mask) == 0) { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } + return true; +} + +static uint32_t + furi_hal_i2c_get_start_signal(FuriHalI2cBegin begin, bool ten_bit_address, bool read) { + switch(begin) { + case FuriHalI2cBeginRestart: + if(read) { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_READ : + LL_I2C_GENERATE_RESTART_7BIT_READ; + } else { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_WRITE : + LL_I2C_GENERATE_RESTART_7BIT_WRITE; + } + case FuriHalI2cBeginResume: + return LL_I2C_GENERATE_NOSTARTSTOP; + case FuriHalI2cBeginStart: + default: + return read ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_START_WRITE; + } +} + +static uint32_t furi_hal_i2c_get_end_signal(FuriHalI2cEnd end) { + switch(end) { + case FuriHalI2cEndAwaitRestart: + return LL_I2C_MODE_SOFTEND; + case FuriHalI2cEndPause: + return LL_I2C_MODE_RELOAD; + case FuriHalI2cEndStop: + default: + return LL_I2C_MODE_AUTOEND; + } +} + +static bool furi_hal_i2c_transfer_is_aborted(I2C_TypeDef* i2c) { + return LL_I2C_IsActiveFlag_STOP(i2c) && + !(LL_I2C_IsActiveFlag_TC(i2c) || LL_I2C_IsActiveFlag_TCR(i2c)); +} + +static bool furi_hal_i2c_transfer( + I2C_TypeDef* i2c, + uint8_t* data, + uint32_t size, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { bool ret = true; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } + while(size > 0) { + bool should_stop = furi_hal_cortex_timer_is_expired(timer) || + furi_hal_i2c_transfer_is_aborted(i2c); + + // Modifying the data pointer's data is UB if read is true + if(read && LL_I2C_IsActiveFlag_RXNE(i2c)) { + *data = LL_I2C_ReceiveData8(i2c); + data++; + size--; + } else if(!read && LL_I2C_IsActiveFlag_TXIS(i2c)) { + LL_I2C_TransmitData8(i2c, *data); + data++; + size--; } - if(!ret) { + // Exit on timeout or premature stop, probably caused by a nacked address or byte + if(should_stop) { + ret = size == 0; // If the transfer was over, still a success break; } + } - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + if(ret) { + ret = furi_hal_i2c_wait_for_end(i2c, end, timer); + } - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + LL_I2C_ClearFlag_STOP(i2c); return ret; } -bool furi_hal_i2c_rx( +static bool furi_hal_i2c_transaction( + I2C_TypeDef* i2c, + uint16_t address, + bool ten_bit, + uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { + uint32_t addr_size = ten_bit ? LL_I2C_ADDRSLAVE_10BIT : LL_I2C_ADDRSLAVE_7BIT; + uint32_t start_signal = furi_hal_i2c_get_start_signal(begin, ten_bit, read); + + if(!furi_hal_i2c_wait_for_idle(i2c, begin, timer)) { + return false; + } + + do { + uint8_t transfer_size = size; + FuriHalI2cEnd transfer_end = end; + + if(size > 255) { + transfer_size = 255; + transfer_end = FuriHalI2cEndPause; + } + + uint32_t end_signal = furi_hal_i2c_get_end_signal(transfer_end); + + LL_I2C_HandleTransfer(i2c, address, addr_size, transfer_size, end_signal, start_signal); + + if(!furi_hal_i2c_transfer(i2c, data, transfer_size, transfer_end, read, timer)) { + return false; + } + + size -= transfer_size; + data += transfer_size; + start_signal = LL_I2C_GENERATE_NOSTARTSTOP; + } while(size > 0); + + return true; +} + +bool furi_hal_i2c_rx_ext( FuriHalI2cBusHandle* handle, - uint8_t address, + uint16_t address, + bool ten_bit, uint8_t* data, - uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout) { furi_check(handle->bus->current_handle == handle); - furi_assert(timeout > 0); - bool ret = true; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, data, size, begin, end, true, timer); +} - if(!ret) { - break; - } +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + const uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout) { + furi_check(handle->bus->current_handle == handle); - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_READ); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_RXNE(handle->bus->i2c)) { - *data = LL_I2C_ReceiveData8(handle->bus->i2c); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, (uint8_t*)data, size, begin, end, false, timer); +} - return ret; +bool furi_hal_i2c_tx( + FuriHalI2cBusHandle* handle, + uint8_t address, + const uint8_t* data, + size_t size, + uint32_t timeout) { + furi_assert(timeout > 0); + + return furi_hal_i2c_tx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); +} + +bool furi_hal_i2c_rx( + FuriHalI2cBusHandle* handle, + uint8_t address, + uint8_t* data, + size_t size, + uint32_t timeout) { + furi_assert(timeout > 0); + + return furi_hal_i2c_rx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); } bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, - uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - uint8_t rx_size, + size_t rx_size, uint32_t timeout) { - if(furi_hal_i2c_tx(handle, address, tx_data, tx_size, timeout) && - furi_hal_i2c_rx(handle, address, rx_data, rx_size, timeout)) { - return true; - } else { - return false; - } + return furi_hal_i2c_tx_ext( + handle, + address, + false, + tx_data, + tx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout) && + furi_hal_i2c_rx_ext( + handle, + address, + false, + rx_data, + rx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout); } bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); bool ret = true; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - handle->bus->i2c->CR2 = - ((((uint32_t)(i2c_addr) & (I2C_CR2_SADD)) | (I2C_CR2_START) | (I2C_CR2_AUTOEND)) & - (~I2C_CR2_RD_WRN)); - - while((!LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) && - (!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c))) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - if(LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) { - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - LL_I2C_ClearFlag_NACK(handle->bus->i2c); - - // Clear STOP Flag generated by autoend - LL_I2C_ClearFlag_STOP(handle->bus->i2c); + if(!furi_hal_i2c_wait_for_idle(handle->bus->i2c, FuriHalI2cBeginStart, timer)) { + return false; + } - // Generate actual STOP - LL_I2C_GenerateStopCondition(handle->bus->i2c); + LL_I2C_HandleTransfer( + handle->bus->i2c, + i2c_addr, + LL_I2C_ADDRSLAVE_7BIT, + 0, + LL_I2C_MODE_AUTOEND, + LL_I2C_GENERATE_START_WRITE); - ret = false; - } + if(!furi_hal_i2c_wait_for_end(handle->bus->i2c, FuriHalI2cEndStop, timer)) { + return false; + } - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } + ret = !LL_I2C_IsActiveFlag_NACK(handle->bus->i2c); - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + LL_I2C_ClearFlag_NACK(handle->bus->i2c); + LL_I2C_ClearFlag_STOP(handle->bus->i2c); return ret; } @@ -257,7 +344,7 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout) { furi_check(handle); @@ -272,11 +359,12 @@ bool furi_hal_i2c_write_reg_8( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[2]; - tx_data[0] = reg_addr; - tx_data[1] = data; + const uint8_t tx_data[2] = { + reg_addr, + data, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 2, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 2, timeout); } bool furi_hal_i2c_write_reg_16( @@ -287,69 +375,42 @@ bool furi_hal_i2c_write_reg_16( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[3]; - tx_data[0] = reg_addr; - tx_data[1] = (data >> 8) & 0xFF; - tx_data[2] = data & 0xFF; + const uint8_t tx_data[3] = { + reg_addr, + (data >> 8) & 0xFF, + data & 0xFF, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 3, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 3, timeout); } bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); - bool ret = true; - uint8_t size = len + 1; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - if(!ret) { - break; - } - - LL_I2C_HandleTransfer( - handle->bus->i2c, - i2c_addr, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - if(size == len + 1) { - LL_I2C_TransmitData8(handle->bus->i2c, mem_addr); - } else { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - } - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); - - return ret; + return furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + &mem_addr, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndPause, + timeout) && + furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + data, + len, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + timeout); } diff --git a/firmware/targets/furi_hal_include/furi_hal_i2c.h b/firmware/targets/furi_hal_include/furi_hal_i2c.h index 566574ab82..f493655b4d 100644 --- a/firmware/targets/furi_hal_include/furi_hal_i2c.h +++ b/firmware/targets/furi_hal_include/furi_hal_i2c.h @@ -1,11 +1,11 @@ /** - * @file furi_hal_i2c.h - * I2C HAL API + * @file furi_hal_i2c.h I2C HAL API */ #pragma once #include +#include #include #include @@ -13,6 +13,35 @@ extern "C" { #endif +/** Transaction beginning signal */ +typedef enum { + /*!Begin the transaction by sending a START condition followed by the + * address */ + FuriHalI2cBeginStart, + /*!Begin the transaction by sending a RESTART condition followed by the + * address + * @note Must follow a transaction ended with + * FuriHalI2cEndAwaitRestart */ + FuriHalI2cBeginRestart, + /*!Continue the previous transaction with new data + * @note Must follow a transaction ended with FuriHalI2cEndPause and + * be of the same type (RX/TX) */ + FuriHalI2cBeginResume, +} FuriHalI2cBegin; + +/** Transaction end signal */ +typedef enum { + /*!End the transaction by sending a STOP condition */ + FuriHalI2cEndStop, + /*!End the transaction by clock stretching + * @note Must be followed by a transaction using + * FuriHalI2cBeginRestart */ + FuriHalI2cEndAwaitRestart, + /*!Pauses the transaction by clock stretching + * @note Must be followed by a transaction using FuriHalI2cBeginResume */ + FuriHalI2cEndPause, +} FuriHalI2cEnd; + /** Early Init I2C */ void furi_hal_i2c_init_early(); @@ -22,78 +51,126 @@ void furi_hal_i2c_deinit_early(); /** Init I2C */ void furi_hal_i2c_init(); -/** Acquire i2c bus handle +/** Acquire I2C bus handle * - * @return Instance of FuriHalI2cBus + * @param handle Pointer to FuriHalI2cBusHandle instance */ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle); -/** Release i2c bus handle - * - * @param bus instance of FuriHalI2cBus aquired in `furi_hal_i2c_acquire` +/** Release I2C bus handle + * + * @param handle Pointer to FuriHalI2cBusHandle instance acquired in + * `furi_hal_i2c_acquire` */ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); -/** Perform I2C tx transfer +/** Perform I2C TX transfer * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, + const uint8_t* data, + size_t size, + uint32_t timeout); + +/** + * Perform I2C TX transfer, with additional settings. + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, const uint8_t* data, - const uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout); -/** Perform I2C rx transfer +/** Perform I2C RX transfer * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, + uint8_t* data, + size_t size, + uint32_t timeout); + +/** Perform I2C RX transfer, with additional settings. + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_rx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, uint8_t* data, - const uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout); -/** Perform I2C tx and rx transfers +/** Perform I2C TX and RX transfers * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param tx_data pointer to tx data buffer - * @param tx_size size of tx data buffer - * @param rx_data pointer to rx data buffer - * @param rx_size size of rx data buffer - * @param timeout timeout in ticks + * @param tx_data Pointer to TX data buffer + * @param tx_size Size of TX data buffer + * @param rx_data Pointer to RX data buffer + * @param rx_size Size of RX data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, const uint8_t* tx_data, - const uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - const uint8_t rx_size, + size_t rx_size, uint32_t timeout); /** Check if I2C device presents on bus * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param timeout Timeout in milliseconds * * @return true if device present and is ready, false otherwise */ @@ -101,11 +178,11 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, /** Perform I2C device register read (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -118,11 +195,11 @@ bool furi_hal_i2c_read_reg_8( /** Perform I2C device register read (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -135,12 +212,12 @@ bool furi_hal_i2c_read_reg_16( /** Perform I2C device memory read * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -149,16 +226,16 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout); /** Perform I2C device register write (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -171,11 +248,11 @@ bool furi_hal_i2c_write_reg_8( /** Perform I2C device register write (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -188,12 +265,12 @@ bool furi_hal_i2c_write_reg_16( /** Perform I2C device memory * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -201,8 +278,8 @@ bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout); #ifdef __cplusplus From 182c8defb1af98ef5c11f3ece013061da4283112 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:06:45 +0900 Subject: [PATCH 2/8] [FL-3458] Add confirmation before exiting USB-UART (#3043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add confirmation before exiting USB-UART * Redo the confirm view to be a scene to ignore the back button Co-authored-by: hedger Co-authored-by: あく --- applications/main/gpio/gpio_app.c | 7 +++ applications/main/gpio/gpio_app_i.h | 3 ++ .../main/gpio/scenes/gpio_scene_config.h | 1 + .../gpio/scenes/gpio_scene_exit_confirm.c | 44 +++++++++++++++++++ .../main/gpio/scenes/gpio_scene_usb_uart.c | 3 ++ 5 files changed, 58 insertions(+) create mode 100644 applications/main/gpio/scenes/gpio_scene_exit_confirm.c diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 1ecff1ec2e..020fbf79a1 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -43,6 +43,11 @@ GpioApp* gpio_app_alloc() { app->notifications = furi_record_open(RECORD_NOTIFICATION); + // Dialog view + app->dialog = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewExitConfirm, dialog_ex_get_view(app->dialog)); + app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( app->view_dispatcher, @@ -79,10 +84,12 @@ void gpio_app_free(GpioApp* app) { view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUart); view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCfg); view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewExitConfirm); variable_item_list_free(app->var_item_list); widget_free(app->widget); gpio_test_free(app->gpio_test); gpio_usb_uart_free(app->gpio_usb_uart); + dialog_ex_free(app->dialog); // View dispatcher view_dispatcher_free(app->view_dispatcher); diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index 03fe9f4894..d54ffd3682 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "views/gpio_test.h" #include "views/gpio_usb_uart.h" #include @@ -23,6 +24,7 @@ struct GpioApp { ViewDispatcher* view_dispatcher; SceneManager* scene_manager; Widget* widget; + DialogEx* dialog; VariableItemList* var_item_list; VariableItem* var_item_flow; @@ -39,4 +41,5 @@ typedef enum { GpioAppViewUsbUart, GpioAppViewUsbUartCfg, GpioAppViewUsbUartCloseRpc, + GpioAppViewExitConfirm, } GpioAppView; diff --git a/applications/main/gpio/scenes/gpio_scene_config.h b/applications/main/gpio/scenes/gpio_scene_config.h index 3406e42d3a..d6fd24d19d 100644 --- a/applications/main/gpio/scenes/gpio_scene_config.h +++ b/applications/main/gpio/scenes/gpio_scene_config.h @@ -3,3 +3,4 @@ ADD_SCENE(gpio, test, Test) ADD_SCENE(gpio, usb_uart, UsbUart) ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg) ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc) +ADD_SCENE(gpio, exit_confirm, ExitConfirm) diff --git a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c new file mode 100644 index 0000000000..efb0734a31 --- /dev/null +++ b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c @@ -0,0 +1,44 @@ +#include "gpio_app_i.h" + +void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { + GpioApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void gpio_scene_exit_confirm_on_enter(void* context) { + GpioApp* app = context; + DialogEx* dialog = app->dialog; + + dialog_ex_set_context(dialog, app); + dialog_ex_set_left_button_text(dialog, "Exit"); + dialog_ex_set_right_button_text(dialog, "Stay"); + dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); + dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); +} + +bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + consumed = scene_manager_previous_scene(app->scene_manager); + } else if(event.event == DialogExResultLeft) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void gpio_scene_exit_confirm_on_exit(void* context) { + GpioApp* app = context; + + // Clean view + dialog_ex_reset(app->dialog); +} diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index c5e085192b..9a3514ca4f 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -42,6 +42,9 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg); return true; + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm); + return true; } else if(event.type == SceneManagerEventTypeTick) { uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt; uint32_t rx_cnt_last = scene_usb_uart->state.rx_cnt; From a089aeb2bd60d4c39905a21af87bfb7603bb4e9d Mon Sep 17 00:00:00 2001 From: Filipe Paz Rodrigues Date: Thu, 21 Sep 2023 02:09:00 -0700 Subject: [PATCH 3/8] Add Initial CCID support (#3048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Initial CCID support * Sync api symbols * Format sources Co-authored-by: あく --- applications/debug/ccid_test/application.fam | 16 + applications/debug/ccid_test/ccid_test_app.c | 159 ++++++ .../debug/ccid_test/iso7816_t0_apdu.c | 36 ++ .../debug/ccid_test/iso7816_t0_apdu.h | 32 ++ firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- .../targets/f7/furi_hal/furi_hal_usb_ccid.c | 498 ++++++++++++++++++ firmware/targets/furi_hal_include/furi_hal.h | 1 + .../targets/furi_hal_include/furi_hal_usb.h | 1 + .../furi_hal_include/furi_hal_usb_ccid.h | 31 ++ lib/libusb_stm32 | 2 +- 11 files changed, 789 insertions(+), 3 deletions(-) create mode 100644 applications/debug/ccid_test/application.fam create mode 100644 applications/debug/ccid_test/ccid_test_app.c create mode 100644 applications/debug/ccid_test/iso7816_t0_apdu.c create mode 100644 applications/debug/ccid_test/iso7816_t0_apdu.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c create mode 100644 firmware/targets/furi_hal_include/furi_hal_usb_ccid.h diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam new file mode 100644 index 0000000000..e0cbc8d85e --- /dev/null +++ b/applications/debug/ccid_test/application.fam @@ -0,0 +1,16 @@ +App( + appid="ccid_test", + name="CCID Debug", + apptype=FlipperAppType.DEBUG, + entry_point="ccid_test_app", + cdefines=["CCID_TEST"], + requires=[ + "gui", + ], + provides=[ + "ccid_test", + ], + stack_size=1 * 1024, + order=120, + fap_category="Debug", +) diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c new file mode 100644 index 0000000000..a2f936d742 --- /dev/null +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -0,0 +1,159 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "iso7816_t0_apdu.h" + +typedef enum { + EventTypeInput, +} EventType; + +typedef struct { + Gui* gui; + ViewPort* view_port; + FuriMessageQueue* event_queue; + FuriHalUsbCcidConfig ccid_cfg; +} CcidTestApp; + +typedef struct { + union { + InputEvent input; + }; + EventType type; +} CcidTestAppEvent; + +typedef enum { + CcidTestSubmenuIndexInsertSmartcard, + CcidTestSubmenuIndexRemoveSmartcard, + CcidTestSubmenuIndexInsertSmartcardReader +} SubmenuIndex; + +void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) { + UNUSED(context); + + iso7816_answer_to_reset(atrBuffer, atrlen); +} + +void xfr_datablock_callback(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context) { + UNUSED(context); + + struct ISO7816_Response_APDU responseAPDU; + //class not supported + responseAPDU.SW1 = 0x6E; + responseAPDU.SW2 = 0x00; + + iso7816_write_response_apdu(&responseAPDU, dataBlock, dataBlockLen); +} + +static const CcidCallbacks ccid_cb = { + icc_power_on_callback, + xfr_datablock_callback, +}; + +static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "CCID Test App"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void ccid_test_app__input_callback(InputEvent* input_event, void* ctx) { + FuriMessageQueue* event_queue = ctx; + + CcidTestAppEvent event; + event.type = EventTypeInput; + event.input = *input_event; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +uint32_t ccid_test_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +CcidTestApp* ccid_test_app_alloc() { + CcidTestApp* app = malloc(sizeof(CcidTestApp)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + //viewport + app->view_port = view_port_alloc(); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + view_port_draw_callback_set(app->view_port, ccid_test_app_render_callback, NULL); + + //message queue + app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent)); + furi_check(app->event_queue); + view_port_input_callback_set(app->view_port, ccid_test_app__input_callback, app->event_queue); + + return app; +} + +void ccid_test_app_free(CcidTestApp* app) { + furi_assert(app); + + //message queue + furi_message_queue_free(app->event_queue); + + //view port + gui_remove_view_port(app->gui, app->view_port); + view_port_free(app->view_port); + + // Close gui record + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Free rest + free(app); +} + +int32_t ccid_test_app(void* p) { + UNUSED(p); + + //setup view + CcidTestApp* app = ccid_test_app_alloc(); + + //setup CCID USB + // On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist + app->ccid_cfg.vid = 0x1234; + app->ccid_cfg.pid = 0x5678; + + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb); + furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true); + + //handle button events + CcidTestAppEvent event; + while(1) { + FuriStatus event_status = + furi_message_queue_get(app->event_queue, &event, FuriWaitForever); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + break; + } + } + } + view_port_update(app->view_port); + } + + //tear down USB + furi_hal_usb_set_config(usb_mode_prev, NULL); + furi_hal_ccid_set_callbacks(NULL); + + //teardown view + ccid_test_app_free(app); + return 0; +} diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816_t0_apdu.c new file mode 100644 index 0000000000..29f5f7a86c --- /dev/null +++ b/applications/debug/ccid_test/iso7816_t0_apdu.c @@ -0,0 +1,36 @@ +/* Implements rudimentary iso7816-3 support for APDU (T=0) */ +#include +#include +#include +#include "iso7816_t0_apdu.h" + +void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen) { + //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00 + uint8_t AtrBuffer[2] = { + 0x3B, //TS (direct convention) + 0x00 // T0 (Y(1): b0000, K: 0 (historical bytes)) + }; + *atrlen = 2; + + memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen)); +} + +void iso7816_read_command_apdu( + struct ISO7816_Command_APDU* command, + const uint8_t* dataBuffer, + uint32_t dataLen) { + furi_assert(dataLen <= 4); + command->CLA = dataBuffer[0]; + command->INS = dataBuffer[1]; + command->P1 = dataBuffer[2]; + command->P2 = dataBuffer[3]; +} + +void iso7816_write_response_apdu( + const struct ISO7816_Response_APDU* response, + uint8_t* dataBuffer, + uint32_t* dataLen) { + dataBuffer[0] = response->SW1; + dataBuffer[1] = response->SW2; + *dataLen = 2; +} \ No newline at end of file diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h new file mode 100644 index 0000000000..8a8c99f85d --- /dev/null +++ b/applications/debug/ccid_test/iso7816_t0_apdu.h @@ -0,0 +1,32 @@ +#ifndef _ISO7816_T0_APDU_H_ +#define _ISO7816_T0_APDU_H_ + +#include + +struct ISO7816_Command_APDU { + //header + uint8_t CLA; + uint32_t INS; + uint8_t P1; + uint8_t P2; + + //body + uint8_t Nc; + uint8_t Ne; +} __attribute__((packed)); + +struct ISO7816_Response_APDU { + uint8_t SW1; + uint32_t SW2; +} __attribute__((packed)); + +void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); +void iso7816_read_command_apdu( + struct ISO7816_Command_APDU* command, + const uint8_t* dataBuffer, + uint32_t dataLen); +void iso7816_write_response_apdu( + const struct ISO7816_Response_APDU* response, + uint8_t* dataBuffer, + uint32_t* dataLen); +#endif //_ISO7816_T0_APDU_H_ diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f4e990becb..338697ad70 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.0,, +Version,+,39.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -76,6 +76,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, @@ -105,6 +106,7 @@ Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, Header,-,lib/libusb_stm32/inc/stm32_compat.h,, Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_ccid.h,, Header,+,lib/libusb_stm32/inc/usb_cdc.h,, Header,+,lib/libusb_stm32/inc/usb_cdca.h,, Header,+,lib/libusb_stm32/inc/usb_cdce.h,, @@ -1008,6 +1010,9 @@ Function,+,furi_hal_bus_enable,void,FuriHalBus Function,+,furi_hal_bus_init_early,void, Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus Function,+,furi_hal_bus_reset,void,FuriHalBus +Function,+,furi_hal_ccid_ccid_insert_smartcard,void, +Function,+,furi_hal_ccid_ccid_remove_smartcard,void, +Function,+,furi_hal_ccid_set_callbacks,void,CcidCallbacks* Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -2692,6 +2697,7 @@ Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, Variable,-,suboptarg,char*, +Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d37d5f333d..f3f42d0f09 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.0,, +Version,+,39.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -82,6 +82,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, @@ -123,6 +124,7 @@ Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, Header,-,lib/libusb_stm32/inc/stm32_compat.h,, Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_ccid.h,, Header,+,lib/libusb_stm32/inc/usb_cdc.h,, Header,+,lib/libusb_stm32/inc/usb_cdca.h,, Header,+,lib/libusb_stm32/inc/usb_cdce.h,, @@ -1079,6 +1081,9 @@ Function,+,furi_hal_bus_enable,void,FuriHalBus Function,+,furi_hal_bus_init_early,void, Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus Function,+,furi_hal_bus_reset,void,FuriHalBus +Function,+,furi_hal_ccid_ccid_insert_smartcard,void, +Function,+,furi_hal_ccid_ccid_remove_smartcard,void, +Function,+,furi_hal_ccid_set_callbacks,void,CcidCallbacks* Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -3479,6 +3484,7 @@ Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, Variable,+,subghz_protocol_registry,const SubGhzProtocolRegistry, Variable,-,suboptarg,char*, +Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c new file mode 100644 index 0000000000..559713d015 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include + +#include "usb.h" +#include "usb_ccid.h" + +static const uint8_t USB_DEVICE_NO_CLASS = 0x0; +static const uint8_t USB_DEVICE_NO_SUBCLASS = 0x0; +static const uint8_t USB_DEVICE_NO_PROTOCOL = 0x0; + +#define FIXED_CONTROL_ENDPOINT_SIZE 8 +#define IF_NUM_MAX 1 + +#define CCID_VID_DEFAULT 0x1234 +#define CCID_PID_DEFAULT 0xABCD +#define CCID_TOTAL_SLOTS 1 +#define CCID_SLOT_INDEX 0 + +#define CCID_DATABLOCK_SIZE 256 + +#define ENDPOINT_DIR_IN 0x80 +#define ENDPOINT_DIR_OUT 0x00 + +#define INTERFACE_ID_CCID 0 + +#define CCID_IN_EPADDR (ENDPOINT_DIR_IN | 2) + +/** Endpoint address of the CCID data OUT endpoint, for host-to-device data transfers. */ +#define CCID_OUT_EPADDR (ENDPOINT_DIR_OUT | 1) + +/** Endpoint size in bytes of the CCID data being sent between IN and OUT endpoints. */ +#define CCID_EPSIZE 64 + +struct CcidIntfDescriptor { + struct usb_interface_descriptor ccid; + struct usb_ccid_descriptor ccid_desc; + struct usb_endpoint_descriptor ccid_bulk_in; + struct usb_endpoint_descriptor ccid_bulk_out; +} __attribute__((packed)); + +struct CcidConfigDescriptor { + struct usb_config_descriptor config; + struct CcidIntfDescriptor intf_0; +} __attribute__((packed)); + +enum CCID_Features_Auto_t { + CCID_Features_Auto_None = 0x0, + CCID_Features_Auto_ParameterConfiguration = 0x2, + CCID_Features_Auto_ICCActivation = 0x4, + CCID_Features_Auto_VoltageSelection = 0x8, + + CCID_Features_Auto_ICCClockFrequencyChange = 0x10, + CCID_Features_Auto_ICCBaudRateChange = 0x20, + CCID_Features_Auto_ParameterNegotiation = 0x40, + CCID_Features_Auto_PPS = 0x80, +}; + +enum CCID_Features_ExchangeLevel_t { + CCID_Features_ExchangeLevel_TPDU = 0x00010000, + CCID_Features_ExchangeLevel_ShortAPDU = 0x00020000, + CCID_Features_ExchangeLevel_ShortExtendedAPDU = 0x00040000 +}; + +/* Device descriptor */ +static struct usb_device_descriptor ccid_device_desc = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 0, 0), + .bDeviceClass = USB_DEVICE_NO_CLASS, + .bDeviceSubClass = USB_DEVICE_NO_SUBCLASS, + .bDeviceProtocol = USB_DEVICE_NO_PROTOCOL, + .bMaxPacketSize0 = FIXED_CONTROL_ENDPOINT_SIZE, + .idVendor = CCID_VID_DEFAULT, + .idProduct = CCID_PID_DEFAULT, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = UsbDevManuf, + .iProduct = UsbDevProduct, + .iSerialNumber = UsbDevSerial, + .bNumConfigurations = 1, +}; + +/* Device configuration descriptor*/ +static const struct CcidConfigDescriptor ccid_cfg_desc = { + .config = + { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = USB_DTYPE_CONFIGURATION, + .wTotalLength = sizeof(struct CcidConfigDescriptor), + .bNumInterfaces = 1, + + .bConfigurationValue = 1, + .iConfiguration = NO_DESCRIPTOR, + .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED, + .bMaxPower = USB_CFG_POWER_MA(100), + }, + .intf_0 = + { + .ccid = + {.bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + + .bInterfaceNumber = INTERFACE_ID_CCID, + .bAlternateSetting = 0x00, + .bNumEndpoints = 2, + + .bInterfaceClass = USB_CLASS_CCID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + + .iInterface = NO_DESCRIPTOR + + }, + .ccid_desc = + {.bLength = sizeof(struct usb_ccid_descriptor), + .bDescriptorType = USB_DTYPE_CCID_FUNCTIONAL, + .bcdCCID = CCID_CURRENT_SPEC_RELEASE_NUMBER, + .bMaxSlotIndex = 0x00, + .bVoltageSupport = CCID_VOLTAGESUPPORT_5V, + .dwProtocols = 0x01, //T0 + .dwDefaultClock = 16000, //16MHz + .dwMaximumClock = 16000, //16MHz + .bNumClockSupported = 0, + .dwDataRate = 307200, + .dwMaxDataRate = 307200, + .bNumDataRatesSupported = 0, + .dwMaxIFSD = 2038, + .dwSynchProtocols = 0, + .dwMechanical = 0, + .dwFeatures = CCID_Features_ExchangeLevel_ShortAPDU | + CCID_Features_Auto_ParameterConfiguration | + CCID_Features_Auto_ICCActivation | + CCID_Features_Auto_VoltageSelection, + .dwMaxCCIDMessageLength = 0x0c00, + .bClassGetResponse = 0xff, + .bClassEnvelope = 0xff, + .wLcdLayout = 0, + .bPINSupport = 0, + .bMaxCCIDBusySlots = 1}, + .ccid_bulk_in = + {.bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = CCID_IN_EPADDR, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = CCID_EPSIZE, + .bInterval = 0x05 + + }, + .ccid_bulk_out = + {.bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = CCID_OUT_EPADDR, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = CCID_EPSIZE, + .bInterval = 0x05}, + }, +}; + +static void ccid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); +static void ccid_deinit(usbd_device* dev); +static void ccid_on_wakeup(usbd_device* dev); +static void ccid_on_suspend(usbd_device* dev); + +FuriHalUsbInterface usb_ccid = { + .init = ccid_init, + .deinit = ccid_deinit, + .wakeup = ccid_on_wakeup, + .suspend = ccid_on_suspend, + + .dev_descr = (struct usb_device_descriptor*)&ccid_device_desc, + + .str_manuf_descr = NULL, + .str_prod_descr = NULL, + .str_serial_descr = NULL, + + .cfg_descr = (void*)&ccid_cfg_desc, +}; + +static usbd_respond ccid_ep_config(usbd_device* dev, uint8_t cfg); +static usbd_respond ccid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); +static usbd_device* usb_dev; +static bool connected = false; +static bool smartcard_inserted = true; +static CcidCallbacks* callbacks[CCID_TOTAL_SLOTS] = {NULL}; + +static void* ccid_set_string_descr(char* str) { + furi_assert(str); + + size_t len = strlen(str); + struct usb_string_descriptor* dev_str_desc = malloc(len * 2 + 2); + dev_str_desc->bLength = len * 2 + 2; + dev_str_desc->bDescriptorType = USB_DTYPE_STRING; + for(size_t i = 0; i < len; i++) dev_str_desc->wString[i] = str[i]; + + return dev_str_desc; +} + +static void ccid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { + UNUSED(intf); + + FuriHalUsbCcidConfig* cfg = (FuriHalUsbCcidConfig*)ctx; + + usb_dev = dev; + + usb_ccid.dev_descr->iManufacturer = 0; + usb_ccid.dev_descr->iProduct = 0; + usb_ccid.str_manuf_descr = NULL; + usb_ccid.str_prod_descr = NULL; + usb_ccid.dev_descr->idVendor = CCID_VID_DEFAULT; + usb_ccid.dev_descr->idProduct = CCID_PID_DEFAULT; + + if(cfg != NULL) { + usb_ccid.dev_descr->idVendor = cfg->vid; + usb_ccid.dev_descr->idProduct = cfg->pid; + + if(cfg->manuf[0] != '\0') { + usb_ccid.str_manuf_descr = ccid_set_string_descr(cfg->manuf); + usb_ccid.dev_descr->iManufacturer = UsbDevManuf; + } + + if(cfg->product[0] != '\0') { + usb_ccid.str_prod_descr = ccid_set_string_descr(cfg->product); + usb_ccid.dev_descr->iProduct = UsbDevProduct; + } + } + + usbd_reg_config(dev, ccid_ep_config); + usbd_reg_control(dev, ccid_control); + + usbd_connect(dev, true); +} + +static void ccid_deinit(usbd_device* dev) { + usbd_reg_config(dev, NULL); + usbd_reg_control(dev, NULL); + + free(usb_ccid.str_prod_descr); + free(usb_ccid.str_serial_descr); +} + +static void ccid_on_wakeup(usbd_device* dev) { + UNUSED(dev); + connected = true; +} + +static void ccid_on_suspend(usbd_device* dev) { + UNUSED(dev); + connected = false; +} + +struct ccid_bulk_message_header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} __attribute__((packed)); + +static struct rdr_to_pc_slot_status responseSlotStatus; +static struct rdr_to_pc_data_block responseDataBlock; +static struct rdr_to_pc_parameters_t0 responseParameters; +uint8_t SendDataBlock[CCID_DATABLOCK_SIZE]; + +uint8_t CALLBACK_CCID_GetSlotStatus(uint8_t slot, uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +uint8_t + CALLBACK_CCID_IccPowerOn(uint8_t slot, uint8_t* atrBuffer, uint32_t* atrlen, uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + if(callbacks[CCID_SLOT_INDEX] != NULL) { + callbacks[CCID_SLOT_INDEX]->icc_power_on_callback(atrBuffer, atrlen, NULL); + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; + } + + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +uint8_t CALLBACK_CCID_XfrBlock( + uint8_t slot, + uint8_t* dataBlock, + uint32_t* dataBlockLen, + uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + if(callbacks[CCID_SLOT_INDEX] != NULL) { + callbacks[CCID_SLOT_INDEX]->xfr_datablock_callback(dataBlock, dataBlockLen, NULL); + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; + } + + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +void furi_hal_ccid_ccid_insert_smartcard() { + smartcard_inserted = true; +} + +void furi_hal_ccid_ccid_remove_smartcard() { + smartcard_inserted = false; +} + +void furi_hal_ccid_set_callbacks(CcidCallbacks* cb) { + callbacks[CCID_SLOT_INDEX] = cb; +} + +static void ccid_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(event); + UNUSED(ep); +} + +static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + + if(event == usbd_evt_eprx) { + if(connected == false) return; + + struct ccid_bulk_message_header message; + usbd_ep_read(usb_dev, ep, &message, sizeof(message)); + + uint8_t Status; + uint8_t Error = CCID_ERROR_NOERROR; + + uint32_t dataBlockLen = 0; + uint8_t* dataBlockBuffer = NULL; + + if(message.bMessageType == PC_TO_RDR_GETSLOTSTATUS) { + responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; + responseSlotStatus.dwLength = 0; + responseSlotStatus.bSlot = message.bSlot; + responseSlotStatus.bSeq = message.bSeq; + + responseSlotStatus.bClockStatus = 0; + + Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); + } else if(message.bMessageType == PC_TO_RDR_ICCPOWERON) { + responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock.bSlot = message.bSlot; + responseDataBlock.bSeq = message.bSeq; + responseDataBlock.bChainParameter = 0; + + dataBlockLen = 0; + dataBlockBuffer = (uint8_t*)SendDataBlock; + Status = CALLBACK_CCID_IccPowerOn( + message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); + + furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); + responseDataBlock.dwLength = dataBlockLen; + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); + usbd_ep_write( + usb_dev, + CCID_IN_EPADDR, + &responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); + } else if(message.bMessageType == PC_TO_RDR_ICCPOWEROFF) { + responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; + responseSlotStatus.dwLength = 0; + responseSlotStatus.bSlot = message.bSlot; + responseSlotStatus.bSeq = message.bSeq; + + responseSlotStatus.bClockStatus = 0; + + uint8_t Status; + uint8_t Error = CCID_ERROR_NOERROR; + Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); + } else if(message.bMessageType == PC_TO_RDR_SETPARAMETERS) { + responseParameters.bMessageType = RDR_TO_PC_PARAMETERS; + responseParameters.bSlot = message.bSlot; + responseParameters.bSeq = message.bSeq; + responseParameters.bProtocolNum = 0; //T0 + + uint8_t Status = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR; + uint8_t Error = CCID_ERROR_NOERROR; + + responseParameters.bStatus = Status; + responseParameters.bError = Error; + + responseParameters.dwLength = sizeof(struct rdr_to_pc_parameters_t0); + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseParameters, sizeof(responseParameters)); + } else if(message.bMessageType == PC_TO_RDR_XFRBLOCK) { + responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock.bSlot = message.bSlot; + responseDataBlock.bSeq = message.bSeq; + responseDataBlock.bChainParameter = 0; + + dataBlockLen = 0; + dataBlockBuffer = (uint8_t*)SendDataBlock; + Status = CALLBACK_CCID_XfrBlock( + message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); + + furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); + responseDataBlock.dwLength = dataBlockLen; + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); + usbd_ep_write( + usb_dev, + CCID_IN_EPADDR, + &responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); + } + } +} + +/* Configure endpoints */ +static usbd_respond ccid_ep_config(usbd_device* dev, uint8_t cfg) { + switch(cfg) { + case 0: + /* deconfiguring device */ + usbd_ep_deconfig(dev, CCID_IN_EPADDR); + usbd_ep_deconfig(dev, CCID_OUT_EPADDR); + usbd_reg_endpoint(dev, CCID_IN_EPADDR, 0); + usbd_reg_endpoint(dev, CCID_OUT_EPADDR, 0); + return usbd_ack; + case 1: + /* configuring device */ + usbd_ep_config(dev, CCID_IN_EPADDR, USB_EPTYPE_BULK, CCID_EPSIZE); + usbd_ep_config(dev, CCID_OUT_EPADDR, USB_EPTYPE_BULK, CCID_EPSIZE); + usbd_reg_endpoint(dev, CCID_IN_EPADDR, ccid_rx_ep_callback); + usbd_reg_endpoint(dev, CCID_OUT_EPADDR, ccid_tx_ep_callback); + return usbd_ack; + default: + return usbd_fail; + } +} + +/* Control requests handler */ +static usbd_respond ccid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { + UNUSED(callback); + /* CDC control requests */ + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_INTERFACE | USB_REQ_CLASS) && + (req->wIndex == 0 || req->wIndex == 2)) { + switch(req->bRequest) { + case CCID_ABORT: + return usbd_fail; + case CCID_GET_CLOCK_FREQUENCIES: + dev->status.data_ptr = (void*)&(ccid_cfg_desc.intf_0.ccid_desc.dwDefaultClock); + dev->status.data_count = sizeof(ccid_cfg_desc.intf_0.ccid_desc.dwDefaultClock); + return usbd_ack; + default: + return usbd_fail; + } + } + return usbd_fail; +} \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 9341dccecb..e6fd9eb1cc 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -35,6 +35,7 @@ struct STOP_EXTERNING_ME {}; #include #include #include +#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/firmware/targets/furi_hal_include/furi_hal_usb.h index 8b49f6c653..a98797955d 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb.h @@ -28,6 +28,7 @@ extern FuriHalUsbInterface usb_cdc_single; extern FuriHalUsbInterface usb_cdc_dual; extern FuriHalUsbInterface usb_hid; extern FuriHalUsbInterface usb_hid_u2f; +extern FuriHalUsbInterface usb_ccid; typedef enum { FuriHalUsbStateEventReset, diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h new file mode 100644 index 0000000000..e3ee0dfc38 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h @@ -0,0 +1,31 @@ +#pragma once +#include "hid_usage_desktop.h" +#include "hid_usage_button.h" +#include "hid_usage_keyboard.h" +#include "hid_usage_consumer.h" +#include "hid_usage_led.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t vid; + uint16_t pid; + char manuf[32]; + char product[32]; +} FuriHalUsbCcidConfig; + +typedef struct { + void (*icc_power_on_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); + void (*xfr_datablock_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); +} CcidCallbacks; + +void furi_hal_ccid_set_callbacks(CcidCallbacks* cb); + +void furi_hal_ccid_ccid_insert_smartcard(); +void furi_hal_ccid_ccid_remove_smartcard(); + +#ifdef __cplusplus +} +#endif diff --git a/lib/libusb_stm32 b/lib/libusb_stm32 index 9168e2a31d..6ca2857519 160000 --- a/lib/libusb_stm32 +++ b/lib/libusb_stm32 @@ -1 +1 @@ -Subproject commit 9168e2a31db946326fb84016a74ea2ab5bf87f54 +Subproject commit 6ca2857519f996244f7b324dd227fdf0a075fffb From 3fbb9f24f841417505c540f142bd81d935c9be0e Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:29:28 +0900 Subject: [PATCH 4/8] [FL-3583] Fix multiline aligned text going out of bounds (again) (#3084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/elements.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 37ecfde4c6..a6ab84fb8d 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -290,6 +290,7 @@ void elements_multiline_text_aligned( } else if((y + font_height) > canvas_height(canvas)) { line = furi_string_alloc_printf("%.*s...\n", chars_fit, start); } else { + chars_fit -= 1; // account for the dash line = furi_string_alloc_printf("%.*s-\n", chars_fit, start); } canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line)); From a73a83f04d74e66efbef695b1c5ecd6d78b64bf2 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 21 Sep 2023 11:36:46 +0200 Subject: [PATCH 5/8] IR Universal Audio Remote: Amend 98d4309 -NAD Amp (#2954) (#3092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original PR was cut short Co-authored-by: あく --- assets/resources/infrared/assets/audio.ir | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index 5cdc9048a8..d5a0f86dc3 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -424,4 +424,5 @@ command: 05 FA 00 00 name: Next type: parsed protocol: NECext -address: 87 7C 00 0 +address: 87 7C 00 00 +command: 06 F9 00 00 From b80dfbe0c5a8803f46404310220755dc6dee552e Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 21 Sep 2023 12:44:55 +0300 Subject: [PATCH 6/8] github: fixed grep arg for RC builds (#3093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: fixed grep arg for RC builds * scripts: fbt: checking for .git existence, not for it being a dir Co-authored-by: あく --- .github/actions/submit_sdk/action.yml | 2 +- fbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index 269185d5ad..b515b52855 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -58,7 +58,7 @@ runs: echo "API version is already released" exit 0 fi - if ! echo "${{ inputs.firmware-version }}" | grep -q "-rc" ; then + if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then SDK_ID=$(jq -r ._id found_sdk.json) echo "Marking SDK $SDK_ID as released" curl -X 'POST' \ diff --git a/fbt b/fbt index 471285a76c..26f325d455 100755 --- a/fbt +++ b/fbt @@ -25,7 +25,7 @@ if [ -z "$FBT_VERBOSE" ]; then fi if [ -z "$FBT_NO_SYNC" ]; then - if [ ! -d "$SCRIPT_PATH/.git" ]; then + if [ ! -e "$SCRIPT_PATH/.git" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi From 1891d54baf962b954b1541b8109bf842aed88273 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 21 Sep 2023 15:56:00 +0300 Subject: [PATCH 7/8] [FL-3600] Added `fal_embedded` parameter for PLUGIN apps (#3083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt, ufbt: added `fal_embedded` parameter for PLIGIN apps, to embed them into .fap * fbt: fixed dependency settings for assets * fbt: extapps: Removed unneeded casts * fbt: extapps: code simplification * fbt: fal_embedded: fixed dependency relations Co-authored-by: あく --- documentation/AppManifests.md | 1 + firmware.scons | 2 +- scripts/fbt/appmanifest.py | 13 ++++ scripts/fbt/fapassets.py | 28 +++++---- scripts/fbt_tools/fbt_extapps.py | 105 ++++++++++++++++++------------- 5 files changed, 93 insertions(+), 56 deletions(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index b48a6b8edd..72c15ad48f 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -56,6 +56,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): - **fap_weburl**: string, may be empty. Application's homepage. - **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. - **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application. Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. diff --git a/firmware.scons b/firmware.scons index 657822700d..82f775d719 100644 --- a/firmware.scons +++ b/firmware.scons @@ -143,7 +143,7 @@ fwenv.PrepareApplicationsBuild() # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: - fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT="${BUILD_DIR}/.extapps") + fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", exports={"ENV": fwenv}, diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 7bb8e40b2d..ff49707b1d 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -79,11 +79,19 @@ class Library: fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list) fap_private_libs: List[Library] = field(default_factory=list) fap_file_assets: Optional[str] = None + fal_embedded: bool = False # Internally used by fbt _appmanager: Optional["AppManager"] = None _appdir: Optional[object] = None _apppath: Optional[str] = None _plugins: List["FlipperApplication"] = field(default_factory=list) + _assets_dirs: List[object] = field(default_factory=list) + _section_fapmeta: Optional[object] = None + _section_fapfileassets: Optional[object] = None + + @property + def embeds_plugins(self): + return any(plugin.fal_embedded for plugin in self._plugins) def supports_hardware_target(self, target: str): return target in self.targets or "all" in self.targets @@ -137,6 +145,11 @@ def _validate_app_params(self, *args, **kw): raise FlipperManifestException( f"Plugin {kw.get('appid')} must have 'requires' in manifest" ) + else: + if kw.get("fal_embedded"): + raise FlipperManifestException( + f"App {kw.get('appid')} cannot have fal_embedded set" + ) # Harmless - cdefines for external apps are meaningless # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): # raise FlipperManifestException( diff --git a/scripts/fbt/fapassets.py b/scripts/fbt/fapassets.py index 9902fd79a1..cd65ad20fb 100644 --- a/scripts/fbt/fapassets.py +++ b/scripts/fbt/fapassets.py @@ -1,7 +1,7 @@ import hashlib import os import struct -from typing import TypedDict +from typing import TypedDict, List class File(TypedDict): @@ -32,20 +32,19 @@ class FileBundler: u8[] file_content """ - def __init__(self, directory_path: str): - self.directory_path = directory_path - self.file_list: list[File] = [] - self.directory_list: list[Dir] = [] - self._gather() + def __init__(self, assets_dirs: List[object]): + self.src_dirs = list(assets_dirs) - def _gather(self): - for root, dirs, files in os.walk(self.directory_path): + def _gather(self, directory_path: str): + if not os.path.isdir(directory_path): + raise Exception(f"Assets directory {directory_path} does not exist") + for root, dirs, files in os.walk(directory_path): for file_info in files: file_path = os.path.join(root, file_info) file_size = os.path.getsize(file_path) self.file_list.append( { - "path": os.path.relpath(file_path, self.directory_path), + "path": os.path.relpath(file_path, directory_path), "size": file_size, "content_path": file_path, } @@ -57,15 +56,20 @@ def _gather(self): # os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path) # ) self.directory_list.append( - { - "path": os.path.relpath(dir_path, self.directory_path), - } + {"path": os.path.relpath(dir_path, directory_path)} ) self.file_list.sort(key=lambda f: f["path"]) self.directory_list.sort(key=lambda d: d["path"]) + def _process_src_dirs(self): + self.file_list: list[File] = [] + self.directory_list: list[Dir] = [] + for directory_path in self.src_dirs: + self._gather(directory_path) + def export(self, target_path: str): + self._process_src_dirs() self._md5_hash = hashlib.md5() with open(target_path, "wb") as f: # Write header magic and version diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 6059628f00..aa6354c9e3 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -3,7 +3,7 @@ import pathlib import shutil from dataclasses import dataclass, field -from typing import Optional, Dict, List +from typing import Dict, List, Optional import SCons.Warnings from ansi.color import fg @@ -32,11 +32,15 @@ class FlipperExternalAppInfo: class AppBuilder: + @staticmethod + def get_app_work_dir(env, app): + return env["EXT_APPS_WORK_DIR"].Dir(app.appid) + def __init__(self, env, app): self.fw_env = env self.app = app - self.ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") - self.app_work_dir = os.path.join(self.ext_apps_work_dir, self.app.appid) + self.ext_apps_work_dir = env["EXT_APPS_WORK_DIR"] + self.app_work_dir = self.get_app_work_dir(env, app) self.app_alias = f"fap_{self.app.appid}" self.externally_built_files = [] self.private_libs = [] @@ -83,9 +87,9 @@ def _compile_assets(self): return fap_icons = self.app_env.CompileIcons( - self.app_env.Dir(self.app_work_dir), + self.app_work_dir, self.app._appdir.Dir(self.app.fap_icon_assets), - icon_bundle_name=f"{self.app.fap_icon_assets_symbol if self.app.fap_icon_assets_symbol else self.app.appid }_icons", + icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid }_icons", ) self.app_env.Alias("_fap_icons", fap_icons) self.fw_env.Append(_APP_ICONS=[fap_icons]) @@ -95,7 +99,7 @@ def _build_private_libs(self): self.private_libs.append(self._build_private_lib(lib_def)) def _build_private_lib(self, lib_def): - lib_src_root_path = os.path.join(self.app_work_dir, "lib", lib_def.name) + lib_src_root_path = self.app_work_dir.Dir("lib").Dir(lib_def.name) self.app_env.AppendUnique( CPPPATH=list( self.app_env.Dir(lib_src_root_path) @@ -119,9 +123,7 @@ def _build_private_lib(self, lib_def): private_lib_env = self.app_env.Clone() private_lib_env.AppendUnique( - CCFLAGS=[ - *lib_def.cflags, - ], + CCFLAGS=lib_def.cflags, CPPDEFINES=lib_def.cdefines, CPPPATH=list( map( @@ -132,14 +134,17 @@ def _build_private_lib(self, lib_def): ) return private_lib_env.StaticLibrary( - os.path.join(self.app_work_dir, lib_def.name), + self.app_work_dir.File(lib_def.name), lib_sources, ) def _build_app(self): + if self.app.fap_file_assets: + self.app._assets_dirs = [self.app._appdir.Dir(self.app.fap_file_assets)] + self.app_env.Append( LIBS=[*self.app.fap_libs, *self.private_libs], - CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], + CPPPATH=[self.app_work_dir, self.app._appdir], ) app_sources = list( @@ -155,32 +160,46 @@ def _build_app(self): app_artifacts = FlipperExternalAppInfo(self.app) app_artifacts.debug = self.app_env.Program( - os.path.join(self.ext_apps_work_dir, f"{self.app.appid}_d"), + self.ext_apps_work_dir.File(f"{self.app.appid}_d.elf"), app_sources, APP_ENTRY=self.app.entry_point, )[0] app_artifacts.compact = self.app_env.EmbedAppMetadata( - os.path.join(self.ext_apps_work_dir, self.app.appid), + self.ext_apps_work_dir.File(f"{self.app.appid}.fap"), app_artifacts.debug, APP=self.app, )[0] + if self.app.embeds_plugins: + self.app._assets_dirs.append(self.app_work_dir.Dir("assets")) + app_artifacts.validator = self.app_env.ValidateAppImports( app_artifacts.compact )[0] if self.app.apptype == FlipperAppType.PLUGIN: for parent_app_id in self.app.requires: - fal_path = ( - f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}" - ) - deployable = True - # If it's a plugin for a non-deployable app, don't include it in the resources - if parent_app := self.app._appmanager.get(parent_app_id): - if not parent_app.is_default_deployable: - deployable = False - app_artifacts.dist_entries.append((deployable, fal_path)) + if self.app.fal_embedded: + parent_app = self.app._appmanager.get(parent_app_id) + if not parent_app: + raise UserError( + f"Embedded plugin {self.app.appid} requires unknown app {parent_app_id}" + ) + self.app_env.Install( + target=self.get_app_work_dir(self.app_env, parent_app) + .Dir("assets") + .Dir("plugins"), + source=app_artifacts.compact, + ) + else: + fal_path = f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}" + deployable = True + # If it's a plugin for a non-deployable app, don't include it in the resources + if parent_app := self.app._appmanager.get(parent_app_id): + if not parent_app.is_default_deployable: + deployable = False + app_artifacts.dist_entries.append((deployable, fal_path)) else: fap_path = f"apps/{self.app.fap_category}/{app_artifacts.compact.name}" app_artifacts.dist_entries.append( @@ -194,7 +213,7 @@ def _configure_deps_and_aliases(self, app_artifacts: FlipperExternalAppInfo): # Extra things to clean up along with the app self.app_env.Clean( app_artifacts.debug, - [*self.externally_built_files, self.app_env.Dir(self.app_work_dir)], + [*self.externally_built_files, self.app_work_dir], ) # Create listing of the app @@ -219,13 +238,10 @@ def _configure_deps_and_aliases(self, app_artifacts: FlipperExternalAppInfo): ) # Add dependencies on file assets - if self.app.fap_file_assets: + for assets_dir in self.app._assets_dirs: self.app_env.Depends( app_artifacts.compact, - self.app_env.GlobRecursive( - "*", - self.app._appdir.Dir(self.app.fap_file_assets), - ), + (assets_dir, self.app_env.GlobRecursive("*", assets_dir)), ) # Always run the validator for the app's binary when building the app @@ -344,25 +360,26 @@ def embed_app_metadata_emitter(target, source, env): if app.apptype == FlipperAppType.PLUGIN: target[0].name = target[0].name.replace(".fap", ".fal") - target.append(env.File(source[0].abspath + _FAP_META_SECTION)) + app_work_dir = AppBuilder.get_app_work_dir(env, app) + app._section_fapmeta = app_work_dir.File(_FAP_META_SECTION) + target.append(app._section_fapmeta) - if app.fap_file_assets: - target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) + # At this point, we haven't added dir with embedded plugins to _assets_dirs yet + if app._assets_dirs or app.embeds_plugins: + app._section_fapfileassets = app_work_dir.File(_FAP_FILEASSETS_SECTION) + target.append(app._section_fapfileassets) return (target, source) -def prepare_app_files(target, source, env): +def prepare_app_file_assets(target, source, env): files_section_node = next( filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) ) - app = env["APP"] - directory = env.Dir(app._apppath).Dir(app.fap_file_assets) - if not directory.exists(): - raise UserError(f"File asset directory {directory} does not exist") - - bundler = FileBundler(directory.abspath) + bundler = FileBundler( + list(env.Dir(asset_dir).abspath for asset_dir in env["APP"]._assets_dirs) + ) bundler.export(files_section_node.abspath) @@ -376,12 +393,14 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): objcopy_str = ( "${OBJCOPY} " "--remove-section .ARM.attributes " - "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " + "--add-section ${_FAP_META_SECTION}=${APP._section_fapmeta} " ) - if app.fap_file_assets: - actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) - objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " + if app._section_fapfileassets: + actions.append(Action(prepare_app_file_assets, "$APPFILE_COMSTR")) + objcopy_str += ( + "--add-section ${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets} " + ) objcopy_str += ( "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " @@ -470,7 +489,7 @@ def AddAppBuildTarget(env, appname, build_target_name): def generate(env, **kw): env.SetDefault( - EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", + EXT_APPS_WORK_DIR=env.Dir(env["FBT_FAP_DEBUG_ELF_ROOT"]), APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py", ) if not env["VERBOSE"]: From b98631c633d62eb5d1384b8101a3ce738ff53031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 21 Sep 2023 23:34:48 +0900 Subject: [PATCH 8/8] Gui: handle view port lockup and notify developer about it (#3102) * Gui: handle view port lockup and notify developer about it * Gui: fix more viewport deadlock cases and add proper notification --- applications/services/gui/view_port.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 6723a777bb..25f670a7c1 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -6,6 +6,8 @@ #include "gui.h" #include "gui_i.h" +#define TAG "ViewPort" + _Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); _Static_assert( (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 && @@ -174,9 +176,15 @@ void view_port_input_callback_set( void view_port_update(ViewPort* view_port) { furi_assert(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->gui && view_port->is_enabled) gui_update(view_port->gui); - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_release(view_port->mutex); } void view_port_gui_set(ViewPort* view_port, Gui* gui) { @@ -189,14 +197,21 @@ void view_port_gui_set(ViewPort* view_port, Gui* gui) { void view_port_draw(ViewPort* view_port, Canvas* canvas) { furi_assert(view_port); furi_assert(canvas); - 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); + } + furi_check(view_port->gui); if(view_port->draw_callback) { view_port_setup_canvas_orientation(view_port, canvas); view_port->draw_callback(canvas, view_port->draw_callback_context); } - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + + furi_mutex_release(view_port->mutex); } void view_port_input(ViewPort* view_port, InputEvent* event) {