Skip to main content

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}