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
//! Prints panic information via a serial port, then goes into an infinite loop.
//!
//! Status: experimental; biased towards Arduino
//!
//! This crate implements a panic handler which prints panic information on a serial port (or other type of output - see below).
//!
//! ## Why?
//!
//! Seeing panic messages (or at least their location) is essential to make sense of what went wrong.
//!
//! I don't want to live without it.
//!
//! ## What is printed?
//!
//! There are three levels of detail at which panics can be printed, depending on how much space you are willing to waste in your firmware.
//! The level of detail is chosen by selecting **feature flags**:
//! - `location`: prints location information.
//!   Example:
//!   ```
//!   Panic at src/main.rs:91:9
//!   ```
//! - `message`: prints the actual full panic message. This uses `core::fmt` under the hood, so expect an increase in firmware size.
//!   Example:
//!   ```
//!   attempt to subtract with overflow
//!   ```
//! - `full` == `location` & `message`: Combined location and message.
//!    Example:
//!    ```
//!    Panic at src/main.rs:91:9: attempt to subtract with overflow
//!    ```
//! - (no features): if no features are chosen, a static message is printed.
//!    Example:
//!    ```
//!    PANIC !
//!    ```
//!    This option is easiest on firmware size.
//!
//! ## Usage
//!
//! An example project for Arduino Uno based on these instructions can be found here: <https://github.com/nilclass/panic-serial-example>.
//!
//! 1. Remove any existing panic handler. For example if you are currently using `panic_halt`, remove that dependency & it's usage.
//! 2. Add `panic-serial` dependency to your project:
//!    ```sh
//!    # Check "What is printed" section above for features to choose
//!    cargo add panic-serial --features full
//!    ```
//! 3. Within your `main.rs` (or elsewhere at top level) invoke the `impl_panic_handler` macro:
//!    ```
//!    panic_serial::impl_panic_handler!(
//!      // This is the type of the UART port to use for printing the message:
//!      arduino_hal::usart::Usart<
//!        arduino_hal::pac::USART0,
//!        arduino_hal::port::Pin<arduino_hal::port::mode::Input, arduino_hal::hal::port::PD0>,
//!        arduino_hal::port::Pin<arduino_hal::port::mode::Output, arduino_hal::hal::port::PD1>
//!      >
//!    );
//!    ```
//!   This will do two things:
//!   - define the actual panic handler
//!   - define a function called `share_serial_port_with_panic`, which we'll use in the next step
//! 4. Call `share_serial_port_with_panic` within `main`:
//!    ```
//!    #[arduino_hal::entry]
//!    fn main() -> ! {
//!      // ...
//!      let serial = arduino_hal::default_serial!(dp, pins, 57600);
//!      // this gives ownership of the serial port to panic-serial. We receive a mutable reference to it though, so we can keep using it.
//!      let serial = share_serial_port_with_panic(serial);
//!      // continue using serial:
//!      ufmt::uwriteln!(serial, "Hello there!\r").unwrap();
//!
//!      // ...
//!    }
//!    ```
//!
//! ## How does it work?
//!
//! The `impl_panic_handler` macro defines a mutable static `PANIC_PORT: Option<$your_type>`.
//! When you call `share_serial_port_with_panic`, that option gets filled, and you get back `PANIC_PORT.as_mut().unwrap()`.
//!
//! If a panic happens, the panic handler either just loops (if you never called `share_serial_port_with_panic`), or prints
//! the panic info to the given port.
//! It does this in two steps:
//! 1. call `port.flush()`
//! 2. use `ufmt` (or `core::fmt`) to print the fragments.
//!
//! Technically this works with *anything* that implements `ufmt::uWrite` and has a `flush()` method.
//!
//! ## How unsafe is this?
//!
//! When you find out, please tell me.
//!

#![no_std]
#![feature(panic_info_message)]

use ufmt::uWrite;
use core::panic::PanicInfo;
use core::fmt::Write;

struct WriteWrapper<'a, W: uWrite>(&'a mut W);

impl<'a, W: uWrite> Write for WriteWrapper<'a, W> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.0.write_str(s).map_err(|_| core::fmt::Error)
    }
}

/// Called internally by the panic handler.
pub fn _print_panic<W: uWrite>(w: &mut W, info: &PanicInfo) {
    let location_feature = cfg!(feature="location");
    let message_feature = cfg!(feature="message");

    if location_feature {
        if let Some(location) = info.location() {
            _ = ufmt::uwrite!(w, "Panic at {}:{}:{}", location.file(), location.line(), location.column());
            _ = w.write_str(if message_feature { ": " } else { "\r\n" });
        }
    }

    if message_feature {
        if let Some(message) = info.message() {
            _ = core::fmt::write(&mut WriteWrapper(w), *message);
            _ = w.write_str("\r\n");
        }
    }

    if !message_feature && !location_feature {
        _ = ufmt::uwriteln!(w, "PANIC !\r");
    }
}

/// Implements the panic handler. You need to call this for the package to work.
///
/// This macro defines the panic handler, as well as a function called `share_serial_port_with_panic`.
/// That function takes an argument of the given `$type` and returns a `&'static mut $type`.
///
#[macro_export]
macro_rules! impl_panic_handler {
    ($type:ty) => {
        static mut PANIC_PORT: Option<$type> = None;

        #[inline(never)]
        #[panic_handler]
        fn panic(info: &::core::panic::PanicInfo) -> ! {
            if let Some(panic_port) = unsafe { PANIC_PORT.as_mut() } {
                _ = panic_port.flush();
                ::panic_serial::_print_panic(panic_port, info);
            }
            loop {
                ::core::sync::atomic::compiler_fence(::core::sync::atomic::Ordering::SeqCst);
            }
        }

        pub fn share_serial_port_with_panic(port: $type) -> &'static mut $type {
            unsafe {
                PANIC_PORT = Some(port);
                PANIC_PORT.as_mut().unwrap()
            }
        }
    };
}