gb_rt/lib.rs
1#![no_std]
2#![feature(asm_experimental_arch)]
3
4//! Core Game Boy (SM83) runtime.
5//!
6//! Depending on this crate links the startup code in `rrt0.s` (reset entry, RST
7//! and interrupt vectors) and makes the linker script `gb.ld` available to the
8//! ROM build pipeline. Each interrupt vector jumps to an `_on_*` handler that
9//! defaults to a no-op and is overridden by defining a strong symbol.
10
11// The startup is assembled by the compiler itself (no host assembler is invoked),
12// landing in this crate's object. Nothing references `_reset` from Rust, so the
13// startup would be dropped when the staticlib is built; the ROM pipeline instead
14// links this crate's rlib directly, where the linker's ENTRY(_reset) pulls it in.
15core::arch::global_asm!(include_str!("rrt0.s"));
16
17pub mod boot;
18pub mod builtin;
19
20/// Proof that interrupts are disabled, re-exported from `critical-section`.
21///
22/// A handler marked with [`macro@interrupt`] can take one as a parameter.
23pub use critical_section::CriticalSection;
24
25/// Attribute marking the program entry point. See [`macro@entry`].
26pub use gb_rt_macros::entry;
27
28/// Attribute installing an interrupt handler at its vector. See [`macro@interrupt`].
29pub use gb_rt_macros::interrupt;
30
31/// Whether this program was built for CGB double speed mode.
32///
33/// The `cgb-double-speed` feature sets it, and the startup switches before
34/// [`entry`](macro@crate::entry) hands over. Reading it is how the rest of the
35/// crates work out what a clock counts at.
36pub const DOUBLE_SPEED: bool = cfg!(feature = "cgb-double-speed");
37
38/// Switch into CGB double speed mode, once, before the program runs.
39///
40/// [`entry`](macro@crate::entry) puts a call at the top of the program, so
41/// there is nothing to remember and nowhere else this belongs.
42///
43/// # Safety
44///
45/// Executes `stop`. That is only sound at the very start, before interrupts are
46/// on and before anything has been drawn: the CPU pauses for 2050 M-cycles with
47/// video memory locking frozen, which shows as a black or object-less frame.
48#[doc(hidden)]
49#[inline]
50pub unsafe fn __enter_double_speed() {
51 // A cartridge built for this is a Game Boy Color one, but an original Game
52 // Boy will still run it, and there `stop` is a machine that never wakes.
53 if !DOUBLE_SPEED || crate::boot::a() != 0x11 {
54 return;
55 }
56 unsafe {
57 core::arch::asm!(
58 // `stop` wakes on a joypad line falling, so the lines are taken out
59 // of the picture first: no interrupt may fire, and no row selected.
60 "xor a",
61 "ldh ($ff), a", // IE
62 "ld a, $30",
63 "ldh ($00), a", // JOYP, both rows off
64 "ld a, $01",
65 "ldh ($4d), a", // KEY1, switch armed
66 // `stop`. The assembler has no mnemonic for it, and the byte after
67 // is the one the CPU skips.
68 ".byte 0x10, 0x00",
69 out("a") _,
70 options(nostack),
71 );
72 }
73}