diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fad877..6362be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4. ### Added - Addressable devices for a shared bus. `boot::run` takes the bus addressing: `Addressable::new(chip_uid)` keeps the bootloader silent until a host selects it by the chip's 96-bit unique id, `Unaddressed` answers every frame as before. `app::new_addressable_app(ctl, wait_bytes)` does the same in the app. `chip_uid` reads the ESIG unique id, and `Usart` implements `Transport::wait_bytes` from its baud divisor. -- `boot_version!(compat = "name")` and `app_version!(compat = "name")` (re-exported from core): a bootloader with a compatibility key accepts only app images with the same key. The V00x and V103 examples set one. +- `boot_version!(compat = "name")` and `app_version!(compat = "name")` (re-exported from core): a bootloader with a compatibility key accepts only app images with the same key. The V00x and V103 examples set one. `boot` and `app` also re-export `compat_key` and `pack_version`, for an image that sets its own `version = ...`. - The bootloader runs its USART at the baud stored in boot metadata, falling back to `UsartConfig::baud` when nothing is stored or `pclk` cannot reach the rate (divisor below 16). Divisors are computed at build time, so there is no runtime divide; the CH32V003 example bootloader grows by 64 bytes. - `app::BaudRate` re-export for `App::set_baud` / `App::baud`. - `boot::run_with_payload::` runs the bootloader with an `N`-byte frame payload (at least 18; a Write may span flash pages); `boot::run` uses the 52-byte default. @@ -43,7 +43,7 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4. - `addressing`: the `Addressing` trait, `Unaddressed`, and `Addressable::new(uid)` for devices sharing a bus. An addressable device answers `Select` when its id starts with the prefix, after a slot of 0 to 15 byte-times drawn from its id and the host's seed, and any other command only while selected. It never answers a frame it cannot read or another device's reply. `Dispatcher::with_addressing`, `Core::with_addressing` and `App::with_addressing` take it. - `Transport::wait_bytes(n)`, a wait of about `n` byte-times for the `Select` slot; the default returns at once. -- Compatibility keys. `boot_version!` and `app_version!` take `compat = "name"` (hashed at compile time) or `compat_key = `, and end the image with a 6-byte tail, `ImageTail { compat, version }`. A bootloader with a key refuses `Verify` of an image with another key, or none, with `Status::Incompatible` and stays in `Updating`; without a key it carries no check (with LTO the unset key folds away). +- Compatibility keys. `boot_version!` and `app_version!` take `compat = "name"` (hashed at compile time) or `compat_key = `, and end the image with a 6-byte tail, `ImageTail { compat, version }`. A `version = ` argument, alone or after the key, replaces the crate's `Cargo.toml` version; `pack_version` is re-exported to build one. A bootloader with a key refuses `Verify` of an image with another key, or none, with `Status::Incompatible` and stays in `Updating`; without a key it carries no check (with LTO the unset key folds away). - `Reset` with `ResetFlags::BAUD` stores addr byte 0 as the bootloader's baud before resetting, in the bootloader and in `App::poll`. - `App::baud` returns the stored bootloader baud; `App::set_baud` stores one, skipping the flash write when it is already stored. diff --git a/ch32/README.md b/ch32/README.md index 63b7061..7960e1c 100644 --- a/ch32/README.md +++ b/ch32/README.md @@ -46,7 +46,7 @@ fn main() -> ! { `Unaddressed` answers every frame, for a device that has the bus to itself. With several devices on one bus, pass `Addressable::new(chip_uid)` instead: the bootloader then stays silent until a host selects it by the chip's unique id (see the [shared bus guide](https://openservocore.github.io/tinyboot/shared-bus.html)). -`Storage` and `BootMetaStore` are initialized from linker symbols automatically. `boot_version!()` ends the image with its tail: a compatibility key and the crate's `Cargo.toml` version. `boot_version!(compat = "name")` sets the key, and the bootloader then accepts only app images built with the same `app_version!(compat = "name")` (see [app integration](https://openservocore.github.io/tinyboot/app-integration.html)). +`Storage` and `BootMetaStore` are initialized from linker symbols automatically. `boot_version!()` ends the image with its tail: a compatibility key and the crate's `Cargo.toml` version. `boot_version!(compat = "name")` sets the key, and the bootloader then accepts only app images built with the same `app_version!(compat = "name")`. A trailing `version = ` replaces the crate version in either macro (see [app integration](https://openservocore.github.io/tinyboot/app-integration.html)). `boot::run` reads frames with up to 52 payload bytes (a whole frame fits one 64-byte USB packet); `boot::run_with_payload::` takes `N` bytes per Write (at least 18, not limited to a flash page) for faster updates, at `N` bytes of RAM. Hosts read the capacity from Info. diff --git a/ch32/src/app.rs b/ch32/src/app.rs index ae7060f..85525e3 100644 --- a/ch32/src/app.rs +++ b/ch32/src/app.rs @@ -11,7 +11,7 @@ pub use tinyboot_core::BaudRate; pub use tinyboot_core::addressing::{Addressable, Addressing, Unaddressed}; pub use tinyboot_core::app::{App, AppConfig}; pub use tinyboot_core::traits; -pub use tinyboot_core::{app_version, pkg_version}; +pub use tinyboot_core::{app_version, compat_key, pack_version, pkg_version}; /// Create an [`App`] configured for CH32 hardware. pub fn new_app(ctl: BootCtl) -> App { diff --git a/ch32/src/boot.rs b/ch32/src/boot.rs index b1fe8b8..3f0dbf9 100644 --- a/ch32/src/boot.rs +++ b/ch32/src/boot.rs @@ -13,7 +13,7 @@ pub use crate::hal::{Pin, UsartMapping}; pub use tinyboot_core::Platform; pub use tinyboot_core::addressing::{Addressable, Addressing, Unaddressed}; use tinyboot_core::frame::{DEFAULT_PAYLOAD, InfoData}; -pub use tinyboot_core::{boot_version, pkg_version}; +pub use tinyboot_core::{boot_version, compat_key, pack_version, pkg_version}; /// Common imports for bootloader binaries. pub mod prelude { diff --git a/docs/app-integration.md b/docs/app-integration.md index 2693e20..b631ee1 100644 --- a/docs/app-integration.md +++ b/docs/app-integration.md @@ -95,6 +95,15 @@ tinyboot_ch32::app::app_version!(compat = "acme-widget-rev-b"); `compat = "name"` hashes the name at compile time (32-bit FNV-1a); `compat_key = 0x1234_5678` takes a raw value. tinyboot only compares keys, so encode whatever has to match: product, board revision, pinout. `Verify` refuses an image with another key, or none, with `Status::Incompatible` and leaves the device in its bootloader waiting for the right image; a mistaken image never starts. A bootloader built without a key accepts any image and carries no check. +The version in the tail is the crate's `Cargo.toml` version. A firmware with its own version number passes it after the key, or alone, packed 5.5.6 the way `tinyboot info` shows it: + +```rust +tinyboot_ch32::app::app_version!( + compat = "acme-widget-rev-b", + version = tinyboot_ch32::app::pack_version(2, 4, 0) +); +``` + ## On a shared bus Several devices on one bus need the addressable app, created with `new_addressable_app(BootCtl::new(), wait_bytes)`. It stays silent until the host selects it by the chip's unique id, like the addressable bootloader. `wait_bytes(n)` waits about `n` byte-times at the bus baud, and the example apps compute it from the USART's baud divisor. Build the bootloader addressable too. See the [shared bus guide](shared-bus.md). diff --git a/lib/core/src/lib.rs b/lib/core/src/lib.rs index 13e3bfc..39c7231 100644 --- a/lib/core/src/lib.rs +++ b/lib/core/src/lib.rs @@ -26,7 +26,7 @@ pub use tinyboot_protocol::BaudRate; pub use tinyboot_protocol::frame; #[doc(hidden)] pub use tinyboot_protocol::pkg_version; -pub use tinyboot_protocol::{COMPAT_UNSET, compat_key}; +pub use tinyboot_protocol::{COMPAT_UNSET, compat_key, pack_version}; /// Last 6 bytes of a boot or app image, emitted by [`boot_version!`] or /// [`app_version!`] into `.tb_version`. `tb-app.x` starts an app's tail @@ -60,54 +60,63 @@ pub fn tinyboot_compat() -> u32 { #[doc(hidden)] #[macro_export] macro_rules! __image_tail { - ($compat:expr) => { + (@ $compat:expr, $version:expr) => { #[unsafe(export_name = "__tb_tail")] #[unsafe(link_section = ".tb_version")] #[used] static _TB_TAIL: $crate::ImageTail = $crate::ImageTail { compat: $compat, - version: $crate::pkg_version!(), + version: $version, }; }; + () => { + $crate::__image_tail!(@ $crate::COMPAT_UNSET, $crate::pkg_version!()); + }; + (version = $version:expr) => { + $crate::__image_tail!(@ $crate::COMPAT_UNSET, $version); + }; + (compat = $name:expr) => { + $crate::__image_tail!(@ $crate::compat_key($name), $crate::pkg_version!()); + }; + (compat = $name:expr, version = $version:expr) => { + $crate::__image_tail!(@ $crate::compat_key($name), $version); + }; + (compat_key = $key:expr) => { + $crate::__image_tail!(@ $key, $crate::pkg_version!()); + }; + (compat_key = $key:expr, version = $version:expr) => { + $crate::__image_tail!(@ $key, $version); + }; } -/// Place the calling crate's version and a compatibility key into the -/// bootloader image's tail. Use once at module scope in a bootloader -/// binary. +/// Place a compatibility key and a version into the bootloader image's +/// tail. Use once at module scope in a bootloader binary. /// /// - `boot_version!()`: no key; the bootloader accepts any image. /// - `boot_version!(compat = "name")`: the key hashed from a name /// ([`compat_key`](tinyboot_protocol::compat_key)). /// - `boot_version!(compat_key = 0x1234_5678)`: a raw key. /// +/// The version is the calling crate's `Cargo.toml` version unless a +/// `version = ` follows (alone or after the key), packed 5.5.6 as +/// [`pack_version`](tinyboot_protocol::pack_version) does. +/// /// With a key set, `Verify` refuses an image whose key differs with /// `Status::Incompatible`. #[macro_export] macro_rules! boot_version { - () => { - $crate::__image_tail!($crate::COMPAT_UNSET); - }; - (compat = $name:expr) => { - $crate::__image_tail!($crate::compat_key($name)); - }; - (compat_key = $key:expr) => { - $crate::__image_tail!($key); + ($($args:tt)*) => { + $crate::__image_tail!($($args)*); }; } -/// Place the calling crate's version and a compatibility key into the app -/// image's tail. Use once at module scope in an app binary. Arguments as +/// Place a compatibility key and a version into the app image's tail. +/// Use once at module scope in an app binary. Arguments as /// [`boot_version!`]; the key must equal the bootloader's for it to /// accept the image. #[macro_export] macro_rules! app_version { - () => { - $crate::__image_tail!($crate::COMPAT_UNSET); - }; - (compat = $name:expr) => { - $crate::__image_tail!($crate::compat_key($name)); - }; - (compat_key = $key:expr) => { - $crate::__image_tail!($key); + ($($args:tt)*) => { + $crate::__image_tail!($($args)*); }; } diff --git a/lib/core/tests/tail_version.rs b/lib/core/tests/tail_version.rs new file mode 100644 index 0000000..3b9f321 --- /dev/null +++ b/lib/core/tests/tail_version.rs @@ -0,0 +1,11 @@ +//! An image tail with its own version instead of the crate's. + +use tinyboot_protocol::{compat_key, pack_version}; + +tinyboot_core::app_version!(compat = "board-a", version = pack_version(3, 1, 4)); + +#[test] +fn the_tail_carries_the_given_version_and_key() { + assert_eq!(tinyboot_core::tinyboot_version(), pack_version(3, 1, 4)); + assert_eq!(tinyboot_core::tinyboot_compat(), compat_key("board-a")); +}