Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device IDs #444

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

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

14 changes: 14 additions & 0 deletions examples/device-metadata/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "device-metadata"
version = "0.1.0"
authors.workspace = true
license.workspace = true
edition.workspace = true
publish = false

[lints]
workspace = true

[dependencies]
riot-rs = { path = "../../src/riot-rs" }
riot-rs-boards = { path = "../../src/riot-rs-boards" }
14 changes: 14 additions & 0 deletions examples/device-metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# device-metadata

## About

This application prints all that a device knows
about the device and the running program
at startup to the debug console.
It needs to be run within a debug session.
chrysn marked this conversation as resolved.
Show resolved Hide resolved

## How to run

In this folder, run

laze build -b nrf52840dk run
5 changes: 5 additions & 0 deletions examples/device-metadata/laze.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apps:
- name: device-metadata
selects:
- ?release
- sw/threading
18 changes: 18 additions & 0 deletions examples/device-metadata/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![no_main]
#![no_std]
#![feature(used_with_arg)]

use riot_rs::debug::{exit, log::*};

#[riot_rs::thread(autostart)]
fn main() {
info!("Available information:");
info!("Board type: {}", riot_rs::buildinfo::BOARD);
if let Ok(id) = riot_rs::identity::device_id_bytes() {
info!("Device ID: {=[u8]:x}", id.as_ref());
} else {
info!("Device ID is unavailable.");
}

exit(Ok(()));
}
1 change: 1 addition & 0 deletions examples/laze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ subdirs:
- blinky
- core-sizes
- coap
- device-metadata
- embassy
- embassy-http-server
- embassy-net-tcp
Expand Down
99 changes: 99 additions & 0 deletions src/riot-rs-embassy-common/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Tools and traits for describing device identities.
//!
//! See `riot_rs::identity` for general documentation.
#![deny(missing_docs)]

chrysn marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
ROMemories marked this conversation as resolved.
Show resolved Hide resolved
pub trait DeviceId: Sized {
/// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// not be done yet as it depends on the `generic_const_exprs` featureVg
/// not be done yet as it depends on the `generic_const_exprs` feature.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I take that as an indication that the direction is good enough to do a final pass of the docs before a final review and squash?

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 core::error::Error>;

/// The device identifier in serialized bytes format.
fn bytes(&self) -> Self::Bytes;
}

/// 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)]
pub struct NoDeviceId<E: core::error::Error + Default>(
core::convert::Infallible,
core::marker::PhantomData<E>,
ROMemories marked this conversation as resolved.
Show resolved Hide resolved
);

impl<E: core::error::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 core::error::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)]
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 core::error::Error for NotImplemented {}

/// Error indicating that a [`DeviceId`] is not available on this platform.
#[derive(Debug, Default)]
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 core::error::Error for NotAvailable {}
2 changes: 2 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 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
4 changes: 4 additions & 0 deletions src/riot-rs-nrf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,20 @@ 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"] }
nrf52833-pac = "0.12.2"

[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 Down
33 changes: 33 additions & 0 deletions src/riot-rs-nrf/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pub struct DeviceId(u64);

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 };
Comment on lines +14 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[cfg(context = "nrf52840")]
let ficr = unsafe { nrf52840_pac::Peripherals::steal().FICR };
#[cfg(context = "nrf52832")]
let ficr = unsafe { nrf52832_pac::Peripherals::steal().FICR };
#[cfg(context = "nrf52832")]
let ficr = unsafe { nrf52832_pac::Peripherals::steal().FICR };
#[cfg(context = "nrf52840")]
let ficr = unsafe { nrf52840_pac::Peripherals::steal().FICR };

nit: this keeps the order consistent with the manifest

#[cfg(context = "nrf52833")]
let ficr = unsafe { nrf52833_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
17 changes: 17 additions & 0 deletions src/riot-rs-stm32/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pub struct DeviceId(&'static [u8; 12]);

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
31 changes: 31 additions & 0 deletions src/riot-rs/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Access to unique identifiers provided by the device.
//!
//! The main way to use this module is [`device_id_bytes()`], which returns an identifier for the
//! concrete piece of hardware that the software is running on in byte serialized form.
//!
//! 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! Constructing an identifier fails rather than producing a dummy identifier.
//! Constructing an identifier fails rather than produce 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.

/// Obtains a unique identifier of the device in its byte serialized form.
pub fn device_id_bytes() -> Result<impl AsRef<[u8]>, impl core::error::Error> {
ROMemories marked this conversation as resolved.
Show resolved Hide resolved
use riot_rs_embassy_common::identity::DeviceId;

riot_rs_embassy::arch::identity::DeviceId::get().map(|d| d.bytes())
}
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
Loading