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 13 commits into
base: main
Choose a base branch
from
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" }
13 changes: 13 additions & 0 deletions examples/device-metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# device-metadata

## About

This application prints all that a device knows
about the device and the running program
at startup to the debug console.

## How to run

In this folder, run

laze build -b nrf52840dk run
7 changes: 7 additions & 0 deletions examples/device-metadata/laze.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apps:
- name: device-metadata
selects:
- ?release
- sw/threading
depends:
- debug-console
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
96 changes: 96 additions & 0 deletions src/riot-rs-embassy-common/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Tools and traits for describing device identities.
//!
//! See `riot_rs::identity` for general documentation; that module also represents the public parts
//! of this API.
#![deny(missing_docs)]

chrysn marked this conversation as resolved.
Show resolved Hide resolved
/// Trait describing the unique identifier available on a device.
///
/// 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.
///
/// 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 devices where the identifier already *is* a MAC address (or possibly a
/// range thereof) can provide their official addresses.
pub trait DeviceId: Sized {
/// Some `[u8; N]` type, returned by [`.bytes()`][Self::bytes].
///
/// This may not represent all the identifying information available on the device, 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 device 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` feature.
type Bytes: AsRef<[u8]>;

/// Obtains a unique identifier of the device.
///
/// # 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 device, 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 device")
}
}

impl core::error::Error for NotImplemented {}

/// Error indicating that a [`DeviceId`] is not available on this device.
#[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 device")
}
}

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
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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the first actual code in the riot-rs crate. Also, the first that adds docs.
randomand storage get their own crate. Maybe move this into e.g., riot-rs-identity as well?

//!
//! This module provides [`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 device, 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 produce a dummy identifier.
//!
//! It is considered a breaking change in RIOT-rs if a device's identifier changes or becomes an
//! error. Errors changing to valid identifiers is a compatible change.

/// Obtains a unique identifier of the device in its byte serialized form.
///
/// See module level documentation for that identifier's properties.
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