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