makepython_nrf52840/
io.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5use core::fmt::Write;
6use core::panic::PanicInfo;
7use core::ptr::{addr_of, addr_of_mut};
8
9use kernel::debug;
10use kernel::debug::IoWrite;
11use kernel::hil::led;
12use kernel::hil::uart::{self};
13use kernel::ErrorCode;
14use nrf52840::gpio::Pin;
15
16use kernel::hil::uart::Transmit;
17use kernel::utilities::cells::VolatileCell;
18
19struct Writer {
20    initialized: bool,
21}
22
23static mut WRITER: Writer = Writer { initialized: false };
24
25impl Write for Writer {
26    fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
27        self.write(s.as_bytes());
28        Ok(())
29    }
30}
31
32const BUF_LEN: usize = 512;
33static mut STATIC_PANIC_BUF: [u8; BUF_LEN] = [0; BUF_LEN];
34
35static mut DUMMY: DummyUsbClient = DummyUsbClient {
36    fired: VolatileCell::new(false),
37};
38
39struct DummyUsbClient {
40    fired: VolatileCell<bool>,
41}
42
43impl uart::TransmitClient for DummyUsbClient {
44    fn transmitted_buffer(&self, _: &'static mut [u8], _: usize, _: Result<(), ErrorCode>) {
45        self.fired.set(true);
46    }
47}
48
49impl IoWrite for Writer {
50    fn write(&mut self, buf: &[u8]) -> usize {
51        if !self.initialized {
52            self.initialized = true;
53        }
54        // Here we mimic a synchronous UART output by calling transmit_buffer
55        // on the CDC stack and then spinning on USB interrupts until the transaction
56        // is complete. If the USB or CDC stack panicked, this may fail. It will also
57        // fail if the panic occurred prior to the USB connection being initialized.
58        // In the latter case, the LEDs should still blink in the panic pattern.
59
60        // spin so that if any USB DMA is ongoing it will finish
61        // we should only need this on the first call to write()
62        let mut i = 0;
63        loop {
64            i += 1;
65            cortexm4::support::nop();
66            if i > 10000 {
67                break;
68            }
69        }
70
71        // copy_from_slice() requires equal length slices
72        // This will truncate any writes longer than BUF_LEN, but simplifies the
73        // code. In practice, BUF_LEN=512 always seems sufficient for the size of
74        // individual calls to write made by the panic handler.
75        let mut max = BUF_LEN;
76        if buf.len() < BUF_LEN {
77            max = buf.len();
78        }
79
80        unsafe {
81            // If CDC_REF_FOR_PANIC is not yet set we panicked very early,
82            // and not much we can do. Don't want to double fault,
83            // so just return.
84            super::CDC_REF_FOR_PANIC.map(|cdc| {
85                // Lots of unsafe dereferencing of global static mut objects here.
86                // However, this should be okay, because it all happens within
87                // a single thread, and:
88                // - This is the only place the global CDC_REF_FOR_PANIC is used, the logic is the same
89                //   as applies for the global CHIP variable used in the panic handler.
90                // - We do create multiple mutable references to the STATIC_PANIC_BUF, but we never
91                //   access the STATIC_PANIC_BUF after a slice of it is passed to transmit_buffer
92                //   until the slice has been returned in the uart callback.
93                // - Similarly, only this function uses the global DUMMY variable, and we do not
94                //   mutate it.
95                let usb = &mut cdc.controller();
96                STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]);
97                let static_buf = &mut *addr_of_mut!(STATIC_PANIC_BUF);
98                cdc.set_transmit_client(&*addr_of!(DUMMY));
99                let _ = cdc.transmit_buffer(static_buf, max);
100                loop {
101                    if let Some(interrupt) = cortexm4::nvic::next_pending() {
102                        if interrupt == 39 {
103                            usb.handle_interrupt();
104                        }
105                        let n = cortexm4::nvic::Nvic::new(interrupt);
106                        n.clear_pending();
107                        n.enable();
108                    }
109                    if (*addr_of!(DUMMY)).fired.get() {
110                        // buffer finished transmitting, return so we can output additional
111                        // messages when requested by the panic handler.
112                        break;
113                    }
114                }
115                (*addr_of!(DUMMY)).fired.set(false);
116            });
117        }
118        buf.len()
119    }
120}
121
122/// We just use the standard default provided by the debug module in the kernel.
123#[cfg(not(test))]
124#[panic_handler]
125pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! {
126    let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P1_10);
127    let led = &mut led::LedLow::new(led_kernel_pin);
128    let writer = &mut *addr_of_mut!(WRITER);
129    debug::panic(
130        &mut [led],
131        writer,
132        pi,
133        &cortexm4::support::nop,
134        crate::PANIC_RESOURCES.get(),
135    )
136}