Immutable access in this case is way easier to accomplish safely and soundly, because you don't have to think about mutable aliasing and lifetimes as much, for mutable access with different types I would probably implement an abstraction that handles all the conversion and makes sure no aliasing occurs with mutable references involved:
#![no_std]
pub struct PsRam {
ptr: *mut u32,
words: usize,
}
impl PsRam {
/// This function is `unsafe` because the caller has to make sure it's only called
/// once for every address and that it actually points to `len` contiguous words we
/// are allowed to read and write, for slice access later the pointer must further
/// be properly aligned at a word boundary.
pub const unsafe fn new(ptr: *mut u32, words: usize) -> Self {
Self { ptr, words }
}
pub fn as_bytes(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.ptr as *mut u8, 4 * self.words) }
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.ptr as *mut u8, 4 * self.words) }
}
pub fn as_half_words(&self) -> &[u16] {
unsafe { core::slice::from_raw_parts(self.ptr as *mut u16, 2 * self.words) }
}
pub fn as_half_words_mut(&mut self) -> &mut [u16] {
unsafe { core::slice::from_raw_parts_mut(self.ptr as *mut u16, 2 * self.words) }
}
pub fn as_words(&self) -> &[u32] {
unsafe { core::slice::from_raw_parts(self.ptr as *mut u32, self.words) }
}
pub fn as_words_mut(&mut self) -> &mut [u32] {
unsafe { core::slice::from_raw_parts_mut(self.ptr as *mut u32, self.words) }
}
}
This way we can safely write code like this:
const PS_RAM_WORDS: usize = 4; // how ever many words psram contains
let mut psram = unsafe { PsRam::new(0x60000000 as *mut u32, PS_RAM_WORDS) };
// all this works
rprintln!("address of second byte: {:p}", &psram.as_bytes()[1]);
rprintln!("psram as half words: {:?}", psram.as_half_words());
rprintln!("first word of psram: {:#010x}", psram.as_words()[0]);
psram.as_words_mut()[0] = 0x12345678;
rprintln!("first word of psram after edit: {:#010x}", psram.as_words()[0]);
output assuming the memory is 0
initialized:
address of second byte: 0x60000001
psram as half words: [0, 0, 0, 0, 0, 0, 0, 0]
first word of psram: 0x00000000
first word of psram after edit: 0x12345678
While the compiler helps rejecting code containing undefined behavior like this (two aliasing mutable references are not allowed):
let psramb = psram.as_bytes_mut();
let psramw = psram.as_words_mut();
psramb[0] = 13;
psramw[1] = 99;