bootloader and flashloader update

This commit is contained in:
Robin Müller 2025-01-10 15:14:18 +01:00
parent 35527f092a
commit 1664fb5bef
7 changed files with 133 additions and 38 deletions

View File

@ -11,6 +11,7 @@ panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" } panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
crc = "3" crc = "3"
num_enum = { version = "0.7", default-features = false }
static_assertions = "1" static_assertions = "1"
[dependencies.va108xx-hal] [dependencies.va108xx-hal]

View File

@ -9,14 +9,15 @@ The bootloader uses the following memory map:
| Address | Notes | Size | | Address | Notes | Size |
| ------ | ---- | ---- | | ------ | ---- | ---- |
| 0x0 | Bootloader start | code up to 0x3FFC bytes | | 0x0 | Bootloader start | code up to 0x2FFE bytes |
| 0x2FFE | Bootloader CRC | word | | 0x2FFE | Bootloader CRC | half-word |
| 0x3000 | App image A start | code up to 0xE7F8 (~58K) bytes | | 0x3000 | App image A start | code up to 0xE7F4 (~59K) bytes |
| 0x117F8 | App image A CRC check length | word | | 0x117F8 | App image A CRC check length | word |
| 0x117FC | App image A CRC check value | word | | 0x117FC | App image A CRC check value | word |
| 0x11800 | App image B start | code up to 0xE7F8 (~58K) bytes | | 0x117FC | App image B start | code up to 0xE7F4 (~59K) bytes |
| 0x1FFF8 | App image B CRC check length | word | | 0x1FFF0 | App image B CRC check length | word |
| 0x1FFFC | App image B CRC check value | word | | 0x1FFF4 | App image B CRC check value | word |
| 0x1FFF8 | Reserved section, contains boot select parameter | 8 bytes |
| 0x20000 | End of NVM | end | | 0x20000 | End of NVM | end |
## Additional Information ## Additional Information

View File

