Introduction and Limits chapters updated

This commit is contained in:
Lokathor 2018-12-14 22:57:14 -07:00
parent 91057a529d
commit ac5353b773
13 changed files with 258 additions and 17 deletions

View file

@ -43,10 +43,10 @@ fn main() -> std::io::Result<()> {
std::process::Command::new("arm-none-eabi-objcopy").args( std::process::Command::new("arm-none-eabi-objcopy").args(
&["-O", "binary", &["-O", "binary",
&format!("target/thumbv4-none-agb/release/examples/{}",name), &format!("target/thumbv4-none-agb/release/examples/{}",name),
&format!("target/example-{}.gba",name)]) &format!("target/{}.gba",name)])
.output().expect("failed to objcopy!"); .output().expect("failed to objcopy!");
std::process::Command::new("gbafix").args( std::process::Command::new("gbafix").args(
&[&format!("target/example-{}.gba",name)]) &[&format!("target/{}.gba",name)])
.output().expect("failed to gbafix!"); .output().expect("failed to gbafix!");
} }
} }

View file

@ -8,17 +8,19 @@
# gba # 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 Writing a Rust program for the GBA requires a fair amount of special setup. All
detailed for you [in the 0th chapter of the 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) that goes with this book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html).
crate.
If you've done the global setup once before and just want to get a new project If you've done the described global setup once before and just want to get a new
started quickly we got you covered: project started quickly we got you covered:
```sh ```sh
curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME

View file

@ -1,6 +1,6 @@
# Introduction # 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 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. provides the technical advisement, reviews the PRs, and keeps my crazy in check.

View file

@ -21,8 +21,8 @@ goes:
`hello_magic.rs`: `hello_magic.rs`:
```rust ```rust
#![feature(start)]
#![no_std] #![no_std]
#![feature(start)]
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {

View file

@ -1 +1,10 @@
# GBA Limitations # 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.

View file

@ -1 +0,0 @@
# No Floats

View file

@ -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.

View file

@ -1 +0,0 @@
# Core Only

View file

@ -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.

View file

@ -1,5 +1,7 @@
# Volatile Destination # 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 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 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". basically it means "get your grubby mitts off my stuff you over-eager compiler".

View file

@ -9,8 +9,8 @@
* [Newtype](00-introduction/05-newtype.md) * [Newtype](00-introduction/05-newtype.md)
* [Help and Resources](00-introduction/06-help_and_resources.md) * [Help and Resources](00-introduction/06-help_and_resources.md)
* [Limitations](01-limitations/00-index.md) * [Limitations](01-limitations/00-index.md)
* [No Floats](01-limitations/01-no_floats.md) * [No Std](01-limitations/01-no_std.md)
* [Core Only](01-limitations/02-core_only.md) * [Fixed Only](01-limitations/02-fixed_only.md)
* [Volatile Destination](01-limitations/03-volatile_destination.md) * [Volatile Destination](01-limitations/03-volatile_destination.md)
* [Concepts](02-concepts/00-index.md) * [Concepts](02-concepts/00-index.md)
* [CPU](02-concepts/01-cpu.md) * [CPU](02-concepts/01-cpu.md)

View file

@ -1,5 +1,5 @@
#![feature(start)]
#![no_std] #![no_std]
#![feature(start)]
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {

View file

@ -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!");
}
}