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}