diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 8620d26..17e7057 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -1,109 +1,30 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] -#[no_mangle] -pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize { - let mut y: usize; - let mut n: usize = 32; - y = x >> 16; - if y != 0 { - n = n - 16; - x = y; - } - y = x >> 8; - if y != 0 { - n = n - 8; - x = y; - } - y = x >> 4; - if y != 0 { - n = n - 4; - x = y; - } - y = x >> 2; - if y != 0 { - n = n - 2; - x = y; - } - y = x >> 1; - if y != 0 { - n - 2 - } else { - n - x - } -} +use gba::{ + io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + video::Mode3, + Color, +}; #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - unsafe { - const DEBUG_ENABLE_MGBA: *mut u16 = 0x4fff780 as *mut u16; - const DEBUG_OUTPUT_BASE: *mut u8 = 0x4fff600 as *mut u8; - const DEBUG_SEND_MGBA: *mut u16 = 0x4fff700 as *mut u16; - const DEBUG_SEND_FLAG: u16 = 0x100; - const DEBUG_FATAL: u16 = 0; - const DEBUG_ERROR: u16 = 1; - DEBUG_ENABLE_MGBA.write_volatile(0xC0DE); - if DEBUG_ENABLE_MGBA.read_volatile() == 0x1DEA { - // Give the location - if let Some(location) = info.location() { - let mut out_ptr = DEBUG_OUTPUT_BASE; - let line = location.line(); - let mut line_bytes = [ - (line / 10000) as u8, - ((line / 1000) % 10) as u8, - ((line / 1000) % 10) as u8, - ((line / 10) % 10) as u8, - (line % 10) as u8, - ]; - for line_bytes_mut in line_bytes.iter_mut() { - *line_bytes_mut += b'0'; - } - for b in "Panic: " - .bytes() - .chain(location.file().bytes()) - .chain(", Line ".bytes()) - .chain(line_bytes.iter().cloned()) - .take(255) - { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } else { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in "Panic with no location info:".bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); - // Give the payload - if let Some(payload) = info.payload().downcast_ref::<&str>() { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in payload.bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } else { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in "no payload".bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_FATAL); - } + use core::fmt::Write; + use gba::mgba::{MGBADebug, MGBADebugLevel}; + if let Some(mut mgba) = MGBADebug::new() { + let _ = write!(mgba, "{}", info); + mgba.send(MGBADebugLevel::Fatal); } loop {} } #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - (0x400_0000 as *mut u16).write_volatile(0x0403); - (0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); - (0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); - (0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); - panic!("fumoffu!"); - } + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + DISPCNT.write(SETTING); + Mode3::write_pixel(120, 80, Color::from_rgb(31, 0, 0)); + Mode3::write_pixel(136, 80, Color::from_rgb(0, 31, 0)); + Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31)); + panic!("fumoffu!"); } diff --git a/src/lib.rs b/src/lib.rs index 35d464c..193139f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ pub mod base; pub(crate) use self::base::*; pub mod bios; pub mod io; - +pub mod mgba; pub mod video; newtype! { diff --git a/src/mgba.rs b/src/mgba.rs new file mode 100644 index 0000000..24e6648 --- /dev/null +++ b/src/mgba.rs @@ -0,0 +1,79 @@ +//! Special utils for if you're running on the mGBA emulator. + +use super::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +#[allow(missing_docs)] +pub enum MGBADebugLevel { + Fatal = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, +} + +/// Allows writing to the `mGBA` debug output. +#[derive(Debug, PartialEq, Eq)] +pub struct MGBADebug { + bytes_written: u8, +} +impl MGBADebug { + const ENABLE_ADDRESS: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff780) }; + const ENABLE_ADDRESS_INPUT: u16 = 0xC0DE; + const ENABLE_ADDRESS_OUTPUT: u16 = 0x1DEA; + + const OUTPUT_BASE: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff600) }; + + const SEND_ADDRESS: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff700) }; + const SEND_FLAG: u16 = 0x100; + + /// Gives a new MGBADebug, if running within `mGBA` + /// + /// # Fails + /// + /// If you're not running in the `mGBA` emulator. + pub fn new() -> Option { + Self::ENABLE_ADDRESS.write(Self::ENABLE_ADDRESS_INPUT); + if Self::ENABLE_ADDRESS.read() == Self::ENABLE_ADDRESS_OUTPUT { + Some(MGBADebug { bytes_written: 0 }) + } else { + None + } + } + + /// Once output is buffered you must send it out with a level. + /// + /// If the `Fatal` level is selected, the buffer is sent out as `Error` + /// followed by a blank message being sent as `Error`. This is done because + /// the `Fatal` message appears in a popup without showing up in the log, so + /// it might accidentally be discarded. + pub fn send(&mut self, level: MGBADebugLevel) { + if level == MGBADebugLevel::Fatal { + Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Error as u16); + Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Fatal as u16); + } else { + Self::SEND_ADDRESS.write(Self::SEND_FLAG | level as u16); + } + } +} + +impl core::fmt::Write for MGBADebug { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + unsafe { + let mut current = Self::OUTPUT_BASE.offset(self.bytes_written as isize); + let mut str_iter = s.bytes(); + while self.bytes_written < 255 { + match str_iter.next() { + Some(byte) => { + current.write(byte); + current = current.offset(1); + self.bytes_written += 1; + } + None => return Ok(()), + } + } + Err(core::fmt::Error) + } + } +}