Skip to content

Commit cd386ed

Browse files
committed
Finished flashloader and bootloader implementation
1 parent e2a55e7 commit cd386ed

40 files changed

+2753
-979
lines changed

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ members = [
99
"examples/embassy",
1010
"board-tests",
1111
"bootloader",
12+
"flashloader",
1213
]
13-
1414
exclude = [
1515
"defmt-testapp",
16+
"flashloader/slot-a-blinky",
17+
"flashloader/slot-b-blinky",
1618
]
1719

1820
[profile.dev]
1921
codegen-units = 1
2022
debug = 2
2123
debug-assertions = true # <-
2224
incremental = false
23-
# This is problematic for stepping..
24-
# opt-level = 'z' # <-
25+
# 1 instead of 0, the flashloader is too larger otherwise..
26+
# opt-level = 1 # <-
2527
overflow-checks = true # <-
2628

2729
# cargo build/run --release

board-tests/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use va108xx_hal::{
1717
pac::{self, interrupt},
1818
prelude::*,
1919
time::Hertz,
20-
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, IrqCfg},
20+
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, IrqCfg},
2121
};
2222

2323
#[allow(dead_code)]
@@ -168,7 +168,7 @@ fn main() -> ! {
168168
ms_timer.delay_ms(500);
169169
}
170170

171-
let mut delay_timer = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
171+
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
172172
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
173173
for _ in 0..5 {
174174
led1.toggle().ok();

bootloader/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ edition = "2021"
77
cortex-m = "0.7"
88
cortex-m-rt = "0.7"
99
embedded-hal = "1"
10-
embedded-hal-bus = "0.2"
11-
dummy-pin = "1"
1210
panic-rtt-target = { version = "0.1.3" }
1311
panic-halt = { version = "0.2" }
1412
rtt-target = { version = "0.5" }
1513
crc = "3"
14+
static_assertions = "1"
1615

1716
[dependencies.va108xx-hal]
1817
path = "../va108xx-hal"

bootloader/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
VA416xx Bootloader Application
2+
=======
3+
4+
This is the Rust version of the bootloader supplied by Vorago.
5+
6+
## Memory Map
7+
8+
The bootloader uses the following memory map:
9+
10+
| Address | Notes | Size |
11+
| ------ | ---- | ---- |
12+
| 0x0 | Bootloader start | code up to 0x3FFC bytes |
13+
| 0x2FFE | Bootloader CRC | word |
14+
| 0x3000 | App image A start | code up to 0x1DFFC (~120K) bytes |
15+
| 0x117F8 | App image A CRC check length | word |
16+
| 0x117FC | App image A CRC check value | word |
17+
| 0x11800 | App image B start | code up to 0x1DFFC (~120K) bytes |
18+
| 0x1FFF8 | App image B CRC check length | word |
19+
| 0x1FFFC | App image B CRC check value | word |
20+
| 0x20000 | End of NVM | end |
21+
22+
## Additional Information
23+
24+
This bootloader was specifically written for the REB1 board, so it assumes a M95M01 ST EEPROM
25+
is used to load the application code. The bootloader will also delay for a configurable amount
26+
of time before booting. This allows to catch the RTT printout, but should probably be disabled
27+
for production firmware.
28+
29+
This bootloader does not provide tools to flash the NVM memory by itself. Instead, you can use
30+
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader)
31+
application to perform this task using a CCSDS interface via a UART.
32+
33+
The bootloader performs the following steps:
34+
35+
1. The application will calculate the checksum of itself if the bootloader CRC is blank (all zeroes
36+
or all ones). If the CRC is not blank and the checksum check fails, it will immediately boot
37+
application image A. Otherwise, it proceeds to the next step.
38+
2. Check the checksum of App A. If that checksum is valid, it will boot App A. If not, it will
39+
proceed to the next step.
40+
3. Check the checksum of App B. If that checksum is valid, it will boot App B. If not, it will
41+
boot App A as the fallback image.
42+
43+
You could adapt and combine this bootloader with a non-volatile memory to select a prefered app
44+
image, which would be a first step towards an updatable flight software.
45+
46+
Please note that you *MUST* compile the application at slot A and slot B with an appropriate
47+
`memory.x` file where the base address of the `FLASH` was adapted according to the base address
48+
shown in the memory map above. The memory files to do this were provided in the `scripts` folder.

bootloader/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::convert::Infallible;
44

55
/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface.
66
pub trait NvmInterface {
7-
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible>;
8-
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible>;
9-
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, Infallible>;
7+
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Infallible>;
8+
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), Infallible>;
9+
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, Infallible>;
1010
}

bootloader/src/main.rs

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
use bootloader::NvmInterface;
55
use cortex_m_rt::entry;
66
use crc::{Crc, CRC_16_IBM_3740};
7+
use embedded_hal::delay::DelayNs;
78
#[cfg(not(feature = "rtt-panic"))]
89
use panic_halt as _;
910
#[cfg(feature = "rtt-panic")]
1011
use panic_rtt_target as _;
1112
use rtt_target::{rprintln, rtt_init_print};
12-
use va108xx_hal::{pac, time::Hertz};
13+
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
1314
use vorago_reb1::m95m01::M95M01;
1415

