From fd3a308e8a0f1ba3723f59cbdc1ea87f9a4a298c Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 8 Apr 2021 23:57:30 -0600 Subject: [PATCH] remove proc macro usage and finish out the mmio (#125) * . * more updates, much more to do soon. * hello world works again. * fix key interrupts. * remove the old book * don't forget timers. * we can just use search, duh. * cleanup * more bios * finished most mmio, finished bios. * dump some old macros that shouldn't have been pub to begin with. * Update README.md * for now, just make this a u16 * timers * fix hello_world * timer fix * docs on timers * block resetting work ram for now. * put preserves_flags on bios calls as appropriate * add a code page 437 tile sheet. * docs. * sound :( it's all bad but we'll fix it in 0.5 * move most backup files back into the lib. * CI might work now? * fix up non-serial examples. * oops, gotta check out the repo. * readme. --- .cargo/config.toml | 6 + .github/workflows/ci.yaml | 50 +- Cargo.toml | 21 +- README.md | 33 +- examples-bak/bg_demo.rs | 69 -- examples-bak/mgba_panic_handler.rs | 31 - examples/hello_magic.rs | 23 - examples/hello_world.rs | 64 +- examples/irq.rs | 87 +- examples/test_savegame.rs | 55 +- src/art.rs | 7 + src/art/cga_8x8_thick.rs | 98 ++ src/{io => bak}/sio.rs | 22 +- {examples => src/bak}/uart_echo.rs | 0 src/base.rs | 7 - src/bios.rs | 1106 +++++++++--------- src/debug.rs | 122 +- src/ewram.rs | 1 - src/fixed_point.rs | 256 ---- src/io.rs | 20 - src/io/background.rs | 117 -- src/io/color_blend.rs | 76 -- src/io/display.rs | 161 --- src/io/dma.rs | 415 ------- src/io/irq.rs | 191 --- src/io/keypad.rs | 129 -- src/io/sound.rs | 269 ----- src/io/timers.rs | 93 -- src/io/window.rs | 99 -- src/iwram.rs | 1 - src/lib.rs | 134 +-- src/macros.rs | 190 --- src/mmio_addresses.rs | 289 +++++ src/mmio_addresses/mode3.rs | 42 + src/mmio_types.rs | 145 +++ src/mmio_types/background_control.rs | 15 + src/mmio_types/blend_control.rs | 30 + src/mmio_types/color.rs | 19 + src/mmio_types/display_control.rs | 36 + src/mmio_types/display_status.rs | 15 + src/mmio_types/dma_control.rs | 44 + src/mmio_types/interrupt_flags.rs | 23 + src/mmio_types/key_interrupt_control.rs | 21 + src/mmio_types/keys.rs | 68 ++ src/mmio_types/mosaic_size.rs | 10 + src/mmio_types/noise_frequency_control.rs | 13 + src/mmio_types/noise_len_env.rs | 12 + src/mmio_types/register_ram_reset_control.rs | 16 + src/mmio_types/timer_control.rs | 12 + src/mmio_types/tone_duty_len_env.rs | 13 + src/mmio_types/tone_frequency_control.rs | 11 + src/mmio_types/tone_sweep.rs | 11 + src/mmio_types/wave_control.rs | 11 + src/mmio_types/wave_frequency_control.rs | 11 + src/mmio_types/wave_len_volume.rs | 11 + src/mmio_types/window_enable.rs | 14 + src/oam.rs | 196 ---- src/palram.rs | 60 - src/rom.rs | 1 - src/rsrt0.S | 20 +- src/save/eeprom.rs | 50 +- src/save/flash.rs | 1 + src/save/utils.rs | 25 +- src/sync.rs | 4 +- src/vram.rs | 66 -- src/vram/affine.rs | 32 - src/vram/bitmap.rs | 454 ------- src/vram/text.rs | 33 - todo_check.bat | 12 - 69 files changed, 1899 insertions(+), 3900 deletions(-) delete mode 100644 examples-bak/bg_demo.rs delete mode 100644 examples-bak/mgba_panic_handler.rs delete mode 100644 examples/hello_magic.rs create mode 100644 src/art.rs create mode 100644 src/art/cga_8x8_thick.rs rename src/{io => bak}/sio.rs (100%) rename {examples => src/bak}/uart_echo.rs (100%) delete mode 100644 src/base.rs delete mode 100644 src/ewram.rs delete mode 100644 src/fixed_point.rs delete mode 100644 src/io.rs delete mode 100644 src/io/background.rs delete mode 100644 src/io/color_blend.rs delete mode 100644 src/io/display.rs delete mode 100644 src/io/dma.rs delete mode 100644 src/io/irq.rs delete mode 100644 src/io/keypad.rs delete mode 100644 src/io/sound.rs delete mode 100644 src/io/timers.rs delete mode 100644 src/io/window.rs delete mode 100644 src/iwram.rs delete mode 100644 src/macros.rs create mode 100644 src/mmio_addresses.rs create mode 100644 src/mmio_addresses/mode3.rs create mode 100644 src/mmio_types.rs create mode 100644 src/mmio_types/background_control.rs create mode 100644 src/mmio_types/blend_control.rs create mode 100644 src/mmio_types/color.rs create mode 100644 src/mmio_types/display_control.rs create mode 100644 src/mmio_types/display_status.rs create mode 100644 src/mmio_types/dma_control.rs create mode 100644 src/mmio_types/interrupt_flags.rs create mode 100644 src/mmio_types/key_interrupt_control.rs create mode 100644 src/mmio_types/keys.rs create mode 100644 src/mmio_types/mosaic_size.rs create mode 100644 src/mmio_types/noise_frequency_control.rs create mode 100644 src/mmio_types/noise_len_env.rs create mode 100644 src/mmio_types/register_ram_reset_control.rs create mode 100644 src/mmio_types/timer_control.rs create mode 100644 src/mmio_types/tone_duty_len_env.rs create mode 100644 src/mmio_types/tone_frequency_control.rs create mode 100644 src/mmio_types/tone_sweep.rs create mode 100644 src/mmio_types/wave_control.rs create mode 100644 src/mmio_types/wave_frequency_control.rs create mode 100644 src/mmio_types/wave_len_volume.rs create mode 100644 src/mmio_types/window_enable.rs delete mode 100644 src/oam.rs delete mode 100644 src/palram.rs delete mode 100644 src/rom.rs delete mode 100644 src/vram.rs delete mode 100644 src/vram/affine.rs delete mode 100644 src/vram/bitmap.rs delete mode 100644 src/vram/text.rs delete mode 100644 todo_check.bat diff --git a/.cargo/config.toml b/.cargo/config.toml index 03b1d63..ecce87a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,9 @@ +[build] +target = "thumbv4t-none-eabi" + +[unstable] +build-std = ["core"] + [target.thumbv4t-none-eabi] rustflags = ["-Clink-arg=-Tlinker.ld"] runner = "mgba" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4d937b9..48f8fa7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,54 +12,30 @@ jobs: rust: - { toolchain: nightly } steps: + - uses: actions/checkout@v2 + - name: Install Apt Dependencies - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential libssl-dev binutils-arm-none-eabi + run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi + - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust.toolchain }} default: true + - name: Install Rust Source run: rustup component add rust-src - - name: Install cargo-make crate - uses: actions-rs/install@v0.1 - with: - crate: cargo-make - version: latest - use-tool-cache: true - - name: Install gbafix crate - uses: actions-rs/install@v0.1 - with: - crate: gbafix - version: latest - use-tool-cache: true - - uses: actions/checkout@v2 - - name: Make Test + + - name: Build The Examples uses: actions-rs/cargo@v1 with: toolchain: ${{ matrix.rust.toolchain }} - command: make - args: test - - name: Make Release + command: build + args: --examples + + - name: Check That Docs Compile uses: actions-rs/cargo@v1 with: toolchain: ${{ matrix.rust.toolchain }} - command: make - args: justrelease - #build-book: - # runs-on: ubuntu-latest - # steps: - # - uses: actions-rs/toolchain@v1 - # with: - # profile: minimal - # toolchain: stable - # default: true - # - name: Install mdbook crate - # uses: actions-rs/install@v0.1 - # with: - # crate: mdbook - # version: latest - # use-tool-cache: true - # - uses: actions/checkout@v2 - # - name: Build the book - # run: cd book && mdbook build + command: test + args: --doc diff --git a/Cargo.toml b/Cargo.toml index 419badd..38f29c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,27 +12,24 @@ license = "Zlib OR Apache-2.0 OR MIT" publish = false [features] -default = [] +default = ["serial"] serial = ["embedded-hal", "nb"] [dependencies] voladdress = { version = "0.4" } -gba-proc-macro = "0.5" embedded-hal = { version = "0.2.4", optional = true } -nb = { version = "1.0.0", optional = true } +nb = { version = "1", optional = true } [profile.dev] -lto = false panic = "abort" -incremental = false -codegen-units = 1 [profile.release] -lto = true panic = "abort" -incremental = false -codegen-units = 1 -[[example]] -name = "uart_echo" -required-features = ["serial"] +#[[example]] +#name = "uart_echo" +#required-features = ["serial"] + +[package.metadata.docs.rs] +default-target = "thumbv6m-none-eabi" +targets = [] diff --git a/README.md b/README.md index b385ae5..4cb3bef 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ A crate to make GBA programming easy. Currently we don't have as much documentation as we'd like. If you check out the [awesome-gbadev](https://github.com/gbdev/awesome-gbadev) repository they have many resources, though most are oriented towards C. -## First Time Setup +## System Setup + +There's a few extra things to install that you just need to do once per system. Building for the GBA requires Nightly rust, and also uses the `build-std` feature, so you'll need the rust source available. @@ -49,21 +51,28 @@ cargo install cargo-make cargo install gbafix ``` - +arm-none-eabi-objcopy -O binary [RUST_BINARY_NAME] [ROM_NAME].gba +``` + +Then you'll need to patch the header data with `gbafix` +``` +gbafix [ROM_NAME].gba +``` +And you'll be all done! # Contribution diff --git a/examples-bak/bg_demo.rs b/examples-bak/bg_demo.rs deleted file mode 100644 index 515bd7e..0000000 --- a/examples-bak/bg_demo.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![no_std] -#![feature(start)] - -use gba::{ - io::{ - background::{BackgroundControlSetting, BG0CNT}, - display::{DisplayControlSetting, DISPCNT}, - }, - palram::index_palram_bg_4bpp, - vram::{text::TextScreenblockEntry, Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS}, - Color, -}; - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - pub const WHITE: Color = Color::from_rgb(31, 31, 31); - pub const LIGHT_GRAY: Color = Color::from_rgb(25, 25, 25); - pub const DARK_GRAY: Color = Color::from_rgb(15, 15, 15); - // bg palette - index_palram_bg_4bpp(0, 1).write(WHITE); - index_palram_bg_4bpp(0, 2).write(LIGHT_GRAY); - index_palram_bg_4bpp(0, 3).write(DARK_GRAY); - // bg tiles - set_bg_tile_4bpp(0, 0, ALL_TWOS); - set_bg_tile_4bpp(0, 1, ALL_THREES); - // screenblock - let light_entry = TextScreenblockEntry::from_tile_id(0); - let dark_entry = TextScreenblockEntry::from_tile_id(1); - checker_screenblock(8, light_entry, dark_entry); - // bg0 control - BG0CNT.write(BackgroundControlSetting::new().with_screen_base_block(8)); - // Display Control - DISPCNT.write(DisplayControlSetting::new().with_bg0(true)); - loop {} -} - -pub const ALL_TWOS: Tile4bpp = Tile4bpp([ - 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, -]); - -pub const ALL_THREES: Tile4bpp = Tile4bpp([ - 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, -]); - -pub fn set_bg_tile_4bpp(charblock: usize, index: usize, tile: Tile4bpp) { - assert!(charblock < 4); - assert!(index < 512); - unsafe { CHAR_BASE_BLOCKS.index(charblock).cast::().offset(index as isize).write(tile) } -} - -pub fn checker_screenblock(slot: usize, a_entry: TextScreenblockEntry, b_entry: TextScreenblockEntry) { - let mut p = unsafe { SCREEN_BASE_BLOCKS.index(slot).cast::() }; - let mut checker = true; - for _row in 0..32 { - for _col in 0..32 { - unsafe { - p.write(if checker { a_entry } else { b_entry }); - p = p.offset(1); - } - checker = !checker; - } - checker = !checker; - } -} diff --git a/examples-bak/mgba_panic_handler.rs b/examples-bak/mgba_panic_handler.rs deleted file mode 100644 index 5a9914e..0000000 --- a/examples-bak/mgba_panic_handler.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![no_std] -#![feature(start)] -#![forbid(unsafe_code)] - -use gba::{ - io::display::{DisplayControlSetting, DisplayMode, DISPCNT}, - vram::bitmap::Mode3, - Color, -}; - -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - 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 { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_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/examples/hello_magic.rs b/examples/hello_magic.rs deleted file mode 100644 index 8aa3c55..0000000 --- a/examples/hello_magic.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![no_std] -#![feature(start)] - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - 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); - loop {} - } -} - -#[no_mangle] -static __IRQ_HANDLER: extern "C" fn() = irq_handler; - -extern "C" fn irq_handler() {} diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 01ef803..d84651c 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,23 +1,19 @@ #![no_std] -#![feature(start)] -#![forbid(unsafe_code)] +#![no_main] -use gba::{ - fatal, - io::{ - display::{DisplayControlSetting, DisplayMode, DISPCNT, VBLANK_SCANLINE, VCOUNT}, - keypad::read_key_input, - }, - vram::bitmap::Mode3, - Color, -}; +use gba::prelude::*; #[panic_handler] +#[allow(unused)] fn panic(info: &core::panic::PanicInfo) -> ! { // This kills the emulation with a message if we're running inside an // emulator we support (mGBA or NO$GBA), or just crashes the game if we // aren't. - fatal!("{}", info); + //fatal!("{}", info); + + loop { + DISPCNT.read(); + } } /// Performs a busy loop until VBlank starts. @@ -25,7 +21,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! { /// This is very inefficient, and please keep following the lessons until we /// cover how interrupts work! pub fn spin_until_vblank() { - while VCOUNT.read() < VBLANK_SCANLINE {} + while VCOUNT.read() < 160 {} } /// Performs a busy loop until VDraw starts. @@ -33,30 +29,29 @@ pub fn spin_until_vblank() { /// This is very inefficient, and please keep following the lessons until we /// cover how interrupts work! pub fn spin_until_vdraw() { - while VCOUNT.read() >= VBLANK_SCANLINE {} + while VCOUNT.read() >= 160 {} } -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = - DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true); +#[no_mangle] +pub fn main() -> ! { + const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true); DISPCNT.write(SETTING); - let mut px = Mode3::WIDTH / 2; - let mut py = Mode3::HEIGHT / 2; - let mut color = Color::from_rgb(31, 0, 0); + let mut px = mode3::WIDTH / 2; + let mut py = mode3::HEIGHT / 2; + let mut color = Color::from_rgb(31, 3, 1); loop { // read our keys for this frame - let this_frame_keys = read_key_input(); + let keys: Keys = KEYINPUT.read().into(); // adjust game state and wait for vblank - px = px.wrapping_add((2 * this_frame_keys.x_tribool() as i32) as usize); - py = py.wrapping_add((2 * this_frame_keys.y_tribool() as i32) as usize); - if this_frame_keys.l() { + px = px.wrapping_add((2 * keys.x_signum()) as usize); + py = py.wrapping_add((2 * keys.y_signum()) as usize); + if keys.l() { color = Color(color.0.rotate_left(5)); } - if this_frame_keys.r() { + if keys.r() { color = Color(color.0.rotate_right(5)); } @@ -64,17 +59,18 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { spin_until_vblank(); // draw the new game and wait until the next frame starts. - if px >= Mode3::WIDTH || py >= Mode3::HEIGHT { + if (px + 1) >= mode3::WIDTH || (py + 1) >= mode3::HEIGHT { // out of bounds, reset the screen and position. - Mode3::dma_clear_to(Color::from_rgb(0, 0, 0)); - px = Mode3::WIDTH / 2; - py = Mode3::HEIGHT / 2; + mode3::dma3_clear_to(Color::from_rgb(0, 0, 0)); + px = mode3::WIDTH / 2; + py = mode3::HEIGHT / 2; + color = Color(color.0.rotate_left(7)); } else { // draw the new part of the line - Mode3::write(px, py, color); - Mode3::write(px, py + 1, color); - Mode3::write(px + 1, py, color); - Mode3::write(px + 1, py + 1, color); + mode3::bitmap_xy(px, py).write(color); + mode3::bitmap_xy(px, py + 1).write(color); + mode3::bitmap_xy(px + 1, py).write(color); + mode3::bitmap_xy(px + 1, py + 1).write(color); } // now we wait again diff --git a/examples/irq.rs b/examples/irq.rs index 2b77b0b..88e4ad1 100644 --- a/examples/irq.rs +++ b/examples/irq.rs @@ -1,16 +1,8 @@ #![no_std] -#![feature(start)] +#![no_main] +#![feature(isa_attribute)] -use gba::{ - io::{ - display::{DisplayControlSetting, DisplayMode, DisplayStatusSetting, DISPCNT, DISPSTAT}, - irq::{self, IrqEnableSetting, IrqFlags, BIOS_IF, IE, IME}, - keypad::read_key_input, - timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L}, - }, - vram::bitmap::Mode3, - Color, -}; +use gba::prelude::*; const BLACK: Color = Color::from_rgb(0, 0, 0); const RED: Color = Color::from_rgb(31, 0, 0); @@ -26,50 +18,50 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { fn start_timers() { let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16; - const TIMER_SETTINGS: TimerControlSetting = - TimerControlSetting::new().with_overflow_irq(true).with_enabled(true); + const TIMER_SETTINGS: TimerControl = + TimerControl::new().with_irq_on_overflow(true).with_enabled(true); - TM0CNT_L.write(init_val); - TM0CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU1024)); - TM1CNT_L.write(init_val); - TM1CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU64)); + TIMER0_RELOAD.write(init_val); + TIMER0_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(3)); + TIMER1_RELOAD.write(init_val); + TIMER1_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(1)); } -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - DISPCNT.write(DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true)); - Mode3::clear_to(BLACK); +#[no_mangle] +fn main() -> ! { + DISPCNT.write(DisplayControl::new().with_display_mode(3).with_display_bg2(true)); + mode3::dma3_clear_to(BLACK); // Set the IRQ handler to use. - irq::set_irq_handler(irq_handler); + unsafe { USER_IRQ_HANDLER.write(Some(irq_handler_a32)) }; // Enable all interrupts that are set in the IE register. - unsafe { IME.write(IrqEnableSetting::IRQ_YES) }; + unsafe { IME.write(true) }; // Request that VBlank, HBlank and VCount will generate IRQs. - const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new() - .with_vblank_irq_enable(true) - .with_hblank_irq_enable(true) - .with_vcounter_irq_enable(true); + const DISPLAY_SETTINGS: DisplayStatus = DisplayStatus::new() + .with_vblank_irq_enabled(true) + .with_hblank_irq_enabled(true) + .with_vcount_irq_enabled(true); DISPSTAT.write(DISPLAY_SETTINGS); // Start two timers with overflow IRQ generation. start_timers(); loop { - let this_frame_keys = read_key_input(); + let this_frame_keys: Keys = KEYINPUT.read().into(); // The VBlank IRQ must be enabled at minimum, or else the CPU will halt // at the call to vblank_interrupt_wait() as the VBlank IRQ will never // be triggered. - let mut flags = IrqFlags::new().with_vblank(true); + let mut flags = InterruptFlags::new().with_vblank(true); // Enable interrupts based on key input. if this_frame_keys.a() { flags = flags.with_hblank(true); } if this_frame_keys.b() { - flags = flags.with_vcounter(true); + flags = flags.with_vcount(true); } if this_frame_keys.l() { flags = flags.with_timer0(true); @@ -83,7 +75,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { // Puts the CPU into low power mode until a VBlank IRQ is received. This // will yield considerably better power efficiency as opposed to spin // waiting. - gba::bios::vblank_interrupt_wait(); + unsafe { VBlankIntrWait() }; } } @@ -91,20 +83,31 @@ static mut PIXEL: usize = 0; fn write_pixel(color: Color) { unsafe { - Mode3::write(PIXEL, 0, color); - PIXEL = (PIXEL + 1) % (Mode3::WIDTH * Mode3::HEIGHT); + (0x0600_0000 as *mut Color).wrapping_offset(PIXEL as isize).write_volatile(color); + PIXEL += 1; + if PIXEL == (mode3::WIDTH * mode3::HEIGHT) { + PIXEL = 0; + } } } -extern "C" fn irq_handler(flags: IrqFlags) { +#[instruction_set(arm::a32)] +extern "C" fn irq_handler_a32() { + // we just use this a32 function to jump over back to t32 code. + irq_handler_t32() +} + +fn irq_handler_t32() { + let flags = IRQ_PENDING.read(); + if flags.vblank() { vblank_handler(); } if flags.hblank() { hblank_handler(); } - if flags.vcounter() { - vcounter_handler(); + if flags.vcount() { + vcount_handler(); } if flags.timer0() { timer0_handler(); @@ -119,29 +122,29 @@ fn vblank_handler() { // When using `interrupt_wait()` or `vblank_interrupt_wait()`, IRQ handlers must acknowledge // the IRQ on the BIOS Interrupt Flags register. - unsafe { BIOS_IF.write(BIOS_IF.read().with_vblank(true)) }; + unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_vblank(true)) }; } fn hblank_handler() { write_pixel(GREEN); - unsafe { BIOS_IF.write(BIOS_IF.read().with_hblank(true)) }; + unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_hblank(true)) }; } -fn vcounter_handler() { +fn vcount_handler() { write_pixel(RED); - unsafe { BIOS_IF.write(BIOS_IF.read().with_vcounter(true)) }; + unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_vcount(true)) }; } fn timer0_handler() { write_pixel(YELLOW); - unsafe { BIOS_IF.write(BIOS_IF.read().with_timer0(true)) }; + unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_timer0(true)) }; } fn timer1_handler() { write_pixel(PINK); - unsafe { BIOS_IF.write(BIOS_IF.read().with_timer1(true)) }; + unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_timer1(true)) }; } diff --git a/examples/test_savegame.rs b/examples/test_savegame.rs index 1ef4194..2529a77 100644 --- a/examples/test_savegame.rs +++ b/examples/test_savegame.rs @@ -1,31 +1,20 @@ #![no_std] -#![feature(start)] -#![forbid(unsafe_code)] +#![no_main] use core::cmp; -use gba::{ - fatal, - io::{ - display::{DisplayControlSetting, DisplayMode, DISPCNT}, - timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L}, - }, - save::*, - vram::bitmap::Mode3, - warn, Color, -}; +use gba::{fatal, prelude::*, save::*, warn}; -fn set_screen_color(r: u16, g: u16, b: u16) { - const SETTING: DisplayControlSetting = - DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true); +fn set_screen_color(r: u8, g: u8, b: u8) { + const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true); DISPCNT.write(SETTING); - Mode3::dma_clear_to(Color::from_rgb(r, g, b)); + mode3::dma3_clear_to(Color::from_rgb(r, g, b)); } fn set_screen_progress(cur: usize, max: usize) { - let lines = cur * (Mode3::WIDTH / max); + let lines = cur * (mode3::WIDTH / max); let color = Color::from_rgb(0, 31, 0); for x in 0..lines { - for y in 0..Mode3::HEIGHT { - Mode3::write(x, y, color); + for y in 0..mode3::HEIGHT { + mode3::bitmap_xy(x, y).write(color); } } } @@ -33,7 +22,7 @@ fn set_screen_progress(cur: usize, max: usize) { #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { set_screen_color(31, 0, 0); - fatal!("{}", info); + fatal!("{}", info) } #[derive(Clone)] @@ -63,25 +52,25 @@ fn check_status(r: Result) -> T { } fn setup_timers() { - TM0CNT_L.write(0); - TM1CNT_L.write(0); + TIMER0_RELOAD.write(0); + TIMER1_RELOAD.write(0); - let ctl = TimerControlSetting::new().with_tick_rate(TimerTickRate::CPU1024).with_enabled(true); - TM0CNT_H.write(ctl); - let ctl = TimerControlSetting::new().with_tick_rate(TimerTickRate::Cascade).with_enabled(true); - TM1CNT_H.write(ctl); + let ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true); + TIMER0_CONTROL.write(ctl); + let ctl = TimerControl::new().with_chained_counting(true).with_enabled(true); + TIMER1_CONTROL.write(ctl); } // I'm fully aware how slow this is. But this is just example code, so, eh. fn get_timer_secs() -> f32 { - let raw_timer = (TM1CNT_L.read() as u32) << 16 | TM0CNT_L.read() as u32; + let raw_timer = (TIMER1_COUNTER.read() as u32) << 16 | TIMER0_COUNTER.read() as u32; (raw_timer as f32 * 1024.0) / ((1 << 24) as f32) } macro_rules! output { - ($($args:tt)*) => { - // we use warn so it shows by default on mGBA, nothing more. - warn!("{:7.3}\t{}", get_timer_secs(), format_args!($($args)*)) - }; + ($($args:tt)*) => { + // we use warn so it shows by default on mGBA, nothing more. + warn!("{:7.3}\t{}", get_timer_secs(), format_args!($($args)*)) + }; } fn do_test(seed: Rng, offset: usize, len: usize, block_size: usize) -> Result<(), Error> { @@ -128,8 +117,8 @@ fn do_test(seed: Rng, offset: usize, len: usize, block_size: usize) -> Result<() Ok(()) } -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { +#[no_mangle] +fn main() -> ! { // set a pattern to show that the ROM is working at all. set_screen_color(31, 31, 0); diff --git a/src/art.rs b/src/art.rs new file mode 100644 index 0000000..3932e64 --- /dev/null +++ b/src/art.rs @@ -0,0 +1,7 @@ +//! This module has constants for various tile sheets in compressed form. +//! +//! Depending on the tile sheet, the optimal compression varies. The docs of +//! each constant explain how to decompress the data correctly. + +mod cga_8x8_thick; +pub use cga_8x8_thick::*; diff --git a/src/art/cga_8x8_thick.rs b/src/art/cga_8x8_thick.rs new file mode 100644 index 0000000..05abc74 --- /dev/null +++ b/src/art/cga_8x8_thick.rs @@ -0,0 +1,98 @@ +/// The CGA [Code Page 437][cp437] type face, with thick lines. +/// +/// There's 256 tiles, packed down to 1bpp. To decompress this easily you can +/// call [`BitUnPack`] as follows: +/// ```no_run +/// # use gba::prelude::*; +/// # use gba::art::CGA_8X8_THICK; +/// # use core::convert::TryFrom; +/// # use core::mem::size_of_val; +/// let info = UnpackInfo { +/// source_data_len_bytes: size_of_val(&CGA_8X8_THICK) as usize, +/// source_unit_bit_width: 1, +/// // this assumes we want to unpack to 4bpp tiles +/// destination_unit_bit_width: 4, +/// data_offset: 0, +/// }; +/// // our example unpacks directly to the start of VRAM. +/// unsafe { BitUnPack(CGA_8X8_THICK.as_ptr(), 0x0600_0000 as *mut u32, &info) }; +/// ``` +/// +/// I am not a lawyer, but type faces are not protected by copyright in the USA. +/// The copyright status of font faces [varies by country][wp]. If it matters, +/// CGA was first released in 1981. +/// +/// [cp437]: https://en.wikipedia.org/wiki/Code_page_437 +/// +/// [wp]: +/// https://en.wikipedia.org/wiki/Intellectual_property_protection_of_typefaces +pub const CGA_8X8_THICK: [u32; 512] = [ + // Note(Lokathor): I generated this by (1) converting the type face file from + // an RGB PNG to Indexed Color PNG using GIMP, (2) running `grit + // CGA8x8thick-indexed.png -gB1` to output an assembly file full of the + // compressed data `.word` entries, (3) copying all those words into Rust. + 0x00000000, 0x00000000, 0x81A5817E, 0x7E8199BD, 0xFFDBFF7E, 0x7EFFE7C3, 0x7F7F7F36, 0x00081C3E, + 0x7F3E1C08, 0x00081C3E, 0x7F1C3E1C, 0x1C086B7F, 0x3E1C0808, 0x1C083E7F, 0x3C180000, 0x0000183C, + 0xC3E7FFFF, 0xFFFFE7C3, 0x42663C00, 0x003C6642, 0xBD99C3FF, 0xFFC399BD, 0xBEF0E0F0, 0x1E333333, + 0x6666663C, 0x187E183C, 0x0CFCCCFC, 0x070F0E0C, 0xC6FEC6FE, 0x0367E6C6, 0xE73CDB18, 0x18DB3CE7, + 0x7F1F0701, 0x0001071F, 0x7F7C7040, 0x0040707C, 0x187E3C18, 0x183C7E18, 0x66666666, 0x00660066, + 0xDEDBDBFE, 0x00D8D8D8, 0x361CC67C, 0x1E331C36, 0x00000000, 0x007E7E7E, 0x187E3C18, 0xFF183C7E, + 0x187E3C18, 0x00181818, 0x18181818, 0x00183C7E, 0x7F301800, 0x00001830, 0x7F060C00, 0x00000C06, + 0x03030000, 0x00007F03, 0xFF662400, 0x00002466, 0x7E3C1800, 0x0000FFFF, 0x7EFFFF00, 0x0000183C, + 0x00000000, 0x00000000, 0x0C1E1E0C, 0x000C000C, 0x00363636, 0x00000000, 0x367F3636, 0x0036367F, + 0x1E033E0C, 0x000C1F30, 0x18336300, 0x0063660C, 0x6E1C361C, 0x006E333B, 0x00030606, 0x00000000, + 0x06060C18, 0x00180C06, 0x18180C06, 0x00060C18, 0xFF3C6600, 0x0000663C, 0x3F0C0C00, 0x00000C0C, + 0x00000000, 0x060C0C00, 0x3F000000, 0x00000000, 0x00000000, 0x000C0C00, 0x0C183060, 0x00010306, + 0x7B73633E, 0x003E676F, 0x0C0C0E0C, 0x003F0C0C, 0x1C30331E, 0x003F3306, 0x1C30331E, 0x001E3330, + 0x33363C38, 0x0078307F, 0x301F033F, 0x001E3330, 0x1F03061C, 0x001E3333, 0x1830333F, 0x000C0C0C, + 0x1E33331E, 0x001E3333, 0x3E33331E, 0x000E1830, 0x000C0C00, 0x000C0C00, 0x000C0C00, 0x060C0C00, + 0x03060C18, 0x00180C06, 0x003F0000, 0x00003F00, 0x30180C06, 0x00060C18, 0x1830331E, 0x000C000C, + 0x7B7B633E, 0x001E037B, 0x33331E0C, 0x0033333F, 0x3E66663F, 0x003F6666, 0x0303663C, 0x003C6603, + 0x6666361F, 0x001F3666, 0x1E16467F, 0x007F4616, 0x1E16467F, 0x000F0616, 0x0303663C, 0x007C6673, + 0x3F333333, 0x00333333, 0x0C0C0C1E, 0x001E0C0C, 0x30303078, 0x001E3333, 0x1E366667, 0x00676636, + 0x0606060F, 0x007F6646, 0x7F7F7763, 0x0063636B, 0x7B6F6763, 0x00636373, 0x6363361C, 0x001C3663, + 0x3E66663F, 0x000F0606, 0x3333331E, 0x00381E3B, 0x3E66663F, 0x00676636, 0x0C06331E, 0x001E3318, + 0x0C0C2D3F, 0x001E0C0C, 0x33333333, 0x003F3333, 0x33333333, 0x000C1E33, 0x6B636363, 0x0063777F, + 0x1C366363, 0x0063361C, 0x1E333333, 0x001E0C0C, 0x1831637F, 0x007F664C, 0x0606061E, 0x001E0606, + 0x180C0603, 0x00406030, 0x1818181E, 0x001E1818, 0x63361C08, 0x00000000, 0x00000000, 0xFF000000, + 0x00180C0C, 0x00000000, 0x301E0000, 0x006E333E, 0x3E060607, 0x003B6666, 0x331E0000, 0x001E3303, + 0x3E303038, 0x006E3333, 0x331E0000, 0x001E033F, 0x0F06361C, 0x000F0606, 0x336E0000, 0x1F303E33, + 0x6E360607, 0x00676666, 0x0C0E000C, 0x001E0C0C, 0x30300030, 0x1E333330, 0x36660607, 0x0067361E, + 0x0C0C0C0E, 0x001E0C0C, 0x7F330000, 0x00636B7F, 0x331F0000, 0x00333333, 0x331E0000, 0x001E3333, + 0x663B0000, 0x0F063E66, 0x336E0000, 0x78303E33, 0x6E3B0000, 0x000F0666, 0x033E0000, 0x001F301E, + 0x0C3E0C08, 0x00182C0C, 0x33330000, 0x006E3333, 0x33330000, 0x000C1E33, 0x6B630000, 0x00367F7F, + 0x36630000, 0x0063361C, 0x33330000, 0x1F303E33, 0x193F0000, 0x003F260C, 0x070C0C38, 0x00380C0C, + 0x00181818, 0x00181818, 0x380C0C07, 0x00070C0C, 0x00003B6E, 0x00000000, 0x361C0800, 0x007F6363, + 0x3303331E, 0x1E30181E, 0x33003300, 0x007E3333, 0x331E0038, 0x001E033F, 0x603CC37E, 0x00FC667C, + 0x301E0033, 0x007E333E, 0x301E0007, 0x007E333E, 0x301E0C0C, 0x007E333E, 0x031E0000, 0x1C301E03, + 0x663CC37E, 0x003C067E, 0x331E0033, 0x001E033F, 0x331E0007, 0x001E033F, 0x0C0E0033, 0x001E0C0C, + 0x181C633E, 0x003C1818, 0x0C0E0007, 0x001E0C0C, 0x63361C63, 0x0063637F, 0x1E000C0C, 0x00333F33, + 0x063F0038, 0x003F061E, 0x30FE0000, 0x00FE33FE, 0x7F33367C, 0x00733333, 0x1E00331E, 0x001E3333, + 0x1E003300, 0x001E3333, 0x1E000700, 0x001E3333, 0x3300331E, 0x007E3333, 0x33000700, 0x007E3333, + 0x33003300, 0x1F303E33, 0x663C18C3, 0x00183C66, 0x33330033, 0x001E3333, 0x037E1818, 0x18187E03, + 0x0F26361C, 0x003F6706, 0x3F1E3333, 0x0C0C3F0C, 0x5F33331F, 0xE363F363, 0x3C18D870, 0x0E1B1818, + 0x301E0038, 0x007E333E, 0x0C0E001C, 0x001E0C0C, 0x1E003800, 0x001E3333, 0x33003800, 0x007E3333, + 0x1F001F00, 0x00333333, 0x3733003F, 0x00333B3F, 0x7C36363C, 0x00007E00, 0x1C36361C, 0x00003E00, + 0x060C000C, 0x001E3303, 0x3F000000, 0x00000303, 0x3F000000, 0x00003030, 0x7B3363C3, 0xF03366CC, + 0xDB3363C3, 0xC0F3F6EC, 0x18001818, 0x00181818, 0x3366CC00, 0x0000CC66, 0xCC663300, 0x00003366, + 0x11441144, 0x11441144, 0x55AA55AA, 0x55AA55AA, 0x77DBEEDB, 0x77DBEEDB, 0x18181818, 0x18181818, + 0x18181818, 0x1818181F, 0x181F1818, 0x1818181F, 0x6C6C6C6C, 0x6C6C6C6F, 0x00000000, 0x6C6C6C7F, + 0x181F0000, 0x1818181F, 0x606F6C6C, 0x6C6C6C6F, 0x6C6C6C6C, 0x6C6C6C6C, 0x607F0000, 0x6C6C6C6F, + 0x606F6C6C, 0x0000007F, 0x6C6C6C6C, 0x0000007F, 0x181F1818, 0x0000001F, 0x00000000, 0x1818181F, + 0x18181818, 0x000000F8, 0x18181818, 0x000000FF, 0x00000000, 0x181818FF, 0x18181818, 0x181818F8, + 0x00000000, 0x000000FF, 0x18181818, 0x181818FF, 0x18F81818, 0x181818F8, 0x6C6C6C6C, 0x6C6C6CEC, + 0x0CEC6C6C, 0x000000FC, 0x0CFC0000, 0x6C6C6CEC, 0x00EF6C6C, 0x000000FF, 0x00FF0000, 0x6C6C6CEF, + 0x0CEC6C6C, 0x6C6C6CEC, 0x00FF0000, 0x000000FF, 0x00EF6C6C, 0x6C6C6CEF, 0x00FF1818, 0x000000FF, + 0x6C6C6C6C, 0x000000FF, 0x00FF0000, 0x181818FF, 0x00000000, 0x6C6C6CFF, 0x6C6C6C6C, 0x000000FC, + 0x18F81818, 0x000000F8, 0x18F80000, 0x181818F8, 0x00000000, 0x6C6C6CFC, 0x6C6C6C6C, 0x6C6C6CFF, + 0x18FF1818, 0x181818FF, 0x18181818, 0x0000001F, 0x00000000, 0x181818F8, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00000000, 0xFFFFFFFF, 0x0F0F0F0F, 0x0F0F0F0F, 0xF0F0F0F0, 0xF0F0F0F0, 0xFFFFFFFF, 0x00000000, + 0x3B6E0000, 0x006E3B13, 0x1F331E00, 0x03031F33, 0x03333F00, 0x00030303, 0x36367F00, 0x00363636, + 0x0C06333F, 0x003F3306, 0x1B7E0000, 0x000E1B1B, 0x66666600, 0x03063E66, 0x183B6E00, 0x00181818, + 0x331E0C3F, 0x3F0C1E33, 0x7F63361C, 0x001C3663, 0x6363361C, 0x00773636, 0x3E180C38, 0x001E3333, + 0xDB7E0000, 0x00007EDB, 0xDB7E3060, 0x03067EDB, 0x1F03061C, 0x001C0603, 0x3333331E, 0x00333333, + 0x3F003F00, 0x00003F00, 0x0C3F0C0C, 0x003F000C, 0x0C180C06, 0x003F0006, 0x0C060C18, 0x003F0018, + 0x18D8D870, 0x18181818, 0x18181818, 0x0E1B1B18, 0x3F000C0C, 0x000C0C00, 0x003B6E00, 0x00003B6E, + 0x1C36361C, 0x00000000, 0x18000000, 0x00000018, 0x00000000, 0x00000018, 0x303030F0, 0x383C3637, + 0x3636361E, 0x00000036, 0x060C180E, 0x0000001E, 0x3C3C0000, 0x00003C3C, 0x00000000, 0x00000000, +]; diff --git a/src/io/sio.rs b/src/bak/sio.rs similarity index 100% rename from src/io/sio.rs rename to src/bak/sio.rs index c6296e2..a96f046 100644 --- a/src/io/sio.rs +++ b/src/bak/sio.rs @@ -95,6 +95,17 @@ newtype!( u16 ); +newtype_enum! { + /// General IO modes. + IoMode = u16, + /// * IO disabled + Disabled = 0, + /// * General Purpose IO + GPIO = 2, + /// * JoyBus mode + JoyBus = 3, +} + #[allow(missing_docs)] impl IoControlSetting { phantom_fields! { @@ -112,17 +123,6 @@ impl IoControlSetting { } } -newtype_enum! { - /// General IO modes. - IoMode = u16, - /// * IO disabled - Disabled = 0, - /// * General Purpose IO - GPIO = 2, - /// * JoyBus mode - JoyBus = 3, -} - /// Empty struct that implements embedded_hal traits. #[cfg(feature = "serial")] #[derive(Clone)] diff --git a/examples/uart_echo.rs b/src/bak/uart_echo.rs similarity index 100% rename from examples/uart_echo.rs rename to src/bak/uart_echo.rs diff --git a/src/base.rs b/src/base.rs deleted file mode 100644 index ed07b9e..0000000 --- a/src/base.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Holds fundamental types/ops which the rest of the crate is built on. -//! -//! These things should probably, in time, be extracted out into their own -//! crate (or convert to using a robust existing crate). - -pub mod fixed_point; -//pub(crate) use self::fixed_point::*; diff --git a/src/bios.rs b/src/bios.rs index 3b4144a..29c518f 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -1,627 +1,629 @@ -//! This module contains wrappers for all GBA BIOS function calls. +#![allow(non_snake_case)] + +//! The BIOS includes several System Call Functions which can be accessed by SWI +//! instructions. //! -//! 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. +//! * All BIOS functions clobber `r0`, `r1`, and `r3`. +//! * Some functions also use `r2` as an input register. +//! * All other registers are unaffected. -#![cfg_attr(not(target_arch = "arm"), allow(unused_variables))] +// Note(Lokathor): This makes intra-doc links work. +#[allow(unused)] +use crate::prelude::*; -use super::*; -use io::irq::IrqFlags; - -//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The -//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 -//I guess. - -/// (`swi 0x00`) SoftReset the device. +/// (`swi 0x00`) Performs a "soft reset" of the device. /// -/// This function does not ever return. +/// Loads `r14` based on the `u8` value at `0x0300_7FFA`: +/// * zero: `0x0800_0000` (ROM) +/// * non-zero: `0x0200_0000` (EWRAM) /// -/// 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`. +/// Then resets the following memory and registers: +/// * `0x300_7E00` ..= `0x300_7FFF`: zeroed +/// * `r0` ..= `r12`: zeroed +/// * `sp_usr`: `0x300_7F00` +/// * `sp_irq`: `0x300_7FA0` +/// * `sp_svc`: `0x300_7FE0` +/// * `lr_svc`, `lr_irq` : zeroed +/// * `spsr_svc`, `spsr_irq`: zeroed /// -/// * 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() -> ! { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - asm!("swi 0x00", options(noreturn)) - } +/// Then jumps to the `r14` address. This never returns. +pub unsafe fn SoftReset() -> ! { + asm!("swi 0x00", options(noreturn)) } -/// (`swi 0x01`) RegisterRamReset. +/// (`swi 0x01`) Resets RAM and/or IO registers /// -/// 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 object 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 LSB 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 EWRAM and clear -/// that. If you do then you return to nothing and have a bad time. -#[inline(always)] -pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - asm!("swi 0x01", in("r0") flags.0); - } -} -newtype! { - /// Flags for use with `register_ram_reset`. - RegisterRAMResetFlags, u8 -} -#[allow(missing_docs)] -impl RegisterRAMResetFlags { - phantom_fields! { - self.0: u8, - ewram: 0, - iwram: 1, - palram: 2, - vram: 3, - oam: 4, - sio: 5, - sound: 6, - other_io: 7, - } +/// * Note that if the IWRAM flag is used it doesn't reset the final `0x200` +/// bytes of IWRAM. Instead, those bytes are reset during a call to the +/// [`SoftReset`] function. +/// * BIOS Bug: Data in `SIODATA32` is always destroyed, even if the `sio` flag +/// is not set. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn RegisterRamReset(flags: crate::mmio_types::ResetFlags) { + asm!("swi 0x01", + inlateout("r0") flags.0 => _, + out("r1") _, + out("r3") _, + options(nomem, nostack, preserves_flags) + ) } -/// (`swi 0x02`) Halts the CPU until an interrupt occurs. +/// (`swi 0x02`) Halts the CPU until an interrupt request occurs. /// -/// Components _other than_ the CPU continue to function. Halt mode ends when -/// any enabled interrupt triggers. -#[inline(always)] -pub fn halt() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x02"); - } - } +/// The CPU is placed into low-power mode, while other parts (video, sound, +/// timers, serial, keypad) continue to operate. This mode only terminates when +/// one of the enabled interrupts is requested. +/// +/// This halt state uses [`IE`] to determine what interrupts to allow, but the +/// [`IME`] value is ignored (interrupts can occur even if `IME` is `false`). +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn Halt() { + asm!("swi 0x02", + out("r0") _, + out("r1") _, + out("r3") _, + options(nomem, nostack, preserves_flags) + ) } -/// (`swi 0x03`) Stops the CPU as well as most other components. +/// (`swi 0x03`) Puts the CPU in a *very* low power state. /// -/// Stop mode must be stopped by an interrupt, but can _only_ be stopped by a -/// Keypad, Game Pak, or General-Purpose-SIO interrupt. +/// While stopped, the CPU, Sound, Video, SIO-shift-clock, DMA, and Timers are +/// all disabled. /// -/// Before going into stop mode you should manually disable video and sound (or -/// they will continue to consume power), and you should also disable any other -/// optional externals such as rumble and infra-red. -#[inline(always)] -pub fn stop() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x03"); - } - } +/// The system can return from this state only if there is an interrupt from the +/// Keypad, Game Pak, or General-Purpose-SIO. +/// +/// Before calling Stop you are advised to disable the Video to reduce battery +/// usage, otherwise it just freezes. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn Stop() { + asm!("swi 0x03", + out("r0") _, + out("r1") _, + out("r3") _, + options(nomem, nostack, preserves_flags) + ) } -/// (`swi 0x04`) "IntrWait", similar to halt but with more options. +/// (`swi 0x04`) "Interrupt Wait". /// -/// * The first argument controls if you want to ignore all current flags and -/// wait until a new flag is set. -/// * The second argument is what flags you're waiting on (same format as the -/// [`IE`](io::irq::IE)/[`IF`](io::irq::IF) registers). +/// This is similar to [`Halt`], but when an interrupt does occur this function +/// will automatically return the CPU to halt state unless the interrupt is one +/// of the interrupt types specified by `flags`. /// -/// If you're trying to handle more than one interrupt at once this has less -/// overhead than calling `halt` over and over. +/// If you set `discard_current_flags` then any pending interrupts are cleared +/// and this function will wait until a new flag is set. Otherwise the function +/// will return immediately if you request a wait for an interrupt that's +/// already pending. /// -/// When using this routing your interrupt handler MUST update the BIOS -/// Interrupt Flags at [`BIOS_IF`](io::irq::BIOS_IF) in addition to -/// the usual interrupt acknowledgement. -#[inline(always)] -pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!( - "swi 0x04", - in("r0") ignore_current_flags as u8, - in("r1") target_flags.0, - ); - } - } +/// When handling an interrupt through this function you must perform the normal +/// acknowledgement using [`IRQ_ACKNOWLEDGE`] and **also** acknowledge using +/// [`INTR_WAIT_ACKNOWLEDGE`]. +/// +/// **Caution:** This function automatically also sets [`IME`] to `true`. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn IntrWait(discard_current_flags: bool, flags: crate::mmio_types::InterruptFlags) { + // Note(Lokathor): we don't mark this preserves_flags because the user's IRQ + // handler gets called which might end up trashing the flags. + asm!("swi 0x03", + inlateout("r0") discard_current_flags as u8 => _, + inlateout("r1") flags.0 => _, + out("r3") _, + options(nomem, nostack) + ) } -/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait. +/// (`swi 0x05`) "VBlank Interrupt Wait" /// -/// This is as per `interrupt_wait(true, IrqFlags::new().with_vblank(true))` -/// (aka "wait for a new vblank"). You must follow the same guidelines that -/// [`interrupt_wait`](interrupt_wait) outlines. -#[inline(always)] -pub fn vblank_interrupt_wait() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!( - "swi 0x05", - out("r0") _, - out("r1") _, - ); - } - } +/// Waits for the next VBlank interrupt. +/// +/// This function is just shorthand for the following: +/// ```no_run +/// # use crate::prelude::*; +/// const VBLANK_IRQ: InterruptFlags = InterruptFlags::new().with_vblank(true); +/// IntrWait(true, VBLANK_IRQ) +/// ``` +/// See [`IntrWait`] +/// +/// **Note:** Because this uses `IntrWait`, [`IME`] will be set to `true` +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn VBlankIntrWait() { + // Note(Lokathor): we don't mark this preserves_flags because the user's IRQ + // handler gets called which might end up trashing the flags. + asm!( + "swi 0x05", + out("r0") _, + out("r1") _, + out("r3") _, + options(nomem, nostack) + ) } -/// (`swi 0x06`) Software Division and Remainder. +/// (`swi 0x06`) Performs `i32` division. /// -/// ## Panics -/// -/// If the denominator is 0. -#[inline(always)] -pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) { - assert!(denominator != 0); - #[cfg(not(target_arch = "arm"))] - { - (numerator / denominator, numerator % denominator) - } - #[cfg(target_arch = "arm")] - { - let div_out: i32; - let rem_out: i32; - unsafe { - asm!( - "swi 0x06", - inout("r0") numerator => div_out, - inout("r1") denominator => rem_out, - out("r3") _, - options(nostack, nomem), - ); - } - (div_out, rem_out) +/// **Outputs:** `(n/d, n%d, (n/d).unsigned_abs())` +#[inline] +#[must_use] +#[instruction_set(arm::t32)] +pub fn Div(number: i32, denominator: core::num::NonZeroI32) -> (i32, i32, u32) { + let d: i32; + let m: i32; + let abs_d: u32; + unsafe { + asm!("swi 0x06", + inlateout("r0") number => d, + inlateout("r1") denominator.get() => m, + lateout("r3") abs_d, + options(pure, nomem, nostack, preserves_flags), + ) } + (d, m, abs_d) } -/// As `div_rem`, keeping only the `div` output. -#[inline(always)] -pub fn div(numerator: i32, denominator: i32) -> i32 { - div_rem(numerator, denominator).0 +/// (`swi 0x08`) Square root of an integer value. +/// +/// To obtain as much fraction as possible, shift the input left by 2N bits to +/// get an output that is left shifted by N bits. +/// * sqrt(2) => 0 +/// * sqrt(2 << 30) => 1.41421 << 15 +#[inline] +#[instruction_set(arm::t32)] +pub fn Sqrt(number: u32) -> u16 { + let output: u32; + unsafe { + asm!("swi 0x08", + inlateout("r0") number => output, + out("r1") _, + out("r3") _, + options(pure, nomem, nostack, preserves_flags), + ) + } + output as u16 } -/// As `div_rem`, keeping only the `rem` output. -#[inline(always)] -pub fn rem(numerator: i32, denominator: i32) -> i32 { - div_rem(numerator, denominator).1 +/// (`swi 0x09`) Arc tangent +/// +/// The input and output have 14 fractional bits. +#[inline] +#[instruction_set(arm::t32)] +pub fn ArcTan(tan: i16) -> i16 { + let output; + unsafe { + asm!("swi 0x09", + inlateout("r0") tan => output, + out("r1") _, + out("r3") _, + options(pure, nomem, nostack, preserves_flags), + ) + } + output } -// (`swi 0x07`): We deliberately don't implement this one. It's the same as DIV -// but with reversed arguments, so it just runs 3 cycles slower as it does the -// swap. - -/// (`swi 0x08`) Integer square root. +/// (`swi 0x0A`) Arc tangent 2 /// -/// 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 { - #[cfg(not(target_arch = "arm"))] - { - 0 // TODO: simulate this properly when not on GBA - } - #[cfg(target_arch = "arm")] - { - let out: u32; - unsafe { - asm!( - "swi 0x08", - inout("r0") val => out, - out("r1") _, - out("r3") _, - options(pure, nomem), - ); - } - out as u16 +/// * The inputs have 14 fractional bits. +/// * The output range is `0 ..= u16::MAX`, reprisenting a portion of 2 PI. +#[inline] +#[instruction_set(arm::t32)] +pub fn ArcTan2(x: i16, y: i16) -> u16 { + let output; + unsafe { + asm!("swi 0x0A", + inlateout("r0") x => output, + in("r1") y, + out("r3") _, + options(pure, nomem, nostack, preserves_flags), + ) } + output } -/// (`swi 0x09`) Gives the arctangent of `theta`. +/// (`swi 0x0B`) Quickly copy/fill some memory. /// -/// The input format is 1 bit for sign, 1 bit for integral part, 14 bits for -/// fractional part. +/// * `src`: points to either `u16` or `u32` data. +/// * `dst`: points to the same type of data. +/// * `len_mode`: bitfield value: +/// * bits 0 ..= 20: the number of elements to copy/fill. +/// * bit 24: enable for fill, otherwise this is a copy. +/// * bit 26: enable for `u32` at a time, otherwise this uses `u16` at a time. /// -/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`. -#[inline(always)] -pub fn atan(theta: i16) -> i16 { - #[cfg(not(target_arch = "arm"))] - { - 0 // TODO: simulate this properly when not on GBA - } - #[cfg(target_arch = "arm")] - { - let out: i16; - unsafe { - asm!( - "swi 0x09", - inout("r0") theta => out, - out("r1") _, - out("r3") _, - options(pure, nomem), - ); - } - out - } +/// All pointers must be aligned to the appropriate type, and also valid for the +/// appropriate element count. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn CpuSet(src: *const core::ffi::c_void, dst: *mut core::ffi::c_void, len_mode: u32) { + asm!("swi 0x0B", + in("r0") src, + in("r1") dst, + in("r2") len_mode, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x0A`) Gives the atan2 of `y` over `x`. +/// (`swi 0x0C`) Quickly copy/fill some memory (most faster!) /// -/// The output `theta` value maps into the range `[0, 2pi)`, or `0 .. 2pi` if -/// you prefer Rust's range notation. +/// * `src` points to the data source. +/// * `dst` points to the data destination. +/// * `len_mode`: bitfield value: +/// * bits 0 ..= 20: the number of `u32` to copy/fill. +/// * bit 24: enable for fill, otherwise this is a copy. /// -/// `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 { - #[cfg(not(target_arch = "arm"))] - { - 0 // TODO: simulate this properly when not on GBA - } - #[cfg(target_arch = "arm")] - { - let out: u16; - unsafe { - asm!( - "swi 0x0A", - inout("r0") x => out, - in("r1") y, - out("r3") _, - options(pure, nomem), - ); - } - out - } +/// All pointers must be aligned. The length must be a multiple of 8. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn CpuFastSet(src: *const u32, dst: *mut u32, len_mode: u32) { + asm!("swi 0x0C", + in("r0") src, + in("r1") dst, + in("r2") len_mode, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x0B`) "CpuSet", `u16` memory copy. -/// -/// * `count` is the number of `u16` values to copy (20 bits or less) -/// * `fixed_source` argument, if true, turns this copying routine into a -/// filling routine. -/// -/// ## Safety -/// -/// * Both pointers must be aligned -#[inline(always)] -pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - let control = count + ((fixed_source as u32) << 24); - asm!( - "swi 0x0B", - in("r0") src, - in("r1") dest, - in("r2") control, - ); - } +#[repr(C)] +pub struct BgAffineSetSrc { + /// 8-bit fraction + pub origin_center_x: i32, + /// 8-bit fraction + pub origin_center_y: i32, + pub display_center_x: i16, + pub display_center_y: i16, + /// 8-bit fraction + pub scale_ratio_x: i16, + /// 8-bit fraction + pub scale_ratio_y: i16, + /// 8-bit fraction, range 0 to u16::MAX + pub angle_of_rotation: u16, +} +#[repr(C)] +pub struct BgAffineSetDst { + pub pa: i16, + pub pb: i16, + pub pc: i16, + pub pd: i16, + pub start_x_coordinate: i32, + pub start_y_coordinate: i32, } -/// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill. +/// (`swi 0x0E`) Calculates BG affine data. /// -/// * `count` is the number of `u32` values to copy (20 bits or less) -/// * `fixed_source` argument, if true, turns this copying routine into a -/// filling routine. +/// * `src`: Points to the start of a slice of [`BgAffineSetSrc`] +/// * `dst`: Points to the start of a slice of [`BgAffineSetDst`] +/// * `count`: The number of elements to process from `src` to `dst`. /// -/// ## Safety -/// -/// * Both pointers must be aligned -#[inline(always)] -pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - let control = count + ((fixed_source as u32) << 24) + (1 << 26); - asm!( - "swi 0x0B", - in("r0") src, - in("r1") dest, - in("r2") control, - ); - } +/// Both pointers must be aligned and valid for the length given. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn BgAffineSet(src: *const BgAffineSetSrc, dst: *mut BgAffineSetDst, count: usize) { + asm!("swi 0x0E", + in("r0") src, + in("r1") dst, + in("r2") count, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks. -/// -/// * The `count` value is the number of `u32` values to transfer (20 bits or -/// less), and it's rounded up to the nearest multiple of 8 words. -/// * The `fixed_source` argument, if true, turns this copying routine into a -/// filling routine. -/// -/// ## Safety -/// -/// * Both pointers must be aligned -#[inline(always)] -pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - let control = count + ((fixed_source as u32) << 24); - asm!( - "swi 0x0C", - in("r0") src, - in("r1") dest, - in("r2") control, - ); - } +#[repr(C)] +pub struct ObjAffineSetSrc { + /// 8-bit fraction + pub scale_ratio_x: i16, + /// 8-bit fraction + pub scale_ratio_y: i16, + /// 8-bit fraction, range 0 to u16::MAX + pub angle: u16, } -/// (`swi 0x0C`) "GetBiosChecksum" (Undocumented) +/// (`swi 0x0F`) Calculates OBJ affine data. /// -/// Though we usually don't cover undocumented functionality, this one can make -/// it into the crate. +/// Unlike with [`BgAffineSet`], this can optionally write the output data +/// directly into OAM (see below). /// -/// The function computes the checksum of the BIOS data. You should get either -/// `0xBAAE_187F` (GBA / GBA SP) or `0xBAAE_1880` (DS in GBA mode). If you get -/// some other value I guess you're probably running on an emulator that just -/// broke the fourth wall. -pub fn get_bios_checksum() -> u32 { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - let out: u32; - unsafe { - asm!( - "swi 0x0D", - out("r0") out, - options(pure, readonly), - ); - } - out - } +/// * `src`: points to the start of a slice of [`ObjAffineSetSrc`] values. +/// * `dst`: points to the start of the output location (`pa`). +/// * `count`: The number of `src` values to process to `dst`. +/// * `out_param_offset`: the number of bytes between *each field* of the output +/// data. +/// * Specify 2 if you want to output to an `[i16; 4]` or similar. +/// * Specify 8 if you want to output directly to OAM. +/// +/// The pointers must be valid for the count given, and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn ObjAffineSet( + src: *const ObjAffineSetSrc, + dst: *mut i16, + count: usize, + out_param_offset: usize, +) { + asm!("swi 0x0F", + in("r0") src, + in("r1") dst, + in("r2") count, + in("r3") out_param_offset, + options(nostack, preserves_flags), + ) } -// TODO: these things will require that we build special structs - -//BgAffineSet -//ObjAffineSet -//BitUnPack -//LZ77UnCompReadNormalWrite8bit -//LZ77UnCompReadNormalWrite16bit -//HuffUnCompReadNormal -//RLUnCompReadNormalWrite8bit -//Diff8bitUnFilterWrite8bit -//Diff8bitUnFilterWrite16bit -//Diff16bitUnFilter - -/// (`swi 0x19`) "SoundBias", adjusts the volume level to a new level. -/// -/// This increases or decreases the current level of the `SOUNDBIAS` register -/// (with short delays) until at the new target level. The upper bits of the -/// register are unaffected. -/// -/// The final sound level setting will be `level` * `0x200`. -pub fn sound_bias(level: u32) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x19", in("r0") level); - } - } +#[repr(C)] +pub struct UnpackInfo { + pub source_data_len_bytes: u16, + /// Supports 1, 2, 4, or 8 bit source elements. + pub source_unit_bit_width: u8, + /// Supports 1, 2, 4, 8, 16, or 32 destination elements. + pub destination_unit_bit_width: u8, + /// This field combines two purposes: + /// * bits 0 ..= 30: this value is added to all non-zero source units. + /// * bit 31: if this is set, add the above to all zero source units. + pub data_offset: u32, } -//SoundDriverInit - -/// (`swi 0x1B`) "SoundDriverMode", sets the sound driver operation mode. +/// (`swi 0x10`) Used to undo bit packing. /// -/// The `mode` input uses the following flags and bits: +/// * `src`: The start of the source bytes. +/// * `dst`: The start of the destination. +/// * `info`: Describes the unpacking to perform. /// -/// * Bits 0-6: Reverb value -/// * Bit 7: Reverb Enable -/// * Bits 8-11: Simultaneously-produced channel count (default=8) -/// * Bits 12-15: Master Volume (1-15, default=15) -/// * Bits 16-19: Playback Frequency Index (see below, default=4) -/// * Bits 20-23: "Final number of D/A converter bits (8-11 = 9-6bits, def. 9=8bits)" TODO: what the hek? -/// * Bits 24 and up: Not used -/// -/// The frequency index selects a frequency from the following array: -/// * 0: 5734 -/// * 1: 7884 -/// * 2: 10512 -/// * 3: 13379 -/// * 4: 15768 -/// * 5: 18157 -/// * 6: 21024 -/// * 7: 26758 -/// * 8: 31536 -/// * 9: 36314 -/// * 10: 40137 -/// * 11: 42048 -pub fn sound_driver_mode(mode: u32) { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x1B", in("r0") mode); - } - } -} -//TODO(lokathor): newtype this mode business. - -/// (`swi 0x1C`) "SoundDriverMain", main of the sound driver -/// -/// You should call `SoundDriverVSync` immediately after the vblank interrupt -/// fires. -/// -/// "After that, this routine is called after BG and OBJ processing is -/// executed." --what? -#[inline(always)] -pub fn sound_driver_main() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x1C"); - } - } +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn BitUnPack(src: *const u8, dst: *mut u32, info: &UnpackInfo) { + asm!("swi 0x10", + in("r0") src, + in("r1") dst, + in("r2") info, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x1D`) "SoundDriverVSync", resets the sound DMA. +/// (`swi 0x11`) LZ77 Decompression with 8-bit output. /// -/// The timing is critical, so you should call this _immediately_ after the -/// vblank interrupt (every 1/60th of a second). -#[inline(always)] -pub fn sound_driver_vsync() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x1D"); - } - } +/// Arguments +/// * `src`: pointer to the source region. The source region is prefixed with a +/// `u32` bitfield value that describes the decompression to perform. It's +/// then followed by the byte sequence to decompress. +/// * Prefix value: `output_data_size << 8 | (1 << 4) | (0)` +/// * Flags: 1 byte that specifies the types of the next 8 blocks (MSB to +/// LSB). +/// * Blocks: +/// * (0) Literal: Copy 1 byte from the source to the output. +/// * (1) Back Reference: Repeat `N+3` bytes from `BACK+1` bytes earlier in +/// the output. This uses the next two bytes from the source to describe +/// the back reference: +/// * first byte bits 0 ..= 3: most significant bits of `BACK` +/// * first byte bits 4 ..= 7: `N` +/// * second byte: least significant bits of `BACK` +/// * (So each `N` is 3 bits, and each `BACK` is 12 bits.) +/// * After 8 blocks there's another flag and then another 8 blocks. +/// * The overall size of the source data should be a multiple of 4 (pad with +/// 0 as necessary). +/// * `dst`: pointer to the destination region. +/// +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn LZ77UnCompReadNormalWrite8bit(src: *const u32, dst: *mut u8) { + asm!("swi 0x11", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x1E`) "SoundChannelClear", clears the direct sound channels and stops -/// the sound. +/// (`swi 0x12`) LZ77 Decompression with 16-bit output. /// -/// "This function may not operate properly when the library which expands the -/// sound driver feature is combined afterwards. In this case, do not use it." -/// --what? -#[inline(always)] -pub fn sound_channel_clear() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x1E"); - } - } +/// This is largely as per [`LZ77UnCompReadNormalWrite8bit`], but each output is +/// 16-bits, which means that `BACK` values of 0 will corrupt the process. This +/// puts a small constraint on the data compressor, but doesn't really affect +/// you when you're using this function to decompress some already-compressed data. +/// +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn LZ77UnCompReadNormalWrite16bit(src: *const u32, dst: *mut u16) { + asm!("swi 0x12", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) } -//MidiKey2Freq -//MultiBoot - -/// (`swi 0x28`) "SoundDriverVSyncOff", disables sound +/// (`swi 0x13`) Decompresses Huffman-encoded data. /// -/// If you can't use vblank interrupts to ensure that `sound_driver_vsync` is -/// called every 1/60th of a second for any reason you must use this function to -/// stop sound DMA. Otherwise the DMA will overrun its buffer and cause random -/// noise. -#[inline(always)] -pub fn sound_driver_vsync_off() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x28"); - } - } +/// * `src`: The source buffer. There's a `u32` header, a huffman tree, and then +/// a compressed bitstream. +/// * header (4 bytes): `(output_byte_count << 8) | (2 << 4) | +/// data_unit_bit_size`, the output bit size per data unit can be 4 or 8. +/// * tree size (1 byte): the number of bytes in the tree table. +/// * tree table (up to 255 bytes): a list of 8-bit nodes, starting with the +/// root node. +/// * root node and non-data child nodes (1 byte): +/// * bits 0 ..= 5: offset to next child node. +/// * next_child0: (CurrentAddr AND NOT 1)+Offset*2+2 +/// * next_child1: as above +1 +/// * bit 6: node1 end flag (1 = next node is data) +/// * bit 7: node0 end flag (1 = next node is data) +/// * data nodes (1 byte): +/// * the literal value to output. If the output unit size is less than 8 +/// bits at a time the upper bits of the literal should be 0. +/// * compressed bitstream (stored as a series of `u32` values). The node bits +/// are stored in each `u32` starting from the high bit. +/// * `dst`: The output buffer. +/// +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn HuffUnCompReadNormal(src: *const u32, dst: *mut u32) { + asm!("swi 0x13", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) } -/// (`swi 0x29`) "SoundDriverVSyncOn", enables sound that was stopped by -/// `sound_driver_vsync_off`. +/// (`swi 0x14`) Expands run-length compressed data, outputting as 8-bit units. /// -/// Restarts sound DMA system. After restarting the sound you must have a vblank -/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second. -#[inline(always)] -pub fn sound_driver_vsync_on() { - #[cfg(not(target_arch = "arm"))] - { - unimplemented!("This function is not supported on this target.") - } - #[cfg(target_arch = "arm")] - { - unsafe { - asm!("swi 0x29"); - } - } +/// * `src`: The source buffer. There's a `u32` header, and then a loop of +/// "flag" and then "data" bytes until the end of the stream. +/// * header (4 bytes): `(output_byte_count << 8) | (3 << 4) | 0` +/// * flag byte: +/// * bits 0 ..= 6: expanded data length, uncompressed N-1, compressed N-3. +/// * bit 7: 0=uncompressed, 1=compressed +/// * data byte: N uncompressed bytes or 1 compressed byte repeated N times. +/// * `dst`: The output buffer. +/// +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn RLUnCompReadNormalWrite8bit(src: *const u32, dst: *mut u8) { + asm!("swi 0x14", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) } + +/// (`swi 0x15`) Expands run-length compressed data, outputting as 16-bit units. +/// +/// This is like [`RLUnCompReadNormalWrite8bit`] but outputs in 16-bit units, so +/// it's suitable for use with VRAM. +/// +/// All pointers must be valid for the correct memory spans and aligned. +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn RLUnCompReadNormalWrite16bit(src: *const u32, dst: *mut u16) { + asm!("swi 0x15", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) +} + +/// (`swi 0x16`) Performs an "unfilter" on 8-bit data units. +/// +/// An unfiltering converts a starting value and a series of delta values into +/// the appropriate totals. +/// * Filtered: 10, +1, +1, +1, +1, +5, +5, ... +/// * Unfiltered: 10, 11, 12, 13, 14, 19, 24, ... +/// +/// This is not itself a compression technique, but it's far easier to compress +/// the filtered form of data in some cases, so this is often used in +/// *combination* with other compression techniques. +/// +/// Arguments +/// * `src`: pointer to the source region. The source region is prefixed with a +/// `u32` bitfield value that describes the unfiltering to perform. It's then +/// followed by the bytes to unfilter. +/// * Prefix value: `element_count << 8 | (8 << 4) | (1)` +/// * `dst`: pointer to the destination region. +/// +/// Note that, because this uses 8-bit writes, it cannot output correctly to +/// VRAM. +/// +/// The source pointer must be aligned to 4 (the header is read as a `u32`), and +/// both pointers must be valid for the correct span: +/// * `src`: `element_count` + 4 bytes +/// * `dst`: `element_count` bytes +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn Diff8bitUnFilterWrite8bit(src: *const u8, dst: *mut u32) { + asm!("swi 0x16", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) +} + +/// (`swi 0x17`) Performs an "unfilter" on 8-bit data units, using 16-bit +/// output. +/// +/// This is *very close* to [`Diff8bitUnFilterWrite8bit`] except that the output +/// is 16-bits per element. +/// +/// Arguments +/// * `src`: pointer to the source region. The source region is prefixed with a +/// `u32` bitfield value that describes the unfiltering to perform. It's then +/// followed by the bytes to unfilter. +/// * Prefix value: `element_count << 8 | (8 << 4) | (1)` +/// * `dst`: pointer to the destination region. +/// +/// Because this outputs with 16-bit writes, it is suitable for use with VRAM. +/// +/// The source pointer must be aligned to 4 (the header is read as a `u32`), and +/// both pointers must be valid for the correct span: +/// * `src`: `element_count` + 4 bytes +/// * `dst`: `element_count` * 2 bytes +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn Diff8bitUnFilterWrite16bit(src: *const u8, dst: *mut u16) { + asm!("swi 0x17", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) +} + +/// (`swi 0x18`) Performs an "unfilter" on 16-bit data units. +/// +/// This is *very close* to [`Diff8bitUnFilterWrite8bit`] except that the output +/// is 16-bits per element and the prefix is different. +/// +/// Arguments +/// * `src`: pointer to the source region. The source region is prefixed with a +/// `u32` bitfield value that describes the unfiltering to perform. It's then +/// followed by the bytes to unfilter. +/// * Prefix value: `element_count << 8 | (8 << 4) | (2)` +/// * `dst`: pointer to the destination region. +/// +/// Because this outputs with 16-bit writes, it is suitable for use with VRAM. +/// +/// The source pointer must be aligned to 4 (the header is read as a `u32`), and +/// both pointers must be valid for the correct span: +/// * `src`: (`element_count` * 2) + 4 bytes +/// * `dst`: `element_count` * 2 bytes +#[inline] +#[instruction_set(arm::t32)] +pub unsafe fn Diff16bitUnFilter(src: *const u16, dst: *mut u16) { + asm!("swi 0x18", + in("r0") src, + in("r1") dst, + out("r3") _, + options(nostack, preserves_flags), + ) +} + +// TODO: MidiKey2Freq (1F) + +// TODO: SoundBias (19) + +// TODO: SoundChannelClear (1E) + +// TODO: SoundDriverInit (1A) + +// TODO: SoundDriverMain (1C) + +// TODO: SoundDriverMode (1B) + +// TODO: SoundDriverVSync (1D) + +// TODO: MultiBoot (25) + +// TODO: SoundDriverVSyncOff (28) + +// TODO: SoundDriverVSyncOn (29) diff --git a/src/debug.rs b/src/debug.rs index fc6a48b..85fbb62 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -3,12 +3,98 @@ //! This is the underlying implementation behind the various print macros in //! the gba crate. It currently supports the latest versions of mGBA and NO$GBA. -use crate::sync::{InitOnce, RawMutex, Static}; +use crate::{ + prelude::*, + sync::{InitOnce, RawMutex, Static}, +}; use core::fmt::{Arguments, Error}; +use voladdress::*; pub mod mgba; pub mod nocash; +/// Delivers a fatal message to the emulator debug output, and crashes +/// the the game. +/// +/// This works basically like `println`. You should avoid null ASCII values. +/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. +/// +/// This has no effect outside of a supported emulator. +#[macro_export] +macro_rules! fatal { + ($($arg:tt)*) => {{ + use $crate::debug; + if !debug::is_debugging_disabled() { + debug::debug_print(debug::DebugLevel::Fatal, &format_args!($($arg)*)).ok(); + } + debug::crash() + }}; +} + +/// Delivers a error message to the emulator debug output. +/// +/// This works basically like `println`. You should avoid null ASCII values. +/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. +/// +/// This has no effect outside of a supported emulator. +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => {{ + use $crate::debug; + if !debug::is_debugging_disabled() { + debug::debug_print(debug::DebugLevel::Error, &format_args!($($arg)*)).ok(); + } + }}; +} + +/// Delivers a warning message to the emulator debug output. +/// +/// This works basically like `println`. You should avoid null ASCII values. +/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. +/// +/// This has no effect outside of a supported emulator. +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => {{ + use $crate::debug; + if !debug::is_debugging_disabled() { + debug::debug_print(debug::DebugLevel::Warning, &format_args!($($arg)*)).ok(); + } + }}; +} + +/// Delivers an info message to the emulator debug output. +/// +/// This works basically like `println`. You should avoid null ASCII values. +/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. +/// +/// This has no effect outside of a supported emulator. +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => {{ + use $crate::debug; + if !debug::is_debugging_disabled() { + debug::debug_print(debug::DebugLevel::Info, &format_args!($($arg)*)).ok(); + } + }}; +} + +/// Delivers a debug message to the emulator debug output. +/// +/// This works basically like `println`. You should avoid null ASCII values. +/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. +/// +/// This has no effect outside of a supported emulator. +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => {{ + use $crate::debug; + if !debug::is_debugging_disabled() { + debug::debug_print(debug::DebugLevel::Debug, &format_args!($($arg)*)).ok(); + } + }}; +} + /// A cross-emulator debug level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] @@ -99,26 +185,20 @@ pub fn is_debugging_disabled() -> bool { /// This is used to implement fatal errors outside of mGBA. #[inline(never)] pub fn crash() -> ! { - #[cfg(all(target_vendor = "nintendo", target_env = "agb"))] - { - IME.write(IrqEnableSetting::IRQ_NO); - unsafe { - // Stop all ongoing DMAs just in case. - DMA0::set_control(DMAControlSetting::new()); - DMA1::set_control(DMAControlSetting::new()); - DMA2::set_control(DMAControlSetting::new()); - DMA3::set_control(DMAControlSetting::new()); + unsafe { + IME.write(false); + // Stop all ongoing DMAs just in case. + DMA0CNT_H.write(DmaControl::new()); + DMA1CNT_H.write(DmaControl::new()); + DMA2CNT_H.write(DmaControl::new()); + DMA3CNT_H.write(DmaControl::new()); - // Writes the halt call back to memory - // - // we use an infinite loop in RAM just to make sure removing the - // Game Pak doesn't break this crash loop. - let target = VolAddress::::new(0x03000000); - target.write(0xe7fe); // assembly instruction: `loop: b loop` - core::mem::transmute::<_, extern "C" fn() -> !>(0x03000001)() - } + // Writes the halt call back to memory + // + // we use an infinite loop in RAM just to make sure removing the + // Game Pak doesn't break this crash loop. + let target = VolAddress::::new(0x03000000); + target.write(0xE7FE); // thumb assembly instruction: `loop: b loop` + core::mem::transmute::<_, extern "C" fn() -> !>(0x03000001)() } - - #[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))] - loop {} } diff --git a/src/ewram.rs b/src/ewram.rs deleted file mode 100644 index 9f37006..0000000 --- a/src/ewram.rs +++ /dev/null @@ -1 +0,0 @@ -//! Module for External Work RAM (`EWRAM`). diff --git a/src/fixed_point.rs b/src/fixed_point.rs deleted file mode 100644 index 249ed91..0000000 --- a/src/fixed_point.rs +++ /dev/null @@ -1,256 +0,0 @@ -#![allow(non_camel_case_types)] -// Note(Lokathor): NOT CURRENTLY USED. -// -// If we want this in the future we can convert it to const generics. - -//! Module for fixed point math types and operations. - -use core::{ - marker::PhantomData, - ops::{Add, Div, Mul, Neg, Shl, Shr, Sub}, -}; -use typenum::{consts::False, marker_traits::Unsigned, type_operators::IsEqual, U8}; - -/// Fixed point `T` value with `F` fractional bits. -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct Fx { - num: T, - phantom: PhantomData, -} - -impl Fx { - /// Uses the provided value directly. - pub fn from_raw(r: T) -> Self { - Fx { num: r, phantom: PhantomData } - } - - /// Unwraps the inner value. - pub fn into_raw(self) -> T { - self.num - } - - /// Casts the base type, keeping the fractional bit quantity the same. - pub fn cast_inner Z>(self, op: C) -> Fx { - Fx { num: op(self.num), phantom: PhantomData } - } -} - -impl, F: Unsigned> Add for Fx { - type Output = Self; - fn add(self, rhs: Fx) -> Self::Output { - Fx { num: self.num + rhs.num, phantom: PhantomData } - } -} - -impl, F: Unsigned> Sub for Fx { - type Output = Self; - fn sub(self, rhs: Fx) -> Self::Output { - Fx { num: self.num - rhs.num, phantom: PhantomData } - } -} - -impl, F: Unsigned> Shl for Fx { - type Output = Self; - fn shl(self, rhs: u32) -> Self::Output { - Fx { num: self.num << rhs, phantom: PhantomData } - } -} - -impl, F: Unsigned> Shr for Fx { - type Output = Self; - fn shr(self, rhs: u32) -> Self::Output { - Fx { num: self.num >> rhs, phantom: PhantomData } - } -} - -impl, F: Unsigned> Neg for Fx { - type Output = Self; - fn neg(self) -> Self::Output { - Fx { num: -self.num, phantom: PhantomData } - } -} - -macro_rules! fixed_point_methods { - ($t:ident) => { - impl Fx<$t, F> { - /// Gives the smallest positive non-zero value. - pub fn precision() -> Self { - Fx { num: 1, phantom: PhantomData } - } - - /// Makes a value with the integer part shifted into place. - pub fn from_int_part(i: $t) -> Self { - Fx { num: i << F::U8, phantom: PhantomData } - } - - /// Changes the fractional bit quantity, keeping the base type the same. - pub fn adjust_fractional_bits>(self) -> Fx<$t, Y> { - let leftward_movement: i32 = Y::to_i32() - F::to_i32(); - Fx { - num: if leftward_movement > 0 { - self.num << leftward_movement - } else { - self.num >> (-leftward_movement) - }, - phantom: PhantomData, - } - } - } - }; -} - -fixed_point_methods! {u8} -fixed_point_methods! {i8} -fixed_point_methods! {i16} -fixed_point_methods! {u16} -fixed_point_methods! {i32} -fixed_point_methods! {u32} - -macro_rules! fixed_point_signed_multiply { - ($t:ident) => { - impl Mul for Fx<$t, F> { - type Output = Self; - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, rhs: Fx<$t, F>) -> Self::Output { - let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32); - if pre_shift < 0 { - if pre_shift == core::i32::MIN { - Fx { num: core::$t::MIN, phantom: PhantomData } - } else { - Fx { num: (-((-pre_shift) >> F::U8)) as $t, phantom: PhantomData } - } - } else { - Fx { num: (pre_shift >> F::U8) as $t, phantom: PhantomData } - } - } - } - }; -} - -fixed_point_signed_multiply! {i8} -fixed_point_signed_multiply! {i16} -fixed_point_signed_multiply! {i32} - -macro_rules! fixed_point_unsigned_multiply { - ($t:ident) => { - impl Mul for Fx<$t, F> { - type Output = Self; - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, rhs: Fx<$t, F>) -> Self::Output { - Fx { - num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t, - phantom: PhantomData, - } - } - } - }; -} - -fixed_point_unsigned_multiply! {u8} -fixed_point_unsigned_multiply! {u16} -fixed_point_unsigned_multiply! {u32} - -macro_rules! fixed_point_signed_division { - ($t:ident) => { - impl Div for Fx<$t, F> { - type Output = Self; - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, rhs: Fx<$t, F>) -> Self::Output { - let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); - let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); - Fx { num: divide_result as $t, phantom: PhantomData } - } - } - }; -} - -fixed_point_signed_division! {i8} -fixed_point_signed_division! {i16} -fixed_point_signed_division! {i32} - -macro_rules! fixed_point_unsigned_division { - ($t:ident) => { - impl Div for Fx<$t, F> { - type Output = Self; - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, rhs: Fx<$t, F>) -> Self::Output { - let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); - let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); - Fx { num: divide_result as $t, phantom: PhantomData } - } - } - }; -} - -fixed_point_unsigned_division! {u8} -fixed_point_unsigned_division! {u16} -fixed_point_unsigned_division! {u32} - -/// Alias for an `i16` fixed point value with 8 fractional bits. -pub type fx8_8 = Fx; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add() { - use typenum::U4; - let one = Fx::::from_int_part(1); - let two = Fx::::from_int_part(2); - assert!(one + one == two) - } - - #[test] - fn test_sub() { - use typenum::U4; - let one = Fx::::from_int_part(1); - let two = Fx::::from_int_part(2); - assert!(two - one == one) - } - - #[test] - fn test_shl() { - use typenum::U4; - let one = Fx::::from_int_part(1); - let two = Fx::::from_int_part(2); - assert!(one << 1 == two) - } - - #[test] - fn test_shr() { - use typenum::U4; - let one = Fx::::from_int_part(1); - let two = Fx::::from_int_part(2); - assert!(two >> 1 == one) - } - - #[test] - fn test_neg() { - use typenum::U4; - let one = Fx::::from_int_part(1); - let neg_one = Fx::::from_int_part(-1); - assert!(-one == neg_one); - assert!(-(-one) == one); - } - - #[test] - fn test_mul() { - use typenum::U4; - let half = Fx::::from_int_part(1) >> 1; - let two = Fx::::from_int_part(2); - let three = Fx::::from_int_part(3); - let twelve = Fx::::from_int_part(12); - assert!(two * three == twelve * half); - } - - #[test] - fn test_div() { - use typenum::U4; - let two = Fx::::from_int_part(2); - let six = Fx::::from_int_part(6); - let twelve = Fx::::from_int_part(12); - assert!(twelve / two == six); - } -} diff --git a/src/io.rs b/src/io.rs deleted file mode 100644 index 57d3b98..0000000 --- a/src/io.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! This module contains definitions and types for the IO Registers. -//! -//! ## Naming -//! -//! In the interest of making things easy to search for, all io register -//! constants are given the names used in the -//! [GBATEK](https://problemkaputt.de/gbatek.htm) technical description. - -use super::*; - -pub mod background; -pub mod color_blend; -pub mod display; -pub mod dma; -pub mod irq; -pub mod keypad; -pub mod sio; -pub mod sound; -pub mod timers; -pub mod window; diff --git a/src/io/background.rs b/src/io/background.rs deleted file mode 100644 index 7cd9f5e..0000000 --- a/src/io/background.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Module for Background controls - -use super::*; - -/// BG0 Control. Read/Write. Display Mode 0/1 only. -pub const BG0CNT: VolAddress = - unsafe { VolAddress::new(0x400_0008) }; -/// BG1 Control. Read/Write. Display Mode 0/1 only. -pub const BG1CNT: VolAddress = - unsafe { VolAddress::new(0x400_000A) }; -/// BG2 Control. Read/Write. Display Mode 0/1/2 only. -pub const BG2CNT: VolAddress = - unsafe { VolAddress::new(0x400_000C) }; -/// BG3 Control. Read/Write. Display Mode 0/2 only. -pub const BG3CNT: VolAddress = - unsafe { VolAddress::new(0x400_000E) }; - -newtype! { - /// Allows configuration of a background layer. - /// - /// Bits 0-1: BG Priority (lower number is higher priority, like an index) - /// Bits 2-3: Character Base Block (0 through 3, 16k each) - /// Bit 6: Mosaic mode - /// Bit 7: is 8bpp - /// Bit 8-12: Screen Base Block (0 through 31, 2k each) - /// Bit 13: Display area overflow wraps (otherwise transparent, affine BG only) - /// Bit 14-15: Screen Size - BackgroundControlSetting, u16 -} -impl BackgroundControlSetting { - phantom_fields! { - self.0: u16, - bg_priority: 0-1, - char_base_block: 2-3, - mosaic: 6, - is_8bpp: 7, - screen_base_block: 8-12, - affine_display_overflow_wrapping: 13, - size: 14-15=BGSize, - } -} - -/// The size of a background. -/// -/// The meaning changes depending on if the background is Text or Affine mode. -/// -/// * In text mode, the screen base block determines where to start reading the -/// tile arrangement data (2k). Size Zero gives one screen block of use. Size -/// One and Two cause two of them to be used (horizontally or vertically, -/// respectively). Size Three is four blocks used, [0,1] above and then [2,3] -/// below. Each screen base block used is always a 32x32 tile grid. -/// * In affine mode, the screen base block determines where to start reading -/// data followed by the size of data as shown. The number of tiles varies -/// according to the size used. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum BGSize { - /// * Text: 256x256px (2k) - /// * Affine: 128x128px (256b) - Zero = 0, - /// * Text: 512x256px (4k) - /// * Affine: 256x256px (1k) - One = 1, - /// * Text: 256x512px (4k) - /// * Affine: 512x512px (4k) - Two = 2, - /// * Text: 512x512px (8k) - /// * Affine: 1024x1024px (16k) - Three = 3, -} - -/// BG0 X-Offset. Write only. Text mode only. 9 bits. -pub const BG0HOFS: VolAddress = unsafe { VolAddress::new(0x400_0010) }; -/// BG0 Y-Offset. Write only. Text mode only. 9 bits. -pub const BG0VOFS: VolAddress = unsafe { VolAddress::new(0x400_0012) }; - -/// BG1 X-Offset. Write only. Text mode only. 9 bits. -pub const BG1HOFS: VolAddress = unsafe { VolAddress::new(0x400_0014) }; -/// BG1 Y-Offset. Write only. Text mode only. 9 bits. -pub const BG1VOFS: VolAddress = unsafe { VolAddress::new(0x400_0016) }; - -/// BG2 X-Offset. Write only. Text mode only. 9 bits. -pub const BG2HOFS: VolAddress = unsafe { VolAddress::new(0x400_0018) }; -/// BG2 Y-Offset. Write only. Text mode only. 9 bits. -pub const BG2VOFS: VolAddress = unsafe { VolAddress::new(0x400_001A) }; - -/// BG3 X-Offset. Write only. Text mode only. 9 bits. -pub const BG3HOFS: VolAddress = unsafe { VolAddress::new(0x400_001C) }; -/// BG3 Y-Offset. Write only. Text mode only. 9 bits. -pub const BG3VOFS: VolAddress = unsafe { VolAddress::new(0x400_001E) }; - -// TODO: affine backgrounds -// BG2X_L -// BG2X_H -// BG2Y_L -// BG2Y_H -// BG2PA -// BG2PB -// BG2PC -// BG2PD -// BG3PA -// BG3PB -// BG3PC -// BG3PD - -// TODO: windowing -// pub const WIN0H: VolAddress = unsafe { VolAddress::new(0x400_0040) }; -// pub const WIN1H: VolAddress = unsafe { VolAddress::new(0x400_0042) }; -// pub const WIN0V: VolAddress = unsafe { VolAddress::new(0x400_0044) }; -// pub const WIN1V: VolAddress = unsafe { VolAddress::new(0x400_0046) }; -// pub const WININ: VolAddress = unsafe { VolAddress::new(0x400_0048) }; -// pub const WINOUT: VolAddress = unsafe { VolAddress::new(0x400_004A) }; - -// TODO: blending -// pub const BLDCNT: VolAddress = unsafe { VolAddress::new(0x400_0050) }; -// pub const BLDALPHA: VolAddress = unsafe { VolAddress::new(0x400_0052) }; -// pub const BLDY: VolAddress = unsafe { VolAddress::new(0x400_0054) }; diff --git a/src/io/color_blend.rs b/src/io/color_blend.rs deleted file mode 100644 index bcabc84..0000000 --- a/src/io/color_blend.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Module that holds stuff for the color blending ability. - -use super::*; - -/// Color Special Effects Selection (R/W) -pub const BLDCNT: VolAddress = - unsafe { VolAddress::new(0x400_0050) }; - -newtype! { - /// TODO: docs - ColorEffectSetting, u16 -} - -impl ColorEffectSetting { - phantom_fields! { - self.0: u16, - bg0_1st_target_pixel: 0, - bg1_1st_target_pixel: 1, - bg2_1st_target_pixel: 2, - bg3_1st_target_pixel: 3, - obj_1st_target_pixel: 4, - backdrop_1st_target_pixel: 5, - color_special_effect: 6-7=ColorSpecialEffect, - bg0_2nd_target_pixel: 8, - bg1_2nd_target_pixel: 9, - bg2_2nd_target_pixel: 10, - bg3_2nd_target_pixel: 11, - obj_2nd_target_pixel: 12, - backdrop_2nd_target_pixel: 13, - } -} - -newtype_enum! { - /// TODO: docs - ColorSpecialEffect = u16, - /// TODO: docs - None = 0, - /// TODO: docs - AlphaBlending = 1, - /// TODO: docs - BrightnessIncrease = 2, - /// TODO: docs - BrightnessDecrease = 3, -} - -/// Alpha Blending Coefficients (R/W) (not W) -pub const BLDALPHA: VolAddress = - unsafe { VolAddress::new(0x400_0052) }; - -newtype! { - /// TODO: docs - AlphaBlendingSetting, u16 -} - -impl AlphaBlendingSetting { - phantom_fields! { - self.0: u16, - eva_coefficient: 0-4, - evb_coefficient: 8-12, - } -} - -/// Brightness (Fade-In/Out) Coefficient (W) (not R/W) -pub const BLDY: VolAddress = unsafe { VolAddress::new(0x400_0054) }; - -newtype! { - /// TODO: docs - BrightnessSetting, u32 -} - -impl BrightnessSetting { - phantom_fields! { - self.0: u32, - evy_coefficient: 0-4, - } -} diff --git a/src/io/display.rs b/src/io/display.rs deleted file mode 100644 index bfdb546..0000000 --- a/src/io/display.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Contains types and definitions for display related IO registers. - -use super::*; - -/// LCD Control. Read/Write. -/// -/// The "force vblank" bit is always set when your Rust code first executes. -pub const DISPCNT: VolAddress = - unsafe { VolAddress::new(0x400_0000) }; - -newtype!( - /// Setting for the display control register. - /// - /// * 0-2: `DisplayMode` - /// * 3: CGB mode flag - /// * 4: Display frame 1 (Modes 4/5 only) - /// * 5: "hblank interval free", allows full access to OAM during hblank - /// * 6: Object tile memory 1-dimensional - /// * 7: Force vblank - /// * 8: Display bg0 layer - /// * 9: Display bg1 layer - /// * 10: Display bg2 layer - /// * 11: Display bg3 layer - /// * 12: Display objects layer - /// * 13: Window 0 display - /// * 14: Window 1 display - /// * 15: Object window - DisplayControlSetting, - u16 -); - -#[allow(missing_docs)] -impl DisplayControlSetting { - phantom_fields! { - self.0: u16, - mode: 0-2=DisplayMode, - frame1: 4, - hblank_interval_free: 5, - oam_memory_1d: 6, - force_vblank: 7, - bg0: 8, - bg1: 9, - bg2: 10, - bg3: 11, - obj: 12, - win0: 13, - win1: 14, - obj_window: 15, - } -} - -newtype_enum! { - /// The six display modes available on the GBA. - DisplayMode = u16, - /// * Affine: No - /// * Layers: 0/1/2/3 - /// * Size(px): 256x256 to 512x512 - /// * Tiles: 1024 - /// * Palette Modes: 4bpp or 8bpp - Mode0 = 0, - /// * BG0 / BG1: As Mode0 - /// * BG2: As Mode2 - Mode1 = 1, - /// * Affine: Yes - /// * Layers: 2/3 - /// * Size(px): 128x128 to 1024x1024 - /// * Tiles: 256 - /// * Palette Modes: 8bpp - Mode2 = 2, - /// * Affine: Yes - /// * Layers: 2 - /// * Size(px): 240x160 (1 page) - /// * Bitmap - /// * Full Color - Mode3 = 3, - /// * Affine: Yes - /// * Layers: 2 - /// * Size(px): 240x160 (2 pages) - /// * Bitmap - /// * Palette Modes: 8bpp - Mode4 = 4, - /// * Affine: Yes - /// * Layers: 2 - /// * Size(px): 160x128 (2 pages) - /// * Bitmap - /// * Full Color - Mode5 = 5, -} - -/// Assigns the given display control setting. -pub fn set_display_control(setting: DisplayControlSetting) { - DISPCNT.write(setting); -} -/// Obtains the current display control setting. -pub fn display_control() -> DisplayControlSetting { - DISPCNT.read() -} - -/// Display Status and IRQ Control. Read/Write. -pub const DISPSTAT: VolAddress = - unsafe { VolAddress::new(0x400_0004) }; - -newtype!( - /// A newtype over display status and interrupt control values. - DisplayStatusSetting, - u16 -); - -impl DisplayStatusSetting { - phantom_fields! { - self.0: u16, - vblank_flag: 0, - hblank_flag: 1, - vcounter_flag: 2, - vblank_irq_enable: 3, - hblank_irq_enable: 4, - vcounter_irq_enable: 5, - vcount_setting: 8-15, - } -} - -/// Vertical Counter (LY). Read only. -/// -/// Gives the current scanline that the display controller is working on. If -/// this is at or above the `VBLANK_SCANLINE` value then the display controller -/// is in a "vertical blank" period. -pub const VCOUNT: VolAddress = unsafe { VolAddress::new(0x400_0006) }; - -/// If the `VCOUNT` register reads equal to or above this then you're in vblank. -pub const VBLANK_SCANLINE: u16 = 160; - -/// Global mosaic effect control. Write-only. -pub const MOSAIC: VolAddress = unsafe { VolAddress::new(0x400_004C) }; - -newtype! { - /// Allows control of the Mosaic effect. - /// - /// Values are the _increase_ for each top-left pixel to be duplicated in the - /// final result. If you want to duplicate some other pixel than the top-left, - /// you can offset the background or object by an appropriate amount. - /// - /// 0) No effect (1+0) - /// 1) Each pixel becomes 2 pixels (1+1) - /// 2) Each pixel becomes 3 pixels (1+2) - /// 3) Each pixel becomes 4 pixels (1+3) - /// - /// * Bits 0-3: BG mosaic horizontal increase - /// * Bits 4-7: BG mosaic vertical increase - /// * Bits 8-11: Object mosaic horizontal increase - /// * Bits 12-15: Object mosaic vertical increase - MosaicSetting, u16 -} -impl MosaicSetting { - phantom_fields! { - self.0: u16, - bg_horizontal_inc: 0-3, - bg_vertical_inc: 4-7, - obj_horizontal_inc: 8-11, - obj_vertical_inc: 12-15, - } -} diff --git a/src/io/dma.rs b/src/io/dma.rs deleted file mode 100644 index ee59cf2..0000000 --- a/src/io/dma.rs +++ /dev/null @@ -1,415 +0,0 @@ -//! Module for using the four Direct Memory Access (DMA) units. -//! -//! The GBA has four DMA units, numbered 0 through 3. If you ever try to have -//! more than one active at once the lowest numbered DMA will take priority and -//! complete first. Any use of DMA halts the CPU's operation. DMA can also be -//! configured to activate automatically at certain times, and when configured -//! like that the CPU runs in between the automatic DMA activations. (This is -//! actually the intended method for doing sound.) Each DMA unit has an intended -//! use: -//! -//! * DMA0: highest priority, but can only read from internal memory. -//! * DMA1/DMA2: Intended for sound transfers. -//! * DMA3: Can be used to write into Game Pak ROM / FlashROM (not SRAM). -//! -//! ## DMA Anatomy -//! -//! Each DMA is utilized via a combination four IO registers: -//! -//! * **Source Address:** (`*const u32`) Where to read from. DMA0 can only read -//! from internal memory, the other units can read from any non-SRAM memory. -//! * **Destination Address:** (`*mut u32`) Where to write to. DMA0/1/2 can only -//! write to internal memory, DMA3 can write to any non-SRAM memory. -//! * **Word Count:** (`u16`) How many units to transfer. Despite being called -//! "word count" you can also use DMA to transfer half-words. DMA0/1/2 are -//! limited to a 14-bit counter value, DMA3 allowed the full 16-bit range to -//! be used for the counter. Note that even when transferring half-words you -//! MUST have both Source and Destination be 32-bit aligned. -//! * **Control:** (`DMAControlSetting`) This is one of those fiddly bit-flag -//! registers with all sorts of settings. See the type for more info. -//! -//! Note that Source, Destination, and Count are all read-only, while the -//! Control is read/write. When a DMA unit is _Enabled_ it copies the relevent -//! Source, Destination, and Count values into its own internal registers (so a -//! second Enable will reuse the old values). If the DMA _Repeats_ it re-copies -//! the Count, and also the Destination if -//! `DMADestAddressControl::IncrementReload` is configured in the Control, but -//! not the Source. -//! -//! When the DMA completes the Enable bit will be cleared from the Control, -//! unless the Repeat bit is set in the Control, in which case the Enable bit is -//! left active and the DMA will automatically activate again at the right time -//! (depending on the Start Timing setting). You have to manually turn off the -//! correct bit to stop the DMA unit. -//! -//! ## Safety -//! -//! As you might have noticed by now, utilizing DMA can be very fiddly. It moves -//! around bytes with no concern for the type system, including the `Clone` and -//! `Copy` traits that Rust relies on. Use of DMA can be made _somewhat safe_ -//! via wrapper methods (such as those we've provided), but it's fundamentally -//! an unsafe thing to use. -//! -//! ## DMA Can Cause Subtle Bugs -//! -//! Since the CPU is halted while DMA is active you can miss out on interrupts -//! that should have fired. This can cause any number of unintended effects. DMA -//! is primarily intended for loading large amounts of graphical data from ROM, -//! or loading sound data at regulated intervals to avoid pops and crackles. It -//! _can_ be used for general purpose bulk transfers but you are advised to use -//! restraint. - -use super::*; - -newtype! { - /// Allows you to configure a DMA unit. - DMAControlSetting, u16 -} -#[allow(missing_docs)] -impl DMAControlSetting { - phantom_fields! { - self.0: u16, - dest_address_control: 5-6=DMADestAddressControl, - source_address_control: 7-8=DMASrcAddressControl, - dma_repeat: 9, - use_32bit: 10, - start_time: 12-13=DMAStartTiming, - irq_when_done: 14, - enabled: 15, - } -} - -/// Sets how the destination address should be adjusted per data transfer. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum DMADestAddressControl { - /// Offset +1 - Increment = 0, - /// Offset -1 - Decrement = 1, - /// No change - Fixed = 2, - /// Offset +1 per transfer and auto-reset to base when the DMA repeats. - IncrementReload = 3, -} - -/// Sets how the source address should be adjusted per data transfer. -/// -/// Note that only 0,1,2 are allowed, 3 is prohibited. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum DMASrcAddressControl { - /// Offset +1 - Increment = 0, - /// Offset -1 - Decrement = 1, - /// No change - Fixed = 2, -} - -/// Sets when the DMA should activate. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum DMAStartTiming { - /// Causes the DMA to start as soon as possible (2 wait cycles after enabled) - Immediate = 0, - /// Start at VBlank - VBlank = 1, - /// Start at HBlank - HBlank = 2, - /// The special timing depends on the DMA it's used with: - /// * 0: Prohibited - /// * 1/2: Sound FIFO, - /// * 3: Video Capture, for transferring from memory/camera into VRAM - Special = 3, -} - -pub struct DMA0; -impl DMA0 { - /// DMA 0 Source Address, read only. - const DMA0SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00B0) }; - /// DMA 0 Destination Address, read only. - const DMA0DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00B4) }; - /// DMA 0 Word Count, read only. - const DMA0CNT_L: VolAddress = unsafe { VolAddress::new(0x400_00B8) }; - /// DMA 0 Control, read/write. - const DMA0CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_00BA) }; - - /// Assigns the source register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to read from. - pub unsafe fn set_source(src: *const u32) { - crate::sync::memory_read_hint(src); - Self::DMA0SAD.write(src); - } - - /// Assigns the destination register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to write to. - pub unsafe fn set_dest(dest: *mut u32) { - Self::DMA0DAD.write(dest); - crate::sync::memory_write_hint(dest); - } - - /// Assigns the count register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The count given must specify a valid number of units to write, starting at - /// the assigned destination address. - pub unsafe fn set_count(count: u16) { - Self::DMA0CNT_L.write(count) - } - - /// Reads the current control setting. - pub fn control() -> DMAControlSetting { - Self::DMA0CNT_H.read() - } - - /// Writes the control setting given. - /// - /// # Safety - /// - /// You must ensure that the Source, Destination, and Count values are set - /// correctly **before** you activate the Enable bit. - pub unsafe fn set_control(setting: DMAControlSetting) { - Self::DMA0CNT_H.write(setting) - } -} - -pub struct DMA1; -impl DMA1 { - /// DMA 1 Source Address, read only. - const DMA1SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00BC) }; - /// DMA 1 Destination Address, read only. - const DMA1DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00C0) }; - /// DMA 1 Word Count, read only. - const DMA1CNT_L: VolAddress = unsafe { VolAddress::new(0x400_00C4) }; - /// DMA 1 Control, read/write. - const DMA1CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_00C6) }; - - /// Assigns the source register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to read from. - pub unsafe fn set_source(src: *const u32) { - crate::sync::memory_read_hint(src); - Self::DMA1SAD.write(src); - } - - /// Assigns the destination register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to write to. - pub unsafe fn set_dest(dest: *mut u32) { - Self::DMA1DAD.write(dest); - crate::sync::memory_write_hint(dest); - } - - /// Assigns the count register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The count given must specify a valid number of units to write, starting at - /// the assigned destination address. - pub unsafe fn set_count(count: u16) { - Self::DMA1CNT_L.write(count) - } - - /// Reads the current control setting. - pub fn control() -> DMAControlSetting { - Self::DMA1CNT_H.read() - } - - /// Writes the control setting given. - /// - /// # Safety - /// - /// You must ensure that the Source, Destination, and Count values are set - /// correctly **before** you activate the Enable bit. - pub unsafe fn set_control(setting: DMAControlSetting) { - Self::DMA1CNT_H.write(setting) - } -} - -pub struct DMA2; -impl DMA2 { - /// DMA 2 Source Address, read only. - const DMA2SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00C8) }; - /// DMA 2 Destination Address, read only. - const DMA2DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00CC) }; - /// DMA 2 Word Count, read only. - const DMA2CNT_L: VolAddress = unsafe { VolAddress::new(0x400_00D0) }; - /// DMA 2 Control, read/write. - const DMA2CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_00D2) }; - - /// Assigns the source register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to read from. - pub unsafe fn set_source(src: *const u32) { - crate::sync::memory_read_hint(src); - Self::DMA2SAD.write(src); - } - - /// Assigns the destination register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to write to. - pub unsafe fn set_dest(dest: *mut u32) { - Self::DMA2DAD.write(dest); - crate::sync::memory_write_hint(dest); - } - - /// Assigns the count register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The count given must specify a valid number of units to write, starting at - /// the assigned destination address. - pub unsafe fn set_count(count: u16) { - Self::DMA2CNT_L.write(count) - } - - /// Reads the current control setting. - pub fn control() -> DMAControlSetting { - Self::DMA2CNT_H.read() - } - - /// Writes the control setting given. - /// - /// # Safety - /// - /// You must ensure that the Source, Destination, and Count values are set - /// correctly **before** you activate the Enable bit. - pub unsafe fn set_control(setting: DMAControlSetting) { - Self::DMA2CNT_H.write(setting) - } -} - -/// This is the "general purpose" DMA unit, with the fewest limits. -pub struct DMA3; -impl DMA3 { - /// DMA 3 Source Address, read only. - const DMA3SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00D4) }; - /// DMA 3 Destination Address, read only. - const DMA3DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00D8) }; - /// DMA 3 Word Count, read only. - const DMA3CNT_L: VolAddress = unsafe { VolAddress::new(0x400_00DC) }; - /// DMA 3 Control, read/write. - const DMA3CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_00DE) }; - - /// Assigns the source register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to read from. - pub unsafe fn set_source(src: *const u32) { - crate::sync::memory_read_hint(src); - Self::DMA3SAD.write(src); - } - - /// Assigns the destination register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The source pointer must be aligned and valid to write to. - pub unsafe fn set_dest(dest: *mut u32) { - Self::DMA3DAD.write(dest); - crate::sync::memory_write_hint(dest); - } - - /// Assigns the count register. - /// - /// This register is read only, so it is not exposed directly. - /// - /// # Safety - /// - /// The count given must specify a valid number of units to write, starting at - /// the assigned destination address. - pub unsafe fn set_count(count: u16) { - Self::DMA3CNT_L.write(count) - } - - /// Reads the current control setting. - pub fn control() -> DMAControlSetting { - Self::DMA3CNT_H.read() - } - - /// Writes the control setting given. - /// - /// # Safety - /// - /// You must ensure that the Source, Destination, and Count values are set - /// correctly **before** you activate the Enable bit. - pub unsafe fn set_control(setting: DMAControlSetting) { - Self::DMA3CNT_H.write(setting) - } - - /// Fills `count` slots (starting at `dest`) with the value at `src`. - /// - /// # Safety - /// - /// Both pointers must be aligned, and all positions specified for writing - /// must be valid for writing. - pub unsafe fn fill32(src: *const u32, dest: *mut u32, count: u16) { - const FILL_CONTROL: DMAControlSetting = DMAControlSetting::new() - .with_source_address_control(DMASrcAddressControl::Fixed) - .with_use_32bit(true) - .with_enabled(true); - // TODO: destination checking against SRAM - crate::sync::memory_read_hint(src); - Self::DMA3SAD.write(src); - Self::DMA3DAD.write(dest); - Self::DMA3CNT_L.write(count); - Self::DMA3CNT_H.write(FILL_CONTROL); - crate::sync::memory_write_hint(dest); - - // Note(Lokathor): Once DMA is set to activate it takes 2 cycles for it to - // kick in. You can do any non-DMA thing you like before that, but since - // it's only two cycles we just insert two NOP instructions to ensure that - // successive calls to `fill32` or other DMA methods don't interfere with - // each other. - asm!( - " - NOP - NOP - ", - options(nomem, nostack) - ); - } -} diff --git a/src/io/irq.rs b/src/io/irq.rs deleted file mode 100644 index 56ae2e6..0000000 --- a/src/io/irq.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Module containing a wrapper for interrupt request (IRQ) handling. -//! -//! When an interrupt is executed, the CPU will be set to IRQ mode and code -//! execution will jump to the physical interrupt vector, located in BIOS. The -//! BIOS interrupt handler will then save several registers to the IRQ stack -//! pointer and execution will jump to the user interrupt handler starting at -//! `0x0300_7FFC`, in ARM mode. -//! -//! Currently, the user interrupt handler is defined in `rsrt0.S`. It is set up -//! to execute a user-specified interrupt handler after saving some registers. -//! This handler is declared as a static function pointer on the Rust side, and -//! can be set by using [`set_irq_handler`](irq::set_irq_handler). -//! -//! ## Notes -//! * The interrupt will only be triggered if [`IME`](irq::IME) is enabled, the -//! flag corresponding to the interrupt is enabled on the [`IE`](irq::IE) -//! register, and the "IRQ Enable" flag is set on the register related to the -//! interrupt, which varies. For example, to enable interrupts on VBlank you -//! would set the -//! [`vblank_irq_enable`](io::display::DisplayStatusSetting::vblank_irq_enable) -//! flag on the [`DISPSTAT`](io::display::DISPCNT) register. -//! * If you intend to use [`interrupt_wait`](bios::interrupt_wait) or -//! [`vblank_interrupt_wait`](bios::vblank_interrupt_wait) to wait for an -//! interrupt, your interrupt handler MUST update the BIOS Interrupt Flags at -//! [`BIOS_IF`](irq::BIOS_IF) in addition to the usual interrupt -//! acknowledgement (which is handled for you by the user interrupt handler). -//! This is done by setting the corresponding IRQ flag on -//! [`BIOS_IF`](irq::BIOS_IF) at the end of the interrupt handler. -//! * You can change the low-level details of the interrupt handler by editing -//! the `MainIrqHandler` routine in `rsrt0.S`. For example, you could declare -//! an external static variable in Rust holding a table of interrupt function -//! pointers and jump directly into one of them in assembly, without the need -//! to write the branching logic in Rust. However, note that the main -//! interrupt handler MUST acknowledge all interrupts received by setting -//! their corresponding bits to `1` in the [`IF`](irq::IF) register. -//! * If you wait on one or more interrupts, be sure at least one of them is -//! able to be triggered or the call to wait will never return. -//! * If you wait on multiple interrupts and those interrupts fire too quickly, -//! it is possible that the call to wait will never return as interrupts will -//! be constantly received before control is returned to the caller. This -//! usually only happens when waiting on multiple timer interrupts with very -//! fast overflow rates. -//! -//! ## Example -//! -//! ```rust -//! extern "C" fn irq_handler(flags: IrqFlags) { -//! if flags.vblank() { -//! // Run drawing logic here. -//! -//! // Acknowledge the IRQ on the BIOS Interrupt Flags register. -//! BIOS_IF.write(BIOS_IF.read().with_vblank(true)); -//! } -//! } -//! -//! fn main_loop() { -//! // Set the IRQ handler to use. -//! irq::set_irq_handler(irq_handler); -//! -//! // Handle only the VBlank interrupt. -//! const FLAGS: IrqFlags = IrqFlags::new().with_vblank(true); -//! IE.write(flags); -//! -//! // Enable all interrupts that are set in the IE register. -//! IME.write(IrqEnableSetting::UseIE); -//! -//! // Enable IRQ generation during VBlank. -//! const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new() -//! .with_vblank_irq_enable(true); -//! DISPSTAT.write(DISPLAY_SETTINGS); -//! -//! loop { -//! // Sleep the CPU until a VBlank IRQ is generated. -//! bios::vblank_interrupt_wait(); -//! } -//! } -//! ``` -//! -//! ## Implementation Details -//! -//! This is the setup the provided user interrupt handler in `rsrt0.S` will do -//! when an interrupt is received, in order. It is based on the _Recommended -//! User Interrupt Handling_ portion of the GBATEK reference. -//! -//! 1. Save the status of [`IME`](irq::IME). -//! 2. Save the IRQ stack pointer and change to system mode to use the user -//! stack instead of the IRQ stack (to prevent stack overflow). -//! 3. Disable interrupts by setting [`IME`](irq::IME) to 0, so other interrupts -//! will not preempt the main interrupt handler. -//! 4. Acknowledge all IRQs that occurred and were enabled in the -//! [`IE`](irq::IE) register by writing the bits to the [`IF`](irq::IF) -//! register. -//! 5. Save the user stack pointer, switch to Thumb mode and jump to the -//! user-specified interrupt handler. The IRQ flags that were set are passed -//! as an argument in `r0`. -//! 6. When the handler returns, restore the user stack pointer and switch back -//! to IRQ mode. -//! 7. Restore the IRQ stack pointer and the status of [`IME`](irq::IME). -//! 8. Return to the BIOS interrupt handler. - -use super::*; - -newtype!( - /// A newtype over all interrupt flags. - IrqFlags, - pub(crate) u16 -); - -impl IrqFlags { - phantom_fields! { - self.0: u16, - vblank: 0, - hblank: 1, - vcounter: 2, - timer0: 3, - timer1: 4, - timer2: 5, - timer3: 6, - serial: 7, - dma0: 8, - dma1: 9, - dma2: 10, - dma3: 11, - keypad: 12, - game_pak: 13, - } -} - -/// Interrupt Enable Register. Read/Write. -/// -/// After setting up interrupt handlers, set the flags on this register type corresponding to the -/// IRQs you want to handle. -pub const IE: VolAddress = unsafe { VolAddress::new(0x400_0200) }; - -/// Interrupt Request Flags / IRQ Acknowledge. Read/Write. -/// -/// The main user interrupt handler will acknowledge the interrupt that was set -/// by writing to this register, so there is usually no need to modify it. -/// However, if the main interrupt handler in `rsrt0.S` is changed, then the -/// handler must write a `1` bit to all bits that are enabled on this register -/// when it is called. -pub const IF: VolAddress = unsafe { VolAddress::new(0x400_0202) }; - -newtype! { - /// Setting to control whether interrupts are enabled. - IrqEnableSetting, u16 -} - -impl IrqEnableSetting { - phantom_fields! { - self.0: u16, - /// System-wide control for if interrupts of all kinds are enabled or not. - interrupts_enabled: 0, - } - - /// Yes, you want to have interrupts. - pub const IRQ_YES: Self = Self::new().with_interrupts_enabled(true); - - /// No, you do not want to have interrupts. - pub const IRQ_NO: Self = Self::new(); -} - -/// Interrupt Master Enable Register. Read/Write. -pub const IME: VolAddress = unsafe { VolAddress::new(0x400_0208) }; - -/// BIOS Interrupt Flags. Read/Write. -/// -/// When using either [`interrupt_wait`](bios::interrupt_wait) or -/// [`vblank_interrupt_wait`](bios::vblank_interrupt_wait), the corresponding -/// interrupt handler MUST set the flag of the interrupt it has handled on this -/// register in addition to the usual interrupt acknowledgement. -pub const BIOS_IF: VolAddress = unsafe { VolAddress::new(0x0300_7FF8) }; - -/// A function pointer for use as an interrupt handler. -pub type IrqHandler = extern "C" fn(IrqFlags); - -/// Sets the function to run when an interrupt is executed. The function will -/// receive the interrupts that were acknowledged by the main interrupt handler -/// as an argument. -pub fn set_irq_handler(handler: IrqHandler) { - unsafe { - __IRQ_HANDLER = handler; - } -} - -extern "C" fn default_handler(_flags: IrqFlags) {} - -// Inner definition of the interrupt handler. It is referenced in `rsrt0.S`. -#[doc(hidden)] -#[no_mangle] -static mut __IRQ_HANDLER: IrqHandler = default_handler; diff --git a/src/io/keypad.rs b/src/io/keypad.rs deleted file mode 100644 index eaa90b3..0000000 --- a/src/io/keypad.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Allows access to the keypad. - -use super::*; - -/// The Key Input Register. -/// -/// This register follows the "low-active" convention. If you want your code to -/// follow the "high-active" convention (hint: you probably do, it's far easier -/// to work with) then call `read_key_input()` rather than reading this register -/// directly. It will perform the necessary bit flip operation for you. -pub const KEYINPUT: VolAddress = unsafe { VolAddress::new(0x400_0130) }; - -/// A "tribool" value helps us interpret the arrow pad. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] -pub enum TriBool { - /// -1 - Minus = -1, - /// +0 - Neutral = 0, - /// +1 - Plus = 1, -} - -newtype! { - /// Records a particular key press combination. - /// - /// Methods here follow the "high-active" convention, where a bit is enabled - /// when it's part of the set. - KeyInput, u16 -} - -impl KeyInput { - phantom_fields! { - self.0: u16, - a: 0, - b: 1, - select: 2, - start: 3, - right: 4, - left: 5, - up: 6, - down: 7, - r: 8, - l: 9, - } - - /// Takes the set difference between these keys and another set of keys. - pub fn difference(self, other: Self) -> Self { - KeyInput(self.0 ^ other.0) - } - - /// Right/left tribool. - /// - /// Right is Plus and Left is Minus - pub fn x_tribool(self) -> TriBool { - if self.right() { - TriBool::Plus - } else if self.left() { - TriBool::Minus - } else { - TriBool::Neutral - } - } - - /// Up/down tribool. - /// - /// Down is Plus and Up is Minus - pub fn y_tribool(self) -> TriBool { - if self.down() { - TriBool::Plus - } else if self.up() { - TriBool::Minus - } else { - TriBool::Neutral - } - } -} - -/// Gets the current state of the keys -pub fn read_key_input() -> KeyInput { - // Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6 - // unused bits are always low, so we XOR with this mask to get a result where - // the only active bits are currently pressed keys. - KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) -} - -/// Use this to configure when a keypad interrupt happens. -/// -/// See the `KeyInterruptSetting` type for more. -pub const KEYCNT: VolAddress = - unsafe { VolAddress::new(0x400_0132) }; - -newtype! { - /// Allows configuration of when a keypad interrupt fires. - /// - /// * The most important bit here is the `irq_enabled` bit, which determines - /// if an interrupt happens at all. - /// * The second most important bit is the `irq_logical_and` bit. If this bit - /// is set, _all_ the selected buttons are required to be set for the - /// interrupt to be fired (logical AND). If it's not set then _any_ of the - /// buttons selected can be pressed to fire the interrupt (logical OR). - /// * All other bits select a particular button to be required or not as part - /// of the interrupt firing. - /// - /// NOTE: This _only_ configures the operation of when keypad interrupts can - /// fire. You must still set the [`IME`](irq::IME) to have interrupts at all, - /// and you must further set [`IE`](irq::IE) for keypad interrupts to be - /// possible. - KeyInterruptSetting, u16 -} -#[allow(missing_docs)] -impl KeyInterruptSetting { - phantom_fields! { - self.0: u16, - a: 0, - b: 1, - select: 2, - start: 3, - right: 4, - left: 5, - up: 6, - down: 7, - r: 8, - l: 9, - irq_enabled: 14, - irq_logical_and: 15, - } -} diff --git a/src/io/sound.rs b/src/io/sound.rs deleted file mode 100644 index 163e8b1..0000000 --- a/src/io/sound.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! Module for sound registers. - -use super::*; - -//TODO within these "read/write" registers only some bits are actually read/write! - -/// Sound Channel 1 Sweep Register (`NR10`). Read/Write. -pub const SOUND1CNT_L: VolAddress = - unsafe { VolAddress::new(0x400_0060) }; - -newtype! { - /// TODO: docs - SweepRegisterSetting, u16 -} - -impl SweepRegisterSetting { - phantom_fields! { - self.0: u16, - sweep_shift: 0-2, - sweep_decreasing: 3, - sweep_time: 4-6, - } -} - -/// Sound Channel 1 Duty/Length/Envelope (`NR11`, `NR12`). Read/Write. -pub const SOUND1CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_0062) }; - -newtype! { - /// TODO: docs - DutyLenEnvelopeSetting, u16 -} - -impl DutyLenEnvelopeSetting { - phantom_fields! { - self.0: u16, - sound_length: 0-5, - wave_pattern_duty: 6-7, //TODO: enum this - envelope_step_time: 8-10, - envelope_increasing: 11, - initial_envelope_volume: 12-15, - } -} - -/// Sound Channel 1 Frequency/Control (`NR13`, `NR14`). Read/Write. -pub const SOUND1CNT_X: VolAddress = - unsafe { VolAddress::new(0x400_0064) }; - -newtype! { - /// TODO: docs - FrequencyControlSetting, u32 // TODO: u16 or u32? -} - -impl FrequencyControlSetting { - phantom_fields! { - self.0: u32, - frequency: 0-10, - length_flag: 14, - is_initial: 15, - } -} - -/// Sound Channel 2 Channel 2 Duty/Length/Envelope (`NR21`, `NR22`). Read/Write. -pub const SOUND2CNT_L: VolAddress = - unsafe { VolAddress::new(0x400_0068) }; - -/// Sound Channel 2 Frequency/Control (`NR23`, `NR24`). Read/Write. -pub const SOUND2CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_006C) }; - -/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write. -pub const SOUND3CNT_L: VolAddress = - unsafe { VolAddress::new(0x400_0070) }; - -newtype! { - /// TODO: docs - StopWaveRAMSelectSetting, u16 -} - -impl StopWaveRAMSelectSetting { - phantom_fields! { - self.0: u16, - wave_ram_dimension_2d: 5, - wave_ram_bank_number: 6, - sound_channel_3_playing: 7, - } -} - -/// Sound Channel 3 Length/Volume (`NR23`, `NR24`). Read/Write. -pub const SOUND3CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_0072) }; - -newtype! { - /// TODO: docs - LengthVolumeSetting, u16 -} - -impl LengthVolumeSetting { - phantom_fields! { - self.0: u16, - sound_length: 0-7, - sound_volume: 13-14, - force_75percent: 15, - } -} - -/// Sound Channel 3 Frequency/Control (`NR33`, `NR34`). Read/Write. -pub const SOUND3CNT_X: VolAddress = - unsafe { VolAddress::new(0x400_0074) }; - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM0_L: VolAddress = unsafe { VolAddress::new(0x400_0090) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM0_H: VolAddress = unsafe { VolAddress::new(0x400_0092) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM1_L: VolAddress = unsafe { VolAddress::new(0x400_0094) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM1_H: VolAddress = unsafe { VolAddress::new(0x400_0096) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM2_L: VolAddress = unsafe { VolAddress::new(0x400_0098) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM2_H: VolAddress = unsafe { VolAddress::new(0x400_009A) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM3_L: VolAddress = unsafe { VolAddress::new(0x400_009C) }; -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM3_H: VolAddress = unsafe { VolAddress::new(0x400_009E) }; - -/// Sound Channel 4 Length/Envelope (`NR41`, `NR42`). Read/Write. -pub const SOUND4CNT_L: VolAddress = - unsafe { VolAddress::new(0x400_0078) }; - -newtype! { - /// TODO: docs - LengthEnvelopeSetting, u32 // TODO: is this u32? -} - -impl LengthEnvelopeSetting { - phantom_fields! { - self.0: u32, - sound_length: 0-5, - envelope_step_time: 8-10, - envelope_increasing: 11, - initial_envelope_volume: 12-15, - } -} - -/// Sound Channel 4 Frequency/Control (`NR43`, `NR44`). Read/Write. -pub const SOUND4CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_007C) }; - -newtype! { - /// TODO: docs - NoiseFrequencySetting, u32 // TODO: is this u32? -} - -impl NoiseFrequencySetting { - phantom_fields! { - self.0: u32, - frequency_divide_ratio: 0-2, - counter_step_width_7bit: 3, - shift_clock_frequency: 4-7, - length_flag_stop: 14, - initial_restart: 15, - } -} - -// TODO: unify FIFO as - -/// Sound A FIFO, Data 0 and Data 1 (W) -pub const FIFO_A_L: VolAddress = unsafe { VolAddress::new(0x400_00A0) }; -/// Sound A FIFO, Data 2 and Data 3 (W) -pub const FIFO_A_H: VolAddress = unsafe { VolAddress::new(0x400_00A2) }; -/// Sound B FIFO, Data 0 and Data 1 (W) -pub const FIFO_B_L: VolAddress = unsafe { VolAddress::new(0x400_00A4) }; -/// Sound B FIFO, Data 2 and Data 3 (W) -pub const FIFO_B_H: VolAddress = unsafe { VolAddress::new(0x400_00A6) }; - -/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write. -pub const SOUNDCNT_L: VolAddress = - unsafe { VolAddress::new(0x400_0080) }; - -newtype! { - /// TODO: docs - NonWaveVolumeEnableSetting, u16 -} - -impl NonWaveVolumeEnableSetting { - phantom_fields! { - self.0: u16, - right_master_volume: 0-2, - left_master_volume: 4-6, - right_enable_flags: 8-11, // TODO: this is junk - left_enable_flags: 12-15, // TODO: junk - } -} - -/// DMA Sound Control/Mixing. Read/Write. -pub const SOUNDCNT_H: VolAddress = - unsafe { VolAddress::new(0x400_0082) }; - -newtype! { - /// TODO: docs - WaveVolumeEnableSetting, u16 -} - -impl WaveVolumeEnableSetting { - phantom_fields! { - self.0: u16, - sound_number_volume: 0-1=NumberSoundVolume, - dma_sound_a_full_volume: 2, - dma_sound_b_full_volume: 3, - dma_sound_a_enable_right: 8, - dma_sound_a_enable_left: 9, - dma_sound_a_timer_select: 10, - dma_sound_a_reset_fifo: 11, - dma_sound_b_enable_right: 12, - dma_sound_b_enable_left: 13, - dma_sound_b_timer_select: 14, - dma_sound_b_reset_fifo: 15, - } -} - -newtype_enum! { - /// TODO: docs - NumberSoundVolume = u16, - /// TODO: docs - Quarter = 0, - /// TODO: docs - Half = 1, - /// TODO: docs - Full = 2, -} - -/// Sound on/off (`NR52`). Read/Write. -pub const SOUNDCNT_X: VolAddress = - unsafe { VolAddress::new(0x400_0084) }; - -newtype! { - /// TODO: docs - SoundMasterSetting, u16 -} - -impl SoundMasterSetting { - phantom_fields! { - self.0: u16, - sound1_on: 0, - sound2_on: 1, - sound3_on: 2, - sound4_on: 3, - psg_fifo_master_enabled: 7, - } -} - -/// Sound on/off (`NR52`). Read/Write. -pub const SOUNDBIAS: VolAddress = - unsafe { VolAddress::new(0x400_0088) }; - -newtype! { - /// TODO: docs - SoundPWMSetting, u16 -} - -impl SoundMasterSetting { - phantom_fields! { - self.0: u16, - bias_level: 1-9, - amplitude_resolution: 14-15, // TODO: enum this - } -} diff --git a/src/io/timers.rs b/src/io/timers.rs deleted file mode 100644 index 8903176..0000000 --- a/src/io/timers.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Module for timers. -//! -//! The timers are slightly funny in that reading and writing from them works -//! somewhat differently than with basically any other part of memory. -//! -//! When you read a timer's counter you read the current value. -//! -//! When you write a timer's counter you write _the counter's reload value_. -//! This is used whenever you enable the timer or any time the timer overflows. -//! You cannot set a timer to a given counter value, but you can set a timer to -//! start at some particular value every time it reloads. -//! -//! The timer counters are `u16`, so if you want to set them to run for a -//! certain number of ticks before overflow you would write something like -//! -//! ```rust -//! let init_val: u16 = u32::wrapping_sub(0x1_0000, ticks) as u16; -//! ``` -//! -//! A timer reloads any time it overflows _or_ goes from disabled to enabled. If -//! you want to "pause" a timer _without_ making it reload when resumed then you -//! should not disable it. Instead, you should set its `TimerTickRate` to -//! `Cascade` and disable _the next lower timer_ so that it won't overflow into -//! the timer you have on hold. - -use super::*; - -// TODO: striding blocks? - -/// Timer 0 Counter/Reload. Special (see module). -pub const TM0CNT_L: VolAddress = unsafe { VolAddress::new(0x400_0100) }; - -/// Timer 1 Counter/Reload. Special (see module). -pub const TM1CNT_L: VolAddress = unsafe { VolAddress::new(0x400_0104) }; - -/// Timer 2 Counter/Reload. Special (see module). -pub const TM2CNT_L: VolAddress = unsafe { VolAddress::new(0x400_0108) }; - -/// Timer 3 Counter/Reload. Special (see module). -pub const TM3CNT_L: VolAddress = unsafe { VolAddress::new(0x400_010C) }; - -/// Timer 0 Control. Read/Write. -pub const TM0CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_0102) }; - -/// Timer 1 Control. Read/Write. -pub const TM1CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_0106) }; - -/// Timer 2 Control. Read/Write. -pub const TM2CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_010A) }; - -/// Timer 3 Control. Read/Write. -pub const TM3CNT_H: VolAddress = - unsafe { VolAddress::new(0x400_010E) }; - -newtype! { - /// Allows control of a timer unit. - /// - /// * Bits 0-2: How often the timer should tick up one unit. You can either - /// specify a number of CPU cycles or "cascade" mode, where there's a single - /// tick per overflow of the next lower timer. For example, Timer 1 would - /// tick up once per overflow of Timer 0 if it were in cascade mode. Cascade - /// mode naturally does nothing when used with Timer 0. - /// * Bit 6: Raise a timer interrupt upon overflow. - /// * Bit 7: Enable the timer. - TimerControlSetting, u16 -} -impl TimerControlSetting { - phantom_fields! { - self.0: u16, - tick_rate: 0-2=TimerTickRate, - overflow_irq: 6, - enabled: 7, - } -} - -/// Controls how often an enabled timer ticks upward. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum TimerTickRate { - /// Once every CPU cycle - CPU1 = 0, - /// Once per 64 CPU cycles - CPU64 = 1, - /// Once per 256 CPU cycles - CPU256 = 2, - /// Once per 1,024 CPU cycles - CPU1024 = 3, - /// Once per overflow of the next lower timer. (Useless with Timer 0) - Cascade = 4, -} diff --git a/src/io/window.rs b/src/io/window.rs deleted file mode 100644 index 0daeaeb..0000000 --- a/src/io/window.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Module that holds stuff for the Window ability. - -use super::*; - -/// Window 0 Horizontal Dimensions (W) -pub const WIN0H: VolAddress = - unsafe { VolAddress::new(0x400_0040) }; - -/// Window 1 Horizontal Dimensions (W) -pub const WIN1H: VolAddress = - unsafe { VolAddress::new(0x400_0042) }; - -newtype! { - /// TODO: docs - HorizontalWindowSetting, u16 -} - -impl HorizontalWindowSetting { - phantom_fields! { - self.0: u16, - col_end: 0-7, - col_start: 8-15, - } -} - -/// Window 0 Vertical Dimensions (W) -pub const WIN0V: VolAddress = - unsafe { VolAddress::new(0x400_0044) }; - -/// Window 1 Vertical Dimensions (W) -pub const WIN1V: VolAddress = - unsafe { VolAddress::new(0x400_0046) }; - -newtype! { - /// TODO: docs - VerticalWindowSetting, u16 -} - -impl VerticalWindowSetting { - phantom_fields! { - self.0: u16, - row_end: 0-7, - row_start: 8-15, - } -} - -/// Control of Inside of Window(s) (R/W) -pub const WININ: VolAddress = - unsafe { VolAddress::new(0x400_0048) }; - -newtype! { - /// TODO: docs - InsideWindowSetting, u16 -} - -impl InsideWindowSetting { - phantom_fields! { - self.0: u16, - win0_bg0: 0, - win0_bg1: 1, - win0_bg2: 2, - win0_bg3: 3, - win0_obj: 4, - win0_color_special: 5, - win1_bg0: 8, - win1_bg1: 9, - win1_bg2: 10, - win1_bg3: 11, - win1_obj: 12, - win1_color_special: 13, - } -} - -/// Control of Outside of Windows & Inside of OBJ Window (R/W) -pub const WINOUT: VolAddress = - unsafe { VolAddress::new(0x400_004A) }; - -newtype! { - /// TODO: docs - OutsideWindowSetting, u16 -} - -impl OutsideWindowSetting { - phantom_fields! { - self.0: u16, - outside_bg0: 0, - outside_bg1: 1, - outside_bg2: 2, - outside_bg3: 3, - outside_obj: 4, - outside_color_special: 5, - obj_win_bg0: 8, - obj_win_bg1: 9, - obj_win_bg2: 10, - obj_win_bg3: 11, - obj_win_obj: 12, - obj_win_color_special: 13, - } -} diff --git a/src/iwram.rs b/src/iwram.rs deleted file mode 100644 index 20bf81d..0000000 --- a/src/iwram.rs +++ /dev/null @@ -1 +0,0 @@ -//! Module for Internal Work RAM (`IWRAM`). diff --git a/src/lib.rs b/src/lib.rs index ecf25bd..f517fad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,49 +1,56 @@ -#![cfg_attr(not(test), no_std)] +#![no_std] #![feature(asm, global_asm, isa_attribute)] -#![allow(unused_imports)] -//#![warn(missing_docs)] //! This crate helps you write GBA ROMs. //! -//! ## SAFETY POLICY +//! ## Safety //! -//! Some parts of this crate are safe wrappers around unsafe operations. This is -//! good, and what you'd expect from a Rust crate. +//! This crate takes *minimal* precautions to avoid GBA specific code from being +//! run on a standard desktop by accident by using `#[cfg(target_arch = "arm")]` +//! in appropriate places. However, there are obviously many other ARM devices +//! in the world. If you actually run the GBA specific code on something that +//! isn't a GBA, then that's your fault. //! -//! However, the safe wrappers all assume that you will _only_ attempt to -//! execute this crate on a GBA or in a GBA Emulator. +//! ## Docs.rs //! -//! **Do not** use this crate in programs that aren't running on the GBA. If you -//! do, it's a giant bag of Undefined Behavior. +//! The docs on docs.rs are generated for the `thumbv6m-none-eabi` target +//! because the docs.rs docker image isn't currently able to use the +//! `-Zbuild-std=core` ability of cargo. Instead, we have it just build using a +//! "close enough" Tier 2 target. +//! +//! When building your actual GBA games you should of course use the +//! `thumbv4t-none-eabi` target. -pub(crate) use gba_proc_macro::phantom_fields; +pub mod prelude { + pub use crate::mmio_types::*; -use voladdress::*; + #[cfg(target_arch = "arm")] + pub use crate::mmio_addresses::*; -pub mod macros; + #[cfg(target_arch = "arm")] + pub use crate::bios::*; +} +pub mod mmio_types; + +#[cfg(target_arch = "arm")] +pub mod mmio_addresses; + +#[cfg(target_arch = "arm")] pub mod bios; -pub mod iwram; - -pub mod ewram; - -pub mod io; - -pub mod palram; - -pub mod vram; - -pub mod oam; - -pub mod rom; - -pub mod save; +pub mod art; +#[cfg(target_arch = "arm")] pub mod sync; +#[cfg(target_arch = "arm")] +pub mod save; + +#[cfg(target_arch = "arm")] pub mod debug; +/* extern "C" { /// This marks the end of the `.data` and `.bss` sections in IWRAM. /// @@ -56,25 +63,7 @@ extern "C" { pub static __bss_end: u8; } -newtype! { - /// A color on the GBA is an RGB 5.5.5 within a `u16` - #[derive(PartialOrd, Ord, Hash)] - Color, pub u16 -} - -impl Color { - /// Constructs a color from the channel values provided (should be 0..=31). - /// - /// No actual checks are performed, so illegal channel values can overflow - /// into each other and produce an unintended color. - pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color { - Color(b << 10 | g << 5 | r) - } -} - -// -// After here is totally unsorted nonsense -// +TODO: math module for math functions you probably want on the GBA /// Performs unsigned divide and remainder, gives None if dividing by 0. pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> { @@ -180,55 +169,4 @@ pub unsafe fn divrem_i32_unchecked(numer: i32, denom: i32) -> (i32, i32) { (false, false) => (udiv as i32, urem as i32), } } - -/* -#[cfg(test)] -mod tests { - use super::*; - use quickcheck::quickcheck; - - // We have an explicit property on the non_restoring division - quickcheck! { - fn divrem_u32_non_restoring_prop(num: u32, denom: u32) -> bool { - if denom > 0 { - divrem_u32_non_restoring(num, denom) == (num / denom, num % denom) - } else { - true - } - } - } - - // We have an explicit property on the simple division - quickcheck! { - fn divrem_u32_simple_prop(num: u32, denom: u32) -> bool { - if denom > 0 { - divrem_u32_simple(num, denom) == (num / denom, num % denom) - } else { - true - } - } - } - - // Test the u32 wrapper - quickcheck! { - fn divrem_u32_prop(num: u32, denom: u32) -> bool { - if denom > 0 { - divrem_u32(num, denom).unwrap() == (num / denom, num % denom) - } else { - divrem_u32(num, denom).is_none() - } - } - } - - // test the i32 wrapper - quickcheck! { - fn divrem_i32_prop(num: i32, denom: i32) -> bool { - if denom == 0 || num == core::i32::MIN && denom == -1 { - divrem_i32(num, denom).is_none() - } else { - divrem_i32(num, denom).unwrap() == (num / denom, num % denom) - } - } - } -} */ diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index bfb5237..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! Contains the macros for the crate. -//! -//! Because (unlike everything else in Rust) a macro has to be declared before -//! use, we place them in their own module and then declare that module at the -//! start of the crate. - -/// Assists in defining a newtype wrapper over some base type. -/// -/// Note that rustdoc and derives are all the "meta" stuff, so you can write all -/// of your docs and derives in front of your newtype in the same way you would -/// for a normal struct. Then the inner type to be wrapped it name. -/// -/// The macro _assumes_ that you'll be using it to wrap numeric types and that -/// it's safe to have a `0` value, so it automatically provides a `const fn` -/// method for `new` that just wraps `0`. Also, it derives Debug, Clone, Copy, -/// Default, PartialEq, and Eq. If all this is not desired you can add `, no -/// frills` to the invocation. -/// -/// ```no_run -/// newtype! { -/// /// Records a particular key press combination. -/// KeyInput, u16 -/// } -/// newtype! { -/// /// You can't derive most stuff above array size 32, so we add -/// /// the `, no frills` modifier to this one. -/// BigArray, [u8; 200], no frills -/// } -/// ``` -#[macro_export] -macro_rules! newtype { - ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => { - $(#[$attr])* - #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] - #[repr(transparent)] - pub struct $new_name($v $old_name); - impl $new_name { - /// A `const` "zero value" constructor - pub const fn new() -> Self { - $new_name(0) - } - } - }; - ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => { - $(#[$attr])* - #[repr(transparent)] - pub struct $new_name($v $old_name); - }; -} - -/// Assists in defining a newtype that's an enum. -/// -/// First give `NewType = OldType,`, then define the tags and their explicit -/// values with zero or more entries of `TagName = base_value,`. In both cases -/// you can place doc comments or other attributes directly on to the type -/// declaration or the tag declaration. -/// -/// The generated enum will get an appropriate `repr` attribute as well as -/// Debug, Clone, Copy, PartialEq, and Eq -/// -/// ```no_run -/// newtype_enum! { -/// /// The Foo -/// Foo = u16, -/// /// The Bar -/// Bar = 0, -/// /// The Zap -/// Zap = 1, -/// } -/// ``` -#[macro_export] -macro_rules! newtype_enum { - ( - $(#[$struct_attr:meta])* - $new_name:ident = $old_name:ident, - $($(#[$tag_attr:meta])* $tag_name:ident = $base_value:expr,)* - ) => { - $(#[$struct_attr])* - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr($old_name)] - pub enum $new_name { - $( - $(#[$tag_attr])* - $tag_name = $base_value, - )* - } - }; -} - -/// Delivers a fatal message to the emulator debug output, and crashes -/// the the game. -/// -/// This works basically like `println`. You should avoid null ASCII values. -/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. -/// -/// This has no effect outside of a supported emulator. -#[macro_export] -macro_rules! fatal { - ($($arg:tt)*) => {{ - use $crate::debug; - if !debug::is_debugging_disabled() { - debug::debug_print(debug::DebugLevel::Fatal, &format_args!($($arg)*)).ok(); - } - debug::crash() - }}; -} - -/// Delivers a error message to the emulator debug output. -/// -/// This works basically like `println`. You should avoid null ASCII values. -/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. -/// -/// This has no effect outside of a supported emulator. -#[macro_export] -macro_rules! error { - ($($arg:tt)*) => {{ - use $crate::debug; - if !debug::is_debugging_disabled() { - debug::debug_print(debug::DebugLevel::Error, &format_args!($($arg)*)).ok(); - } - }}; -} - -/// Delivers a warning message to the emulator debug output. -/// -/// This works basically like `println`. You should avoid null ASCII values. -/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. -/// -/// This has no effect outside of a supported emulator. -#[macro_export] -macro_rules! warn { - ($($arg:tt)*) => {{ - use $crate::debug; - if !debug::is_debugging_disabled() { - debug::debug_print(debug::DebugLevel::Warning, &format_args!($($arg)*)).ok(); - } - }}; -} - -/// Delivers an info message to the emulator debug output. -/// -/// This works basically like `println`. You should avoid null ASCII values. -/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. -/// -/// This has no effect outside of a supported emulator. -#[macro_export] -macro_rules! info { - ($($arg:tt)*) => {{ - use $crate::debug; - if !debug::is_debugging_disabled() { - debug::debug_print(debug::DebugLevel::Info, &format_args!($($arg)*)).ok(); - } - }}; -} - -/// Delivers a debug message to the emulator debug output. -/// -/// This works basically like `println`. You should avoid null ASCII values. -/// Furthermore on mGBA, there is a maximum length of 255 bytes per message. -/// -/// This has no effect outside of a supported emulator. -#[macro_export] -macro_rules! debug { - ($($arg:tt)*) => {{ - use $crate::debug; - if !debug::is_debugging_disabled() { - debug::debug_print(debug::DebugLevel::Debug, &format_args!($($arg)*)).ok(); - } - }}; -} - -/// Using timers 0 and 1, performs a crude timing of the expression given. -#[macro_export] -macro_rules! time_this01 { - ($x:expr) => {{ - use $crate::io::timers::*; - const NORMAL_ON: TimerControlSetting = TimerControlSetting::new().with_enabled(true); - const CASCADE_ON: TimerControlSetting = - TimerControlSetting::new().with_enabled(true).with_tick_rate(TimerTickRate::Cascade); - const OFF: TimerControlSetting = TimerControlSetting::new(); - TM1CNT_H.write(CASCADE_ON); - TM0CNT_H.write(NORMAL_ON); - $x; - TM0CNT_H.write(OFF); - TM1CNT_H.write(OFF); - let end_low = TM0CNT_L.read() as u32; - let end_high = TM1CNT_L.read() as u32; - end_high << 16 | end_low - }}; -} diff --git a/src/mmio_addresses.rs b/src/mmio_addresses.rs new file mode 100644 index 0000000..315ce34 --- /dev/null +++ b/src/mmio_addresses.rs @@ -0,0 +1,289 @@ +use crate::prelude::*; + +use voladdress::*; + +pub mod mode3; + +// TODO: modules for the other video modes + +/// [DISPCNT](https://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) +pub const DISPCNT: VolAddress = unsafe { VolAddress::new(0x0400_0000) }; + +/// [DISPSTAT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus) +pub const DISPSTAT: VolAddress = unsafe { VolAddress::new(0x0400_0004) }; + +/// [VCOUNT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus) +pub const VCOUNT: VolAddress = unsafe { VolAddress::new(0x0400_0006) }; + +/// [BG0CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol) +pub const BG0CNT: VolAddress = + unsafe { VolAddress::new(0x0400_0008) }; + +/// [BG1CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol) +pub const BG1CNT: VolAddress = + unsafe { VolAddress::new(0x0400_000A) }; + +/// [BG2CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol) +pub const BG2CNT: VolAddress = + unsafe { VolAddress::new(0x0400_000C) }; + +/// [BG3CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol) +pub const BG3CNT: VolAddress = + unsafe { VolAddress::new(0x0400_000E) }; + +/// [BG0HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG0HOFS: VolAddress = unsafe { VolAddress::new(0x0400_0010) }; +/// [BG0VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG0VOFS: VolAddress = unsafe { VolAddress::new(0x0400_0012) }; + +/// [BG1HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG1HOFS: VolAddress = unsafe { VolAddress::new(0x0400_0014) }; +/// [BG1VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG1VOFS: VolAddress = unsafe { VolAddress::new(0x0400_0016) }; + +/// [BG2HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG2HOFS: VolAddress = unsafe { VolAddress::new(0x0400_0018) }; +/// [BG2VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG2VOFS: VolAddress = unsafe { VolAddress::new(0x0400_001A) }; + +/// [BG3HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG3HOFS: VolAddress = unsafe { VolAddress::new(0x0400_001C) }; +/// [BG3VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling) +pub const BG3VOFS: VolAddress = unsafe { VolAddress::new(0x0400_001E) }; + +/// [BG2PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2PA: VolAddress = unsafe { VolAddress::new(0x0400_0020) }; +/// [BG2PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2PB: VolAddress = unsafe { VolAddress::new(0x0400_0022) }; +/// [BG2PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2PC: VolAddress = unsafe { VolAddress::new(0x0400_0024) }; +/// [BG2PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2PD: VolAddress = unsafe { VolAddress::new(0x0400_0026) }; + +/// [BG2X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2X: VolAddress = unsafe { VolAddress::new(0x0400_0028) }; +/// [BG2Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG2Y: VolAddress = unsafe { VolAddress::new(0x0400_002C) }; + +/// [BG3PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3PA: VolAddress = unsafe { VolAddress::new(0x0400_0030) }; +/// [BG3PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3PB: VolAddress = unsafe { VolAddress::new(0x0400_0032) }; +/// [BG3PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3PC: VolAddress = unsafe { VolAddress::new(0x0400_0034) }; +/// [BG3PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3PD: VolAddress = unsafe { VolAddress::new(0x0400_0036) }; + +/// [BG3X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3X: VolAddress = unsafe { VolAddress::new(0x0400_0038) }; +/// [BG3Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling) +pub const BG3Y: VolAddress = unsafe { VolAddress::new(0x0400_003C) }; + +/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN0H_RIGHT: VolAddress = unsafe { VolAddress::new(0x0400_0040) }; +/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN0H_LEFT: VolAddress = unsafe { VolAddress::new(0x0400_0041) }; + +/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN1H_RIGHT: VolAddress = unsafe { VolAddress::new(0x0400_0042) }; +/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN1H_LEFT: VolAddress = unsafe { VolAddress::new(0x0400_0043) }; + +/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN0V_BOTTOM: VolAddress = unsafe { VolAddress::new(0x0400_0044) }; +/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN0V_TOP: VolAddress = unsafe { VolAddress::new(0x0400_0045) }; + +/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN1V_BOTTOM: VolAddress = unsafe { VolAddress::new(0x0400_0046) }; +/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN1V_TOP: VolAddress = unsafe { VolAddress::new(0x0400_0047) }; + +/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN_IN_0: VolAddress = unsafe { VolAddress::new(0x0400_0048) }; +/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN_IN_1: VolAddress = unsafe { VolAddress::new(0x0400_0049) }; +/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN_OUT: VolAddress = unsafe { VolAddress::new(0x0400_004A) }; +/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature) +pub const WIN_IN_OBJ: VolAddress = + unsafe { VolAddress::new(0x0400_004B) }; + +/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction) +pub const MOSAIC_BG: VolAddress = unsafe { VolAddress::new(0x0400_004C) }; +/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction) +pub const MOSAIC_OBJ: VolAddress = unsafe { VolAddress::new(0x0400_004D) }; + +/// [BLDCNT](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects) +pub const BLDCNT: VolAddress = unsafe { VolAddress::new(0x0400_0050) }; + +/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects) +pub const BLDALPHA_A: VolAddress = unsafe { VolAddress::new(0x0400_0052) }; + +/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects) +pub const BLDALPHA_B: VolAddress = unsafe { VolAddress::new(0x0400_0053) }; + +/// [BLDY](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects) +pub const BLDY: VolAddress = unsafe { VolAddress::new(0x0400_0054) }; + +/// [SOUND1CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep) +pub const TONE1_SWEEP: VolAddress = unsafe { VolAddress::new(0x0400_0060) }; +/// [SOUND1CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep) +pub const TONE1_DUTY_LEN_ENV: VolAddress = + unsafe { VolAddress::new(0x0400_0062) }; +/// [SOUND1CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep) +pub const TONE1_FREQ_CNT: VolAddress = + unsafe { VolAddress::new(0x0400_0064) }; + +/// [SOUND2CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone) +pub const TONE2_DUTY_LEN_ENV: VolAddress = + unsafe { VolAddress::new(0x0400_0068) }; +/// [SOUND2CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone) +pub const TONE2_FREQ_CNT: VolAddress = + unsafe { VolAddress::new(0x0400_006C) }; + +/// [SOUND3CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput) +pub const WAVE_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_0070) }; +/// [SOUND3CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput) +pub const WAVE_LEN_VOLUME: VolAddress = + unsafe { VolAddress::new(0x0400_0072) }; +/// [SOUND3CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput) +pub const WAVE_FREQ_CNT: VolAddress = + unsafe { VolAddress::new(0x0400_0074) }; +/// [WAVE_RAM](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput) +pub const WAVE_RAM: VolBlock = unsafe { VolBlock::new(0x0400_0090) }; + +/// [SOUND4CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise) +pub const NOISE_LEN_ENV: VolAddress = + unsafe { VolAddress::new(0x0400_0078) }; +/// [SOUND4CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise) +pub const NOISE_FREQ_CNT: VolAddress = + unsafe { VolAddress::new(0x0400_007C) }; + +/// [FIFO_A](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound) +pub const FIFO_A: VolAddress = unsafe { VolAddress::new(0x0400_00A0) }; +/// [FIFO_B](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound) +pub const FIFO_B: VolAddress = unsafe { VolAddress::new(0x0400_00A4) }; + +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Source Address (W) (internal memory) +pub const DMA0SAD: VolAddress = unsafe { VolAddress::new(0x0400_00B0) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Destination Address (W) (internal memory) +pub const DMA0DAD: VolAddress = unsafe { VolAddress::new(0x0400_00B4) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Word Count (W) (14 bit, 1..4000h) +pub const DMA0CNT_L: VolAddress = unsafe { VolAddress::new(0x0400_00B8) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Control (R/W) +pub const DMA0CNT_H: VolAddress = unsafe { VolAddress::new(0x0400_00BA) }; + +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Source Address (W) (any memory) +pub const DMA1SAD: VolAddress = unsafe { VolAddress::new(0x0400_00BC) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Destination Address (W) (internal memory) +pub const DMA1DAD: VolAddress = unsafe { VolAddress::new(0x0400_00C0) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Word Count (W) (14 bit, 1..4000h) +pub const DMA1CNT_L: VolAddress = unsafe { VolAddress::new(0x0400_00C4) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Control (R/W) +pub const DMA1CNT_H: VolAddress = unsafe { VolAddress::new(0x0400_00C6) }; + +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Source Address (W) (any memory) +pub const DMA2SAD: VolAddress = unsafe { VolAddress::new(0x0400_00C8) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Destination Address (W) (internal memory) +pub const DMA2DAD: VolAddress = unsafe { VolAddress::new(0x0400_00CC) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Word Count (W) (14 bit, 1..4000h) +pub const DMA2CNT_L: VolAddress = unsafe { VolAddress::new(0x0400_00D0) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Control (R/W) +pub const DMA2CNT_H: VolAddress = unsafe { VolAddress::new(0x0400_00D2) }; + +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Source Address (W) (any memory) +pub const DMA3SAD: VolAddress = unsafe { VolAddress::new(0x0400_00D4) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Destination Address (W) (any memory) +pub const DMA3DAD: VolAddress = unsafe { VolAddress::new(0x0400_00D8) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Word Count (W) (16 bit, 1..10000h) +pub const DMA3CNT_L: VolAddress = unsafe { VolAddress::new(0x0400_00DC) }; +/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Control (R/W) +pub const DMA3CNT_H: VolAddress = unsafe { VolAddress::new(0x0400_00DE) }; + +/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER0_COUNTER: VolAddress = unsafe { VolAddress::new(0x0400_0100) }; +/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER1_COUNTER: VolAddress = unsafe { VolAddress::new(0x0400_0104) }; +/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER2_COUNTER: VolAddress = unsafe { VolAddress::new(0x0400_0108) }; +/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER3_COUNTER: VolAddress = unsafe { VolAddress::new(0x0400_010C) }; + +/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER0_RELOAD: VolAddress = unsafe { VolAddress::new(0x0400_0100) }; +/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER1_RELOAD: VolAddress = unsafe { VolAddress::new(0x0400_0104) }; +/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER2_RELOAD: VolAddress = unsafe { VolAddress::new(0x0400_0108) }; +/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER3_RELOAD: VolAddress = unsafe { VolAddress::new(0x0400_010C) }; + +/// [TM0CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER0_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_0102) }; +/// [TM1CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER1_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_0106) }; +/// [TM2CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER2_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_010A) }; +/// [TM3CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers) +pub const TIMER3_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_010E) }; + +/// [SIOCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports) +pub const SIOCNT: VolAddress = unsafe { VolAddress::new(0x400_0128) }; +/// [SIODATA8](https://problemkaputt.de/gbatek.htm#gbacommunicationports) +pub const SIODATA8: VolAddress = unsafe { VolAddress::new(0x400_012A) }; +/// [RCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports) +pub const RCNT: VolAddress = unsafe { VolAddress::new(0x0400_0134) }; + +/// [KEYINPUT](https://problemkaputt.de/gbatek.htm#gbakeypadinput) +pub const KEYINPUT: VolAddress = unsafe { VolAddress::new(0x0400_0130) }; +/// [KEYCNT](https://problemkaputt.de/gbatek.htm#gbakeypadinput) +pub const KEYCNT: VolAddress = + unsafe { VolAddress::new(0x0400_0130) }; + +/// Points to the (A32) user interrupt handler function. +pub const USER_IRQ_HANDLER: VolAddress, Safe, Unsafe> = + unsafe { VolAddress::new(0x0300_7FFC) }; +/// "Interrupt Master Enable", [IME](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) +pub const IME: VolAddress = unsafe { VolAddress::new(0x0400_0208) }; +/// "Interrupts Enabled", [IE](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) +pub const IE: VolAddress = unsafe { VolAddress::new(0x0400_0200) }; +/// Shows which interrupts are pending. +/// +/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (reading) +pub const IRQ_PENDING: VolAddress = + unsafe { VolAddress::new(0x0400_0202) }; +/// Acknowledges an interrupt as having been handled. +/// +/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (writing) +pub const IRQ_ACKNOWLEDGE: VolAddress = + unsafe { VolAddress::new(0x0400_0202) }; +/// Use this during [`IntrWait`] and [`VBlankIntrWait`] interrupt handling. +/// +/// You should: +/// * read the current value +/// * set any additional interrupt bits that you wish to mark as handled (do not +/// clear any currently set bits!) +/// * write the new value back to this register +/// +/// ```no_run +/// # use crate::prelude::*; +/// // to acknowledge a vblank interrupt +/// let current = INTR_WAIT_ACKNOWLEDGE.read(); +/// unsafe { INTR_WAIT_ACKNOWLEDGE.write(current.with_vblank(true)) }; +/// ``` +/// +/// [GBATEK: IntrWait](https://problemkaputt.de/gbatek.htm#bioshaltfunctions) +pub const INTR_WAIT_ACKNOWLEDGE: VolAddress = unsafe { + // Note(Lokathor): This uses a mirrored location that's closer to the main IO + // Control memory region so that LLVM has a better chance of being able to + // just do an offset read/write from an address that's already in a register. + // The "base" address of this location is 0x0300_7FF8, so some documents may + // refer to that value instead. + VolAddress::new(0x0300_FFF8) +}; diff --git a/src/mmio_addresses/mode3.rs b/src/mmio_addresses/mode3.rs new file mode 100644 index 0000000..75460ff --- /dev/null +++ b/src/mmio_addresses/mode3.rs @@ -0,0 +1,42 @@ +use crate::prelude::Color; + +use voladdress::*; + +pub const WIDTH: usize = 240; + +pub const HEIGHT: usize = 160; + +pub const BITMAP: VolBlock = + unsafe { VolBlock::new(0x0600_0000) }; + +pub const fn bitmap_xy(x: usize, y: usize) -> VolAddress { + BITMAP.index(y * WIDTH + x) +} + +pub fn dma3_clear_to(color: Color) { + use crate::prelude::{ + DestAddrControl, DmaControl, DmaStartTiming, SrcAddrControl, DMA3CNT_H, DMA3CNT_L, DMA3DAD, + DMA3SAD, + }; + let wide_color: u32 = color.0 as u32 | ((color.0 as u32) << 16); + unsafe { + DMA3SAD.write(&wide_color as *const _ as usize); + DMA3DAD.write(0x0600_0000); + const MODE3_WORD_COUNT: u16 = (WIDTH * HEIGHT * 2 / 4_usize) as u16; + DMA3CNT_L.write(MODE3_WORD_COUNT); + const CTRL: DmaControl = DmaControl::new() + .with_dest_addr(DestAddrControl::Increment) + .with_src_addr(SrcAddrControl::Fixed) + .with_transfer_u32(true) + .with_start_time(DmaStartTiming::Immediately) + .with_enabled(true); + DMA3CNT_H.write(CTRL); + asm!( + " + nop + nop + ", + options(nostack), + ); + } +} diff --git a/src/mmio_types.rs b/src/mmio_types.rs new file mode 100644 index 0000000..1e2b4ad --- /dev/null +++ b/src/mmio_types.rs @@ -0,0 +1,145 @@ +#![allow(unused)] + +/// Sets up a constant new constructor for a zeroed value. +macro_rules! const_new { + () => { + pub const fn new() -> Self { + Self(0) + } + }; +} +pub(crate) use const_new; + +/// Sets up a bitfield integer +macro_rules! bitfield_int { + ($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => { + pub const fn $get(self) -> $nt { + const MASK: $inner = ((1 << $high) - 1) << $low; + ((self.0 & MASK) >> $low) as $nt + } + pub const fn $with(self, $get: $nt) -> Self { + const MASK: $inner = ((1 << $high) - 1) << $low; + Self(self.0 ^ ((self.0 ^ ($get as $inner)) & MASK)) + } + pub fn $set(&mut self, $get: $nt) { + *self = self.$with($get); + } + }; +} +pub(crate) use bitfield_int; + +/// Sets up a bitfield int wrapped newtype +macro_rules! bitfield_newtype { + ($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => { + pub const fn $get(self) -> $nt { + const MASK: $inner = ((1 << $high) - 1) << $low; + $nt(self.0 & MASK) + } + pub const fn $with(self, $get: $nt) -> Self { + const MASK: $inner = ((1 << $high) - 1) << $low; + Self(self.0 ^ ((self.0 ^ $get.0) & MASK)) + } + pub fn $set(&mut self, $get: $nt) { + *self = self.$with($get); + } + }; +} +pub(crate) use bitfield_newtype; + +/// Sets up a bitfield enum (CAUTION: misuse of this can cause UB!) +macro_rules! bitfield_enum { + ($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => { + // TODO: make this const when we have const transmute + pub fn $get(self) -> $nt { + const MASK: $inner = ((1 << $high) - 1) << $low; + unsafe { core::mem::transmute(self.0 & MASK) } + } + pub const fn $with(self, $get: $nt) -> Self { + const MASK: $inner = ((1 << $high) - 1) << $low; + Self(self.0 ^ ((self.0 ^ $get as $inner) & MASK)) + } + pub fn $set(&mut self, $get: $nt) { + *self = self.$with($get); + } + }; +} +pub(crate) use bitfield_enum; + +/// Sets up a bitfield bool +macro_rules! bitfield_bool { + ($inner:ty; $bit:literal, $get:ident, $with:ident, $set:ident) => { + pub const fn $get(self) -> bool { + (self.0 & (1 << $bit)) != 0 + } + pub const fn $with(self, $get: bool) -> Self { + Self(self.0 ^ ((($get as $inner).wrapping_neg() ^ self.0) & (1 << $bit))) + } + pub fn $set(&mut self, $get: bool) { + *self = self.$with($get); + } + }; +} +pub(crate) use bitfield_bool; + +mod display_control; +pub use display_control::*; + +mod display_status; +pub use display_status::*; + +mod background_control; +pub use background_control::*; + +mod window_enable; +pub use window_enable::*; + +mod mosaic_size; +pub use mosaic_size::*; + +mod blend_control; +pub use blend_control::*; + +mod color; +pub use color::*; + +mod keys; +pub use keys::*; + +mod dma_control; +pub use dma_control::*; + +mod key_interrupt_control; +pub use key_interrupt_control::*; + +mod register_ram_reset_control; +pub use register_ram_reset_control::*; + +mod interrupt_flags; +pub use interrupt_flags::*; + +mod timer_control; +pub use timer_control::*; + +mod tone_duty_len_env; +pub use tone_duty_len_env::*; + +mod tone_frequency_control; +pub use tone_frequency_control::*; + +mod tone_sweep; +pub use tone_sweep::*; + +mod wave_control; +pub use wave_control::*; + +mod wave_len_volume; +pub use wave_len_volume::*; + +mod wave_frequency_control; +pub use wave_frequency_control::*; + +mod noise_len_env; +pub use noise_len_env::*; + +mod noise_frequency_control; +pub use noise_frequency_control::*; diff --git a/src/mmio_types/background_control.rs b/src/mmio_types/background_control.rs new file mode 100644 index 0000000..fa72610 --- /dev/null +++ b/src/mmio_types/background_control.rs @@ -0,0 +1,15 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct BackgroundControl(u16); +impl BackgroundControl { + const_new!(); + bitfield_int!(u16; 0..=1: u8, priority, with_priority, set_priority); + bitfield_int!(u16; 2..=3: u8, char_base_block, with_char_base_block, set_char_base_block); + bitfield_bool!(u16; 6, mosaic, with_mosaic, set_mosaic); + bitfield_bool!(u16; 7, is_8bpp, with_is_8bpp, set_is_8bpp); + bitfield_int!(u16; 8..=12: u8, screen_base_block, with_screen_base_block, set_screen_base_block); + bitfield_bool!(u16; 13, affine_overflow_wrapped, with_affine_overflow_wrapped, set_affine_overflow_wrapped); + bitfield_int!(u16; 14..=15: u8, screen_size, with_screen_size, set_screen_size); +} diff --git a/src/mmio_types/blend_control.rs b/src/mmio_types/blend_control.rs new file mode 100644 index 0000000..cb60c40 --- /dev/null +++ b/src/mmio_types/blend_control.rs @@ -0,0 +1,30 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct BlendControl(u16); +impl BlendControl { + const_new!(); + bitfield_bool!(u16; 0, bg0_1st_target, with_bg0_1st_target, set_bg0_1st_target); + bitfield_bool!(u16; 1, bg1_1st_target, with_bg1_1st_target, set_bg1_1st_target); + bitfield_bool!(u16; 2, bg2_1st_target, with_bg2_1st_target, set_bg2_1st_target); + bitfield_bool!(u16; 3, bg3_1st_target, with_bg3_1st_target, set_bg3_1st_target); + bitfield_bool!(u16; 4, obj_1st_target, with_obj_1st_target, set_obj_1st_target); + bitfield_bool!(u16; 5, backdrop_1st_target, with_backdrop_1st_target, set_backdrop_1st_target); + bitfield_enum!(u16; 6..=7: ColorSpecialEffect, effect, with_effect, set_effect); + bitfield_bool!(u16; 8, bg0_2nd_target, with_bg0_2nd_target, set_bg0_2nd_target); + bitfield_bool!(u16; 9, bg1_2nd_target, with_bg1_2nd_target, set_bg1_2nd_target); + bitfield_bool!(u16; 10, bg2_2nd_target, with_bg2_2nd_target, set_bg2_2nd_target); + bitfield_bool!(u16; 11, bg3_2nd_target, with_bg3_2nd_target, set_bg3_2nd_target); + bitfield_bool!(u16; 12, obj_2nd_target, with_obj_2nd_target, set_obj_2nd_target); + bitfield_bool!(u16; 13, backdrop_2nd_target, with_backdrop_2nd_target, set_backdrop_2nd_target); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ColorSpecialEffect { + NoEffect = 0 << 6, + AlphaBlend = 1 << 6, + BrightnessIncrease = 2 << 6, + BrightnessDecrease = 3 << 6, +} diff --git a/src/mmio_types/color.rs b/src/mmio_types/color.rs new file mode 100644 index 0000000..46d4d1d --- /dev/null +++ b/src/mmio_types/color.rs @@ -0,0 +1,19 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct Color(pub u16); +impl Color { + const_new!(); + bitfield_int!(u16; 0..=4: u8, red, with_red, set_red); + bitfield_int!(u16; 5..=9: u8, green, with_green, set_green); + bitfield_int!(u16; 10..=14: u8, blue, with_blue, set_blue); +} +impl Color { + pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self { + let r = red as u16; + let g = green as u16; + let b = blue as u16; + Self(b << 10 | g << 15 | r) + } +} diff --git a/src/mmio_types/display_control.rs b/src/mmio_types/display_control.rs new file mode 100644 index 0000000..129e5b8 --- /dev/null +++ b/src/mmio_types/display_control.rs @@ -0,0 +1,36 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct DisplayControl(u16); +impl DisplayControl { + const_new!(); + bitfield_int!(u16; 0..=2: u16, display_mode, with_display_mode, set_display_mode); + bitfield_bool!(u16; 4, display_frame1, with_display_frame1, set_display_frame1); + bitfield_bool!(u16; 5, hblank_interval_free, with_hblank_interval_free, set_hblank_interval_free); + bitfield_bool!(u16; 6, obj_vram_1d, with_obj_vram_1d, set_obj_vram_1d); + bitfield_bool!(u16; 7, forced_blank, with_forced_blank, set_forced_blank); + bitfield_bool!(u16; 8, display_bg0, with_display_bg0, set_display_bg0); + bitfield_bool!(u16; 9, display_bg1, with_display_bg1, set_display_bg1); + bitfield_bool!(u16; 10, display_bg2, with_display_bg2, set_display_bg2); + bitfield_bool!(u16; 11, display_bg3, with_display_bg3, set_display_bg3); + bitfield_bool!(u16; 12, display_obj, with_display_obj, set_display_obj); + bitfield_bool!(u16; 13, display_win0, with_display_win0, set_display_win0); + bitfield_bool!(u16; 14, display_win1, with_display_win1, set_display_win1); + bitfield_bool!(u16; 15, display_obj_win, with_display_obj_win, set_display_obj_win); +} + +/* +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DisplayMode { + _0 = 0, + _1 = 1, + _2 = 2, + _3 = 3, + _4 = 4, + _5 = 5, + _6 = 6, + _7 = 7, +} +*/ diff --git a/src/mmio_types/display_status.rs b/src/mmio_types/display_status.rs new file mode 100644 index 0000000..e62fea1 --- /dev/null +++ b/src/mmio_types/display_status.rs @@ -0,0 +1,15 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct DisplayStatus(u16); +impl DisplayStatus { + const_new!(); + bitfield_bool!(u16; 0, is_vblank, with_is_vblank, set_is_vblank); + bitfield_bool!(u16; 1, is_hblank, with_is_hblank, set_is_hblank); + bitfield_bool!(u16; 2, is_vcount, with_is_vcount, set_is_vcount); + bitfield_bool!(u16; 3, vblank_irq_enabled, with_vblank_irq_enabled, set_vblank_irq_enabled); + bitfield_bool!(u16; 4, hblank_irq_enabled, with_hblank_irq_enabled, set_hblank_irq_enabled); + bitfield_bool!(u16; 5, vcount_irq_enabled, with_vcount_irq_enabled, set_vcount_irq_enabled); + bitfield_int!(u16; 8..=15: u16, vcount, with_vcount, set_vcount); +} diff --git a/src/mmio_types/dma_control.rs b/src/mmio_types/dma_control.rs new file mode 100644 index 0000000..fd67ca9 --- /dev/null +++ b/src/mmio_types/dma_control.rs @@ -0,0 +1,44 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct DmaControl(u16); +impl DmaControl { + const_new!(); + bitfield_enum!(u16; 5..=6: DestAddrControl, dest_addr, with_dest_addr, set_dest_addr); + bitfield_enum!(u16; 7..=8: SrcAddrControl, src_addr, with_src_addr, set_src_addr); + bitfield_bool!(u16; 9, dma_repeat, with_dma_repeat, set_dma_repeat); + bitfield_bool!(u16; 10, transfer_u32, with_transfer_u32, set_transfer_u32); + bitfield_bool!(u16; 11, drq_from_game_pak, with_drq_from_game_pak, set_drq_from_game_pak); + bitfield_enum!(u16; 12..=13: DmaStartTiming, start_time, with_start_time, set_start_time); + bitfield_bool!(u16; 14, irq_when_done, with_irq_when_done, set_irq_when_done); + bitfield_bool!(u16; 15, enabled, with_enabled, set_enabled); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DestAddrControl { + Increment = 0 << 5, + Decrement = 1 << 5, + Fixed = 2 << 5, + IncrementReload = 3 << 5, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum SrcAddrControl { + Increment = 0 << 7, + Decrement = 1 << 7, + Fixed = 2 << 7, + /// Never use this value, it is only to guard against UB bit patterns + Prohibited = 3 << 7, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DmaStartTiming { + Immediately = 0 << 12, + VBlank = 1 << 12, + HBlank = 2 << 12, + Special = 3 << 12, +} diff --git a/src/mmio_types/interrupt_flags.rs b/src/mmio_types/interrupt_flags.rs new file mode 100644 index 0000000..2bac83e --- /dev/null +++ b/src/mmio_types/interrupt_flags.rs @@ -0,0 +1,23 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct InterruptFlags(pub(crate) u16); +impl InterruptFlags { + const_new!(); + bitfield_bool!(u16; 0, vblank, with_vblank, set_vblank); + bitfield_bool!(u16; 1, hblank, with_hblank, set_hblank); + bitfield_bool!(u16; 2, vcount, with_vcount, set_vcount); + bitfield_bool!(u16; 3, timer0, with_timer0, set_timer0); + bitfield_bool!(u16; 4, timer1, with_timer1, set_timer1); + bitfield_bool!(u16; 5, timer2, with_timer2, set_timer2); + bitfield_bool!(u16; 6, timer3, with_timer3, set_timer3); + bitfield_bool!(u16; 7, serial, with_serial, set_serial); + bitfield_bool!(u16; 8, dma0, with_dma0, set_dma0); + bitfield_bool!(u16; 9, dma1, with_dma1, set_dma1); + bitfield_bool!(u16; 10, dma2, with_dma2, set_dma2); + bitfield_bool!(u16; 11, dma3, with_dma3, set_dma3); + bitfield_bool!(u16; 12, keypad, with_keypad, set_keypad); + bitfield_bool!(u16; 13, gamepak, with_gamepak, set_gamepak); +} +// TODO: bit ops for interrupt flags diff --git a/src/mmio_types/key_interrupt_control.rs b/src/mmio_types/key_interrupt_control.rs new file mode 100644 index 0000000..6445c83 --- /dev/null +++ b/src/mmio_types/key_interrupt_control.rs @@ -0,0 +1,21 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct KeyInterruptControl(u16); +impl KeyInterruptControl { + const_new!(); + bitfield_bool!(u16; 0, a, with_a, set_a); + bitfield_bool!(u16; 1, b, with_b, set_b); + bitfield_bool!(u16; 2, select, with_select, set_select); + bitfield_bool!(u16; 3, start, with_start, set_start); + bitfield_bool!(u16; 4, right, with_right, set_right); + bitfield_bool!(u16; 5, left, with_left, set_left); + bitfield_bool!(u16; 6, up, with_up, set_up); + bitfield_bool!(u16; 7, down, with_down, set_down); + bitfield_bool!(u16; 8, r, with_r, set_r); + bitfield_bool!(u16; 9, l, with_l, set_l); + // + bitfield_bool!(u16; 14, enabled, with_enabled, set_enabled); + bitfield_bool!(u16; 15, require_all, with_require_all, set_require_all); +} diff --git a/src/mmio_types/keys.rs b/src/mmio_types/keys.rs new file mode 100644 index 0000000..a3fd3d2 --- /dev/null +++ b/src/mmio_types/keys.rs @@ -0,0 +1,68 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct KeysLowActive(u16); +impl KeysLowActive { + const_new!(); + bitfield_bool!(u16; 0, a_released, with_a_released, set_a_released); + bitfield_bool!(u16; 1, b_released, with_b_released, set_b_released); + bitfield_bool!(u16; 2, select_released, with_select_released, set_select_released); + bitfield_bool!(u16; 3, start_released, with_start_released, set_start_released); + bitfield_bool!(u16; 4, right_released, with_right_released, set_right_released); + bitfield_bool!(u16; 5, left_released, with_left_released, set_left_released); + bitfield_bool!(u16; 6, up_released, with_up_released, set_up_released); + bitfield_bool!(u16; 7, down_released, with_down_released, set_down_released); + bitfield_bool!(u16; 8, r_released, with_r_released, set_r_released); + bitfield_bool!(u16; 9, l_released, with_l_released, set_l_released); +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct Keys(u16); +impl Keys { + const_new!(); + bitfield_bool!(u16; 0, a, with_a, set_a); + bitfield_bool!(u16; 1, b, with_b, set_b); + bitfield_bool!(u16; 2, select, with_select, set_select); + bitfield_bool!(u16; 3, start, with_start, set_start); + bitfield_bool!(u16; 4, right, with_right, set_right); + bitfield_bool!(u16; 5, left, with_left, set_left); + bitfield_bool!(u16; 6, up, with_up, set_up); + bitfield_bool!(u16; 7, down, with_down, set_down); + bitfield_bool!(u16; 8, r, with_r, set_r); + bitfield_bool!(u16; 9, l, with_l, set_l); + + pub const fn x_signum(self) -> i32 { + if self.right() { + 1 + } else if self.left() { + -1 + } else { + 0 + } + } + + pub const fn y_signum(self) -> i32 { + if self.down() { + 1 + } else if self.up() { + -1 + } else { + 0 + } + } +} +// TODO: bit ops for keys + +impl From for Keys { + fn from(low_active: KeysLowActive) -> Self { + Self(low_active.0 ^ 0b11_1111_1111) + } +} + +impl From for KeysLowActive { + fn from(keys: Keys) -> Self { + Self(keys.0 ^ 0b11_1111_1111) + } +} diff --git a/src/mmio_types/mosaic_size.rs b/src/mmio_types/mosaic_size.rs new file mode 100644 index 0000000..a69a51f --- /dev/null +++ b/src/mmio_types/mosaic_size.rs @@ -0,0 +1,10 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct MosaicSize(u8); +impl MosaicSize { + const_new!(); + bitfield_int!(u8; 0..=3: u8, horizontal, with_horizontal, set_horizontal); + bitfield_int!(u8; 4..=7: u8, vertical, with_vertical, set_vertical); +} diff --git a/src/mmio_types/noise_frequency_control.rs b/src/mmio_types/noise_frequency_control.rs new file mode 100644 index 0000000..b4227af --- /dev/null +++ b/src/mmio_types/noise_frequency_control.rs @@ -0,0 +1,13 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct NoiseFrequencyControl(u16); +impl NoiseFrequencyControl { + const_new!(); + bitfield_int!(u16; 0..=2: u16, div_ratio, with_div_ratio, set_div_ratio); + bitfield_bool!(u16; 3, counter_width, with_counter_width, set_counter_width); + bitfield_int!(u16; 4..=7: u16, shift_frequency, with_shift_frequency, set_shift_frequency); + bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop); + bitfield_bool!(u16; 15, restart, with_restart, set_restart); +} diff --git a/src/mmio_types/noise_len_env.rs b/src/mmio_types/noise_len_env.rs new file mode 100644 index 0000000..186435b --- /dev/null +++ b/src/mmio_types/noise_len_env.rs @@ -0,0 +1,12 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct NoiseLenEnv(u16); +impl NoiseLenEnv { + const_new!(); + bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length); + bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step); + bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing); + bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume); +} diff --git a/src/mmio_types/register_ram_reset_control.rs b/src/mmio_types/register_ram_reset_control.rs new file mode 100644 index 0000000..e5a92bc --- /dev/null +++ b/src/mmio_types/register_ram_reset_control.rs @@ -0,0 +1,16 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct ResetFlags(pub(crate) u8); +impl ResetFlags { + const_new!(); + //bitfield_bool!(u8; 0, ewram, with_ewram, set_ewram); + //bitfield_bool!(u8; 1, iwram, with_iwram, set_iwram); + bitfield_bool!(u8; 2, palram, with_palram, set_palram); + bitfield_bool!(u8; 3, vram, with_vram, set_vram); + bitfield_bool!(u8; 4, oam, with_oam, set_oam); + bitfield_bool!(u8; 5, sio, with_sio, set_sio); + bitfield_bool!(u8; 6, sound, with_sound, set_sound); + bitfield_bool!(u8; 7, all_other_io, with_all_other_io, set_all_other_io); +} diff --git a/src/mmio_types/timer_control.rs b/src/mmio_types/timer_control.rs new file mode 100644 index 0000000..bfd717a --- /dev/null +++ b/src/mmio_types/timer_control.rs @@ -0,0 +1,12 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct TimerControl(u8); +impl TimerControl { + const_new!(); + bitfield_int!(u8; 0..=1: u8, prescaler_selection, with_prescaler_selection, set_prescaler_selection); + bitfield_bool!(u8; 4, chained_counting, with_chained_counting, set_chained_counting); + bitfield_bool!(u8; 6, irq_on_overflow, with_irq_on_overflow, set_irq_on_overflow); + bitfield_bool!(u8; 7, enabled, with_enabled, set_enabled); +} diff --git a/src/mmio_types/tone_duty_len_env.rs b/src/mmio_types/tone_duty_len_env.rs new file mode 100644 index 0000000..6a418c1 --- /dev/null +++ b/src/mmio_types/tone_duty_len_env.rs @@ -0,0 +1,13 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct ToneDutyLenEnv(u16); +impl ToneDutyLenEnv { + const_new!(); + bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length); + bitfield_int!(u16; 6..=7: u16, wave_pattern, with_wave_pattern, set_wave_pattern); + bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step); + bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing); + bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume); +} diff --git a/src/mmio_types/tone_frequency_control.rs b/src/mmio_types/tone_frequency_control.rs new file mode 100644 index 0000000..2728ce3 --- /dev/null +++ b/src/mmio_types/tone_frequency_control.rs @@ -0,0 +1,11 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct ToneFrequencyControl(u16); +impl ToneFrequencyControl { + const_new!(); + bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency); + bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop); + bitfield_bool!(u16; 15, restart, with_restart, set_restart); +} diff --git a/src/mmio_types/tone_sweep.rs b/src/mmio_types/tone_sweep.rs new file mode 100644 index 0000000..eb9b7c2 --- /dev/null +++ b/src/mmio_types/tone_sweep.rs @@ -0,0 +1,11 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct ToneSweep(u8); +impl ToneSweep { + const_new!(); + bitfield_int!(u8; 0..=2: u8, sweep_shift, with_sweep_shift, set_sweep_shift); + bitfield_bool!(u8; 3, frequency_decreasing, with_frequency_decreasing, set_frequency_decreasing); + bitfield_int!(u8; 4..=6: u8, sweep_time, with_sweep_time, set_sweep_time); +} diff --git a/src/mmio_types/wave_control.rs b/src/mmio_types/wave_control.rs new file mode 100644 index 0000000..0fecf07 --- /dev/null +++ b/src/mmio_types/wave_control.rs @@ -0,0 +1,11 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct WaveControl(u8); +impl WaveControl { + const_new!(); + bitfield_bool!(u8; 5, two_banks, with_two_banks, set_two_banks); + bitfield_bool!(u8; 6, use_bank1, with_use_bank1, set_use_bank1); + bitfield_bool!(u8; 7, playing, with_playing, set_playing); +} diff --git a/src/mmio_types/wave_frequency_control.rs b/src/mmio_types/wave_frequency_control.rs new file mode 100644 index 0000000..bcbcd05 --- /dev/null +++ b/src/mmio_types/wave_frequency_control.rs @@ -0,0 +1,11 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct WaveFrequencyControl(u16); +impl WaveFrequencyControl { + const_new!(); + bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency); + bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop); + bitfield_bool!(u16; 15, restart, with_restart, set_restart); +} diff --git a/src/mmio_types/wave_len_volume.rs b/src/mmio_types/wave_len_volume.rs new file mode 100644 index 0000000..2c92b4e --- /dev/null +++ b/src/mmio_types/wave_len_volume.rs @@ -0,0 +1,11 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct WaveLenVolume(u16); +impl WaveLenVolume { + const_new!(); + bitfield_int!(u16; 0..=7: u16, length, with_length, set_length); + bitfield_int!(u16; 13..=14: u16, volume, with_volume, set_volume); + bitfield_bool!(u16; 15, force75, with_force75, set_force75); +} diff --git a/src/mmio_types/window_enable.rs b/src/mmio_types/window_enable.rs new file mode 100644 index 0000000..d82e0d5 --- /dev/null +++ b/src/mmio_types/window_enable.rs @@ -0,0 +1,14 @@ +use super::*; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct WindowEnable(u8); +impl WindowEnable { + const_new!(); + bitfield_bool!(u8; 0, bg0, with_bg0, set_bg0); + bitfield_bool!(u8; 1, bg1, with_bg1, set_bg1); + bitfield_bool!(u8; 2, bg2, with_bg2, set_bg2); + bitfield_bool!(u8; 3, bg3, with_bg3, set_bg3); + bitfield_bool!(u8; 4, obj, with_obj, set_obj); + bitfield_bool!(u8; 5, effect, with_effect, set_effect); +} diff --git a/src/oam.rs b/src/oam.rs deleted file mode 100644 index 8c3c622..0000000 --- a/src/oam.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Types and declarations for the Object Attribute Memory (`OAM`). - -use super::*; - -newtype! { - /// 0th part of an object's attributes. - /// - /// * Bits 0-7: row-coordinate - /// * Bits 8-9: Rendering style: Normal, Affine, Disabled, Double Area Affine - /// * Bits 10-11: Object mode: Normal, SemiTransparent, Object Window - /// * Bit 12: Mosaic - /// * Bit 13: is 8bpp - /// * Bits 14-15: Object Shape: Square, Horizontal, Vertical - OBJAttr0, u16 -} -impl OBJAttr0 { - phantom_fields! { - self.0: u16, - row_coordinate: 0-7, - obj_rendering: 8-9=ObjectRender, - obj_mode: 10-11=ObjectMode, - mosaic: 12, - is_8bpp: 13, - obj_shape: 14-15=ObjectShape, - } -} - -/// What style of rendering for this object -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum ObjectRender { - /// Standard, non-affine rendering - Normal = 0, - /// Affine rendering - Affine = 1, - /// Object disabled (saves cycles for elsewhere!) - Disabled = 2, - /// Affine with double render space (helps prevent clipping) - DoubleAreaAffine = 3, -} - -/// What mode to ues for the object. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum ObjectMode { - /// Show the object normally - Normal = 0, - /// The object becomes the "Alpha Blending 1st target" (see Alpha Blending) - SemiTransparent = 1, - /// Use the object's non-transparent pixels as part of a mask for the object - /// window (see Windows). - OBJWindow = 2, -} - -/// What shape the object's appearance should be. -/// -/// The specifics also depend on the `ObjectSize` set. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum ObjectShape { - /// Equal parts wide and tall - Square = 0, - /// Wider than tall - Horizontal = 1, - /// Taller than wide - Vertical = 2, -} - -newtype! { - /// 1st part of an object's attributes. - /// - /// * Bits 0-8: column coordinate - /// * Bits 9-13: - /// * Normal render: Bit 12 holds hflip and 13 holds vflip. - /// * Affine render: The affine parameter selection. - /// * Bits 14-15: Object Size - OBJAttr1, u16 -} -impl OBJAttr1 { - phantom_fields! { - self.0: u16, - col_coordinate: 0-8, - affine_index: 9-13, - hflip: 12, - vflip: 13, - obj_size: 14-15=ObjectSize, - } -} - -/// The object's size. -/// -/// Also depends on the `ObjectShape` set. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum ObjectSize { - /// * Square: 8x8px - /// * Horizontal: 16x8px - /// * Vertical: 8x16px - Zero = 0, - /// * Square: 16x16px - /// * Horizontal: 32x8px - /// * Vertical: 8x32px - One = 1, - /// * Square: 32x32px - /// * Horizontal: 32x16px - /// * Vertical: 16x32px - Two = 2, - /// * Square: 64x64px - /// * Horizontal: 64x32px - /// * Vertical: 32x64px - Three = 3, -} - -newtype! { - /// 2nd part of an object's attributes. - /// - /// * Bits 0-9: Base Tile Index (tile offset from CBB4) - /// * Bits 10-11: Priority - /// * Bits 12-15: Palbank (if using 4bpp) - OBJAttr2, u16 -} -impl OBJAttr2 { - phantom_fields! { - self.0: u16, - tile_id: 0-9, - priority: 10-11, - palbank: 12-15, - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ObjectAttributes { - pub attr0: OBJAttr0, - pub attr1: OBJAttr1, - pub attr2: OBJAttr2, -} - -/// The object attributes, but there are gaps in the array, so we must not -/// expose this directly. -const OBJ_ATTR_APPROX: VolBlock<[u16; 4], Safe, Safe, 128> = unsafe { VolBlock::new(0x700_0000) }; -// TODO: VolSeries - -pub fn write_obj_attributes(slot: usize, attributes: ObjectAttributes) -> Option<()> { - OBJ_ATTR_APPROX.get(slot).map(|va| unsafe { - let va_u16 = va.cast::(); - va_u16.cast::().write(attributes.attr0); - va_u16.offset(1).cast::().write(attributes.attr1); - va_u16.offset(2).cast::().write(attributes.attr2); - }) -} - -pub fn read_obj_attributes(slot: usize) -> Option { - OBJ_ATTR_APPROX.get(slot).map(|va| unsafe { - let va_u16 = va.cast::(); - let attr0 = va_u16.cast::().read(); - let attr1 = va_u16.offset(1).cast::().read(); - let attr2 = va_u16.offset(2).cast::().read(); - ObjectAttributes { attr0, attr1, attr2 } - }) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct AffineParameters { - pub pa: i16, - pub pb: i16, - pub pc: i16, - pub pd: i16, -} -// TODO: find the correct fixed-point type here. - -/// The object attributes, but there are gaps in the array, so we must not -/// expose this directly. -const AFFINE_PARAMS_APPROX: VolBlock<[i16; 16], Safe, Safe, 32> = - unsafe { VolBlock::new(0x700_0000) }; -// TODO: VolSeries - -pub fn write_affine_parameters(slot: usize, params: AffineParameters) -> Option<()> { - AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe { - let va_i16 = va.cast::(); - va_i16.offset(3).write(params.pa); - va_i16.offset(7).write(params.pb); - va_i16.offset(11).write(params.pc); - va_i16.offset(15).write(params.pd); - }) -} - -pub fn read_affine_parameters(slot: usize) -> Option { - AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe { - let va_i16 = va.cast::(); - let pa = va_i16.offset(3).read(); - let pb = va_i16.offset(7).read(); - let pc = va_i16.offset(11).read(); - let pd = va_i16.offset(15).read(); - AffineParameters { pa, pb, pc, pd } - }) -} diff --git a/src/palram.rs b/src/palram.rs deleted file mode 100644 index bf9037f..0000000 --- a/src/palram.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! Module that allows interacting with palette memory, (`PALRAM`). -//! -//! The `PALRAM` contains 256 `Color` values for Background use, and 256 `Color` -//! values for Object use. -//! -//! Each block of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where -//! there's a single palette of 256 entries. It can also be viewed as "4 bits -//! per pixel" (4bpp), where there's 16 "palbank" entries that each have 16 -//! slots. **Both** interpretations are correct, simultaneously. If you're a -//! real palette wizard you can carefully arrange for some things to use 4bpp -//! mode while other things use 8bpp mode and have it all look good. -//! -//! ## Transparency -//! -//! In 8bpp mode the 0th palette index is "transparent" when used in an image -//! (giving you 255 usable slots). In 4bpp mode the 0th palbank index _of each -//! palbank_ is considered a transparency pixel (giving you 15 usable slots per -//! palbank). -//! -//! ## Clear Color -//! -//! The 0th palette index of the background palette holds the color that the -//! display will show if no background or object draws over top of a given pixel -//! during rendering. - -use super::*; - -// TODO: PalIndex newtypes? - -/// The `PALRAM` for background colors, 256 slot view. -pub const PALRAM_BG: VolBlock = unsafe { VolBlock::new(0x500_0000) }; - -/// The `PALRAM` for object colors, 256 slot view. -pub const PALRAM_OBJ: VolBlock = unsafe { VolBlock::new(0x500_0200) }; - -/// Obtains the address of the specified 8bpp background palette slot. -pub const fn index_palram_bg_8bpp(slot: u8) -> VolAddress { - PALRAM_BG.index(slot as usize) -} - -/// Obtains the address of the specified 8bpp object palette slot. -pub const fn index_palram_obj_8bpp(slot: u8) -> VolAddress { - PALRAM_OBJ.index(slot as usize) -} - -/// Obtains the address of the specified 4bpp background palbank and palslot. -/// -/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation -/// will wrap. -pub const fn index_palram_bg_4bpp(palbank: u8, palslot: u8) -> VolAddress { - PALRAM_BG.index(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) -} - -/// Obtains the address of the specified 4bpp object palbank and palslot. -/// -/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation -/// will wrap. -pub const fn index_palram_obj_4bpp(palbank: u8, palslot: u8) -> VolAddress { - PALRAM_OBJ.index(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) -} diff --git a/src/rom.rs b/src/rom.rs deleted file mode 100644 index 2414747..0000000 --- a/src/rom.rs +++ /dev/null @@ -1 +0,0 @@ -//! Module for things related to ROM. diff --git a/src/rsrt0.S b/src/rsrt0.S index 7e0e137..fc1a652 100644 --- a/src/rsrt0.S +++ b/src/rsrt0.S @@ -1,4 +1,6 @@ - .arm +.arm +.global __start + __start: b .Linit @@ -7,19 +9,19 @@ __start: .Linit: @ Set address of user IRQ handler - ldr r0, =MainIrqHandler - ldr r1, =0x03FFFFFC - str r0, [r1] + @ldr r0, =MainIrqHandler + @ldr r1, =0x03FFFFFC + @str r0, [r1] @ set IRQ stack pointer mov r0, #0x12 msr CPSR_c, r0 - ldr sp, =0x3007fa0 + ldr sp, =0x03007FA0 @ set user stack pointer mov r0, #0x1f msr CPSR_c, r0 - ldr sp, =0x3007f00 + ldr sp, =0x03007F00 @ copy .data and .text_iwram section to IWRAM ldr r0, =__iwram_lma @ source address @@ -31,12 +33,15 @@ __start: addne r2, #3 asrne r2, #2 addne r2, #0x04000000 - swine 0xb0000 + swine 0xB0000 @ jump to user code ldr r0, =main bx r0 + @ main should be `fn() -> !`, but it doesn't hurt to guard + 1: b 1b +/* .arm .global MainIrqHandler .align 4, 0 @@ -89,3 +94,4 @@ MainIrqHandler: @ Return to BIOS IRQ handler bx lr .pool +*/ diff --git a/src/save/eeprom.rs b/src/save/eeprom.rs index a32fa90..9643be5 100644 --- a/src/save/eeprom.rs +++ b/src/save/eeprom.rs @@ -4,7 +4,7 @@ use super::{Error, MediaType, RawSaveAccess}; use crate::{ - io::dma::*, + prelude::*, save::{lock_media, MediaInfo, Timeout}, sync::with_irqs_disabled, }; @@ -21,48 +21,50 @@ fn disable_dmas(func: impl FnOnce()) { with_irqs_disabled(|| unsafe { // Disable other DMAs. This avoids our read/write from being interrupted // by a higher priority DMA channel. - let dma0_ctl = DMA0::control(); - let dma1_ctl = DMA1::control(); - let dma2_ctl = DMA2::control(); - DMA0::set_control(dma0_ctl.with_enabled(false)); - DMA1::set_control(dma1_ctl.with_enabled(false)); - DMA2::set_control(dma2_ctl.with_enabled(false)); + let dma0_ctl = DMA0CNT_H.read(); + let dma1_ctl = DMA1CNT_H.read(); + let dma2_ctl = DMA2CNT_H.read(); + DMA0CNT_H.write(dma0_ctl.with_enabled(false)); + DMA1CNT_H.write(dma1_ctl.with_enabled(false)); + DMA2CNT_H.write(dma2_ctl.with_enabled(false)); // Executes the body of the function with DMAs and IRQs disabled. func(); // Continues higher priority DMAs if they were enabled before. - DMA0::set_control(dma0_ctl); - DMA1::set_control(dma1_ctl); - DMA2::set_control(dma2_ctl); + DMA0CNT_H.write(dma0_ctl); + DMA1CNT_H.write(dma1_ctl); + DMA2CNT_H.write(dma2_ctl); }); } /// Sends a DMA command to EEPROM. fn dma_send(source: &[u32], ct: u16) { disable_dmas(|| unsafe { - DMA3::set_source(source.as_ptr()); - DMA3::set_dest(0x0DFFFF00 as *mut _); - DMA3::set_count(ct); - let dma3_ctl = DMAControlSetting::new() - .with_dest_address_control(DMADestAddressControl::Increment) - .with_source_address_control(DMASrcAddressControl::Increment) + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + DMA3SAD.write(source.as_ptr() as usize); + DMA3DAD.write(0x0DFFFF00); + DMA3CNT_L.write(ct); + let dma3_ctl = DmaControl::new() + .with_dest_addr(DestAddrControl::Increment) + .with_src_addr(SrcAddrControl::Increment) .with_enabled(true); - DMA3::set_control(dma3_ctl); + DMA3CNT_H.write(dma3_ctl); }); } /// Receives a DMA packet from EEPROM. fn dma_receive(source: &mut [u32], ct: u16) { disable_dmas(|| unsafe { - DMA3::set_source(0x0DFFFF00 as *const _); - DMA3::set_dest(source.as_ptr() as *mut _); - DMA3::set_count(ct); - let dma3_ctl = DMAControlSetting::new() - .with_dest_address_control(DMADestAddressControl::Increment) - .with_source_address_control(DMASrcAddressControl::Increment) + DMA3SAD.write(0x0DFFFF00); + DMA3DAD.write(source.as_mut_ptr() as usize); + DMA3CNT_L.write(ct); + let dma3_ctl = DmaControl::new() + .with_dest_addr(DestAddrControl::Increment) + .with_src_addr(SrcAddrControl::Increment) .with_enabled(true); - DMA3::set_control(dma3_ctl); + DMA3CNT_H.write(dma3_ctl); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); }); } diff --git a/src/save/flash.rs b/src/save/flash.rs index 7ea7e56..e66e55a 100644 --- a/src/save/flash.rs +++ b/src/save/flash.rs @@ -124,6 +124,7 @@ pub fn detect_chip_id() -> Result { /// Information relating to a particular flash chip that could be found in a /// Game Pak. +#[allow(dead_code)] struct ChipInfo { /// The wait state required to read from the chip. read_wait: u8, diff --git a/src/save/utils.rs b/src/save/utils.rs index 5db996b..c255095 100644 --- a/src/save/utils.rs +++ b/src/save/utils.rs @@ -3,7 +3,7 @@ use super::Error; use crate::{ - io::timers::*, + prelude::*, sync::{RawMutex, RawMutexGuard, Static}, }; use voladdress::*; @@ -45,7 +45,7 @@ pub struct Timeout { _lock_guard: RawMutexGuard<'static>, active: bool, timer_l: VolAddress, - timer_h: VolAddress, + timer_h: VolAddress, } impl Timeout { /// Creates a new timeout from the timer passed to [`set_timer_for_timeout`]. @@ -66,17 +66,17 @@ impl Timeout { active: id != TimerId::None, timer_l: match id { TimerId::None => unsafe { VolAddress::new(0) }, - TimerId::T0 => TM0CNT_L, - TimerId::T1 => TM1CNT_L, - TimerId::T2 => TM2CNT_L, - TimerId::T3 => TM3CNT_L, + TimerId::T0 => unsafe { VolAddress::new(TIMER0_COUNTER.as_usize()) }, + TimerId::T1 => unsafe { VolAddress::new(TIMER1_COUNTER.as_usize()) }, + TimerId::T2 => unsafe { VolAddress::new(TIMER2_COUNTER.as_usize()) }, + TimerId::T3 => unsafe { VolAddress::new(TIMER3_COUNTER.as_usize()) }, }, timer_h: match id { TimerId::None => unsafe { VolAddress::new(0) }, - TimerId::T0 => TM0CNT_H, - TimerId::T1 => TM1CNT_H, - TimerId::T2 => TM2CNT_H, - TimerId::T3 => TM3CNT_H, + TimerId::T0 => TIMER0_CONTROL, + TimerId::T1 => TIMER1_CONTROL, + TimerId::T2 => TIMER2_CONTROL, + TimerId::T3 => TIMER3_CONTROL, }, }) } @@ -85,9 +85,8 @@ impl Timeout { pub fn start(&self) { if self.active { self.timer_l.write(0); - let timer_ctl = - TimerControlSetting::new().with_tick_rate(TimerTickRate::CPU1024).with_enabled(true); - self.timer_h.write(TimerControlSetting::new()); + let timer_ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true); + self.timer_h.write(TimerControl::new()); self.timer_h.write(timer_ctl); } } diff --git a/src/sync.rs b/src/sync.rs index d2086e0..3e17060 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1,6 +1,6 @@ //! A module containing functions and utilities useful for synchronizing state. -use crate::io::irq::{IrqEnableSetting, IME}; +use crate::prelude::*; mod locks; mod statics; @@ -55,7 +55,7 @@ pub unsafe extern "C" fn __sync_synchronize() {} /// for game functionality. pub fn with_irqs_disabled(mut func: impl FnOnce() -> T) -> T { let current_ime = IME.read(); - unsafe { IME.write(IrqEnableSetting::IRQ_NO) }; + unsafe { IME.write(false) }; // prevents the contents of the function from being reordered before IME is disabled. memory_write_hint(&mut func); diff --git a/src/vram.rs b/src/vram.rs deleted file mode 100644 index 5bfeb27..0000000 --- a/src/vram.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Module for all things relating to the Video RAM. -//! -//! Note that the GBA has six different display modes available, and the -//! _meaning_ of Video RAM depends on which display mode is active. In all -//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`. -//! -//! # Safety -//! -//! Note that all possible bit patterns are technically allowed within Video -//! Memory. If you write the "wrong" thing into video memory you don't crash the -//! GBA, instead you just get graphical glitches (or perhaps nothing at all). -//! Accordingly, the "safe" functions here will check that you're in bounds, but -//! they won't bother to check that you've set the video mode they're designed -//! for. - -pub(crate) use super::*; - -pub mod affine; -pub mod bitmap; -pub mod text; - -use text::TextScreenblockEntry; - -/// The start of VRAM. -/// -/// Depending on what display mode is currently set there's different ways that -/// your program should interpret the VRAM space. Accordingly, we give the raw -/// value as just being a `usize`. Specific video mode types then wrap this as -/// being the correct thing. -pub const VRAM_BASE_USIZE: usize = 0x600_0000; - -pub const PAGE1_OFFSET: usize = 0xA000; - -/// The character base blocks. -pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], Safe, Safe, 6> = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - -/// The screen entry base blocks. -pub const SCREEN_BASE_BLOCKS: VolBlock<[u8; 0x800], Safe, Safe, 32> = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - -newtype! { - /// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment. - #[derive(Debug, Clone, Copy, Default)] - Tile4bpp, pub [u32; 8], no frills -} - -newtype! { - /// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment. - #[derive(Debug, Clone, Copy, Default)] - Tile8bpp, pub [u32; 16], no frills -} - -/// Gives the specified charblock in 4bpp view. -pub fn get_4bpp_character_block(slot: usize) -> VolBlock { - unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).as_usize()) } -} - -/// Gives the specified charblock in 8bpp view. -pub fn get_8bpp_character_block(slot: usize) -> VolBlock { - unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).as_usize()) } -} - -pub fn get_screen_block(slot: usize) -> VolBlock { - unsafe { VolBlock::new(SCREEN_BASE_BLOCKS.index(slot).as_usize()) } -} diff --git a/src/vram/affine.rs b/src/vram/affine.rs deleted file mode 100644 index 6b1f556..0000000 --- a/src/vram/affine.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Module for affine things. - -use super::*; - -newtype! { - /// A screenblock entry for use in Affine mode. - AffineScreenblockEntry, u8 -} - -newtype! { - /// A 16x16 screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills -} - -newtype! { - /// A 32x32 screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills -} - -newtype! { - /// A 64x64 screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills -} - -newtype! { - /// A 128x128 screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills -} diff --git a/src/vram/bitmap.rs b/src/vram/bitmap.rs deleted file mode 100644 index 2ae38f9..0000000 --- a/src/vram/bitmap.rs +++ /dev/null @@ -1,454 +0,0 @@ -//! Module for the Bitmap video modes. - -use super::*; - -/// A bitmap video mode with full color and full resolution. -/// -/// * **Width:** 240 -/// * **Height:** 160 -/// -/// Because it takes so much space to have full color and full resolution at the -/// same time, there's no alternate page available when using mode 3. -/// -/// As with all the bitmap video modes, the bitmap is considered to be BG2, so -/// you have to enable BG2 as well if you want to see the bitmap. -pub struct Mode3; - -impl Mode3 { - /// The screen's width in this mode. - pub const WIDTH: usize = 240; - - /// The screen's height in this mode. - pub const HEIGHT: usize = 160; - - const VRAM: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - const WORDS_BLOCK: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - /// Gets the address of the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - fn get(col: usize, row: usize) -> Option> { - Self::VRAM.get(col + row * Self::WIDTH) - } - - /// Reads the color of the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn read(col: usize, row: usize) -> Option { - Self::get(col, row).map(VolAddress::::read) - } - - /// Writes a color to the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn write(col: usize, row: usize, color: Color) -> Option<()> { - Self::get(col, row).map(|va| va.write(color)) - } - - /// Clear the screen to the color specified. - /// - /// Takes ~430,000 cycles (~1.5 frames). - pub fn clear_to(color: Color) { - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - for va in Self::WORDS_BLOCK.iter() { - va.write(bulk_color) - } - } - - /// Clears the screen to the color specified using DMA3. - /// - /// Takes ~61,500 frames (~73% of VBlank) - pub fn dma_clear_to(color: Color) { - use crate::io::dma::DMA3; - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - unsafe { - DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::WORDS_BLOCK.len() as u16) - }; - } - - /// Draws a line between the two points given `(c1,r1,c2,r2,color)`. - /// - /// Works fine with out of bounds points. It only draws to in bounds - /// locations. - pub fn draw_line(c1: isize, r1: isize, c2: isize, r2: isize, color: Color) { - let mut col = c1; - let mut row = r1; - let w = c2 - c1; - let h = r2 - r1; - let mut dx1 = 0; - let mut dx2 = 0; - let mut dy1 = 0; - let mut dy2 = 0; - let mut longest = w.abs(); - let mut shortest = h.abs(); - if w < 0 { - dx1 = -1; - } else if w > 0 { - dx1 = 1; - }; - if h < 0 { - dy1 = -1; - } else if h > 0 { - dy1 = 1; - }; - if w < 0 { - dx2 = -1; - } else if w > 0 { - dx2 = 1; - }; - if !(longest > shortest) { - core::mem::swap(&mut longest, &mut shortest); - if h < 0 { - dy2 = -1; - } else if h > 0 { - dy2 = 1 - }; - dx2 = 0; - } - let mut numerator = longest >> 1; - - (0..(longest + 1)).for_each(|_| { - Self::write(col as usize, row as usize, color); - numerator += shortest; - if !(numerator < longest) { - numerator -= longest; - col += dx1; - row += dy1; - } else { - col += dx2; - row += dy2; - } - }); - } -} - -/// Used to select what page to read from or write to in Mode 4 and Mode 5. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Page { - /// Page 0 - Zero, - /// Page 1 - One, -} - -/// A bitmap video mode with full resolution and paletted color. -/// -/// * **Width:** 240 -/// * **Height:** 160 -/// * **Pages:** 2 -/// -/// Because the pixels use palette indexes there's enough space to have two -/// pages. -/// -/// As with all the bitmap video modes, the bitmap is considered to be BG2, so -/// you have to enable BG2 as well if you want to see the bitmap. -pub struct Mode4; - -impl Mode4 { - /// The screen's width in this mode. - pub const WIDTH: usize = 240; - - /// The screen's height in this mode. - pub const HEIGHT: usize = 160; - - const PAGE0_INDEXES: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - const PAGE1_INDEXES: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) }; - - const PAGE0_WORDS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - const PAGE1_WORDS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) }; - - /// Reads the color of the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn read(page: Page, col: usize, row: usize) -> Option { - match page { - Page::Zero => Self::PAGE0_INDEXES, - Page::One => Self::PAGE1_INDEXES, - } - .get(col + row * Self::WIDTH) - .map(VolAddress::::read) - } - - /// Writes a color to the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn write(page: Page, col: usize, row: usize, pal8bpp: u8) -> Option<()> { - // Note(Lokathor): Byte writes to VRAM aren't permitted, we have to jump - // through some hoops. - if col < Self::WIDTH && row < Self::HEIGHT { - let real_index = col + row * Self::WIDTH; - let rounded_down_index = real_index & !1; - let address: VolAddress = unsafe { - match page { - Page::Zero => Self::PAGE0_INDEXES, - Page::One => Self::PAGE1_INDEXES, - } - .index(rounded_down_index) - .cast::() - }; - if real_index != rounded_down_index { - // even byte, change the high bits - let old_val = address.read(); - address.write((old_val & 0xFF) | ((pal8bpp as u16) << 8)); - } else { - // odd byte, change the low bits - let old_val = address.read(); - address.write((old_val & 0xFF00) | pal8bpp as u16); - } - Some(()) - } else { - None - } - } - - /// Clear the screen to the palette index specified. - /// - /// Takes ~215,000 cycles (~76% of a frame) - pub fn clear_to(page: Page, pal8bpp: u8) { - let pal8bpp_32 = pal8bpp as u32; - let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; - let words = match page { - Page::Zero => Self::PAGE0_WORDS, - Page::One => Self::PAGE1_WORDS, - }; - for va in words.iter() { - va.write(bulk_color) - } - } - - /// Clears the screen to the palette index specified using DMA3. - /// - /// Takes ~30,800 frames (~37% of VBlank) - pub fn dma_clear_to(page: Page, pal8bpp: u8) { - use crate::io::dma::DMA3; - - let pal8bpp_32 = pal8bpp as u32; - let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; - let words_address = match page { - Page::Zero => Self::PAGE0_WORDS.index(0).as_usize(), - Page::One => Self::PAGE1_WORDS.index(0).as_usize(), - }; - unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) }; - } - - /// Draws a line between the two points given `(c1,r1,c2,r2,color)`. - /// - /// Works fine with out of bounds points. It only draws to in bounds - /// locations. - pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, pal8bpp: u8) { - let mut col = c1; - let mut row = r1; - let w = c2 - c1; - let h = r2 - r1; - let mut dx1 = 0; - let mut dx2 = 0; - let mut dy1 = 0; - let mut dy2 = 0; - let mut longest = w.abs(); - let mut shortest = h.abs(); - if w < 0 { - dx1 = -1; - } else if w > 0 { - dx1 = 1; - }; - if h < 0 { - dy1 = -1; - } else if h > 0 { - dy1 = 1; - }; - if w < 0 { - dx2 = -1; - } else if w > 0 { - dx2 = 1; - }; - if !(longest > shortest) { - core::mem::swap(&mut longest, &mut shortest); - if h < 0 { - dy2 = -1; - } else if h > 0 { - dy2 = 1 - }; - dx2 = 0; - } - let mut numerator = longest >> 1; - - (0..(longest + 1)).for_each(|_| { - Self::write(page, col as usize, row as usize, pal8bpp); - numerator += shortest; - if !(numerator < longest) { - numerator -= longest; - col += dx1; - row += dy1; - } else { - col += dx2; - row += dy2; - } - }); - } -} - -/// Mode 5 is a bitmap mode with full color and reduced resolution. -/// -/// * **Width:** 160 -/// * **Height:** 128 -/// * **Pages:** 2 -/// -/// Because of the reduced resolutions there's enough space to have two pages. -/// -/// As with all the bitmap video modes, the bitmap is considered to be BG2, so -/// you have to enable BG2 as well if you want to see the bitmap. -pub struct Mode5; - -impl Mode5 { - /// The screen's width in this mode. - pub const WIDTH: usize = 160; - - /// The screen's height in this mode. - pub const HEIGHT: usize = 128; - - const PAGE0_PIXELS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - const PAGE1_PIXELS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) }; - - const PAGE0_WORDS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE) }; - - const PAGE1_WORDS: VolBlock = - unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) }; - - /// Reads the color of the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn read(page: Page, col: usize, row: usize) -> Option { - match page { - Page::Zero => Self::PAGE0_PIXELS, - Page::One => Self::PAGE1_PIXELS, - } - .get(col + row * Self::WIDTH) - .map(VolAddress::::read) - } - - /// Writes a color to the pixel specified. - /// - /// ## Failure - /// - /// Gives `None` if out of bounds - pub fn write(page: Page, col: usize, row: usize, color: Color) -> Option<()> { - match page { - Page::Zero => Self::PAGE0_PIXELS, - Page::One => Self::PAGE1_PIXELS, - } - .get(col + row * Self::WIDTH) - .map(|va| va.write(color)) - } - - /// Clear the screen to the color specified. - /// - /// Takes ~215,000 cycles (~76% of a frame) - pub fn clear_to(page: Page, color: Color) { - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - let words = match page { - Page::Zero => Self::PAGE0_WORDS, - Page::One => Self::PAGE1_WORDS, - }; - for va in words.iter() { - va.write(bulk_color) - } - } - - /// Clears the screen to the color specified using DMA3. - /// - /// Takes ~30,800 frames (~37% of VBlank) - pub fn dma_clear_to(page: Page, color: Color) { - use crate::io::dma::DMA3; - - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - let words_address = match page { - Page::Zero => Self::PAGE0_WORDS.index(0).as_usize(), - Page::One => Self::PAGE1_WORDS.index(0).as_usize(), - }; - unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) }; - } - - /// Draws a line between the two points given `(c1,r1,c2,r2,color)`. - /// - /// Works fine with out of bounds points. It only draws to in bounds - /// locations. - pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, color: Color) { - let mut col = c1; - let mut row = r1; - let w = c2 - c1; - let h = r2 - r1; - let mut dx1 = 0; - let mut dx2 = 0; - let mut dy1 = 0; - let mut dy2 = 0; - let mut longest = w.abs(); - let mut shortest = h.abs(); - if w < 0 { - dx1 = -1; - } else if w > 0 { - dx1 = 1; - }; - if h < 0 { - dy1 = -1; - } else if h > 0 { - dy1 = 1; - }; - if w < 0 { - dx2 = -1; - } else if w > 0 { - dx2 = 1; - }; - if !(longest > shortest) { - core::mem::swap(&mut longest, &mut shortest); - if h < 0 { - dy2 = -1; - } else if h > 0 { - dy2 = 1 - }; - dx2 = 0; - } - let mut numerator = longest >> 1; - - (0..(longest + 1)).for_each(|_| { - Self::write(page, col as usize, row as usize, color); - numerator += shortest; - if !(numerator < longest) { - numerator -= longest; - col += dx1; - row += dy1; - } else { - col += dx2; - row += dy2; - } - }); - } -} diff --git a/src/vram/text.rs b/src/vram/text.rs deleted file mode 100644 index 07db59b..0000000 --- a/src/vram/text.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Module for tiled mode types and operations. - -use super::*; - -newtype! { - /// A screenblock entry for use in Text mode. - TextScreenblockEntry, u16 -} -impl TextScreenblockEntry { - /// Generates a default entry with the specified tile index. - pub const fn from_tile_id(id: u16) -> Self { - Self::new().with_tile_id(id) - } - - phantom_fields! { - self.0: u16, - tile_id: 0-9, - hflip: 10, - vflip: 11, - palbank: 12-15, - } -} - -newtype! { - /// A screenblock for use in Text mode. - #[derive(Clone, Copy)] - TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills -} - -#[test] -pub fn test_text_screen_block_size() { - assert_eq!(core::mem::size_of::(), 0x800); -} diff --git a/todo_check.bat b/todo_check.bat deleted file mode 100644 index 6f1e3ea..0000000 --- a/todo_check.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -echo ------- -echo ------- - -set Wildcard=*.rs - -echo TODOS FOUND: -findstr -s -n -i -l "TODO" %Wildcard% - -echo ------- -echo -------