mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 11:11:31 +11:00
183 lines
6 KiB
Rust
183 lines
6 KiB
Rust
|
//! This module contains wrappers for all GBA BIOS function calls.
|
||
|
//!
|
||
|
//! A GBA BIOS call has significantly more overhead than a normal function call,
|
||
|
//! so think carefully before using them too much.
|
||
|
//!
|
||
|
//! The actual content of each function here is generally a single inline asm
|
||
|
//! instruction to invoke the correct BIOS function (`swi x`, with `x` being
|
||
|
//! whatever value is necessary for that function). Some functions also perform
|
||
|
//! necessary checks to save you from yourself, such as not dividing by zero.
|
||
|
|
||
|
/// (`swi 0x00`) SoftReset the device.
|
||
|
///
|
||
|
/// This function does not ever return.
|
||
|
///
|
||
|
/// Instead, it clears the top `0x200` bytes of IWRAM (containing stacks, and
|
||
|
/// BIOS IRQ vector/flags), re-initializes the system, supervisor, and irq stack
|
||
|
/// pointers (new values listed below), sets `r0` through `r12`, `LR_svc`,
|
||
|
/// `SPSR_svc`, `LR_irq`, and `SPSR_irq` to zero, and enters system mode. The
|
||
|
/// return address is loaded into `r14` and then the function jumps there with
|
||
|
/// `bx r14`.
|
||
|
///
|
||
|
/// * sp_svc: `0x300_7FE0`
|
||
|
/// * sp_irq: `0x300_7FA0`
|
||
|
/// * sp_sys: `0x300_7F00`
|
||
|
/// * Zero-filled Area: `0x300_7E00` to `0x300_7FFF`
|
||
|
/// * Return Address: Depends on the 8-bit flag value at `0x300_7FFA`. In either
|
||
|
/// case execution proceeds in ARM mode.
|
||
|
/// * zero flag: `0x800_0000` (ROM), which for our builds means that the
|
||
|
/// `crt0` program to execute (just like with a fresh boot), and then
|
||
|
/// control passes into `main` and so on.
|
||
|
/// * non-zero flag: `0x200_0000` (RAM), This is where a multiboot image would
|
||
|
/// go if you were doing a multiboot thing. However, this project doesn't
|
||
|
/// support multiboot at the moment. You'd need an entirely different build
|
||
|
/// pipeline because there's differences in header format and things like
|
||
|
/// that. Perhaps someday, but probably not even then. Submit the PR for it
|
||
|
/// if you like!
|
||
|
///
|
||
|
/// ## Safety
|
||
|
///
|
||
|
/// This functions isn't ever unsafe to the current iteration of the program.
|
||
|
/// However, because not all memory is fully cleared you theoretically could
|
||
|
/// threaten the _next_ iteration of the program that runs. I'm _fairly_
|
||
|
/// convinced that you can't actually use this to force purely safe code to
|
||
|
/// perform UB, but such a scenario might exist.
|
||
|
#[inline(always)]
|
||
|
pub unsafe fn soft_reset() -> ! {
|
||
|
asm!(/* ASM */ "swi 0x00"
|
||
|
:/* OUT */ // none
|
||
|
:/* INP */ // none
|
||
|
:/* CLO */ // none
|
||
|
:/* OPT */ "volatile"
|
||
|
);
|
||
|
core::hint::unreachable_unchecked()
|
||
|
}
|
||
|
|
||
|
/// (`swi 0x01`) RegisterRamReset.
|
||
|
///
|
||
|
/// Clears the portions of memory given by the `flags` value, sets the Display
|
||
|
/// Control Register to `0x80` (forced blank and nothing else), then returns.
|
||
|
///
|
||
|
/// * Flag bits:
|
||
|
/// 0) Clears the 256k of EWRAM (don't use if this is where your function call
|
||
|
/// will return to!)
|
||
|
/// 1) Clears the 32k of IWRAM _excluding_ the last `0x200` bytes (see also:
|
||
|
/// the `soft_reset` function).
|
||
|
/// 2) Clears all Palette data.
|
||
|
/// 3) Clears all VRAM.
|
||
|
/// 4) Clears all OAM (reminder: a zeroed obj isn't disabled!)
|
||
|
/// 5) Reset SIO registers (resets them to general purpose mode)
|
||
|
/// 6) Reset Sound registers
|
||
|
/// 7) Reset all IO registers _other than_ SIO and Sound
|
||
|
///
|
||
|
/// **Bug:** The least significant byte of `SIODATA32` is always zeroed, even if
|
||
|
/// bit 5 was not enabled. This is sadly a bug in the design of the GBA itself.
|
||
|
///
|
||
|
/// ## Safety
|
||
|
///
|
||
|
/// It is generally a safe operation to suddenly clear any part of the GBA's
|
||
|
/// memory, except in the case that you were executing out of IWRAM and clear
|
||
|
/// that. If you do that you return to nothing and have a bad time.
|
||
|
#[inline(always)]
|
||
|
pub unsafe fn register_ram_reset(flags: u8) {
|
||
|
asm!(/* ASM */ "swi 0x01"
|
||
|
:/* OUT */ // none
|
||
|
:/* INP */ "{r0}"(flags)
|
||
|
:/* CLO */ // none
|
||
|
:/* OPT */ "volatile"
|
||
|
);
|
||
|
}
|
||
|
//TODO(lokathor): newtype this flag business.
|
||
|
|
||
|
/// (`swi 0x06`) Software Division and Remainder.
|
||
|
///
|
||
|
/// ## Panics
|
||
|
///
|
||
|
/// If the denominator is 0.
|
||
|
#[inline(always)]
|
||
|
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||
|
assert!(denominator != 0);
|
||
|
let div_out: i32;
|
||
|
let rem_out: i32;
|
||
|
unsafe {
|
||
|
asm!(/* ASM */ "swi 0x06"
|
||
|
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
|
||
|
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
|
||
|
:/* CLO */ "r3"
|
||
|
:/* OPT */
|
||
|
);
|
||
|
}
|
||
|
(div_out, rem_out)
|
||
|
}
|
||
|
|
||
|
/// As `div_rem`, but keeping only the `div` part.
|
||
|
#[inline(always)]
|
||
|
pub fn div(numerator: i32, denominator: i32) -> i32 {
|
||
|
div_rem(numerator, denominator).0
|
||
|
}
|
||
|
|
||
|
/// As `div_rem`, but keeping only the `rem` part.
|
||
|
#[inline(always)]
|
||
|
pub fn rem(numerator: i32, denominator: i32) -> i32 {
|
||
|
div_rem(numerator, denominator).1
|
||
|
}
|
||
|
|
||
|
/// (`swi 0x08`) Integer square root.
|
||
|
///
|
||
|
/// If you want more fractional precision, you can shift your input to the left
|
||
|
/// by `2n` bits to get `n` more bits of fractional precision in your output.
|
||
|
#[inline(always)]
|
||
|
pub fn sqrt(val: u32) -> u16 {
|
||
|
let out: u16;
|
||
|
unsafe {
|
||
|
asm!(/* ASM */ "swi 0x08"
|
||
|
:/* OUT */ "={r0}"(out)
|
||
|
:/* INP */ "{r0}"(val)
|
||
|
:/* CLO */ "r1", "r3"
|
||
|
:/* OPT */
|
||
|
);
|
||
|
}
|
||
|
out
|
||
|
}
|
||
|
|
||
|
/// (`swi 0x09`) Gives the arctangent of `theta`.
|
||
|
///
|
||
|
/// The input format is 1 bit for sign, 1 bit for integral part, 14 bits for
|
||
|
/// fractional part.
|
||
|
///
|
||
|
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
|
||
|
#[inline(always)]
|
||
|
pub fn atan(theta: i16) -> i16 {
|
||
|
let out: i16;
|
||
|
unsafe {
|
||
|
asm!(/* ASM */ "swi 0x09"
|
||
|
:/* OUT */ "={r0}"(out)
|
||
|
:/* INP */ "{r0}"(theta)
|
||
|
:/* CLO */ "r1", "r3"
|
||
|
:/* OPT */
|
||
|
);
|
||
|
}
|
||
|
out
|
||
|
}
|
||
|
|
||
|
/// (`swi 0x0A`) Gives the atan2 of `y` over `x`.
|
||
|
///
|
||
|
/// The output `theta` value maps into the range `[0, 2pi)`, or `0 .. 2pi` if
|
||
|
/// you prefer Rust's range notation.
|
||
|
///
|
||
|
/// `y` and `x` use the same format as with `atan`: 1 bit for sign, 1 bit for
|
||
|
/// integral, 14 bits for fractional.
|
||
|
#[inline(always)]
|
||
|
pub fn atan2(y: i16, x: i16) -> u16 {
|
||
|
let out: u16;
|
||
|
unsafe {
|
||
|
asm!(/* ASM */ "swi 0x0A"
|
||
|
:/* OUT */ "={r0}"(out)
|
||
|
:/* INP */ "{r0}"(x), "{r1}"(y)
|
||
|
:/* CLO */ "r3"
|
||
|
:/* OPT */
|
||
|
);
|
||
|
}
|
||
|
out
|
||
|
}
|