1516
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
1617
// the binary stays small enough.
1718
const RTT_PRINTOUT: bool = true;
18-
const DEBUG_PRINTOUTS: bool = false;
19+
const DEBUG_PRINTOUTS: bool = true;
20+
// Small delay, allows RTT printout to catch up.
21+
const BOOT_DELAY_MS: u32 = 2000;
1922

2023
// Dangerous option! An image with this option set to true will flash itself from RAM directly
2124
// 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);
3538

3639
// Important bootloader addresses and offsets, vector table information.
3740

41+
const NVM_SIZE: u32 = 0x20000;
3842
const BOOTLOADER_START_ADDR: u32 = 0x0;
3943
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
4044
// This is also the maximum size of the bootloader.
4145
const BOOTLOADER_END_ADDR: u32 = 0x3000;
42-
const APP_A_START_ADDR: u32 = 0x3000;
46+
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
47+
// 0x117F8
4348
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
4449
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
50+
// 0x117FC
4551
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
46-
pub const APP_A_END_ADDR: u32 = 0x11000;
52+
// 0x11800
53+
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + APP_IMG_SZ;
4754
// The actual size of the image which is relevant for CRC calculation.
48-
const APP_B_START_ADDR: u32 = 0x11000;
55+
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
4956
// The actual size of the image which is relevant for CRC calculation.
57+
// 0x1FFF8
5058
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
5159
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
60+
// 0x1FFFC
5261
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
53-
pub const APP_B_END_ADDR: u32 = 0x20000;
54-
pub const APP_IMG_SZ: u32 = 0xE800;
62+
// 0x20000
63+
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
64+
pub const APP_IMG_SZ: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2;
65+
66+
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
5567

5668
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
5769
pub const VECTOR_TABLE_LEN: u32 = 0xC0;
@@ -69,15 +81,15 @@ pub struct NvmWrapper(pub M95M01);
6981

7082
// Newtype pattern. We could now more easily swap the used NVM type.
7183
impl NvmInterface for NvmWrapper {
72-
fn write(&mut self, address: u32, data: &[u8]) -> Result<(), core::convert::Infallible> {
84+
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), core::convert::Infallible> {
7385
self.0.write(address, data)
7486
}
7587

76-
fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
88+
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
7789
self.0.read(address, buf)
7890
}
7991

80-
fn verify(&mut self, address: u32, data: &[u8]) -> Result<bool, core::convert::Infallible> {
92+
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, core::convert::Infallible> {
8193
self.0.verify(address, data)
8294
}
8395
}
@@ -90,6 +102,7 @@ fn main() -> ! {
90102
}
91103
let mut dp = pac::Peripherals::take().unwrap();
92104
let cp = cortex_m::Peripherals::take().unwrap();
105+
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
93106

94107
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
95108

@@ -124,9 +137,9 @@ fn main() -> ! {
124137
}
125138
}
126139

127-
nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes())
140+
nvm.write(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes())
128141
.expect("writing CRC failed");
129-
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
142+
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes()) {
130143
if RTT_PRINTOUT {
131144
rprintln!(
132145
"error: CRC verification for bootloader self-flash failed: {:?}",
@@ -139,23 +152,28 @@ fn main() -> ! {
139152
let mut nvm = NvmWrapper(nvm);
140153

141154
// Check bootloader's CRC (and write it if blank)
142-
check_own_crc(&dp.sysconfig, &cp, &mut nvm);
155+
check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer);
143156

144157
if check_app_crc(AppSel::A) {
145-
boot_app(&dp.sysconfig, &cp, AppSel::A)
158+
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
146159
} else if check_app_crc(AppSel::B) {
147-
boot_app(&dp.sysconfig, &cp, AppSel::B)
160+
boot_app(&dp.sysconfig, &cp, AppSel::B, &mut timer)
148161
} else {
149162
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
150163
rprintln!("both images corrupt! booting image A");
151164
}
152165
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
153166
// Both images seem to be corrupt. Boot default image A.
154-
boot_app(&dp.sysconfig, &cp, AppSel::A)
167+
boot_app(&dp.sysconfig, &cp, AppSel::A, &mut timer)
155168
}
156169
}
157170

158-
fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper) {
171+
fn check_own_crc(
172+
sysconfig: &pac::Sysconfig,
173+
cp: &cortex_m::Peripherals,
174+
nvm: &mut NvmWrapper,
175+
timer: &mut CountdownTimer<pac::Tim0>,
176+
) {
159177
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
160178
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
161179
// 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
176194
rprintln!("BL CRC blank - prog new CRC");
177195
}
178196
// Blank CRC, write it to NVM.
179-
nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes())
197+
nvm.write(BOOTLOADER_CRC_ADDR as usize, &crc_calc.to_be_bytes())
180198
.expect("writing CRC failed");
181199
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
182200
// 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
191209
);
192210
}
193211
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
194-
boot_app(sysconfig, cp, AppSel::A);
212+
boot_app(sysconfig, cp, AppSel::A, timer);
195213
}
196214
}
197215

