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