1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! defmt logging frontend.
//!
//! The module provides a [`defmt`](https://defmt.ferrous-systems.com/introduction.html)
//! implementation that transfers data using any supported backend.
//!
//! The implementation only supports one logging channel. The acquire-release sequence
//! on that channel uses a critical section to ensure mutual exclusion.

use bbqueue as bbq;
type Producer = bbq::Producer<'static, { crate::BUFFER_SIZE }>;

#[cfg(feature = "lpuart")]
use imxrt_hal::{dma::channel::Channel, lpuart::Lpuart};

/// The defmt frontend provides safe access to the producer
/// and encoder.
mod frontend {
    use super::Producer;
    use core::{
        mem::MaybeUninit,
        sync::atomic::{AtomicBool, Ordering},
    };
    use critical_section::RestoreState;

    /// The defmt global_logger is a static singleton. This differs
    /// from the log singleton, which is dynamically set at runtime.
    ///
    /// Since the singleton is static, it could be invoked before
    /// PRODUCER has been initialized. This INIT flag guards against
    /// that case.
    static INIT: AtomicBool = AtomicBool::new(false);
    /// The producer holds encoded defmt frames.
    ///
    /// Assume initialized when INIT is true.
    static mut PRODUCER: MaybeUninit<Producer> = MaybeUninit::uninit();

    /// The restore state for the critical section.
    static mut RESTORE_STATE: RestoreState = RestoreState::invalid();

    pub fn init(producer: Producer) {
        // Safety: module inspection shows that this function is only called once,
        // so we're not changing the pointed-to value while another accessor is
        // looking at this memory.
        unsafe { PRODUCER.write(producer) };
        INIT.store(true, Ordering::Release);
    }

    /// Try to acquire the logger, skipping everything if the logger isn't initialized.
    pub fn acquire<R>(func: impl FnOnce(&mut Producer, &mut defmt::Encoder) -> R) -> Option<R> {
        INIT.load(Ordering::Acquire).then(|| {
            // Safety: we're relying on the defmt::Logger user to meet some of these
            // critical section guarantees. We're also relying on the firmware developer
            // to either
            //
            // - build separate binaries for multi-core systems
            // - select a proper critical_section implementation for those multi-core systems
            //   that needed to share the logging queue.
            unsafe { RESTORE_STATE = critical_section::acquire() };

            // Safety:
            // - the memory is initialized because INIT is true.
            // - we have exclusive access due to acquire.
            func(unsafe { PRODUCER.assume_init_mut() }, encoder())
        })
    }

    /// Try to release the logger, skipping everything if the loger isn't initialized.
    pub fn release<R>(func: impl FnOnce(&mut Producer, &mut defmt::Encoder) -> R) -> Option<R> {
        INIT.load(Ordering::Acquire).then(|| {
            // Safety:
            // - the memory is initialized.
            // - the defmt user can only call this after calling
            //   acquire, so this is the only mutable access.
            let result = func(unsafe { PRODUCER.assume_init_mut() }, encoder());

            // Safety: we're relying on the defmt::Logger user to meet some critical
            // section guarantees. See the related safety comment on acquire.
            unsafe { critical_section::release(RESTORE_STATE) };
            result
        })
    }

    pub fn write<R>(func: impl FnOnce(&mut Producer, &mut defmt::Encoder) -> R) -> Option<R> {
        INIT.load(Ordering::Acquire).then(|| {
            // Safety:
            // - the memory is initialized because INIT is true.
            // - the defmt user can only call this after calling
            //   acquire, so there's only one mutable access.
            func(unsafe { PRODUCER.assume_init_mut() }, encoder())
        })
    }

    /// Access the defmt log frame encoder.
    fn encoder() -> &'static mut defmt::Encoder {
        static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
        // Safety: memory is statically initialized. Inspection of this
        // module shows that this is always accessed within the acquire-
        // release critical section, so there's only one access to this
        // mutable data.
        unsafe { &mut ENCODER }
    }
}

#[defmt::global_logger]
struct Logger;

unsafe impl defmt::Logger for Logger {
    fn acquire() {
        frontend::acquire(|producer, encoder| {
            encoder.start_frame(|buffer| {
                let _ = crate::try_write_producer(buffer, producer);
            });
        });
    }

    // In release and write, we enver check if there was a corresponding
    // acquire call. This means that, if the demft front-end was initialized
    // after an acquire but before a release / write (somehow?), then the
    // producer may receive incomplete frames (those not prefixed with any
    // start_frame encodings). The safety contracts for release and write
    // make this assumption safe.

    unsafe fn release() {
        frontend::release(|producer, encoder| {
            encoder.end_frame(|buffer| {
                let _ = crate::try_write_producer(buffer, producer);
            });
        });
    }

    unsafe fn write(bytes: &[u8]) {
        frontend::write(|producer, encoder| {
            encoder.write(bytes, |buffer| {
                let _ = crate::try_write_producer(buffer, producer);
            });
        });
    }

    unsafe fn flush() {
        // Nothing to do today.
    }
}

/// Initialize a USB logger with the `defmt` frontend and custom configurations.
///
/// See the crate-level documentation to understand how the USB device backend works.
#[cfg(feature = "usbd")]
pub fn usb_with_config<P: imxrt_usbd::Peripherals>(
    peripherals: P,
    interrupts: super::Interrupts,
    backend_config: &crate::UsbdConfig,
) -> Result<crate::Poller, crate::AlreadySetError<P>> {
    let (producer, consumer) = match crate::BUFFER.try_split() {
        Ok((prod, cons)) => (prod, cons),
        Err(_) => return Err(crate::AlreadySetError::new(peripherals)),
    };

    critical_section::with(|_| {
        frontend::init(producer);
        // Safety: BUFFER.try_split() guarantees that this is only called once.
        unsafe { crate::usbd::init(peripherals, interrupts, consumer, backend_config) };
        Ok(crate::Poller::new(crate::usbd::VTABLE))
    })
}

/// Initialize a USB logger with the `defmt` frontend.
///
/// This function uses default configurations for the backend.
/// See the crate-level documentation to understand how the USB device backend works.
#[cfg(feature = "usbd")]
pub fn usbd<P: imxrt_usbd::Peripherals>(
    peripherals: P,
    interrupts: super::Interrupts,
) -> Result<crate::Poller, crate::AlreadySetError<P>> {
    usb_with_config(
        peripherals,
        interrupts,
        &crate::UsbdConfigBuilder::new().build(),
    )
}

/// Initialize a LPUART & DMA logger with the `defmt` frontend.
///
/// See the crate-level documentation to understand how the LPUART backend works.
#[cfg(feature = "lpuart")]
pub fn lpuart<P, const LPUART: u8>(
    lpuart: Lpuart<P, LPUART>,
    dma_channel: Channel,
    interrupts: crate::Interrupts,
) -> Result<crate::Poller, crate::AlreadySetError<(Lpuart<P, LPUART>, Channel)>> {
    let (producer, consumer) = match crate::BUFFER.try_split() {
        Ok((prod, cons)) => (prod, cons),
        Err(_) => return Err(crate::AlreadySetError::new((lpuart, dma_channel))),
    };

    critical_section::with(|_| {
        frontend::init(producer);
        unsafe { crate::lpuart::init(lpuart, dma_channel, consumer, interrupts) };
        Ok(crate::Poller::new(crate::lpuart::VTABLE))
    })
}