@@ -240,43 +258,52 @@ fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) ->
240258

241259
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
242260
// base address in code RAM (0x0) and then performing a soft reset.
243-
fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! {
261+
fn boot_app(
262+
syscfg: &pac::Sysconfig,
263+
cp: &cortex_m::Peripherals,
264+
app_sel: AppSel,
265+
timer: &mut CountdownTimer<pac::Tim0>,
266+
) -> ! {
244267
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
245268
rprintln!("booting app {:?}", app_sel);
246269
}
270+
timer.delay_ms(BOOT_DELAY_MS);
271+
272+
// Clear all interrupts set.
273+
unsafe {
274+
cp.NVIC.icer[0].write(0xFFFFFFFF);
275+
cp.NVIC.icpr[0].write(0xFFFFFFFF);
276+
}
247277
// Disable ROM protection.
248-
syscfg.rom_prot().write(|w| unsafe { w.bits(1) });
278+
syscfg.rom_prot().write(|w| w.wren().set_bit());
249279
let base_addr = if app_sel == AppSel::A {
250280
APP_A_START_ADDR
251281
} else {
252282
APP_B_START_ADDR
253283
};
254-
// Clear all interrupts set.
255284
unsafe {
256-
cp.NVIC.icer[0].write(0xFFFFFFFF);
257-
cp.NVIC.icpr[0].write(0xFFFFFFFF);
258-
259285
// First 4 bytes done with inline assembly, writing to the physical address 0x0 can not
260286
// be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2.
261-
core::ptr::read(base_addr as *const u32);
287+
let first_four_bytes = core::ptr::read(base_addr as *const u32);
262288
core::arch::asm!(
263-
"str {0}, [{1}]", // Load 4 bytes from src into r0 register
264-
in(reg) base_addr, // Input: App vector table.
289+
"str {0}, [{1}]",
290+
in(reg) first_four_bytes, // Input: App vector table.
265291
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
266292
);
267293
core::slice::from_raw_parts_mut(
268-
(BOOTLOADER_START_ADDR + 4) as *mut u32,
294+
(BOOTLOADER_START_ADDR + 4) as *mut u8,
269295
(VECTOR_TABLE_LEN - 4) as usize,
270296
)
271297
.copy_from_slice(core::slice::from_raw_parts(
272-
(base_addr + 4) as *const u32,
298+
(base_addr + 4) as *const u8,
273299
(VECTOR_TABLE_LEN - 4) as usize,
274300
));
275301
}
276-
/* Disable re-loading from FRAM/code ROM on soft reset */
302+
// Disable re-loading from FRAM/code ROM on soft reset
277303
syscfg
278304
.rst_cntl_rom()
279305
.modify(|_, w| w.sysrstreq().clear_bit());
306+
280307
soft_reset(cp);
281308
}
282309

@@ -292,5 +319,8 @@ fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
292319
// Ensure completion of memory access.
293320
cortex_m::asm::dsb();
294321

295-
unreachable!();
322+
// Loop until the reset occurs.
323+
loop {
324+
cortex_m::asm::nop();
325+
}
296326
}

examples/rtic/Cargo.toml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,11 @@ cortex-m-rt = "0.7"
99
embedded-hal = "1"
1010
embedded-io = "0.6"
1111
rtt-target = { version = "0.5" }
12+
panic-rtt-target = { version = "0.1" }
13+
1214
# Even though we do not use this directly, we need to activate this feature explicitely
1315
# so that RTIC compiles because thumv6 does not have CAS operations natively.
1416
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
15-
panic-rtt-target = { version = "0.1" }
16-
17-
[dependencies.va108xx-hal]
18-
path = "../../va108xx-hal"
19-
20-
[dependencies.vorago-reb1]
21-
path = "../../vorago-reb1"
2217

2318
[dependencies.rtic]
2419
version = "2"
@@ -31,3 +26,20 @@ features = ["cortex-m-systick"]
3126
[dependencies.rtic-sync]
3227
version = "1.3"
3328
features = ["defmt-03"]
29+
30+
[dependencies.once_cell]
31+
version = "1"
32+
default-features = false
33+
features = ["critical-section"]
34+
35+
[dependencies.ringbuf]
36+
version = "0.4"
37+
git = "https://github.com/us-irs/ringbuf.git"
38+
branch = "use-portable-atomic-crate"
39+
default-features = false
40+
41+
[dependencies.va108xx-hal]
42+
path = "../../va108xx-hal"
43+
44+
[dependencies.vorago-reb1]
45+
path = "../../vorago-reb1"

0 commit comments

Comments
 (0)