I accept the point - I've been pointing out targets, not the actual incident or blocking problem.
However, here is a target with a problem.
Robin Mueller, a researcher at the University of Stuttgart's Institute of Space Systems, was developing a Rust bootloader for the Vorago VA108xx and VA416xx - radiation-hardened Cortex-M4 MCUs deployed in aerospace - where programme RAM starts at 0x0. He needed to read the running application image from address 0x0 to flash it to non-volatile memory. Skipping the first few bytes was not an option - the image starts there. Standard Rust pointer operations were impossible; the only path was inline ArmĀ® assembly.
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let bootloader_data = {
unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
)
}
};
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(bootloader_data);
let bootloader_crc = digest.finalize();
nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
if DEFMT_PRINTOUTS {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if DEFMT_PRINTOUTS {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes());
if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
if DEFMT_PRINTOUTS {
defmt::error!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
}
}
}
// Reading from address 0x0 is problematic in Rust.
// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5.
// This solution falls back to assembler to deal with this.
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
unsafe {
core::arch::asm!(
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
"str r0, [{1}]", // Store r0 register into first_four_bytes
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
in(reg) buf as *mut [u8; 4], // Input: destination pointer
);
}
}
fn check_own_crc(
sysconfig: &pac::Sysconfig,
cp: &cortex_m::Peripherals,
nvm: &mut NvmWrapper,
timer: &mut CountdownTimer,
) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
// panics.
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
...
}
I circumvented the issue by falling back to assembler, but this feels really hacky to me.. Shouldn't Rust be low level enough to allow me to deal with these issues? I found this pre-RFC: Pre-RFC: Conditionally-supported volatile access to address 0 - libs - Rust Internals.
The project is published as va108xx and va416xx on crates.io and sources available at