From b452a6bcf6858893a85882614e2dcde5a3405748 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Jan 2024 18:18:35 +0100 Subject: Centralize license and MSRV boilerplate into the repo readme. --- LICENSE-APACHE | 2 +- LICENSE-MIT | 2 +- README.md | 8 +++++++- cyw43-pio/README.md | 14 -------------- cyw43/README.md | 15 --------------- embassy-boot/boot/README.md | 15 --------------- embassy-boot/nrf/README.md | 15 --------------- embassy-boot/rp/README.md | 14 -------------- embassy-boot/stm32/README.md | 14 -------------- embassy-executor-macros/README.md | 10 ---------- embassy-futures/README.md | 15 --------------- embassy-hal-internal/README.md | 10 ---------- embassy-net-adin1110/README.md | 10 ---------- embassy-net-driver-channel/README.md | 10 ---------- embassy-net-driver/README.md | 10 ---------- embassy-net-enc28j60/README.md | 10 ---------- embassy-net-esp-hosted/README.md | 11 ----------- embassy-net-ppp/README.md | 10 ---------- embassy-net-tuntap/README.md | 10 ---------- embassy-net-wiznet/README.md | 11 ----------- embassy-net/README.md | 10 ---------- embassy-nrf/README.md | 15 --------------- embassy-rp/README.md | 14 -------------- embassy-sync/README.md | 14 -------------- embassy-usb-dfu/README.md | 14 -------------- embassy-usb-driver/README.md | 15 --------------- embassy-usb-logger/README.md | 14 -------------- embassy-usb/README.md | 16 ---------------- 28 files changed, 9 insertions(+), 319 deletions(-) diff --git a/LICENSE-APACHE b/LICENSE-APACHE index ea4fa15c9..8f7956e20 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2019-2022 Embassy project contributors +Copyright (c) Embassy project contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index 87c052836..1fe5730a4 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Embassy project contributors +Copyright (c) Embassy project contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index d2a24dfcc..24347a43f 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ EMBedded ASYnc! :) ## License -This work is licensed under either of +Embassy is licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) @@ -153,5 +153,11 @@ This work is licensed under either of at your option. +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + [1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started [2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples diff --git a/cyw43-pio/README.md b/cyw43-pio/README.md index 2b22db360..4a2b2aa4b 100644 --- a/cyw43-pio/README.md +++ b/cyw43-pio/README.md @@ -1,17 +1,3 @@ # cyw43-pio RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. The PIO driver offloads SPI communication with the WiFi chip and improves throughput. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/cyw43/README.md b/cyw43/README.md index 2c24c7d36..dabdf0471 100644 --- a/cyw43/README.md +++ b/cyw43/README.md @@ -44,18 +44,3 @@ This example implements a TCP echo server on port 1234. You can try connecting t nc 192.168.0.250 1234 ``` Send it some data, you should see it echoed back and printed in the firmware's logs. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md index 3fc81f24b..3c2d45e96 100644 --- a/embassy-boot/boot/README.md +++ b/embassy-boot/boot/README.md @@ -33,18 +33,3 @@ The bootloader supports different hardware in separate crates: * `embassy-boot-nrf` - for the nRF microcontrollers. * `embassy-boot-rp` - for the RP2040 microcontrollers. * `embassy-boot-stm32` - for the STM32 microcontrollers. - - -## Minimum supported Rust version (MSRV) - -`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md index fe581823d..9dc5b0eb9 100644 --- a/embassy-boot/nrf/README.md +++ b/embassy-boot/nrf/README.md @@ -9,18 +9,3 @@ An adaptation of `embassy-boot` for nRF. * Load applications with or without the softdevice. * Configure bootloader partitions based on linker script. * Using watchdog timer to detect application failure. - - -## Minimum supported Rust version (MSRV) - -`embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md index 315d655e3..b664145a9 100644 --- a/embassy-boot/rp/README.md +++ b/embassy-boot/rp/README.md @@ -10,17 +10,3 @@ NOTE: The applications using this bootloader should not link with the `link-rp.x * Configure bootloader partitions based on linker script. * Load applications from active partition. - -## Minimum supported Rust version (MSRV) - -`embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md index b4d7ba5a4..f6dadc8e7 100644 --- a/embassy-boot/stm32/README.md +++ b/embassy-boot/stm32/README.md @@ -8,17 +8,3 @@ An adaptation of `embassy-boot` for STM32. * Configure bootloader partitions based on linker script. * Load applications from active partition. - -## Minimum supported Rust version (MSRV) - -`embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-executor-macros/README.md b/embassy-executor-macros/README.md index a959c85d5..3a8d49aa1 100644 --- a/embassy-executor-macros/README.md +++ b/embassy-executor-macros/README.md @@ -3,13 +3,3 @@ An [Embassy](https://embassy.dev) project. NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-futures/README.md b/embassy-futures/README.md index 7add22c7b..b28a8431a 100644 --- a/embassy-futures/README.md +++ b/embassy-futures/README.md @@ -11,18 +11,3 @@ ideal for embedded systems. ## Interoperability Futures from this crate can run on any executor. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - diff --git a/embassy-hal-internal/README.md b/embassy-hal-internal/README.md index 6b060d1c0..1adce5b33 100644 --- a/embassy-hal-internal/README.md +++ b/embassy-hal-internal/README.md @@ -4,13 +4,3 @@ An [Embassy](https://embassy.dev) project. Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport everything you need to use them effectively. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md index 8ea10b714..39a38960d 100644 --- a/embassy-net-adin1110/README.md +++ b/embassy-net-adin1110/README.md @@ -76,13 +76,3 @@ Summary: Size/request: 289 B Size/sec: 51.11 KiB ``` - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-driver-channel/README.md b/embassy-net-driver-channel/README.md index 90a216388..1955624d1 100644 --- a/embassy-net-driver-channel/README.md +++ b/embassy-net-driver-channel/README.md @@ -84,13 +84,3 @@ These `embassy-net` drivers are implemented using this crate. You can look at th ## Interoperability This crate can run on any executor. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-driver/README.md b/embassy-net-driver/README.md index 6a757380d..24fcaafc4 100644 --- a/embassy-net-driver/README.md +++ b/embassy-net-driver/README.md @@ -16,13 +16,3 @@ packet queues for RX and TX. ## Interoperability This crate can run on any executor. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-enc28j60/README.md b/embassy-net-enc28j60/README.md index 39011ca13..5c663b52e 100644 --- a/embassy-net-enc28j60/README.md +++ b/embassy-net-enc28j60/README.md @@ -7,13 +7,3 @@ Based on [@japaric](https://github.com/japaric)'s [`enc28j60`](https://github.co ## Interoperability This crate can run on any executor. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-esp-hosted/README.md b/embassy-net-esp-hosted/README.md index 3c9cc4c9e..f6c216835 100644 --- a/embassy-net-esp-hosted/README.md +++ b/embassy-net-esp-hosted/README.md @@ -14,14 +14,3 @@ See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf528 This crate can run on any executor. It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). - - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-ppp/README.md b/embassy-net-ppp/README.md index 58d67395a..0eb7cee87 100644 --- a/embassy-net-ppp/README.md +++ b/embassy-net-ppp/README.md @@ -7,13 +7,3 @@ This crate can run on any executor. It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async). - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-tuntap/README.md b/embassy-net-tuntap/README.md index c5d9e746c..60a4a1bd9 100644 --- a/embassy-net-tuntap/README.md +++ b/embassy-net-tuntap/README.md @@ -5,13 +5,3 @@ ## Interoperability This crate can run on any executor. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net-wiznet/README.md b/embassy-net-wiznet/README.md index b8e4bdc8e..786aab18d 100644 --- a/embassy-net-wiznet/README.md +++ b/embassy-net-wiznet/README.md @@ -14,14 +14,3 @@ See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) di This crate can run on any executor. It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). - - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-net/README.md b/embassy-net/README.md index 52d048e6a..94aa6f550 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -51,13 +51,3 @@ This crate can run on any executor. [`embassy-time`](https://crates.io/crates/embassy-time) is used for timekeeping and timeouts. You must link an `embassy-time` driver in your project to use this crate. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md index 39de3854b..5e2bd86de 100644 --- a/embassy-nrf/README.md +++ b/embassy-nrf/README.md @@ -43,18 +43,3 @@ as the methods without the suffix will be allocating a statically sized buffer ( Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as mutable slices always reside in RAM. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - diff --git a/embassy-rp/README.md b/embassy-rp/README.md index cd79fe501..1d14eca52 100644 --- a/embassy-rp/README.md +++ b/embassy-rp/README.md @@ -7,17 +7,3 @@ for many peripherals. The benefit of using the async APIs is that the HAL takes complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. NOTE: The Embassy HALs can be used both for non-async and async operations. For async, you can choose which runtime you want to use. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 55618f72d..c2e13799e 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -17,17 +17,3 @@ Synchronization primitives and data structures with async support: ## Interoperability Futures from this crate can run on any executor. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-usb-dfu/README.md b/embassy-usb-dfu/README.md index d8bc19bfd..bdd5b033a 100644 --- a/embassy-usb-dfu/README.md +++ b/embassy-usb-dfu/README.md @@ -4,17 +4,3 @@ An implementation of the USB DFU 1.1 protocol using embassy-boot. It has 2 compo * DFU protocol mode, enabled by the `dfu` feature. This mode corresponds to the transfer phase DFU protocol described by the USB IF. It supports DFU_DNLOAD requests if marked by the user, and will automatically reset the chip once a DFU transaction has been completed. It also responds to DFU_GETSTATUS, DFU_GETSTATE, DFU_ABORT, and DFU_CLRSTATUS with no user intervention. * DFU runtime mode, enabled by the `application feature`. This mode allows users to expose a DFU interface on their USB device, informing the host of the capability to DFU over USB, and allowing the host to reset the device into its bootloader to complete a DFU operation. Supports DFU_GETSTATUS and DFU_DETACH. When detach/reset is seen by the device as described by the standard, will write a new DFU magic number into the bootloader state in flash, and reset the system. - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md index 93aef7596..7628a6937 100644 --- a/embassy-usb-driver/README.md +++ b/embassy-usb-driver/README.md @@ -15,18 +15,3 @@ instead of this one. ## Interoperability This crate can run on any executor. - -## Minimum supported Rust version (MSRV) - -This crate requires nightly Rust, due to using "async fn in trait" support. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - diff --git a/embassy-usb-logger/README.md b/embassy-usb-logger/README.md index 6cb18e87d..81b0dcd0e 100644 --- a/embassy-usb-logger/README.md +++ b/embassy-usb-logger/README.md @@ -13,17 +13,3 @@ async fn logger_task(driver: Driver<'static, USB>) { embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); } ``` - -## Minimum supported Rust version (MSRV) - -Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-usb/README.md b/embassy-usb/README.md index a3d45b561..da656e8e9 100644 --- a/embassy-usb/README.md +++ b/embassy-usb/README.md @@ -23,22 +23,6 @@ with different values, compilation fails. Max amount of interfaces that can be created in one device. Default: 4. - ## Interoperability This crate can run on any executor. - -## Minimum supported Rust version (MSRV) - -This crate requires nightly Rust, due to using "async fn in trait" support. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - -- cgit From e0775fbc8ab1ecc83bce42fe6e11accf481bc9e1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Jan 2024 18:55:59 +0100 Subject: Flatten embassy-boot dir tree --- .github/ci/doc.sh | 8 +- .github/ci/test.sh | 6 +- ci.sh | 8 +- embassy-boot-nrf/Cargo.toml | 44 +++ embassy-boot-nrf/README.md | 11 + embassy-boot-nrf/src/fmt.rs | 258 +++++++++++++ embassy-boot-nrf/src/lib.rs | 145 ++++++++ embassy-boot-rp/Cargo.toml | 79 ++++ embassy-boot-rp/README.md | 12 + embassy-boot-rp/build.rs | 8 + embassy-boot-rp/src/fmt.rs | 258 +++++++++++++ embassy-boot-rp/src/lib.rs | 90 +++++ embassy-boot-stm32/Cargo.toml | 70 ++++ embassy-boot-stm32/README.md | 10 + embassy-boot-stm32/build.rs | 8 + embassy-boot-stm32/src/fmt.rs | 258 +++++++++++++ embassy-boot-stm32/src/lib.rs | 44 +++ embassy-boot/Cargo.toml | 51 +++ embassy-boot/README.md | 35 ++ embassy-boot/boot/Cargo.toml | 51 --- embassy-boot/boot/README.md | 35 -- embassy-boot/boot/src/boot_loader.rs | 411 --------------------- .../boot/src/digest_adapters/ed25519_dalek.rs | 30 -- embassy-boot/boot/src/digest_adapters/mod.rs | 5 - embassy-boot/boot/src/digest_adapters/salty.rs | 29 -- embassy-boot/boot/src/firmware_updater/asynch.rs | 329 ----------------- embassy-boot/boot/src/firmware_updater/blocking.rs | 340 ----------------- embassy-boot/boot/src/firmware_updater/mod.rs | 49 --- embassy-boot/boot/src/fmt.rs | 258 ------------- embassy-boot/boot/src/lib.rs | 323 ---------------- embassy-boot/boot/src/mem_flash.rs | 170 --------- embassy-boot/boot/src/test_flash/asynch.rs | 64 ---- embassy-boot/boot/src/test_flash/blocking.rs | 68 ---- embassy-boot/boot/src/test_flash/mod.rs | 5 - embassy-boot/nrf/Cargo.toml | 44 --- embassy-boot/nrf/README.md | 11 - embassy-boot/nrf/src/fmt.rs | 258 ------------- embassy-boot/nrf/src/lib.rs | 145 -------- embassy-boot/rp/Cargo.toml | 79 ---- embassy-boot/rp/README.md | 12 - embassy-boot/rp/build.rs | 8 - embassy-boot/rp/src/fmt.rs | 258 ------------- embassy-boot/rp/src/lib.rs | 90 ----- embassy-boot/src/boot_loader.rs | 411 +++++++++++++++++++++ embassy-boot/src/digest_adapters/ed25519_dalek.rs | 30 ++ embassy-boot/src/digest_adapters/mod.rs | 5 + embassy-boot/src/digest_adapters/salty.rs | 29 ++ embassy-boot/src/firmware_updater/asynch.rs | 329 +++++++++++++++++ embassy-boot/src/firmware_updater/blocking.rs | 340 +++++++++++++++++ embassy-boot/src/firmware_updater/mod.rs | 49 +++ embassy-boot/src/fmt.rs | 258 +++++++++++++ embassy-boot/src/lib.rs | 323 ++++++++++++++++ embassy-boot/src/mem_flash.rs | 170 +++++++++ embassy-boot/src/test_flash/asynch.rs | 64 ++++ embassy-boot/src/test_flash/blocking.rs | 68 ++++ embassy-boot/src/test_flash/mod.rs | 5 + embassy-boot/stm32/Cargo.toml | 70 ---- embassy-boot/stm32/README.md | 10 - embassy-boot/stm32/build.rs | 8 - embassy-boot/stm32/src/fmt.rs | 258 ------------- embassy-boot/stm32/src/lib.rs | 44 --- embassy-usb-dfu/Cargo.toml | 2 +- examples/boot/application/nrf/Cargo.toml | 4 +- examples/boot/application/rp/Cargo.toml | 2 +- examples/boot/application/stm32f3/Cargo.toml | 2 +- examples/boot/application/stm32f7/Cargo.toml | 2 +- examples/boot/application/stm32h7/Cargo.toml | 2 +- examples/boot/application/stm32l0/Cargo.toml | 2 +- examples/boot/application/stm32l1/Cargo.toml | 2 +- examples/boot/application/stm32l4/Cargo.toml | 2 +- examples/boot/application/stm32wb-dfu/Cargo.toml | 2 +- examples/boot/application/stm32wl/Cargo.toml | 2 +- examples/boot/bootloader/nrf/Cargo.toml | 2 +- examples/boot/bootloader/rp/Cargo.toml | 2 +- examples/boot/bootloader/stm32/Cargo.toml | 2 +- examples/boot/bootloader/stm32wb-dfu/Cargo.toml | 2 +- 76 files changed, 3489 insertions(+), 3489 deletions(-) create mode 100644 embassy-boot-nrf/Cargo.toml create mode 100644 embassy-boot-nrf/README.md create mode 100644 embassy-boot-nrf/src/fmt.rs create mode 100644 embassy-boot-nrf/src/lib.rs create mode 100644 embassy-boot-rp/Cargo.toml create mode 100644 embassy-boot-rp/README.md create mode 100644 embassy-boot-rp/build.rs create mode 100644 embassy-boot-rp/src/fmt.rs create mode 100644 embassy-boot-rp/src/lib.rs create mode 100644 embassy-boot-stm32/Cargo.toml create mode 100644 embassy-boot-stm32/README.md create mode 100644 embassy-boot-stm32/build.rs create mode 100644 embassy-boot-stm32/src/fmt.rs create mode 100644 embassy-boot-stm32/src/lib.rs create mode 100644 embassy-boot/Cargo.toml create mode 100644 embassy-boot/README.md delete mode 100644 embassy-boot/boot/Cargo.toml delete mode 100644 embassy-boot/boot/README.md delete mode 100644 embassy-boot/boot/src/boot_loader.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/mod.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/salty.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/asynch.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/blocking.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/mod.rs delete mode 100644 embassy-boot/boot/src/fmt.rs delete mode 100644 embassy-boot/boot/src/lib.rs delete mode 100644 embassy-boot/boot/src/mem_flash.rs delete mode 100644 embassy-boot/boot/src/test_flash/asynch.rs delete mode 100644 embassy-boot/boot/src/test_flash/blocking.rs delete mode 100644 embassy-boot/boot/src/test_flash/mod.rs delete mode 100644 embassy-boot/nrf/Cargo.toml delete mode 100644 embassy-boot/nrf/README.md delete mode 100644 embassy-boot/nrf/src/fmt.rs delete mode 100644 embassy-boot/nrf/src/lib.rs delete mode 100644 embassy-boot/rp/Cargo.toml delete mode 100644 embassy-boot/rp/README.md delete mode 100644 embassy-boot/rp/build.rs delete mode 100644 embassy-boot/rp/src/fmt.rs delete mode 100644 embassy-boot/rp/src/lib.rs create mode 100644 embassy-boot/src/boot_loader.rs create mode 100644 embassy-boot/src/digest_adapters/ed25519_dalek.rs create mode 100644 embassy-boot/src/digest_adapters/mod.rs create mode 100644 embassy-boot/src/digest_adapters/salty.rs create mode 100644 embassy-boot/src/firmware_updater/asynch.rs create mode 100644 embassy-boot/src/firmware_updater/blocking.rs create mode 100644 embassy-boot/src/firmware_updater/mod.rs create mode 100644 embassy-boot/src/fmt.rs create mode 100644 embassy-boot/src/lib.rs create mode 100644 embassy-boot/src/mem_flash.rs create mode 100644 embassy-boot/src/test_flash/asynch.rs create mode 100644 embassy-boot/src/test_flash/blocking.rs create mode 100644 embassy-boot/src/test_flash/mod.rs delete mode 100644 embassy-boot/stm32/Cargo.toml delete mode 100644 embassy-boot/stm32/README.md delete mode 100644 embassy-boot/stm32/build.rs delete mode 100644 embassy-boot/stm32/src/fmt.rs delete mode 100644 embassy-boot/stm32/src/lib.rs diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 0bbe7f690..aaccb8a67 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -16,10 +16,10 @@ mv rust-toolchain-nightly.toml rust-toolchain.toml # which makes rustup very sad rustc --version > /dev/null -docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup -docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup -docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup -docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup +docserver-builder -i ./embassy-boot -o webroot/crates/embassy-boot/git.zup +docserver-builder -i ./embassy-boot-nrf -o webroot/crates/embassy-boot-nrf/git.zup +docserver-builder -i ./embassy-boot-rp -o webroot/crates/embassy-boot-rp/git.zup +docserver-builder -i ./embassy-boot-stm32 -o webroot/crates/embassy-boot-stm32/git.zup docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup diff --git a/.github/ci/test.sh b/.github/ci/test.sh index b6a5bcd56..8a58939f6 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -14,9 +14,9 @@ cargo test --manifest-path ./embassy-hal-internal/Cargo.toml cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue,mock-driver cargo test --manifest-path ./embassy-time-driver/Cargo.toml -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-dalek -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-salty +cargo test --manifest-path ./embassy-boot/Cargo.toml +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-dalek +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-salty cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote diff --git a/ci.sh b/ci.sh index ac0457679..7805c8ad1 100755 --- a/ci.sh +++ b/ci.sh @@ -136,10 +136,10 @@ cargo batch \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ - --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ - --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ - --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \ - --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ diff --git a/embassy-boot-nrf/Cargo.toml b/embassy-boot-nrf/Cargo.toml new file mode 100644 index 000000000..7fc53648a --- /dev/null +++ b/embassy-boot-nrf/Cargo.toml @@ -0,0 +1,44 @@ +[package] +edition = "2021" +name = "embassy-boot-nrf" +version = "0.1.0" +description = "Bootloader lib for nRF chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot-nrf/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-nrf/src/" +features = ["embassy-nrf/nrf52840"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-nrf = { version = "0.1.0", path = "../embassy-nrf", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +nrf-softdevice-mbr = { version = "0.2.0", optional = true } + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-nrf/defmt", +] +softdevice = [ + "nrf-softdevice-mbr", +] diff --git a/embassy-boot-nrf/README.md b/embassy-boot-nrf/README.md new file mode 100644 index 000000000..9dc5b0eb9 --- /dev/null +++ b/embassy-boot-nrf/README.md @@ -0,0 +1,11 @@ +# embassy-boot-nrf + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for nRF. + +## Features + +* Load applications with or without the softdevice. +* Configure bootloader partitions based on linker script. +* Using watchdog timer to detect application failure. diff --git a/embassy-boot-nrf/src/fmt.rs b/embassy-boot-nrf/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-nrf/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-nrf/src/lib.rs b/embassy-boot-nrf/src/lib.rs new file mode 100644 index 000000000..5b20a93c6 --- /dev/null +++ b/embassy-boot-nrf/src/lib.rs @@ -0,0 +1,145 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, +}; +use embassy_nrf::nvmc::PAGE_SIZE; +use embassy_nrf::peripherals::WDT; +use embassy_nrf::wdt; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for nRF devices. +pub struct BootLoader; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); + Self + } + + /// Boots the application without softdevice mechanisms. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(not(feature = "softdevice"))] + pub unsafe fn load(self, start: u32) -> ! { + let mut p = cortex_m::Peripherals::steal(); + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + cortex_m::asm::bootload(start as *const u32) + } + + /// Boots the application assuming softdevice is present. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(feature = "softdevice")] + pub unsafe fn load(self, _app: u32) -> ! { + use nrf_softdevice_mbr as mbr; + const NRF_SUCCESS: u32 = 0; + + // Address of softdevice which we'll forward interrupts to + let addr = 0x1000; + let mut cmd = mbr::sd_mbr_command_t { + command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, + params: mbr::sd_mbr_command_t__bindgen_ty_1 { + irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, + }, + }; + let ret = mbr::sd_mbr_command(&mut cmd); + assert_eq!(ret, NRF_SUCCESS); + + let msp = *(addr as *const u32); + let rv = *((addr + 4) as *const u32); + + trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); + + // These instructions perform the following operations: + // + // * Modify control register to use MSP as stack pointer (clear spsel bit) + // * Synchronize instruction barrier + // * Initialize stack pointer (0x1000) + // * Set link register to not return (0xFF) + // * Jump to softdevice reset vector + core::arch::asm!( + "mrs {tmp}, CONTROL", + "bics {tmp}, {spsel}", + "msr CONTROL, {tmp}", + "isb", + "msr MSP, {msp}", + "mov lr, {new_lr}", + "bx {rv}", + // `out(reg) _` is not permitted in a `noreturn` asm! call, + // so instead use `in(reg) 0` and don't restore it afterwards. + tmp = in(reg) 0, + spsel = in(reg) 2, + new_lr = in(reg) 0xFFFFFFFFu32, + msp = in(reg) msp, + rv = in(reg) rv, + options(noreturn), + ); + } +} + +/// A flash implementation that wraps any flash and will pet a watchdog when touching flash. +pub struct WatchdogFlash { + flash: FLASH, + wdt: wdt::WatchdogHandle, +} + +impl WatchdogFlash { + /// Start a new watchdog with a given flash and WDT peripheral and a timeout + pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { + let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { + Ok(x) => x, + Err(_) => { + // In case the watchdog is already running, just spin and let it expire, since + // we can't configure it anyway. This usually happens when we first program + // the device and the watchdog was previously active + info!("Watchdog already active with wrong config, waiting for it to timeout..."); + loop {} + } + }; + Self { flash, wdt } + } +} + +impl ErrorType for WatchdogFlash { + type Error = FLASH::Error; +} + +impl NorFlash for WatchdogFlash { + const WRITE_SIZE: usize = FLASH::WRITE_SIZE; + const ERASE_SIZE: usize = FLASH::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.write(offset, data) + } +} + +impl ReadNorFlash for WatchdogFlash { + const READ_SIZE: usize = FLASH::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot-rp/Cargo.toml b/embassy-boot-rp/Cargo.toml new file mode 100644 index 000000000..dacb27747 --- /dev/null +++ b/embassy-boot-rp/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2021" +name = "embassy-boot-rp" +version = "0.1.0" +description = "Bootloader lib for RP2040 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-rp/src/" +target = "thumbv6m-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-rp = { version = "0.1.0", path = "../embassy-rp", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +embassy-time = { version = "0.2.0", path = "../embassy-time" } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-rp/defmt", +] +log = [ + "dep:log", + "embassy-boot/log", + "embassy-rp/log", +] +debug = ["defmt-rtt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot-rp/README.md b/embassy-boot-rp/README.md new file mode 100644 index 000000000..b664145a9 --- /dev/null +++ b/embassy-boot-rp/README.md @@ -0,0 +1,12 @@ +# embassy-boot-rp + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for RP2040. + +NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. diff --git a/embassy-boot-rp/build.rs b/embassy-boot-rp/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot-rp/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-boot-rp/src/fmt.rs b/embassy-boot-rp/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-rp/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-rp/src/lib.rs b/embassy-boot-rp/src/lib.rs new file mode 100644 index 000000000..07a5b3f4d --- /dev/null +++ b/embassy-boot-rp/src/lib.rs @@ -0,0 +1,90 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, State, +}; +use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; +use embassy_rp::peripherals::{FLASH, WATCHDOG}; +use embassy_rp::watchdog::Watchdog; +use embassy_time::Duration; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for RP2040 devices. +pub struct BootLoader; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + + cortex_m::asm::bootload(start as *const u32) + } +} + +/// A flash implementation that will feed a watchdog when touching flash. +pub struct WatchdogFlash<'d, const SIZE: usize> { + flash: Flash<'d, FLASH, Blocking, SIZE>, + watchdog: Watchdog, +} + +impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { + /// Start a new watchdog with a given flash and watchdog peripheral and a timeout + pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { + let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); + let mut watchdog = Watchdog::new(watchdog); + watchdog.start(timeout); + Self { flash, watchdog } + } +} + +impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { + type Error = as ErrorType>::Error; +} + +impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { + const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; + const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_write(offset, data) + } +} + +impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { + const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot-stm32/Cargo.toml b/embassy-boot-stm32/Cargo.toml new file mode 100644 index 000000000..f4e31bae8 --- /dev/null +++ b/embassy-boot-stm32/Cargo.toml @@ -0,0 +1,70 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32" +version = "0.1.0" +description = "Bootloader lib for STM32 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot-stm32/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-stm32/src/" +features = ["embassy-stm32/stm32f429zi"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] +log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] +debug = ["defmt-rtt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot-stm32/README.md b/embassy-boot-stm32/README.md new file mode 100644 index 000000000..f6dadc8e7 --- /dev/null +++ b/embassy-boot-stm32/README.md @@ -0,0 +1,10 @@ +# embassy-boot-stm32 + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for STM32. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. diff --git a/embassy-boot-stm32/build.rs b/embassy-boot-stm32/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot-stm32/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-boot-stm32/src/fmt.rs b/embassy-boot-stm32/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-stm32/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-stm32/src/lib.rs b/embassy-boot-stm32/src/lib.rs new file mode 100644 index 000000000..4b4091ac9 --- /dev/null +++ b/embassy-boot-stm32/src/lib.rs @@ -0,0 +1,44 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, State, +}; +use embedded_storage::nor_flash::NorFlash; + +/// A bootloader for STM32 devices. +pub struct BootLoader { + /// The reported state of the bootloader after preparing for boot + pub state: State, +} + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self { state } + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + + cortex_m::asm::bootload(start as *const u32) + } +} diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml new file mode 100644 index 000000000..a70849018 --- /dev/null +++ b/embassy-boot/Cargo.toml @@ -0,0 +1,51 @@ +[package] +edition = "2021" +name = "embassy-boot" +version = "0.1.1" +description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +digest = "0.10" +log = { version = "0.4", optional = true } +ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } +embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +salty = { version = "0.3", optional = true } +signature = { version = "2.0", default-features = false } + +[dev-dependencies] +log = "0.4" +env_logger = "0.9" +rand = "0.8" +futures = { version = "0.3", features = ["executor"] } +sha1 = "0.10.5" +critical-section = { version = "1.1.1", features = ["std"] } +ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } + +[features] +ed25519-dalek = ["dep:ed25519-dalek", "_verify"] +ed25519-salty = ["dep:salty", "_verify"] + +#Internal features +_verify = [] diff --git a/embassy-boot/README.md b/embassy-boot/README.md new file mode 100644 index 000000000..3c2d45e96 --- /dev/null +++ b/embassy-boot/README.md @@ -0,0 +1,35 @@ +# embassy-boot + +An [Embassy](https://embassy.dev) project. + +A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. + +The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. + +By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. + +## Overview + +The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: + +* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. +* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application. +* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition. +* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. + +For any partition, the following preconditions are required: + +* Partitions must be aligned on the page size. +* Partitions must be a multiple of the page size. + +The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. + +For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html). + +## Hardware support + +The bootloader supports different hardware in separate crates: + +* `embassy-boot-nrf` - for the nRF microcontrollers. +* `embassy-boot-rp` - for the RP2040 microcontrollers. +* `embassy-boot-stm32` - for the STM32 microcontrollers. diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml deleted file mode 100644 index 3c84ffcd3..000000000 --- a/embassy-boot/boot/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot" -version = "0.1.1" -description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" -target = "thumbv7em-none-eabi" -features = ["defmt"] - -[package.metadata.docs.rs] -features = ["defmt"] - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -digest = "0.10" -log = { version = "0.4", optional = true } -ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -salty = { version = "0.3", optional = true } -signature = { version = "2.0", default-features = false } - -[dev-dependencies] -log = "0.4" -env_logger = "0.9" -rand = "0.8" -futures = { version = "0.3", features = ["executor"] } -sha1 = "0.10.5" -critical-section = { version = "1.1.1", features = ["std"] } -ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } - -[features] -ed25519-dalek = ["dep:ed25519-dalek", "_verify"] -ed25519-salty = ["dep:salty", "_verify"] - -#Internal features -_verify = [] diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md deleted file mode 100644 index 3c2d45e96..000000000 --- a/embassy-boot/boot/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# embassy-boot - -An [Embassy](https://embassy.dev) project. - -A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. - -The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. - -By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. - -## Overview - -The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: - -* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. -* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application. -* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition. -* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. - -For any partition, the following preconditions are required: - -* Partitions must be aligned on the page size. -* Partitions must be a multiple of the page size. - -The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. - -For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html). - -## Hardware support - -The bootloader supports different hardware in separate crates: - -* `embassy-boot-nrf` - for the nRF microcontrollers. -* `embassy-boot-rp` - for the RP2040 microcontrollers. -* `embassy-boot-stm32` - for the STM32 microcontrollers. diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs deleted file mode 100644 index e568001bc..000000000 --- a/embassy-boot/boot/src/boot_loader.rs +++ /dev/null @@ -1,411 +0,0 @@ -use core::cell::RefCell; - -use embassy_embedded_hal::flash::partition::BlockingPartition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; - -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// Errors returned by bootloader -#[derive(PartialEq, Eq, Debug)] -pub enum BootError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Invalid bootloader magic - BadMagic, -} - -#[cfg(feature = "defmt")] -impl defmt::Format for BootError { - fn format(&self, fmt: defmt::Formatter) { - match self { - BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), - BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), - } - } -} - -impl From for BootError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - BootError::Flash(error.kind()) - } -} - -/// Bootloader flash configuration holding the three flashes used by the bootloader -/// -/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. -/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition -/// the provided flash according to symbols defined in the linkerfile. -pub struct BootLoaderConfig { - /// Flash type used for the active partition - the partition which will be booted from. - pub active: ACTIVE, - /// Flash type used for the dfu partition - the partition which will be swapped in when requested. - pub dfu: DFU, - /// Flash type used for the state partition. - pub state: STATE, -} - -impl<'a, FLASH: NorFlash> - BootLoaderConfig< - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, - > -{ - /// Create a bootloader config from the flash and address symbols defined in the linkerfile - // #[cfg(target_os = "none")] - pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - let start = &__bootloader_active_start as *const u32 as u32; - let end = &__bootloader_active_end as *const u32 as u32; - trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - - Self { active, dfu, state } - } -} - -/// BootLoader works with any flash implementing embedded_storage. -pub struct BootLoader { - active: ACTIVE, - dfu: DFU, - /// The state partition has the following format: - /// All ranges are in multiples of WRITE_SIZE bytes. - /// | Range | Description | - /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | - /// | 2..2 + N | Progress index used while swapping or reverting - state: STATE, -} - -impl BootLoader { - /// Get the page size which is the "unit of operation" within the bootloader. - const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { - ACTIVE::ERASE_SIZE as u32 - } else { - DFU::ERASE_SIZE as u32 - }; - - /// Create a new instance of a bootloader with the flash partitions. - /// - /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. - /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: config.active, - dfu: config.dfu, - state: config.state, - } - } - - /// Perform necessary boot preparations like swapping images. - /// - /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap - /// algorithm to work correctly. - /// - /// The provided aligned_buf argument must satisfy any alignment requirements - /// given by the partition flashes. All flash operations will use this buffer. - /// - /// ## SWAPPING - /// - /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. - /// The swap index contains the copy progress, as to allow continuation of the copy process on - /// power failure. The index counter is represented within 1 or more pages (depending on total - /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) - /// contains a zero value. This ensures that index updates can be performed atomically and - /// avoid a situation where the wrong index value is set (page write size is "atomic"). - /// - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 0 | 1 | 2 | 3 | - | - /// | DFU | 0 | 3 | 2 | 1 | X | - /// - /// The algorithm starts by copying 'backwards', and after the first step, the layout is - /// as follows: - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 1 | 1 | 2 | 1 | - | - /// | DFU | 1 | 3 | 2 | 1 | 3 | - /// - /// The next iteration performs the same steps - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 2 | 1 | 2 | 1 | - | - /// | DFU | 2 | 3 | 2 | 2 | 3 | - /// - /// And again until we're done - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 3 | 3 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// - /// ## REVERTING - /// - /// The reverting algorithm uses the swap index to discover that images were swapped, but that - /// the application failed to mark the boot successful. In this case, the revert algorithm will - /// run. - /// - /// The revert index is located separately from the swap index, to ensure that revert can continue - /// on power failure. - /// - /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 2 | 2 | 3 | - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 3 | - | - /// | DFU | 3 | 3 | 2 | 1 | 3 | - /// - pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { - // Ensure we have enough progress pages to store copy progress - assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); - assert!(aligned_buf.len() >= STATE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); - - // Ensure our partitions are able to handle boot operations - assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); - - // Copy contents from partition N to active - let state = self.read_state(aligned_buf)?; - if state == State::Swap { - // - // Check if we already swapped. If we're in the swap state, this means we should revert - // since the app has failed to mark boot as successful - // - if !self.is_swapped(aligned_buf)? { - trace!("Swapping"); - self.swap(aligned_buf)?; - trace!("Swapping done"); - } else { - trace!("Reverting"); - self.revert(aligned_buf)?; - - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - - // Invalidate progress - state_word.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, state_word)?; - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32)?; - - // Set magic - state_word.fill(BOOT_MAGIC); - self.state.write(0, state_word)?; - } - } - Ok(state) - } - - fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { - let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; - let progress = self.current_progress(aligned_buf)?; - - Ok(progress >= page_count * 2) - } - - fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { - let write_size = STATE::WRITE_SIZE as u32; - let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; - let state_word = &mut aligned_buf[..write_size as usize]; - - self.state.read(write_size, state_word)?; - if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { - // Progress is invalid - return Ok(max_index); - } - - for index in 0..max_index { - self.state.read((2 + index) as u32 * write_size, state_word)?; - - if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { - return Ok(index); - } - } - Ok(max_index) - } - - fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - state_word.fill(!STATE_ERASE_VALUE); - self.state - .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; - Ok(()) - } - - fn copy_page_once_to_active( - &mut self, - progress_index: usize, - from_offset: u32, - to_offset: u32, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - if self.current_progress(aligned_buf)? <= progress_index { - let page_size = Self::PAGE_SIZE as u32; - - self.active.erase(to_offset, to_offset + page_size)?; - - for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; - self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; - } - - self.update_progress(progress_index, aligned_buf)?; - } - Ok(()) - } - - fn copy_page_once_to_dfu( - &mut self, - progress_index: usize, - from_offset: u32, - to_offset: u32, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - if self.current_progress(aligned_buf)? <= progress_index { - let page_size = Self::PAGE_SIZE as u32; - - self.dfu.erase(to_offset as u32, to_offset + page_size)?; - - for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; - self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; - } - - self.update_progress(progress_index, aligned_buf)?; - } - Ok(()) - } - - fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; - for page_num in 0..page_count { - let progress_index = (page_num * 2) as usize; - - // Copy active page to the 'next' DFU page. - let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; - //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; - - // Copy DFU page to the active page - let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; - } - - Ok(()) - } - - fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; - for page_num in 0..page_count { - let progress_index = (page_count * 2 + page_num * 2) as usize; - - // Copy the bad active page to the DFU page - let active_from_offset = page_num * Self::PAGE_SIZE; - let dfu_to_offset = page_num * Self::PAGE_SIZE; - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; - - // Copy the DFU page back to the active page - let active_to_offset = page_num * Self::PAGE_SIZE; - let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; - } - - Ok(()) - } - - fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - self.state.read(0, state_word)?; - - if !state_word.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } - } -} - -fn assert_partitions( - active: &ACTIVE, - dfu: &DFU, - state: &STATE, - page_size: u32, -) { - assert_eq!(active.capacity() as u32 % page_size, 0); - assert_eq!(dfu.capacity() as u32 % page_size, 0); - // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm - assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); - assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - #[should_panic] - fn test_range_asserts() { - const ACTIVE_SIZE: usize = 4194304 - 4096; - const DFU_SIZE: usize = 4194304; - const STATE_SIZE: usize = 4096; - static ACTIVE: MemFlash = MemFlash::new(0xFF); - static DFU: MemFlash = MemFlash::new(0xFF); - static STATE: MemFlash = MemFlash::new(0xFF); - assert_partitions(&ACTIVE, &DFU, &STATE, 4096); - } -} diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs deleted file mode 100644 index 2e4e03da3..000000000 --- a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs +++ /dev/null @@ -1,30 +0,0 @@ -use digest::typenum::U64; -use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; -use ed25519_dalek::Digest; - -pub struct Sha512(ed25519_dalek::Sha512); - -impl Default for Sha512 { - fn default() -> Self { - Self(ed25519_dalek::Sha512::new()) - } -} - -impl Update for Sha512 { - fn update(&mut self, data: &[u8]) { - Digest::update(&mut self.0, data) - } -} - -impl FixedOutput for Sha512 { - fn finalize_into(self, out: &mut digest::Output) { - let result = self.0.finalize(); - out.as_mut_slice().copy_from_slice(result.as_slice()) - } -} - -impl OutputSizeUser for Sha512 { - type OutputSize = U64; -} - -impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs deleted file mode 100644 index 9b4b4b60c..000000000 --- a/embassy-boot/boot/src/digest_adapters/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(feature = "ed25519-dalek")] -pub(crate) mod ed25519_dalek; - -#[cfg(feature = "ed25519-salty")] -pub(crate) mod salty; diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs deleted file mode 100644 index 2b5dcf3af..000000000 --- a/embassy-boot/boot/src/digest_adapters/salty.rs +++ /dev/null @@ -1,29 +0,0 @@ -use digest::typenum::U64; -use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; - -pub struct Sha512(salty::Sha512); - -impl Default for Sha512 { - fn default() -> Self { - Self(salty::Sha512::new()) - } -} - -impl Update for Sha512 { - fn update(&mut self, data: &[u8]) { - self.0.update(data) - } -} - -impl FixedOutput for Sha512 { - fn finalize_into(self, out: &mut digest::Output) { - let result = self.0.finalize(); - out.as_mut_slice().copy_from_slice(result.as_slice()) - } -} - -impl OutputSizeUser for Sha512 { - type OutputSize = U64; -} - -impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs deleted file mode 100644 index 2e43e1cc1..000000000 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ /dev/null @@ -1,329 +0,0 @@ -use digest::Digest; -#[cfg(target_os = "none")] -use embassy_embedded_hal::flash::partition::Partition; -#[cfg(target_os = "none")] -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embedded_storage_async::nor_flash::NorFlash; - -use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { - dfu: DFU, - state: FirmwareState<'d, STATE>, -} - -#[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> -{ - /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - Partition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - Partition::new(flash, start, end - start) - }; - - Self { dfu, state } - } -} - -impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self { - dfu: config.dfu, - state: FirmwareState::new(config.state, aligned), - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub async fn get_state(&mut self) -> Result { - self.state.get_state().await - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - #[cfg(feature = "_verify")] - pub async fn verify_and_mark_updated( - &mut self, - _public_key: &[u8; 32], - _signature: &[u8; 64], - _update_len: u32, - ) -> Result<(), FirmwareUpdaterError> { - assert!(_update_len <= self.dfu.capacity() as u32); - - self.state.verify_booted().await?; - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature); - - let mut chunk_buf = [0; 2]; - let mut message = [0; 64]; - self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; - let signature = Signature::try_from(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.state.mark_updated().await - } - - /// Verify the update in DFU with any digest. - pub async fn hash( - &mut self, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(offset, chunk_buf).await?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - #[cfg(not(feature = "_verify"))] - pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_updated().await - } - - /// Mark to trigger USB DFU on next boot. - pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.verify_booted().await?; - self.state.mark_dfu().await - } - - /// Mark firmware boot successful and stop rollback on reset. - pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_booted().await - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); - - self.state.verify_booted().await?; - - self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; - - self.dfu.write(offset as u32, data).await?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { - self.state.verify_booted().await?; - self.dfu.erase(0, self.dfu.capacity() as u32).await?; - - Ok(&mut self.dfu) - } -} - -/// Manages the state partition of the firmware update. -/// -/// Can be used standalone for more fine grained control, or as part of the updater. -pub struct FirmwareState<'d, STATE> { - state: STATE, - aligned: &'d mut [u8], -} - -impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { - /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self::new(config.state, aligned) - } - - /// Create a firmware state instance with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, - /// and follow the alignment rules for the flash being read from and written to. - pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { - assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); - Self { state, aligned } - } - - // Make sure we are running a booted firmware to avoid reverting to a bad state. - async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { - Ok(()) - } else { - Err(FirmwareUpdaterError::BadState) - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub async fn get_state(&mut self) -> Result { - self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Mark to trigger firmware swap on next boot. - pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(SWAP_MAGIC).await - } - - /// Mark to trigger USB DFU on next boot. - pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(DFU_DETACH_MAGIC).await - } - - /// Mark firmware boot successful and stop rollback on reset. - pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(BOOT_MAGIC).await - } - - async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { - self.state.read(0, &mut self.aligned).await?; - - if self.aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; - - if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - self.aligned.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; - } - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32).await?; - - // Set magic - self.aligned.fill(magic); - self.state.write(0, &self.aligned).await?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use embassy_embedded_hal::flash::partition::Partition; - use embassy_sync::blocking_mutex::raw::NoopRawMutex; - use embassy_sync::mutex::Mutex; - use futures::executor::block_on; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - fn can_verify_sha1() { - let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); - let state = Partition::new(&flash, 0, 4096); - let dfu = Partition::new(&flash, 65536, 65536); - let mut aligned = [0; 8]; - - let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - let mut to_write = [0; 4096]; - to_write[..7].copy_from_slice(update.as_slice()); - - let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); - block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); - let mut chunk_buf = [0; 2]; - let mut hash = [0; 20]; - block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs deleted file mode 100644 index f1368540d..000000000 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ /dev/null @@ -1,340 +0,0 @@ -use digest::Digest; -#[cfg(target_os = "none")] -use embassy_embedded_hal::flash::partition::BlockingPartition; -#[cfg(target_os = "none")] -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embedded_storage::nor_flash::NorFlash; - -use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { - dfu: DFU, - state: BlockingFirmwareState<'d, STATE>, -} - -#[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> -{ - /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - pub fn from_linkerfile_blocking( - flash: &'a embassy_sync::blocking_mutex::Mutex>, - ) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - - Self { dfu, state } - } -} - -impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self { - dfu: config.dfu, - state: BlockingFirmwareState::new(config.state, aligned), - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub fn get_state(&mut self) -> Result { - self.state.get_state() - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - #[cfg(feature = "_verify")] - pub fn verify_and_mark_updated( - &mut self, - _public_key: &[u8; 32], - _signature: &[u8; 64], - _update_len: u32, - ) -> Result<(), FirmwareUpdaterError> { - assert!(_update_len <= self.dfu.capacity() as u32); - - self.state.verify_booted()?; - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature); - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message)?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; - let signature = Signature::try_from(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message)?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.state.mark_updated() - } - - /// Verify the update in DFU with any digest. - pub fn hash( - &mut self, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(offset, chunk_buf)?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - #[cfg(not(feature = "_verify"))] - pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_updated() - } - - /// Mark to trigger USB DFU device on next boot. - pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.verify_booted()?; - self.state.mark_dfu() - } - - /// Mark firmware boot successful and stop rollback on reset. - pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_booted() - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); - self.state.verify_booted()?; - - self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; - - self.dfu.write(offset as u32, data)?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { - self.state.verify_booted()?; - self.dfu.erase(0, self.dfu.capacity() as u32)?; - - Ok(&mut self.dfu) - } -} - -/// Manages the state partition of the firmware update. -/// -/// Can be used standalone for more fine grained control, or as part of the updater. -pub struct BlockingFirmwareState<'d, STATE> { - state: STATE, - aligned: &'d mut [u8], -} - -impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { - /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self::new(config.state, aligned) - } - - /// Create a firmware state instance with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { - assert_eq!(aligned.len(), STATE::WRITE_SIZE); - Self { state, aligned } - } - - // Make sure we are running a booted firmware to avoid reverting to a bad state. - fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { - Ok(()) - } else { - Err(FirmwareUpdaterError::BadState) - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub fn get_state(&mut self) -> Result { - self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } - } - - /// Mark to trigger firmware swap on next boot. - pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(SWAP_MAGIC) - } - - /// Mark to trigger USB DFU on next boot. - pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(DFU_DETACH_MAGIC) - } - - /// Mark firmware boot successful and stop rollback on reset. - pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(BOOT_MAGIC) - } - - fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { - self.state.read(0, &mut self.aligned)?; - - if self.aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; - - if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - self.aligned.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; - } - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32)?; - - // Set magic - self.aligned.fill(magic); - self.state.write(0, &self.aligned)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use core::cell::RefCell; - - use embassy_embedded_hal::flash::partition::BlockingPartition; - use embassy_sync::blocking_mutex::raw::NoopRawMutex; - use embassy_sync::blocking_mutex::Mutex; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - fn can_verify_sha1() { - let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); - let state = BlockingPartition::new(&flash, 0, 4096); - let dfu = BlockingPartition::new(&flash, 65536, 65536); - let mut aligned = [0; 8]; - - let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - let mut to_write = [0; 4096]; - to_write[..7].copy_from_slice(update.as_slice()); - - let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); - updater.write_firmware(0, to_write.as_slice()).unwrap(); - let mut chunk_buf = [0; 2]; - let mut hash = [0; 20]; - updater - .hash::(update.len() as u32, &mut chunk_buf, &mut hash) - .unwrap(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs deleted file mode 100644 index 4814786bf..000000000 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -mod asynch; -mod blocking; - -pub use asynch::{FirmwareState, FirmwareUpdater}; -pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; -use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; - -/// Firmware updater flash configuration holding the two flashes used by the updater -/// -/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. -/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition -/// the provided flash according to symbols defined in the linkerfile. -pub struct FirmwareUpdaterConfig { - /// The dfu flash partition - pub dfu: DFU, - /// The state flash partition - pub state: STATE, -} - -/// Errors returned by FirmwareUpdater -#[derive(Debug)] -pub enum FirmwareUpdaterError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Signature errors. - Signature(signature::Error), - /// Bad state. - BadState, -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FirmwareUpdaterError { - fn format(&self, fmt: defmt::Formatter) { - match self { - FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), - FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), - FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), - } - } -} - -impl From for FirmwareUpdaterError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - FirmwareUpdaterError::Flash(error.kind()) - } -} diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/boot/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/boot/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs deleted file mode 100644 index b4f03e01e..000000000 --- a/embassy-boot/boot/src/lib.rs +++ /dev/null @@ -1,323 +0,0 @@ -#![no_std] -#![allow(async_fn_in_trait)] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -mod boot_loader; -mod digest_adapters; -mod firmware_updater; -#[cfg(test)] -mod mem_flash; -#[cfg(test)] -mod test_flash; - -// The expected value of the flash after an erase -// TODO: Use the value provided by NorFlash when available -pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; -pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; -pub use firmware_updater::{ - BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, - FirmwareUpdaterError, -}; - -pub(crate) const BOOT_MAGIC: u8 = 0xD0; -pub(crate) const SWAP_MAGIC: u8 = 0xF0; -pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; - -/// The state of the bootloader after running prepare. -#[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - /// Bootloader is ready to boot the active partition. - Boot, - /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. - Swap, - /// Application has received a request to reboot into DFU mode to apply an update. - DfuDetach, -} - -/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. -#[repr(align(32))] -pub struct AlignedBuffer(pub [u8; N]); - -impl AsRef<[u8]> for AlignedBuffer { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl AsMut<[u8]> for AlignedBuffer { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -#[cfg(test)] -mod tests { - #![allow(unused_imports)] - - use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; - use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; - use futures::executor::block_on; - - use super::*; - use crate::boot_loader::BootLoaderConfig; - use crate::firmware_updater::FirmwareUpdaterConfig; - use crate::mem_flash::MemFlash; - use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; - - /* - #[test] - fn test_bad_magic() { - let mut flash = MemFlash([0xff; 131072]); - let mut flash = SingleFlashConfig::new(&mut flash); - - let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); - - assert_eq!( - bootloader.prepare_boot(&mut flash), - Err(BootError::BadMagic) - ); - } - */ - - #[test] - fn test_boot_state() { - let flash = BlockingTestFlash::new(BootLoaderConfig { - active: MemFlash::<57344, 4096, 4>::default(), - dfu: MemFlash::<61440, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); - - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 4096]; - assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state() { - const FIRMWARE_SIZE: usize = 57344; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::::default(), - dfu: MemFlash::<61440, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - // Writing after marking updated is not allowed until marked as booted. - let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); - assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 1024]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - - // Running again should cause a revert - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - // Last DFU page is untouched - flash.dfu().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - - // Mark as booted - let flash = flash.into_async(); - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.mark_booted()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state_active_page_biggest() { - const FIRMWARE_SIZE: usize = 12288; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::<12288, 4096, 8>::random(), - dfu: MemFlash::<16384, 2048, 8>::random(), - state: MemFlash::<2048, 128, 4>::random(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 4096]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state_dfu_page_biggest() { - const FIRMWARE_SIZE: usize = 12288; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::::random(), - dfu: MemFlash::<16384, 4096, 8>::random(), - state: MemFlash::<2048, 128, 4>::random(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - let mut page = [0; 4096]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - } - - #[test] - #[cfg(feature = "_verify")] - fn test_verify() { - // The following key setup is based on: - // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example - - use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; - use rand::rngs::OsRng; - - let mut csprng = OsRng {}; - let keypair = SigningKey::generate(&mut csprng); - - let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; - let mut digest = Sha512::new(); - digest.update(&firmware); - let message = digest.finalize(); - let signature: Signature = keypair.sign(&message); - - let public_key = keypair.verifying_key(); - - // Setup flash - let flash = BlockingTestFlash::new(BootLoaderConfig { - active: MemFlash::<0, 0, 0>::default(), - dfu: MemFlash::<4096, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - let firmware_len = firmware.len(); - - let mut write_buf = [0; 4096]; - write_buf[0..firmware_len].copy_from_slice(firmware); - flash.dfu().write(0, &write_buf).unwrap(); - - // On with the test - let flash = flash.into_async(); - let mut aligned = [0; 4]; - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - - assert!(block_on(updater.verify_and_mark_updated( - &public_key.to_bytes(), - &signature.to_bytes(), - firmware_len as u32, - )) - .is_ok()); - } -} diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs deleted file mode 100644 index 40f352c8d..000000000 --- a/embassy-boot/boot/src/mem_flash.rs +++ /dev/null @@ -1,170 +0,0 @@ -#![allow(unused)] - -use core::ops::{Bound, Range, RangeBounds}; - -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - -pub struct MemFlash { - pub mem: [u8; SIZE], - pub pending_write_successes: Option, -} - -#[derive(Debug)] -pub struct MemFlashError; - -impl MemFlash { - pub const fn new(fill: u8) -> Self { - Self { - mem: [fill; SIZE], - pending_write_successes: None, - } - } - - #[cfg(test)] - pub fn random() -> Self { - let mut mem = [0; SIZE]; - for byte in mem.iter_mut() { - *byte = rand::random::(); - } - Self { - mem, - pending_write_successes: None, - } - } - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { - let len = bytes.len(); - bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); - Ok(()) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { - let offset = offset as usize; - assert!(bytes.len() % WRITE_SIZE == 0); - assert!(offset % WRITE_SIZE == 0); - assert!(offset + bytes.len() <= SIZE); - - if let Some(pending_successes) = self.pending_write_successes { - if pending_successes > 0 { - self.pending_write_successes = Some(pending_successes - 1); - } else { - return Err(MemFlashError); - } - } - - for ((offset, mem_byte), new_byte) in self - .mem - .iter_mut() - .enumerate() - .skip(offset) - .take(bytes.len()) - .zip(bytes) - { - assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); - *mem_byte = *new_byte; - } - - Ok(()) - } - - fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { - let from = from as usize; - let to = to as usize; - assert!(from % ERASE_SIZE == 0); - assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); - for i in from..to { - self.mem[i] = 0xFF; - } - Ok(()) - } - - pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { - let offset = offset as usize; - assert!(bytes.len() % WRITE_SIZE == 0); - assert!(offset % WRITE_SIZE == 0); - assert!(offset + bytes.len() <= SIZE); - - self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); - - Ok(()) - } -} - -impl Default - for MemFlash -{ - fn default() -> Self { - Self::new(0xFF) - } -} - -impl ErrorType - for MemFlash -{ - type Error = MemFlashError; -} - -impl NorFlashError for MemFlashError { - fn kind(&self) -> NorFlashErrorKind { - NorFlashErrorKind::Other - } -} - -impl ReadNorFlash - for MemFlash -{ - const READ_SIZE: usize = 1; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes) - } - - fn capacity(&self) -> usize { - SIZE - } -} - -impl NorFlash - for MemFlash -{ - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) - } - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) - } -} - -impl AsyncReadNorFlash - for MemFlash -{ - const READ_SIZE: usize = 1; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes) - } - - fn capacity(&self) -> usize { - SIZE - } -} - -impl AsyncNorFlash - for MemFlash -{ - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) - } - - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) - } -} diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs deleted file mode 100644 index 3ac9e71ab..000000000 --- a/embassy-boot/boot/src/test_flash/asynch.rs +++ /dev/null @@ -1,64 +0,0 @@ -use embassy_embedded_hal::flash::partition::Partition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::mutex::Mutex; -use embedded_storage_async::nor_flash::NorFlash; - -use crate::BootLoaderConfig; - -pub struct AsyncTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - active: Mutex, - dfu: Mutex, - state: Mutex, -} - -impl AsyncTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: Mutex::new(config.active), - dfu: Mutex::new(config.dfu), - state: Mutex::new(config.state), - } - } - - pub fn active(&self) -> Partition { - Self::create_partition(&self.active) - } - - pub fn dfu(&self) -> Partition { - Self::create_partition(&self.dfu) - } - - pub fn state(&self) -> Partition { - Self::create_partition(&self.state) - } - - fn create_partition(mutex: &Mutex) -> Partition { - Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) - } -} - -impl AsyncTestFlash -where - ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, - DFU: NorFlash + embedded_storage::nor_flash::NorFlash, - STATE: NorFlash + embedded_storage::nor_flash::NorFlash, -{ - pub fn into_blocking(self) -> super::BlockingTestFlash { - let config = BootLoaderConfig { - active: self.active.into_inner(), - dfu: self.dfu.into_inner(), - state: self.state.into_inner(), - }; - super::BlockingTestFlash::new(config) - } -} diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs deleted file mode 100644 index 5ec476c65..000000000 --- a/embassy-boot/boot/src/test_flash/blocking.rs +++ /dev/null @@ -1,68 +0,0 @@ -use core::cell::RefCell; - -use embassy_embedded_hal::flash::partition::BlockingPartition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embedded_storage::nor_flash::NorFlash; - -use crate::BootLoaderConfig; - -pub struct BlockingTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - active: Mutex>, - dfu: Mutex>, - state: Mutex>, -} - -impl BlockingTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: Mutex::new(RefCell::new(config.active)), - dfu: Mutex::new(RefCell::new(config.dfu)), - state: Mutex::new(RefCell::new(config.state)), - } - } - - pub fn active(&self) -> BlockingPartition { - Self::create_partition(&self.active) - } - - pub fn dfu(&self) -> BlockingPartition { - Self::create_partition(&self.dfu) - } - - pub fn state(&self) -> BlockingPartition { - Self::create_partition(&self.state) - } - - pub fn create_partition( - mutex: &Mutex>, - ) -> BlockingPartition { - BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) - } -} - -impl BlockingTestFlash -where - ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, - DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, - STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, -{ - pub fn into_async(self) -> super::AsyncTestFlash { - let config = BootLoaderConfig { - active: self.active.into_inner().into_inner(), - dfu: self.dfu.into_inner().into_inner(), - state: self.state.into_inner().into_inner(), - }; - super::AsyncTestFlash::new(config) - } -} diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs deleted file mode 100644 index 79b15a081..000000000 --- a/embassy-boot/boot/src/test_flash/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod asynch; -mod blocking; - -pub(crate) use asynch::AsyncTestFlash; -pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml deleted file mode 100644 index 9f74fb126..000000000 --- a/embassy-boot/nrf/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-nrf" -version = "0.1.0" -description = "Bootloader lib for nRF chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/nrf/src/" -features = ["embassy-nrf/nrf52840"] -target = "thumbv7em-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-nrf = { path = "../../embassy-nrf" } -embassy-boot = { path = "../boot", default-features = false } -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -nrf-softdevice-mbr = { version = "0.2.0", optional = true } - -[features] -defmt = [ - "dep:defmt", - "embassy-boot/defmt", - "embassy-nrf/defmt", -] -softdevice = [ - "nrf-softdevice-mbr", -] diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md deleted file mode 100644 index 9dc5b0eb9..000000000 --- a/embassy-boot/nrf/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# embassy-boot-nrf - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for nRF. - -## Features - -* Load applications with or without the softdevice. -* Configure bootloader partitions based on linker script. -* Using watchdog timer to detect application failure. diff --git a/embassy-boot/nrf/src/fmt.rs b/embassy-boot/nrf/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/nrf/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs deleted file mode 100644 index 5b20a93c6..000000000 --- a/embassy-boot/nrf/src/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, -}; -use embassy_nrf::nvmc::PAGE_SIZE; -use embassy_nrf::peripherals::WDT; -use embassy_nrf::wdt; -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; - -/// A bootloader for nRF devices. -pub struct BootLoader; - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); - Self - } - - /// Boots the application without softdevice mechanisms. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - #[cfg(not(feature = "softdevice"))] - pub unsafe fn load(self, start: u32) -> ! { - let mut p = cortex_m::Peripherals::steal(); - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - cortex_m::asm::bootload(start as *const u32) - } - - /// Boots the application assuming softdevice is present. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - #[cfg(feature = "softdevice")] - pub unsafe fn load(self, _app: u32) -> ! { - use nrf_softdevice_mbr as mbr; - const NRF_SUCCESS: u32 = 0; - - // Address of softdevice which we'll forward interrupts to - let addr = 0x1000; - let mut cmd = mbr::sd_mbr_command_t { - command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, - params: mbr::sd_mbr_command_t__bindgen_ty_1 { - irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, - }, - }; - let ret = mbr::sd_mbr_command(&mut cmd); - assert_eq!(ret, NRF_SUCCESS); - - let msp = *(addr as *const u32); - let rv = *((addr + 4) as *const u32); - - trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); - - // These instructions perform the following operations: - // - // * Modify control register to use MSP as stack pointer (clear spsel bit) - // * Synchronize instruction barrier - // * Initialize stack pointer (0x1000) - // * Set link register to not return (0xFF) - // * Jump to softdevice reset vector - core::arch::asm!( - "mrs {tmp}, CONTROL", - "bics {tmp}, {spsel}", - "msr CONTROL, {tmp}", - "isb", - "msr MSP, {msp}", - "mov lr, {new_lr}", - "bx {rv}", - // `out(reg) _` is not permitted in a `noreturn` asm! call, - // so instead use `in(reg) 0` and don't restore it afterwards. - tmp = in(reg) 0, - spsel = in(reg) 2, - new_lr = in(reg) 0xFFFFFFFFu32, - msp = in(reg) msp, - rv = in(reg) rv, - options(noreturn), - ); - } -} - -/// A flash implementation that wraps any flash and will pet a watchdog when touching flash. -pub struct WatchdogFlash { - flash: FLASH, - wdt: wdt::WatchdogHandle, -} - -impl WatchdogFlash { - /// Start a new watchdog with a given flash and WDT peripheral and a timeout - pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { - let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { - Ok(x) => x, - Err(_) => { - // In case the watchdog is already running, just spin and let it expire, since - // we can't configure it anyway. This usually happens when we first program - // the device and the watchdog was previously active - info!("Watchdog already active with wrong config, waiting for it to timeout..."); - loop {} - } - }; - Self { flash, wdt } - } -} - -impl ErrorType for WatchdogFlash { - type Error = FLASH::Error; -} - -impl NorFlash for WatchdogFlash { - const WRITE_SIZE: usize = FLASH::WRITE_SIZE; - const ERASE_SIZE: usize = FLASH::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.erase(from, to) - } - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.write(offset, data) - } -} - -impl ReadNorFlash for WatchdogFlash { - const READ_SIZE: usize = FLASH::READ_SIZE; - fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.read(offset, data) - } - fn capacity(&self) -> usize { - self.flash.capacity() - } -} diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml deleted file mode 100644 index 90bab0996..000000000 --- a/embassy-boot/rp/Cargo.toml +++ /dev/null @@ -1,79 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-rp" -version = "0.1.0" -description = "Bootloader lib for RP2040 chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/" -target = "thumbv6m-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } -log = { version = "0.4", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-rp = { path = "../../embassy-rp", default-features = false } -embassy-boot = { path = "../boot", default-features = false } -embassy-time = { path = "../../embassy-time" } - -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -[features] -defmt = [ - "dep:defmt", - "embassy-boot/defmt", - "embassy-rp/defmt", -] -log = [ - "dep:log", - "embassy-boot/log", - "embassy-rp/log", -] -debug = ["defmt-rtt"] - -[profile.dev] -debug = 2 -debug-assertions = true -incremental = false -opt-level = 'z' -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 'z' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md deleted file mode 100644 index b664145a9..000000000 --- a/embassy-boot/rp/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# embassy-boot-rp - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for RP2040. - -NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. - -## Features - -* Configure bootloader partitions based on linker script. -* Load applications from active partition. diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/rp/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); - } -} diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/rp/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs deleted file mode 100644 index 07a5b3f4d..000000000 --- a/embassy-boot/rp/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, -}; -use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; -use embassy_rp::peripherals::{FLASH, WATCHDOG}; -use embassy_rp::watchdog::Watchdog; -use embassy_time::Duration; -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; - -/// A bootloader for RP2040 devices. -pub struct BootLoader; - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self - } - - /// Boots the application. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(self, start: u32) -> ! { - trace!("Loading app at 0x{:x}", start); - #[allow(unused_mut)] - let mut p = cortex_m::Peripherals::steal(); - #[cfg(not(armv6m))] - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - - cortex_m::asm::bootload(start as *const u32) - } -} - -/// A flash implementation that will feed a watchdog when touching flash. -pub struct WatchdogFlash<'d, const SIZE: usize> { - flash: Flash<'d, FLASH, Blocking, SIZE>, - watchdog: Watchdog, -} - -impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { - /// Start a new watchdog with a given flash and watchdog peripheral and a timeout - pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { - let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); - let mut watchdog = Watchdog::new(watchdog); - watchdog.start(timeout); - Self { flash, watchdog } - } -} - -impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { - type Error = as ErrorType>::Error; -} - -impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { - const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; - const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_erase(from, to) - } - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_write(offset, data) - } -} - -impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { - const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; - fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_read(offset, data) - } - fn capacity(&self) -> usize { - self.flash.capacity() - } -} diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs new file mode 100644 index 000000000..e568001bc --- /dev/null +++ b/embassy-boot/src/boot_loader.rs @@ -0,0 +1,411 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; + +use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] +pub enum BootError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Invalid bootloader magic + BadMagic, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + +impl From for BootError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + BootError::Flash(error.kind()) + } +} + +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, FLASH: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + > +{ + /// Create a bootloader config from the flash and address symbols defined in the linkerfile + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + +/// BootLoader works with any flash implementing embedded_storage. +pub struct BootLoader { + active: ACTIVE, + dfu: DFU, + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, +} + +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. + /// + /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. + /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } + } + + /// Perform necessary boot preparations like swapping images. + /// + /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap + /// algorithm to work correctly. + /// + /// The provided aligned_buf argument must satisfy any alignment requirements + /// given by the partition flashes. All flash operations will use this buffer. + /// + /// ## SWAPPING + /// + /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. + /// The swap index contains the copy progress, as to allow continuation of the copy process on + /// power failure. The index counter is represented within 1 or more pages (depending on total + /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) + /// contains a zero value. This ensures that index updates can be performed atomically and + /// avoid a situation where the wrong index value is set (page write size is "atomic"). + /// + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 0 | 1 | 2 | 3 | - | + /// | DFU | 0 | 3 | 2 | 1 | X | + /// + /// The algorithm starts by copying 'backwards', and after the first step, the layout is + /// as follows: + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 1 | 1 | 2 | 1 | - | + /// | DFU | 1 | 3 | 2 | 1 | 3 | + /// + /// The next iteration performs the same steps + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 2 | 1 | 2 | 1 | - | + /// | DFU | 2 | 3 | 2 | 2 | 3 | + /// + /// And again until we're done + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 3 | 3 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// + /// ## REVERTING + /// + /// The reverting algorithm uses the swap index to discover that images were swapped, but that + /// the application failed to mark the boot successful. In this case, the revert algorithm will + /// run. + /// + /// The revert index is located separately from the swap index, to ensure that revert can continue + /// on power failure. + /// + /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 2 | 2 | 3 | + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 3 | - | + /// | DFU | 3 | 3 | 2 | 1 | 3 | + /// + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { + // Ensure we have enough progress pages to store copy progress + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + // Ensure our partitions are able to handle boot operations + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); + + // Copy contents from partition N to active + let state = self.read_state(aligned_buf)?; + if state == State::Swap { + // + // Check if we already swapped. If we're in the swap state, this means we should revert + // since the app has failed to mark boot as successful + // + if !self.is_swapped(aligned_buf)? { + trace!("Swapping"); + self.swap(aligned_buf)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(aligned_buf)?; + + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + + // Invalidate progress + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + state_word.fill(BOOT_MAGIC); + self.state.write(0, state_word)?; + } + } + Ok(state) + } + + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; + + Ok(progress >= page_count * 2) + } + + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; + let state_word = &mut aligned_buf[..write_size as usize]; + + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { + // Progress is invalid + return Ok(max_index); + } + + for index in 0..max_index { + self.state.read((2 + index) as u32 * write_size, state_word)?; + + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { + return Ok(index); + } + } + Ok(max_index) + } + + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; + Ok(()) + } + + fn copy_page_once_to_active( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.active.erase(to_offset, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn copy_page_once_to_dfu( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.dfu.erase(to_offset as u32, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_num * 2) as usize; + + // Copy active page to the 'next' DFU page. + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; + //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy DFU page to the active page + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_count * 2 + page_num * 2) as usize; + + // Copy the bad active page to the DFU page + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy the DFU page back to the active page + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; + + if !state_word.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) + } else { + Ok(State::Boot) + } + } +} + +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + #[should_panic] + fn test_range_asserts() { + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); + } +} diff --git a/embassy-boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..2e4e03da3 --- /dev/null +++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs @@ -0,0 +1,30 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + Digest::update(&mut self.0, data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/src/digest_adapters/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +pub(crate) mod salty; diff --git a/embassy-boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/src/digest_adapters/salty.rs @@ -0,0 +1,29 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..2e43e1cc1 --- /dev/null +++ b/embassy-boot/src/firmware_updater/asynch.rs @@ -0,0 +1,329 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: FirmwareState<'d, STATE>, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: FirmwareState::new(config.state, aligned), + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state(&mut self) -> Result { + self.state.get_state().await + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted().await?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut chunk_buf = [0; 2]; + let mut message = [0; 64]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.state.mark_updated().await + } + + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated().await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.state.mark_dfu().await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted().await + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + + self.state.verify_booted().await?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; + + self.dfu.write(offset as u32, data).await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.dfu.erase(0, self.dfu.capacity() as u32).await?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct FirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { + /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, + /// and follow the alignment rules for the flash being read from and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + if self.get_state().await? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state(&mut self) -> Result { + self.state.read(0, &mut self.aligned).await?; + + if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Mark to trigger firmware swap on next boot. + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC).await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC).await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC).await + } + + async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned).await?; + + if self.aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; + + if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32).await?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned).await?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embassy_embedded_hal::flash::partition::Partition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::mutex::Mutex; + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs new file mode 100644 index 000000000..f1368540d --- /dev/null +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -0,0 +1,340 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::BlockingPartition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: BlockingFirmwareState<'d, STATE>, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile_blocking( + flash: &'a embassy_sync::blocking_mutex::Mutex>, + ) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: BlockingFirmwareState::new(config.state, aligned), + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state(&mut self) -> Result { + self.state.get_state() + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted()?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.state.mark_updated() + } + + /// Verify the update in DFU with any digest. + pub fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated() + } + + /// Mark to trigger USB DFU device on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted()?; + self.state.mark_dfu() + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted() + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + self.state.verify_booted()?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; + + self.dfu.write(offset as u32, data)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted()?; + self.dfu.erase(0, self.dfu.capacity() as u32)?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct BlockingFirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { + /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state(&mut self) -> Result { + self.state.read(0, &mut self.aligned)?; + + if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) + } else { + Ok(State::Boot) + } + } + + /// Mark to trigger firmware swap on next boot. + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC) + } + + /// Mark to trigger USB DFU on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC) + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC) + } + + fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + + use embassy_embedded_hal::flash::partition::BlockingPartition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::blocking_mutex::Mutex; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + updater.write_firmware(0, to_write.as_slice()).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..4814786bf --- /dev/null +++ b/embassy-boot/src/firmware_updater/mod.rs @@ -0,0 +1,49 @@ +mod asynch; +mod blocking; + +pub use asynch::{FirmwareState, FirmwareUpdater}; +pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +/// Firmware updater flash configuration holding the two flashes used by the updater +/// +/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct FirmwareUpdaterConfig { + /// The dfu flash partition + pub dfu: DFU, + /// The state flash partition + pub state: STATE, +} + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), + /// Bad state. + BadState, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} diff --git a/embassy-boot/src/fmt.rs b/embassy-boot/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs new file mode 100644 index 000000000..b4f03e01e --- /dev/null +++ b/embassy-boot/src/lib.rs @@ -0,0 +1,323 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +mod boot_loader; +mod digest_adapters; +mod firmware_updater; +#[cfg(test)] +mod mem_flash; +#[cfg(test)] +mod test_flash; + +// The expected value of the flash after an erase +// TODO: Use the value provided by NorFlash when available +pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; +pub use firmware_updater::{ + BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, + FirmwareUpdaterError, +}; + +pub(crate) const BOOT_MAGIC: u8 = 0xD0; +pub(crate) const SWAP_MAGIC: u8 = 0xF0; +pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; + +/// The state of the bootloader after running prepare. +#[derive(PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + /// Bootloader is ready to boot the active partition. + Boot, + /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. + Swap, + /// Application has received a request to reboot into DFU mode to apply an update. + DfuDetach, +} + +/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. +#[repr(align(32))] +pub struct AlignedBuffer(pub [u8; N]); + +impl AsRef<[u8]> for AlignedBuffer { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + #![allow(unused_imports)] + + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + use futures::executor::block_on; + + use super::*; + use crate::boot_loader::BootLoaderConfig; + use crate::firmware_updater::FirmwareUpdaterConfig; + use crate::mem_flash::MemFlash; + use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; + + /* + #[test] + fn test_bad_magic() { + let mut flash = MemFlash([0xff; 131072]); + let mut flash = SingleFlashConfig::new(&mut flash); + + let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); + + assert_eq!( + bootloader.prepare_boot(&mut flash), + Err(BootError::BadMagic) + ); + } + */ + + #[test] + fn test_boot_state() { + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); + + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state() { + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + // Writing after marking updated is not allowed until marked as booted. + let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); + assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 1024]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + + // Running again should cause a revert + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + // Last DFU page is untouched + flash.dfu().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + + // Mark as booted + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.mark_booted()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_dfu_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::random(), + dfu: MemFlash::<16384, 4096, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[test] + #[cfg(feature = "_verify")] + fn test_verify() { + // The following key setup is based on: + // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example + + use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; + use rand::rngs::OsRng; + + let mut csprng = OsRng {}; + let keypair = SigningKey::generate(&mut csprng); + + let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; + let mut digest = Sha512::new(); + digest.update(&firmware); + let message = digest.finalize(); + let signature: Signature = keypair.sign(&message); + + let public_key = keypair.verifying_key(); + + // Setup flash + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<0, 0, 0>::default(), + dfu: MemFlash::<4096, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + let firmware_len = firmware.len(); + + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + flash.dfu().write(0, &write_buf).unwrap(); + + // On with the test + let flash = flash.into_async(); + let mut aligned = [0; 4]; + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + + assert!(block_on(updater.verify_and_mark_updated( + &public_key.to_bytes(), + &signature.to_bytes(), + firmware_len as u32, + )) + .is_ok()); + } +} diff --git a/embassy-boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs new file mode 100644 index 000000000..40f352c8d --- /dev/null +++ b/embassy-boot/src/mem_flash.rs @@ -0,0 +1,170 @@ +#![allow(unused)] + +use core::ops::{Bound, Range, RangeBounds}; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +pub struct MemFlash { + pub mem: [u8; SIZE], + pub pending_write_successes: Option, +} + +#[derive(Debug)] +pub struct MemFlashError; + +impl MemFlash { + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + pending_write_successes: None, + } + } + + #[cfg(test)] + pub fn random() -> Self { + let mut mem = [0; SIZE]; + for byte in mem.iter_mut() { + *byte = rand::random::(); + } + Self { + mem, + pending_write_successes: None, + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + if let Some(pending_successes) = self.pending_write_successes { + if pending_successes > 0 { + self.pending_write_successes = Some(pending_successes - 1); + } else { + return Err(MemFlashError); + } + } + + for ((offset, mem_byte), new_byte) in self + .mem + .iter_mut() + .enumerate() + .skip(offset) + .take(bytes.len()) + .zip(bytes) + { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; + } + Ok(()) + } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xFF) + } +} + +impl ErrorType + for MemFlash +{ + type Error = MemFlashError; +} + +impl NorFlashError for MemFlashError { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} diff --git a/embassy-boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/src/test_flash/asynch.rs @@ -0,0 +1,64 @@ +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage_async::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex, + dfu: Mutex, + state: Mutex, +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), + } + } + + pub fn active(&self) -> Partition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> Partition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> Partition { + Self::create_partition(&self.state) + } + + fn create_partition(mutex: &Mutex) -> Partition { + Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) + } +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage::nor_flash::NorFlash, +{ + pub fn into_blocking(self) -> super::BlockingTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner(), + dfu: self.dfu.into_inner(), + state: self.state.into_inner(), + }; + super::BlockingTestFlash::new(config) + } +} diff --git a/embassy-boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..5ec476c65 --- /dev/null +++ b/embassy-boot/src/test_flash/blocking.rs @@ -0,0 +1,68 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex>, + dfu: Mutex>, + state: Mutex>, +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner().into_inner(), + dfu: self.dfu.into_inner().into_inner(), + state: self.state.into_inner().into_inner(), + }; + super::AsyncTestFlash::new(config) + } +} diff --git a/embassy-boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs new file mode 100644 index 000000000..79b15a081 --- /dev/null +++ b/embassy-boot/src/test_flash/mod.rs @@ -0,0 +1,5 @@ +mod asynch; +mod blocking; + +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml deleted file mode 100644 index 70919b76d..000000000 --- a/embassy-boot/stm32/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-stm32" -version = "0.1.0" -description = "Bootloader lib for STM32 chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/stm32/src/" -features = ["embassy-stm32/stm32f429zi"] -target = "thumbv7em-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } -log = { version = "0.4", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-stm32 = { path = "../../embassy-stm32", default-features = false } -embassy-boot = { path = "../boot", default-features = false } -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -[features] -defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] -log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] -debug = ["defmt-rtt"] - -[profile.dev] -debug = 2 -debug-assertions = true -incremental = false -opt-level = 'z' -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 'z' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md deleted file mode 100644 index f6dadc8e7..000000000 --- a/embassy-boot/stm32/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# embassy-boot-stm32 - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for STM32. - -## Features - -* Configure bootloader partitions based on linker script. -* Load applications from active partition. diff --git a/embassy-boot/stm32/build.rs b/embassy-boot/stm32/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/stm32/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); - } -} diff --git a/embassy-boot/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/stm32/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs deleted file mode 100644 index 4b4091ac9..000000000 --- a/embassy-boot/stm32/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, -}; -use embedded_storage::nor_flash::NorFlash; - -/// A bootloader for STM32 devices. -pub struct BootLoader { - /// The reported state of the bootloader after preparing for boot - pub state: State, -} - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self { state } - } - - /// Boots the application. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(self, start: u32) -> ! { - trace!("Loading app at 0x{:x}", start); - #[allow(unused_mut)] - let mut p = cortex_m::Peripherals::steal(); - #[cfg(not(armv6m))] - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - - cortex_m::asm::bootload(start as *const u32) - } -} diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index ee110ee87..2d8895123 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -17,7 +17,7 @@ categories = [ bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } defmt = { version = "0.3.5", optional = true } -embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" } +embassy-boot = { version = "0.1.1", path = "../embassy-boot" } # embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../embassy-sync" } diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index eba9a0579..7b62d9a20 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -9,8 +9,8 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [] } embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } -embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot", features = [] } -embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf", features = [] } +embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot-nrf", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 38c9f8cff..ccaa9f8ef 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [] } embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", ] } -embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp", features = [] } +embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot-rp", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = "0.3" diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 9c0aeb463..f4bc285bf 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32" } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index e81ff618e..575220ade 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index f75ffc8e6..12c34565a 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index f58bd9557..9f705dc26 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 887126f9d..7ba5f143b 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index d5fabac82..08cb87e0b 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 394546b75..58bba66d7 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" } embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 6c4dc7975..7ce560de2 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index a7273175f..3e41d1479 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", features = [] } -embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } +embassy-boot-nrf = { path = "../../../../embassy-boot-nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index 5bc61e9ec..3cf61a002 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-rp = { path = "../../../../embassy-rp", features = [] } -embassy-boot-rp = { path = "../../../../embassy-boot/rp" } +embassy-boot-rp = { path = "../../../../embassy-boot-rp" } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = [] } diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index 2d88b0f78..74c01b0f4 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index ada073970..96635afa2 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } -- cgit From ddacbf68aff53447bdfac7db2380a9f0aaaf0b83 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Jan 2024 18:56:17 +0100 Subject: net-esp-hosted: fix readme. --- embassy-net-esp-hosted/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/embassy-net-esp-hosted/README.md b/embassy-net-esp-hosted/README.md index f6c216835..524231e6c 100644 --- a/embassy-net-esp-hosted/README.md +++ b/embassy-net-esp-hosted/README.md @@ -1,14 +1,9 @@ # ESP-Hosted `embassy-net` integration -[`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running the the ESP-Hosted stack. +[`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running the the [ESP-Hosted](https://github.com/espressif/esp-hosted) stack. See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840) directory for usage examples with the nRF52840. -## Supported chips - -- W5500 -- W5100S - ## Interoperability This crate can run on any executor. -- cgit From 0af44292a0862b1c115def1ed1a48e29daae241c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Jan 2024 19:10:56 +0100 Subject: usb: add readme. --- embassy-usb/README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/embassy-usb/README.md b/embassy-usb/README.md index da656e8e9..7411fcf52 100644 --- a/embassy-usb/README.md +++ b/embassy-usb/README.md @@ -1,6 +1,28 @@ # embassy-usb -TODO crate description +Async USB device stack for embedded devices in Rust. + +## Features + +- Native async. +- Fully lock-free: endpoints are separate objects that can be used independently without needing a central mutex. If the driver supports it, they can even be used from different priority levels. +- Suspend/resume, remote wakeup. +- USB composite devices. +- Ergonomic descriptor builder. +- Ready-to-use implementations for a few USB classes (note you can still implement any class yourself oustide the crate). + - Serial ports (CDC ACM) + - Ethernet (CDC NCM) + - Human Interface Devices (HID) + - MIDI + +## Adding support for new hardware + +To add `embassy-usb` support for new hardware (i.e. a new MCU chip), you have to write a driver that implements +the [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) traits. + +Driver crates should depend only on `embassy-usb-driver`, not on the main `embassy-usb` crate. +This allows existing drivers to continue working for newer `embassy-usb` major versions, without needing an update, if the driver +trait has not had breaking changes. ## Configuration -- cgit