Skip to main content

airfrog_bin/
lib.rs

1// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
2//
3// MIT License
4
5//! Airfrog is the tiny wireless co-processor for ARM.
6//!
7//! <https://piers.rocks/u/airfrog>
8//!
9//! airfrog-bin - Airfrog's binary API shared server/client constants and types
10//!
11//! See [`Binary API`](https://github.com/piersfinlayson/airfrog/blob/main/docs/REST-API.md)
12//! for the binary API specification.
13//!
14//! This crate is `no_std` and platform agnostic.
15//!
16//! This crate is used by the default airfrog firmware to implement the binary
17//! API server.
18//!
19//! It is used by [`probe-rs`](https://github.com/piersfinlayson/probe-rs) to
20//! implement a client for airfrog's binary API.
21
22#![no_std]
23
24extern crate alloc;
25use alloc::vec;
26use alloc::vec::Vec;
27use core::fmt;
28#[allow(unused_imports)]
29use log::{debug, error, info, trace, warn};
30
31/// Port used to serve binary API requests.
32/// Chosen as AF is 0x4146 in hex.
33pub const PORT: u16 = 4146;
34
35// Binary API version
36pub const VERSION: u8 = 0x01;
37
38/// Maximum number of words supported on a bulk data request
39pub const MAX_WORD_COUNT: u16 = 256;
40
41/// Binary API command types
42pub const CMD_DP_READ: u8 = 0x00;
43pub const CMD_DP_WRITE: u8 = 0x01;
44pub const CMD_AP_READ: u8 = 0x02;
45pub const CMD_AP_WRITE: u8 = 0x03;
46pub const CMD_AP_BULK_READ: u8 = 0x12;
47pub const CMD_AP_BULK_WRITE: u8 = 0x13;
48pub const CMD_MULTI_REG_WRITE: u8 = 0x14;
49pub const CMD_PING: u8 = 0xF0;
50pub const CMD_RESET_TARGET: u8 = 0xF1;
51pub const CMD_CLOCK: u8 = 0xF2;
52pub const CMD_SET_SPEED: u8 = 0xF3;
53pub const CMD_DISCONNECT: u8 = 0xFF;
54
55/// Binary API response codes
56pub const RSP_OK: u8 = 0x00;
57pub const RSP_ERR_CMD: u8 = 0x81;
58pub const RSP_ERR_SWD: u8 = 0x82;
59pub const RSP_ERR_TIMEOUT: u8 = 0x83;
60pub const RSP_ERR_NET: u8 = 0x84;
61pub const RSP_ERR_API: u8 = 0x85;
62
63/// Binary API single byte command codes
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[repr(u8)]
66pub enum Command {
67    DpRead = CMD_DP_READ,
68    DpWrite = CMD_DP_WRITE,
69    ApRead = CMD_AP_READ,
70    ApWrite = CMD_AP_WRITE,
71    ApBulkRead = CMD_AP_BULK_READ,
72    ApBulkWrite = CMD_AP_BULK_WRITE,
73    MultiRegWrite = CMD_MULTI_REG_WRITE,
74    Ping = CMD_PING,
75    ResetTarget = CMD_RESET_TARGET,
76    Clock = CMD_CLOCK,
77    SetSpeed = CMD_SET_SPEED,
78    Disconnect = CMD_DISCONNECT,
79}
80
81impl fmt::Display for Command {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Command::DpRead => write!(f, "DP Read"),
85            Command::DpWrite => write!(f, "DP Write"),
86            Command::ApRead => write!(f, "AP Read"),
87            Command::ApWrite => write!(f, "AP Write"),
88            Command::ApBulkRead => write!(f, "AP Bulk Read"),
89            Command::ApBulkWrite => write!(f, "AP Bulk Write"),
90            Command::MultiRegWrite => write!(f, "Multi Register Write"),
91            Command::Ping => write!(f, "Ping"),
92            Command::ResetTarget => write!(f, "Reset Target"),
93            Command::Clock => write!(f, "Clock"),
94            Command::SetSpeed => write!(f, "Set Speed"),
95            Command::Disconnect => write!(f, "Disconnect"),
96        }
97    }
98}
99
100impl Command {
101    /// Converts a Command to its byte representation
102    ///
103    /// Returns:
104    /// - `u8`: The byte representation of the command.
105    pub fn to_byte(self) -> u8 {
106        self as u8
107    }
108
109    /// Convert a command byte to a `Command` enum variant
110    ///
111    /// Arguments:
112    /// - `cmd`: The command byte to convert.
113    ///
114    /// Returns:
115    /// - `Ok(Command)`: If the command byte is recognized.
116    /// - `Err(ProtocolError::Command)`: If the command byte is not
117    ///   recognized.
118    pub fn from_byte(cmd: u8) -> Result<Self, ProtocolError> {
119        match cmd {
120            CMD_DP_READ => Ok(Self::DpRead),
121            CMD_DP_WRITE => Ok(Self::DpWrite),
122            CMD_AP_READ => Ok(Self::ApRead),
123            CMD_AP_WRITE => Ok(Self::ApWrite),
124            CMD_AP_BULK_READ => Ok(Self::ApBulkRead),
125            CMD_AP_BULK_WRITE => Ok(Self::ApBulkWrite),
126            CMD_MULTI_REG_WRITE => Ok(Self::MultiRegWrite),
127            CMD_PING => Ok(Self::Ping),
128            CMD_RESET_TARGET => Ok(Self::ResetTarget),
129            CMD_CLOCK => Ok(Self::Clock),
130            CMD_SET_SPEED => Ok(Self::SetSpeed),
131            CMD_DISCONNECT => Ok(Self::Disconnect),
132            _ => Err(ProtocolError::Command(cmd)),
133        }
134    }
135
136    /// Determine how many more bytes to read for this command type
137    ///
138    /// Returns:
139    /// - `Ok((usize, bool))`: The number of bytes needed and whether the
140    ///   command will receive additional data (i.e. is a bulk command).
141    /// - `Err(SwdError::Api)`: If the command is not recognized.
142    pub fn remaining_bytes(&self) -> Result<(usize, bool), ProtocolError> {
143        match self {
144            Self::DpRead => Ok((1, false)),       // reg
145            Self::DpWrite => Ok((5, false)),      // reg + data
146            Self::ApRead => Ok((1, false)),       // reg
147            Self::ApWrite => Ok((5, false)),      // reg + data
148            Self::ApBulkRead => Ok((3, true)),    // reg + 2 byte count
149            Self::ApBulkWrite => Ok((3, true)),   // reg + 2 byte count (+ N * 4 bytes for data)
150            Self::MultiRegWrite => Ok((2, true)), // count
151            Self::Ping | Self::ResetTarget | Self::Disconnect => Ok((0, false)), // no additional data
152            Self::Clock => Ok((3, false)), // level|post + 2 byte cycles
153            Self::SetSpeed => Ok((1, false)), // speed byte
154        }
155    }
156
157    /// Determines how many variable bytes this command requires
158    ///
159    /// Arguments:
160    /// - `count`: The number of words to read for bulk commands.
161    ///
162    /// Returns:
163    /// - `Ok(usize)`: The number of bytes to read for the command.
164    /// - `Err(SwdError::Api)`: If the command is not recognized or if the
165    ///   count is invalid.
166    pub fn var_bytes(&self, count: u16) -> Result<usize, ProtocolError> {
167        match self {
168            Self::ApBulkRead | Self::ApBulkWrite => {
169                if count > MAX_WORD_COUNT {
170                    Err(ProtocolError::Arg)
171                } else {
172                    Ok(count as usize * 4) // 4 bytes per word
173                }
174            }
175            Self::MultiRegWrite => {
176                if count > MAX_WORD_COUNT {
177                    Err(ProtocolError::Arg)
178                } else {
179                    Ok(count as usize * 6) // 1 byte reg type + 1 byte reg + 4 bytes data
180                }
181            }
182            _ => Ok(0), // No variable bytes for other commands
183        }
184    }
185}
186
187/// Binary API single byte response codes
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189#[repr(u8)]
190pub enum ResponseCode {
191    Ok = RSP_OK,
192    Cmd = RSP_ERR_CMD,
193    Swd = RSP_ERR_SWD,
194    Timeout = RSP_ERR_TIMEOUT,
195    Net = RSP_ERR_NET,
196    Api = RSP_ERR_API,
197}
198
199impl fmt::Display for ResponseCode {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            ResponseCode::Ok => write!(f, "OK"),
203            ResponseCode::Cmd => write!(f, "Command Error"),
204            ResponseCode::Swd => write!(f, "SWD Error"),
205            ResponseCode::Timeout => write!(f, "Timeout Error"),
206            ResponseCode::Net => write!(f, "Network Error"),
207            ResponseCode::Api => write!(f, "API Error"),
208        }
209    }
210}
211
212/// Airfrog speed
213#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
214#[repr(u8)]
215pub enum Speed {
216    /// Slow speed, 500 kHz
217    Slow = 3,
218
219    /// Medium speed, 1000 kHz
220    Medium = 2,
221
222    /// Fast speed, 2000 kHz
223    Fast = 1,
224
225    /// Turbo speed, 4000 kHz - this is the default speed
226    #[default]
227    Turbo = 0,
228}
229
230impl Speed {
231    /// Convert from kHz value to Speed
232    ///
233    /// Arguments:
234    /// - `khz`: The kHz value to convert.
235    ///
236    /// Returns:
237    /// - `Speed`: The corresponding Speed variant.
238    pub fn from_khz(khz: u32) -> Self {
239        match khz {
240            0..=750 => Speed::Slow,
241            751..=1500 => Speed::Medium,
242            1501..=3000 => Speed::Fast,
243            _ => Speed::Turbo,
244        }
245    }
246
247    /// Convert from Speed to kHz
248    ///
249    /// Returns:
250    /// - `u32`: The kHz value corresponding to the Speed variant.
251    pub fn to_khz(self) -> u32 {
252        match self {
253            Speed::Slow => 500,
254            Speed::Medium => 1000,
255            Speed::Fast => 2000,
256            Speed::Turbo => 4000,
257        }
258    }
259
260    /// Convert from a byte value to a Speed
261    ///
262    /// Arguments:
263    /// - `byte`: The byte value to convert.
264    ///
265    /// Returns:
266    /// - `Ok(Speed)`: If the byte value is valid.
267    /// - `Err(ProtocolError::Arg)`: If the byte value is not
268    ///   recognized.
269    pub fn from_byte(byte: u8) -> Result<Self, ProtocolError> {
270        match byte {
271            0 => Ok(Speed::Turbo),
272            1 => Ok(Speed::Fast),
273            2 => Ok(Speed::Medium),
274            3 => Ok(Speed::Slow),
275            _ => {
276                debug!("Invalid speed byte: {byte}");
277                Err(ProtocolError::Arg)
278            }
279        }
280    }
281}
282
283/// Type used to represent errors that can occur in sending or receiving
284/// commands over the binary API.
285#[derive(Debug)]
286pub enum ProtocolError {
287    /// Invalid command byte received
288    Command(u8),
289
290    /// Invalid argument provided
291    Arg,
292}
293
294/// Type used to represent errors that can occur in parsing received commands
295/// over the binary API.
296#[derive(Debug)]
297pub enum ParseError<T> {
298    Transport(T),
299    Protocol(ProtocolError),
300}
301
302impl<T> From<ProtocolError> for ParseError<T> {
303    fn from(e: ProtocolError) -> Self {
304        ParseError::Protocol(e)
305    }
306}
307
308impl<T> ParseError<T> {
309    fn transport(e: T) -> Self {
310        ParseError::Transport(e)
311    }
312}
313
314/// Async reader trait for reading data from a stream
315pub trait AsyncReader {
316    type Error;
317    fn read_exact(&mut self, buf: &mut [u8]) -> impl Future<Output = Result<(), Self::Error>>;
318}
319
320/// Sync writer trait for writing data to a stream
321pub trait SyncWriter {
322    type Error;
323    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error>;
324}
325
326/// Represents the type of register being accessed in a command.
327#[derive(Debug, PartialEq, Eq, Clone)]
328#[repr(u8)]
329pub enum RegType {
330    /// Debug Port (DP) register
331    Dp = 0x00,
332    /// Access Port (AP) register
333    Ap = 0x01,
334}
335
336#[derive(Debug, PartialEq, Eq, Clone)]
337pub struct MultiReg {
338    pub reg_type: RegType,
339    pub reg: u8,
340    pub data: u32,
341}
342
343/// Represents a binary API operation that can be performed over the Airfrog
344/// SWD interface.  See the
345/// [Binary API documentation](https://github.com/piersfinlayson/airfrog/blob/main/docs/REST-API.md)
346/// for details.
347#[derive(Debug, PartialEq, Eq, Clone)]
348pub enum Op {
349    DpRead {
350        reg: u8,
351    },
352    DpWrite {
353        reg: u8,
354        data: u32,
355    },
356    ApRead {
357        reg: u8,
358    },
359    ApWrite {
360        reg: u8,
361        data: u32,
362    },
363    ApBulkRead {
364        reg: u8,
365        count: u16,
366    },
367    ApBulkWrite {
368        reg: u8,
369        data: Vec<u32>,
370    },
371    MultiRegWrite {
372        count: u16,
373        data: Vec<MultiReg>,
374    },
375    Ping,
376    ResetTarget,
377    Clock {
378        level: LineLevel,
379        post_level: LineLevel,
380        cycles: u16,
381    },
382    SetSpeed {
383        speed: Speed,
384    },
385    Disconnect,
386}
387
388impl fmt::Display for Op {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        if f.alternate() {
391            match self {
392                Op::DpRead { reg } => write!(f, "DpRead(reg={reg})"),
393                Op::DpWrite { reg, data } => write!(f, "DpWrite(reg={reg}, data={data})"),
394                Op::ApRead { reg } => write!(f, "ApRead(reg={reg})"),
395                Op::ApWrite { reg, data } => write!(f, "ApWrite(reg={reg}, data={data})"),
396                Op::ApBulkRead { reg, count } => {
397                    write!(f, "ApBulkRead(reg={reg}, count={count})")
398                }
399                Op::ApBulkWrite { reg, data } => {
400                    write!(f, "ApBulkWrite(reg={reg}, data={data:?})")
401                }
402                Op::MultiRegWrite { count, data } => {
403                    write!(f, "MultiRegWrite(count={count}, data={data:?})")
404                }
405                Op::Ping => write!(f, "Ping"),
406                Op::ResetTarget => write!(f, "ResetTarget"),
407                Op::Clock {
408                    level,
409                    post_level,
410                    cycles,
411                } => {
412                    write!(
413                        f,
414                        "Clock(level={level:?}, post_level={post_level:?}, cycles={cycles})",
415                    )
416                }
417                Op::SetSpeed { speed } => write!(f, "SetSpeed(speed={speed:?})"),
418                Op::Disconnect => write!(f, "Disconnect"),
419            }
420        } else {
421            match self {
422                Op::DpRead { .. } => write!(f, "DP Read"),
423                Op::DpWrite { .. } => write!(f, "DP Write"),
424                Op::ApRead { .. } => write!(f, "AP Read"),
425                Op::ApWrite { .. } => write!(f, "AP Write"),
426                Op::ApBulkRead { .. } => write!(f, "AP Bulk Read"),
427                Op::ApBulkWrite { .. } => write!(f, "AP Bulk Write"),
428                Op::MultiRegWrite { .. } => write!(f, "Multi Register Write"),
429                Op::Ping => write!(f, "Ping"),
430                Op::ResetTarget => write!(f, "Reset Target"),
431                Op::Clock { .. } => write!(f, "Clock"),
432                Op::SetSpeed { .. } => write!(f, "Set Speed"),
433                Op::Disconnect => write!(f, "Disconnect"),
434            }
435        }
436    }
437}
438
439// Public Op methods
440impl Op {
441    pub async fn recv_cmd<R: AsyncReader>(reader: &mut R) -> Result<Command, ParseError<R::Error>> {
442        let mut cmd = [0u8; 1];
443        reader
444            .read_exact(&mut cmd)
445            .await
446            .map_err(ParseError::transport)?;
447        let command = Command::from_byte(cmd[0])?;
448        Ok(command)
449    }
450
451    /// Used by an airfrog binary API server to receive a complete command from
452    /// an API client.
453    ///
454    /// Called once the command byte has been read from the stream.
455    ///
456    /// Arguments:
457    /// - `cmd`: The command byte received.
458    /// - `reader`: A mutable reference to a reader that implements the
459    ///   `Reader` trait
460    ///
461    /// Returns:
462    /// - `Ok(Self)`: If the command was successfully parsed.
463    /// - `Err(ParseError<R::Error>)`: If there was an error parsing the
464    ///   received command.
465    pub async fn async_recv<R: AsyncReader>(
466        command: Command,
467        reader: &mut R,
468    ) -> Result<Self, ParseError<R::Error>> {
469        trace!("Received command: {command}");
470        let (bytes_needed, _) = command.remaining_bytes()?;
471
472        let buf = if bytes_needed > 0 {
473            let mut buf = vec![0u8; bytes_needed];
474            reader
475                .read_exact(&mut buf)
476                .await
477                .inspect_err(|_| debug!("Failed to read static command bytes {bytes_needed}"))
478                .map_err(ParseError::transport)?;
479            buf
480        } else {
481            vec![]
482        };
483
484        match command {
485            Command::DpRead => Ok(Op::DpRead { reg: buf[0] }),
486            Command::DpWrite => {
487                let reg = buf[0];
488                let data = Self::parse_word(&buf[1..5])?;
489                Ok(Op::DpWrite { reg, data })
490            }
491            Command::ApRead => Ok(Op::ApRead { reg: buf[0] }),
492            Command::ApWrite => {
493                let reg = buf[0];
494                let data = Self::parse_word(&buf[1..5])?;
495                Ok(Op::ApWrite { reg, data })
496            }
497            Command::ApBulkRead => {
498                let reg = buf[0];
499                let count = Self::parse_count(&buf[1..3])?;
500                Ok(Op::ApBulkRead { reg, count })
501            }
502            Command::ApBulkWrite => {
503                let reg = buf[0];
504                let count = Self::parse_count(&buf[1..3])?;
505
506                // Read in the additional data bytes
507                let data_bytes = command.var_bytes(count)?;
508                let mut data_buf = vec![0u8; data_bytes];
509                reader
510                    .read_exact(&mut data_buf)
511                    .await
512                    .inspect_err(|_| debug!("Failed to read variable command bytes {data_bytes}"))
513                    .map_err(ParseError::transport)?;
514
515                // Process them
516                let mut data = Vec::with_capacity(count as usize);
517                for chunk in data_buf.chunks_exact(4) {
518                    data.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
519                }
520
521                Ok(Op::ApBulkWrite { reg, data })
522            }
523            Command::MultiRegWrite => {
524                let count = Self::parse_count(&buf[0..2])?;
525
526                // Read in the additional data bytes
527                let data_bytes = command.var_bytes(count)?;
528                let mut data_buf = vec![0u8; data_bytes];
529                reader
530                    .read_exact(&mut data_buf)
531                    .await
532                    .inspect_err(|_| debug!("Failed to read variable command bytes {data_bytes}"))
533                    .map_err(ParseError::transport)?;
534
535                // Process them
536                let mut data = Vec::with_capacity(count as usize);
537                for chunk in data_buf.chunks_exact(6) {
538                    let reg_type = match chunk[0] {
539                        0x00 => RegType::Dp,
540                        0x01 => RegType::Ap,
541                        _ => return Err(ParseError::Protocol(ProtocolError::Arg)),
542                    };
543                    let reg = chunk[1];
544                    let value = u32::from_le_bytes([chunk[2], chunk[3], chunk[4], chunk[5]]);
545                    data.push(MultiReg {
546                        reg_type,
547                        reg,
548                        data: value,
549                    });
550                }
551                Ok(Op::MultiRegWrite { count, data })
552            }
553            Command::Ping => Ok(Op::Ping),
554            Command::ResetTarget => Ok(Op::ResetTarget),
555            Command::Clock => {
556                let (level, post_level) = LineLevel::levels_from_byte(buf[0])?;
557                let cycles = u16::from_le_bytes([buf[1], buf[2]]);
558                Ok(Op::Clock {
559                    level,
560                    post_level,
561                    cycles,
562                })
563            }
564            Command::SetSpeed => {
565                let speed = Speed::from_byte(buf[0])?;
566                Ok(Op::SetSpeed { speed })
567            }
568            Command::Disconnect => Ok(Op::Disconnect),
569        }
570    }
571
572    /// Used by an airfrog binary API client to send a command to an API
573    /// server.
574    ///
575    /// Arguments:
576    /// - `writer`: A mutable reference to a writer that implements the
577    ///   `Writer` trait
578    ///
579    /// Returns:
580    /// - `Ok(())`: If the command was successfully sent.
581    /// - `Err(W::Error)`: If there was an error sending the command.
582    pub fn sync_send<W: SyncWriter>(&self, _writer: &mut W) -> Result<(), W::Error> {
583        todo!()
584    }
585}
586
587// Internal Op methods
588impl Op {
589    fn parse_word(bytes: &[u8]) -> Result<u32, ProtocolError> {
590        if bytes.len() != 4 {
591            debug!("Invalid word bytes: {bytes:?}");
592            return Err(ProtocolError::Arg);
593        }
594        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
595    }
596
597    fn parse_count(bytes: &[u8]) -> Result<u16, ProtocolError> {
598        if bytes.len() != 2 {
599            debug!("Invalid count bytes: {bytes:?}");
600            return Err(ProtocolError::Arg);
601        }
602        Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
603    }
604}
605
606/// Used to represent the SWDIO line state on clock operations.
607#[derive(Debug, PartialEq, Eq, Clone)]
608#[repr(u8)]
609pub enum LineLevel {
610    Low = 0,
611    High = 1,
612    Input = 2,
613}
614
615impl LineLevel {
616    /// Convert from a byte value to a LineLevel
617    ///
618    /// Arguments:
619    /// - `value`: The byte value to convert.
620    ///
621    /// Returns:
622    /// - `Ok((LineLevel, LineLevel)`: the leve and post level.
623    /// - `Err(ProtocolError::Arg)`: If the byte value is not
624    ///   recognized.
625    pub fn levels_from_byte(value: u8) -> Result<(Self, Self), ProtocolError> {
626        let level_bits = value & 0x0F;
627        let level = match level_bits {
628            0 => LineLevel::Low,
629            1 => LineLevel::High,
630            2 => LineLevel::Input,
631            _ => {
632                debug!("Invalid level bits: {level_bits}");
633                return Err(ProtocolError::Arg);
634            }
635        };
636
637        let post_bits = value >> 4;
638        let post_level = match post_bits {
639            0 => LineLevel::Low,
640            1 => LineLevel::High,
641            2 => LineLevel::Input,
642            _ => {
643                debug!("Invalid post level bits: {post_bits}");
644                return Err(ProtocolError::Arg);
645            }
646        };
647
648        Ok((level, post_level))
649    }
650}