gba/book/src/02-concepts/02-bios.md

173 lines
7.4 KiB
Markdown
Raw Normal View History

2018-12-09 04:57:38 +11:00
# BIOS
2018-12-16 19:21:43 +11:00
* **Address Span:** `0x0` to `0x3FFF` (16k)
The [BIOS](https://en.wikipedia.org/wiki/BIOS) of the GBA is a small read-only
portion of memory at the very base of the address space. However, it is also
hardware protected against reading, so if you try to read from BIOS memory when
the program counter isn't pointed into the BIOS (eg: any time code _you_ write
is executing) then you get [basically garbage
data](https://problemkaputt.de/gbatek.htm#gbaunpredictablethings) back.
So we're not going to spend time here talking about what bits to read or write
within BIOS memory like we do with the other sections. Instead we're going to
spend time talking about [inline
assembly](https://doc.rust-lang.org/unstable-book/language-features/asm.html)
([tracking issue](https://github.com/rust-lang/rust/issues/29722)) and then use
it to call the [GBA BIOS
Functions](https://problemkaputt.de/gbatek.htm#biosfunctions).
Note that BIOS calls have _more overhead than normal function calls_, so don't
go using them all over the place if you don't have to.
I'd like to take a moment to thank [Marc Brinkmann](https://github.com/mbr)
(with contributions from [Oliver Schneider](https://github.com/oli-obk) and
[Philipp Oppermann](https://github.com/phil-opp)) for writing [this blog
post](http://embed.rs/articles/2016/arm-inline-assembly-rust/). It's at least
ten times the tutorial quality as the `asm` entry in the Unstable Book has. In
their defense, the actual spec of how inline ASM works in rust is "basically
what clang does", and that's specified as "basically what GCC does", and that's
basically not specified at all despite GCC being like 30 years old.
So we're in for a very slow, careful, and pedantic ride on this one.
## Inline ASM
The inline asm docs describe an asm call as looking like this:
```rust
asm!(assembly template
: output operands
: input operands
: clobbers
: options
);
```
And once you stick a lot of stuff in there it can _absolutely_ be hard to
remember the ordering of the elements. So we'll start with a code block that
has some commends throw in on each line:
```rust
asm!(/* ASM */ TODO
:/* OUT */ TODO
:/* INP */ TODO
:/* CLO */ TODO
:/* OPT */
);
```
Note: it's possible to use an inline ASM style where you allow LLVM to determine
the exact register placement. We will _not_ do that in this section because each
BIOS call has specific input and output slots that we must follow. However, if
you want to use inline asm for other purposes elsewhere in your code you can use
it then.
* **ASM:** The actual asm instructions to use.
* When writing inline asm, remember that we're writing for 16-bit THUMB mode
because that's what all of our Rust code is compiled to. You can switch to
32-bit ARM mode on the fly, but be sure to switch back before the inline ASM
block ends or things will go _bad_.
* You can write code for specific registers (`r0` through `r7` are available
in THUMB mode) or you can write code for _register slots_ and let LLVM pick
what actual registers to assign to what slots. In this case, you'd instead
write `$0`, `$1` and so on (however many you need). Outputs take up one slot
each, followed by inputs taking up one slot each.
* **OUT:** The output variables, if any. Comma separated list.
* Output is specified as `"constraint" (binding)`
* A constraint is either `=` (write), `+` (read and write), or `&` (early
clobber) followed by either the name of a specific register in curly braces,
such as `{r0}`, or simply `r` if you want to let LLVM assign it.
* If you're writing to `r0` you'd use `={r0}`, if you're read writing from
`r3` you'd use `+{r3}` and so on.
* Bindings named in the outputs must be mutable bindings or bindings that
are declared but not yet assigned to.
* GBA registers are 32-bit, and you must always use an appropriately sized
type for the binding.
* LLVM assumes when selecting registers for you that no output is written to
until all inputs are read. If this is not the case you need to use the `&`
designation on your output to give LLVM the heads up so that LLVM doesn't
assign it as an input register.
* **INP:** The inputs, if any. Comma separated list.
* Similar to outputs, the input format is `"constraint" (binding)`
* Inputs don't have a symbol prefix, you simply name the specific register in
curly braces or use `r` to let LLVM pick.
* Inputs should always be 32-bit types. (TODO: can you use smaller types and
have it 'just work'?)
* **CLO:** This is possibly _the most important part to get right_. The
"clobbers" part describes what registers are affected by this use of asm. The
compiler will use this to make sure that you don't accidentally destroy any of
your data.
* The clobbers list is a comma separated series of string literals that each
name one of the registers clobbered.
* Example: "r0", "r1", "r3"
* **OPT:** This lets us specify any options. At the moment the only option we
care about is that some asm calls will need to be "volatile". As with reads
and writes, the compiler will attempt to eliminate asm that it thinks isn't
necessary, so if there's no output from an asm block we'll need to mark it
volatile to make sure that it gets done.
That seems like a whole lot, but since we're only handling BIOS calls in this
section we can tone it down quite a bit:
* Inputs are always `r0`, `r1`, `r2`, and/or `r3`, depending on function.
* Outputs are always zero or more of `r0`, `r1`, and `r3`.
* Any of the output registers that aren't actually used should be marked as
clobbered.
* All other registers are unaffected.
All of the GBA BIOS calls are performed using the
[swi](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0068b/BABFCEEG.html)
instruction, combined with a value depending on what BIOS function you're trying
to invoke. If you're in 16-bit code you use the value directly, and if you're in
32-bit mode you shift the value up by 16 bits first.
### Example BIOS Function: Division
The GBA doesn't have hardware division. You have to do it in software.
You can implement that yourself (we might get around to trying that, i was even
sent [a link to a
paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2008/08/tr-2008-141.pdf)
that I promptly did not read), or you can call the BIOS to do it for you and
trust that it's being as efficient as possible.
GBATEK gives a very clear explanation of it:
```txt
Signed Division, r0/r1.
r0 signed 32bit Number
r1 signed 32bit Denom
Return:
r0 Number DIV Denom ;signed
r1 Number MOD Denom ;signed
r3 ABS (Number DIV Denom) ;unsigned
For example, incoming -1234, 10 should return -123, -4, +123.
The function usually gets caught in an endless loop upon division by zero.
```
Of course, the math folks tell me that the `r1` value should be properly called
the "remainder" not the "modulus". We'll go with that for our function, doesn't
hurt to use the correct names. The function itself is a single assert, then we
name some bindings without giving them a value, make the asm call, and then
return what we got.
```rust
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
assert!(denominator != 0);
let div_out: i32;
let rem_out: i32;
unsafe {
asm!(/* ASM */ "swi 0x06"
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
:/* CLO */ "r3"
:/* OPT */
);
}
(div_out, rem_out)
}
```
I _hope_ this makes sense by now.