Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow finding the caboose without a header #1555

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 136 additions & 54 deletions drv/stm32h7-update-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#![no_main]

use core::convert::Infallible;
use core::ops::Range;
use drv_caboose::{CabooseError, CabooseReader};
use drv_stm32h7_update_api::{
ImageVersion, BLOCK_SIZE_BYTES, FLASH_WORDS_PER_BLOCK, FLASH_WORD_BYTES,
Expand Down Expand Up @@ -279,6 +280,140 @@ impl<'a> ServerImpl<'a> {
ringbuf_entry!(Trace::EraseEnd);
b
}

fn check_for_caboose(
&mut self,
image_start: u32,
image_end: u32,
) -> Option<Range<u32>> {
if image_end <= image_start {
return None;
}

let caboose_size_addr = match image_end.checked_sub(4) {
Some(e) => e,
None => return None,
};

let caboose_size: u32 = unsafe {
core::ptr::read_volatile(caboose_size_addr as *const u32)
};

let caboose_start = image_end.saturating_sub(caboose_size);
if caboose_start < image_start {
// This branch will be encountered if there's no caboose, because
// then the nominal caboose size will be 0xFFFFFFFF, which will send
// us out of the bank2 region.
return None;
} else {
// SAFETY: we know this pointer is within the bank2 flash region,
// since it's checked above.
let v = unsafe {
core::ptr::read_volatile(caboose_start as *const u32)
};
if v == CABOOSE_MAGIC {
return Some(caboose_start + 4..image_end - 4);
} else {
return None;
}
};
}

fn find_caboose_range(&mut self) -> Result<Range<u32>, CabooseError> {
// This code is very similar to `kipc::read_caboose_pos`, but it
// operates on the alternate flash bank rather than on the loaded image.
// SAFETY: populated by the linker, so this should be valid
let image_start = unsafe { __REGION_BANK2_BASE.as_ptr() } as u32;
let region_end = unsafe { __REGION_BANK2_END.as_ptr() } as u32;

// If all is going according to plan, there will be a valid Hubris image
// flashed into the other slot, delimited by `__REGION_BANK2_BASE` and
// `__REGION_BASE2_END` (which are symbols injected by the linker).
//
// We'll first want to read the image header, which is at a fixed
// location at the end of the vector table. The length of the vector
// table is fixed in hardware, so this should never change.
const HEADER_OFFSET: u32 = 0x298;
let header: ImageHeader = unsafe {
core::ptr::read_volatile(
(image_start + HEADER_OFFSET) as *const ImageHeader,
)
};
if header.magic == 0xa {
//return Err(CabooseError::NoImageHeader);

// Calculate where the image header implies that the image should end
//
// This is a one-past-the-end value.
let image_end = image_start + header.total_image_len;

// Then, check that value against the BANK2 bounds.
if image_end > region_end {
return Err(CabooseError::MissingCaboose);
} else {
return self
.check_for_caboose(image_start, image_end)
.ok_or(CabooseError::MissingCaboose);
}
}

// Section 4.3.10 erase size is minimum 128Kbyte. Find the first
// fully erased sector (will read as `0xff`, see line about
// "If the application software reads back a word that has been erased,
// all the bits will be read at 1, without ECC error.")
const ERASE_SECTOR_SIZE: usize = 0x2_0000;
const SIG_SIZE: u32 = 512;
//let mut last: u32 = 0;
for region in (image_start..region_end).step_by(ERASE_SECTOR_SIZE) {
let s = unsafe {
core::slice::from_raw_parts(
region as *const u8,
ERASE_SECTOR_SIZE,
)
};
if s.iter().all(|&x| x == 0xff) {
// If the first region is all unprogrammed we don't have an image
// and therefore no caboose
if region == image_start {
return Err(CabooseError::MissingCaboose);
}
// We have a candidate! This is the first sector that is
// completely unprogrammed. We're going to search backwards from
// the end of the _previous_ sector (this sector - sector size)
// to find the last programmed word
let check_addr = region - (ERASE_SECTOR_SIZE as u32);
let sub = unsafe {
core::slice::from_raw_parts(
check_addr as *const u8,
ERASE_SECTOR_SIZE,
)
};
// A failure here is probably a bug in our algorithm?
let (offset, _) = sub
.iter()
.rev()
.enumerate()
.find(|&(_, x)| *x != 0xff)
.ok_or(CabooseError::MissingCaboose)?;
let maybe_end =
check_addr + ((ERASE_SECTOR_SIZE - offset) as u32);
if let Some(r) = self.check_for_caboose(image_start, maybe_end)
{
return Ok(r);
}
// We might have a 512 byte signature, check for that
if let Some(new_end) = maybe_end.checked_sub(SIG_SIZE) {
if let Some(r) =
self.check_for_caboose(image_start, new_end)
{
return Ok(r);
}
}
}
}

return Err(CabooseError::MissingCaboose);
}
}

impl idl::InOrderUpdateImpl for ServerImpl<'_> {
Expand Down Expand Up @@ -411,61 +546,8 @@ impl idl::InOrderUpdateImpl for ServerImpl<'_> {
name: [u8; 4],
data: Leased<idol_runtime::W, [u8]>,
) -> Result<u32, RequestError<CabooseError>> {
// This code is very similar to `kipc::read_caboose_pos`, but it
// operates on the alternate flash bank rather than on the loaded image.
let image_start = unsafe { __REGION_BANK2_BASE.as_ptr() } as u32;

// If all is going according to plan, there will be a valid Hubris image
// flashed into the other slot, delimited by `__REGION_BANK2_BASE` and
// `__REGION_BASE2_END` (which are symbols injected by the linker).
//
// We'll first want to read the image header, which is at a fixed
// location at the end of the vector table. The length of the vector
// table is fixed in hardware, so this should never change.
const HEADER_OFFSET: u32 = 0x298;
let header: ImageHeader = unsafe {
core::ptr::read_volatile(
(image_start + HEADER_OFFSET) as *const ImageHeader,
)
};
if header.magic != HEADER_MAGIC {
return Err(CabooseError::NoImageHeader.into());
}

// Calculate where the image header implies that the image should end
//
// This is a one-past-the-end value.
let image_end = image_start + header.total_image_len;

// Then, check that value against the BANK2 bounds.
//
// SAFETY: populated by the linker, so this should be valid
if image_end > unsafe { __REGION_BANK2_END.as_ptr() } as u32 {
return Err(CabooseError::MissingCaboose.into());
}

let caboose_range = self.find_caboose_range()?;
// By construction, the last word of the caboose is its size as a `u32`
let caboose_size: u32 =
unsafe { core::ptr::read_volatile((image_end - 4) as *const u32) };

let caboose_start = image_end.saturating_sub(caboose_size);
let caboose_range = if caboose_start < image_start {
// This branch will be encountered if there's no caboose, because
// then the nominal caboose size will be 0xFFFFFFFF, which will send
// us out of the bank2 region.
return Err(CabooseError::MissingCaboose.into());
} else {
// SAFETY: we know this pointer is within the bank2 flash region,
// since it's checked above.
let v = unsafe {
core::ptr::read_volatile(caboose_start as *const u32)
};
if v == CABOOSE_MAGIC {
caboose_start + 4..image_end - 4
} else {
return Err(CabooseError::MissingCaboose.into());
}
};

// SAFETY: this is a slice within the bank2 flash
let caboose = unsafe {
Expand Down
Loading