aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-02-09 15:07:25 +0000
committerGitHub <[email protected]>2022-02-09 15:07:25 +0000
commit3d6b8bd9832d5a29cab4aa21434663e6ea6f4488 (patch)
tree5e457a46c63f9565e82b9dd401701cbd7aba20b7
parentd91bd0b9a69b8411f2a1d58bfad5d4dce51e7110 (diff)
parente990021b9a9d3acc309c21bd4ddf3ff090bb7999 (diff)
Merge #604
604: Add embassy-boot r=lulf a=lulf Continuation of https://github.com/embassy-rs/embassy/pull/588 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. The bootloader is intended to be configurable for different flash sizes and architectures, and only requires that embedded-storage flash traits are implemented. The nRF version can be configured programatically as a library, or using linker scripts to set the partition locations for DFU, ACTIVE and STATE * DFU: Where the next firmware version should be written. This is used by the FirmwareUpdater * ACTIVE: Where the current firmware version resides. Written by bootloader when swap magic is set * STATE: Contains the bootloader magic and the copy progress. Can be 1-N pages long (depending on how much flash you have which will determine the copy progress index size Co-authored-by: Ulf Lilleengen <[email protected]> Co-authored-by: Ulf Lilleengen <[email protected]>
-rw-r--r--embassy-boot/boot/Cargo.toml23
-rw-r--r--embassy-boot/boot/src/fmt.rs225
-rw-r--r--embassy-boot/boot/src/lib.rs550
-rw-r--r--embassy-boot/nrf/.cargo/config.toml18
-rw-r--r--embassy-boot/nrf/Cargo.toml65
-rw-r--r--embassy-boot/nrf/README.md11
-rw-r--r--embassy-boot/nrf/build.rs37
-rw-r--r--embassy-boot/nrf/memory-bm.x18
-rw-r--r--embassy-boot/nrf/memory-s140.x31
-rw-r--r--embassy-boot/nrf/memory.x18
-rw-r--r--embassy-boot/nrf/src/fmt.rs225
-rw-r--r--embassy-boot/nrf/src/lib.rs210
-rw-r--r--embassy-boot/nrf/src/main.rs49
-rw-r--r--embassy-traits/Cargo.toml2
-rw-r--r--embassy-traits/src/adapter.rs53
-rw-r--r--examples/boot/.cargo/config.toml7
-rw-r--r--examples/boot/Cargo.toml19
-rw-r--r--examples/boot/README.md31
-rw-r--r--examples/boot/build.rs34
-rw-r--r--examples/boot/memory.x14
-rw-r--r--examples/boot/src/bin/a.rs49
-rw-r--r--examples/boot/src/bin/b.rs26
22 files changed, 1715 insertions, 0 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml
new file mode 100644
index 000000000..0a3006ffc
--- /dev/null
+++ b/embassy-boot/boot/Cargo.toml
@@ -0,0 +1,23 @@
1[package]
2authors = [
3 "Ulf Lilleengen <[email protected]>",
4]
5edition = "2018"
6name = "embassy-boot"
7version = "0.1.0"
8description = "Bootloader using Embassy"
9
10[lib]
11
12[dependencies]
13defmt = { version = "0.3", optional = true }
14log = { version = "0.4", optional = true }
15embassy = { path = "../../embassy", default-features = false }
16embedded-storage = "0.3.0"
17embedded-storage-async = "0.3.0"
18
19[dev-dependencies]
20log = "0.4"
21env_logger = "0.9"
22rand = "0.8"
23futures = { version = "0.3", features = ["executor"] }
diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/boot/src/fmt.rs
new file mode 100644
index 000000000..066970813
--- /dev/null
+++ b/embassy-boot/boot/src/fmt.rs
@@ -0,0 +1,225 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4#[cfg(all(feature = "defmt", feature = "log"))]
5compile_error!("You may not enable both `defmt` and `log` features.");
6
7macro_rules! assert {
8 ($($x:tt)*) => {
9 {
10 #[cfg(not(feature = "defmt"))]
11 ::core::assert!($($x)*);
12 #[cfg(feature = "defmt")]
13 ::defmt::assert!($($x)*);
14 }
15 };
16}
17
18macro_rules! assert_eq {
19 ($($x:tt)*) => {
20 {
21 #[cfg(not(feature = "defmt"))]
22 ::core::assert_eq!($($x)*);
23 #[cfg(feature = "defmt")]
24 ::defmt::assert_eq!($($x)*);
25 }
26 };
27}
28
29macro_rules! assert_ne {
30 ($($x:tt)*) => {
31 {
32 #[cfg(not(feature = "defmt"))]
33 ::core::assert_ne!($($x)*);
34 #[cfg(feature = "defmt")]
35 ::defmt::assert_ne!($($x)*);
36 }
37 };
38}
39
40macro_rules! debug_assert {
41 ($($x:tt)*) => {
42 {
43 #[cfg(not(feature = "defmt"))]
44 ::core::debug_assert!($($x)*);
45 #[cfg(feature = "defmt")]
46 ::defmt::debug_assert!($($x)*);
47 }
48 };
49}
50
51macro_rules! debug_assert_eq {
52 ($($x:tt)*) => {
53 {
54 #[cfg(not(feature = "defmt"))]
55 ::core::debug_assert_eq!($($x)*);
56 #[cfg(feature = "defmt")]
57 ::defmt::debug_assert_eq!($($x)*);
58 }
59 };
60}
61
62macro_rules! debug_assert_ne {
63 ($($x:tt)*) => {
64 {
65 #[cfg(not(feature = "defmt"))]
66 ::core::debug_assert_ne!($($x)*);
67 #[cfg(feature = "defmt")]
68 ::defmt::debug_assert_ne!($($x)*);
69 }
70 };
71}
72
73macro_rules! todo {
74 ($($x:tt)*) => {
75 {
76 #[cfg(not(feature = "defmt"))]
77 ::core::todo!($($x)*);
78 #[cfg(feature = "defmt")]
79 ::defmt::todo!($($x)*);
80 }
81 };
82}
83
84macro_rules! unreachable {
85 ($($x:tt)*) => {
86 {
87 #[cfg(not(feature = "defmt"))]
88 ::core::unreachable!($($x)*);
89 #[cfg(feature = "defmt")]
90 ::defmt::unreachable!($($x)*);
91 }
92 };
93}
94
95macro_rules! panic {
96 ($($x:tt)*) => {
97 {
98 #[cfg(not(feature = "defmt"))]
99 ::core::panic!($($x)*);
100 #[cfg(feature = "defmt")]
101 ::defmt::panic!($($x)*);
102 }
103 };
104}
105
106macro_rules! trace {
107 ($s:literal $(, $x:expr)* $(,)?) => {
108 {
109 #[cfg(feature = "log")]
110 ::log::trace!($s $(, $x)*);
111 #[cfg(feature = "defmt")]
112 ::defmt::trace!($s $(, $x)*);
113 #[cfg(not(any(feature = "log", feature="defmt")))]
114 let _ = ($( & $x ),*);
115 }
116 };
117}
118
119macro_rules! debug {
120 ($s:literal $(, $x:expr)* $(,)?) => {
121 {
122 #[cfg(feature = "log")]
123 ::log::debug!($s $(, $x)*);
124 #[cfg(feature = "defmt")]
125 ::defmt::debug!($s $(, $x)*);
126 #[cfg(not(any(feature = "log", feature="defmt")))]
127 let _ = ($( & $x ),*);
128 }
129 };
130}
131
132macro_rules! info {
133 ($s:literal $(, $x:expr)* $(,)?) => {
134 {
135 #[cfg(feature = "log")]
136 ::log::info!($s $(, $x)*);
137 #[cfg(feature = "defmt")]
138 ::defmt::info!($s $(, $x)*);
139 #[cfg(not(any(feature = "log", feature="defmt")))]
140 let _ = ($( & $x ),*);
141 }
142 };
143}
144
145macro_rules! warn {
146 ($s:literal $(, $x:expr)* $(,)?) => {
147 {
148 #[cfg(feature = "log")]
149 ::log::warn!($s $(, $x)*);
150 #[cfg(feature = "defmt")]
151 ::defmt::warn!($s $(, $x)*);
152 #[cfg(not(any(feature = "log", feature="defmt")))]
153 let _ = ($( & $x ),*);
154 }
155 };
156}
157
158macro_rules! error {
159 ($s:literal $(, $x:expr)* $(,)?) => {
160 {
161 #[cfg(feature = "log")]
162 ::log::error!($s $(, $x)*);
163 #[cfg(feature = "defmt")]
164 ::defmt::error!($s $(, $x)*);
165 #[cfg(not(any(feature = "log", feature="defmt")))]
166 let _ = ($( & $x ),*);
167 }
168 };
169}
170
171#[cfg(feature = "defmt")]
172macro_rules! unwrap {
173 ($($x:tt)*) => {
174 ::defmt::unwrap!($($x)*)
175 };
176}
177
178#[cfg(not(feature = "defmt"))]
179macro_rules! unwrap {
180 ($arg:expr) => {
181 match $crate::fmt::Try::into_result($arg) {
182 ::core::result::Result::Ok(t) => t,
183 ::core::result::Result::Err(e) => {
184 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
185 }
186 }
187 };
188 ($arg:expr, $($msg:expr),+ $(,)? ) => {
189 match $crate::fmt::Try::into_result($arg) {
190 ::core::result::Result::Ok(t) => t,
191 ::core::result::Result::Err(e) => {
192 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
193 }
194 }
195 }
196}
197
198#[derive(Debug, Copy, Clone, Eq, PartialEq)]
199pub struct NoneError;
200
201pub trait Try {
202 type Ok;
203 type Error;
204 fn into_result(self) -> Result<Self::Ok, Self::Error>;
205}
206
207impl<T> Try for Option<T> {
208 type Ok = T;
209 type Error = NoneError;
210
211 #[inline]
212 fn into_result(self) -> Result<T, NoneError> {
213 self.ok_or(NoneError)
214 }
215}
216
217impl<T, E> Try for Result<T, E> {
218 type Ok = T;
219 type Error = E;
220
221 #[inline]
222 fn into_result(self) -> Self {
223 self
224 }
225}
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
new file mode 100644
index 000000000..909397b7b
--- /dev/null
+++ b/embassy-boot/boot/src/lib.rs
@@ -0,0 +1,550 @@
1#![feature(type_alias_impl_trait)]
2#![feature(generic_associated_types)]
3#![no_std]
4///! embassy-boot is a bootloader and firmware updater for embedded devices with flash
5///! storage implemented using embedded-storage
6///!
7///! The bootloader works in conjunction with the firmware application, and only has the
8///! ability to manage two flash banks with an active and a updatable part. It implements
9///! a swap algorithm that is power-failure safe, and allows reverting to the previous
10///! version of the firmware, should the application crash and fail to mark itself as booted.
11///!
12///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf,
13///! which defines the limits and flash type for that particular platform.
14///!
15mod fmt;
16
17use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
18use embedded_storage_async::nor_flash::AsyncNorFlash;
19
20pub const BOOT_MAGIC: u32 = 0xD00DF00D;
21pub const SWAP_MAGIC: u32 = 0xF00FDAAD;
22
23#[derive(Copy, Clone, Debug)]
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25pub struct Partition {
26 pub from: usize,
27 pub to: usize,
28}
29
30impl Partition {
31 pub const fn new(from: usize, to: usize) -> Self {
32 Self { from, to }
33 }
34 pub const fn len(&self) -> usize {
35 self.to - self.from
36 }
37}
38
39#[derive(PartialEq, Debug)]
40#[cfg_attr(feature = "defmt", derive(defmt::Format))]
41pub enum State {
42 Boot,
43 Swap,
44}
45
46#[derive(PartialEq, Debug)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub enum BootError<E> {
49 Flash(E),
50 BadMagic,
51}
52
53impl<E> From<E> for BootError<E> {
54 fn from(error: E) -> Self {
55 BootError::Flash(error)
56 }
57}
58
59/// BootLoader works with any flash implementing embedded_storage and can also work with
60/// different page sizes.
61pub struct BootLoader<const PAGE_SIZE: usize> {
62 // Page with current state of bootloader. The state partition has the following format:
63 // | Range | Description |
64 // | 0 - 4 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
65 // | 4 - N | Progress index used while swapping or reverting |
66 state: Partition,
67 // Location of the partition which will be booted from
68 active: Partition,
69 // Location of the partition which will be swapped in when requested
70 dfu: Partition,
71}
72
73impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
74 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
75 assert_eq!(active.len() % PAGE_SIZE, 0);
76 assert_eq!(dfu.len() % PAGE_SIZE, 0);
77 // DFU partition must have an extra page
78 assert!(dfu.len() - active.len() >= PAGE_SIZE);
79 // Ensure we have enough progress pages to store copy progress
80 assert!(active.len() / PAGE_SIZE >= (state.len() - 4) / PAGE_SIZE);
81 Self { active, dfu, state }
82 }
83
84 pub fn boot_address(&self) -> usize {
85 self.active.from
86 }
87
88 /// Perform necessary boot preparations like swapping images.
89 ///
90 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
91 /// algorithm to work correctly.
92 ///
93 /// SWAPPING
94 ///
95 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
96 /// The swap index contains the copy progress, as to allow continuation of the copy process on
97 /// power failure. The index counter is represented within 1 or more pages (depending on total
98 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
99 /// contains a zero value. This ensures that index updates can be performed atomically and
100 /// avoid a situation where the wrong index value is set (page write size is "atomic").
101 ///
102 /// +-----------+------------+--------+--------+--------+--------+
103 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
104 /// +-----------+------------+--------+--------+--------+--------+
105 /// | Active | 0 | 1 | 2 | 3 | - |
106 /// | DFU | 0 | 3 | 2 | 1 | X |
107 /// +-----------+-------+--------+--------+--------+--------+
108 ///
109 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
110 /// as follows:
111 ///
112 /// +-----------+------------+--------+--------+--------+--------+
113 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
114 /// +-----------+------------+--------+--------+--------+--------+
115 /// | Active | 1 | 1 | 2 | 1 | - |
116 /// | DFU | 1 | 3 | 2 | 1 | 3 |
117 /// +-----------+------------+--------+--------+--------+--------+
118 ///
119 /// The next iteration performs the same steps
120 ///
121 /// +-----------+------------+--------+--------+--------+--------+
122 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
123 /// +-----------+------------+--------+--------+--------+--------+
124 /// | Active | 2 | 1 | 2 | 1 | - |
125 /// | DFU | 2 | 3 | 2 | 2 | 3 |
126 /// +-----------+------------+--------+--------+--------+--------+
127 ///
128 /// And again until we're done
129 ///
130 /// +-----------+------------+--------+--------+--------+--------+
131 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
132 /// +-----------+------------+--------+--------+--------+--------+
133 /// | Active | 3 | 3 | 2 | 1 | - |
134 /// | DFU | 3 | 3 | 1 | 2 | 3 |
135 /// +-----------+------------+--------+--------+--------+--------+
136 ///
137 /// REVERTING
138 ///
139 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
140 /// the application failed to mark the boot successful. In this case, the revert algorithm will
141 /// run.
142 ///
143 /// The revert index is located separately from the swap index, to ensure that revert can continue
144 /// on power failure.
145 ///
146 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
147 ///
148 /// +-----------+--------------+--------+--------+--------+--------+
149 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
150 //*/
151 /// +-----------+--------------+--------+--------+--------+--------+
152 /// | Active | 3 | 1 | 2 | 1 | - |
153 /// | DFU | 3 | 3 | 1 | 2 | 3 |
154 /// +-----------+--------------+--------+--------+--------+--------+
155 ///
156 ///
157 /// +-----------+--------------+--------+--------+--------+--------+
158 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
159 /// +-----------+--------------+--------+--------+--------+--------+
160 /// | Active | 3 | 1 | 2 | 1 | - |
161 /// | DFU | 3 | 3 | 2 | 2 | 3 |
162 /// +-----------+--------------+--------+--------+--------+--------+
163 ///
164 /// +-----------+--------------+--------+--------+--------+--------+
165 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
166 /// +-----------+--------------+--------+--------+--------+--------+
167 /// | Active | 3 | 1 | 2 | 3 | - |
168 /// | DFU | 3 | 3 | 2 | 1 | 3 |
169 /// +-----------+--------------+--------+--------+--------+--------+
170 ///
171 pub fn prepare_boot<F: NorFlash + ReadNorFlash>(
172 &mut self,
173 flash: &mut F,
174 ) -> Result<State, BootError<F::Error>> {
175 // Copy contents from partition N to active
176 let state = self.read_state(flash)?;
177 match state {
178 State::Swap => {
179 //
180 // Check if we already swapped. If we're in the swap state, this means we should revert
181 // since the app has failed to mark boot as successful
182 //
183 if !self.is_swapped(flash)? {
184 trace!("Swapping");
185 self.swap(flash)?;
186 } else {
187 trace!("Reverting");
188 self.revert(flash)?;
189
190 // Overwrite magic and reset progress
191 flash.write(self.state.from as u32, &[0, 0, 0, 0])?;
192 flash.erase(self.state.from as u32, self.state.to as u32)?;
193 flash.write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())?;
194 }
195 }
196 _ => {}
197 }
198 Ok(state)
199 }
200
201 fn is_swapped<F: ReadNorFlash>(&mut self, flash: &mut F) -> Result<bool, F::Error> {
202 let page_count = self.active.len() / PAGE_SIZE;
203 let progress = self.current_progress(flash)?;
204
205 Ok(progress >= page_count * 2)
206 }
207
208 fn current_progress<F: ReadNorFlash>(&mut self, flash: &mut F) -> Result<usize, F::Error> {
209 let max_index = ((self.state.len() - 4) / 4) - 1;
210 for i in 0..max_index {
211 let mut buf: [u8; 4] = [0; 4];
212 flash.read((self.state.from + 4 + i * 4) as u32, &mut buf)?;
213 if buf == [0xFF, 0xFF, 0xFF, 0xFF] {
214 return Ok(i);
215 }
216 }
217 Ok(max_index)
218 }
219
220 fn update_progress<F: NorFlash>(&mut self, idx: usize, flash: &mut F) -> Result<(), F::Error> {
221 let w = self.state.from + 4 + idx * 4;
222 flash.write(w as u32, &[0, 0, 0, 0])?;
223 Ok(())
224 }
225
226 fn active_addr(&self, n: usize) -> usize {
227 self.active.from + n * PAGE_SIZE
228 }
229
230 fn dfu_addr(&self, n: usize) -> usize {
231 self.dfu.from + n * PAGE_SIZE
232 }
233
234 fn copy_page_once<F: NorFlash + ReadNorFlash>(
235 &mut self,
236 idx: usize,
237 from: usize,
238 to: usize,
239 flash: &mut F,
240 ) -> Result<(), F::Error> {
241 let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
242 if self.current_progress(flash)? <= idx {
243 flash.read(from as u32, &mut buf)?;
244 flash.erase(to as u32, (to + PAGE_SIZE) as u32)?;
245 flash.write(to as u32, &buf)?;
246 self.update_progress(idx, flash)?;
247 }
248 Ok(())
249 }
250
251 fn swap<F: NorFlash + ReadNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
252 let page_count = self.active.len() / PAGE_SIZE;
253 // trace!("Page count: {}", page_count);
254 for page in 0..page_count {
255 // Copy active page to the 'next' DFU page.
256 let active_page = self.active_addr(page_count - 1 - page);
257 let dfu_page = self.dfu_addr(page_count - page);
258 // info!("Copy active {} to dfu {}", active_page, dfu_page);
259 self.copy_page_once(page * 2, active_page, dfu_page, flash)?;
260
261 // Copy DFU page to the active page
262 let active_page = self.active_addr(page_count - 1 - page);
263 let dfu_page = self.dfu_addr(page_count - 1 - page);
264 //info!("Copy dfy {} to active {}", dfu_page, active_page);
265 self.copy_page_once(page * 2 + 1, dfu_page, active_page, flash)?;
266 }
267
268 Ok(())
269 }
270
271 fn revert<F: NorFlash + ReadNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
272 let page_count = self.active.len() / PAGE_SIZE;
273 for page in 0..page_count {
274 // Copy the bad active page to the DFU page
275 let active_page = self.active_addr(page);
276 let dfu_page = self.dfu_addr(page);
277 self.copy_page_once(page_count * 2 + page * 2, active_page, dfu_page, flash)?;
278
279 // Copy the DFU page back to the active page
280 let active_page = self.active_addr(page);
281 let dfu_page = self.dfu_addr(page + 1);
282 self.copy_page_once(page_count * 2 + page * 2 + 1, dfu_page, active_page, flash)?;
283 }
284
285 Ok(())
286 }
287
288 fn read_state<F: ReadNorFlash>(&mut self, flash: &mut F) -> Result<State, BootError<F::Error>> {
289 let mut magic: [u8; 4] = [0; 4];
290 flash.read(self.state.from as u32, &mut magic)?;
291
292 match u32::from_le_bytes(magic) {
293 SWAP_MAGIC => Ok(State::Swap),
294 _ => Ok(State::Boot),
295 }
296 }
297}
298
299/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
300/// 'mess up' the internal bootloader state
301pub struct FirmwareUpdater {
302 state: Partition,
303 dfu: Partition,
304}
305
306impl FirmwareUpdater {
307 pub const fn new(dfu: Partition, state: Partition) -> Self {
308 Self { dfu, state }
309 }
310
311 /// Return the length of the DFU area
312 pub fn firmware_len(&self) -> usize {
313 self.dfu.len()
314 }
315
316 /// Instruct bootloader that DFU should commence at next boot.
317 pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
318 flash.write(self.state.from as u32, &[0, 0, 0, 0]).await?;
319 flash
320 .erase(self.state.from as u32, self.state.to as u32)
321 .await?;
322 info!(
323 "Setting swap magic at {} to 0x{:x}, LE: 0x{:x}",
324 self.state.from,
325 &SWAP_MAGIC,
326 &SWAP_MAGIC.to_le_bytes()
327 );
328 flash
329 .write(self.state.from as u32, &SWAP_MAGIC.to_le_bytes())
330 .await?;
331 Ok(())
332 }
333
334 /// Mark firmware boot successfully
335 pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
336 flash.write(self.state.from as u32, &[0, 0, 0, 0]).await?;
337 flash
338 .erase(self.state.from as u32, self.state.to as u32)
339 .await?;
340 flash
341 .write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())
342 .await?;
343 Ok(())
344 }
345
346 // Write to a region of the DFU page
347 pub async fn write_firmware<F: AsyncNorFlash>(
348 &mut self,
349 offset: usize,
350 data: &[u8],
351 flash: &mut F,
352 ) -> Result<(), F::Error> {
353 info!(
354 "Writing firmware at offset 0x{:x} len {}",
355 self.dfu.from + offset,
356 data.len()
357 );
358
359 flash
360 .erase(
361 (self.dfu.from + offset) as u32,
362 (self.dfu.from + offset + data.len()) as u32,
363 )
364 .await?;
365 flash.write((self.dfu.from + offset) as u32, data).await
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use core::convert::Infallible;
373 use core::future::Future;
374 use embedded_storage_async::nor_flash::AsyncReadNorFlash;
375 use futures::executor::block_on;
376
377 const STATE: Partition = Partition::new(0, 4096);
378 const ACTIVE: Partition = Partition::new(4096, 61440);
379 const DFU: Partition = Partition::new(61440, 122880);
380
381 #[test]
382 fn test_bad_magic() {
383 let mut flash = MemFlash([0xff; 131072]);
384
385 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
386
387 assert_eq!(
388 bootloader.prepare_boot(&mut flash),
389 Err(BootError::BadMagic)
390 );
391 }
392
393 #[test]
394 fn test_boot_state() {
395 let mut flash = MemFlash([0xff; 131072]);
396 flash.0[0..4].copy_from_slice(&BOOT_MAGIC.to_le_bytes());
397
398 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
399
400 assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
401 }
402
403 #[test]
404 fn test_swap_state() {
405 env_logger::init();
406 let mut flash = MemFlash([0xff; 131072]);
407
408 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
409 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
410
411 for i in ACTIVE.from..ACTIVE.to {
412 flash.0[i] = original[i - ACTIVE.from];
413 }
414
415 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
416 let mut updater = FirmwareUpdater::new(DFU, STATE);
417 for i in (DFU.from..DFU.to).step_by(4) {
418 let base = i - DFU.from;
419 let data: [u8; 4] = [
420 update[base],
421 update[base + 1],
422 update[base + 2],
423 update[base + 3],
424 ];
425 block_on(updater.write_firmware(i - DFU.from, &data, &mut flash)).unwrap();
426 }
427 block_on(updater.mark_update(&mut flash)).unwrap();
428
429 assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
430
431 for i in ACTIVE.from..ACTIVE.to {
432 assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i);
433 }
434
435 // First DFU page is untouched
436 for i in DFU.from + 4096..DFU.to {
437 assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i);
438 }
439
440 // Running again should cause a revert
441 assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
442
443 for i in ACTIVE.from..ACTIVE.to {
444 assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i);
445 }
446
447 // Last page is untouched
448 for i in DFU.from..DFU.to - 4096 {
449 assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i);
450 }
451
452 // Mark as booted
453 block_on(updater.mark_booted(&mut flash)).unwrap();
454 assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
455 }
456
457 struct MemFlash([u8; 131072]);
458
459 impl NorFlash for MemFlash {
460 const WRITE_SIZE: usize = 4;
461 const ERASE_SIZE: usize = 4096;
462 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
463 let from = from as usize;
464 let to = to as usize;
465 for i in from..to {
466 self.0[i] = 0xFF;
467 self.0[i] = 0xFF;
468 self.0[i] = 0xFF;
469 self.0[i] = 0xFF;
470 }
471 Ok(())
472 }
473
474 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
475 assert!(data.len() % 4 == 0);
476 assert!(offset % 4 == 0);
477 assert!(offset as usize + data.len() < 131072);
478
479 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
480
481 Ok(())
482 }
483 }
484
485 impl ReadNorFlash for MemFlash {
486 const READ_SIZE: usize = 4;
487 type Error = Infallible;
488
489 fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
490 let len = buf.len();
491 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
492 Ok(())
493 }
494
495 fn capacity(&self) -> usize {
496 131072
497 }
498 }
499
500 impl AsyncReadNorFlash for MemFlash {
501 const READ_SIZE: usize = 4;
502 type Error = Infallible;
503
504 type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
505 fn read<'a>(&'a mut self, offset: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
506 async move {
507 let len = buf.len();
508 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
509 Ok(())
510 }
511 }
512
513 fn capacity(&self) -> usize {
514 131072
515 }
516 }
517
518 impl AsyncNorFlash for MemFlash {
519 const WRITE_SIZE: usize = 4;
520 const ERASE_SIZE: usize = 4096;
521
522 type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
523 fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
524 async move {
525 let from = from as usize;
526 let to = to as usize;
527 for i in from..to {
528 self.0[i] = 0xFF;
529 self.0[i] = 0xFF;
530 self.0[i] = 0xFF;
531 self.0[i] = 0xFF;
532 }
533 Ok(())
534 }
535 }
536
537 type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
538 fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
539 async move {
540 assert!(data.len() % 4 == 0);
541 assert!(offset % 4 == 0);
542 assert!(offset as usize + data.len() < 131072);
543
544 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
545
546 Ok(())
547 }
548 }
549 }
550}
diff --git a/embassy-boot/nrf/.cargo/config.toml b/embassy-boot/nrf/.cargo/config.toml
new file mode 100644
index 000000000..c3957b866
--- /dev/null
+++ b/embassy-boot/nrf/.cargo/config.toml
@@ -0,0 +1,18 @@
1[unstable]
2namespaced-features = true
3build-std = ["core"]
4build-std-features = ["panic_immediate_abort"]
5
6[target.'cfg(all(target_arch = "arm", target_os = "none"))']
7#runner = "./fruitrunner"
8runner = "probe-run --chip nrf52840_xxAA"
9
10rustflags = [
11 # Code-size optimizations.
12 "-Z", "trap-unreachable=no",
13 #"-C", "no-vectorize-loops",
14 "-C", "force-frame-pointers=yes",
15]
16
17[build]
18target = "thumbv7em-none-eabi"
diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml
new file mode 100644
index 000000000..512e7d378
--- /dev/null
+++ b/embassy-boot/nrf/Cargo.toml
@@ -0,0 +1,65 @@
1[package]
2authors = [
3 "Ulf Lilleengen <[email protected]>",
4]
5edition = "2018"
6name = "embassy-boot-nrf"
7version = "0.1.0"
8description = "Bootloader for nRF chips"
9
10[dependencies]
11defmt = { version = "0.3", optional = true }
12defmt-rtt = { version = "0.3", optional = true }
13
14embassy = { path = "../../embassy", default-features = false }
15embassy-nrf = { path = "../../embassy-nrf", default-features = false }
16embassy-boot = { path = "../boot", default-features = false }
17cortex-m = { version = "0.7" }
18cortex-m-rt = { version = "0.7" }
19embedded-storage = "0.3.0"
20embedded-storage-async = "0.3.0"
21cfg-if = "1.0.0"
22
23nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
24
25[features]
26defmt = [
27 "dep:defmt",
28 "embassy-boot/defmt",
29 "embassy-nrf/defmt",
30]
31softdevice = [
32 "nrf-softdevice-mbr",
33]
34debug = ["defmt-rtt"]
35
36[profile.dev]
37debug = 2
38debug-assertions = true
39incremental = false
40opt-level = 'z'
41overflow-checks = true
42
43[profile.release]
44codegen-units = 1
45debug = 2
46debug-assertions = false
47incremental = false
48lto = 'fat'
49opt-level = 'z'
50overflow-checks = false
51
52# do not optimize proc-macro crates = faster builds from scratch
53[profile.dev.build-override]
54codegen-units = 8
55debug = false
56debug-assertions = false
57opt-level = 0
58overflow-checks = false
59
60[profile.release.build-override]
61codegen-units = 8
62debug = false
63debug-assertions = false
64opt-level = 0
65overflow-checks = false
diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md
new file mode 100644
index 000000000..23497a038
--- /dev/null
+++ b/embassy-boot/nrf/README.md
@@ -0,0 +1,11 @@
1# Bootloader for nRF
2
3The bootloader uses `embassy-boot` to interact with the flash.
4
5# Usage
6
7Flash the bootloader
8
9```
10cargo flash --features embassy-nrf/nrf52832 --release --chip nRF52832_xxAA
11```
diff --git a/embassy-boot/nrf/build.rs b/embassy-boot/nrf/build.rs
new file mode 100644
index 000000000..e1da69328
--- /dev/null
+++ b/embassy-boot/nrf/build.rs
@@ -0,0 +1,37 @@
1//! This build script copies the `memory.x` file from the crate root into
2//! a directory where the linker can always find it at build time.
3//! For many projects this is optional, as the linker always searches the
4//! project root directory -- wherever `Cargo.toml` is. However, if you
5//! are using a workspace or have a more complicated build setup, this
6//! build script becomes required. Additionally, by requesting that
7//! Cargo re-run the build script whenever `memory.x` is changed,
8//! updating `memory.x` ensures a rebuild of the application with the
9//! new memory settings.
10
11use std::env;
12use std::fs::File;
13use std::io::Write;
14use std::path::PathBuf;
15
16fn main() {
17 // Put `memory.x` in our output directory and ensure it's
18 // on the linker search path.
19 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
20 File::create(out.join("memory.x"))
21 .unwrap()
22 .write_all(include_bytes!("memory.x"))
23 .unwrap();
24 println!("cargo:rustc-link-search={}", out.display());
25
26 // By default, Cargo will re-run a build script whenever
27 // any file in the project changes. By specifying `memory.x`
28 // here, we ensure the build script is only re-run when
29 // `memory.x` is changed.
30 println!("cargo:rerun-if-changed=memory.x");
31
32 println!("cargo:rustc-link-arg-bins=--nmagic");
33 println!("cargo:rustc-link-arg-bins=-Tlink.x");
34 if env::var("CARGO_FEATURE_DEFMT").is_ok() {
35 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
36 }
37}
diff --git a/embassy-boot/nrf/memory-bm.x b/embassy-boot/nrf/memory-bm.x
new file mode 100644
index 000000000..257d65644
--- /dev/null
+++ b/embassy-boot/nrf/memory-bm.x
@@ -0,0 +1,18 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 FLASH : ORIGIN = 0x00000000, LENGTH = 24K
5 BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K
6 ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K
7 DFU : ORIGIN = 0x00017000, LENGTH = 68K
8 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
9}
10
11__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
12__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
13
14__bootloader_active_start = ORIGIN(ACTIVE);
15__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE);
16
17__bootloader_dfu_start = ORIGIN(DFU);
18__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/embassy-boot/nrf/memory-s140.x b/embassy-boot/nrf/memory-s140.x
new file mode 100644
index 000000000..105db9972
--- /dev/null
+++ b/embassy-boot/nrf/memory-s140.x
@@ -0,0 +1,31 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 MBR : ORIGIN = 0x00000000, LENGTH = 4K
5 SOFTDEVICE : ORIGIN = 0x00001000, LENGTH = 155648
6 ACTIVE : ORIGIN = 0x00027000, LENGTH = 425984
7 DFU : ORIGIN = 0x0008F000, LENGTH = 430080
8 FLASH : ORIGIN = 0x000f9000, LENGTH = 24K
9 BOOTLOADER_STATE : ORIGIN = 0x000ff000, LENGTH = 4K
10 RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 0x2fff8
11 uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4
12}
13
14__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
15__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
16
17__bootloader_active_start = ORIGIN(ACTIVE);
18__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE);
19
20__bootloader_dfu_start = ORIGIN(DFU);
21__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
22
23__bootloader_start = ORIGIN(FLASH);
24
25SECTIONS
26{
27 .uicr_bootloader_start_address :
28 {
29 LONG(__bootloader_start)
30 } > uicr_bootloader_start_address
31}
diff --git a/embassy-boot/nrf/memory.x b/embassy-boot/nrf/memory.x
new file mode 100644
index 000000000..257d65644
--- /dev/null
+++ b/embassy-boot/nrf/memory.x
@@ -0,0 +1,18 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 FLASH : ORIGIN = 0x00000000, LENGTH = 24K
5 BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K
6 ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K
7 DFU : ORIGIN = 0x00017000, LENGTH = 68K
8 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
9}
10
11__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
12__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
13
14__bootloader_active_start = ORIGIN(ACTIVE);
15__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE);
16
17__bootloader_dfu_start = ORIGIN(DFU);
18__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/embassy-boot/nrf/src/fmt.rs b/embassy-boot/nrf/src/fmt.rs
new file mode 100644
index 000000000..066970813
--- /dev/null
+++ b/embassy-boot/nrf/src/fmt.rs
@@ -0,0 +1,225 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4#[cfg(all(feature = "defmt", feature = "log"))]
5compile_error!("You may not enable both `defmt` and `log` features.");
6
7macro_rules! assert {
8 ($($x:tt)*) => {
9 {
10 #[cfg(not(feature = "defmt"))]
11 ::core::assert!($($x)*);
12 #[cfg(feature = "defmt")]
13 ::defmt::assert!($($x)*);
14 }
15 };
16}
17
18macro_rules! assert_eq {
19 ($($x:tt)*) => {
20 {
21 #[cfg(not(feature = "defmt"))]
22 ::core::assert_eq!($($x)*);
23 #[cfg(feature = "defmt")]
24 ::defmt::assert_eq!($($x)*);
25 }
26 };
27}
28
29macro_rules! assert_ne {
30 ($($x:tt)*) => {
31 {
32 #[cfg(not(feature = "defmt"))]
33 ::core::assert_ne!($($x)*);
34 #[cfg(feature = "defmt")]
35 ::defmt::assert_ne!($($x)*);
36 }
37 };
38}
39
40macro_rules! debug_assert {
41 ($($x:tt)*) => {
42 {
43 #[cfg(not(feature = "defmt"))]
44 ::core::debug_assert!($($x)*);
45 #[cfg(feature = "defmt")]
46 ::defmt::debug_assert!($($x)*);
47 }
48 };
49}
50
51macro_rules! debug_assert_eq {
52 ($($x:tt)*) => {
53 {
54 #[cfg(not(feature = "defmt"))]
55 ::core::debug_assert_eq!($($x)*);
56 #[cfg(feature = "defmt")]
57 ::defmt::debug_assert_eq!($($x)*);
58 }
59 };
60}
61
62macro_rules! debug_assert_ne {
63 ($($x:tt)*) => {
64 {
65 #[cfg(not(feature = "defmt"))]
66 ::core::debug_assert_ne!($($x)*);
67 #[cfg(feature = "defmt")]
68 ::defmt::debug_assert_ne!($($x)*);
69 }
70 };
71}
72
73macro_rules! todo {
74 ($($x:tt)*) => {
75 {
76 #[cfg(not(feature = "defmt"))]
77 ::core::todo!($($x)*);
78 #[cfg(feature = "defmt")]
79 ::defmt::todo!($($x)*);
80 }
81 };
82}
83
84macro_rules! unreachable {
85 ($($x:tt)*) => {
86 {
87 #[cfg(not(feature = "defmt"))]
88 ::core::unreachable!($($x)*);
89 #[cfg(feature = "defmt")]
90 ::defmt::unreachable!($($x)*);
91 }
92 };
93}
94
95macro_rules! panic {
96 ($($x:tt)*) => {
97 {
98 #[cfg(not(feature = "defmt"))]
99 ::core::panic!($($x)*);
100 #[cfg(feature = "defmt")]
101 ::defmt::panic!($($x)*);
102 }
103 };
104}
105
106macro_rules! trace {
107 ($s:literal $(, $x:expr)* $(,)?) => {
108 {
109 #[cfg(feature = "log")]
110 ::log::trace!($s $(, $x)*);
111 #[cfg(feature = "defmt")]
112 ::defmt::trace!($s $(, $x)*);
113 #[cfg(not(any(feature = "log", feature="defmt")))]
114 let _ = ($( & $x ),*);
115 }
116 };
117}
118
119macro_rules! debug {
120 ($s:literal $(, $x:expr)* $(,)?) => {
121 {
122 #[cfg(feature = "log")]
123 ::log::debug!($s $(, $x)*);
124 #[cfg(feature = "defmt")]
125 ::defmt::debug!($s $(, $x)*);
126 #[cfg(not(any(feature = "log", feature="defmt")))]
127 let _ = ($( & $x ),*);
128 }
129 };
130}
131
132macro_rules! info {
133 ($s:literal $(, $x:expr)* $(,)?) => {
134 {
135 #[cfg(feature = "log")]
136 ::log::info!($s $(, $x)*);
137 #[cfg(feature = "defmt")]
138 ::defmt::info!($s $(, $x)*);
139 #[cfg(not(any(feature = "log", feature="defmt")))]
140 let _ = ($( & $x ),*);
141 }
142 };
143}
144
145macro_rules! warn {
146 ($s:literal $(, $x:expr)* $(,)?) => {
147 {
148 #[cfg(feature = "log")]
149 ::log::warn!($s $(, $x)*);
150 #[cfg(feature = "defmt")]
151 ::defmt::warn!($s $(, $x)*);
152 #[cfg(not(any(feature = "log", feature="defmt")))]
153 let _ = ($( & $x ),*);
154 }
155 };
156}
157
158macro_rules! error {
159 ($s:literal $(, $x:expr)* $(,)?) => {
160 {
161 #[cfg(feature = "log")]
162 ::log::error!($s $(, $x)*);
163 #[cfg(feature = "defmt")]
164 ::defmt::error!($s $(, $x)*);
165 #[cfg(not(any(feature = "log", feature="defmt")))]
166 let _ = ($( & $x ),*);
167 }
168 };
169}
170
171#[cfg(feature = "defmt")]
172macro_rules! unwrap {
173 ($($x:tt)*) => {
174 ::defmt::unwrap!($($x)*)
175 };
176}
177
178#[cfg(not(feature = "defmt"))]
179macro_rules! unwrap {
180 ($arg:expr) => {
181 match $crate::fmt::Try::into_result($arg) {
182 ::core::result::Result::Ok(t) => t,
183 ::core::result::Result::Err(e) => {
184 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
185 }
186 }
187 };
188 ($arg:expr, $($msg:expr),+ $(,)? ) => {
189 match $crate::fmt::Try::into_result($arg) {
190 ::core::result::Result::Ok(t) => t,
191 ::core::result::Result::Err(e) => {
192 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
193 }
194 }
195 }
196}
197
198#[derive(Debug, Copy, Clone, Eq, PartialEq)]
199pub struct NoneError;
200
201pub trait Try {
202 type Ok;
203 type Error;
204 fn into_result(self) -> Result<Self::Ok, Self::Error>;
205}
206
207impl<T> Try for Option<T> {
208 type Ok = T;
209 type Error = NoneError;
210
211 #[inline]
212 fn into_result(self) -> Result<T, NoneError> {
213 self.ok_or(NoneError)
214 }
215}
216
217impl<T, E> Try for Result<T, E> {
218 type Ok = T;
219 type Error = E;
220
221 #[inline]
222 fn into_result(self) -> Self {
223 self
224 }
225}
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
new file mode 100644
index 000000000..32250b2db
--- /dev/null
+++ b/embassy-boot/nrf/src/lib.rs
@@ -0,0 +1,210 @@
1#![no_std]
2#![feature(generic_associated_types)]
3#![feature(type_alias_impl_trait)]
4
5mod fmt;
6
7pub use embassy_boot::{FirmwareUpdater, Partition, State, BOOT_MAGIC};
8use embassy_nrf::{
9 nvmc::{Nvmc, PAGE_SIZE},
10 peripherals::WDT,
11 wdt,
12};
13use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
14
15pub struct BootLoader {
16 boot: embassy_boot::BootLoader<PAGE_SIZE>,
17}
18
19impl BootLoader {
20 /// Create a new bootloader instance using parameters from linker script
21 pub fn default() -> Self {
22 extern "C" {
23 static __bootloader_state_start: u32;
24 static __bootloader_state_end: u32;
25 static __bootloader_active_start: u32;
26 static __bootloader_active_end: u32;
27 static __bootloader_dfu_start: u32;
28 static __bootloader_dfu_end: u32;
29 }
30
31 let active = unsafe {
32 Partition::new(
33 &__bootloader_active_start as *const u32 as usize,
34 &__bootloader_active_end as *const u32 as usize,
35 )
36 };
37 let dfu = unsafe {
38 Partition::new(
39 &__bootloader_dfu_start as *const u32 as usize,
40 &__bootloader_dfu_end as *const u32 as usize,
41 )
42 };
43 let state = unsafe {
44 Partition::new(
45 &__bootloader_state_start as *const u32 as usize,
46 &__bootloader_state_end as *const u32 as usize,
47 )
48 };
49
50 trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
51 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
52 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
53
54 Self::new(active, dfu, state)
55 }
56
57 /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
58 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
59 Self {
60 boot: embassy_boot::BootLoader::new(active, dfu, state),
61 }
62 }
63
64 /// Boots the application without softdevice mechanisms
65 pub fn prepare<F: NorFlash + ReadNorFlash>(&mut self, flash: &mut F) -> usize {
66 match self.boot.prepare_boot(flash) {
67 Ok(_) => self.boot.boot_address(),
68 Err(_) => panic!("boot prepare error!"),
69 }
70 }
71
72 #[cfg(not(feature = "softdevice"))]
73 pub unsafe fn load(&mut self, start: usize) -> ! {
74 let mut p = cortex_m::Peripherals::steal();
75 p.SCB.invalidate_icache();
76 p.SCB.vtor.write(start as u32);
77 cortex_m::asm::bootload(start as *const u32)
78 }
79
80 #[cfg(feature = "softdevice")]
81 pub unsafe fn load(&mut self, _app: usize) -> ! {
82 use nrf_softdevice_mbr as mbr;
83 const NRF_SUCCESS: u32 = 0;
84
85 // Address of softdevice which we'll forward interrupts to
86 let addr = 0x1000;
87 let mut cmd = mbr::sd_mbr_command_t {
88 command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET,
89 params: mbr::sd_mbr_command_t__bindgen_ty_1 {
90 irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t {
91 address: addr,
92 },
93 },
94 };
95 let ret = mbr::sd_mbr_command(&mut cmd);
96 assert_eq!(ret, NRF_SUCCESS);
97
98 let msp = *(addr as *const u32);
99 let rv = *((addr + 4) as *const u32);
100
101 trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv);
102
103 // These instructions perform the following operations:
104 //
105 // * Modify control register to use MSP as stack pointer (clear spsel bit)
106 // * Synchronize instruction barrier
107 // * Initialize stack pointer (0x1000)
108 // * Set link register to not return (0xFF)
109 // * Jump to softdevice reset vector
110 core::arch::asm!(
111 "mrs {tmp}, CONTROL",
112 "bics {tmp}, {spsel}",
113 "msr CONTROL, {tmp}",
114 "isb",
115 "msr MSP, {msp}",
116 "mov lr, {new_lr}",
117 "bx {rv}",
118 // `out(reg) _` is not permitted in a `noreturn` asm! call,
119 // so instead use `in(reg) 0` and don't restore it afterwards.
120 tmp = in(reg) 0,
121 spsel = in(reg) 2,
122 new_lr = in(reg) 0xFFFFFFFFu32,
123 msp = in(reg) msp,
124 rv = in(reg) rv,
125 options(noreturn),
126 );
127 }
128}
129
130/// A flash implementation that wraps NVMC and will pet a watchdog when touching flash.
131pub struct WatchdogFlash<'d> {
132 flash: Nvmc<'d>,
133 wdt: wdt::WatchdogHandle,
134}
135
136impl<'d> WatchdogFlash<'d> {
137 /// Start a new watchdog with a given flash and WDT peripheral and a timeout
138 pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self {
139 let mut config = wdt::Config::default();
140 config.timeout_ticks = 32768 * timeout; // timeout seconds
141 config.run_during_sleep = true;
142 config.run_during_debug_halt = false;
143 let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
144 Ok(x) => x,
145 Err(_) => {
146 // In case the watchdog is already running, just spin and let it expire, since
147 // we can't configure it anyway. This usually happens when we first program
148 // the device and the watchdog was previously active
149 info!("Watchdog already active with wrong config, waiting for it to timeout...");
150 loop {}
151 }
152 };
153 Self { flash, wdt }
154 }
155}
156
157impl<'d> ErrorType for WatchdogFlash<'d> {
158 type Error = <Nvmc<'d> as ErrorType>::Error;
159}
160
161impl<'d> NorFlash for WatchdogFlash<'d> {
162 const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE;
163 const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE;
164
165 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
166 self.wdt.pet();
167 self.flash.erase(from, to)
168 }
169 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
170 self.wdt.pet();
171 self.flash.write(offset, data)
172 }
173}
174
175impl<'d> ReadNorFlash for WatchdogFlash<'d> {
176 const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE;
177 fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
178 self.wdt.pet();
179 self.flash.read(offset, data)
180 }
181 fn capacity(&self) -> usize {
182 self.flash.capacity()
183 }
184}
185
186pub mod updater {
187 use super::*;
188 pub fn new() -> embassy_boot::FirmwareUpdater {
189 extern "C" {
190 static __bootloader_state_start: u32;
191 static __bootloader_state_end: u32;
192 static __bootloader_dfu_start: u32;
193 static __bootloader_dfu_end: u32;
194 }
195
196 let dfu = unsafe {
197 Partition::new(
198 &__bootloader_dfu_start as *const u32 as usize,
199 &__bootloader_dfu_end as *const u32 as usize,
200 )
201 };
202 let state = unsafe {
203 Partition::new(
204 &__bootloader_state_start as *const u32 as usize,
205 &__bootloader_state_end as *const u32 as usize,
206 )
207 };
208 embassy_boot::FirmwareUpdater::new(dfu, state)
209 }
210}
diff --git a/embassy-boot/nrf/src/main.rs b/embassy-boot/nrf/src/main.rs
new file mode 100644
index 000000000..cd264d4c2
--- /dev/null
+++ b/embassy-boot/nrf/src/main.rs
@@ -0,0 +1,49 @@
1#![no_std]
2#![no_main]
3
4use cortex_m_rt::{entry, exception};
5
6#[cfg(feature = "defmt")]
7use defmt_rtt as _;
8
9use embassy_boot_nrf::*;
10use embassy_nrf::nvmc::Nvmc;
11
12#[entry]
13fn main() -> ! {
14 let p = embassy_nrf::init(Default::default());
15
16 // Uncomment this if you are debugging the bootloader with debugger/RTT attached,
17 // as it prevents a hard fault when accessing flash 'too early' after boot.
18 /*
19 for i in 0..10000000 {
20 cortex_m::asm::nop();
21 }
22 */
23
24 let mut bl = BootLoader::default();
25 let start = bl.prepare(&mut WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5));
26 unsafe { bl.load(start) }
27}
28
29#[no_mangle]
30#[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
31unsafe extern "C" fn HardFault() {
32 cortex_m::peripheral::SCB::sys_reset();
33}
34
35#[exception]
36unsafe fn DefaultHandler(_: i16) -> ! {
37 const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
38 let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16;
39
40 panic!("DefaultHandler #{:?}", irqn);
41}
42
43#[panic_handler]
44fn panic(_info: &core::panic::PanicInfo) -> ! {
45 unsafe {
46 cortex_m::asm::udf();
47 core::hint::unreachable_unchecked();
48 }
49}
diff --git a/embassy-traits/Cargo.toml b/embassy-traits/Cargo.toml
index 39875687f..fa2082ef3 100644
--- a/embassy-traits/Cargo.toml
+++ b/embassy-traits/Cargo.toml
@@ -11,4 +11,6 @@ std = []
11embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } 11embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
12embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.6", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy" } 12embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.6", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy" }
13embedded-hal-async = { version = "0.0.1", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy"} 13embedded-hal-async = { version = "0.0.1", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy"}
14embedded-storage = "0.3.0"
15embedded-storage-async = "0.3.0"
14nb = "1.0.0" 16nb = "1.0.0"
diff --git a/embassy-traits/src/adapter.rs b/embassy-traits/src/adapter.rs
index 415b5e814..735f9aacc 100644
--- a/embassy-traits/src/adapter.rs
+++ b/embassy-traits/src/adapter.rs
@@ -254,3 +254,56 @@ where
254 async move { self.wrapped.bflush() } 254 async move { self.wrapped.bflush() }
255 } 255 }
256} 256}
257
258/// NOR flash wrapper
259use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
260use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash};
261
262impl<T> ErrorType for BlockingAsync<T>
263where
264 T: ErrorType,
265{
266 type Error = T::Error;
267}
268
269impl<T> AsyncNorFlash for BlockingAsync<T>
270where
271 T: NorFlash,
272{
273 const WRITE_SIZE: usize = <T as NorFlash>::WRITE_SIZE;
274 const ERASE_SIZE: usize = <T as NorFlash>::ERASE_SIZE;
275
276 type WriteFuture<'a>
277 where
278 Self: 'a,
279 = impl Future<Output = Result<(), Self::Error>> + 'a;
280 fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
281 async move { self.wrapped.write(offset, data) }
282 }
283
284 type EraseFuture<'a>
285 where
286 Self: 'a,
287 = impl Future<Output = Result<(), Self::Error>> + 'a;
288 fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
289 async move { self.wrapped.erase(from, to) }
290 }
291}
292
293impl<T> AsyncReadNorFlash for BlockingAsync<T>
294where
295 T: ReadNorFlash,
296{
297 const READ_SIZE: usize = <T as ReadNorFlash>::READ_SIZE;
298 type ReadFuture<'a>
299 where
300 Self: 'a,
301 = impl Future<Output = Result<(), Self::Error>> + 'a;
302 fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
303 async move { self.wrapped.read(address, data) }
304 }
305
306 fn capacity(&self) -> usize {
307 self.wrapped.capacity()
308 }
309}
diff --git a/examples/boot/.cargo/config.toml b/examples/boot/.cargo/config.toml
new file mode 100644
index 000000000..d044e9b4c
--- /dev/null
+++ b/examples/boot/.cargo/config.toml
@@ -0,0 +1,7 @@
1[unstable]
2namespaced-features = true
3build-std = ["core"]
4build-std-features = ["panic_immediate_abort"]
5
6[build]
7target = "thumbv7em-none-eabi"
diff --git a/examples/boot/Cargo.toml b/examples/boot/Cargo.toml
new file mode 100644
index 000000000..36e2e169d
--- /dev/null
+++ b/examples/boot/Cargo.toml
@@ -0,0 +1,19 @@
1[package]
2authors = ["Ulf Lilleengen <[email protected]>"]
3edition = "2018"
4name = "embassy-boot-examples"
5version = "0.1.0"
6
7[dependencies]
8embassy = { version = "0.1.0", path = "../../embassy" }
9embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["time-driver-rtc1", "gpiote"] }
10embassy-boot-nrf = { version = "0.1.0", path = "../../embassy-boot/nrf" }
11embassy-traits = { version = "0.1.0", path = "../../embassy-traits" }
12
13defmt = { version = "0.3", optional = true }
14defmt-rtt = { version = "0.3", optional = true }
15panic-reset = { version = "0.1.1" }
16embedded-hal = { version = "0.2.6" }
17
18cortex-m = "0.7.3"
19cortex-m-rt = "0.7.0"
diff --git a/examples/boot/README.md b/examples/boot/README.md
new file mode 100644
index 000000000..b97513a9d
--- /dev/null
+++ b/examples/boot/README.md
@@ -0,0 +1,31 @@
1# Examples using bootloader
2
3Example for nRF52 demonstrating the bootloader. The example consists of application binaries, 'a'
4which allows you to press a button to start the DFU process, and 'b' which is the updated
5application.
6
7
8## Prerequisites
9
10* `cargo-binutils`
11* `cargo-flash`
12* `embassy-boot-nrf`
13
14## Usage
15
16
17
18```
19# Flash bootloader
20cargo flash --manifest-path ../../embassy-boot/nrf/Cargo.toml --release --features embassy-nrf/nrf52840 --chip nRF52840_xxAA
21# Build 'b'
22cargo build --release --features embassy-nrf/nrf52840 --bin b
23# Generate binary for 'b'
24cargo objcopy --release --features embassy-nrf/nrf52840 --bin b -- -O binary b.bin
25```
26
27# Flash `a` (which includes b.bin)
28
29```
30cargo flash --release --features embassy-nrf/nrf52840 --bin a --chip nRF52840_xxAA
31```
diff --git a/examples/boot/build.rs b/examples/boot/build.rs
new file mode 100644
index 000000000..cd1a264c4
--- /dev/null
+++ b/examples/boot/build.rs
@@ -0,0 +1,34 @@
1//! This build script copies the `memory.x` file from the crate root into
2//! a directory where the linker can always find it at build time.
3//! For many projects this is optional, as the linker always searches the
4//! project root directory -- wherever `Cargo.toml` is. However, if you
5//! are using a workspace or have a more complicated build setup, this
6//! build script becomes required. Additionally, by requesting that
7//! Cargo re-run the build script whenever `memory.x` is changed,
8//! updating `memory.x` ensures a rebuild of the application with the
9//! new memory settings.
10
11use std::env;
12use std::fs::File;
13use std::io::Write;
14use std::path::PathBuf;
15
16fn main() {
17 // Put `memory.x` in our output directory and ensure it's
18 // on the linker search path.
19 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
20 File::create(out.join("memory.x"))
21 .unwrap()
22 .write_all(include_bytes!("memory.x"))
23 .unwrap();
24 println!("cargo:rustc-link-search={}", out.display());
25
26 // By default, Cargo will re-run a build script whenever
27 // any file in the project changes. By specifying `memory.x`
28 // here, we ensure the build script is only re-run when
29 // `memory.x` is changed.
30 println!("cargo:rerun-if-changed=memory.x");
31
32 println!("cargo:rustc-link-arg-bins=--nmagic");
33 println!("cargo:rustc-link-arg-bins=-Tlink.x");
34}
diff --git a/examples/boot/memory.x b/examples/boot/memory.x
new file mode 100644
index 000000000..dfb72103f
--- /dev/null
+++ b/examples/boot/memory.x
@@ -0,0 +1,14 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K
5 FLASH : ORIGIN = 0x00007000, LENGTH = 64K
6 DFU : ORIGIN = 0x00017000, LENGTH = 68K
7 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
8}
9
10__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
11__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
12
13__bootloader_dfu_start = ORIGIN(DFU);
14__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/examples/boot/src/bin/a.rs b/examples/boot/src/bin/a.rs
new file mode 100644
index 000000000..88880e688
--- /dev/null
+++ b/examples/boot/src/bin/a.rs
@@ -0,0 +1,49 @@
1#![no_std]
2#![no_main]
3#![macro_use]
4#![feature(generic_associated_types)]
5#![feature(type_alias_impl_trait)]
6
7use embassy_boot_nrf::updater;
8use embassy_nrf::{
9 gpio::{Input, Pull},
10 gpio::{Level, Output, OutputDrive},
11 nvmc::Nvmc,
12 Peripherals,
13};
14use embassy_traits::adapter::BlockingAsync;
15use embedded_hal::digital::v2::InputPin;
16use panic_reset as _;
17
18static APP_B: &[u8] = include_bytes!("../../b.bin");
19
20#[embassy::main]
21async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
22 let mut button = Input::new(p.P0_11, Pull::Up);
23 let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard);
24 //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard);
25 //let mut button = Input::new(p.P1_02, Pull::Up);
26
27 let nvmc = Nvmc::new(p.NVMC);
28 let mut nvmc = BlockingAsync::new(nvmc);
29
30 loop {
31 button.wait_for_any_edge().await;
32 if button.is_low().unwrap() {
33 let mut updater = updater::new();
34 let mut offset = 0;
35 for chunk in APP_B.chunks(4096) {
36 let mut buf: [u8; 4096] = [0; 4096];
37 buf[..chunk.len()].copy_from_slice(chunk);
38 updater
39 .write_firmware(offset, &buf, &mut nvmc)
40 .await
41 .unwrap();
42 offset += chunk.len();
43 }
44 updater.mark_update(&mut nvmc).await.unwrap();
45 led.set_high();
46 cortex_m::peripheral::SCB::sys_reset();
47 }
48 }
49}
diff --git a/examples/boot/src/bin/b.rs b/examples/boot/src/bin/b.rs
new file mode 100644
index 000000000..18bb6330c
--- /dev/null
+++ b/examples/boot/src/bin/b.rs
@@ -0,0 +1,26 @@
1#![no_std]
2#![no_main]
3#![macro_use]
4#![feature(generic_associated_types)]
5#![feature(type_alias_impl_trait)]
6
7use embassy::time::{Duration, Timer};
8use embassy_nrf::{
9 gpio::{Level, Output, OutputDrive},
10 Peripherals,
11};
12
13use panic_reset as _;
14
15#[embassy::main]
16async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
17 let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard);
18 //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard);
19
20 loop {
21 led.set_high();
22 Timer::after(Duration::from_millis(300)).await;
23 led.set_low();
24 Timer::after(Duration::from_millis(300)).await;
25 }
26}