@ -5,6 +5,7 @@ use bootloader::NvmInterface;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use crc::{Crc, CRC_16_IBM_3740}; use crc::{Crc, CRC_16_IBM_3740};
use embedded_hal::delay::DelayNs; use embedded_hal::delay::DelayNs;
use num_enum::TryFromPrimitive;
#[cfg(not(feature = "rtt-panic"))] #[cfg(not(feature = "rtt-panic"))]
use panic_halt as _; use panic_halt as _;
#[cfg(feature = "rtt-panic")] #[cfg(feature = "rtt-panic")]
@ -59,8 +60,9 @@ 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. // Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
// 0x1FFFC // 0x1FFFC
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4; const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
// 0x20000 // 0x20000. 8 bytes at end of EEPROM reserved for preferred image parameter. This reserved
pub const APP_B_END_ADDR: u32 = NVM_SIZE; // size should be a multiple of 8 due to alignment requirements.
pub const APP_B_END_ADDR: u32 = NVM_SIZE - 8;
pub const APP_IMG_SZ: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2; 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); static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
@ -68,13 +70,15 @@ static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0
pub const VECTOR_TABLE_OFFSET: u32 = 0x0; pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0xC0; pub const VECTOR_TABLE_LEN: u32 = 0xC0;
pub const RESET_VECTOR_OFFSET: u32 = 0x4; pub const RESET_VECTOR_OFFSET: u32 = 0x4;
pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740); const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
enum AppSel { enum AppSel {
A, A = 0,
B, B = 1,
} }
pub struct NvmWrapper(pub M95M01); pub struct NvmWrapper(pub M95M01);
@ -154,10 +158,22 @@ fn main() -> ! {
// Check bootloader's CRC (and write it if blank) // Check bootloader's CRC (and write it if blank)
check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer); check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer);
if check_app_crc(AppSel::A) { let preferred_app = AppSel::try_from(unsafe {
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer) (PREFERRED_SLOT_OFFSET as *const u8)
} else if check_app_crc(AppSel::B) { .read_unaligned()
boot_app(&dp.sysconfig, &cp, AppSel::B, &mut timer) .to_be()
})
.unwrap_or(AppSel::A);
let other_app = if preferred_app == AppSel::A {
AppSel::B
} else {
AppSel::A
};
if check_app_crc(preferred_app) {
boot_app(&dp.sysconfig, &cp, preferred_app, &mut timer)
} else if check_app_crc(other_app) {
boot_app(&dp.sysconfig, &cp, other_app, &mut timer)
} else { } else {
if DEBUG_PRINTOUTS && RTT_PRINTOUT { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("both images corrupt! booting image A"); rprintln!("both images corrupt! booting image A");

View File

@ -11,6 +11,7 @@ embedded-hal-nb = "1"
embedded-io = "0.6" embedded-io = "0.6"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
num_enum = { version = "0.7", default-features = false }
log = "0.4" log = "0.4"
crc = "3" crc = "3"

View File

@ -30,20 +30,21 @@ BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 2
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2 BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2
APP_A_START_ADDR = 0x3000 APP_A_START_ADDR = 0x3000
APP_A_END_ADDR = 0x11800 APP_B_END_ADDR = 0x20000 - 8
IMG_SLOT_SIZE = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
APP_A_END_ADDR = APP_A_START_ADDR + IMG_SLOT_SIZE
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8 APP_A_SIZE_ADDR = APP_A_END_ADDR - 8
APP_A_CRC_ADDR = APP_A_END_ADDR - 4 APP_A_CRC_ADDR = APP_A_END_ADDR - 4
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8 APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_B_START_ADDR = APP_A_END_ADDR APP_B_START_ADDR = APP_A_END_ADDR
APP_B_END_ADDR = 0x20000
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8 APP_B_SIZE_ADDR = APP_B_END_ADDR - 8
APP_B_CRC_ADDR = APP_B_END_ADDR - 4 APP_B_CRC_ADDR = APP_B_END_ADDR - 4
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8 APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
CHUNK_SIZE = 400 CHUNK_SIZE = 400
@ -58,6 +59,7 @@ PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum): class ActionId(enum.IntEnum):
CORRUPT_APP_A = 128 CORRUPT_APP_A = 128
CORRUPT_APP_B = 129 CORRUPT_APP_B = 129
SET_BOOT_SLOT = 130
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -78,11 +80,37 @@ class Target(enum.Enum):
APP_B = 2 APP_B = 2
class AppSel(enum.IntEnum):
APP_A = 0
APP_B = 1
class ImageLoader: class ImageLoader:
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None: def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
self.com_if = com_if self.com_if = com_if
self.verificator = verificator self.verificator = verificator
def handle_boot_sel_cmd(self, target: AppSel):
_LOGGER.info("Sending ping command")
action_tc = PusTc(
apid=0x00,
service=PusService.S8_FUNC_CMD,
subservice=ActionId.SET_BOOT_SLOT,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes([target]),
)
self.verificator.add_tc(action_tc)
self.com_if.send(bytes(action_tc.pack()))
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no reply received for boot image selection command")
for reply in self.com_if.receive():
result = self.verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received boot image selection command confirmation")
def handle_ping_cmd(self): def handle_ping_cmd(self):
_LOGGER.info("Sending ping command") _LOGGER.info("Sending ping command")
ping_tc = PusTc( ping_tc = PusTc(
@ -106,7 +134,6 @@ class ImageLoader:
_LOGGER.info("received ping completion reply") _LOGGER.info("received ping completion reply")
def handle_corruption_cmd(self, target: Target): def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER: if target == Target.BOOTLOADER:
_LOGGER.error("can not corrupt bootloader") _LOGGER.error("can not corrupt bootloader")
if target == Target.APP_A: if target == Target.APP_A:
@ -131,7 +158,8 @@ class ImageLoader:
_LOGGER.info("Parsing ELF file for loadable sections") _LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0 total_size = 0
loadable_segments, total_size = create_loadable_segments(target, file_path) loadable_segments, total_size = create_loadable_segments(target, file_path)
segments_info_str(target, loadable_segments, total_size, file_path) check_segments(target, total_size)
print_segments_info(target, loadable_segments, total_size, file_path)
result = self._perform_flashing_algorithm(loadable_segments) result = self._perform_flashing_algorithm(loadable_segments)
if result != 0: if result != 0:
return result return result
@ -251,6 +279,9 @@ def main() -> int:
prog="image-loader", description="Python VA416XX Image Loader Application" prog="image-loader", description="Python VA416XX Image Loader Application"
) )
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command") parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
parser.add_argument(
"-s", "--sel", choices=["a", "b"], help="Set boot slot (Slot A or B)"
)
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target") parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
parser.add_argument( parser.add_argument(
"-t", "-t",
@ -286,6 +317,14 @@ def main() -> int:
target = Target.APP_A target = Target.APP_A
elif args.target == "b": elif args.target == "b":
target = Target.APP_B target = Target.APP_B
boot_sel = None
if args.sel:
if args.sel == "a":
boot_sel = AppSel.APP_A
elif args.sel == "b":
boot_sel = AppSel.APP_B
image_loader = ImageLoader(com_if, verificator) image_loader = ImageLoader(com_if, verificator)
file_path = None file_path = None
result = -1 result = -1
@ -293,6 +332,8 @@ def main() -> int:
image_loader.handle_ping_cmd() image_loader.handle_ping_cmd()
com_if.close() com_if.close()
return 0 return 0
if args.sel and boot_sel is not None:
image_loader.handle_boot_sel_cmd(boot_sel)
if target: if target:
if not args.corrupt: if not args.corrupt:
if not args.path: if not args.path:
@ -307,7 +348,7 @@ def main() -> int:
return -1 return -1
image_loader.handle_corruption_cmd(target) image_loader.handle_corruption_cmd(target)
else: else:
assert file_path is not None if file_path is not None:
assert target is not None assert target is not None
result = image_loader.handle_flash_cmd(target, file_path) result = image_loader.handle_flash_cmd(target, file_path)
@ -377,7 +418,22 @@ def create_loadable_segments(
return loadable_segments, total_size return loadable_segments, total_size
def segments_info_str( def check_segments(
target: Target,
total_size: int,
):
# Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER and total_size > BOOTLOADER_MAX_SIZE:
raise ValueError(
f"provided bootloader app larger than allowed {total_size} bytes"
)
elif target == Target.APP_A and total_size > APP_A_MAX_SIZE:
raise ValueError(f"provided App A larger than allowed {total_size} bytes")
elif target == Target.APP_B and total_size > APP_B_MAX_SIZE:
raise ValueError(f"provided App B larger than allowed {total_size} bytes")
def print_segments_info(
target: Target, target: Target,
loadable_segments: List[LoadableSegment], loadable_segments: List[LoadableSegment],
total_size: int, total_size: int,
@ -385,21 +441,10 @@ def segments_info_str(
): ):
# Set context string and perform basic sanity checks. # Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER: if target == Target.BOOTLOADER:
if total_size > BOOTLOADER_MAX_SIZE:
_LOGGER.error(
f"provided bootloader app larger than allowed {total_size} bytes"
)
return -1
context_str = "Bootloader" context_str = "Bootloader"
elif target == Target.APP_A: elif target == Target.APP_A:
if total_size > APP_A_MAX_SIZE:
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes")
return -1
context_str = "App Slot A" context_str = "App Slot A"
elif target == Target.APP_B: elif target == Target.APP_B:
if total_size > APP_B_MAX_SIZE:
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes")
return -1
context_str = "App Slot B" context_str = "App Slot B"
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})") _LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
for idx, segment in enumerate(loadable_segments): for idx, segment in enumerate(loadable_segments):

View File

@ -1,7 +1,7 @@
/* Special linker script for application slot B with an offset at address 0x11800 */ /* Special linker script for application slot B */
MEMORY MEMORY
{ {
FLASH : ORIGIN = 0x00011800, LENGTH = 0xE800 FLASH : ORIGIN = 0x000117FC, LENGTH = 0xE800
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */ RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 32K */
} }

View File

@ -3,6 +3,7 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use num_enum::TryFromPrimitive;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use panic_rtt_target as _; use panic_rtt_target as _;
use ringbuf::{ use ringbuf::{
@ -26,6 +27,14 @@ const RX_DEBUGGING: bool = false;
pub enum ActionId { pub enum ActionId {
CorruptImageA = 128, CorruptImageA = 128,
CorruptImageB = 129, CorruptImageB = 129,
SetBootSlot = 130,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
enum AppSel {
A = 0,
B = 1,
} }
// Larger buffer for TC to be able to hold the possibly large memory write packets. // Larger buffer for TC to be able to hold the possibly large memory write packets.
@ -58,10 +67,12 @@ pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
} }
pub const APP_A_START_ADDR: u32 = 0x3000; pub const APP_A_START_ADDR: u32 = 0x3000;
pub const APP_A_END_ADDR: u32 = 0x11800; pub const APP_A_END_ADDR: u32 = 0x117FC;
pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR; pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
pub const APP_B_END_ADDR: u32 = 0x20000; pub const APP_B_END_ADDR: u32 = 0x20000;
pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
#[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])] #[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])]
mod app { mod app {
use super::*; use super::*;
@ -346,6 +357,26 @@ mod app {
rprintln!("corrupting App Image B"); rprintln!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR); corrupt_image(APP_B_START_ADDR);
} }
if pus_tc.subservice() == ActionId::SetBootSlot as u8 {
if pus_tc.app_data().is_empty() {
log::warn!(target: "TC Handler", "App data for preferred image command too short");
}
let app_sel_result = AppSel::try_from(pus_tc.app_data()[0]);
if app_sel_result.is_err() {
log::warn!("Invalid app selection value: {}", pus_tc.app_data()[0]);
}
log::info!(target: "TC Handler", "received boot selection command with app select: {:?}", app_sel_result.unwrap());
cx.local
.nvm
.write(PREFERRED_SLOT_OFFSET as usize, &[pus_tc.app_data()[0]])
.expect("writing to NVM failed");
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
}
} }
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "received ping TC"); log::info!(target: "TC Handler", "received ping TC");