diff --git a/applications/debug/unit_tests/tests/furi/furi_errno_test.c b/applications/debug/unit_tests/tests/furi/furi_errno_test.c new file mode 100644 index 000000000..b42e7c082 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_errno_test.c @@ -0,0 +1,51 @@ +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "ErrnoTest" +#define THREAD_CNT 16 +#define ITER_CNT 1000 + +static int32_t errno_fuzzer(void* context) { + int start_value = (int)context; + int32_t fails = 0; + + for(int i = start_value; i < start_value + ITER_CNT; i++) { + errno = i; + furi_thread_yield(); + if(errno != i) fails++; + } + + for(int i = 0; i < ITER_CNT; i++) { + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != 0) fails++; + + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456123456123456123456123456123456123456123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != ERANGE) fails++; + } + + return fails; +} + +void test_errno_saving(void) { + FuriThread* threads[THREAD_CNT]; + + for(int i = 0; i < THREAD_CNT; i++) { + int start_value = i * ITER_CNT; + threads[i] = furi_thread_alloc_ex("ErrnoFuzzer", 1024, errno_fuzzer, (void*)start_value); + furi_thread_set_priority(threads[i], FuriThreadPriorityNormal); + furi_thread_start(threads[i]); + } + + for(int i = 0; i < THREAD_CNT; i++) { + furi_thread_join(threads[i]); + mu_assert_int_eq(0, furi_thread_get_return_code(threads[i])); + furi_thread_free(threads[i]); + } +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index be579d2b8..2a76d5184 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,6 +8,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); +void test_errno_saving(void); static int foo = 0; @@ -42,6 +43,10 @@ MU_TEST(mu_test_furi_event_loop) { test_furi_event_loop(); } +MU_TEST(mu_test_errno_saving) { + test_errno_saving(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -51,6 +56,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_errno_saving); } int run_minunit_test_furi(void) { diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 2948faef9..82cda2c6d 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -2,6 +2,7 @@ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include +#include #pragma GCC diagnostic ignored "-Wredundant-decls" #endif @@ -26,6 +27,7 @@ #define configUSE_16_BIT_TICKS 0 #define configMAX_PRIORITIES (32) #define configMINIMAL_STACK_SIZE ((uint16_t)128) +#define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) @@ -146,9 +148,14 @@ standard names. */ #define configOVERRIDE_DEFAULT_TICK_CONFIGURATION \ 1 /* required only for Keil but does not hurt otherwise */ -#define traceTASK_SWITCHED_IN() \ - extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ - furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack) +#define traceTASK_SWITCHED_IN() \ + extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ + furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack); \ + errno = pxCurrentTCB->iTaskErrno +// ^^^^^ acquire errno directly from TCB because FreeRTOS assigns its `FreeRTOS_errno' _after_ our hook is called + +// referencing `FreeRTOS_errno' here vvvvv because FreeRTOS calls our hook _before_ copying the value into the TCB, hence a manual write to the TCB would get overwritten +#define traceTASK_SWITCHED_OUT() FreeRTOS_errno = errno #define portCLEAN_UP_TCB(pxTCB) \ extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \