cotton_usb_host/host_controller.rs
1use crate::wire::SetupPacket;
2use core::cell::Cell;
3use core::ops::Deref;
4use futures::Stream;
5
6/// Errors reported from a USB operation
7#[cfg_attr(feature = "defmt", derive(defmt::Format))]
8#[cfg_attr(feature = "std", derive(Debug))]
9#[derive(Copy, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum UsbError {
12 /// The device has stalled the endpoint
13 ///
14 /// See USB 2.0 section 8.5.2 and 8.5.3.4, and, for a prolific user
15 /// of stall conditions, the USB Mass Storage Bulk-Only Transport
16 /// section 6.
17 Stall,
18 /// The USB transaction has timed out
19 ///
20 /// A NAK response is automatically retried, but if NAKs persist, eventually
21 /// the transfer will time out.
22 Timeout,
23 /// The input FIFO overflowed
24 ///
25 /// This error, produced by the USB host-controller hardware, probably
26 /// represents a bug in cotton-usb-host (or perhaps a failure to service
27 /// interrupts quickly enough).
28 Overflow,
29 /// The USB hardware experienced a bit-stuffing error
30 BitStuffError,
31 /// The USB hardware experienced a CRC error
32 CrcError,
33 /// The USB hardware received a DATA1 packet when expecting DATA0, or vice versa
34 ///
35 /// This probably indicates a bug in cotton-usb-host (which is
36 /// intended to take care of the data toggle without client code
37 /// needing to intervene).
38 DataSeqError,
39 /// A buffer supplied to cotton-usb-host was too small for the intended data
40 BufferTooSmall,
41 /// A USB transaction was attempted when all hardware resources were in use
42 ///
43 /// This error is returned from
44 /// [`HostController::try_alloc_interrupt_pipe()`] when all
45 /// interrupt-capable pipes are already in use. Users of
46 /// [`UsbBus::interrupt_endpoint_in()`](crate::usb_bus::UsbBus::interrupt_endpoint_in)
47 /// do not experience this error, as that call will wait for the
48 /// next pipe to be available.
49 AllPipesInUse,
50 /// The device has reacted in a way contrary to the expected protocol
51 ProtocolError,
52 /// The limit of attached USB devices has been reached
53 TooManyDevices,
54 /// [`UsbDevice::open_in_endpoint()`](crate::usb_bus::UsbDevice::open_in_endpoint) was called with a bogus endpoint number
55 NoSuchEndpoint,
56}
57
58/// Connection speed for a USB device
59#[cfg_attr(feature = "defmt", derive(defmt::Format))]
60#[cfg_attr(feature = "std", derive(Debug))]
61#[derive(Copy, Clone, PartialEq, Eq)]
62pub enum UsbSpeed {
63 /// USB 1.1 Low Speed (1.5Mbits/s)
64 Low1_5,
65 /// USB 1.1 Full Speed (12Mbits/s)
66 Full12,
67 /// USB 2.0 High Speed (480Mbits/s)
68 High480,
69}
70
71/// Events generated by hotplug/hot-unplug detection
72///
73/// See [`HostController::device_detect`]
74#[cfg_attr(feature = "defmt", derive(defmt::Format))]
75#[cfg_attr(feature = "std", derive(Debug))]
76#[derive(Copy, Clone, PartialEq, Eq)]
77pub enum DeviceStatus {
78 /// A device is connected (and negotiated a certain speed)
79 Present(UsbSpeed),
80 /// No device is connected
81 Absent,
82}
83
84/// Special treatment for a particular transfer
85#[cfg_attr(feature = "defmt", derive(defmt::Format))]
86#[cfg_attr(feature = "std", derive(Debug))]
87#[derive(Copy, Clone, PartialEq, Eq)]
88pub enum TransferExtras {
89 /// Normal transfer
90 Normal,
91 /// Low-speed transfer to a high-speed hub (USB 2.0 s8.6.5)
92 WithPreamble,
93}
94
95/// The data phase of a USB control-endpoint transaction
96///
97/// A transaction on a control endpoint involves one of:
98/// - IN (data transferred from device to host)
99/// - OUT (data transferred from host to device)
100/// - neither (the Setup packet contains all the relevant data)
101///
102/// See USB 2.0 section 8.5.3 and fiture 8-37.
103#[cfg_attr(feature = "std", derive(Debug))]
104#[derive(PartialEq, Eq)]
105pub enum DataPhase<'a> {
106 /// IN transaction (device-to-host)
107 In(&'a mut [u8]),
108 /// OUT transaction (host-to-device)
109 Out(&'a [u8]),
110 /// No data phase in control transaction
111 None,
112}
113
114impl DataPhase<'_> {
115 /// Is this DataPhase an IN variant?
116 pub fn is_in(&self) -> bool {
117 matches!(self, DataPhase::In(_))
118 }
119
120 /// Is this DataPhase an OUT variant?
121 pub fn is_out(&self) -> bool {
122 matches!(self, DataPhase::Out(_))
123 }
124
125 /// Is this DataPhase a no-data variant?
126 pub fn is_none(&self) -> bool {
127 matches!(self, DataPhase::None)
128 }
129
130 /// If this DataPhase is an IN variant, call the supplied function
131 /// on the received data
132 pub fn in_with<F: FnOnce(&mut [u8])>(&mut self, f: F) {
133 if let Self::In(x) = self {
134 f(x)
135 }
136 }
137}
138
139/// Is this a fixed-size transfer or variable-size transfer?
140///
141/// According to USB 2.0 s5.3.2, the host must behave differently in
142/// each case, so needs to know. (In particular, a fixed-size transfer
143/// doesn't have a zero-length packet even if the data fills an exact
144/// number of packets -- whereas a variable-size transfer does have a
145/// zero-length packet in that case.)
146#[cfg_attr(feature = "defmt", derive(defmt::Format))]
147#[cfg_attr(feature = "std", derive(Debug))]
148#[derive(Copy, Clone, PartialEq, Eq)]
149pub enum TransferType {
150 /// Both ends know (via other means) how long the transfer should be
151 FixedSize,
152 /// Open-ended transfer (the size given is a maximum)
153 VariableSize,
154}
155
156/// A packet as received on an interrupt IN endpoint
157pub struct InterruptPacket {
158 /// USB address (1-127) of device from which packet was received
159 pub address: u8,
160 /// Endpoint number on which packet was received
161 pub endpoint: u8,
162 /// Packet size (i.e., length of valid prefix of [`InterruptPacket::data`])
163 pub size: u8,
164 /// Packet contents
165 pub data: [u8; 64],
166}
167
168impl Default for InterruptPacket {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl InterruptPacket {
175 /// Construct a new InterruptPacket
176 ///
177 /// The address, endpoint, size and data should be filled-in before doing
178 /// anything with it.
179 pub const fn new() -> Self {
180 Self {
181 address: 0,
182 endpoint: 0,
183 size: 0,
184 data: [0u8; 64],
185 }
186 }
187}
188
189impl Deref for InterruptPacket {
190 type Target = [u8];
191
192 fn deref(&self) -> &Self::Target {
193 &self.data[0..(self.size as usize)]
194 }
195}
196
197/// Encapsulating a particular USB hardware host controller
198///
199/// This trait can be implemented for different USB hardware (e.g.,
200/// RP2040, Synopsys DWC, or XHCI) and allows the rest of the crate --
201/// particularly [`UsbBus`](crate::usb_bus::UsbBus) -- to be hardware-agnostic.
202pub trait HostController {
203 /// The concrete type returned by [`HostController::alloc_interrupt_pipe`]
204 type InterruptPipe: Stream<Item = InterruptPacket> + Unpin;
205 /// The concrete type returned by [`HostController::device_detect`]
206 type DeviceDetect: Stream<Item = DeviceStatus>;
207
208 /// Return a Stream of device-detect events
209 ///
210 /// This stream covers hot-plug and hot-unplug to/from the USB host
211 /// controller itself (i.e., not to/from downstream hubs).
212 fn device_detect(&self) -> Self::DeviceDetect;
213
214 /// Reset the root port of the USB controller (USB 2.0 section 11.5.1.5)
215 ///
216 /// TinyUSB does not appear to do this on RP2040, but it's surely
217 /// important, especially if VBUS can remain powered across a
218 /// reset of the RP2040 itself.
219 fn reset_root_port(&self, rst: bool);
220
221 /// Perform a USB control transfer
222 ///
223 /// A control-capable pipe is allocated for the duration of the
224 /// transaction, and de-allocated again at the end.
225 fn control_transfer(
226 &self,
227 address: u8,
228 transfer_extras: TransferExtras,
229 packet_size: u8,
230 setup: SetupPacket,
231 data_phase: DataPhase<'_>,
232 ) -> impl core::future::Future<Output = Result<usize, UsbError>>;
233
234 /// Perform a USB bulk in transfer
235 ///
236 /// A bulk-capable pipe is allocated for the duration of the
237 /// transaction, and de-allocated again at the end.
238 ///
239 /// The passed-in data_toggle must be correct for the current state
240 /// of the endpoint, and is updated for the endpoint state after the
241 /// transaction.
242 ///
243 /// NB No "transfer_extras": LS devices can't have bulk endpoints.
244 fn bulk_in_transfer(
245 &self,
246 address: u8,
247 endpoint: u8,
248 packet_size: u16,
249 data: &mut [u8],
250 transfer_type: TransferType,
251 data_toggle: &Cell<bool>,
252 ) -> impl core::future::Future<Output = Result<usize, UsbError>>;
253
254 /// Perform a USB bulk out transfer
255 ///
256 /// A bulk-capable pipe is allocated for the duration of the
257 /// transaction, and de-allocated again at the end.
258 ///
259 /// The passed-in data_toggle must be correct for the current state
260 /// of the endpoint, and is updated for the endpoint state after the
261 /// transaction.
262 ///
263 /// NB No "transfer_extras": LS devices can't have bulk endpoints.
264 fn bulk_out_transfer(
265 &self,
266 address: u8,
267 endpoint: u8,
268 packet_size: u16,
269 data: &[u8],
270 transfer_type: TransferType,
271 data_toggle: &Cell<bool>,
272 ) -> impl core::future::Future<Output = Result<usize, UsbError>>;
273
274 /// Allocate an interrupt pipe
275 ///
276 /// The pipe is owned by the returned object, and remains
277 /// allocated as long as the returned object exists.
278 ///
279 /// If no interrupt-capable pipes are available when the function is
280 /// called, it awaits for one to become available.
281 ///
282 /// The returned object implements a stream of [`InterruptPacket`] events.
283 fn alloc_interrupt_pipe(
284 &self,
285 address: u8,
286 transfer_extras: TransferExtras,
287 endpoint: u8,
288 max_packet_size: u16,
289 interval_ms: u8,
290 ) -> impl core::future::Future<Output = Self::InterruptPipe>;
291
292 /// Allocate an interrupt pipe
293 ///
294 /// The pipe is owned by the returned object, and remains
295 /// allocated as long as the returned object exists.
296 ///
297 /// If no interrupt-capable pipes are available when the function is
298 /// called, it immediately returns `Err(UsbError::AllPipesInUse)`.
299 ///
300 /// The returned object implements a stream of [`InterruptPacket`] events.
301 fn try_alloc_interrupt_pipe(
302 &self,
303 address: u8,
304 transfer_extras: TransferExtras,
305 endpoint: u8,
306 max_packet_size: u16,
307 interval_ms: u8,
308 ) -> Result<Self::InterruptPipe, UsbError>;
309}
310
311#[cfg(all(test, feature = "std"))]
312#[path = "tests/host_controller.rs"]
313pub(crate) mod tests;