diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index e5edc6c..0ee1610 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,5 +1,6 @@ name: ROMEO CI Builder -on: push +on: + pull_request jobs: Build Code: @@ -10,21 +11,31 @@ jobs: with: token: ${{ secrets.ROMEO_NUMALFIX_TOKEN }} submodules: 'recursive' - - name: Configure z7 + - name: Build z7 run: | mkdir build_z7 cd build_z7 - cmake -DCMAKE_TOOLCHAIN_FILE=../bsp_z7/cmake/arm-none-eabi.toolchain .. - - name: Build z7 + cmake -DCMAKE_TOOLCHAIN_FILE=../bsp_z7/cmake/arm-none-eabi.toolchain -DCMAKE_BUILD_TYPE=Release .. + make -j8 + - name: Low level tests z7 run: | cd build_z7 + cmake -DARM_SEMIHOSTING=ON -DROMEO_LOW_LEVEL_TESTS=ON .. make -j8 - - name: Configure linux + qemu-system-arm -semihosting -nographic -monitor none -serial none -serial stdio -machine xilinx-zynq-a9 -m 500M -kernel romeo-low_level_tests + - name: Build linux run: | mkdir build_linux cd build_linux cmake .. - - name: Build linux + make -j8 + - name: Low level tests linux run: | cd build_linux + cmake -DROMEO_LOW_LEVEL_TESTS=ON .. make -j8 + ./romeo-low_level_tests + - name: Rust tests + run: | + cd mission_rust + cargo test \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 5815ed4..7cb3ea4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/FreeRTOS/FreeRTOS-Kernel [submodule "contrib/lwip"] path = contrib/lwip - url = ../lwip + url = ../../romeo/lwip [submodule "fsbl-compiled"] path = fsbl-compiled - url = ../fsbl-compiled + url = ../../romeo/fsbl-compiled diff --git a/CMakeLists.txt b/CMakeLists.txt index a59a412..a59c9eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,13 @@ set(ROMEO_WARNING_FLAGS -Wpedantic -Werror) # TODO so far, this only affects mission code, not bsp +option(ROMEO_LOW_LEVEL_TESTS "build low level tests INSTEAD of normal binary" OFF) + +if(${ROMEO_LOW_LEVEL_TESTS}) + add_compile_definitions(LOW_LEVEL_TESTS) + set(OBSW_NAME romeo-low_level_tests) +endif() + # CMake options which are only available when crosscompiling if (${CMAKE_CROSSCOMPILING}) set(ZYNQ_UART UART1 CACHE STRING "Which PS UART to use for stdout") diff --git a/bsp_linux/freeRTOS/FreeRTOSConfig.h b/bsp_linux/freeRTOS/FreeRTOSConfig.h index da4e77b..fa99e9e 100644 --- a/bsp_linux/freeRTOS/FreeRTOSConfig.h +++ b/bsp_linux/freeRTOS/FreeRTOSConfig.h @@ -46,7 +46,7 @@ #define configMINIMAL_STACK_SIZE ( ( unsigned short ) PTHREAD_STACK_MIN ) /* The stack size being passed is equal to the minimum stack size needed by pthread_create(). */ #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 200 * 1024 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 12 ) -#define configUSE_TRACE_FACILITY 1 +#define configUSE_TRACE_FACILITY 0 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 @@ -78,6 +78,14 @@ #define configRECORD_STACK_HIGH_ADDRESS 1 + +// see low_level_tests/mod.rs, some weird effect in FreeRTOS +#ifndef LOW_LEVEL_TESTS +#define configMAX_PRIORITIES ( 10 ) +#else +#define configMAX_PRIORITIES ( 1024 ) +#endif + /* Software timer related configuration options. The maximum possible task * priority is configMAX_PRIORITIES - 1. The priority of the timer task is * deliberately set higher to ensure it is correctly capped back to @@ -87,7 +95,8 @@ #define configTIMER_QUEUE_LENGTH 20 #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) -#define configMAX_PRIORITIES ( 10 ) + + /* Run time stats gathering configuration options. */ unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */ diff --git a/bsp_linux/main.c b/bsp_linux/main.c index 6e80ac2..ced7b19 100644 --- a/bsp_linux/main.c +++ b/bsp_linux/main.c @@ -8,6 +8,7 @@ #include #include #include +#include const char *sim_ip = "localhost"; int ai_family = AF_UNSPEC; @@ -21,13 +22,15 @@ void done_error() { exit(1); } int test_socket(); // TODO link to GCC's personality or make the linux build not use it? +// -> linux build is std at the moment, only used for tests +#ifdef LOW_LEVEL_TESTS void rust_eh_personality() { puts("eh_personality"); } +#endif void print_usage(const char *name) { fprintf(stderr, "Usage: %s [-s sim_ip] [-4|6]\n", name); } -#include int main(int argc, char **argv) { hw_device_open( diff --git a/bsp_z7/freeRTOS/FreeRTOSConfig.h b/bsp_z7/freeRTOS/FreeRTOSConfig.h index d80aa50..bbfe1f0 100644 --- a/bsp_z7/freeRTOS/FreeRTOSConfig.h +++ b/bsp_z7/freeRTOS/FreeRTOSConfig.h @@ -91,7 +91,10 @@ #define configUSE_QUEUE_SETS 1 #define configSUPPORT_STATIC_ALLOCATION 1 #define configSUPPORT_DYNAMIC_ALLOCATION 0 -#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1 +// TODO disable for non-testing? +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 /* Include the query-heap CLI command to query the free heap space. */ #define configINCLUDE_QUERY_HEAP_COMMAND 0 diff --git a/bsp_z7/main.c b/bsp_z7/main.c index 4e28bc4..dc0758d 100644 --- a/bsp_z7/main.c +++ b/bsp_z7/main.c @@ -15,6 +15,8 @@ #include "xscutimer.h" #include "xuartps_hw.h" +#include + /* * Configure the hardware as necessary to run this demo. */ @@ -172,11 +174,21 @@ void vApplicationIdleHook(void) { management options. If there is a lot of heap memory free then the configTOTAL_HEAP_SIZE value in FreeRTOSConfig.h can be reduced to free up RAM. */ - // xFreeHeapSpace = xPortGetFreeHeapSize(); + // xFreeHeapSpace = xPortGetFreeHeapSize(); // xMinimumEverFreeHeapSpace = xPortGetMinimumEverFreeHeapSize(); // /* Remove compiler warning about xFreeHeapSpace being set but never used. // */ (void)xFreeHeapSpace; (void)xMinimumEverFreeHeapSpace; +#ifdef LOW_LEVEL_TESTS +#define ERROR_MSG "\nPanic occured in the code executing the tests.\nWe'll consider this a failure:\n\ntest result: ❌ FAILED HORRIBLY\n" + // During low level tests, panics do not halt the execution but delete the current task. + // If this happens in the initial task, only the idle task remains. In this case, we + // simply end the execution. + + write(2, ERROR_MSG, sizeof(ERROR_MSG)); + + done_error(); +#endif } void vApplicationTickHook(void) { diff --git a/docker/Dockerfile b/docker/Dockerfile index 17a6e65..dbb13e6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,8 @@ FROM debian:12.7-slim # Install required packages -RUN apt-get update && apt-get upgrade --yes - -RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ +RUN dpkg-reconfigure debconf --frontend=noninteractive && apt-get update && apt-get upgrade --yes && \ + apt-get install --yes --no-install-recommends \ git \ ssh \ make \ @@ -14,6 +13,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends ca-certificates \ gcc-arm-none-eabi \ libnewlib-arm-none-eabi \ + qemu-system-arm \ # remove for image size && rm -rf /var/lib/apt/lists/* diff --git a/mission/freeRTOS_rust_helper.c b/mission/freeRTOS_rust_helper.c index 37cf13a..94bb42d 100644 --- a/mission/freeRTOS_rust_helper.c +++ b/mission/freeRTOS_rust_helper.c @@ -10,7 +10,6 @@ // TODO namespace the names // TODO panic if able, but not in runtime calls - // Wraps xTaskGetCurrentTaskHandle and returns NULL if no Task is running // // xTaskGetCurrentTaskHandle() will return a handle even if no task is @@ -33,8 +32,8 @@ StackType_t init_task_stack[configMINIMAL_STACK_SIZE * 10]; void freertos_init_and_start_scheduling(TaskFunction_t init_task) { // TaskHandle_t handle = - xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10, - NULL, configMAX_PRIORITIES - 1, init_task_stack, &init_task_data); + xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10, NULL, + configMAX_PRIORITIES - 1, init_task_stack, &init_task_data); // vTaskSetThreadLocalStoragePointer(handle, 0, NULL); vTaskStartScheduler(); @@ -194,7 +193,8 @@ uint8_t freertos_once(char *once_data_in, uint32_t once_data_len, once_data->local_mutex = xSemaphoreCreateRecursiveMutexStatic(&once_data->local_mutex_data); // if (once_data->local_mutex == NULL) { - // // will not happen, xSemaphoreCreateRecursiveMutexStatic returns its parameter + // // will not happen, xSemaphoreCreateRecursiveMutexStatic returns its + // parameter // // which we checked above to be != NULL // } once_data->once_state = INIT; @@ -236,7 +236,8 @@ uint8_t freertos_simple_once(uint8_t *once_data) { uint8_t result = 0; // This function is basically a flag stored in once_data, protected by // a critical section - // Critical section is ok, because simple arithmetic is bounded in execution time + // Critical section is ok, because simple arithmetic is bounded in execution + // time taskENTER_CRITICAL(); if (*once_data == 0) { *once_data = 1; @@ -253,9 +254,10 @@ uint32_t freertos_task_priority_max(void) { } // Note char* to keep sizeof correct on any platform -void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter, uint32_t priority, - char *task_data, uint32_t task_data_len, - char *stack, uint32_t stack_size) { +void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter, + uint32_t priority, char *task_data, + uint32_t task_data_len, char *stack, + uint32_t stack_size) { // TODO verify uint32_t vs configSTACK_DEPTH_TYPE if (task_data_len < sizeof(StaticTask_t)) { // printf("freertos_create_task_static: task data needs to be %zu long\n", @@ -275,9 +277,10 @@ void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter, // printf("freertos_create_task_static: Stack: %p %"PRIu32" %zu %zu\n", stack, // stack_size, stack_size_words, sizeof(StackType_t)); - - return xTaskCreateStatic(taskFunction, "rust", stack_size_words, parameter, priority, - (StackType_t *)stack, (StaticTask_t *)task_data); + + return xTaskCreateStatic(taskFunction, "rust", stack_size_words, parameter, + priority, (StackType_t *)stack, + (StaticTask_t *)task_data); } void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) { @@ -293,6 +296,15 @@ void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) { } // TODO: might be the wrong place -int freertos_get_sys_error(){ - return errno; +int freertos_get_sys_error() { return errno; } + +// private, for testing only TODO #define +void freertos_task_notify(void *task, uint32_t value) { + xTaskNotify(task, value, eSetValueWithOverwrite); +} + +uint32_t freertos_task_notify_wait() { + uint32_t value; + xTaskNotifyWait(0, 0, &value, portMAX_DELAY); + return value; } \ No newline at end of file diff --git a/mission/mission.c b/mission/mission.c index 932d66b..bf4ed81 100644 --- a/mission/mission.c +++ b/mission/mission.c @@ -11,7 +11,11 @@ #include #include +#ifndef LOW_LEVEL_TESTS void rust_main(void); +#else +void rust_low_level_tests(void); +#endif // called to stop execution on error // to be implemented by bsp (do not return from it!) @@ -19,10 +23,18 @@ void done_error(); void init_task(void *_) { (void)_; +#ifndef LOW_LEVEL_TESTS rust_main(); +#else + rust_low_level_tests(); +#endif } +#ifndef LOW_LEVEL_TESTS #define STARTUP_MESSAGE1 "\nROMEO embedded obsw\nRelease: " +#else +#define STARTUP_MESSAGE1 "\nROMEO embedded obsw-tests\nRelease: " +#endif #define STARTUP_MESSAGE2 "\nBuild time: " void mission(void) { diff --git a/mission_rust/CMakeLists.txt b/mission_rust/CMakeLists.txt index c9eff4f..9f37471 100644 --- a/mission_rust/CMakeLists.txt +++ b/mission_rust/CMakeLists.txt @@ -7,11 +7,15 @@ #TODO look into corrosion cmake plugin +if(${ROMEO_LOW_LEVEL_TESTS}) + set(FEATURES "--features=\"low_level_tests\"") +endif() + if (${CMAKE_CROSSCOMPILING}) add_custom_target( mission_rust_internal - COMMAND cargo build -Zbuild-std=core --target=${CMAKE_SYSTEM_PROCESSOR} $<$:--release> + COMMAND cargo build -Zbuild-std=core --target=${CMAKE_SYSTEM_PROCESSOR} $<$:--release> ${FEATURES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -27,7 +31,7 @@ else() add_custom_target( mission_rust_internal - COMMAND cargo build $<$:--release> + COMMAND cargo build $<$:--release> ${FEATURES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/mission_rust/Cargo.toml b/mission_rust/Cargo.toml index 78f6b6b..07bef52 100644 --- a/mission_rust/Cargo.toml +++ b/mission_rust/Cargo.toml @@ -4,10 +4,13 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["staticlib"] +crate-type = ["staticlib", "lib"] [profile.dev] panic = 'abort' [profile.release] -panic = 'abort' \ No newline at end of file +panic = 'abort' + +[features] +low_level_tests = [] \ No newline at end of file diff --git a/mission_rust/src/fsrc/introspection.rs b/mission_rust/src/fsrc/introspection.rs index 38e155d..e9cf973 100644 --- a/mission_rust/src/fsrc/introspection.rs +++ b/mission_rust/src/fsrc/introspection.rs @@ -1,31 +1,31 @@ /// A trait to allow objects to be queried for some of their inner attributes at runtime. -/// +/// pub trait Introspection { /// Executes a function/closure on all members of the implementing struct. - /// + /// /// The parameters for the closure a reference to the member as well as the name of the member. - /// + /// /// To keep the trait object safe, we need to pass the closure as a reference. Otherwise, /// the trait can not be used as a `dyn` reference, because the function becomes generic. /// (Either using `impl FnMut` not `where F: FnMut` will be treated as generic making the /// trait not object safe). /// Additionally, the closure is typed as `FnMut`, to allow the closure to capture mutably. - /// - /// + /// + /// /// # Examples - /// + /// /// ```rust - /// use fsrc::introspection::Introspection; - /// + /// use mission_rust::fsrc::introspection::Introspection; + /// /// struct Empty {} - /// + /// /// impl Empty { /// fn getRandomNumber(&self) -> u8 { /// return 4 // chosen by fair dice roll. /// // guaranteed to be random. /// } /// } - /// + /// /// fn parsing_something(something: &dyn Introspection) { /// let mut a = 0; /// something.for_each_member(&mut |x, _| { @@ -39,18 +39,18 @@ pub trait Introspection { fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()); /// Same as [for_each_member](Introspection::for_each_member), only with a return value for the closure. - /// + /// /// # Examples - /// + /// /// The return value allows using `x.downcast_ref()?` or similar constructs in the closure like in: - /// + /// /// ```rust - /// use fsrc::introspection::Introspection; - /// + /// use mission_rust::fsrc::introspection::Introspection; + /// /// struct Empty {} - /// + /// /// fn do_something(_: &Empty) {} - /// + /// /// fn parsing_something(something: &dyn Introspection) { /// something.for_each_member_return(&mut |x, _| { /// do_something(x.downcast_ref()?); @@ -58,6 +58,6 @@ pub trait Introspection { /// }); /// } /// ``` - /// + /// fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>); -} \ No newline at end of file +} diff --git a/mission_rust/src/fsrc/osal/ffi.rs b/mission_rust/src/fsrc/osal/ffi.rs index 3c54ef4..42162b8 100644 --- a/mission_rust/src/fsrc/osal/ffi.rs +++ b/mission_rust/src/fsrc/osal/ffi.rs @@ -23,6 +23,9 @@ impl Sizes { pub const TASK_MINIMAL_STACK_SIZE: usize = 10240; } + + +#[cfg(not(test))] extern "C" { //////////////////////// /// FreeRTOS API @@ -30,7 +33,7 @@ extern "C" { //void *create_task_static(TaskFunction_t taskFunction, void *parameter, // char *task_data, uint32_t task_data_len, char *stack, uint32_t stack_size) pub fn freertos_create_task_static( - taskFunction: TaskFunction, + task_function: TaskFunction, parameter: *const core::ffi::c_void, priority: u32, task_data: *mut core::ffi::c_char, @@ -101,6 +104,127 @@ extern "C" { //int hw_device_open(const char * path, size_t path_len); pub fn hw_device_open(path: *const core::ffi::c_char, path_len: core::ffi::c_size_t) -> core::ffi::c_int; } + +#[cfg(test)] +pub use test_api::*; + +#[cfg(test)] +mod test_api{ + use super::*; + + //////////////////////// + /// FreeRTOS API + /// shimmed in mission/freeRTOS_rust_helper.c + //void *create_task_static(TaskFunction_t taskFunction, void *parameter, + // char *task_data, uint32_t task_data_len, char *stack, uint32_t stack_size) + pub unsafe fn freertos_create_task_static( + _task_function: TaskFunction, + _parameter: *const core::ffi::c_void, + _priority: u32, + _task_data: *mut core::ffi::c_char, + _task_data_len: u32, + _stack: *mut core::ffi::c_char, + _stack_size: u32, + ) -> *const core::ffi::c_void{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + // pub fn stop_it(); + + pub unsafe fn freertos_task_suspend(_handle: *const core::ffi::c_void){ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_task_delete(_handle: *const core::ffi::c_void) -> !{ + panic!("freeRTOS APIs are not implemented for tests") + } + + pub unsafe fn freertos_task_storage_set( + _handle: *const core::ffi::c_void, + _data: *const core::ffi::c_void, + ){ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_task_storage_get(_handle: *const core::ffi::c_void) -> *const core::ffi::c_void{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_task_delay(_milliseconds: u32){ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_task_stack_watermark() -> u32{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_task_current() -> *const core::ffi::c_void{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + // TODO this should be passed by compile time value + pub unsafe fn freertos_task_priority_max() -> u32{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + //void *freertos_queue_create_static(uint32_t depth, uint32_t element_size, + // char *queue_data, uint32_t queue_data_len, + // uint8_t *queue, uint32_t queue_len) { + pub unsafe fn freertos_queue_create_static( + _depth: u32, + _element_size: u32, + _queue_data: *mut core::ffi::c_char, + _queue_data_len: u32, + _queue: *mut u8, + ) -> *const core::ffi::c_void{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_queue_receive( + _queue: *const core::ffi::c_void, + _message: *const core::ffi::c_void, + ) -> u8{ + panic!("freeRTOS APIs are not implemented for tests"); + } + pub unsafe fn freertos_queue_send( + _queue: *const core::ffi::c_void, + _message: *const core::ffi::c_void, + ) -> u8{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_mutex_create_static( + _mutex_data: *const core::ffi::c_char, + _mutex_data_len: u32, + ) -> *const core::ffi::c_void{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + pub unsafe fn freertos_mutex_take(_mutex: *const core::ffi::c_void) -> u8{ + panic!("freeRTOS APIs are not implemented for tests"); + } + pub unsafe fn freertos_mutex_give(_mutex: *const core::ffi::c_void) -> u8{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + //uint8_t freertos_simple_once(uint8_t *once_data) + pub unsafe fn freertos_simple_once(_once_data: *const u8) -> u8{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + //int freertos_get_sys_error() + pub unsafe fn freertos_get_sys_error() -> core::ffi::c_int{ + panic!("freeRTOS APIs are not implemented for tests"); + } + + //////////////////////// + /// Harware Abstraction API in common/include/interfaces.h + /// Used for access to peripherals to make switching between linux and embedded easier + //int hw_device_open(const char * path, size_t path_len); + pub unsafe fn hw_device_open(_path: *const core::ffi::c_char, _path_len: core::ffi::c_size_t) -> core::ffi::c_int{ + panic!("freeRTOS APIs are not implemented for tests"); + } +} //////////////////////// /// libc API (read/write/etc) #[cfg(target_env = "gnu")] diff --git a/mission_rust/src/lib.rs b/mission_rust/src/lib.rs index f05e40c..9061f96 100644 --- a/mission_rust/src/lib.rs +++ b/mission_rust/src/lib.rs @@ -1,12 +1,14 @@ -#![no_std] +#![cfg_attr(any(target_env = "newlib", feature = "low_level_tests"), no_std)] #![feature(never_type)] #![feature(c_size_t)] // for ffi, tracking issue [88345] -//TODO os errors in API calls mod dh; pub mod fsrc; mod panic; +#[cfg(feature = "low_level_tests")] +mod low_level_tests; + use core::fmt::Write; use core::time::Duration; @@ -19,10 +21,7 @@ use osal::{ use crate::{ dh::EchoHandler, - fsrc::{ - dh::debug::DeviceHandlerDebugger, - osal::io::HardwareInterface, - }, + fsrc::{dh::debug::DeviceHandlerDebugger, osal::io::HardwareInterface}, }; static THREAD_INIT: StaticReadOnceLock< @@ -34,7 +33,7 @@ extern "C" fn rust_main() { // we are already in a task (init task started by C), so we can use all APIs safely, // but start a new init task anyway to be able to control that task's stack here sifln!("Rust startup 🚀"); - THREAD_INIT.take_no_init().unwrap().spawn(init_task); + THREAD_INIT.take_no_init().unwrap_or_else(|| {loop{}}).spawn(init_task); // delete self, init task will take over thread::current().delete(); } @@ -198,7 +197,7 @@ fn init_task() -> ! { Duration::from_millis(500), 0.5, ); - + debugger.run().unwrap(); let test1 = TEST1.take().unwrap(); @@ -208,22 +207,28 @@ fn init_task() -> ! { let clone = sender_handle.clone(); let sender = SENDER.take_no_init().unwrap(); - let mutex_copy = test1.mutex.clone(); - let a = Duration::from_millis(10); + // let mutex_copy = test1.mutex.clone(); + // let a = Duration::from_millis(10); - THREAD_2.take_no_init().unwrap().spawn(move || { - test2.run(mutex_copy, a); - }); + // THREAD_2.take_no_init().unwrap().spawn(move || { + // test2.run(mutex_copy, a); + // }); - THREAD_1.take_no_init().unwrap().spawn(move || { - test1.run(); - sender.run(clone); - }); + // THREAD_1.take_no_init().unwrap().spawn(move || { + // test1.run(); + // sender.run(clone); + // }); THREAD_3.take_no_init().unwrap().spawn(move || { receiver.run(); }); - + sifln!("=====================Mission delete"); osal::thread::current().delete(); } + +mod tests { + #[test] + fn work_at_all() { + } +} diff --git a/mission_rust/src/low_level_tests/mod.rs b/mission_rust/src/low_level_tests/mod.rs new file mode 100644 index 0000000..a9e8905 --- /dev/null +++ b/mission_rust/src/low_level_tests/mod.rs @@ -0,0 +1,151 @@ +//! Low level tests which need to be run with FreeRTOS present +//! +//! They can not be run as standard rust tests as they depend on the C code. +//! As such this module is to be linked +//! as part of the main library with the low level C code. +//! +//! Entry into the tests is provided by the extern "C" function +//! rust_low_level_tests() to be called from C. It is assumed to be running in an init Thread +//! and will spawn a new testing thread for each unit test. + +use super::*; + +const OK: u32 = 0; +pub const PANIC: u32 = 1; + +macro_rules! function_with_name { + ($x:expr) => {{ + ($x, type_name_of($x)) + }}; +} + +fn type_name_of(_: T) -> &'static str { + core::any::type_name::() +} + +extern "C" { + // forward declaration of done[_error]() which are implemented in bsp. + // this is not part of ffi as users are not expected to use it. + fn done() -> !; + fn done_error() -> !; + + // Until further notice, these are private to testing + //uint32_t freertos_task_notify(void *task, uint32_t value) + fn freertos_task_notify(task: *const core::ffi::c_void, value: u32); + //uint32_t freertos_task_notify_wait() + fn freertos_task_notify_wait() -> u32; +} + +pub static mut CONTROLLER_TASK_HANDLE: *const core::ffi::c_void = 0 as *const core::ffi::c_void; + +/// Stack for test threads, is reused as tests are run one after another +/// +/// assumes we run in qemu, so memory is no consideration +static TEST_RUNNER_STACK: StaticReadOnceLock<[core::ffi::c_char; 1024000]> = StaticReadOnceLock::new([0; 1024000]); +static TEST_RUNNER_THREAD_DATA: StaticReadOnceLock< + [core::ffi::c_char; osal::ffi::Sizes::TASK_DATA_SIZE], +> = StaticReadOnceLock::new([0; osal::ffi::Sizes::TASK_DATA_SIZE]); + +/// Runs the low level tests +/// +/// test are assumed to panic when failing. As we are running no-std with a custom panic, +/// tests are not continued after failing, as we have no reliable way of catching panics so far. +#[no_mangle] +extern "C" fn rust_low_level_tests() { + // Safe because I do not care right now. TODO + unsafe { CONTROLLER_TASK_HANDLE = osal::ffi::freertos_task_current() }; + run_tests(&mut [function_with_name!(work_at_all)]); +} + +#[cfg(not(target_os = "linux"))] +fn increment_on_linux(number: &mut u32) { +} + +#[cfg(target_os = "linux")] +fn increment_on_linux(number: &mut u32) { + *number = *number + 1; +} + +/// Runner for low level tests, will attempt to look a bit like std rust tests. +/// expects a function and its name, to print it +/// use the function_with_name! macro to generate the tuple +fn run_tests(tests: &mut [(fn(), &'static str)]) -> ! { + let stack = TEST_RUNNER_STACK.take_no_init().unwrap(); + let thread_data = TEST_RUNNER_THREAD_DATA.take_no_init().unwrap(); + + + let number_of_tests = tests.len(); + + sifln!(); + if number_of_tests == 0 { + sifln!("no tests implemented 😢") + } else if number_of_tests == 1 { + sifln!("running 1 test") + } else { + sifln!("running {number_of_tests} tests"); + } + + let mut passed = 0; + let mut failed = 0; + let mut priority = 1; + + for test in tests { + let test_function = test.0 as *const core::ffi::c_void; + for element in thread_data.iter_mut() { + *element = 0; + } + for element in stack.iter_mut() { + *element = 0; + } + unsafe { + osal::ffi::freertos_create_task_static( + run_one_test, + test_function, + priority, + thread_data.as_mut_ptr(), + thread_data.len().try_into().unwrap(), + stack.as_mut_ptr(), + stack.len().try_into().unwrap(), + ) + }; + let result = unsafe { freertos_task_notify_wait() }; + sif!("test {} ... ", test.1); + if result == OK { + sifln!("✅ ok"); + passed+=1; + } else { + sifln!("❌ FAILED"); + failed+=1; + } + // some weird bug/effect in FreeRTOS on linux breaks here + // if a task priority is reused. + // So, we just increase the priority lineary. We mande sure in FreeRTOSConfig.h + // to have enough (1024) available. + increment_on_linux(&mut priority) + } + sifln!(); + + if failed == 0 { + sifln!("test result: ✅ ok. {passed} passed; {failed} failed"); + unsafe { done() } + } else { + sifln!("test result: ❌ FAILED. {passed} passed; {failed} failed"); + unsafe { done_error() } + } +} + +#[no_mangle] +extern "C" fn run_one_test(parameter: *mut core::ffi::c_void) -> ! { + let function: fn() = unsafe { core::mem::transmute(parameter) }; + function(); + unsafe { + freertos_task_notify(CONTROLLER_TASK_HANDLE, OK); + } + osal::thread::current().delete(); +} + +fn work_at_all() {} + +fn fail() { + assert_eq!(1, 2); +} diff --git a/mission_rust/src/panic.rs b/mission_rust/src/panic.rs index 26f0205..1899a4a 100644 --- a/mission_rust/src/panic.rs +++ b/mission_rust/src/panic.rs @@ -1,26 +1,53 @@ -use core::panic::PanicInfo; -use core::fmt::Write; +#[cfg(any(target_env = "newlib", feature = "low_level_tests"))] +pub use no_std::*; -extern "C" { - fn done_error(); // exit code != 0 -} +#[cfg(any(target_env = "newlib", feature = "low_level_tests"))] +mod no_std { + use core::fmt::Write; + use core::panic::PanicInfo; -#[cfg(not(test))] -#[panic_handler] -fn panic(panic: &PanicInfo<'_>) -> ! { - // unsafe { this breaks in ISR - // osal::stop_it(); - // } - _ = writeln!(crate::fsrc::sif::Stderr {}, ""); - _ = writeln!( - crate::fsrc::sif::Stderr {}, - "Thread '{}' {}", - crate::fsrc::osal::thread::current().name(), - panic - ); - //TODO: stop RTOS, exit if hosted - unsafe { done_error() }; - loop {} + + extern "C" { + #[cfg(not(feature = "low_level_tests"))] + fn done_error() -> !; // exit code != 0 + + #[cfg(feature = "low_level_tests")] + //uint32_t freertos_task_notify(void *task, uint32_t value) + fn freertos_task_notify(task: *const core::ffi::c_void, value: u32); + } + + #[panic_handler] + fn panic(panic: &PanicInfo<'_>) -> ! { + // unsafe { this breaks in ISR + // osal::stop_it(); + // } + _ = writeln!(crate::fsrc::sif::Stderr {}, ""); + _ = writeln!( + crate::fsrc::sif::Stderr {}, + "Thread '{}' {}", + crate::fsrc::osal::thread::current().name(), + panic + ); + #[cfg(not(feature = "low_level_tests"))] + { + unsafe { done_error() }; + #[allow(unreachable_code)] // safeguard as this is only a weak contract with C + loop {} + } + #[cfg(feature = "low_level_tests")] + { + // When running low level tests, do not exit on panic + // but delete thread so the main thread can continue + // with the next test + unsafe { + freertos_task_notify( + crate::low_level_tests::CONTROLLER_TASK_HANDLE, + crate::low_level_tests::PANIC, + ); + } + crate::fsrc::osal::thread::current().delete(); + } + } } #[no_mangle] @@ -41,4 +68,4 @@ extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi: #[no_mangle] extern "C" fn rust_alloc_failed() { panic!("allocation failed!"); -} \ No newline at end of file +}