I do have an array of [u8;4096] at a specific memory address. This "region" will be treated as some generic buffer that can hold structured data. As the contents are read from different hardware/process that is not in control of my rust implementation this region need to be updated in a volatile way. Further more the structured data has varying length.
This is what I actually was able to come up with:
use core::{mem, slice, ptr};
#[repr(C)]
struct Slot {
data: [u8; 4096],
}
/// A message that is stored within the [Slot]. Access to the message is only available through the [SlotAccessor]
/// functions.
#[repr(C)]
pub struct SlotMessage {
pub header: SlotMessageHeader,
pub data: Vec<u8>, //[u8; 0]
}
#[repr(C)]
pub struct SlotMessageHeader {
pub msgid: u32,
pub size: u32,
}
struct SlotAccessor {
/// Raw pointer to the array of [Slot]s
inner: *mut Slot,
/// The len of the array the raw pointer points to
len: usize,
}
impl SlotAccessor {
/// Create the [SlotAccessor] based on the raw pointer and the desired array length.
///
/// # Safety
/// This is safe if the raw pointer is ensured to point to a memory region of the [Slot] format with at least 'len'
/// concecutive elements.
pub unsafe fn new(slot_array: *mut Slot, len: usize) -> Self {
Self {
inner: slot_array,
len,
}
}
pub fn store_message<F: FnOnce(&mut SlotMessage)>(
&mut self,
slot_index: isize,
slot_offset: usize,
payload_size: usize,
f: F)
{
assert!(slot_index > 0);
assert!((slot_index as usize) < self.len);
unsafe {
let slot_ptr = self.inner.offset(slot_index);
let msg_ptr = &mut (*slot_ptr).data[slot_offset & 4095] as *mut u8 as *mut SlotMessageHeader;
let payload_ptr = (msg_ptr as *mut u8).offset(mem::size_of::<SlotMessageHeader>() as isize);
let msg_hdr = ptr::read_volatile(msg_ptr);
let msg_payload = slice::from_raw_parts_mut(payload_ptr, payload_size).to_vec();
let mut message = SlotMessage {
header: msg_hdr,
data: msg_payload,
};
f(&mut message);
ptr::write_volatile(msg_ptr, message.header);
for p in 0..message.data.len() {
ptr::write_volatile(payload_ptr.offset(p as isize), message.data[p]);
}
}
}
}
But the store_message really "smells" to me and feels odd. Is there any better way to achieve this? I'd liked to pass the message payload data as a slice instead of a vec to the closure but if I need to do this intermediate step it's fine assuming the performance hit is not to dramatic
The only alternative I can think of is for SlotMessage to hold a private reference (or pointer) to a slice, and allow mutation only through a public setter. For example:
You can't safely give out &mut [u8] access, nor use the IndexMut trait to provide syntax sugar for set, because those would both allow callers to make non-volatile writes to the volatile region.
It seems like you should be using raw pointers for that, as the compiler would assume that the program has exclusive access to the mutable slice, as that's what mutability means on references?
Yes, good point. Edited my previous comment to use a raw pointer.
The as_slice method might also be unsound if the memory might change while the SlotMessage is borrowed. It could be replaced by a .get(i) method similar to set.
The set function also appears incorrect now, as the (*self.data)[i..] expression creates a mutable reference to (part of) the memory region, the creation of which is an assertion that we have exclusive access, no?
thanks to all of you for your valuable input so far. As I'm still a bit struggling with how I would properly create the SlotMessage as proposed by @mbrubeck as a view into a [u8;4096] array/slice and using the set function to update an individual element in the payload of the SlotMessage I thought whether it could be an option to make the SlotMessage generic over the data/payload and then rewrite the function to store a message like this:
This pretty much compiles fine so far, but I'm still a bit concerned thit it might be sound? Another question then would also be: is there a way to constrain T in the excample above to be an array with any size? (create a custom trait and implement it for an arbitrary number of array length to be used as trait bound for T does not feel as usefull apprach to me)
The language design team is now working on stabilizing a subset of const generics that will provide a much cleaner way to do this. Until then, however, the best options I know are the one you suggested, or a similar approach using type-level numbers as in generic-array.