Skip to main content

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;