From 2d8705917cc7fbfbf007b3c78d2f069b75930c66 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Wed, 31 Jan 2024 15:38:13 +0100 Subject: [PATCH 01/15] continuing on thread safety --- mission_rust/src/fsrc/osal/mod.rs | 35 +++++++++++++++++++---------- mission_rust/src/fsrc/queues/mod.rs | 19 ++++++++++++---- mission_rust/src/fsrc/tasks/mod.rs | 31 +++++++++++++++++++------ mission_rust/src/lib.rs | 2 +- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/mission_rust/src/fsrc/osal/mod.rs b/mission_rust/src/fsrc/osal/mod.rs index 7c62870..05191c4 100644 --- a/mission_rust/src/fsrc/osal/mod.rs +++ b/mission_rust/src/fsrc/osal/mod.rs @@ -3,6 +3,17 @@ 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) @@ -37,20 +48,20 @@ extern "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 task_delete_self() { + unsafe { + delete_task(0 as *const core::ffi::c_void); } } -pub fn enable_global_threading(){ - unsafe{enable_global_threading_c()}; +pub fn global_threading_available() -> bool { + unsafe { global_threading_available_c() == 1 } } -pub fn disable_global_threading(){ - unsafe{disable_global_threading_c()}; -} \ No newline at end of file +pub fn enable_global_threading() { + unsafe { enable_global_threading_c() }; +} + +pub fn disable_global_threading() { + unsafe { disable_global_threading_c() }; +} diff --git a/mission_rust/src/fsrc/queues/mod.rs b/mission_rust/src/fsrc/queues/mod.rs index 457843e..4f13dc3 100644 --- a/mission_rust/src/fsrc/queues/mod.rs +++ b/mission_rust/src/fsrc/queues/mod.rs @@ -1,4 +1,4 @@ -use crate::osal; +use crate::{check_global_threading_available, osal}; pub struct MessageQueue { queue_data: [Message; DEPTH], @@ -18,7 +18,7 @@ impl MessageQueue { } pub fn receive(&self) -> Option { - osal::check_global_threading_available(); + check_global_threading_available!(); let actual_id = match self.queue_id { None => return None, Some(id) => id, @@ -26,7 +26,11 @@ impl MessageQueue { 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 + // safe beacuse: + // OS will write not more than length of message queue elements + // queue was created with size_of:: as length of message queue elements + // in MessageQueue::initialize + // making this pointer access safe as long as OS is holding the contract 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); @@ -39,6 +43,7 @@ impl MessageQueue { } fn initialize(&mut self) { + check_global_threading_available!(); if self.queue_id != None { return; } @@ -53,6 +58,7 @@ impl MessageQueue { } pub fn get_sender(&self) -> MessageQueueSender { + check_global_threading_available!(); 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"); @@ -68,13 +74,18 @@ impl MessageQueueSender { } pub fn send(&self, message: Message) -> Result<(), ()> { - osal::check_global_threading_available(); + check_global_threading_available!(); let queue_id = match self.queue_id { None => return Err(()), Some(id) => id, }; let res: u8; unsafe { + // safe beacuse: + // OS will read not more than length of message queue elements + // queue was created with size_of:: as length of message queue elements + // in MessageQueue::initialize + // 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 = osal::queue_send(queue_id, message_pointer); diff --git a/mission_rust/src/fsrc/tasks/mod.rs b/mission_rust/src/fsrc/tasks/mod.rs index 936a43c..3013ca3 100644 --- a/mission_rust/src/fsrc/tasks/mod.rs +++ b/mission_rust/src/fsrc/tasks/mod.rs @@ -89,8 +89,17 @@ pub struct TaskExecutor<'a> { } impl<'a> TaskExecutor<'a> { - pub fn init_and_run(&mut self) { - //TODO unlock global multitasking mutex + /// 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()) }, }; @@ -98,17 +107,22 @@ impl<'a> TaskExecutor<'a> { let _ = task.initialize(&object_manager).unwrap(); } drop(object_manager); + crate::fsrc::osal::enable_global_threading(); 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 + // 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 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()); + 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"); @@ -116,6 +130,9 @@ impl<'a> TaskExecutor<'a> { task.set_handle(handle); } } + if delete_init_task { + crate::osal::task_delete_self(); + } } } @@ -141,7 +158,7 @@ impl<'a> crate::objectmanager::ObjectManager<'a> for TaskObjectManager<'a> { impl<'a> Drop for TaskExecutor<'a> { fn drop(&mut self) { - //TODO lock global multitasking mutex + crate::fsrc::osal::disable_global_threading(); for task in self.tasks.iter_mut() { unsafe { crate::fsrc::osal::delete_task(task.get_handle()); diff --git a/mission_rust/src/lib.rs b/mission_rust/src/lib.rs index 590aacc..75bbf24 100644 --- a/mission_rust/src/lib.rs +++ b/mission_rust/src/lib.rs @@ -199,7 +199,7 @@ fn mission() { tasks: &mut [&mut t1, &mut t2], }; - task_executor.init_and_run(); + task_executor.init_and_run(false); sifln!("Mission delay"); unsafe { From 3bf0667cbbc413f48246e3c7c9b7c4749e0db3bc Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Thu, 1 Feb 2024 17:10:42 +0100 Subject: [PATCH 02/15] mutexes and datasets --- mission/freeRTOS_rust_helper.c | 2 +- mission_rust/src/fsrc/datasets/mod.rs | 63 ++++++++++++++++----------- mission_rust/src/fsrc/mutex.rs | 39 +++++++---------- mission_rust/src/fsrc/queues/mod.rs | 2 +- mission_rust/src/fsrc/tasks/mod.rs | 3 +- 5 files changed, 57 insertions(+), 52 deletions(-) diff --git a/mission/freeRTOS_rust_helper.c b/mission/freeRTOS_rust_helper.c index 6518fbb..4e841a4 100644 --- a/mission/freeRTOS_rust_helper.c +++ b/mission/freeRTOS_rust_helper.c @@ -4,7 +4,7 @@ // TODO namespace the names -SemaphoreHandle_t * global_threading_semaphore = NULL; +SemaphoreHandle_t global_threading_semaphore = NULL; uint8_t global_threading_available_c() { if (global_threading_semaphore == NULL) { diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index a0c7841..eeb5065 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -1,12 +1,14 @@ -use super::{mutex, objectmanager}; +use crate::check_global_threading_available; + use super::objectmanager::SystemObjectIF; +use super::{mutex, objectmanager}; #[derive(Copy, Clone, PartialEq)] pub struct TypeId { id: usize, } -// inspired by https://github.com/jswrenn/deflect/ +// inspired by Jack Wrenn at https://github.com/jswrenn/deflect/ pub trait HasTypeId { #[inline(never)] fn get_type_id(&self) -> TypeId { @@ -28,33 +30,34 @@ pub trait DataSetIF { fn get_mutex(&self) -> mutex::RawMutex; } -pub struct OwnedDataset { +pub struct OwnedDataset { actual_data: T, mutex: mutex::RawMutex, } -pub struct ReferencedDataset { +pub struct ReferencedDataset { //we use a pointer here to avoid lifetimes actual_data: Option<*const T>, mutex: Option, } -impl DataSetIF for OwnedDataset { +impl DataSetIF for OwnedDataset { fn get_type_id(&self) -> TypeId { self.actual_data.get_type_id() } fn get_actual_data(&self) -> &dyn HasTypeId { + // only return pointer when threading + check_global_threading_available!(); &self.actual_data } fn get_mutex(&self) -> mutex::RawMutex { - self.initialize(); - return self.mutex; + return self.mutex.clone(); } } -impl OwnedDataset { +impl OwnedDataset { pub fn new() -> OwnedDataset { OwnedDataset:: { actual_data: T::default(), @@ -62,12 +65,19 @@ impl OwnedDataset { } } + pub fn new_default(default_values: T) -> OwnedDataset { + OwnedDataset:: { + actual_data: default_values, + mutex: mutex::RawMutex::new(), + } + } + pub fn read(&mut self) -> Result { let _mutex_guard = match self.mutex.take() { Err(()) => return Err(()), Ok(guard) => guard, }; - Ok(self.actual_data) + Ok(self.actual_data.clone()) } //TODO do we want to know if it fails? @@ -79,12 +89,10 @@ impl OwnedDataset { self.actual_data = data; } - fn initialize(&mut self) { - - } + fn initialize(&mut self) {} } -impl ReferencedDataset { +impl ReferencedDataset { pub fn new() -> ReferencedDataset { ReferencedDataset:: { actual_data: None, @@ -93,33 +101,36 @@ impl ReferencedDataset { } pub fn read(&mut self) -> Result { - let _mutex_guard = match self.mutex { + 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 }), - } + let pointer = match self.actual_data { + None => return Err(()), + Some(data) => data, + }; + // we are only allowed to use the pointer during threading + check_global_threading_available!(); + Ok(unsafe { (*pointer).clone() }) } - //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<(), ()> { + 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) { + let other_set = match owner.get_set(type_id) { None => { return Err(()); } - Some(set) => { - other_set = set; - } - } + Some(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); diff --git a/mission_rust/src/fsrc/mutex.rs b/mission_rust/src/fsrc/mutex.rs index 1770dfe..3aeae00 100644 --- a/mission_rust/src/fsrc/mutex.rs +++ b/mission_rust/src/fsrc/mutex.rs @@ -1,7 +1,11 @@ use super::osal; +// for now, this is an implementation based on FreeRTOS, where we can preallocate mutexes +// this allows us to assume that mutexes are valid starting from their creation until end of runtime +// and so we do not use the global_threading lock + pub struct RawMutex { - handle: Option<*const core::ffi::c_void> + handle: *const core::ffi::c_void } pub struct RawMutexGuard { @@ -15,31 +19,20 @@ impl Drop for RawMutexGuard { } } -//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 + let handle = unsafe {osal::create_mutex()}; + if handle == 0 as *const core::ffi::c_void { + panic!("Could not create mutex") } - } - - 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 + handle: handle } } pub fn take(&self) -> Result { - 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 }), + match unsafe {osal::take_mutex(self.handle)} { + 1 => Ok(RawMutexGuard { handle: self.handle }), _ => Err(()) //TODO error code } } @@ -57,12 +50,12 @@ impl RawMutex { // _ => 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") +impl Clone for RawMutex { + fn clone(&self) -> Self { + Self { + handle: self.handle } - self.handle = Some(handle); } } \ No newline at end of file diff --git a/mission_rust/src/fsrc/queues/mod.rs b/mission_rust/src/fsrc/queues/mod.rs index 4f13dc3..f752174 100644 --- a/mission_rust/src/fsrc/queues/mod.rs +++ b/mission_rust/src/fsrc/queues/mod.rs @@ -81,7 +81,7 @@ impl MessageQueueSender { }; let res: u8; unsafe { - // safe beacuse: + // safe because: // OS will read not more than length of message queue elements // queue was created with size_of:: as length of message queue elements // in MessageQueue::initialize diff --git a/mission_rust/src/fsrc/tasks/mod.rs b/mission_rust/src/fsrc/tasks/mod.rs index 3013ca3..04395ca 100644 --- a/mission_rust/src/fsrc/tasks/mod.rs +++ b/mission_rust/src/fsrc/tasks/mod.rs @@ -103,11 +103,12 @@ impl<'a> TaskExecutor<'a> { 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); - crate::fsrc::osal::enable_global_threading(); 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 From 6db9b52cb48f0e15dc8a84a943ddc83285f46ddb Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Fri, 2 Feb 2024 17:28:44 +0100 Subject: [PATCH 03/15] this is going to be interesting --- mission_rust/src/fsrc/datasets/mod.rs | 2 -- mission_rust/src/fsrc/mod.rs | 1 + mission_rust/src/fsrc/queues/mod.rs | 7 +++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index eeb5065..911d2a3 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -88,8 +88,6 @@ impl OwnedDataset { }; self.actual_data = data; } - - fn initialize(&mut self) {} } impl ReferencedDataset { diff --git a/mission_rust/src/fsrc/mod.rs b/mission_rust/src/fsrc/mod.rs index f56e588..a892f6b 100644 --- a/mission_rust/src/fsrc/mod.rs +++ b/mission_rust/src/fsrc/mod.rs @@ -6,4 +6,5 @@ pub mod osal; pub mod tasks; pub mod objectmanager; pub mod datasets; +pub mod store; mod mutex; \ No newline at end of file diff --git a/mission_rust/src/fsrc/queues/mod.rs b/mission_rust/src/fsrc/queues/mod.rs index f752174..b7be59d 100644 --- a/mission_rust/src/fsrc/queues/mod.rs +++ b/mission_rust/src/fsrc/queues/mod.rs @@ -1,5 +1,8 @@ use crate::{check_global_threading_available, osal}; + +// TODO test dropping of droppable data within messages (no drop on sending, dropping when receiving, no dropping when forwarding in new message) + pub struct MessageQueue { queue_data: [Message; DEPTH], queue_id: Option<*const core::ffi::c_void>, @@ -73,12 +76,16 @@ impl MessageQueueSender { Self { queue_id: None } } + // TODO if error, message is dropped, is this intended or should we return the message back out? pub fn send(&self, message: Message) -> Result<(), ()> { check_global_threading_available!(); let queue_id = match self.queue_id { None => return Err(()), Some(id) => id, }; + // 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: From 3155243ae9493086c4cf6dd0baa8238be1252d94 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Fri, 2 Feb 2024 17:28:58 +0100 Subject: [PATCH 04/15] starting stores --- mission_rust/src/fsrc/store.rs | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 mission_rust/src/fsrc/store.rs diff --git a/mission_rust/src/fsrc/store.rs b/mission_rust/src/fsrc/store.rs new file mode 100644 index 0000000..2355c72 --- /dev/null +++ b/mission_rust/src/fsrc/store.rs @@ -0,0 +1,38 @@ +use crate::check_global_threading_available; + +struct Store{ + mutex: crate::fsrc::mutex::RawMutex +} + +trait StoreBackend { + +} + +struct StoreAccessor { + mutex: crate::fsrc::mutex::RawMutex +} + +impl Store { + pub fn get_accessor(&self) -> StoreAccessor { + check_global_threading_available!(); + StoreAccessor{mutex: self.mutex.clone()} + } +} + +struct StoreSlot{ + data: *mut [u8] +} + +impl StoreSlot { + pub fn get_data(&self) -> &[u8] { + check_global_threading_available!(); + unsafe{&(*self.data)} + } + + pub fn get_all_data(&self) -> &[u8] { + check_global_threading_available!(); + unsafe{&(*self.data)} + } + + +} \ No newline at end of file From 4460e470b2ce95bc2b9c30fb542c81035854235b Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Sat, 3 Feb 2024 00:03:49 +0100 Subject: [PATCH 05/15] this time, we won't reinvent the wheel --- mission_rust/src/fsrc/datasets/mod.rs | 53 +++++++++++---------------- mission_rust/src/fsrc/store.rs | 10 ++--- mission_rust/src/lib.rs | 4 +- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index 911d2a3..771fbe4 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -1,52 +1,39 @@ +use core::any::Any; + use crate::check_global_threading_available; use super::objectmanager::SystemObjectIF; use super::{mutex, objectmanager}; -#[derive(Copy, Clone, PartialEq)] -pub struct TypeId { - id: usize, -} - -// inspired by Jack Wrenn at 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> { + fn get_set(&self, _type_id: core::any::TypeId) -> Option<&dyn DataSetIF> { None } } pub trait DataSetIF { - fn get_type_id(&self) -> TypeId; - fn get_actual_data(&self) -> &dyn HasTypeId; + fn get_type_id(&self) -> core::any::TypeId; // TODO can we get rid of this? Currently, it helps implementing DatapoolOwnerIF::get_set() but if we can optimize/generate that, this might be not needed any more + fn get_actual_data(&self) -> &dyn core::any::Any; fn get_mutex(&self) -> mutex::RawMutex; } -pub struct OwnedDataset { +pub struct OwnedDataset { actual_data: T, mutex: mutex::RawMutex, } -pub struct ReferencedDataset { +pub struct ReferencedDataset { //we use a pointer here to avoid lifetimes actual_data: Option<*const T>, mutex: Option, } -impl DataSetIF for OwnedDataset { - fn get_type_id(&self) -> TypeId { - self.actual_data.get_type_id() +impl DataSetIF for OwnedDataset { + fn get_type_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() } - fn get_actual_data(&self) -> &dyn HasTypeId { + fn get_actual_data(&self) -> &dyn core::any::Any { // only return pointer when threading check_global_threading_available!(); &self.actual_data @@ -57,7 +44,7 @@ impl DataSetIF for OwnedDataset { } } -impl OwnedDataset { +impl OwnedDataset { pub fn new() -> OwnedDataset { OwnedDataset:: { actual_data: T::default(), @@ -90,7 +77,7 @@ impl OwnedDataset { } } -impl ReferencedDataset { +impl ReferencedDataset { pub fn new() -> ReferencedDataset { ReferencedDataset:: { actual_data: None, @@ -121,17 +108,19 @@ impl ReferencedDataset { 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 type_id = core::any::TypeId::of::(); let other_set = match owner.get_set(type_id) { None => { - return Err(()); + return Err(()); //TODO panic? -> should initialize just panic if failing? } Some(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); + // let's recheck that owner.get_set did not lie about the type_id + if let Some(other_data) = other_set.get_actual_data().downcast_ref::() { + self.actual_data = Some(other_data); + } else { + return Err(()); + } self.mutex = Some(other_set.get_mutex()); Ok(()) } diff --git a/mission_rust/src/fsrc/store.rs b/mission_rust/src/fsrc/store.rs index 2355c72..d3d999d 100644 --- a/mission_rust/src/fsrc/store.rs +++ b/mission_rust/src/fsrc/store.rs @@ -24,15 +24,13 @@ struct StoreSlot{ } impl StoreSlot { - pub fn get_data(&self) -> &[u8] { + pub fn get_data(&self) -> &mut [u8] { check_global_threading_available!(); - unsafe{&(*self.data)} + unsafe{&mut (*self.data)} } - pub fn get_all_data(&self) -> &[u8] { + pub fn get_all_data(&self) -> &mut [u8] { check_global_threading_available!(); - unsafe{&(*self.data)} + unsafe{&mut (*self.data)} } - - } \ No newline at end of file diff --git a/mission_rust/src/lib.rs b/mission_rust/src/lib.rs index 75bbf24..65a341e 100644 --- a/mission_rust/src/lib.rs +++ b/mission_rust/src/lib.rs @@ -55,8 +55,6 @@ struct HandlerData { y: f32 } -impl datasets::HasTypeId for HandlerData {} - struct Handler { id: objectmanager::ObjectId, command_queue: queues::MessageQueue<10>, @@ -135,7 +133,7 @@ impl SystemObjectIF for Handler { } impl datasets::DatapoolOwnerIF for Handler { - fn get_set(&self, type_id: datasets::TypeId) -> Option<&dyn datasets::DataSetIF>{ + fn get_set(&self, type_id: core::any::TypeId) -> Option<&dyn datasets::DataSetIF>{ if type_id == self.data.get_type_id(){ Some(&self.data) } else { From 3064a5c2e884d5a2f43b2292d3ee138c5ba80a49 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Mon, 5 Feb 2024 22:23:17 +0100 Subject: [PATCH 06/15] refining what is what in stores --- mission_rust/src/fsrc/datasets/mod.rs | 2 +- mission_rust/src/fsrc/store.rs | 69 ++++++++++++++++++++------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index 771fbe4..fd12f2a 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -115,7 +115,7 @@ impl ReferencedDataset { } Some(set) => set, }; - // let's recheck that owner.get_set did not lie about the type_id + // let's recheck that owner.get_set() did not lie about the type_id if let Some(other_data) = other_set.get_actual_data().downcast_ref::() { self.actual_data = Some(other_data); } else { diff --git a/mission_rust/src/fsrc/store.rs b/mission_rust/src/fsrc/store.rs index d3d999d..3950f6b 100644 --- a/mission_rust/src/fsrc/store.rs +++ b/mission_rust/src/fsrc/store.rs @@ -1,36 +1,71 @@ +use core::slice::SliceIndex; + use crate::check_global_threading_available; -struct Store{ - mutex: crate::fsrc::mutex::RawMutex -} - trait StoreBackend { - + fn free_slot(&mut self, length: usize) -> Option<*mut [u8]>; } -struct StoreAccessor { - mutex: crate::fsrc::mutex::RawMutex +struct Store<'a> { + mutex: crate::fsrc::mutex::RawMutex, + backend: &'a mut dyn StoreBackend, } -impl Store { - pub fn get_accessor(&self) -> StoreAccessor { +impl<'a> Store<'a> { + pub fn accessor(&mut self) -> StoreAccessor { check_global_threading_available!(); - StoreAccessor{mutex: self.mutex.clone()} + // holy shit, this is really unsafe, even for us: + // we cast away the lifetime + // this is safe as we only do it in threading + // and the Accessor will only use it while still in threading + // (the accessor is a smart pointer refusing to be smart outside + // of threading) + let static_pointer = unsafe { core::mem::transmute::<*mut Store<'a>, *mut Store<'static>>(self) }; + StoreAccessor { + store: static_pointer, + } + } + + fn free_slot(&mut self, length: usize) -> Result { + if let Ok(_guard) = self.mutex.take() { + if let Some(data) = self.backend.free_slot(length){ + Ok(StoreSlot { data: data }) + } else { + Err(()) + } + } else { + Err(()) + } } } -struct StoreSlot{ - data: *mut [u8] +// A smart pointer to a Store protecting insane (static) pointer usage +// only works during threading, panics otherwise +struct StoreAccessor { + store: *mut Store<'static>, +} + +impl StoreAccessor { + fn free_slot(&self, length: usize) -> Result { + check_global_threading_available!(); + unsafe { (*self.store).free_slot(length) } + } +} + +struct StoreSlot { + data: *mut [u8], } impl StoreSlot { - pub fn get_data(&self) -> &mut [u8] { + pub fn get(&self, index: I) -> &mut [u8] + where I: SliceIndex + { check_global_threading_available!(); - unsafe{&mut (*self.data)} + unsafe { &mut (*self.data) } } - pub fn get_all_data(&self) -> &mut [u8] { + pub fn get_all(&self) -> &mut [u8] { check_global_threading_available!(); - unsafe{&mut (*self.data)} + unsafe { &mut (*self.data) } } -} \ No newline at end of file +} From c0d82ee7d1f3db5985abca8d2613b9da60873b85 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 6 Feb 2024 14:42:53 +0100 Subject: [PATCH 07/15] some more words --- mission_rust/doc/Static_Allocation.md | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/mission_rust/doc/Static_Allocation.md b/mission_rust/doc/Static_Allocation.md index 9556dd5..4e1812d 100644 --- a/mission_rust/doc/Static_Allocation.md +++ b/mission_rust/doc/Static_Allocation.md @@ -7,19 +7,21 @@ As far as rust goes, static allocation and multithreading with a C RTOS is not p 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). +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. 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. +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 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. +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 thared ressources will result in a panic. This adds an additional overhead to all access to shared data, namely checking the semaphore. +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 @@ -29,9 +31,11 @@ The only time where references to other objects can be acquired is the initializ 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. +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 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. +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 @@ -43,7 +47,7 @@ Two options exist for storing the descriptors. Either they are stored in a preal 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. +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. @@ -61,4 +65,14 @@ The handling of the failure is delegated to the structs using the primitives, as * 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) \ No newline at end of file +* 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. \ No newline at end of file From 6beb4385086ea8a26a0e9b91845b3d707ce0281a Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 6 Feb 2024 14:43:35 +0100 Subject: [PATCH 08/15] TODO for stack usage --- mission_rust/src/fsrc/tasks/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mission_rust/src/fsrc/tasks/mod.rs b/mission_rust/src/fsrc/tasks/mod.rs index 04395ca..2cf2bce 100644 --- a/mission_rust/src/fsrc/tasks/mod.rs +++ b/mission_rust/src/fsrc/tasks/mod.rs @@ -2,6 +2,8 @@ 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; @@ -162,6 +164,7 @@ impl<'a> Drop for TaskExecutor<'a> { 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()); } } From 7ba3f23add5055ad4d81cd07c7a09cac6e755295 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 6 Feb 2024 14:44:09 +0100 Subject: [PATCH 09/15] mutex refinements --- mission_rust/src/fsrc/datasets/mod.rs | 4 +- mission_rust/src/fsrc/mutex.rs | 73 ++++++++++++++++++--------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index fd12f2a..0485888 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -52,9 +52,9 @@ impl OwnedDataset { } } - pub fn new_default(default_values: T) -> OwnedDataset { + pub fn new_init_values(init_values: T) -> OwnedDataset { OwnedDataset:: { - actual_data: default_values, + actual_data: init_values, mutex: mutex::RawMutex::new(), } } diff --git a/mission_rust/src/fsrc/mutex.rs b/mission_rust/src/fsrc/mutex.rs index 3aeae00..a17073e 100644 --- a/mission_rust/src/fsrc/mutex.rs +++ b/mission_rust/src/fsrc/mutex.rs @@ -1,11 +1,13 @@ +use crate::check_global_threading_available; + use super::osal; -// for now, this is an implementation based on FreeRTOS, where we can preallocate mutexes -// this allows us to assume that mutexes are valid starting from their creation until end of runtime -// and so we do not use the global_threading lock +// 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: *const core::ffi::c_void + handle: Option<*const core::ffi::c_void>, + descriptor: [u8; 10], //TODO which size? } pub struct RawMutexGuard { @@ -14,26 +16,59 @@ pub struct RawMutexGuard { impl Drop for RawMutexGuard { fn drop(&mut self) { - //TODO do we need some check here? - unsafe{osal::give_mutex(self.handle)}; + 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 { - let handle = unsafe {osal::create_mutex()}; - if handle == 0 as *const core::ffi::c_void { - panic!("Could not create mutex") - } Self { - handle: handle + handle: None, + descriptor: [0; 10], } } - pub fn take(&self) -> Result { - match unsafe {osal::take_mutex(self.handle)} { - 1 => Ok(RawMutexGuard { handle: self.handle }), - _ => Err(()) //TODO error code + 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 { + 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], } } @@ -51,11 +86,3 @@ impl RawMutex { // } // } } - -impl Clone for RawMutex { - fn clone(&self) -> Self { - Self { - handle: self.handle - } - } -} \ No newline at end of file From 8e8b3f3da4cffde10b1cfd30174649f4faabf3f1 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Thu, 8 Feb 2024 18:02:47 +0100 Subject: [PATCH 10/15] Introspection trait --- mission_rust/src/fsrc/introspection.rs | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 mission_rust/src/fsrc/introspection.rs diff --git a/mission_rust/src/fsrc/introspection.rs b/mission_rust/src/fsrc/introspection.rs new file mode 100644 index 0000000..38e155d --- /dev/null +++ b/mission_rust/src/fsrc/introspection.rs @@ -0,0 +1,63 @@ +/// A trait to allow objects to be queried for some of their inner attributes at runtime. +/// +pub trait Introspection { + /// Executes a function/closure on all members of the implementing struct. + /// + /// The parameters for the closure a reference to the member as well as the name of the member. + /// + /// To keep the trait object safe, we need to pass the closure as a reference. Otherwise, + /// the trait can not be used as a `dyn` reference, because the function becomes generic. + /// (Either using `impl FnMut` not `where F: FnMut` will be treated as generic making the + /// trait not object safe). + /// Additionally, the closure is typed as `FnMut`, to allow the closure to capture mutably. + /// + /// + /// # Examples + /// + /// ```rust + /// use fsrc::introspection::Introspection; + /// + /// struct Empty {} + /// + /// impl Empty { + /// fn getRandomNumber(&self) -> u8 { + /// return 4 // chosen by fair dice roll. + /// // guaranteed to be random. + /// } + /// } + /// + /// fn parsing_something(something: &dyn Introspection) { + /// let mut a = 0; + /// something.for_each_member(&mut |x, _| { + /// if let Some( empty) = x.downcast_ref::() { + /// a = empty.getRandomNumber(); + /// } + /// }); + /// } + /// ```` + /// + fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()); + + /// Same as [for_each_member](Introspection::for_each_member), only with a return value for the closure. + /// + /// # Examples + /// + /// The return value allows using `x.downcast_ref()?` or similar constructs in the closure like in: + /// + /// ```rust + /// use fsrc::introspection::Introspection; + /// + /// struct Empty {} + /// + /// fn do_something(_: &Empty) {} + /// + /// fn parsing_something(something: &dyn Introspection) { + /// something.for_each_member_return(&mut |x, _| { + /// do_something(x.downcast_ref()?); + /// None + /// }); + /// } + /// ``` + /// + fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>); +} \ No newline at end of file From 139c6a0356a05d4e6a54255f8cd01dcb319ac213 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Thu, 8 Feb 2024 18:03:09 +0100 Subject: [PATCH 11/15] Dataset using introspection is better --- mission_rust/src/fsrc/datasets/mod.rs | 78 +++++++------------------- mission_rust/src/fsrc/mod.rs | 3 +- mission_rust/src/fsrc/objectmanager.rs | 2 +- mission_rust/src/fsrc/store.rs | 1 + mission_rust/src/lib.rs | 46 ++++++++------- 5 files changed, 49 insertions(+), 81 deletions(-) diff --git a/mission_rust/src/fsrc/datasets/mod.rs b/mission_rust/src/fsrc/datasets/mod.rs index 0485888..33fa489 100644 --- a/mission_rust/src/fsrc/datasets/mod.rs +++ b/mission_rust/src/fsrc/datasets/mod.rs @@ -1,21 +1,5 @@ -use core::any::Any; - -use crate::check_global_threading_available; - -use super::objectmanager::SystemObjectIF; use super::{mutex, objectmanager}; - -pub trait DatapoolOwnerIF { - fn get_set(&self, _type_id: core::any::TypeId) -> Option<&dyn DataSetIF> { - None - } -} - -pub trait DataSetIF { - fn get_type_id(&self) -> core::any::TypeId; // TODO can we get rid of this? Currently, it helps implementing DatapoolOwnerIF::get_set() but if we can optimize/generate that, this might be not needed any more - fn get_actual_data(&self) -> &dyn core::any::Any; - fn get_mutex(&self) -> mutex::RawMutex; -} +use crate::check_global_threading_available; pub struct OwnedDataset { actual_data: T, @@ -28,22 +12,6 @@ pub struct ReferencedDataset { mutex: Option, } -impl DataSetIF for OwnedDataset { - fn get_type_id(&self) -> core::any::TypeId { - core::any::TypeId::of::() - } - - fn get_actual_data(&self) -> &dyn core::any::Any { - // only return pointer when threading - check_global_threading_available!(); - &self.actual_data - } - - fn get_mutex(&self) -> mutex::RawMutex { - return self.mutex.clone(); - } -} - impl OwnedDataset { pub fn new() -> OwnedDataset { OwnedDataset:: { @@ -86,17 +54,10 @@ impl ReferencedDataset { } pub fn read(&mut self) -> Result { - let _mutex_guard = match &self.mutex { - None => return Err(()), - Some(mutex) => match mutex.take() { - Err(()) => return Err(()), - Ok(guard) => guard, - }, - }; - let pointer = match self.actual_data { - None => return Err(()), - Some(data) => data, - }; + let mutex = self.mutex.as_ref().ok_or(())?; + let _guard = mutex.take()?; + let pointer = self.actual_data.ok_or(())?; + // we are only allowed to use the pointer during threading check_global_threading_available!(); Ok(unsafe { (*pointer).clone() }) @@ -108,20 +69,21 @@ impl ReferencedDataset { owner_of_the_set: objectmanager::ObjectId, ) -> Result<(), ()> { let owner = object_manager.get_object(owner_of_the_set)?; - let type_id = core::any::TypeId::of::(); - let other_set = match owner.get_set(type_id) { - None => { - return Err(()); //TODO panic? -> should initialize just panic if failing? - } - Some(set) => set, - }; - // let's recheck that owner.get_set() did not lie about the type_id - if let Some(other_data) = other_set.get_actual_data().downcast_ref::() { - self.actual_data = Some(other_data); - } else { - return Err(()); - } - self.mutex = Some(other_set.get_mutex()); + owner.for_each_member_return(&mut |x, _| { + self.try_initialize_from(x.downcast_ref()?); + None + }); Ok(()) } + + fn try_initialize_from(&mut self, other: &OwnedDataset) { + // TODO check some id to allow the other to own multiple + // instances of . Otherwise, this will select the last + // one. + // if !self.id == other.id { + // return + // } + self.actual_data = Some(&other.actual_data); + self.mutex = Some(other.mutex.clone()); + } } diff --git a/mission_rust/src/fsrc/mod.rs b/mission_rust/src/fsrc/mod.rs index a892f6b..9cf4c32 100644 --- a/mission_rust/src/fsrc/mod.rs +++ b/mission_rust/src/fsrc/mod.rs @@ -7,4 +7,5 @@ pub mod tasks; pub mod objectmanager; pub mod datasets; pub mod store; -mod mutex; \ No newline at end of file +mod mutex; +pub mod introspection; \ No newline at end of file diff --git a/mission_rust/src/fsrc/objectmanager.rs b/mission_rust/src/fsrc/objectmanager.rs index 36e6cc7..b172776 100644 --- a/mission_rust/src/fsrc/objectmanager.rs +++ b/mission_rust/src/fsrc/objectmanager.rs @@ -5,7 +5,7 @@ pub trait ObjectManager<'a> { fn get_object(&self, id: ObjectId) -> Result<&'a dyn SystemObjectIF, ()>; } -pub trait SystemObjectIF: tasks::ExecutableObjectIF + datasets::DatapoolOwnerIF { +pub trait SystemObjectIF: tasks::ExecutableObjectIF + introspection::Introspection { fn get_id(&self) -> ObjectId; fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()>; fn get_command_queue(&self) -> queues::MessageQueueSender; diff --git a/mission_rust/src/fsrc/store.rs b/mission_rust/src/fsrc/store.rs index 3950f6b..37cbd28 100644 --- a/mission_rust/src/fsrc/store.rs +++ b/mission_rust/src/fsrc/store.rs @@ -39,6 +39,7 @@ impl<'a> Store<'a> { } } +// TODO needs to be able to be uninitialized as it should be usable as member // A smart pointer to a Store protecting insane (static) pointer usage // only works during threading, panics otherwise struct StoreAccessor { diff --git a/mission_rust/src/lib.rs b/mission_rust/src/lib.rs index 65a341e..514ad7a 100644 --- a/mission_rust/src/lib.rs +++ b/mission_rust/src/lib.rs @@ -3,12 +3,11 @@ //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?) -mod fsrc; +pub mod fsrc; use core::fmt::Write; use core::panic::PanicInfo; -use fsrc::datasets::{DataSetIF, DatapoolOwnerIF}; use fsrc::objectmanager::SystemObjectIF; use fsrc::*; @@ -49,16 +48,16 @@ extern "C" fn rust_main() { sifln!("Mission done"); } -#[derive (Copy, Clone, Default)] +#[derive(Copy, Clone, Default)] struct HandlerData { x: u32, - y: f32 + y: f32, } struct Handler { id: objectmanager::ObjectId, command_queue: queues::MessageQueue<10>, - data: datasets::OwnedDataset + data: datasets::OwnedDataset, } struct HandlerSender { @@ -66,10 +65,9 @@ struct HandlerSender { other_handler: objectmanager::ObjectId, cycle: u8, other_handler_queue: queues::MessageQueueSender, - other_data: datasets::ReferencedDataset + other_data: datasets::ReferencedDataset, } - impl Handler { fn handle_message(&self, message: queues::Message) { match message { @@ -132,17 +130,23 @@ impl SystemObjectIF for Handler { } } -impl datasets::DatapoolOwnerIF for Handler { - fn get_set(&self, type_id: core::any::TypeId) -> Option<&dyn datasets::DataSetIF>{ - if type_id == self.data.get_type_id(){ - Some(&self.data) - } else { - None - } - } +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 DatapoolOwnerIF for HandlerSender {} +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 { @@ -154,14 +158,14 @@ impl SystemObjectIF for HandlerSender { } 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 { + let other_handler = match other_handler_maybe { Ok(other) => other, Err(_) => return Err(()), }; self.other_handler_queue = other_handler.get_command_queue(); - self.other_data.initialize(object_manager, self.other_handler)?; + self.other_data + .initialize(object_manager, self.other_handler)?; Ok(()) } } @@ -172,14 +176,14 @@ fn mission() { let mut h1 = Handler { id: 1, command_queue: queues::MessageQueue::new(), - data: datasets::OwnedDataset::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() + other_data: datasets::ReferencedDataset::new(), }; let array: &mut [&mut dyn objectmanager::SystemObjectIF] = &mut [&mut h1]; From 27f0c2ee6b9e1b717ff22775f22415e36f2e7832 Mon Sep 17 00:00:00 2001 From: Paul Nehlich Date: Tue, 27 Feb 2024 14:12:25 +0100 Subject: [PATCH 12/15] README.md aktualisiert, neue Dependency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2da8193..dc28b16 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ cd lib/sw_apps/zynq_fsbl/src/ && make BOARD=zed CFLAGS=-DFSBL_DEBUG_INFO Requirements [TBC]: - cmake - arm-none-eabi-gcc - +- doxygen satisfy Rust requirements From c72100bddbfc9f0e253660d5daee47d54efae4e6 Mon Sep 17 00:00:00 2001 From: Paul Nehlich Date: Tue, 27 Feb 2024 14:20:01 +0100 Subject: [PATCH 13/15] README.md aktualisiert --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc28b16..fd04055 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,11 @@ cd lib/sw_apps/zynq_fsbl/src/ && make BOARD=zed CFLAGS=-DFSBL_DEBUG_INFO Requirements [TBC]: - cmake - arm-none-eabi-gcc -- doxygen +- doxygen +- graphviz +Configure doxygen: +- export DOT_PATH=/usr/local/bin satisfy Rust requirements ```sh From f853ed5d9ab346b4c0a0a2807889b3ff8712a73f Mon Sep 17 00:00:00 2001 From: Paul Nehlich Date: Thu, 21 Mar 2024 10:19:26 +0100 Subject: [PATCH 14/15] README.md aktualisiert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd04055..9e18444 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cd ../mission_rust cargo update rustup toolchain install nightly rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu -rustup override set default nightly +rustup override set nightly cargo build -Z build-std ``` From 47cbe5a214292e58531fde7c669db261dbdea475 Mon Sep 17 00:00:00 2001 From: Paul Nehlich Date: Thu, 21 Mar 2024 10:31:55 +0100 Subject: [PATCH 15/15] README.md aktualisiert --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9e18444..d2c9129 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,10 @@ rustup override set nightly cargo build -Z build-std ``` +In .../obsw/ ```sh +git submodule init +git submodule update mkdir build_cli cd build_cli cmake -DCMAKE_TOOLCHAIN_FILE=../bsp_z7/cmake/arm-none-eabi.toolchain ..