From 6fdeb6b7da1295c5d3f30a19d392043344cde8cc Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 18 Oct 2024 09:45:35 +0200 Subject: [PATCH] feat(device-id): Add to riot-rs and the archs --- Cargo.lock | 4 + src/riot-rs-embassy-common/Cargo.toml | 6 +- src/riot-rs-embassy-common/src/identity.rs | 119 +++++++++++++++++++++ src/riot-rs-embassy-common/src/lib.rs | 13 +++ src/riot-rs-embassy/Cargo.toml | 1 - src/riot-rs-embassy/src/arch/mod.rs | 6 ++ src/riot-rs-esp/src/lib.rs | 6 ++ src/riot-rs-nrf/Cargo.toml | 8 +- src/riot-rs-nrf/src/identity.rs | 34 ++++++ src/riot-rs-nrf/src/lib.rs | 2 + src/riot-rs-rp/src/lib.rs | 6 ++ src/riot-rs-stm32/src/identity.rs | 20 ++++ src/riot-rs-stm32/src/lib.rs | 2 + src/riot-rs/Cargo.toml | 1 + src/riot-rs/src/identity.rs | 32 ++++++ src/riot-rs/src/lib.rs | 2 + 16 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 src/riot-rs-embassy-common/src/identity.rs create mode 100644 src/riot-rs-nrf/src/identity.rs create mode 100644 src/riot-rs-stm32/src/identity.rs create mode 100644 src/riot-rs/src/identity.rs diff --git a/Cargo.lock b/Cargo.lock index 9ead0189c..326410b47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3607,6 +3607,7 @@ dependencies = [ "riot-rs-boards", "riot-rs-debug", "riot-rs-embassy", + "riot-rs-embassy-common", "riot-rs-macros", "riot-rs-random", "riot-rs-rt", @@ -3760,6 +3761,9 @@ dependencies = [ "embassy-executor", "embassy-nrf", "embedded-hal-async", + "nrf52832-pac", + "nrf52840-pac", + "nrf5340-app-pac", "paste", "portable-atomic", "riot-rs-debug", diff --git a/src/riot-rs-embassy-common/Cargo.toml b/src/riot-rs-embassy-common/Cargo.toml index 7058ee863..5fa831539 100644 --- a/src/riot-rs-embassy-common/Cargo.toml +++ b/src/riot-rs-embassy-common/Cargo.toml @@ -10,8 +10,8 @@ repository.workspace = true workspace = true [dependencies] -defmt = { workspace = true, optional = true } -fugit = { workspace = true, optional = true } +defmt = { workspace = true } +fugit = { workspace = true, optional = true, features = ["defmt"] } embassy-futures = { workspace = true } embassy-time = { workspace = true } embedded-hal = { workspace = true } @@ -23,5 +23,3 @@ external-interrupts = [] ## Enables I2C support. i2c = ["dep:fugit"] - -defmt = ["dep:defmt", "fugit?/defmt"] diff --git a/src/riot-rs-embassy-common/src/identity.rs b/src/riot-rs-embassy-common/src/identity.rs new file mode 100644 index 000000000..7b9ea98c7 --- /dev/null +++ b/src/riot-rs-embassy-common/src/identity.rs @@ -0,0 +1,119 @@ +//! Tools and traits for describing device identities. +//! +//! See `riot_rs::identity` for general documentation. +#![deny(missing_docs)] + +/// Trait describing the unique identifier available on a board. +/// +/// See the module level documentation on the characteristics of the identifier. +/// +/// # Evolution +/// +/// In its current state, this type is mainly a wrapper around a binary identifier with a +/// length constant at build time. +/// +/// As it is used more, additional methods can be provided for concrete types of identifiers, such +/// as MAC addresses. By default, those would be generated in some way from what is available in +/// the identifier -- but boards where the identifier already *is* a MAC address (or possibly a +/// range thereof) can provide their official addresses. +/// +/// This trait is `Sealed` in the `riot-rs-embassy-common` crate to ensure that associated types +/// and non-provided methods can be added. Therefore, its re-export in the `riot-rs` crate is a +/// promise that the trait evolves at the (slower) `riot-rs` speed towards users, while it can +/// evolve at the (possibly faster) RIOT-rs internal speed of `riot-rs-embassy-common` for +/// implementers. +pub trait DeviceId: Sized + core::fmt::Debug + defmt::Format + crate::Sealed { + /// Some `[u8; N]` type, returned by [`.bytes()`][Self::bytes]. + /// + /// This may not represent all the identifying information available on the board, but can + /// represent a unique portion thereof. + /// + /// (For example, if a device has two consecutive MAC addresses assigned, the type as a whole + /// may represent both, but the conventional serialized identity of the board may just be one + /// of them). + /// + /// # Evolution + /// + /// In the long run, it will be preferable to add a `const BYTES_LEN: usize;` and enforce the + /// type `[u8; Self::BYTES_LEN]` as the return value of [`.bytes(_)]`][Self::bytes]. This can + /// not be done yet as it depends on the `generic_const_exprs` featureVg + type Bytes: AsRef<[u8]>; + + /// Obtains a unique identifier of the device. + /// + /// For callers, there is the convenience function `riot_rs::identity::device_identity()` + /// available, which just calls this trait method on `riot_rs::arch::identity::DeviceId`. + /// + /// # Errors + /// + /// This produces an error if no device ID is available on this board, or is not implemented. + /// It is encouraged to use [`core::convert::Infallible`] where possible. + fn get() -> Result; + + /// The device identifier in serialized bytes format. + fn bytes(&self) -> Self::Bytes; +} + +/// Error trait for obtaining [`DeviceId`]. +/// +/// This is part of the signature of [`DeviceId::get`], and indicates that no identifier is +/// available. +/// +/// Like [`DeviceId`], it is sealed; the same considerations for stability as listed there apply. +pub trait Error: core::error::Error + defmt::Format + crate::Sealed {} + +impl crate::Sealed for core::convert::Infallible {} +impl Error for core::convert::Infallible {} + +/// An uninhabited type implementing [`DeviceId`] that always errs. +/// +/// This can be used both on architectures that do not have a unique identifier on their boards, +/// and when it has not yet been implemented. +/// +/// Typical types for `E` are [`NotImplemented`] or [`NotAvailable`]. +#[derive(Debug, defmt::Format)] +pub struct NoDeviceId(core::convert::Infallible, core::marker::PhantomData); + +impl crate::Sealed for NoDeviceId {} + +impl DeviceId for NoDeviceId { + // We could also come up with a custom never type that AsRef's into [u8], but that won't fly + // once there is a BYTES_LEN. + type Bytes = [u8; 0]; + + fn get() -> Result { + Err::<_, E>(Default::default()) + } + + fn bytes(&self) -> [u8; 0] { + match self.0 {} + } +} + +/// Error indicating that a [`DeviceId`] may be available on this platform, but is not implemented. +#[derive(Debug, Default, defmt::Format)] +pub struct NotImplemented; + +impl core::fmt::Display for NotImplemented { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Device ID not implemented on this platform") + } +} + +impl crate::Sealed for NotImplemented {} +impl core::error::Error for NotImplemented {} +impl Error for NotImplemented {} + +/// Error indicating that a [`DeviceId`] is not available on this platform. +#[derive(Debug, Default, defmt::Format)] +pub struct NotAvailable; + +impl core::fmt::Display for NotAvailable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Device ID not available on this platform") + } +} + +impl crate::Sealed for NotAvailable {} +impl core::error::Error for NotAvailable {} +impl Error for NotAvailable {} diff --git a/src/riot-rs-embassy-common/src/lib.rs b/src/riot-rs-embassy-common/src/lib.rs index 41002b5f4..c6735866e 100644 --- a/src/riot-rs-embassy-common/src/lib.rs +++ b/src/riot-rs-embassy-common/src/lib.rs @@ -13,6 +13,8 @@ pub mod executor_swi; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity; + pub mod reexports { //! Crate re-exports. @@ -21,3 +23,14 @@ pub mod reexports { pub use embassy_time; pub use embedded_hal_async; } + +/// Soft sealing trait, which opts a dependent trait out of RIOT-rs's stability guarantees. +/// +/// Traits that depend on [`Sealed`] are supposed to only be implemented by RIOT-rs internally, +/// e.g. for a particular architecture. As RIOT-rs is composed of a group of crates and Rust has no +/// concept of items being private to a family of crates, this can not be enforced. +/// +/// The precise evolution strategy depends on the trait that requires [`Sealed`]; it is up to the +/// implementer of this trait to keep track of changes of *all* RIOT-rs traits which that type +/// implements. +pub trait Sealed {} diff --git a/src/riot-rs-embassy/Cargo.toml b/src/riot-rs-embassy/Cargo.toml index b515bf02d..6ac72624b 100644 --- a/src/riot-rs-embassy/Cargo.toml +++ b/src/riot-rs-embassy/Cargo.toml @@ -114,7 +114,6 @@ defmt = [ "embassy-net?/defmt", "embassy-time?/defmt", "embassy-usb?/defmt", - "riot-rs-embassy-common/defmt", "riot-rs-esp/defmt", "riot-rs-nrf/defmt", "riot-rs-rp/defmt", diff --git a/src/riot-rs-embassy/src/arch/mod.rs b/src/riot-rs-embassy/src/arch/mod.rs index 32f55fa52..6ef48f082 100644 --- a/src/riot-rs-embassy/src/arch/mod.rs +++ b/src/riot-rs-embassy/src/arch/mod.rs @@ -15,6 +15,12 @@ pub mod hwrng; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity { + use riot_rs_embassy_common::identity; + + pub type DeviceId = identity::NoDeviceId; +} + #[cfg(feature = "usb")] pub mod usb; diff --git a/src/riot-rs-esp/src/lib.rs b/src/riot-rs-esp/src/lib.rs index d5ee5cd9f..d2d4ca86c 100644 --- a/src/riot-rs-esp/src/lib.rs +++ b/src/riot-rs-esp/src/lib.rs @@ -9,6 +9,12 @@ pub mod gpio; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity { + use riot_rs_embassy_common::identity; + + pub type DeviceId = identity::NoDeviceId; +} + #[cfg(feature = "wifi")] pub mod wifi; diff --git a/src/riot-rs-nrf/Cargo.toml b/src/riot-rs-nrf/Cargo.toml index 821c0fd3c..dbf48661d 100644 --- a/src/riot-rs-nrf/Cargo.toml +++ b/src/riot-rs-nrf/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] cfg-if = { workspace = true } -defmt = { workspace = true, optional = true } +defmt = { workspace = true } embassy-executor = { workspace = true, default-features = false, features = [ "arch-cortex-m", ] } @@ -30,6 +30,7 @@ riot-rs-random = { workspace = true, optional = true } [target.'cfg(context = "nrf52832")'.dependencies] embassy-nrf = { workspace = true, features = ["nrf52832"] } +nrf52832-pac = "0.12.2" [target.'cfg(context = "nrf52833")'.dependencies] # Disable NFC support for now, as we do not support it yet. @@ -37,9 +38,11 @@ embassy-nrf = { workspace = true, features = ["nfc-pins-as-gpio", "nrf52833"] } [target.'cfg(context = "nrf52840")'.dependencies] embassy-nrf = { workspace = true, features = ["nrf52840"] } +nrf52840-pac = "0.12.2" [target.'cfg(context = "nrf5340")'.dependencies] embassy-nrf = { workspace = true, features = ["nrf5340-app-s"] } +nrf5340-app-pac = "0.12.2" [features] ## Enables GPIO interrupt support. @@ -58,7 +61,8 @@ i2c = ["riot-rs-embassy-common/i2c", "embassy-executor/integrated-timers"] usb = [] ## Enables defmt support. -defmt = ["dep:defmt", "embassy-nrf/defmt"] +# (at least in dependencies; the crate itself needs it for its DeviceId type) +defmt = ["embassy-nrf/defmt"] ## Enables the interrupt executor. executor-interrupt = ["embassy-executor/executor-interrupt"] diff --git a/src/riot-rs-nrf/src/identity.rs b/src/riot-rs-nrf/src/identity.rs new file mode 100644 index 000000000..1f0be6152 --- /dev/null +++ b/src/riot-rs-nrf/src/identity.rs @@ -0,0 +1,34 @@ +#[derive(Debug, defmt::Format)] +pub struct DeviceId(u64); + +impl riot_rs_embassy_common::Sealed for DeviceId {} + +impl riot_rs_embassy_common::identity::DeviceId for DeviceId { + #[allow( + refining_impl_trait_reachable, + reason = "Making this fallible would be a breaking API change for RIOT-rs." + )] + fn get() -> Result { + // Embassy does not wrap the FICR register, and given that all we need from there is a register + // read that is perfectly fine to do through a stolen register, let's do that rather than + // thread the access through several layers. + + // SAFETY: The register is used for read-only operations on constant values. + #[cfg(context = "nrf52840")] + let ficr = unsafe { nrf52840_pac::Peripherals::steal().FICR }; + #[cfg(context = "nrf52832")] + let ficr = unsafe { nrf52832_pac::Peripherals::steal().FICR }; + #[cfg(context = "nrf5340")] + let ficr = &unsafe { nrf5340_app_pac::Peripherals::steal().FICR_S }.info; + + let low = ficr.deviceid[0].read().bits(); + let high = ficr.deviceid[1].read().bits(); + Ok(Self((u64::from(high) << u32::BITS) | u64::from(low))) + } + + type Bytes = [u8; 8]; + + fn bytes(&self) -> Self::Bytes { + self.0.to_le_bytes() + } +} diff --git a/src/riot-rs-nrf/src/lib.rs b/src/riot-rs-nrf/src/lib.rs index 94a139915..db8ebf1e4 100644 --- a/src/riot-rs-nrf/src/lib.rs +++ b/src/riot-rs-nrf/src/lib.rs @@ -16,6 +16,8 @@ pub mod hwrng; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity; + #[cfg(feature = "usb")] pub mod usb; diff --git a/src/riot-rs-rp/src/lib.rs b/src/riot-rs-rp/src/lib.rs index bb1d5b879..ed1003b8a 100644 --- a/src/riot-rs-rp/src/lib.rs +++ b/src/riot-rs-rp/src/lib.rs @@ -21,6 +21,12 @@ pub mod hwrng; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity { + use riot_rs_embassy_common::identity; + + pub type DeviceId = identity::NoDeviceId; +} + #[cfg(feature = "usb")] pub mod usb; diff --git a/src/riot-rs-stm32/src/identity.rs b/src/riot-rs-stm32/src/identity.rs new file mode 100644 index 000000000..21debff5f --- /dev/null +++ b/src/riot-rs-stm32/src/identity.rs @@ -0,0 +1,20 @@ +#[derive(Debug, defmt::Format)] +pub struct DeviceId(&'static [u8; 12]); + +impl riot_rs_embassy_common::Sealed for DeviceId {} + +impl riot_rs_embassy_common::identity::DeviceId for DeviceId { + type Bytes = &'static [u8; 12]; + + #[allow( + refining_impl_trait_reachable, + reason = "Making this fallible would be a breaking API change for RIOT-rs." + )] + fn get() -> Result { + Ok(Self(embassy_stm32::uid::uid())) + } + + fn bytes(&self) -> Self::Bytes { + self.0 + } +} diff --git a/src/riot-rs-stm32/src/lib.rs b/src/riot-rs-stm32/src/lib.rs index 207bd5596..7228b0512 100644 --- a/src/riot-rs-stm32/src/lib.rs +++ b/src/riot-rs-stm32/src/lib.rs @@ -14,6 +14,8 @@ pub mod extint_registry; #[cfg(feature = "i2c")] pub mod i2c; +pub mod identity; + use embassy_stm32::Config; pub use embassy_stm32::{interrupt, peripherals, OptionalPeripherals, Peripherals}; diff --git a/src/riot-rs/Cargo.toml b/src/riot-rs/Cargo.toml index da6ffc527..bc6c56e36 100644 --- a/src/riot-rs/Cargo.toml +++ b/src/riot-rs/Cargo.toml @@ -15,6 +15,7 @@ riot-rs-bench = { workspace = true, optional = true } riot-rs-boards = { path = "../riot-rs-boards" } riot-rs-debug = { workspace = true } riot-rs-embassy = { path = "../riot-rs-embassy" } +riot-rs-embassy-common = { workspace = true } riot-rs-macros = { path = "../riot-rs-macros" } riot-rs-random = { workspace = true, optional = true } riot-rs-rt = { path = "../riot-rs-rt" } diff --git a/src/riot-rs/src/identity.rs b/src/riot-rs/src/identity.rs new file mode 100644 index 000000000..dcb1ab79b --- /dev/null +++ b/src/riot-rs/src/identity.rs @@ -0,0 +1,32 @@ +//! Access to unique identifiers provided by the device. +//! +//! The main way to use this module is [`device_identity()`], which returns an identifier for the +//! concrete piece of hardware that the software is running on. +//! +//! Concrete properties of a device identity are: +//! +//! * Identifiers are reasonably unique: They are either unique by construction (serial number, MAC +//! address) or random identifiers (>= 64 bit). +//! +//! * The scope of the identifier is within a RIOT-rs board. Their scope may be broader, eg. when +//! a identifier is unique per MCU family, or even globally. +//! +//! * Identifiers do not change during regular development with a board, which includes the use of +//! a programmer. Identifiers may change under deliberate conditions, eg. when a device has a +//! one-time programmable identity, or when there is a custom functionality to overwrite the +//! built-in identifier that is not triggered by the device erase that is performed as part of +//! programming the device. +//! +//! Constructing an identifier fails rather than producing a dummy identifier. +//! +//! It is considered a breaking change in a board or this module if a board's identifier changes or +//! becomes an error as result of an update to RIOT-rs. Errors changing to valid identifiers is a +//! compatible change. + +#[doc(inline)] +pub use riot_rs_embassy_common::identity::{DeviceId, Error}; + +/// Obtains a unique identifier of the device. +pub fn device_identity() -> Result { + riot_rs_embassy::arch::identity::DeviceId::get() +} diff --git a/src/riot-rs/src/lib.rs b/src/riot-rs/src/lib.rs index e88a610cf..d51628f20 100644 --- a/src/riot-rs/src/lib.rs +++ b/src/riot-rs/src/lib.rs @@ -13,6 +13,8 @@ pub mod buildinfo; +pub mod identity; + #[cfg(feature = "bench")] #[doc(inline)] pub use riot_rs_bench as bench;