Skip to content

Commit

Permalink
feat(device-id): Add to riot-rs and the archs
Browse files Browse the repository at this point in the history
  • Loading branch information
chrysn committed Oct 18, 2024
1 parent 9f1328e commit 6fdeb6b
Show file tree
Hide file tree
Showing 16 changed files with 255 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions src/riot-rs-embassy-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -23,5 +23,3 @@ external-interrupts = []

## Enables I2C support.
i2c = ["dep:fugit"]

defmt = ["dep:defmt", "fugit?/defmt"]
119 changes: 119 additions & 0 deletions src/riot-rs-embassy-common/src/identity.rs
Original file line number Diff line number Diff line change
@@ -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<Self, impl Error>;

/// 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<E: Error + Default>(core::convert::Infallible, core::marker::PhantomData<E>);

impl<E: Error + Default> crate::Sealed for NoDeviceId<E> {}

impl<E: Error + Default> DeviceId for NoDeviceId<E> {
// 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<Self, impl Error> {
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 {}
13 changes: 13 additions & 0 deletions src/riot-rs-embassy-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub mod executor_swi;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity;

pub mod reexports {
//! Crate re-exports.
Expand All @@ -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 {}
1 change: 0 additions & 1 deletion src/riot-rs-embassy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-embassy/src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<identity::NotImplemented>;
}

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-esp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<identity::NotImplemented>;
}

#[cfg(feature = "wifi")]
pub mod wifi;

Expand Down
8 changes: 6 additions & 2 deletions src/riot-rs-nrf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] }
Expand All @@ -30,16 +30,19 @@ 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.
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.
Expand All @@ -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"]
34 changes: 34 additions & 0 deletions src/riot-rs-nrf/src/identity.rs
Original file line number Diff line number Diff line change
@@ -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<Self, core::convert::Infallible> {
// 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()
}
}
2 changes: 2 additions & 0 deletions src/riot-rs-nrf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod hwrng;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity;

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-rp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<identity::NotImplemented>;
}

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
20 changes: 20 additions & 0 deletions src/riot-rs-stm32/src/identity.rs
Original file line number Diff line number Diff line change
@@ -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<Self, core::convert::Infallible> {
Ok(Self(embassy_stm32::uid::uid()))
}

fn bytes(&self) -> Self::Bytes {
self.0
}
}
2 changes: 2 additions & 0 deletions src/riot-rs-stm32/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
1 change: 1 addition & 0 deletions src/riot-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
32 changes: 32 additions & 0 deletions src/riot-rs/src/identity.rs
Original file line number Diff line number Diff line change
@@ -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<impl DeviceId, impl Error> {
riot_rs_embassy::arch::identity::DeviceId::get()
}
2 changes: 2 additions & 0 deletions src/riot-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

pub mod buildinfo;

pub mod identity;

#[cfg(feature = "bench")]
#[doc(inline)]
pub use riot_rs_bench as bench;
Expand Down

0 comments on commit 6fdeb6b

Please sign in to comment.