Finished flashloader and bootloader implementation
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
This commit is contained in:
@ -4,18 +4,21 @@
|
||||
use bootloader::NvmInterface;
|
||||
use cortex_m_rt::entry;
|
||||
use crc::{Crc, CRC_16_IBM_3740};
|
||||
use embedded_hal::delay::DelayNs;
|
||||
#[cfg(not(feature = "rtt-panic"))]
|
||||
use panic_halt as _;
|
||||
#[cfg(feature = "rtt-panic")]
|
||||
use panic_rtt_target as _;
|
||||
use rtt_target::{rprintln, rtt_init_print};
|
||||
use va108xx_hal::{pac, time::Hertz};
|
||||
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
|
||||
use vorago_reb1::m95m01::M95M01;
|
||||
|
||||
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
|
||||
// the binary stays small enough.
|
||||
const RTT_PRINTOUT: bool = true;
|
||||
const DEBUG_PRINTOUTS: bool = false;
|
||||
const DEBUG_PRINTOUTS: bool = true;
|
||||
// Small delay, allows RTT printout to catch up.
|
||||
const BOOT_DELAY_MS: u32 = 2000;
|
||||
|
||||
// Dangerous option! An image with this option set to true will flash itself from RAM directly
|
||||
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
|
||||
@ -35,23 +38,32 @@ const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
|
||||
|
||||
// Important bootloader addresses and offsets, vector table information.
|
||||
|
||||
const NVM_SIZE: u32 = 0x20000;
|
||||
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
||||
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
|
||||
// This is also the maximum size of the bootloader.
|
||||
const BOOTLOADER_END_ADDR: u32 = 0x3000;
|
||||
const APP_A_START_ADDR: u32 = 0x3000;
|
||||
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
|
||||
// 0x117F8
|
||||
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
|
||||
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||
// 0x117FC
|
||||
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
|
||||
pub const APP_A_END_ADDR: u32 = 0x11000;
|
||||
// 0x11800
|
||||
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + APP_IMG_SZ;
|
||||
// The actual size of the image which is relevant for CRC calculation.
|
||||
const APP_B_START_ADDR: u32 = 0x11000;
|
||||
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
|
||||
// The actual size of the image which is relevant for CRC calculation.
|
||||
// 0x1FFF8
|
||||
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
||||
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
|
||||
// 0x1FFFC
|
||||
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
||||
pub const APP_B_END_ADDR: u32 = 0x20000;
|
||||
pub const APP_IMG_SZ: u32 = 0xE800;
|
||||
// 0x20000
|
||||
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
|
||||
pub const APP_IMG_SZ: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2;
|
||||
|
||||
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
|
||||
|
||||
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
||||
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
|
||||
@ -69,15 +81,15 @@ pub struct NvmWrapper(pub M95M01);
|
||||
|
||||
// Newtype pattern. We could now more easily swap the used NVM type.
|
||||
impl NvmInterface for NvmWrapper {
|
||||
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), core::convert::Infallible> {
|
||||
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), core::convert::Infallible> {
|
||||
self.0.write(address, data)
|
||||
}
|
||||
|
||||
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
|
||||
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
|
||||
self.0.read(address, buf)
|
||||
}
|
||||
|
||||
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, core::convert::Infallible> {
|
||||
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, core::convert::Infallible> {
|
||||
self.0.verify(address, data)
|
||||
}
|
||||
}
|
||||
@ -90,6 +102,7 @@ fn main() -> ! {
|
||||
}
|
||||
let mut dp = pac::Peripherals::take().unwrap();
|
||||
let cp = cortex_m::Peripherals::take().unwrap();
|
||||
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
|
||||
|
||||
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
|
||||
|
||||
@ -124,9 +137,9 @@ fn main() -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes())
|
||||
nvm.write(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes())
|
||||
.expect("writing CRC failed");
|
||||
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
|
||||
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes()) {
|
||||
if RTT_PRINTOUT {
|
||||
rprintln!(
|
||||
"error: CRC verification for bootloader self-flash failed: {:?}",
|
||||
@ -139,23 +152,28 @@ fn main() -> ! {
|
||||
let mut nvm = NvmWrapper(nvm);
|
||||
|
||||
// Check bootloader's CRC (and write it if blank)
|
||||
check_own_crc(&dp.sysconfig, &cp, &mut nvm);
|
||||
check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer);
|
||||
|
||||
if check_app_crc(AppSel::A) {
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::A)
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
|
||||
} else if check_app_crc(AppSel::B) {
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::B)
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::B, &mut timer)
|
||||
} else {
|
||||
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||
rprintln!("both images corrupt! booting image A");
|
||||
}
|
||||
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
||||
// Both images seem to be corrupt. Boot default image A.
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::A)
|
||||
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper) {
|
||||
fn check_own_crc(
|
||||
sysconfig: &pac::Sysconfig,
|
||||
cp: &cortex_m::Peripherals,
|
||||
nvm: &mut NvmWrapper,
|
||||
timer: &mut CountdownTimer<pac::Tim0>,
|
||||
) {
|
||||
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
|
||||
@ -176,7 +194,7 @@ fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &m
|
||||
rprintln!("BL CRC blank - prog new CRC");
|
||||
}
|
||||
// Blank CRC, write it to NVM.
|
||||
nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes())
|
||||
nvm.write(BOOTLOADER_CRC_ADDR as usize, &crc_calc.to_be_bytes())
|
||||
.expect("writing CRC failed");
|
||||
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
|
||||
// necessary because somehow the boot will not work if we just continue as usual.
|
||||
@ -191,7 +209,7 @@ fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &m
|
||||
);
|
||||
}
|
||||
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
||||
boot_app(sysconfig, cp, AppSel::A);
|
||||
boot_app(sysconfig, cp, AppSel::A, timer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,43 +258,52 @@ fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) ->
|
||||
|
||||
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
|
||||
// base address in code RAM (0x0) and then performing a soft reset.
|
||||
fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! {
|
||||
fn boot_app(
|
||||
syscfg: &pac::Sysconfig,
|
||||
cp: &cortex_m::Peripherals,
|
||||
app_sel: AppSel,
|
||||
timer: &mut CountdownTimer<pac::Tim0>,
|
||||
) -> ! {
|
||||
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
|
||||
rprintln!("booting app {:?}", app_sel);
|
||||
}
|
||||
timer.delay_ms(BOOT_DELAY_MS);
|
||||
|
||||
// Clear all interrupts set.
|
||||
unsafe {
|
||||
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
||||
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
||||
}
|
||||
// Disable ROM protection.
|
||||
syscfg.rom_prot().write(|w| unsafe { w.bits(1) });
|
||||
syscfg.rom_prot().write(|w| w.wren().set_bit());
|
||||
let base_addr = if app_sel == AppSel::A {
|
||||
APP_A_START_ADDR
|
||||
} else {
|
||||
APP_B_START_ADDR
|
||||
};
|
||||
// Clear all interrupts set.
|
||||
unsafe {
|
||||
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
||||
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
||||
|
||||
// First 4 bytes done with inline assembly, writing to the physical address 0x0 can not
|
||||
// be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2.
|
||||
core::ptr::read(base_addr as *const u32);
|
||||
let first_four_bytes = core::ptr::read(base_addr as *const u32);
|
||||
core::arch::asm!(
|
||||
"str {0}, [{1}]", // Load 4 bytes from src into r0 register
|
||||
in(reg) base_addr, // Input: App vector table.
|
||||
"str {0}, [{1}]",
|
||||
in(reg) first_four_bytes, // Input: App vector table.
|
||||
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
|
||||
);
|
||||
core::slice::from_raw_parts_mut(
|
||||
(BOOTLOADER_START_ADDR + 4) as *mut u32,
|
||||
(BOOTLOADER_START_ADDR + 4) as *mut u8,
|
||||
(VECTOR_TABLE_LEN - 4) as usize,
|
||||
)
|
||||
.copy_from_slice(core::slice::from_raw_parts(
|
||||
(base_addr + 4) as *const u32,
|
||||
(base_addr + 4) as *const u8,
|
||||
(VECTOR_TABLE_LEN - 4) as usize,
|
||||
));
|
||||
}
|
||||
/* Disable re-loading from FRAM/code ROM on soft reset */
|
||||
// Disable re-loading from FRAM/code ROM on soft reset
|
||||
syscfg
|
||||
.rst_cntl_rom()
|
||||
.modify(|_, w| w.sysrstreq().clear_bit());
|
||||
|
||||
soft_reset(cp);
|
||||
}
|
||||
|
||||
@ -292,5 +319,8 @@ fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
|
||||
// Ensure completion of memory access.
|
||||
cortex_m::asm::dsb();
|
||||
|
||||
unreachable!();
|
||||
// Loop until the reset occurs.
|
||||
loop {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user