Add embassy-boot
Embassy-boot is a simple bootloader that works together with an application to provide firmware update capabilities with a minimal risk. The bootloader consists of a platform-independent part, which implements the swap algorithm, and a platform-dependent part (currently only for nRF) that provides addition functionality such as watchdog timers softdevice support.
This commit is contained in:
committed by
Ulf Lilleengen
parent
d91bd0b9a6
commit
ed2a87a262
225
embassy-boot/nrf/src/fmt.rs
Normal file
225
embassy-boot/nrf/src/fmt.rs
Normal file
@ -0,0 +1,225 @@
|
||||
#![macro_use]
|
||||
#![allow(unused_macros)]
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
203
embassy-boot/nrf/src/lib.rs
Normal file
203
embassy-boot/nrf/src/lib.rs
Normal file
@ -0,0 +1,203 @@
|
||||
#![no_std]
|
||||
#![feature(generic_associated_types)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{FirmwareUpdater, Partition, State, BOOT_MAGIC};
|
||||
use embassy_nrf::{
|
||||
nvmc::{Nvmc, PAGE_SIZE},
|
||||
peripherals::WDT,
|
||||
wdt,
|
||||
};
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
pub struct BootLoader {
|
||||
boot: embassy_boot::BootLoader<PAGE_SIZE>,
|
||||
}
|
||||
|
||||
impl BootLoader {
|
||||
/// Create a new bootloader instance using parameters from linker script
|
||||
pub fn default() -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_active_start: u32;
|
||||
static __bootloader_active_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let active = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_active_start as *const u32 as usize,
|
||||
&__bootloader_active_end as *const u32 as usize,
|
||||
)
|
||||
};
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as usize,
|
||||
&__bootloader_dfu_end as *const u32 as usize,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as usize,
|
||||
&__bootloader_state_end as *const u32 as usize,
|
||||
)
|
||||
};
|
||||
|
||||
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||
|
||||
Self::new(active, dfu, state)
|
||||
}
|
||||
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boots the application without softdevice mechanisms
|
||||
pub fn prepare<F: NorFlash + ReadNorFlash>(&mut self, flash: &mut F) -> usize {
|
||||
match self.boot.prepare_boot(flash) {
|
||||
Ok(_) => self.boot.boot_address(),
|
||||
Err(_) => panic!("boot prepare error!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "softdevice"))]
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
let mut p = cortex_m::Peripherals::steal();
|
||||
p.SCB.invalidate_icache();
|
||||
p.SCB.vtor.write(start as u32);
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
|
||||
#[cfg(feature = "softdevice")]
|
||||
pub unsafe fn load(&mut self, _app: usize) -> ! {
|
||||
use nrf_softdevice_mbr as mbr;
|
||||
const NRF_SUCCESS: u32 = 0;
|
||||
|
||||
// Address of softdevice which we'll forward interrupts to
|
||||
let addr = 0x1000;
|
||||
let mut cmd = mbr::sd_mbr_command_t {
|
||||
command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET,
|
||||
params: mbr::sd_mbr_command_t__bindgen_ty_1 {
|
||||
irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t {
|
||||
address: addr,
|
||||
},
|
||||
},
|
||||
};
|
||||
let ret = mbr::sd_mbr_command(&mut cmd);
|
||||
assert_eq!(ret, NRF_SUCCESS);
|
||||
|
||||
let msp = *(addr as *const u32);
|
||||
let rv = *((addr + 4) as *const u32);
|
||||
|
||||
trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv);
|
||||
|
||||
core::arch::asm!(
|
||||
"mrs {tmp}, CONTROL",
|
||||
"bics {tmp}, {spsel}",
|
||||
"msr CONTROL, {tmp}",
|
||||
"isb",
|
||||
"msr MSP, {msp}",
|
||||
"mov lr, {new_lr}",
|
||||
"bx {rv}",
|
||||
// `out(reg) _` is not permitted in a `noreturn` asm! call,
|
||||
// so instead use `in(reg) 0` and don't restore it afterwards.
|
||||
tmp = in(reg) 0,
|
||||
spsel = in(reg) 2,
|
||||
new_lr = in(reg) 0xFFFFFFFFu32,
|
||||
msp = in(reg) msp,
|
||||
rv = in(reg) rv,
|
||||
options(noreturn),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A flash implementation that wraps NVMC and will pet a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<'d> {
|
||||
flash: Nvmc<'d>,
|
||||
wdt: wdt::WatchdogHandle,
|
||||
}
|
||||
|
||||
impl<'d> WatchdogFlash<'d> {
|
||||
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
|
||||
pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self {
|
||||
let mut config = wdt::Config::default();
|
||||
config.timeout_ticks = 32768 * timeout; // timeout seconds
|
||||
config.run_during_sleep = true;
|
||||
config.run_during_debug_halt = false;
|
||||
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
// In case the watchdog is already running, just spin and let it expire, since
|
||||
// we can't configure it anyway. This usually happens when we first program
|
||||
// the device and the watchdog was previously active
|
||||
info!("Watchdog already active with wrong config, waiting for it to timeout...");
|
||||
loop {}
|
||||
}
|
||||
};
|
||||
Self { flash, wdt }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ErrorType for WatchdogFlash<'d> {
|
||||
type Error = <Nvmc<'d> as ErrorType>::Error;
|
||||
}
|
||||
|
||||
impl<'d> NorFlash for WatchdogFlash<'d> {
|
||||
const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
self.flash.erase(from, to)
|
||||
}
|
||||
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
self.flash.write(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ReadNorFlash for WatchdogFlash<'d> {
|
||||
const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE;
|
||||
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
self.flash.read(offset, data)
|
||||
}
|
||||
fn capacity(&self) -> usize {
|
||||
self.flash.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod updater {
|
||||
use super::*;
|
||||
pub fn new() -> embassy_boot::FirmwareUpdater {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as usize,
|
||||
&__bootloader_dfu_end as *const u32 as usize,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as usize,
|
||||
&__bootloader_state_end as *const u32 as usize,
|
||||
)
|
||||
};
|
||||
embassy_boot::FirmwareUpdater::new(dfu, state)
|
||||
}
|
||||
}
|
46
embassy-boot/nrf/src/main.rs
Normal file
46
embassy-boot/nrf/src/main.rs
Normal file
@ -0,0 +1,46 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m_rt::{entry, exception};
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
use defmt_rtt as _;
|
||||
|
||||
use embassy_boot_nrf::*;
|
||||
use embassy_nrf::nvmc::Nvmc;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_nrf::init(Default::default());
|
||||
/*
|
||||
for i in 0..10000000 {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
*/
|
||||
|
||||
let mut bl = BootLoader::default();
|
||||
let start = bl.prepare(&mut WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5));
|
||||
unsafe { bl.load(start) }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
|
||||
unsafe extern "C" fn HardFault() {
|
||||
cortex_m::peripheral::SCB::sys_reset();
|
||||
}
|
||||
|
||||
#[exception]
|
||||
unsafe fn DefaultHandler(_: i16) -> ! {
|
||||
const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
|
||||
let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16;
|
||||
|
||||
panic!("DefaultHandler #{:?}", irqn);
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
unsafe {
|
||||
core::arch::asm!("udf #0");
|
||||
core::hint::unreachable_unchecked();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user