RKTK API Docs RKTK Home Repo

rktk_drivers_common/
panic_utils.rs

1//! Panic message handling utilities.
2//!
3//! Because doing something complicated in panic_handler is difficult (eg. async function can't be called),
4//! alternatively you can reboot the device in panic_handler.
5//! By saving panic info in uninit section, you can display panic message on display after reboot.
6//!
7//! Note that this module depends on .uninit section, which is handled by cortex_m_rt.
8
9use core::{fmt::Write, mem::MaybeUninit, ptr::write_volatile};
10
11use embassy_time::Timer;
12use embedded_graphics::{
13    mono_font::{MonoFont, MonoTextStyle, ascii::FONT_8X13},
14    pixelcolor::BinaryColor,
15    prelude::*,
16    text::{Baseline, Text},
17};
18use rktk::{
19    drivers::interface::display::DisplayDriver,
20    task::display::utils::{RotatedDrawTarget, Rotation},
21};
22
23#[unsafe(link_section = ".uninit.PANICINFO")]
24static mut PANIC_INFO: MaybeUninit<PanicMessage> = MaybeUninit::uninit();
25const PANIC_INFO_MAGIC: u32 = 0x54_41_43_4B;
26
27#[repr(C)]
28pub struct PanicMessage {
29    magic: u32,
30    len: u16,
31    data: [u8; 1024],
32}
33
34impl Write for PanicMessage {
35    fn write_str(&mut self, s: &str) -> core::fmt::Result {
36        let writable_str = if s.len() + self.len as usize > self.data.len() {
37            let writable_len = self.data.len() - self.len as usize;
38            if writable_len == 0 {
39                return Ok(());
40            }
41            s.get(..writable_len).unwrap_or(s)
42        } else {
43            s
44        };
45
46        let new_len = writable_str.len() as u16 + self.len;
47        self.data[self.len as usize..new_len as usize].copy_from_slice(writable_str.as_bytes());
48        self.len = new_len;
49
50        Ok(())
51    }
52}
53
54impl Default for PanicMessage {
55    fn default() -> Self {
56        Self {
57            magic: PANIC_INFO_MAGIC,
58            len: 0,
59            data: [0; 1024],
60        }
61    }
62}
63
64impl PanicMessage {
65    fn reset() -> Self {
66        Self {
67            magic: 0,
68            len: 0,
69            data: [0; 1024],
70        }
71    }
72}
73
74/// Save panic info to uninit section.
75///
76/// This function should be called in panic_handler.
77pub fn save_panic_info(info: &core::panic::PanicInfo) {
78    let mut panic_info = PanicMessage::default();
79    write!(panic_info, "{}", info).ok();
80
81    unsafe {
82        write_volatile(&raw mut PANIC_INFO, MaybeUninit::new(panic_info));
83    }
84}
85
86fn read_panic_message() -> Option<PanicMessage> {
87    unsafe {
88        let info = core::ptr::read(&raw const PANIC_INFO);
89        let info = info.assume_init();
90        if info.magic == PANIC_INFO_MAGIC {
91            write_volatile(&raw mut PANIC_INFO, MaybeUninit::new(PanicMessage::reset()));
92            Some(info)
93        } else {
94            None
95        }
96    }
97}
98
99fn parse_panic_message(panic_info: &PanicMessage) -> &str {
100    // if panic_info.len == 0 {
101    //     return "No panic message";
102    // }
103    match core::str::from_utf8(&panic_info.data[..panic_info.len as usize]) {
104        Ok(str) => str,
105        Err(e) => {
106            let valid_len = e.valid_up_to();
107            core::str::from_utf8(&panic_info.data[..valid_len]).unwrap()
108        }
109    }
110}
111
112const FONT: MonoFont = FONT_8X13;
113
114/// Display panic message on display is exists.
115/// This is intended to be called before [`rktk::task::start`] and takes a display builder.
116///
117/// If previous panic message exists, this function will display it on the display and return None.
118/// Otherwise, it will return the display builder.
119///
120/// When None is returned, caller can stop execution using something like [`cortex_m::asm::udf`]
121pub async fn display_message_if_panicked<D: DisplayDriver>(display: &mut D) {
122    if let Some(panic_info) = read_panic_message() {
123        if display.init().await.is_ok() {
124            let char_width = FONT.character_size.width as usize;
125
126            let str = parse_panic_message(&panic_info);
127
128            rktk_log::error!("Previous panic detected: {:?}", str);
129
130            let str_len = str
131                .lines()
132                .map(|line| line.chars().count())
133                .max()
134                .unwrap_or(0) as usize;
135
136            let orig_display_size = display.as_mut().bounding_box().size;
137            let rotation = if orig_display_size.width > orig_display_size.height {
138                Rotation::Rotate0
139            } else {
140                Rotation::Rotate90
141            };
142
143            let rotated_display = RotatedDrawTarget::new(display.as_mut(), rotation);
144            let display_width = rotated_display.bounding_box().size.width as usize;
145            let overflow_len = if str_len * char_width > display_width {
146                str_len - display_width / char_width + 1
147            } else {
148                0
149            };
150
151            display_mes(display, "Panic!", Point::zero(), rotation).await;
152            Timer::after_millis(400).await;
153
154            if overflow_len > 0 {
155                loop {
156                    for i in 0..=overflow_len {
157                        display_mes(
158                            display,
159                            str,
160                            Point::new(-((i * char_width) as i32), 0),
161                            rotation,
162                        )
163                        .await;
164                        Timer::after_millis(200).await;
165                    }
166                }
167            } else {
168                display_mes(display, str, Point::zero(), rotation).await;
169                Timer::after_secs(100000000).await;
170            }
171        }
172    }
173}
174pub async fn display_mes<D: DisplayDriver>(
175    display: &mut D,
176    str: &str,
177    pos: Point,
178    rotation: Rotation,
179) {
180    {
181        let mut rotated_display = RotatedDrawTarget::new(display.as_mut(), rotation);
182
183        let _ = rotated_display.clear(BinaryColor::Off);
184        let _ = Text::with_baseline(
185            str,
186            pos,
187            MonoTextStyle::new(&FONT, BinaryColor::On),
188            Baseline::Top,
189        )
190        .draw(&mut rotated_display);
191    }
192    let _ = display.flush().await;
193}