From ac5353b77375602662cb780698f2abdaa276c344 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Fri, 14 Dec 2018 22:57:14 -0700 Subject: [PATCH] Introduction and Limits chapters updated --- Makefile.toml | 4 +- README.md | 18 +-- book/src/00-introduction/00-index.md | 2 +- book/src/00-introduction/04-hello-magic.md | 2 +- book/src/01-limitations/00-index.md | 9 ++ book/src/01-limitations/01-no_floats.md | 1 - book/src/01-limitations/01-no_std.md | 108 +++++++++++++++++ book/src/01-limitations/02-core_only.md | 1 - book/src/01-limitations/02-fixed_only.md | 13 +++ .../01-limitations/03-volatile_destination.md | 2 + book/src/SUMMARY.md | 4 +- examples/hello_magic.rs | 2 +- examples/mgba_panic_handler.rs | 109 ++++++++++++++++++ 13 files changed, 258 insertions(+), 17 deletions(-) delete mode 100644 book/src/01-limitations/01-no_floats.md create mode 100644 book/src/01-limitations/01-no_std.md delete mode 100644 book/src/01-limitations/02-core_only.md create mode 100644 book/src/01-limitations/02-fixed_only.md create mode 100644 examples/mgba_panic_handler.rs diff --git a/Makefile.toml b/Makefile.toml index 66d47ec..e01af30 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -43,10 +43,10 @@ fn main() -> std::io::Result<()> { std::process::Command::new("arm-none-eabi-objcopy").args( &["-O", "binary", &format!("target/thumbv4-none-agb/release/examples/{}",name), - &format!("target/example-{}.gba",name)]) + &format!("target/{}.gba",name)]) .output().expect("failed to objcopy!"); std::process::Command::new("gbafix").args( - &[&format!("target/example-{}.gba",name)]) + &[&format!("target/{}.gba",name)]) .output().expect("failed to gbafix!"); } } diff --git a/README.md b/README.md index fdc60f9..0233d8f 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,19 @@ # gba -A crate that helps you make GameBoy Advance (GBA) games +This repository is both a [Tutorial Book](https://rust-console.github.io/gba/) +that teaches you what you need to know to write Rust games for the GameBoy +Advance (GBA), and also a [crate](https://crates.io/crates/gba) that you can +use to do the same. -# First Time Setup +## First Time Setup -This crate requires a fair amount of special setup. All of the steps are -detailed for you [in the 0th chapter of the -book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html) that goes with this -crate. +Writing a Rust program for the GBA requires a fair amount of special setup. All +of the steps are detailed for you [in the Introduction chapter of the +book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html). -If you've done the global setup once before and just want to get a new project -started quickly we got you covered: +If you've done the described global setup once before and just want to get a new +project started quickly we got you covered: ```sh curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME diff --git a/book/src/00-introduction/00-index.md b/book/src/00-introduction/00-index.md index 0567c77..4a75dba 100644 --- a/book/src/00-introduction/00-index.md +++ b/book/src/00-introduction/00-index.md @@ -1,6 +1,6 @@ # Introduction -This is the book for learning how to write GBA games in Rust. +This is the book for learning how to write GameBoy Advance (GBA) games in Rust. I'm **Lokathor**, the main author of the book. There's also **Ketsuban** who provides the technical advisement, reviews the PRs, and keeps my crazy in check. diff --git a/book/src/00-introduction/04-hello-magic.md b/book/src/00-introduction/04-hello-magic.md index dd36821..5d8a679 100644 --- a/book/src/00-introduction/04-hello-magic.md +++ b/book/src/00-introduction/04-hello-magic.md @@ -21,8 +21,8 @@ goes: `hello_magic.rs`: ```rust -#![feature(start)] #![no_std] +#![feature(start)] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/book/src/01-limitations/00-index.md b/book/src/01-limitations/00-index.md index 71e12e6..3cfc7fc 100644 --- a/book/src/01-limitations/00-index.md +++ b/book/src/01-limitations/00-index.md @@ -1 +1,10 @@ # GBA Limitations + +There's a lot of seemingly normal Rust code that you can't write when compiling +for the GBA. + +I mean there's a lot of stuff that you _can_ write, but we don't wanna get +tripped up by assuming we can use something only to get stuck when it's missing. + +We'll start the book by reviewing all the major things we don't have so that we +can avoid any surprises later. diff --git a/book/src/01-limitations/01-no_floats.md b/book/src/01-limitations/01-no_floats.md deleted file mode 100644 index e27e28b..0000000 --- a/book/src/01-limitations/01-no_floats.md +++ /dev/null @@ -1 +0,0 @@ -# No Floats diff --git a/book/src/01-limitations/01-no_std.md b/book/src/01-limitations/01-no_std.md new file mode 100644 index 0000000..39a4c25 --- /dev/null +++ b/book/src/01-limitations/01-no_std.md @@ -0,0 +1,108 @@ +# No Std + +First up, as you already saw in the `hello_magic` code, we have to use the +`#![no_std]` outer attribute on our program when we target the GBA. You can find +some info about `no_std` in two official sources: + +* [unstable + book section](https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib) +* [embedded + book section](https://rust-embedded.github.io/book/intro/no-std.html?highlight=no_std#a--no_std--rust-environment) + +The unstable book is borderline useless here because it's describing too many +things in too many words. The embedded book is much better, but still fairly +terse. + +## Bare Metal + +The GBA falls under what the Embedded Book calls "Bare Metal Environments". +Basically, the machine powers on and immediately begins executing some ASM code. +Our ASM startup was provided by `Ketsuban` (check the `crt0.s` file). We'll go +over _how_ it works much later on, for now it's enough to know that it does +work, and eventually controls passes into Rust code. + +On the rust code side of things, we determine our starting point with the +`#[start]` attribute on our `main` function. The `main` function also has a +specific type signature that's different from the usual `main` that you'd see in +Rust. I'd tell you to read the unstable-book entry on `#[start]` but they +[literally](https://doc.rust-lang.org/unstable-book/language-features/start.html) +just tell you to look at the [tracking issue for +it](https://github.com/rust-lang/rust/issues/29633) instead, and that's not very +helpful either. Basically it just _has_ to be declared the way it is, even +though there's nothing passing in the arguments and there's no place that the +return value will go. The compiler won't accept it any other way. + +## No Standard Library + +The Embedded Book tells us that we can't use the standard library, but we get +access to something called "libcore", which sounds kinda funny. What they're +talking about is just [the core +crate](https://doc.rust-lang.org/core/index.html), which is called `libcore` +within the rust repository for historical reasons. + +The `core` crate is actually still a really big portion of Rust. The standard +library doesn't actually hold too much code (relatively speaking), instead it +just takes code form other crates and then re-exports it in an organized way. So +with just `core` instead of `std`, what are we missing? + +In no particular order: + +* Allocation +* Clock +* Network +* File System + +The allocation system and all the types that you can use if you have a global +allocator are neatly packaged up in the +[alloc](https://doc.rust-lang.org/alloc/index.html) crate. The rest isn't as +nicely organized. + +It's _possible_ to implement a fair portion of the entire standard library +within a GBA context and make the rest just panic if you try to use it. However, +do you really need all that? Eh... probably not? + +* We don't need a file system, because all of our data is just sitting there in + the ROM for us to use. When programming we can organize our `const` data into + modules and such to keep it organized, but once the game is compiled it's just + one huge flat address space. +* Networking, well, the GBA has a Link Cable you can use to communicate with + another GBA, but it's not really like a unix socket with TCP, so the standard + Rust networking isn't a very good match. +* Clock is actually two different things at once. One is the ability to store + the time long term, which is a bit of hardware that some gamepaks have in them + (eg: pokemon ruby/sapphire/emerald). The GBA itself can't keep time while + power is off. However, the second part is just tracking time moment to moment, + which the GBA can totally do. We'll see how to access the timers soon enough. + +Which just leaves us with allocation. Do we need an allocator? Depends on your +game. For demos and small games you probably don't need one. For bigger games +you'll maybe want to get an allocator going eventually. It's in some sense a +crutch, but it's a very useful one. + +So I promise that at some point we'll cover how to get an allocator going. +Either a Rust Global Allocator (if practical), which would allow for a lot of +the standard library types to be used "for free" once it was set up, or just a +custom allocator that's GBA specific if Rust's global allocator style isn't a +good fit for the GBA (I honestly haven't looked into it). + +## Bare Metal Panic + +TODO: expand this + +* Write `0xC0DE` to `0x4fff780` (`u16`) to enable mGBA logging. Write any other + value to disable it. +* Read `0x4fff780` (`u16`) to check mGBA logging status. + * You get `0x1DEA` if debugging is active. + * Otherwise you get standard open bus nonsense values. +* Write your message into the virtual `[u8; 255]` array starting at `0x4fff600`. + mGBA will interpret these bytes as a CString value. +* Write `0x100` PLUS the message level to `0x4fff700` (`u16`) when you're ready + to send a message line: + * 0: Fatal (halts execution with a popup) + * 1: Error + * 2: Warning + * 3: Info + * 4: Debug +* Sending the message also automatically zeroes the output buffer. +* View the output within the "Tools" menu, "View Logs...". Note that the Fatal + message, if any doesn't get logged. diff --git a/book/src/01-limitations/02-core_only.md b/book/src/01-limitations/02-core_only.md deleted file mode 100644 index c213ee4..0000000 --- a/book/src/01-limitations/02-core_only.md +++ /dev/null @@ -1 +0,0 @@ -# Core Only diff --git a/book/src/01-limitations/02-fixed_only.md b/book/src/01-limitations/02-fixed_only.md new file mode 100644 index 0000000..9ad5de3 --- /dev/null +++ b/book/src/01-limitations/02-fixed_only.md @@ -0,0 +1,13 @@ +# Fixed Only + +In addition to not having the standard library available, we don't even have a +floating point unit available! We can't do floating pont math in hardware! We +could still do floating point math as software computations if we wanted, but +that's a slow, slow thing to do. + +Instead let's learn about another way to have fractional values called "Fixed +Point" + +## Fixed Point + +TODO: describe fixed point, make some types, do the impls, all that. diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index d635e88..14fb69d 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1,5 +1,7 @@ # Volatile Destination +TODO: replace all this one "the rant" is finalized + There's a reasonable chance that you've never heard of `volatile` before, so what's that? Well, it's a term that can be used in more than one context, but basically it means "get your grubby mitts off my stuff you over-eager compiler". diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ccd7a41..c9aca10 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,8 +9,8 @@ * [Newtype](00-introduction/05-newtype.md) * [Help and Resources](00-introduction/06-help_and_resources.md) * [Limitations](01-limitations/00-index.md) - * [No Floats](01-limitations/01-no_floats.md) - * [Core Only](01-limitations/02-core_only.md) + * [No Std](01-limitations/01-no_std.md) + * [Fixed Only](01-limitations/02-fixed_only.md) * [Volatile Destination](01-limitations/03-volatile_destination.md) * [Concepts](02-concepts/00-index.md) * [CPU](02-concepts/01-cpu.md) diff --git a/examples/hello_magic.rs b/examples/hello_magic.rs index 247ba9b..75eb6bc 100644 --- a/examples/hello_magic.rs +++ b/examples/hello_magic.rs @@ -1,5 +1,5 @@ -#![feature(start)] #![no_std] +#![feature(start)] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs new file mode 100644 index 0000000..8620d26 --- /dev/null +++ b/examples/mgba_panic_handler.rs @@ -0,0 +1,109 @@ +#![no_std] +#![feature(start)] + +#[no_mangle] +pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize { + let mut y: usize; + let mut n: usize = 32; + y = x >> 16; + if y != 0 { + n = n - 16; + x = y; + } + y = x >> 8; + if y != 0 { + n = n - 8; + x = y; + } + y = x >> 4; + if y != 0 { + n = n - 4; + x = y; + } + y = x >> 2; + if y != 0 { + n = n - 2; + x = y; + } + y = x >> 1; + if y != 0 { + n - 2 + } else { + n - x + } +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + unsafe { + const DEBUG_ENABLE_MGBA: *mut u16 = 0x4fff780 as *mut u16; + const DEBUG_OUTPUT_BASE: *mut u8 = 0x4fff600 as *mut u8; + const DEBUG_SEND_MGBA: *mut u16 = 0x4fff700 as *mut u16; + const DEBUG_SEND_FLAG: u16 = 0x100; + const DEBUG_FATAL: u16 = 0; + const DEBUG_ERROR: u16 = 1; + DEBUG_ENABLE_MGBA.write_volatile(0xC0DE); + if DEBUG_ENABLE_MGBA.read_volatile() == 0x1DEA { + // Give the location + if let Some(location) = info.location() { + let mut out_ptr = DEBUG_OUTPUT_BASE; + let line = location.line(); + let mut line_bytes = [ + (line / 10000) as u8, + ((line / 1000) % 10) as u8, + ((line / 1000) % 10) as u8, + ((line / 10) % 10) as u8, + (line % 10) as u8, + ]; + for line_bytes_mut in line_bytes.iter_mut() { + *line_bytes_mut += b'0'; + } + for b in "Panic: " + .bytes() + .chain(location.file().bytes()) + .chain(", Line ".bytes()) + .chain(line_bytes.iter().cloned()) + .take(255) + { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } else { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in "Panic with no location info:".bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); + // Give the payload + if let Some(payload) = info.payload().downcast_ref::<&str>() { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in payload.bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } else { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in "no payload".bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_FATAL); + } + } + 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); + panic!("fumoffu!"); + } +}