imxrt_usbd/bus.rs
1//! USB bus implementation
2//!
3//! The bus
4//!
5//! - initializes the USB driver
6//! - adapts the USB driver to meet the `usb-device` `Sync` requirements
7//! - dispatches reads and writes to the proper endpoints
8//! - exposes the i.MX RT-specific API to the user (`configure`, `set_interrupts`)
9//!
10//! Most of the interesting behavior happens in the driver.
11
12use super::driver::Driver;
13use crate::gpt;
14use core::cell::RefCell;
15use cortex_m::interrupt::{self, Mutex};
16use usb_device::{
17 bus::{PollResult, UsbBus},
18 endpoint::{EndpointAddress, EndpointType},
19 UsbDirection,
20};
21
22pub use super::driver::Speed;
23
24/// A full- and high-speed `UsbBus` implementation
25///
26/// The `BusAdapter` adapts the USB peripheral instances, and exposes a `UsbBus` implementation.
27///
28/// # Requirements
29///
30/// The driver assumes that you've prepared all USB clocks (CCM clock gates, CCM analog PLLs).
31///
32/// Before polling for USB class traffic, you must call [`configure()`](BusAdapter::configure())
33/// *after* your device has been configured. This can be accomplished by polling the USB
34/// device and checking its state until it's been configured. Once configured, use `UsbDevice::bus()`
35/// to access the i.MX RT `BusAdapter`, and call `configure()`. You should only do this once.
36/// After that, you may poll for class traffic.
37///
38/// # Example
39///
40/// This example shows you how to create a `BusAdapter`, build a simple USB device, and
41/// prepare the device for class traffic.
42///
43/// Note that this example does not demonstrate USB class allocation or polling. See
44/// your USB class' documentation for details. This example also skips the clock initialization.
45///
46/// ```no_run
47/// use imxrt_usbd::BusAdapter;
48///
49/// # struct Ps;
50/// # unsafe impl imxrt_usbd::Peripherals for Ps { fn usb(&self) -> *const () { panic!() } fn usbphy(&self) -> *const () { panic!() }}
51/// static EP_MEMORY: imxrt_usbd::EndpointMemory<1024> = imxrt_usbd::EndpointMemory::new();
52/// static EP_STATE: imxrt_usbd::EndpointState = imxrt_usbd::EndpointState::max_endpoints();
53///
54/// // TODO initialize clocks...
55///
56/// let my_usb_peripherals = // Your Peripherals instance...
57/// # Ps;
58/// let bus_adapter = BusAdapter::new(
59/// my_usb_peripherals,
60/// &EP_MEMORY,
61/// &EP_STATE,
62/// );
63///
64/// // Create the USB device...
65/// use usb_device::prelude::*;
66/// let bus_allocator = usb_device::bus::UsbBusAllocator::new(bus_adapter);
67/// let mut device = UsbDeviceBuilder::new(&bus_allocator, UsbVidPid(0x5824, 0x27dd))
68/// .strings(&[StringDescriptors::default().product("imxrt-usbd")]).unwrap()
69/// // Other builder methods...
70/// .build();
71///
72/// // Poll until configured...
73/// loop {
74/// if device.poll(&mut []) {
75/// let state = device.state();
76/// if state == usb_device::device::UsbDeviceState::Configured {
77/// break;
78/// }
79/// }
80/// }
81///
82/// // Configure the bus
83/// device.bus().configure();
84///
85/// // Ready for class traffic!
86/// ```
87///
88/// # Design
89///
90/// This section talks about the driver design. It assumes that
91/// you're familiar with the details of the i.MX RT USB peripheral. If you
92/// just want to use the driver, you can skip this section.
93///
94/// ## Packets and transfers
95///
96/// All i.MX RT USB drivers manage queue heads (QH), and transfer
97/// descriptors (TD). For the driver, each (QH) is assigned
98/// only one (TD) to perform I/O. We then assume each TD describes a single
99/// packet. This is simple to implement, but it means that the
100/// driver can only have one packet in flight per endpoint. You're expected
101/// to quickly respond to `poll()` outputs, and schedule the next transfer
102/// in the time required for devices. This becomes more important as you
103/// increase driver speeds.
104///
105/// The hardware can zero-length terminate (ZLT) packets as needed if you
106/// call [`enable_zlt`](BusAdapter::enable_zlt). By default, this feature is
107/// off, because most `usb-device` classes / devices take care to send zero-length
108/// packets, and enabling this feature could interfere with the class / device
109/// behaviors.
110pub struct BusAdapter {
111 usb: Mutex<RefCell<Driver>>,
112 cs: Option<cortex_m::interrupt::CriticalSection>,
113}
114
115impl BusAdapter {
116 /// Create a high-speed USB bus adapter
117 ///
118 /// This is equivalent to [`BusAdapter::with_speed`] when supplying [`Speed::High`]. See
119 /// the `with_speed` documentation for more information.
120 ///
121 /// # Panics
122 ///
123 /// Panics if `buffer` or `state` has already been associated with another USB bus.
124 pub fn new<P: crate::Peripherals, const SIZE: usize, const EP_COUNT: usize>(
125 peripherals: P,
126 buffer: &'static crate::buffer::EndpointMemory<SIZE>,
127 state: &'static crate::state::EndpointState<EP_COUNT>,
128 ) -> Self {
129 Self::with_speed(peripherals, buffer, state, Speed::High)
130 }
131
132 /// Create a USB bus adapter with the given speed
133 ///
134 /// Specify [`Speed::LowFull`] to throttle the USB data rate.
135 ///
136 /// When this function returns, the `BusAdapter` has initialized the PHY and USB core peripherals.
137 /// The adapter expects to own these two peripherals, along with the other peripherals required
138 /// by the [`Peripherals`](crate::Peripherals) safety contract.
139 ///
140 /// You must also provide a region of memory that will used for endpoint I/O. The
141 /// memory region will be partitioned for the endpoints, based on their requirements.
142 ///
143 /// # Panics
144 ///
145 /// Panics if `buffer` or `state` has already been associated with another USB bus.
146 pub fn with_speed<P: crate::Peripherals, const SIZE: usize, const EP_COUNT: usize>(
147 peripherals: P,
148 buffer: &'static crate::buffer::EndpointMemory<SIZE>,
149 state: &'static crate::state::EndpointState<EP_COUNT>,
150 speed: Speed,
151 ) -> Self {
152 Self::init(peripherals, buffer, state, speed, None)
153 }
154
155 /// Create a USB bus adapter that never takes a critical section
156 ///
157 /// See [`BusAdapter::with_speed`] for general information.
158 ///
159 /// # Safety
160 ///
161 /// The returned object fakes its `Sync` safety. Specifically, the object
162 /// will not take critical sections in its `&[mut] self` methods to ensure safe
163 /// access. By using this object, you must manually hold the guarantees of
164 /// `Sync` without the compiler's help.
165 ///
166 /// # Panics
167 ///
168 /// Panics if `buffer` or `state` has already been associated with another USB bus.
169 pub unsafe fn without_critical_sections<
170 P: crate::Peripherals,
171 const SIZE: usize,
172 const EP_COUNT: usize,
173 >(
174 peripherals: P,
175 buffer: &'static crate::buffer::EndpointMemory<SIZE>,
176 state: &'static crate::state::EndpointState<EP_COUNT>,
177 speed: Speed,
178 ) -> Self {
179 Self::init(
180 peripherals,
181 buffer,
182 state,
183 speed,
184 // Safety: see the above API docs. Caller knows that we're faking our
185 // Sync capability.
186 Some(unsafe { cortex_m::interrupt::CriticalSection::new() }),
187 )
188 }
189
190 fn init<P: crate::Peripherals, const SIZE: usize, const EP_COUNT: usize>(
191 peripherals: P,
192 buffer: &'static crate::buffer::EndpointMemory<SIZE>,
193 state: &'static crate::state::EndpointState<EP_COUNT>,
194 speed: Speed,
195 cs: Option<cortex_m::interrupt::CriticalSection>,
196 ) -> Self {
197 let mut usb = Driver::new(peripherals, buffer, state);
198
199 usb.initialize(speed);
200
201 BusAdapter {
202 usb: Mutex::new(RefCell::new(usb)),
203 cs,
204 }
205 }
206 /// Enable (`true`) or disable (`false`) interrupts for this USB peripheral
207 ///
208 /// The interrupt causes are implementation specific. To handle the interrupt,
209 /// call [`poll()`](BusAdapter::poll).
210 pub fn set_interrupts(&self, interrupts: bool) {
211 self.with_usb_mut(|usb| usb.set_interrupts(interrupts));
212 }
213
214 /// Enable zero-length termination (ZLT) for the given endpoint
215 ///
216 /// When ZLT is enabled, software does not need to send a zero-length packet
217 /// to terminate a transfer where the number of bytes equals the max packet size.
218 /// The hardware will send this zero-length packet itself. By default, ZLT is off,
219 /// and software is expected to send these packets. Enable this if you're confident
220 /// that your (third-party) device / USB class isn't already sending these packets.
221 ///
222 /// This call does nothing if the endpoint isn't allocated.
223 pub fn enable_zlt(&self, ep_addr: EndpointAddress) {
224 self.with_usb_mut(|usb| usb.enable_zlt(ep_addr));
225 }
226
227 /// Immutable access to the USB peripheral
228 fn with_usb<R>(&self, func: impl FnOnce(&Driver) -> R) -> R {
229 let with_cs = |cs: &'_ _| {
230 let usb = self.usb.borrow(cs);
231 let usb = usb.borrow();
232 func(&usb)
233 };
234 if let Some(cs) = &self.cs {
235 with_cs(cs)
236 } else {
237 interrupt::free(with_cs)
238 }
239 }
240
241 /// Mutable access to the USB peripheral
242 fn with_usb_mut<R>(&self, func: impl FnOnce(&mut Driver) -> R) -> R {
243 let with_cs = |cs: &'_ _| {
244 let usb = self.usb.borrow(cs);
245 let mut usb = usb.borrow_mut();
246 func(&mut usb)
247 };
248 if let Some(cs) = &self.cs {
249 with_cs(cs)
250 } else {
251 interrupt::free(with_cs)
252 }
253 }
254
255 /// Apply device configurations, and perform other post-configuration actions
256 ///
257 /// You must invoke this once, and only after your device has been configured. If
258 /// the device is reset and reconfigured, you must invoke `configure()` again. See
259 /// the top-level example for how this could be achieved.
260 pub fn configure(&self) {
261 self.with_usb_mut(|usb| {
262 usb.on_configured();
263 debug!("CONFIGURED");
264 });
265 }
266
267 /// Acquire one of the GPT timer instances.
268 ///
269 /// `instance` identifies which GPT instance you're accessing.
270 /// This may take a critical section for the duration of `func`.
271 ///
272 /// # Panics
273 ///
274 /// Panics if the GPT instance is already borrowed. This could happen
275 /// if you call `gpt_mut` again within the `func` callback.
276 pub fn gpt_mut<R>(&self, instance: gpt::Instance, func: impl FnOnce(&mut gpt::Gpt) -> R) -> R {
277 self.with_usb_mut(|usb| usb.gpt_mut(instance, func))
278 }
279}
280
281impl UsbBus for BusAdapter {
282 /// The USB hardware can guarantee that we set the status before we receive
283 /// the status, and we're taking advantage of that. We expect this flag to
284 /// result in a call to set_address before the status happens. This means
285 /// that we can meet the timing requirements without help from software.
286 ///
287 /// It's not a quirk; it's a feature :)
288 const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = true;
289
290 fn alloc_ep(
291 &mut self,
292 ep_dir: UsbDirection,
293 ep_addr: Option<EndpointAddress>,
294 ep_type: EndpointType,
295 max_packet_size: u16,
296 _interval: u8,
297 ) -> usb_device::Result<EndpointAddress> {
298 self.with_usb_mut(|usb| {
299 if let Some(addr) = ep_addr {
300 if usb.is_allocated(addr) {
301 return Err(usb_device::UsbError::InvalidEndpoint);
302 }
303 let buffer = usb
304 .allocate_buffer(max_packet_size as usize)
305 .ok_or(usb_device::UsbError::EndpointMemoryOverflow)?;
306 usb.allocate_ep(addr, buffer, ep_type);
307 Ok(addr)
308 } else {
309 for idx in 1..8 {
310 let addr = EndpointAddress::from_parts(idx, ep_dir);
311 if usb.is_allocated(addr) {
312 continue;
313 }
314 let buffer = usb
315 .allocate_buffer(max_packet_size as usize)
316 .ok_or(usb_device::UsbError::EndpointMemoryOverflow)?;
317 usb.allocate_ep(addr, buffer, ep_type);
318 return Ok(addr);
319 }
320 Err(usb_device::UsbError::EndpointOverflow)
321 }
322 })
323 }
324
325 fn set_device_address(&self, addr: u8) {
326 self.with_usb_mut(|usb| {
327 usb.set_address(addr);
328 });
329 }
330
331 fn enable(&mut self) {
332 self.with_usb_mut(|usb| usb.attach());
333 }
334
335 fn reset(&self) {
336 self.with_usb_mut(|usb| {
337 usb.bus_reset();
338 });
339 }
340
341 fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> usb_device::Result<usize> {
342 self.with_usb_mut(|usb| {
343 if !usb.is_allocated(ep_addr) {
344 return Err(usb_device::UsbError::InvalidEndpoint);
345 }
346
347 // Keep map_err if warn! is compiled out.
348 #[allow(clippy::map_identity)]
349 let written = if ep_addr.index() == 0 {
350 usb.ctrl0_write(buf)
351 } else {
352 usb.ep_write(buf, ep_addr)
353 }
354 .map_err(|status| {
355 warn!(
356 "EP{=usize} {} STATUS {}",
357 ep_addr.index(),
358 ep_addr.direction(),
359 status
360 );
361 status
362 })?;
363
364 Ok(written)
365 })
366 }
367
368 fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> usb_device::Result<usize> {
369 self.with_usb_mut(|usb| {
370 if !usb.is_allocated(ep_addr) {
371 return Err(usb_device::UsbError::InvalidEndpoint);
372 }
373
374 // Keep map_err if warn! is compiled out.
375 #[allow(clippy::map_identity)]
376 let read = if ep_addr.index() == 0 {
377 usb.ctrl0_read(buf)
378 } else {
379 usb.ep_read(buf, ep_addr)
380 }
381 .map_err(|status| {
382 warn!(
383 "EP{=usize} {} STATUS {}",
384 ep_addr.index(),
385 ep_addr.direction(),
386 status
387 );
388 status
389 })?;
390
391 Ok(read)
392 })
393 }
394
395 fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) {
396 self.with_usb_mut(|usb| {
397 if usb.is_allocated(ep_addr) {
398 usb.ep_stall(stalled, ep_addr);
399 }
400 });
401 }
402
403 fn is_stalled(&self, ep_addr: EndpointAddress) -> bool {
404 self.with_usb(|usb| usb.is_ep_stalled(ep_addr))
405 }
406
407 fn suspend(&self) {
408 // TODO
409 }
410
411 fn resume(&self) {
412 // TODO
413 }
414
415 fn poll(&self) -> PollResult {
416 self.with_usb_mut(|usb| usb.poll())
417 }
418}