make the BIOS test safe

This commit is contained in:
Lokathor 2018-12-25 14:46:08 -07:00
parent ecc7ea940d
commit 08ff34ae43

View file

@ -8,6 +8,8 @@
//! whatever value is necessary for that function). Some functions also perform //! whatever value is necessary for that function). Some functions also perform
//! necessary checks to save you from yourself, such as not dividing by zero. //! necessary checks to save you from yourself, such as not dividing by zero.
use super::register_bit;
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The //TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
//functions that never return must panic, the functions that return nothing //functions that never return must panic, the functions that return nothing
//should just do so, and the math functions should just return the correct math //should just do so, and the math functions should just return the correct math
@ -49,13 +51,17 @@
/// perform UB, but such a scenario might exist. /// perform UB, but such a scenario might exist.
#[inline(always)] #[inline(always)]
pub unsafe fn soft_reset() -> ! { pub unsafe fn soft_reset() -> ! {
asm!(/* ASM */ "swi 0x00" if cfg!(test) {
:/* OUT */ // none panic!("Attempted soft reset during testing");
:/* INP */ // none } else {
:/* CLO */ // none asm!(/* ASM */ "swi 0x00"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
core::hint::unreachable_unchecked() :/* CLO */ // none
:/* OPT */ "volatile"
);
core::hint::unreachable_unchecked()
}
} }
/// (`swi 0x01`) RegisterRamReset. /// (`swi 0x01`) RegisterRamReset.
@ -84,15 +90,34 @@ pub unsafe fn soft_reset() -> ! {
/// memory, except in the case that you were executing out of EWRAM and clear /// memory, except in the case that you were executing out of EWRAM and clear
/// that. If you do then you return to nothing and have a bad time. /// that. If you do then you return to nothing and have a bad time.
#[inline(always)] #[inline(always)]
pub unsafe fn register_ram_reset(flags: u8) { pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) {
asm!(/* ASM */ "swi 0x01" if cfg!(test) {
:/* OUT */ // none // do nothing in test mode
:/* INP */ "{r0}"(flags) } else {
:/* CLO */ // none asm!(/* ASM */ "swi 0x01"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(flags.0)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
}
newtype! {
/// Flags for use with `register_ram_reset`.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
RegisterRAMResetFlags, u8
}
#[allow(missing_docs)]
impl RegisterRAMResetFlags {
register_bit!(EWRAM, u8, 0, ewram);
register_bit!(IWRAM, u8, 1 << 1, iwram);
register_bit!(PALRAM, u8, 1 << 2, palram);
register_bit!(VRAM, u8, 1 << 3, vram);
register_bit!(OAM, u8, 1 << 4, oam);
register_bit!(SIO, u8, 1 << 5, sio);
register_bit!(SOUND, u8, 1 << 6, sound);
register_bit!(OTHER_IO, u8, 1 << 7, other_io);
} }
//TODO(lokathor): newtype this flag business.
/// (`swi 0x02`) Halts the CPU until an interrupt occurs. /// (`swi 0x02`) Halts the CPU until an interrupt occurs.
/// ///
@ -100,13 +125,17 @@ pub unsafe fn register_ram_reset(flags: u8) {
/// any enabled interrupt triggers. /// any enabled interrupt triggers.
#[inline(always)] #[inline(always)]
pub fn halt() { pub fn halt() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x02" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x02"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -120,13 +149,17 @@ pub fn halt() {
/// optional externals such as rumble and infra-red. /// optional externals such as rumble and infra-red.
#[inline(always)] #[inline(always)]
pub fn stop() { pub fn stop() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x03" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x03"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -145,13 +178,17 @@ pub fn stop() {
/// acknowledgement. /// acknowledgement.
#[inline(always)] #[inline(always)]
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) { pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x04" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags) unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x04"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
//TODO(lokathor): newtype this flag business. //TODO(lokathor): newtype this flag business.
@ -162,13 +199,17 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
/// must follow the same guidelines that `interrupt_wait` outlines. /// must follow the same guidelines that `interrupt_wait` outlines.
#[inline(always)] #[inline(always)]
pub fn vblank_interrupt_wait() { pub fn vblank_interrupt_wait() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x04" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ "r0", "r1" // both set to 1 by the routine asm!(/* ASM */ "swi 0x04"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ "r0", "r1" // both set to 1 by the routine
:/* OPT */ "volatile"
);
}
} }
} }
@ -219,16 +260,20 @@ pub fn rem(numerator: i32, denominator: i32) -> i32 {
/// by `2n` bits to get `n` more bits of fractional precision in your output. /// by `2n` bits to get `n` more bits of fractional precision in your output.
#[inline(always)] #[inline(always)]
pub fn sqrt(val: u32) -> u16 { pub fn sqrt(val: u32) -> u16 {
let out: u16; if cfg!(test) {
unsafe { 0 // TODO: simulate this properly during testing builds.
asm!(/* ASM */ "swi 0x08" } else {
:/* OUT */ "={r0}"(out) let out: u16;
:/* INP */ "{r0}"(val) unsafe {
:/* CLO */ "r1", "r3" asm!(/* ASM */ "swi 0x08"
:/* OPT */ :/* OUT */ "={r0}"(out)
); :/* INP */ "{r0}"(val)
:/* CLO */ "r1", "r3"
:/* OPT */
);
}
out
} }
out
} }
/// (`swi 0x09`) Gives the arctangent of `theta`. /// (`swi 0x09`) Gives the arctangent of `theta`.
@ -239,16 +284,20 @@ pub fn sqrt(val: u32) -> u16 {
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`. /// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
#[inline(always)] #[inline(always)]
pub fn atan(theta: i16) -> i16 { pub fn atan(theta: i16) -> i16 {
let out: i16; if cfg!(test) {
unsafe { 0 // TODO: simulate this properly during testing builds.
asm!(/* ASM */ "swi 0x09" } else {
:/* OUT */ "={r0}"(out) let out: i16;
:/* INP */ "{r0}"(theta) unsafe {
:/* CLO */ "r1", "r3" asm!(/* ASM */ "swi 0x09"
:/* OPT */ :/* OUT */ "={r0}"(out)
); :/* INP */ "{r0}"(theta)
:/* CLO */ "r1", "r3"
:/* OPT */
);
}
out
} }
out
} }
/// (`swi 0x0A`) Gives the atan2 of `y` over `x`. /// (`swi 0x0A`) Gives the atan2 of `y` over `x`.
@ -260,16 +309,20 @@ pub fn atan(theta: i16) -> i16 {
/// integral, 14 bits for fractional. /// integral, 14 bits for fractional.
#[inline(always)] #[inline(always)]
pub fn atan2(y: i16, x: i16) -> u16 { pub fn atan2(y: i16, x: i16) -> u16 {
let out: u16; if cfg!(test) {
unsafe { 0 // TODO: simulate this properly during testing builds.
asm!(/* ASM */ "swi 0x0A" } else {
:/* OUT */ "={r0}"(out) let out: u16;
:/* INP */ "{r0}"(x), "{r1}"(y) unsafe {
:/* CLO */ "r3" asm!(/* ASM */ "swi 0x0A"
:/* OPT */ :/* OUT */ "={r0}"(out)
); :/* INP */ "{r0}"(x), "{r1}"(y)
:/* CLO */ "r3"
:/* OPT */
);
}
out
} }
out
} }
/// (`swi 0x0B`) "CpuSet", `u16` memory copy. /// (`swi 0x0B`) "CpuSet", `u16` memory copy.
@ -283,13 +336,17 @@ pub fn atan2(y: i16, x: i16) -> u16 {
/// * Both pointers must be aligned /// * Both pointers must be aligned
#[inline(always)] #[inline(always)]
pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) { pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) {
let control = count + ((fixed_source as u32) << 24); if cfg!(test) {
asm!(/* ASM */ "swi 0x0B" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) let control = count + ((fixed_source as u32) << 24);
:/* CLO */ // none asm!(/* ASM */ "swi 0x0B"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
/// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill. /// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill.
@ -303,13 +360,17 @@ pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_sourc
/// * Both pointers must be aligned /// * Both pointers must be aligned
#[inline(always)] #[inline(always)]
pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
let control = count + ((fixed_source as u32) << 24) + (1 << 26); if cfg!(test) {
asm!(/* ASM */ "swi 0x0B" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) let control = count + ((fixed_source as u32) << 24) + (1 << 26);
:/* CLO */ // none asm!(/* ASM */ "swi 0x0B"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
/// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks. /// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks.
@ -324,13 +385,17 @@ pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_sourc
/// * Both pointers must be aligned /// * Both pointers must be aligned
#[inline(always)] #[inline(always)]
pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
let control = count + ((fixed_source as u32) << 24); if cfg!(test) {
asm!(/* ASM */ "swi 0x0C" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) let control = count + ((fixed_source as u32) << 24);
:/* CLO */ // none asm!(/* ASM */ "swi 0x0C"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
/// (`swi 0x0C`) "GetBiosChecksum" (Undocumented) /// (`swi 0x0C`) "GetBiosChecksum" (Undocumented)
@ -343,16 +408,20 @@ pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_so
/// some other value I guess you're probably running on an emulator that just /// some other value I guess you're probably running on an emulator that just
/// broke the fourth wall. /// broke the fourth wall.
pub fn get_bios_checksum() -> u32 { pub fn get_bios_checksum() -> u32 {
let out: u32; if cfg!(test) {
unsafe { 0
asm!(/* ASM */ "swi 0x0D" } else {
:/* OUT */ "={r0}"(out) let out: u32;
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x0D"
:/* OPT */ // none :/* OUT */ "={r0}"(out)
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ // none
);
}
out
} }
out
} }
// TODO: these things will require that we build special structs // TODO: these things will require that we build special structs
@ -376,13 +445,17 @@ pub fn get_bios_checksum() -> u32 {
/// ///
/// The final sound level setting will be `level` * `0x200`. /// The final sound level setting will be `level` * `0x200`.
pub fn sound_bias(level: u32) { pub fn sound_bias(level: u32) {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x19" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(level) unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x19"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(level)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -414,13 +487,17 @@ pub fn sound_bias(level: u32) {
/// * 10: 40137 /// * 10: 40137
/// * 11: 42048 /// * 11: 42048
pub fn sound_driver_mode(mode: u32) { pub fn sound_driver_mode(mode: u32) {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x1B" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ "{r0}"(mode) unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x1B"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ "{r0}"(mode)
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
//TODO(lokathor): newtype this mode business. //TODO(lokathor): newtype this mode business.
@ -434,13 +511,17 @@ pub fn sound_driver_mode(mode: u32) {
/// executed." --what? /// executed." --what?
#[inline(always)] #[inline(always)]
pub fn sound_driver_main() { pub fn sound_driver_main() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x1C" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x1C"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -450,13 +531,17 @@ pub fn sound_driver_main() {
/// vblank interrupt (every 1/60th of a second). /// vblank interrupt (every 1/60th of a second).
#[inline(always)] #[inline(always)]
pub fn sound_driver_vsync() { pub fn sound_driver_vsync() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x1D" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x1D"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -468,13 +553,17 @@ pub fn sound_driver_vsync() {
/// --what? /// --what?
#[inline(always)] #[inline(always)]
pub fn sound_channel_clear() { pub fn sound_channel_clear() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x1E" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x1E"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -489,13 +578,17 @@ pub fn sound_channel_clear() {
/// noise. /// noise.
#[inline(always)] #[inline(always)]
pub fn sound_driver_vsync_off() { pub fn sound_driver_vsync_off() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x28" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x28"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }
@ -506,12 +599,16 @@ pub fn sound_driver_vsync_off() {
/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second. /// interrupt followed by a `sound_driver_vsync` within 2/60th of a second.
#[inline(always)] #[inline(always)]
pub fn sound_driver_vsync_on() { pub fn sound_driver_vsync_on() {
unsafe { if cfg!(test) {
asm!(/* ASM */ "swi 0x29" // do nothing in test mode
:/* OUT */ // none } else {
:/* INP */ // none unsafe {
:/* CLO */ // none asm!(/* ASM */ "swi 0x29"
:/* OPT */ "volatile" :/* OUT */ // none
); :/* INP */ // none
:/* CLO */ // none
:/* OPT */ "volatile"
);
}
} }
} }