mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 11:31:31 +11:00
Use RFC 2873 asm syntax (#93)
The new syntax is way safer and more ergonomic. In fact, it renders obsolete some of the warnings in the docs related to the use of `asm!`.
This commit is contained in:
parent
af98b63aa1
commit
1696c66b1b
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gba"
|
name = "gba"
|
||||||
description = "A crate (and book) for making GBA games with Rust."
|
description = "A crate (and book) for making GBA games with Rust."
|
||||||
version = "0.4.0-pre"
|
version = "0.4.0-pre1"
|
||||||
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
||||||
repository = "https://github.com/rust-console/gba"
|
repository = "https://github.com/rust-console/gba"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -26,7 +26,7 @@ at all. (TODO: investigate more about what parts of the BIOS we could
|
||||||
potentially offer faster alternatives for.)
|
potentially offer faster alternatives for.)
|
||||||
|
|
||||||
I'd like to take a moment to thank [Marc Brinkmann](https://github.com/mbr)
|
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
|
(with contributions from [Oliver Scherer](https://github.com/oli-obk) and
|
||||||
[Philipp Oppermann](https://github.com/phil-opp)) for writing [this blog
|
[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
|
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
|
ten times the tutorial quality as the `asm` entry in the Unstable Book has. In
|
||||||
|
@ -39,15 +39,7 @@ So let's be slow and pedantic about this process.
|
||||||
|
|
||||||
## Inline ASM
|
## Inline ASM
|
||||||
|
|
||||||
**Fair Warning:** Inline asm is one of the least stable parts of Rust overall,
|
**Fair Warning:** The general information that follows regarding the asm macro
|
||||||
and if you write bad things you can trigger internal compiler errors and panics
|
|
||||||
and crashes and make LLVM choke and die without explanation. If you write some
|
|
||||||
inline asm and then suddenly your program suddenly stops compiling without
|
|
||||||
explanation, try commenting out that whole inline asm use and see if it's
|
|
||||||
causing the problem. Double check that you've written every single part of the
|
|
||||||
asm call absolutely correctly, etc, etc.
|
|
||||||
|
|
||||||
**Bonus Warning:** The general information that follows regarding the asm macro
|
|
||||||
is consistent from system to system, but specific information about register
|
is consistent from system to system, but specific information about register
|
||||||
names, register quantities, asm instruction argument ordering, and so on is
|
names, register quantities, asm instruction argument ordering, and so on is
|
||||||
specific to ARM on the GBA. If you're programming for any other device you'll
|
specific to ARM on the GBA. If you're programming for any other device you'll
|
||||||
|
@ -57,39 +49,44 @@ Now then, with those out of the way, the inline asm docs describe an asm call as
|
||||||
looking like this:
|
looking like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
asm!(assembly template
|
let x = 10u32;
|
||||||
: output operands
|
let y = 34u32;
|
||||||
: input operands
|
let result: u32;
|
||||||
: clobbers
|
asm!(
|
||||||
: options
|
// assembly template
|
||||||
);
|
"add {lhs}, {rhs}",
|
||||||
```
|
lhs = inout(reg_thumb) x => result,
|
||||||
|
rhs = in(reg_thumb) y,
|
||||||
And once you stick a lot of stuff in there it can _absolutely_ be hard to
|
options(nostack, nomem),
|
||||||
remember the ordering of the elements. So we'll start with a code block that
|
|
||||||
has some comments thrown in on each line:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
asm!(/* ASM */ TODO
|
|
||||||
:/* OUT */ TODO
|
|
||||||
:/* INP */ TODO
|
|
||||||
:/* CLO */ TODO
|
|
||||||
:/* OPT */
|
|
||||||
);
|
);
|
||||||
|
// result == 44
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `asm` macro follows the [RFC
|
||||||
|
2873](https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md)
|
||||||
|
syntax. The following is just a summary of the RFC.
|
||||||
|
|
||||||
Now we have to decide what we're gonna write. Obviously we're going to do some
|
Now we have to decide what we're gonna write. Obviously we're going to do some
|
||||||
instructions, but those instructions use registers, and how are we gonna talk
|
instructions, but those instructions use registers, and how are we gonna talk
|
||||||
about them? We've got two choices.
|
about them? We've got two choices.
|
||||||
|
|
||||||
1) We can pick each and every register used by specifying exact register names.
|
1) We can pick each and every register used by specifying exact register names.
|
||||||
In THUMB mode we have 8 registers available, named `r0` through `r7`. If you
|
In THUMB mode we have 8 registers available, named `r0` through `r7`. To use
|
||||||
switch into 32-bit mode there's additional registers that are also available.
|
those registers you would write `in("r0") x` instead of
|
||||||
|
`rhs = in(reg_thumb) x`, and directly refer to `r0` in the assembly template.
|
||||||
|
|
||||||
2) We can specify slots for registers we need and let LLVM decide. In this style
|
2) We can specify slots for registers we need and let LLVM decide. This is what
|
||||||
you name your slots `$0`, `$1` and so on. Slot numbers are assigned first to
|
we do when we write `rhs = in(reg_thumb) y` and use `{rhs}` in the assembly
|
||||||
all specified outputs, then to all specified inputs, in the order that you
|
template.
|
||||||
list them.
|
|
||||||
|
The `reg_thumb` stands for the register class we are using. Since we are
|
||||||
|
in THUMB mode, the set of registers we can use is limited. `reg_thumb` tells
|
||||||
|
LLVM: "use only registers available in THUMB mode". In 32-bit mode, you have
|
||||||
|
access to more register and you should use a different register class.
|
||||||
|
|
||||||
|
The register classes [are described in the
|
||||||
|
RFC](https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md#register-operands).
|
||||||
|
Look for "ARM" register classes.
|
||||||
|
|
||||||
In the case of the GBA BIOS, each BIOS function has pre-designated input and
|
In the case of the GBA BIOS, each BIOS function has pre-designated input and
|
||||||
output registers, so we will use the first style. If you use inline ASM in other
|
output registers, so we will use the first style. If you use inline ASM in other
|
||||||
|
@ -110,22 +107,22 @@ Remember that our Rust code is in 16-bit mode. You _can_ switch to 32-bit mode
|
||||||
within your asm as long as you switch back by the time the block ends. Otherwise
|
within your asm as long as you switch back by the time the block ends. Otherwise
|
||||||
you'll have a bad time.
|
you'll have a bad time.
|
||||||
|
|
||||||
### Outputs
|
### Register bindings
|
||||||
|
|
||||||
A comma separated list. Each entry looks like
|
After the assembly string literal, you need to define your binding (which
|
||||||
|
rust variables are getting into your registers and which ones are going to refer
|
||||||
|
to their value afterward).
|
||||||
|
|
||||||
* `"constraint" (binding)`
|
There are many operand types [as per the
|
||||||
|
RFC](https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md#operand-type),
|
||||||
|
but you will most often use:
|
||||||
|
|
||||||
An output constraint starts with a symbol:
|
```
|
||||||
|
[alias =] in(<reg>) <binding> // input
|
||||||
* `=` for write only
|
[alias =] out(<reg>) <binding> // output
|
||||||
* `+` for reads and writes
|
[alias =] inout(<reg>) <in binding> => <out binding> // both
|
||||||
* `&` for for "early clobber", meaning that you'll write to this at some point
|
out(<reg>) _ // Clobber
|
||||||
before all input values have been read. It prevents this register from being
|
```
|
||||||
assigned to an input register.
|
|
||||||
|
|
||||||
Followed by _either_ the letter `r` (if you want LLVM to pick the register to
|
|
||||||
use) or curly braces around a specific register (if you want to pick).
|
|
||||||
|
|
||||||
* The binding can be any single 32-bit or smaller value.
|
* The binding can be any single 32-bit or smaller value.
|
||||||
* If your binding has bit pattern requirements ("must be non-zero", etc) you are
|
* If your binding has bit pattern requirements ("must be non-zero", etc) you are
|
||||||
|
@ -134,23 +131,13 @@ use) or curly braces around a specific register (if you want to pick).
|
||||||
being in a fit state to do that.
|
being in a fit state to do that.
|
||||||
* The binding must be either a mutable binding or a binding that was
|
* The binding must be either a mutable binding or a binding that was
|
||||||
pre-declared but not yet assigned.
|
pre-declared but not yet assigned.
|
||||||
|
|
||||||
Anything else is UB.
|
|
||||||
|
|
||||||
### Inputs
|
|
||||||
|
|
||||||
This is a similar comma separated list.
|
|
||||||
|
|
||||||
* `"constraint" (binding)`
|
|
||||||
|
|
||||||
An input constraint doesn't have the symbol prefix, you just pick either `r` or
|
|
||||||
a named register with curly braces around it.
|
|
||||||
|
|
||||||
* An input binding must be a single 32-bit or smaller value.
|
* An input binding must be a single 32-bit or smaller value.
|
||||||
* An input binding _should_ be a type that is `Copy` but this is not an absolute
|
* An input binding _should_ be a type that is `Copy` but this is not an absolute
|
||||||
requirement. Having the input be read is semantically similar to using
|
requirement. Having the input be read is semantically similar to using
|
||||||
`core::ptr::read(&binding)` and forgetting the value when you're done.
|
`core::ptr::read(&binding)` and forgetting the value when you're done.
|
||||||
|
|
||||||
|
Anything else is UB.
|
||||||
|
|
||||||
### Clobbers
|
### Clobbers
|
||||||
|
|
||||||
Sometimes your asm will touch registers other than the ones declared for input
|
Sometimes your asm will touch registers other than the ones declared for input
|
||||||
|
@ -166,11 +153,21 @@ Failure to define all of your clobbers can cause UB.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
There's only one option we'd care to specify. That option is "volatile".
|
By default the compiler won't optimize the code you wrote in an `asm` block. You
|
||||||
|
will need to specify with the `options(..)` parameter that your code can be
|
||||||
|
optimized. The available options [are specified in the
|
||||||
|
RFC](https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md#options-1).
|
||||||
|
|
||||||
Just like with a function call, LLVM will skip a block of asm if it doesn't see
|
An optimization might duplicate or remove your instructions from the final
|
||||||
that any outputs from the asm were used later on. Nearly every single BIOS call
|
code.
|
||||||
(other than the math operations) will need to be marked as "volatile".
|
|
||||||
|
Typically when executing a BIOS call (such as `swi 0x01`, which resets the
|
||||||
|
console), it's important that the instruction is executed, and not optimized
|
||||||
|
away, even though it has no observable input and output to the compiler.
|
||||||
|
|
||||||
|
However some BIOS calls, such as _some_ math functions, have no observable
|
||||||
|
effects outside of the registers we specified, in this case, we instruct the
|
||||||
|
compiler to optimize them.
|
||||||
|
|
||||||
### BIOS ASM
|
### BIOS ASM
|
||||||
|
|
||||||
|
@ -215,11 +212,12 @@ pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
let div_out: i32;
|
let div_out: i32;
|
||||||
let rem_out: i32;
|
let rem_out: i32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x06"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
|
"swi 0x06",
|
||||||
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
|
inout("r0") numerator => div_out,
|
||||||
:/* CLO */ "r3"
|
inout("r1") denominator => rem_out,
|
||||||
:/* OPT */
|
out("r3") _,
|
||||||
|
options(nostack, nomem),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(div_out, rem_out)
|
(div_out, rem_out)
|
||||||
|
|
|
@ -315,29 +315,3 @@ OTHER_MAGIC.index(120 + 96 * 240).write_volatile(0x7C00);
|
||||||
If you wanna see these types and methods with a full docs write up you should
|
If you wanna see these types and methods with a full docs write up you should
|
||||||
check the GBA crate's source.
|
check the GBA crate's source.
|
||||||
|
|
||||||
## Volatile ASM
|
|
||||||
|
|
||||||
In addition to some memory locations being volatile, it's also possible for
|
|
||||||
inline assembly to be declared volatile. This is basically the same idea, "hey
|
|
||||||
just do what I'm telling you, don't get smart about it".
|
|
||||||
|
|
||||||
Normally when you have some `asm!` it's basically treated like a function,
|
|
||||||
there's inputs and outputs and the compiler will try to optimize it so that if
|
|
||||||
you don't actually use the outputs it won't bother with doing those
|
|
||||||
instructions. However, `asm!` is basically a pure black box, so the compiler
|
|
||||||
doesn't know what's happening inside at all, and it can't see if there's any
|
|
||||||
important side effects going on.
|
|
||||||
|
|
||||||
An example of an important side effect that doesn't have output values would be
|
|
||||||
putting the CPU into a low power state while we want for the next VBlank. This
|
|
||||||
lets us save quite a bit of battery power. It requires some setup to be done
|
|
||||||
safely (otherwise the GBA won't ever actually wake back up from the low power
|
|
||||||
state), but the `asm!` you use once you're ready is just a single instruction
|
|
||||||
with no return value. The compiler can't tell what's going on, so you just have
|
|
||||||
to say "do it anyway".
|
|
||||||
|
|
||||||
Note that if you use a linker script to include any ASM with your Rust program
|
|
||||||
(eg: the `crt0.s` file that we setup in the "Development Setup" section), all of
|
|
||||||
that ASM is "volatile" for these purposes. Volatile isn't actually a _hardware_
|
|
||||||
concept, it's just an LLVM concept, and the linker script runs after LLVM has
|
|
||||||
done its work.
|
|
||||||
|
|
|
@ -786,15 +786,12 @@ overhead I mentioned), the BIOS does its thing, and then eventually control
|
||||||
returns to us.
|
returns to us.
|
||||||
|
|
||||||
The precise details of what the BIOS call does depends on the function number
|
The precise details of what the BIOS call does depends on the function number
|
||||||
that we call. We'd even have to potentially mark it as volatile asm if there's
|
that we call. The numerator goes into register 0, and the denominator goes into
|
||||||
no clear outputs, otherwise the compiler would "helpfully" eliminate it for us
|
register 1, the divmod happens, and then the division output is left in register
|
||||||
during optimization. In our case there _are_ clear outputs. The numerator goes
|
0 and the modulus output is left in register 1. I keep calling it "divmod"
|
||||||
into register 0, and the denominator goes into register 1, the divmod happens,
|
because div and modulus are two sides of the same coin. There's no way to do one
|
||||||
and then the division output is left in register 0 and the modulus output is
|
of them faster by not doing the other or anything like that, so we'll first
|
||||||
left in register 1. I keep calling it "divmod" because div and modulus are two
|
define it as a unified function that returns a tuple:
|
||||||
sides of the same coin. There's no way to do one of them faster by not doing the
|
|
||||||
other or anything like that, so we'll first define it as a unified function that
|
|
||||||
returns a tuple:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#![feature(asm)]
|
#![feature(asm)]
|
||||||
|
@ -806,12 +803,18 @@ pub fn div_modulus(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
let div_out: i32;
|
let div_out: i32;
|
||||||
let mod_out: i32;
|
let mod_out: i32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* assembly template */ "swi 0x06"
|
asm!(
|
||||||
:/* output operands */ "={r0}"(div_out), "={r1}"(mod_out)
|
// Assembly template
|
||||||
:/* input operands */ "{r0}"(numerator), "{r1}"(denominator)
|
"swi 0x06",
|
||||||
:/* clobbers */ "r3"
|
// in+output registers
|
||||||
:/* options */
|
inout("r0") numerator => div_out,
|
||||||
);
|
inout("r0") denominator => mod_out,
|
||||||
|
// Clobber (not part of in/output but used by the operation)
|
||||||
|
out("r3") _,
|
||||||
|
// Additional compiler optimization options. See for details:
|
||||||
|
// https://github.com/Amanieu/rfcs/blob/inline-asm/text/0000-inline-asm.md#options-1
|
||||||
|
options(nostack, nomem),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(div_out, mod_out)
|
(div_out, mod_out)
|
||||||
}
|
}
|
||||||
|
|
184
src/bios.rs
184
src/bios.rs
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
||||||
|
|
||||||
|
use core::mem;
|
||||||
use super::*;
|
use super::*;
|
||||||
use io::irq::IrqFlags;
|
use io::irq::IrqFlags;
|
||||||
|
|
||||||
|
@ -60,13 +61,7 @@ pub unsafe fn soft_reset() -> ! {
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
asm!(/* ASM */ "swi 0x00"
|
asm!("swi 0x00", options(noreturn))
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
core::hint::unreachable_unchecked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,12 +98,7 @@ pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) {
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
asm!(/* ASM */ "swi 0x01"
|
asm!("swi 0x01", in("r0") flags.0);
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ "{r0}"(flags.0)
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newtype! {
|
newtype! {
|
||||||
|
@ -143,12 +133,7 @@ pub fn halt() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x02"
|
asm!("swi 0x02");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,12 +155,7 @@ pub fn stop() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x03"
|
asm!("swi 0x03");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,11 +182,10 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x04"
|
asm!(
|
||||||
:/* OUT */ // none
|
"swi 0x04",
|
||||||
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
in("r0") mem::transmute::<bool, u8>(ignore_current_flags),
|
||||||
:/* CLO */ // none
|
in("r1") mem::transmute::<IrqFlags, u16>(target_flags),
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,11 +205,10 @@ pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x05"
|
asm!(
|
||||||
:/* OUT */ // none
|
"swi 0x05",
|
||||||
:/* INP */ // none
|
out("r0") _,
|
||||||
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
out("r1") _,
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,11 +231,12 @@ pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
let div_out: i32;
|
let div_out: i32;
|
||||||
let rem_out: i32;
|
let rem_out: i32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x06"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
|
"swi 0x06",
|
||||||
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
|
inout("r0") numerator => div_out,
|
||||||
:/* CLO */ "r3"
|
inout("r1") denominator => rem_out,
|
||||||
:/* OPT */
|
out("r3") _,
|
||||||
|
options(nostack, nomem),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(div_out, rem_out)
|
(div_out, rem_out)
|
||||||
|
@ -292,16 +271,17 @@ pub fn sqrt(val: u32) -> u16 {
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
let out: u16;
|
let out: u32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x08"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(out)
|
"swi 0x08",
|
||||||
:/* INP */ "{r0}"(val)
|
inout("r0") val => out,
|
||||||
:/* CLO */ "r1", "r3"
|
out("r1") _,
|
||||||
:/* OPT */
|
out("r3") _,
|
||||||
|
options(pure, nomem),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
out
|
out as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,11 +301,12 @@ pub fn atan(theta: i16) -> i16 {
|
||||||
{
|
{
|
||||||
let out: i16;
|
let out: i16;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x09"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(out)
|
"swi 0x09",
|
||||||
:/* INP */ "{r0}"(theta)
|
inout("r0") theta => out,
|
||||||
:/* CLO */ "r1", "r3"
|
out("r1") _,
|
||||||
:/* OPT */
|
out("r3") _,
|
||||||
|
options(pure, nomem),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
@ -349,11 +330,12 @@ pub fn atan2(y: i16, x: i16) -> u16 {
|
||||||
{
|
{
|
||||||
let out: u16;
|
let out: u16;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x0A"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(out)
|
"swi 0x0A",
|
||||||
:/* INP */ "{r0}"(x), "{r1}"(y)
|
inout("r0") x => out,
|
||||||
:/* CLO */ "r3"
|
in("r1") y,
|
||||||
:/* OPT */
|
out("r3") _,
|
||||||
|
options(pure, nomem),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
@ -378,11 +360,11 @@ pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_sourc
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24);
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
asm!(/* ASM */ "swi 0x0B"
|
asm!(
|
||||||
:/* OUT */ // none
|
"swi 0x0B",
|
||||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
in("r0") src,
|
||||||
:/* CLO */ // none
|
in("r1") dest,
|
||||||
:/* OPT */ "volatile"
|
in("r2") control,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,11 +387,11 @@ pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_sourc
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
||||||
asm!(/* ASM */ "swi 0x0B"
|
asm!(
|
||||||
:/* OUT */ // none
|
"swi 0x0B",
|
||||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
in("r0") src,
|
||||||
:/* CLO */ // none
|
in("r1") dest,
|
||||||
:/* OPT */ "volatile"
|
in("r2") control,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -433,11 +415,11 @@ pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_so
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24);
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
asm!(/* ASM */ "swi 0x0C"
|
asm!(
|
||||||
:/* OUT */ // none
|
"swi 0x0C",
|
||||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
in("r0") src,
|
||||||
:/* CLO */ // none
|
in("r1") dest,
|
||||||
:/* OPT */ "volatile"
|
in("r2") control,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,11 +442,10 @@ pub fn get_bios_checksum() -> u32 {
|
||||||
{
|
{
|
||||||
let out: u32;
|
let out: u32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x0D"
|
asm!(
|
||||||
:/* OUT */ "={r0}"(out)
|
"swi 0x0D",
|
||||||
:/* INP */ // none
|
out("r0") out,
|
||||||
:/* CLO */ // none
|
options(pure, readonly),
|
||||||
:/* OPT */ // none
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
@ -499,12 +480,7 @@ pub fn sound_bias(level: u32) {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x19"
|
asm!("swi 0x19", in("r0") level);
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ "{r0}"(level)
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -544,12 +520,7 @@ pub fn sound_driver_mode(mode: u32) {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x1B"
|
asm!("swi 0x1B", in("r0") mode);
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ "{r0}"(mode)
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,12 +542,7 @@ pub fn sound_driver_main() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x1C"
|
asm!("swi 0x1C");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -594,12 +560,7 @@ pub fn sound_driver_vsync() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x1D"
|
asm!("swi 0x1D");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,12 +580,7 @@ pub fn sound_channel_clear() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x1E"
|
asm!("swi 0x1E");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,12 +603,7 @@ pub fn sound_driver_vsync_off() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x28"
|
asm!("swi 0x28");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -671,12 +622,7 @@ pub fn sound_driver_vsync_on() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x29"
|
asm!("swi 0x29");
|
||||||
:/* OUT */ // none
|
|
||||||
:/* INP */ // none
|
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,12 +389,9 @@ impl DMA3 {
|
||||||
// it's only two cycles we just insert two NOP instructions to ensure that
|
// 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
|
// successive calls to `fill32` or other DMA methods don't interfere with
|
||||||
// each other.
|
// each other.
|
||||||
asm!(/* ASM */ "NOP
|
asm!("
|
||||||
NOP"
|
NOP
|
||||||
:/* OUT */ // none
|
NOP
|
||||||
:/* INP */ // none
|
", options(nomem, nostack));
|
||||||
:/* CLO */ // none
|
|
||||||
:/* OPT */ "volatile"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue