diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index 86c4e98..3a35405 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -115,6 +115,8 @@ fn main() -> ! { let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks); 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( @@ -123,15 +125,6 @@ fn main() -> ! { ) } }; - let mut first_four_bytes: [u8; 4] = [0; 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) &mut first_four_bytes as *mut [u8; 4], // Input: destination pointer - ); - } let mut digest = CRC_ALGO.digest(); digest.update(&first_four_bytes); digest.update(bootloader_data); @@ -173,17 +166,22 @@ fn main() -> ! { } fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) { - let crc_exp = unsafe { *(BOOTLOADER_CRC_ADDR as *const u32) }; + let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u32).read_unaligned().to_be() }; wdt.feed(); // 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 crc_calc = CRC_ALGO.checksum(unsafe { + 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); + digest.update(unsafe { &*core::ptr::slice_from_raw_parts( - BOOTLOADER_START_ADDR as *const u8, - (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4) as usize, + (BOOTLOADER_START_ADDR + 4) as *const u8, + (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize, ) }); + let crc_calc = digest.finalize(); wdt.feed(); if crc_exp == 0x0000 || crc_exp == 0xffff { if DEBUG_PRINTOUTS { @@ -191,18 +189,33 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) { } // Blank CRC, write it to NVM. nvm.write_data(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes()); - // The Vorago bootloader resets here. I am not sure why this is done, just continue with - // the regular boot process.. + // 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. + // cortex_m::peripheral::SCB::sys_reset(); } else if crc_exp != crc_calc { // Bootloader is corrupted. Try to run App A. if DEBUG_PRINTOUTS { - rprintln!("bootloader CRC corrupt. booting image A immediately"); + rprintln!( + "bootloader CRC corrupt, read {} and expected {}. booting image A immediately", + crc_calc, + crc_exp + ); } // TODO: Shift out minimal CCSDS frame to notify about bootloader corruption. boot_app(AppSel::A, cp); } } +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_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool { if DEBUG_PRINTOUTS { rprintln!("Checking image {:?}", app_sel); @@ -220,8 +233,13 @@ fn check_app_given_addr( image_size_addr: u32, wdt: &OptWdt, ) -> bool { - let crc_exp = unsafe { *(crc_addr as *const u32) }; - let image_size = unsafe { *(image_size_addr as *const u32) }; + let crc_exp = unsafe { (crc_addr as *const u32).read_unaligned().to_be() }; + let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() }; + // Sanity check. + if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { + rprintln!("detected invalid app size {}", image_size); + return false; + } wdt.feed(); let crc_calc = CRC_ALGO.checksum(unsafe { core::slice::from_raw_parts(start_addr as *const u8, image_size as usize) @@ -234,6 +252,9 @@ fn check_app_given_addr( } fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! { + if DEBUG_PRINTOUTS { + rprintln!("booting app {:?}", app_sel); + } let clkgen = unsafe { pac::Clkgen::steal() }; clkgen .ctrl0() diff --git a/flashloader/image-loader.py b/flashloader/image-loader.py index 55014e7..d010e63 100755 --- a/flashloader/image-loader.py +++ b/flashloader/image-loader.py @@ -5,6 +5,7 @@ import struct import logging import argparse import time +import enum from tmtccmd.com.serial_base import SerialCfg from tmtccmd.com.serial_cobs import SerialCobsComIF from tmtccmd.com.ser_utils import prompt_com_port @@ -34,9 +35,17 @@ APP_IMG_SZ = 0x1E000 CHUNK_SIZE = 896 MEMORY_SERVICE = 6 +ACTION_SERVICE = 8 + RAW_MEMORY_WRITE_SUBSERVICE = 2 BOOT_NVM_MEMORY_ID = 1 + +class ActionId(enum.IntEnum): + CORRUPT_APP_A = 128 + CORRUPT_APP_B = 129 + + _LOGGER = logging.getLogger(__name__) @@ -57,11 +66,12 @@ def main() -> int: prog="image-loader", description="Python VA416XX Image Loader Application" ) parser.add_argument("-p", "--ping", action="store_true", help="Send ping command") + parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target") parser.add_argument( - "-f", - "--flash", + "-t", + "--target", choices=["bl", "a", "b"], - help="Flash target (Bootloader or slot A or B)", + help="Target (Bootloader or slot A or B)", ) parser.add_argument( "path", nargs="?", default=None, help="Path to the App to flash" @@ -84,19 +94,40 @@ def main() -> int: com_if = SerialCobsComIF(serial_cfg) com_if.open() file_path = None - if args.flash: - if not args.path: - _LOGGER.error("App Path needs to be specified for the flash process") - return -1 - file_path = Path(args.path) - if not file_path.exists(): - _LOGGER.error("File does not exist") - return -1 - ping_tc = PusTc(apid=0x00, service=PusService.S17_TEST, subservice=1) + if args.target: + if not args.corrupt: + if not args.path: + _LOGGER.error("App Path needs to be specified for the flash process") + return -1 + file_path = Path(args.path) + if not file_path.exists(): + _LOGGER.error("File does not exist") + return -1 if args.ping: _LOGGER.info("Sending ping command") + ping_tc = PusTc(apid=0x00, service=PusService.S17_TEST, subservice=1) com_if.send(ping_tc.pack()) - if args.flash: + if args.corrupt: + if not args.target: + _LOGGER.error("target for corruption command required") + return -1 + if args.target == "bl": + _LOGGER.error("can not corrupt bootloader") + if args.target == "a": + packet = PusTc( + apid=0, + service=ACTION_SERVICE, + subservice=ActionId.CORRUPT_APP_A, + ) + com_if.send(packet.pack()) + if args.target == "b": + packet = PusTc( + apid=0, + service=ACTION_SERVICE, + subservice=ActionId.CORRUPT_APP_B, + ) + com_if.send(packet.pack()) + else: assert file_path is not None loadable_segments = [] _LOGGER.info("Parsing ELF file for loadable sections") @@ -110,19 +141,19 @@ def main() -> int: # Basic validity checks of the base addresses. if idx == 0: if ( - args.flash == "bl" + args.target == "bl" and segment.header.p_paddr != BOOTLOADER_START_ADDR ): raise ValueError( f"detected possibly invalid start address {segment.header.p_paddr:#08x} for " f"bootloader, expected {BOOTLOADER_START_ADDR}" ) - if args.flash == "a" and segment.header.p_paddr != APP_A_START_ADDR: + if args.target == "a" and segment.header.p_paddr != APP_A_START_ADDR: raise ValueError( f"detected possibly invalid start address {segment.header.p_paddr:#08x} for " f"App A, expected {APP_A_START_ADDR}" ) - if args.flash == "b" and segment.header.p_paddr != APP_B_START_ADDR: + if args.target == "b" and segment.header.p_paddr != APP_B_START_ADDR: raise ValueError( f"detected possibly invalid start address {segment.header.p_paddr:#08x} for " f"App B, expected {APP_B_START_ADDR}" @@ -150,11 +181,11 @@ def main() -> int: ) total_size += segment.header.p_filesz context_str = None - if args.flash == "bl": + if args.target == "bl": context_str = "Bootloader" - elif args.flash == "a": + elif args.target == "a": context_str = "App Slot A" - elif args.flash == "b": + elif args.target == "b": context_str = "App Slot B" _LOGGER.info( f"Flashing {context_str} with image {file_path} (size {total_size})" @@ -181,7 +212,7 @@ def main() -> int: current_addr += next_chunk_size pos_in_segment += next_chunk_size time.sleep(0.2) - if args.flash == "bl": + if args.target == "bl": _LOGGER.info("Blanking the bootloader checksum") # Blank the checksum. For the bootloader, the bootloader will calculate the # checksum itself on the initial run. @@ -192,10 +223,10 @@ def main() -> int: else: crc_addr = None size_addr = None - if args.flash == "a": + if args.target == "a": crc_addr = APP_A_CRC_ADDR size_addr = APP_A_SIZE_ADDR - elif args.flash == "b": + elif args.target == "b": crc_addr = APP_B_CRC_ADDR size_addr = APP_B_SIZE_ADDR assert crc_addr is not None diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index 27e878f..b1b4b35 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -33,6 +33,10 @@ const COBS_RX_DEBUGGING: bool = false; const BOOT_NVM_MEMORY_ID: u8 = 1; +pub enum ActionId { + CorruptImageA = 128, + CorruptImageB = 129, +} pub trait WdtInterface { fn feed(&self); } @@ -49,6 +53,11 @@ impl WdtInterface for OptWdt { static CLOCKS: OnceCell = OnceCell::new(); +pub const APP_A_START_ADDR: u32 = 0x4000; +pub const APP_A_END_ADDR: u32 = 0x22000; +pub const APP_B_START_ADDR: u32 = 0x22000; +pub const APP_B_END_ADDR: u32 = 0x40000; + #[rtic::app(device = pac, dispatchers = [U1, U2, U3])] mod app { use super::*; @@ -303,6 +312,31 @@ mod app { cx.shared.decode_buffer_busy.lock(|busy| *busy = false); match PusTcReader::new(cx.local.read_buf) { Ok((pus_tc, _)) => { + if pus_tc.service() == PusServiceId::Action as u8 { + let mut corrupt_image = |base_addr: u32| { + // Safety: We only use this for NVM handling and we only do NVM + // handling here. + let mut sys_cfg = unsafe { pac::Sysconfig::steal() }; + let nvm = Nvm::new( + &mut sys_cfg, + cx.local.rom_spi.take().unwrap(), + CLOCKS.get().as_ref().unwrap(), + ); + let mut buf = [0u8; 4]; + nvm.read_data(base_addr + 32, &mut buf); + buf[0] += 1; + nvm.write_data(base_addr + 32, &buf); + *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); + }; + if pus_tc.subservice() == ActionId::CorruptImageA as u8 { + rprintln!("corrupting App Image A"); + corrupt_image(APP_A_START_ADDR); + } + if pus_tc.subservice() == ActionId::CorruptImageB as u8 { + rprintln!("corrupting App Image B"); + corrupt_image(APP_B_START_ADDR); + } + } if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { log::info!(target: "TC Handler", "received ping TC"); } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {