imxrt_log/usbd.rs
1//! USB serial (CDC) backend using imxrt-usbd.
2
3use static_cell::StaticCell;
4use usb_device::device::UsbDeviceState;
5
6const VID_PID: usb_device::device::UsbVidPid = usb_device::device::UsbVidPid(0x5824, 0x27dd);
7const PRODUCT: &str = "imxrt-log";
8
9/// Provide some extra overhead for the interrupt endpoint.
10///
11/// If you start noticing panics, check to make sure that this buffer
12/// is large enough for all the max packet sizes for all the endpoints.
13const ENDPOINT_BYTES: usize = MAX_PACKET_SIZE * 2 + EP0_CONTROL_PACKET_SIZE * 2 + 128;
14static ENDPOINT_MEMORY: imxrt_usbd::EndpointMemory<ENDPOINT_BYTES> =
15 imxrt_usbd::EndpointMemory::new();
16static ENDPOINT_STATE: imxrt_usbd::EndpointState<6> = imxrt_usbd::EndpointState::new();
17
18type Bus = imxrt_usbd::BusAdapter;
19type BusAllocator = usb_device::bus::UsbBusAllocator<Bus>;
20type Class<'a> = usbd_serial::CdcAcmClass<'a, Bus>;
21type Device<'a> = usb_device::device::UsbDevice<'a, Bus>;
22
23/// High-speed bulk endpoint limit.
24const MAX_PACKET_SIZE: usize = crate::config::USB_BULK_MPS;
25/// Size for control transfers on endpoint 0.
26const EP0_CONTROL_PACKET_SIZE: usize = 64;
27/// The USB GPT timer we use to (infrequently) check for data.
28const GPT_INSTANCE: imxrt_usbd::gpt::Instance = imxrt_usbd::gpt::Instance::Gpt0;
29
30pub(crate) struct Backend {
31 class: Class<'static>,
32 device: Device<'static>,
33 consumer: crate::Consumer,
34 configured: bool,
35}
36
37impl Backend {
38 pub(crate) fn poll(&mut self) {
39 // Is there a CDC class event, like a completed transfer? If so, check
40 // the consumer immediately, even if a timer hasn't expired.
41 //
42 // Checking the consumer on class traffic lets the driver burst out data.
43 // Suppose the user wants to use the USB GPT timer, and they configure a very
44 // long interval. That interval expires, and we see tons of data in the consumer.
45 // We should write that out as fast as possible, even if the timer hasn't elapsed.
46 // That's the behavior provided by the class_event flag.
47 let class_event = self.device.poll(&mut [&mut self.class]);
48 let timer_event = self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| {
49 let mut elapsed = false;
50 while gpt.is_elapsed() {
51 gpt.clear_elapsed();
52 elapsed = true;
53 }
54 // Simulate a timer event if the timer is not running.
55 //
56 // If the timer is not running, its because the user disabled interrupts,
57 // and they're using their own timer / polling loop. There might not always
58 // be a class traffic (transfer complete) event when the user polls, so
59 // signaling true allows the poll to check the consumer for new data and
60 // send it.
61 //
62 // If the timer is running, checking the consumer depends on the elapsed
63 // timer.
64 elapsed || !gpt.is_running()
65 });
66 let check_consumer = class_event || timer_event;
67
68 if self.device.state() != UsbDeviceState::Configured {
69 if self.configured {
70 // Turn off the timer, but only if we were previously configured.
71 self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| gpt.stop());
72 }
73 self.configured = false;
74 // We can't use the class if we're not configured,
75 // so bail out here.
76 return;
77 }
78
79 // We're now configured. Are we newly configured?
80 if !self.configured {
81 // Must call this when we transition into configured.
82 self.device.bus().configure();
83 self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| {
84 // There's no need for a timer if interrupts are disabled.
85 // If the user disabled USB interrupts and decided to poll this
86 // from another timer, this USB timer could unnecessarily block
87 // that timer from checking the consumer queue.
88 if gpt.is_interrupt_enabled() {
89 gpt.run()
90 }
91 });
92 self.configured = true;
93 }
94
95 // If the host sends us data, pretend to read it.
96 // This prevents us from continuously NAKing the host,
97 // which the host might not appreciate.
98 self.class.read_packet(&mut []).ok();
99
100 // There's no need to wait if we were are newly configured.
101 if check_consumer && let Ok(grant) = self.consumer.read() {
102 let buf = grant.buf();
103 // Don't try to write more than we can fit in a single packet!
104 // See the usbd-serial documentation for this caveat. We didn't
105 // statically allocate enough space for anything larger.
106 if let Ok(written) = self
107 .class
108 .write_packet(&buf[..MAX_PACKET_SIZE.min(buf.len())])
109 {
110 grant.release(written);
111 // Log data is in the intermediate buffer, so it's OK to release the grant.
112 //
113 // If the I/O fails here, we'll try again on the next poll. There's no guarantee
114 // we'll see a improvement though...
115 }
116 } // else, no data, or some error. Let those logs accumulate!
117 }
118}
119
120/// Initialize the USB logger.
121///
122/// # Panics
123///
124/// Panics if called more than once.
125pub(crate) fn init<const N: u8>(
126 peripherals: imxrt_usbd::Instances<N>,
127 interrupts: crate::Interrupts,
128 consumer: super::Consumer,
129 config: &UsbdConfig,
130) -> &'static mut Backend {
131 static BACKEND: StaticCell<Backend> = StaticCell::new();
132 BACKEND.init_with(|| {
133 static BUS: StaticCell<BusAllocator> = StaticCell::new();
134 let bus = BUS.init_with(|| {
135 // Safety: we ensure that the bus, class, and all other related USB objects
136 // are accessed in poll(). poll() is not reentrant, so there's no racing
137 // occuring across executing contexts.
138 let bus = unsafe {
139 imxrt_usbd::BusAdapter::without_critical_sections(
140 peripherals,
141 &ENDPOINT_MEMORY,
142 &ENDPOINT_STATE,
143 crate::config::USB_SPEED,
144 )
145 };
146 bus.set_interrupts(interrupts == crate::Interrupts::Enabled);
147 bus.gpt_mut(GPT_INSTANCE, |gpt| {
148 gpt.stop();
149 gpt.clear_elapsed();
150 gpt.set_interrupt_enabled(interrupts == crate::Interrupts::Enabled);
151 gpt.set_mode(imxrt_usbd::gpt::Mode::Repeat);
152 gpt.set_load(config.poll_interval_us);
153 gpt.reset();
154 });
155 usb_device::bus::UsbBusAllocator::new(bus)
156 });
157 let class = usbd_serial::CdcAcmClass::new(bus, MAX_PACKET_SIZE as u16);
158
159 let device = usb_device::device::UsbDeviceBuilder::new(bus, VID_PID)
160 .strings(&[usb_device::device::StringDescriptors::default().product(PRODUCT)])
161 .unwrap()
162 .device_class(usbd_serial::USB_CLASS_CDC)
163 .max_packet_size_0(EP0_CONTROL_PACKET_SIZE as u8)
164 .unwrap()
165 .build();
166
167 // Not sure which endpoints the CDC ACM class will pick,
168 // so enable the setting for all non-zero endpoints.
169 for idx in 1..8 {
170 for dir in &[usb_device::UsbDirection::In, usb_device::UsbDirection::Out] {
171 let ep_addr = usb_device::endpoint::EndpointAddress::from_parts(idx, *dir);
172 // CDC class requires that we send the ZLP.
173 // Let the hardware do that for us.
174 device.bus().enable_zlt(ep_addr);
175 }
176 }
177
178 Backend {
179 class,
180 device,
181 consumer,
182 configured: false,
183 }
184 })
185}
186
187/// USB device configuration builder.
188///
189/// Use this to construct a [`UsbdConfig`], which provides settings
190/// to the USB device. For additional configurations that can only
191/// be safely expressed statically, see the package configuration
192/// documentation.
193///
194/// # Default values
195///
196/// The snippet below demonstrates the default values.
197///
198/// ```
199/// use imxrt_log::{UsbdConfigBuilder, UsbdConfig};
200///
201/// const DEFAULT_VALUES: UsbdConfig =
202/// UsbdConfigBuilder::new()
203/// .poll_interval_us(4_000)
204/// .build();
205///
206/// assert_eq!(DEFAULT_VALUES, UsbdConfigBuilder::new().build());
207/// ```
208#[derive(PartialEq, Eq, Debug)]
209#[cfg_attr(feature = "defmt", derive(defmt::Format))]
210pub struct UsbdConfigBuilder {
211 cfg: UsbdConfig,
212}
213
214impl Default for UsbdConfigBuilder {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl UsbdConfigBuilder {
221 /// Create a new builder with the default values.
222 pub const fn new() -> Self {
223 Self {
224 cfg: UsbdConfig::default(),
225 }
226 }
227
228 /// Build the USB device configuration.
229 pub const fn build(self) -> UsbdConfig {
230 self.cfg
231 }
232
233 /// Set the USB timer polling interval, in microseconds.
234 ///
235 /// This value has no effect if interrupts are disabled. See the USB device
236 /// backend documentation for more information.
237 ///
238 /// Note that the USB device driver internally clamps this value to 2^24.
239 pub const fn poll_interval_us(mut self, poll_interval_us: u32) -> Self {
240 self.cfg.poll_interval_us = poll_interval_us;
241 self
242 }
243}
244
245/// A USB device configuration.
246///
247/// Use [`UsbdConfigBuilder`] to build a configuration.
248#[derive(PartialEq, Eq, Debug)]
249#[cfg_attr(feature = "defmt", derive(defmt::Format))]
250pub struct UsbdConfig {
251 poll_interval_us: u32,
252}
253
254impl UsbdConfig {
255 /// Returns a configuration with the default values.
256 const fn default() -> Self {
257 Self {
258 poll_interval_us: 4_000,
259 }
260 }
261}