embassy_usb_driver/host.rs
1//! USB host driver traits and data types.
2
3use core::time::Duration;
4
5use crate::{EndpointInfo, EndpointType, Speed};
6
7/// Speed of a low- or full-speed device reached through split transactions
8/// (USB 2.0 §11.14) or a `PRE` prefix (USB 1.1 §11.8.6).
9///
10/// High-speed devices are not valid split targets; split metadata only applies
11/// to devices operating at low or full speed behind a hub.
12#[derive(Copy, Clone, Eq, PartialEq, Debug)]
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14pub enum SplitSpeed {
15 /// 1.5 Mbit/s
16 Low,
17 /// 12 Mbit/s
18 Full,
19}
20
21/// Per-pipe information necessary to encode a split-transaction token
22/// (USB 2.0 §11.14) or a legacy full-speed `PRE` packet (USB 1.1 §11.8.6).
23#[derive(Copy, Clone, Eq, PartialEq, Debug)]
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25pub struct SplitInfo {
26 hub_addr: u8,
27 port: u8,
28 device_speed: SplitSpeed,
29}
30
31impl SplitInfo {
32 /// Create a new [`SplitInfo`].
33 ///
34 /// `hub_addr` is the USB address of the hub that owns the Transaction
35 /// Translator; `port` is the 1-based port number on that hub where the
36 /// target device is attached; `device_speed` is the speed of that target
37 /// device ([`SplitSpeed::Low`] or [`SplitSpeed::Full`] only).
38 pub const fn new(hub_addr: u8, port: u8, device_speed: SplitSpeed) -> Self {
39 Self {
40 hub_addr,
41 port,
42 device_speed,
43 }
44 }
45
46 /// USB address of the hub that owns the Transaction Translator.
47 pub const fn hub_addr(self) -> u8 {
48 self.hub_addr
49 }
50
51 /// 1-based port number on the hub where the target device is attached.
52 pub const fn port(self) -> u8 {
53 self.port
54 }
55
56 /// Speed of the split target device (low or full only).
57 pub const fn device_speed(self) -> SplitSpeed {
58 self.device_speed
59 }
60}
61
62/// Errors returned by [`UsbPipe`] operations.
63#[derive(Copy, Clone, Eq, PartialEq, Debug)]
64#[cfg_attr(feature = "defmt", derive(defmt::Format))]
65#[non_exhaustive]
66pub enum PipeError {
67 /// The packet is too long to fit in the buffer.
68 BufferOverflow,
69
70 /// CRC or other hardware-level framing error.
71 BadResponse,
72
73 /// The device sent more data than expected (babble).
74 Babble,
75
76 /// Data toggle sequence mismatch detected.
77 DataToggleError,
78
79 /// Transaction was canceled
80 Canceled,
81
82 /// The device endpoint is stalled.
83 Stall,
84
85 /// Device did not respond in time
86 Timeout,
87
88 /// Device disconnected
89 Disconnected,
90}
91
92/// Device has been attached/detached
93#[derive(Copy, Clone, Eq, PartialEq, Debug)]
94#[cfg_attr(feature = "defmt", derive(defmt::Format))]
95#[non_exhaustive]
96pub enum DeviceEvent {
97 /// Indicates a root-device has become attached
98 Connected(Speed),
99
100 /// Indicates that a device has been detached
101 Disconnected,
102
103 /// Root port overcurrent protection tripped.
104 Overcurrent,
105}
106
107/// Indicates type of error of Host interface
108#[derive(Copy, Clone, Eq, PartialEq, Debug)]
109#[cfg_attr(feature = "defmt", derive(defmt::Format))]
110#[non_exhaustive]
111pub enum HostError {
112 /// A pipe-level transfer error occurred.
113 PipeError(PipeError),
114 /// The control request was not acknowledged by the device.
115 RequestFailed,
116 /// A descriptor returned by the device could not be parsed.
117 InvalidDescriptor,
118 /// No free device slots available.
119 OutOfSlots,
120 /// No free host pipes available.
121 OutOfPipes,
122 /// The addressed device does not exist.
123 NoSuchDevice,
124 /// Insufficient memory for the requested operation.
125 InsufficientMemory,
126 /// An unspecified error with a static description.
127 Other(&'static str),
128}
129
130impl From<PipeError> for HostError {
131 fn from(value: PipeError) -> Self {
132 HostError::PipeError(value)
133 }
134}
135
136/// Pipe allocator trait for USB host drivers.
137///
138/// Implementations are expected to back allocator state with `'d`-lifetime
139/// storage (typically statics or user-provided `&'d` buffers), not with
140/// fields on the controller struct.
141pub trait UsbHostAllocator<'d>: Sized + Clone {
142 /// Pipe implementation produced by this allocator.
143 type Pipe<T: pipe::Type, D: pipe::Direction>: UsbPipe<T, D> + 'd;
144
145 /// Allocate a pipe for communication with a device endpoint.
146 ///
147 /// This can be a scarce resource; for one-off requests please scope the
148 /// pipe so that it is dropped after completion.
149 ///
150 /// `split` — when `Some`, every transfer on this pipe is routed as a
151 /// split transaction through the specified hub's TT (USB 2.0 §11.14), or
152 /// as a legacy `PRE` packet on full-speed controllers (USB 1.1 §11.8.6).
153 /// Pass `None` when the device is reached directly (host at the same
154 /// speed as the device, or the device is high-speed).
155 fn alloc_pipe<T: pipe::Type, D: pipe::Direction>(
156 &self,
157 addr: u8,
158 endpoint: &EndpointInfo,
159 split: Option<SplitInfo>,
160 ) -> Result<Self::Pipe<T, D>, HostError>;
161}
162
163/// Main USB host controller trait.
164///
165/// Covers the bus-level operations that must be serialised on a single
166/// controller instance (root-port event waiting, bus reset). Pipe allocation
167/// lives on the companion [`UsbHostAllocator`] trait.
168///
169/// Implemented by the HAL.
170pub trait UsbHostController<'d>: Sized {
171 /// Pipe allocator associated with this controller.
172 type Allocator: UsbHostAllocator<'d>;
173
174 /// Return an allocator handle.
175 ///
176 /// Callers may keep the handle and use it to allocate pipes concurrently
177 /// with [`wait_for_device_event`](Self::wait_for_device_event)
178 /// or [`bus_reset`](Self::bus_reset).
179 fn allocator(&self) -> Self::Allocator;
180
181 /// Wait for a root-port attach/detach.
182 ///
183 /// On attach, the implementation must drive a bus reset to completion
184 /// before returning and must report the speed that the device settled
185 /// on after reset.
186 async fn wait_for_device_event(&mut self) -> DeviceEvent;
187
188 /// Force a bus reset on the root port.
189 ///
190 /// Invalidates every pipe currently allocated against addresses other
191 /// than 0. Used to recover from a misbehaving device or to force
192 /// re-enumeration without unplug.
193 async fn bus_reset(&mut self);
194}
195
196/// Type-level pipe markers for endpoint type and direction.
197///
198/// These structs and traits are used as generic parameters on [`UsbPipe`]
199/// to statically enforce correct endpoint type and direction at compile time.
200///
201/// All marker traits are sealed — they cannot be implemented outside this crate.
202pub mod pipe {
203 use super::EndpointType;
204
205 mod sealed {
206 pub trait Sealed {}
207 }
208
209 /// Marker trait for the endpoint transfer type of a pipe.
210 pub trait Type: sealed::Sealed + 'static {
211 /// Returns the [`EndpointType`] this marker represents.
212 fn ep_type() -> EndpointType;
213 }
214
215 /// Marker for a control endpoint pipe.
216 pub struct Control {}
217 /// Marker for an interrupt endpoint pipe.
218 pub struct Interrupt {}
219 /// Marker for a bulk endpoint pipe.
220 pub struct Bulk {}
221 /// Marker for an isochronous endpoint pipe.
222 pub struct Isochronous {}
223
224 impl sealed::Sealed for Control {}
225 impl sealed::Sealed for Interrupt {}
226 impl sealed::Sealed for Bulk {}
227 impl sealed::Sealed for Isochronous {}
228
229 impl Type for Control {
230 fn ep_type() -> EndpointType {
231 EndpointType::Control
232 }
233 }
234 impl Type for Interrupt {
235 fn ep_type() -> EndpointType {
236 EndpointType::Interrupt
237 }
238 }
239 impl Type for Bulk {
240 fn ep_type() -> EndpointType {
241 EndpointType::Bulk
242 }
243 }
244 impl Type for Isochronous {
245 fn ep_type() -> EndpointType {
246 EndpointType::Isochronous
247 }
248 }
249
250 /// Trait bound satisfied only by [`Control`] pipes.
251 #[diagnostic::on_unimplemented(message = "This is not a CONTROL pipe")]
252 pub trait IsControl: Type {}
253 impl IsControl for Control {}
254
255 /// Trait bound satisfied only by [`Interrupt`] pipes.
256 #[diagnostic::on_unimplemented(message = "This is not an INTERRUPT pipe")]
257 pub trait IsInterrupt: Type {}
258 impl IsInterrupt for Interrupt {}
259
260 /// Trait bound satisfied only by [`Bulk`] or [`Interrupt`] pipes.
261 #[diagnostic::on_unimplemented(message = "This is not a BULK or INTERRUPT pipe")]
262 pub trait IsBulkOrInterrupt: Type {}
263 impl IsBulkOrInterrupt for Bulk {}
264 impl IsBulkOrInterrupt for Interrupt {}
265
266 /// Marker trait for the transfer direction of a pipe.
267 pub trait Direction: sealed::Sealed + 'static {
268 /// Returns `true` if this direction supports IN (device-to-host) transfers.
269 fn is_in() -> bool;
270 /// Returns `true` if this direction supports OUT (host-to-device) transfers.
271 fn is_out() -> bool;
272 }
273
274 /// Marker for an IN-only (device-to-host) pipe.
275 pub struct In {}
276 /// Marker for an OUT-only (host-to-device) pipe.
277 pub struct Out {}
278 /// Marker for a bidirectional pipe (used for control endpoints).
279 pub struct InOut {}
280
281 impl sealed::Sealed for In {}
282 impl sealed::Sealed for Out {}
283 impl sealed::Sealed for InOut {}
284
285 impl Direction for In {
286 fn is_in() -> bool {
287 true
288 }
289 fn is_out() -> bool {
290 false
291 }
292 }
293 impl Direction for Out {
294 fn is_in() -> bool {
295 false
296 }
297 fn is_out() -> bool {
298 true
299 }
300 }
301 impl Direction for InOut {
302 fn is_in() -> bool {
303 true
304 }
305 fn is_out() -> bool {
306 true
307 }
308 }
309
310 /// Trait bound satisfied by directions that support IN transfers.
311 #[diagnostic::on_unimplemented(message = "This is not an IN pipe")]
312 pub trait IsIn: Direction {}
313 impl IsIn for In {}
314 impl IsIn for InOut {}
315
316 /// Trait bound satisfied by directions that support OUT transfers.
317 #[diagnostic::on_unimplemented(message = "This is not an OUT pipe")]
318 pub trait IsOut: Direction {}
319 impl IsOut for Out {}
320 impl IsOut for InOut {}
321}
322
323/// Timeouts applied to a control pipe's NAK-retry behaviour.
324#[derive(Copy, Clone, Debug, Eq, PartialEq)]
325#[cfg_attr(feature = "defmt", derive(defmt::Format))]
326#[non_exhaustive]
327pub struct TimeoutConfig {
328 /// Maximum response timeout for transactions with a Data Stage.
329 pub data_timeout: Duration,
330
331 /// Maximum response timeout for transactions without a Data Stage.
332 pub no_data_timeout: Duration,
333}
334
335impl Default for TimeoutConfig {
336 fn default() -> Self {
337 TimeoutConfig {
338 data_timeout: Duration::from_millis(500),
339 no_data_timeout: Duration::from_millis(50),
340 }
341 }
342}
343
344/// ## USB Pipes
345/// These contain the required information to send a packet correctly to a device endpoint.
346/// The information is carried with the pipe on creation (see [`UsbHostAllocator::alloc_pipe`]).
347///
348/// It is up to the HAL's driver how to implement concurrent requests, some hardware IP may allow for multiple hardware channels
349/// while others may only have a single channel which needs to be multiplexed in software, while others still use DMA request linked-lists.
350/// Any of these are compatible with the UsbPipe with varying degrees of sync primitives required.
351///
352/// ### NAK handling
353/// Implementations must retry on NAK if appropriate for the transfer type.
354/// - For **control** transfers, the implementation should retry until the configurable timeout expires (see [`UsbPipe::set_timeout`]).
355/// - For **bulk** transfers, the implementation must retry indefinitely. Use `embassy_time::with_timeout` around the future to impose a deadline; dropping the future must abort the transfer.
356/// - For **interrupt** transfers, a NAK indicates no data is available; the implementation should poll again at the next interval.
357///
358/// ### Data toggle
359/// Implementations are responsible for maintaining the data toggle sequence for bulk and interrupt endpoints.
360/// The toggle is initialized to DATA0 when the pipe is allocated and should advance after each successful transfer.
361///
362/// ### Cancellation
363/// All transfer methods (`control_in`, `control_out`, `request_in`, `request_out`) are asynchronous.
364/// If the returned future is dropped before completion, the implementation must abort the in-progress
365/// transfer and leave the pipe in a consistent state for future requests.
366pub trait UsbPipe<T: pipe::Type, D: pipe::Direction> {
367 /// Send IN control request.
368 ///
369 /// Returns the number of bytes received into `buf`.
370 async fn control_in(&mut self, setup: &[u8; 8], buf: &mut [u8]) -> Result<usize, PipeError>
371 where
372 T: pipe::IsControl,
373 D: pipe::IsIn;
374
375 /// Send OUT control request
376 async fn control_out(&mut self, setup: &[u8; 8], buf: &[u8]) -> Result<(), PipeError>
377 where
378 T: pipe::IsControl,
379 D: pipe::IsOut;
380
381 /// Send IN request of type other from control
382 /// For interrupt pipes this will return the result of the next successful interrupt poll
383 async fn request_in(&mut self, buf: &mut [u8]) -> Result<usize, PipeError>
384 where
385 D: pipe::IsIn;
386
387 /// Send OUT request of type other from control
388 /// ensure_transaction_end: Send a zero length packet at the end of transaction if last packet is of max size.
389 async fn request_out(&mut self, buf: &[u8], ensure_transaction_end: bool) -> Result<(), PipeError>
390 where
391 D: pipe::IsOut;
392
393 /// Configure the timeouts of this pipe.
394 fn set_timeout(&mut self, timeout: TimeoutConfig)
395 where
396 T: pipe::IsControl;
397
398 /// Reset the host-side data toggle on this pipe to DATA0.
399 ///
400 /// The caller must invoke this method after:
401 ///
402 /// - `CLEAR_FEATURE(ENDPOINT_HALT)` successfully clears a functional
403 /// stall on this endpoint.
404 /// - `SET_CONFIGURATION` succeeds (all non-control endpoints on the
405 /// affected interfaces must be reset).
406 /// - `SET_INTERFACE` succeeds (all non-control endpoints on the
407 /// affected interface must be reset).
408 fn reset_data_toggle(&mut self)
409 where
410 T: pipe::IsBulkOrInterrupt;
411}