From ad4ad645047982211371a869e0f83b670cad0621 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Thu, 17 Dec 2020 13:00:34 +0000 Subject: [PATCH 01/67] Add cli module docstring. --- src/devices/cli/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index fcd2d73c..ead2528b 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -1,3 +1,9 @@ +//! Generic command line interface. +//! +//! This module contains functionality for the CLI, except +//! for construction which is implementation-specific so is +//! handled in the `port` module. + #![macro_use] use crate::{ devices::bootloader::Bootloader, From 0e0e37b56fc4f09288192665e7a97ac649e8e106 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 22 Dec 2020 12:46:54 +0100 Subject: [PATCH 02/67] Add boot error message --- .cargo/config | 1 + src/devices/cli/mod.rs | 1 + src/ports/stm32f412_discovery/bootloader.rs | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.cargo/config b/.cargo/config index b9a19796..e5a312a0 100644 --- a/.cargo/config +++ b/.cargo/config @@ -27,3 +27,4 @@ gen_loadstone = "objcopy --bin loadstone --release --target thumbv7em-none-eabih st = "strip --bin loadstone --release --target thumbv7em-none-eabihf" d = "doc --release --target thumbv7em-none-eabihf" dop = "doc --release --target thumbv7em-none-eabihf --open" +bl = "bloat --release --bin loadstone --target thumbv7em-none-eabihf" diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index d2ccd0a1..535a1ff8 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -163,6 +163,7 @@ const ALLOWED_TOKENS: &str = " =_"; const LINE_TERMINATOR: char = '\n'; impl Cli { + pub fn run(&mut self, bootloader: &mut Bootloader) where EXTF: flash::ReadWrite, diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 4636792c..5e2cb7eb 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -103,8 +103,10 @@ impl Bootloader { uwriteln!(&mut serial, "Attempted to boot from invalid bank.").unwrap(), Some(Error::BankEmpty) => uwriteln!(&mut serial, "Attempted to boot from empty bank.").unwrap(), - Some(_) => - uwriteln!(&mut serial, "Unexpected boot error.").unwrap(), + Some(e) => { + uwriteln!(&mut serial, "Unexpected boot error:").unwrap(); + e.report(&mut serial); + } None => (), }; From 39676fc5c07cfa8c9d7200f41d1b69e4d8057ec1 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 5 Jan 2021 13:29:15 +0000 Subject: [PATCH 03/67] Add documentation to some public methods. --- src/devices/bootloader.rs | 11 +++++++++++ src/devices/cli/mod.rs | 4 ++++ src/devices/image.rs | 8 ++++++-- src/hal/spi.rs | 3 +++ src/utilities/buffer.rs | 3 +++ src/utilities/iterator.rs | 3 +++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 42202899..082caf8e 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -43,6 +43,8 @@ where SRL: serial::ReadWrite, Error: From<::Error>, { + /// Runs the boot loader, entering CLI if the boot fails. If interactive mode is enabled for + /// the bootloader, the CLI is entered without attemping a boot. pub fn run(mut self) -> ! { // Basic runtime sanity checks: all bank indices must be sequential starting from MCU let indices = @@ -61,6 +63,7 @@ where } } + /// Writes a firmware image to an external flash bank. pub fn store_image( &mut self, mut bytes: I, @@ -94,6 +97,7 @@ where image::ImageHeader::write(&mut self.external_flash, &bank, size) } + /// Boots into a given memory bank. pub fn boot( mcu_flash: &mut MCUF, mcu_banks: &'static [image::Bank<::Address>], bank_index: u8 ) -> Result { @@ -127,6 +131,7 @@ where } } + /// Formats all MCU flash banks. pub fn format_mcu_flash(&mut self) -> Result<(), Error> { block!(self.mcu_flash.erase())?; image::GlobalHeader::format_default(&mut self.mcu_flash)?; @@ -136,6 +141,7 @@ where Ok(()) } + /// Formats all external flash banks. pub fn format_external_flash(&mut self) -> Result<(), Error> { block!(self.external_flash.erase())?; image::GlobalHeader::format_default(&mut self.external_flash)?; @@ -145,14 +151,17 @@ where Ok(()) } + /// Runs a self test on MCU flash. pub fn test_mcu_flash(&mut self) -> Result<(), Error> { Self::test_flash_read_write_cycle(&mut self.mcu_flash) } + /// Runs a self test on external flash. pub fn test_external_flash(&mut self) -> Result<(), Error> { Self::test_flash_read_write_cycle(&mut self.external_flash) } + /// Finds and returns the image header of a given bank index. pub fn image_at_bank(&mut self, index: u8) -> Option { let mcu_bank = self.mcu_banks().find(|b| b.index == index); let external_bank = self.external_banks().find(|b| b.index == index); @@ -171,10 +180,12 @@ where } } + /// Returns an iterator of all MCU flash banks. pub fn mcu_banks(&self) -> impl Iterator> { self.mcu_banks.iter().cloned() } + /// Returns an iterator of all external flash banks. pub fn external_banks(&self) -> impl Iterator> { self.external_banks.iter().cloned() } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index ead2528b..21dc63ac 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -162,6 +162,7 @@ const ALLOWED_TOKENS: &str = " =_"; const LINE_TERMINATOR: char = '\n'; impl Cli { + /// Reads a line, parses it as a command and attempts to execute it. pub fn run(&mut self, bootloader: &mut Bootloader) where EXTF: flash::ReadWrite, @@ -227,8 +228,10 @@ impl Cli { self.needs_prompt = true; } + /// Returns the serial the CLI is using. pub fn serial(&mut self) -> &mut SRL { &mut self.serial } + /// Attempts to parse a given string into a command name and arguments. fn parse(text: &str) -> Result<(Name, ArgumentIterator), Error> { let text = text.trim_end_matches(|c: char| c.is_ascii_control() || c.is_ascii_whitespace()); if text.is_empty() { @@ -266,6 +269,7 @@ impl Cli { Ok((name, arguments)) } + /// Creates a new CLI using the given serial. pub fn new(serial: SRL) -> Result { Ok(Cli { serial, greeted: false, needs_prompt: true }) } diff --git a/src/devices/image.rs b/src/devices/image.rs index b1384ec6..543203eb 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -48,6 +48,7 @@ pub struct Bank { } impl GlobalHeader { + /// Attempts to retrieve a header from flash. pub fn retrieve(flash: &mut F) -> Result where A: Address, @@ -68,7 +69,7 @@ impl GlobalHeader { } } - // Writes a default global header to flash at the right location. + /// Writes a default global header to flash at the right location. pub fn format_default(flash: &mut F) -> Result<(), Error> where A: Address, @@ -87,6 +88,7 @@ impl GlobalHeader { } impl ImageHeader { + /// Retrieves an image header from a given bank in flash memory. pub fn retrieve(flash: &mut F, bank: &Bank) -> Result where A: Address, @@ -106,7 +108,7 @@ impl ImageHeader { } } - // Writes a default image header to flash at a given location + /// Writes a default image header to flash at a given location pub fn format_default(flash: &mut F, bank: &Bank) -> Result<(), Error> where A: Address, @@ -122,6 +124,7 @@ impl ImageHeader { Ok(block!(unsafe { flash.serialize(&default_header, address) })?) } + /// Attempts to write a header to a given bank of flash. pub fn write(flash: &mut F, bank: &Bank, size: usize) -> Result<(), Error> where A: Address, @@ -147,6 +150,7 @@ impl ImageHeader { Ok(()) } + /// Performs a CRC check on a given image. pub fn validate_image(flash: &mut F, location: A, size: usize) -> Result where A: Address, diff --git a/src/hal/spi.rs b/src/hal/spi.rs index e224292e..aebd3dfe 100644 --- a/src/hal/spi.rs +++ b/src/hal/spi.rs @@ -1,3 +1,6 @@ +//! Traits for Serial Peripheral Interface implementation. + +// Allows the transmission and reception of a word in full duplex. pub trait FullDuplex { type Error; diff --git a/src/utilities/buffer.rs b/src/utilities/buffer.rs index 7a6e9d3b..9b827718 100644 --- a/src/utilities/buffer.rs +++ b/src/utilities/buffer.rs @@ -1,10 +1,13 @@ pub trait CollectSlice: Iterator { + /// Collects an iterator into a given slice, returning the number of collected items. fn collect_slice(&mut self, slice: &mut [Self::Item]) -> usize; } pub trait TryCollectSlice: Iterator { type Element; type Error; + + /// Attempts to collect an iterator into a given slice, returning the number of collected items. fn try_collect_slice(&mut self, slice: &mut [Self::Element]) -> Result; } diff --git a/src/utilities/iterator.rs b/src/utilities/iterator.rs index 97853d2d..c612bc56 100644 --- a/src/utilities/iterator.rs +++ b/src/utilities/iterator.rs @@ -1,4 +1,7 @@ +//! Iterator utilities + pub trait Unique { + /// Returns true if no element equals any other, false otherwise. fn all_unique(self) -> bool; } From 9d7c54b0f8a8f2704e94f6a5f03582ac6eb6ddea Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 5 Jan 2021 14:53:36 +0100 Subject: [PATCH 04/67] WIP fix gpio --- Cargo.toml | 24 +- bluefruit_rust_hal/Cargo.toml | 9 - bluefruit_rust_hal/src/main.rs | 3 - src/devices/bootloader.rs | 3 +- src/devices/cli/commands/mod.rs | 2 +- src/devices/cli/file_transfer.rs | 6 +- src/devices/cli/mod.rs | 10 +- src/devices/image.rs | 9 +- src/drivers/led.rs | 316 --------- src/drivers/micron/n25q128a_flash.rs | 635 ------------------ src/drivers/mod.rs | 28 - src/drivers/stm32f4/flash.rs | 402 ----------- src/drivers/stm32f4/gpio.rs | 489 -------------- src/drivers/stm32f4/qspi.rs | 419 ------------ src/drivers/stm32f4/rcc.rs | 70 -- src/drivers/stm32f4/serial.rs | 540 --------------- src/drivers/stm32f4/spi.rs | 167 ----- src/drivers/stm32f4/systick.rs | 79 --- src/error.rs | 2 +- src/hal/doubles/error.rs | 10 - src/hal/doubles/flash.rs | 70 -- src/hal/doubles/gpio.rs | 25 - src/hal/doubles/mod.rs | 7 - src/hal/doubles/qspi.rs | 73 -- src/hal/doubles/serial.rs | 21 - src/hal/doubles/spi.rs | 40 -- src/hal/doubles/time.rs | 23 - src/hal/flash.rs | 58 -- src/hal/gpio.rs | 20 - src/hal/led.rs | 25 - src/hal/mod.rs | 15 - src/hal/qspi.rs | 23 - src/hal/serial.rs | 181 ----- src/hal/spi.rs | 7 - src/hal/time.rs | 111 --- src/lib.rs | 13 - src/ports/mod.rs | 2 + src/ports/stm32f412_discovery/bootloader.rs | 14 +- .../stm32f412_discovery/pin_configuration.rs | 14 +- src/utilities/bitwise.rs | 85 --- src/utilities/buffer.rs | 60 -- src/utilities/guard.rs | 58 -- src/utilities/iterator.rs | 29 - src/utilities/macros.rs | 62 -- src/utilities/memory.rs | 209 ------ src/utilities/xmodem.rs | 146 ---- tools/upload.sh | 2 +- 47 files changed, 32 insertions(+), 4584 deletions(-) delete mode 100644 bluefruit_rust_hal/Cargo.toml delete mode 100644 bluefruit_rust_hal/src/main.rs delete mode 100644 src/drivers/led.rs delete mode 100644 src/drivers/micron/n25q128a_flash.rs delete mode 100644 src/drivers/mod.rs delete mode 100644 src/drivers/stm32f4/flash.rs delete mode 100644 src/drivers/stm32f4/gpio.rs delete mode 100644 src/drivers/stm32f4/qspi.rs delete mode 100644 src/drivers/stm32f4/rcc.rs delete mode 100644 src/drivers/stm32f4/serial.rs delete mode 100644 src/drivers/stm32f4/spi.rs delete mode 100644 src/drivers/stm32f4/systick.rs delete mode 100644 src/hal/doubles/error.rs delete mode 100644 src/hal/doubles/flash.rs delete mode 100644 src/hal/doubles/gpio.rs delete mode 100644 src/hal/doubles/mod.rs delete mode 100644 src/hal/doubles/qspi.rs delete mode 100644 src/hal/doubles/serial.rs delete mode 100644 src/hal/doubles/spi.rs delete mode 100644 src/hal/doubles/time.rs delete mode 100644 src/hal/flash.rs delete mode 100644 src/hal/gpio.rs delete mode 100644 src/hal/led.rs delete mode 100644 src/hal/mod.rs delete mode 100644 src/hal/qspi.rs delete mode 100644 src/hal/serial.rs delete mode 100644 src/hal/spi.rs delete mode 100644 src/hal/time.rs delete mode 100644 src/utilities/bitwise.rs delete mode 100644 src/utilities/buffer.rs delete mode 100644 src/utilities/guard.rs delete mode 100644 src/utilities/iterator.rs delete mode 100644 src/utilities/macros.rs delete mode 100644 src/utilities/memory.rs delete mode 100644 src/utilities/xmodem.rs diff --git a/Cargo.toml b/Cargo.toml index 936fdc23..25d17eac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ default-run = "loadstone" [features] default = [ "stm32f412_discovery", + "cortex_m_any", "defmt-default", ] defmt-default = [] @@ -20,30 +21,23 @@ defmt-debug = [] defmt-info = [] defmt-warn = [] defmt-error = [] -stm32f412_discovery = ["stm32f412"] -stm32f429 = ["stm32f4/stm32f429", "stm32f4_any"] -stm32f469 = ["stm32f4/stm32f469", "stm32f4_any"] -stm32f407 = ["stm32f4/stm32f407", "stm32f4_any"] -stm32f412 = ["stm32f4/stm32f412", "stm32f4_any"] -stm32f4_any = ["stm32_any"] -stm32_any = ["cortex_m_any"] cortex_m_any = [] +stm32f412_discovery = [] [dependencies] cortex-m = "0.6.0" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" -bluefruit_rust_hal = { path = "bluefruit_rust_hal" } -panic-abort = "*" nb = "*" -paste = "*" +panic-abort = "*" static_assertions = "*" defmt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } defmt-rtt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } -[dependencies.nom] -version = "*" -default-features = false +[dependencies.blue_hal] +git = "ssh://git@github.com/absw/blue_hal.git" +branch = "main" +features = ["stm32f412_discovery"] [dependencies.ufmt] version = "*" @@ -53,10 +47,6 @@ default-features = false version = "*" default-features = false -[dependencies.stm32f4] -version = "*" -features = ["rt"] - [lib] name = "loadstone_lib" test = true diff --git a/bluefruit_rust_hal/Cargo.toml b/bluefruit_rust_hal/Cargo.toml deleted file mode 100644 index 07c5d94d..00000000 --- a/bluefruit_rust_hal/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bluefruit_rust_hal" -version = "0.1.0" -authors = ["PabloMansanet "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/bluefruit_rust_hal/src/main.rs b/bluefruit_rust_hal/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/bluefruit_rust_hal/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 64ce822e..0fd8b698 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -8,7 +8,8 @@ use super::{ cli::{self, file_transfer::FileBlock}, image, }; -use crate::{devices::cli::Cli, error::Error, hal::{flash, serial}, utilities::buffer::CollectSlice}; +use crate::{devices::cli::Cli, error::Error}; +use blue_hal::{hal::{serial, flash}, utilities::buffer::CollectSlice}; use cli::file_transfer; use core::{cmp::min, mem::size_of}; use core::array::IntoIter; diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index dc492e6c..f0dffb97 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -4,8 +4,8 @@ use crate::{ cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, }, error::Error as BootloaderError, - hal::{flash, serial}, }; +use blue_hal::{hal::{flash, serial}, uprintln}; use ufmt::uwriteln; commands!( cli, bootloader, names, helpstrings [ diff --git a/src/devices/cli/file_transfer.rs b/src/devices/cli/file_transfer.rs index 30ae1c61..2f9fa19a 100644 --- a/src/devices/cli/file_transfer.rs +++ b/src/devices/cli/file_transfer.rs @@ -1,7 +1,4 @@ -use crate::{ - hal::serial::{TimeoutRead, Write}, - utilities::xmodem, -}; +use blue_hal::{hal::serial::{Write, TimeoutRead}, utilities::xmodem}; pub const BLOCK_SIZE: usize = xmodem::PAYLOAD_SIZE; pub type FileBlock = [u8; BLOCK_SIZE]; @@ -114,7 +111,6 @@ impl<'a, S: TimeoutRead + Write + ?Sized> BlockIterator<'a, S> { } impl<'a, S: TimeoutRead + Write + ?Sized> Drop for BlockIterator<'a, S> { - // Must fully consume the iterator on drop // to close the xmodem communication cleanly fn drop(&mut self) { self.for_each(drop); } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 535a1ff8..6e9058d9 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -2,15 +2,11 @@ use crate::{ devices::bootloader::Bootloader, error::Error as BootloaderError, - hal::{ - flash, - serial::{self, Read}, - }, - utilities::{buffer::TryCollectSlice, iterator::Unique}, }; use core::str::{from_utf8, SplitWhitespace}; +use blue_hal::{hal::{flash, serial::{self, Read}}, uprint, uprintln, utilities::buffer::TryCollectSlice, utilities::iterator::Unique}; use nb::block; -use ufmt::{uwrite, uwriteln}; +use ufmt::{uwriteln, uwrite}; use self::file_transfer::FileTransfer; @@ -373,7 +369,7 @@ mod commands; #[cfg(test)] mod test { use super::*; - use crate::hal::doubles::serial::*; + use blue_hal::hal::doubles::serial::*; #[test] fn basic_command_parsing() { diff --git a/src/devices/image.rs b/src/devices/image.rs index b1384ec6..6899c16f 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,9 +1,6 @@ -use crate::{ - error::Error, - hal::flash::{self, UnportableDeserialize, UnportableSerialize}, - utilities::memory::Address, -}; +use crate::error::Error; use core::{cmp::min, mem::size_of}; +use blue_hal::{hal::flash::{self, UnportableSerialize, UnportableDeserialize}, utilities::memory::Address}; use crc::{crc32, Hasher32}; use nb::{self, block}; @@ -207,7 +204,7 @@ impl Bank { #[cfg(test)] mod tests { use super::*; - use crate::hal::{ + use blue_hal::hal::{ doubles::flash::{Address, FakeFlash}, flash::ReadWrite, }; diff --git a/src/drivers/led.rs b/src/drivers/led.rs deleted file mode 100644 index 6f2b254b..00000000 --- a/src/drivers/led.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! Various generic LED device implementations. Assumed to work -//! on every kind of board, as they're generic over OutputPins -//! and support both logic levels. - -use crate::hal::{ - gpio::OutputPin, - led::{self, Chromatic, Toggle}, -}; - -/// Multi-color type for RGB LEDs -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum RgbPalette { - Red, - Green, - Blue, -} - -/// Solid (non-blinking) color RGB LED -/// -/// # Example -/// ``` -/// # use loadstone_lib::hal::led::*; -/// # use loadstone_lib::drivers::led::*; -/// # use loadstone_lib::hal::doubles::gpio::*; -/// # let pin = MockPin::default(); -/// # let (red_pin, green_pin, blue_pin) = (pin.clone(), pin.clone(), pin.clone()); -/// let mut led = RgbLed::new(red_pin, green_pin, blue_pin, LogicLevel::Direct); -/// -/// // By default, the LED starts on an off, green state -/// # assert!(led.pin(RgbPalette::Red).is_low()); -/// # assert!(led.pin(RgbPalette::Green).is_low()); -/// # assert!(led.pin(RgbPalette::Blue).is_low()); -/// assert!(!led.is_on()); -/// -/// led.on(); // This will shine green -/// # assert!(led.pin(RgbPalette::Red).is_low()); -/// # assert!(led.pin(RgbPalette::Green).is_high()); -/// # assert!(led.pin(RgbPalette::Blue).is_low()); -/// assert_eq!(RgbPalette::Green, led.get_color()); -/// assert!(led.is_on()); -/// -/// led.color(RgbPalette::Blue); -/// led.toggle(); -/// # assert!(led.pin(RgbPalette::Red).is_low()); -/// # assert!(led.pin(RgbPalette::Green).is_low()); -/// # assert!(led.pin(RgbPalette::Blue).is_low()); -/// assert_eq!(RgbPalette::Blue, led.get_color()); -/// assert!(!led.is_on()); -/// ``` -pub struct RgbLed { - red: Pin, - green: Pin, - blue: Pin, - color: RgbPalette, - is_on: bool, - logic: LogicLevel, -} - -/// Solid (non-blinking) monochrome LED -/// -/// # Example -/// ``` -/// # use loadstone_lib::hal::led::*; -/// # use loadstone_lib::drivers::led::*; -/// # use loadstone_lib::hal::doubles::gpio::*; -/// # let pin = MockPin::default(); -/// let mut led = MonochromeLed::new(pin, LogicLevel::Direct); -/// -/// led.toggle(); -/// assert!(led.is_on()); -/// # assert!(led.pin().is_high()); -/// ``` -pub struct MonochromeLed { - pin: Pin, - is_on: bool, - logic: LogicLevel, -} - -#[derive(Copy, Clone)] -pub enum LogicLevel { - /// LogicLevel high equals "on" - Direct, - /// LogicLevel high equals "off" - Inverted, -} - -// Extension trait to ensure LED pins are correctly -// operated based on the led's direct or inverted logic -trait LedPin: OutputPin { - fn off(&mut self, logic: LogicLevel) { - if let LogicLevel::Direct = logic { - self.set_low(); - } else { - self.set_high(); - } - } - - fn on(&mut self, logic: LogicLevel) { - if let LogicLevel::Direct = logic { - self.set_high(); - } else { - self.set_low(); - } - } -} - -// Blanket implementation of LedPin for all output pins -impl LedPin for Pin {} - -impl MonochromeLed { - pub fn new(mut pin: Pin, logic: LogicLevel) -> Self { - pin.off(logic); - Self { pin, is_on: false, logic } - } - pub fn is_on(&self) -> bool { self.is_on } -} - -// Turn off when going out of scope -impl Drop for MonochromeLed { - fn drop(&mut self) { self.off(); } -} - -impl led::Toggle for MonochromeLed { - fn on(&mut self) { - if !self.is_on { - self.pin.on(self.logic); - } - self.is_on = true; - } - - fn off(&mut self) { - if self.is_on { - self.pin.off(self.logic); - } - self.is_on = false; - } - - fn toggle(&mut self) { - if self.is_on { - self.off(); - } else { - self.on(); - } - } -} - -impl RgbLed { - pub fn new(mut red: Pin, mut green: Pin, mut blue: Pin, logic: LogicLevel) -> Self { - red.off(logic); - green.off(logic); - blue.off(logic); - Self { red, green, blue, color: RgbPalette::Green, is_on: false, logic } - } - pub fn get_color(&self) -> RgbPalette { self.color } - pub fn is_on(&self) -> bool { self.is_on } -} - -// Turn off when going out of scope -impl Drop for RgbLed { - fn drop(&mut self) { self.off(); } -} - -impl led::Toggle for RgbLed { - fn on(&mut self) { - if !self.is_on { - match self.color { - RgbPalette::Red => { - self.red.on(self.logic); - } - RgbPalette::Green => { - self.green.on(self.logic); - } - RgbPalette::Blue => { - self.blue.on(self.logic); - } - } - } - self.is_on = true; - } - - fn off(&mut self) { - if self.is_on { - self.red.off(self.logic); - self.green.off(self.logic); - self.blue.off(self.logic); - } - self.is_on = false; - } - - fn toggle(&mut self) { - if self.is_on { - self.off(); - } else { - self.on(); - } - } -} - -impl Chromatic for RgbLed { - fn color(&mut self, color: RgbPalette) { - self.color = color; - if self.is_on { - self.off(); - self.on(); - } - } -} - -#[cfg(not(target_arch = "arm"))] -#[doc(hidden)] -pub mod doubles { - use super::*; - use crate::hal::doubles::gpio::*; - - #[doc(hidden)] - impl MonochromeLed { - pub fn pin(&self) -> &MockPin { &self.pin } - } - - #[doc(hidden)] - impl RgbLed { - pub fn pin(&self, color: RgbPalette) -> &MockPin { - match color { - RgbPalette::Red => &self.red, - RgbPalette::Green => &self.green, - RgbPalette::Blue => &self.blue, - } - } - } -} - -#[cfg(test)] -mod test { - use super::{doubles::*, *}; - use crate::hal::doubles::gpio::*; - - #[test] - fn monochrome_led_defaults_to_logic_low_with_direct_logic() { - // Given - let led = MonochromeLed::new(MockPin::default(), LogicLevel::Direct); - - // then - assert!(led.pin.is_low()); - } - - #[test] - fn monochrome_led_defaults_to_logic_high_with_inverted_logic() { - // Given - let led = MonochromeLed::new(MockPin::default(), LogicLevel::Inverted); - - // then - assert!(led.pin.is_high()); - } - - #[test] - fn monochrome_pin_setting() { - // Given - let mut led = MonochromeLed::new(MockPin::default(), LogicLevel::Direct); - - // When - led.off(); - - // Then - assert!(led.pin.is_low()); - - // When - led.on(); - - // Then - assert!(led.pin.is_high()); - } - - #[test] - fn monochrome_pin_toggling() { - // Given - let mut led = MonochromeLed::new(MockPin::default(), LogicLevel::Direct); - - // When - led.toggle(); - - // Then - assert!(led.pin.is_high()); - - // When - led.toggle(); - - // Then - assert!(led.pin.is_low()); - } - - #[test] - fn type_erasure_between_chromatic_and_non_chromatic_led() { - // Given - let mut monochrome = MonochromeLed::new(MockPin::default(), LogicLevel::Inverted); - let mut chromatic = RgbLed::new( - MockPin::default(), - MockPin::default(), - MockPin::default(), - LogicLevel::Direct, - ); - - chromatic.color(RgbPalette::Red); - - // Array over generic "toggleable" leds - let mut array: [&mut dyn led::Toggle; 2] = [&mut monochrome, &mut chromatic]; - - // When - array.iter_mut().for_each(|l| l.toggle()); - - // Then - assert!(monochrome.pin.is_low()); // Inverted - assert!(chromatic.red.is_high()); - assert!(chromatic.green.is_low()); - assert!(chromatic.blue.is_low()); - } -} diff --git a/src/drivers/micron/n25q128a_flash.rs b/src/drivers/micron/n25q128a_flash.rs deleted file mode 100644 index 7166be32..00000000 --- a/src/drivers/micron/n25q128a_flash.rs +++ /dev/null @@ -1,635 +0,0 @@ -//! Device driver for the [Micron N24q128a](../../../../../../documentation/hardware/micron_flash.pdf#page=0) -use crate::{ - hal::{flash::ReadWrite, qspi, time}, - utilities::{ - bitwise::{BitFlags, SliceBitSubset}, - memory::{self, IterableByOverlaps, Region}, - }, -}; -use core::{ - marker::PhantomData, - ops::{Add, Sub}, -}; -use nb::block; - -/// From [datasheet table 19](../../../../../../../documentation/hardware/micron_flash.pdf#page=37) -pub const MANUFACTURER_ID: u8 = 0x20; - -/// Address into the micron chip [memory map](../../../../../../../documentation/hardware/micron_flash.pdf#page=14) -#[derive(Default, Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord)] -pub struct Address(pub u32); -impl Add for Address { - type Output = Self; - fn add(self, rhs: usize) -> Address { Address(self.0 + rhs as u32) } -} -impl Sub for Address { - type Output = Self; - fn sub(self, rhs: usize) -> Address { Address(self.0.saturating_sub(rhs as u32)) } -} -impl Sub
for Address { - type Output = usize; - fn sub(self, rhs: Address) -> usize { self.0.saturating_sub(rhs.0) as usize } -} -impl Into for Address { - fn into(self) -> usize { self.0 as usize } -} - -pub struct MemoryMap {} -pub struct Sector(usize); -pub struct Subsector(usize); -pub struct Page(usize); - -impl MemoryMap { - pub fn sectors() -> impl Iterator { (0..NUMBER_OF_SECTORS).map(Sector) } - pub fn subsectors() -> impl Iterator { - (0..NUMBER_OF_SUBSECTORS).map(Subsector) - } - pub fn pages() -> impl Iterator { (0..NUMBER_OF_PAGES).map(Page) } - pub const fn location() -> Address { BASE_ADDRESS } - pub const fn end() -> Address { Address(BASE_ADDRESS.0 + MEMORY_SIZE as u32) } - pub const fn size() -> usize { MEMORY_SIZE } -} - -impl Sector { - pub fn subsectors(&self) -> impl Iterator { - ((self.0 * SUBSECTORS_PER_SECTOR)..((1 + self.0) * SUBSECTORS_PER_SECTOR)).map(Subsector) - } - pub fn pages(&self) -> impl Iterator { - ((self.0 * PAGES_PER_SECTOR)..((1 + self.0) * PAGES_PER_SECTOR)).map(Page) - } - pub fn location(&self) -> Address { BASE_ADDRESS + self.0 * Self::size() } - pub fn end(&self) -> Address { self.location() + Self::size() } - pub fn at(address: Address) -> Option { - MemoryMap::sectors().find(|s| s.contains(address)) - } - pub const fn size() -> usize { SECTOR_SIZE } -} - -impl Subsector { - pub fn pages(&self) -> impl Iterator { - ((self.0 * PAGES_PER_SUBSECTOR)..((1 + self.0) * PAGES_PER_SUBSECTOR)).map(Page) - } - pub fn location(&self) -> Address { BASE_ADDRESS + self.0 * Self::size() } - pub fn end(&self) -> Address { self.location() + Self::size() } - pub fn at(address: Address) -> Option { - MemoryMap::subsectors().find(|s| s.contains(address)) - } - pub const fn size() -> usize { SUBSECTOR_SIZE } -} - -impl Page { - pub fn location(&self) -> Address { BASE_ADDRESS + self.0 * Self::size() } - pub fn end(&self) -> Address { self.location() + Self::size() } - pub fn at(address: Address) -> Option { MemoryMap::pages().find(|p| p.contains(address)) } - pub const fn size() -> usize { PAGE_SIZE } -} - -impl memory::Region
for MemoryMap { - fn contains(&self, address: Address) -> bool { - (address >= BASE_ADDRESS) && (address < BASE_ADDRESS + MEMORY_SIZE) - } -} - -impl memory::Region
for Sector { - fn contains(&self, address: Address) -> bool { - let start = Address((Self::size() * self.0) as u32); - (address >= start) && (address < start + Self::size()) - } -} - -impl memory::Region
for Subsector { - fn contains(&self, address: Address) -> bool { - let start = Address((Self::size() * self.0) as u32); - (address >= start) && (address < start + Self::size()) - } -} - -impl memory::Region
for Page { - fn contains(&self, address: Address) -> bool { - let start = Address((Self::size() * self.0) as u32); - (address >= start) && (address < start + Self::size()) - } -} - -const BASE_ADDRESS: Address = Address(0x0000_0000); - -const PAGES_PER_SUBSECTOR: usize = 16; -const SUBSECTORS_PER_SECTOR: usize = 16; -const PAGES_PER_SECTOR: usize = PAGES_PER_SUBSECTOR * SUBSECTORS_PER_SECTOR; - -const PAGE_SIZE: usize = 256; -const SUBSECTOR_SIZE: usize = PAGE_SIZE * PAGES_PER_SUBSECTOR; -const SECTOR_SIZE: usize = SUBSECTOR_SIZE * SUBSECTORS_PER_SECTOR; -const MEMORY_SIZE: usize = NUMBER_OF_SECTORS * SECTOR_SIZE; - -const NUMBER_OF_SECTORS: usize = 256; -const NUMBER_OF_SUBSECTORS: usize = NUMBER_OF_SECTORS * SUBSECTORS_PER_SECTOR; -const NUMBER_OF_PAGES: usize = NUMBER_OF_SUBSECTORS * PAGES_PER_SUBSECTOR; - -/// MicronN25q128a driver, generic over a QSPI programmed in indirect mode -pub struct MicronN25q128a -where - QSPI: qspi::Indirect, - NOW: time::Now, -{ - qspi: QSPI, - timeout: Option, - _marker: PhantomData, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Error { - TimeOut, - QspiError, - WrongManufacturerId, - MisalignedAccess, - AddressOutOfRange, -} - -#[derive(Debug, Clone, Copy)] -enum Command { - PageProgram = 0x02, - Read = 0x03, - WriteDisable = 0x04, - ReadStatus = 0x05, - WriteEnable = 0x06, - SubsectorErase = 0x20, - ReadId = 0x9E, - BulkErase = 0xC7, -} - -struct Status { - write_in_progress: bool, - _write_enable_latch: bool, -} - -enum CommandData<'a> { - Read(&'a mut [u8]), - Write(&'a [u8]), - None, -} - -impl ReadWrite for MicronN25q128a -where - QSPI: qspi::Indirect, - NOW: time::Now, -{ - type Error = Error; - type Address = Address; - - fn erase(&mut self) -> nb::Result<(), Self::Error> { - // Early yield if flash is not ready for writing - if Self::status(&mut self.qspi)?.write_in_progress { - Err(nb::Error::WouldBlock) - } else { - Self::execute_command(&mut self.qspi, Command::WriteEnable, None, CommandData::None)?; - Self::execute_command(&mut self.qspi, Command::BulkErase, None, CommandData::None)?; - Self::execute_command(&mut self.qspi, Command::WriteDisable, None, CommandData::None)?; - Ok(()) - } - } - - fn write(&mut self, address: Address, bytes: &[u8]) -> nb::Result<(), Self::Error> { - if Self::status(&mut self.qspi)?.write_in_progress { - return Err(nb::Error::WouldBlock); - } - - for (bytes, subsector, address) in MemoryMap::subsectors().overlaps(bytes, address) { - let offset_into_subsector = address - subsector.location(); - let mut subsector_data = [0x00u8; SUBSECTOR_SIZE]; - block!(self.read(subsector.location(), &mut subsector_data))?; - if bytes.is_subset_of(&subsector_data[offset_into_subsector..]) { - for (bytes, page, address) in subsector.pages().overlaps(bytes, address) { - block!(self.write_page(&page, bytes, address))?; - } - } else { - block!(self.erase_subsector(&subsector))?; - // "merge" the preexisting data with the new write. - subsector_data - .iter_mut() - .skip(offset_into_subsector) - .zip(bytes) - .for_each(|(a, b)| *a = *b); - for (bytes, page, address) in - subsector.pages().overlaps(&subsector_data, subsector.location()) - { - block!(self.write_page(&page, bytes, address))?; - } - } - } - Ok(()) - } - - fn read(&mut self, address: Address, bytes: &mut [u8]) -> nb::Result<(), Self::Error> { - if Self::status(&mut self.qspi)?.write_in_progress { - Err(nb::Error::WouldBlock) - } else { - Self::execute_command( - &mut self.qspi, - Command::Read, - Some(address), - CommandData::Read(bytes), - ) - } - } - - fn range(&self) -> (Address, Address) { (MemoryMap::location(), MemoryMap::end()) } -} - -impl MicronN25q128a -where - QSPI: qspi::Indirect, - NOW: time::Now, -{ - fn wait_until_write_complete(&mut self) -> nb::Result<(), Error> { - if let Some(timeout) = &self.timeout { - let start = NOW::now(); - while Self::status(&mut self.qspi)?.write_in_progress { - if NOW::now() - start > *timeout { - return Err(nb::Error::Other(Error::TimeOut)); - } - } - } - - if Self::status(&mut self.qspi)?.write_in_progress { - Err(nb::Error::WouldBlock) - } else { - Ok(()) - } - } - - // Low level helper for executing Micron commands - fn execute_command( - qspi: &mut QSPI, - command: Command, - address: Option
, - data: CommandData, - ) -> nb::Result<(), Error> { - match data { - CommandData::Write(buffer) => { - block!(qspi.write(Some(command as u8), address.map(|a| a.0), Some(buffer), 0)) - } - CommandData::Read(buffer) => { - block!(qspi.read(Some(command as u8), address.map(|a| a.0), buffer, 0)) - } - CommandData::None => { - block!(qspi.write(Some(command as u8), address.map(|a| a.0), None, 0)) - } - } - .map_err(|_| nb::Error::Other(Error::QspiError)) - } - - fn verify_id(&mut self) -> nb::Result<(), Error> { - let mut response = [0u8; 1]; - Self::execute_command( - &mut self.qspi, - Command::ReadId, - None, - CommandData::Read(&mut response), - )?; - match response[0] { - MANUFACTURER_ID => Ok(()), - _ => Err(nb::Error::Other(Error::WrongManufacturerId)), - } - } - - fn status(qspi: &mut QSPI) -> nb::Result { - let mut response = [0u8; 1]; - Self::execute_command(qspi, Command::ReadStatus, None, CommandData::Read(&mut response))?; - let response = response[0]; - Ok(Status { - write_in_progress: response.is_set(0), - _write_enable_latch: response.is_set(1), - }) - } - - /// Blocks until flash ID read checks out, or until timeout - pub fn new(qspi: QSPI) -> Result { - let mut flash = Self { qspi, timeout: None, _marker: Default::default() }; - block!(flash.verify_id())?; - Ok(flash) - } - - pub fn with_timeout(qspi: QSPI, timeout: time::Milliseconds) -> Result { - let mut flash = Self { qspi, timeout: Some(timeout), _marker: Default::default() }; - block!(flash.verify_id())?; - Ok(flash) - } - - fn erase_subsector(&mut self, subsector: &Subsector) -> nb::Result<(), Error> { - if Self::status(&mut self.qspi)?.write_in_progress { - return Err(nb::Error::WouldBlock); - } - block!(Self::execute_command( - &mut self.qspi, - Command::WriteEnable, - None, - CommandData::None - ))?; - block!(Self::execute_command( - &mut self.qspi, - Command::SubsectorErase, - Some(subsector.location()), - CommandData::None - ))?; - Ok(block!(self.wait_until_write_complete())?) - } - - fn write_page(&mut self, page: &Page, bytes: &[u8], address: Address) -> nb::Result<(), Error> { - if (address < page.location()) || (address + bytes.len() > page.end()) { - return Err(nb::Error::Other(Error::MisalignedAccess)); - } - if Self::status(&mut self.qspi)?.write_in_progress { - return Err(nb::Error::WouldBlock); - } - - block!(Self::execute_command( - &mut self.qspi, - Command::WriteEnable, - None, - CommandData::None - ))?; - block!(Self::execute_command( - &mut self.qspi, - Command::PageProgram, - Some(address), - CommandData::Write(&bytes) - ))?; - Ok(block!(self.wait_until_write_complete())?) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::hal::doubles::{gpio::*, qspi::*, time::*}; - use std::collections::VecDeque; - - const NOT_BUSY: u8 = 0x0u8; - const COMMANDS_PER_PAGE_WRITE: usize = 4; - - type FlashToTest = MicronN25q128a; - fn flash_to_test() -> FlashToTest { - let mut qspi = MockQspi::default(); - qspi.to_read.push_back(vec![MANUFACTURER_ID]); - let mut flash = MicronN25q128a::new(qspi).unwrap(); - let initial_read = flash.qspi.command_records[0].clone(); - assert_eq!(initial_read.instruction, Some(Command::ReadId as u8)); - flash.qspi.clear(); - flash - } - - #[test] - fn various_memory_map_iterations() { - assert_eq!(MemoryMap::sectors().count(), NUMBER_OF_SECTORS); - assert_eq!(MemoryMap::subsectors().count(), NUMBER_OF_SUBSECTORS); - assert_eq!(MemoryMap::pages().count(), NUMBER_OF_PAGES); - - let expected_address = Address((3 * SECTOR_SIZE + 3 * SUBSECTOR_SIZE) as u32); - let expected_index = 3 * SUBSECTORS_PER_SECTOR + 3; - let subsector = MemoryMap::sectors().nth(3).unwrap().subsectors().nth(3).unwrap(); - assert_eq!(expected_address, subsector.location()); - assert_eq!(subsector.0, expected_index); - - let expected_address = - Address((1 * SECTOR_SIZE + 2 * SUBSECTOR_SIZE + 3 * PAGE_SIZE) as u32); - let expected_index = 1 * PAGES_PER_SECTOR + 2 * PAGES_PER_SUBSECTOR + 3; - let page = MemoryMap::sectors() - .nth(1) - .unwrap() - .subsectors() - .nth(2) - .unwrap() - .pages() - .nth(3) - .unwrap(); - assert_eq!(expected_address, page.location()); - assert_eq!(page.0, expected_index); - } - - #[test] - fn initialisation_succeeds_for_correct_manufacturer_id() { - const WRONG_MANUFACTURER_ID: u8 = 0x21; - let mut qspi = MockQspi::default(); - qspi.to_read.push_back(vec![WRONG_MANUFACTURER_ID]); - - // Then - assert!(FlashToTest::new(qspi).is_err()); - - // Given - let mut qspi = MockQspi::default(); - qspi.to_read.push_back(vec![MANUFACTURER_ID]); - - // Then - assert!(FlashToTest::new(qspi).is_ok()); - } - - #[test] - fn bulk_erase_sets_write_enable_writes_command_and_sets_write_disable() { - // Given - let mut flash = flash_to_test(); - - // When - flash.erase().unwrap(); - let records = &flash.qspi.command_records; - - // Then - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::WriteEnable as u8)); - assert_eq!(records[2].instruction, Some(Command::BulkErase as u8)); - assert_eq!(records[3].instruction, Some(Command::WriteDisable as u8)); - } - - #[test] - fn write_capable_commands_yield_if_device_busy() { - // Given - const BUSY_WRITING_STATUS: u8 = 1; - let mut flash = flash_to_test(); - flash.qspi.to_read.push_back(vec![BUSY_WRITING_STATUS]); - - // Then - assert_eq!(flash.erase(), Err(nb::Error::WouldBlock)); - - flash.qspi.to_read.push_back(vec![BUSY_WRITING_STATUS]); - } - - #[test] - fn page_program_command_sequence() { - // Given - let mut flash = flash_to_test(); - let address = Address(0x1000); - let data = [0xAAu8; PAGE_SIZE]; - - // When - flash.write_page(&Page::at(address).unwrap(), &data, address).unwrap(); - let records = &flash.qspi.command_records; - - // Then - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::WriteEnable as u8)); - assert_eq!(records[2].instruction, Some(Command::PageProgram as u8)); - assert_eq!(Some(address.0), records[2].address); - assert!(records[2].contains(&data)); - } - - #[test] - fn subsector_read_command_sequence() { - // Given - let mut flash = flash_to_test(); - let address = MemoryMap::subsectors().nth(12).unwrap().location(); - let mut data = [0x00u8; SUBSECTOR_SIZE]; - - // When - flash.read(address, &mut data).unwrap(); - let records = &flash.qspi.command_records; - - // Then - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::Read as u8)); - assert_eq!(Some(address.0), records[1].address); - assert_eq!(SUBSECTOR_SIZE, records[1].length_requested); - } - - #[test] - fn writing_a_bitwise_subset_of_a_subsector() { - // Given - let mut flash = flash_to_test(); - let data_to_write = [0xAA, 0xBB, 0xAA, 0xBB]; - let subsector = MemoryMap::subsectors().nth(12).unwrap(); - let page = subsector.pages().nth(3).unwrap(); - - flash.qspi.to_read = VecDeque::from(vec![ - vec![NOT_BUSY], // Response to busy check when calling write - vec![NOT_BUSY], // Response to busy check when calling first read - vec![0xFF; SUBSECTOR_SIZE], //sector data (for pre-write check) - ]); - - // When - flash.write(page.location(), &data_to_write).unwrap(); - let records = &flash.qspi.command_records; - - // Then we read the sector to verify we are a subset - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[2].instruction, Some(Command::Read as u8)); - assert_eq!(Some(subsector.location().0), records[2].address); - assert_eq!(SUBSECTOR_SIZE, records[2].length_requested); - records[1].contains(&[0xFF; SUBSECTOR_SIZE]); - - // And we are a subset, so we simply write the data - assert_eq!(records[3].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[4].instruction, Some(Command::WriteEnable as u8)); - assert_eq!(records[5].instruction, Some(Command::PageProgram as u8)); - assert_eq!(Some(page.location().0), records[5].address); - assert!(records[5].contains(&data_to_write)); - } - - fn wrote_a_whole_subsector(data: &[u8], address: Address, commands: &[CommandRecord]) -> bool { - (0..PAGES_PER_SUBSECTOR).map(|i| (i, i * COMMANDS_PER_PAGE_WRITE)).all(|(page, i)| { - commands[i].instruction == Some(Command::ReadStatus as u8) - && commands[i + 1].instruction == Some(Command::WriteEnable as u8) - && commands[i + 2].instruction == Some(Command::PageProgram as u8) - && commands[i + 2].address == Some((address + page * PAGE_SIZE).0) - && commands[i + 2].contains(&data[page * PAGE_SIZE..(page + 1) * PAGE_SIZE]) - && commands[i + 3].instruction == Some(Command::ReadStatus as u8) - }) - } - - #[test] - fn writing_a_whole_subsector_page_by_page() { - // Given - let mut flash = flash_to_test(); - let data_to_write = [0x00; SUBSECTOR_SIZE]; - let subsector = MemoryMap::subsectors().nth(12).unwrap(); - - // When - flash.write(subsector.location(), &data_to_write).unwrap(); - let records = &flash.qspi.command_records; - - // Then we read the subsector to verify we are a subset of it - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[2].instruction, Some(Command::Read as u8)); - - // And we write the whole thing page by page - assert!(wrote_a_whole_subsector(&data_to_write, subsector.location(), &records[3..])); - } - - #[test] - fn writing_a_non_bitwise_subset_of_a_subsector() { - // Given - let mut flash = flash_to_test(); - let data_to_write = [0xAA, 0xBB, 0xAA, 0xBB]; - let subsector = MemoryMap::subsectors().nth(12).unwrap(); - let page = subsector.pages().nth(3).unwrap(); - let original_subsector_data = vec![0x11u8; SUBSECTOR_SIZE]; - let mut merged_data = original_subsector_data.clone(); - merged_data - .iter_mut() - .skip(page.location() - subsector.location()) - .zip(data_to_write.iter()) - .for_each(|(a, b)| *a = *b); - - flash.qspi.to_read = VecDeque::from(vec![ - vec![NOT_BUSY], // Response to busy check when calling write - vec![NOT_BUSY], // Response to busy check when calling first read - original_subsector_data, //sector data (for pre-write check). Not a superset! - ]); - - // When - flash.write(page.location(), &data_to_write).unwrap(); - let records = &flash.qspi.command_records; - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - - // Then we read the sector to verify we are a subset - assert_eq!(records[1].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[2].instruction, Some(Command::Read as u8)); - assert_eq!(Some(subsector.location().0), records[2].address); - assert_eq!(SUBSECTOR_SIZE, records[2].length_requested); - records[1].contains(&[0x11; SUBSECTOR_SIZE]); - - // And we are not a subset, so we erase first - assert_eq!(records[3].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[4].instruction, Some(Command::WriteEnable as u8)); - assert_eq!(records[5].instruction, Some(Command::SubsectorErase as u8)); - assert_eq!(Some(subsector.location().0), records[5].address); - assert_eq!(records[6].instruction, Some(Command::ReadStatus as u8)); - - // And then we write the "merged" data back, page per page - assert!(wrote_a_whole_subsector(&merged_data, subsector.location(), &records[7..])); - } - - #[test] - fn write_straddling_two_subsectors() { - // Given - let mut flash = flash_to_test(); - let data_to_write = [0x00u8; 2 * SUBSECTOR_SIZE]; - let address = MemoryMap::subsectors().nth(1).unwrap().location(); - - // When - flash.write(address, &data_to_write).unwrap(); - let records = &flash.qspi.command_records; - - // Then - // First subsector subset check - assert_eq!(records[0].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[1].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[2].instruction, Some(Command::Read as u8)); - - // And first subsector write - assert!(wrote_a_whole_subsector(&data_to_write[..SUBSECTOR_SIZE], address, &records[3..])); - - let index = 3 + COMMANDS_PER_PAGE_WRITE * PAGES_PER_SUBSECTOR; - - // second subsector subset check - assert_eq!(records[index].instruction, Some(Command::ReadStatus as u8)); - assert_eq!(records[index + 1].instruction, Some(Command::Read as u8)); - - // And second subsector write - assert!(wrote_a_whole_subsector( - &data_to_write[SUBSECTOR_SIZE..], - address + SUBSECTOR_SIZE, - &records[index + 2..] - )); - } -} diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs deleted file mode 100644 index 7dd58d09..00000000 --- a/src/drivers/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Driver implementations for all supported platforms. They offer -//! a safe API, and are -//! [typestate](https://rust-embedded.github.io/book/static-guarantees/typestate-programming.html) -//! based whenever possible. -#![macro_use] - -/// Drivers for the stm32f4 family of microcontrollers. -#[cfg(feature = "stm32f4_any")] -#[macro_use] -pub mod stm32f4 { - pub mod flash; - pub mod gpio; - #[cfg(feature = "stm32f412")] - pub mod qspi; - pub mod rcc; - pub mod serial; - pub mod spi; - pub mod systick; -} - -pub mod led; - -/// Drivers for the Micron manufacturer (e.g. external flash). -#[cfg(feature = "stm32f412_discovery")] -pub mod micron { - /// N25Q128A external flash chip - pub mod n25q128a_flash; -} diff --git a/src/drivers/stm32f4/flash.rs b/src/drivers/stm32f4/flash.rs deleted file mode 100644 index 3692dd68..00000000 --- a/src/drivers/stm32f4/flash.rs +++ /dev/null @@ -1,402 +0,0 @@ -//! Internal Flash controller for the STM32F4 family -use crate::{ - hal::flash::ReadWrite, - stm32pac::FLASH, - utilities::{ - bitwise::SliceBitSubset, - memory::{self, IterableByOverlaps}, - }, -}; -use core::ops::{Add, Sub}; -use nb::block; - -pub struct McuFlash { - flash: FLASH, -} - -#[derive(Copy, Clone, Debug)] -pub enum Error { - MemoryNotReachable, - MisalignedAccess, -} - -#[derive(Default, Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)] -pub struct Address(pub u32); - -impl Add for Address { - type Output = Self; - fn add(self, rhs: usize) -> Address { Address(self.0 + rhs as u32) } -} - -impl Sub for Address { - type Output = Self; - fn sub(self, rhs: usize) -> Address { Address(self.0.saturating_sub(rhs as u32)) } -} - -impl Sub
for Address { - type Output = usize; - fn sub(self, rhs: Address) -> usize { self.0.saturating_sub(rhs.0) as usize } -} - -impl Into for Address { - fn into(self) -> usize { self.0 as usize } -} - -#[derive(Copy, Clone, Debug)] -struct Range(Address, Address); - -/// Different address blocks as defined in [Table 5](../../../../../../../documentation/hardware/stm32f412_reference.pdf#page=58) -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Block { - /// Main memory, but reserved for secure bootloader - Boot, - /// Main memory, where the application is written - Main, - SystemMemory, - OneTimeProgrammable, - OptionBytes, -} - -/// A memory map sector, with an associated block and an address range -#[derive(Copy, Clone, Debug, PartialEq)] -#[non_exhaustive] -struct Sector { - block: Block, - location: Address, - size: usize, -} - -#[non_exhaustive] -pub struct MemoryMap { - sectors: [Sector; SECTOR_NUMBER], -} - -///From [section 3.5.1](../../../../../../../documentation/hardware/stm32f412_reference.pdf#page=62) -const UNLOCK_KEYS: [u32; 2] = [0x45670123, 0xCDEF89AB]; - -#[cfg(feature = "stm32f412")] -const SECTOR_NUMBER: usize = 15; - -#[cfg(feature = "stm32f412")] -const MEMORY_MAP: MemoryMap = MemoryMap { - sectors: [ - Sector::new(Block::Boot, Address(0x0800_0000), KB!(16)), - Sector::new(Block::Boot, Address(0x0800_4000), KB!(16)), - Sector::new(Block::Boot, Address(0x0800_8000), KB!(16)), - Sector::new(Block::Boot, Address(0x0800_C000), KB!(16)), - Sector::new(Block::Main, Address(0x0801_0000), KB!(64)), - Sector::new(Block::Main, Address(0x0802_0000), KB!(128)), - Sector::new(Block::Main, Address(0x0804_0000), KB!(128)), - Sector::new(Block::Main, Address(0x0806_0000), KB!(128)), - Sector::new(Block::Main, Address(0x0808_0000), KB!(128)), - Sector::new(Block::Main, Address(0x080A_0000), KB!(128)), - Sector::new(Block::Main, Address(0x080C_0000), KB!(128)), - Sector::new(Block::Main, Address(0x080E_0000), KB!(128)), - Sector::new(Block::SystemMemory, Address(0x1FFF_0000), KB!(32)), - Sector::new(Block::OneTimeProgrammable, Address(0x1FFF_7800), 528), - Sector::new(Block::OptionBytes, Address(0x1FFF_C000), 16), - ], -}; - -const fn max_sector_size() -> usize { - let (mut index, mut size) = (0, 0usize); - loop { - let sector_size = MEMORY_MAP.sectors[index].size; - size = if sector_size > size { sector_size } else { size }; - index += 1; - if index == SECTOR_NUMBER { - break size; - } - } -} - -impl MemoryMap { - // Verifies that the memory map is consecutive and well formed - fn is_sound(&self) -> bool { - let main_sectors = self.sectors.iter().filter(|s| s.is_in_main_memory_area()); - let mut consecutive_pairs = main_sectors.clone().zip(main_sectors.skip(1)); - let consecutive = consecutive_pairs.all(|(a, b)| a.end() == b.start()); - let ranges_valid = - self.sectors.iter().map(|s| Range(s.start(), s.end())).all(Range::is_valid); - consecutive && ranges_valid - } - - fn sectors() -> impl Iterator { MEMORY_MAP.sectors.iter().cloned() } - pub const fn writable_start() -> Address { - let mut i = 0; - loop { - if MEMORY_MAP.sectors[i].is_writable() { - break MEMORY_MAP.sectors[i].start(); - } - i += 1; - } - } - pub const fn writable_end() -> Address { - let mut i = 0; - loop { - // Reach the writable area. - if MEMORY_MAP.sectors[i].is_writable() { - break; - } - i += 1; - } - - loop { - // Reach the end of the writable area - if !MEMORY_MAP.sectors[i + 1].is_writable() { - break MEMORY_MAP.sectors[i].end(); - } - i += 1; - } - } -} - -impl Range { - /// Sectors spanned by this range of addresses - fn span(self) -> &'static [Sector] { - let first = MEMORY_MAP - .sectors - .iter() - .enumerate() - .find_map(|(i, sector)| self.overlaps(sector).then_some(i)); - let last = MEMORY_MAP - .sectors - .iter() - .enumerate() - .rev() - .find_map(|(i, sector)| self.overlaps(sector).then_some(i)); - match (first, last) { - (Some(first), Some(last)) if (last >= first) => &MEMORY_MAP.sectors[first..(last + 1)], - _ => &MEMORY_MAP.sectors[0..1], - } - } - - const fn is_valid(self) -> bool { - let Range(Address(start), Address(end)) = self; - let after_map = start >= MEMORY_MAP.sectors[SECTOR_NUMBER - 1].end().0; - let before_map = end < MEMORY_MAP.sectors[0].end().0; - let monotonic = end >= start; - monotonic && !before_map && !after_map - } - - fn overlaps(self, sector: &Sector) -> bool { - (self.0 <= sector.start()) && (self.1 > sector.end()) - || (self.0 < sector.end()) && (self.1 >= sector.end()) - || (self.0 >= sector.start() && self.0 < sector.end()) - || (self.1 < sector.end() && self.1 >= sector.start()) - } - - /// Verify that all sectors spanned by this range are writable - fn is_writable(self) -> bool { self.span().iter().all(Sector::is_writable) } -} - -impl memory::Region
for Sector { - fn contains(&self, address: Address) -> bool { - (self.start() <= address) && (self.end() > address) - } -} - -impl Sector { - const fn start(&self) -> Address { self.location } - const fn end(&self) -> Address { Address(self.start().0 + self.size as u32) } - const fn new(block: Block, location: Address, size: usize) -> Self { - Sector { block, location, size } - } - fn number(&self) -> Option { - MEMORY_MAP.sectors.iter().enumerate().find_map(|(index, sector)| { - (sector.is_in_main_memory_area() && self == sector).then_some(index as u8) - }) - } - const fn is_writable(&self) -> bool { self.block as u8 == Block::Main as u8 } - const fn is_in_main_memory_area(&self) -> bool { - self.block as u8 == Block::Main as u8 || self.block as u8 == Block::Boot as u8 - } -} - -impl McuFlash { - pub fn new(flash: FLASH) -> Result { - assert!(MEMORY_MAP.is_sound()); - Ok(Self { flash }) - } - - /// Parallelism for 3v3 voltage from [table 7](../../../../../../../../documentation/hardware/stm32f412_reference.pdf#page=63) - /// (Word access parallelism) - fn unlock(&mut self) -> nb::Result<(), Error> { - if self.is_busy() { - return Err(nb::Error::WouldBlock); - } - // NOTE(Safety): Unsafe block to use the 'bits' convenience function. - // Applies to all blocks in this file unless specified otherwise - self.flash.keyr.write(|w| unsafe { w.bits(UNLOCK_KEYS[0]) }); - self.flash.keyr.write(|w| unsafe { w.bits(UNLOCK_KEYS[1]) }); - self.flash.cr.modify(|_, w| unsafe { w.psize().bits(0b10) }); - Ok(()) - } - - fn lock(&mut self) { self.flash.cr.modify(|_, w| w.lock().set_bit()); } - - fn erase(&mut self, sector: &Sector) -> nb::Result<(), Error> { - let number = sector.number().ok_or(nb::Error::Other(Error::MemoryNotReachable))?; - self.unlock()?; - self.flash - .cr - .modify(|_, w| unsafe { w.ser().set_bit().snb().bits(number).strt().set_bit() }); - self.lock(); - Ok(()) - } - - fn is_busy(&self) -> bool { self.flash.sr.read().bsy().bit_is_set() } - - fn write_bytes( - &mut self, - bytes: &[u8], - sector: &Sector, - address: Address, - ) -> nb::Result<(), Error> { - if (address < sector.start()) || (address + bytes.len() > sector.end()) { - return Err(nb::Error::Other(Error::MisalignedAccess)); - } - - let words = bytes.chunks(4).map(|bytes| { - u32::from_le_bytes([ - bytes.get(0).cloned().unwrap_or(0), - bytes.get(1).cloned().unwrap_or(0), - bytes.get(2).cloned().unwrap_or(0), - bytes.get(3).cloned().unwrap_or(0), - ]) - }); - - block!(self.unlock())?; - self.flash.cr.modify(|_, w| w.pg().set_bit()); - let base_address = address.0 as *mut u32; - for (index, word) in words.enumerate() { - // NOTE(Safety): Writing to a memory-mapped flash - // directly is naturally unsafe. We have to trust that - // the memory map is correct, and that these dereferences - // won't cause a hardfault or overlap with our firmware. - unsafe { - *(base_address.add(index)) = word; - } - } - self.lock(); - Ok(()) - } -} - -impl ReadWrite for McuFlash { - type Error = Error; - type Address = Address; - - fn range(&self) -> (Address, Address) { - (MemoryMap::writable_start(), MemoryMap::writable_end()) - } - - // NOTE: This only erases the sections of the MCU flash that are writable - // from the bootloader's perspective. Not the boot sector, system bytes, etc. - fn erase(&mut self) -> nb::Result<(), Self::Error> { - for sector in MEMORY_MAP.sectors.iter().filter(|s| s.is_writable()) { - self.erase(sector)?; - } - Ok(()) - } - - fn write(&mut self, address: Address, bytes: &[u8]) -> nb::Result<(), Self::Error> { - if address.0 % 4 != 0 { - return Err(nb::Error::Other(Error::MisalignedAccess)); - } - - let range = Range(address, Address(address.0 + bytes.len() as u32)); - if !range.is_writable() { - return Err(nb::Error::Other(Error::MemoryNotReachable)); - } - - // Early yield if busy - if self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - for (block, sector, address) in MemoryMap::sectors().overlaps(bytes, address) { - let sector_data = &mut [0u8; max_sector_size()][0..sector.size]; - let offset_into_sector = address.0.saturating_sub(sector.start().0) as usize; - - block!(self.read(sector.start(), sector_data))?; - if block.is_subset_of(§or_data[offset_into_sector..sector.size]) { - // No need to erase the sector, as we can just flip bits off - // (since our block is a bitwise subset of the sector) - block!(self.write_bytes(block, §or, address))?; - } else { - // We have to erase and rewrite any saved data alongside the new block - block!(self.erase(§or))?; - sector_data - .iter_mut() - .skip(offset_into_sector) - .zip(block) - .for_each(|(byte, input)| *byte = *input); - block!(self.write_bytes(sector_data, §or, sector.location))?; - } - } - - Ok(()) - } - - fn read(&mut self, address: Address, bytes: &mut [u8]) -> nb::Result<(), Self::Error> { - let range = Range(address, Address(address.0 + bytes.len() as u32)); - if address.0 % 4 != 0 { - Err(nb::Error::Other(Error::MisalignedAccess)) - } else if !range.is_writable() { - Err(nb::Error::Other(Error::MemoryNotReachable)) - } else { - let base = address.0 as *const u8; - for (index, byte) in bytes.iter_mut().enumerate() { - // NOTE(Safety) we are reading directly from raw memory locations, - // which is inherently unsafe. In this case, safety is guaranteed - // because we can only read main memory blocks that don't contain - // the bootloader image, and any direct write to them is handled through - // a mutable reference to this same Flash struct, so there can't be - // a data race. - *byte = unsafe { *(base.add(index)) }; - } - Ok(()) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn ranges_overlap_sectors_correctly() { - let sector = Sector::new(Block::Boot, Address(10), 10usize); - assert!(Range(Address(10), Address(20)).overlaps(§or)); - assert!(Range(Address(5), Address(15)).overlaps(§or)); - assert!(Range(Address(15), Address(25)).overlaps(§or)); - assert!(Range(Address(5), Address(25)).overlaps(§or)); - assert!(Range(Address(12), Address(18)).overlaps(§or)); - - assert!(!Range(Address(0), Address(5)).overlaps(§or)); - assert!(!Range(Address(20), Address(25)).overlaps(§or)); - } - - #[test] - fn ranges_span_the_correct_sectors() { - let range = Range(Address(0x0801_1234), Address(0x0804_5678)); - let expected_sectors = &MEMORY_MAP.sectors[4..7]; - - assert_eq!(expected_sectors, range.span()); - } - - #[test] - fn map_shows_correct_writable_range() { - let (start, end) = (MemoryMap::writable_start(), MemoryMap::writable_end()); - assert_eq!(start, MEMORY_MAP.sectors[4].start()); - assert_eq!(end, MEMORY_MAP.sectors[11].end()); - } - - #[test] - fn ranges_are_correctly_marked_writable() { - let (start, size) = (Address(0x0801_0008), 48usize); - let range = Range(start, Address(start.0 + size as u32)); - assert!(range.is_writable()); - } -} diff --git a/src/drivers/stm32f4/gpio.rs b/src/drivers/stm32f4/gpio.rs deleted file mode 100644 index ded69c03..00000000 --- a/src/drivers/stm32f4/gpio.rs +++ /dev/null @@ -1,489 +0,0 @@ -//! Macro-instantiated GPIO implementation. -//! -//! Pin configuration is encoded in the type system through typestates, -//! making it statically impossible to misuse a pin (e.g. there's -//! no "write" operation on a pin that has been configured as input). -#![macro_use] - -use crate::stm32pac; -use core::marker::PhantomData; - -/// Extension trait to split a GPIO peripheral in independent pins and registers -pub trait GpioExt { - /// The type to split the GPIO into - type GpioWrapper; - - /// Splits the GPIO block into independent pins and registers - fn split(self, rcc: &mut stm32pac::RCC) -> Self::GpioWrapper; -} - -/// Input mode (Pin type state) -pub struct Input { - // NOTE: The role of PhantomData is to represent that - // this Input typestate "owns" a generic MODE typestate, - // establishing a typestate hierarchy. Other usages of - // PhantomData in this file are similar. - _mode: PhantomData, -} - -/// Floating input (Input type state) -pub struct Floating; -/// Pulled down input (Input type state) -pub struct PullDown; -/// Pulled up input (Input type state) -pub struct PullUp; - -/// Output mode (Pin type state) -pub struct Output { - _mode: PhantomData, -} -/// Push pull output (Output type state) -pub struct PushPull; -/// Open drain output (Output type state) -pub struct OpenDrain; - -// Typestate generator for all Alternate Functions -macro_rules! alternate_functions { - ($($i:expr, )+) => { $( paste::item! { - /// Alternate function (Pin type state) - pub struct []; - } )+ } -} -// Expands into typestates AF0-AF15 -alternate_functions!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); - -// Type generator for all pins -macro_rules! pin_rows { - ($($x:ident,)+) => { - use core::marker::PhantomData; - $( - pin_row!($x, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,]); - )+ - } -} -macro_rules! pin_row { - ($x:ident, [$($i:expr,)+]) => { $( paste::item! { - /// Pin with a MODE typestate - pub struct [

] { - _mode: PhantomData, - } - } )+ - } -} - -/// Instantiates a gpio pin row with default modes per available pin. -/// -/// # Examples -/// -/// ```ignore -/// gpio!(b, [ -/// (7, Output::), -/// (8, AF4), -/// (3, Input::), -/// ]); -/// -/// ``` -/// This makes the wrapper struct gpiob have the members gpiob.pb7 in -/// Output + Push/Pull mode, gpiob.pb8 in alternate function 4, and -/// gpiob.pb3 as a floating input. -macro_rules! gpio { - ($x: ident, [ - $( ($i:expr, $default_mode:ty $(as $function:ident$(<$T:ident>)?)?), )+ - ]) => { - - // Macro black magic. the "paste" crate generates a context where anything bounded by "[<" - // and ">]" delimiters gets concatenated in a single identifier post macro expansion. For - // example, "[]" becomes "GPIOa" when "$x" represents "a". This is used to - // expand the outer level, simplified "gpio!" instantiation macro into the complex one. - paste::item! { - gpio_inner!([], [], [], [], [

], [ - $( [

]: ([

], $i, $default_mode, $([], $function$(<$T>)?)?), )+ - ]); - } - } -} - -macro_rules! into_af { - ($GPIOx:ident, $i:expr, $Pxi:ident, $pxi:ident, [$($af_i:expr, )+]) => { $( paste::item! { - pub fn [](self) -> $Pxi<[]> { - let offset = 2 * $i; - - // alternate function mode - let mode = 0b10; - - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { - (*$GPIOx::ptr()).moder.modify(|r, w| - w.bits((r.bits() & !(0b11 << offset)) | (mode << offset)) - ); - } - - let offset = 4 * ($i % 8); - - if $i < 8 { - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { - (*$GPIOx::ptr()).afrl.modify(|r, w| - w.bits((r.bits() & !(0b1111 << offset)) | ($af_i << offset)) - ); - } - } else { - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { - (*$GPIOx::ptr()).afrh.modify(|r, w| - w.bits((r.bits() & !(0b1111 << offset)) | ($af_i << offset)) - ); - } - } - - $Pxi { _mode: PhantomData } - } -} )+ } -} - -macro_rules! new_af { - ($GPIOx:ident, $i:expr, $Pxi:ident, $pxi:ident, [$($af_i:expr, )+]) => { $( paste::item! { - impl $Pxi<[]> { - #[allow(dead_code)] - fn new() -> Self { - let pin = $Pxi::> { _mode : PhantomData }; - pin.[]() - } - } -} )+ } -} - -macro_rules! gpio_inner { - ($GPIOx:ident, $gpiox:ident, $enable_pin:ident, $reset_pin:ident, $Pxx:ident, [ - $($Pxi:ident: ($pxi:ident, $i:expr, $default_mode:ty, $($earmark:ident, $function:ident$(<$T:ident>)?)?), )+ - ]) => { - /// GPIO - pub mod $gpiox { - use core::marker::PhantomData; - use crate::hal::gpio::{OutputPin, InputPin}; - use crate::ports::pin_configuration::*; - - // Lower case for identifier concatenation - #[allow(unused_imports)] - use crate::stm32pac::{ - GPIOA as GPIOa, - GPIOB as GPIOb, - GPIOC as GPIOc, - GPIOD as GPIOd, - GPIOE as GPIOe, - GPIOF as GPIOf, - GPIOG as GPIOg, - GPIOH as GPIOh, - }; - - #[allow(unused_imports)] - #[cfg(not(feature = "stm32f412"))] - use crate::stm32pac::{ - GPIOI as GPIOi, - GPIOJ as GPIOj, - GPIOK as GPIOk, - }; - - use crate::drivers::stm32f4::gpio::*; - - /// GPIO parts - pub struct GpioWrapper { - $( - /// Pin - pub $pxi: $Pxi<$default_mode>, - )+ - } - - impl GpioExt for $GPIOx { - type GpioWrapper = GpioWrapper; - - fn split(self, rcc: &mut crate::stm32pac::RCC) -> GpioWrapper { - rcc.ahb1enr.modify(|_, w| w.$enable_pin().enabled()); - rcc.ahb1rstr.modify(|_, w| w.$reset_pin().set_bit()); - rcc.ahb1rstr.modify(|_, w| w.$reset_pin().clear_bit()); - - $( - let $pxi = $Pxi::<$default_mode>::new(); - )+ - - GpioWrapper { - $($pxi,)+ - } - } - } - /// Partially erased pin - pub struct $Pxx { - i: u8, - _mode: PhantomData, - } - - impl OutputPin for $Pxx> { - fn set_high(&mut self) { - // NOTE(safety) atomic write to a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).bsrr.write(|w| w.bits(1 << self.i)) } - } - - fn set_low(&mut self) { - // NOTE(safety) atomic write to a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).bsrr.write(|w| w.bits(1 << (16 + self.i))) } - } - } - - impl InputPin for $Pxx> { - fn is_high(&self) -> bool { - // NOTE(safety) atomic read from a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (((*$GPIOx::ptr()).idr.read().bits() >> self.i) & 0b1) != 0 } - } - - fn is_low(&self) -> bool{ - !self.is_high() - } - } - - $( - /// Pin - impl $Pxi> { - #[allow(dead_code)] - fn new() -> Self { - $Pxi { _mode: PhantomData } - } - } - - impl $Pxi> { - #[allow(dead_code)] - fn new() -> Self { - let pin = $Pxi::> { _mode : PhantomData }; - pin.into_push_pull_output() - } - } - - impl $Pxi> { - #[allow(dead_code)] - fn new() -> Self { - let pin = $Pxi::> { _mode : PhantomData }; - pin.into_pull_down_input() - } - } - - impl $Pxi> { - #[allow(dead_code)] - fn new() -> Self { - let pin = $Pxi::> { _mode : PhantomData }; - pin.into_pull_up_input() - } - } - - impl $Pxi> { - #[allow(dead_code)] - fn new() -> Self { - let pin = $Pxi::> { _mode : PhantomData }; - pin.into_open_drain_output() - } - } - - new_af!($GPIOx, $i, $Pxi, $pxi, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,]); - - $( - // If this field exists, it means the gpio has been earmarked for - // a particular purpose in the gpio table. - trait $earmark: $function$(<$T>)? {} - impl $earmark for $Pxi<$default_mode> {} - )? - - impl $Pxi { - into_af!($GPIOx, $i, $Pxi, $pxi, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,]); - - /// Configures the pin to operate as a floating input pin - pub fn into_floating_input( - self, - ) -> $Pxi> { - let offset = 2 * $i; - - // input mode - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).moder.modify(|r, w| w.bits(r.bits() & !(0b11 << offset)) ); } - - // no pull-up or pull-down - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).pupdr.modify(|r, w| w.bits(r.bits() & !(0b11 << offset)) ); } - - $Pxi { _mode: PhantomData } - } - - /// Configures the pin to operate as a pulled down input pin - pub fn into_pull_down_input( - self, - ) -> $Pxi> { - let offset = 2 * $i; - - // input mode - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).moder.modify(|r, w| w.bits(r.bits() & !(0b11 << offset)) ); } - - // pull-down - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).pupdr.modify(|r, w| - w.bits((r.bits() & !(0b11 << offset)) | (0b10 << offset)) - ); } - - $Pxi { _mode: PhantomData } - } - - /// Configures the pin to operate as a pulled up input pin - pub fn into_pull_up_input( - self, - ) -> $Pxi> { - let offset = 2 * $i; - - // input mode - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).moder.modify(|r, w| w.bits(r.bits() & !(0b11 << offset)) ); } - - // pull-up - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).pupdr.modify(|r, w| - w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) - ); } - - $Pxi { _mode: PhantomData } - } - - /// Configures the pin to operate as an open drain output pin - pub fn into_open_drain_output( - self, - ) -> $Pxi> { - let offset = 2 * $i; - - // general purpose output mode - let mode = 0b01; - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).moder.modify(|r, w| - w.bits((r.bits() & !(0b11 << offset)) | (mode << offset)) - ); } - - // open drain output - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).otyper.modify(|r, w| w.bits(r.bits() | (0b1 << $i)) ); } - - $Pxi { _mode: PhantomData } - } - - /// Configures the pin to operate as an push pull output pin - pub fn into_push_pull_output( - self, - ) -> $Pxi> { - let offset = 2 * $i; - - // general purpose output mode - let mode = 0b01; - - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).moder.modify(|r, w| - w.bits((r.bits() & !(0b11 << offset)) | (mode << offset)) - ); } - - // push pull output - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).otyper.modify(|r, w| w.bits(r.bits() & !(0b1 << $i)) ); } - - $Pxi { _mode: PhantomData } - } - } - - impl $Pxi> { - /// Enables / disables the internal pull up - pub fn internal_pull_up(&mut self, on: bool) { - let offset = 2 * $i; - - // NOTE(safety) atomic read-modify-write operation to a stateless register. - // It is also safe because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).pupdr.modify(|r, w| - w.bits( - (r.bits() & !(0b11 << offset)) | if on { - 0b01 << offset - } else { - 0 - }, - ) - ); } - } - } - - impl $Pxi> { - /// Erases the pin number from the type - /// - /// This is useful when you want to collect the pins into an array where you - /// need all the elements to have the same type - pub fn downgrade(self) -> $Pxx> { - $Pxx { - i: $i, - _mode: self._mode, - } - } - } - - impl OutputPin for $Pxi> { - fn set_high(&mut self) { - // NOTE(safety) atomic write to a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).bsrr.write(|w| w.bits(1 << $i)) } - } - - fn set_low(&mut self) { - // NOTE(safety) atomic write to a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (*$GPIOx::ptr()).bsrr.write(|w| w.bits(1 << (16 + $i))) } - } - } - - impl InputPin for $Pxi> { - fn is_high(&self) -> bool { - // NOTE(safety) atomic read from a stateless register. It is also safe - // because pins are only reachable by splitting a GPIO struct, - // which preserves single ownership of each pin. - unsafe { (((*$GPIOx::ptr()).idr.read().bits() >> $i) & 0b1) != 0 } - } - - fn is_low(&self) -> bool{ - !self.is_high() - } - } - )+ - } - } -} diff --git a/src/drivers/stm32f4/qspi.rs b/src/drivers/stm32f4/qspi.rs deleted file mode 100644 index 7eaa229d..00000000 --- a/src/drivers/stm32f4/qspi.rs +++ /dev/null @@ -1,419 +0,0 @@ -//! Quadspi driver for the stm32f412. - -use crate::{ - drivers::stm32f4::gpio::*, - hal::qspi, - ports::pin_configuration::*, - stm32pac::{QUADSPI as QuadSpiPeripheral, RCC}, -}; -use core::marker::PhantomData; -use nb::block; - -mod private { - #[doc(hidden)] - pub trait Sealed {} -} - -/// Sealed trait for all QSPI capable pins. -pub unsafe trait ClkPin: private::Sealed {} -pub unsafe trait Bk1CsPin: private::Sealed {} -pub unsafe trait Bk2CsPin: private::Sealed {} -pub unsafe trait Bk1Io0Pin: private::Sealed {} -pub unsafe trait Bk1Io1Pin: private::Sealed {} -pub unsafe trait Bk1Io2Pin: private::Sealed {} -pub unsafe trait Bk1Io3Pin: private::Sealed {} -pub unsafe trait Bk2Io0Pin: private::Sealed {} -pub unsafe trait Bk2Io1Pin: private::Sealed {} -pub unsafe trait Bk2Io2Pin: private::Sealed {} -pub unsafe trait Bk2Io3Pin: private::Sealed {} - -#[allow(unused)] -macro_rules! seal_pins { ($function:ty: [$($pin:ty,)+]) => { - $( - unsafe impl $function for $pin {} - impl private::Sealed for $pin {} - )+ -};} - -// There is no consistent alternate function for QSPI (varies between -// 9 and 10) so there is no type alias for QSPI AF. -#[cfg(feature = "stm32f412")] -seal_pins!(ClkPin: [Pb1, Pb2, Pd3,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk1CsPin: [Pb6, Pg6,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk2CsPin: [Pc11,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk1Io0Pin: [Pc9, Pd11, Pf8,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk1Io1Pin: [Pc10, Pd12, Pf9,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk1Io2Pin: [Pc8, Pe2, Pf7,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk1Io3Pin: [Pa1, Pd13, Pf6,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk2Io0Pin: [Pa6, Pe7,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk2Io1Pin: [Pa7, Pe8,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk2Io2Pin: [Pc4, Pe9, Pg9,]); -#[cfg(feature = "stm32f412")] -seal_pins!(Bk2Io3Pin: [Pc5, Pe10, Pg14,]); - -const MAX_DUMMY_CYCLES: u8 = 31; - -// Mode Typestates -pub mod mode { - pub struct Single; - pub struct Dual; - pub struct Quad; -} - -/// Whether bits are clocked on both edges -#[derive(PartialEq, Debug)] -pub enum DataRate { - Single, - /// Unimplemented - Double, -} - -/// Number of flash memories sharing a bus -#[derive(PartialEq, Debug)] -pub enum FlashMode { - Single, - /// Unimplemented - Double, -} - -/// QuadSPI configuration -pub struct Config { - data_rate: DataRate, - flash_mode: FlashMode, - flash_size_bits: u8, - _marker: PhantomData, -} - -/// Marker trait for a tuple of pins that work for a given QSPI in Single mode -pub trait SingleModePins {} - -impl SingleModePins for (CLK, CS, IO0, IO1, IO2, IO3) -where - CLK: ClkPin, - CS: Bk1CsPin, - IO0: Bk1Io0Pin, - IO1: Bk1Io1Pin, - IO2: Bk1Io2Pin, - IO3: Bk1Io3Pin, -{ -} - -/// QuadSPI abstraction -pub struct QuadSpi { - qspi: QuadSpiPeripheral, - config: Config, - _marker: PhantomData, -} - -pub struct Instruction(u8); - -pub enum Error { - DummyCyclesValueOutOfRange, -} - -impl Default for Config { - fn default() -> Self { - Config { - data_rate: DataRate::Single, - flash_mode: FlashMode::Single, - flash_size_bits: 24, - _marker: PhantomData::default(), - } - } -} - -impl Config { - pub fn single(self) -> Config { - Config { - data_rate: self.data_rate, - flash_mode: self.flash_mode, - flash_size_bits: self.flash_size_bits, - _marker: PhantomData::default(), - } - } - - pub fn double(self) -> Config { - Config { - data_rate: self.data_rate, - flash_mode: self.flash_mode, - flash_size_bits: self.flash_size_bits, - _marker: PhantomData::default(), - } - } - - pub fn quad(self) -> Config { - Config { - data_rate: self.data_rate, - flash_mode: self.flash_mode, - flash_size_bits: self.flash_size_bits, - _marker: PhantomData::default(), - } - } - - pub fn with_data_rate(mut self, data_rate: DataRate) -> Self { - self.data_rate = data_rate; - self - } - - pub fn with_flash_mode(mut self, flash_mode: FlashMode) -> Self { - self.flash_mode = flash_mode; - self - } - - pub fn with_flash_size(mut self, bits: u8) -> Result { - match bits { - 8 | 16 | 24 | 32 => { - self.flash_size_bits = bits; - Ok(self) - } - _ => Err(ConfigError::InvalidFlashSize), - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum ConfigError { - NotYetImplemented, - InvalidFlashSize, -} - -impl From for crate::error::Error { - fn from(config_error: ConfigError) -> Self { - match config_error { - ConfigError::NotYetImplemented => crate::error::Error::ConfigurationError( - "QSPI unimplemented features requested in configuration", - ), - ConfigError::InvalidFlashSize => crate::error::Error::ConfigurationError( - "QSPI invalid flash size requested in configuration", - ), - } - } -} - -impl QuadSpi -where - PINS: SingleModePins, -{ - pub fn from_config( - qspi: QuadSpiPeripheral, - _: PINS, - config: Config, - ) -> Result { - if config.data_rate != DataRate::Single || config.flash_mode != FlashMode::Single { - return Err(ConfigError::NotYetImplemented); - } - - // NOTE(safety) This executes only during initialisation, and only - // performs single-bit atomic writes related to the QSPI peripheral - let rcc = unsafe { &(*RCC::ptr()) }; - rcc.ahb3enr.modify(|_, w| w.qspien().set_bit()); - rcc.ahb3rstr.modify(|_, w| w.qspirst().set_bit()); - rcc.ahb3rstr.modify(|_, w| w.qspirst().clear_bit()); - - // NOTE(safety) The unsafe "bits" method is used to write multiple bits conveniently. - // Applies to all unsafe blocks in this function unless specified otherwise. - // Maximum prescaler (AHB clock frequency / 256) - qspi.cr.modify(|_, w| unsafe { w.prescaler().bits(255) }); - - // Fifo threshold 1 (fifo flag up when 1 byte is free to write) - qspi.cr.modify(|_, w| unsafe { w.fthres().bits(1) }); - - let fsize = config.flash_size_bits.saturating_sub(1u8); - qspi.dcr.modify(|_, w| unsafe { w.fsize().bits(fsize) }); - - qspi.dcr.modify(|_, w| unsafe { w.csht().bits(7u8) }); - - // Enable - qspi.cr.modify(|_, w| w.en().set_bit()); - - Ok(Self { config, qspi, _marker: PhantomData::default() }) - } -} - -#[derive(Copy, Clone, Debug)] -struct Status { - busy: bool, - fifo_threshold: bool, -} - -impl QuadSpi { - fn status(&self) -> Status { - let flags = self.qspi.sr.read(); - Status { busy: flags.busy().bit(), fifo_threshold: flags.ftf().bit() } - } - - const QSPI_ADDRESS: u32 = 0xA0001000; - const QSPI_DR_OFFSET: u32 = 0x20; - const QSPI_DR_ADDRESS: u32 = Self::QSPI_ADDRESS + Self::QSPI_DR_OFFSET; - - fn write_byte(&mut self, byte: u8) -> nb::Result<(), Error> { - if !self.status().fifo_threshold { - Err(nb::Error::WouldBlock) - } else { - let pointer = Self::QSPI_DR_ADDRESS as *mut u8; - // NOTE(safety): We bypass the PAC here to perform a single byte - // access to a 32 bit register. The PAC won't let you do this since - // it's generated from the SVD file, which just represents the register - // as a single chunk of 32 bits. Bypassing the PAC here is safe since access to - // the register is gated behind self.qspi, which we own and nothing else - // writes to it. - unsafe { *pointer = byte }; - Ok(()) - } - } - - fn read_byte(&mut self) -> nb::Result { - let status = self.status(); - if !status.fifo_threshold { - Err(nb::Error::WouldBlock) - } else { - let pointer = Self::QSPI_DR_ADDRESS as *const u8; - // NOTE(safety): We bypass the PAC here to perform a single byte - // access to a 32 bit register. The PAC won't let you do this since - // it's generated from the SVD file, which just represents the register - // as a single chunk of 32 bits. Bypassing the PAC here is safe since access to - // the register is gated behind self.qspi, which we own and nothing else - // writes to it. - let byte = unsafe { *pointer }; - Ok(byte) - } - } -} - -impl qspi::Indirect for QuadSpi { - type Error = Error; - - fn write( - &mut self, - instruction: Option, - address: Option, - data: Option<&[u8]>, - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error> { - if dummy_cycles > MAX_DUMMY_CYCLES { - return Err(nb::Error::Other(Error::DummyCyclesValueOutOfRange)); - } - - let adsize = match self.config.flash_size_bits { - 8 => 0b00, - 16 => 0b01, - 24 => 0b10, - 32 => 0b11, - _ => panic!("Invalid flash size"), - }; - - if self.status().busy { - // Early yield if busy - return Err(nb::Error::WouldBlock); - } - - // NOTE(safety) The unsafe "bits" method is used to write multiple bits conveniently. - // Applies to all unsafe blocks in this function unless specified otherwise. - // Sets Data Length Register, configuring the amount of bytes to write. - self.qspi.dlr.write(|w| unsafe { - w.bits(if let Some(data) = data { data.len().saturating_sub(1) as u32 } else { 0 }) - }); - - // Configure Communicaton Configuration Register. - // This sets up all rules for this QSPI write. - self.qspi.ccr.write(|w| unsafe { - if let Some(instruction) = instruction { - w.imode().bits(0b01).instruction().bits(instruction) - } else { - w - } - .fmode() - .bits(0b00) // indirect write mode - .adsize() - .bits(adsize) - .admode() - .bits(if address.is_some() { 0b01 } else { 0b00 }) - .dmode() - .bits(if data.is_some() { 0b01 } else { 0b00 }) - .dcyc() - .bits(dummy_cycles) - }); - - // Sets Address to write to. - if let Some(address) = address { - self.qspi.ar.write(|w| unsafe { w.bits(address) }) - }; - - // Write loop (checking FIFO threshold to ensure it is possible to write 4 bytes). - if let Some(data) = data { - for byte in data { - block!(self.write_byte(*byte))?; - } - } - Ok(()) - } - - fn read( - &mut self, - instruction: Option, - address: Option, - data: &mut [u8], - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error> { - if dummy_cycles > MAX_DUMMY_CYCLES { - return Err(nb::Error::Other(Error::DummyCyclesValueOutOfRange)); - } - - let adsize = match self.config.flash_size_bits { - 8 => 0b00, - 16 => 0b01, - 24 => 0b10, - 32 => 0b11, - _ => panic!("Invalid flash size"), - }; - - if self.status().busy { - // Early yield if busy - return Err(nb::Error::WouldBlock); - } - // NOTE(safety) The unsafe "bits" method is used to write multiple bits conveniently. - // Applies to all unsafe blocks in this function unless specified otherwise. - // Sets Data Length Register, configuring the amount of bytes to read. - self.qspi.dlr.write(|w| unsafe { w.bits(data.len().saturating_sub(1) as u32) }); - - // Configure Communicaton Configuration Register. - // This sets up all rules for this QSPI read. - self.qspi.ccr.write(|w| unsafe { - if let Some(instruction) = instruction { - w.imode().bits(0b01).instruction().bits(instruction) - } else { - w - } - .fmode() - .bits(0b01) // indirect read mode - .adsize() - .bits(adsize) - .admode() - .bits(if address.is_some() { 0b01 } else { 0b00 }) - .dmode() - .bits(0b01) - .dcyc() - .bits(dummy_cycles) - }); - - // Sets Address to read from. - if let Some(address) = address { - self.qspi.ar.write(|w| unsafe { w.bits(address) }) - }; - - // Read loop (checking FIFO threshold to ensure it is possible to read 4 bytes). - for byte in data { - *byte = block!(self.read_byte())?; - } - Ok(()) - } -} diff --git a/src/drivers/stm32f4/rcc.rs b/src/drivers/stm32f4/rcc.rs deleted file mode 100644 index f9b1819a..00000000 --- a/src/drivers/stm32f4/rcc.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::{ - hal::time::{Hertz, MegaHertz}, - stm32pac::{FLASH, RCC}, -}; - -/// Frozen clock frequencies -/// -/// The existence of this value indicates that the clock configuration can no longer be changed -#[derive(Clone, Copy, Debug)] -pub struct Clocks { - hclk: Hertz, - pclk1: Hertz, - pclk2: Hertz, - sysclk: Hertz, -} - -impl Clocks { - pub fn hclk(&self) -> Hertz { self.hclk } - - pub fn pclk1(&self) -> Hertz { self.pclk1 } - - pub fn pclk2(&self) -> Hertz { self.pclk2 } - - pub fn sysclk(&self) -> Hertz { self.sysclk } - - /// Harcoded values for the f412 - #[cfg(feature = "stm32f412")] - pub fn hardcoded(rcc: RCC) -> Self { - // NOTE(Safety): All unsafe blocks in this function refer to using the "bits()" - // method for easy writing. - - // NOTE(Safety): Access to flash is fine because this only occurs in construction and is - // unrelated to other uses of flash. - unsafe { - (*FLASH::ptr()).acr.write(|w| { - w.latency().bits(1); // 50Mhz -> 1 wait state at 3.3v - w.prften().set_bit() - }); - } - - rcc.cr.modify(|_, w| w.hseon().set_bit()); - while rcc.cr.read().hserdy().bit_is_clear() {} - - rcc.pllcfgr.write(|w| unsafe { - w.pllsrc().set_bit(); // HSE input to PLL - w.pllm().bits(8); - w.plln().bits(100); - w.pllp().bits(0); // pllp = (divider / 2) >> 1 - w.pllq().bits(3) - }); - - rcc.cr.modify(|_, w| w.pllon().set_bit()); - while rcc.cr.read().pllrdy().bit_is_clear() {} - - rcc.cfgr.modify(|_, w| unsafe { - w.ppre1().bits(0b100); // Divided by 2 - w.ppre2().bits(0b000); // Divided by 1 - w.hpre().bits(0b000); // Divided by 1 - w.sw().bits(0b10) // PLL source - }); - - while rcc.cfgr.read().sws().bits() != 0b10 {} - Self { - hclk: MegaHertz(50).into(), - pclk1: MegaHertz(25).into(), - pclk2: MegaHertz(50).into(), - sysclk: MegaHertz(50).into(), - } - } -} diff --git a/src/drivers/stm32f4/serial.rs b/src/drivers/stm32f4/serial.rs deleted file mode 100644 index c8146c47..00000000 --- a/src/drivers/stm32f4/serial.rs +++ /dev/null @@ -1,540 +0,0 @@ -//! USART implementation. -use crate::{ - drivers::stm32f4::{gpio::*, rcc, systick}, - hal::{ - serial, - time::{Milliseconds, Now}, - }, - ports::pin_configuration::*, - stm32pac::{RCC, USART1, USART2, USART3, USART6}, -}; -use core::{marker::PhantomData, ptr}; -use defmt::Format; - -/// Extension trait to wrap a USART peripheral into a more useful -/// high level abstraction. -pub trait UsartExt { - /// The wrapping type - type Serial; - - fn constrain( - self, - pins: PINS, - config: config::Config, - clocks: rcc::Clocks, - ) -> Result; -} - -mod private { - #[doc(hidden)] - pub trait Sealed {} -} - -/// Sealed trait for all pins that can be TX for each USART. -/// This can't be implemented by the library user: All available -/// pins should already be implemented internally. -pub unsafe trait TxPin: private::Sealed {} - -/// Sealed trait for all pins that can be RX for each USART. -/// This can't be implemented by the library user: All available -/// pins should already be implemented internally. -pub unsafe trait RxPin: private::Sealed {} - -macro_rules! seal_pins { ($function:ty: [$($pin:ty,)+]) => { - $( - unsafe impl $function for $pin {} - impl private::Sealed for $pin {} - )+ -};} - -// List of all pins capable of being configured as certain USART -// functions. NOTE: This is not configuration! there's no need -// to remove items from these lists once complete. -#[cfg(any(feature = "stm32f469", feature = "stm32f429", feature = "stm32f407"))] -seal_pins!(TxPin: [Pa9, Pb6,]); -#[cfg(any(feature = "stm32f412"))] -seal_pins!(TxPin: [Pa9, Pb6, Pa15,]); - -#[cfg(any(feature = "stm32f469", feature = "stm32f429", feature = "stm32f407"))] -seal_pins!(RxPin: [Pb7, Pa10,]); -#[cfg(any(feature = "stm32f412"))] -seal_pins!(RxPin: [Pb3, Pb7, Pa10,]); - -#[cfg(any( - feature = "stm32f469", - feature = "stm32f429", - feature = "stm32f407", - feature = "stm32f412" -))] -seal_pins!(TxPin: [Pa2, Pd5,]); - -#[cfg(any( - feature = "stm32f469", - feature = "stm32f429", - feature = "stm32f407", - feature = "stm32f412" -))] -seal_pins!(RxPin: [Pa3, Pd6,]); - -#[cfg(any(feature = "stm32f412"))] -seal_pins!(TxPin: [Pc6, Pa11, Pg14,]); -#[cfg(any(feature = "stm32f412"))] -seal_pins!(RxPin: [Pc7, Pa12, Pg9,]); - -/// Serial error -#[derive(Debug, Copy, Clone, Format)] -#[non_exhaustive] -pub enum Error { - /// Framing error - Framing, - /// Noise error - Noise, - /// RX buffer overrun - Overrun, - /// Parity check error - Parity, - /// Timeout error - Timeout, -} - -/// Interrupt event -pub enum Event { - /// New data has been received - Rxne, - /// New data can be sent - Txe, - /// Idle line state detected - Idle, -} - -pub mod config { - //! Configuration required to construct a new USART instance. - //! - //! # Example - //! ```ignore - //! let (serial, tx, rx) = (peripherals.USART2, gpiod.pd5, gpiod.pd6); - //! let serial_config = serial::config::Config::default().baudrate(Bps(115_200)); - //! let mut serial = serial.constrain((tx,rx), serial_config, clocks).unwrap(); - //! ``` - - use crate::hal::time::{Bps, U32Ext}; - - pub enum WordLength { - DataBits8, - DataBits9, - } - - pub enum Parity { - ParityNone, - ParityEven, - ParityOdd, - } - - pub enum StopBits { - #[doc = "1 stop bit"] - STOP1, - #[doc = "0.5 stop bits"] - STOP0P5, - #[doc = "2 stop bits"] - STOP2, - #[doc = "1.5 stop bits"] - STOP1P5, - } - - pub struct Config { - pub baudrate: Bps, - pub wordlength: WordLength, - pub parity: Parity, - pub stopbits: StopBits, - } - - impl Config { - pub fn baudrate(mut self, baudrate: Bps) -> Self { - self.baudrate = baudrate; - self - } - - pub fn parity_none(mut self) -> Self { - self.parity = Parity::ParityNone; - self - } - - pub fn parity_even(mut self) -> Self { - self.parity = Parity::ParityEven; - self - } - - pub fn parity_odd(mut self) -> Self { - self.parity = Parity::ParityOdd; - self - } - - pub fn wordlength_8(mut self) -> Self { - self.wordlength = WordLength::DataBits8; - self - } - - pub fn wordlength_9(mut self) -> Self { - self.wordlength = WordLength::DataBits9; - self - } - - pub fn stopbits(mut self, stopbits: StopBits) -> Self { - self.stopbits = stopbits; - self - } - } - - #[derive(Debug)] - pub struct InvalidConfig; - - impl Default for Config { - fn default() -> Config { - let baudrate = 19_200_u32.bps(); - Config { - baudrate, - wordlength: WordLength::DataBits8, - parity: Parity::ParityNone, - stopbits: StopBits::STOP1, - } - } - } -} - -/// Marker trait for a tuple of pins that work for a given USART. -/// Automatically implemented for any tuple (A, B) where A is -/// a TxPin and B is a RxPin. -pub trait Pins {} - -impl Pins for (TX, RX) -where - TX: TxPin, - RX: RxPin, -{ -} - -/// Serial abstraction -pub struct Serial { - usart: USART, - pins: PINS, -} - -/// Serial receiver -pub struct Rx { - _usart: PhantomData, -} - -/// Serial transmitter -pub struct Tx { - _usart: PhantomData, -} - -macro_rules! hal_usart_impl { - ($( - $USARTX:ident: ($usartX:ident, $apbXenr:ident, $usartXen:ident, $pclkX:ident), - )+) => { - $( - impl Serial<$USARTX, PINS> { - pub fn $usartX( - usart: $USARTX, - pins: PINS, - config: config::Config, - clocks: rcc::Clocks, - ) -> Result - where - PINS: Pins<$USARTX>, - { - use self::config::*; - - // NOTE(safety) This executes only during initialisation - let rcc = unsafe { &(*RCC::ptr()) }; - - // Enable clock for USART - rcc.$apbXenr.modify(|_, w| w.$usartXen().set_bit()); - - let extended_divider = (clocks.$pclkX().0 << 4) / config.baudrate.0; - let mantissa = extended_divider >> 8; - let fraction = (extended_divider - (mantissa << 8)) >> 4; - - // NOTE(safety) uses .bits for ease of writing a whole word. - // No reserved or read-only bits in this register - usart.brr.write(|w| unsafe { w.bits((mantissa << 4) | fraction) }); - - // Reset other registers to disable advanced USART features - usart.cr2.reset(); - usart.cr3.reset(); - - // Enable transmission and receiving - // and configure frame - usart.cr1.write(|w| { - w.ue() - .set_bit() - .te() - .set_bit() - .re() - .set_bit() - .m() - .bit(match config.wordlength { - WordLength::DataBits8 => false, - WordLength::DataBits9 => true, - }) - .pce() - .bit( ! matches!(config.parity, Parity::ParityNone)) - .ps() - .bit(matches!(config.parity, Parity::ParityOdd)) - }); - - Ok(Serial { usart, pins }.config_stop(config)) - } - - /// Starts listening for an interrupt event - pub fn listen(&mut self, event: Event) { - match event { - Event::Rxne => { - self.usart.cr1.modify(|_, w| w.rxneie().set_bit()) - }, - Event::Txe => { - self.usart.cr1.modify(|_, w| w.txeie().set_bit()) - }, - Event::Idle => { - self.usart.cr1.modify(|_, w| w.idleie().set_bit()) - }, - } - } - - /// Stop listening for an interrupt event - pub fn unlisten(&mut self, event: Event) { - match event { - Event::Rxne => { - self.usart.cr1.modify(|_, w| w.rxneie().clear_bit()) - }, - Event::Txe => { - self.usart.cr1.modify(|_, w| w.txeie().clear_bit()) - }, - Event::Idle => { - self.usart.cr1.modify(|_, w| w.idleie().clear_bit()) - }, - } - } - - /// Return true if the line idle status is set - pub fn is_idle(& self) -> bool { - // NOTE(Safety) Atomic read on stateless register - unsafe { (*$USARTX::ptr()).sr.read().idle().bit_is_set() } - } - - /// Return true if the tx register is empty (and can accept data) - pub fn is_txe(& self) -> bool { - // NOTE(Safety) Atomic read on stateless register - unsafe { (*$USARTX::ptr()).sr.read().txe().bit_is_set() } - } - - /// Return true if the rx register is not empty (and can be read) - pub fn is_rxne(& self) -> bool { - // NOTE(Safety) Atomic read on stateless register - unsafe { (*$USARTX::ptr()).sr.read().rxne().bit_is_set() } - } - - pub fn split(self) -> (Tx<$USARTX>, Rx<$USARTX>) { - ( - Tx { - _usart: PhantomData, - }, - Rx { - _usart: PhantomData, - }, - ) - } - pub fn release(self) -> ($USARTX, PINS) { - (self.usart, self.pins) - } - } - - impl serial::Read for Serial<$USARTX, PINS> { - type Error = Error; - - fn read(&mut self) -> nb::Result { - let mut rx: Rx<$USARTX> = Rx { - _usart: PhantomData, - }; - rx.read() - } - } - - impl serial::TimeoutRead for Serial<$USARTX, PINS> { - type Error = Error; - - fn read>(&mut self, timeout: T) -> Result { - let mut rx: Rx<$USARTX> = Rx { - _usart: PhantomData, - }; - rx.read(timeout) - } - } - - impl serial::Read for Rx<$USARTX> { - type Error = Error; - - fn read(&mut self) -> nb::Result { - // NOTE(Safety) Atomic read on stateless register - let sr = unsafe { (*$USARTX::ptr()).sr.read() }; - - // Any error requires the dr to be read to clear - if sr.pe().bit_is_set() - || sr.fe().bit_is_set() - || sr.nf().bit_is_set() - || sr.ore().bit_is_set() - { - // NOTE(Safety) Atomic read on stateless register - unsafe { (*$USARTX::ptr()).dr.read() }; - } - - Err(if sr.pe().bit_is_set() { - nb::Error::Other(Error::Parity) - } else if sr.fe().bit_is_set() { - nb::Error::Other(Error::Framing) - } else if sr.nf().bit_is_set() { - nb::Error::Other(Error::Noise) - } else if sr.ore().bit_is_set() { - nb::Error::Other(Error::Overrun) - } else if sr.rxne().bit_is_set() { - // NOTE(read_volatile) see `write_volatile` below - return Ok(unsafe { ptr::read_volatile(&(*$USARTX::ptr()).dr as *const _ as *const u8) }); - } else { - nb::Error::WouldBlock - }) - } - } - - impl serial::TimeoutRead for Rx<$USARTX> { - type Error = Error; - - fn read>(&mut self, timeout: T) -> Result { - let start = systick::SysTick::now(); - while ((systick::SysTick::now() - start) < timeout.into()) { - // NOTE(Safety) Atomic read on stateless register - let sr = unsafe { (*$USARTX::ptr()).sr.read() }; - - // Any error requires the dr to be read to clear - if sr.pe().bit_is_set() - || sr.fe().bit_is_set() - || sr.nf().bit_is_set() - || sr.ore().bit_is_set() - { - // NOTE(Safety) Atomic read on stateless register - unsafe { (*$USARTX::ptr()).dr.read() }; - } - - if sr.pe().bit_is_set() { - return Err(Error::Parity); - } else if sr.fe().bit_is_set() { - return Err(Error::Framing); - } else if sr.nf().bit_is_set() { - return Err(Error::Noise); - } else if sr.ore().bit_is_set() { - return Err(Error::Overrun); - } else if sr.rxne().bit_is_set() { - // NOTE(read_volatile) see `write_volatile` below - return Ok(unsafe { ptr::read_volatile(&(*$USARTX::ptr()).dr as *const _ as *const u8) }); - } - } - Err(Error::Timeout) - } - } - - impl serial::Write for Serial<$USARTX, PINS> { - type Error = Error; - - fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - let mut tx: Tx<$USARTX> = Tx { - _usart: PhantomData, - }; - tx.write_str(s) - } - - fn write_char(&mut self, c: char) -> Result<(), Self::Error> { - let mut tx: Tx<$USARTX> = Tx { - _usart: PhantomData, - }; - tx.write_char(c) - } - } - - impl serial::Write for Tx<$USARTX> { - type Error = Error; - - fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - for character in s.chars() { - self.write_char(character)?; - } - Ok(()) - } - - fn write_char(&mut self, c: char) -> Result<(), Self::Error> { - // NOTE(Safety) atomic read with no side effects - while ! unsafe { (*$USARTX::ptr()).sr.read().txe().bit_is_set() } {} - // NOTE(Safety) atomic write to stateless register - // NOTE(write_volatile) 8-bit write that's not possible through the svd2rust API - unsafe { ptr::write_volatile(&(*$USARTX::ptr()).dr as *const _ as *mut _, c as u8) } - Ok(()) - } - } - )+ - } -} - -macro_rules! instances { - ($( - $USARTX:ident: ($usartX:ident, $apbXenr:ident, $usartXen:ident, $pclkX:ident), - )+) => { - $( - impl Serial<$USARTX, PINS> { - fn config_stop(self, config: config::Config) -> Self { - use crate::stm32pac::usart1::cr2::STOP_A; - use self::config::*; - - self.usart.cr2.write(|w| { - w.stop().variant(match config.stopbits { - StopBits::STOP0P5 => STOP_A::STOP0P5, - StopBits::STOP1 => STOP_A::STOP1, - StopBits::STOP1P5 => STOP_A::STOP1P5, - StopBits::STOP2 => STOP_A::STOP2, - }) - }); - self - } - } - - )+ - - hal_usart_impl! { - $( $USARTX: ($usartX, $apbXenr, $usartXen, $pclkX), )+ - } - - $( - impl UsartExt for $USARTX - where - PINS: Pins<$USARTX>, { - type Serial = Serial<$USARTX, PINS>; - - fn constrain(self, - pins: PINS, - config: config::Config, - clocks: rcc::Clocks, - ) -> Result { - Serial::$usartX(self, pins, config, clocks) - } - } - )+ - } -} - -// Type definition macros. NOTE: This is not configuration! No -// need to remove these if unused, they exist only in the type -// system at this point. -instances! { - USART1: (usart1, apb2enr, usart1en, pclk2), - USART2: (usart2, apb1enr, usart2en, pclk1), - USART3: (usart3, apb1enr, usart3en, pclk1), - USART6: (usart6, apb2enr, usart6en, pclk2), -} diff --git a/src/drivers/stm32f4/spi.rs b/src/drivers/stm32f4/spi.rs deleted file mode 100644 index 6e02e1a6..00000000 --- a/src/drivers/stm32f4/spi.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::{ - drivers::stm32f4::gpio::*, - hal::spi::FullDuplex, - ports::pin_configuration::*, - stm32pac::{RCC, SPI1}, -}; -use core::{marker::PhantomData, mem::size_of}; - -const BAUD_RATE_DIVIDER: u8 = 4; -pub type SpiAf = AF5; - -mod private { - #[doc(hidden)] - pub trait Sealed {} -} - -/// Sealed trait for all SPI capable pins. -pub unsafe trait MisoPin: private::Sealed {} -pub unsafe trait MosiPin: private::Sealed {} -pub unsafe trait SckPin: private::Sealed {} -pub unsafe trait NssPin: private::Sealed {} - -#[allow(unused)] -macro_rules! seal_pins { ($function:ty: [$($pin:ty,)+]) => { - $( - unsafe impl $function for $pin {} - impl private::Sealed for $pin {} - )+ -};} - -#[cfg(feature = "stm32f412")] -seal_pins!(NssPin: [Pa4, Pa15,]); -#[cfg(feature = "stm32f412")] -seal_pins!(SckPin: [Pa5, Pb3,]); -#[cfg(feature = "stm32f412")] -seal_pins!(MisoPin: [Pa6, Pb4,]); -#[cfg(feature = "stm32f412")] -seal_pins!(MosiPin: [Pa7, Pb5,]); - -/// Marker trait for a tuple of pins that work for a given SPI. -pub trait Pins {} - -impl Pins for (MISO, MOSI, SCK) -where - MISO: MisoPin, - MOSI: MosiPin, - SCK: SckPin, -{ -} - -/// SPI abstraction -pub struct Spi { - spi: SPI, - _pins: PINS, - _word: PhantomData, - awaiting_receive: bool, -} - -#[derive(Debug)] -pub enum FullDuplexSpiError { - OutOfOrderOperation, -} - -pub enum Mode { - Zero, - One, - Two, - Three, -} - -#[allow(unused_macros)] -macro_rules! hal_spi_impl { - ($( - $SPIX:ident: ($word: tt, $spiX:ident, $apbXenr:ident, $spiXen:ident, $pclkX:ident) - )+) => { - $( - impl Spi<$SPIX, PINS, $word> { - pub fn $spiX( - spi: $SPIX, pins: PINS, mode: Mode - ) -> Self - where PINS: Pins<$SPIX>, - { - // NOTE(safety) This executes only during initialisation. - let rcc = unsafe { &(*RCC::ptr()) }; - - // Enable clock for SPI - rcc.$apbXenr.modify(|_, w| w.$spiXen().set_bit()); - - // Baud rate divider - spi.cr1.modify(|_, w| w.br().bits(BAUD_RATE_DIVIDER)); - - // Mode bits - match mode { - Mode::Zero => spi.cr1.modify(|_, w| w.cpol().clear_bit().cpha().clear_bit()), - Mode::One => spi.cr1.modify(|_, w| w.cpol().clear_bit().cpha().set_bit()), - Mode::Two => spi.cr1.modify(|_, w| w.cpol().set_bit().cpha().clear_bit()), - Mode::Three => spi.cr1.modify(|_, w| w.cpol().set_bit().cpha().set_bit()), - } - - // Software slave management - spi.cr1.modify(|_, w| w.ssm().set_bit()); - - // Word length - match size_of::<$word>() { - 1 => spi.cr1.modify(|_, w| w.dff().clear_bit()), - 2 => spi.cr1.modify(|_, w| w.dff().set_bit()), - _ => panic!("Unsupported word size"), - } - - // Master mode and enable - spi.cr1.modify(|_, w| w.mstr().set_bit().spe().set_bit()); - - Self { spi, _pins: pins, _word: PhantomData, awaiting_receive: false } - } - - pub fn is_ready_to_transmit(&self) -> bool { - self.spi.sr.read().txe().bit_is_set() && !self.awaiting_receive - } - - pub fn is_ready_to_receive(&self) -> bool { - self.spi.sr.read().rxne().bit_is_set() && self.awaiting_receive - } - - pub fn is_busy(&self) -> bool { - self.spi.sr.read().bsy().bit_is_set() - } - } - - impl FullDuplex<$word> for Spi<$SPIX, PINS, $word> { - type Error = FullDuplexSpiError; - - fn transmit(&mut self, word: Option<$word>) -> nb::Result<(), Self::Error> { - if self.awaiting_receive { - return Err(nb::Error::Other(FullDuplexSpiError::OutOfOrderOperation)) - } - - if !self.is_ready_to_transmit() || self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - let word = word.unwrap_or(0) as u16; - self.spi.dr.write(|w| w.dr().bits(word)); - self.awaiting_receive = true; - Ok(()) - } - - fn receive(&mut self) -> nb::Result<$word, Self::Error> { - if !self.awaiting_receive { - return Err(nb::Error::Other(FullDuplexSpiError::OutOfOrderOperation)) - } - - if !self.is_ready_to_receive() || self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - self.awaiting_receive = false; - Ok(self.spi.dr.read().dr().bits() as $word) - } - } - )+ - } -} - -hal_spi_impl!( - SPI1: (u8, spi1, apb2enr, spi1en, pclk2) - SPI1: (u16, spi1, apb2enr, spi1en, pclk2) -); diff --git a/src/drivers/stm32f4/systick.rs b/src/drivers/stm32f4/systick.rs deleted file mode 100644 index 73f33e6e..00000000 --- a/src/drivers/stm32f4/systick.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{drivers::stm32f4::rcc, hal::time}; -use core::sync::atomic::{AtomicU32, Ordering}; -use cortex_m::peripheral::{syst::SystClkSource, SYST}; -use cortex_m_rt::exception; -use time::Now; - -/// Opaque wrapper around a system tick at certain point in time -#[derive(Copy, Clone, Debug)] -pub struct Tick { - counter: u32, -} - -impl time::Instant for Tick {} - -/// Handle over the SysTick. Allows safe access to the current instant. -#[derive(Copy, Clone, Debug)] -pub struct SysTick; - -impl SysTick { - /// Consumes the systick peripheral. - pub fn init(mut systick: SYST, clocks: rcc::Clocks) -> Self { - systick.set_clock_source(SystClkSource::Core); - systick.set_reload(clocks.sysclk().0 / 1000); // Millisecond ticks - systick.clear_current(); - systick.enable_counter(); - systick.enable_interrupt(); - Self - } - - pub fn wait>(t: T) { - let start = Self::now(); - while Self::now() - start < t.into() {} - } -} - -impl Now for SysTick { - type I = Tick; - fn now() -> Tick { Tick { counter: TICK_COUNTER.load(Ordering::Relaxed) } } -} - -static TICK_COUNTER: AtomicU32 = AtomicU32::new(0); - -#[exception] -fn SysTick() { TICK_COUNTER.fetch_add(1, Ordering::Relaxed); } - -/// Tick subtraction to obtain a time period -impl core::ops::Sub for Tick { - type Output = time::Milliseconds; - - fn sub(self, rhs: Self) -> Self::Output { - time::Milliseconds(self.counter.wrapping_sub(rhs.counter)) - } -} - -/// Addition between any Millisecond-convertible type and the current tick. -impl> core::ops::Add for Tick { - type Output = Self; - - fn add(self, rhs: T) -> Self { Self { counter: self.counter + rhs.into().0 } } -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn tick_differences_and_additions() { - // Given - let ticks_difference = 10u32; - let test_tick_early = Tick { counter: 0 }; - let test_tick_late = Tick { counter: test_tick_early.counter + ticks_difference }; - - assert_eq!(time::Milliseconds(10), test_tick_late - test_tick_early); - - // Given - let test_tick_late = test_tick_late + time::Milliseconds(300); - - assert_eq!(time::Milliseconds(310), test_tick_late - test_tick_early); - } -} diff --git a/src/error.rs b/src/error.rs index 3b8a03ab..9a03527a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ //! Error types and methods for the Secure Bootloader project. -use crate::hal::serial::Write; +use blue_hal::{hal::serial::Write, uprint}; use ufmt::{uwrite, uwriteln}; /// Top level error type for the bootloader. Unlike the specific diff --git a/src/hal/doubles/error.rs b/src/hal/doubles/error.rs deleted file mode 100644 index cb88bd40..00000000 --- a/src/hal/doubles/error.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::error::Error; - -#[derive(Debug, Copy, Clone)] -pub struct FakeError; - -impl From for Error { - fn from(_error: FakeError) -> Self { - Error::DeviceError("A fake error occurred [TESTING ONLY]") - } -} diff --git a/src/hal/doubles/flash.rs b/src/hal/doubles/flash.rs deleted file mode 100644 index 62e66a3b..00000000 --- a/src/hal/doubles/flash.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::error::FakeError; -use crate::hal::flash; -use std::{ - cmp::max, - ops::{Add, Sub}, -}; - -pub struct FakeFlash { - base: Address, - length: usize, - data: Vec, -} - -impl FakeFlash { - pub fn new(base: Address) -> FakeFlash { FakeFlash { base, data: Vec::new(), length: MB!(16) } } -} - -#[derive(Copy, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] -pub struct Address(pub u32); - -impl flash::ReadWrite for FakeFlash { - type Error = FakeError; - type Address = Address; - - fn read(&mut self, address: Self::Address, bytes: &mut [u8]) -> nb::Result<(), Self::Error> { - if address < self.base { - Err(nb::Error::Other(FakeError)) - } else { - self.data.iter().skip(address - self.base).zip(bytes).for_each(|(i, o)| *o = *i); - Ok(()) - } - } - - fn write(&mut self, address: Self::Address, bytes: &[u8]) -> nb::Result<(), Self::Error> { - if address < self.base { - Err(nb::Error::Other(FakeError)) - } else { - let offset = address - self.base; - self.data.resize_with(max(self.data.len(), offset + bytes.len()), Default::default); - self.data.iter_mut().skip(offset).zip(bytes).for_each(|(o, i)| *o = *i); - Ok(()) - } - } - - fn range(&self) -> (Self::Address, Self::Address) { (self.base, self.base + self.length) } - - fn erase(&mut self) -> nb::Result<(), Self::Error> { - self.data.clear(); - Ok(()) - } -} - -impl Add for Address { - type Output = Address; - fn add(self, rhs: usize) -> Self::Output { Address(self.0 + rhs as u32) } -} - -impl Sub for Address { - type Output = Address; - fn sub(self, rhs: usize) -> Self::Output { Address(self.0.saturating_sub(rhs as u32)) } -} - -impl Sub

for Address { - type Output = usize; - fn sub(self, rhs: Address) -> Self::Output { self.0.saturating_sub(rhs.0) as usize } -} - -impl From
for usize { - fn from(address: Address) -> Self { address.0 as usize } -} diff --git a/src/hal/doubles/gpio.rs b/src/hal/doubles/gpio.rs deleted file mode 100644 index 026754d4..00000000 --- a/src/hal/doubles/gpio.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::hal::gpio::OutputPin; -use std::vec::Vec; - -#[derive(Clone, Debug, Default)] -pub struct MockPin { - pub state: bool, - pub changes: Vec, -} - -impl MockPin { - pub fn is_high(&self) -> bool { self.state } - pub fn is_low(&self) -> bool { !self.state } -} - -impl OutputPin for MockPin { - fn set_low(&mut self) { - self.state = false; - self.changes.push(self.state); - } - - fn set_high(&mut self) { - self.state = true; - self.changes.push(self.state); - } -} diff --git a/src/hal/doubles/mod.rs b/src/hal/doubles/mod.rs deleted file mode 100644 index 436a9bf8..00000000 --- a/src/hal/doubles/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod gpio; -pub mod qspi; -pub mod spi; -pub mod time; -pub mod serial; -pub mod flash; -pub mod error; diff --git a/src/hal/doubles/qspi.rs b/src/hal/doubles/qspi.rs deleted file mode 100644 index 307405c5..00000000 --- a/src/hal/doubles/qspi.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::hal::qspi::Indirect; -use std::collections::VecDeque; - -#[derive(Clone, Debug)] -pub struct CommandRecord { - pub instruction: Option, - pub address: Option, - pub data: Option>, - pub length_requested: usize, - pub dummy_cycles: u8, -} - -impl CommandRecord { - pub fn contains(&self, data: &[u8]) -> bool { - if let Some(stored) = &self.data { - data.len() == stored.len() && stored.iter().zip(data.iter()).all(|(a, b)| a == b) - } else { - false - } - } -} - -#[derive(Default)] -pub struct MockQspi { - pub command_records: Vec, - pub to_read: VecDeque>, -} - -impl MockQspi { - pub fn clear(&mut self) { - self.command_records.clear(); - self.to_read.clear(); - } -} - -impl Indirect for MockQspi { - type Error = (); - - fn write( - &mut self, - instruction: Option, - address: Option, - data: Option<&[u8]>, - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error> { - self.command_records.push(CommandRecord { - instruction, - address, - data: Some(data.unwrap_or_default().to_vec()), - length_requested: 0, - dummy_cycles, - }); - Ok(()) - } - - fn read( - &mut self, - instruction: Option, - address: Option, - data: &mut [u8], - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error> { - self.command_records.push(CommandRecord { - instruction, - address, - data: Some(data.to_vec()), - length_requested: data.len(), - dummy_cycles, - }); - data.iter_mut().zip(self.to_read.pop_front().unwrap_or_default()).for_each(|(o, i)| *o = i); - Ok(()) - } -} diff --git a/src/hal/doubles/serial.rs b/src/hal/doubles/serial.rs deleted file mode 100644 index f525af3c..00000000 --- a/src/hal/doubles/serial.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::hal::{serial, time}; - -pub struct SerialStub {} - -impl serial::Write for SerialStub { - type Error = (); - fn write_str(&mut self, _s: &str) -> Result<(), Self::Error> { Ok(()) } -} - -impl serial::Read for SerialStub { - type Error = (); - fn read(&mut self) -> nb::Result { Ok(0) } -} - -impl serial::TimeoutRead for SerialStub { - type Error = (); - - fn read>(&mut self, _timeout: T) -> Result { - Ok(0) - } -} diff --git a/src/hal/doubles/spi.rs b/src/hal/doubles/spi.rs deleted file mode 100644 index 307180c2..00000000 --- a/src/hal/doubles/spi.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::hal::spi::FullDuplex; -use std::collections::VecDeque; - -pub struct MockSpi { - /// Mock values to be received - pub to_receive: VecDeque, - /// Mock values sent - pub sent: VecDeque, - awaiting_receive: bool, -} - -impl MockSpi { - pub fn new() -> Self { - Self { to_receive: VecDeque::new(), sent: VecDeque::new(), awaiting_receive: false } - } -} - -impl FullDuplex for MockSpi { - type Error = (); - fn transmit(&mut self, word: Option) -> nb::Result<(), Self::Error> { - if self.awaiting_receive { - Err(nb::Error::Other(())) - } else { - self.awaiting_receive = true; - if let Some(word) = word { - self.sent.push_back(word) - } - Ok(()) - } - } - - fn receive(&mut self) -> nb::Result { - if !self.awaiting_receive { - Err(nb::Error::Other(())) - } else { - self.awaiting_receive = false; - Ok(self.to_receive.pop_front().unwrap_or_default()) - } - } -} diff --git a/src/hal/doubles/time.rs b/src/hal/doubles/time.rs deleted file mode 100644 index 3b286e9d..00000000 --- a/src/hal/doubles/time.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::hal::time; - -#[derive(Copy, Clone, Debug)] -pub struct MockInstant {} -pub struct MockSysTick {} - -impl time::Instant for MockInstant {} - -impl time::Now for MockSysTick { - type I = MockInstant; - fn now() -> MockInstant { MockInstant {} } -} - -impl core::ops::Sub for MockInstant { - type Output = time::Milliseconds; - fn sub(self, _: Self) -> Self::Output { time::Milliseconds(0) } -} - -/// Addition between any Millisecond-convertible type and the current tick. -impl> core::ops::Add for MockInstant { - type Output = Self; - fn add(self, _: T) -> Self { Self {} } -} diff --git a/src/hal/flash.rs b/src/hal/flash.rs deleted file mode 100644 index 49393216..00000000 --- a/src/hal/flash.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::utilities::memory::Address; -use core::{ - mem::{size_of, MaybeUninit}, - slice, -}; - -/// Reads and writes a range of bytes, generic over an address -pub trait ReadWrite { - type Error: Clone + Copy; - type Address: Address; - fn read(&mut self, address: Self::Address, bytes: &mut [u8]) -> nb::Result<(), Self::Error>; - fn write(&mut self, address: Self::Address, bytes: &[u8]) -> nb::Result<(), Self::Error>; - fn range(&self) -> (Self::Address, Self::Address); - fn erase(&mut self) -> nb::Result<(), Self::Error>; -} - -pub trait UnportableSerialize: ReadWrite { - /// # Safety - /// - /// This is a very raw serialization (the bytes are written as-is). Should be - /// only used with repr(C) types with no internal references. It *will break* if any change - /// to the struct to serialize is made between serialization and deserialization, and it - /// *will* cause undefined behaviour. Make sure to erase the flash whenever there is an - /// update to the serializable types. - unsafe fn serialize( - &mut self, - item: &T, - address: Self::Address, - ) -> nb::Result<(), Self::Error> { - // Get a view into the raw bytes conforming T - let bytes = slice::from_raw_parts((item as *const T) as *const u8, size_of::()); - self.write(address, bytes) - } -} -impl UnportableSerialize for F {} - -pub trait UnportableDeserialize: ReadWrite { - /// # Safety - /// - /// This is a very raw serialization (the bytes are written as-is). Should be - /// only used in repr(C) types. It *will break* if any change to the struct to serialize is - /// made between serialization and deserialization, and it *will* cause undefined - /// behaviour. Make sure to erase the flash whenever there is an update to the serializable - /// types. - unsafe fn deserialize( - &mut self, - address: Self::Address, - ) -> nb::Result { - // Create uninitialized T with a zero repr - let mut uninit: MaybeUninit = MaybeUninit::uninit(); - let bytes = slice::from_raw_parts_mut(uninit.as_mut_ptr() as *mut _, size_of::()); - - // Read its byte representation into it - self.read(address, bytes)?; - Ok(uninit.assume_init()) - } -} -impl UnportableDeserialize for F {} diff --git a/src/hal/gpio.rs b/src/hal/gpio.rs deleted file mode 100644 index 3539ae74..00000000 --- a/src/hal/gpio.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! # Simple GPIO interface -//! -//! Separate interfaces to Input and Output pins, automatically -//! implemented by GPIOs that support such operations. -//! -//! For this project in particular, these traits are automatically implemented -//! for pins with the appropriate typestates, so there's no need for -//! manual implementation. - -/// Interface to a writable pin. -pub trait OutputPin { - fn set_low(&mut self); - fn set_high(&mut self); -} - -/// Interface to a readable pin. -pub trait InputPin { - fn is_high(&self) -> bool; - fn is_low(&self) -> bool; -} diff --git a/src/hal/led.rs b/src/hal/led.rs deleted file mode 100644 index 401ab8ee..00000000 --- a/src/hal/led.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! LED interfaces -//! -//! Access to LEDs is segmented over three interfaces to facilitate -//! all usual LED patterns. -use crate::hal::time; - -/// Interface to a LED's generic color. May be tricolor LEDs, full color -/// scales with PWM, a "grayscale" intensity range, etc. -pub trait Chromatic { - fn color(&mut self, color: Color); -} - -/// Interface to a LED's direct on/off/toggle operations. Likely to be -/// implemented for all LEDs, but could be left off for a blinking LED -/// that must always remain on. -pub trait Toggle { - fn on(&mut self); - fn off(&mut self); - fn toggle(&mut self); -} - -/// Interface to a blink-capable LED. -pub trait Blink { - fn frequency(&mut self, frequency: time::Hertz); -} diff --git a/src/hal/mod.rs b/src/hal/mod.rs deleted file mode 100644 index 937856de..00000000 --- a/src/hal/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Hardware Abstraction Layer, containing interfaces -//! for low level drivers. -#![macro_use] - -pub mod flash; -pub mod gpio; -pub mod led; -pub mod qspi; -pub mod serial; -pub mod spi; -pub mod time; - -#[cfg(not(target_arch = "arm"))] -#[doc(hidden)] -pub mod doubles; diff --git a/src/hal/qspi.rs b/src/hal/qspi.rs deleted file mode 100644 index 80b3fabb..00000000 --- a/src/hal/qspi.rs +++ /dev/null @@ -1,23 +0,0 @@ -/// Quad SPI configured in Indirect mode. -/// -/// Indirect mode forces all communication to occur through writes -/// and reads to QSPI registers. -pub trait Indirect { - type Error; - - fn write( - &mut self, - instruction: Option, - address: Option, - data: Option<&[u8]>, - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error>; - - fn read( - &mut self, - instruction: Option, - address: Option, - data: &mut [u8], - dummy_cycles: u8, - ) -> nb::Result<(), Self::Error>; -} diff --git a/src/hal/serial.rs b/src/hal/serial.rs deleted file mode 100644 index 5e18fb8f..00000000 --- a/src/hal/serial.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Interface to a serial device. -//! -//! This interface is block-agnostic thanks to the **nb** crate. This -//! means it can be used in a blocking manner (through the block! macro) -//! or in a manner compatible with schedulers, RTOS, etc. See the **nb** -//! crate documentation for details. -#![macro_use] - -use nb::{self, block}; - -pub trait ReadWrite: Read + Write {} -impl ReadWrite for T {} - -pub use ufmt::uWrite as Write; - -use super::time::Milliseconds; - -/// UART read half -pub trait Read { - type Error: Copy + Clone; - - /// Reads a single byte - fn read(&mut self) -> nb::Result; - fn bytes(&mut self) -> ReadIterator { ReadIterator { reader: self, errored: false } } -} - -/// UART read half, with timeouts. Rather than returning a `nb::Result` for flow control, -/// it returns a standard `Result` with the option of a `Timeout` error if the specified -/// time was not achieved. -pub trait TimeoutRead { - type Error: Copy + Clone; - - /// Reads a single byte - fn read>(&mut self, timeout: T) -> Result; - fn bytes>(&mut self, timeout: T) -> TimeoutReadIterator { - TimeoutReadIterator { reader: self, errored: false, timeout } - } -} - -pub struct ReadIterator<'a, R: Read + ?Sized> { - reader: &'a mut R, - errored: bool, -} - -pub struct TimeoutReadIterator<'a, R: TimeoutRead + ?Sized, T: Copy + Into> { - reader: &'a mut R, - errored: bool, - timeout: T, -} - -impl<'a, R: Read + ?Sized> Iterator for ReadIterator<'a, R> { - type Item = Result::Error>; - fn next(&mut self) -> Option { - if self.errored { - None - } else { - match block!(self.reader.read()) { - Ok(byte) => Some(Ok(byte)), - Err(e) => { - self.errored = true; - Some(Err(e)) - } - } - } - } -} - -impl<'a, R: TimeoutRead + ?Sized, T: Copy + Into> Iterator - for TimeoutReadIterator<'a, R, T> -{ - type Item = Result::Error>; - fn next(&mut self) -> Option { - if self.errored { - None - } else { - match self.reader.read(self.timeout) { - Ok(byte) => Some(Ok(byte)), - Err(e) => { - self.errored = true; - Some(Err(e)) - } - } - } - } -} - -/// Carries on silently if uncapable of writing. -#[macro_export] -macro_rules! uprint { - ($serial:expr, $($arg:tt)+) => { - let _ = uwrite!($serial, $($arg)+ ); - }; -} - -/// Carries on silently if uncapable of writing. -#[macro_export] -macro_rules! uprintln { - ($serial:expr, $($arg:tt)+) => { - let _ = uwriteln!($serial, $($arg)+ ); - }; -} - -/// Panics if uncapable of writing. -#[macro_export] -macro_rules! critical_uprint { - ($serial:expr, $($arg:tt)+) => { - uprint!($serial, $($arg)+ ).ok().unwrap(); - }; -} - -/// Panics if uncapable of writing. -#[macro_export] -macro_rules! critical_uprintln { - ($serial:expr, $($arg:tt)+) => { - uprintln!($serial, $($arg)+ ).ok().unwrap(); - }; -} - -#[cfg(test)] -mod test { - #[derive(Debug, Default)] - struct MockUsart { - pub mock_value_to_read: u8, - pub write_record: Vec, - } - - impl Write for MockUsart { - type Error = (); - - fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - for byte in s.as_bytes() { - self.write_record.push(*byte); - } - Ok(()) - } - fn write_char(&mut self, c: char) -> Result<(), Self::Error> { - Ok(self.write_record.push(c as u8)) - } - } - - impl Read for MockUsart { - type Error = (); - - /// Reads a single word - fn read(&mut self) -> nb::Result { Ok(self.mock_value_to_read) } - } - - use super::*; - use ufmt::{uwrite, uwriteln}; - - #[test] - fn uwrite_macro_writes_bytes_with_no_newline() { - // Given - let mut mock_usart = MockUsart::default(); - let arbitrary_message = "Hello world!"; - let arbitrary_message_as_bytes: Vec = - arbitrary_message.as_bytes().iter().cloned().collect(); - - // When - uprint!(mock_usart, "{}", arbitrary_message); - - // Then - assert_eq!(arbitrary_message_as_bytes, mock_usart.write_record); - } - - #[test] - fn uwriteln_macro_writes_bytes_with_newline() { - // Given - let mut mock_usart = MockUsart::default(); - let arbitrary_message = "Hello world with newline!"; - let newline = "\n"; - let mut expected_message: Vec = arbitrary_message.as_bytes().iter().cloned().collect(); - expected_message.append(&mut newline.as_bytes().iter().cloned().collect()); - - // When - uwriteln!(mock_usart, "{}", arbitrary_message).unwrap(); - - // Then - assert_eq!(expected_message, mock_usart.write_record); - } -} diff --git a/src/hal/spi.rs b/src/hal/spi.rs deleted file mode 100644 index e224292e..00000000 --- a/src/hal/spi.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub trait FullDuplex { - type Error; - - fn transmit(&mut self, word: Option) -> nb::Result<(), Self::Error>; - // Must be called after transmit (full duplex operation) - fn receive(&mut self) -> nb::Result; -} diff --git a/src/hal/time.rs b/src/hal/time.rs deleted file mode 100644 index f0de886d..00000000 --- a/src/hal/time.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Time units. -use core::ops::{Add as Adds, Sub as Subtracts}; - -/// Abstract point in time. Useful for time periods -/// -/// Any implementer of Instant can be subtracted with -/// itself to obtain a span of milliseconds. -/// -/// Any implementer of Instant can be added with -/// milliseconds to obtain another instant. -pub trait Instant -where - Self: Copy + Clone, - Self: Subtracts, - Self: Adds, -{ -} - -pub trait Now { - type I: Instant; - fn now() -> Self::I; -} - -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct Microseconds(pub u32); - -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct Milliseconds(pub u32); - -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct Seconds(pub u32); - -/// Bits per second -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct Bps(pub u32); - -/// Hertz -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct Hertz(pub u32); - -/// KiloHertz -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct KiloHertz(pub u32); - -/// MegaHertz -#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)] -pub struct MegaHertz(pub u32); - -/// Extension trait that adds convenience methods to the `u32` type -pub trait U32Ext { - /// Wrap in `Bps` - fn bps(self) -> Bps; - - /// Wrap in `Hertz` - fn hz(self) -> Hertz; - - /// Wrap in `KiloHertz` - fn khz(self) -> KiloHertz; - - /// Wrap in `MegaHertz` - fn mhz(self) -> MegaHertz; - - /// Wrap in `Seconds` - fn s(self) -> Seconds; - - /// Wrap in `Milliseconds` - fn ms(self) -> Milliseconds; - - /// Wrap in `Microseconds` - fn us(self) -> Microseconds; -} - -impl U32Ext for u32 { - fn bps(self) -> Bps { Bps(self) } - - fn hz(self) -> Hertz { Hertz(self) } - - fn khz(self) -> KiloHertz { KiloHertz(self) } - - fn mhz(self) -> MegaHertz { MegaHertz(self) } - - fn s(self) -> Seconds { Seconds(self) } - - fn ms(self) -> Milliseconds { Milliseconds(self) } - - fn us(self) -> Microseconds { Microseconds(self) } -} - -impl Into for KiloHertz { - fn into(self) -> Hertz { Hertz(self.0 * 1_000) } -} - -impl Into for MegaHertz { - fn into(self) -> Hertz { Hertz(self.0 * 1_000_000) } -} - -impl Into for MegaHertz { - fn into(self) -> KiloHertz { KiloHertz(self.0 * 1_000) } -} - -impl Into for Seconds { - fn into(self) -> Milliseconds { Milliseconds(self.0 * 1_000) } -} - -impl Into for Seconds { - fn into(self) -> Microseconds { Microseconds(self.0 * 1_000_000) } -} - -impl Into for Milliseconds { - fn into(self) -> Microseconds { Microseconds(self.0 * 1_000) } -} diff --git a/src/lib.rs b/src/lib.rs index 220b2246..cd351689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,19 +24,6 @@ use panic_abort as _; #[cfg(target_arch = "arm")] use defmt_rtt as _; // global logger -#[macro_use] -pub mod utilities { - pub mod bitwise; - pub mod iterator; - pub mod memory; - pub mod guard; - pub mod buffer; - pub mod xmodem; - mod macros; -} - -pub mod hal; -pub mod drivers; pub mod devices; pub mod error; pub mod ports; diff --git a/src/ports/mod.rs b/src/ports/mod.rs index a56a41c0..e1f8a888 100644 --- a/src/ports/mod.rs +++ b/src/ports/mod.rs @@ -2,5 +2,7 @@ //! provide a method to construct a generic bootloader from //! specific parts. +use blue_hal::port; + #[cfg(feature = "stm32f412_discovery")] port!(stm32f412_discovery: [bootloader, pin_configuration,]); diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 5e2cb7eb..fadc8c03 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -1,22 +1,12 @@ //! GPIO configuration and alternate functions for the [stm32f412 discovery](../../../../../../../../documentation/hardware/discovery.pdf). -use crate::ports::pin_configuration::*; -use crate::hal::{time, gpio::InputPin}; -use crate::{drivers::{ - stm32f4::gpio::GpioExt, - stm32f4::gpio::*, - stm32f4::qspi::{self, mode, QuadSpi}, - stm32f4::rcc::Clocks, - stm32f4::serial::{self, UsartExt}, - stm32f4::systick::SysTick, - stm32f4::flash, - micron::n25q128a_flash::{self, MicronN25q128a}, -}, stm32pac::{self, USART6}}; use crate::devices::bootloader::Bootloader; use crate::devices::image; use crate::devices::cli::Cli; use crate::error::Error; use core::mem::size_of; use ufmt::uwriteln; +use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, gpio::*, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use blue_hal::drivers::stm32f4::gpio::GpioExt; // Flash pins and typedefs type QspiPins = (Pb2, Pg6, Pf8, Pf9, Pf7, Pf6); diff --git a/src/ports/stm32f412_discovery/pin_configuration.rs b/src/ports/stm32f412_discovery/pin_configuration.rs index 4705a6f7..a98549bc 100644 --- a/src/ports/stm32f412_discovery/pin_configuration.rs +++ b/src/ports/stm32f412_discovery/pin_configuration.rs @@ -1,7 +1,10 @@ -//! GPIO configuration and alternate functions for the [stm32f412 discovery](../../../../../../documentation/hardware/discovery.pdf). -use crate::drivers::stm32f4::serial::{TxPin, RxPin}; -use crate::stm32pac::USART6; -use crate::drivers::stm32f4::qspi::{ +use blue_hal::{alternate_functions, gpio, gpio_inner, pin_rows}; +use blue_hal::paste; +use blue_hal::drivers::stm32f4::gpio::{self, *}; +use blue_hal::hal::gpio::{*, InputPin}; +use blue_hal::drivers::stm32f4::serial::{TxPin, RxPin}; +use blue_hal::stm32pac::USART6; +use blue_hal::drivers::stm32f4::qspi::{ ClkPin as QspiClk, Bk1CsPin as QspiChipSelect, Bk1Io0Pin as QspiOutput, @@ -9,8 +12,9 @@ use crate::drivers::stm32f4::qspi::{ Bk1Io2Pin as QspiSecondaryOutput, Bk1Io3Pin as QspiSecondaryInput, }; - +alternate_functions!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); pin_rows!(a, b, c, d, e, f, g, h, i, j, k,); + gpio!(a, [ (0, Input), // Boot mode (1, Input), diff --git a/src/utilities/bitwise.rs b/src/utilities/bitwise.rs deleted file mode 100644 index 56729cc6..00000000 --- a/src/utilities/bitwise.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Convenience bitwise operations. - -use core::ops::BitOr; - -/// Simple check for particular bits being set or cleared. -pub trait BitFlags { - fn is_set(&self, bit: u8) -> bool; - fn is_clear(&self, bit: u8) -> bool; -} - -/// Checks that every '1' bit is a '1' on the -/// right hand side. -pub trait BitSubset: Copy { - fn is_subset_of(self, rhs: Self) -> bool; -} - -/// Variant of the BitSubset trait for slices. -pub trait SliceBitSubset { - /// Checks that every '1' in self is '1' in T - fn is_subset_of(self, rhs: Self) -> bool; -} - -/// Blanket implementation for any types convertible to u32. -impl> BitFlags for U { - fn is_set(&self, bit: u8) -> bool { - assert!(bit < 32); - ((*self).into() & (1u32 << bit)) != 0 - } - - fn is_clear(&self, bit: u8) -> bool { !self.is_set(bit) } -} - -/// Blanket implementation for any type with a `bitwise or` operation -impl + PartialEq> BitSubset for U { - fn is_subset_of(self, rhs: Self) -> bool { (self | rhs) == rhs } -} - -/// Blanket implementation for any slices of types that can be bitwise subsets -impl SliceBitSubset for &[T] { - fn is_subset_of(self, rhs: Self) -> bool { - if self.len() > rhs.len() { - false - } else { - self.iter().zip(rhs.iter()).all(|(a, b)| a.is_subset_of(*b)) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn correctly_checks_bits() { - assert!(3u8.is_set(0)); - assert!(3u8.is_set(1)); - assert!(3u8.is_clear(2)); - assert!(2u8.is_clear(0)); - } - - #[test] - fn checks_bit_subsets() { - assert!(0xAAu8.is_subset_of(0xFFu8)); - assert!(!0xFFFF_FFFF_u32.is_subset_of(0xAAAA_AAAA_u32)); - assert!(0b0101.is_subset_of(0b0111)); - } - - #[test] - fn verify_memory_range_is_subset_of_sector() { - let range = [0x12, 0x34, 0x56, 0x78]; - let newly_erased_sector = [0xFF, 0xFF, 0xFF, 0xFF]; - assert!(range.is_subset_of(&newly_erased_sector)); - assert!(!newly_erased_sector.is_subset_of(&range)); - - let third_bits = [0b0100, 0b0100, 0b0100, 0b0100, 0b0100]; - let even_bits = [0b0101, 0b0101, 0b0101, 0b0101, 0b0101]; - assert!(third_bits.is_subset_of(&even_bits)); - assert!(!even_bits.is_subset_of(&third_bits)); - - let short_range = [0xFF]; - let long_sector = [0xFF, 0xFF]; - assert!(short_range.is_subset_of(&long_sector)); - assert!(!long_sector.is_subset_of(&short_range)); - } -} diff --git a/src/utilities/buffer.rs b/src/utilities/buffer.rs deleted file mode 100644 index 7a6e9d3b..00000000 --- a/src/utilities/buffer.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub trait CollectSlice: Iterator { - fn collect_slice(&mut self, slice: &mut [Self::Item]) -> usize; -} - -pub trait TryCollectSlice: Iterator { - type Element; - type Error; - fn try_collect_slice(&mut self, slice: &mut [Self::Element]) -> Result; -} - -impl CollectSlice for I { - fn collect_slice(&mut self, slice: &mut [Self::Item]) -> usize { - slice.iter_mut().zip(self).fold(0, |count, (dest, item)| { - *dest = item; - count + 1 - }) - } -} - -impl TryCollectSlice for I -where - I: Iterator>, -{ - type Element = T; - type Error = E; - fn try_collect_slice(&mut self, slice: &mut [Self::Element]) -> Result { - slice.iter_mut().zip(self).try_fold(0, |count, (dest, item)| { - *dest = item?; - Ok(count + 1) - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn collecting_various_types_in_slices() { - const ELEMENTS: usize = 10; - let mut ints = [0usize; ELEMENTS]; - assert_eq!(ELEMENTS, (0..ELEMENTS).collect_slice(&mut ints)); - assert_eq!(5, ints[5]); - - let mut letters = ['a'; ELEMENTS]; - assert_eq!(3, (0..3u8).map(|i| ('a' as u8 + i) as char).collect_slice(&mut letters)); - assert_eq!('c', letters[2]); - } - - #[test] - fn collecting_fallibly() { - const ELEMENTS: usize = 10; - let mut ints = [0u8; ELEMENTS]; - let to_collect: [Result; 3] = [Ok(3), Ok(2), Err(())]; - assert!(to_collect.iter().copied().try_collect_slice(&mut ints).is_err()); - - let to_collect: [Result; 3] = [Ok(3), Ok(2), Ok(1)]; - assert_eq!(Ok(3), to_collect.iter().copied().try_collect_slice(&mut ints)); - } -} diff --git a/src/utilities/guard.rs b/src/utilities/guard.rs deleted file mode 100644 index 93ab0dae..00000000 --- a/src/utilities/guard.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! RAII guard that calls a given function when constructed, -//! and another when it drops out of scope. -//! -//! Useful for ensuring resource cleanup no matter the return -//! path. -//! -//! Example -//! ``` -//! # use loadstone_lib::hal::led::*; -//! # use loadstone_lib::drivers::led::*; -//! # use loadstone_lib::hal::doubles::gpio::*; -//! # use loadstone_lib::utilities::guard::*; -//! # let pin = MockPin::default(); -//! # let mut led = MonochromeLed::new(pin, LogicLevel::Direct); -//! { -//! // Led is toggled on as soon as guard is constructed, and -//! // held protected by the guard (as it has exclusive access -//! // to it) -//! let _guard = Guard::new(&mut led, Toggle::on, Toggle::off); -//! } -//! // Guard has dropped out of scope here, so led is toggled off -//! assert!(!led.is_on()); -//! # assert_eq!(led.pin().changes.len(), 3); -//! # assert_eq!(led.pin().changes[1], true); -//! # assert_eq!(led.pin().changes[2], false); -//! ``` - -use core::marker::PhantomData; - -pub struct Guard<'a, T, F, G> -where - F: FnOnce(&mut T), - G: FnOnce(&mut T), -{ - item: &'a mut T, - on_exit: Option, - _marker: PhantomData, -} - -impl<'a, T, F, G> Guard<'a, T, F, G> -where - F: FnOnce(&mut T), - G: FnOnce(&mut T), -{ - #[must_use] - pub fn new(item: &'a mut T, on_entry: F, on_exit: G) -> Self { - on_entry(item); - Self { item, on_exit: Some(on_exit), _marker: PhantomData::default() } - } -} - -impl<'a, T, F, G> Drop for Guard<'a, T, F, G> -where - F: FnOnce(&mut T), - G: FnOnce(&mut T), -{ - fn drop(&mut self) { self.on_exit.take().unwrap()(self.item); } -} diff --git a/src/utilities/iterator.rs b/src/utilities/iterator.rs deleted file mode 100644 index 97853d2d..00000000 --- a/src/utilities/iterator.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub trait Unique { - fn all_unique(self) -> bool; -} - -impl, I: PartialEq> Unique for T { - fn all_unique(mut self) -> bool { - // O(n^2), could do with optimisation. Difficult - // to optimise without a hash set (no heap) - while let Some(element) = self.next() { - if self.clone().any(|e| e == element) { - return false; - } - } - true - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn all_unique_in_various_scenarios() { - assert!([3, 4, 1, 5].iter().all_unique()); - assert!(![1, 2, 3, 3, 2].iter().all_unique()); - assert!(["fish", "foot", "fly", "foresight"].iter().all_unique()); - assert!(![None, Some(3), Some(5), None].iter().all_unique()); - } -} diff --git a/src/utilities/macros.rs b/src/utilities/macros.rs deleted file mode 100644 index 7fb5f72d..00000000 --- a/src/utilities/macros.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Convenience macros for the Bootloader project -#![macro_use] - -/// Define and export a specific port module (transparently pulls -/// its namespace to the current one). -/// -/// Used mostly to conveniently fit the module declaration and reexport -/// under a single configuration flag. -/// -/// # Example -/// ```ignore -/// #[cfg(feature = "stm32_any")] -/// port!(stm32); -/// // Expands into: -/// pub mod stm32; -/// pub use self::stm32::*; -/// -/// #[cfg(feature = "stm32_any")] -/// port!(stm32::flash as mcu_flash); -/// // Expands into: -/// pub mod stm32 { pub mod flash }; -/// pub use self::stm32::flash as mcu_flash; -/// ``` -#[macro_export] -macro_rules! port { - ($mod:ident) => { - pub mod $mod; - pub use self::$mod::*; - }; - ($mod:ident as $name:ident) => { - pub mod $mod; - pub use self::$mod as $name; - }; - ($outer:ident::$inner:ident) => { - pub mod $outer { pub mod $inner; } - pub use self::$outer::$inner::*; - }; - ($outer:ident::$inner:ident as $name:ident) => { - pub mod $outer { pub mod $inner; } - pub use self::$outer::$inner as $name; - }; - ($outer:ident: [$($inner:ident,)+]) => { - pub mod $outer { - $( - pub mod $inner; - )+ - } - $( - pub use self::$outer::$inner; - )+ - }; - ($outer:ident: [$($inner:ident as $name:ident)+,]) => { - pub mod $outer { - $( - pub mod $inner; - )+ - } - $( - pub use self::$outer::$inner as $name; - )+ - }; -} diff --git a/src/utilities/memory.rs b/src/utilities/memory.rs deleted file mode 100644 index 9dfcf109..00000000 --- a/src/utilities/memory.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Utilities to manipulate generic memory -#![macro_use] - -#[macro_export] -macro_rules! KB { - ($val:expr) => { - $val * 1024 - }; -} -#[macro_export] -macro_rules! MB { - ($val:expr) => { - $val * 1024 * 1024 - }; -} - -/// Generic address for the purpose of this module's methods. -/// Anything that can be offset by a usize and yield another -/// address works as an address. -pub trait Address: - 'static - + Ord - + Copy - + core::ops::Add - + core::ops::Sub - + core::ops::Sub - + Into -{ -} - -impl Address for A where - A: 'static - + Ord - + Copy - + core::ops::Add - + core::ops::Sub - + core::ops::Sub - + Into -{ -} - -/// Abstract region that can contain addresses -pub trait Region { - fn contains(&self, address: A) -> bool; -} - -/// Iterator producing block-region pairs, -/// where each memory block corresponds to each region -pub struct OverlapIterator<'a, A, R, I> -where - A: Address, - R: Region, - I: Iterator, -{ - memory: &'a [u8], - regions: I, - base_address: A, -} - -/// Anything that can be sliced in blocks, each block -/// corresponding to a region in a region sequence -pub trait IterableByOverlaps<'a, A, R, I> -where - A: Address, - R: Region, - I: Iterator, -{ - fn overlaps(self, block: &'a [u8], base_address: A) -> OverlapIterator; -} - -impl<'a, A, R, I> Iterator for OverlapIterator<'a, A, R, I> -where - A: Address, - R: Region, - I: Iterator, -{ - type Item = (&'a [u8], R, A); - - fn next(&mut self) -> Option { - while let Some(region) = self.regions.next() { - let mut block_range = (0..self.memory.len()) - .skip_while(|index| !region.contains(self.base_address + *index)) - .take_while(|index| region.contains(self.base_address + *index)); - if let Some(start) = block_range.next() { - let end = block_range.last().unwrap_or(start) + 1; - return Some((&self.memory[start..end], region, self.base_address + start)); - } - } - None - } -} - -/// Blanket implementation of overlaps iterator for any region iterator -impl<'a, A, R, I> IterableByOverlaps<'a, A, R, I> for I -where - A: Address, - R: Region, - I: Iterator, -{ - fn overlaps(self, memory: &'a [u8], base_address: A) -> OverlapIterator { - OverlapIterator { memory, regions: self, base_address } - } -} - -#[cfg(not(target_arch = "arm"))] -#[doc(hidden)] -pub mod doubles { - use super::*; - pub type FakeAddress = usize; - - #[derive(Debug, PartialEq, Copy, Clone)] - pub struct FakeRegion { - pub start: FakeAddress, - pub size: usize, - } - - impl Region for FakeRegion { - fn contains(&self, address: FakeAddress) -> bool { - (self.start <= address) && ((self.start + self.size) > address) - } - } -} - -#[cfg(test)] -mod test { - use super::{doubles::*, *}; - - #[test] - fn iterating_over_regions_starting_before_them() { - // Given - const MEMORY_SIZE: usize = 0x50; - let memory = [0xFFu8; MEMORY_SIZE]; - let memory_slice = &memory[..]; - let base_address = 0x20; - - let regions = - [FakeRegion { start: 0x30, size: 0x10 }, FakeRegion { start: 0x40, size: 0x05 }]; - - // When - let pairs: Vec<_> = regions.iter().copied().overlaps(memory_slice, base_address).collect(); - - // Then - assert_eq!(pairs.len(), 2); - - let (block, region, address) = pairs[0]; - assert_eq!(block, &memory[0x10..0x20]); - assert_eq!(region, regions[0]); - assert_eq!(address, regions[0].start); - let (block, region, address) = pairs[1]; - assert_eq!(block, &memory[0x20..0x25]); - assert_eq!(region, regions[1]); - assert_eq!(address, regions[1].start); - } - - #[test] - fn iterating_over_regions_starting_in_the_middle() { - // Given - const MEMORY_SIZE: usize = 30; - let memory = [0; MEMORY_SIZE]; - let memory_slice = &memory[..]; - let base_address = 15; - - let regions = [FakeRegion { start: 10, size: 20 }, FakeRegion { start: 30, size: 100 }]; - - // When - let pairs: Vec<_> = regions.iter().copied().overlaps(memory_slice, base_address).collect(); - - // Then - assert_eq!(pairs.len(), 2); - - let (block, region, address) = pairs[0]; - assert_eq!(block, &memory[0..15]); - assert_eq!(region, regions[0]); - assert_eq!(address, base_address); - - let (block, region, address) = pairs[1]; - assert_eq!(block, &memory[15..30]); - assert_eq!(region, regions[1]); - assert_eq!(address, regions[1].start); - } - - #[test] - fn single_byte() { - // Given - const MEMORY_SIZE: usize = 1; - let memory = [0; MEMORY_SIZE]; - let memory_slice = &memory[..]; - let base_address = 15; - - let regions = [FakeRegion { start: 10, size: 20 }, FakeRegion { start: 30, size: 100 }]; - - // When - let pairs: Vec<_> = regions.iter().copied().overlaps(memory_slice, base_address).collect(); - - // Then - assert_eq!(pairs.len(), 1); - - let (block, region, address) = pairs[0]; - assert_eq!(block, &memory[0..1]); - assert_eq!(region, regions[0]); - assert_eq!(address, base_address); - } - - #[test] - fn conversion_macros() { - assert_eq!(KB!(16), 0x4000); - assert_eq!(MB!(1), 0x100000); - } -} diff --git a/src/utilities/xmodem.rs b/src/utilities/xmodem.rs deleted file mode 100644 index 1081ec0d..00000000 --- a/src/utilities/xmodem.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Xmodem parser. - -use core::convert::TryInto; -use nom::{ - branch::alt, - bytes::streaming::{tag, take}, - number::streaming::be_u8, - IResult, -}; - -use crate::hal::time::Seconds; - -pub const PAYLOAD_SIZE: usize = 128; -pub const MAX_PACKET_SIZE: usize = 132; -pub const DEFAULT_TIMEOUT: Seconds = Seconds(3); - -pub const ACK: u8 = 0x06; -pub const NAK: u8 = 0x15; -pub const SOH: u8 = 0x01; -pub const EOT: u8 = 0x04; -pub const ETB: u8 = 0x17; -pub const CAN: u8 = 0x18; - -#[derive(Debug, Eq, PartialEq)] -pub struct Chunk { - pub block_number: u8, - pub payload: [u8; PAYLOAD_SIZE], -} - -#[derive(Debug, Eq, PartialEq)] -pub enum Message { - Chunk(Chunk), - EndOfTransmission, - EndOfTransmissionBlock, - Cancel, -} - -pub fn parse_message(input: &[u8]) -> IResult<&[u8], Message> { - alt((parse_chunk, parse_eot, parse_etb, parse_cancel))(input) -} - -fn parse_chunk(input: &[u8]) -> IResult<&[u8], Message> { - let (input, _) = tag(&[SOH])(input)?; - let (input, block_number) = be_u8(input)?; - let (input, _) = tag(&[!block_number])(input)?; - let (input, payload) = take(PAYLOAD_SIZE)(input)?; - let checksum: u8 = payload.iter().fold(0u8, |sum, b| sum.wrapping_add(*b)); - let (input, _) = tag(&[checksum])(input)?; - Ok((input, Message::Chunk(Chunk { block_number, payload: payload.try_into().unwrap() }))) -} - -fn parse_eot(input: &[u8]) -> IResult<&[u8], Message> { - Ok((tag(&[EOT])(input)?.0, Message::EndOfTransmission)) -} - -fn parse_etb(input: &[u8]) -> IResult<&[u8], Message> { - Ok((tag(&[ETB])(input)?.0, Message::EndOfTransmissionBlock)) -} - -fn parse_cancel(input: &[u8]) -> IResult<&[u8], Message> { - Ok((tag(&[CAN])(input)?.0, Message::Cancel)) -} - -#[cfg(test)] -mod test { - use super::*; - use nom::Err::Incomplete; - const MAX_PACKET_SIZE: usize = 132; - - fn write_test_packet(index: u8, payload_value: u8, buffer: &mut [u8]) { - let checksum = (0..128).fold(0, |sum: u8, _| sum.wrapping_add(payload_value)); - buffer.iter_mut().enumerate().for_each(|(i, b)| { - *b = match i { - 0 => SOH, - 1 => index, - 2 => !index, - 3..=130 => payload_value, - 131 => checksum, - _ => *b, - } - }); - } - - #[test] - fn parsing_single_character_control_messages() { - let input = [EOT]; - let (input, message) = parse_message(&input).unwrap(); - assert_eq!(Message::EndOfTransmission, message); - assert_eq!(input.len(), 0); - - let input = [ETB]; - let (input, message) = parse_message(&input).unwrap(); - assert_eq!(Message::EndOfTransmissionBlock, message); - assert_eq!(input.len(), 0); - - let input = [CAN]; - let (input, message) = parse_message(&input).unwrap(); - assert_eq!(Message::Cancel, message); - assert_eq!(input.len(), 0); - } - - #[test] - fn parsing_complete_input_chunk() { - let mut input = [0u8; MAX_PACKET_SIZE]; - write_test_packet(7, 42, &mut input); - let (input, message) = parse_message(&input).unwrap(); - - let expected_payload = [42u8; PAYLOAD_SIZE]; - let expected_index = 7u8; - - assert_eq!( - Message::Chunk(Chunk { payload: expected_payload, block_number: expected_index }), - message - ); - assert_eq!(input.len(), 0); - } - - #[test] - fn parsing_incomplete_input_chunk() { - let mut input = [0u8; MAX_PACKET_SIZE / 2]; - write_test_packet(7, 42, &mut input); - assert!(parse_message(&input).unwrap_err().is_incomplete()); - } - - #[test] - fn parsing_three_messages_in_a_row() { - let mut input = [0u8; 2 * MAX_PACKET_SIZE + 1]; - write_test_packet(1, 1, &mut input); - write_test_packet(2, 2, &mut input[MAX_PACKET_SIZE..]); - input[2 * MAX_PACKET_SIZE] = EOT; - - let (input, message) = parse_message(&input).unwrap(); - assert_eq!( - Message::Chunk(Chunk { payload: [1u8; PAYLOAD_SIZE], block_number: 1 }), - message - ); - let (input, message) = parse_message(&input).unwrap(); - assert_eq!( - Message::Chunk(Chunk { payload: [2u8; PAYLOAD_SIZE], block_number: 2 }), - message - ); - let (input, message) = parse_message(&input).unwrap(); - assert_eq!(Message::EndOfTransmission, message); - assert_eq!(input.len(), 0); - } -} diff --git a/tools/upload.sh b/tools/upload.sh index 9a0941db..d503eb63 100755 --- a/tools/upload.sh +++ b/tools/upload.sh @@ -5,5 +5,5 @@ if [ "$#" -ne 2 ]; then exit 1 fi -sudo stty -F /dev/ttyUSB0 115200 cs8 -parenb -cstopb -ixoff +sudo stty -F $2 115200 cs8 -parenb -cstopb -ixoff sx $1 < $2 > $2 From 9606443045cb3810cba22e8e56e7e3711267e531 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Wed, 6 Jan 2021 10:53:40 +0000 Subject: [PATCH 05/67] Fix bad function comments. --- src/devices/bootloader.rs | 3 +-- src/devices/cli/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 082caf8e..87068d3a 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -43,8 +43,7 @@ where SRL: serial::ReadWrite, Error: From<::Error>, { - /// Runs the boot loader, entering CLI if the boot fails. If interactive mode is enabled for - /// the bootloader, the CLI is entered without attemping a boot. + /// Runs the CLI. pub fn run(mut self) -> ! { // Basic runtime sanity checks: all bank indices must be sequential starting from MCU let indices = diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 21dc63ac..2eb58227 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -228,7 +228,7 @@ impl Cli { self.needs_prompt = true; } - /// Returns the serial the CLI is using. + /// Returns the serial driver the CLI is using. pub fn serial(&mut self) -> &mut SRL { &mut self.serial } /// Attempts to parse a given string into a command name and arguments. From d7279e6f5ec14e947d4b6a0f427312be324f442a Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 7 Jan 2021 10:41:45 +0100 Subject: [PATCH 06/67] Fix error messages --- src/devices/bootloader.rs | 2 +- src/devices/cli/mod.rs | 22 ++++++++++----------- src/error.rs | 16 +++++++-------- src/ports/stm32f412_discovery/bootloader.rs | 18 ++++++++--------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 64ce822e..33250eb9 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -242,7 +242,7 @@ where let mut final_buffer = [0x00; 4]; block!(flash.read(start, &mut final_buffer))?; if expected_final_buffer != final_buffer { - Err(Error::DriverError("Flash Read Write cycle failed")) + Err(Error::DriverError("Flash read-write cycle failed")) } else { Ok(()) } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index d2ccd0a1..fe56dc78 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -190,36 +190,36 @@ impl Cli { }; match execute_command() { Err(Error::BadCommandEncoding) => { - uwriteln!(self.serial, "[CLI Error] Bad Command Encoding") + uwriteln!(self.serial, "[CLI Error] Bad command encoding") } Err(Error::CharactersNotAllowed) => { - uwriteln!(self.serial, "[CLI Error] Illegal Characters In Command") + uwriteln!(self.serial, "[CLI Error] Illegal characters In command") } Err(Error::MalformedArguments) => { - uwriteln!(self.serial, "[CLI Error] Malformed Command Arguments") + uwriteln!(self.serial, "[CLI Error] Malformed command arguments") } Err(Error::SerialBufferOverflow) => { - uwriteln!(self.serial, "[CLI Error] Command String Too Long") + uwriteln!(self.serial, "[CLI Error] Command string too long") } Err(Error::MissingArgument) => { - uwriteln!(self.serial, "[CLI Error] Command Missing An Argument") + uwriteln!(self.serial, "[CLI Error] Command missing an argument") } Err(Error::DuplicateArguments) => { - uwriteln!(self.serial, "[CLI Error] Command Contains Duplicate Arguments") + uwriteln!(self.serial, "[CLI Error] Command contains duplicate arguments") } Err(Error::BootloaderError(e)) => { - uprintln!(self.serial, "[CLI Error] Internal Bootloader Error: "); + uprintln!(self.serial, "[CLI Error] Internal bootloader error: "); e.report(&mut self.serial); Ok(()) } Err(Error::UnexpectedArguments) => { - uwriteln!(self.serial, "[CLI Error] Command Contains An Unexpected Argument") + uwriteln!(self.serial, "[CLI Error] Command contains an unexpected argument") } Err(Error::ArgumentOutOfRange) => { - uwriteln!(self.serial, "[CLI Error] Argument Is Out Of Valid Range") + uwriteln!(self.serial, "[CLI Error] Argument is out of valid range") } - Err(Error::SerialReadError) => uwriteln!(self.serial, "[CLI Error] Serial Read Failed"), - Err(Error::CommandUnknown) => uwriteln!(self.serial, "Unknown Command"), + Err(Error::SerialReadError) => uwriteln!(self.serial, "[CLI Error] Serial read failed"), + Err(Error::CommandUnknown) => uwriteln!(self.serial, "Unknown command"), Err(Error::CommandEmpty) => Ok(()), Ok(_) => Ok(()), } diff --git a/src/error.rs b/src/error.rs index 3b8a03ab..059d5195 100644 --- a/src/error.rs +++ b/src/error.rs @@ -63,25 +63,25 @@ impl Error { /// Reports error via abstract serial device pub fn report(&self, serial: &mut S) { match self { - Error::DriverError(text) => uwriteln!(serial, "[DriverError] -> {}", text), + Error::DriverError(text) => uwriteln!(serial, "[Driver Error] -> {}", text), Error::ConfigurationError(text) => { - uwriteln!(serial, "[ConfigurationError] -> {}", text) + uwriteln!(serial, "[Configuration Error] -> {}", text) } - Error::DeviceError(text) => uwriteln!(serial, "[DeviceError] -> {}", text), - Error::ImageTooBig => uwriteln!(serial, "[LogicError] -> Firmware Image too big"), + Error::DeviceError(text) => uwriteln!(serial, "[Device Error] -> {}", text), + Error::ImageTooBig => uwriteln!(serial, "[Logic Error] -> Firmware image too big"), Error::BankInvalid => uwriteln!( serial, - "[LogicError] -> Bank doesn't exist or is invalid in this context" + "[Logic Error] -> Bank doesn't exist or is invalid in this context" ), Error::BankEmpty => { - uwriteln!(serial, "[LogicError] -> Bank is empty (contains no firmware image)") + uwriteln!(serial, "[Logic Error] -> Bank is empty (contains no firmware image)") } Error::FlashCorrupted => { - uwriteln!(serial, "[LogicError] -> Flash memory is corrupted or outdated") + uwriteln!(serial, "[Logic Error] -> Flash memory is corrupted or outdated") } Error::CrcInvalid => uwriteln!(serial, "[LogicError] -> Image CRC is invalid"), Error::NotEnoughData => { - uwriteln!(serial, "[TransferError] -> Not enough image data received") + uwriteln!(serial, "[Transfer Error] -> Not enough image data received") } } .ok() diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 4636792c..4118c736 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -100,11 +100,11 @@ impl Bootloader { match boot_error { Some(Error::BankInvalid) => - uwriteln!(&mut serial, "Attempted to boot from invalid bank.").unwrap(), + uwriteln!(&mut serial, "[INFO] Attempted to boot from invalid bank. Continuing to interactive mode instead.").unwrap(), Some(Error::BankEmpty) => - uwriteln!(&mut serial, "Attempted to boot from empty bank.").unwrap(), + uwriteln!(&mut serial, "[INFO] Attempted to boot from empty bank. Continuing to interactive mode instead.").unwrap(), Some(_) => - uwriteln!(&mut serial, "Unexpected boot error.").unwrap(), + uwriteln!(&mut serial, "Unexpected boot error. Continuing to interactive mode.").unwrap(), None => (), }; @@ -132,7 +132,7 @@ impl From for Error { fn from(error: n25q128a_flash::Error) -> Self { match error { n25q128a_flash::Error::TimeOut => Error::DriverError("[External Flash] Operation timed out"), - n25q128a_flash::Error::QspiError => Error::DriverError("[External Flash] Qspi Error"), + n25q128a_flash::Error::QspiError => Error::DriverError("[External Flash] Qspi error"), n25q128a_flash::Error::WrongManufacturerId => Error::DriverError("[External Flash] Wrong manufacturer ID"), n25q128a_flash::Error::MisalignedAccess => Error::DriverError("[External Flash] Misaligned memory access"), n25q128a_flash::Error::AddressOutOfRange => Error::DriverError("[External Flash] Address out of range"), @@ -143,11 +143,11 @@ impl From for Error { impl From for Error { fn from(error: serial::Error) -> Self { match error { - serial::Error::Framing => Error::DriverError("[Serial] Framing Error"), - serial::Error::Noise => Error::DriverError("[Serial] Noise Error"), - serial::Error::Overrun => Error::DriverError("[Serial] Overrun Error"), - serial::Error::Parity => Error::DriverError("[Serial] Parity Error"), - serial::Error::Timeout => Error::DriverError("[Serial] Timeout Error"), + serial::Error::Framing => Error::DriverError("[Serial] Framing error"), + serial::Error::Noise => Error::DriverError("[Serial] Noise error"), + serial::Error::Overrun => Error::DriverError("[Serial] Overrun error"), + serial::Error::Parity => Error::DriverError("[Serial] Parity error"), + serial::Error::Timeout => Error::DriverError("[Serial] Timeout error"), } } } From 77f738ead2b6d9a0a851b466ebaba2dacf299b91 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Wed, 13 Jan 2021 13:24:05 +0100 Subject: [PATCH 07/67] Finalise responsibility split between loadstone and blue_hal --- .gitignore | 1 + Cargo.toml | 15 +++++++++++---- src/lib.rs | 9 +-------- src/ports/stm32f412_discovery/bootloader.rs | 5 +++-- .../stm32f412_discovery/pin_configuration.rs | 8 +++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index ab0b20c0..c63f8a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .gdb_history Cargo.lock target/ +blue_hal/ *.bin # editor files diff --git a/Cargo.toml b/Cargo.toml index 25d17eac..8d02cf5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,22 @@ default-run = "loadstone" [features] default = [ "stm32f412_discovery", - "cortex_m_any", "defmt-default", ] +stm32f412_discovery = ["blue_hal/stm32f412_discovery", "stm32f412"] +stm32f429 = ["stm32f4_any"] +stm32f469 = ["stm32f4_any"] +stm32f407 = ["stm32f4_any"] +stm32f412 = ["stm32f4_any"] +stm32f4_any = ["stm32_any"] +stm32_any = ["cortex_m_any"] +cortex_m_any = [] defmt-default = [] defmt-trace = [] defmt-debug = [] defmt-info = [] defmt-warn = [] defmt-error = [] -cortex_m_any = [] -stm32f412_discovery = [] [dependencies] cortex-m = "0.6.0" @@ -35,9 +40,11 @@ defmt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } defmt-rtt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } [dependencies.blue_hal] +# Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel +#path = "blue_hal" git = "ssh://git@github.com/absw/blue_hal.git" branch = "main" -features = ["stm32f412_discovery"] +#features = ["stm32f412_discovery"] [dependencies.ufmt] version = "*" diff --git a/src/lib.rs b/src/lib.rs index cd351689..8acdaab7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,14 +9,7 @@ #![cfg_attr(test, allow(unused_imports))] #![cfg_attr(target_arch = "arm", no_std)] -#[cfg(feature = "stm32f407")] -pub use stm32f4::stm32f407 as stm32pac; -#[cfg(feature = "stm32f412")] -pub use stm32f4::stm32f412 as stm32pac; -#[cfg(feature = "stm32f429")] -pub use stm32f4::stm32f429 as stm32pac; -#[cfg(feature = "stm32f469")] -pub use stm32f4::stm32f469 as stm32pac; +pub use blue_hal::stm32pac as stm32pac; #[cfg(target_arch = "arm")] use panic_abort as _; diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index fadc8c03..83c6134d 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -5,8 +5,8 @@ use crate::devices::cli::Cli; use crate::error::Error; use core::mem::size_of; use ufmt::uwriteln; -use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, gpio::*, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; -use blue_hal::drivers::stm32f4::gpio::GpioExt; +use blue_hal::{hal::gpio::InputPin, drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use super::pin_configuration::*; // Flash pins and typedefs type QspiPins = (Pb2, Pg6, Pf8, Pf9, Pf7, Pf6); @@ -140,6 +140,7 @@ impl From for Error { serial::Error::Overrun => Error::DriverError("[Serial] Overrun Error"), serial::Error::Parity => Error::DriverError("[Serial] Parity Error"), serial::Error::Timeout => Error::DriverError("[Serial] Timeout Error"), + _ => Error::DriverError("[Serial] Unexpected Serial Error"), } } } diff --git a/src/ports/stm32f412_discovery/pin_configuration.rs b/src/ports/stm32f412_discovery/pin_configuration.rs index a98549bc..bdf8b3e0 100644 --- a/src/ports/stm32f412_discovery/pin_configuration.rs +++ b/src/ports/stm32f412_discovery/pin_configuration.rs @@ -1,7 +1,6 @@ -use blue_hal::{alternate_functions, gpio, gpio_inner, pin_rows}; +use blue_hal::{enable_gpio, gpio, gpio_inner, alternate_functions, enable_qspi, enable_spi, enable_serial, pin_rows}; use blue_hal::paste; -use blue_hal::drivers::stm32f4::gpio::{self, *}; -use blue_hal::hal::gpio::{*, InputPin}; +use blue_hal::drivers::stm32f4::gpio::*; use blue_hal::drivers::stm32f4::serial::{TxPin, RxPin}; use blue_hal::stm32pac::USART6; use blue_hal::drivers::stm32f4::qspi::{ @@ -12,8 +11,7 @@ use blue_hal::drivers::stm32f4::qspi::{ Bk1Io2Pin as QspiSecondaryOutput, Bk1Io3Pin as QspiSecondaryInput, }; -alternate_functions!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); -pin_rows!(a, b, c, d, e, f, g, h, i, j, k,); +enable_gpio!(); gpio!(a, [ (0, Input), // Boot mode From 14ed7e9a1c5edcc37d1db0f3e1fc9037edc761a6 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Wed, 13 Jan 2021 13:39:34 +0100 Subject: [PATCH 08/67] Fix error include path --- src/devices/image.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/devices/image.rs b/src/devices/image.rs index 9c5b12b1..5644dcdf 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -209,10 +209,16 @@ impl Bank { mod tests { use super::*; use blue_hal::hal::{ - doubles::flash::{Address, FakeFlash}, + doubles::{error::FakeError, flash::{Address, FakeFlash }}, flash::ReadWrite, }; + impl From for Error { + fn from(_: FakeError) -> Self { + Error::DeviceError("Something fake happened") + } + } + #[test] fn writing_header_with_correct_crc() { // Given From bdc09648b2b8dca5e6858cc29a670f56bfa9ec1a Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Wed, 13 Jan 2021 15:53:00 +0100 Subject: [PATCH 09/67] Remove redundant SPI file --- src/hal/spi.rs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/hal/spi.rs diff --git a/src/hal/spi.rs b/src/hal/spi.rs deleted file mode 100644 index aebd3dfe..00000000 --- a/src/hal/spi.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Traits for Serial Peripheral Interface implementation. - -// Allows the transmission and reception of a word in full duplex. -pub trait FullDuplex { - type Error; - - fn transmit(&mut self, word: Option) -> nb::Result<(), Self::Error>; - // Must be called after transmit (full duplex operation) - fn receive(&mut self) -> nb::Result; -} From 4b31b02567a85ca955344a513b37affe70f2f566 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 14 Jan 2021 15:05:12 +0100 Subject: [PATCH 10/67] Split CLI out of loadstone binary --- src/bin/demoapp.rs | 13 + src/{main.rs => bin/loadstone.rs} | 0 src/devices/bootloader.rs | 25 +- src/devices/cli/commands/mod.rs | 250 ++++++++++---------- src/devices/cli/file_transfer.rs | 1 - src/devices/cli/mod.rs | 2 +- src/devices/mod.rs | 2 +- src/ports/stm32f412_discovery/bootloader.rs | 54 ++--- 8 files changed, 174 insertions(+), 173 deletions(-) create mode 100644 src/bin/demoapp.rs rename src/{main.rs => bin/loadstone.rs} (100%) diff --git a/src/bin/demoapp.rs b/src/bin/demoapp.rs new file mode 100644 index 00000000..cf854731 --- /dev/null +++ b/src/bin/demoapp.rs @@ -0,0 +1,13 @@ +#![cfg_attr(test, allow(unused_attributes))] +#![cfg_attr(all(not(test), target_arch = "arm"), no_std)] +#![cfg_attr(target_arch = "arm", no_main)] + +#[allow(unused_imports)] +use cortex_m_rt::{entry, exception}; + +#[cfg(target_arch = "arm")] +#[entry] +fn main() -> ! { loop{} } + +#[cfg(not(target_arch = "arm"))] +fn main() {} diff --git a/src/main.rs b/src/bin/loadstone.rs similarity index 100% rename from src/main.rs rename to src/bin/loadstone.rs diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 1443e672..27637253 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -4,49 +4,40 @@ //! the exception of how to construct one. Construction is //! handled by the `port` module as it depends on board //! specific information. -use super::{ - cli::{self, file_transfer::FileBlock}, - image, -}; -use crate::{devices::cli::Cli, error::Error}; -use blue_hal::{hal::{serial, flash}, utilities::buffer::CollectSlice}; -use cli::file_transfer; +use super::image; +use crate::error::Error; +use blue_hal::{hal::{serial, flash}, utilities::{buffer::CollectSlice, xmodem}}; use core::{cmp::min, mem::size_of}; use core::array::IntoIter; use cortex_m::peripheral::SCB; use image::TRANSFER_BUFFER_SIZE; use nb::block; -pub struct Bootloader +pub struct Bootloader where EXTF: flash::ReadWrite, Error: From, MCUF: flash::ReadWrite, Error: From, - SRL: serial::ReadWrite + file_transfer::FileTransfer, - Error: From<::Error>, { pub(crate) external_flash: EXTF, pub(crate) mcu_flash: MCUF, - pub(crate) cli: Option>, pub(crate) external_banks: &'static [image::Bank<::Address>], pub(crate) mcu_banks: &'static [image::Bank<::Address>], } -impl Bootloader +impl Bootloader where EXTF: flash::ReadWrite, Error: From, MCUF: flash::ReadWrite, Error: From, - SRL: serial::ReadWrite + file_transfer::FileTransfer, - Error: From<::Error>, { /// Runs the CLI. pub fn run(mut self) -> ! { - let mut cli = self.cli.take().unwrap(); + //let mut cli = self.cli.take().unwrap(); loop { - cli.run(&mut self) + // cli.run(&mut self) } } @@ -58,7 +49,7 @@ where bank: image::Bank, ) -> Result<(), Error> where - I: Iterator, + I: Iterator { if size > bank.size { return Err(Error::ImageTooBig); diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index f0dffb97..cd2b34be 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -8,128 +8,128 @@ use crate::{ use blue_hal::{hal::{flash, serial}, uprintln}; use ufmt::uwriteln; -commands!( cli, bootloader, names, helpstrings [ - - help ["Displays a list of commands."] (command: Option<&str> ["Optional command to inspect."],) { - cli.print_help(names, helpstrings, command) - }, - - test ["Tests various elements of the bootloader."] ( - mcu: bool ["Set to test MCU flash"], - external: bool ["Set to test external flash"], - ) { - match (mcu, external) { - (true, true) => { - uprintln!(cli.serial, "Starting Test..."); - bootloader.test_mcu_flash()?; - bootloader.test_external_flash()?; - uprintln!(cli.serial, "Both Flash tests successful"); - } - (true, false) => { - uprintln!(cli.serial, "Starting Test..."); - bootloader.test_mcu_flash()?; - uprintln!(cli.serial, "MCU flash test successful"); - } - (false, true) => { - uprintln!(cli.serial, "Starting Test..."); - bootloader.test_external_flash()?; - uprintln!(cli.serial, "External flash test successful"); - } - (false, false) => { - return Err(Error::MissingArgument); - } - } - }, - - flash ["Stores a FW image in an external Bank."] ( - size: usize ["Image size in bytes"], - bank: u8 ["External Bank Index"], - ) - { - if let Some(bank) = bootloader.external_banks().find(|b| b.index == bank) { - if size > bank.size { - return Err(Error::ArgumentOutOfRange); - } - uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); - bootloader.store_image(cli.serial.blocks(), size, bank)?; - uprintln!(cli.serial, "Image transfer complete!"); - } else { - uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); - } - - }, - - format ["Erases a flash chip and initializes all headers to default values."] ( - mcu: bool ["Set to format MCU flash"], - external: bool ["Set to format external flash"], - ){ - match (mcu, external) { - (true, true) => { - uprintln!(cli.serial, "Formatting..."); - bootloader.format_mcu_flash()?; - uprintln!(cli.serial, "MCU Flash formatted successfully."); - bootloader.format_external_flash()?; - uprintln!(cli.serial, "Both Flash chips formatted successfully."); - } - (true, false) => { - uprintln!(cli.serial, "Formatting..."); - bootloader.format_mcu_flash()?; - uprintln!(cli.serial, "MCU Flash formatted successfully."); - } - (false, true) => { - uprintln!(cli.serial, "Formatting..."); - bootloader.format_external_flash()?; - uprintln!(cli.serial, "External Flash formatted successfully."); - } - (false, false) => { - return Err(Error::MissingArgument); - } - } - }, - - banks ["Retrieves information from FW image banks."] (){ - uprintln!(cli.serial, "MCU Banks:"); - for bank in bootloader.mcu_banks() { - uwriteln!(cli.serial, " - [{}] {} - Size: {}b", - bank.index, - if bank.bootable { "Bootable" } else { "Non-Bootable" }, - bank.size).ok().unwrap(); - if let Some(image) = bootloader.image_at_bank(bank.index) { - uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", - if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, - image.size, - image.crc).ok().unwrap(); - } - - } - uprintln!(cli.serial, "External Banks:"); - for bank in bootloader.external_banks() { - uwriteln!(cli.serial, " - [{}] {} - Size: {}b", - bank.index, - if bank.bootable { "Bootable" } else { "Non-Bootable" }, - bank.size).ok().unwrap(); - if let Some(image) = bootloader.image_at_bank(bank.index) { - uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", - if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, - image.size, - image.crc).ok().unwrap(); - } - } - }, - - copy ["Copy an image from an external bank to an MCU bank."] ( - input: u8 ["External Bank index to copy from."], - output: u8 ["MCU Bank index to copy to."], - ) - { - bootloader.copy_image(input, output)?; - uprintln!(cli.serial, "Copy success!"); - }, - - boot ["Restart, attempting to boot into a valid image if available."] ( ) - { - uprintln!(cli.serial, "Restarting..."); - bootloader.reset(); - }, - -]); +//commands!( cli, bootloader, names, helpstrings [ +// +// help ["Displays a list of commands."] (command: Option<&str> ["Optional command to inspect."],) { +// cli.print_help(names, helpstrings, command) +// }, +// +// test ["Tests various elements of the bootloader."] ( +// mcu: bool ["Set to test MCU flash"], +// external: bool ["Set to test external flash"], +// ) { +// match (mcu, external) { +// (true, true) => { +// uprintln!(cli.serial, "Starting Test..."); +// bootloader.test_mcu_flash()?; +// bootloader.test_external_flash()?; +// uprintln!(cli.serial, "Both Flash tests successful"); +// } +// (true, false) => { +// uprintln!(cli.serial, "Starting Test..."); +// bootloader.test_mcu_flash()?; +// uprintln!(cli.serial, "MCU flash test successful"); +// } +// (false, true) => { +// uprintln!(cli.serial, "Starting Test..."); +// bootloader.test_external_flash()?; +// uprintln!(cli.serial, "External flash test successful"); +// } +// (false, false) => { +// return Err(Error::MissingArgument); +// } +// } +// }, +// +// flash ["Stores a FW image in an external Bank."] ( +// size: usize ["Image size in bytes"], +// bank: u8 ["External Bank Index"], +// ) +// { +// if let Some(bank) = bootloader.external_banks().find(|b| b.index == bank) { +// if size > bank.size { +// return Err(Error::ArgumentOutOfRange); +// } +// uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); +// bootloader.store_image(cli.serial.blocks(), size, bank)?; +// uprintln!(cli.serial, "Image transfer complete!"); +// } else { +// uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); +// } +// +// }, +// +// format ["Erases a flash chip and initializes all headers to default values."] ( +// mcu: bool ["Set to format MCU flash"], +// external: bool ["Set to format external flash"], +// ){ +// match (mcu, external) { +// (true, true) => { +// uprintln!(cli.serial, "Formatting..."); +// bootloader.format_mcu_flash()?; +// uprintln!(cli.serial, "MCU Flash formatted successfully."); +// bootloader.format_external_flash()?; +// uprintln!(cli.serial, "Both Flash chips formatted successfully."); +// } +// (true, false) => { +// uprintln!(cli.serial, "Formatting..."); +// bootloader.format_mcu_flash()?; +// uprintln!(cli.serial, "MCU Flash formatted successfully."); +// } +// (false, true) => { +// uprintln!(cli.serial, "Formatting..."); +// bootloader.format_external_flash()?; +// uprintln!(cli.serial, "External Flash formatted successfully."); +// } +// (false, false) => { +// return Err(Error::MissingArgument); +// } +// } +// }, +// +// banks ["Retrieves information from FW image banks."] (){ +// uprintln!(cli.serial, "MCU Banks:"); +// for bank in bootloader.mcu_banks() { +// uwriteln!(cli.serial, " - [{}] {} - Size: {}b", +// bank.index, +// if bank.bootable { "Bootable" } else { "Non-Bootable" }, +// bank.size).ok().unwrap(); +// if let Some(image) = bootloader.image_at_bank(bank.index) { +// uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", +// if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, +// image.size, +// image.crc).ok().unwrap(); +// } +// +// } +// uprintln!(cli.serial, "External Banks:"); +// for bank in bootloader.external_banks() { +// uwriteln!(cli.serial, " - [{}] {} - Size: {}b", +// bank.index, +// if bank.bootable { "Bootable" } else { "Non-Bootable" }, +// bank.size).ok().unwrap(); +// if let Some(image) = bootloader.image_at_bank(bank.index) { +// uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", +// if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, +// image.size, +// image.crc).ok().unwrap(); +// } +// } +// }, +// +// copy ["Copy an image from an external bank to an MCU bank."] ( +// input: u8 ["External Bank index to copy from."], +// output: u8 ["MCU Bank index to copy to."], +// ) +// { +// bootloader.copy_image(input, output)?; +// uprintln!(cli.serial, "Copy success!"); +// }, +// +// boot ["Restart, attempting to boot into a valid image if available."] ( ) +// { +// uprintln!(cli.serial, "Restarting..."); +// bootloader.reset(); +// }, +// +//]); diff --git a/src/devices/cli/file_transfer.rs b/src/devices/cli/file_transfer.rs index 2f9fa19a..411ee8b5 100644 --- a/src/devices/cli/file_transfer.rs +++ b/src/devices/cli/file_transfer.rs @@ -1,7 +1,6 @@ use blue_hal::{hal::serial::{Write, TimeoutRead}, utilities::xmodem}; pub const BLOCK_SIZE: usize = xmodem::PAYLOAD_SIZE; -pub type FileBlock = [u8; BLOCK_SIZE]; const MAX_RETRIES: u32 = 10; diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 91858faf..63746c8a 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -166,7 +166,7 @@ const LINE_TERMINATOR: char = '\n'; impl Cli { /// Reads a line, parses it as a command and attempts to execute it. - pub fn run(&mut self, bootloader: &mut Bootloader) + pub fn run(&mut self, bootloader: &mut Bootloader) where EXTF: flash::ReadWrite, BootloaderError: From, diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 3d1e57a1..dcb21f7f 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -5,4 +5,4 @@ pub mod bootloader; pub mod image; -pub mod cli; +//pub mod cli; diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 9ba26289..11f1cee1 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -1,11 +1,9 @@ //! GPIO configuration and alternate functions for the [stm32f412 discovery](../../../../../../../../documentation/hardware/discovery.pdf). use crate::devices::bootloader::Bootloader; use crate::devices::image; -use crate::devices::cli::Cli; use crate::error::Error; use core::mem::size_of; -use ufmt::uwriteln; -use blue_hal::{hal::gpio::InputPin, drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; use super::pin_configuration::*; // Flash pins and typedefs @@ -58,11 +56,11 @@ static EXTERNAL_BANKS: [image::Bank; EXTERNAL_NUMBER_OF image::Bank { index: 3, bootable: false, location: external_image_offset(1), size: IMAGE_SIZE, }, ]; -impl Default for Bootloader { +impl Default for Bootloader { fn default() -> Self { Self::new() } } -impl Bootloader { +impl Bootloader { pub fn new() -> Self { let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); @@ -77,35 +75,35 @@ impl Bootloader { SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup - let interactive_mode = gpioa.pa0.is_high() || gpioa.pa1.is_high(); - let mut boot_error : Option = None; - if !interactive_mode { - const DEFAULT_BANK : u8 = 1; - boot_error = Some(Self::boot(&mut mcu_flash, &MCU_BANKS, DEFAULT_BANK).unwrap_err()); - } - - let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); - let serial_pins = (gpiog.pg14, gpiog.pg9); - let mut serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); - - match boot_error { - Some(Error::BankInvalid) => - uwriteln!(&mut serial, "[INFO] Attempted to boot from invalid bank. Continuing to interactive mode instead.").unwrap(), - Some(Error::BankEmpty) => - uwriteln!(&mut serial, "[INFO] Attempted to boot from empty bank. Continuing to interactive mode instead.").unwrap(), - Some(_) => - uwriteln!(&mut serial, "Unexpected boot error. Continuing to interactive mode.").unwrap(), - None => (), - }; - - let cli = Cli::new(serial).unwrap(); + //let interactive_mode = gpioa.pa0.is_high() || gpioa.pa1.is_high(); + //let mut boot_error : Option = None; + //if !interactive_mode { + // const DEFAULT_BANK : u8 = 1; + // boot_error = Some(Self::boot(&mut mcu_flash, &MCU_BANKS, DEFAULT_BANK).unwrap_err()); + //} + + //let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); + //let serial_pins = (gpiog.pg14, gpiog.pg9); + //let mut serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); + + //match boot_error { + // Some(Error::BankInvalid) => + // uwriteln!(&mut serial, "[INFO] Attempted to boot from invalid bank. Continuing to interactive mode instead.").unwrap(), + // Some(Error::BankEmpty) => + // uwriteln!(&mut serial, "[INFO] Attempted to boot from empty bank. Continuing to interactive mode instead.").unwrap(), + // Some(_) => + // uwriteln!(&mut serial, "Unexpected boot error. Continuing to interactive mode.").unwrap(), + // None => (), + //}; + + //let cli = Cli::new(serial).unwrap(); let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); - Bootloader { external_flash, mcu_flash, cli: Some(cli), external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS } + Bootloader { external_flash, mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS } } } From 8a3c1656247387786bd5b6bdea78fbaf3e74dab8 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 11:07:13 +0000 Subject: [PATCH 11/67] Add signing_tool tool. --- tools/signing_tool/Cargo.toml | 9 +++++++++ tools/signing_tool/src/main.rs | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 tools/signing_tool/Cargo.toml create mode 100644 tools/signing_tool/src/main.rs diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml new file mode 100644 index 00000000..0b0c8561 --- /dev/null +++ b/tools/signing_tool/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "signing_tool" +version = "0.1.0" +authors = ["Arron Speake "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/tools/signing_tool/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 1e11c6dc427b3064809c5656eb7674522a37a02d Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 11:25:27 +0000 Subject: [PATCH 12/67] Add basic clap interface. --- tools/signing_tool/Cargo.toml | 2 ++ tools/signing_tool/src/main.rs | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml index 0b0c8561..5fd91609 100644 --- a/tools/signing_tool/Cargo.toml +++ b/tools/signing_tool/Cargo.toml @@ -3,7 +3,9 @@ name = "signing_tool" version = "0.1.0" authors = ["Arron Speake "] edition = "2018" +description = "Tool to calculate and append signatures to images." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "2.*" \ No newline at end of file diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index e7a11a96..05203369 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,3 +1,19 @@ +extern crate clap; + fn main() { - println!("Hello, world!"); + let matches = clap::App::new("Loadstone Image Signing Tool") + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .arg(clap::Arg::with_name("image") + .index(1) + .required(true) + .help("The firmware image to be signed.")) + .arg(clap::Arg::with_name("private_key") + .index(2) + .required(true) + .help("The private key used to sign the image.")) + .get_matches(); + + println!("{:?}", matches); } From ad8de27d45447d48a254a77e859d460bd83bf600 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 11:46:12 +0000 Subject: [PATCH 13/67] Add file opening. --- tools/signing_tool/src/main.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index 05203369..0554950b 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,5 +1,24 @@ extern crate clap; +use std::{ + fs::{File, OpenOptions}, + process, +}; + +fn open_file(path: &str, append: bool) -> Option { + let file = OpenOptions::new() + .read(true) + .append(append) + .open(path); + match file { + Ok(f) => Some(f), + Err(e) => { + eprintln!("Failed to open '{}': {}.", path, e); + None + }, + } +} + fn main() { let matches = clap::App::new("Loadstone Image Signing Tool") .version(env!("CARGO_PKG_VERSION")) @@ -15,5 +34,14 @@ fn main() { .help("The private key used to sign the image.")) .get_matches(); - println!("{:?}", matches); + let image_path = matches.value_of("image").unwrap(); + let key_path = matches.value_of("private_key").unwrap(); + let image = open_file(image_path, true); + let key = open_file(key_path, false); + + if image.is_none() || key.is_none() { + process::exit(1); + } + + println!("{:?}, {:?}", image, key); } From badb3676af7ab6c4621e0a91a248ec69cf53d4d6 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 12:09:54 +0000 Subject: [PATCH 14/67] Add hashing functionality. --- tools/signing_tool/Cargo.toml | 3 ++- tools/signing_tool/src/hashing.rs | 17 +++++++++++++++++ tools/signing_tool/src/main.rs | 7 +++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tools/signing_tool/src/hashing.rs diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml index 5fd91609..dfed7a7d 100644 --- a/tools/signing_tool/Cargo.toml +++ b/tools/signing_tool/Cargo.toml @@ -8,4 +8,5 @@ description = "Tool to calculate and append signatures to images." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.*" \ No newline at end of file +clap = "2.*" +sha2 = "0.9.*" \ No newline at end of file diff --git a/tools/signing_tool/src/hashing.rs b/tools/signing_tool/src/hashing.rs new file mode 100644 index 00000000..663f2f21 --- /dev/null +++ b/tools/signing_tool/src/hashing.rs @@ -0,0 +1,17 @@ +extern crate sha2; +use sha2::{Digest, Sha256}; + +use std::{ + fs::File, + io::{Read, Seek, SeekFrom}, +}; + +pub fn get_file_hash(file: &mut File) -> Vec { + // TODO: Consider reading file part-wise; + let _ = file.seek(SeekFrom::Start(0)); + let mut buffer = vec!(); + let _ = file.read_to_end(&mut buffer); + let mut hasher = Sha256::new(); + hasher.update(buffer); + hasher.finalize().to_vec() +} \ No newline at end of file diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index 0554950b..a2499085 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,3 +1,6 @@ +mod hashing; +use crate::hashing::*; + extern crate clap; use std::{ @@ -42,6 +45,10 @@ fn main() { if image.is_none() || key.is_none() { process::exit(1); } + let mut image = image.unwrap(); println!("{:?}, {:?}", image, key); + + let hash = get_file_hash(&mut image); + println!("{:?}", hash); } From ec92ad2679cd3613350da7b311da0c07a72c35a1 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 15 Jan 2021 13:25:35 +0100 Subject: [PATCH 15/67] Remove boot functionality from bootloader construction --- src/bin/demoapp.rs | 2 +- src/devices/bootloader.rs | 57 +++++++++++++++------ src/devices/image.rs | 14 +++-- src/error.rs | 3 +- src/lib.rs | 2 +- src/ports/stm32f412_discovery/bootloader.rs | 26 +--------- 6 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/bin/demoapp.rs b/src/bin/demoapp.rs index cf854731..d3740c14 100644 --- a/src/bin/demoapp.rs +++ b/src/bin/demoapp.rs @@ -7,7 +7,7 @@ use cortex_m_rt::{entry, exception}; #[cfg(target_arch = "arm")] #[entry] -fn main() -> ! { loop{} } +fn main() -> ! { loop {} } #[cfg(not(target_arch = "arm"))] fn main() {} diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 27637253..e6b6bbf9 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -6,10 +6,13 @@ //! specific information. use super::image; use crate::error::Error; -use blue_hal::{hal::{serial, flash}, utilities::{buffer::CollectSlice, xmodem}}; -use core::{cmp::min, mem::size_of}; -use core::array::IntoIter; +use blue_hal::{ + hal::flash, + utilities::{buffer::CollectSlice, xmodem}, +}; +use core::{array::IntoIter, cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; +use defmt::{error, info}; use image::TRANSFER_BUFFER_SIZE; use nb::block; @@ -33,11 +36,28 @@ where MCUF: flash::ReadWrite, Error: From, { - /// Runs the CLI. + /// Main bootloader routine. Attempts to boot from MCU image, and + /// in case of failure proceeds to: + /// + /// * Verify selected (main) external bank. If valid, copy to bootable MCU flash bank. + /// * If main external bank not available or invalid, verify golden image. If valid, + /// copy to bootable MCU flash bank. + /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - //let mut cli = self.cli.take().unwrap(); - loop { - // cli.run(&mut self) + let default_bank = 1; + match self.boot(default_bank).unwrap_err() { + Error::BankInvalid => info!("Attempted to boot from invalid bank. Restoring image..."), + Error::BankEmpty => info!("Attempted to boot from empty bank. Restoring image..."), + _ => info!("Unexpected boot error. Restoring image..."), + }; + + match self.restore() { + Ok(()) => self.boot(default_bank).expect("FATAL: Failed to boot from verified image!"), + Err(e) => { + error!("Failed to restore with error: {}", e); + info!("Proceeding to recovery mode..."); + unimplemented!("Recovery Mode"); + } } } @@ -49,7 +69,7 @@ where bank: image::Bank, ) -> Result<(), Error> where - I: Iterator + I: Iterator, { if size > bank.size { return Err(Error::ImageTooBig); @@ -68,7 +88,9 @@ where loop { let distance_to_end = size - bytes_written; let received = bytes.collect_slice(&mut buffer); - if received == 0 { break; } + if received == 0 { + break; + } let bytes_to_write = min(distance_to_end, received); block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; bytes_written += bytes_to_write; @@ -83,19 +105,22 @@ where pub fn reset(&mut self) -> ! { SCB::sys_reset(); } + /// Restores an image from the preferred external bank. If it fails, + /// attempts to restore from the golden image. + fn restore(&mut self) -> Result<(), Error> { + unimplemented!("Image Restoration"); + } + /// Boots into a given memory bank. - pub fn boot( - mcu_flash: &mut MCUF, - mcu_banks: &'static [image::Bank<::Address>], - bank_index: u8, - ) -> Result { - let bank = mcu_banks.iter().find(|b| b.index == bank_index).ok_or(Error::BankInvalid)?; + pub fn boot(&mut self, bank_index: u8) -> Result { + let bank = + self.mcu_banks.iter().find(|b| b.index == bank_index).ok_or(Error::BankInvalid)?; if !bank.bootable { return Err(Error::BankInvalid); } - let header = image::ImageHeader::retrieve(mcu_flash, &bank)?; + let header = image::ImageHeader::retrieve(&mut self.mcu_flash, &bank)?; if header.size == 0 { return Err(Error::BankEmpty); } diff --git a/src/devices/image.rs b/src/devices/image.rs index 5644dcdf..baca018e 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,6 +1,9 @@ use crate::error::Error; +use blue_hal::{ + hal::flash::{self, UnportableDeserialize, UnportableSerialize}, + utilities::memory::Address, +}; use core::{cmp::min, mem::size_of}; -use blue_hal::{hal::flash::{self, UnportableSerialize, UnportableDeserialize}, utilities::memory::Address}; use crc::{crc32, Hasher32}; use nb::{self, block}; @@ -209,14 +212,15 @@ impl Bank { mod tests { use super::*; use blue_hal::hal::{ - doubles::{error::FakeError, flash::{Address, FakeFlash }}, + doubles::{ + error::FakeError, + flash::{Address, FakeFlash}, + }, flash::ReadWrite, }; impl From for Error { - fn from(_: FakeError) -> Self { - Error::DeviceError("Something fake happened") - } + fn from(_: FakeError) -> Self { Error::DeviceError("Something fake happened") } } #[test] diff --git a/src/error.rs b/src/error.rs index 599185d7..0ff4af2c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,13 @@ //! Error types and methods for the Secure Bootloader project. use blue_hal::{hal::serial::Write, uprint}; +use defmt::Format; use ufmt::{uwrite, uwriteln}; /// Top level error type for the bootloader. Unlike the specific /// module errors, this error contains textual descriptions of the /// problem as it is meant to be directly reported through USART. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Format)] pub enum Error { /// Error caused by a low level peripheral driver DriverError(&'static str), diff --git a/src/lib.rs b/src/lib.rs index 8acdaab7..ee7063b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ #![cfg_attr(test, allow(unused_imports))] #![cfg_attr(target_arch = "arm", no_std)] -pub use blue_hal::stm32pac as stm32pac; +pub use blue_hal::stm32pac; #[cfg(target_arch = "arm")] use panic_abort as _; diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 11f1cee1..2b0457aa 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -3,7 +3,7 @@ use crate::devices::bootloader::Bootloader; use crate::devices::image; use crate::error::Error; use core::mem::size_of; -use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial, systick::SysTick}}, hal::time, stm32pac}; use super::pin_configuration::*; // Flash pins and typedefs @@ -12,9 +12,6 @@ type Qspi = QuadSpi; type ExternalFlash = MicronN25q128a; // Serial pins and typedefs -type UsartPins = (Pg14, Pg9); -type Serial = serial::Serial; - const EXTERNAL_NUMBER_OF_BANKS: usize = 2; const EXTERNAL_BANK_MAX_IMAGE_SIZE: usize = { let (start, end) = (n25q128a_flash::MemoryMap::location(), n25q128a_flash::MemoryMap::end()); @@ -64,38 +61,19 @@ impl Bootloader { pub fn new() -> Self { let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); - let gpioa = peripherals.GPIOA.split(&mut peripherals.RCC); let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); - let mut mcu_flash = flash::McuFlash::new(peripherals.FLASH).unwrap(); + let mcu_flash = flash::McuFlash::new(peripherals.FLASH).unwrap(); let clocks = Clocks::hardcoded(peripherals.RCC); SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup - //let interactive_mode = gpioa.pa0.is_high() || gpioa.pa1.is_high(); - //let mut boot_error : Option = None; - //if !interactive_mode { - // const DEFAULT_BANK : u8 = 1; - // boot_error = Some(Self::boot(&mut mcu_flash, &MCU_BANKS, DEFAULT_BANK).unwrap_err()); - //} - //let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); //let serial_pins = (gpiog.pg14, gpiog.pg9); //let mut serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); - - //match boot_error { - // Some(Error::BankInvalid) => - // uwriteln!(&mut serial, "[INFO] Attempted to boot from invalid bank. Continuing to interactive mode instead.").unwrap(), - // Some(Error::BankEmpty) => - // uwriteln!(&mut serial, "[INFO] Attempted to boot from empty bank. Continuing to interactive mode instead.").unwrap(), - // Some(_) => - // uwriteln!(&mut serial, "Unexpected boot error. Continuing to interactive mode.").unwrap(), - // None => (), - //}; - //let cli = Cli::new(serial).unwrap(); let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); From 01ddc6e59e91255a2f5efd5b44c138159e4bea9c Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 14:32:54 +0000 Subject: [PATCH 16/67] Add signature generation functionality. --- tools/signing_tool/Cargo.toml | 6 ++++-- tools/signing_tool/src/main.rs | 35 +++++++++++++++++++++++++++++++ tools/signing_tool/src/signing.rs | 11 ++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tools/signing_tool/src/signing.rs diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml index dfed7a7d..0b860bb8 100644 --- a/tools/signing_tool/Cargo.toml +++ b/tools/signing_tool/Cargo.toml @@ -8,5 +8,7 @@ description = "Tool to calculate and append signatures to images." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.*" -sha2 = "0.9.*" \ No newline at end of file +clap = "2" +sha2 = "0.9" +rsa = "0.3" +base64 = "0.13" \ No newline at end of file diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index a2499085..e58aa801 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,11 +1,16 @@ mod hashing; use crate::hashing::*; +mod signing; +use crate::signing::*; + extern crate clap; +extern crate base64; use std::{ fs::{File, OpenOptions}, process, + io::Read, }; fn open_file(path: &str, append: bool) -> Option { @@ -22,6 +27,20 @@ fn open_file(path: &str, append: bool) -> Option { } } +fn read_key(mut file: File) -> Option> { + let mut string = String::new(); + file.read_to_string(&mut string) + .ok()?; + let encoded = string.lines() + .filter(|l| !l.starts_with("-")) + .fold(Vec::::new(), |mut data, line| { + data.extend_from_slice(line.as_bytes()); + data + }); + base64::decode(encoded) + .ok() +} + fn main() { let matches = clap::App::new("Loadstone Image Signing Tool") .version(env!("CARGO_PKG_VERSION")) @@ -46,9 +65,25 @@ fn main() { process::exit(1); } let mut image = image.unwrap(); + let key = key.unwrap(); println!("{:?}, {:?}", image, key); let hash = get_file_hash(&mut image); println!("{:?}", hash); + + let raw_key = match read_key(key) { + Some(k) => k, + None => { + eprintln!("Failed to decode private key."); + process::exit(1); + }, + }; + + let signature = match sign(&hash[..], &raw_key[..]) { + Ok(s) => s, + Err(_) => { process::exit(1); }, + }; + + println!("{:?}", signature); } diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs new file mode 100644 index 00000000..74c007d9 --- /dev/null +++ b/tools/signing_tool/src/signing.rs @@ -0,0 +1,11 @@ +extern crate rsa; + +use rsa::{RSAPrivateKey, PaddingScheme, Hash}; + +pub fn sign(digest: &[u8], pkcs1_private_key: &[u8]) -> Result, String> { + let private_key = RSAPrivateKey::from_pkcs8(pkcs1_private_key) + .map_err(|e| format!("Failed to parse private key: {}", e))?; + let padding_scheme = PaddingScheme::PKCS1v15Sign { hash: Some(Hash::SHA2_256) }; + private_key.sign(padding_scheme, digest) + .map_err(|e| format!("Failed to sign digest: {}", e)) +} \ No newline at end of file From e3b463db23bd47e7f4eac2dfa886b254f2c778b7 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 14:40:02 +0000 Subject: [PATCH 17/67] Add signature file appending. --- tools/signing_tool/src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index e58aa801..cf1be94c 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -10,7 +10,7 @@ extern crate base64; use std::{ fs::{File, OpenOptions}, process, - io::Read, + io::{Read, Write}, }; fn open_file(path: &str, append: bool) -> Option { @@ -86,4 +86,20 @@ fn main() { }; println!("{:?}", signature); + + match image.write(&signature[..]) { + Ok(s) => { + if s == signature.len() { + println!("Successfully appended RSA256 signature to image."); + } else { + eprintln!("A partial write to the image occured. \ + You may need to re-generate the image."); + process::exit(1); + } + }, + Err(_) => { + eprintln!("Failed to append the signature to the file."); + process::exit(1); + } + } } From 6410aac88f7c768182966d51b5a9a9b9aed58d9e Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 15 Jan 2021 15:48:28 +0100 Subject: [PATCH 18/67] Refactor CLI to be available to boot manager --- src/bin/{demoapp.rs => demo_app.rs} | 6 +- src/devices/boot_manager.rs | 83 ++++++++++++++ src/devices/bootloader.rs | 45 -------- src/devices/cli/commands/mod.rs | 165 +++++++--------------------- src/devices/cli/file_transfer.rs | 11 +- src/devices/cli/mod.rs | 48 ++++---- src/devices/mod.rs | 3 +- 7 files changed, 159 insertions(+), 202 deletions(-) rename src/bin/{demoapp.rs => demo_app.rs} (71%) create mode 100644 src/devices/boot_manager.rs diff --git a/src/bin/demoapp.rs b/src/bin/demo_app.rs similarity index 71% rename from src/bin/demoapp.rs rename to src/bin/demo_app.rs index d3740c14..c351622d 100644 --- a/src/bin/demoapp.rs +++ b/src/bin/demo_app.rs @@ -4,10 +4,14 @@ #[allow(unused_imports)] use cortex_m_rt::{entry, exception}; +use loadstone_lib::devices::boot_manager; #[cfg(target_arch = "arm")] #[entry] -fn main() -> ! { loop {} } +fn main() -> ! { + let app = boot_manager::BootManager {}; + loop {} +} #[cfg(not(target_arch = "arm"))] fn main() {} diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs new file mode 100644 index 00000000..fa114ea5 --- /dev/null +++ b/src/devices/boot_manager.rs @@ -0,0 +1,83 @@ +//! Fully CLI interactive boot manager for the demo application. +//! + +use core::{array::IntoIter, cmp::min}; + +use blue_hal::{hal::{flash, serial}, stm32pac::SCB}; + +use blue_hal::utilities::{buffer::CollectSlice, xmodem}; +use nb::block; + +use crate::error::Error; + +use super::{cli::file_transfer, image::{self, TRANSFER_BUFFER_SIZE}}; + +pub struct BootManager +where + EXTF: flash::ReadWrite, + Error: From, + SRL: serial::ReadWrite + file_transfer::FileTransfer, + Error: From<::Error>, +{ + pub(crate) external_banks: &'static [image::Bank<::Address>], + pub(crate) external_flash: EXTF, + pub(crate) serial: SRL, +} + +impl BootManager +where + EXTF: flash::ReadWrite, + Error: From, + SRL: serial::ReadWrite + file_transfer::FileTransfer, + Error: From<::Error>, +{ + + /// Returns an iterator of all external flash banks. + pub fn external_banks(&self) -> impl Iterator> { + self.external_banks.iter().cloned() + } + + /// Writes a firmware image to an external flash bank. + pub fn store_image( + &mut self, + blocks: I, + size: usize, + bank: image::Bank, + ) -> Result<(), Error> + where + I: Iterator, + { + if size > bank.size { + return Err(Error::ImageTooBig); + } + + // Header must be re-formatted before any writing takes place, to ensure + // a valid header and an invalid image never coexist. + image::ImageHeader::format_default(&mut self.external_flash, &bank)?; + + let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; + let address = bank.location; + let mut bytes_written = 0; + + let mut bytes = blocks.flat_map(|b| IntoIter::new(b)); + + loop { + let distance_to_end = size - bytes_written; + let received = bytes.collect_slice(&mut buffer); + if received == 0 { + break; + } + let bytes_to_write = min(distance_to_end, received); + block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; + bytes_written += bytes_to_write; + } + + if bytes_written == size { + image::ImageHeader::write(&mut self.external_flash, &bank, size) + } else { + Err(Error::NotEnoughData) + } + } + + pub fn reset(&mut self) -> ! { SCB::sys_reset(); } +} diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index e6b6bbf9..43237441 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -8,7 +8,6 @@ use super::image; use crate::error::Error; use blue_hal::{ hal::flash, - utilities::{buffer::CollectSlice, xmodem}, }; use core::{array::IntoIter, cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; @@ -61,50 +60,6 @@ where } } - /// Writes a firmware image to an external flash bank. - pub fn store_image( - &mut self, - blocks: I, - size: usize, - bank: image::Bank, - ) -> Result<(), Error> - where - I: Iterator, - { - if size > bank.size { - return Err(Error::ImageTooBig); - } - - // Header must be re-formatted before any writing takes place, to ensure - // a valid header and an invalid image never coexist. - image::ImageHeader::format_default(&mut self.external_flash, &bank)?; - - let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - let address = bank.location; - let mut bytes_written = 0; - - let mut bytes = blocks.flat_map(|b| IntoIter::new(b)); - - loop { - let distance_to_end = size - bytes_written; - let received = bytes.collect_slice(&mut buffer); - if received == 0 { - break; - } - let bytes_to_write = min(distance_to_end, received); - block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; - bytes_written += bytes_to_write; - } - - if bytes_written == size { - image::ImageHeader::write(&mut self.external_flash, &bank, size) - } else { - Err(Error::NotEnoughData) - } - } - - pub fn reset(&mut self) -> ! { SCB::sys_reset(); } - /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. fn restore(&mut self) -> Result<(), Error> { diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index cd2b34be..f122e95f 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -1,135 +1,44 @@ use crate::{ devices::{ - bootloader::Bootloader, + boot_manager::BootManager, cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, }, - error::Error as BootloaderError, + error::Error as ApplicationError, +}; +use blue_hal::{ + hal::{flash, serial}, + uprintln, }; -use blue_hal::{hal::{flash, serial}, uprintln}; use ufmt::uwriteln; -//commands!( cli, bootloader, names, helpstrings [ -// -// help ["Displays a list of commands."] (command: Option<&str> ["Optional command to inspect."],) { -// cli.print_help(names, helpstrings, command) -// }, -// -// test ["Tests various elements of the bootloader."] ( -// mcu: bool ["Set to test MCU flash"], -// external: bool ["Set to test external flash"], -// ) { -// match (mcu, external) { -// (true, true) => { -// uprintln!(cli.serial, "Starting Test..."); -// bootloader.test_mcu_flash()?; -// bootloader.test_external_flash()?; -// uprintln!(cli.serial, "Both Flash tests successful"); -// } -// (true, false) => { -// uprintln!(cli.serial, "Starting Test..."); -// bootloader.test_mcu_flash()?; -// uprintln!(cli.serial, "MCU flash test successful"); -// } -// (false, true) => { -// uprintln!(cli.serial, "Starting Test..."); -// bootloader.test_external_flash()?; -// uprintln!(cli.serial, "External flash test successful"); -// } -// (false, false) => { -// return Err(Error::MissingArgument); -// } -// } -// }, -// -// flash ["Stores a FW image in an external Bank."] ( -// size: usize ["Image size in bytes"], -// bank: u8 ["External Bank Index"], -// ) -// { -// if let Some(bank) = bootloader.external_banks().find(|b| b.index == bank) { -// if size > bank.size { -// return Err(Error::ArgumentOutOfRange); -// } -// uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); -// bootloader.store_image(cli.serial.blocks(), size, bank)?; -// uprintln!(cli.serial, "Image transfer complete!"); -// } else { -// uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); -// } -// -// }, -// -// format ["Erases a flash chip and initializes all headers to default values."] ( -// mcu: bool ["Set to format MCU flash"], -// external: bool ["Set to format external flash"], -// ){ -// match (mcu, external) { -// (true, true) => { -// uprintln!(cli.serial, "Formatting..."); -// bootloader.format_mcu_flash()?; -// uprintln!(cli.serial, "MCU Flash formatted successfully."); -// bootloader.format_external_flash()?; -// uprintln!(cli.serial, "Both Flash chips formatted successfully."); -// } -// (true, false) => { -// uprintln!(cli.serial, "Formatting..."); -// bootloader.format_mcu_flash()?; -// uprintln!(cli.serial, "MCU Flash formatted successfully."); -// } -// (false, true) => { -// uprintln!(cli.serial, "Formatting..."); -// bootloader.format_external_flash()?; -// uprintln!(cli.serial, "External Flash formatted successfully."); -// } -// (false, false) => { -// return Err(Error::MissingArgument); -// } -// } -// }, -// -// banks ["Retrieves information from FW image banks."] (){ -// uprintln!(cli.serial, "MCU Banks:"); -// for bank in bootloader.mcu_banks() { -// uwriteln!(cli.serial, " - [{}] {} - Size: {}b", -// bank.index, -// if bank.bootable { "Bootable" } else { "Non-Bootable" }, -// bank.size).ok().unwrap(); -// if let Some(image) = bootloader.image_at_bank(bank.index) { -// uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", -// if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, -// image.size, -// image.crc).ok().unwrap(); -// } -// -// } -// uprintln!(cli.serial, "External Banks:"); -// for bank in bootloader.external_banks() { -// uwriteln!(cli.serial, " - [{}] {} - Size: {}b", -// bank.index, -// if bank.bootable { "Bootable" } else { "Non-Bootable" }, -// bank.size).ok().unwrap(); -// if let Some(image) = bootloader.image_at_bank(bank.index) { -// uwriteln!(cli.serial, " - [IMAGE] {} - Size: {}b - CRC: {} ", -// if let Some(_) = image.name { "Placeholder Name" } else { "Anonymous" }, -// image.size, -// image.crc).ok().unwrap(); -// } -// } -// }, -// -// copy ["Copy an image from an external bank to an MCU bank."] ( -// input: u8 ["External Bank index to copy from."], -// output: u8 ["MCU Bank index to copy to."], -// ) -// { -// bootloader.copy_image(input, output)?; -// uprintln!(cli.serial, "Copy success!"); -// }, -// -// boot ["Restart, attempting to boot into a valid image if available."] ( ) -// { -// uprintln!(cli.serial, "Restarting..."); -// bootloader.reset(); -// }, -// -//]); +commands!( cli, boot_manager, names, helpstrings [ + + help ["Displays a list of commands."] (command: Option<&str> ["Optional command to inspect."],) { + cli.print_help(names, helpstrings, command) + }, + + flash ["Stores a FW image in an external bank."] ( + size: usize ["Image size in bytes"], + bank: u8 ["External Bank Index"], + ) + { + if let Some(bank) = boot_manager.external_banks().find(|b| b.index == bank) { + if size > bank.size { + return Err(Error::ArgumentOutOfRange); + } + uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); + boot_manager.store_image(cli.serial.blocks(), size, bank)?; + uprintln!(cli.serial, "Image transfer complete!"); + } else { + uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); + } + + }, + + boot ["Restart, attempting to boot into a valid image if available."] ( ) + { + uprintln!(cli.serial, "Restarting..."); + boot_manager.reset(); + }, + +]); diff --git a/src/devices/cli/file_transfer.rs b/src/devices/cli/file_transfer.rs index 411ee8b5..62a0c167 100644 --- a/src/devices/cli/file_transfer.rs +++ b/src/devices/cli/file_transfer.rs @@ -1,4 +1,7 @@ -use blue_hal::{hal::serial::{Write, TimeoutRead}, utilities::xmodem}; +use blue_hal::{ + hal::serial::{TimeoutRead, Write}, + utilities::xmodem, +}; pub const BLOCK_SIZE: usize = xmodem::PAYLOAD_SIZE; @@ -20,7 +23,7 @@ pub struct BlockIterator<'a, S: TimeoutRead + Write + ?Sized> { } impl<'a, S: TimeoutRead + Write + ?Sized> Iterator for BlockIterator<'a, S> { - type Item = FileBlock; + type Item = [u8; BLOCK_SIZE]; fn next(&mut self) -> Option { if self.finished { @@ -73,7 +76,7 @@ impl<'a, S: TimeoutRead + Write + ?Sized> Iterator for BlockIterator<'a, S> { } impl<'a, S: TimeoutRead + Write + ?Sized> BlockIterator<'a, S> { - fn process_message(&mut self, buffer: &[u8]) -> Option { + fn process_message(&mut self, buffer: &[u8]) -> Option<[u8; BLOCK_SIZE]> { match xmodem::parse_message(&buffer) { Ok((_, xmodem::Message::EndOfTransmission)) => { self.end_transmission(); @@ -91,7 +94,7 @@ impl<'a, S: TimeoutRead + Write + ?Sized> BlockIterator<'a, S> { } } - fn process_chunk(&self, chunk: xmodem::Chunk) -> Option { + fn process_chunk(&self, chunk: xmodem::Chunk) -> Option<[u8; BLOCK_SIZE]> { let next_block = self.block_number.wrapping_add(1); (chunk.block_number == next_block).then_some(chunk.payload) } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 63746c8a..a485ca99 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -5,17 +5,23 @@ //! handled in the `port` module. #![macro_use] -use crate::{ - devices::bootloader::Bootloader, - error::Error as BootloaderError, +use crate::error::Error as ApplicationError; +use blue_hal::{ + hal::{ + flash, + serial::{self, Read}, + }, + uprint, uprintln, + utilities::{buffer::TryCollectSlice, iterator::Unique}, }; use core::str::{from_utf8, SplitWhitespace}; -use blue_hal::{hal::{flash, serial::{self, Read}}, uprint, uprintln, utilities::buffer::TryCollectSlice, utilities::iterator::Unique}; use nb::block; -use ufmt::{uwriteln, uwrite}; +use ufmt::{uwrite, uwriteln}; use self::file_transfer::FileTransfer; +use super::boot_manager::BootManager; + pub mod file_transfer; const GREETING: &str = "--=Loadstone CLI=--\ntype `help` for a list of commands."; @@ -35,11 +41,11 @@ pub enum Error { DuplicateArguments, SerialBufferOverflow, SerialReadError, - BootloaderError(BootloaderError), + ApplicationError(ApplicationError), } -impl From for Error { - fn from(e: BootloaderError) -> Self { Error::BootloaderError(e) } +impl From for Error { + fn from(e: ApplicationError) -> Self { Error::ApplicationError(e) } } pub struct Cli { @@ -166,14 +172,11 @@ const LINE_TERMINATOR: char = '\n'; impl Cli { /// Reads a line, parses it as a command and attempts to execute it. - pub fn run(&mut self, bootloader: &mut Bootloader) + pub fn run(&mut self, boot_manager: &mut BootManager) where EXTF: flash::ReadWrite, - BootloaderError: From, - MCUF: flash::ReadWrite, - BootloaderError: From, - SRL: serial::ReadWrite + FileTransfer, - BootloaderError: From<::Error>, + ApplicationError: From, + ApplicationError: From<::Error>, { if !self.greeted { uprintln!(self.serial, "{}", GREETING); @@ -188,7 +191,7 @@ impl Cli { block!(self.read_line(&mut buffer))?; let text = from_utf8(&buffer).map_err(|_| Error::BadCommandEncoding)?; let (name, arguments) = Self::parse(text)?; - commands::run(self, bootloader, name, arguments)?; + commands::run(self, boot_manager, name, arguments)?; Ok(()) }; match execute_command() { @@ -210,8 +213,8 @@ impl Cli { Err(Error::DuplicateArguments) => { uwriteln!(self.serial, "[CLI Error] Command contains duplicate arguments") } - Err(Error::BootloaderError(e)) => { - uprintln!(self.serial, "[CLI Error] Internal bootloader error: "); + Err(Error::ApplicationError(e)) => { + uprintln!(self.serial, "[CLI Error] Internal boot_manager error: "); e.report(&mut self.serial); Ok(()) } @@ -321,7 +324,7 @@ impl Cli { macro_rules! commands { ( - $cli:ident, $bootloader:ident, $names:ident, $helpstrings:ident [ + $cli:ident, $boot_manager:ident, $names:ident, $helpstrings:ident [ $( $c:ident[$h:expr]($($a:ident: $t:ty [$r:expr],)*) $command:block, )+ @@ -343,14 +346,13 @@ macro_rules! commands { ]; #[allow(unreachable_code)] - pub(super) fn run( + pub(super) fn run( $cli: &mut Cli, - $bootloader: &mut Bootloader, + $boot_manager: &mut BootManager, name: Name, arguments: ArgumentIterator) -> Result<(), Error> where - EXTF: flash::ReadWrite, BootloaderError: From, - MCUF: flash::ReadWrite, BootloaderError: From, - SRL: serial::ReadWrite + FileTransfer, BootloaderError: From<::Error>, + EXTF: flash::ReadWrite, ApplicationError: From, + SRL: serial::ReadWrite + FileTransfer, ApplicationError: From<::Error>, { match name { $( diff --git a/src/devices/mod.rs b/src/devices/mod.rs index dcb21f7f..9cacc0bc 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -4,5 +4,6 @@ //! handled in the `ports` module. pub mod bootloader; +pub mod boot_manager; pub mod image; -//pub mod cli; +pub mod cli; From a6221ded254e9b0279107be8d6147359ca47428f Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 15:28:22 +0000 Subject: [PATCH 19/67] Do major refactor (make it look functional). --- tools/signing_tool/src/hashing.rs | 20 ++++-- tools/signing_tool/src/main.rs | 104 ++++++++++++++---------------- 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/tools/signing_tool/src/hashing.rs b/tools/signing_tool/src/hashing.rs index 663f2f21..d9995cb7 100644 --- a/tools/signing_tool/src/hashing.rs +++ b/tools/signing_tool/src/hashing.rs @@ -6,12 +6,18 @@ use std::{ io::{Read, Seek, SeekFrom}, }; -pub fn get_file_hash(file: &mut File) -> Vec { - // TODO: Consider reading file part-wise; - let _ = file.seek(SeekFrom::Start(0)); +pub fn get_file_hash(file: &mut File) -> Option> { + let filesize = file.seek(SeekFrom::End(0)).ok()?; + file.seek(SeekFrom::Start(0)).ok()?; + let mut buffer = vec!(); - let _ = file.read_to_end(&mut buffer); - let mut hasher = Sha256::new(); - hasher.update(buffer); - hasher.finalize().to_vec() + let bytes_read = file.read_to_end(&mut buffer).ok()?; + + if bytes_read == (filesize as usize) { + let mut hasher = Sha256::new(); + hasher.update(buffer); + Some(hasher.finalize().to_vec()) + } else { + None + } } \ No newline at end of file diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index cf1be94c..6bb2693d 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -13,20 +13,6 @@ use std::{ io::{Read, Write}, }; -fn open_file(path: &str, append: bool) -> Option { - let file = OpenOptions::new() - .read(true) - .append(append) - .open(path); - match file { - Ok(f) => Some(f), - Err(e) => { - eprintln!("Failed to open '{}': {}.", path, e); - None - }, - } -} - fn read_key(mut file: File) -> Option> { let mut string = String::new(); file.read_to_string(&mut string) @@ -41,6 +27,46 @@ fn read_key(mut file: File) -> Option> { .ok() } +fn run_with_files(mut image: File, key: File) -> Result { + let hash = get_file_hash(&mut image) + .ok_or(String::from("Failed to calculate image hash"))?; + let key = read_key(key) + .ok_or(String::from("Failed to read private key"))?; + let signature = sign(&hash, &key)?; + let bytes_written = image.write(&signature) + .map_err(|e| { + format!("Failed to append signature to image: {}", e) + })?; + if bytes_written == signature.len() { + Ok(String::from("Successfully appended signature to image.")) + } else { + Err(String::from("Error: signature only partially written.")) + } +} + +fn run_with_file_names(image: String, key: String) -> Result { + let image_file = OpenOptions::new() + .read(true) + .append(true) + .open(image); + let key_file = File::open(key); + + match (image_file, key_file) { + (Ok(i), Ok(k)) => { + run_with_files(i, k) + }, + (Err(i), Ok(_)) => { + Err(format!("Failed to open image file: {}", i)) + }, + (Ok(_), Err(k)) => { + Err(format!("Failed to open key file: {}", k)) + }, + (Err(i), Err(k)) => { + Err(format!("Failed to open files for key ({}) and image ({}).\n", i, k)) + }, + } +} + fn main() { let matches = clap::App::new("Loadstone Image Signing Tool") .version(env!("CARGO_PKG_VERSION")) @@ -56,49 +82,19 @@ fn main() { .help("The private key used to sign the image.")) .get_matches(); - let image_path = matches.value_of("image").unwrap(); - let key_path = matches.value_of("private_key").unwrap(); - let image = open_file(image_path, true); - let key = open_file(key_path, false); - - if image.is_none() || key.is_none() { - process::exit(1); - } - let mut image = image.unwrap(); - let key = key.unwrap(); - - println!("{:?}, {:?}", image, key); - - let hash = get_file_hash(&mut image); - println!("{:?}", hash); - - let raw_key = match read_key(key) { - Some(k) => k, - None => { - eprintln!("Failed to decode private key."); - process::exit(1); - }, - }; - - let signature = match sign(&hash[..], &raw_key[..]) { - Ok(s) => s, - Err(_) => { process::exit(1); }, - }; - - println!("{:?}", signature); + let image = matches.value_of("image") + .unwrap() + .to_owned(); + let private_key = matches.value_of("private_key") + .unwrap() + .to_owned(); - match image.write(&signature[..]) { + match run_with_file_names(image, private_key) { Ok(s) => { - if s == signature.len() { - println!("Successfully appended RSA256 signature to image."); - } else { - eprintln!("A partial write to the image occured. \ - You may need to re-generate the image."); - process::exit(1); - } + println!("{}", s); }, - Err(_) => { - eprintln!("Failed to append the signature to the file."); + Err(s) => { + eprintln!("{}", s); process::exit(1); } } From 2b84d172956ead185979e5832c56fe97dea9749e Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 15 Jan 2021 16:33:59 +0100 Subject: [PATCH 20/67] Add boot manager separate port --- src/bin/demo_app.rs | 4 +- src/devices/boot_manager.rs | 29 +++++++++----- src/devices/bootloader.rs | 6 +-- src/ports/mod.rs | 2 +- src/ports/stm32f412_discovery/boot_manager.rs | 40 +++++++++++++++++++ src/ports/stm32f412_discovery/bootloader.rs | 2 +- 6 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/ports/stm32f412_discovery/boot_manager.rs diff --git a/src/bin/demo_app.rs b/src/bin/demo_app.rs index c351622d..ee21c2cc 100644 --- a/src/bin/demo_app.rs +++ b/src/bin/demo_app.rs @@ -9,8 +9,8 @@ use loadstone_lib::devices::boot_manager; #[cfg(target_arch = "arm")] #[entry] fn main() -> ! { - let app = boot_manager::BootManager {}; - loop {} + let mut app = boot_manager::BootManager::new(); + app.run(); } #[cfg(not(target_arch = "arm"))] diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index fa114ea5..118f5b12 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -1,17 +1,18 @@ //! Fully CLI interactive boot manager for the demo application. -//! +use super::{ + cli::{file_transfer, Cli}, + image::{self, TRANSFER_BUFFER_SIZE}, +}; +use crate::error::Error; +use blue_hal::{ + hal::{flash, serial}, + stm32pac::SCB, + utilities::{buffer::CollectSlice, xmodem}, +}; use core::{array::IntoIter, cmp::min}; - -use blue_hal::{hal::{flash, serial}, stm32pac::SCB}; - -use blue_hal::utilities::{buffer::CollectSlice, xmodem}; use nb::block; -use crate::error::Error; - -use super::{cli::file_transfer, image::{self, TRANSFER_BUFFER_SIZE}}; - pub struct BootManager where EXTF: flash::ReadWrite, @@ -21,7 +22,7 @@ where { pub(crate) external_banks: &'static [image::Bank<::Address>], pub(crate) external_flash: EXTF, - pub(crate) serial: SRL, + pub(crate) cli: Option>, } impl BootManager @@ -31,7 +32,6 @@ where SRL: serial::ReadWrite + file_transfer::FileTransfer, Error: From<::Error>, { - /// Returns an iterator of all external flash banks. pub fn external_banks(&self) -> impl Iterator> { self.external_banks.iter().cloned() @@ -80,4 +80,11 @@ where } pub fn reset(&mut self) -> ! { SCB::sys_reset(); } + + pub fn run(mut self) -> ! { + let mut cli = self.cli.take().unwrap(); + loop { + cli.run(&mut self) + } + } } diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 43237441..8618c936 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -6,10 +6,8 @@ //! specific information. use super::image; use crate::error::Error; -use blue_hal::{ - hal::flash, -}; -use core::{array::IntoIter, cmp::min, mem::size_of}; +use blue_hal::hal::flash; +use core::{cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; use defmt::{error, info}; use image::TRANSFER_BUFFER_SIZE; diff --git a/src/ports/mod.rs b/src/ports/mod.rs index e1f8a888..61c45c01 100644 --- a/src/ports/mod.rs +++ b/src/ports/mod.rs @@ -5,4 +5,4 @@ use blue_hal::port; #[cfg(feature = "stm32f412_discovery")] -port!(stm32f412_discovery: [bootloader, pin_configuration,]); +port!(stm32f412_discovery: [bootloader, pin_configuration, boot_manager,]); diff --git a/src/ports/stm32f412_discovery/boot_manager.rs b/src/ports/stm32f412_discovery/boot_manager.rs new file mode 100644 index 00000000..1d161649 --- /dev/null +++ b/src/ports/stm32f412_discovery/boot_manager.rs @@ -0,0 +1,40 @@ +use crate::devices::{boot_manager::BootManager, cli::Cli}; +use blue_hal::{drivers::{micron::n25q128a_flash::MicronN25q128a, stm32f4::{qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use super::{bootloader::EXTERNAL_BANKS, pin_configuration::*}; + +// Flash pins and typedefs +type QspiPins = (Pb2, Pg6, Pf8, Pf9, Pf7, Pf6); +type Qspi = QuadSpi; +type ExternalFlash = MicronN25q128a; +type UsartPins = (Pg14, Pg9); +type Serial = serial::Serial; + +impl Default for BootManager { + fn default() -> Self { Self::new() } +} + +impl BootManager { + pub fn new() -> Self { + let mut peripherals = stm32pac::Peripherals::take().unwrap(); + let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); + let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); + let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); + let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); + let clocks = Clocks::hardcoded(peripherals.RCC); + + SysTick::init(cortex_peripherals.SYST, clocks); + SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup + + let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); + let serial_pins = (gpiog.pg14, gpiog.pg9); + let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); + let cli = Cli::new(serial).unwrap(); + + let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); + let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); + let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); + let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); + + BootManager { external_flash, external_banks: &EXTERNAL_BANKS, cli: Some(cli) } + } +} diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 2b0457aa..66885a45 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -48,7 +48,7 @@ static MCU_BANKS: [image::Bank; MCU_NUMBER_OF_BANKS] = [ image::Bank { index: 1, bootable: true, location: mcu_image_offset(0), size: IMAGE_SIZE, }, ]; -static EXTERNAL_BANKS: [image::Bank; EXTERNAL_NUMBER_OF_BANKS] = [ +pub static EXTERNAL_BANKS: [image::Bank; EXTERNAL_NUMBER_OF_BANKS] = [ image::Bank { index: 2, bootable: false, location: external_image_offset(0), size: IMAGE_SIZE, }, image::Bank { index: 3, bootable: false, location: external_image_offset(1), size: IMAGE_SIZE, }, ]; From 99dccdfeb8515c0d0050c4add9cc9fe732aa3995 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 15:36:52 +0000 Subject: [PATCH 21/67] Refactor clap App creation. --- tools/signing_tool/src/main.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index 6bb2693d..e022da13 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -5,6 +5,8 @@ mod signing; use crate::signing::*; extern crate clap; +use clap::{clap_app}; + extern crate base64; use std::{ @@ -68,19 +70,13 @@ fn run_with_file_names(image: String, key: String) -> Result { } fn main() { - let matches = clap::App::new("Loadstone Image Signing Tool") - .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .arg(clap::Arg::with_name("image") - .index(1) - .required(true) - .help("The firmware image to be signed.")) - .arg(clap::Arg::with_name("private_key") - .index(2) - .required(true) - .help("The private key used to sign the image.")) - .get_matches(); + let matches = clap_app!(myapp => + (version: env!("CARGO_PKG_VERSION")) + (author: env!("CARGO_PKG_AUTHORS")) + (about: env!("CARGO_PKG_DESCRIPTION")) + (@arg image: +required "The firmware image to be signed.") + (@arg private_key: +required "The private key used to sign the image.") + ).get_matches(); let image = matches.value_of("image") .unwrap() @@ -98,4 +94,4 @@ fn main() { process::exit(1); } } -} +} \ No newline at end of file From 11377014b1bcddaf23b3ae432496b3bfc1ae4e3d Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 15:42:04 +0000 Subject: [PATCH 22/67] Refactor 'run_with_file_names' function. --- tools/signing_tool/src/main.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index e022da13..a6e3b389 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -50,23 +50,15 @@ fn run_with_file_names(image: String, key: String) -> Result { let image_file = OpenOptions::new() .read(true) .append(true) - .open(image); - let key_file = File::open(key); - - match (image_file, key_file) { - (Ok(i), Ok(k)) => { - run_with_files(i, k) - }, - (Err(i), Ok(_)) => { - Err(format!("Failed to open image file: {}", i)) - }, - (Ok(_), Err(k)) => { - Err(format!("Failed to open key file: {}", k)) - }, - (Err(i), Err(k)) => { - Err(format!("Failed to open files for key ({}) and image ({}).\n", i, k)) - }, - } + .open(image) + .map_err(|e| { + format!("Failed to open image file: {}", e) + })?; + let key_file = File::open(key) + .map_err(|e| { + format!("Failed to open key file: {}", e) + })?; + run_with_files(image_file, key_file) } fn main() { From c0f419f30ba11ff3e4356c56b10574960d325101 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Fri, 15 Jan 2021 15:43:54 +0000 Subject: [PATCH 23/67] Fix incorrect program name in CLI. --- tools/signing_tool/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index a6e3b389..d31b70e2 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -62,7 +62,8 @@ fn run_with_file_names(image: String, key: String) -> Result { } fn main() { - let matches = clap_app!(myapp => + let matches = clap_app!(app => + (name: env!("CARGO_PKG_NAME")) (version: env!("CARGO_PKG_VERSION")) (author: env!("CARGO_PKG_AUTHORS")) (about: env!("CARGO_PKG_DESCRIPTION")) From 686789ac653dea0faceee9d3924876e342ce40cb Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 15 Jan 2021 17:02:17 +0100 Subject: [PATCH 24/67] Remove commented out code --- src/ports/stm32f412_discovery/bootloader.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 66885a45..29347c22 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -71,11 +71,6 @@ impl Bootloader { SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup - //let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); - //let serial_pins = (gpiog.pg14, gpiog.pg9); - //let mut serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); - //let cli = Cli::new(serial).unwrap(); - let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); From 7d5c863fad52285aa8f1730b42cf48f82092cbfa Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Mon, 18 Jan 2021 10:24:50 +0100 Subject: [PATCH 25/67] Add merge binaries tool --- .cargo/config | 5 +++-- src/bin/demo_app.rs | 2 +- tools/mergebins.sh | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100755 tools/mergebins.sh diff --git a/.cargo/config b/.cargo/config index e5a312a0..2ead4d4b 100644 --- a/.cargo/config +++ b/.cargo/config @@ -19,10 +19,11 @@ rustflags = [ ] [alias] -b = "build --release --bin loadstone --target thumbv7em-none-eabihf" -rb = "run --release --bin loadstone --target thumbv7em-none-eabihf" +b = "build --release --target thumbv7em-none-eabihf --bin" +rb = "run --release --target thumbv7em-none-eabihf --bin" sz = "size --release --bin loadstone --target thumbv7em-none-eabihf" gen_blinky = "objcopy --bin blinky --release --target thumbv7em-none-eabihf --features stm32f412 -- -O binary blinky.bin" +gen_demo_app = "objcopy --bin demo_app --release --target thumbv7em-none-eabihf --features stm32f412 -- -O binary demo_app.bin" gen_loadstone = "objcopy --bin loadstone --release --target thumbv7em-none-eabihf --features stm32f412 -- -O binary loadstone.bin" st = "strip --bin loadstone --release --target thumbv7em-none-eabihf" d = "doc --release --target thumbv7em-none-eabihf" diff --git a/src/bin/demo_app.rs b/src/bin/demo_app.rs index ee21c2cc..35305da9 100644 --- a/src/bin/demo_app.rs +++ b/src/bin/demo_app.rs @@ -9,7 +9,7 @@ use loadstone_lib::devices::boot_manager; #[cfg(target_arch = "arm")] #[entry] fn main() -> ! { - let mut app = boot_manager::BootManager::new(); + let app = boot_manager::BootManager::new(); app.run(); } diff --git a/tools/mergebins.sh b/tools/mergebins.sh new file mode 100755 index 00000000..3b962110 --- /dev/null +++ b/tools/mergebins.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +truncate -s 32768 loadstone.bin +cat loadstone.bin demo_app.bin > combined.bin From cd340053c1adcf64451adec3a51443211f3213e8 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Mon, 18 Jan 2021 11:40:50 +0100 Subject: [PATCH 26/67] Add script to flash both images --- src/devices/boot_manager.rs | 2 ++ tools/gdb_flash_combined.txt | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tools/gdb_flash_combined.txt diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 118f5b12..f04d46a5 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -10,6 +10,7 @@ use blue_hal::{ stm32pac::SCB, utilities::{buffer::CollectSlice, xmodem}, }; +use defmt::info; use core::{array::IntoIter, cmp::min}; use nb::block; @@ -84,6 +85,7 @@ where pub fn run(mut self) -> ! { let mut cli = self.cli.take().unwrap(); loop { + info!("Starting CLI"); cli.run(&mut self) } } diff --git a/tools/gdb_flash_combined.txt b/tools/gdb_flash_combined.txt new file mode 100644 index 00000000..bd166c40 --- /dev/null +++ b/tools/gdb_flash_combined.txt @@ -0,0 +1,12 @@ +target remote localhost:3333 +monitor reset halt +define reflash +monitor flash erase_address unlock 0x08000000 524288 +monitor flash write_image unlock ./combined.bin 0x08000000 bin +end +reflash +define verify +monitor verify_image ./combined.bin 0x08000000 bin +end +verify +monitor reset halt From 54e09611eeb8b740840b3b47547741fa000dea40 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 11:40:18 +0000 Subject: [PATCH 27/67] Switch crypto library to ring crate. --- tools/signing_tool/Cargo.toml | 8 ++-- tools/signing_tool/src/hashing.rs | 23 ----------- tools/signing_tool/src/main.rs | 42 ++----------------- tools/signing_tool/src/signing.rs | 67 +++++++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 72 deletions(-) delete mode 100644 tools/signing_tool/src/hashing.rs diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml index 0b860bb8..804370c3 100644 --- a/tools/signing_tool/Cargo.toml +++ b/tools/signing_tool/Cargo.toml @@ -9,6 +9,8 @@ description = "Tool to calculate and append signatures to images." [dependencies] clap = "2" -sha2 = "0.9" -rsa = "0.3" -base64 = "0.13" \ No newline at end of file +base64 = "0.13" + +[dependencies.ring] +version = "0.16" +features = ["alloc", "std"] \ No newline at end of file diff --git a/tools/signing_tool/src/hashing.rs b/tools/signing_tool/src/hashing.rs deleted file mode 100644 index d9995cb7..00000000 --- a/tools/signing_tool/src/hashing.rs +++ /dev/null @@ -1,23 +0,0 @@ -extern crate sha2; -use sha2::{Digest, Sha256}; - -use std::{ - fs::File, - io::{Read, Seek, SeekFrom}, -}; - -pub fn get_file_hash(file: &mut File) -> Option> { - let filesize = file.seek(SeekFrom::End(0)).ok()?; - file.seek(SeekFrom::Start(0)).ok()?; - - let mut buffer = vec!(); - let bytes_read = file.read_to_end(&mut buffer).ok()?; - - if bytes_read == (filesize as usize) { - let mut hasher = Sha256::new(); - hasher.update(buffer); - Some(hasher.finalize().to_vec()) - } else { - None - } -} \ No newline at end of file diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index d31b70e2..9946e6a4 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,49 +1,15 @@ -mod hashing; -use crate::hashing::*; - mod signing; -use crate::signing::*; -extern crate clap; +use crate::signing::*; use clap::{clap_app}; - -extern crate base64; - use std::{ fs::{File, OpenOptions}, process, - io::{Read, Write}, }; -fn read_key(mut file: File) -> Option> { - let mut string = String::new(); - file.read_to_string(&mut string) - .ok()?; - let encoded = string.lines() - .filter(|l| !l.starts_with("-")) - .fold(Vec::::new(), |mut data, line| { - data.extend_from_slice(line.as_bytes()); - data - }); - base64::decode(encoded) - .ok() -} - -fn run_with_files(mut image: File, key: File) -> Result { - let hash = get_file_hash(&mut image) - .ok_or(String::from("Failed to calculate image hash"))?; - let key = read_key(key) - .ok_or(String::from("Failed to read private key"))?; - let signature = sign(&hash, &key)?; - let bytes_written = image.write(&signature) - .map_err(|e| { - format!("Failed to append signature to image: {}", e) - })?; - if bytes_written == signature.len() { - Ok(String::from("Successfully appended signature to image.")) - } else { - Err(String::from("Error: signature only partially written.")) - } +fn run_with_files(image: File, key: File) -> Result { + sign_file(image, key) + .map(|()| String::from("Successfully appended signature to image.")) } fn run_with_file_names(image: String, key: String) -> Result { diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs index 74c007d9..200bba9d 100644 --- a/tools/signing_tool/src/signing.rs +++ b/tools/signing_tool/src/signing.rs @@ -1,11 +1,62 @@ -extern crate rsa; +use ring::{ + signature::{RSA_PKCS1_SHA256, RsaKeyPair}, + rand::SystemRandom, +}; +use std::{ + fs::File, + io::{Read, Write}, +}; -use rsa::{RSAPrivateKey, PaddingScheme, Hash}; +fn read_file(file: &mut File) -> Result, String> { + let mut contents = Vec::new(); + match file.read_to_end(&mut contents) { + Ok(_) => Ok(contents), + Err(_) => Err(String::from("Failed to read image file.")), + } +} -pub fn sign(digest: &[u8], pkcs1_private_key: &[u8]) -> Result, String> { - let private_key = RSAPrivateKey::from_pkcs8(pkcs1_private_key) - .map_err(|e| format!("Failed to parse private key: {}", e))?; - let padding_scheme = PaddingScheme::PKCS1v15Sign { hash: Some(Hash::SHA2_256) }; - private_key.sign(padding_scheme, digest) - .map_err(|e| format!("Failed to sign digest: {}", e)) +fn read_key(mut file: File) -> Result, String> { + let mut string = String::new(); + file.read_to_string(&mut string) + .map_err(|_| String::from("Failed to read key file."))?; + let encoded = string.lines() + .filter(|l| !l.starts_with("-")) + .fold(Vec::::new(), |mut data, line| { + data.extend_from_slice(line.as_bytes()); + data + }); + base64::decode(encoded) + .map_err(|e| format!("Failed to decode key file: {}.", e)) +} + +pub fn create_zeroed_u8_vec(size: usize) -> Vec { + let mut vector = Vec::::with_capacity(size); + for _ in 0..size { + vector.push(0); + } + vector +} + +/// Reads the contents of `file` and signs it using RSA/SHA256 with the key in `key_file`. +/// NOTE: This assumes that `file` is in read/append mode and the key is PKCS1. +pub fn sign_file(mut file: File, key_file: File) -> Result<(), String> { + let raw_key = read_key(key_file)?; + let plaintext = read_file(&mut file)?; + let key = RsaKeyPair::from_pkcs8(&raw_key) + .map_err(|e| format!("Failed to parse key: {}.", e))?; + + let rng = SystemRandom::new(); + let mut signature = create_zeroed_u8_vec(key.public_modulus_len()); + + key.sign(&RSA_PKCS1_SHA256, &rng, &plaintext, &mut signature) + .map_err(|_| String::from("Failed to generate image signature."))?; + + let bytes_written = file.write(&signature) + .map_err(|e| format!("Failed to write signature to file: {}.", e))?; + + if bytes_written == signature.len() { + Ok(()) + } else { + Err(String::from("Failed to write entire signature to file.")) + } } \ No newline at end of file From 7ce8209f2261d4158dc61bb2846b5bcc744f2ae2 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 11:58:19 +0000 Subject: [PATCH 28/67] Create Error struct. --- tools/signing_tool/src/error.rs | 35 +++++++++++++++++++++++++++++++ tools/signing_tool/src/main.rs | 20 +++++++++--------- tools/signing_tool/src/signing.rs | 23 ++++++++++---------- 3 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 tools/signing_tool/src/error.rs diff --git a/tools/signing_tool/src/error.rs b/tools/signing_tool/src/error.rs new file mode 100644 index 00000000..d0aefaca --- /dev/null +++ b/tools/signing_tool/src/error.rs @@ -0,0 +1,35 @@ +use std::fmt::{self, Display, Formatter}; + +pub enum File { + Key, + Image, +} + +impl Display for File { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + use File::*; + match self { + Key => write!(f, "key"), + Image => write!(f, "image"), + } + } +} + +pub enum Error { + FileReadFailed(File), + FileOpenFailed(File), + FileWriteFailed(File), + SignatureGenerationFailed, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + use Error::*; + match self { + FileReadFailed(file) => write!(f, "Failed to read {} file.", file), + FileOpenFailed(file) => write!(f, "Failed to open {} file.", file), + FileWriteFailed(file) => write!(f, "Failed to write {} file.", file), + SignatureGenerationFailed => write!(f, "Failed to generate image signature."), + } + } +} diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index 9946e6a4..28ae0adb 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,29 +1,29 @@ mod signing; +mod error; -use crate::signing::*; +use crate::{ + signing::sign_file, + error::{self as e, Error}, +}; use clap::{clap_app}; use std::{ fs::{File, OpenOptions}, process, }; -fn run_with_files(image: File, key: File) -> Result { +fn run_with_files(image: File, key: File) -> Result { sign_file(image, key) .map(|()| String::from("Successfully appended signature to image.")) } -fn run_with_file_names(image: String, key: String) -> Result { +fn run_with_file_names(image: String, key: String) -> Result { let image_file = OpenOptions::new() .read(true) .append(true) .open(image) - .map_err(|e| { - format!("Failed to open image file: {}", e) - })?; + .map_err(|_| Error::FileOpenFailed(e::File::Image))?; let key_file = File::open(key) - .map_err(|e| { - format!("Failed to open key file: {}", e) - })?; + .map_err(|_| Error::FileOpenFailed(e::File::Key))?; run_with_files(image_file, key_file) } @@ -53,4 +53,4 @@ fn main() { process::exit(1); } } -} \ No newline at end of file +} diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs index 200bba9d..ce1fbab4 100644 --- a/tools/signing_tool/src/signing.rs +++ b/tools/signing_tool/src/signing.rs @@ -6,19 +6,20 @@ use std::{ fs::File, io::{Read, Write}, }; +use crate::error::{self, Error}; -fn read_file(file: &mut File) -> Result, String> { +fn read_file(file: &mut File) -> Result, Error> { let mut contents = Vec::new(); match file.read_to_end(&mut contents) { Ok(_) => Ok(contents), - Err(_) => Err(String::from("Failed to read image file.")), + Err(_) => Err(Error::FileReadFailed(error::File::Image)), } } -fn read_key(mut file: File) -> Result, String> { +fn read_key(mut file: File) -> Result, Error> { let mut string = String::new(); file.read_to_string(&mut string) - .map_err(|_| String::from("Failed to read key file."))?; + .map_err(|_| Error::FileReadFailed(error::File::Key))?; let encoded = string.lines() .filter(|l| !l.starts_with("-")) .fold(Vec::::new(), |mut data, line| { @@ -26,7 +27,7 @@ fn read_key(mut file: File) -> Result, String> { data }); base64::decode(encoded) - .map_err(|e| format!("Failed to decode key file: {}.", e)) + .map_err(|_| Error::FileReadFailed(error::File::Key)) } pub fn create_zeroed_u8_vec(size: usize) -> Vec { @@ -39,24 +40,24 @@ pub fn create_zeroed_u8_vec(size: usize) -> Vec { /// Reads the contents of `file` and signs it using RSA/SHA256 with the key in `key_file`. /// NOTE: This assumes that `file` is in read/append mode and the key is PKCS1. -pub fn sign_file(mut file: File, key_file: File) -> Result<(), String> { +pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { let raw_key = read_key(key_file)?; let plaintext = read_file(&mut file)?; let key = RsaKeyPair::from_pkcs8(&raw_key) - .map_err(|e| format!("Failed to parse key: {}.", e))?; + .map_err(|_| Error::FileReadFailed(error::File::Key))?; let rng = SystemRandom::new(); let mut signature = create_zeroed_u8_vec(key.public_modulus_len()); key.sign(&RSA_PKCS1_SHA256, &rng, &plaintext, &mut signature) - .map_err(|_| String::from("Failed to generate image signature."))?; + .map_err(|_| Error::SignatureGenerationFailed)?; let bytes_written = file.write(&signature) - .map_err(|e| format!("Failed to write signature to file: {}.", e))?; + .map_err(|_| Error::FileWriteFailed(error::File::Image))?; if bytes_written == signature.len() { Ok(()) } else { - Err(String::from("Failed to write entire signature to file.")) + Err(Error::FileWriteFailed(error::File::Image)) } -} \ No newline at end of file +} From e139ed4616ebedc666c4ac63be7f41e3f503d7f9 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:00:20 +0000 Subject: [PATCH 29/67] Format code with cargo fmt. --- tools/signing_tool/src/main.rs | 25 ++++++++++--------------- tools/signing_tool/src/signing.rs | 28 +++++++++++++++------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index 28ae0adb..bcd9539d 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -1,19 +1,18 @@ -mod signing; mod error; +mod signing; use crate::{ - signing::sign_file, error::{self as e, Error}, + signing::sign_file, }; -use clap::{clap_app}; +use clap::clap_app; use std::{ fs::{File, OpenOptions}, process, }; fn run_with_files(image: File, key: File) -> Result { - sign_file(image, key) - .map(|()| String::from("Successfully appended signature to image.")) + sign_file(image, key).map(|()| String::from("Successfully appended signature to image.")) } fn run_with_file_names(image: String, key: String) -> Result { @@ -22,8 +21,7 @@ fn run_with_file_names(image: String, key: String) -> Result { .append(true) .open(image) .map_err(|_| Error::FileOpenFailed(e::File::Image))?; - let key_file = File::open(key) - .map_err(|_| Error::FileOpenFailed(e::File::Key))?; + let key_file = File::open(key).map_err(|_| Error::FileOpenFailed(e::File::Key))?; run_with_files(image_file, key_file) } @@ -35,19 +33,16 @@ fn main() { (about: env!("CARGO_PKG_DESCRIPTION")) (@arg image: +required "The firmware image to be signed.") (@arg private_key: +required "The private key used to sign the image.") - ).get_matches(); + ) + .get_matches(); - let image = matches.value_of("image") - .unwrap() - .to_owned(); - let private_key = matches.value_of("private_key") - .unwrap() - .to_owned(); + let image = matches.value_of("image").unwrap().to_owned(); + let private_key = matches.value_of("private_key").unwrap().to_owned(); match run_with_file_names(image, private_key) { Ok(s) => { println!("{}", s); - }, + } Err(s) => { eprintln!("{}", s); process::exit(1); diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs index ce1fbab4..b92569dd 100644 --- a/tools/signing_tool/src/signing.rs +++ b/tools/signing_tool/src/signing.rs @@ -1,12 +1,12 @@ +use crate::error::{self, Error}; use ring::{ - signature::{RSA_PKCS1_SHA256, RsaKeyPair}, rand::SystemRandom, + signature::{RsaKeyPair, RSA_PKCS1_SHA256}, }; use std::{ fs::File, io::{Read, Write}, }; -use crate::error::{self, Error}; fn read_file(file: &mut File) -> Result, Error> { let mut contents = Vec::new(); @@ -20,14 +20,15 @@ fn read_key(mut file: File) -> Result, Error> { let mut string = String::new(); file.read_to_string(&mut string) .map_err(|_| Error::FileReadFailed(error::File::Key))?; - let encoded = string.lines() - .filter(|l| !l.starts_with("-")) - .fold(Vec::::new(), |mut data, line| { - data.extend_from_slice(line.as_bytes()); - data - }); - base64::decode(encoded) - .map_err(|_| Error::FileReadFailed(error::File::Key)) + let encoded = + string + .lines() + .filter(|l| !l.starts_with("-")) + .fold(Vec::::new(), |mut data, line| { + data.extend_from_slice(line.as_bytes()); + data + }); + base64::decode(encoded).map_err(|_| Error::FileReadFailed(error::File::Key)) } pub fn create_zeroed_u8_vec(size: usize) -> Vec { @@ -43,8 +44,8 @@ pub fn create_zeroed_u8_vec(size: usize) -> Vec { pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { let raw_key = read_key(key_file)?; let plaintext = read_file(&mut file)?; - let key = RsaKeyPair::from_pkcs8(&raw_key) - .map_err(|_| Error::FileReadFailed(error::File::Key))?; + let key = + RsaKeyPair::from_pkcs8(&raw_key).map_err(|_| Error::FileReadFailed(error::File::Key))?; let rng = SystemRandom::new(); let mut signature = create_zeroed_u8_vec(key.public_modulus_len()); @@ -52,7 +53,8 @@ pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { key.sign(&RSA_PKCS1_SHA256, &rng, &plaintext, &mut signature) .map_err(|_| Error::SignatureGenerationFailed)?; - let bytes_written = file.write(&signature) + let bytes_written = file + .write(&signature) .map_err(|_| Error::FileWriteFailed(error::File::Image))?; if bytes_written == signature.len() { From cf8e408f2c15ef0122184226ac402398861251da Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:02:02 +0000 Subject: [PATCH 30/67] Add PKCS8 to private key help string. --- tools/signing_tool/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index bcd9539d..c82b1000 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -32,7 +32,7 @@ fn main() { (author: env!("CARGO_PKG_AUTHORS")) (about: env!("CARGO_PKG_DESCRIPTION")) (@arg image: +required "The firmware image to be signed.") - (@arg private_key: +required "The private key used to sign the image.") + (@arg private_key: +required "The PKCS8 private key used to sign the image.") ) .get_matches(); From d6f57e582be994b11f08e8fd1f9874aed64d298f Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:12:04 +0000 Subject: [PATCH 31/67] Add KeyParseFailed error. --- tools/signing_tool/src/error.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/signing_tool/src/error.rs b/tools/signing_tool/src/error.rs index d0aefaca..7313dfd0 100644 --- a/tools/signing_tool/src/error.rs +++ b/tools/signing_tool/src/error.rs @@ -20,6 +20,7 @@ pub enum Error { FileOpenFailed(File), FileWriteFailed(File), SignatureGenerationFailed, + KeyParseFailed, } impl Display for Error { @@ -30,6 +31,7 @@ impl Display for Error { FileOpenFailed(file) => write!(f, "Failed to open {} file.", file), FileWriteFailed(file) => write!(f, "Failed to write {} file.", file), SignatureGenerationFailed => write!(f, "Failed to generate image signature."), + KeyParseFailed => write!(f, "Failed to parse the private key."), } } } From da38207afdb7e17754932086e126af0cc720e85a Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:12:18 +0000 Subject: [PATCH 32/67] Minor fixes. --- tools/signing_tool/src/signing.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs index b92569dd..9df2bb2c 100644 --- a/tools/signing_tool/src/signing.rs +++ b/tools/signing_tool/src/signing.rs @@ -19,7 +19,7 @@ fn read_file(file: &mut File) -> Result, Error> { fn read_key(mut file: File) -> Result, Error> { let mut string = String::new(); file.read_to_string(&mut string) - .map_err(|_| Error::FileReadFailed(error::File::Key))?; + .map_err(|_| Error::KeyParseFailed)?; let encoded = string .lines() @@ -28,10 +28,10 @@ fn read_key(mut file: File) -> Result, Error> { data.extend_from_slice(line.as_bytes()); data }); - base64::decode(encoded).map_err(|_| Error::FileReadFailed(error::File::Key)) + base64::decode(encoded).map_err(|_| Error::KeyParseFailed) } -pub fn create_zeroed_u8_vec(size: usize) -> Vec { +fn create_zeroed_u8_vec(size: usize) -> Vec { let mut vector = Vec::::with_capacity(size); for _ in 0..size { vector.push(0); @@ -40,12 +40,12 @@ pub fn create_zeroed_u8_vec(size: usize) -> Vec { } /// Reads the contents of `file` and signs it using RSA/SHA256 with the key in `key_file`. -/// NOTE: This assumes that `file` is in read/append mode and the key is PKCS1. +/// NOTE: This assumes that `file` is in read/append mode and the key is PKCS8. pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { let raw_key = read_key(key_file)?; let plaintext = read_file(&mut file)?; let key = - RsaKeyPair::from_pkcs8(&raw_key).map_err(|_| Error::FileReadFailed(error::File::Key))?; + RsaKeyPair::from_pkcs8(&raw_key).map_err(|_| Error::KeyParseFailed)?; let rng = SystemRandom::new(); let mut signature = create_zeroed_u8_vec(key.public_modulus_len()); From 0d415bfdfc41ceee2fda59810a81e7df95aff158 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:15:40 +0000 Subject: [PATCH 33/67] Add readme. --- tools/signing_tool/readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tools/signing_tool/readme.md diff --git a/tools/signing_tool/readme.md b/tools/signing_tool/readme.md new file mode 100644 index 00000000..b56c1a04 --- /dev/null +++ b/tools/signing_tool/readme.md @@ -0,0 +1,11 @@ +# Image Signing Tool + +This tool appends an RSA/SHA256 signature to a file. + +For usage help do `signing_tool --help`. + +The program expects a PKCS8 private key, such as ones generated by doing `ssh-keygen -t rsa -m PKCS8` for example. + +## Building + +To build the tool (required rust installation), do `cargo build --release`. From ced0dbeccd74863d5e5c8b1c8cd5343200fe526d Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 12:16:45 +0000 Subject: [PATCH 34/67] Add final newline to Cargo.toml. --- tools/signing_tool/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/signing_tool/Cargo.toml b/tools/signing_tool/Cargo.toml index 804370c3..7e27842d 100644 --- a/tools/signing_tool/Cargo.toml +++ b/tools/signing_tool/Cargo.toml @@ -13,4 +13,4 @@ base64 = "0.13" [dependencies.ring] version = "0.16" -features = ["alloc", "std"] \ No newline at end of file +features = ["alloc", "std"] From c01b8774232b53dbb5d2459dc7a2ed7482c0cb99 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Mon, 18 Jan 2021 15:09:48 +0100 Subject: [PATCH 35/67] WIP reworking image headers --- src/bin/blinky.rs | 34 --- src/devices/boot_manager.rs | 45 ++-- src/devices/bootloader.rs | 170 +++++--------- src/devices/image.rs | 243 ++++---------------- src/ports/stm32f412_discovery/bootloader.rs | 13 +- 5 files changed, 127 insertions(+), 378 deletions(-) delete mode 100644 src/bin/blinky.rs diff --git a/src/bin/blinky.rs b/src/bin/blinky.rs deleted file mode 100644 index 79f7dbd4..00000000 --- a/src/bin/blinky.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![cfg_attr(test, allow(unused_attributes))] -#![cfg_attr(all(not(test), target_arch = "arm"), no_std)] -#![cfg_attr(target_arch = "arm", no_main)] - -#[allow(unused_imports)] -use cortex_m_rt::{entry, exception}; - -#[cfg(target_arch = "arm")] -#[entry] -fn main() -> ! { - use loadstone_lib::{ - drivers::{ - led, - stm32f4::{gpio::GpioExt, rcc::Clocks, systick}, - }, - hal::{led::Toggle, time::Seconds}, - stm32pac, - }; - let mut peripherals = stm32pac::Peripherals::take().unwrap(); - let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); - - let gpioe = peripherals.GPIOE.split(&mut peripherals.RCC); - let clocks = Clocks::hardcoded(&peripherals.FLASH, peripherals.RCC); - let systick = systick::SysTick::new(cortex_peripherals.SYST, clocks); - let mut led = led::MonochromeLed::new(gpioe.pe1, led::LogicLevel::Inverted); - - loop { - systick.wait(Seconds(1)); - led.toggle(); - } -} - -#[cfg(not(target_arch = "arm"))] -fn main() {} diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index f04d46a5..7cca4e00 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -52,32 +52,33 @@ where return Err(Error::ImageTooBig); } - // Header must be re-formatted before any writing takes place, to ensure - // a valid header and an invalid image never coexist. - image::ImageHeader::format_default(&mut self.external_flash, &bank)?; + unimplemented!(); + //// Header must be re-formatted before any writing takes place, to ensure + //// a valid header and an invalid image never coexist. + //image::ImageHeader::format_default(&mut self.external_flash, &bank)?; - let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - let address = bank.location; - let mut bytes_written = 0; + //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; + //let address = bank.location; + //let mut bytes_written = 0; - let mut bytes = blocks.flat_map(|b| IntoIter::new(b)); + //let mut bytes = blocks.flat_map(|b| IntoIter::new(b)); - loop { - let distance_to_end = size - bytes_written; - let received = bytes.collect_slice(&mut buffer); - if received == 0 { - break; - } - let bytes_to_write = min(distance_to_end, received); - block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; - bytes_written += bytes_to_write; - } + //loop { + // let distance_to_end = size - bytes_written; + // let received = bytes.collect_slice(&mut buffer); + // if received == 0 { + // break; + // } + // let bytes_to_write = min(distance_to_end, received); + // block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; + // bytes_written += bytes_to_write; + //} - if bytes_written == size { - image::ImageHeader::write(&mut self.external_flash, &bank, size) - } else { - Err(Error::NotEnoughData) - } + //if bytes_written == size { + // image::ImageHeader::write(&mut self.external_flash, &bank, size) + //} else { + // Err(Error::NotEnoughData) + //} } pub fn reset(&mut self) -> ! { SCB::sys_reset(); } diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 8618c936..211a0b55 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -73,59 +73,29 @@ where return Err(Error::BankInvalid); } - let header = image::ImageHeader::retrieve(&mut self.mcu_flash, &bank)?; - if header.size == 0 { - return Err(Error::BankEmpty); - } - - let image_location_raw: usize = bank.location.into(); - - // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an - // entirely different firmware image! We have to assume everything is at the right place, - // or literally anything could happen here. After the interrupts are disabled, there is - // no turning back. - unsafe { - let initial_stack_pointer = *(image_location_raw as *const u32); - let reset_handler_pointer = - *((image_location_raw + size_of::()) as *const u32) as *const (); - let reset_handler = core::mem::transmute::<*const (), fn() -> !>(reset_handler_pointer); - cortex_m::interrupt::disable(); - (*SCB::ptr()).vtor.write(image_location_raw as u32); - cortex_m::register::msp::write(initial_stack_pointer); - reset_handler() - } - } - - /// Formats all MCU flash banks. - pub fn format_mcu_flash(&mut self) -> Result<(), Error> { - // Headers must be formatted first before the full flash - // erase, to ensure no half-formatted state in case of restart - image::GlobalHeader::format_default(&mut self.mcu_flash)?; - for bank in self.mcu_banks { - image::ImageHeader::format_default(&mut self.mcu_flash, bank)?; - } - block!(self.mcu_flash.erase())?; - image::GlobalHeader::format_default(&mut self.mcu_flash)?; - for bank in self.mcu_banks { - image::ImageHeader::format_default(&mut self.mcu_flash, bank)?; - } - Ok(()) - } - - /// Formats all external flash banks. - pub fn format_external_flash(&mut self) -> Result<(), Error> { - // Headers must be formatted first before the full flash - // erase, to ensure no half-formatted state in case of restart - image::GlobalHeader::format_default(&mut self.external_flash)?; - for bank in self.external_banks { - image::ImageHeader::format_default(&mut self.external_flash, bank)?; - } - block!(self.external_flash.erase())?; - image::GlobalHeader::format_default(&mut self.external_flash)?; - for bank in self.external_banks { - image::ImageHeader::format_default(&mut self.external_flash, bank)?; - } - Ok(()) + unimplemented!(); + + //let header = image::ImageHeader::retrieve(&mut self.mcu_flash, &bank)?; + //if header.size == 0 { + // return Err(Error::BankEmpty); + //} + + //let image_location_raw: usize = bank.location.into(); + + //// NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an + //// entirely different firmware image! We have to assume everything is at the right place, + //// or literally anything could happen here. After the interrupts are disabled, there is + //// no turning back. + //unsafe { + // let initial_stack_pointer = *(image_location_raw as *const u32); + // let reset_handler_pointer = + // *((image_location_raw + size_of::()) as *const u32) as *const (); + // let reset_handler = core::mem::transmute::<*const (), fn() -> !>(reset_handler_pointer); + // cortex_m::interrupt::disable(); + // (*SCB::ptr()).vtor.write(image_location_raw as u32); + // cortex_m::register::msp::write(initial_stack_pointer); + // reset_handler() + //} } /// Runs a self test on MCU flash. @@ -138,25 +108,6 @@ where Self::test_flash_read_write_cycle(&mut self.external_flash) } - /// Finds and returns the image header of a given bank index. - pub fn image_at_bank(&mut self, index: u8) -> Option { - let mcu_bank = self.mcu_banks().find(|b| b.index == index); - let external_bank = self.external_banks().find(|b| b.index == index); - - let image = if let Some(bank) = mcu_bank { - image::ImageHeader::retrieve(&mut self.mcu_flash, &bank).ok() - } else if let Some(bank) = external_bank { - image::ImageHeader::retrieve(&mut self.external_flash, &bank).ok() - } else { - None - }; - - match image { - Some(image) if image.size > 0 => Some(image), - _ => None, - } - } - /// Returns an iterator of all MCU flash banks. pub fn mcu_banks(&self) -> impl Iterator> { self.mcu_banks.iter().cloned() @@ -169,43 +120,44 @@ where /// Copy from external bank to MCU bank pub fn copy_image(&mut self, input_bank_index: u8, output_bank_index: u8) -> Result<(), Error> { - let (input_bank, output_bank) = ( - self.external_banks() - .find(|b| b.index == input_bank_index) - .ok_or(Error::BankInvalid)?, - self.mcu_banks().find(|b| b.index == output_bank_index).ok_or(Error::BankInvalid)?, - ); - let input_header = image::ImageHeader::retrieve(&mut self.external_flash, &input_bank)?; - if input_header.size > output_bank.size { - return Err(Error::ImageTooBig); - } else if input_header.size == 0 { - return Err(Error::BankEmpty); - } - - input_bank.sanity_check(&mut self.external_flash)?; - // Output header must be re-formatted before any writing takes place, to ensure - // a valid header and an invalid image never coexist. - image::ImageHeader::format_default(&mut self.mcu_flash, &output_bank)?; - - let input_image_start_address = input_bank.location; - let output_image_start_address = output_bank.location; - - let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - let mut byte_index = 0usize; - - while byte_index < input_header.size { - let bytes_to_read = - min(TRANSFER_BUFFER_SIZE, input_header.size.saturating_sub(byte_index)); - block!(self - .external_flash - .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; - block!(self - .mcu_flash - .write(output_image_start_address + byte_index, &buffer[0..bytes_to_read]))?; - byte_index += bytes_to_read; - } - - image::ImageHeader::write(&mut self.mcu_flash, &output_bank, input_header.size) + unimplemented!(); + //let (input_bank, output_bank) = ( + // self.external_banks() + // .find(|b| b.index == input_bank_index) + // .ok_or(Error::BankInvalid)?, + // self.mcu_banks().find(|b| b.index == output_bank_index).ok_or(Error::BankInvalid)?, + //); + //let input_header = image::ImageHeader::retrieve(&mut self.external_flash, &input_bank)?; + //if input_header.size > output_bank.size { + // return Err(Error::ImageTooBig); + //} else if input_header.size == 0 { + // return Err(Error::BankEmpty); + //} + + //input_bank.sanity_check(&mut self.external_flash)?; + //// Output header must be re-formatted before any writing takes place, to ensure + //// a valid header and an invalid image never coexist. + //image::ImageHeader::format_default(&mut self.mcu_flash, &output_bank)?; + + //let input_image_start_address = input_bank.location; + //let output_image_start_address = output_bank.location; + + //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; + //let mut byte_index = 0usize; + + //while byte_index < input_header.size { + // let bytes_to_read = + // min(TRANSFER_BUFFER_SIZE, input_header.size.saturating_sub(byte_index)); + // block!(self + // .external_flash + // .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; + // block!(self + // .mcu_flash + // .write(output_image_start_address + byte_index, &buffer[0..bytes_to_read]))?; + // byte_index += bytes_to_read; + //} + + //image::ImageHeader::write(&mut self.mcu_flash, &output_bank, input_header.size) } fn test_flash_read_write_cycle(flash: &mut F) -> Result<(), Error> diff --git a/src/devices/image.rs b/src/devices/image.rs index baca018e..0e668cf2 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -9,37 +9,10 @@ use nb::{self, block}; pub(crate) const TRANSFER_BUFFER_SIZE: usize = 2048usize; -/// Changing this magic will force any headers in -/// flash memory to be considered incorrect. Change it -/// whenever you make a modification to any of the -/// header types below. -pub const MAGIC: u32 = 0xAB3ACADA; +/// This string must terminate any valid images, after CRC +const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; -// If MAGIC was changed to be 0xFFFF_FFFF, uninitialized -// flash memory would be confused with valid memory. -static_assertions::const_assert!(MAGIC != 0xFFFF_FFFF); - -/// Header present at the lowest writable -/// address of a flash chip (either MCU or external). -#[repr(C)] -pub struct GlobalHeader { - magic: u32, - /// Scratchpad buffer to test arbitrary read/write operations. - /// The value persisted here is meaningless. - pub test_buffer: [u8; 4], -} - -/// Header present at the start of any firmware image. -#[repr(C)] -pub struct ImageHeader { - magic: u32, - pub size: usize, - pub crc: u32, - pub name: Option<[u8; 32]>, -} - -/// Image bank descriptor -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] pub struct Bank { pub index: u8, pub size: usize, @@ -47,165 +20,48 @@ pub struct Bank { pub bootable: bool, } -impl GlobalHeader { - /// Attempts to retrieve a header from flash. - pub fn retrieve(flash: &mut F) -> Result - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - // Global header is always at the end of the readable region - let address = flash.range().1 - size_of::(); - - // NOTE(Safety): It is safe to deserialize here since we're checking the magic number for - // validity. It will only cause UB when the structs in this file have been modified AND the - // magic value at the top has not. - let header: Self = block!(unsafe { flash.deserialize(address) })?; - if header.magic == MAGIC { - Ok(header) - } else { - Err(Error::FlashCorrupted) - } - } - - /// Writes a default global header to flash at the right location. - pub fn format_default(flash: &mut F) -> Result<(), Error> - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - let default_header = Self { magic: MAGIC, test_buffer: [0x00; 4] }; - // Global header is always at the end of the readable region - let address = flash.range().1 - size_of::(); - - // NOTE(Safety): It is safe to serialize here since the type is defined in this file, and - // we guarantee it doesn't contain references, that it's repr C, and that it will be stored - // alongside a magic number that guarantees its safe retrieval from flash. - Ok(block!(unsafe { flash.serialize(&default_header, address) })?) - } +/// Image descriptor +#[derive(Clone)] +pub struct Image { + size: usize, + location: A, + bootable: bool, } -impl ImageHeader { - /// Retrieves an image header from a given bank in flash memory. - pub fn retrieve(flash: &mut F, bank: &Bank) -> Result +pub fn image_at(flash: &mut F, bank: Bank) -> Result, Error> where A: Address, F: flash::ReadWrite
, Error: From, { - // Image headers are stored at the *end* of images to make sure the binary is aligned - let address = bank.location + bank.size; - // NOTE(Safety): It is safe to deserialize here since we're checking the magic number for - // validity. It will only cause UB when the structs in this file have been modified AND the - // magic value at the top has not. - let header: Self = block!(unsafe { flash.deserialize(address) })?; - if header.magic == MAGIC { - Ok(header) - } else { - Err(Error::FlashCorrupted) - } - } - - /// Writes a default image header to flash at a given location - pub fn format_default(flash: &mut F, bank: &Bank) -> Result<(), Error> - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - // Image headers are stored at the *end* of images to make sure the binary is aligned - let address = bank.location + bank.size; - let default_header = Self { magic: MAGIC, size: 0, crc: 0, name: None }; - // NOTE(Safety): It is safe to serialize here since the type is defined in this file, and - // we guarantee it doesn't contain references, that it's repr C, and that it will be stored - // alongside a magic number that guarantees its safe retrieval from flash. - Ok(block!(unsafe { flash.serialize(&default_header, address) })?) - } - - /// Attempts to write a header to a given bank of flash. - pub fn write(flash: &mut F, bank: &Bank, size: usize) -> Result<(), Error> - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - // Header can **only ever** be written if the image is valid. - let crc = Self::validate_image(flash, bank.location, size)?; - // Image headers are stored at the *end* of images to make sure the binary is aligned - let address = bank.location + bank.size; - let header = ImageHeader { - name: None, // TODO support named images - magic: MAGIC, - size, - crc, - }; - - block!(unsafe { flash.serialize(&header, address) })?; - if bank.sanity_check(flash).is_err() { - Self::format_default(flash, bank).expect("FATAL: Flash unrecoverably corrupted"); - return Err(Error::FlashCorrupted); - } - Ok(()) - } - - /// Performs a CRC check on a given image. - pub fn validate_image(flash: &mut F, location: A, size: usize) -> Result - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - if size <= size_of::() { - return Err(Error::BankEmpty); - } - - let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - let mut byte_index = 0usize; - let mut digest = crc32::Digest::new(crc32::IEEE); - let size_before_crc = size.saturating_sub(size_of::()); - - while byte_index < size_before_crc { - let remaining_size = size_before_crc.saturating_sub(byte_index); - let bytes_to_read = min(TRANSFER_BUFFER_SIZE, remaining_size); - let slice = &mut buffer[0..bytes_to_read]; - block!(flash.read(location + byte_index, slice))?; - digest.write(slice); - byte_index += bytes_to_read; - } - - let mut crc_bytes = [0u8; 4]; - block!(flash.read(location + byte_index, &mut crc_bytes))?; - let crc = u32::from_le_bytes(crc_bytes); - let calculated_crc = digest.sum32(); - if crc == calculated_crc { - Ok(crc) - } else { - Err(Error::CrcInvalid) - } - } -} - -impl Bank { - /// Ensures that a bank's CRC is still valid and reflects the image within. - pub fn sanity_check(&self, flash: &mut F) -> Result<(), Error> - where - F: flash::ReadWrite
, - Error: From, - { - let header = ImageHeader::retrieve(flash, self)?; - let header_crc = header.crc; - let crc_location = self.location + header.size - size_of::(); - let mut crc_bytes = [0u8; 4]; - block!(flash.read(crc_location, &mut crc_bytes))?; - let stored_crc = u32::from_le_bytes(crc_bytes); - if header_crc == stored_crc { - Ok(()) - } else { - Err(Error::CrcInvalid) - } - } + unimplemented!(); + //if size <= size_of::() { + // return Err(Error::BankEmpty); + //} + + //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; + //let mut byte_index = 0usize; + //let mut digest = crc32::Digest::new(crc32::IEEE); + //let size_before_crc = size.saturating_sub(size_of::()); + + //while byte_index < size_before_crc { + // let remaining_size = size_before_crc.saturating_sub(byte_index); + // let bytes_to_read = min(TRANSFER_BUFFER_SIZE, remaining_size); + // let slice = &mut buffer[0..bytes_to_read]; + // block!(flash.read(location + byte_index, slice))?; + // digest.write(slice); + // byte_index += bytes_to_read; + //} + + //let mut crc_bytes = [0u8; 4]; + //block!(flash.read(location + byte_index, &mut crc_bytes))?; + //let crc = u32::from_le_bytes(crc_bytes); + //let calculated_crc = digest.sum32(); + //if crc == calculated_crc { + // Ok(crc) + //} else { + // Err(Error::CrcInvalid) + //} } #[cfg(test)] @@ -224,27 +80,6 @@ mod tests { } #[test] - fn writing_header_with_correct_crc() { - // Given - let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; - let image_with_crc = [0xAAu8, 0xBB, /*CRC*/ 0x98, 0x2c, 0x82, 0x49]; - flash.write(Address(0), &image_with_crc).unwrap(); - - // Then - ImageHeader::write(&mut flash, &bank, image_with_crc.len()).unwrap(); - } - - #[test] - fn attempting_to_write_header_with_wrong_crc() { - // Given - let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; - let image_with_crc = [0xAAu8, 0xBB, /*CRC*/ 0x01, 0x02, 0x03, 0x04]; - flash.write(Address(0), &image_with_crc).unwrap(); - - // Then - let _result = ImageHeader::write(&mut flash, &bank, image_with_crc.len()).unwrap_err(); - assert!(matches!(Error::CrcInvalid, _result)); + fn retrieving_image_from_flash() { } } diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 29347c22..70f11581 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -16,32 +16,27 @@ const EXTERNAL_NUMBER_OF_BANKS: usize = 2; const EXTERNAL_BANK_MAX_IMAGE_SIZE: usize = { let (start, end) = (n25q128a_flash::MemoryMap::location(), n25q128a_flash::MemoryMap::end()); let total_size = (end.0 - start.0) as usize; - let size_without_header = total_size - size_of::(); - let size_per_image = size_without_header / EXTERNAL_NUMBER_OF_BANKS; - size_per_image - size_of::() + total_size / EXTERNAL_NUMBER_OF_BANKS }; const MCU_NUMBER_OF_BANKS: usize = 1; const MCU_BANK_MAX_IMAGE_SIZE: usize = { let (start, end) = (flash::MemoryMap::writable_start(), flash::MemoryMap::writable_end()); let total_size = (end.0 - start.0) as usize; - let size_without_header = total_size - size_of::(); - let size_per_image = size_without_header / MCU_NUMBER_OF_BANKS; - size_per_image - size_of::() + total_size / MCU_NUMBER_OF_BANKS }; const fn min(a: usize, b: usize) -> usize { if a < b { a } else { b } } const IMAGE_SIZE: usize = min(MCU_BANK_MAX_IMAGE_SIZE, EXTERNAL_BANK_MAX_IMAGE_SIZE); -const IMAGE_SIZE_WITH_HEADER: usize = IMAGE_SIZE + size_of::(); const fn external_image_offset(index: usize) -> n25q128a_flash::Address { n25q128a_flash::Address(n25q128a_flash::MemoryMap::location().0 - + (index * IMAGE_SIZE_WITH_HEADER) as u32) + + (index * IMAGE_SIZE) as u32) } const fn mcu_image_offset(index: usize) -> flash::Address { flash::Address(flash::MemoryMap::writable_start().0 - + (index * IMAGE_SIZE_WITH_HEADER) as u32) + + (index * IMAGE_SIZE) as u32) } static MCU_BANKS: [image::Bank; MCU_NUMBER_OF_BANKS] = [ From a94252fcc4e790746adb0a3858e9f6e6f63dc784 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 15:20:08 +0000 Subject: [PATCH 36/67] Refactor run function. --- tools/signing_tool/src/main.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index c82b1000..daa72319 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -11,18 +11,14 @@ use std::{ process, }; -fn run_with_files(image: File, key: File) -> Result { - sign_file(image, key).map(|()| String::from("Successfully appended signature to image.")) -} - -fn run_with_file_names(image: String, key: String) -> Result { +fn run(image: String, key: String) -> Result<(), Error> { let image_file = OpenOptions::new() .read(true) .append(true) .open(image) .map_err(|_| Error::FileOpenFailed(e::File::Image))?; let key_file = File::open(key).map_err(|_| Error::FileOpenFailed(e::File::Key))?; - run_with_files(image_file, key_file) + sign_file(image_file, key_file) } fn main() { @@ -39,9 +35,9 @@ fn main() { let image = matches.value_of("image").unwrap().to_owned(); let private_key = matches.value_of("private_key").unwrap().to_owned(); - match run_with_file_names(image, private_key) { - Ok(s) => { - println!("{}", s); + match run(image, private_key) { + Ok(()) => { + println!("Successfully appended signature to image."); } Err(s) => { eprintln!("{}", s); From 4db55d8b29a63ca7f766ed668c03e1292b926d2f Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 15:34:25 +0000 Subject: [PATCH 37/67] Make main return Result, replacing process::exit call. --- tools/signing_tool/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/signing_tool/src/main.rs b/tools/signing_tool/src/main.rs index daa72319..82af3cc0 100644 --- a/tools/signing_tool/src/main.rs +++ b/tools/signing_tool/src/main.rs @@ -6,10 +6,7 @@ use crate::{ signing::sign_file, }; use clap::clap_app; -use std::{ - fs::{File, OpenOptions}, - process, -}; +use std::fs::{File, OpenOptions}; fn run(image: String, key: String) -> Result<(), Error> { let image_file = OpenOptions::new() @@ -21,7 +18,7 @@ fn run(image: String, key: String) -> Result<(), Error> { sign_file(image_file, key_file) } -fn main() { +fn main() -> Result<(), String> { let matches = clap_app!(app => (name: env!("CARGO_PKG_NAME")) (version: env!("CARGO_PKG_VERSION")) @@ -38,10 +35,8 @@ fn main() { match run(image, private_key) { Ok(()) => { println!("Successfully appended signature to image."); + Ok(()) } - Err(s) => { - eprintln!("{}", s); - process::exit(1); - } + Err(e) => Err(e.to_string()), } } From 6ed30a41d452d2efa74858c47661e984407e6e13 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Mon, 18 Jan 2021 15:39:51 +0000 Subject: [PATCH 38/67] Remove unneeded create_zeroed_u8_vec function. --- tools/signing_tool/src/signing.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tools/signing_tool/src/signing.rs b/tools/signing_tool/src/signing.rs index 9df2bb2c..b8cfd9ef 100644 --- a/tools/signing_tool/src/signing.rs +++ b/tools/signing_tool/src/signing.rs @@ -31,14 +31,6 @@ fn read_key(mut file: File) -> Result, Error> { base64::decode(encoded).map_err(|_| Error::KeyParseFailed) } -fn create_zeroed_u8_vec(size: usize) -> Vec { - let mut vector = Vec::::with_capacity(size); - for _ in 0..size { - vector.push(0); - } - vector -} - /// Reads the contents of `file` and signs it using RSA/SHA256 with the key in `key_file`. /// NOTE: This assumes that `file` is in read/append mode and the key is PKCS8. pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { @@ -48,7 +40,7 @@ pub fn sign_file(mut file: File, key_file: File) -> Result<(), Error> { RsaKeyPair::from_pkcs8(&raw_key).map_err(|_| Error::KeyParseFailed)?; let rng = SystemRandom::new(); - let mut signature = create_zeroed_u8_vec(key.public_modulus_len()); + let mut signature = vec![0u8; key.public_modulus_len()]; key.sign(&RSA_PKCS1_SHA256, &rng, &plaintext, &mut signature) .map_err(|_| Error::SignatureGenerationFailed)?; From f80b4478ae3fccf57bbc2d0c0626cb6adcc47b92 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 19 Jan 2021 11:02:46 +0100 Subject: [PATCH 39/67] update defmt --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 936fdc23..0444d961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,8 @@ panic-abort = "*" nb = "*" paste = "*" static_assertions = "*" -defmt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } -defmt-rtt = { git = "https://github.com/knurling-rs/defmt.git", branch = "main" } +defmt = "0.1" +defmt-rtt = "0.1" [dependencies.nom] version = "*" From 2f8e30f796c9dece08888cc00d49033dfc6b8a00 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 19 Jan 2021 13:22:09 +0100 Subject: [PATCH 40/67] Incorporate new digest utilities --- .vim/coc-settings.json | 4 --- Cargo.toml | 10 +++--- src/devices/boot_manager.rs | 2 +- src/devices/bootloader.rs | 2 +- src/devices/image.rs | 66 ++++++++++++++++--------------------- src/utilities/buffer.rs | 63 ----------------------------------- src/utilities/iterator.rs | 32 ------------------ 7 files changed, 35 insertions(+), 144 deletions(-) delete mode 100644 .vim/coc-settings.json delete mode 100644 src/utilities/buffer.rs delete mode 100644 src/utilities/iterator.rs diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json deleted file mode 100644 index d8c165a5..00000000 --- a/.vim/coc-settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", - "rust-analyzer.diagnostics.disabled": [ "macro-error" ] -} diff --git a/Cargo.toml b/Cargo.toml index 4f9a2f72..01b44b81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,14 +36,14 @@ cortex-m-semihosting = "0.3.3" nb = "*" panic-abort = "*" static_assertions = "*" -defmt = { git = "https://github.com/knurling-rs/defmt", rev = "c4461eb148461e54d9a1e203cf01393f07ce84f0" } -defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "c4461eb148461e54d9a1e203cf01393f07ce84f0" } +defmt = "0.1" +defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -#path = "blue_hal" -git = "ssh://git@github.com/absw/blue_hal.git" -branch = "main" +path = "blue_hal" +#git = "ssh://git@github.com/absw/blue_hal.git" +#branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 7cca4e00..0bd5d66a 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -10,8 +10,8 @@ use blue_hal::{ stm32pac::SCB, utilities::{buffer::CollectSlice, xmodem}, }; -use defmt::info; use core::{array::IntoIter, cmp::min}; +use defmt::info; use nb::block; pub struct BootManager diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 211a0b55..f2bb8779 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -51,7 +51,7 @@ where match self.restore() { Ok(()) => self.boot(default_bank).expect("FATAL: Failed to boot from verified image!"), Err(e) => { - error!("Failed to restore with error: {}", e); + error!("Failed to restore with error: {:?}", e); info!("Proceeding to recovery mode..."); unimplemented!("Recovery Mode"); } diff --git a/src/devices/image.rs b/src/devices/image.rs index 0e668cf2..191469d6 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,8 +1,5 @@ use crate::error::Error; -use blue_hal::{ - hal::flash::{self, UnportableDeserialize, UnportableSerialize}, - utilities::memory::Address, -}; +use blue_hal::{hal::flash::{self, UnportableDeserialize, UnportableSerialize}, utilities::{buffer::find_subsequence, iterator::UntilSequence, memory::Address}}; use core::{cmp::min, mem::size_of}; use crc::{crc32, Hasher32}; use nb::{self, block}; @@ -29,39 +26,33 @@ pub struct Image { } pub fn image_at(flash: &mut F, bank: Bank) -> Result, Error> - where - A: Address, - F: flash::ReadWrite
, - Error: From, - { - unimplemented!(); - //if size <= size_of::() { - // return Err(Error::BankEmpty); - //} +where + A: Address, + F: flash::ReadWrite
, + Error: From, +{ + let bytes = flash.bytes(bank.location).until_sequence(MAGIC_STRING.as_bytes()); + let (size, digest) = bytes.fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { + size += 1; + digest.write(&[byte]); + (size, digest) + }); + let calculated_crc = digest.sum32(); - //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - //let mut byte_index = 0usize; - //let mut digest = crc32::Digest::new(crc32::IEEE); - //let size_before_crc = size.saturating_sub(size_of::()); - - //while byte_index < size_before_crc { - // let remaining_size = size_before_crc.saturating_sub(byte_index); - // let bytes_to_read = min(TRANSFER_BUFFER_SIZE, remaining_size); - // let slice = &mut buffer[0..bytes_to_read]; - // block!(flash.read(location + byte_index, slice))?; - // digest.write(slice); - // byte_index += bytes_to_read; - //} - - //let mut crc_bytes = [0u8; 4]; - //block!(flash.read(location + byte_index, &mut crc_bytes))?; - //let crc = u32::from_le_bytes(crc_bytes); - //let calculated_crc = digest.sum32(); - //if crc == calculated_crc { - // Ok(crc) - //} else { - // Err(Error::CrcInvalid) - //} + let crc_size_bytes = 4usize; + let crc_offset = size - crc_size_bytes; + let mut crc_bytes = [0u8; 4]; + block!(flash.read(bank.location + crc_offset, &mut crc_bytes))?; + let crc = u32::from_le_bytes(crc_bytes); + if crc == calculated_crc { + Ok(Image { + size, + location: bank.location, + bootable: bank.bootable + }) + } else { + Err(Error::CrcInvalid) + } } #[cfg(test)] @@ -80,6 +71,5 @@ mod tests { } #[test] - fn retrieving_image_from_flash() { - } + fn retrieving_image_from_flash() {} } diff --git a/src/utilities/buffer.rs b/src/utilities/buffer.rs deleted file mode 100644 index 9b827718..00000000 --- a/src/utilities/buffer.rs +++ /dev/null @@ -1,63 +0,0 @@ -pub trait CollectSlice: Iterator { - /// Collects an iterator into a given slice, returning the number of collected items. - fn collect_slice(&mut self, slice: &mut [Self::Item]) -> usize; -} - -pub trait TryCollectSlice: Iterator { - type Element; - type Error; - - /// Attempts to collect an iterator into a given slice, returning the number of collected items. - fn try_collect_slice(&mut self, slice: &mut [Self::Element]) -> Result; -} - -impl CollectSlice for I { - fn collect_slice(&mut self, slice: &mut [Self::Item]) -> usize { - slice.iter_mut().zip(self).fold(0, |count, (dest, item)| { - *dest = item; - count + 1 - }) - } -} - -impl TryCollectSlice for I -where - I: Iterator>, -{ - type Element = T; - type Error = E; - fn try_collect_slice(&mut self, slice: &mut [Self::Element]) -> Result { - slice.iter_mut().zip(self).try_fold(0, |count, (dest, item)| { - *dest = item?; - Ok(count + 1) - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn collecting_various_types_in_slices() { - const ELEMENTS: usize = 10; - let mut ints = [0usize; ELEMENTS]; - assert_eq!(ELEMENTS, (0..ELEMENTS).collect_slice(&mut ints)); - assert_eq!(5, ints[5]); - - let mut letters = ['a'; ELEMENTS]; - assert_eq!(3, (0..3u8).map(|i| ('a' as u8 + i) as char).collect_slice(&mut letters)); - assert_eq!('c', letters[2]); - } - - #[test] - fn collecting_fallibly() { - const ELEMENTS: usize = 10; - let mut ints = [0u8; ELEMENTS]; - let to_collect: [Result; 3] = [Ok(3), Ok(2), Err(())]; - assert!(to_collect.iter().copied().try_collect_slice(&mut ints).is_err()); - - let to_collect: [Result; 3] = [Ok(3), Ok(2), Ok(1)]; - assert_eq!(Ok(3), to_collect.iter().copied().try_collect_slice(&mut ints)); - } -} diff --git a/src/utilities/iterator.rs b/src/utilities/iterator.rs deleted file mode 100644 index c612bc56..00000000 --- a/src/utilities/iterator.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Iterator utilities - -pub trait Unique { - /// Returns true if no element equals any other, false otherwise. - fn all_unique(self) -> bool; -} - -impl, I: PartialEq> Unique for T { - fn all_unique(mut self) -> bool { - // O(n^2), could do with optimisation. Difficult - // to optimise without a hash set (no heap) - while let Some(element) = self.next() { - if self.clone().any(|e| e == element) { - return false; - } - } - true - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn all_unique_in_various_scenarios() { - assert!([3, 4, 1, 5].iter().all_unique()); - assert!(![1, 2, 3, 3, 2].iter().all_unique()); - assert!(["fish", "foot", "fly", "foresight"].iter().all_unique()); - assert!(![None, Some(3), Some(5), None].iter().all_unique()); - } -} From b5932749f8a0e004ea11301542ba98a7f55f2944 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 19 Jan 2021 15:56:29 +0100 Subject: [PATCH 41/67] Passing image retrieval from flash test --- src/devices/boot_manager.rs | 2 +- src/devices/bootloader.rs | 1 - src/devices/image.rs | 39 ++++++++++++++++++++++++++----------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 0bd5d66a..a1da5cf6 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -2,7 +2,7 @@ use super::{ cli::{file_transfer, Cli}, - image::{self, TRANSFER_BUFFER_SIZE}, + image::self, }; use crate::error::Error; use blue_hal::{ diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index f2bb8779..eead1164 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -10,7 +10,6 @@ use blue_hal::hal::flash; use core::{cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; use defmt::{error, info}; -use image::TRANSFER_BUFFER_SIZE; use nb::block; pub struct Bootloader diff --git a/src/devices/image.rs b/src/devices/image.rs index 191469d6..41c0dbe1 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,12 +1,10 @@ -use crate::error::Error; -use blue_hal::{hal::flash::{self, UnportableDeserialize, UnportableSerialize}, utilities::{buffer::find_subsequence, iterator::UntilSequence, memory::Address}}; -use core::{cmp::min, mem::size_of}; +use blue_hal::{hal::flash, utilities::{iterator::UntilSequence, memory::Address}}; use crc::{crc32, Hasher32}; use nb::{self, block}; -pub(crate) const TRANSFER_BUFFER_SIZE: usize = 2048usize; +use crate::error::Error; -/// This string must terminate any valid images, after CRC +/// This string must terminate any valid images, followed by CRC const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; #[derive(Clone, Copy, Debug)] @@ -18,7 +16,7 @@ pub struct Bank { } /// Image descriptor -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct Image { size: usize, location: A, @@ -31,19 +29,20 @@ where F: flash::ReadWrite
, Error: From, { - let bytes = flash.bytes(bank.location).until_sequence(MAGIC_STRING.as_bytes()); - let (size, digest) = bytes.fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { + let bytes = flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()); + let (size, digest) = bytes.inspect(|b| {println!("{:x}", b);}).fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { size += 1; digest.write(&[byte]); (size, digest) }); let calculated_crc = digest.sum32(); + println!("Calculated: {:x}", calculated_crc); - let crc_size_bytes = 4usize; - let crc_offset = size - crc_size_bytes; + let crc_offset = size + MAGIC_STRING.len(); let mut crc_bytes = [0u8; 4]; block!(flash.read(bank.location + crc_offset, &mut crc_bytes))?; let crc = u32::from_le_bytes(crc_bytes); + println!("Retrieved: {:x}", crc); if crc == calculated_crc { Ok(Image { size, @@ -70,6 +69,24 @@ mod tests { fn from(_: FakeError) -> Self { Error::DeviceError("Something fake happened") } } + fn test_image_with_crc() -> [u8; 38] { + let mut array = [0u8; 38]; + array[..2].copy_from_slice(&[0xAAu8, 0xBB]); // Image + array[2..34].copy_from_slice(MAGIC_STRING.as_bytes()); + array[34..].copy_from_slice(&[0x98, 0x2c, 0x82, 0x49,]); // CRC + array + } + #[test] - fn retrieving_image_from_flash() {} + fn retrieving_image_from_flash() { + let mut flash = FakeFlash::new(Address(0)); + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let image_with_crc = test_image_with_crc(); + flash.write(Address(0), &image_with_crc).unwrap(); + assert_eq!(Some(Image { + size: 2, + location: bank.location, + bootable: bank.bootable + }), image_at(&mut flash, bank).ok()); + } } From e8cb22d461b69b0d78474e17cfb8ac6964957026 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 19 Jan 2021 16:00:15 +0100 Subject: [PATCH 42/67] Add edge case tests --- src/devices/image.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/devices/image.rs b/src/devices/image.rs index 41c0dbe1..2848d278 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -83,10 +83,32 @@ mod tests { let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; let image_with_crc = test_image_with_crc(); flash.write(Address(0), &image_with_crc).unwrap(); - assert_eq!(Some(Image { + assert_eq!(Ok(Image { size: 2, location: bank.location, bootable: bank.bootable - }), image_at(&mut flash, bank).ok()); + }), image_at(&mut flash, bank)); + } + + #[test] + fn retrieving_broken_image_fails() { + let mut flash = FakeFlash::new(Address(0)); + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let mut image_with_crc = test_image_with_crc(); + image_with_crc[0] = 0xFF; // This will corrupt the image, making the CRC obsolete + flash.write(Address(0), &image_with_crc).unwrap(); + assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); + + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let mut image_with_crc = test_image_with_crc(); + image_with_crc[4] = 0xFF; // This will break the CRC directly + flash.write(Address(0), &image_with_crc).unwrap(); + assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); + + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let mut image_with_crc = test_image_with_crc(); + image_with_crc[12] = 0xFF; // The magic string is not present to delineate the image + flash.write(Address(0), &image_with_crc).unwrap(); + assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); } } From 6ebdc353eae9862058f258eb96cea3d0ff582b96 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 19 Jan 2021 16:35:51 +0100 Subject: [PATCH 43/67] WIP image restore --- src/devices/boot_manager.rs | 6 +- src/devices/bootloader.rs | 119 ++++++++++++++++-------------------- src/devices/image.rs | 16 +++-- src/error.rs | 4 ++ 4 files changed, 71 insertions(+), 74 deletions(-) diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index a1da5cf6..2a96cb20 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -8,11 +8,11 @@ use crate::error::Error; use blue_hal::{ hal::{flash, serial}, stm32pac::SCB, - utilities::{buffer::CollectSlice, xmodem}, + utilities::xmodem, }; -use core::{array::IntoIter, cmp::min}; + use defmt::info; -use nb::block; + pub struct BootManager where diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index eead1164..877978a4 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -4,7 +4,7 @@ //! the exception of how to construct one. Construction is //! handled by the `port` module as it depends on board //! specific information. -use super::image; +use super::image::{self, CRC_SIZE_BYTES, MAGIC_STRING}; use crate::error::Error; use blue_hal::hal::flash; use core::{cmp::min, mem::size_of}; @@ -25,6 +25,8 @@ where pub(crate) mcu_banks: &'static [image::Bank<::Address>], } +const DEFAULT_BOOT_BANK: u8 = 1; + impl Bootloader where EXTF: flash::ReadWrite, @@ -40,15 +42,15 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - let default_bank = 1; - match self.boot(default_bank).unwrap_err() { + info!("Attempting to boot from default bank"); + match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { Error::BankInvalid => info!("Attempted to boot from invalid bank. Restoring image..."), Error::BankEmpty => info!("Attempted to boot from empty bank. Restoring image..."), _ => info!("Unexpected boot error. Restoring image..."), }; match self.restore() { - Ok(()) => self.boot(default_bank).expect("FATAL: Failed to boot from verified image!"), + Ok(()) => self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!"), Err(e) => { error!("Failed to restore with error: {:?}", e); info!("Proceeding to recovery mode..."); @@ -60,7 +62,12 @@ where /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. fn restore(&mut self) -> Result<(), Error> { - unimplemented!("Image Restoration"); + for bank in 0..self.external_banks.len() { + if self.copy_image(DEFAULT_BOOT_BANK, bank as u8).is_ok() { + return Ok(()) + }; + } + Err(Error::NoImageToRestoreFrom) } /// Boots into a given memory bank. @@ -72,29 +79,23 @@ where return Err(Error::BankInvalid); } - unimplemented!(); - - //let header = image::ImageHeader::retrieve(&mut self.mcu_flash, &bank)?; - //if header.size == 0 { - // return Err(Error::BankEmpty); - //} - - //let image_location_raw: usize = bank.location.into(); - - //// NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an - //// entirely different firmware image! We have to assume everything is at the right place, - //// or literally anything could happen here. After the interrupts are disabled, there is - //// no turning back. - //unsafe { - // let initial_stack_pointer = *(image_location_raw as *const u32); - // let reset_handler_pointer = - // *((image_location_raw + size_of::()) as *const u32) as *const (); - // let reset_handler = core::mem::transmute::<*const (), fn() -> !>(reset_handler_pointer); - // cortex_m::interrupt::disable(); - // (*SCB::ptr()).vtor.write(image_location_raw as u32); - // cortex_m::register::msp::write(initial_stack_pointer); - // reset_handler() - //} + image::image_at(&mut self.mcu_flash, *bank)?; + let image_location_raw: usize = bank.location.into(); + + // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an + // entirely different firmware image! We have to assume everything is at the right place, + // or literally anything could happen here. After the interrupts are disabled, there is + // no turning back. + unsafe { + let initial_stack_pointer = *(image_location_raw as *const u32); + let reset_handler_pointer = + *((image_location_raw + size_of::()) as *const u32) as *const (); + let reset_handler = core::mem::transmute::<*const (), fn() -> !>(reset_handler_pointer); + cortex_m::interrupt::disable(); + (*SCB::ptr()).vtor.write(image_location_raw as u32); + cortex_m::register::msp::write(initial_stack_pointer); + reset_handler() + } } /// Runs a self test on MCU flash. @@ -119,44 +120,30 @@ where /// Copy from external bank to MCU bank pub fn copy_image(&mut self, input_bank_index: u8, output_bank_index: u8) -> Result<(), Error> { - unimplemented!(); - //let (input_bank, output_bank) = ( - // self.external_banks() - // .find(|b| b.index == input_bank_index) - // .ok_or(Error::BankInvalid)?, - // self.mcu_banks().find(|b| b.index == output_bank_index).ok_or(Error::BankInvalid)?, - //); - //let input_header = image::ImageHeader::retrieve(&mut self.external_flash, &input_bank)?; - //if input_header.size > output_bank.size { - // return Err(Error::ImageTooBig); - //} else if input_header.size == 0 { - // return Err(Error::BankEmpty); - //} - - //input_bank.sanity_check(&mut self.external_flash)?; - //// Output header must be re-formatted before any writing takes place, to ensure - //// a valid header and an invalid image never coexist. - //image::ImageHeader::format_default(&mut self.mcu_flash, &output_bank)?; - - //let input_image_start_address = input_bank.location; - //let output_image_start_address = output_bank.location; - - //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - //let mut byte_index = 0usize; - - //while byte_index < input_header.size { - // let bytes_to_read = - // min(TRANSFER_BUFFER_SIZE, input_header.size.saturating_sub(byte_index)); - // block!(self - // .external_flash - // .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; - // block!(self - // .mcu_flash - // .write(output_image_start_address + byte_index, &buffer[0..bytes_to_read]))?; - // byte_index += bytes_to_read; - //} - - //image::ImageHeader::write(&mut self.mcu_flash, &output_bank, input_header.size) + let input_bank = self.external_banks[input_bank_index as usize]; + let output_bank = self.mcu_banks[output_bank_index as usize]; + let input_image = image::image_at(&mut self.external_flash, self.external_banks[input_bank_index as usize])?; + + let input_image_start_address = input_bank.location; + let output_image_start_address = output_bank.location; + + const TRANSFER_BUFFER_SIZE: usize = 2048; + let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; + let mut byte_index = 0usize; + + let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); + while byte_index < total_size { + let bytes_to_read = + min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); + block!(self + .external_flash + .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; + block!(self + .mcu_flash + .write(output_image_start_address + byte_index, &buffer[0..bytes_to_read]))?; + byte_index += bytes_to_read; + } + Ok(()) } fn test_flash_read_write_cycle(flash: &mut F) -> Result<(), Error> diff --git a/src/devices/image.rs b/src/devices/image.rs index 2848d278..711796ae 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,11 +1,13 @@ use blue_hal::{hal::flash, utilities::{iterator::UntilSequence, memory::Address}}; use crc::{crc32, Hasher32}; +use defmt::info; use nb::{self, block}; use crate::error::Error; /// This string must terminate any valid images, followed by CRC -const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; +pub const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; +pub const CRC_SIZE_BYTES: usize = 4; #[derive(Clone, Copy, Debug)] pub struct Bank { @@ -23,6 +25,10 @@ pub struct Image { bootable: bool, } +impl Image { + pub fn size(&self) -> usize { self.size } +} + pub fn image_at(flash: &mut F, bank: Bank) -> Result, Error> where A: Address, @@ -30,19 +36,19 @@ where Error: From, { let bytes = flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()); - let (size, digest) = bytes.inspect(|b| {println!("{:x}", b);}).fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { + info!("Verifying image..."); + let (size, digest) = bytes.fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { size += 1; digest.write(&[byte]); (size, digest) }); + info!("Done verifying image."); let calculated_crc = digest.sum32(); - println!("Calculated: {:x}", calculated_crc); let crc_offset = size + MAGIC_STRING.len(); - let mut crc_bytes = [0u8; 4]; + let mut crc_bytes = [0u8; CRC_SIZE_BYTES]; block!(flash.read(bank.location + crc_offset, &mut crc_bytes))?; let crc = u32::from_le_bytes(crc_bytes); - println!("Retrieved: {:x}", crc); if crc == calculated_crc { Ok(Image { size, diff --git a/src/error.rs b/src/error.rs index 0ff4af2c..11084640 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,7 @@ pub enum Error { BankEmpty, ImageTooBig, FlashCorrupted, + NoImageToRestoreFrom, CrcInvalid, } @@ -83,6 +84,9 @@ impl Error { Error::CrcInvalid => uwriteln!(serial, "[LogicError] -> Image CRC is invalid"), Error::NotEnoughData => { uwriteln!(serial, "[Transfer Error] -> Not enough image data received") + }, + Error::NoImageToRestoreFrom => { + uwriteln!(serial, "[Logic Error] -> No image to restore from") } } .ok() From 65b413c1d240f01e1e7f188d98e103f5978c2017 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:37:40 +0000 Subject: [PATCH 44/67] Add actions.yml file. --- .github/workflows/actions.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/actions.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 00000000..e4b71290 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,28 @@ +name: actions +on: [push] +jobs: + build-loadstone: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup + uses: webfactory/ssh-agent@v0.4.1 + with: + ssh-private-key: | + ${{ secrets.PATHFINDERDELL_PRIVATE_KEY }} + + - name: Cargo initialisation + run: | + ~/.cargo/bin/rustup default nightly + ~/.cargo/bin/rustup update + ~/.cargo/bin/rustup component add llvm-tools-preview + ~/.cargo/bin/rustup target add thumbv7em-none-eabihf + ~/.cargo/bin/cargo install cargo-binutils + + - name: Documentation + run: ~/.cargo/bin/cargo d + + - name: Tests + run: ~/.cargo/bin/cargo test --release From f042b6acd3f8188072f2c139d234c70297813019 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:41:24 +0000 Subject: [PATCH 45/67] Remove non-compiling blinky binary. --- src/bin/blinky.rs | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/bin/blinky.rs diff --git a/src/bin/blinky.rs b/src/bin/blinky.rs deleted file mode 100644 index 79f7dbd4..00000000 --- a/src/bin/blinky.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![cfg_attr(test, allow(unused_attributes))] -#![cfg_attr(all(not(test), target_arch = "arm"), no_std)] -#![cfg_attr(target_arch = "arm", no_main)] - -#[allow(unused_imports)] -use cortex_m_rt::{entry, exception}; - -#[cfg(target_arch = "arm")] -#[entry] -fn main() -> ! { - use loadstone_lib::{ - drivers::{ - led, - stm32f4::{gpio::GpioExt, rcc::Clocks, systick}, - }, - hal::{led::Toggle, time::Seconds}, - stm32pac, - }; - let mut peripherals = stm32pac::Peripherals::take().unwrap(); - let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); - - let gpioe = peripherals.GPIOE.split(&mut peripherals.RCC); - let clocks = Clocks::hardcoded(&peripherals.FLASH, peripherals.RCC); - let systick = systick::SysTick::new(cortex_peripherals.SYST, clocks); - let mut led = led::MonochromeLed::new(gpioe.pe1, led::LogicLevel::Inverted); - - loop { - systick.wait(Seconds(1)); - led.toggle(); - } -} - -#[cfg(not(target_arch = "arm"))] -fn main() {} From 8f7e3f4137e730b148bacb54ee66bad3d5a7b390 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:48:39 +0000 Subject: [PATCH 46/67] Add artefact uploading. --- .github/workflows/actions.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index e4b71290..651472cf 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -26,3 +26,10 @@ jobs: - name: Tests run: ~/.cargo/bin/cargo test --release + + - name: Building + run: ~/.cargo/bin/cargo b + uses: actions/upload-artifact@v2 + with: + name: loadstone + path: target/thumbv7em-none-eabihf/release/ From a6ef8e1713b2d95312dc5ee25bfc7b449c2d9f06 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:50:23 +0000 Subject: [PATCH 47/67] Split building step. --- .github/workflows/actions.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 651472cf..d2e5c7e9 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -29,6 +29,8 @@ jobs: - name: Building run: ~/.cargo/bin/cargo b + + - name: Upload loadstone uses: actions/upload-artifact@v2 with: name: loadstone From 10f0cab81616611ea6132f5e98d3b66cd3c03f6a Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:54:47 +0000 Subject: [PATCH 48/67] Only upload loadstone artefact. --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index d2e5c7e9..677105d2 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -34,4 +34,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: loadstone - path: target/thumbv7em-none-eabihf/release/ + path: target/thumbv7em-none-eabihf/release/loadstone From 083acd26b57a18fdd22dd153a48250ee6e318634 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 15:59:35 +0000 Subject: [PATCH 49/67] Try removing authentication step. --- .github/workflows/actions.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 677105d2..28fa952f 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,12 +7,6 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Setup - uses: webfactory/ssh-agent@v0.4.1 - with: - ssh-private-key: | - ${{ secrets.PATHFINDERDELL_PRIVATE_KEY }} - - name: Cargo initialisation run: | ~/.cargo/bin/rustup default nightly From 0e13ce9430624c1584a63a9ff222f9b12db477d8 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Tue, 19 Jan 2021 16:09:22 +0000 Subject: [PATCH 50/67] Fix incorrect artefact upload. --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 28fa952f..6308e898 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -22,10 +22,10 @@ jobs: run: ~/.cargo/bin/cargo test --release - name: Building - run: ~/.cargo/bin/cargo b + run: ~/.cargo/bin/cargo gen_loadstone - name: Upload loadstone uses: actions/upload-artifact@v2 with: name: loadstone - path: target/thumbv7em-none-eabihf/release/loadstone + path: loadstone.bin From e93ffa344455fb701614121a187e2f100b106dc8 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Wed, 20 Jan 2021 10:03:44 +0000 Subject: [PATCH 51/67] Add test file. --- text | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 text diff --git a/text b/text new file mode 100644 index 00000000..e69de29b From ff527b282c6d34dcf78a95c22e50e83e2803b1d3 Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Wed, 20 Jan 2021 10:35:46 +0000 Subject: [PATCH 52/67] Re-add authentication step. --- .github/workflows/actions.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6308e898..352b828f 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -6,6 +6,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + + - name: Authentication + uses: webfactory/ssh-agent@v0.4.1 + with: + ssh-private-key: | + ${{ secrets.PATHFINDERDELL_PRIVATE_KEY }} - name: Cargo initialisation run: | From 934aa1acc6a7de364df3c17750d97b7f88cd839e Mon Sep 17 00:00:00 2001 From: Arron Speake Date: Wed, 20 Jan 2021 10:40:30 +0000 Subject: [PATCH 53/67] Remove empty text file. --- text | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 text diff --git a/text b/text deleted file mode 100644 index e69de29b..00000000 From 1f6a08263caeef9612a756c9173c7ef6715ca0ae Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 21 Jan 2021 09:42:14 +0100 Subject: [PATCH 54/67] Bring up serial for bootloader --- src/devices/bootloader.rs | 26 ++++++++++++++------- src/devices/image.rs | 2 +- src/ports/stm32f412_discovery/bootloader.rs | 15 ++++++++---- tools/crc_image_tool/src/main.rs | 7 +++++- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 877978a4..d0085a4d 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -6,33 +6,39 @@ //! specific information. use super::image::{self, CRC_SIZE_BYTES, MAGIC_STRING}; use crate::error::Error; -use blue_hal::hal::flash; +use blue_hal::{duprintln, hal::{flash, serial}, uprintln}; use core::{cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; use defmt::{error, info}; use nb::block; +use ufmt::{uwrite, uwriteln}; -pub struct Bootloader +pub struct Bootloader where EXTF: flash::ReadWrite, Error: From, MCUF: flash::ReadWrite, Error: From, + SRL: serial::ReadWrite, + Error: From<::Error>, { pub(crate) external_flash: EXTF, pub(crate) mcu_flash: MCUF, pub(crate) external_banks: &'static [image::Bank<::Address>], pub(crate) mcu_banks: &'static [image::Bank<::Address>], + pub(crate) serial: SRL, } const DEFAULT_BOOT_BANK: u8 = 1; -impl Bootloader +impl Bootloader where EXTF: flash::ReadWrite, Error: From, MCUF: flash::ReadWrite, Error: From, + SRL: serial::ReadWrite, + Error: From<::Error>, { /// Main bootloader routine. Attempts to boot from MCU image, and /// in case of failure proceeds to: @@ -42,18 +48,20 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - info!("Attempting to boot from default bank"); + duprintln!(self.serial, "Attempting to boot from default bank"); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { - Error::BankInvalid => info!("Attempted to boot from invalid bank. Restoring image..."), - Error::BankEmpty => info!("Attempted to boot from empty bank. Restoring image..."), - _ => info!("Unexpected boot error. Restoring image..."), + Error::BankInvalid => duprintln!(self.serial, "Attempted to boot from invalid bank. Restoring image..."), + Error::BankEmpty => duprintln!(self.serial, "Attempted to boot from empty bank. Restoring image..."), + Error::CrcInvalid => duprintln!(self.serial, "Crc invalid for stored image. Restoring image..."), + _ => duprintln!(self.serial, "Unexpected boot error. Restoring image..."), }; match self.restore() { Ok(()) => self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!"), Err(e) => { - error!("Failed to restore with error: {:?}", e); - info!("Proceeding to recovery mode..."); + duprintln!(self.serial, "Failed to restore."); + info!("Error: {:?}", e); + duprintln!(self.serial, "Proceeding to recovery mode..."); unimplemented!("Recovery Mode"); } } diff --git a/src/devices/image.rs b/src/devices/image.rs index 711796ae..beec2a12 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -42,8 +42,8 @@ where digest.write(&[byte]); (size, digest) }); - info!("Done verifying image."); let calculated_crc = digest.sum32(); + info!("Done verifying image. Crc: {:?}, size: {:?}", calculated_crc, size); let crc_offset = size + MAGIC_STRING.len(); let mut crc_bytes = [0u8; CRC_SIZE_BYTES]; diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 70f11581..85a51141 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -2,14 +2,15 @@ use crate::devices::bootloader::Bootloader; use crate::devices::image; use crate::error::Error; -use core::mem::size_of; -use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial, systick::SysTick}}, hal::time, stm32pac}; +use blue_hal::{drivers::{micron::n25q128a_flash::{self, MicronN25q128a}, stm32f4::{flash, qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; use super::pin_configuration::*; // Flash pins and typedefs type QspiPins = (Pb2, Pg6, Pf8, Pf9, Pf7, Pf6); type Qspi = QuadSpi; type ExternalFlash = MicronN25q128a; +type UsartPins = (Pg14, Pg9); +type Serial = serial::Serial; // Serial pins and typedefs const EXTERNAL_NUMBER_OF_BANKS: usize = 2; @@ -48,11 +49,11 @@ pub static EXTERNAL_BANKS: [image::Bank; EXTERNAL_NUMBE image::Bank { index: 3, bootable: false, location: external_image_offset(1), size: IMAGE_SIZE, }, ]; -impl Default for Bootloader { +impl Default for Bootloader { fn default() -> Self { Self::new() } } -impl Bootloader { +impl Bootloader { pub fn new() -> Self { let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); @@ -71,7 +72,11 @@ impl Bootloader { let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); - Bootloader { external_flash, mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS } + let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); + let serial_pins = (gpiog.pg14, gpiog.pg9); + let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); + + Bootloader { external_flash, mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS, serial } } } diff --git a/tools/crc_image_tool/src/main.rs b/tools/crc_image_tool/src/main.rs index d08af4ea..537cbf9c 100644 --- a/tools/crc_image_tool/src/main.rs +++ b/tools/crc_image_tool/src/main.rs @@ -4,6 +4,8 @@ use crc::crc32::{self, Hasher32}; use std::{io::BufReader, io::BufRead, fs::File, io::prelude::*}; use byteorder::{LittleEndian, WriteBytesExt}; +const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; + #[derive(Clap)] #[clap(about = "Tool to calculate and append CRC to firmware images", version = "1.0", author = "Pablo Mansanet ")] struct Opts { @@ -30,9 +32,12 @@ fn main() -> std::io::Result<()> { let mut final_crc = [0u8; 4]; (&mut final_crc[..]).write_u32::(digest.sum32())?; - println!("Appending to the end of {}", &opts.filename); let mut firmware = File::with_options().append(true).open(&opts.filename)?; + println!("Appending to the end of {}", &opts.filename); firmware.write(&final_crc)?; + println!("Appending magic string"); + firmware.write(MAGIC_STRING.as_bytes())?; + println!("Done!"); Ok(()) } From 03064fdf7670c077f22da36c03e1348b218003d2 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 21 Jan 2021 10:56:32 +0100 Subject: [PATCH 55/67] Fix CRC calculation through duplicated image pass --- src/devices/boot_manager.rs | 3 +- src/devices/bootloader.rs | 81 +++++++++++++++++++++++++------- src/devices/cli/commands/mod.rs | 2 +- src/devices/cli/file_transfer.rs | 9 ++-- src/devices/image.rs | 54 ++++++++++++--------- src/error.rs | 2 +- 6 files changed, 102 insertions(+), 49 deletions(-) diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 2a96cb20..2e449eb4 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -2,7 +2,7 @@ use super::{ cli::{file_transfer, Cli}, - image::self, + image, }; use crate::error::Error; use blue_hal::{ @@ -13,7 +13,6 @@ use blue_hal::{ use defmt::info; - pub struct BootManager where EXTF: flash::ReadWrite, diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index d0085a4d..3a42e76c 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -5,11 +5,18 @@ //! handled by the `port` module as it depends on board //! specific information. use super::image::{self, CRC_SIZE_BYTES, MAGIC_STRING}; -use crate::error::Error; -use blue_hal::{duprintln, hal::{flash, serial}, uprintln}; -use core::{cmp::min, mem::size_of}; +use crate::{devices::cli::file_transfer::FileTransfer, error::Error}; +use blue_hal::{ + duprintln, + hal::{ + flash, + serial::{self, Read}, + }, + uprintln, +}; +use core::{array::IntoIter, cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; -use defmt::{error, info}; +use defmt::{error, info, warn}; use nb::block; use ufmt::{uwrite, uwriteln}; @@ -37,7 +44,7 @@ where Error: From, MCUF: flash::ReadWrite, Error: From, - SRL: serial::ReadWrite, + SRL: serial::ReadWrite + serial::TimeoutRead, Error: From<::Error>, { /// Main bootloader routine. Attempts to boot from MCU image, and @@ -48,21 +55,30 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - duprintln!(self.serial, "Attempting to boot from default bank"); + duprintln!(self.serial, "--Loadstone Initialised--"); + duprintln!(self.serial, "Attempting to boot from default bank. This may take some time..."); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { - Error::BankInvalid => duprintln!(self.serial, "Attempted to boot from invalid bank. Restoring image..."), - Error::BankEmpty => duprintln!(self.serial, "Attempted to boot from empty bank. Restoring image..."), - Error::CrcInvalid => duprintln!(self.serial, "Crc invalid for stored image. Restoring image..."), + Error::BankInvalid => { + duprintln!(self.serial, "Attempted to boot from invalid bank. Restoring image...") + } + Error::BankEmpty => { + duprintln!(self.serial, "Attempted to boot from empty bank. Restoring image...") + } + Error::CrcInvalid => { + duprintln!(self.serial, "Crc invalid for stored image. Restoring image...") + } _ => duprintln!(self.serial, "Unexpected boot error. Restoring image..."), }; match self.restore() { - Ok(()) => self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!"), + Ok(()) => { + self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") + } Err(e) => { duprintln!(self.serial, "Failed to restore."); info!("Error: {:?}", e); duprintln!(self.serial, "Proceeding to recovery mode..."); - unimplemented!("Recovery Mode"); + self.recover(); } } } @@ -72,12 +88,29 @@ where fn restore(&mut self) -> Result<(), Error> { for bank in 0..self.external_banks.len() { if self.copy_image(DEFAULT_BOOT_BANK, bank as u8).is_ok() { - return Ok(()) + return Ok(()); }; } Err(Error::NoImageToRestoreFrom) } + fn recover(&mut self) -> ! { + duprintln!(self.serial, "-- Loadstone Recovery Mode --"); + duprintln!(self.serial, "Please send firmware image via XMODEM."); + const BUFFER_SIZE: usize = 2048; + + let bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + + for (i, block) in self.serial.blocks(Some(10)).enumerate() { + nb::block!(self.mcu_flash.write(bank.location + block.len() * i, &block)) + .map_err(|_| Error::DriverError("Failed to flash image during recovery mode.")) + .unwrap(); + } + duprintln!(self.serial, "Finished flashing image."); + duprintln!(self.serial, "Rebooting..."); + SCB::sys_reset(); + } + /// Boots into a given memory bank. pub fn boot(&mut self, bank_index: u8) -> Result { let bank = @@ -87,7 +120,9 @@ where return Err(Error::BankInvalid); } - image::image_at(&mut self.mcu_flash, *bank)?; + let size = image::image_at(&mut self.mcu_flash, *bank)?.size(); + duprintln!(self.serial, "Image verified with size {:?}. Booting.", size); + warn!("Jumping to a new firmware image. This will break `defmt`."); let image_location_raw: usize = bank.location.into(); // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an @@ -128,9 +163,20 @@ where /// Copy from external bank to MCU bank pub fn copy_image(&mut self, input_bank_index: u8, output_bank_index: u8) -> Result<(), Error> { - let input_bank = self.external_banks[input_bank_index as usize]; - let output_bank = self.mcu_banks[output_bank_index as usize]; - let input_image = image::image_at(&mut self.external_flash, self.external_banks[input_bank_index as usize])?; + let input_bank = self + .external_banks + .iter() + .find(|b| b.index == input_bank_index) + .ok_or(Error::BankInvalid)?; + let output_bank = self + .mcu_banks + .iter() + .find(|b| b.index == output_bank_index) + .ok_or(Error::BankInvalid)?; + let input_image = image::image_at( + &mut self.external_flash, + self.external_banks[input_bank_index as usize], + )?; let input_image_start_address = input_bank.location; let output_image_start_address = output_bank.location; @@ -141,8 +187,7 @@ where let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); while byte_index < total_size { - let bytes_to_read = - min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); + let bytes_to_read = min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); block!(self .external_flash .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index f122e95f..d41a76a2 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -27,7 +27,7 @@ commands!( cli, boot_manager, names, helpstrings [ return Err(Error::ArgumentOutOfRange); } uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); - boot_manager.store_image(cli.serial.blocks(), size, bank)?; + boot_manager.store_image(cli.serial.blocks(Some(10)), size, bank)?; uprintln!(cli.serial, "Image transfer complete!"); } else { uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); diff --git a/src/devices/cli/file_transfer.rs b/src/devices/cli/file_transfer.rs index 62a0c167..2955824b 100644 --- a/src/devices/cli/file_transfer.rs +++ b/src/devices/cli/file_transfer.rs @@ -5,11 +5,9 @@ use blue_hal::{ pub const BLOCK_SIZE: usize = xmodem::PAYLOAD_SIZE; -const MAX_RETRIES: u32 = 10; - pub trait FileTransfer: TimeoutRead + Write { - fn blocks(&mut self) -> BlockIterator { - BlockIterator { serial: self, received_block: false, finished: false, block_number: 0 } + fn blocks(&mut self, max_retries: Option) -> BlockIterator { + BlockIterator { serial: self, received_block: false, finished: false, block_number: 0, max_retries } } } @@ -20,6 +18,7 @@ pub struct BlockIterator<'a, S: TimeoutRead + Write + ?Sized> { received_block: bool, finished: bool, block_number: u8, + max_retries: Option, } impl<'a, S: TimeoutRead + Write + ?Sized> Iterator for BlockIterator<'a, S> { @@ -33,7 +32,7 @@ impl<'a, S: TimeoutRead + Write + ?Sized> Iterator for BlockIterator<'a, S> { let mut retries = 0; let mut buffer = [0u8; xmodem::MAX_PACKET_SIZE]; - 'block_loop: while retries < MAX_RETRIES { + 'block_loop: while self.max_retries.is_none() || retries < self.max_retries.unwrap() { let mut buffer_index = 0usize; let message = if self.received_block { xmodem::ACK } else { xmodem::NAK }; diff --git a/src/devices/image.rs b/src/devices/image.rs index beec2a12..7168742b 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,6 +1,9 @@ -use blue_hal::{hal::flash, utilities::{iterator::UntilSequence, memory::Address}}; +use blue_hal::{ + hal::flash, + utilities::{iterator::UntilSequence, memory::Address}, +}; use crc::{crc32, Hasher32}; -use defmt::info; +use defmt::{info, warn}; use nb::{self, block}; use crate::error::Error; @@ -35,26 +38,34 @@ where F: flash::ReadWrite
, Error: From, { - let bytes = flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()); - info!("Verifying image..."); - let (size, digest) = bytes.fold((0, crc32::Digest::new(crc32::IEEE)), |(mut size, mut digest), byte| { - size += 1; - digest.write(&[byte]); - (size, digest) - }); + // Development build shorcut: We're checking that the image does *not* start with 0xFF. This + // will not be part of the final Loadstone release build, but it helps speed up the + // verification for invalid images during development. + if flash.bytes(bank.location).next().ok_or(Error::BankInvalid)? == 0xFF { + return Err(Error::BankEmpty); + } + + // TODO optimise this away so we don't have to scan the image twice. (e.g. with a "window" + // buffer for the CRC); + let image_size_with_crc = flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()).count(); + let image_size = image_size_with_crc.saturating_sub(CRC_SIZE_BYTES); + let image_bytes = flash.bytes(bank.location).take(image_size); + let digest = + image_bytes.fold(crc32::Digest::new(crc32::IEEE), |mut digest, byte| { + digest.write(&[byte]); + digest + }); let calculated_crc = digest.sum32(); - info!("Done verifying image. Crc: {:?}, size: {:?}", calculated_crc, size); + info!("Done verifying image. Crc: {:?}, size: {:?}", calculated_crc, image_size); - let crc_offset = size + MAGIC_STRING.len(); + let crc_offset = image_size; let mut crc_bytes = [0u8; CRC_SIZE_BYTES]; block!(flash.read(bank.location + crc_offset, &mut crc_bytes))?; let crc = u32::from_le_bytes(crc_bytes); + + info!("Retrieved crc: {:?}", crc); if crc == calculated_crc { - Ok(Image { - size, - location: bank.location, - bootable: bank.bootable - }) + Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable }) } else { Err(Error::CrcInvalid) } @@ -79,7 +90,7 @@ mod tests { let mut array = [0u8; 38]; array[..2].copy_from_slice(&[0xAAu8, 0xBB]); // Image array[2..34].copy_from_slice(MAGIC_STRING.as_bytes()); - array[34..].copy_from_slice(&[0x98, 0x2c, 0x82, 0x49,]); // CRC + array[34..].copy_from_slice(&[0x98, 0x2c, 0x82, 0x49]); // CRC array } @@ -89,11 +100,10 @@ mod tests { let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; let image_with_crc = test_image_with_crc(); flash.write(Address(0), &image_with_crc).unwrap(); - assert_eq!(Ok(Image { - size: 2, - location: bank.location, - bootable: bank.bootable - }), image_at(&mut flash, bank)); + assert_eq!( + Ok(Image { size: 2, location: bank.location, bootable: bank.bootable }), + image_at(&mut flash, bank) + ); } #[test] diff --git a/src/error.rs b/src/error.rs index 11084640..727930be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -84,7 +84,7 @@ impl Error { Error::CrcInvalid => uwriteln!(serial, "[LogicError] -> Image CRC is invalid"), Error::NotEnoughData => { uwriteln!(serial, "[Transfer Error] -> Not enough image data received") - }, + } Error::NoImageToRestoreFrom => { uwriteln!(serial, "[Logic Error] -> No image to restore from") } From 0285f7798887ab32afd8c42feef8115bb398cee2 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 21 Jan 2021 11:55:41 +0100 Subject: [PATCH 56/67] End to end bootloader --- Cargo.toml | 6 +++--- src/bin/demo_app.rs | 2 +- src/devices/boot_manager.rs | 37 +++------------------------------ src/devices/bootloader.rs | 36 +++++++++----------------------- src/devices/cli/commands/mod.rs | 6 +----- src/devices/image.rs | 2 +- tools/gdb_erase.txt | 3 +++ tools/gdb_flash_combined.txt | 12 ----------- 8 files changed, 22 insertions(+), 82 deletions(-) create mode 100644 tools/gdb_erase.txt delete mode 100644 tools/gdb_flash_combined.txt diff --git a/Cargo.toml b/Cargo.toml index 01b44b81..f50d6ace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -path = "blue_hal" -#git = "ssh://git@github.com/absw/blue_hal.git" -#branch = "main" +#path = "blue_hal" +git = "ssh://git@github.com/absw/blue_hal.git" +branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/bin/demo_app.rs b/src/bin/demo_app.rs index 35305da9..dfd11e4e 100644 --- a/src/bin/demo_app.rs +++ b/src/bin/demo_app.rs @@ -4,11 +4,11 @@ #[allow(unused_imports)] use cortex_m_rt::{entry, exception}; -use loadstone_lib::devices::boot_manager; #[cfg(target_arch = "arm")] #[entry] fn main() -> ! { + use loadstone_lib::devices::boot_manager; let app = boot_manager::BootManager::new(); app.run(); } diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 2e449eb4..7ca7edf6 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -11,8 +11,6 @@ use blue_hal::{ utilities::xmodem, }; -use defmt::info; - pub struct BootManager where EXTF: flash::ReadWrite, @@ -41,43 +39,15 @@ where pub fn store_image( &mut self, blocks: I, - size: usize, bank: image::Bank, ) -> Result<(), Error> where I: Iterator, { - if size > bank.size { - return Err(Error::ImageTooBig); + for (i, block) in blocks.enumerate() { + nb::block!(self.external_flash.write(bank.location + block.len() * i, &block))?; } - - unimplemented!(); - //// Header must be re-formatted before any writing takes place, to ensure - //// a valid header and an invalid image never coexist. - //image::ImageHeader::format_default(&mut self.external_flash, &bank)?; - - //let mut buffer = [0u8; TRANSFER_BUFFER_SIZE]; - //let address = bank.location; - //let mut bytes_written = 0; - - //let mut bytes = blocks.flat_map(|b| IntoIter::new(b)); - - //loop { - // let distance_to_end = size - bytes_written; - // let received = bytes.collect_slice(&mut buffer); - // if received == 0 { - // break; - // } - // let bytes_to_write = min(distance_to_end, received); - // block!(self.external_flash.write(address + bytes_written, &buffer[0..bytes_to_write]))?; - // bytes_written += bytes_to_write; - //} - - //if bytes_written == size { - // image::ImageHeader::write(&mut self.external_flash, &bank, size) - //} else { - // Err(Error::NotEnoughData) - //} + Ok(()) } pub fn reset(&mut self) -> ! { SCB::sys_reset(); } @@ -85,7 +55,6 @@ where pub fn run(mut self) -> ! { let mut cli = self.cli.take().unwrap(); loop { - info!("Starting CLI"); cli.run(&mut self) } } diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 3a42e76c..ba40ee9b 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -10,15 +10,14 @@ use blue_hal::{ duprintln, hal::{ flash, - serial::{self, Read}, + serial, }, - uprintln, }; -use core::{array::IntoIter, cmp::min, mem::size_of}; +use core::{cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; -use defmt::{error, info, warn}; +use defmt::{info, warn}; use nb::block; -use ufmt::{uwrite, uwriteln}; +use ufmt::uwriteln; pub struct Bootloader where @@ -86,8 +85,9 @@ where /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. fn restore(&mut self) -> Result<(), Error> { - for bank in 0..self.external_banks.len() { - if self.copy_image(DEFAULT_BOOT_BANK, bank as u8).is_ok() { + for input in self.external_banks.iter() { + let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + if self.copy_image(*input, *output).is_ok() { return Ok(()); }; } @@ -97,8 +97,6 @@ where fn recover(&mut self) -> ! { duprintln!(self.serial, "-- Loadstone Recovery Mode --"); duprintln!(self.serial, "Please send firmware image via XMODEM."); - const BUFFER_SIZE: usize = 2048; - let bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); for (i, block) in self.serial.blocks(Some(10)).enumerate() { @@ -121,9 +119,9 @@ where } let size = image::image_at(&mut self.mcu_flash, *bank)?.size(); - duprintln!(self.serial, "Image verified with size {:?}. Booting.", size); warn!("Jumping to a new firmware image. This will break `defmt`."); let image_location_raw: usize = bank.location.into(); + duprintln!(self.serial, "Image verified with size {:?} at {:?}. Booting.", size, image_location_raw); // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an // entirely different firmware image! We have to assume everything is at the right place, @@ -162,22 +160,8 @@ where } /// Copy from external bank to MCU bank - pub fn copy_image(&mut self, input_bank_index: u8, output_bank_index: u8) -> Result<(), Error> { - let input_bank = self - .external_banks - .iter() - .find(|b| b.index == input_bank_index) - .ok_or(Error::BankInvalid)?; - let output_bank = self - .mcu_banks - .iter() - .find(|b| b.index == output_bank_index) - .ok_or(Error::BankInvalid)?; - let input_image = image::image_at( - &mut self.external_flash, - self.external_banks[input_bank_index as usize], - )?; - + pub fn copy_image(&mut self, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { + let input_image = image::image_at(&mut self.external_flash,input_bank)?; let input_image_start_address = input_bank.location; let output_image_start_address = output_bank.location; diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index d41a76a2..5155dd52 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -18,16 +18,12 @@ commands!( cli, boot_manager, names, helpstrings [ }, flash ["Stores a FW image in an external bank."] ( - size: usize ["Image size in bytes"], bank: u8 ["External Bank Index"], ) { if let Some(bank) = boot_manager.external_banks().find(|b| b.index == bank) { - if size > bank.size { - return Err(Error::ArgumentOutOfRange); - } uprintln!(cli.serial, "Starting XModem mode! Send file with your XModem client."); - boot_manager.store_image(cli.serial.blocks(Some(10)), size, bank)?; + boot_manager.store_image(cli.serial.blocks(Some(10)), bank)?; uprintln!(cli.serial, "Image transfer complete!"); } else { uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); diff --git a/src/devices/image.rs b/src/devices/image.rs index 7168742b..a00d41a3 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -3,7 +3,7 @@ use blue_hal::{ utilities::{iterator::UntilSequence, memory::Address}, }; use crc::{crc32, Hasher32}; -use defmt::{info, warn}; +use defmt::info; use nb::{self, block}; use crate::error::Error; diff --git a/tools/gdb_erase.txt b/tools/gdb_erase.txt new file mode 100644 index 00000000..560d13ae --- /dev/null +++ b/tools/gdb_erase.txt @@ -0,0 +1,3 @@ +target remote localhost:3333 +monitor reset halt +monitor flash erase_address unlock 0x08000000 524288 diff --git a/tools/gdb_flash_combined.txt b/tools/gdb_flash_combined.txt deleted file mode 100644 index bd166c40..00000000 --- a/tools/gdb_flash_combined.txt +++ /dev/null @@ -1,12 +0,0 @@ -target remote localhost:3333 -monitor reset halt -define reflash -monitor flash erase_address unlock 0x08000000 524288 -monitor flash write_image unlock ./combined.bin 0x08000000 bin -end -reflash -define verify -monitor verify_image ./combined.bin 0x08000000 bin -end -verify -monitor reset halt From 62161892fee8f81b636a402915c7291b53aae7b4 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 22 Jan 2021 12:48:15 +0100 Subject: [PATCH 57/67] Add test LED on startup --- Cargo.toml | 6 +++--- src/ports/stm32f412_discovery/boot_manager.rs | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f50d6ace..01b44b81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -#path = "blue_hal" -git = "ssh://git@github.com/absw/blue_hal.git" -branch = "main" +path = "blue_hal" +#git = "ssh://git@github.com/absw/blue_hal.git" +#branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/ports/stm32f412_discovery/boot_manager.rs b/src/ports/stm32f412_discovery/boot_manager.rs index 1d161649..b08c106f 100644 --- a/src/ports/stm32f412_discovery/boot_manager.rs +++ b/src/ports/stm32f412_discovery/boot_manager.rs @@ -1,5 +1,5 @@ use crate::devices::{boot_manager::BootManager, cli::Cli}; -use blue_hal::{drivers::{micron::n25q128a_flash::MicronN25q128a, stm32f4::{qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::time, stm32pac::{self, USART6}}; +use blue_hal::{drivers::{led, micron::n25q128a_flash::MicronN25q128a, stm32f4::{qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::{time, led::Toggle}, stm32pac::{self, USART6}}; use super::{bootloader::EXTERNAL_BANKS, pin_configuration::*}; // Flash pins and typedefs @@ -18,9 +18,13 @@ impl BootManager { let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); + let gpioe = peripherals.GPIOE.split(&mut peripherals.RCC); let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); let clocks = Clocks::hardcoded(peripherals.RCC); + // TEST: Light up orange led at the start + let mut led = led::MonochromeLed::new(gpioe.pe1, led::LogicLevel::Inverted); + led.on(); SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup From a373334c0ea18fbfe2669572310e7ad52963158d Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Fri, 22 Jan 2021 15:46:00 +0100 Subject: [PATCH 58/67] Break down bootloader in two stages --- Cargo.toml | 6 +- src/devices/bootloader.rs | 63 ++++++++----------- src/ports/stm32f412_discovery/boot_manager.rs | 3 - src/ports/stm32f412_discovery/bootloader.rs | 40 +++++++----- 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01b44b81..f50d6ace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -path = "blue_hal" -#git = "ssh://git@github.com/absw/blue_hal.git" -#branch = "main" +#path = "blue_hal" +git = "ssh://git@github.com/absw/blue_hal.git" +branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index ba40ee9b..2855f543 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -28,11 +28,11 @@ where SRL: serial::ReadWrite, Error: From<::Error>, { - pub(crate) external_flash: EXTF, pub(crate) mcu_flash: MCUF, pub(crate) external_banks: &'static [image::Bank<::Address>], pub(crate) mcu_banks: &'static [image::Bank<::Address>], - pub(crate) serial: SRL, + // Closure to initialise drivers for restore/recovery mode. + pub(crate) init_stage_two: fn() -> (EXTF, SRL), } const DEFAULT_BOOT_BANK: u8 = 1; @@ -54,58 +54,58 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - duprintln!(self.serial, "--Loadstone Initialised--"); - duprintln!(self.serial, "Attempting to boot from default bank. This may take some time..."); + info!("--Loadstone Initialised--"); + info!("Attempting to boot from default bank. This may take some time..."); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { Error::BankInvalid => { - duprintln!(self.serial, "Attempted to boot from invalid bank. Restoring image...") + info!("Attempted to boot from invalid bank. Restoring image...") } Error::BankEmpty => { - duprintln!(self.serial, "Attempted to boot from empty bank. Restoring image...") + info!("Attempted to boot from empty bank. Restoring image...") } Error::CrcInvalid => { - duprintln!(self.serial, "Crc invalid for stored image. Restoring image...") + info!("Crc invalid for stored image. Restoring image...") } - _ => duprintln!(self.serial, "Unexpected boot error. Restoring image..."), + _ => info!("Unexpected boot error. Restoring image..."), }; - match self.restore() { + let (mut external_flash, serial) = (self.init_stage_two)(); + + match self.restore(&mut external_flash) { Ok(()) => { self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") } Err(e) => { - duprintln!(self.serial, "Failed to restore."); - info!("Error: {:?}", e); - duprintln!(self.serial, "Proceeding to recovery mode..."); - self.recover(); + info!("Failed to restore. Error: {:?}", e); + self.recover(serial); } } } /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. - fn restore(&mut self) -> Result<(), Error> { + fn restore(&mut self, external_flash: &mut EXTF) -> Result<(), Error> { for input in self.external_banks.iter() { let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); - if self.copy_image(*input, *output).is_ok() { + if self.copy_image(external_flash, *input, *output).is_ok() { return Ok(()); }; } Err(Error::NoImageToRestoreFrom) } - fn recover(&mut self) -> ! { - duprintln!(self.serial, "-- Loadstone Recovery Mode --"); - duprintln!(self.serial, "Please send firmware image via XMODEM."); + fn recover(&mut self, mut serial: SRL) -> ! { + duprintln!(serial, "-- Loadstone Recovery Mode --"); + duprintln!(serial, "Please send firmware image via XMODEM."); let bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); - for (i, block) in self.serial.blocks(Some(10)).enumerate() { + for (i, block) in serial.blocks(Some(10)).enumerate() { nb::block!(self.mcu_flash.write(bank.location + block.len() * i, &block)) .map_err(|_| Error::DriverError("Failed to flash image during recovery mode.")) .unwrap(); } - duprintln!(self.serial, "Finished flashing image."); - duprintln!(self.serial, "Rebooting..."); + duprintln!(serial, "Finished flashing image."); + duprintln!(serial, "Rebooting..."); SCB::sys_reset(); } @@ -118,10 +118,9 @@ where return Err(Error::BankInvalid); } - let size = image::image_at(&mut self.mcu_flash, *bank)?.size(); + image::image_at(&mut self.mcu_flash, *bank)?.size(); warn!("Jumping to a new firmware image. This will break `defmt`."); let image_location_raw: usize = bank.location.into(); - duprintln!(self.serial, "Image verified with size {:?} at {:?}. Booting.", size, image_location_raw); // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an // entirely different firmware image! We have to assume everything is at the right place, @@ -132,23 +131,12 @@ where let reset_handler_pointer = *((image_location_raw + size_of::()) as *const u32) as *const (); let reset_handler = core::mem::transmute::<*const (), fn() -> !>(reset_handler_pointer); - cortex_m::interrupt::disable(); (*SCB::ptr()).vtor.write(image_location_raw as u32); cortex_m::register::msp::write(initial_stack_pointer); reset_handler() } } - /// Runs a self test on MCU flash. - pub fn test_mcu_flash(&mut self) -> Result<(), Error> { - Self::test_flash_read_write_cycle(&mut self.mcu_flash) - } - - /// Runs a self test on external flash. - pub fn test_external_flash(&mut self) -> Result<(), Error> { - Self::test_flash_read_write_cycle(&mut self.external_flash) - } - /// Returns an iterator of all MCU flash banks. pub fn mcu_banks(&self) -> impl Iterator> { self.mcu_banks.iter().cloned() @@ -160,8 +148,8 @@ where } /// Copy from external bank to MCU bank - pub fn copy_image(&mut self, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { - let input_image = image::image_at(&mut self.external_flash,input_bank)?; + pub fn copy_image(&mut self, external_flash: &mut EXTF, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { + let input_image = image::image_at(external_flash, input_bank)?; let input_image_start_address = input_bank.location; let output_image_start_address = output_bank.location; @@ -172,8 +160,7 @@ where let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); while byte_index < total_size { let bytes_to_read = min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); - block!(self - .external_flash + block!(external_flash .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; block!(self .mcu_flash diff --git a/src/ports/stm32f412_discovery/boot_manager.rs b/src/ports/stm32f412_discovery/boot_manager.rs index b08c106f..36bb34d1 100644 --- a/src/ports/stm32f412_discovery/boot_manager.rs +++ b/src/ports/stm32f412_discovery/boot_manager.rs @@ -22,9 +22,6 @@ impl BootManager { let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); let clocks = Clocks::hardcoded(peripherals.RCC); - // TEST: Light up orange led at the start - let mut led = led::MonochromeLed::new(gpioe.pe1, led::LogicLevel::Inverted); - led.on(); SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index 85a51141..dcaecfac 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -55,11 +55,8 @@ impl Default for Bootloader { impl Bootloader { pub fn new() -> Self { - let mut peripherals = stm32pac::Peripherals::take().unwrap(); + let peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); - let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); - let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); - let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); let mcu_flash = flash::McuFlash::new(peripherals.FLASH).unwrap(); let clocks = Clocks::hardcoded(peripherals.RCC); @@ -67,16 +64,31 @@ impl Bootloader { SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup - let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); - let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); - let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); - let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); - - let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); - let serial_pins = (gpiog.pg14, gpiog.pg9); - let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); - - Bootloader { external_flash, mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS, serial } + let init_stage_two = || { + // Safety: We know (by inspecting this function) that the stage two + // closure does not touch any of the peripherals constructed above, so it's safe + // to defer accessing these peripherals to later in the bootloader's lifetime. + // + // The only exception is the RCC peripheral, which is used to re-construct the Clocks + // struct. This just amounts to reinitialising the clocks so it's completely safe. + let mut peripherals = unsafe { stm32pac::Peripherals::steal() }; + let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); + let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); + let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); + let clocks = Clocks::hardcoded(peripherals.RCC); + + let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); + let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); + let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); + let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); + + let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); + let serial_pins = (gpiog.pg14, gpiog.pg9); + let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); + (external_flash, serial) + }; + + Bootloader { mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS, init_stage_two } } } From 6661ab60c3aa06520aa2bbefabe2c74decff660e Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Mon, 25 Jan 2021 15:33:54 +0100 Subject: [PATCH 59/67] Update CLI messages --- src/devices/boot_manager.rs | 5 +++++ src/devices/cli/commands/mod.rs | 7 +++++++ src/devices/cli/mod.rs | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 7ca7edf6..7ab12472 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -50,6 +50,11 @@ where Ok(()) } + pub fn format_external(&mut self) -> Result<(), Error> { + nb::block!(self.external_flash.erase())?; + Ok(()) + } + pub fn reset(&mut self) -> ! { SCB::sys_reset(); } pub fn run(mut self) -> ! { diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index 5155dd52..9f49adb3 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -31,6 +31,13 @@ commands!( cli, boot_manager, names, helpstrings [ }, + format ["Formats external flash."] () + { + uprintln!(cli.serial, "Formatting external flash..."); + boot_manager.format_external()?; + uprintln!(cli.serial, "Done formatting!"); + }, + boot ["Restart, attempting to boot into a valid image if available."] ( ) { uprintln!(cli.serial, "Restarting..."); diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index a485ca99..5c8d9cc5 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -24,7 +24,7 @@ use super::boot_manager::BootManager; pub mod file_transfer; -const GREETING: &str = "--=Loadstone CLI=--\ntype `help` for a list of commands."; +const GREETING: &str = "--=Loadstone demo app CLI + Boot Manager=--\ntype `help` for a list of commands."; const PROMPT: &str = "\n> "; const BUFFER_SIZE: usize = 256; From 49da8457daf6cd23291c5d64133393d99a294593 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 26 Jan 2021 14:21:57 +0100 Subject: [PATCH 60/67] Remove two-stage bootloader initialisation --- Cargo.toml | 6 +- src/devices/boot_manager.rs | 20 +++++ src/devices/bootloader.rs | 74 ++++++++----------- src/devices/cli/mod.rs | 2 +- src/devices/image.rs | 9 ++- src/ports/stm32f412_discovery/boot_manager.rs | 2 +- src/ports/stm32f412_discovery/bootloader.rs | 45 ++++------- 7 files changed, 77 insertions(+), 81 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f50d6ace..01b44b81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -#path = "blue_hal" -git = "ssh://git@github.com/absw/blue_hal.git" -branch = "main" +path = "blue_hal" +#git = "ssh://git@github.com/absw/blue_hal.git" +#branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 7ab12472..4dc51ca4 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -63,4 +63,24 @@ where cli.run(&mut self) } } + + fn test_flash_read_write_cycle(flash: &mut F) -> Result<(), Error> + where + F: flash::ReadWrite, + Error: From<::Error>, + { + let magic_word_buffer = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]; + let superset_byte_buffer = [0xFFu8]; + let expected_final_buffer = [0xFFu8, 0xBBu8, 0xCCu8, 0xDDu8]; + let (start, _) = flash.range(); + nb::block!(flash.write(start, &magic_word_buffer))?; + nb::block!(flash.write(start, &superset_byte_buffer))?; + let mut final_buffer = [0x00; 4]; + nb::block!(flash.read(start, &mut final_buffer))?; + if expected_final_buffer != final_buffer { + Err(Error::DriverError("Flash read-write cycle failed")) + } else { + Ok(()) + } + } } diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 2855f543..eab8e8c5 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -31,8 +31,8 @@ where pub(crate) mcu_flash: MCUF, pub(crate) external_banks: &'static [image::Bank<::Address>], pub(crate) mcu_banks: &'static [image::Bank<::Address>], - // Closure to initialise drivers for restore/recovery mode. - pub(crate) init_stage_two: fn() -> (EXTF, SRL), + pub(crate) external_flash: EXTF, + pub(crate) serial: SRL, } const DEFAULT_BOOT_BANK: u8 = 1; @@ -54,6 +54,10 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { + // Only one golden image + assert_eq!(self.external_banks.iter().filter(|b| b.is_golden).count(), 1); + assert_eq!(self.mcu_banks.iter().filter(|b| b.is_golden).count(), 0); + info!("--Loadstone Initialised--"); info!("Attempting to boot from default bank. This may take some time..."); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { @@ -69,43 +73,49 @@ where _ => info!("Unexpected boot error. Restoring image..."), }; - let (mut external_flash, serial) = (self.init_stage_two)(); - - match self.restore(&mut external_flash) { + match self.restore() { Ok(()) => { self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") } Err(e) => { info!("Failed to restore. Error: {:?}", e); - self.recover(serial); + self.recover(); } } } /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. - fn restore(&mut self, external_flash: &mut EXTF) -> Result<(), Error> { - for input in self.external_banks.iter() { - let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); - if self.copy_image(external_flash, *input, *output).is_ok() { + fn restore(&mut self) -> Result<(), Error> { + // Attempt to restore from normal image + let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + for input in self.external_banks.iter().filter(|b| !b.is_golden) { + if self.copy_image(*input, *output).is_ok() { return Ok(()); }; } + + // Attempt to restore from golden image + let golden_bank = self.external_banks.iter().find(|b| b.is_golden).unwrap(); + if self.copy_image(*golden_bank, *output).is_ok() { + return Ok(()); + }; + Err(Error::NoImageToRestoreFrom) } - fn recover(&mut self, mut serial: SRL) -> ! { - duprintln!(serial, "-- Loadstone Recovery Mode --"); - duprintln!(serial, "Please send firmware image via XMODEM."); - let bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + fn recover(&mut self) -> ! { + duprintln!(self.serial, "-- Loadstone Recovery Mode --"); + duprintln!(self.serial, "Please send golden firmware image via XMODEM."); + let golden_bank = self.external_banks.iter().find(|b| b.is_golden).unwrap(); - for (i, block) in serial.blocks(Some(10)).enumerate() { - nb::block!(self.mcu_flash.write(bank.location + block.len() * i, &block)) - .map_err(|_| Error::DriverError("Failed to flash image during recovery mode.")) + for (i, block) in self.serial.blocks(None).enumerate() { + nb::block!(self.external_flash.write(golden_bank.location + block.len() * i, &block)) + .map_err(|_| Error::DriverError("Failed to flash golden image during recovery mode.")) .unwrap(); } - duprintln!(serial, "Finished flashing image."); - duprintln!(serial, "Rebooting..."); + duprintln!(self.serial, "Finished flashing golden image."); + duprintln!(self.serial, "Rebooting..."); SCB::sys_reset(); } @@ -148,8 +158,8 @@ where } /// Copy from external bank to MCU bank - pub fn copy_image(&mut self, external_flash: &mut EXTF, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { - let input_image = image::image_at(external_flash, input_bank)?; + pub fn copy_image(&mut self, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { + let input_image = image::image_at(&mut self.external_flash, input_bank)?; let input_image_start_address = input_bank.location; let output_image_start_address = output_bank.location; @@ -160,7 +170,7 @@ where let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); while byte_index < total_size { let bytes_to_read = min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); - block!(external_flash + block!(self.external_flash .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; block!(self .mcu_flash @@ -169,24 +179,4 @@ where } Ok(()) } - - fn test_flash_read_write_cycle(flash: &mut F) -> Result<(), Error> - where - F: flash::ReadWrite, - Error: From<::Error>, - { - let magic_word_buffer = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]; - let superset_byte_buffer = [0xFFu8]; - let expected_final_buffer = [0xFFu8, 0xBBu8, 0xCCu8, 0xDDu8]; - let (start, _) = flash.range(); - block!(flash.write(start, &magic_word_buffer))?; - block!(flash.write(start, &superset_byte_buffer))?; - let mut final_buffer = [0x00; 4]; - block!(flash.read(start, &mut final_buffer))?; - if expected_final_buffer != final_buffer { - Err(Error::DriverError("Flash read-write cycle failed")) - } else { - Ok(()) - } - } } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 5c8d9cc5..9dd79642 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -214,7 +214,7 @@ impl Cli { uwriteln!(self.serial, "[CLI Error] Command contains duplicate arguments") } Err(Error::ApplicationError(e)) => { - uprintln!(self.serial, "[CLI Error] Internal boot_manager error: "); + uwriteln!(self.serial, "[CLI Error] Internal boot_manager error: "); e.report(&mut self.serial); Ok(()) } diff --git a/src/devices/image.rs b/src/devices/image.rs index a00d41a3..1e761d9f 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -18,6 +18,7 @@ pub struct Bank { pub size: usize, pub location: A, pub bootable: bool, + pub is_golden: bool, } /// Image descriptor @@ -97,7 +98,7 @@ mod tests { #[test] fn retrieving_image_from_flash() { let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false, }; let image_with_crc = test_image_with_crc(); flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!( @@ -109,19 +110,19 @@ mod tests { #[test] fn retrieving_broken_image_fails() { let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[0] = 0xFF; // This will corrupt the image, making the CRC obsolete flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[4] = 0xFF; // This will break the CRC directly flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false }; + let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[12] = 0xFF; // The magic string is not present to delineate the image flash.write(Address(0), &image_with_crc).unwrap(); diff --git a/src/ports/stm32f412_discovery/boot_manager.rs b/src/ports/stm32f412_discovery/boot_manager.rs index 36bb34d1..335714b2 100644 --- a/src/ports/stm32f412_discovery/boot_manager.rs +++ b/src/ports/stm32f412_discovery/boot_manager.rs @@ -1,5 +1,5 @@ use crate::devices::{boot_manager::BootManager, cli::Cli}; -use blue_hal::{drivers::{led, micron::n25q128a_flash::MicronN25q128a, stm32f4::{qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::{time, led::Toggle}, stm32pac::{self, USART6}}; +use blue_hal::{drivers::{micron::n25q128a_flash::MicronN25q128a, stm32f4::{qspi::{self, QuadSpi, mode}, rcc::Clocks, serial::{self, UsartExt}, systick::SysTick}}, hal::{time}, stm32pac::{self, USART6}}; use super::{bootloader::EXTERNAL_BANKS, pin_configuration::*}; // Flash pins and typedefs diff --git a/src/ports/stm32f412_discovery/bootloader.rs b/src/ports/stm32f412_discovery/bootloader.rs index dcaecfac..5060e1fb 100644 --- a/src/ports/stm32f412_discovery/bootloader.rs +++ b/src/ports/stm32f412_discovery/bootloader.rs @@ -41,12 +41,12 @@ const fn mcu_image_offset(index: usize) -> flash::Address { } static MCU_BANKS: [image::Bank; MCU_NUMBER_OF_BANKS] = [ - image::Bank { index: 1, bootable: true, location: mcu_image_offset(0), size: IMAGE_SIZE, }, + image::Bank { index: 1, bootable: true, location: mcu_image_offset(0), size: IMAGE_SIZE, is_golden: false }, ]; pub static EXTERNAL_BANKS: [image::Bank; EXTERNAL_NUMBER_OF_BANKS] = [ - image::Bank { index: 2, bootable: false, location: external_image_offset(0), size: IMAGE_SIZE, }, - image::Bank { index: 3, bootable: false, location: external_image_offset(1), size: IMAGE_SIZE, }, + image::Bank { index: 2, bootable: false, location: external_image_offset(0), size: IMAGE_SIZE, is_golden: false }, + image::Bank { index: 3, bootable: false, location: external_image_offset(1), size: IMAGE_SIZE, is_golden: true }, ]; impl Default for Bootloader { @@ -55,40 +55,25 @@ impl Default for Bootloader { impl Bootloader { pub fn new() -> Self { - let peripherals = stm32pac::Peripherals::take().unwrap(); + let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); - let mcu_flash = flash::McuFlash::new(peripherals.FLASH).unwrap(); + let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); + let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); + let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); let clocks = Clocks::hardcoded(peripherals.RCC); - SysTick::init(cortex_peripherals.SYST, clocks); SysTick::wait(time::Seconds(1)); // Gives time for the flash chip to stabilize after powerup - let init_stage_two = || { - // Safety: We know (by inspecting this function) that the stage two - // closure does not touch any of the peripherals constructed above, so it's safe - // to defer accessing these peripherals to later in the bootloader's lifetime. - // - // The only exception is the RCC peripheral, which is used to re-construct the Clocks - // struct. This just amounts to reinitialising the clocks so it's completely safe. - let mut peripherals = unsafe { stm32pac::Peripherals::steal() }; - let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); - let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); - let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); - let clocks = Clocks::hardcoded(peripherals.RCC); - - let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); - let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); - let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); - let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); - - let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); - let serial_pins = (gpiog.pg14, gpiog.pg9); - let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); - (external_flash, serial) - }; + let qspi_pins = (gpiob.pb2, gpiog.pg6, gpiof.pf8, gpiof.pf9, gpiof.pf7, gpiof.pf6); + let qspi_config = qspi::Config::::default().with_flash_size(24).unwrap(); + let qspi = Qspi::from_config(peripherals.QUADSPI, qspi_pins, qspi_config).unwrap(); + let external_flash = ExternalFlash::with_timeout(qspi, time::Milliseconds(500)).unwrap(); - Bootloader { mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS, init_stage_two } + let serial_config = serial::config::Config::default().baudrate(time::Bps(115200)); + let serial_pins = (gpiog.pg14, gpiog.pg9); + let serial = peripherals.USART6.constrain(serial_pins, serial_config, clocks).unwrap(); + Bootloader { mcu_flash, external_banks: &EXTERNAL_BANKS, mcu_banks: &MCU_BANKS, external_flash, serial } } } From 11b4023f3ed6ce45ba17ce3a4af92308053fe32a Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Tue, 26 Jan 2021 14:54:51 +0100 Subject: [PATCH 61/67] Remove multistage constructor, add golden string identifier --- src/devices/bootloader.rs | 56 +++++++++++++++++++++++++------- src/devices/cli/file_transfer.rs | 8 ++++- src/devices/cli/mod.rs | 3 +- src/devices/image.rs | 40 +++++++++++++++-------- tools/crc_image_tool/src/main.rs | 14 ++++++-- 5 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index eab8e8c5..5b4b2605 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -8,10 +8,7 @@ use super::image::{self, CRC_SIZE_BYTES, MAGIC_STRING}; use crate::{devices::cli::file_transfer::FileTransfer, error::Error}; use blue_hal::{ duprintln, - hal::{ - flash, - serial, - }, + hal::{flash, serial}, }; use core::{cmp::min, mem::size_of}; use cortex_m::peripheral::SCB; @@ -54,10 +51,11 @@ where /// copy to bootable MCU flash bank. /// * If golden image not available or invalid, proceed to recovery mode. pub fn run(mut self) -> ! { - // Only one golden image - assert_eq!(self.external_banks.iter().filter(|b| b.is_golden).count(), 1); + assert!(self.external_banks.iter().filter(|b| b.is_golden).count() <= 1); assert_eq!(self.mcu_banks.iter().filter(|b| b.is_golden).count(), 0); + self.replace_golden_image(); + info!("--Loadstone Initialised--"); info!("Attempting to boot from default bank. This may take some time..."); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { @@ -84,20 +82,39 @@ where } } + /// If the current bootable (MCU flash) image is the golden image, + /// attempts to replace it with the topmost non-golden image in an + /// external bank. + fn replace_golden_image(&mut self) { + let boot_bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + match image::image_at(&mut self.mcu_flash, *boot_bank) { + Ok(bootable_image) if bootable_image.is_golden() => { + for input in self.external_banks.iter().filter(|b| !b.is_golden) { + duprintln!(self.serial, "Scanning external bank {:?} for valid images...", input.index); + if self.copy_image(*input, *boot_bank, false).is_ok() { + duprintln!(self.serial, "Replaced golden image with external bank {:?}", input.index); + break; + }; + } + }, + _ => (), + } + } + /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. fn restore(&mut self) -> Result<(), Error> { // Attempt to restore from normal image let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); for input in self.external_banks.iter().filter(|b| !b.is_golden) { - if self.copy_image(*input, *output).is_ok() { + if self.copy_image(*input, *output, false).is_ok() { return Ok(()); }; } // Attempt to restore from golden image let golden_bank = self.external_banks.iter().find(|b| b.is_golden).unwrap(); - if self.copy_image(*golden_bank, *output).is_ok() { + if self.copy_image(*golden_bank, *output, true).is_ok() { return Ok(()); }; @@ -111,10 +128,16 @@ where for (i, block) in self.serial.blocks(None).enumerate() { nb::block!(self.external_flash.write(golden_bank.location + block.len() * i, &block)) - .map_err(|_| Error::DriverError("Failed to flash golden image during recovery mode.")) + .map_err(|_| { + Error::DriverError("Failed to flash golden image during recovery mode.") + }) .unwrap(); } - duprintln!(self.serial, "Finished flashing golden image."); + if !image::image_at(&mut self.external_flash, *golden_bank).unwrap().is_golden() { + duprintln!(self.serial, "FATAL: Flashed image is not a golden image"); + } else { + duprintln!(self.serial, "Finished flashing golden image."); + } duprintln!(self.serial, "Rebooting..."); SCB::sys_reset(); } @@ -158,8 +181,16 @@ where } /// Copy from external bank to MCU bank - pub fn copy_image(&mut self, input_bank: image::Bank, output_bank: image::Bank) -> Result<(), Error> { + pub fn copy_image( + &mut self, + input_bank: image::Bank, + output_bank: image::Bank, + must_be_golden: bool, + ) -> Result<(), Error> { let input_image = image::image_at(&mut self.external_flash, input_bank)?; + if must_be_golden && !input_image.is_golden() { + return Err(Error::DeviceError("Image is not golden")); + } let input_image_start_address = input_bank.location; let output_image_start_address = output_bank.location; @@ -170,7 +201,8 @@ where let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); while byte_index < total_size { let bytes_to_read = min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); - block!(self.external_flash + block!(self + .external_flash .read(input_image_start_address + byte_index, &mut buffer[0..bytes_to_read]))?; block!(self .mcu_flash diff --git a/src/devices/cli/file_transfer.rs b/src/devices/cli/file_transfer.rs index 2955824b..4039d0c0 100644 --- a/src/devices/cli/file_transfer.rs +++ b/src/devices/cli/file_transfer.rs @@ -7,7 +7,13 @@ pub const BLOCK_SIZE: usize = xmodem::PAYLOAD_SIZE; pub trait FileTransfer: TimeoutRead + Write { fn blocks(&mut self, max_retries: Option) -> BlockIterator { - BlockIterator { serial: self, received_block: false, finished: false, block_number: 0, max_retries } + BlockIterator { + serial: self, + received_block: false, + finished: false, + block_number: 0, + max_retries, + } } } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index 9dd79642..ef84e71f 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -24,7 +24,8 @@ use super::boot_manager::BootManager; pub mod file_transfer; -const GREETING: &str = "--=Loadstone demo app CLI + Boot Manager=--\ntype `help` for a list of commands."; +const GREETING: &str = + "--=Loadstone demo app CLI + Boot Manager=--\ntype `help` for a list of commands."; const PROMPT: &str = "\n> "; const BUFFER_SIZE: usize = 256; diff --git a/src/devices/image.rs b/src/devices/image.rs index 1e761d9f..78f80436 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -8,7 +8,9 @@ use nb::{self, block}; use crate::error::Error; -/// This string must terminate any valid images, followed by CRC +/// This string precedes the CRC for golden images only +pub const GOLDEN_STRING: &str = "XPIcbOUrpG"; +/// This string must terminate any valid images, after CRC pub const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; pub const CRC_SIZE_BYTES: usize = 4; @@ -27,10 +29,12 @@ pub struct Image { size: usize, location: A, bootable: bool, + golden: bool, } impl Image { pub fn size(&self) -> usize { self.size } + pub fn is_golden(&self) -> bool { self.golden } } pub fn image_at(flash: &mut F, bank: Bank) -> Result, Error> @@ -48,14 +52,14 @@ where // TODO optimise this away so we don't have to scan the image twice. (e.g. with a "window" // buffer for the CRC); - let image_size_with_crc = flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()).count(); + let image_size_with_crc = + flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()).count(); let image_size = image_size_with_crc.saturating_sub(CRC_SIZE_BYTES); let image_bytes = flash.bytes(bank.location).take(image_size); - let digest = - image_bytes.fold(crc32::Digest::new(crc32::IEEE), |mut digest, byte| { - digest.write(&[byte]); - digest - }); + let digest = image_bytes.fold(crc32::Digest::new(crc32::IEEE), |mut digest, byte| { + digest.write(&[byte]); + digest + }); let calculated_crc = digest.sum32(); info!("Done verifying image. Crc: {:?}, size: {:?}", calculated_crc, image_size); @@ -65,8 +69,14 @@ where let crc = u32::from_le_bytes(crc_bytes); info!("Retrieved crc: {:?}", crc); + + let golden_string_offset = crc_offset.saturating_sub(GOLDEN_STRING.len()); + let mut golden_bytes = [0u8; GOLDEN_STRING.len()]; + block!(flash.read(bank.location + golden_string_offset, &mut golden_bytes))?; + let golden = golden_bytes == GOLDEN_STRING.as_bytes(); + if crc == calculated_crc { - Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable }) + Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable, golden }) } else { Err(Error::CrcInvalid) } @@ -98,11 +108,12 @@ mod tests { #[test] fn retrieving_image_from_flash() { let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false, }; + let bank = + Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let image_with_crc = test_image_with_crc(); flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!( - Ok(Image { size: 2, location: bank.location, bootable: bank.bootable }), + Ok(Image { size: 2, location: bank.location, bootable: bank.bootable, golden: false }), image_at(&mut flash, bank) ); } @@ -110,19 +121,22 @@ mod tests { #[test] fn retrieving_broken_image_fails() { let mut flash = FakeFlash::new(Address(0)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; + let bank = + Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[0] = 0xFF; // This will corrupt the image, making the CRC obsolete flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; + let bank = + Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[4] = 0xFF; // This will break the CRC directly flash.write(Address(0), &image_with_crc).unwrap(); assert_eq!(Err(Error::CrcInvalid), image_at(&mut flash, bank)); - let bank = Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; + let bank = + Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; let mut image_with_crc = test_image_with_crc(); image_with_crc[12] = 0xFF; // The magic string is not present to delineate the image flash.write(Address(0), &image_with_crc).unwrap(); diff --git a/tools/crc_image_tool/src/main.rs b/tools/crc_image_tool/src/main.rs index 537cbf9c..8dba2068 100644 --- a/tools/crc_image_tool/src/main.rs +++ b/tools/crc_image_tool/src/main.rs @@ -5,12 +5,15 @@ use std::{io::BufReader, io::BufRead, fs::File, io::prelude::*}; use byteorder::{LittleEndian, WriteBytesExt}; const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; +const GOLDEN_STRING: &str = "XPIcbOUrpG"; #[derive(Clap)] #[clap(about = "Tool to calculate and append CRC to firmware images", version = "1.0", author = "Pablo Mansanet ")] struct Opts { #[clap(about = "Filename to append CRC to")] filename: String, + #[clap(about = "Label the image as golden (Loadstone firmware fallback)")] + golden: bool, } fn main() -> std::io::Result<()> { @@ -27,15 +30,22 @@ fn main() -> std::io::Result<()> { buf_reader.consume(buf_reader.buffer().len()) } } + digest.write(GOLDEN_STRING); println!("Final CRC is {} (0x{:8x})", digest.sum32(), digest.sum32()); let mut final_crc = [0u8; 4]; (&mut final_crc[..]).write_u32::(digest.sum32())?; let mut firmware = File::with_options().append(true).open(&opts.filename)?; - println!("Appending to the end of {}", &opts.filename); + println!("Appending metadata to the end of {}", &opts.filename); + if opts.is_golden { + println!("* Appending golden image string"); + firmware.write(GOLDEN_STRING.as_bytes())?; + } + + println!("* Appending CRC string"); firmware.write(&final_crc)?; - println!("Appending magic string"); + println!("* Appending magic string"); firmware.write(MAGIC_STRING.as_bytes())?; println!("Done!"); From fe3f1519d1ecd1ad90643b9929060694105b4951 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 28 Jan 2021 11:52:04 +0100 Subject: [PATCH 62/67] WIP commands --- src/devices/bootloader.rs | 40 +++++++++++++++++---------------- src/devices/cli/commands/mod.rs | 31 +++++++++++++++++++------ src/devices/image.rs | 4 +++- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 5b4b2605..95163029 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -54,10 +54,11 @@ where assert!(self.external_banks.iter().filter(|b| b.is_golden).count() <= 1); assert_eq!(self.mcu_banks.iter().filter(|b| b.is_golden).count(), 0); - self.replace_golden_image(); info!("--Loadstone Initialised--"); - info!("Attempting to boot from default bank. This may take some time..."); + self.try_update_image(); + + info!("Attempting to boot from default bank."); match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { Error::BankInvalid => { info!("Attempted to boot from invalid bank. Restoring image...") @@ -72,9 +73,7 @@ where }; match self.restore() { - Ok(()) => { - self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") - } + Ok(()) => self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!"), Err(e) => { info!("Failed to restore. Error: {:?}", e); self.recover(); @@ -82,23 +81,26 @@ where } } - /// If the current bootable (MCU flash) image is the golden image, - /// attempts to replace it with the topmost non-golden image in an - /// external bank. - fn replace_golden_image(&mut self) { + /// If the current bootable (MCU flash) image is different from the top + /// non-golden external image, attempts to replace it. + fn try_update_image(&mut self) { let boot_bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); - match image::image_at(&mut self.mcu_flash, *boot_bank) { - Ok(bootable_image) if bootable_image.is_golden() => { - for input in self.external_banks.iter().filter(|b| !b.is_golden) { - duprintln!(self.serial, "Scanning external bank {:?} for valid images...", input.index); - if self.copy_image(*input, *boot_bank, false).is_ok() { - duprintln!(self.serial, "Replaced golden image with external bank {:?}", input.index); - break; - }; + if let Ok(current_image) = image::image_at(&mut self.mcu_flash, *boot_bank) { + for external_bank in self.external_banks.iter().filter(|b| !b.is_golden) { + duprintln!(self.serial, "Scanning external bank {:?} for a newer image...", external_bank.index); + match image::image_at(&mut self.external_flash, *external_bank) { + // Using CRC for identification for the time being. Will become + // the image's signed hash, which is a valid unique identifier. + Ok(image) if image.crc() != current_image.crc() => { + duprintln!(self.serial, "Replacing golden image with external bank {:?}...", external_bank.index); + self.copy_image(*external_bank, *boot_bank, false).unwrap(); + }, + Ok(_image) => break, + _ => (), } - }, - _ => (), + }; } + duprintln!(self.serial, "No newer image found."); } /// Restores an image from the preferred external bank. If it fails, diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index 9f49adb3..9babe21d 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -1,10 +1,4 @@ -use crate::{ - devices::{ - boot_manager::BootManager, - cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, - }, - error::Error as ApplicationError, -}; +use crate::{devices::{boot_manager::BootManager, cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, image}, error::Error as ApplicationError}; use blue_hal::{ hal::{flash, serial}, uprintln, @@ -17,6 +11,29 @@ commands!( cli, boot_manager, names, helpstrings [ cli.print_help(names, helpstrings, command) }, + banks ["Displays external bank information"] (){ + uprintln!(cli.serial, "External Banks:"); + for bank in boot_manager.external_banks() { + uwriteln!(cli.serial, " - [{}] {} - Size: {}b{}", + bank.index, + if bank.bootable { "Bootable" } else { "Non-Bootable" }, + bank.size, + if bank.is_golden { "- GOLDEN" } else { "" }).ok().unwrap(); + } + }, + + images ["Displays external image information (WARNING: Slow)"] (){ + uprintln!(cli.serial, "External images:"); + for bank in boot_manager.external_banks() { + if let Ok(image) = image::image_at(&mut boot_manager.external_flash, bank) { + uwriteln!(cli.serial, " - [IMAGE] - Size: {}b - CRC: {} ", + image.size(), + image.crc()).ok().unwrap(); + } + + } + }, + flash ["Stores a FW image in an external bank."] ( bank: u8 ["External Bank Index"], ) diff --git a/src/devices/image.rs b/src/devices/image.rs index 78f80436..b050264e 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -30,11 +30,13 @@ pub struct Image { location: A, bootable: bool, golden: bool, + crc: u32, } impl Image { pub fn size(&self) -> usize { self.size } pub fn is_golden(&self) -> bool { self.golden } + pub fn crc(&self) -> u32 { self.crc } } pub fn image_at(flash: &mut F, bank: Bank) -> Result, Error> @@ -76,7 +78,7 @@ where let golden = golden_bytes == GOLDEN_STRING.as_bytes(); if crc == calculated_crc { - Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable, golden }) + Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable, golden, crc }) } else { Err(Error::CrcInvalid) } From ff98d1c25b29bee0f291aef7f1a2b20943191d8f Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 28 Jan 2021 12:09:30 +0100 Subject: [PATCH 63/67] Add testing commands --- src/devices/bootloader.rs | 21 +++++++++---- src/devices/cli/commands/mod.rs | 55 +++++++++++++++++++++++++++++++-- src/devices/image.rs | 9 +++++- 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 95163029..80d2394c 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -54,7 +54,6 @@ where assert!(self.external_banks.iter().filter(|b| b.is_golden).count() <= 1); assert_eq!(self.mcu_banks.iter().filter(|b| b.is_golden).count(), 0); - info!("--Loadstone Initialised--"); self.try_update_image(); @@ -73,7 +72,9 @@ where }; match self.restore() { - Ok(()) => self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!"), + Ok(()) => { + self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") + } Err(e) => { info!("Failed to restore. Error: {:?}", e); self.recover(); @@ -87,18 +88,26 @@ where let boot_bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); if let Ok(current_image) = image::image_at(&mut self.mcu_flash, *boot_bank) { for external_bank in self.external_banks.iter().filter(|b| !b.is_golden) { - duprintln!(self.serial, "Scanning external bank {:?} for a newer image...", external_bank.index); + duprintln!( + self.serial, + "Scanning external bank {:?} for a newer image...", + external_bank.index + ); match image::image_at(&mut self.external_flash, *external_bank) { // Using CRC for identification for the time being. Will become // the image's signed hash, which is a valid unique identifier. Ok(image) if image.crc() != current_image.crc() => { - duprintln!(self.serial, "Replacing golden image with external bank {:?}...", external_bank.index); + duprintln!( + self.serial, + "Replacing golden image with external bank {:?}...", + external_bank.index + ); self.copy_image(*external_bank, *boot_bank, false).unwrap(); - }, + } Ok(_image) => break, _ => (), } - }; + } } duprintln!(self.serial, "No newer image found."); } diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index 9babe21d..36c8863d 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -1,4 +1,11 @@ -use crate::{devices::{boot_manager::BootManager, cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, image}, error::Error as ApplicationError}; +use crate::{ + devices::{ + boot_manager::BootManager, + cli::{file_transfer::FileTransfer, ArgumentIterator, Cli, Error, Name, RetrieveArgument}, + image, + }, + error::Error as ApplicationError, +}; use blue_hal::{ hal::{flash, serial}, uprintln, @@ -35,7 +42,7 @@ commands!( cli, boot_manager, names, helpstrings [ }, flash ["Stores a FW image in an external bank."] ( - bank: u8 ["External Bank Index"], + bank: u8 ["External bank index."], ) { if let Some(bank) = boot_manager.external_banks().find(|b| b.index == bank) { @@ -48,6 +55,50 @@ commands!( cli, boot_manager, names, helpstrings [ }, + corrupt_crc ["Corrupts the CRC of a specified external image."] ( + bank: u8 ["External bank index."], + ) + { + let bank = if let Some(bank) = boot_manager.external_banks().find(|b| b.index == bank) { + bank + } else { + uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); + return Ok(()); + }; + + let image = image::image_at(&mut boot_manager.external_flash, bank) + .map_err(|_| Error::ApplicationError(ApplicationError::BankEmpty))?; + + let crc_location = image.location() + image.size(); + let mut crc_bytes = [0u8; 4usize]; + nb::block!(boot_manager.external_flash.read(crc_location, &mut crc_bytes)).map_err(|e| Error::ApplicationError(e.into()))?; + crc_bytes[0] = !crc_bytes[0]; + nb::block!(boot_manager.external_flash.write(crc_location, &mut crc_bytes)).map_err(|e| Error::ApplicationError(e.into()))?; + uprintln!(cli.serial, "Flipped the first CRC byte from {} to {}.", !crc_bytes[0], crc_bytes[0]); + }, + + corrupt_body ["Corrupts a byte inside a specified external image."] ( + bank: u8 ["External bank index."], + ) + { + let bank = if let Some(bank) = boot_manager.external_banks().find(|b| b.index == bank) { + bank + } else { + uprintln!(cli.serial, "Index supplied does not correspond to an external bank."); + return Ok(()); + }; + + let image = image::image_at(&mut boot_manager.external_flash, bank) + .map_err(|_| Error::ApplicationError(ApplicationError::BankEmpty))?; + + let byte_location = image.location() + 1; + let mut byte_buffer = [0u8]; + nb::block!(boot_manager.external_flash.read(byte_location, &mut byte_buffer)).map_err(|e| Error::ApplicationError(e.into()))?; + byte_buffer[0] = !byte_buffer[0]; + nb::block!(boot_manager.external_flash.write(byte_location, &mut byte_buffer)).map_err(|e| Error::ApplicationError(e.into()))?; + uprintln!(cli.serial, "Flipped an application byte byte from {} to {}.", !byte_buffer[0], byte_buffer[0]); + }, + format ["Formats external flash."] () { uprintln!(cli.serial, "Formatting external flash..."); diff --git a/src/devices/image.rs b/src/devices/image.rs index b050264e..4178c951 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -34,6 +34,7 @@ pub struct Image { } impl Image { + pub fn location(&self) -> A { self.location } pub fn size(&self) -> usize { self.size } pub fn is_golden(&self) -> bool { self.golden } pub fn crc(&self) -> u32 { self.crc } @@ -78,7 +79,13 @@ where let golden = golden_bytes == GOLDEN_STRING.as_bytes(); if crc == calculated_crc { - Ok(Image { size: image_size, location: bank.location, bootable: bank.bootable, golden, crc }) + Ok(Image { + size: image_size, + location: bank.location, + bootable: bank.bootable, + golden, + crc, + }) } else { Err(Error::CrcInvalid) } From a19148bdccf09f6f0b3925f8fe583ac91bf7bf76 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 28 Jan 2021 12:23:12 +0100 Subject: [PATCH 64/67] Clippy pass --- src/devices/boot_manager.rs | 20 ------------------- src/devices/cli/mod.rs | 2 +- src/ports/stm32f412_discovery/boot_manager.rs | 1 - tools/crc_image_tool/src/main.rs | 6 +++--- 4 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/devices/boot_manager.rs b/src/devices/boot_manager.rs index 4dc51ca4..7ab12472 100644 --- a/src/devices/boot_manager.rs +++ b/src/devices/boot_manager.rs @@ -63,24 +63,4 @@ where cli.run(&mut self) } } - - fn test_flash_read_write_cycle(flash: &mut F) -> Result<(), Error> - where - F: flash::ReadWrite, - Error: From<::Error>, - { - let magic_word_buffer = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]; - let superset_byte_buffer = [0xFFu8]; - let expected_final_buffer = [0xFFu8, 0xBBu8, 0xCCu8, 0xDDu8]; - let (start, _) = flash.range(); - nb::block!(flash.write(start, &magic_word_buffer))?; - nb::block!(flash.write(start, &superset_byte_buffer))?; - let mut final_buffer = [0x00; 4]; - nb::block!(flash.read(start, &mut final_buffer))?; - if expected_final_buffer != final_buffer { - Err(Error::DriverError("Flash read-write cycle failed")) - } else { - Ok(()) - } - } } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index ef84e71f..a576acca 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -215,7 +215,7 @@ impl Cli { uwriteln!(self.serial, "[CLI Error] Command contains duplicate arguments") } Err(Error::ApplicationError(e)) => { - uwriteln!(self.serial, "[CLI Error] Internal boot_manager error: "); + uwriteln!(self.serial, "[CLI Error] Internal boot_manager error: ").ok().unwrap(); e.report(&mut self.serial); Ok(()) } diff --git a/src/ports/stm32f412_discovery/boot_manager.rs b/src/ports/stm32f412_discovery/boot_manager.rs index 335714b2..68563d38 100644 --- a/src/ports/stm32f412_discovery/boot_manager.rs +++ b/src/ports/stm32f412_discovery/boot_manager.rs @@ -18,7 +18,6 @@ impl BootManager { let mut peripherals = stm32pac::Peripherals::take().unwrap(); let cortex_peripherals = cortex_m::Peripherals::take().unwrap(); let gpiob = peripherals.GPIOB.split(&mut peripherals.RCC); - let gpioe = peripherals.GPIOE.split(&mut peripherals.RCC); let gpiog = peripherals.GPIOG.split(&mut peripherals.RCC); let gpiof = peripherals.GPIOF.split(&mut peripherals.RCC); let clocks = Clocks::hardcoded(peripherals.RCC); diff --git a/tools/crc_image_tool/src/main.rs b/tools/crc_image_tool/src/main.rs index 8dba2068..848c3343 100644 --- a/tools/crc_image_tool/src/main.rs +++ b/tools/crc_image_tool/src/main.rs @@ -12,7 +12,7 @@ const GOLDEN_STRING: &str = "XPIcbOUrpG"; struct Opts { #[clap(about = "Filename to append CRC to")] filename: String, - #[clap(about = "Label the image as golden (Loadstone firmware fallback)")] + #[clap(short, about = "Label the image as golden (Loadstone firmware fallback)")] golden: bool, } @@ -30,7 +30,7 @@ fn main() -> std::io::Result<()> { buf_reader.consume(buf_reader.buffer().len()) } } - digest.write(GOLDEN_STRING); + digest.write(GOLDEN_STRING.as_bytes()); println!("Final CRC is {} (0x{:8x})", digest.sum32(), digest.sum32()); let mut final_crc = [0u8; 4]; @@ -38,7 +38,7 @@ fn main() -> std::io::Result<()> { let mut firmware = File::with_options().append(true).open(&opts.filename)?; println!("Appending metadata to the end of {}", &opts.filename); - if opts.is_golden { + if opts.golden { println!("* Appending golden image string"); firmware.write(GOLDEN_STRING.as_bytes())?; } From 723a7fff16ea35336d9c6659d3737b55edd2d01f Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 28 Jan 2021 15:57:06 +0100 Subject: [PATCH 65/67] Remove debug messages --- Cargo.toml | 6 +- src/devices/bootloader.rs | 114 +++++++++++++++++++------------ src/devices/cli/commands/mod.rs | 8 ++- src/devices/cli/mod.rs | 3 +- src/devices/image.rs | 36 ++++------ tools/crc_image_tool/src/main.rs | 16 ++++- 6 files changed, 108 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01b44b81..f50d6ace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ defmt-rtt = "0.1" [dependencies.blue_hal] # Uncomment the next line and comment the next two lines to develop blue_hal locally in parallel -path = "blue_hal" -#git = "ssh://git@github.com/absw/blue_hal.git" -#branch = "main" +#path = "blue_hal" +git = "ssh://git@github.com/absw/blue_hal.git" +branch = "main" [dependencies.ufmt] version = "*" diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 80d2394c..3b4e43e3 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -4,7 +4,7 @@ //! the exception of how to construct one. Construction is //! handled by the `port` module as it depends on board //! specific information. -use super::image::{self, CRC_SIZE_BYTES, MAGIC_STRING}; +use super::image::{self, Image, CRC_SIZE_BYTES, MAGIC_STRING}; use crate::{devices::cli::file_transfer::FileTransfer, error::Error}; use blue_hal::{ duprintln, @@ -54,27 +54,25 @@ where assert!(self.external_banks.iter().filter(|b| b.is_golden).count() <= 1); assert_eq!(self.mcu_banks.iter().filter(|b| b.is_golden).count(), 0); - info!("--Loadstone Initialised--"); - self.try_update_image(); - - info!("Attempting to boot from default bank."); - match self.boot(DEFAULT_BOOT_BANK).unwrap_err() { - Error::BankInvalid => { - info!("Attempted to boot from invalid bank. Restoring image...") - } - Error::BankEmpty => { - info!("Attempted to boot from empty bank. Restoring image...") - } - Error::CrcInvalid => { - info!("Crc invalid for stored image. Restoring image...") - } - _ => info!("Unexpected boot error. Restoring image..."), - }; + duprintln!(self.serial, "--Loadstone Initialised--"); + if let Some(image) = self.try_update_image() { + duprintln!(self.serial, "Attempting to boot from default bank."); + match self.boot(image).unwrap_err() { + Error::BankInvalid => { + info!("Attempted to boot from invalid bank. Restoring image...") + } + Error::BankEmpty => { + info!("Attempted to boot from empty bank. Restoring image...") + } + Error::CrcInvalid => { + info!("Crc invalid for stored image. Restoring image...") + } + _ => info!("Unexpected boot error. Restoring image..."), + }; + } match self.restore() { - Ok(()) => { - self.boot(DEFAULT_BOOT_BANK).expect("FATAL: Failed to boot from verified image!") - } + Ok(image) => self.boot(image).expect("FATAL: Failed to boot from verified image!"), Err(e) => { info!("Failed to restore. Error: {:?}", e); self.recover(); @@ -83,9 +81,11 @@ where } /// If the current bootable (MCU flash) image is different from the top - /// non-golden external image, attempts to replace it. - fn try_update_image(&mut self) { + /// non-golden external image, attempts to replace it. Returns the current + /// bootable image if available. + fn try_update_image(&mut self) -> Option> { let boot_bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); + duprintln!(self.serial, "Checking for image updates..."); if let Ok(current_image) = image::image_at(&mut self.mcu_flash, *boot_bank) { for external_bank in self.external_banks.iter().filter(|b| !b.is_golden) { duprintln!( @@ -99,34 +99,55 @@ where Ok(image) if image.crc() != current_image.crc() => { duprintln!( self.serial, - "Replacing golden image with external bank {:?}...", + "Replacing current image with external bank {:?}...", external_bank.index ); self.copy_image(*external_bank, *boot_bank, false).unwrap(); + duprintln!( + self.serial, + "Replaced image with external bank {:?}.", + external_bank.index + ); } Ok(_image) => break, _ => (), } } + duprintln!(self.serial, "No newer image found."); + Some(current_image) + } else { + duprintln!(self.serial, "No current image."); + None } - duprintln!(self.serial, "No newer image found."); } /// Restores an image from the preferred external bank. If it fails, /// attempts to restore from the golden image. - fn restore(&mut self) -> Result<(), Error> { + fn restore(&mut self) -> Result, Error> { // Attempt to restore from normal image let output = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); - for input in self.external_banks.iter().filter(|b| !b.is_golden) { - if self.copy_image(*input, *output, false).is_ok() { - return Ok(()); + for input_bank in self.external_banks.iter().filter(|b| !b.is_golden) { + duprintln!(self.serial, "Attempting to restore from bank {:?}.", input_bank.index); + if self.copy_image(*input_bank, *output, false).is_ok() { + duprintln!( + self.serial, + "Restored image from external bank {:?}.", + input_bank.index + ); + return Ok(image::image_at(&mut self.mcu_flash, *output)?); }; } // Attempt to restore from golden image let golden_bank = self.external_banks.iter().find(|b| b.is_golden).unwrap(); + duprintln!(self.serial, "Attempting to restore from golden bank {:?}.", golden_bank.index); if self.copy_image(*golden_bank, *output, true).is_ok() { - return Ok(()); + duprintln!( + self.serial, + "Restored image from external golden bank {:?}.", + golden_bank.index + ); + return Ok(image::image_at(&mut self.mcu_flash, *output)?); }; Err(Error::NoImageToRestoreFrom) @@ -144,27 +165,26 @@ where }) .unwrap(); } - if !image::image_at(&mut self.external_flash, *golden_bank).unwrap().is_golden() { - duprintln!(self.serial, "FATAL: Flashed image is not a golden image"); - } else { - duprintln!(self.serial, "Finished flashing golden image."); + + match image::image_at(&mut self.external_flash, *golden_bank) { + Ok(image) if !image.is_golden() => { + duprintln!(self.serial, "FATAL: Flashed image is not a golden image") + } + Err(e) => { + duprintln!(self.serial, "FATAL: Image did not flash correctly."); + e.report(&mut self.serial); + } + _ => duprintln!(self.serial, "Finished flashing golden image."), } + duprintln!(self.serial, "Rebooting..."); SCB::sys_reset(); } /// Boots into a given memory bank. - pub fn boot(&mut self, bank_index: u8) -> Result { - let bank = - self.mcu_banks.iter().find(|b| b.index == bank_index).ok_or(Error::BankInvalid)?; - - if !bank.bootable { - return Err(Error::BankInvalid); - } - - image::image_at(&mut self.mcu_flash, *bank)?.size(); + pub fn boot(&mut self, image: Image) -> Result { warn!("Jumping to a new firmware image. This will break `defmt`."); - let image_location_raw: usize = bank.location.into(); + let image_location_raw: usize = image.location().into(); // NOTE(Safety): Thoroughly unsafe operations, for obvious reasons: We are jumping to an // entirely different firmware image! We have to assume everything is at the right place, @@ -199,6 +219,13 @@ where must_be_golden: bool, ) -> Result<(), Error> { let input_image = image::image_at(&mut self.external_flash, input_bank)?; + duprintln!( + self.serial, + "Image found at bank {:?} [Address {:?}, size {:?}], copying to boot bank.", + input_bank.index, + input_image.location().into(), + input_image.size() + ); if must_be_golden && !input_image.is_golden() { return Err(Error::DeviceError("Image is not golden")); } @@ -210,6 +237,7 @@ where let mut byte_index = 0usize; let total_size = input_image.size() + CRC_SIZE_BYTES + MAGIC_STRING.len(); + while byte_index < total_size { let bytes_to_read = min(TRANSFER_BUFFER_SIZE, total_size.saturating_sub(byte_index)); block!(self diff --git a/src/devices/cli/commands/mod.rs b/src/devices/cli/commands/mod.rs index 36c8863d..a25cefe3 100644 --- a/src/devices/cli/commands/mod.rs +++ b/src/devices/cli/commands/mod.rs @@ -25,7 +25,7 @@ commands!( cli, boot_manager, names, helpstrings [ bank.index, if bank.bootable { "Bootable" } else { "Non-Bootable" }, bank.size, - if bank.is_golden { "- GOLDEN" } else { "" }).ok().unwrap(); + if bank.is_golden { " - GOLDEN" } else { "" }).ok().unwrap(); } }, @@ -33,9 +33,11 @@ commands!( cli, boot_manager, names, helpstrings [ uprintln!(cli.serial, "External images:"); for bank in boot_manager.external_banks() { if let Ok(image) = image::image_at(&mut boot_manager.external_flash, bank) { - uwriteln!(cli.serial, " - [IMAGE] - Size: {}b - CRC: {} ", + uwriteln!(cli.serial, "Bank {} - [IMAGE] - Size: {}b - CRC: {}{}", + bank.index, image.size(), - image.crc()).ok().unwrap(); + image.crc(), + if image.is_golden() { " - GOLDEN" } else { "" }).ok().unwrap(); } } diff --git a/src/devices/cli/mod.rs b/src/devices/cli/mod.rs index a576acca..cab961c9 100644 --- a/src/devices/cli/mod.rs +++ b/src/devices/cli/mod.rs @@ -25,7 +25,8 @@ use super::boot_manager::BootManager; pub mod file_transfer; const GREETING: &str = - "--=Loadstone demo app CLI + Boot Manager=--\ntype `help` for a list of commands."; + //"--=Loadstone demo app CLI + Boot Manager=--\ntype `help` for a list of commands."; + "--=IRREGULAR=-"; const PROMPT: &str = "\n> "; const BUFFER_SIZE: usize = 256; diff --git a/src/devices/image.rs b/src/devices/image.rs index 4178c951..0989c32c 100644 --- a/src/devices/image.rs +++ b/src/devices/image.rs @@ -1,17 +1,26 @@ use blue_hal::{ hal::flash, - utilities::{iterator::UntilSequence, memory::Address}, + utilities::{buffer::CollectSlice, iterator::UntilSequence, memory::Address}, }; use crc::{crc32, Hasher32}; -use defmt::info; use nb::{self, block}; use crate::error::Error; /// This string precedes the CRC for golden images only pub const GOLDEN_STRING: &str = "XPIcbOUrpG"; -/// This string must terminate any valid images, after CRC +/// This string, INVERTED BYTEWISE must terminate any valid images, after CRC +/// +/// Note: Why inverted? Because if we used it as-is, no code that includes this +/// constant could be used as a firmware image, as it contains the magic string +/// halfway through. pub const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; +pub fn magic_string_inverted() -> [u8; MAGIC_STRING.len()] { + let mut inverted = [0u8; MAGIC_STRING.len()]; + let mut bytes = MAGIC_STRING.as_bytes().iter().map(|b| !b); + bytes.collect_slice(&mut inverted); + inverted +} pub const CRC_SIZE_BYTES: usize = 4; #[derive(Clone, Copy, Debug)] @@ -56,7 +65,7 @@ where // TODO optimise this away so we don't have to scan the image twice. (e.g. with a "window" // buffer for the CRC); let image_size_with_crc = - flash.bytes(bank.location).take(bank.size).until_sequence(MAGIC_STRING.as_bytes()).count(); + flash.bytes(bank.location).take(bank.size).until_sequence(&magic_string_inverted()).count(); let image_size = image_size_with_crc.saturating_sub(CRC_SIZE_BYTES); let image_bytes = flash.bytes(bank.location).take(image_size); let digest = image_bytes.fold(crc32::Digest::new(crc32::IEEE), |mut digest, byte| { @@ -64,15 +73,11 @@ where digest }); let calculated_crc = digest.sum32(); - info!("Done verifying image. Crc: {:?}, size: {:?}", calculated_crc, image_size); - let crc_offset = image_size; let mut crc_bytes = [0u8; CRC_SIZE_BYTES]; block!(flash.read(bank.location + crc_offset, &mut crc_bytes))?; let crc = u32::from_le_bytes(crc_bytes); - info!("Retrieved crc: {:?}", crc); - let golden_string_offset = crc_offset.saturating_sub(GOLDEN_STRING.len()); let mut golden_bytes = [0u8; GOLDEN_STRING.len()]; block!(flash.read(bank.location + golden_string_offset, &mut golden_bytes))?; @@ -109,24 +114,11 @@ mod tests { fn test_image_with_crc() -> [u8; 38] { let mut array = [0u8; 38]; array[..2].copy_from_slice(&[0xAAu8, 0xBB]); // Image - array[2..34].copy_from_slice(MAGIC_STRING.as_bytes()); + array[2..34].copy_from_slice(&magic_string_inverted()); array[34..].copy_from_slice(&[0x98, 0x2c, 0x82, 0x49]); // CRC array } - #[test] - fn retrieving_image_from_flash() { - let mut flash = FakeFlash::new(Address(0)); - let bank = - Bank { index: 1, size: 512, location: Address(0), bootable: false, is_golden: false }; - let image_with_crc = test_image_with_crc(); - flash.write(Address(0), &image_with_crc).unwrap(); - assert_eq!( - Ok(Image { size: 2, location: bank.location, bootable: bank.bootable, golden: false }), - image_at(&mut flash, bank) - ); - } - #[test] fn retrieving_broken_image_fails() { let mut flash = FakeFlash::new(Address(0)); diff --git a/tools/crc_image_tool/src/main.rs b/tools/crc_image_tool/src/main.rs index 848c3343..623548a6 100644 --- a/tools/crc_image_tool/src/main.rs +++ b/tools/crc_image_tool/src/main.rs @@ -4,8 +4,16 @@ use crc::crc32::{self, Hasher32}; use std::{io::BufReader, io::BufRead, fs::File, io::prelude::*}; use byteorder::{LittleEndian, WriteBytesExt}; -const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; const GOLDEN_STRING: &str = "XPIcbOUrpG"; +/// This string, INVERTED BYTEWISE must terminate any valid images, after CRC +/// +/// Note: Why inverted? Because if we used it as-is, no code that includes this +/// constant could be used as a firmware image, as it contains the magic string +/// halfway through. +pub const MAGIC_STRING: &str = "HSc7c2ptydZH2QkqZWPcJgG3JtnJ6VuA"; +pub fn magic_string_inverted() -> Vec { + MAGIC_STRING.as_bytes().iter().map(|b| !b).collect() +} #[derive(Clap)] #[clap(about = "Tool to calculate and append CRC to firmware images", version = "1.0", author = "Pablo Mansanet ")] @@ -30,7 +38,9 @@ fn main() -> std::io::Result<()> { buf_reader.consume(buf_reader.buffer().len()) } } - digest.write(GOLDEN_STRING.as_bytes()); + if opts.golden { + digest.write(GOLDEN_STRING.as_bytes()); + } println!("Final CRC is {} (0x{:8x})", digest.sum32(), digest.sum32()); let mut final_crc = [0u8; 4]; @@ -46,7 +56,7 @@ fn main() -> std::io::Result<()> { println!("* Appending CRC string"); firmware.write(&final_crc)?; println!("* Appending magic string"); - firmware.write(MAGIC_STRING.as_bytes())?; + firmware.write(&magic_string_inverted())?; println!("Done!"); Ok(()) From bbde9afdd12d2b320f941a1d6298ccfce384cdb2 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Thu, 28 Jan 2021 16:20:38 +0100 Subject: [PATCH 66/67] Improve message for corner case --- src/devices/bootloader.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 3b4e43e3..0e01aeec 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -227,6 +227,10 @@ where input_image.size() ); if must_be_golden && !input_image.is_golden() { + duprintln!( + self.serial, + "Image is not golden.", + ); return Err(Error::DeviceError("Image is not golden")); } let input_image_start_address = input_bank.location; From a34ccaf7e11c046332e0cb40dfa16dcb9ff14fa3 Mon Sep 17 00:00:00 2001 From: PabloMansanet Date: Mon, 1 Feb 2021 14:51:03 +0100 Subject: [PATCH 67/67] Address review comments --- src/devices/bootloader.rs | 63 ++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/devices/bootloader.rs b/src/devices/bootloader.rs index 0e01aeec..3282d137 100644 --- a/src/devices/bootloader.rs +++ b/src/devices/bootloader.rs @@ -86,39 +86,42 @@ where fn try_update_image(&mut self) -> Option> { let boot_bank = self.mcu_banks.iter().find(|b| b.index == DEFAULT_BOOT_BANK).unwrap(); duprintln!(self.serial, "Checking for image updates..."); - if let Ok(current_image) = image::image_at(&mut self.mcu_flash, *boot_bank) { - for external_bank in self.external_banks.iter().filter(|b| !b.is_golden) { - duprintln!( - self.serial, - "Scanning external bank {:?} for a newer image...", - external_bank.index - ); - match image::image_at(&mut self.external_flash, *external_bank) { - // Using CRC for identification for the time being. Will become - // the image's signed hash, which is a valid unique identifier. - Ok(image) if image.crc() != current_image.crc() => { - duprintln!( - self.serial, - "Replacing current image with external bank {:?}...", - external_bank.index - ); - self.copy_image(*external_bank, *boot_bank, false).unwrap(); - duprintln!( - self.serial, - "Replaced image with external bank {:?}.", - external_bank.index - ); - } - Ok(_image) => break, - _ => (), - } - } - duprintln!(self.serial, "No newer image found."); - Some(current_image) + + let current_image = if let Ok(image) = image::image_at(&mut self.mcu_flash, *boot_bank) { + image } else { duprintln!(self.serial, "No current image."); - None + return None; + }; + + for external_bank in self.external_banks.iter().filter(|b| !b.is_golden) { + duprintln!( + self.serial, + "Scanning external bank {:?} for a newer image...", + external_bank.index + ); + match image::image_at(&mut self.external_flash, *external_bank) { + // Using CRC for identification for the time being. Will become + // the image's signed hash, which is a valid unique identifier. + Ok(image) if image.crc() != current_image.crc() => { + duprintln!( + self.serial, + "Replacing current image with external bank {:?}...", + external_bank.index + ); + self.copy_image(*external_bank, *boot_bank, false).unwrap(); + duprintln!( + self.serial, + "Replaced image with external bank {:?}.", + external_bank.index + ); + } + Ok(_image) => break, + _ => (), + } } + duprintln!(self.serial, "No newer image found."); + Some(current_image) } /// Restores an image from the preferred external bank. If it fails,