pub const ONEROM_MAGIC: u32 =
b'O' as u32 | (b'N' as u32) << 8 | (b'E' as u32) << 16 | (b'R' as u32) << 24;
pub const PICOBOOT_DIR_IN: u8 = 0x80;
pub const ONEROM_CMD_SET_LED: u8 = 0x01;
pub const ONEROM_CMD_GET_CAPS: u8 = 0x02;
pub const ONEROM_CMD_GPIO_SET: u8 = 0x03;
pub const ONEROM_CMD_GPIO_QUERY: u8 = 0x04;
pub const ONEROM_CMD_ARGS_LEN: usize = 16;
pub const ONEROM_CAPS_LEN: u32 = 32;
pub const ONEROM_GPIO_ENTRY_LEN: usize = 4;
pub const ONEROM_FEAT_GPIO_SET: u32 = 1 << 0;
pub const ONEROM_FEAT_GPIO_QUERY: u32 = 1 << 1;
pub const ONEROM_FEAT_GPIO_HOLD: u32 = 1 << 2;
pub const ONEROM_GPIO_FLAG_FORCE: u8 = 1 << 0;
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
#[error("Capabilities response is too short to decode: {0} bytes")]
CapsTooShort(usize),
#[error("GPIO query response is {0} bytes, not a whole number of 4-byte entries")]
GpioEntriesMisaligned(usize),
}
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum LedSubCmd {
Off = 0x00,
On = 0x01,
Beacon = 0x02,
Flame = 0x03,
}
#[derive(Debug, Clone, Copy)]
pub struct SetLedArgs {
pub led_id: u8,
pub sub_cmd: LedSubCmd,
}
impl SetLedArgs {
pub fn encode(&self) -> [u8; ONEROM_CMD_ARGS_LEN] {
let mut args = [0u8; ONEROM_CMD_ARGS_LEN];
args[0] = self.led_id;
args[1] = self.sub_cmd as u8;
args
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioState {
Low = 0,
High = 1,
Input = 2,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioUse {
Free = 0,
ServingRead = 1,
ServingDriven = 2,
SystemPin = 3,
}
impl GpioUse {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Free),
1 => Some(Self::ServingRead),
2 => Some(Self::ServingDriven),
3 => Some(Self::SystemPin),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GpioSetArgs {
pub gpio: u8,
pub state: GpioState,
pub after_state: GpioState,
pub flags: u8,
pub duration_ms: u32,
}
impl GpioSetArgs {
pub fn encode(&self) -> [u8; ONEROM_CMD_ARGS_LEN] {
let mut args = [0u8; ONEROM_CMD_ARGS_LEN];
args[0] = self.gpio;
args[1] = self.state as u8;
args[2] = self.after_state as u8;
args[3] = self.flags;
args[4..8].copy_from_slice(&self.duration_ms.to_le_bytes());
args
}
}
#[derive(Debug, Clone, Copy)]
pub struct GpioQueryArgs {
pub first_gpio: u8,
pub count: u8,
}
impl GpioQueryArgs {
pub fn encode(&self) -> [u8; ONEROM_CMD_ARGS_LEN] {
let mut args = [0u8; ONEROM_CMD_ARGS_LEN];
args[0] = self.first_gpio;
args[1] = self.count;
args
}
pub fn transfer_len(&self) -> u32 {
self.count as u32 * ONEROM_GPIO_ENTRY_LEN as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GpioEntry {
pub gpio_use_raw: u8,
pub level: u8,
pub is_output: u8,
}
impl GpioEntry {
pub fn decode_all(buf: &[u8]) -> Result<Vec<Self>, DecodeError> {
if !buf.len().is_multiple_of(ONEROM_GPIO_ENTRY_LEN) {
return Err(DecodeError::GpioEntriesMisaligned(buf.len()));
}
Ok(buf
.chunks_exact(ONEROM_GPIO_ENTRY_LEN)
.map(|e| Self {
gpio_use_raw: e[0],
level: e[1],
is_output: e[2],
})
.collect())
}
pub fn gpio_use(&self) -> Option<GpioUse> {
GpioUse::from_u8(self.gpio_use_raw)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Caps {
pub struct_len: u16,
pub ext_major: u8,
pub ext_minor: u8,
pub features: u32,
pub num_gpios: u8,
pub max_hold_ms: u32,
}
impl Caps {
pub fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
if buf.len() < 2 {
return Err(DecodeError::CapsTooShort(buf.len()));
}
let struct_len = u16::from_le_bytes([buf[0], buf[1]]);
let usable = (struct_len as usize).min(buf.len());
let u8_at = |off: usize| -> u8 { if off < usable { buf[off] } else { 0 } };
let u32_at = |off: usize| -> u32 {
if off + 4 <= usable {
u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
} else {
0
}
};
Ok(Self {
struct_len,
ext_major: u8_at(2),
ext_minor: u8_at(3),
features: u32_at(4),
num_gpios: u8_at(8),
max_hold_ms: u32_at(12),
})
}
pub fn has_feature(&self, feature: u32) -> bool {
self.features & feature != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_ids_match_the_header() {
assert_eq!(ONEROM_MAGIC, 0x5245_4E4F); assert_eq!(ONEROM_CMD_SET_LED, 0x01);
assert_eq!(ONEROM_CMD_GET_CAPS, 0x02);
assert_eq!(ONEROM_CMD_GPIO_SET, 0x03);
assert_eq!(ONEROM_CMD_GPIO_QUERY, 0x04);
assert_eq!(ONEROM_CMD_GET_CAPS | PICOBOOT_DIR_IN, 0x82);
assert_eq!(ONEROM_CMD_GPIO_QUERY | PICOBOOT_DIR_IN, 0x84);
}
#[test]
fn enum_discriminants_match_the_firmware() {
assert_eq!(GpioState::Low as u8, 0);
assert_eq!(GpioState::High as u8, 1);
assert_eq!(GpioState::Input as u8, 2);
assert_eq!(GpioUse::from_u8(0), Some(GpioUse::Free));
assert_eq!(GpioUse::from_u8(1), Some(GpioUse::ServingRead));
assert_eq!(GpioUse::from_u8(2), Some(GpioUse::ServingDriven));
assert_eq!(GpioUse::from_u8(3), Some(GpioUse::SystemPin));
assert_eq!(GpioUse::from_u8(4), None);
assert_eq!(GpioUse::from_u8(0xFF), None);
assert_eq!(ONEROM_GPIO_FLAG_FORCE, 0x01);
assert_eq!(ONEROM_FEAT_GPIO_SET, 0x0000_0001);
assert_eq!(ONEROM_FEAT_GPIO_QUERY, 0x0000_0002);
assert_eq!(ONEROM_FEAT_GPIO_HOLD, 0x0000_0004);
}
#[test]
fn gpio_set_args_encode_to_the_header_layout() {
let args = GpioSetArgs {
gpio: 23,
state: GpioState::Low,
after_state: GpioState::Input,
flags: ONEROM_GPIO_FLAG_FORCE,
duration_ms: 100,
};
assert_eq!(
args.encode(),
[
23, 0, 2, 1, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
);
}
#[test]
fn gpio_set_args_duration_is_little_endian() {
let args = GpioSetArgs {
gpio: 0,
state: GpioState::High,
after_state: GpioState::Low,
flags: 0,
duration_ms: 0x0102_0304,
};
assert_eq!(
args.encode(),
[0, 1, 0, 0, 0x04, 0x03, 0x02, 0x01, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn gpio_query_args_encode_to_the_header_layout() {
let args = GpioQueryArgs {
first_gpio: 4,
count: 30,
};
assert_eq!(
args.encode(),
[4, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn gpio_query_transfer_len_fits_picoboot_limits() {
for count in [1u8, 30, 48] {
let len = GpioQueryArgs {
first_gpio: 0,
count,
}
.transfer_len();
assert_eq!(len, count as u32 * 4);
assert_eq!(len % 4, 0, "picoboot expects a multiple of 4");
assert!(len <= 256, "picoboot expects at most 256 bytes");
}
assert_eq!(
GpioQueryArgs {
first_gpio: 0,
count: 48
}
.transfer_len(),
192
);
}
#[test]
fn gpio_entries_decode_from_the_header_layout() {
let buf = [
0, 1, 0, 0xAA, 2, 0, 1, 0xBB, 9, 1, 1, 0xCC, ];
let entries = GpioEntry::decode_all(&buf).expect("decodes");
assert_eq!(entries.len(), 3);
assert_eq!(
entries[0],
GpioEntry {
gpio_use_raw: 0,
level: 1,
is_output: 0
}
);
assert_eq!(entries[0].gpio_use(), Some(GpioUse::Free));
assert_eq!(
entries[1],
GpioEntry {
gpio_use_raw: 2,
level: 0,
is_output: 1
}
);
assert_eq!(entries[1].gpio_use(), Some(GpioUse::ServingDriven));
assert_eq!(entries[2].gpio_use_raw, 9);
assert_eq!(entries[2].gpio_use(), None);
}
#[test]
fn gpio_entries_reject_a_partial_entry() {
assert!(matches!(
GpioEntry::decode_all(&[0, 0, 0, 0, 1]),
Err(DecodeError::GpioEntriesMisaligned(5))
));
assert_eq!(GpioEntry::decode_all(&[]).expect("empty is legal").len(), 0);
}
fn caps_bytes() -> [u8; 32] {
[
0x20, 0x00, 0x01, 0x02, 0x07, 0x00, 0x00, 0x00, 48, 0xAA, 0xAA, 0xAA, 0xE8, 0x03, 0x00, 0x00, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
0xBB, 0xBB,
]
}
#[test]
fn caps_decode_from_the_header_layout() {
let caps = Caps::decode(&caps_bytes()).expect("decodes");
assert_eq!(caps.struct_len, 32);
assert_eq!(caps.ext_major, 1);
assert_eq!(caps.ext_minor, 2);
assert_eq!(caps.features, 0x0000_0007);
assert_eq!(caps.num_gpios, 48);
assert_eq!(caps.max_hold_ms, 1000);
assert!(caps.has_feature(ONEROM_FEAT_GPIO_SET));
assert!(caps.has_feature(ONEROM_FEAT_GPIO_QUERY));
assert!(caps.has_feature(ONEROM_FEAT_GPIO_HOLD));
assert!(!caps.has_feature(1 << 3));
}
#[test]
fn caps_decode_honours_num_gpios_of_thirty() {
let mut buf = caps_bytes();
buf[8] = 30;
assert_eq!(Caps::decode(&buf).expect("decodes").num_gpios, 30);
}
#[test]
fn caps_decode_zeroes_fields_beyond_struct_len() {
let mut buf = caps_bytes();
buf[0] = 9;
let caps = Caps::decode(&buf).expect("decodes");
assert_eq!(caps.struct_len, 9);
assert_eq!(caps.ext_major, 1);
assert_eq!(caps.features, 0x0000_0007);
assert_eq!(caps.num_gpios, 48);
assert_eq!(caps.max_hold_ms, 0);
}
#[test]
fn caps_decode_accepts_a_struct_len_over_thirty_two() {
let mut buf = caps_bytes();
buf[0] = 48;
let caps = Caps::decode(&buf).expect("decodes");
assert_eq!(caps.struct_len, 48);
assert_eq!(caps.num_gpios, 48);
assert_eq!(caps.max_hold_ms, 1000);
}
#[test]
fn caps_decode_accepts_a_longer_response() {
let mut buf = caps_bytes().to_vec();
buf[0] = 48;
buf.extend_from_slice(&[0xCC; 16]);
let caps = Caps::decode(&buf).expect("decodes");
assert_eq!(caps.struct_len, 48);
assert_eq!(caps.max_hold_ms, 1000);
}
#[test]
fn caps_decode_accepts_a_short_response() {
let caps = Caps::decode(&caps_bytes()[..8]).expect("decodes");
assert_eq!(caps.struct_len, 32);
assert_eq!(caps.ext_major, 1);
assert_eq!(caps.features, 0x0000_0007);
assert_eq!(caps.num_gpios, 0);
assert_eq!(caps.max_hold_ms, 0);
}
#[test]
fn caps_decode_rejects_a_response_without_a_struct_len() {
assert!(matches!(
Caps::decode(&[0x20]),
Err(DecodeError::CapsTooShort(1))
));
assert!(matches!(
Caps::decode(&[]),
Err(DecodeError::CapsTooShort(0))
));
}
}