static freeRTOS osal

This commit is contained in:
2024-12-21 23:27:34 +01:00
parent 164b8ed6c2
commit 10b98c3835
28 changed files with 1559 additions and 791 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ endif()
if(${CMAKE_CROSSCOMPILING})
#TODO: this somewhat hardcodes zynq as the only cross target
set(FREERTOS_PORT GCC_ARM_CA9 CACHE STRING "")
set(FREERTOS_HEAP 1 CACHE STRING "")
set(FREERTOS_HEAP ${CMAKE_CURRENT_SOURCE_DIR}/bsp_z7/freeRTOS/no_heap.c CACHE STRING "")
# config library for FreeRTOS
add_library(freertos_config INTERFACE)
+4 -3
View File
@@ -50,7 +50,7 @@
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_RECURSIVE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 20
#define configUSE_APPLICATION_TASK_TAG 1
@@ -58,6 +58,7 @@
#define configUSE_ALTERNATIVE_API 0
#define configUSE_QUEUE_SETS 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
/* The following 2 memory allocation schemes are possible for this demo:
*
@@ -72,7 +73,7 @@
* Static only configuration is not possible for this demo as it utilizes
* dynamic allocation.
*/
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configRECORD_STACK_HIGH_ADDRESS 1
@@ -86,7 +87,7 @@
#define configTIMER_QUEUE_LENGTH 20
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
#define configMAX_PRIORITIES ( 7 )
#define configMAX_PRIORITIES ( 10 )
/* Run time stats gathering configuration options. */
unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */
+3 -3
View File
@@ -23,8 +23,7 @@ void done() {
int test_socket();
// Don't ask me, it makes the linker happy and does not seem
// to break anything ¯\_(ツ)_/¯
// TODO link to GCC's personality or make the linux build not use it?
void rust_eh_personality() { puts("eh_personality"); }
void print_usage(const char * name) {
@@ -61,4 +60,5 @@ int main(int argc, char **argv) {
mission();
return 0;
}
}
+5 -2
View File
@@ -75,7 +75,7 @@
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configMAX_PRIORITIES ( 7 )
#define configMAX_PRIORITIES ( 10 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 250 ) /* Large in case configUSE_TASK_FPU_SUPPORT is 2 in which case all tasks have an FPU context. */
#define configTOTAL_HEAP_SIZE ( 204800 )
#define configMAX_TASK_NAME_LEN ( 10 )
@@ -90,7 +90,8 @@
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_QUEUE_SETS 1
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
/* Include the query-heap CLI command to query the free heap space. */
#define configINCLUDE_QUERY_HEAP_COMMAND 0
@@ -127,6 +128,8 @@ to exclude the API function. */
#define INCLUDE_xTaskGetTaskHandle 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_xSemaphoreGetMutexHolder 1
#define INCLUDE_uxTaskGetStackHighWaterMark2 1
#define INCLUDE_xTaskGetSchedulerState 1
/* The private watchdog is used to generate run time stats. */
+6
View File
@@ -0,0 +1,6 @@
// FreeRTOS build system needs to have a heap implementation, even if no dynamic allocation is used.
// So we provide one:
void no_heap() {
}
-56
View File
@@ -120,62 +120,6 @@ void vInitialiseTimerForRunTimeStats(void) {
XScuWdt_Start(&xWatchDogInstance);
}
/* configUSE_STATIC_ALLOCATION is set to 1, so the application must provide an
implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
used by the Idle task. */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize) {
/* If the buffers to be provided to the Idle task are declared inside this
function then they must be declared static - otherwise they will be allocated
on the stack and so not exists after this function exits. */
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
state will be stored. */
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* Pass out the array that will be used as the Idle task's stack. */
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/*-----------------------------------------------------------*/
/* configUSE_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the
application must provide an implementation of vApplicationGetTimerTaskMemory()
to provide the memory that is used by the Timer service task. */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize);
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize) {
/* If the buffers to be provided to the Timer task are declared inside this
function then they must be declared static - otherwise they will be allocated
on the stack and so not exists after this function exits. */
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];
/* Pass out a pointer to the StaticTask_t structure in which the Timer
task's state will be stored. */
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* Pass out the array that will be used as the Timer task's stack. */
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
// Marker for debugging sessions
__attribute__((noinline)) void done() { asm(""); }
@@ -23,8 +23,14 @@
#include "xparameters_ps.h"
#ifdef ZYNQ_USE_UART0
#define STDIN_BASEADDRESS 0xE0000000
#define STDOUT_BASEADDRESS 0xE0000000
#else
#define STDIN_BASEADDRESS 0xE0001000
#define STDOUT_BASEADDRESS 0xE0001000
#endif
/******************************************************************/
+247 -66
View File
@@ -2,89 +2,128 @@
#include "semphr.h"
#include "task.h"
#include <inttypes.h>
#include <string.h>
// TODO namespace the names
// TODO panic if able, but not in runtime calls
SemaphoreHandle_t global_threading_semaphore = NULL;
uint8_t global_threading_available_c() {
if (global_threading_semaphore == NULL) {
StaticSemaphore_t global_once_mutex_data;
SemaphoreHandle_t global_once_mutex = NULL;
global_threading_semaphore = xSemaphoreCreateBinary();
// xSemaphoreGive(global_threading_semaphore);
void freertos_init_once() {
global_once_mutex =
xSemaphoreCreateRecursiveMutexStatic(&global_once_mutex_data);
if (global_once_mutex == NULL) {
// TODO panic
}
if (uxSemaphoreGetCount(global_threading_semaphore) == 1) {
return 1;
}
// Wraps xTaskGetCurrentTaskHandle and returns NULL if no Task is running
//
// xTaskGetCurrentTaskHandle() will return a handle even if no task is
// running as long as one has been created already.
void *freertos_task_current() {
// If scheduler is not running, xTaskGetCurrentTaskHandle() might return a
// valid handle of a already created task, so we check for Scheduler state
// before calling it
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
return NULL;
} else {
return 0;
return (void *)xTaskGetCurrentTaskHandle();
}
}
void enable_global_threading_c() { xSemaphoreGive(global_threading_semaphore); }
void disable_global_threading_c() {
xSemaphoreTake(global_threading_semaphore, portMAX_DELAY);
}
const char *INVALID_TASK = "invalid task";
const char *get_task_name() {
/* this function is called from rust's panic,
* so we need to be extra sure to not cause another
* one. pcTaskGetName will trigger an assertion
* on debug builds if no task is running so we
* check if the current task is valid before using it.
* xTaskGetCurrentTaskHandle seems to be a lightweight
* way to do that */
void *task_handle = xTaskGetCurrentTaskHandle();
if (task_handle == NULL) {
return INVALID_TASK;
}
const char *name = pcTaskGetName(NULL);
if (name == NULL) {
return INVALID_TASK;
}
if (strlen(name) > configMAX_TASK_NAME_LEN) {
return INVALID_TASK;
}
return name;
}
void stop_it() { taskENTER_CRITICAL(); }
// TODO return some error code?
void *create_task(TaskFunction_t taskFunction, void *parameter,
uint32_t stack_size) {
// TODO verify uint32_t vs configSTACK_DEPTH_TYPE
TaskHandle_t newTask;
BaseType_t result =
xTaskCreate(taskFunction, "rust", stack_size, parameter, 4, &newTask);
if (result == pdTRUE) {
return newTask;
} else {
return NULL;
}
StaticTask_t init_task_data;
StackType_t init_task_stack[configMINIMAL_STACK_SIZE * 10];
void freertos_init_and_start_scheduling(TaskFunction_t init_task) {
freertos_init_once();
// TaskHandle_t handle =
xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10,
NULL, configMAX_PRIORITIES - 1, init_task_stack, &init_task_data);
// vTaskSetThreadLocalStoragePointer(handle, 0, NULL);
vTaskStartScheduler();
}
void task_delay(uint32_t milliseconds) {
// TODO verify uint32_t vs TickType_t
vTaskDelay(pdMS_TO_TICKS(milliseconds));
void freertos_task_suspend(void *handle) { vTaskSuspend(handle); }
void freertos_task_delete(void *handle) { vTaskDelete(handle); }
void freertos_task_delay(uint32_t milliseconds) {
// calculate conversion in 64bit to avoid overflow (Tick rate is < 32bit)
uint64_t ticks_64 =
((uint64_t)milliseconds * (uint64_t)configTICK_RATE_HZ) / (uint64_t)1000;
// convert to target type
TickType_t delay_ticks = (TickType_t)ticks_64;
// check overflow
if ((delay_ticks != ticks_64) || (delay_ticks > portMAX_DELAY)) {
delay_ticks = portMAX_DELAY;
}
vTaskDelay(delay_ticks);
}
void freertos_task_storage_set(void *handle, void *data) {
// if NULL is passed, try to get current task's handle
if (handle == NULL) {
handle = freertos_task_current();
if (handle == NULL) {
// No task running, invalid use of API
// TODO panic
return;
}
}
vTaskSetThreadLocalStoragePointer(handle, 0, data);
}
void *freertos_task_storage_get(void *handle) {
// if NULL is passed, try to get current task's handle
if (handle == NULL) {
handle = freertos_task_current();
if (handle == NULL) {
// No task running, invalid use of API
// TODO panic
return NULL;
}
}
return pvTaskGetThreadLocalStoragePointer(handle, 0);
}
uint32_t freertos_task_stack_watermark() {
// TODO verify types
return uxTaskGetStackHighWaterMark2(NULL);
}
void delete_task(void *task) {
vTaskSuspend(
task); // we can not use vDeleteTask as it would free the allocated memory
// which is forbidden using heap1 (which we use)
// which is forbidden using static allocation
}
void *create_queue(uint32_t length, uint32_t element_size) {
void *freertos_queue_create_static(uint32_t depth, uint32_t element_size,
char *queue_data, uint32_t queue_data_len,
uint8_t *queue) {
if (queue_data_len < sizeof(StaticQueue_t)) {
// TODO panic
// printf("freertos_queue_create_static: queue_data needs to be %zu long\n",
// sizeof(StaticQueue_t));
return 0;
}
// TODO verify uint32_t vs UBaseType_t
QueueHandle_t newQueue = xQueueCreate(length, element_size);
QueueHandle_t newQueue = xQueueCreateStatic(depth, element_size, queue,
(StaticQueue_t *)queue_data);
return newQueue;
}
uint8_t queue_receive(void *queue, void *message) {
uint8_t freertos_queue_receive(void *queue, void *message) {
if (xQueueReceive(queue, message, 0) == pdPASS) {
return 1;
} else {
@@ -92,7 +131,7 @@ uint8_t queue_receive(void *queue, void *message) {
}
}
uint8_t queue_send(void *queue, void *message) {
uint8_t freertos_queue_send(void *queue, void *message) {
if (xQueueSend(queue, message, 0) != pdPASS) {
return 1;
} else {
@@ -100,11 +139,7 @@ uint8_t queue_send(void *queue, void *message) {
}
}
void *create_mutex() { return xSemaphoreCreateRecursiveMutex(); }
uint8_t take_mutex(void *handle) {
// TODO check if global semaphore is free (ie, we are doing multitasking)
// if not, pointers are invalid, bail out
uint8_t freertos_mutex_take(void *handle) {
if (xSemaphoreTakeRecursive(handle, portMAX_DELAY) == pdPASS) {
return 1;
} else {
@@ -112,12 +147,158 @@ uint8_t take_mutex(void *handle) {
}
}
uint8_t give_mutex(void *handle) {
// TODO check if global semaphore is free (ie, we are doing multitasking)
// if not, pointers are invalid, bail out
uint8_t freertos_mutex_give(void *handle) {
if (xSemaphoreGiveRecursive(handle) == pdPASS) {
return 1;
} else {
return 0;
}
}
enum OnceState { UNINIT = 0, INIT = 1, TAKEN = 2 };
typedef struct {
SemaphoreHandle_t local_mutex;
StaticSemaphore_t local_mutex_data;
uint8_t once_state;
} StaticOnceData_t;
/**
* mimics pthread_once()
*
* uses a global mutex to threadsafely check if the passed local mutex
* was created already, creating it if not.
*
* After that, the local mutex guards once_data, which is used to only
* call the function once. All calls sharing the local_mutex and
* once_data will block until the function was called once.
*
* local_mutex and once_data need to be initialized to 0 before any calls.
* local_mutex, local_mutex_data and once_data must not be used other
* than a call to this function.
*
* panics
*/
uint8_t freertos_once(char *once_data_in, uint32_t once_data_len,
void *function(void)) {
if (once_data_len < sizeof(StaticOnceData_t)) {
// TODO panic
// printf("freertos_once: data needs to be %zu long\n",
// sizeof(StaticTask_t));
return 0;
}
StaticOnceData_t *once_data = (StaticOnceData_t *)once_data_in;
// TODO assert global_once_mutex != NULL
// first, we need to check if the local mutex was already created
// this needs to be protected by a mutex to be threadsafe
if (xSemaphoreTakeRecursive(global_once_mutex, portMAX_DELAY) != pdTRUE) {
// TODO panic
}
if (once_data->once_state == UNINIT) {
once_data->local_mutex =
xSemaphoreCreateRecursiveMutexStatic(&once_data->local_mutex_data);
if (once_data->local_mutex == NULL) {
// TODO panic
}
once_data->once_state = INIT;
}
if (xSemaphoreGiveRecursive(global_once_mutex) != pdTRUE) {
// TODO panic
}
// Now, we know local mutex is valid, so we use it to guard access to
// the once_state
if (xSemaphoreTakeRecursive(once_data->local_mutex, portMAX_DELAY) !=
pdTRUE) {
// TODO panic
}
// cache result to avoid concurrent access
uint8_t result = 0;
if (once_data->once_state == INIT) {
once_data->once_state = TAKEN;
result = 1;
if (function != NULL) {
function();
}
}
if (xSemaphoreGiveRecursive(once_data->local_mutex) != pdTRUE) {
// TODO panic
}
return result;
}
/**
* Returns 1 only for the first call, further calls return 0
*
* once_data needs to be initialized to 0 and not be accessed outside of this
* function
*/
uint8_t freertos_simple_once(uint8_t *once_data) {
// TODO assert global_once_mutex != NULL
uint8_t result = 0;
// This function is basically a flag stored in once_data, protected by the
// global_once_mutex
if (xSemaphoreTakeRecursive(global_once_mutex, portMAX_DELAY) != pdTRUE) {
// TODO panic
}
if (*once_data == 0) {
*once_data = 1;
result = 1;
}
if (xSemaphoreGiveRecursive(global_once_mutex) != pdTRUE) {
// TODO panic
}
return result;
}
uint32_t freertos_task_priority_max() {
// -1 is the max to be used per documentation
return configMAX_PRIORITIES - 1;
}
// 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) {
// 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",
// sizeof(StaticTask_t));
return NULL;
}
if (priority == UINT32_MAX) {
priority = freertos_task_priority_max();
}
if (priority > freertos_task_priority_max()) {
return NULL;
}
size_t stack_size_words = stack_size / sizeof(StackType_t);
// 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);
}
void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) {
// printf("freertos_create_mutex_static: create: %p, %"PRIu32"\n", mutex_data,
// mutex_data_len);
if (mutex_data_len < sizeof(StaticSemaphore_t)) {
// TODO panic?
// printf("freertos_create_mutex_static: mutex data needs to be %zu long\n",
// sizeof(StaticSemaphore_t));
return NULL;
}
return xSemaphoreCreateRecursiveMutexStatic((StaticSemaphore_t *)mutex_data);
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <FreeRTOS.h>
// Init framework objects, and start an init task.
// Init task is created with minimal stack size, so it should be
// used to dispatch an actual init task if stack space is needed
void freertos_init_and_start_scheduling(TaskFunction_t init_task);
+114 -42
View File
@@ -3,25 +3,23 @@
#include <stdio.h>
/* Scheduler include files. */
#include "FreeRTOS.h"
#include "freeRTOS_rust_helper.h"
#include "semphr.h"
#include "task.h"
void rust_main();
#include <hardware/interfaces.h>
#include <unistd.h>
void rust_main();
void test_hardware() {
int fd0 = hw_device_open("uart0", 5);
write(fd0, "UART0\n", 6);
int fd1 = hw_device_open("uart1", 5);
write(fd1, "uart1\n", 6);
uint8_t buffer[255];
// uint8_t buffer[255];
// for (int i = 0; i< sizeof(buffer); i++) {
// buffer[i] = i;
@@ -29,55 +27,31 @@ void test_hardware() {
// write(fd0, buffer, sizeof(buffer));
vTaskDelay(10 / portTICK_PERIOD_MS);
// vTaskDelay(10 / portTICK_PERIOD_MS);
write(1, "got:\n", 5);
// write(1, "got:\n", 5);
int read_bytes = read(fd0, buffer, sizeof(buffer));
write(1, buffer, read_bytes);
read_bytes = read(fd1, buffer, sizeof(buffer));
write(1, buffer, read_bytes);
// int read_bytes = read(fd0, buffer, sizeof(buffer));
// write(1, buffer, read_bytes);
// read_bytes = read(fd1, buffer, sizeof(buffer));
// write(1, buffer, read_bytes);
}
// called to stop execution (either a panic or program ended)
// to be implemented by bsp (do not return from it!)
void done();
void init_task(void * _) {
(void )_;
// printf("Starting Mission\n");
test_hardware();
rust_main();
// printf("Started Tasks, deleting init task\n");
done();
vTaskDelete(NULL);
void init_task(void* _) {
rust_main(_);
}
void mission(void) {
int taskParameters = 0;
test_hardware();
// static const size_t stackSizeWords = 102400;
// StaticTask_t xTaskBuffer;
// StackType_t xStack[stackSizeWords];
xTaskCreate(init_task, /* The function that implements the task. */
"init", /* The text name assigned to the task - for debug only as
it is not used by the kernel. */
10240, /* The size of the stack to allocate to the task. */
&taskParameters, /* The parameter passed to the task - not used in
this simple case. */
4, /* The priority assigned to the task. */
NULL);
vTaskStartScheduler();
freertos_init_and_start_scheduling(init_task);
/* If all is well, the scheduler will now be running, and the following
line will never be reached. If the following line does execute, then
@@ -101,6 +75,9 @@ void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName) {
(void)pcTaskName;
(void)pxTask;
// TODO panic
write(1, "Overflow", 9);
/* Run time stack overflow checking is performed if
configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook
function is called if a stack overflow is detected. */
@@ -135,3 +112,98 @@ void vAssertCalled(const char *pcFile, unsigned long ulLine) {
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
/* configSUPPORT_STATIC_ALLOCATION is set to 1, so the application must provide
an
implementation of vApplicationGetIdleTaskMemory() to provide the memory that
is
used by the Idle task. */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
/* If the buffers to be provided to the Idle task are declared inside this
function then they must be declared static - otherwise they will be
allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
state will be stored. */
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* Pass out the array that will be used as the Idle task's stack. */
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/*-----------------------------------------------------------*/
/* configSUPPORT_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so
the
application must provide an implementation of
vApplicationGetTimerTaskMemory()
to provide the memory that is used by the Timer service task. */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
/* If the buffers to be provided to the Timer task are declared inside this
function then they must be declared static - otherwise they will be
allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];
/* Pass out a pointer to the StaticTask_t structure in which the Timer
task's state will be stored. */
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* Pass out the array that will be used as the Timer task's stack. */
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configTIMER_TASK_STACK_DEPTH is specified in words, not bytes. */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
/*-----------------------------------------------------------*/
-78
View File
@@ -1,78 +0,0 @@
The framework is meant to be used without dynamic allocation. Currently the only supported RTOS is implemented in C, which adds additional constraints.
# Static allocation and rust
As far as rust goes, static allocation and multithreading with a C RTOS is not possible generally. No allocation means that almost all data will be located on the stack. References to data on the stack are per design unsafe in the most wide sense of the word. First, because they are on the stack which will be cleared after a function defining data returns. Secondly, rust specifies data to be ignorant of their location in memory, that is data can be moved in memory without any possibility of hooks which could update foreign references.
In a multithreaded software, references to data need to be passed to enable communication between objects, to be able to execute a task (which requires passing a reference to the task's data (which contains information on other tasks/data) to the RTOS), to send messages (passing the reference to the Queue, however encapsulated, is a reference to the queue which needs to be located somewhere) or to acess shared data (mutexes, same as with queues).
All of these communication techniques are essential for this framework, so solutions need to be provided to be able to write safe code.
While statically allocating all (shared) data, using global `'static` variables putting them into static memory instead of the stack, is generally possible and might be a possible solution to static allocation, it is not consistent with either the object oriented style of this framework, nor with general rust coding style (which discourages static data).
# The framework's approach
## Task Executor
Central element in running multithreaded is the task executor. It is borrowing references to all data to be used during runtime. This way, the references are guaranteed to be valid for the lifetime of the executor. This can be seen as 'pinning' the objects on the stack to fixed adresses.
By then coupling the availability of tasks to the executor (dropping the executor stops all tasks), the references can be guaranteed to be valid for the whole time tasks are available.
The step where references to other objects are stored in structs is called initialization. This is (enforced by the compiler via layout of the corresponding functions) the only time where access to other objects is granted and references can be stored.
The initialization is performed by the task executor which controls that only pinned objects are given access to other pinned objects. Pinned objects are given an API to access other pinned objects (provided by an `ObjectManager`) as well as a token. The token can be passed to functions which will return references to objects to be stored by the calling object. This way, these references can only be created during the initialization.
For an production software, as soon as all tasks are started, the initial task is stopped/deleted. As such, there is no way (and no intention) for the multithreading to ever stop. In that case, the references shared during initialization will be valid for the whole runtime of the program.
As there might be use cases, for example in test cases, where multithreading is to be stopped, additional safeguards are implemented to ensure that the references shared during initialization are invalidated or their use is restricted. As running outside of the multithreaded environment is not meant for production, failing without corruption, ie panicking, is an acceptable way out. This is implemented by using a global semaphore indicating if the thread executor is alive. If that is not the case, all access to shared ressources will result in a panic. This adds an additional overhead to all access to shared data, namely checking the semaphore.
## Shared references
To be able to implement aforementioned safeguards, access to references is guarded by the framework.
The only time where references to other objects can be acquired is the initialization step performed by the task executor. As the task executor borrows all objects mutably, no references (mutable or not) to other objects, can be stored within any object.
Access to the other objects is granted via an object manager implementation, which will provide other objects as a nonmutable reference. Again, this reference can not be stored, only queried, without violating the borrow checker.
The reference obtained by the object manager is typed as `dyn SystemObjectIF`, so the ways to obtain references is governed by this trait and its super traits. These traits only offer threadsafe APIs. Additionally, these APIs require a token which will be passed by the task executor so they can only be used during initialization.
Those references are either protected by a mutex or implemented using queues, which are the two primitives used to implement thread safety. As such, all access to shared references must use either the mutex or the queue API which is protected by an additional semaphore as described above.
In some cases, smart pointers are used. These store raw pointers to other objects offering a threadsafe API. As dereferencing such a pointer is only allowed when objects are pinned, the dereferencing is protected by the additional semaphore as well.
## RTOS Metadata
RTOS metadata is the data the RTOS needs to work on its provided primitives such as tasks, queues and mutexes. Those come in two variants, statically sized, which here are called descriptors, and dynamic data. Descriptors have a size known at compile time which is the same for all instances of the primitive. Dynamic data is for instance the backend of a queue or the stack of a task. These do generally differ in size for the different instances. Keeping with the general theme of object orientation, the dynamic information is encapsulated within the structs abstracting the RTOS primitives.
### Descriptors
Two options exist for storing the descriptors. Either they are stored in a preallocated static C array, or they are stored in memory allocated as part of the corresponding struct.
The first option has the advantage that in C, the size of the descriptors is known at compile time (it is determined by the RTOS configuration which is implemented in C macros). Its disadvantage is that the size of the array needs to be adapted to the actual number ob instances used by the RTOS, which might not be trivially determined except for running the software.
The second option does not have this disadvantage, as the memory is provided by the user of the API. The disadvantage here is that the size of the data needs to be encoded manually for the selected configuration of the RTOS, which again can only be verified during runtime, when rust and C interact.
Both solutions lead to a detection of the configuration error (too few descriptors preallocated, too little memory allocated) only during runtime. As the configuration of the RTOS is expected to be more stable than the number of primitive instances, the second option is implemented.
### Dynamic data
Dynamic data is allocated on the stack as part of the encapsulating struct. Having dynamic data preallocated is not trivial as the actual size is determined by the instantiation of the corresponding struct, which is done at runtime.
### Passing to the RTOS
The metadata should only passed to the RTOS when the references are fixed, that is when the task executor is constructed. To make sure that no uninitialized primitives are used, they are created in an invalid state within `new()` of the encapsulating struct. Using an uninitialized struct does fail mostly silently, as this could be happen during runtime, when no panic is allowed.
As the structs are used to facilitate inter task communication, the initialization is hidden in the call which copies the contained reference out to the using object. That way, structs which are used by other tasks are guaranteed to be initialized, and uninitialized structs can only be used locally, so they do not need to be protected by the primitives at all.
The handling of the failure is delegated to the structs using the primitives, as the 'correct' way to fail is dependent on the usage:
* Uninitialized `MessageQueue`s will return empty from `read()` calls
* Uninitialized `Mutex`es [TBCoded] will behave nominally, but not lock any actual mutex
* Uninitialized `OwnedDataset`s will behave nominally, but not lock any mutex (which is uninitialized)
* Uninitialized `ReferencedDataset`s will return default data on `read()` and do nothing on `commit()`
## Smart pointers
Datasets are smart pointers to T, with a read/commit API. Both a mutex and the threading semaphore protect each call. Clones (`ReferencedDataset`) are created invalid and can only made valid during init using the init token.
Can be compared to the mutex in rust std with a slightly different API.
Stores are a statically preallocated slotted allocation scheme:
StoreAccessor is a smart pointer to the Store, offering same API as Store. Internally, it dereferences a raw pointer in each call, protected by the threading semaphore. Store in turn protects a shared backend with a mutex. Acessors can only be obtained during init. Together, StoreAccessor and Store are an thread safe allocator to the backend.
StoreSlots are smart pointers to memory allocated in a store backend. Can be created/allocated during runtime.
+6 -6
View File
@@ -1,11 +1,11 @@
//TODO control visibility of internal structs
pub mod sif;
pub mod queues;
//pub mod queues;
pub mod osal;
pub mod tasks;
pub mod objectmanager;
pub mod datasets;
pub mod store;
mod mutex;
//pub mod tasks;
//pub mod objectmanager;
//pub mod datasets;
//pub mod store;
//pub mod mutex;
pub mod introspection;
-88
View File
@@ -1,88 +0,0 @@
use crate::check_global_threading_available;
use super::osal;
// TODO we do not discern between a mutex (holding the descriptor) and a clone (pointing to the
// original descriptor), waisting some memory
pub struct RawMutex {
handle: Option<*const core::ffi::c_void>,
descriptor: [u8; 10], //TODO which size?
}
pub struct RawMutexGuard {
handle: *const core::ffi::c_void,
}
impl Drop for RawMutexGuard {
fn drop(&mut self) {
check_global_threading_available!(); // tell me when you reach this one!
// We use nullptr as marker for an uninitialized mutex, which we
// must not give
if self.handle != 0 as *const core::ffi::c_void {
unsafe { osal::give_mutex(self.handle) };
}
}
}
impl RawMutex {
pub fn new() -> Self {
Self {
handle: None,
descriptor: [0; 10],
}
}
fn initialize(&mut self) {
// check_global_threading_available!(); we have a token, so this is redundant
if self.handle != None {
return;
}
// TODO verify handle size
let handle = unsafe { osal::create_mutex() };
if handle == 0 as *const core::ffi::c_void {
panic!("Could not create mutex")
}
self.handle = Some(handle);
}
pub fn take(&self) -> Result<RawMutexGuard, ()> {
check_global_threading_available!();
if let Some(handle) = self.handle {
return match unsafe { osal::take_mutex(handle) } {
1 => Ok(RawMutexGuard { handle: handle }),
_ => Err(()), // Only when timeout expired (we have none) TODO error code
};
} else {
// nullptr makes the guard do nothing
Ok(RawMutexGuard {
handle: 0 as *const core::ffi::c_void,
})
}
}
// TODO protect with token
pub fn clone(&self) -> Self {
let mut_self = self as *const Self as *mut Self; //oh look, a C developer wrote this
unsafe { (*mut_self).initialize() }; //TODO this might be safe (we are in init), but does not look very good
// At this point self.handle must be valid, initialize would have panicked otherwise
Self {
handle: self.handle,
descriptor: [0; 10],
}
}
// Mutex guard takes care of this in its drop
// Do not offer this in API to avoid duplicate give
// pub fn give(&self) -> Result<(),()> {
// osal::check_global_threading_available();
// let handle = match self.handle {
// None => return Ok(()),
// Some(handle) => handle
// };
// match unsafe {osal::give_mutex(handle)} {
// 1 => Ok(()),
// _ => Err(()) //TODO error code
// }
// }
}
+6
View File
@@ -0,0 +1,6 @@
mod ffi;
pub mod sync;
pub mod once;
pub mod thread;
pub mod queue;
+86
View File
@@ -0,0 +1,86 @@
//TODO verify uXX == uintXX_t
type TaskFunction = unsafe extern "C" fn(*mut core::ffi::c_void) -> !;
pub struct Sizes {
}
// TODO these should be passed by compile time value
#[cfg(target_os = "linux")]
impl Sizes {
pub const TASK_DATA_SIZE: usize = 192;
pub const MUTEX_DATA_SIZE: usize = 168;
pub const QUEUE_DATA_SIZE: usize = 168;
pub const TASK_MINIMAL_STACK_SIZE: usize = 16424;
}
// We use none for bare metal, as we only support one architecture so far
#[cfg(target_os = "none")]
impl Sizes {
pub const TASK_DATA_SIZE: usize = 184;
pub const MUTEX_DATA_SIZE: usize = 168;
pub const QUEUE_DATA_SIZE: usize = 168;
pub const TASK_MINIMAL_STACK_SIZE: usize = 10240;
}
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,
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;
// pub fn stop_it();
pub fn freertos_task_suspend(handle: *const core::ffi::c_void);
pub fn freertos_task_delete(handle: *const core::ffi::c_void) -> !;
pub fn freertos_task_storage_set(handle: *const core::ffi::c_void, data: *const core::ffi::c_void);
pub fn freertos_task_storage_get(handle: *const core::ffi::c_void) -> *const core::ffi::c_void;
pub fn freertos_task_delay(milliseconds: u32);
pub fn freertos_task_stack_watermark() -> u32;
pub fn freertos_task_current() -> *const core::ffi::c_void;
// TODO this should be passed by compile time value
pub fn freertos_task_priority_max()-> u32;
//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 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;
pub fn freertos_queue_receive(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
pub fn freertos_queue_send(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
pub fn freertos_mutex_create_static(
mutex_data: *const core::ffi::c_char,
mutex_data_len: u32,
) -> *const core::ffi::c_void;
pub fn freertos_mutex_take(mutex: *const core::ffi::c_void) -> u8;
pub fn freertos_mutex_give(mutex: *const core::ffi::c_void) -> u8;
//uint8_t freertos_simple_once(uint8_t *once_data)
pub fn freertos_simple_once(once_data: *const u8) -> u8;
}
-67
View File
@@ -1,67 +0,0 @@
type TaskFunction = unsafe extern "C" fn(*mut core::ffi::c_void);
//TODO verify uXX == uintXX_t
//TODO safe API
// This is a macro so that the panic is at the right place
#[macro_export]
macro_rules! check_global_threading_available {
() => (
if !crate::osal::global_threading_available() {
panic!("using threaded API outside of threading environment")
}
);
}
extern "C" {
pub fn outbyte(c: u8);
//void *create_task(TaskFunction_t taskFunction, void *parameter, size_t stack_size)
pub fn create_task(
taskFunction: TaskFunction,
parameter: *const core::ffi::c_void,
stack_size: u32,
) -> *const core::ffi::c_void;
pub fn get_task_name() -> *const core::ffi::c_char;
pub fn stop_it();
pub fn delete_task(handle: *const core::ffi::c_void);
pub fn task_delay(milliseconds: u32);
//void *create_queue(size_t length, size_t element_size)
pub fn create_queue(length: u32, element_size: u32) -> *const core::ffi::c_void;
pub fn queue_receive(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
pub fn queue_send(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
pub fn create_mutex() -> *const core::ffi::c_void;
pub fn take_mutex(mutex: *const core::ffi::c_void) -> u8;
pub fn give_mutex(mutex: *const core::ffi::c_void) -> u8;
fn global_threading_available_c() -> u8;
fn enable_global_threading_c();
fn disable_global_threading_c();
}
pub fn task_delete_self() {
unsafe {
delete_task(0 as *const core::ffi::c_void);
}
}
pub fn global_threading_available() -> bool {
unsafe { global_threading_available_c() == 1 }
}
pub fn enable_global_threading() {
unsafe { enable_global_threading_c() };
}
pub fn disable_global_threading() {
unsafe { disable_global_threading_c() };
}
+16
View File
@@ -0,0 +1,16 @@
use super::ffi::freertos_simple_once;
pub struct SimpleOnce {
data: u8
}
impl SimpleOnce {
pub const fn new() -> Self {
Self{data: 0}
}
// returns true only once
pub fn once(&self) -> bool {
unsafe{freertos_simple_once(&self.data) == 1}
}
}
+147
View File
@@ -0,0 +1,147 @@
use core::marker::{self, PhantomData};
use super::ffi::*;
use super::sync::{StaticInit, StaticReadOnceLock};
pub struct MessageQueueSender<T>
where
T: 'static + Clone + Copy,
{
queue_id: *const core::ffi::c_void,
_marker: marker::PhantomData<T>, // we do not actually own any T, only take them in impl
}
impl<T> MessageQueueSender<T>
where
T: 'static + Clone + Copy,
{
pub fn send(&self, message: T) -> Result<(), T> {
// we move ownership out to C (so must not drop it)
// regaining ownership in receive() where it then will be dropped
let message = core::mem::ManuallyDrop::new(message);
let res: u8;
unsafe {
// safe because:
// OS will read not more than length of message queue elements
// queue was created with size_of::<T> as length of message queue elements
// in MessageQueue::static_init()
// making this pointer access safe as long as OS is holding the contract
let message_pointer: *const core::ffi::c_void =
&message as *const _ as *const core::ffi::c_void;
res = super::ffi::freertos_queue_send(self.queue_id, message_pointer);
}
if res == 1 {
Ok(())
} else {
// give the message back to let user decide when to drop
Err(core::mem::ManuallyDrop::into_inner(message))
}
}
}
impl<T> Clone for MessageQueueSender<T>
where
T: 'static + Clone + Copy,
{
fn clone(&self) -> Self {
Self {
queue_id: self.queue_id,
_marker: PhantomData::default(),
}
}
}
impl<T> Copy for MessageQueueSender<T> where T: 'static + Clone + Copy {}
pub struct MessageQueue<T, const DEPTH: usize>
where
T: 'static + Clone + Copy,
{
queue: StaticReadOnceLock<[T; DEPTH]>,
queue_data: StaticReadOnceLock<[core::ffi::c_char; Sizes::QUEUE_DATA_SIZE]>,
queue_id: Option<*const core::ffi::c_void>,
}
impl<T, const DEPTH: usize> MessageQueue<T, DEPTH>
where
T: 'static + Clone + Copy,
{
pub const fn new() -> Self {
Self {
// SAFE: this is uninitialized memory for C to use
// we only type it by T to get the size right,
// the array can not be used other than once, which is when it is
// passed to C
queue: StaticReadOnceLock::new([unsafe { core::mem::zeroed() }; DEPTH]),
queue_data: StaticReadOnceLock::new([0; Sizes::QUEUE_DATA_SIZE]),
queue_id: None,
}
}
pub fn receive(&self) -> Result<T, ()> {
let queue_id = if let Some(queue_id) = self.queue_id {
queue_id
} else {
return Err(());
};
let mut message = core::mem::MaybeUninit::uninit();
let res: u8;
unsafe {
// safe beacuse:
// OS will write not more than length of message queue elements
// queue was created with size_of::<T> as length of message queue elements
// in MessageQueue::static_init()
// making this pointer access safe as long as OS is holding the contract
let message_pointer: *mut core::ffi::c_void =
message.as_mut_ptr() as *mut T as *mut core::ffi::c_void;
res = super::ffi::freertos_queue_receive(queue_id, message_pointer);
}
if res == 1 {
// SAFE: the OS only returns data passed into the queue, which is only ever a valid T
// TODO: alignment correct?
let message = unsafe { message.assume_init() };
Ok(message)
} else {
Err(())
}
}
pub fn sender(&self) -> MessageQueueSender<T> {
if let Some(queue_id) = self.queue_id {
return MessageQueueSender {
queue_id,
_marker: PhantomData::default(),
};
} else {
panic!("MessageQeue::send() called on uninitialized MessageQueue, make sure to call static_init() first")
}
}
}
impl<T, const DEPTH: usize> StaticInit for MessageQueue<T, DEPTH>
where
T: 'static + Copy + Clone + Default,
{
fn static_init(&'static mut self) {
let depth: u32 = u32::try_from(DEPTH).unwrap(); //TODO check these
let element_size: u32 = u32::try_from(core::mem::size_of::<T>()).unwrap();
let queue: *mut u8 = self.queue.take_no_init().unwrap() as *mut T as *mut u8;
let queue_data = self.queue_data.take_no_init().unwrap();
let queue_id = unsafe {
super::ffi::freertos_queue_create_static(
depth,
element_size,
queue_data.as_mut_ptr(),
queue_data.len().try_into().unwrap(),
queue,
)
};
if queue_id == 0 as *mut core::ffi::c_void {
panic!("could not create Queue");
}
self.queue_id = Some(queue_id);
}
}
+11
View File
@@ -0,0 +1,11 @@
// TODO consider sync and send instead of copy for the sharable states
mod mutex_raw;
mod mutex;
pub use self::mutex::{Mutex, MutexClone};
mod static_read_once_lock;
pub use self::static_read_once_lock::{StaticInit,StaticReadOnceLock};
//mod spmc_mutex;
//pub use spmc_mutex::McspMutex;
+130
View File
@@ -0,0 +1,130 @@
use core::ops::{Deref, DerefMut};
use super::{
mutex_raw::{RawMutex, RawMutexClone, RawMutexGuard},
StaticInit,
};
// TODO: Differences to std::Mutex
// No poisoning, as panic halts all execution
pub struct Mutex<T> {
data: T,
// marker to track initialization
// will only be set in static_init, where it is
// ensured to be pointing to a 'static reference
pointer: Option<*mut T>,
mutex: RawMutex,
}
impl<T> Mutex<T> {
pub const fn new(init_value: T) -> Self {
Self {
data: init_value,
pointer: None,
mutex: RawMutex::new(),
}
}
// takes &mut self to be able to work in uninitialized state
// other than std::sync::Mutex, this Mutex is never to be cloned
// (the clone() fn returns MutexClone), so it is assumed that the
// owner will have mut access
pub fn lock(&mut self) -> MutexGuard<T> {
let guard = self.mutex.take();
// Threadsafety is ensured by the inner RawMutex, which will
// be used by any Clones of this Mutex.
// Note that the inner RawMutex might be uninitialized. This will
// happen if this Mutex was not initialized with static_init.
// As clone() checks for initialization, this can only happen if no
// clones exist. In this case, threadsafety is no concern, as only
// One instance and no Clone of this Mutex can exist (it does not impl
// Copy). Borrowing rules ensure in case that only one guard exits
// per Mutex, making access via &mut self.data safe.
MutexGuard {
_guard: guard,
data: &mut self.data,
}
}
// Create a clone
// MutexClone is Copy/Clone, so the address passed out needs to be valid
// for &'static as the clone might dereference it (after taking the mutex)
// This is ensured in static_init, which can only be called on static
// objects and will set self.pointer as a signal. As Mutex does not impl
// Copy, self can not be a non-static uninitialized Copy.
// The clone upholds the same guarantees in its lock fn as Mutex does,
// so the pointer to the data can safely be passed on
pub fn clone(&self) -> MutexClone<T> {
if let Some(pointer) = self.pointer {
MutexClone {
data: pointer,
mutex: self.mutex.clone(),
}
} else {
panic!("Mutex::clone() called on unitialized Mutex, call static_init() first");
}
}
}
impl<T> StaticInit for Mutex<T> {
fn static_init(&'static mut self) {
self.mutex.static_init();
// self is &'static, so the
// address of the member is valid for whole runtime
// cached here so its validity can be checked in clone()
self.pointer = Some(&mut self.data);
}
}
pub struct MutexClone<T> {
data: *mut T,
mutex: RawMutexClone,
}
impl<T> MutexClone<T> {
pub fn lock(&self) -> MutexGuard<'_, T> {
let guard = self.mutex.take();
// SAFE: We only pass out the reference to the data
// after acquiring the inner mutex
// so, from here on, this guard is the only holder of the reference
// and owns it until it is dropped, when it also gives back
// the mutex
// Calling take() twice will deadlock, as does std::sync::Mutex
MutexGuard {
_guard: guard,
data: unsafe { &mut *self.data },
}
}
}
impl<T> Clone for MutexClone<T> {
fn clone(&self) -> Self {
Self {
data: self.data,
mutex: self.mutex,
}
}
}
impl<T> Copy for MutexClone<T> {}
pub struct MutexGuard<'a, T> {
_guard : RawMutexGuard,
data: &'a mut T,
}
impl<T> Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}
impl<T> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.data
}
}
@@ -0,0 +1,106 @@
use core::panic;
use super::super::ffi::*;
use super::static_read_once_lock::{StaticInit, StaticReadOnceLock};
pub struct RawMutex {
mutex_data: StaticReadOnceLock<[core::ffi::c_char; Sizes::MUTEX_DATA_SIZE]>,
mutex_handle: Option<*const core::ffi::c_void>,
}
// Shared access to a RawMutex (which does not implement Clone)
pub struct RawMutexClone {
mutex_handle: *const core::ffi::c_void,
}
unsafe impl Send for RawMutexClone {
}
impl Clone for RawMutexClone {
fn clone(&self) -> Self {
Self{mutex_handle: self.mutex_handle}
}
}
impl Copy for RawMutexClone{}
impl RawMutexClone {
pub fn take(&self) -> RawMutexGuard {
// Safe: Option is set only when create call succeeded
unsafe { freertos_mutex_take(self.mutex_handle) };
RawMutexGuard {
mutex_handle: Some(self.mutex_handle),
}
}
}
impl RawMutex {
pub const fn new() -> Self {
Self {
mutex_data: StaticReadOnceLock::new([0; Sizes::MUTEX_DATA_SIZE]),
mutex_handle: None,
}
}
// Does not panic/err on uninitialized mutex
// Cloning a mutex checks for initialization, so we can be sure to either
// - be the only instance of this mutex, which is threadsafe
// - be shared and initialized, which is threadsafe
pub fn take(&self) -> RawMutexGuard {
if let Some(handle) = self.mutex_handle {
// Safe: Option is set only when create call succeeded
unsafe { freertos_mutex_take(handle) };
}
RawMutexGuard {
mutex_handle: self.mutex_handle,
}
}
// We do not implement Clone, as we want to return a RawMutexClone, but other than that
// it is used quite the same, so we use `clone()` as function name
pub fn clone(&self) -> RawMutexClone {
// clone should be an init operation, so we can panic here on uninitialized mutex
match self.mutex_handle {
None => panic!(
"RawMutex::clone() on uninitialized Mutex, make sure to call static_init() first"
),
Some(handle) => RawMutexClone {
mutex_handle: handle,
},
}
}
}
impl StaticInit for RawMutex {
fn static_init(&'static mut self) {
let mutex_data = self.mutex_data.take_no_init().unwrap();
let handle = unsafe {
freertos_mutex_create_static(
mutex_data.as_mut_ptr(),
mutex_data.len().try_into().unwrap(),
)
};
if handle == 0 as *const core::ffi::c_void {
panic!("RawMutex::init: Could not create mutex");
}
self.mutex_handle = Some(handle);
}
}
// can be uninitialized, but as we do not provide a new() the only way to
// acquire an uninitialized Guard is via a take() on an uninitialized RawMutex
// which is threadsafe (see there)
pub struct RawMutexGuard {
mutex_handle: Option<*const core::ffi::c_void>,
}
impl Drop for RawMutexGuard {
fn drop(&mut self) {
if let Some(handle) = self.mutex_handle {
// Safe: RawMutex only returns a handle when create call succeeded
unsafe { freertos_mutex_give(handle) };
}
}
}
@@ -0,0 +1,38 @@
use super::{mutex::{Mutex, MutexClone}, static_read_once_cell::StaticInit};
struct McspMutex<T> {
mutex: Mutex<T>,
}
impl<T> McspMutex<T>
where
T: Copy + Clone,
{
pub fn read(&mut self) -> T {
*self.mutex.lock()
}
pub fn write(&mut self, data: T) {
*self.mutex.lock() = data;
}
pub fn clone(&self)-> McspMutexClone<T> {
McspMutexClone { mutex: self.mutex.clone() }
}
}
impl<T> StaticInit for McspMutex<T> {
fn static_init(&'static mut self) {
self.mutex.static_init();
}
}
struct McspMutexClone<T> {
mutex: MutexClone<T>
}
impl<T> McspMutexClone<T> where T: Clone + Copy {
pub fn read(&self) -> T {
*self.mutex.lock()
}
}
@@ -0,0 +1,37 @@
use core::cell::UnsafeCell;
use super::super::once::SimpleOnce;
pub trait StaticInit {
fn static_init(&'static mut self);
}
pub struct StaticReadOnceLock<T> {
inner: UnsafeCell<T>,
once: SimpleOnce
}
impl<T: 'static> StaticReadOnceLock<T> {
pub const fn new(init: T) -> Self {
Self{inner: UnsafeCell::new(init), once: SimpleOnce::new()}
}
pub fn take_no_init(&'static self)-> Option<&'static mut T> {
if self.once.once() {
return Some(unsafe{&mut *self.inner.get()})
}
None
}
}
impl<T: 'static + StaticInit> StaticReadOnceLock<T> {
pub fn take(&'static self) -> Option<&'static mut T> {
let inner = unsafe{&mut *self.inner.get()};
inner.static_init();
// Re-Borrow inner.
// TODO Is unsafe by leaking static references via static_init()
self.take_no_init()
}
}
unsafe impl<T> Sync for StaticReadOnceLock<T> {}
+360
View File
@@ -0,0 +1,360 @@
use core::panic;
use super::{
ffi::{self, *},
sync::StaticReadOnceLock,
};
pub struct Sizes {}
impl Sizes {
pub const MINIMAL_STACK_SIZE: usize = ffi::Sizes::TASK_MINIMAL_STACK_SIZE;
}
// Thread Metadata
//
// Metadata is stored in a separate rust struct.
// This includes the thread's name. The name could also be stored in FreeRTOS native
// metadata but this poses the following problems:
// - Converting C Strings to rust is always a hassle
// - The name is passed as a pointer with unknown lifetime
// Especially the second problem can be quite harmful with access to "freed" data.
// To avoid the problems, a rust object with static lifetime is created and stored in
// FreeRTOS thread local storage as void pointer.
// Recasting the void pointer back is inherently unsafe but can be managed.
// Problems arise on threads created in C. Here, convention states to set the thread local
// storage to NULL. Even if that is missed, the FreeRTOS handle is stored in the ThreadHandle struct by rust.
// By comparing the handle used to obtain the thread local storage pointer with the handle
// within the ThreadHandle object, the validity of the returned pointer can be checked with
// high confidence (probability of finding a random 32bit number n at memory location n + x
// where x is dependant on the layout of the ThreadHandle struct).
pub struct ThreadHandle {
name: &'static str,
freertos_handle: Option<*const core::ffi::c_void>,
}
impl ThreadHandle {
pub fn name(&self) -> &str {
self.name
}
pub fn delay(&self, time: core::time::Duration) {
// freertos_task_delay must not be called when no task
// running. If that is the case, freertos_handle will be None
if self.freertos_handle == None {
panic!("Delay called on non running thread");
//TODO call a idling delay?
} else {
let time_32: u32;
let time_option = TryInto::<u32>::try_into(time.as_millis());
if let Ok(time) = time_option {
time_32 = time;
} else {
time_32 = u32::MAX;
}
// SAFE: pass by value into OS
unsafe { freertos_task_delay(time_32) };
}
}
pub fn suspend(&self) {
if let Some(handle) = self.freertos_handle {
//SAFE: handle is only set if returned by OS for a valid task
unsafe {
freertos_task_suspend(handle);
};
} else {
panic!("suspend called on non running thread");
}
}
pub fn delete(&self) -> ! {
if let Some(handle) = self.freertos_handle {
//SAFE: handle is only set if returned by OS for a valid task
unsafe {
freertos_task_delete(handle);
};
} else {
panic!("delete called on non running thread");
}
}
pub fn stack_watermark(&self) -> u32 {
// freertos_task_stack_watermark must not be called when no task
// is running. If that is the case, freertos_handle will be None
if self.freertos_handle == None {
0
} else {
unsafe { freertos_task_stack_watermark() }
}
}
}
impl Clone for ThreadHandle {
fn clone(&self) -> Self {
Self {
name: self.name,
freertos_handle: self.freertos_handle,
}
}
}
impl Copy for ThreadHandle {}
pub fn current() -> ThreadHandle {
// SAFE: Is a getter that will return either a valid pointer or NULL
let freertos_handle = unsafe { freertos_task_current() };
if freertos_handle == 0 as *const core::ffi::c_void {
return ThreadHandle {
name: "No Thread Running",
freertos_handle: None,
};
}
// freertos_handle is valid, create a default return value for error paths
// This will be returned if checks of the data stored in thread local storage
// fail.
// Fields are set to sane defaults
let c_handle = ThreadHandle {
name: "C Thread",
freertos_handle: Some(freertos_handle),
};
// SAFE: this variable is set in StaticThread::spawn to a &'static reference or to NULL in C.
// Only way this is unsafe, is if C spawned a thread and did not nullpointer it.
// We will check the redundant handle information to catch this case
let pointer = unsafe { freertos_task_storage_get(freertos_handle) };
// Nullpointer, C created this task and behaved well
if pointer == 0 as *const core::ffi::c_void {
return c_handle;
}
let pointer = pointer as *const ThreadHandle;
// pointer is not aligned, someone messed up
if !pointer.is_aligned() {
return c_handle;
}
// SAFE: is not a nullpointer and aligned, so we may dereference
// Two options:
// 1) created by StaticThread::spawn and valid
// 2) created by C and not nullpointered: Check the stored freertos_handle and
// compare it to the handle used to get the pointer. Call it a ~32bit checksum
// and trust it.
let handle = unsafe { *(pointer) };
// Stored handle will always have freertos_handle set
if let Some(freertos_handle_from_thread) = handle.freertos_handle {
// verify freertos_handle
if freertos_handle_from_thread == freertos_handle {
return handle;
}
}
return c_handle;
}
pub struct StaticThreadInner<const STACKSIZE: usize> {
stack: StaticReadOnceLock<[core::ffi::c_char; STACKSIZE]>,
thread_data: StaticReadOnceLock<[core::ffi::c_char; ffi::Sizes::TASK_DATA_SIZE]>,
handle: StaticReadOnceLock<ThreadHandle>,
priority: u32,
name: &'static str,
pub closure_data: Option<*mut core::ffi::c_void>,
}
pub struct StaticThread<const STACKSIZE: usize> {
pub inner: StaticThreadInner<STACKSIZE>,
}
pub struct StaticThreadPeriodic<const STACKSIZE: usize> {
pub inner: StaticThreadInner<STACKSIZE>,
period: core::time::Duration,
}
impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
// priority may be u32::MAX to select highest priority
pub const fn new(name: &'static str, priority: u32) -> Self {
Self {
stack: StaticReadOnceLock::new([0; STACKSIZE]),
thread_data: StaticReadOnceLock::new([0; ffi::Sizes::TASK_DATA_SIZE]),
handle: StaticReadOnceLock::new(ThreadHandle {
name: name,
freertos_handle: None,
}),
priority,
name,
closure_data: None,
}
}
// This could partially be moved into impl StaticInit
// But we know the actual type of the closure only at start(), not before,
// so we can not safely determine how much space we need to reserve before
// the stack. That means we can not call freertos_create_task_static() there.
// So we just omit StaticInit and do it here
fn spawn<F: Sized + 'static>(
&'static mut self,
thread_function: F,
thread_parameter: *const core::ffi::c_void,
callback: unsafe extern "C" fn(*mut core::ffi::c_void) -> !,
) -> ThreadHandle {
//SAFE: freertos_task_priority_max() is a getter for a compile time const
if (self.priority != u32::MAX) && (self.priority > unsafe{super::ffi::freertos_task_priority_max()}) {
panic!("StaticThread(\"{}\")::spawn: priority {} is larger than maximum ({})", self.name, self.priority, unsafe{super::ffi::freertos_task_priority_max()});
}
let stack = self.stack.take_no_init().unwrap();
let closure_space = core::mem::size_of::<F>();
// Will be checked later on, but this way we get a more precise output instead of a generic panic
if stack.len() < closure_space {
panic!("StaticThread(\"{}\")::spawn: Stack is too small to store closure. Increase stack size!", self.name);
}
// we need to store the closure in the context of the new thread
// but we do not know its type (is decided when using this function, not
// when creating the Thread struct), so we can not use a member of Thread.
// The only place left is the new thread's stack which will be owned by the
// new thread.
// Stack is behind a &'static reference, so the memory is a valid target to write
// data to.
// SAFE: The closure is 'static so we are safe to move it into the new thread. The stack is a &'static pointer
// and the cast checks its size
unsafe {
core::ptr::write_unaligned(
stack[..closure_space].as_mut_ptr() as *mut F,
thread_function,
)
};
// remember the location of the closure for later (we can not use self.stack,
// as it is behind a StaticReadOnceCell so it can safely be passed to the OS)
self.closure_data = Some(stack as *mut _ as *mut core::ffi::c_void);
// move the beginning of the stack which will be used by the OS
let stack = &mut stack[closure_space..];
// Will be checked later on, but this way we get a more precise output instead of a generic panic
if stack.len() < super::ffi::Sizes::TASK_MINIMAL_STACK_SIZE {
panic!(
"StaticThread(\"{}\")::spawn: Stack size after storing closure is {}, which is too small. Minimal size is {}.",
self.name, stack.len(),
super::ffi::Sizes::TASK_MINIMAL_STACK_SIZE
);
}
let thread_data = self.thread_data.take_no_init().unwrap();
// SAFE: We only pass pointers derived from &'static references to the OS, so the pointers will
// be valid for the complete runtime. This includes values borrowed by the closure
let freertos_handle = unsafe {
super::ffi::freertos_create_task_static(
callback,
thread_parameter,
self.priority,
thread_data.as_mut_ptr(),
thread_data.len().try_into().unwrap(),
stack.as_mut_ptr(),
stack.len().try_into().unwrap(),
)
};
if freertos_handle == 0 as *mut core::ffi::c_void {
panic!("could not create thread");
}
let handle = self.handle.take_no_init().unwrap();
handle.freertos_handle = Some(freertos_handle);
// Store the Handle in task local storage, so we can use the rust types later on
// Stored data will be used as non-mut shared &'static
//SAFE: handle is &'static behind a StaticReadOnceCell, can not be used in rust after this call
unsafe {
freertos_task_storage_set(
freertos_handle,
handle as *const _ as *const core::ffi::c_void,
)
};
// this creates a copy of handle, as it impls Copy
*handle
}
}
impl<const STACKSIZE: usize> StaticThread<STACKSIZE> {
// priority may be u32::MAX to select highest priority
pub const fn new(name: &'static str, priority: u32) -> Self {
Self {
inner: StaticThreadInner::new(name,priority),
}
}
unsafe extern "C" fn callback<F: FnMut() -> ! + 'static>(
thread_object: *mut core::ffi::c_void,
) -> ! {
let thread: &mut Self;
unsafe {
let pointer = thread_object as *mut Self;
thread = &mut *pointer;
}
if let Some(closure_data) = thread.inner.closure_data {
// Documentation of ptr::read_unaligned()says:
// * src must be valid for reads.
// -> it is memory which came from a &'static reference
// *src must point to a properly initialized value of type T.
// -> we only set the Option after using ptr::write_unaligned()
let mut closure = (closure_data as *mut F).read_unaligned();
closure();
} else {
panic!("StaticThread::callback(): No closure set");
}
}
pub fn spawn<F: FnMut() -> ! + 'static>(&'static mut self, thread_function: F) -> ThreadHandle {
self.inner.spawn(thread_function, self as *const _ as *const core::ffi::c_void, Self::callback::<F>)
}
}
impl<const STACKSIZE: usize> StaticThreadPeriodic<STACKSIZE> {
// priority may be u32::MAX to select highest priority
pub const fn new(name: &'static str, priority: u32, period: core::time::Duration) -> Self {
Self {
inner: StaticThreadInner::new(name, priority),
period: period,
}
}
unsafe extern "C" fn callback<F: FnMut() -> () + 'static>(
thread_object: *mut core::ffi::c_void,
) -> ! {
let thread: &mut Self;
unsafe {
let pointer = thread_object as *mut Self;
thread = &mut *pointer;
}
if let Some(closure_data) = thread.inner.closure_data {
// Documentation of ptr::read_unaligned()says:
// * src must be valid for reads.
// -> it is memory which came from a &'static reference
// *src must point to a properly initialized value of type T.
// -> we only set the Option after using ptr::write_unaligned()
let mut closure = (closure_data as *mut F).read_unaligned();
let current = current();
loop {
closure();
// TODO precise period
current.delay(thread.period);
}
} else {
panic!("StaticThread::callback(): No closure set");
}
}
pub fn spawn<F: FnMut() -> () + 'static>(&'static mut self, thread_function: F) -> ThreadHandle {
self.inner.spawn(thread_function, self as *const _ as *const core::ffi::c_void, Self::callback::<F>)
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
pub struct Stdout {}
pub struct Stderr {}
use core::fmt::{Error, Write};
// TODO why or how does pub work (users from same crate need to use core::fmt::... )
pub use core::fmt::{Error, Write};
extern "C" {
pub fn write(fd: core::ffi::c_int, buffer: *const core::ffi::c_void, count: usize) -> core::ffi::c_int;
-172
View File
@@ -1,172 +0,0 @@
use core::slice;
use super::objectmanager::ObjectManager;
// TODO find a way to report uxTaskGetStackHighWaterMarks during runtime as TM?
#[no_mangle]
extern "C" fn task_entry(task_object: *mut core::ffi::c_void) {
let task: &mut dyn TaskIF;
unsafe {
let pointer = task_object as *mut PeriodicTask;
task = &mut *pointer;
}
task.run();
}
pub trait ExecutableObjectIF {
fn perform(&mut self);
}
pub trait TaskIF<'a> {
fn run(&mut self);
fn get_stack_size(&self) -> u32;
fn set_handle(&mut self, task_handle: *const core::ffi::c_void);
fn get_handle(&self) -> *const core::ffi::c_void;
fn get_objects(&'a self) -> &'a [&'a mut dyn crate::objectmanager::SystemObjectIF];
fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()>;
}
pub struct PeriodicTask<'a> {
pub stack_size: u32, //TODO generic type and safety
pub task_handle: *const core::ffi::c_void,
pub period: u32,
pub task_objects: &'a mut [&'a mut dyn crate::objectmanager::SystemObjectIF],
}
impl<'a> PeriodicTask<'a> {
pub fn new(
objects: &'a mut [&'a mut dyn crate::objectmanager::SystemObjectIF],
stack_size: u32,
period: u32,
) -> PeriodicTask<'a> {
let instance: PeriodicTask<'a> = Self {
stack_size: stack_size,
task_handle: 0 as *const core::ffi::c_void,
period: period,
task_objects: objects,
};
instance
}
}
impl<'a> TaskIF<'a> for PeriodicTask<'a> {
fn run(&mut self) {
loop {
for object in self.task_objects.iter_mut() {
object.perform();
}
//TODO make this exact
unsafe {
crate::fsrc::osal::task_delay(self.period); //TODO type of delay should be generic but safe (cap to max in C)
}
}
}
fn get_stack_size(&self) -> u32 {
self.stack_size
}
fn set_handle(&mut self, task_handle: *const core::ffi::c_void) {
self.task_handle = task_handle;
}
fn get_handle(&self) -> *const core::ffi::c_void {
self.task_handle
}
fn get_objects(&'a self) -> &'a [&'a mut dyn crate::objectmanager::SystemObjectIF] {
self.task_objects
}
fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()> {
for object in self.task_objects.iter_mut() {
let result = object.initialize(object_manager);
match result {
Ok(()) => continue,
Err(()) => return Err(()),
}
}
Ok(())
}
}
pub struct TaskExecutor<'a> {
pub tasks: &'a mut [&'a mut dyn TaskIF<'a>],
}
impl<'a> TaskExecutor<'a> {
/// Initializes all tasks (and the objects contained within them)
/// and starts them.
///
/// It also enables global threading, allowing threading APIs to be used (queues, mutexes etc)
/// See TODO about threading
///
///# Arguments
///
/// * `delete_init_task` - If true, will delete the init task (the task this functions is called in) which means the function will not return
///
pub fn init_and_run(&mut self, delete_init_task: bool) {
let object_manager = TaskObjectManager {
tasks: unsafe { slice::from_raw_parts(self.tasks.as_ptr(), self.tasks.len()) },
};
// init uses unsafe methods and checks against the lock, so we need to enable it here
crate::fsrc::osal::enable_global_threading();
for task in self.tasks.iter_mut() {
let _ = task.initialize(&object_manager).unwrap();
}
drop(object_manager);
for task in self.tasks.iter_mut() {
// we give away a raw pointer, to be called by an OS task
// while this is generally very broken, we use a reference tied
// to our own lifetime and destroy the task when we get dropped.
// this way, the reference is guaranteed to be valid over our
// lifetime while the task is deleted at the end of our lifetime
let task_pointer: *const core::ffi::c_void =
*task as *mut _ as *const core::ffi::c_void; //TODO this does work without the "*" in front of the task -> Why??
let handle;
unsafe {
handle = crate::fsrc::osal::create_task(
task_entry,
task_pointer,
u32::try_from(task.get_stack_size()).unwrap(),
);
}
if handle == 0 as *mut core::ffi::c_void {
panic!("could not create Task");
} else {
task.set_handle(handle);
}
}
if delete_init_task {
crate::osal::task_delete_self();
}
}
}
struct TaskObjectManager<'a> {
tasks: &'a [&'a mut dyn TaskIF<'a>],
}
impl<'a> crate::objectmanager::ObjectManager<'a> for TaskObjectManager<'a> {
fn get_object(
&self,
id: crate::objectmanager::ObjectId,
) -> Result<&'a dyn crate::objectmanager::SystemObjectIF, ()> {
for task in self.tasks.iter() {
for object in task.get_objects().iter() {
if object.get_id() == id {
return Ok(*object);
}
}
}
Err(())
}
}
impl<'a> Drop for TaskExecutor<'a> {
fn drop(&mut self) {
crate::fsrc::osal::disable_global_threading();
for task in self.tasks.iter_mut() {
unsafe {
// TODO print uxTaskGetStackHighWaterMark() for each stack
crate::fsrc::osal::delete_task(task.get_handle());
}
}
}
}
+177 -206
View File
@@ -1,242 +1,213 @@
#![no_std]
#![feature(never_type)]
//TODO os errors in API calls
//TODO look into a pattern for late initialized stuff, currently using Option (can we make it compile time safe?)
pub mod fsrc;
mod panic;
use core::time::Duration;
use core::fmt::Write;
use core::panic::PanicInfo;
use fsrc::objectmanager::SystemObjectIF;
use fsrc::*;
use osal::{
sync::{Mutex, MutexClone, StaticInit, StaticReadOnceLock},
queue::{MessageQueue, MessageQueueSender},
thread,
};
extern "C" {
fn done();
}
#[panic_handler]
fn panic(panic: &PanicInfo<'_>) -> ! {
// unsafe { this breaks in ISR
// osal::stop_it();
// }
// TODO: Make this unicode-safe
_ = writeln!(crate::fsrc::sif::Stderr {},"");
_ = write!(crate::fsrc::sif::Stderr {},"in task \"");
unsafe {
//TODO this breaks all the time...
let task_name = core::ffi::CStr::from_ptr(osal::get_task_name());
let task_name_utf8 = core::str::from_utf8(task_name.to_bytes());
match task_name_utf8 {
Ok(string) => {
sif!("{}", string);
}
Err(_) => {
_ = writeln!(crate::fsrc::sif::Stderr {},"Schei Encoding");
}
}
}
_ = writeln!(crate::fsrc::sif::Stderr {},"\":");
_ = writeln!(crate::fsrc::sif::Stderr {},"{}", panic);
//TODO: stop RTOS, exit if hosted
unsafe { done() };
loop {}
}
#[no_mangle]
extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi::c_ulong) {
let file_name = unsafe {
//TODO is from_ptr safe enough?
let file_name = core::ffi::CStr::from_ptr(ptr);
let file_name_utf8 = core::str::from_utf8(file_name.to_bytes());
match file_name_utf8 {
Ok(string) => {
string
}
Err(_) => {
"Schei Encoding"
}
}
};
panic!("assertion failed at {file_name}:{line}");
}
#[no_mangle]
extern "C" fn rust_alloc_failed(){
panic!("allocation failed!");
}
static THREAD_INIT: StaticReadOnceLock<
thread::StaticThread<{ thread::Sizes::MINIMAL_STACK_SIZE + 2024 }>,
> = StaticReadOnceLock::new(thread::StaticThread::new("Init", u32::MAX));
#[no_mangle]
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 🚀");
mission();
sifln!("Mission done");
THREAD_INIT.take_no_init().unwrap().spawn(init_task);
// delete self, init task will take over
thread::current().delete();
}
struct Test {
a: u8,
block: Duration,
mutex: Mutex<u8>,
}
struct Test2 {
a: u8,
period: Duration,
}
impl Test {
fn run(&mut self) {
sifln!("Test: {} taking mutex", self.a);
let mut guard = self.mutex.lock();
let data: u8 = *guard;
sifln!("Test: {} took mutex, was {}", self.a, data);
osal::thread::current().delay(self.block);
*guard = self.a;
sifln!("Test: {} giving mutex, is now {}", self.a, *guard);
drop(guard);
sifln!("Test: {} gave mutex", self.a);
}
}
impl Test2 {
pub fn run(&mut self, mutex: MutexClone<u8>, block: Duration) -> ! {
sifln!(
"Stack watermark {}",
osal::thread::current().stack_watermark()
);
loop {
sifln!("Test2: {} taking mutex", self.a);
let mut guard = mutex.lock();
let data: u8 = *guard;
sifln!("{} took mutex, was {}", self.a, data);
sifln!(
"Test2: Stack watermark {}",
osal::thread::current().stack_watermark()
);
osal::thread::current().delay(block);
*guard = self.a;
sifln!("Test2: {} giving mutex, is now {}", self.a, *guard);
drop(guard);
sifln!("Test2: {} gave mutex", self.a);
osal::thread::current().delay(self.period);
}
}
}
impl StaticInit for Test {
fn static_init(&'static mut self) {
self.mutex.static_init();
}
}
impl StaticInit for Test2 {
fn static_init(&'static mut self) {}
}
static TEST1: StaticReadOnceLock<Test> = StaticReadOnceLock::new(Test {
a: 1,
block: Duration::from_millis(1000),
mutex: Mutex::new(13),
});
static THREAD_1: StaticReadOnceLock<
thread::StaticThreadPeriodic<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
> = StaticReadOnceLock::new(thread::StaticThreadPeriodic::new(
"Thread 1",
2,
Duration::from_millis(1000),
));
static TEST2: StaticReadOnceLock<Test2> = StaticReadOnceLock::new(Test2 {
a: 2,
period: Duration::from_millis(5000),
});
static THREAD_2: StaticReadOnceLock<
thread::StaticThread<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
> = StaticReadOnceLock::new(thread::StaticThread::new("Thread 2", 2));
#[derive(Copy, Clone, Default)]
struct HandlerData {
x: u32,
y: f32,
pub enum Message {
OK,
#[default]
NONE,
DATA(u8),
}
struct Handler {
id: objectmanager::ObjectId,
command_queue: queues::MessageQueue<10>,
data: datasets::OwnedDataset<HandlerData>,
struct Receiver {
queue: MessageQueue<Message, 100>,
}
struct HandlerSender {
id: objectmanager::ObjectId,
other_handler: objectmanager::ObjectId,
cycle: u8,
other_handler_queue: queues::MessageQueueSender,
other_data: datasets::ReferencedDataset<HandlerData>,
}
impl Handler {
fn handle_message(&self, message: queues::Message) {
match message {
queues::Message::OK => {
sifln!("OK");
}
queues::Message::FAILED => {
sifln!("FAILED");
}
queues::Message::DATA(data) => {
sifln!("p1: {}, p2 {}", data.p1, data.p2);
}
}
}
}
impl tasks::ExecutableObjectIF for Handler {
fn perform(&mut self) {
sifln!("Handler {} performs", self.id);
let result = self.command_queue.receive();
match result {
Some(message) => self.handle_message(message),
None => {
sifln!("Handler {} got nothing", self.id);
}
}
}
}
impl tasks::ExecutableObjectIF for HandlerSender {
fn perform(&mut self) {
sifln!("HandlerSender {} performs step {}", self.id, self.cycle);
match self.cycle {
0 => {
let _ = self.other_handler_queue.send(queues::Message::OK);
}
1 => {
let _ = self.other_handler_queue.send(queues::Message::FAILED);
}
2 => {
let _ = self.other_handler_queue.send(queues::Message::DATA(
queues::GenericMessageData { p1: 13, p2: 2 },
));
}
_ => (),
}
self.cycle += 1;
}
}
impl SystemObjectIF for Handler {
fn get_command_queue(&self) -> crate::fsrc::queues::MessageQueueSender {
self.command_queue.get_sender()
}
fn get_id(&self) -> objectmanager::ObjectId {
self.id
}
fn initialize(&mut self, _object_manager: &dyn objectmanager::ObjectManager) -> Result<(), ()> {
Ok(())
}
}
impl introspection::Introspection for HandlerSender {
fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()) {}
fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>) {}
}
impl introspection::Introspection for Handler {
fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()) {
(*f)(&self.command_queue, "command_queue");
(*f)(&self.data, "data");
}
fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>) {
(*f)(&self.command_queue, "command_queue");
(*f)(&self.data, "data");
}
}
impl SystemObjectIF for HandlerSender {
fn get_command_queue(&self) -> crate::fsrc::queues::MessageQueueSender {
queues::MessageQueueSender::new() //TODO
}
fn get_id(&self) -> objectmanager::ObjectId {
self.id
}
fn initialize(&mut self, object_manager: &dyn objectmanager::ObjectManager) -> Result<(), ()> {
let other_handler_maybe = object_manager.get_object(self.other_handler);
let other_handler = match other_handler_maybe {
Ok(other) => other,
Err(_) => return Err(()),
impl Receiver {
fn run(&self) {
let message = self.queue.receive();
let message = if let Ok(message) = message {
message
} else {
sifln!("receiver: got nothing");
return;
};
self.other_handler_queue = other_handler.get_command_queue();
self.other_data
.initialize(object_manager, self.other_handler)?;
Ok(())
match message {
Message::OK => {
sifln!("receiver: ok");
}
Message::NONE => {
sifln!("receiver: NONE");
}
Message::DATA(data) => {
sifln!("receiver: {}", data);
}
}
}
}
fn mission() {
impl StaticInit for Receiver {
fn static_init(&'static mut self) {
self.queue.static_init();
}
}
struct Sender {
count: u8,
}
impl Sender {
fn run(&mut self, queue: MessageQueueSender<Message>) {
let _ = queue.send(Message::DATA(self.count));
self.count += 1;
}
}
static SENDER: StaticReadOnceLock<Sender> = StaticReadOnceLock::new(Sender { count: 0 });
static RECEIVER: StaticReadOnceLock<Receiver> = StaticReadOnceLock::new(Receiver {
queue: MessageQueue::new(),
});
static THREAD_3: StaticReadOnceLock<
thread::StaticThreadPeriodic<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
> = StaticReadOnceLock::new(thread::StaticThreadPeriodic::new(
"Thread 3",
2,
Duration::from_millis(200),
));
fn init_task() -> ! {
sifln!("Mission enter");
let mut h1 = Handler {
id: 1,
command_queue: queues::MessageQueue::new(),
data: datasets::OwnedDataset::new(),
};
let mut h2 = HandlerSender {
id: 2,
other_handler: 3,
cycle: 0,
other_handler_queue: queues::MessageQueueSender::new(),
other_data: datasets::ReferencedDataset::new(),
};
let array: &mut [&mut dyn objectmanager::SystemObjectIF] = &mut [&mut h1];
let test1 = TEST1.take().unwrap();
let test2 = TEST2.take().unwrap();
let receiver = RECEIVER.take().unwrap();
let sender_handle = receiver.queue.sender();
let clone = sender_handle.clone();
let sender = SENDER.take_no_init().unwrap();
let mut t1 = tasks::PeriodicTask::new(array, 512, 200);
let mutex_copy = test1.mutex.clone();
let a = Duration::from_millis(10);
let mut t2: tasks::PeriodicTask = tasks::PeriodicTask {
task_objects: &mut [&mut h2],
stack_size: 512,
period: 400,
task_handle: 0 as *const core::ffi::c_void,
};
THREAD_2.take_no_init().unwrap().spawn(move || {
test2.run(mutex_copy, a);
});
let mut task_executor = tasks::TaskExecutor {
tasks: &mut [&mut t1, &mut t2],
};
task_executor.init_and_run(false);
sifln!("Mission delay");
unsafe {
osal::task_delay(2000);
}
sifln!("executor dropped");
drop(task_executor);
unsafe {
osal::task_delay(2000);
}
sifln!("Mission delay done");
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();
}
+43
View File
@@ -0,0 +1,43 @@
use core::panic::PanicInfo;
use core::fmt::Write;
extern "C" {
fn done();
}
#[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() };
loop {}
}
#[no_mangle]
extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi::c_ulong) {
let file_name = unsafe {
//TODO is from_ptr safe enough?
let file_name = core::ffi::CStr::from_ptr(ptr);
let file_name_utf8 = core::str::from_utf8(file_name.to_bytes());
match file_name_utf8 {
Ok(string) => string,
Err(_) => "Schei Encoding",
}
};
panic!("assertion failed at {file_name}:{line}");
}
#[no_mangle]
extern "C" fn rust_alloc_failed() {
panic!("allocation failed!");
}