forked from ROMEO/obsw
Merge branch 'mohr/rust' into nehlich/rust-readme
This commit is contained in:
commit
b145f7493b
@ -67,7 +67,7 @@ It will exit after the Zynq is configured and ready to firmware.
|
||||
|
||||
Then load the actual obsw, in the build (`build_cli` in the example above) folder run:
|
||||
```sh
|
||||
arm-none-eabi-gdb romeo-obws
|
||||
arm-none-eabi-gdb romeo-obsw
|
||||
>target extended-remote localhost:3333
|
||||
>load
|
||||
>cont
|
||||
|
@ -43,7 +43,7 @@ typedef s32 (*func_ptr)(int c);
|
||||
void xil_printf( const char8 *ctrl1, ...);
|
||||
void xil_vprintf(const char8 *ctrl1, va_list argp);
|
||||
void print( const char8 *ptr);
|
||||
extern void outbyte (char c);
|
||||
extern void outbyte (uint8_t c);
|
||||
extern char inbyte(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -8,12 +8,12 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void outbyte(char c);
|
||||
void outbyte(uint8_t c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
void outbyte(char c) {
|
||||
void outbyte(uint8_t c) {
|
||||
XUartPs_SendByte(STDOUT_BASEADDRESS, c);
|
||||
}
|
||||
|
@ -4,13 +4,37 @@
|
||||
|
||||
// TODO namespace the names
|
||||
|
||||
SemaphoreHandle_t * global_threading_semaphore = NULL;
|
||||
|
||||
uint8_t global_threading_available_c() {
|
||||
if (global_threading_semaphore == NULL) {
|
||||
|
||||
global_threading_semaphore = xSemaphoreCreateBinary();
|
||||
//xSemaphoreGive(global_threading_semaphore);
|
||||
}
|
||||
if (uxSemaphoreGetCount(global_threading_semaphore) == 1) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void enable_global_threading_c() {
|
||||
xSemaphoreGive(global_threading_semaphore);
|
||||
}
|
||||
|
||||
void disable_global_threading_c() {
|
||||
xSemaphoreTake(global_threading_semaphore, portMAX_DELAY);
|
||||
}
|
||||
|
||||
const char *get_task_name() { return pcTaskGetName(NULL); }
|
||||
|
||||
void stop_it() { taskENTER_CRITICAL(); }
|
||||
|
||||
// TODO return some error code?
|
||||
void *create_task(TaskFunction_t taskFunction, void *parameter,
|
||||
size_t stack_size) {
|
||||
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);
|
||||
@ -22,6 +46,7 @@ void *create_task(TaskFunction_t taskFunction, void *parameter,
|
||||
}
|
||||
|
||||
void task_delay(uint32_t milliseconds) {
|
||||
//TODO verify uint32_t vs TickType_t
|
||||
vTaskDelay(pdMS_TO_TICKS(milliseconds));
|
||||
}
|
||||
|
||||
@ -29,7 +54,8 @@ 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)
|
||||
}
|
||||
|
||||
void *create_queue(size_t length, size_t element_size) {
|
||||
void *create_queue(uint32_t length, uint32_t element_size) {
|
||||
//TODO verify uint32_t vs UBaseType_t
|
||||
QueueHandle_t newQueue = xQueueCreate(length, element_size);
|
||||
return newQueue;
|
||||
}
|
||||
@ -48,4 +74,28 @@ uint8_t queue_send(void *queue, void *message) {
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (xSemaphoreTakeRecursive(handle, portMAX_DELAY) == pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (xSemaphoreGiveRecursive(handle) == pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
#TODO can we get CMake to configure cmake --build --clean to run cargo clean?
|
||||
|
||||
add_custom_target(
|
||||
mission_rust_internal
|
||||
COMMAND cargo build $<$<CONFIG:Release>:--release>
|
||||
|
9
mission_rust/Cargo.lock
generated
9
mission_rust/Cargo.lock
generated
@ -2,15 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "cty"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
||||
|
||||
[[package]]
|
||||
name = "mission_rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cty",
|
||||
]
|
||||
|
@ -9,4 +9,3 @@ crate-type = ["staticlib"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cty = "0.2.2"
|
||||
|
64
mission_rust/doc/Static_Allocation.md
Normal file
64
mission_rust/doc/Static_Allocation.md
Normal file
@ -0,0 +1,64 @@
|
||||
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 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. 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 is 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 as well, so at that time refereces are already fixed to their threading-time value.
|
||||
|
||||
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 thared 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 references, ie ones protected by RTOS primitives, to be obtained.
|
||||
|
||||
Those references are either protected by a mutex or implemented using queues, which are the two primitives used to implement threadsafety. 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.
|
||||
|
||||
## 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 an 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)
|
129
mission_rust/src/fsrc/datasets/mod.rs
Normal file
129
mission_rust/src/fsrc/datasets/mod.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use super::{mutex, objectmanager};
|
||||
use super::objectmanager::SystemObjectIF;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct TypeId {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
// inspired by https://github.com/jswrenn/deflect/
|
||||
pub trait HasTypeId {
|
||||
#[inline(never)]
|
||||
fn get_type_id(&self) -> TypeId {
|
||||
TypeId {
|
||||
id: Self::get_type_id as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatapoolOwnerIF {
|
||||
fn get_set(&self, _type_id: TypeId) -> Option<&dyn DataSetIF> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DataSetIF {
|
||||
fn get_type_id(&self) -> TypeId;
|
||||
fn get_actual_data(&self) -> &dyn HasTypeId;
|
||||
fn get_mutex(&self) -> mutex::RawMutex;
|
||||
}
|
||||
|
||||
pub struct OwnedDataset<T: HasTypeId + Copy> {
|
||||
actual_data: T,
|
||||
mutex: mutex::RawMutex,
|
||||
}
|
||||
|
||||
pub struct ReferencedDataset<T: HasTypeId + Copy> {
|
||||
//we use a pointer here to avoid lifetimes
|
||||
actual_data: Option<*const T>,
|
||||
mutex: Option<mutex::RawMutex>,
|
||||
}
|
||||
|
||||
impl<T: HasTypeId + Copy + Default> DataSetIF for OwnedDataset<T> {
|
||||
fn get_type_id(&self) -> TypeId {
|
||||
self.actual_data.get_type_id()
|
||||
}
|
||||
|
||||
fn get_actual_data(&self) -> &dyn HasTypeId {
|
||||
&self.actual_data
|
||||
}
|
||||
|
||||
fn get_mutex(&self) -> mutex::RawMutex {
|
||||
self.initialize();
|
||||
return self.mutex;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasTypeId + Copy + Default> OwnedDataset<T> {
|
||||
pub fn new() -> OwnedDataset<T> {
|
||||
OwnedDataset::<T> {
|
||||
actual_data: T::default(),
|
||||
mutex: mutex::RawMutex::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Result<T, ()> {
|
||||
let _mutex_guard = match self.mutex.take() {
|
||||
Err(()) => return Err(()),
|
||||
Ok(guard) => guard,
|
||||
};
|
||||
Ok(self.actual_data)
|
||||
}
|
||||
|
||||
//TODO do we want to know if it fails?
|
||||
pub fn commit(&mut self, data: T) {
|
||||
let _mutex_guard = match self.mutex.take() {
|
||||
Err(()) => return,
|
||||
Ok(guard) => guard,
|
||||
};
|
||||
self.actual_data = data;
|
||||
}
|
||||
|
||||
fn initialize(&mut self) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasTypeId + Copy + Default> ReferencedDataset<T> {
|
||||
pub fn new() -> ReferencedDataset<T> {
|
||||
ReferencedDataset::<T> {
|
||||
actual_data: None,
|
||||
mutex: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Result<T, ()> {
|
||||
let _mutex_guard = match self.mutex {
|
||||
None => return Err(()),
|
||||
Some(mutex) => match mutex.take() {
|
||||
Err(()) => return Err(()),
|
||||
Ok(guard) => guard,
|
||||
},
|
||||
};
|
||||
match self.actual_data {
|
||||
None => Err(()),
|
||||
Some(data) => Ok(unsafe { *data }),
|
||||
}
|
||||
}
|
||||
|
||||
//Note, passing the object_manager is per design, so that this call can only be used during init (when storing the address is valid)
|
||||
pub fn initialize(&mut self, object_manager: &dyn objectmanager::ObjectManager, owner_of_the_set: objectmanager::ObjectId) -> Result<(), ()> {
|
||||
let owner = object_manager.get_object(owner_of_the_set)?;
|
||||
let temp: T = T::default(); //TODO find nicer solution whithout local instance and trait bound to Default
|
||||
let type_id = temp.get_type_id();
|
||||
let other_set: &dyn DataSetIF;
|
||||
match owner.get_set(type_id) {
|
||||
None => {
|
||||
return Err(());
|
||||
}
|
||||
Some(set) => {
|
||||
other_set = set;
|
||||
}
|
||||
}
|
||||
//pointer cast is safe because we checked the type_id
|
||||
//getting pointer to avoid lifetime check
|
||||
self.actual_data = Some(other_set.get_actual_data() as *const dyn HasTypeId as *const T);
|
||||
self.mutex = Some(other_set.get_mutex());
|
||||
Ok(())
|
||||
}
|
||||
}
|
9
mission_rust/src/fsrc/mod.rs
Normal file
9
mission_rust/src/fsrc/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//TODO control visibility of internal structs
|
||||
|
||||
pub mod sif;
|
||||
pub mod queues;
|
||||
pub mod osal;
|
||||
pub mod tasks;
|
||||
pub mod objectmanager;
|
||||
pub mod datasets;
|
||||
mod mutex;
|
68
mission_rust/src/fsrc/mutex.rs
Normal file
68
mission_rust/src/fsrc/mutex.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use super::osal;
|
||||
|
||||
pub struct RawMutex {
|
||||
handle: Option<*const core::ffi::c_void>
|
||||
}
|
||||
|
||||
pub struct RawMutexGuard {
|
||||
handle: *const core::ffi::c_void,
|
||||
}
|
||||
|
||||
impl Drop for RawMutexGuard {
|
||||
fn drop(&mut self) {
|
||||
//TODO do we need some check here?
|
||||
unsafe{osal::give_mutex(self.handle)};
|
||||
}
|
||||
}
|
||||
|
||||
//TODO for non allocating, keep handle as option, to be set either in an initialize() call
|
||||
// or maybe lazy later on, TBD if lazy is needed or we can guarantee init to be called
|
||||
impl RawMutex {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handle: None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Self {
|
||||
handle: self.handle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&self) -> Result<RawMutexGuard,()> {
|
||||
osal::check_global_threading_available();
|
||||
let handle = match self.handle {
|
||||
None => return Err(()), //TODO plan was that this failes silently -> implement an empty mutex guard?
|
||||
Some(handle) => handle
|
||||
};
|
||||
match unsafe {osal::take_mutex(handle)} {
|
||||
1 => Ok(RawMutexGuard { handle: handle }),
|
||||
_ => Err(()) //TODO error code
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn initialize(&mut self) {
|
||||
let handle = unsafe {osal::create_mutex()};
|
||||
if handle == 0 as *const core::ffi::c_void {
|
||||
panic!("Could not create mutex")
|
||||
}
|
||||
self.handle = Some(handle);
|
||||
}
|
||||
}
|
12
mission_rust/src/fsrc/objectmanager.rs
Normal file
12
mission_rust/src/fsrc/objectmanager.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use super::*;
|
||||
|
||||
pub type ObjectId = usize;
|
||||
pub trait ObjectManager<'a> {
|
||||
fn get_object(&self, id: ObjectId) -> Result<&'a dyn SystemObjectIF, ()>;
|
||||
}
|
||||
|
||||
pub trait SystemObjectIF: tasks::ExecutableObjectIF + datasets::DatapoolOwnerIF {
|
||||
fn get_id(&self) -> ObjectId;
|
||||
fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()>;
|
||||
fn get_command_queue(&self) -> queues::MessageQueueSender;
|
||||
}
|
56
mission_rust/src/fsrc/osal/mod.rs
Normal file
56
mission_rust/src/fsrc/osal/mod.rs
Normal file
@ -0,0 +1,56 @@
|
||||
type TaskFunction = unsafe extern "C" fn(*mut core::ffi::c_void);
|
||||
|
||||
//TODO verify uXX == uintXX_t
|
||||
//TODO safe API
|
||||
|
||||
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 global_threading_available() -> bool {
|
||||
unsafe{global_threading_available_c() == 1}
|
||||
}
|
||||
|
||||
pub fn check_global_threading_available() {
|
||||
if !global_threading_available() {
|
||||
panic!("using queue outside of threading environment")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_global_threading(){
|
||||
unsafe{enable_global_threading_c()};
|
||||
}
|
||||
|
||||
pub fn disable_global_threading(){
|
||||
unsafe{disable_global_threading_c()};
|
||||
}
|
102
mission_rust/src/fsrc/queues/mod.rs
Normal file
102
mission_rust/src/fsrc/queues/mod.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use crate::osal;
|
||||
|
||||
pub struct MessageQueue<const DEPTH: usize> {
|
||||
queue_data: [Message; DEPTH],
|
||||
queue_id: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
pub struct MessageQueueSender {
|
||||
queue_id: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
impl<const DEPTH: usize> MessageQueue<DEPTH> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue_data: [Message::default(); DEPTH],
|
||||
queue_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive(&self) -> Option<Message> {
|
||||
osal::check_global_threading_available();
|
||||
let actual_id = match self.queue_id {
|
||||
None => return None,
|
||||
Some(id) => id,
|
||||
};
|
||||
let mut message: Message = Message::default();
|
||||
let res: u8;
|
||||
unsafe {
|
||||
//message = core::mem::MaybeUninit::zeroed().assume_init(); // We only return it if the queue received something
|
||||
let message_pointer: *mut core::ffi::c_void =
|
||||
&mut message as *mut _ as *mut core::ffi::c_void;
|
||||
res = osal::queue_receive(actual_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
Some(message)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) {
|
||||
if self.queue_id != None {
|
||||
return;
|
||||
}
|
||||
let cast_depth: u32 = u32::try_from(DEPTH).unwrap(); //TODO check these
|
||||
let element_size: u32 = u32::try_from(core::mem::size_of::<Message>()).unwrap();
|
||||
let queue_id = unsafe { osal::create_queue(cast_depth, element_size) };
|
||||
|
||||
if queue_id == 0 as *mut core::ffi::c_void {
|
||||
panic!("could not create Queue");
|
||||
}
|
||||
self.queue_id = Some(queue_id);
|
||||
}
|
||||
|
||||
pub fn get_sender(&self) -> MessageQueueSender {
|
||||
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
|
||||
let queue_id = self.queue_id.expect("Queue uninitialized");
|
||||
MessageQueueSender {
|
||||
queue_id: Some(queue_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageQueueSender {
|
||||
pub fn new() -> Self {
|
||||
Self { queue_id: None }
|
||||
}
|
||||
|
||||
pub fn send(&self, message: Message) -> Result<(), ()> {
|
||||
osal::check_global_threading_available();
|
||||
let queue_id = match self.queue_id {
|
||||
None => return Err(()),
|
||||
Some(id) => id,
|
||||
};
|
||||
let res: u8;
|
||||
unsafe {
|
||||
let message_pointer: *const core::ffi::c_void =
|
||||
&message as *const _ as *const core::ffi::c_void;
|
||||
res = osal::queue_send(queue_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GenericMessageData {
|
||||
pub p1: u32,
|
||||
pub p2: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub enum Message {
|
||||
OK,
|
||||
#[default]
|
||||
FAILED,
|
||||
DATA(GenericMessageData),
|
||||
}
|
37
mission_rust/src/fsrc/sif.rs
Normal file
37
mission_rust/src/fsrc/sif.rs
Normal file
@ -0,0 +1,37 @@
|
||||
// TODO this is platform specific
|
||||
|
||||
pub struct Outbytes {}
|
||||
|
||||
use core::fmt::{Error, Write};
|
||||
|
||||
impl Write for Outbytes {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
||||
for c in s.as_bytes() {
|
||||
unsafe {
|
||||
crate::fsrc::osal::outbyte(*c);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sifln {
|
||||
($(,)?) => (
|
||||
let mut stdout = Outbytes {};
|
||||
writeln!(stdout);
|
||||
);
|
||||
($($arg:tt)*) => (
|
||||
let mut stdout = crate::fsrc::sif::Outbytes {};
|
||||
let _alwaysok = writeln!(stdout, $($arg)*);
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sif {
|
||||
($($arg:tt)*) => (
|
||||
let mut stdout = crate::fsrc::sif::Outbytes {};
|
||||
let _alwaysok = write!(stdout, $($arg)*);
|
||||
);
|
||||
}
|
||||
|
151
mission_rust/src/fsrc/tasks/mod.rs
Normal file
151
mission_rust/src/fsrc/tasks/mod.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use core::slice;
|
||||
|
||||
use super::objectmanager::ObjectManager;
|
||||
|
||||
#[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> {
|
||||
pub fn init_and_run(&mut self) {
|
||||
//TODO unlock global multitasking mutex
|
||||
let object_manager = TaskObjectManager {
|
||||
tasks: unsafe { slice::from_raw_parts(self.tasks.as_ptr(), self.tasks.len()) },
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
//TODO lock global multitasking mutex
|
||||
for task in self.tasks.iter_mut() {
|
||||
unsafe {
|
||||
crate::fsrc::osal::delete_task(task.get_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +1,47 @@
|
||||
#![no_std]
|
||||
|
||||
//TODO look into using core::ffi (some types do not seem to work)
|
||||
//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?)
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sifln {
|
||||
($(,)?) => (
|
||||
let mut stdout = Outbytes {};
|
||||
writeln!(stdout);
|
||||
);
|
||||
($($arg:tt)*) => (
|
||||
let mut stdout = Outbytes {};
|
||||
let _alwaysok = writeln!(stdout, $($arg)*);
|
||||
);
|
||||
}
|
||||
mod fsrc;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sif {
|
||||
($($arg:tt)*) => (
|
||||
let mut stdout = Outbytes {};
|
||||
let _alwaysok = write!(stdout, $($arg)*);
|
||||
);
|
||||
}
|
||||
|
||||
use core::mem::size_of;
|
||||
use core::fmt::Write;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
use fsrc::datasets::{DataSetIF, DatapoolOwnerIF};
|
||||
use fsrc::objectmanager::SystemObjectIF;
|
||||
use fsrc::*;
|
||||
|
||||
extern "C" {
|
||||
fn done();
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(panic: &PanicInfo<'_>) -> ! {
|
||||
unsafe {
|
||||
stop_it();
|
||||
osal::stop_it();
|
||||
}
|
||||
// TODO: Make this unicode-safe
|
||||
sif!("In Task \"");
|
||||
unsafe {
|
||||
let task_name = get_task_name();
|
||||
let mut offset = 0;
|
||||
while *task_name.offset(offset) != 0 {
|
||||
sif!("{}", *task_name.offset(offset) as char);
|
||||
offset = offset + 1;
|
||||
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(_) => {
|
||||
sif!("Schei<EFBFBD> Encoding");
|
||||
}
|
||||
}
|
||||
}
|
||||
sifln!("\":");
|
||||
sifln!("{}", panic);
|
||||
//TODO: stop RTOS, exit if hosted
|
||||
unsafe { done() };
|
||||
loop {}
|
||||
}
|
||||
|
||||
type TaskFunction = unsafe extern "C" fn(*mut cty::c_void);
|
||||
|
||||
extern "C" {
|
||||
fn outbyte(c: cty::c_char);
|
||||
//void *create_task(TaskFunction_t taskFunction, void *parameter, size_t stack_size)
|
||||
fn create_task(
|
||||
taskFunction: TaskFunction,
|
||||
parameter: *const cty::c_void,
|
||||
stack_size: cty::size_t,
|
||||
) -> *const cty::c_void;
|
||||
|
||||
fn get_task_name() -> *const core::ffi::c_uchar;
|
||||
|
||||
fn stop_it();
|
||||
|
||||
fn delete_task(handle: *const cty::c_void);
|
||||
|
||||
fn task_delay(milliseconds: cty::uint32_t);
|
||||
|
||||
//void *create_queue(size_t length, size_t element_size)
|
||||
fn create_queue(length: cty::size_t, element_size: cty::size_t) -> *const cty::c_void;
|
||||
|
||||
fn queue_receive(queue: *const cty::c_void, message: *const cty::c_void) -> cty::uint8_t;
|
||||
fn queue_send(queue: *const cty::c_void, message: *const cty::c_void) -> cty::uint8_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_main() {
|
||||
sifln!("Rust startup 🚀");
|
||||
@ -81,163 +49,72 @@ extern "C" fn rust_main() {
|
||||
sifln!("Mission done");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn task_entry(task_object: *mut cty::c_void) {
|
||||
let task: &mut dyn TaskIF;
|
||||
unsafe {
|
||||
let pointer = task_object as *mut PeriodicTask;
|
||||
task = &mut *pointer;
|
||||
}
|
||||
task.run();
|
||||
#[derive (Copy, Clone, Default)]
|
||||
struct HandlerData {
|
||||
x: u32,
|
||||
y: f32
|
||||
}
|
||||
|
||||
trait ExecutableObjectIF {
|
||||
fn perform(&mut self);
|
||||
}
|
||||
|
||||
trait TaskIF {
|
||||
fn run(&mut self);
|
||||
fn get_stack_size(&self) -> cty::size_t;
|
||||
fn set_handle(&mut self, task_handle: *const cty::c_void);
|
||||
fn get_handle(&self) -> *const cty::c_void;
|
||||
}
|
||||
|
||||
struct PeriodicTask<'a> {
|
||||
stack_size: cty::size_t, //TODO generic type and safety
|
||||
task_handle: *const cty::c_void,
|
||||
period: usize,
|
||||
task_objects: &'a mut [&'a mut dyn ExecutableObjectIF],
|
||||
}
|
||||
|
||||
impl<'a> PeriodicTask<'a> {
|
||||
fn new(
|
||||
objects: &'a mut [&'a mut dyn ExecutableObjectIF],
|
||||
stack_size: usize,
|
||||
period: usize,
|
||||
) -> PeriodicTask<'a> {
|
||||
let instance: PeriodicTask<'a> = Self {
|
||||
stack_size: stack_size,
|
||||
task_handle: 0 as *const cty::c_void,
|
||||
period: period,
|
||||
task_objects: objects,
|
||||
};
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TaskIF for PeriodicTask<'a> {
|
||||
fn run(&mut self) {
|
||||
loop {
|
||||
for object in self.task_objects.iter_mut() {
|
||||
object.perform();
|
||||
}
|
||||
//TODO make this exact
|
||||
unsafe {
|
||||
task_delay(self.period as cty::uint32_t); //TODO type of delay should be generic but safe (cap to max in C)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_stack_size(&self) -> cty::size_t {
|
||||
self.stack_size
|
||||
}
|
||||
fn set_handle(&mut self, task_handle: *const cty::c_void) {
|
||||
self.task_handle = task_handle;
|
||||
}
|
||||
fn get_handle(&self) -> *const cty::c_void {
|
||||
self.task_handle
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskExecutor<'a> {
|
||||
tasks: &'a mut [&'a mut dyn TaskIF],
|
||||
}
|
||||
|
||||
impl<'a> TaskExecutor<'a> {
|
||||
fn run_tasks(&mut self) {
|
||||
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 cty::c_void = *task as *mut _ as *const cty::c_void; //TODO this does work without the "*" in front of the task -> Why??
|
||||
let handle;
|
||||
unsafe {
|
||||
handle = create_task(task_entry, task_pointer, task.get_stack_size());
|
||||
}
|
||||
if handle == 0 as *mut cty::c_void {
|
||||
panic!("could not create Task");
|
||||
} else {
|
||||
task.set_handle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TaskExecutor<'a> {
|
||||
fn drop(&mut self) {
|
||||
for task in self.tasks.iter_mut() {
|
||||
unsafe {
|
||||
delete_task(task.get_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl datasets::HasTypeId for HandlerData {}
|
||||
|
||||
struct Handler {
|
||||
id: u32,
|
||||
command_queue: MessageQueue<Message>,
|
||||
id: objectmanager::ObjectId,
|
||||
command_queue: queues::MessageQueue<10>,
|
||||
data: datasets::OwnedDataset<HandlerData>
|
||||
}
|
||||
|
||||
struct HandlerSender {
|
||||
id: u32,
|
||||
id: objectmanager::ObjectId,
|
||||
other_handler: objectmanager::ObjectId,
|
||||
cycle: u8,
|
||||
other_handler: MessageQueueSender<Message>,
|
||||
other_handler_queue: queues::MessageQueueSender,
|
||||
other_data: datasets::ReferencedDataset<HandlerData>
|
||||
}
|
||||
|
||||
|
||||
impl Handler {
|
||||
fn handle_message(&self, message: Message) {
|
||||
fn handle_message(&self, message: queues::Message) {
|
||||
match message {
|
||||
Message::OK => {
|
||||
queues::Message::OK => {
|
||||
sifln!("OK");
|
||||
}
|
||||
Message::FAILED => {
|
||||
queues::Message::FAILED => {
|
||||
sifln!("FAILED");
|
||||
}
|
||||
Message::DATA(data) => {
|
||||
queues::Message::DATA(data) => {
|
||||
sifln!("p1: {}, p2 {}", data.p1, data.p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableObjectIF for Handler {
|
||||
impl tasks::ExecutableObjectIF for Handler {
|
||||
fn perform(&mut self) {
|
||||
sifln!("Handler {} performs", self.id);
|
||||
let result = self.command_queue.receive();
|
||||
match result {
|
||||
Ok(message) => self.handle_message(message),
|
||||
Err(_) => {
|
||||
Some(message) => self.handle_message(message),
|
||||
None => {
|
||||
sifln!("Handler {} got nothing", self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableObjectIF for HandlerSender {
|
||||
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.send(Message::OK);
|
||||
let _ = self.other_handler_queue.send(queues::Message::OK);
|
||||
}
|
||||
1 => {
|
||||
let _ = self.other_handler.send(Message::FAILED);
|
||||
let _ = self.other_handler_queue.send(queues::Message::FAILED);
|
||||
}
|
||||
2 => {
|
||||
let _ = self
|
||||
.other_handler
|
||||
.send(Message::DATA(GenericMessageData { p1: 13, p2: 2 }));
|
||||
let _ = self.other_handler_queue.send(queues::Message::DATA(
|
||||
queues::GenericMessageData { p1: 13, p2: 2 },
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -245,105 +122,48 @@ impl ExecutableObjectIF for HandlerSender {
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageQueue<T> {
|
||||
queue_id: *const cty::c_void,
|
||||
_unused: Option<T>, //need to constrain the queue to one message type for safety, but compiler needs that to be used
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageQueueSender<T> {
|
||||
queue_id: Option<*const cty::c_void>,
|
||||
_unused: Option<T>, //need to constrain the sender to one message type for safety, but compiler needs that to be used
|
||||
}
|
||||
|
||||
impl<T: Default> MessageQueue<T> {
|
||||
fn new(depth: usize) -> Self {
|
||||
let mut instance: Self;
|
||||
unsafe {
|
||||
instance = Self {
|
||||
queue_id: 0 as *const cty::c_void,
|
||||
_unused: None,
|
||||
};
|
||||
//TODO check cast of depth
|
||||
instance.queue_id = create_queue(depth, core::mem::size_of::<T>());
|
||||
|
||||
if instance.queue_id == 0 as *mut cty::c_void {
|
||||
panic!("could not create Queue");
|
||||
}
|
||||
instance
|
||||
impl datasets::DatapoolOwnerIF for Handler {
|
||||
fn get_set(&self, type_id: datasets::TypeId) -> Option<&dyn datasets::DataSetIF>{
|
||||
if type_id == self.data.get_type_id(){
|
||||
Some(&self.data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sender(&self) -> MessageQueueSender<T> {
|
||||
let instance: MessageQueueSender<T> = MessageQueueSender::<T> {
|
||||
queue_id: Some(self.queue_id),
|
||||
_unused: None,
|
||||
impl DatapoolOwnerIF for HandlerSender {}
|
||||
|
||||
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(()),
|
||||
};
|
||||
instance
|
||||
}
|
||||
self.other_handler_queue = other_handler.get_command_queue();
|
||||
|
||||
fn receive(&self) -> Result<T, ()> {
|
||||
let mut message: T = T::default();
|
||||
let res: cty::uint8_t;
|
||||
unsafe {
|
||||
//message = core::mem::MaybeUninit::zeroed().assume_init(); // We only return it if the queue received something
|
||||
let message_pointer: *mut cty::c_void = &mut message as *mut _ as *mut cty::c_void;
|
||||
res = queue_receive(self.queue_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
Ok(message)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MessageQueueSender<T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue_id: None,
|
||||
_unused: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&self, message: T) -> Result<(), ()> {
|
||||
let queue_id = self.queue_id.expect("unitialized Message Queue");
|
||||
let res: cty::uint8_t;
|
||||
unsafe {
|
||||
let message_pointer: *const cty::c_void = &message as *const _ as *const cty::c_void;
|
||||
res = queue_send(queue_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct GenericMessageData {
|
||||
p1: u32,
|
||||
p2: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
enum Message {
|
||||
OK,
|
||||
#[default]
|
||||
FAILED,
|
||||
DATA(GenericMessageData),
|
||||
}
|
||||
|
||||
struct Outbytes {}
|
||||
|
||||
use core::fmt::{Error, Write};
|
||||
impl Write for Outbytes {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
||||
for c in s.as_bytes() {
|
||||
unsafe {
|
||||
outbyte(*c);
|
||||
}
|
||||
}
|
||||
self.other_data.initialize(object_manager, self.other_handler)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -353,47 +173,42 @@ fn mission() {
|
||||
|
||||
let mut h1 = Handler {
|
||||
id: 1,
|
||||
command_queue: MessageQueue::new(5),
|
||||
command_queue: queues::MessageQueue::new(),
|
||||
data: datasets::OwnedDataset::new()
|
||||
};
|
||||
let mut h2 = HandlerSender {
|
||||
id: 2,
|
||||
other_handler: 3,
|
||||
cycle: 0,
|
||||
other_handler: MessageQueueSender::<Message>::new(),
|
||||
other_handler_queue: queues::MessageQueueSender::new(),
|
||||
other_data: datasets::ReferencedDataset::new()
|
||||
};
|
||||
|
||||
h2.other_handler = h1.command_queue.get_sender();
|
||||
let array: &mut [&mut dyn objectmanager::SystemObjectIF] = &mut [&mut h1];
|
||||
|
||||
let array: &mut [&mut dyn ExecutableObjectIF] = &mut [&mut h1];
|
||||
let mut t1 = tasks::PeriodicTask::new(array, 512, 200);
|
||||
|
||||
let mut t1 = PeriodicTask::new(array, 512, 200);
|
||||
|
||||
let mut t2: PeriodicTask = PeriodicTask {
|
||||
let mut t2: tasks::PeriodicTask = tasks::PeriodicTask {
|
||||
task_objects: &mut [&mut h2],
|
||||
stack_size: 512,
|
||||
period: 400,
|
||||
task_handle: 0 as *const cty::c_void,
|
||||
task_handle: 0 as *const core::ffi::c_void,
|
||||
};
|
||||
|
||||
let _i = 1;
|
||||
|
||||
sifln!("sizeof {}, pointer struct {:p}, pointer element {:p}, next element{:p}", size_of::<PeriodicTask>(), &t2, t2.task_objects, &_i);
|
||||
|
||||
let mut task_executor = TaskExecutor {
|
||||
let mut task_executor = tasks::TaskExecutor {
|
||||
tasks: &mut [&mut t1, &mut t2],
|
||||
};
|
||||
|
||||
sifln!("{:p}", task_executor.tasks[0]);
|
||||
|
||||
task_executor.run_tasks();
|
||||
task_executor.init_and_run();
|
||||
|
||||
sifln!("Mission delay");
|
||||
unsafe {
|
||||
task_delay(2000);
|
||||
osal::task_delay(2000);
|
||||
}
|
||||
sifln!("executor dropped");
|
||||
drop(task_executor);
|
||||
unsafe {
|
||||
task_delay(2000);
|
||||
osal::task_delay(2000);
|
||||
}
|
||||
sifln!("Mission delay done");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user