From 0790f781032e0041cb53edc1a8b3ff05d9687b33 Mon Sep 17 00:00:00 2001 From: Corwin Kuiper Date: Sat, 6 Mar 2021 17:58:59 +0000 Subject: [PATCH] initial commit --- .gitignore | 3 + Cargo.lock | 16 ++++ Cargo.toml | 19 +++++ Makefile | 44 ++++++++++ README.md | 16 ++++ crt0.s | 19 +++++ examples/bitmap3.rs | 39 +++++++++ examples/bitmap4.rs | 42 ++++++++++ examples/panic.rs | 21 +++++ examples/syscall.rs | 21 +++++ gba.json | 35 ++++++++ gba.ld | 51 +++++++++++ interrupt_simple.s | 17 ++++ src/display.rs | 196 +++++++++++++++++++++++++++++++++++++++++++ src/input.rs | 99 ++++++++++++++++++++++ src/interrupt.rs | 56 +++++++++++++ src/lib.rs | 49 +++++++++++ src/memory_mapped.rs | 56 +++++++++++++ src/mgba.rs | 67 +++++++++++++++ src/single.rs | 48 +++++++++++ src/syscall.rs | 104 +++++++++++++++++++++++ 21 files changed, 1018 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 README.md create mode 100644 crt0.s create mode 100644 examples/bitmap3.rs create mode 100644 examples/bitmap4.rs create mode 100644 examples/panic.rs create mode 100644 examples/syscall.rs create mode 100644 gba.json create mode 100644 gba.ld create mode 100644 interrupt_simple.s create mode 100644 src/display.rs create mode 100644 src/input.rs create mode 100644 src/interrupt.rs create mode 100644 src/lib.rs create mode 100644 src/memory_mapped.rs create mode 100644 src/mgba.rs create mode 100644 src/single.rs create mode 100644 src/syscall.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b98ee0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/out +/.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..577aa7a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "gba" +version = "0.1.0" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..850dae4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "gba" +version = "0.1.0" +authors = ["Corwin Kuiper "] +edition = "2018" + +[profile.dev] +panic = "abort" +lto = true + +[profile.release] +panic = "abort" +lto = true + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "1.0" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9862b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +BINUTILS_PREFIX=arm-none-eabi- +CC=$(BINUTILS_PREFIX)as +ARCH = -mthumb-interwork -mthumb + +RUSTFILES=$(shell find . -name '*.rs') + +.ONESHELL: + +out/debug/%.gba: cargo-debug-% + @mkdir -p $(dir $@) + @OUTNAME=$(patsubst out/debug/%.gba,%,$@) + @$(BINUTILS_PREFIX)objcopy -O binary target/gba/debug/examples/$${OUTNAME} out/debug/$${OUTNAME}.gba + @gbafix $@ + +out/release/%.gba: cargo-release-% + @mkdir -p $(dir $@) + @OUTNAME=$(patsubst out/release/%.gba,%,$@) + @$(BINUTILS_PREFIX)objcopy -O binary target/gba/release/examples/$${OUTNAME} out/release/$${OUTNAME}.gba + @gbafix $@ + +d-%: out/debug/%.gba + @OUTNAME=$(patsubst d-%,%,$@) + @mgba-qt $< + @rm out/debug/$${OUTNAME}.sav + +r-%: out/release/%.gba + @OUTNAME=$(patsubst r-%,%,$@) + @mgba-qt $< + @rm out/release/$${OUTNAME}.sav + +cargo-release-%: $(RUSTFILES) out/crt0.o + @OUTNAME=$(patsubst cargo-release-%,%, $@) + @rustup run nightly cargo xbuild --release --target=gba.json --example=$${OUTNAME} + +cargo-debug-%: $(RUSTFILES) out/crt0.o + @OUTNAME=$(patsubst cargo-debug-%,%, $@) + @rustup run nightly cargo xbuild --target=gba.json --example=$${OUTNAME} + +out/crt0.o: crt0.s interrupt_simple.s + @mkdir $(dir $@) + @$(CC) $(ARCH) -o out/crt0.o crt0.s + +clippy: + rustup run nightly cargo xclippy --target=gba.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c15af82 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Rust for the Gameboy Advance + +This is my in development library for rust on the gameboy advance. It uses +information from GbaTek, Tonc, and the existing +[rust-console/gba](https://github.com/rust-console/gba). Namely the gba.json +file comes from [rust-console/gba](https://github.com/rust-console/gba). + +Note that this currently contains no documentation of any kind, unless you count +examples as documentation. + +## Requirements + +* Nightly rust, probably quite a recent version. +* Devkitarm. + +This is probably easiest to do in a container. \ No newline at end of file diff --git a/crt0.s b/crt0.s new file mode 100644 index 0000000..124d95f --- /dev/null +++ b/crt0.s @@ -0,0 +1,19 @@ + .arm + .global __start +__start: + b .Initialise + + @ Filled in by gbafix + .fill 188, 1, 0 + +.Initialise: + @ Set interrupt handler + ldr r0, =InterruptHandlerSimple + ldr r1, =0x03007FFC + str r0, [r1] + + @ load main and branch + ldr r0, =main + bx r0 + +.include "interrupt_simple.s" diff --git a/examples/bitmap3.rs b/examples/bitmap3.rs new file mode 100644 index 0000000..584d8cc --- /dev/null +++ b/examples/bitmap3.rs @@ -0,0 +1,39 @@ +#![no_std] +#![feature(start)] + +extern crate gba; + +use gba::display; + +struct Vector2D { + x: i32, + y: i32, +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + let gba = gba::Gba::new(); + let bitmap = gba.display.bitmap3(); + + let mut input = gba::input::ButtonController::new(); + let mut pos = Vector2D { + x: display::WIDTH / 2, + y: display::HEIGHT / 2, + }; + + gba::interrupt::enable(gba::interrupt::Interrupt::VBlank); + gba::interrupt::enable_interrupts(); + gba::display::enable_VBlank_interrupt(); + + loop { + gba::display::wait_for_VBlank(); + + input.update(); + pos.x += input.x_tri() as i32; + pos.y += input.y_tri() as i32; + + pos.x = pos.x.clamp(0, display::WIDTH - 1); + pos.y = pos.y.clamp(0, display::HEIGHT - 1); + bitmap.draw_point(pos.x, pos.y, 0x001F); + } +} diff --git a/examples/bitmap4.rs b/examples/bitmap4.rs new file mode 100644 index 0000000..0799643 --- /dev/null +++ b/examples/bitmap4.rs @@ -0,0 +1,42 @@ +#![no_std] +#![feature(start)] + +extern crate gba; + +use gba::display; + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + let gba = gba::Gba::new(); + let bitmap = gba.display.bitmap4(); + + bitmap.set_palette_entry(1, 0x001F); + bitmap.set_palette_entry(2, 0x03E0); + + bitmap.draw_point_page( + display::WIDTH / 2, + display::HEIGHT / 2, + 1, + display::Page::Front, + ); + bitmap.draw_point_page( + display::WIDTH / 2 + 5, + display::HEIGHT / 2, + 2, + display::Page::Back, + ); + + gba::interrupt::enable(gba::interrupt::Interrupt::VBlank); + gba::interrupt::enable_interrupts(); + gba::display::enable_VBlank_interrupt(); + + let mut count = 0; + + loop { + gba::display::wait_for_VBlank(); + count += 1; + if count % 6 == 0 { + bitmap.flip_page(); + } + } +} diff --git a/examples/panic.rs b/examples/panic.rs new file mode 100644 index 0000000..180c597 --- /dev/null +++ b/examples/panic.rs @@ -0,0 +1,21 @@ +#![no_std] +#![feature(start)] + +extern crate gba; + +use gba::display; + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + let gba = gba::Gba::new(); + let bitmap = gba.display.bitmap3(); + + let mut input = gba::input::ButtonController::new(); + + loop { + input.update(); + if input.is_just_pressed(gba::input::Button::A) { + bitmap.draw_point(display::WIDTH, 0, 0x05); + } + } +} diff --git a/examples/syscall.rs b/examples/syscall.rs new file mode 100644 index 0000000..3c5effd --- /dev/null +++ b/examples/syscall.rs @@ -0,0 +1,21 @@ +#![no_std] +#![feature(start)] + +extern crate gba; +use gba::{display, syscall}; + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + let gba = gba::Gba::new(); + let bitmap = gba.display.bitmap3(); + + for x in 0..display::WIDTH { + let y = syscall::sqrt(x << 6); + let y = (display::HEIGHT - y).clamp(0, display::HEIGHT - 1); + bitmap.draw_point(x, y, 0x001F); + } + + loop { + syscall::halt(); + } +} diff --git a/gba.json b/gba.json new file mode 100644 index 0000000..4137d41 --- /dev/null +++ b/gba.json @@ -0,0 +1,35 @@ +{ + "abi-blacklist": [ + "stdcall", + "fastcall", + "vectorcall", + "thiscall", + "win64", + "sysv64" + ], + "arch": "arm", + "atomic-cas": false, + "cpu": "arm7tdmi", + "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", + "emit-debug-gdb-scripts": false, + "env": "agb", + "executables": true, + "features": "+soft-float,+strict-align", + "linker": "arm-none-eabi-ld", + "linker-flavor": "ld", + "linker-is-gnu": true, + "llvm-target": "thumbv4-none-eabi", + "os": "none", + "panic-strategy": "abort", + "pre-link-args": { + "ld": [ + "out/crt0.o", + "-Tgba.ld" + ] + }, + "relocation-model": "static", + "target-c-int-width": "32", + "target-endian": "little", + "target-pointer-width": "32", + "vendor": "nintendo" +} \ No newline at end of file diff --git a/gba.ld b/gba.ld new file mode 100644 index 0000000..4e1ce2f --- /dev/null +++ b/gba.ld @@ -0,0 +1,51 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(__start) + +MEMORY { + ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K + iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K + rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M +} + +__text_start = ORIGIN(rom); + +SECTIONS { + . = __text_start; + + .crt0 : { + KEEP (*(.crt0)); + . = ALIGN(4); + } > rom + + .text : { + *(.text .text*); + . = ALIGN(4); + } > rom + __text_end = .; + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } > rom + + __data_start = .; + .data : { + *(.data .data.*); + . = ALIGN(4); + } > iwram + __data_end = .; + + .bss : { + *(.bss .bss.*); + . = ALIGN(4); + } > iwram + + .stack 0x80000 : { + *(.stack) + } + + /* discard anything not already mentioned */ + /DISCARD/ : { *(*) } +} \ No newline at end of file diff --git a/interrupt_simple.s b/interrupt_simple.s new file mode 100644 index 0000000..61df2f3 --- /dev/null +++ b/interrupt_simple.s @@ -0,0 +1,17 @@ +@ An interrupt handler that simply acknowledges all interrupts + .arm + .global InterruptHandlerSimple + .align +InterruptHandlerSimple: + ldr r2, =0x04000200 + ldrh r1, [r2] + ldrh r3, [r2, #2] + and r0, r1, r3 + strh r0, [r2, #2] + + ldr r2, =0x03007FF8 + ldrh r1, [r2] + orr r1, r1, r0 + strh r1, [r2] + + bx lr diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..498adec --- /dev/null +++ b/src/display.rs @@ -0,0 +1,196 @@ +use crate::{ + memory_mapped::{MemoryMapped, MemoryMapped1DArray, MemoryMapped2DArray}, + single::{Single, SingleToken}, +}; +use bitflags::bitflags; +use core::convert::TryInto; + +const DISPLAY_CONTROL: MemoryMapped = MemoryMapped::new(0x0400_0000); +const DISPLAY_STATUS: MemoryMapped = MemoryMapped::new(0x0400_0004); +const VCOUNT: MemoryMapped = MemoryMapped::new(0x0400_0006); + +const PALETTE_BACKGROUND: MemoryMapped1DArray = MemoryMapped1DArray::new(0x0500_0000); +const PALETTE_SPRITE: MemoryMapped1DArray = MemoryMapped1DArray::new(0x0500_0200); + +const BITMAP_MODE_3: MemoryMapped2DArray = + MemoryMapped2DArray::new(0x600_0000); + +const BITMAP_PAGE_FRONT_MODE_4: MemoryMapped2DArray< + u16, + { (WIDTH / 2) as usize }, + { HEIGHT as usize }, +> = MemoryMapped2DArray::new(0x600_0000); +const BITMAP_PAGE_BACK_MODE_4: MemoryMapped2DArray< + u16, + { (WIDTH / 2) as usize }, + { HEIGHT as usize }, +> = MemoryMapped2DArray::new(0x600_A000); + +pub const WIDTH: i32 = 240; +pub const HEIGHT: i32 = 160; + +pub enum DisplayMode { + Tiled0 = 0, + Tiled1 = 1, + Tiled2 = 2, + Bitmap3 = 3, + Bitmap4 = 4, + Bitmap5 = 5, +} + +pub enum Page { + Front = 0, + Back = 1, +} + +bitflags! { + pub struct GraphicsSettings: u16 { + const PAGE_SELECT = 1 << 0x4; + const OAM_HBLANK = 1 << 0x5; + const SPRITE1_D = 1 << 0x6; + const SCREEN_BLANK = 1 << 0x7; + const LAYER_BG0 = 1 << 0x8; + const LAYER_BG1 = 1 << 0x9; + const LAYER_BG2 = 1 << 0xA; + const LAYER_BG3 = 1 << 0xB; + const LAYER_OBJ = 1 << 0xC; + const WINDOW0 = 1 << 0xD; + const WINDOW1 = 1 << 0xE; + const WINDOW_OBJECT = 1 << 0xF; + } +} + +pub struct Display { + in_mode: Single, +} + +impl Default for Display { + fn default() -> Self { + Self::new() + } +} + +impl Display { + pub(crate) const fn new() -> Self { + Display { + in_mode: Single::new(), + } + } + + pub fn bitmap3(&self) -> Bitmap3 { + Bitmap3::new( + self.in_mode + .take() + .expect("Cannot create new mode as mode already taken"), + ) + } + pub fn bitmap4(&self) -> Bitmap4 { + Bitmap4::new( + self.in_mode + .take() + .expect("Cannot create new mode as mode already taken"), + ) + } +} + +pub struct Bitmap3<'a> { + _in_mode: SingleToken<'a>, +} + +impl<'a> Bitmap3<'a> { + fn new(in_mode: SingleToken<'a>) -> Self { + set_graphics_mode(DisplayMode::Bitmap3); + set_graphics_settings(GraphicsSettings::LAYER_BG2); + Bitmap3 { _in_mode: in_mode } + } + pub fn draw_point(&self, x: i32, y: i32, colour: u16) { + let x = x.try_into().unwrap(); + let y = y.try_into().unwrap(); + BITMAP_MODE_3.set(x, y, colour) + } +} + +pub struct Bitmap4<'a> { + _in_mode: SingleToken<'a>, +} + +impl<'a> Bitmap4<'a> { + fn new(in_mode: SingleToken<'a>) -> Self { + set_graphics_mode(DisplayMode::Bitmap4); + set_graphics_settings(GraphicsSettings::LAYER_BG2); + Bitmap4 { _in_mode: in_mode } + } + + pub fn draw_point_page(&self, x: i32, y: i32, colour: u8, page: Page) { + let addr = match page { + Page::Front => BITMAP_PAGE_FRONT_MODE_4, + Page::Back => BITMAP_PAGE_BACK_MODE_4, + }; + + let x_in_screen = (x / 2) as usize; + let y_in_screen = y as usize; + + let c = addr.get(x_in_screen, y_in_screen); + if x & 0b1 != 0 { + addr.set(x_in_screen, y_in_screen, c | (colour as u16) << 8); + } else { + addr.set(x_in_screen, y_in_screen, c | colour as u16); + } + } + + pub fn draw_point(&self, x: i32, y: i32, colour: u8) { + let disp = DISPLAY_CONTROL.get(); + + let page = if disp & GraphicsSettings::PAGE_SELECT.bits() != 0 { + Page::Back + } else { + Page::Front + }; + + self.draw_point_page(x, y, colour, page) + } + + pub fn set_palette_entry(&self, entry: u32, colour: u16) { + PALETTE_BACKGROUND.set(entry as usize, colour); + } + + pub fn flip_page(&self) { + let disp = DISPLAY_CONTROL.get(); + let swapped = disp ^ GraphicsSettings::PAGE_SELECT.bits(); + DISPLAY_CONTROL.set(swapped); + } +} + +fn set_graphics_mode(mode: DisplayMode) { + let current = DISPLAY_CONTROL.get(); + let current = current & (!0b111); + let s = current | (mode as u16 & 0b111); + + DISPLAY_CONTROL.set(s); +} + +pub fn set_graphics_settings(settings: GraphicsSettings) { + let current = DISPLAY_CONTROL.get(); + // preserve display mode + let current = current & 0b111; + let s = settings.bits() | current; + + DISPLAY_CONTROL.set(s); +} + +#[allow(non_snake_case)] +pub fn busy_wait_for_VBlank() { + while VCOUNT.get() >= 160 {} + while VCOUNT.get() < 160 {} +} + +#[allow(non_snake_case)] +pub fn enable_VBlank_interrupt() { + let status = DISPLAY_STATUS.get() | (1 << 3); + DISPLAY_STATUS.set(status); +} + +#[allow(non_snake_case)] +pub fn wait_for_VBlank() { + crate::syscall::wait_for_VBlank(); +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..53d2220 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,99 @@ +use bitflags::bitflags; +use core::convert::From; + +pub enum Tri { + Positive = 1, + Zero = 0, + Negative = -1, +} + +impl From<(bool, bool)> for Tri { + fn from(a: (bool, bool)) -> Tri { + let b1 = a.0 as i8; + let b2 = a.1 as i8; + unsafe { core::mem::transmute(b2 - b1) } + } +} + +bitflags! { + pub struct Button: u32 { + const A = 1 << 0; + const B = 1 << 1; + const SELECT = 1 << 2; + const START = 1 << 3; + const RIGHT = 1 << 4; + const LEFT = 1 << 5; + const UP = 1 << 6; + const DOWN = 1 << 7; + const R = 1 << 8; + const L = 1 << 9; + } +} + +const KEY_MASK: u16 = 0b1111111111; + +const BUTTON_INPUT: *mut u16 = (0x04000130) as *mut u16; + +// const BUTTON_INTURRUPT: *mut u16 = (0x04000132) as *mut u16; + +pub struct ButtonController { + previous: u16, + current: u16, +} + +impl Default for ButtonController { + fn default() -> Self { + ButtonController::new() + } +} + +impl ButtonController { + pub fn new() -> Self { + ButtonController { + previous: 0, + current: 0, + } + } + + pub fn update(&mut self) { + self.previous = self.current; + self.current = unsafe { BUTTON_INPUT.read_volatile() } ^ KEY_MASK; + } + + pub fn x_tri(&self) -> Tri { + let left = self.is_pressed(Button::LEFT); + let right = self.is_pressed(Button::RIGHT); + + (left, right).into() + } + + pub fn y_tri(&self) -> Tri { + let up = self.is_pressed(Button::UP); + let down = self.is_pressed(Button::DOWN); + + (up, down).into() + } + + pub fn is_pressed(&self, keys: Button) -> bool { + let currently_pressed = self.current as u32; + let keys = keys.bits(); + (currently_pressed & keys) != 0 + } + pub fn is_released(&self, keys: Button) -> bool { + !self.is_pressed(keys) + } + + pub fn is_just_pressed(&self, keys: Button) -> bool { + let current = self.current as u32; + let previous = self.previous as u32; + let keys = keys.bits(); + ((current & keys) != 0) && ((previous & keys) == 0) + } + + pub fn is_just_released(&self, keys: Button) -> bool { + let current = self.current as u32; + let previous = self.previous as u32; + let keys = keys.bits(); + ((current & keys) == 0) && ((previous & keys) != 0) + } +} diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 0000000..730b191 --- /dev/null +++ b/src/interrupt.rs @@ -0,0 +1,56 @@ +use crate::memory_mapped::MemoryMapped; + +pub enum Interrupt { + VBlank, + HBlank, + VCounter, + Timer0, + Timer1, + Timer2, + Timer3, + Serial, + Dma0, + Dma1, + Dma2, + Dma3, + Keypad, + Gamepak, +} + +const ENABLED_INTERRUPTS: MemoryMapped = MemoryMapped::new(0x04000200); +const INTERRUPTS_ENABLED: MemoryMapped = MemoryMapped::new(0x04000208); + +pub fn enable(interrupt: Interrupt) { + let _interrupt_token = temporary_interrupt_disable(); + let interrupt = interrupt as usize; + let enabled = ENABLED_INTERRUPTS.get() | (1 << (interrupt as u16)); + ENABLED_INTERRUPTS.set(enabled); +} + +pub fn disable(interrupt: Interrupt) { + let _interrupt_token = temporary_interrupt_disable(); + let interrupt = interrupt as usize; + let enabled = ENABLED_INTERRUPTS.get() & !(1 << (interrupt as u16)); + ENABLED_INTERRUPTS.set(enabled); +} + +pub struct Disable {} + +impl Drop for Disable { + fn drop(&mut self) { + enable_interrupts(); + } +} + +pub fn temporary_interrupt_disable() -> Disable { + disable_interrupts(); + Disable {} +} + +pub fn enable_interrupts() { + INTERRUPTS_ENABLED.set(1); +} + +fn disable_interrupts() { + INTERRUPTS_ENABLED.set(0); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..194beca --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,49 @@ +#![no_std] +#![feature(asm)] +#![deny(clippy::all)] + +use core::fmt::Write; +pub mod display; +pub mod input; +pub mod interrupt; + +mod memory_mapped; +mod mgba; +mod single; + +pub mod syscall; + +#[panic_handler] +#[allow(unused_must_use)] +fn panic_implementation(info: &core::panic::PanicInfo) -> ! { + if let Some(mut mgba) = mgba::Mgba::new() { + write!(mgba, "{}", info); + mgba.set_level(mgba::DebugLevel::Fatal); + } + + loop {} +} + +static mut GBASINGLE: single::Singleton = single::Singleton::new(Gba::single_new()); + +pub struct Gba { + pub display: display::Display, +} + +impl Gba { + pub fn new() -> Self { + unsafe { GBASINGLE.take() } + } + + const fn single_new() -> Self { + Self { + display: display::Display::new(), + } + } +} + +impl Default for Gba { + fn default() -> Self { + Self::new() + } +} diff --git a/src/memory_mapped.rs b/src/memory_mapped.rs new file mode 100644 index 0000000..7807754 --- /dev/null +++ b/src/memory_mapped.rs @@ -0,0 +1,56 @@ +pub struct MemoryMapped { + address: *mut T, +} + +impl MemoryMapped { + pub const fn new(address: usize) -> Self { + MemoryMapped { + address: address as *mut T, + } + } + + pub fn get(&self) -> T { + unsafe { self.address.read_volatile() } + } + + pub fn set(&self, val: T) { + unsafe { self.address.write_volatile(val) } + } +} + +pub struct MemoryMapped1DArray { + array: *mut [T; N], +} + +#[allow(dead_code)] +impl MemoryMapped1DArray { + pub const fn new(address: usize) -> Self { + MemoryMapped1DArray { + array: address as *mut [T; N], + } + } + pub fn get(&self, n: usize) -> T { + unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() } + } + pub fn set(&self, n: usize, val: T) { + unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) } + } +} + +pub struct MemoryMapped2DArray { + array: *mut [[T; X]; Y], +} + +impl MemoryMapped2DArray { + pub const fn new(address: usize) -> Self { + MemoryMapped2DArray { + array: address as *mut [[T; X]; Y], + } + } + pub fn get(&self, x: usize, y: usize) -> T { + unsafe { (&mut (*self.array)[y][x] as *mut T).read_volatile() } + } + pub fn set(&self, x: usize, y: usize, val: T) { + unsafe { (&mut (*self.array)[y][x] as *mut T).write_volatile(val) } + } +} diff --git a/src/mgba.rs b/src/mgba.rs new file mode 100644 index 0000000..6e5d445 --- /dev/null +++ b/src/mgba.rs @@ -0,0 +1,67 @@ +use crate::memory_mapped::MemoryMapped; + +#[derive(Eq, PartialEq, Clone, Copy)] +#[repr(u16)] +#[allow(dead_code)] +pub enum DebugLevel { + Fatal = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, +} + +const OUTPUT: *mut u8 = 0x04FF_F600 as *mut u8; +const ENABLE: MemoryMapped = MemoryMapped::new(0x04FF_F780); + +const ENABLE_HANDSHAKE_IN: u16 = 0xC0DE; +const ENABLE_HANDSHAKE_OUT: u16 = 0x1DEA; + +const DEBUG_LEVEL: MemoryMapped = MemoryMapped::new(0x04FF_F700); +const DEBUG_FLAG_CODE: u16 = 0x0100; + +fn is_running_in_mgba() -> bool { + ENABLE.set(ENABLE_HANDSHAKE_IN); + ENABLE.get() == ENABLE_HANDSHAKE_OUT +} + +pub struct Mgba { + bytes_written: usize, +} + +impl Mgba { + pub fn new() -> Option { + if is_running_in_mgba() { + Some(Mgba { bytes_written: 0 }) + } else { + None + } + } +} + +impl Mgba { + pub fn set_level(&mut self, level: DebugLevel) { + DEBUG_LEVEL.set(DEBUG_FLAG_CODE | level as u16); + self.bytes_written = 0; + } +} + +impl core::fmt::Write for Mgba { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + unsafe { + let mut current_location = OUTPUT.add(self.bytes_written); + let mut str_iter = s.bytes(); + while self.bytes_written < 255 { + match str_iter.next() { + Some(byte) => { + current_location.write(byte); + current_location = current_location.offset(1); + self.bytes_written += 1; + } + None => return Ok(()), + } + } + } + Ok(()) + } +} diff --git a/src/single.rs b/src/single.rs new file mode 100644 index 0000000..83f218b --- /dev/null +++ b/src/single.rs @@ -0,0 +1,48 @@ +use core::cell::Cell; + +pub struct Singleton { + single: Option, +} + +impl Singleton { + pub const fn new(s: T) -> Self { + Singleton { single: Some(s) } + } + pub fn take(&mut self) -> T { + let g = core::mem::replace(&mut self.single, None); + g.unwrap() + } +} + +pub struct Single { + is_taken: Cell, +} + +pub struct SingleToken<'a> { + cell: &'a Cell, +} + +impl Single { + pub const fn new() -> Self { + Single { + is_taken: Cell::new(false), + } + } + + pub fn take(&self) -> Result { + if self.is_taken.get() { + Err("Already taken") + } else { + self.is_taken.set(true); + Ok(SingleToken { + cell: &self.is_taken, + }) + } + } +} + +impl Drop for SingleToken<'_> { + fn drop(&mut self) { + self.cell.set(false); + } +} diff --git a/src/syscall.rs b/src/syscall.rs new file mode 100644 index 0000000..fa5d4e2 --- /dev/null +++ b/src/syscall.rs @@ -0,0 +1,104 @@ +#[allow(non_snake_case)] + +pub fn halt() { + unsafe { + asm!( + "swi 0x02", + lateout("r0") _, + lateout("r1") _, + lateout("r2") _, + lateout("r3") _ + ); + } +} + +pub fn stop() { + unsafe { + asm!( + "swi 0x03", + lateout("r0") _, + lateout("r1") _, + lateout("r2") _, + lateout("r3") _ + ); + } +} + +pub fn wait_for_interrupt() { + unsafe { + asm!( + "swi 0x04", + lateout("r0") _, + lateout("r1") _, + lateout("r2") _, + lateout("r3") _ + ); + } +} + +#[allow(non_snake_case)] +pub fn wait_for_VBlank() { + unsafe { + asm!( + "swi 0x05", + lateout("r0") _, + lateout("r1") _, + lateout("r2") _, + lateout("r3") _ + ); + } +} + +pub fn div(numerator: i32, denominator: i32) -> (i32, i32, i32) { + let divide: i32; + let modulo: i32; + let abs_divide: i32; + unsafe { + asm!( + "swi 0x06", + in("r0") numerator, + in("r1") denominator, + lateout("r0") divide, + lateout("r1") modulo, + lateout("r3") abs_divide, + ); + } + (divide, modulo, abs_divide) +} + +pub fn sqrt(n: i32) -> i32 { + let result: i32; + unsafe { + asm!( + "swi 0x08", + in("r0") n, + lateout("r0") result, + ); + } + result +} + +pub fn arc_tan(n: i16) -> i16 { + let result: i16; + unsafe { + asm!( + "swi 0x09", + in("r0") n, + lateout("r0") result, + ); + } + result +} + +pub fn arc_tan2(x: i16, y: i32) -> i16 { + let result: i16; + unsafe { + asm!( + "swi 0x09", + in("r0") x, + in("r1") y, + lateout("r0") result, + ); + } + result +}