Skip to main content

airfrog_rpc/
lib.rs

1//! Remote Procedure Call crate for co-processing with ARM targets over SWD and other debug
2//! protocols.
3//!
4//! This crate enables reliable, bidirectional communication between a debug host and target
5//! microcontroller using memory-mapped channels. Unlike traditional debugging tools, this
6//! allows the host and target act as co-processors alongisde each other, with one being
7//! controlled by the other, handling commands and returning results.
8//!
9//! `no_std`.  Requires `alloc` for async traits, typically used on the Host side..
10//!
11//! Includes sample host and target implementations.
12//!
13//! ## Architecture
14//!
15//! Assumes a Host (debug controller) and Target (microcontroller) architecture.
16//!
17//! Communication typically takes place using two unidirectional channels in the target's SRAM:
18//! - **Command channel**: Host writes commands, target reads
19//! - **Response channel**: Target writes responses, host reads
20//!
21//! Channels can be used in either direction, and for different purposes as required.  All that
22//! is required is a dedicated memory region in the target's SRAM for each channel.  CCM RAM
23//! may also be used on STM32F4 MCUs if available.
24//!
25//! Each channel contains a control block (sequence numbers, flags, data size) followed by a
26//! data area, with the data area used to transmit information on the channel.  The amount of
27//! memory provided for each channel is used for both the control block and data area.  The
28//! control block is small (tens of byes), so the majority of the reserved memory is available
29//! for data.
30//!
31//! Only a single producer and single consumer are supported per channel, and only one data
32//! chunk can be written and "transferred" by the producer to the consumer at a time.
33//!
34//! The channel protocol ensures reliable delivery using producer/consumer sequence numbers
35//! and atomic word operations using the target's memory ordering guarantees.
36//!
37//! Data can be writte in any format, using bytes or 32-bit little endian words.
38//!
39//! Currently this crate only supports little-endian Hosts _and_ Targets.
40//!
41//! ## Modules
42//!
43//! - [`channel`] - Channel objects for unidirectional communication in either direction
44//! - [`client`] - RPC client for sending commands and receiving responses, typically used
45//!   on the host
46//! - [`io`] - Async I/O traits for debug interface access, typically used for host access
47//!   to target RAM/flash peripherals
48//!
49//! ## Supported Targets
50//!
51//! Works on ARM Cortex-M microcontrollers with strong memory ordering, with SWD accessing
52//! the SRAM via the AHB bus matrix, and no data caches:
53//! - Cortex-M0/M0+/M3/M4/M23/M33 (STM32, RP2040/2350, nRF52, etc.)
54//!
55//! The target implementation, [`channel::RamChannel`] can be used as is.  The host
56//! implementation needs [`io::Reader`] and [`io::Writer`] implementations.
57//!
58//! `airfrog::airfrog_bin::firmare` contains an SWD implementation of [`io::Reader`] and
59//! [`io::Writer`] for ESP32-C3, and uses [`channel::ReaderWriterChannel`], allowing it
60//! to communicate using these channels with an SWD target.
61//!
62//! ## Getting Started
63//!
64//! The target must reserved dedicated SRAM regions for each channel.
65//!
66//! The host (debug controller) must know the addresses of these regions and reads/writes
67//! them over the debug interface.
68//!
69//! If you wish the host to dynamically learn the addresses/sizes of these regions you must
70//! implement this separately - for example, include pointers at well know SRAM or flash
71//! locations on the target pointeing to these regions.  Alternatively, you could arrange
72//! for the locations and sizes to be fixed by the target's linker script.
73//!
74//! Each communication channel consists of a control block, and data area.  The control
75//! block is used by the protocol to ensure reliable message passing.  The data area is
76//! where the actual command or response payload is stored.
77//!
78//! **Target setup**:
79//! 1. Reserve SRAM region(s) e.g. 1KB each for each channel
80//! 2. Create a [`channel::RamChannelIo`] instance for each required channel
81//! 3. Create a [`channel::RamChannel`] instance for each channel
82//! 4. Poll [`channel::RamChannel::data_available()`] for your consumer channel, in your
83//!    main loop or dedicated task
84//! 5. When data arrives, process it, and optionally send responses on alternate channel
85//! 6. Data format is application-specific and currently either bytes or u32s
86//!
87//! **Host setup**:
88//! 1. Configure channel locations, or dynamically read from the target using well-known
89//!    locations for pointers to the channel locations/sizes
90//! 2. Create a [`channel::ReaderWriterChannelIo`] instance with your debug interface
91//!    reader/writer implementation (see `airfrog::airfrog_bin::firmware` for an SWD
92//!    implementation)
93//! 3. Create a [`channel::ReaderWriterChannel`] instance for your channel.
94//! 4. Send data with [`channel::ReaderWriterChannel::publish_bytes()`].
95//! 5. Before using a different channel you will likely need to ensure the previous
96//!    channel is dropped, to free up the Io instance to be mutably borrowed by your
97//!    new channel.
98//!
99//! As implied a Channel is intended to be short-lived - create, use, drop. This allows
100//! temporary ownership of the Reader/Writer, which may be a shared hardware resource on
101//! the host.  If this becomes tedious, create a wrapper to abstract away this creation/
102//! destruction or use [`client::AsyncRpcClient`] which abstracts this lifecycle away,
103//! and provides a higher-level request() API, using a pair of channels (one command,
104//! the other response).
105//!
106//! The RPC layer handles reliable delivery, but your application defines the actual
107//! command/response protocol and data formats.
108//!
109//! While the above documentation describes the Host controlling the Target, it is
110//! possible to use the channel(s) in the reverse direction.
111//!
112//! See individual module and struct documentation for usage examples.
113//!
114//! ## Features
115//!
116//! Default features:
117//! - `async` - Enable async channel implementations and traits (requires `alloc`), which
118//!   is generally required by the Host, but not by the Target.
119//!
120//! Compile with `--no-default-features` to disable unnecessary async support for a Target.
121
122// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
123//
124// MIT License
125
126#![no_std]
127
128#[cfg(feature = "async")]
129extern crate alloc;
130
131pub mod channel;
132pub mod client;
133pub mod io;
134
135/// RPC errors
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub enum Error {
138    /// No data available
139    NoData,
140    /// Channel busy
141    Busy,
142    /// Timeout waiting for response
143    Timeout,
144    /// Invalid operation
145    InvalidOperation,
146    /// Payload too large for buffer
147    PayloadTooLarge,
148    /// Sequence mismatch
149    SequenceMismatch,
150    /// Buffer too small for operation
151    BufferTooSmall,
152    /// I/O error
153    Io,
154    /// Uninitialized channel
155    Uninit,
156    /// Data area or buffer not aligned
157    NotAligned,
158}
159
160/// Type to represent the result of an RPC operation
161pub type Result<T> = core::result::Result<T, Error>;