Skip to main content

banc_icd/
lib.rs

1//! Interface control document (ICD) for the banc HIL test framework.
2//!
3//! Two endpoint families live here, both domain-neutral:
4//!
5//! - [`node`]: minimal node management every banc-speaking device implements
6//!   (identify, reset). Ping comes from `postcard_rpc::standard_icd`.
7//! - [`assistant`]: the v0 surface of the reference assistant — GPIO, pin-edge
8//!   monitoring, UART, SPI/I2C controller transactions, and timestamped edge
9//!   capture. All timing data carries assistant-local timestamps; the host
10//!   never asserts timing from its own wall clock.
11//!
12//! Consumers building combined firmware (their own endpoints + banc's) must
13//! list banc's endpoints in their single `endpoints!`/`define_dispatch!`
14//! table. postcard-rpc keys are structural (path + schema hash), so
15//! re-declaring marker types against the wire types and [`paths`] constants
16//! exported here yields identical keys to the ones banc-host uses.
17
18#![no_std]
19#![forbid(unsafe_code)]
20
21use postcard_rpc::{endpoints, topics, TopicDirection};
22use postcard_schema::Schema;
23use serde::{Deserialize, Serialize};
24
25/// Bumped on any breaking change to paths or schemas. Hosts compare this
26/// against [`node::Identity::protocol_version`] before running suites.
27pub const PROTOCOL_VERSION: u32 = 0;
28
29/// Canonical path strings, exported so consumer ICD tables can reference them
30/// instead of retyping (a typo would silently change the key).
31pub mod paths {
32    pub const NODE_IDENTIFY: &str = "banc/node/identify";
33    pub const NODE_RESET: &str = "banc/node/reset";
34
35    pub const GPIO_CONFIG: &str = "banc/assistant/gpio/config";
36    pub const GPIO_SET: &str = "banc/assistant/gpio/set";
37    pub const GPIO_READ: &str = "banc/assistant/gpio/read";
38    pub const PIN_MONITOR: &str = "banc/assistant/pin/monitor";
39    pub const PIN_EDGE: &str = "banc/assistant/pin/edge";
40    pub const UART_CONFIG: &str = "banc/assistant/uart/config";
41    pub const UART_TX: &str = "banc/assistant/uart/tx";
42    pub const UART_RX: &str = "banc/assistant/uart/rx";
43    pub const SPI_TRANSFER: &str = "banc/assistant/spi/transfer";
44    pub const I2C_TRANSACTION: &str = "banc/assistant/i2c/transaction";
45    pub const CAPTURE_CONTROL: &str = "banc/assistant/capture/control";
46    pub const CAPTURE_READ: &str = "banc/assistant/capture/read";
47}
48
49/// Node-management types: implemented by every banc node regardless of role.
50pub mod node {
51    use super::*;
52
53    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
54    pub enum NodeRole {
55        /// The banc reference assistant firmware.
56        Assistant,
57        /// A consumer-defined node (its ICD describes what it does).
58        Custom,
59    }
60
61    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
62    pub struct Identity {
63        pub role: NodeRole,
64        pub protocol_version: u32,
65        /// Stable per-device ID (e.g. flash unique ID). Also surfaced as the
66        /// USB serial string so hosts can route before connecting.
67        pub unique_id: u64,
68        pub fw_name: heapless::String<32>,
69        pub fw_version: heapless::String<16>,
70    }
71}
72
73/// Reference-assistant v0 types.
74pub mod assistant {
75    use super::*;
76
77    /// Max payload per UART/SPI/I2C transaction chunk in v0.
78    pub const CHUNK: usize = 64;
79    /// Max pin events returned per capture-read chunk.
80    pub const EVENTS_PER_CHUNK: usize = 16;
81
82    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
83    pub enum Level {
84        Low,
85        High,
86    }
87
88    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
89    pub enum Pull {
90        None,
91        Up,
92        Down,
93    }
94
95    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
96    pub enum PinMode {
97        Input(Pull),
98        Output,
99    }
100
101    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
102    pub enum Error {
103        /// Pin/bus index not wired up on this assistant.
104        Unsupported,
105        /// Pin/bus is claimed by another function (e.g. capture running).
106        Busy,
107        /// The peripheral reported a fault (NAK, framing error, overrun...).
108        Hardware,
109        /// Request out of range (bad length, bad offset).
110        Invalid,
111    }
112
113    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
114    pub struct GpioConfig {
115        pub pin: u8,
116        pub mode: PinMode,
117    }
118
119    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
120    pub struct GpioSet {
121        pub pin: u8,
122        pub level: Level,
123    }
124
125    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
126    pub struct GpioRead {
127        pub pin: u8,
128    }
129
130    /// One observed edge, timestamped by the assistant's own clock
131    /// (microseconds since assistant boot). This is the ground truth all
132    /// host-side timing assertions run against.
133    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
134    pub struct PinEvent {
135        pub pin: u8,
136        pub level: Level,
137        pub timestamp_us: u64,
138    }
139
140    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
141    pub struct PinMonitor {
142        pub pin: u8,
143        /// true: publish PinEdgeTopic on every edge; false: stop.
144        pub enable: bool,
145    }
146
147    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
148    pub struct UartConfig {
149        pub baud: u32,
150    }
151
152    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
153    pub struct Chunk {
154        pub data: heapless::Vec<u8, CHUNK>,
155    }
156
157    /// UART bytes as received, stamped with the assistant-local time of the
158    /// first byte in the chunk.
159    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
160    pub struct UartRxChunk {
161        pub timestamp_us: u64,
162        pub data: heapless::Vec<u8, CHUNK>,
163    }
164
165    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
166    pub struct SpiTransfer {
167        /// Bytes to clock out; the same number of bytes is clocked in.
168        pub write: heapless::Vec<u8, CHUNK>,
169    }
170
171    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
172    pub struct I2cTransaction {
173        pub addr: u8,
174        pub write: heapless::Vec<u8, CHUNK>,
175        /// Bytes to read after the write phase (0 = write-only).
176        pub read_len: u8,
177    }
178
179    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
180    pub enum CaptureAction {
181        /// Arm edge capture on a pin; events accumulate assistant-side.
182        Start,
183        /// Disarm; captured events stay retrievable until the next Start.
184        Stop,
185    }
186
187    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
188    pub struct CaptureControl {
189        pub pin: u8,
190        pub action: CaptureAction,
191    }
192
193    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
194    pub struct CaptureStatus {
195        /// Events currently buffered.
196        pub count: u32,
197        /// Events dropped because the buffer filled. Nonzero means the
198        /// capture is incomplete; hosts should fail timing assertions
199        /// rather than reason over a gappy record.
200        pub dropped: u32,
201    }
202
203    #[derive(Serialize, Deserialize, Schema, Debug, Clone, Copy, PartialEq, Eq)]
204    pub struct CaptureRead {
205        /// Event index to read from (chunked retrieval).
206        pub offset: u32,
207    }
208
209    #[derive(Serialize, Deserialize, Schema, Debug, Clone, PartialEq, Eq)]
210    pub struct CaptureChunk {
211        pub events: heapless::Vec<PinEvent, EVENTS_PER_CHUNK>,
212        /// Events remaining after this chunk.
213        pub remaining: u32,
214    }
215
216    // The endpoints! macro takes one token tree per type column, so the
217    // Result response types go through aliases.
218    pub type AckResult = Result<(), Error>;
219    pub type LevelResult = Result<Level, Error>;
220    pub type ChunkResult = Result<Chunk, Error>;
221    pub type CaptureStatusResult = Result<CaptureStatus, Error>;
222    pub type CaptureChunkResult = Result<CaptureChunk, Error>;
223}
224
225use assistant::*;
226use node::*;
227
228endpoints! {
229    list = ENDPOINT_LIST;
230    | EndpointTy            | RequestTy       | ResponseTy                      | Path                             |
231    | ----------            | ---------       | ----------                      | ----                             |
232    | IdentifyEndpoint      | ()              | Identity                        | "banc/node/identify"             |
233    | ResetEndpoint         | ()              | ()                              | "banc/node/reset"                |
234    | GpioConfigEndpoint    | GpioConfig      | AckResult                       | "banc/assistant/gpio/config"     |
235    | GpioSetEndpoint       | GpioSet         | AckResult                       | "banc/assistant/gpio/set"        |
236    | GpioReadEndpoint      | GpioRead        | LevelResult                     | "banc/assistant/gpio/read"       |
237    | PinMonitorEndpoint    | PinMonitor      | AckResult                       | "banc/assistant/pin/monitor"     |
238    | UartConfigEndpoint    | UartConfig      | AckResult                       | "banc/assistant/uart/config"     |
239    | UartTxEndpoint        | Chunk           | AckResult                       | "banc/assistant/uart/tx"         |
240    | SpiTransferEndpoint   | SpiTransfer     | ChunkResult                     | "banc/assistant/spi/transfer"    |
241    | I2cTransactionEndpoint| I2cTransaction  | ChunkResult                     | "banc/assistant/i2c/transaction" |
242    | CaptureControlEndpoint| CaptureControl  | CaptureStatusResult             | "banc/assistant/capture/control" |
243    | CaptureReadEndpoint   | CaptureRead     | CaptureChunkResult              | "banc/assistant/capture/read"    |
244}
245
246topics! {
247    list = TOPICS_OUT_LIST;
248    direction = TopicDirection::ToClient;
249    | TopicTy               | MessageTy       | Path                             |
250    | -------               | ---------       | ----                             |
251    | PinEdgeTopic          | PinEvent        | "banc/assistant/pin/edge"        |
252    | UartRxTopic           | UartRxChunk     | "banc/assistant/uart/rx"         |
253}
254
255topics! {
256    list = TOPICS_IN_LIST;
257    direction = TopicDirection::ToServer;
258    | TopicTy               | MessageTy       | Path                             |
259    | -------               | ---------       | ----                             |
260}