xous-core/services/graphics-server/src/panic.rs

236 lines
No EOL
10 KiB
Rust

use std::thread;
use core::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use utralib::generated::*;
const PANIC_STD_SERVER: &[u8; 16] = b"panic-to-screen!";
/// We can have no allocations inside this, and ideally, it's as minimal as possible.
///
/// The graphics handler code and font renderer code are duplicated into this module to
/// minimize cross-thread dependencies.
///
/// Some simplifying assumptions include:
/// - Only Latin, monospace (16-px wide) characters from the `mono` font set.
/// - Panics occupy a fixed area in the center of the screen, that can only hold
/// a limited amount of text: 304-px x 384-px text area = 40 char x 24 lines = 960 chars
/// - The Panic frame is slightly larger to give a cosmetic border
/// - Panics are black background with white text to distinguish them as secured system messages.
/// - Panics can't be dismissed, and should persist even if other threads are capable of running.
/// - The Panic handler can conflict with the regular display routines because it unsafely
/// creates a copy of all the hardware access structures. Thus there is a variable
/// shared with the parent thread to stop redraws permanently in the case of a panic.
///
/// Note that the frame buffer is 336 px wide, which is 10.5 32-bit words.
/// The excess 16 bits are the dirty bit field.
use crate::{
backend::{FB_WIDTH_PIXELS, FB_WIDTH_WORDS, FB_SIZE},
api::{PixelColor, GlyphSprite}, blitstr2::NULL_GLYPH_SPRITE
};
/// How far down the screen the panic box draws
const TOP_OFFSET: usize = 48;
/// Width and height of the panic box in characters
const WIDTH_CHARS: usize = 40;
const HEIGHT_CHARS: usize = 24;
/// these are fixed by the monospace font
const GLYPH_HEIGHT: usize = 15;
const GLYPH_WIDTH: usize = 7;
/// this can be adjusted to create more border around the panic box
const TEXT_MARGIN: usize = 8;
/// some derived constants to help with layout
const BOTTOM_LINE: usize = TOP_OFFSET + HEIGHT_CHARS * GLYPH_HEIGHT + TEXT_MARGIN * 2;
const LEFT_EDGE: usize = (FB_WIDTH_PIXELS - (WIDTH_CHARS * GLYPH_WIDTH + TEXT_MARGIN * 2)) / 2; // 24
const RIGHT_EDGE: usize = FB_WIDTH_PIXELS - LEFT_EDGE; // 312
pub(crate) fn panic_handler_thread(
is_panic: Arc::<AtomicBool>,
hwfb: u32,
control: u32,
) {
thread::spawn({
move || {
let panic_server = xous::create_server_with_address(PANIC_STD_SERVER)
.expect("Couldn't create panic server");
let mut display = unsafe { PanicDisplay::new(hwfb, control) };
loop {
let msg = xous::receive_message(panic_server).unwrap();
// this will put the graphics renderer in panic mode.
if !is_panic.load(Ordering::Relaxed) {
// this locks out updates from the main loop
is_panic.store(true, Ordering::Relaxed);
display.panic_rectangle();
display.append_string(" ~~Guru Meditation~~\n\n");
}
let body = match msg.body.memory_message() {
Some(body) => body,
None => {
log::error!("Incorrect message type to panic renderer");
return;
}
};
let len = match body.valid {
Some(v) => v,
None => continue, // ignore, don't fail
}.get();
let s = unsafe{core::str::from_utf8_unchecked(
&body.buf.as_slice()[..len]
)};
display.append_string(s);
display.update_dirty();
}
}
});
}
/// All-in-one object to manage a framebuffer and layout text.
struct PanicDisplay<'a> {
/// hardware framebuffer (no double buffering)
fb: &'a mut [u32],
/// hardware register copy (very dangerous)
csr: CSR::<u32>,
/// current x/y position of the latest character to add to the panic box
x: usize,
y: usize,
}
impl<'a> PanicDisplay<'a> {
/// # Safety
///
/// This function takes "plain old 32-bit numbers" and transforms them
/// into hardware pointers to the frame buffer and CSR offsets. It's meant
/// to be pared with the `hw_regs` output from the XousDisplay object, and
/// there will be frame buffer conflicts unless some kind of mutex is wrapped
/// around any other code that might compete for access to these resources.
unsafe fn new(raw_fb: u32, control: u32) -> Self {
PanicDisplay {
fb: core::slice::from_raw_parts_mut(raw_fb as *mut u32, FB_SIZE),
csr: CSR::new(control as *mut u32),
// initialize to top left of panic box's margin
x: TEXT_MARGIN,
y: TEXT_MARGIN,
}
}
/// commits the data in the framebuffer to the screen
fn update_dirty(&mut self) {
self.csr.wfo(utra::memlcd::COMMAND_UPDATEDIRTY, 1);
while self.busy() {}
}
/// indicates if a commit is in progress
fn busy(&self) -> bool {
self.csr.rf(utra::memlcd::BUSY_BUSY) == 1
}
fn put_pixel(&mut self, x: usize, y: usize, color: PixelColor) {
if color == PixelColor::Light {
self.fb[(x + y * FB_WIDTH_WORDS * 32) / 32] |= 1 << (x % 32)
} else {
self.fb[(x + y * FB_WIDTH_WORDS * 32) / 32] &= !(1 << (x % 32))
}
// set the dirty bit on the line that contains the pixel
self.fb[y * FB_WIDTH_WORDS + (FB_WIDTH_WORDS - 1)] |= 0x1_0000;
}
/// draw a black rectangle into which all the characters are placed. This could
/// probably be optimized to do its work in words instead of as pixels, but it works.
fn panic_rectangle(&mut self) {
for y in TOP_OFFSET..BOTTOM_LINE {
for x in LEFT_EDGE..RIGHT_EDGE {
self.put_pixel(x, y, PixelColor::Dark);
}
}
}
/// Blit a glyph
/// Examples of word alignment for destination frame buffer:
/// 1. Fits in word: xr:1..7 => (data[0].bit_30)->(data[0].bit_26), mask:0x7c00_0000
/// 2. Spans words: xr:30..36 => (data[0].bit_01)->(data[1].bit_29), mask:[0x0000_0003,0xe000_000]
///
/// This is copied out of the blitstr2 module and adapted for the panic handler.
///
fn draw_glyph(&mut self, x: usize, y: usize, gs: GlyphSprite) {
const SPRITE_PX: i16 = 16;
const SPRITE_WORDS: i16 = 8;
if gs.glyph.len() < SPRITE_WORDS as usize {
// Fail silently if the glyph slice was too small
// TODO: Maybe return an error? Not sure which way is better.
return;
}
let high = gs.high as i16;
let wide = gs.wide as i16;
if high > SPRITE_PX || wide > SPRITE_PX {
// Fail silently if glyph height or width is out of spec
// TODO: Maybe return an error?
return;
}
// Calculate word alignment for destination buffer
let x0 = (x + LEFT_EDGE) as i16;
let x1 = (x + LEFT_EDGE) as i16 + wide - 1;
let dest_low_word = x0 >> 5;
let dest_high_word = x1 >> 5;
let px_in_dest_low_word = 32 - (x0 & 0x1f);
// Blit it (use glyph height to avoid blitting empty rows)
let mut row_base = (y + TOP_OFFSET) as i16 * FB_WIDTH_WORDS as i16;
let row_upper_limit = BOTTOM_LINE as i16 * FB_WIDTH_WORDS as i16;
let row_lower_limit = TOP_OFFSET as i16 * FB_WIDTH_WORDS as i16;
let glyph = gs.glyph;
for y in 0..high as usize {
if row_base >= row_upper_limit {
// Clip anything that would run off the end of the frame buffer
break;
}
if row_base >= row_lower_limit {
// Unpack pixels for this glyph row.
// CAUTION: some math magic happening here...
// when y==0, this does (glyph[0] >> 0) & mask,
// when y==1, this does (glyph[0] >> 16) & mask,
// when y==2, this does (glyph[1] >> 0) & mask,
// ...
let mask = 0x0000ffff as u32;
let shift = (y as u32 & 1) << 4;
let pattern = (glyph[y >> 1] >> shift) & mask;
// compute partial masks to prevent glyphs from "spilling over" the clip rectangle
let mut partial_mask_lo = 0xffff_ffff;
let mut partial_mask_hi = 0xffff_ffff;
if x0 < LEFT_EDGE as i16 || x1 >= RIGHT_EDGE as i16 {
let x0a = if x0 < LEFT_EDGE as i16 { RIGHT_EDGE as i16 } else { x0 };
let x1a = if x1 >= LEFT_EDGE as i16 { RIGHT_EDGE as i16 } else { x1 };
let mut ones = (1u64 << ((x1a - x0a) as u64 + 1)) - 1;
ones <<= x0a as u64 & 0x1f;
partial_mask_lo = ones as u32;
partial_mask_hi = (ones >> 32) as u32;
}
// XOR glyph pixels onto destination buffer. Note that despite the masks above, we will not render
// partial glyphs that cross the absolute bounds of the left and right edge of the screen.
if x0 >= 0 && x1 < FB_WIDTH_PIXELS as i16 {
self.fb[(row_base + dest_low_word) as usize] ^= (pattern << (32 - px_in_dest_low_word)) & partial_mask_lo;
if wide > px_in_dest_low_word {
self.fb[(row_base + dest_high_word) as usize] ^= (pattern >> px_in_dest_low_word) & partial_mask_hi;
}
}
self.fb[(row_base as usize + FB_WIDTH_WORDS - 1) as usize] |= 0x1_0000; // set the dirty bit on the line
}
// Advance destination offset using + instead of * to maybe save some CPU cycles
row_base += FB_WIDTH_WORDS as i16;
}
}
/// start laying out the string from the top left and just wrap character by character, considering newlines
fn append_string(&mut self, s: &str) {
for ch in s.chars() {
if ch == '\n' {
self.y += GLYPH_HEIGHT;
self.x = TEXT_MARGIN;
continue;
}
self.draw_glyph(self.x, self.y, crate::blitstr2::mono_glyph(ch).unwrap_or(NULL_GLYPH_SPRITE));
self.x += GLYPH_WIDTH;
if self.x >= WIDTH_CHARS * GLYPH_WIDTH {
self.x = TEXT_MARGIN;
self.y += GLYPH_HEIGHT;
}
}
}
}