-
Notifications
You must be signed in to change notification settings - Fork 26
fwmanager: Add device-table schema; declare a mock board in target/mock #367
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
Merged
+250
−6
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0171bf7
fwmanager: Add device-table schema; declare a mock board in target/mock
chrysh 04a30f3
fwmanager: make DeviceConfig generic over the reset signal type
chrysh 145b279
fwmanager: document why the schema types are intentionally exhaustive
chrysh bcd47b7
fwmanager: encode boot-progress checkpointing in the device table
chrysh 0c503b6
fwmanager: test validate()'s reject paths
chrysh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| // Licensed under the Apache-2.0 license | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Schema for the per-board device table. Board device tables | ||
| //! (`target/<board>/devices.rs`) declare the values; no concrete line or | ||
| //! device is named here. | ||
|
|
||
| /// What the orchestrator requires before it commits a staged image. | ||
| /// | ||
| /// Intentionally exhaustive (not `#[non_exhaustive]`): adding a variant is | ||
| /// a breaking change, so the compiler forces every match on the policy — | ||
| /// in particular the orchestrator's commit decision — to handle the new | ||
| /// variant explicitly instead of falling into a wildcard arm. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub enum CommitPolicy { | ||
| /// The device reports it came up. | ||
| Liveness, | ||
| /// Liveness plus SPDM re-attestation of the running image. | ||
| LivenessAndAttestation, | ||
| } | ||
|
|
||
| /// How the orchestrator observes a device's boot-progress signal. | ||
| /// | ||
| /// Generic over the id type `G` the board's boot monitor uses to read a | ||
| /// boot-complete line, for the same reason `DeviceConfig` is generic over | ||
| /// its reset signal: signal ids are board-specific. | ||
| /// | ||
| /// Intentionally exhaustive (not `#[non_exhaustive]`): adding a signal | ||
| /// kind is a breaking change, so every consumer that dispatches on it is | ||
| /// forced to handle the new kind explicitly. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub enum BootSignal<G> { | ||
| /// The device raises a boot-complete GPIO line. | ||
| GpioBootComplete(G), | ||
| /// The device sends a heartbeat message. | ||
| Heartbeat, | ||
| /// The device's MCTP endpoint answers as ready. | ||
| MctpReady, | ||
| /// The device answers a firmware version query. | ||
| VersionQuery, | ||
| } | ||
|
|
||
| /// One boot-progress checkpoint: a signal the orchestrator waits for, and | ||
| /// how long it waits. | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct BootCheckpoint<G> { | ||
| /// Names the checkpoint in timeout reports. | ||
| pub name: &'static str, | ||
| pub signal: BootSignal<G>, | ||
| /// How long the orchestrator waits for `signal` before it declares the | ||
| /// checkpoint — and the device's boot — failed. Expiry is the | ||
| /// orchestrator's own judgment; hung devices report nothing. | ||
| pub window: core::time::Duration, | ||
| } | ||
|
|
||
| /// One managed downstream device, as declared by the board config. | ||
| /// | ||
| /// Generic over the board's reset signal type `R`, which must match the | ||
| /// `ResetId` of the reset controller behind the board's `BootControl` | ||
| /// implementation — the compiler rejects a table whose ids the controller | ||
| /// cannot accept. | ||
| /// | ||
| /// Intentionally exhaustive (not `#[non_exhaustive]`): board tables | ||
| /// construct this struct by literal, which the attribute would forbid. | ||
| /// Adding a field is a breaking change that updates every board table. | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct DeviceConfig<R, G: 'static> { | ||
| pub name: &'static str, | ||
| /// Reset signal id, passed to HalBootControl::new. | ||
| pub reset_signal: R, | ||
| /// Boot-progress checkpoints, in the order the device passes them. | ||
| /// The device counts as booted when the last one is reached; a | ||
| /// checkpoint whose window expires fails the boot. | ||
| pub checkpoints: &'static [BootCheckpoint<G>], | ||
| pub commit_policy: CommitPolicy, | ||
| } | ||
|
|
||
| /// Checks a device table. Board configs call this in a const context so a | ||
| /// bad table fails the build. | ||
| pub const fn validate<R, G>(devices: &[DeviceConfig<R, G>]) { | ||
| let mut i = 0; | ||
| while i < devices.len() { | ||
| assert!(!devices[i].name.is_empty(), "device name must not be empty"); | ||
| assert!( | ||
| !devices[i].checkpoints.is_empty(), | ||
| "device must declare at least one boot checkpoint" | ||
| ); | ||
| let mut c = 0; | ||
| while c < devices[i].checkpoints.len() { | ||
| assert!( | ||
| !devices[i].checkpoints[c].name.is_empty(), | ||
| "checkpoint name must not be empty" | ||
| ); | ||
| assert!( | ||
| !devices[i].checkpoints[c].window.is_zero(), | ||
| "checkpoint window must not be zero" | ||
| ); | ||
| c += 1; | ||
| } | ||
| i += 1; | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use core::time::Duration; | ||
|
|
||
| // Board tables run validate() at compile time, where a rejection is a | ||
| // build error nobody can assert on. These tests call it at runtime to | ||
| // prove the reject paths actually fire — a vacuous loop would pass | ||
| // every `const _` check silently. | ||
|
|
||
| const CHECKPOINT: BootCheckpoint<u8> = BootCheckpoint { | ||
| name: "boot-complete", | ||
| signal: BootSignal::GpioBootComplete(0), | ||
| window: Duration::from_secs(1), | ||
| }; | ||
|
|
||
| const DEVICE: DeviceConfig<u8, u8> = DeviceConfig { | ||
| name: "dev", | ||
| reset_signal: 0, | ||
| checkpoints: &[CHECKPOINT], | ||
| commit_policy: CommitPolicy::Liveness, | ||
| }; | ||
|
|
||
| #[test] | ||
| fn accepts_a_valid_table() { | ||
| validate(&[DEVICE]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "device name must not be empty")] | ||
| fn rejects_an_empty_device_name() { | ||
| validate(&[DEVICE, DeviceConfig { name: "", ..DEVICE }]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "at least one boot checkpoint")] | ||
| fn rejects_an_empty_checkpoint_list() { | ||
| validate(&[DeviceConfig { | ||
| checkpoints: &[], | ||
| ..DEVICE | ||
| }]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "checkpoint name must not be empty")] | ||
| fn rejects_an_empty_checkpoint_name() { | ||
| validate(&[DeviceConfig { | ||
| checkpoints: &[BootCheckpoint { | ||
| name: "", | ||
| ..CHECKPOINT | ||
| }], | ||
| ..DEVICE | ||
| }]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "checkpoint window must not be zero")] | ||
| fn rejects_a_zero_checkpoint_window() { | ||
| validate(&[DeviceConfig { | ||
| checkpoints: &[ | ||
| CHECKPOINT, | ||
| BootCheckpoint { | ||
| window: Duration::ZERO, | ||
| ..CHECKPOINT | ||
| }, | ||
| ], | ||
| ..DEVICE | ||
| }]); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Licensed under the Apache-2.0 license | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| load("@rules_rust//rust:defs.bzl", "rust_library") | ||
|
|
||
| package(default_visibility = ["//visibility:public"]) | ||
|
|
||
| rust_library( | ||
| name = "devices", | ||
| srcs = ["devices.rs"], | ||
| crate_name = "board_devices", | ||
| edition = "2024", | ||
| deps = ["//services/fwmanager/api:fwmanager_api"], | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Licensed under the Apache-2.0 license | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Mock board: a device table exercising every device archetype the | ||
| //! orchestrator manages. Not a real board — consumed by host tests and QEMU | ||
| //! runs until a hardware target declares its own table. | ||
|
|
||
| #![no_std] | ||
|
|
||
| use core::time::Duration; | ||
|
|
||
| use fwmanager_api::config::{BootCheckpoint, BootSignal, CommitPolicy, DeviceConfig}; | ||
|
|
||
| /// Declaration order is the boot order: the orchestrator releases devices | ||
| /// top to bottom, one at a time. | ||
| /// | ||
| /// The mock board's reset controller and boot monitor both address | ||
| /// signals by plain index, so both id types are `u8`. | ||
| pub const MANAGED_DEVICES: &[DeviceConfig<u8, u8>] = &[ | ||
| // Direct-flash SPI device (BMC archetype): the eRoT fronts its flash. | ||
| // Single checkpoint: it raises a boot-complete GPIO. | ||
| DeviceConfig { | ||
| name: "bmc", | ||
| reset_signal: 7, | ||
| checkpoints: &[BootCheckpoint { | ||
| name: "boot-complete", | ||
| signal: BootSignal::GpioBootComplete(12), | ||
| window: Duration::from_secs(90), | ||
| }], | ||
| commit_policy: CommitPolicy::Liveness, | ||
| }, | ||
| // PLDM device (NIC archetype): self-updating, SPDM-capable. Two | ||
| // checkpoints, exercising the multi-checkpoint path. | ||
| DeviceConfig { | ||
| name: "nic", | ||
| reset_signal: 3, | ||
| checkpoints: &[ | ||
| BootCheckpoint { | ||
| name: "mctp-ready", | ||
| signal: BootSignal::MctpReady, | ||
| window: Duration::from_secs(20), | ||
| }, | ||
| BootCheckpoint { | ||
| name: "heartbeat", | ||
| signal: BootSignal::Heartbeat, | ||
| window: Duration::from_secs(10), | ||
| }, | ||
| ], | ||
| commit_policy: CommitPolicy::LivenessAndAttestation, | ||
| }, | ||
| ]; | ||
|
|
||
| const _: () = fwmanager_api::config::validate(MANAGED_DEVICES); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.