aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/logger.rs
blob: 9f9452e74d6b2e3e3983ae52b956b25a3cde1e4c (plain)
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
use crate::rtc;
use rb::{Reader, RingBuffer, Writer};

use core::cell::UnsafeCell;
use core::fmt::{self, Write};
use embedded_hal::{digital::v2::OutputPin, serial};
use log::{Metadata, Record};
use trinket_m0::{
    gpio::{Pa6, Pa7, PfD},
    sercom::{Sercom0Pad2, Sercom0Pad3, UART0},
};

static mut UART0: usize = 0;

struct JoinedRingBuffer<'a> {
    lbr: Reader<'a, u8>,
    lbw: Writer<'a, u8>,
}

impl<'a> JoinedRingBuffer<'a> {
    const fn new(rb: &'a RingBuffer<u8>) -> Self {
        let (lbr, lbw) = rb.split();
        Self { lbr: lbr, lbw: lbw }
    }
}

impl fmt::Write for JoinedRingBuffer<'_> {
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
        for b in s.bytes() {
            if let Err(_) = self.lbw.unshift(b) {
                // Ignore buffer full errors for logging.
                return Ok(());
            }
        }
        Ok(())
    }
}

static mut LB: RingBuffer<u8> = RingBuffer::<u8>::new(0);
static mut JRB: JoinedRingBuffer = unsafe { JoinedRingBuffer::new(&LB) };

// The UART isn't necessarily Sync, so wrap it in something that
// is. As long as flush() is only called from one thread, we're fine,
// but this is a guarantee that the logger module doesn't make.
pub struct WriteWrapper<W> {
    w: W,
}
impl<W> WriteWrapper<W>
where
    W: serial::Write<u8>,
{
    pub const fn new(writer: W) -> Self {
        Self { w: writer }
    }
}
unsafe impl<W> Sync for WriteWrapper<W> {}

pub struct SerialLogger<W, L> {
    writer: UnsafeCell<WriteWrapper<W>>,
    led: UnsafeCell<L>,
}

impl<W, L> SerialLogger<W, L>
where
    W: serial::Write<u8>,
    L: OutputPin + Send + Sync,
{
    pub fn new(writer: WriteWrapper<W>, led: L) -> Self {
        // Stash this for unsafe usage in case there's an issue with
        // the rest of the logging.
        unsafe { UART0 = core::mem::transmute(&writer.w) };
        Self {
            writer: UnsafeCell::new(writer),
            led: UnsafeCell::new(led),
        }
    }
}
unsafe impl<W, L> Send for SerialLogger<W, L> {}
unsafe impl<W, L> Sync for SerialLogger<W, L> {}

impl<W, L> log::Log for SerialLogger<W, L>
where
    W: serial::Write<u8>,
    L: OutputPin + Send + Sync,
{
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= log::max_level()
    }

    fn log(&self, record: &Record) {
        if !self.enabled(record.metadata()) {
            return;
        }

        let jrb = unsafe { &mut JRB };
        write!(
            jrb,
            "[{}] {} {} -- {}\r\n",
            rtc::millis(),
            record.level(),
            record.target(),
            record.args()
        )
        .ok();
    }

    fn flush(&self) {
        // Unsafe due to mutable static. We can only deal with the
        // tail position of the buffer here to keep things safe.
        let jrb = unsafe { &mut JRB };
        if jrb.lbr.is_empty() {
            return;
        }

        let led = unsafe { &mut (*self.led.get()) };
        let writer = unsafe { &mut (*self.writer.get()) };

        led.set_high().ok();
        while let Some(b) = jrb.lbr.shift() {
            nb::block!(writer.w.write(b)).ok();
        }
        led.set_low().ok();
    }
}

// Write to the UART right now, instead of putting it on a ring
// buffer. This function is a huge hack, and only useful for debugging
// either before the main loop starts or if the ring buffer is broken.
pub unsafe fn write_fmt_now(args: fmt::Arguments, nl: bool) {
    if UART0 == 0 {
        return;
    }
    let uart: &mut UART0<Sercom0Pad3<Pa7<PfD>>, Sercom0Pad2<Pa6<PfD>>, (), ()> =
        core::mem::transmute(UART0);
    fmt::write(uart, args).expect("writing fmt now to uart");
    if nl {
        uart.write_str("\r\n").expect("writing nl now to uart");
    }
}