mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 10:51:30 +11:00
v0.7.0-remake (#168)
* Literally clear the repo to nothing for starters * The screen color changes with key presses There's enough code here that the screen color can change when you press the keys. Right now it's most all unsafe code directly in the example file. The strange thing is that llvm is making us define atomic helper functions when it's not supposed to do that, so we need to investigate more before proceeding too much. * can't tell if this is broken or if mgba is busted on mac * unix dumper * don't panic * oops * GbaCell into its own file, also i had broken the rt irq handler. * closer to an acceptable demo. * make IrqFn into just an alias. * wrap most current mmio types. * more stuff * Use paste to remove a whole bunch of repetition (#166) * gba cells are "unwind safe", they can't be in a bad intermediate state, that's the point * Once again we must thank Yandros for saving us from ourselves. * assembly updates. * oops * finish video MMIO basics, no docs yet. * small note. * simplify asm irq handler stack usage. * ewram support. * more cell stuff. * Remove the non-Zlib dependency, sorry Sp00ph :( * add proper dma support. * small docs improvement. * bitmap and oam declarations * more macros * single bit constants will be extremely useful * add `IntrWait` * rename for clarity * improvements * use the new constant * improve sound support * timers * key support * add a prelude module. * fix doc * more and more assembly (mem fns will need global_asm probably) * more comments. * comment updates. * readme * more video support, and put IrqFn alias into interrupts module. * finish working in the EABI code. * CI config. * funding file. * rustc plz inline these functions when you can :3 * "rt0" is unnecessarily cryptic * overhaul the readme. * phrasing Co-authored-by: Sp00ph <61327188+Sp00ph@users.noreply.github.com>
This commit is contained in:
parent
380b3612b8
commit
bf853b71d6
|
@ -5,5 +5,5 @@ target = "thumbv4t-none-eabi"
|
|||
build-std = ["core"]
|
||||
|
||||
[target.thumbv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tlinker.ld"]
|
||||
runner = "mgba"
|
||||
runner = "mgba-qt"
|
||||
rustflags = ["-Clink-arg=-Tlink_scripts/mono_boot.ld"]
|
||||
|
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [Lokathor]
|
||||
|
|
@ -6,7 +6,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
|
@ -32,10 +32,3 @@ jobs:
|
|||
toolchain: ${{ matrix.rust.toolchain }}
|
||||
command: build
|
||||
args: --examples
|
||||
|
||||
- name: Check That Docs Compile
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust.toolchain }}
|
||||
command: test
|
||||
args: --doc
|
23
.gitignore
vendored
23
.gitignore
vendored
|
@ -1,21 +1,2 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# The crt0.o file should, under the currently suggested build scheme, be
|
||||
# recompiled every build anyway.
|
||||
crt0.o
|
||||
|
||||
# Don't track VSCode Workspace settings
|
||||
/.vscode
|
||||
|
||||
# Don't track IntelliJ Workspaces
|
||||
/.idea
|
||||
/*.ida
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
|
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.overrideCommand": [
|
||||
"cargo",
|
||||
"build",
|
||||
"--quiet",
|
||||
"--workspace",
|
||||
"--message-format=json",
|
||||
"--lib",
|
||||
"--bins",
|
||||
"--examples"
|
||||
]
|
||||
}
|
34
Cargo.toml
34
Cargo.toml
|
@ -1,35 +1,13 @@
|
|||
[package]
|
||||
name = "gba"
|
||||
description = "A crate for making GBA games with Rust."
|
||||
version = "0.5.3"
|
||||
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
||||
repository = "https://github.com/rust-console/gba"
|
||||
readme = "README.md"
|
||||
keywords = ["gba"]
|
||||
edition = "2018"
|
||||
description = "A crate for 'raw' style GBA development. If you want a 'managed' experience, try the `agb` crate instead."
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
license = "Zlib OR Apache-2.0 OR MIT"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serial = ["embedded-hal", "nb"]
|
||||
|
||||
[dependencies]
|
||||
voladdress = { version = "0.4" }
|
||||
#
|
||||
embedded-hal = { version = "0.2.4", optional = true }
|
||||
nb = { version = "1", optional = true }
|
||||
bitfrob = "0.2.3"
|
||||
voladdress = "1.0.2"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
#[[example]]
|
||||
#name = "uart_echo"
|
||||
#required-features = ["serial"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv4t-none-eabi"
|
||||
cargo-args = ["-Z", "build-std=core"]
|
||||
rustdoc-args = ["--cfg","docs_rs"]
|
||||
opt-level = 3
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
|
@ -1,5 +0,0 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,9 +0,0 @@
|
|||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
|
@ -1,70 +0,0 @@
|
|||
[config]
|
||||
skip_core_tasks = true
|
||||
|
||||
[tasks.verify-toolchain]
|
||||
script_runner = "@duckscript"
|
||||
script = [
|
||||
'''
|
||||
channel = get_env CARGO_MAKE_RUST_CHANNEL
|
||||
assert_eq ${channel} nightly "Rust toolchain must be set to nightly"
|
||||
'''
|
||||
]
|
||||
|
||||
|
||||
[tasks.build-examples-debug]
|
||||
dependencies = ["verify-toolchain"]
|
||||
command = "cargo"
|
||||
args = ["build", "--examples", "--target=thumbv4t-none-eabi", "-Zbuild-std=core"]
|
||||
|
||||
[tasks.build-examples-release]
|
||||
dependencies = ["verify-toolchain"]
|
||||
command = "cargo"
|
||||
args = ["build", "--examples", "--release", "--target=thumbv4t-none-eabi", "-Zbuild-std=core"]
|
||||
|
||||
[tasks.pack-roms]
|
||||
script_runner = "@duckscript"
|
||||
script = [
|
||||
'''
|
||||
release_target = get_env RELEASE_TARGET
|
||||
examples_path = set ./target/thumbv4t-none-eabi/${release_target}/examples
|
||||
examples = glob_array ./examples/*.rs
|
||||
for example in ${examples}
|
||||
example = substring ${example} -3
|
||||
example = basename ${example}
|
||||
binary_exists = is_path_exists ${examples_path}/${example}
|
||||
|
||||
if ${binary_exists}
|
||||
echo "Packing: ${examples_path}/${example} to ${examples_path}/${example}.gba"
|
||||
exec arm-none-eabi-objcopy -O binary ${examples_path}/${example} ${examples_path}/${example}.gba
|
||||
echo "Fixing headers: ${examples_path}/${example}.gba"
|
||||
exec gbafix ${examples_path}/${example}.gba
|
||||
else
|
||||
echo "Binary does not exist: ${examples_path}/${example}"
|
||||
end
|
||||
end
|
||||
'''
|
||||
]
|
||||
|
||||
[tasks.pack-roms-release]
|
||||
dependencies = ["build-examples-release"]
|
||||
env = { RELEASE_TARGET = "release" }
|
||||
run_task = "pack-roms"
|
||||
|
||||
[tasks.pack-roms-debug]
|
||||
dependencies = ["build-examples-debug"]
|
||||
env = { RELEASE_TARGET = "debug" }
|
||||
run_task = "pack-roms"
|
||||
|
||||
[tasks.test]
|
||||
dependencies = ["verify-toolchain"]
|
||||
command = "cargo"
|
||||
args = ["test", "--lib"]
|
||||
|
||||
[tasks.justrelease]
|
||||
dependencies = ["pack-roms-release"]
|
||||
|
||||
[tasks.build-all]
|
||||
dependencies = ["pack-roms-debug", "pack-roms-release"]
|
||||
|
||||
[tasks.default]
|
||||
alias = "build-all"
|
132
README.md
132
README.md
|
@ -1,79 +1,93 @@
|
|||
[![License:Zlib](https://img.shields.io/badge/License-Zlib-green.svg)](https://opensource.org/licenses/Zlib)
|
||||
[![License:Apache2](https://img.shields.io/badge/License-Apache2-green.svg)](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
[![License:MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
|
||||
# `gba`
|
||||
|
||||
[![ci](https://github.com/rust-console/gba/workflows/ci/badge.svg?branch=main)](https://github.com/rust-console/gba/actions?query=workflow%3Aci)
|
||||
[![crates.io](https://img.shields.io/crates/v/gba.svg)](https://crates.io/crates/gba)
|
||||
[![docs.rs](https://docs.rs/gba/badge.svg)](https://docs.rs/gba/latest/gba/)
|
||||
## Status: Pending
|
||||
|
||||
* ![Stability:None](https://img.shields.io/badge/Stability-None-red.svg)
|
||||
This branch is a **pending** release for `0.7` of the crate.
|
||||
|
||||
# gba
|
||||
## How To Make Your Own GBA Project Using This Crate
|
||||
|
||||
A crate to make GBA programming easy.
|
||||
This will require the use of Nightly Rust. Any recent-ish version of Nightly should be fine.
|
||||
|
||||
Currently we don't have as much documentation as we'd like.
|
||||
If you check out the [awesome-gbadev](https://github.com/gbdev/awesome-gbadev) repository they have many resources, though most are oriented towards C.
|
||||
### Get ARM Binutils
|
||||
|
||||
## System Setup
|
||||
You'll need the ARM version of the GNU binutils in your path, specifically the linker (`arm-none-eabi-ld`).
|
||||
|
||||
There's a few extra things to install that you just need to do once per system.
|
||||
Linux folks can use the package manager. Mac and Windows folks can use the [ARM Website](https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain).
|
||||
|
||||
Building for the GBA requires Nightly rust, and also uses the `build-std` feature, so you'll need the rust source available.
|
||||
### Run `rustup component add rust-src`
|
||||
|
||||
```sh
|
||||
rustup install nightly
|
||||
rustup +nightly component add rust-src
|
||||
This makes rustup keep the standard library source code on hand, which is necessary for `build-std` to work.
|
||||
|
||||
### Create `.cargo/config.toml`
|
||||
|
||||
You should set up your project's cargo config like so:
|
||||
|
||||
```toml
|
||||
[build]
|
||||
target = "thumbv4t-none-eabi"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
|
||||
[target.thumbv4t-none-eabi]
|
||||
runner = "mgba-qt"
|
||||
rustflags = ["-Clink-arg=-Tlink_scripts/mono_boot.ld"]
|
||||
```
|
||||
|
||||
You'll also need the ARM binutils so that you can have the assembler and linker for the ARMv4T architecture.
|
||||
The way to get them varies by platform:
|
||||
* Ubuntu and other debian-like linux distros will usually have them in the package manager.
|
||||
```shell
|
||||
sudo apt-get install binutils-arm-none-eabi
|
||||
```
|
||||
* With OSX you can get them via homebrew.
|
||||
```shell
|
||||
brew install --cask gcc-arm-embedded
|
||||
```
|
||||
* On Windows you can get the installer from ARM's website and run that.
|
||||
* Download the [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
|
||||
* When installing the toolchain, make sure to select "Add path to environment variable" during install.
|
||||
* You'll have to restart any open command prompts after you so run the installer so that they see the new PATH value.
|
||||
This sets the default build target to be `thumbv4t-none-eabi` using the unstable `build-std` cargo feature.
|
||||
|
||||
Finally, rustc itself is only able to make ELF format files. These can be run in emulators, but aren't able to be played on actual hardware.
|
||||
You'll need to convert the ELF file into a GBA rom. There's a `cargo-make` file in this repository to do this, and it relies on a tool called `gbafix`
|
||||
to assign the right header data to the ROM when packing it.
|
||||
Also, this sets `cargo run` to run the binary as an argument to `mgba-qt`.
|
||||
If you're on windows then your copy of mGBA will be called "mgba.exe" instead.
|
||||
|
||||
```sh
|
||||
cargo install cargo-make
|
||||
cargo install gbafix
|
||||
Also, this sets [mono_boot.ld](link_scripts/mono_boot.ld) as the linker script.
|
||||
You'll need to copy this into your project.
|
||||
If you save it to another location, adjust the path accordingly.
|
||||
|
||||
### Make Your Executables
|
||||
|
||||
At this point you can make a `bin` or an `example`.
|
||||
|
||||
Every executable will need to be `no_std` and `no_main`.
|
||||
Place these at the top of the file:
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
```
|
||||
|
||||
## Project Setup
|
||||
Every executable will need a panic handler defined, even if your code can't actually panic.
|
||||
A minimal panic handler looks like this:
|
||||
|
||||
To build a GBA project, you'll want to copy the `.cargo/config.toml` file from this repo into your own project.
|
||||
|
||||
Then use one of the examples as a guide to get started.
|
||||
|
||||
When you build your project, cargo will put outputs in the `target/thumbv4t-none-eabi/` directory.
|
||||
This includes the `debug/` and `release/` sub-directories.
|
||||
Your binary will be in there, but it'll be in ELF format.
|
||||
You can run this directly in an emulator such as [mGBA](https://mgba.io/) if you'd like.
|
||||
|
||||
When you're ready to convert your program into a "proper" GBA rom you'll need to run an `objcopy`
|
||||
to extract just the raw binary data:
|
||||
```
|
||||
arm-none-eabi-objcopy -O binary [RUST_BINARY_NAME] [ROM_NAME].gba
|
||||
```rust
|
||||
#[panic_handler]
|
||||
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Then you'll need to patch the header data with `gbafix`
|
||||
```
|
||||
gbafix [ROM_NAME].gba
|
||||
```
|
||||
And you'll be all done!
|
||||
Every executable will need a `main` function defined.
|
||||
We used the `no_main` attribute on the executable so that Rust will allow us to use a non-standard function signature:
|
||||
|
||||
# Contribution
|
||||
```rust
|
||||
#[no_mangle]
|
||||
extern "C" fn main() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
This crate is tri-licensed under Zlib / Apache-2.0 / MIT.
|
||||
Any contributions you submit must be licensed the same.
|
||||
### Optional: Use `objcopy` and `gbafix`
|
||||
|
||||
The `cargo build` will produce ELF files, which mGBA can run directly.
|
||||
|
||||
If you want to run your program on real hardware you'll need to:
|
||||
|
||||
1) `objcopy` the raw binary out of the ELF into its own file.
|
||||
2) Use `gbafix` to give the file appropriate header data to that file.
|
||||
|
||||
You can get `gbafix` through cargo: `cargo install gbafix`.
|
||||
|
||||
## Other GBA Crates
|
||||
|
||||
This crate provides a largely "unmanaged" interaction with the GBA's hardware.
|
||||
If you would like an API that use the borrow checker to guide you more,
|
||||
the [agb](https://docs.rs/agb) crate might be what you want.
|
||||
|
|
20
build.rs
20
build.rs
|
@ -1,20 +0,0 @@
|
|||
fn main() {
|
||||
// we skip assembling the runtime for docs.rs builds.
|
||||
if !cfg!(docs_rs) {
|
||||
let out_file = "rsrt0.o";
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
let out_dir_file = format!("{}/{}", out_dir, out_file);
|
||||
let as_output = std::process::Command::new("arm-none-eabi-as")
|
||||
.args(&["-o", out_dir_file.as_str()])
|
||||
.arg("-mthumb-interwork")
|
||||
.arg("-mcpu=arm7tdmi")
|
||||
.arg("src/rsrt0.S")
|
||||
.output()
|
||||
.expect("failed to run arm-none-eabi-as");
|
||||
if !as_output.status.success() {
|
||||
panic!("{}", String::from_utf8_lossy(&as_output.stderr));
|
||||
}
|
||||
//
|
||||
println!("cargo:rustc-link-search={}", out_dir);
|
||||
}
|
||||
}
|
2
dump.bat
Normal file
2
dump.bat
Normal file
|
@ -0,0 +1,2 @@
|
|||
cargo build --examples
|
||||
arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/hello >target/dump-hello.txt
|
3
dump.sh
Executable file
3
dump.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
cargo build --examples
|
||||
arm-none-eabi-objdump --headers --disassemble --demangle --architecture=armv4t --no-show-raw-insn -Mreg-names-std target/thumbv4t-none-eabi/debug/examples/hello >target/dump-hello.txt
|
34
examples/hello.rs
Normal file
34
examples/hello.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(isa_attribute)]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
static KEYS: GbaCell<KeyInput> = GbaCell::new(KeyInput::new());
|
||||
|
||||
extern "C" fn irq_handler(_: u16) {
|
||||
// just as a demo, we'll read the keys during vblank.
|
||||
KEYS.write(KEYINPUT.read());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn main() -> ! {
|
||||
RUST_IRQ_HANDLER.write(Some(irq_handler));
|
||||
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
|
||||
IE.write(IrqBits::VBLANK);
|
||||
IME.write(true);
|
||||
|
||||
DISPCNT.write(DisplayControl::new().with_show_bg0(true));
|
||||
|
||||
loop {
|
||||
VBlankIntrWait();
|
||||
|
||||
let k = KEYS.read();
|
||||
BACKDROP_COLOR.write(Color(k.to_u16()));
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused)]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
// This kills the emulation with a message if we're running inside an
|
||||
// emulator we support (mGBA or NO$GBA), or just crashes the game if we
|
||||
// aren't.
|
||||
//fatal!("{}", info);
|
||||
|
||||
loop {
|
||||
DISPCNT.read();
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vblank() {
|
||||
while VCOUNT.read() < 160 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vdraw() {
|
||||
while VCOUNT.read() >= 160 {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() -> ! {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
|
||||
let mut px = mode3::WIDTH / 2;
|
||||
let mut py = mode3::HEIGHT / 2;
|
||||
let mut color = Color::from_rgb(31, 3, 1);
|
||||
|
||||
loop {
|
||||
// read our keys for this frame
|
||||
let keys: Keys = KEYINPUT.read().into();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px = px.wrapping_add((2 * keys.x_signum()) as usize);
|
||||
py = py.wrapping_add((2 * keys.y_signum()) as usize);
|
||||
if keys.l() {
|
||||
color = Color(color.0.rotate_left(5));
|
||||
}
|
||||
if keys.r() {
|
||||
color = Color(color.0.rotate_right(5));
|
||||
}
|
||||
|
||||
// now we wait
|
||||
spin_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
if (px + 1) >= mode3::WIDTH || (py + 1) >= mode3::HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
mode3::dma3_clear_to(Color::from_rgb(0, 0, 0));
|
||||
px = mode3::WIDTH / 2;
|
||||
py = mode3::HEIGHT / 2;
|
||||
color = Color(color.0.rotate_left(7));
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
mode3::bitmap_xy(px, py).write(color);
|
||||
mode3::bitmap_xy(px, py + 1).write(color);
|
||||
mode3::bitmap_xy(px + 1, py).write(color);
|
||||
mode3::bitmap_xy(px + 1, py + 1).write(color);
|
||||
}
|
||||
|
||||
// now we wait again
|
||||
spin_until_vdraw();
|
||||
}
|
||||
}
|
162
examples/irq.rs
162
examples/irq.rs
|
@ -1,162 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(isa_attribute)]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||
const RED: Color = Color::from_rgb(31, 0, 0);
|
||||
const GREEN: Color = Color::from_rgb(0, 31, 0);
|
||||
const BLUE: Color = Color::from_rgb(0, 0, 31);
|
||||
const YELLOW: Color = Color::from_rgb(31, 31, 0);
|
||||
const PINK: Color = Color::from_rgb(31, 0, 31);
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn start_timers() {
|
||||
let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16;
|
||||
const TIMER_SETTINGS: TimerControl =
|
||||
TimerControl::new().with_irq_on_overflow(true).with_enabled(true);
|
||||
|
||||
TIMER0_RELOAD.write(init_val);
|
||||
TIMER0_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(3));
|
||||
TIMER1_RELOAD.write(init_val);
|
||||
TIMER1_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(1));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn main() -> ! {
|
||||
DISPCNT.write(DisplayControl::new().with_display_mode(3).with_display_bg2(true));
|
||||
mode3::dma3_clear_to(BLACK);
|
||||
|
||||
// Set the IRQ handler to use.
|
||||
unsafe { USER_IRQ_HANDLER.write(Some(irq_handler_a32)) };
|
||||
|
||||
// Enable all interrupts that are set in the IE register.
|
||||
unsafe { IME.write(true) };
|
||||
|
||||
// Request that VBlank, HBlank and VCount will generate IRQs.
|
||||
const DISPLAY_SETTINGS: DisplayStatus = DisplayStatus::new()
|
||||
.with_vblank_irq_enabled(true)
|
||||
.with_hblank_irq_enabled(true)
|
||||
.with_vcount_irq_enabled(true);
|
||||
DISPSTAT.write(DISPLAY_SETTINGS);
|
||||
|
||||
// Start two timers with overflow IRQ generation.
|
||||
start_timers();
|
||||
|
||||
loop {
|
||||
let this_frame_keys: Keys = KEYINPUT.read().into();
|
||||
|
||||
// The VBlank IRQ must be enabled at minimum, or else the CPU will halt
|
||||
// at the call to vblank_interrupt_wait() as the VBlank IRQ will never
|
||||
// be triggered.
|
||||
let mut flags = InterruptFlags::new().with_vblank(true);
|
||||
|
||||
// Enable interrupts based on key input.
|
||||
if this_frame_keys.a() {
|
||||
flags = flags.with_hblank(true);
|
||||
}
|
||||
if this_frame_keys.b() {
|
||||
flags = flags.with_vcount(true);
|
||||
}
|
||||
if this_frame_keys.l() {
|
||||
flags = flags.with_timer0(true);
|
||||
}
|
||||
if this_frame_keys.r() {
|
||||
flags = flags.with_timer1(true);
|
||||
}
|
||||
|
||||
unsafe { IE.write(flags) };
|
||||
|
||||
// Puts the CPU into low power mode until a VBlank IRQ is received. This
|
||||
// will yield considerably better power efficiency as opposed to spin
|
||||
// waiting.
|
||||
unsafe { VBlankIntrWait() };
|
||||
}
|
||||
}
|
||||
|
||||
static mut PIXEL: usize = 0;
|
||||
|
||||
fn write_pixel(color: Color) {
|
||||
unsafe {
|
||||
(0x0600_0000 as *mut Color).wrapping_offset(PIXEL as isize).write_volatile(color);
|
||||
PIXEL += 1;
|
||||
if PIXEL == (mode3::WIDTH * mode3::HEIGHT) {
|
||||
PIXEL = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instruction_set(arm::a32)]
|
||||
extern "C" fn irq_handler_a32() {
|
||||
// we just use this a32 function to jump over back to t32 code.
|
||||
irq_handler_t32()
|
||||
}
|
||||
|
||||
fn irq_handler_t32() {
|
||||
// disable Interrupt Master Enable to prevent an interrupt during the handler
|
||||
unsafe { IME.write(false) };
|
||||
|
||||
// read which interrupts are pending, and "filter" the selection by which are
|
||||
// supposed to be enabled.
|
||||
let which_interrupts_to_handle = IRQ_PENDING.read() & IE.read();
|
||||
|
||||
// read the current IntrWait value. It sorta works like a running total, so
|
||||
// any interrupts we process we'll enable in this value, which we write back
|
||||
// at the end.
|
||||
let mut intr_wait_flags = INTR_WAIT_ACKNOWLEDGE.read();
|
||||
|
||||
if which_interrupts_to_handle.vblank() {
|
||||
vblank_handler();
|
||||
intr_wait_flags.set_vblank(true);
|
||||
}
|
||||
if which_interrupts_to_handle.hblank() {
|
||||
hblank_handler();
|
||||
intr_wait_flags.set_hblank(true);
|
||||
}
|
||||
if which_interrupts_to_handle.vcount() {
|
||||
vcount_handler();
|
||||
intr_wait_flags.set_vcount(true);
|
||||
}
|
||||
if which_interrupts_to_handle.timer0() {
|
||||
timer0_handler();
|
||||
intr_wait_flags.set_timer0(true);
|
||||
}
|
||||
if which_interrupts_to_handle.timer1() {
|
||||
timer1_handler();
|
||||
intr_wait_flags.set_timer1(true);
|
||||
}
|
||||
|
||||
// acknowledge that we did stuff.
|
||||
IRQ_ACKNOWLEDGE.write(which_interrupts_to_handle);
|
||||
|
||||
// write out any IntrWait changes.
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(intr_wait_flags) };
|
||||
|
||||
// re-enable as we go out.
|
||||
unsafe { IME.write(true) };
|
||||
}
|
||||
|
||||
fn vblank_handler() {
|
||||
write_pixel(BLUE);
|
||||
}
|
||||
|
||||
fn hblank_handler() {
|
||||
write_pixel(GREEN);
|
||||
}
|
||||
|
||||
fn vcount_handler() {
|
||||
write_pixel(RED);
|
||||
}
|
||||
|
||||
fn timer0_handler() {
|
||||
write_pixel(YELLOW);
|
||||
}
|
||||
|
||||
fn timer1_handler() {
|
||||
write_pixel(PINK);
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused)]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
loop {
|
||||
DISPCNT.read();
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vblank() {
|
||||
while VCOUNT.read() < 160 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vdraw() {
|
||||
while VCOUNT.read() >= 160 {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() -> ! {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
|
||||
const RED: Color = Color::from_rgb(31, 0, 0);
|
||||
const GREEN: Color = Color::from_rgb(0, 31, 0);
|
||||
|
||||
let mut keys = Keys::new();
|
||||
|
||||
fn draw_square(x: usize, y: usize, color: Color) {
|
||||
mode3::bitmap_xy(x, y).write(color);
|
||||
mode3::bitmap_xy(x, y + 1).write(color);
|
||||
mode3::bitmap_xy(x, y + 2).write(color);
|
||||
mode3::bitmap_xy(x + 1, y).write(color);
|
||||
mode3::bitmap_xy(x + 1, y + 1).write(color);
|
||||
mode3::bitmap_xy(x + 1, y + 2).write(color);
|
||||
mode3::bitmap_xy(x + 2, y).write(color);
|
||||
mode3::bitmap_xy(x + 2, y + 1).write(color);
|
||||
mode3::bitmap_xy(x + 2, y + 2).write(color);
|
||||
}
|
||||
|
||||
loop {
|
||||
// wait until we're into the vblank period
|
||||
spin_until_vdraw();
|
||||
spin_until_vblank();
|
||||
|
||||
draw_square(1, 1, if keys.l() { GREEN } else { RED });
|
||||
draw_square(mode3::WIDTH - 4, 1, if keys.r() { GREEN } else { RED });
|
||||
//
|
||||
draw_square(mode3::WIDTH - 4, mode3::HEIGHT / 2, if keys.a() { GREEN } else { RED });
|
||||
draw_square(mode3::WIDTH - 4 - 20, mode3::HEIGHT / 2 + 20, if keys.b() { GREEN } else { RED });
|
||||
//
|
||||
draw_square(mode3::WIDTH / 2 + 10, mode3::HEIGHT - 5, if keys.start() { GREEN } else { RED });
|
||||
draw_square(mode3::WIDTH / 2 - 10, mode3::HEIGHT - 5, if keys.select() { GREEN } else { RED });
|
||||
//
|
||||
draw_square(30, mode3::HEIGHT / 2 - 20, if keys.up() { GREEN } else { RED });
|
||||
draw_square(30, mode3::HEIGHT / 2 + 20, if keys.down() { GREEN } else { RED });
|
||||
draw_square(10, mode3::HEIGHT / 2, if keys.left() { GREEN } else { RED });
|
||||
draw_square(50, mode3::HEIGHT / 2, if keys.right() { GREEN } else { RED });
|
||||
|
||||
// read our keys for next frame
|
||||
keys = KEYINPUT.read().into();
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused)]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
// This kills the emulation with a message if we're running inside an
|
||||
// emulator we support (mGBA or NO$GBA), or just crashes the game if we
|
||||
// aren't.
|
||||
//fatal!("{}", info);
|
||||
|
||||
loop {
|
||||
DISPCNT.read();
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vblank() {
|
||||
while VCOUNT.read() < 160 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vdraw() {
|
||||
while VCOUNT.read() >= 160 {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() -> ! {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
// Create default RNG
|
||||
let mut rng = RNG::default();
|
||||
|
||||
let mut px: usize = 0;
|
||||
let mut py: usize = 0;
|
||||
let mut color;
|
||||
|
||||
loop {
|
||||
// Generate color from RNG
|
||||
color = rng.next_color();
|
||||
// now we wait
|
||||
spin_until_vblank();
|
||||
// Draw pixels to screen
|
||||
mode3::bitmap_xy(px, py).write(color);
|
||||
mode3::bitmap_xy(px, py + 1).write(color);
|
||||
mode3::bitmap_xy(px + 1, py).write(color);
|
||||
mode3::bitmap_xy(px + 1, py + 1).write(color);
|
||||
// Increment x and y, wrap as needed
|
||||
px += 2;
|
||||
if px >= mode3::WIDTH {
|
||||
px = 0;
|
||||
py += 2;
|
||||
if py >= mode3::HEIGHT {
|
||||
py = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// now we wait again
|
||||
spin_until_vdraw();
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
// Note(Lokathor): this demo must be run in release mode or it'll just be way
|
||||
// too slow. When testing is running the screen fills from yellow to green. If
|
||||
// there's a panic you should see red. If all tests complete the screen turns
|
||||
// blue at the end.
|
||||
|
||||
use core::cmp;
|
||||
use gba::{fatal, prelude::*, warning};
|
||||
|
||||
fn set_screen_color(r: u8, g: u8, b: u8) {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
mode3::dma3_clear_to(Color::from_rgb(r, g, b));
|
||||
}
|
||||
fn set_screen_progress(cur: usize, max: usize) {
|
||||
let lines = cur * (mode3::WIDTH / max);
|
||||
let color = Color::from_rgb(0, 31, 0);
|
||||
for x in 0..lines {
|
||||
for y in 0..mode3::HEIGHT {
|
||||
mode3::bitmap_xy(x, y).write(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
set_screen_color(31, 0, 0);
|
||||
fatal!("{}", info)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Rng(u32);
|
||||
impl Rng {
|
||||
fn iter(&mut self) {
|
||||
self.0 = self.0.wrapping_mul(2891336453).wrapping_add(100001);
|
||||
}
|
||||
fn next_u8(&mut self) -> u8 {
|
||||
self.iter();
|
||||
(self.0 >> 22) as u8 ^ self.0 as u8
|
||||
}
|
||||
fn next_under(&mut self, under: u32) -> u32 {
|
||||
self.iter();
|
||||
let scale = 31 - under.leading_zeros();
|
||||
((self.0 >> scale) ^ self.0) % under
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_BLOCK_SIZE: usize = 4 * 1024;
|
||||
|
||||
fn check_status<T>(r: Result<T, Error>) -> T {
|
||||
match r {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Error encountered: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_timers() {
|
||||
TIMER0_RELOAD.write(0);
|
||||
TIMER1_RELOAD.write(0);
|
||||
|
||||
let ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true);
|
||||
TIMER0_CONTROL.write(ctl);
|
||||
let ctl = TimerControl::new().with_chained_counting(true).with_enabled(true);
|
||||
TIMER1_CONTROL.write(ctl);
|
||||
}
|
||||
|
||||
// I'm fully aware how slow this is. But this is just example code, so, eh.
|
||||
fn get_timer_secs() -> f32 {
|
||||
let raw_timer = (TIMER1_COUNTER.read() as u32) << 16 | TIMER0_COUNTER.read() as u32;
|
||||
(raw_timer as f32 * 1024.0) / ((1 << 24) as f32)
|
||||
}
|
||||
macro_rules! output {
|
||||
($($args:tt)*) => {
|
||||
// we use warn so it shows by default on mGBA, nothing more.
|
||||
warning!("{:7.3}\t{}", get_timer_secs(), format_args!($($args)*))
|
||||
};
|
||||
}
|
||||
|
||||
fn do_test(seed: Rng, offset: usize, len: usize, block_size: usize) -> Result<(), Error> {
|
||||
let access = SaveAccess::new()?;
|
||||
let mut buffer = [0; MAX_BLOCK_SIZE];
|
||||
|
||||
output!(" - Clearing media...");
|
||||
access.prepare_write(offset..offset + len)?;
|
||||
|
||||
output!(" - Writing media...");
|
||||
let mut rng = seed.clone();
|
||||
let mut current = offset;
|
||||
let end = offset + len;
|
||||
while current != end {
|
||||
let cur_len = cmp::min(end - current, block_size);
|
||||
for i in 0..cur_len {
|
||||
buffer[i] = rng.next_u8();
|
||||
}
|
||||
access.write(current, &buffer[..cur_len])?;
|
||||
current += cur_len;
|
||||
}
|
||||
|
||||
output!(" - Validating media...");
|
||||
rng = seed.clone();
|
||||
current = offset;
|
||||
while current != end {
|
||||
let cur_len = cmp::min(end - current, block_size);
|
||||
access.read(current, &mut buffer[..cur_len])?;
|
||||
for i in 0..cur_len {
|
||||
let cur_byte = rng.next_u8();
|
||||
assert!(
|
||||
buffer[i] == cur_byte,
|
||||
"Read does not match earlier write: {} != {} @ 0x{:05x}",
|
||||
buffer[i],
|
||||
cur_byte,
|
||||
current + i,
|
||||
);
|
||||
}
|
||||
current += cur_len;
|
||||
}
|
||||
|
||||
output!(" - Done!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn main() -> ! {
|
||||
// set a pattern to show that the ROM is working at all.
|
||||
set_screen_color(31, 31, 0);
|
||||
|
||||
// sets up the timers so we can print time with our outputs.
|
||||
setup_timers();
|
||||
|
||||
// set the save type
|
||||
use_flash_128k();
|
||||
set_timer_for_timeout(3);
|
||||
|
||||
// check some metainfo on the save type
|
||||
let access = check_status(SaveAccess::new());
|
||||
output!("Media info: {:#?}", access.media_info());
|
||||
output!("Media size: {} bytes", access.len());
|
||||
output!("");
|
||||
|
||||
// actually test the save implementation
|
||||
if access.len() >= (1 << 12) {
|
||||
output!("[ Full write, 4KiB blocks ]");
|
||||
check_status(do_test(Rng(2000), 0, access.len(), 4 * 1024));
|
||||
set_screen_progress(1, 10);
|
||||
}
|
||||
|
||||
output!("[ Full write, 0.5KiB blocks ]");
|
||||
check_status(do_test(Rng(1000), 0, access.len(), 512));
|
||||
set_screen_progress(2, 10);
|
||||
|
||||
// test with random segments now.
|
||||
let mut rng = Rng(12345);
|
||||
for i in 0..8 {
|
||||
let rand_length = rng.next_under((access.len() >> 1) as u32) as usize + 50;
|
||||
let rand_offset = rng.next_under(access.len() as u32 - rand_length as u32) as usize;
|
||||
let block_size = cmp::min(rand_length >> 2, MAX_BLOCK_SIZE - 100);
|
||||
let block_size = rng.next_under(block_size as u32) as usize + 50;
|
||||
|
||||
output!(
|
||||
"[ Partial, offset = 0x{:06x}, len = {}, bs = {}]",
|
||||
rand_offset,
|
||||
rand_length,
|
||||
block_size,
|
||||
);
|
||||
check_status(do_test(Rng(i * 10000), rand_offset, rand_length, block_size));
|
||||
set_screen_progress(3 + i as usize, 10);
|
||||
}
|
||||
|
||||
// show a pattern so we know it worked
|
||||
set_screen_color(0, 29, 29);
|
||||
output!("All tests complete!");
|
||||
loop {}
|
||||
}
|
68
init.sh
68
init.sh
|
@ -1,68 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
APP_NAME=$1
|
||||
if [ -z "$APP_NAME"]; then APP_NAME="rust-console-hello"; fi
|
||||
|
||||
TARGET="thumbv4-none-agb"
|
||||
CRT_LOCAL="crt0.s"
|
||||
|
||||
echo "Initializing rust-console gba at: $1"
|
||||
|
||||
# initialize cargo
|
||||
cargo init $APP_NAME --bin
|
||||
|
||||
# remove old
|
||||
rm -rf $APP_NAME/thumbv4-none-agb.json*
|
||||
rm -rf $APP_NAME/$CRT_LOCAL*
|
||||
rm -rf $APP_NAME/linker.ld*
|
||||
rm -rf $APP_NAME/Makefile*
|
||||
rm -rf $APP_NAME/README.md*
|
||||
|
||||
# download dependencies
|
||||
wget https://raw.githubusercontent.com/rust-console/gba/master/thumbv4-none-agb.json
|
||||
mv $TARGET.json $APP_NAME/$TARGET.json
|
||||
|
||||
wget https://raw.githubusercontent.com/rust-console/gba/master/crt0.s
|
||||
mv crt0.s $APP_NAME/$CRT_LOCAL
|
||||
|
||||
wget https://raw.githubusercontent.com/rust-console/gba/master/linker.ld;
|
||||
mv linker.ld $APP_NAME/linker.ld
|
||||
|
||||
# substitute cargo main file with a new basic one
|
||||
rm -rf $APP_NAME/src/main.rs
|
||||
wget https://raw.githubusercontent.com/rust-console/gba/master/examples/hello_world.rs
|
||||
mv hello_world.rs $APP_NAME/src/main.rs
|
||||
|
||||
# precreate target directory for crt0.o file
|
||||
mkdir $APP_NAME/target
|
||||
|
||||
# setup make file
|
||||
echo -e "CRT_FILE=$(echo $CRT_LOCAL)
|
||||
CRT_OUTPUT=target/crt0.o
|
||||
PROJECT_NAME=$(echo $APP_NAME)
|
||||
TARGET=$(echo $TARGET)
|
||||
THUMB_TARGET=$(echo $TARGET).json
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
\tarm-none-eabi-as \$(CRT_FILE) -o \$(CRT_OUTPUT)
|
||||
\tcargo xbuild --target \$(THUMB_TARGET)
|
||||
|
||||
build-prod:
|
||||
\tarm-none-eabi-as \$(CRT_FILE) -o \$(CRT_OUTPUT)
|
||||
\tcargo xbuild --target \$(THUMB_TARGET) --release
|
||||
\tarm-none-eabi-objcopy -O binary target/\$(TARGET)/release/\$(PROJECT_NAME) target/\$(PROJECT_NAME).gba
|
||||
\tgbafix target/\$(PROJECT_NAME).gba
|
||||
" > $APP_NAME/Makefile
|
||||
|
||||
# setup the readme file
|
||||
echo -e "Rust console project
|
||||
----
|
||||
|
||||
## Development
|
||||
|
||||
\`\`\`sh
|
||||
make
|
||||
\`\`\`
|
||||
" > $APP_NAME/README.md
|
94
link_scripts/mono_boot.ld
Normal file
94
link_scripts/mono_boot.ld
Normal file
|
@ -0,0 +1,94 @@
|
|||
ENTRY(__start)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K
|
||||
rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.text : {
|
||||
/* be sure that the ROM header is the very first */
|
||||
*(.text.gba_rom_header);
|
||||
*(.text .text.*);
|
||||
. = ALIGN(4);
|
||||
} >rom = 0x00
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*);
|
||||
. = ALIGN(4);
|
||||
} >rom = 0x00
|
||||
|
||||
. = ALIGN(4);
|
||||
__iwram_position_in_rom = .;
|
||||
.data : {
|
||||
__iwram_start = ABSOLUTE(.);
|
||||
|
||||
*(.data .data.*);
|
||||
*(.iwram .iwram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__iwram_end = ABSOLUTE(.);
|
||||
} >iwram AT>rom = 0x00
|
||||
|
||||
. = ALIGN(4);
|
||||
__ewram_position_in_rom = .;
|
||||
.ewram : {
|
||||
__ewram_start = ABSOLUTE(.);
|
||||
|
||||
*(.ewram .ewram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__ewram_end = ABSOLUTE(.);
|
||||
} >ewram AT>rom = 0x00
|
||||
|
||||
. = ALIGN(4);
|
||||
__bss_position_in_rom = .;
|
||||
.bss : {
|
||||
__bss_start = ABSOLUTE(.);
|
||||
|
||||
*(.bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__bss_end = ABSOLUTE(.);
|
||||
} >iwram
|
||||
|
||||
__iwram_word_copy_count = (__iwram_end - __iwram_start) / 4;
|
||||
__ewram_word_copy_count = (__ewram_end - __ewram_start) / 4;
|
||||
__bss_word_clear_count = (__bss_end - __bss_start) / 4;
|
||||
|
||||
/* debugging sections */
|
||||
/* Stabs */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
|
||||
/* discard anything not already mentioned */
|
||||
/DISCARD/ : { *(*) }
|
||||
}
|
79
linker.ld
79
linker.ld
|
@ -1,79 +0,0 @@
|
|||
ENTRY(__start)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K
|
||||
rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.text : {
|
||||
KEEP(rsrt0.o(.text));
|
||||
*(.text .text.*);
|
||||
. = ALIGN(4);
|
||||
} >rom = 0xff
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*);
|
||||
. = ALIGN(4);
|
||||
} >rom = 0xff
|
||||
|
||||
__iwram_lma = .;
|
||||
.iwram : {
|
||||
__iwram_start = ABSOLUTE(.);
|
||||
|
||||
__data_start = ABSOLUTE(.);
|
||||
*(.data .data.*);
|
||||
. = ALIGN(4);
|
||||
__data_end = ABSOLUTE(.);
|
||||
|
||||
__text_iwram_start = ABSOLUTE(.);
|
||||
*(.text_iwram .text_iwram.*);
|
||||
*(.iwram .iwram.*);
|
||||
. = ALIGN(4);
|
||||
__text_iwram_end = ABSOLUTE(.);
|
||||
|
||||
__iwram_end = ABSOLUTE(.);
|
||||
} >iwram AT>rom = 0xff
|
||||
|
||||
.bss : {
|
||||
*(.bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
__bss_end = ABSOLUTE(.);
|
||||
} >iwram
|
||||
|
||||
/* debugging sections */
|
||||
/* Stabs */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
|
||||
/* discard anything not already mentioned */
|
||||
/DISCARD/ : { *(*) }
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "" ]; then
|
||||
echo "Usage: $0 [example to build]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo build --example $1 --release || exit 1
|
||||
arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/release/examples/$1 target/$1.gba || exit 1
|
||||
gbafix target/$1.gba || exit 1
|
||||
echo "ROM built successfully!"
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
18
rustfmt.toml
18
rustfmt.toml
|
@ -1,9 +1,13 @@
|
|||
color = "Never"
|
||||
error_on_line_overflow = false
|
||||
fn_args_density = "Compressed"
|
||||
merge_imports = true
|
||||
reorder_imports = true
|
||||
use_try_shorthand = true
|
||||
# Stable (1.0)
|
||||
edition = "2018"
|
||||
fn_args_layout = "Compressed"
|
||||
max_width = 80
|
||||
tab_spaces = 2
|
||||
max_width = 100
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
use_small_heuristics = "Max"
|
||||
|
||||
# Unstable
|
||||
format_code_in_doc_comments = true
|
||||
imports_granularity = "Crate"
|
||||
wrap_comments = true
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
//! This module has constants for various tile sheets in compressed form.
|
||||
//!
|
||||
//! Depending on the tile sheet, the optimal compression varies. The docs of
|
||||
//! each constant explain how to decompress the data correctly.
|
||||
|
||||
mod cga_8x8_thick;
|
||||
pub use cga_8x8_thick::*;
|
|
@ -1,98 +0,0 @@
|
|||
/// The CGA [Code Page 437][cp437] type face, with thick lines.
|
||||
///
|
||||
/// There's 256 tiles, packed down to 1bpp. To decompress this easily you can
|
||||
/// call [`BitUnPack`] as follows:
|
||||
/// ```no_run
|
||||
/// # use gba::prelude::*;
|
||||
/// # use gba::art::CGA_8X8_THICK;
|
||||
/// # use core::convert::TryFrom;
|
||||
/// # use core::mem::size_of_val;
|
||||
/// let info = UnpackInfo {
|
||||
/// source_data_len_bytes: size_of_val(&CGA_8X8_THICK) as u16,
|
||||
/// source_unit_bit_width: 1,
|
||||
/// // this assumes we want to unpack to 4bpp tiles
|
||||
/// destination_unit_bit_width: 4,
|
||||
/// data_offset: 0,
|
||||
/// };
|
||||
/// // our example unpacks directly to the start of VRAM.
|
||||
/// unsafe { BitUnPack(CGA_8X8_THICK.as_ptr().cast::<u8>(), 0x0600_0000 as *mut u32, &info) };
|
||||
/// ```
|
||||
///
|
||||
/// I am not a lawyer, but type faces are not protected by copyright in the USA.
|
||||
/// The copyright status of font faces [varies by country][wp]. If it matters,
|
||||
/// CGA was first released in 1981.
|
||||
///
|
||||
/// [cp437]: https://en.wikipedia.org/wiki/Code_page_437
|
||||
///
|
||||
/// [wp]:
|
||||
/// https://en.wikipedia.org/wiki/Intellectual_property_protection_of_typefaces
|
||||
pub const CGA_8X8_THICK: [u32; 512] = [
|
||||
// Note(Lokathor): I generated this by (1) converting the type face file from
|
||||
// an RGB PNG to Indexed Color PNG using GIMP, (2) running `grit
|
||||
// CGA8x8thick-indexed.png -gB1` to output an assembly file full of the
|
||||
// compressed data `.word` entries, (3) copying all those words into Rust.
|
||||
0x00000000, 0x00000000, 0x81A5817E, 0x7E8199BD, 0xFFDBFF7E, 0x7EFFE7C3, 0x7F7F7F36, 0x00081C3E,
|
||||
0x7F3E1C08, 0x00081C3E, 0x7F1C3E1C, 0x1C086B7F, 0x3E1C0808, 0x1C083E7F, 0x3C180000, 0x0000183C,
|
||||
0xC3E7FFFF, 0xFFFFE7C3, 0x42663C00, 0x003C6642, 0xBD99C3FF, 0xFFC399BD, 0xBEF0E0F0, 0x1E333333,
|
||||
0x6666663C, 0x187E183C, 0x0CFCCCFC, 0x070F0E0C, 0xC6FEC6FE, 0x0367E6C6, 0xE73CDB18, 0x18DB3CE7,
|
||||
0x7F1F0701, 0x0001071F, 0x7F7C7040, 0x0040707C, 0x187E3C18, 0x183C7E18, 0x66666666, 0x00660066,
|
||||
0xDEDBDBFE, 0x00D8D8D8, 0x361CC67C, 0x1E331C36, 0x00000000, 0x007E7E7E, 0x187E3C18, 0xFF183C7E,
|
||||
0x187E3C18, 0x00181818, 0x18181818, 0x00183C7E, 0x7F301800, 0x00001830, 0x7F060C00, 0x00000C06,
|
||||
0x03030000, 0x00007F03, 0xFF662400, 0x00002466, 0x7E3C1800, 0x0000FFFF, 0x7EFFFF00, 0x0000183C,
|
||||
0x00000000, 0x00000000, 0x0C1E1E0C, 0x000C000C, 0x00363636, 0x00000000, 0x367F3636, 0x0036367F,
|
||||
0x1E033E0C, 0x000C1F30, 0x18336300, 0x0063660C, 0x6E1C361C, 0x006E333B, 0x00030606, 0x00000000,
|
||||
0x06060C18, 0x00180C06, 0x18180C06, 0x00060C18, 0xFF3C6600, 0x0000663C, 0x3F0C0C00, 0x00000C0C,
|
||||
0x00000000, 0x060C0C00, 0x3F000000, 0x00000000, 0x00000000, 0x000C0C00, 0x0C183060, 0x00010306,
|
||||
0x7B73633E, 0x003E676F, 0x0C0C0E0C, 0x003F0C0C, 0x1C30331E, 0x003F3306, 0x1C30331E, 0x001E3330,
|
||||
0x33363C38, 0x0078307F, 0x301F033F, 0x001E3330, 0x1F03061C, 0x001E3333, 0x1830333F, 0x000C0C0C,
|
||||
0x1E33331E, 0x001E3333, 0x3E33331E, 0x000E1830, 0x000C0C00, 0x000C0C00, 0x000C0C00, 0x060C0C00,
|
||||
0x03060C18, 0x00180C06, 0x003F0000, 0x00003F00, 0x30180C06, 0x00060C18, 0x1830331E, 0x000C000C,
|
||||
0x7B7B633E, 0x001E037B, 0x33331E0C, 0x0033333F, 0x3E66663F, 0x003F6666, 0x0303663C, 0x003C6603,
|
||||
0x6666361F, 0x001F3666, 0x1E16467F, 0x007F4616, 0x1E16467F, 0x000F0616, 0x0303663C, 0x007C6673,
|
||||
0x3F333333, 0x00333333, 0x0C0C0C1E, 0x001E0C0C, 0x30303078, 0x001E3333, 0x1E366667, 0x00676636,
|
||||
0x0606060F, 0x007F6646, 0x7F7F7763, 0x0063636B, 0x7B6F6763, 0x00636373, 0x6363361C, 0x001C3663,
|
||||
0x3E66663F, 0x000F0606, 0x3333331E, 0x00381E3B, 0x3E66663F, 0x00676636, 0x0C06331E, 0x001E3318,
|
||||
0x0C0C2D3F, 0x001E0C0C, 0x33333333, 0x003F3333, 0x33333333, 0x000C1E33, 0x6B636363, 0x0063777F,
|
||||
0x1C366363, 0x0063361C, 0x1E333333, 0x001E0C0C, 0x1831637F, 0x007F664C, 0x0606061E, 0x001E0606,
|
||||
0x180C0603, 0x00406030, 0x1818181E, 0x001E1818, 0x63361C08, 0x00000000, 0x00000000, 0xFF000000,
|
||||
0x00180C0C, 0x00000000, 0x301E0000, 0x006E333E, 0x3E060607, 0x003B6666, 0x331E0000, 0x001E3303,
|
||||
0x3E303038, 0x006E3333, 0x331E0000, 0x001E033F, 0x0F06361C, 0x000F0606, 0x336E0000, 0x1F303E33,
|
||||
0x6E360607, 0x00676666, 0x0C0E000C, 0x001E0C0C, 0x30300030, 0x1E333330, 0x36660607, 0x0067361E,
|
||||
0x0C0C0C0E, 0x001E0C0C, 0x7F330000, 0x00636B7F, 0x331F0000, 0x00333333, 0x331E0000, 0x001E3333,
|
||||
0x663B0000, 0x0F063E66, 0x336E0000, 0x78303E33, 0x6E3B0000, 0x000F0666, 0x033E0000, 0x001F301E,
|
||||
0x0C3E0C08, 0x00182C0C, 0x33330000, 0x006E3333, 0x33330000, 0x000C1E33, 0x6B630000, 0x00367F7F,
|
||||
0x36630000, 0x0063361C, 0x33330000, 0x1F303E33, 0x193F0000, 0x003F260C, 0x070C0C38, 0x00380C0C,
|
||||
0x00181818, 0x00181818, 0x380C0C07, 0x00070C0C, 0x00003B6E, 0x00000000, 0x361C0800, 0x007F6363,
|
||||
0x3303331E, 0x1E30181E, 0x33003300, 0x007E3333, 0x331E0038, 0x001E033F, 0x603CC37E, 0x00FC667C,
|
||||
0x301E0033, 0x007E333E, 0x301E0007, 0x007E333E, 0x301E0C0C, 0x007E333E, 0x031E0000, 0x1C301E03,
|
||||
0x663CC37E, 0x003C067E, 0x331E0033, 0x001E033F, 0x331E0007, 0x001E033F, 0x0C0E0033, 0x001E0C0C,
|
||||
0x181C633E, 0x003C1818, 0x0C0E0007, 0x001E0C0C, 0x63361C63, 0x0063637F, 0x1E000C0C, 0x00333F33,
|
||||
0x063F0038, 0x003F061E, 0x30FE0000, 0x00FE33FE, 0x7F33367C, 0x00733333, 0x1E00331E, 0x001E3333,
|
||||
0x1E003300, 0x001E3333, 0x1E000700, 0x001E3333, 0x3300331E, 0x007E3333, 0x33000700, 0x007E3333,
|
||||
0x33003300, 0x1F303E33, 0x663C18C3, 0x00183C66, 0x33330033, 0x001E3333, 0x037E1818, 0x18187E03,
|
||||
0x0F26361C, 0x003F6706, 0x3F1E3333, 0x0C0C3F0C, 0x5F33331F, 0xE363F363, 0x3C18D870, 0x0E1B1818,
|
||||
0x301E0038, 0x007E333E, 0x0C0E001C, 0x001E0C0C, 0x1E003800, 0x001E3333, 0x33003800, 0x007E3333,
|
||||
0x1F001F00, 0x00333333, 0x3733003F, 0x00333B3F, 0x7C36363C, 0x00007E00, 0x1C36361C, 0x00003E00,
|
||||
0x060C000C, 0x001E3303, 0x3F000000, 0x00000303, 0x3F000000, 0x00003030, 0x7B3363C3, 0xF03366CC,
|
||||
0xDB3363C3, 0xC0F3F6EC, 0x18001818, 0x00181818, 0x3366CC00, 0x0000CC66, 0xCC663300, 0x00003366,
|
||||
0x11441144, 0x11441144, 0x55AA55AA, 0x55AA55AA, 0x77DBEEDB, 0x77DBEEDB, 0x18181818, 0x18181818,
|
||||
0x18181818, 0x1818181F, 0x181F1818, 0x1818181F, 0x6C6C6C6C, 0x6C6C6C6F, 0x00000000, 0x6C6C6C7F,
|
||||
0x181F0000, 0x1818181F, 0x606F6C6C, 0x6C6C6C6F, 0x6C6C6C6C, 0x6C6C6C6C, 0x607F0000, 0x6C6C6C6F,
|
||||
0x606F6C6C, 0x0000007F, 0x6C6C6C6C, 0x0000007F, 0x181F1818, 0x0000001F, 0x00000000, 0x1818181F,
|
||||
0x18181818, 0x000000F8, 0x18181818, 0x000000FF, 0x00000000, 0x181818FF, 0x18181818, 0x181818F8,
|
||||
0x00000000, 0x000000FF, 0x18181818, 0x181818FF, 0x18F81818, 0x181818F8, 0x6C6C6C6C, 0x6C6C6CEC,
|
||||
0x0CEC6C6C, 0x000000FC, 0x0CFC0000, 0x6C6C6CEC, 0x00EF6C6C, 0x000000FF, 0x00FF0000, 0x6C6C6CEF,
|
||||
0x0CEC6C6C, 0x6C6C6CEC, 0x00FF0000, 0x000000FF, 0x00EF6C6C, 0x6C6C6CEF, 0x00FF1818, 0x000000FF,
|
||||
0x6C6C6C6C, 0x000000FF, 0x00FF0000, 0x181818FF, 0x00000000, 0x6C6C6CFF, 0x6C6C6C6C, 0x000000FC,
|
||||
0x18F81818, 0x000000F8, 0x18F80000, 0x181818F8, 0x00000000, 0x6C6C6CFC, 0x6C6C6C6C, 0x6C6C6CFF,
|
||||
0x18FF1818, 0x181818FF, 0x18181818, 0x0000001F, 0x00000000, 0x181818F8, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x00000000, 0xFFFFFFFF, 0x0F0F0F0F, 0x0F0F0F0F, 0xF0F0F0F0, 0xF0F0F0F0, 0xFFFFFFFF, 0x00000000,
|
||||
0x3B6E0000, 0x006E3B13, 0x1F331E00, 0x03031F33, 0x03333F00, 0x00030303, 0x36367F00, 0x00363636,
|
||||
0x0C06333F, 0x003F3306, 0x1B7E0000, 0x000E1B1B, 0x66666600, 0x03063E66, 0x183B6E00, 0x00181818,
|
||||
0x331E0C3F, 0x3F0C1E33, 0x7F63361C, 0x001C3663, 0x6363361C, 0x00773636, 0x3E180C38, 0x001E3333,
|
||||
0xDB7E0000, 0x00007EDB, 0xDB7E3060, 0x03067EDB, 0x1F03061C, 0x001C0603, 0x3333331E, 0x00333333,
|
||||
0x3F003F00, 0x00003F00, 0x0C3F0C0C, 0x003F000C, 0x0C180C06, 0x003F0006, 0x0C060C18, 0x003F0018,
|
||||
0x18D8D870, 0x18181818, 0x18181818, 0x0E1B1B18, 0x3F000C0C, 0x000C0C00, 0x003B6E00, 0x00003B6E,
|
||||
0x1C36361C, 0x00000000, 0x18000000, 0x00000018, 0x00000000, 0x00000018, 0x303030F0, 0x383C3637,
|
||||
0x3636361E, 0x00000036, 0x060C180E, 0x0000001E, 0x3C3C0000, 0x00003C3C, 0x00000000, 0x00000000,
|
||||
];
|
919
src/asm_runtime.rs
Normal file
919
src/asm_runtime.rs
Normal file
|
@ -0,0 +1,919 @@
|
|||
use core::ffi::c_void;
|
||||
|
||||
use crate::{
|
||||
dma::DmaControl,
|
||||
gba_cell::GbaCell,
|
||||
interrupts::IrqFn,
|
||||
mmio::{DMA3_SRC, IME},
|
||||
};
|
||||
|
||||
/// Builds an assembly string that puts the contained code in the section
|
||||
/// specified.
|
||||
///
|
||||
/// ```txt
|
||||
/// put_code_in_section!( ".example.section", {
|
||||
/// "lines"
|
||||
/// "go"
|
||||
/// "here"
|
||||
/// });
|
||||
/// ```
|
||||
macro_rules! put_code_in_section {
|
||||
($section_name:expr, {
|
||||
$($asm_line:expr),+ $(,)?
|
||||
}) => {
|
||||
concat!(
|
||||
concat!(".section ", $section_name, "\n"),
|
||||
$( concat!($asm_line, "\n") ),+ ,
|
||||
concat!(".previous\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an assembly string wrapped in `.code 32` and `.code 16` as necessary
|
||||
///
|
||||
/// ```txt
|
||||
/// emit_a32_code!{
|
||||
/// "lines"
|
||||
/// "go"
|
||||
/// "here"
|
||||
/// };
|
||||
/// ```
|
||||
#[cfg(target_feature = "thumb-mode")]
|
||||
macro_rules! emit_a32_code {
|
||||
($($asm_line:expr),+ $(,)?) => {
|
||||
concat!(
|
||||
concat!(".code 32\n"),
|
||||
$( concat!($asm_line, "\n") ),+ ,
|
||||
concat!(".code 16\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an assembly string wrapped in `.code 32` and `.code 16` as necessary
|
||||
///
|
||||
/// ```txt
|
||||
/// emit_a32_code!{
|
||||
/// "lines"
|
||||
/// "go"
|
||||
/// "here"
|
||||
/// };
|
||||
/// ```
|
||||
#[cfg(not(target_feature = "thumb-mode"))]
|
||||
macro_rules! emit_a32_code {
|
||||
($($asm_line:expr),+ $(,)?) => {
|
||||
concat!(
|
||||
$( concat!($asm_line, "\n") ),+ ,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an assembly string that pushes some regs, does the body, then pops
|
||||
/// the regs.
|
||||
///
|
||||
/// The `reglist` expression should include the appropriate level of braces for
|
||||
/// the enclosing assembly block (two for normal asm, or one for raw asm).
|
||||
///
|
||||
/// ```txt
|
||||
/// with_pushed_registers!( "reglist", {
|
||||
/// "lines"
|
||||
/// "go"
|
||||
/// "here"
|
||||
/// });
|
||||
/// ```
|
||||
macro_rules! with_pushed_registers {
|
||||
($reglist:expr, {
|
||||
$($asm_line:expr),* $(,)?
|
||||
}) => {
|
||||
concat!(
|
||||
concat!("push ", $reglist, "\n"),
|
||||
$( concat!($asm_line, "\n") ),* ,
|
||||
concat!("pop ", $reglist, "\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads SPSR into the register named, does the block, and writes the same
|
||||
/// register back to SPSR.
|
||||
macro_rules! with_spsr_held_in {
|
||||
($reg:literal {
|
||||
$($asm_line:expr),* $(,)?
|
||||
}) => {
|
||||
concat!(
|
||||
concat!("mrs ", $reg, ", SPSR\n"),
|
||||
$( concat!($asm_line, "\n") ),* ,
|
||||
concat!("msr SPSR, ", $reg, "\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets `lr` to just after the `bx`, then uses `bx` with the given register.
|
||||
///
|
||||
/// This generates a label, so pick a `label_id` that won't interfere with any
|
||||
/// nearby code.
|
||||
macro_rules! adr_lr_then_bx_to {
|
||||
(reg=$reg_name:expr, label_id=$label:expr) => {
|
||||
concat!(
|
||||
concat!("adr lr, ", $label, "f\n"),
|
||||
concat!("bx ", $reg_name, "\n"),
|
||||
concat!($label, ":\n"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Expands to the asm line to set the control bits of CPSR.
|
||||
///
|
||||
/// * Can only be used in `a32`
|
||||
/// * Only sets the control bits, all other bits (eg: flag bits) are unchanged.
|
||||
///
|
||||
/// Currently, not all possible patterns are covered by this macro, just the
|
||||
/// patterns needed by this runtime when it was written. In general, any of the
|
||||
/// five CPU modes can be combined with irq and fiq masking each being either
|
||||
/// off or on. If a desired combination is missing just add it.
|
||||
macro_rules! set_cpu_control {
|
||||
// CPSR low bits are: `I F T MMMMM`, and T must always be left as 0.
|
||||
// * 0b10011: Supervisor (SVC)
|
||||
// * 0b11111: System (SYS)
|
||||
(System, irq_masked: false, fiq_masked: false) => {
|
||||
"msr CPSR_c, #0b00011111\n"
|
||||
};
|
||||
(Supervisor, irq_masked: true, fiq_masked: false) => {
|
||||
"msr CPSR_c, #0b10010010\n"
|
||||
};
|
||||
}
|
||||
|
||||
/// Performs the appropriate test, then either runs the block or jumps past it,
|
||||
/// depending on the test result.
|
||||
///
|
||||
/// Currently supports:
|
||||
/// * `$reg == $op2`
|
||||
/// * `$reg != $op2`
|
||||
macro_rules! when {
|
||||
($reg:literal == $op2:literal [label_id=$label:literal] {
|
||||
$($asm_line:expr),* $(,)?
|
||||
}) => {
|
||||
concat!(
|
||||
concat!("cmp ", $reg, ", ", $op2, "\n"),
|
||||
concat!("bne ", $label, "f\n"),
|
||||
$( concat!($asm_line, "\n") ),* ,
|
||||
concat!($label, ":\n"),
|
||||
)
|
||||
};
|
||||
($reg:literal != $op2:literal [label_id=$label:literal] {
|
||||
$($asm_line:expr),* $(,)?
|
||||
}) => {
|
||||
concat!(
|
||||
concat!("cmp ", $reg, ", ", $op2, "\n"),
|
||||
concat!("beq ", $label, "f\n"),
|
||||
$( concat!($asm_line, "\n") ),* ,
|
||||
concat!($label, ":\n"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// The function pointer that the assembly runtime calls when an interrupt
|
||||
/// occurs.
|
||||
pub static RUST_IRQ_HANDLER: GbaCell<Option<IrqFn>> = GbaCell::new(None);
|
||||
|
||||
const DMA_32_BIT_MEMCPY: DmaControl =
|
||||
DmaControl::new().with_transfer_32bit(true).with_enabled(true);
|
||||
|
||||
const DMA3_OFFSET: usize = DMA3_SRC.as_usize() - 0x0400_0000;
|
||||
const IME_OFFSET: usize = IME.as_usize() - 0x0400_0000;
|
||||
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".text.gba_rom_header"]
|
||||
unsafe extern "C" fn __start() -> ! {
|
||||
core::arch::asm!(
|
||||
"b 1f",
|
||||
".space 0xE0",
|
||||
"1:", /* post header */
|
||||
"mov r12, #{mmio_base}",
|
||||
"add r0, r12, #{waitcnt_offset}",
|
||||
"ldr r1, ={waitcnt_setting}",
|
||||
"strh r1, [r0]",
|
||||
|
||||
/* iwram copy */
|
||||
"ldr r4, =__iwram_word_copy_count",
|
||||
when!("r4" != "#0" [label_id=1] {
|
||||
"add r3, r12, #{dma3_offset}",
|
||||
"mov r5, #{dma3_setting}",
|
||||
"ldr r0, =__iwram_start",
|
||||
"ldr r2, =__iwram_position_in_rom",
|
||||
"str r2, [r3]", /* source */
|
||||
"str r0, [r3, #4]", /* destination */
|
||||
"strh r4, [r3, #8]", /* word count */
|
||||
"strh r5, [r3, #10]", /* set control bits */
|
||||
}),
|
||||
|
||||
/* ewram copy */
|
||||
"ldr r4, =__ewram_word_copy_count",
|
||||
when!("r4" != "#0" [label_id=1] {
|
||||
"add r3, r12, #{dma3_offset}",
|
||||
"mov r5, #{dma3_setting}",
|
||||
"ldr r0, =__ewram_start",
|
||||
"ldr r2, =__ewram_position_in_rom",
|
||||
"str r2, [r3]", /* source */
|
||||
"str r0, [r3, #4]", /* destination */
|
||||
"strh r4, [r3, #8]", /* word count */
|
||||
"strh r5, [r3, #10]", /* set control bits */
|
||||
}),
|
||||
|
||||
/* bss zero */
|
||||
"ldr r4, =__bss_word_clear_count",
|
||||
when!("r4" != "#0" [label_id=1] {
|
||||
"ldr r0, =__bss_start",
|
||||
"mov r2, #0",
|
||||
"2:",
|
||||
"str r2, [r0], #4",
|
||||
"subs r4, r4, #1",
|
||||
"bne 2b",
|
||||
}),
|
||||
|
||||
/* assign the runtime irq handler */
|
||||
"ldr r1, ={runtime_irq_handler}",
|
||||
"str r1, [r12, #-4]",
|
||||
|
||||
/* call to rust main */
|
||||
"ldr r0, =main",
|
||||
"bx r0",
|
||||
// main shouldn't return, but if it does just SoftReset
|
||||
"swi #0",
|
||||
mmio_base = const 0x0400_0000,
|
||||
waitcnt_offset = const 0x204,
|
||||
waitcnt_setting = const 0x4317 /*sram8,r0:3.1,r1:4.2,r2:8.2,no_phi,prefetch*/,
|
||||
dma3_offset = const DMA3_OFFSET,
|
||||
dma3_setting = const DMA_32_BIT_MEMCPY.to_u16(),
|
||||
runtime_irq_handler = sym runtime_irq_handler,
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.runtime.irq.handler"]
|
||||
unsafe extern "C" fn runtime_irq_handler() {
|
||||
// On Entry: r0 = 0x0400_0000 (mmio_base)
|
||||
core::arch::asm!(
|
||||
/* swap IME off, user can turn it back on if they want */
|
||||
"add r12, r0, #{ime_offset}",
|
||||
"mov r3, #0",
|
||||
"swp r3, r3, [r12]",
|
||||
|
||||
/* Read/Update IE and IF */
|
||||
"ldr r0, [r12, #-8]",
|
||||
"and r0, r0, r0, LSR #16",
|
||||
"strh r0, [r12, #-6]",
|
||||
|
||||
/* Read/Update BIOS_IF */
|
||||
"sub r2, r12, #(0x208+8)",
|
||||
"ldrh r1, [r2]",
|
||||
"orr r1, r1, r0",
|
||||
"strh r1, [r2]",
|
||||
|
||||
/* Call the Rust fn pointer (if set), using System mode */
|
||||
"ldr r1, ={RUST_IRQ_HANDLER}",
|
||||
"ldr r1, [r1]",
|
||||
when!("r1" != "#0" [label_id=9] {
|
||||
with_spsr_held_in!("r2" {
|
||||
set_cpu_control!(System, irq_masked: false, fiq_masked: false),
|
||||
|
||||
// Note(Lokathor): We are *SKIPPING* the part where we ensure that the
|
||||
// System stack pointer is aligned to 8 during the call to the rust
|
||||
// function. This is *technically* against the AAPCS ABI, but the GBA's
|
||||
// ARMv4T CPU does not even support any instructions that require an
|
||||
// alignment of 8. By not bothering to align the stack, we save about 5
|
||||
// cycles total. Which is neat, but if this were on the DS (which has an
|
||||
// ARMv5TE CPU) you'd want to ensure the aligned stack.
|
||||
|
||||
with_pushed_registers!("{{r2, r3, r12, lr}}", {
|
||||
adr_lr_then_bx_to!(reg="r1", label_id=1)
|
||||
}),
|
||||
|
||||
set_cpu_control!(Supervisor, irq_masked: true, fiq_masked: false),
|
||||
}),
|
||||
}),
|
||||
|
||||
/* Restore initial IME setting and return */
|
||||
"swp r3, r3, [r12]",
|
||||
"bx lr",
|
||||
ime_offset = const IME_OFFSET,
|
||||
RUST_IRQ_HANDLER = sym RUST_IRQ_HANDLER,
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns 0 in `r0`, while placing the `numerator` into `r1`.
|
||||
///
|
||||
/// This is written in that slightly strange way so that `div` function and
|
||||
/// `divmod` functions can share the same code path.
|
||||
///
|
||||
/// See: [__aeabi_idiv0][aeabi-division-by-zero]
|
||||
///
|
||||
/// [aeabi-division-by-zero]: https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#division-by-zero
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
// this should literally never get called for real, so we leave it in ROM
|
||||
extern "C" fn __aeabi_idiv0(numerator: i32) -> i32 {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
// this comment stops rustfmt from making this a one-liner
|
||||
"mov r1, r0",
|
||||
"mov r0, #0",
|
||||
"bx lr",
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `u32 / u32`
|
||||
///
|
||||
/// This implementation is *not* the fastest possible division, but it is
|
||||
/// extremely compact.
|
||||
///
|
||||
/// See: [__aeabi_uidiv][aeabi-integer-32-32-division]
|
||||
///
|
||||
/// [aeabi-integer-32-32-division]:
|
||||
/// https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#integer-32-32-32-division-functions
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uidiv"]
|
||||
extern "C" fn __aeabi_uidiv(numerator: u32, denominator: u32) -> u32 {
|
||||
// Note(Lokathor): Other code in this module relies on being able to call this
|
||||
// function without affecting r12, so any future implementations of this code
|
||||
// **must not** destroy r12.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
// Check for divide by 0
|
||||
"cmp r1, #0",
|
||||
"beq __aeabi_idiv0",
|
||||
// r3(shifted_denom) = denom
|
||||
"mov r3, r1",
|
||||
// while shifted_denom < (num>>1): shifted_denom =<< 1;
|
||||
"cmp r3, r0, lsr #1",
|
||||
"2:",
|
||||
"lslls r3, r3, #1",
|
||||
"cmp r3, r0, lsr #1",
|
||||
"bls 2b",
|
||||
// r0=quot(init 0), r1=denom, r2=num, r3=shifted_denom
|
||||
"mov r2, r0",
|
||||
"mov r0, #0",
|
||||
// subtraction loop
|
||||
"3:",
|
||||
"cmp r2, r3",
|
||||
"subcs r2, r2, r3",
|
||||
"adc r0, r0, r0",
|
||||
"mov r3, r3, lsr #1",
|
||||
"cmp r3, r1",
|
||||
"bcs 3b",
|
||||
"bx lr",
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `i32 / i32`
|
||||
///
|
||||
/// See: [__aeabi_idiv][aeabi-integer-32-32-division]
|
||||
///
|
||||
/// [aeabi-integer-32-32-division]:
|
||||
/// https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#integer-32-32-32-division-functions
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.idiv"]
|
||||
extern "C" fn __aeabi_idiv(numerator: i32, denominator: i32) -> u32 {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
// determine if `numerator` and `denominator` are the same sign
|
||||
"eor r12, r1, r0",
|
||||
// convert both values to their unsigned absolute value.
|
||||
"cmp r0, #0",
|
||||
"rsblt r0, r0, #0",
|
||||
"cmp r1, #0",
|
||||
"rsclt r1, r1, #0",
|
||||
with_pushed_registers!("{{lr}}", {
|
||||
// divide them using `u32` division (this will check for divide by 0)
|
||||
"bl __aeabi_uidiv",
|
||||
}),
|
||||
// if they started as different signs, flip the output's sign.
|
||||
"cmp r12, #0",
|
||||
"rsblt r0, r0, #0",
|
||||
"bx lr",
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `(u32 / u32, u32 % u32)` in `(r0, r1)`.
|
||||
///
|
||||
/// The `u64` return value is a mild lie that gets Rust to grab up both the `r0`
|
||||
/// and `r1` values when the function returns. If you transmute the return value
|
||||
/// into `[u32; 2]` then you can separate the two parts of the return value, and
|
||||
/// it will have no runtime cost.
|
||||
///
|
||||
/// See: [__aeabi_uidivmod][aeabi-integer-32-32-division]
|
||||
///
|
||||
/// [aeabi-integer-32-32-division]:
|
||||
/// https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#integer-32-32-32-division-functions
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uidivmod"]
|
||||
extern "C" fn __aeabi_uidivmod(numerator: u32, denominator: u32) -> u64 {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
// We need to save *both* input args until after the uidiv call. One of
|
||||
// them can be saved in `r12` because we know our uidiv doesn't actually
|
||||
// touch `r12`, while the other will be pushed onto the stack along with
|
||||
// `lr`. Since the function's output will be in `r0`, we push/pop `r1`.
|
||||
"mov r12, r0",
|
||||
with_pushed_registers!("{{r1, lr}}", {
|
||||
"bl __aeabi_uidiv",
|
||||
}),
|
||||
// Now r0 holds the `quot`, and we use it along with the input args to
|
||||
// calculate the `rem`.
|
||||
"mul r2, r0, r1",
|
||||
"sub r1, r12, r2",
|
||||
"bx lr",
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `(i32 / i32, i32 % i32)` in `(r0, r1)`.
|
||||
///
|
||||
/// The `u64` return value is a mild lie that gets Rust to grab up both the `r0`
|
||||
/// and `r1` values when the function returns. If you transmute the return value
|
||||
/// into `[i32; 2]` then you can separate the two parts of the return value, and
|
||||
/// it will have no runtime cost.
|
||||
///
|
||||
/// See: [__aeabi_idivmod][aeabi-integer-32-32-division]
|
||||
///
|
||||
/// [aeabi-integer-32-32-division]:
|
||||
/// https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#integer-32-32-32-division-functions
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.idivmod"]
|
||||
extern "C" fn __aeabi_idivmod(numerator: i32, denominator: i32) -> u64 {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
with_pushed_registers!("{{r4, r5, lr}}", {
|
||||
// store old numerator then make it the unsigned absolute
|
||||
"movs r4, r0",
|
||||
"rsblt r0, r0, #0",
|
||||
// store old denominator then make it the unsigned absolute
|
||||
"movs r5, r1",
|
||||
"rsblt r1, r1, #0",
|
||||
// divmod using unsigned.
|
||||
"bl __aeabi_uidivmod",
|
||||
// if signs started opposite, quot becomes negative
|
||||
"eors r12, r4, r5",
|
||||
"rsblt r0, r0, #0",
|
||||
// if numerator started negative, rem is negative
|
||||
"cmp r4, #0",
|
||||
"rsblt r1, r1, #0",
|
||||
}),
|
||||
"bx lr",
|
||||
options(noreturn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads 4 bytes, starting at the address given.
|
||||
///
|
||||
/// See [__aeabi_uread4]
|
||||
///
|
||||
/// [__aeabi_uread4]: https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#unaligned-memory-access
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uread4"]
|
||||
unsafe extern "C" fn __aeabi_uread4(address: *const c_void) -> u32 {
|
||||
core::arch::asm!(
|
||||
"ldrb r2, [r0]",
|
||||
"ldrb r3, [r0, #1]",
|
||||
"orr r2, r2, r3, lsl #8",
|
||||
"ldrb r3, [r0, #2]",
|
||||
"orr r2, r2, r3, lsl #16",
|
||||
"ldrb r3, [r0, #3]",
|
||||
"orr r2, r2, r3, lsl #24",
|
||||
"mov r0, r2",
|
||||
"bx lr",
|
||||
options(noreturn),
|
||||
)
|
||||
}
|
||||
|
||||
/// Writes 4 bytes, starting at the address given.
|
||||
///
|
||||
/// See [__aeabi_uwrite4]
|
||||
///
|
||||
/// [__aeabi_uwrite4]: https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#unaligned-memory-access
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uwrite4"]
|
||||
unsafe extern "C" fn __aeabi_uwrite4(value: u32, address: *mut c_void) {
|
||||
core::arch::asm!(
|
||||
"strb r0, [r1]",
|
||||
"lsr r2, r0, #8",
|
||||
"strb r2, [r1, #1]",
|
||||
"lsr r2, r2, #8",
|
||||
"strb r2, [r1, #2]",
|
||||
"lsr r2, r2, #8",
|
||||
"strb r2, [r1, #3]",
|
||||
"bx lr",
|
||||
options(noreturn),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reads 8 bytes, starting at the address given.
|
||||
///
|
||||
/// See [__aeabi_uread8]
|
||||
///
|
||||
/// [__aeabi_uread8]: https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#unaligned-memory-access
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uread8"]
|
||||
unsafe extern "C" fn __aeabi_uread8(address: *const c_void) -> u64 {
|
||||
core::arch::asm!(
|
||||
"ldrb r1, [r0, #4]",
|
||||
"ldrb r2, [r0, #5]",
|
||||
"orr r1, r1, r2, lsl #8",
|
||||
"ldrb r2, [r0, #6]",
|
||||
"orr r1, r1, r2, lsl #16",
|
||||
"ldrb r2, [r0, #7]",
|
||||
"orr r1, r1, r2, lsl #24",
|
||||
"b __aeabi_uread4",
|
||||
options(noreturn),
|
||||
)
|
||||
}
|
||||
|
||||
/// Writes 8 bytes, starting at the address given.
|
||||
///
|
||||
/// See [__aeabi_uwrite8]
|
||||
///
|
||||
/// [__aeabi_uwrite8]: https://github.com/ARM-software/abi-aa/blob/main/rtabi32/rtabi32.rst#unaligned-memory-access
|
||||
#[naked]
|
||||
#[no_mangle]
|
||||
#[instruction_set(arm::a32)]
|
||||
#[link_section = ".iwram.aeabi.uwrite8"]
|
||||
unsafe extern "C" fn __aeabi_uwrite8(value: u64, address: *mut c_void) {
|
||||
core::arch::asm!(
|
||||
"strb r0, [r2]",
|
||||
"lsr r3, r0, #8",
|
||||
"strb r3, [r2, #1]",
|
||||
"lsr r3, r3, #8",
|
||||
"strb r3, [r2, #2]",
|
||||
"lsr r3, r3, #8",
|
||||
"strb r3, [r2, #3]",
|
||||
"strb r1, [r2, #4]",
|
||||
"lsr r3, r1, #8",
|
||||
"strb r3, [r2, #5]",
|
||||
"lsr r3, r3, #8",
|
||||
"strb r3, [r2, #6]",
|
||||
"lsr r3, r3, #8",
|
||||
"strb r3, [r2, #7]",
|
||||
"bx lr",
|
||||
options(noreturn),
|
||||
)
|
||||
}
|
||||
|
||||
/// Provides the `libc` styled memory copy (transfer between exclusive regions)
|
||||
#[inline]
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn memcpy(
|
||||
dest: *mut u8, src: *const u8, byte_count: usize,
|
||||
) -> *mut u8 {
|
||||
__aeabi_memcpy(dest, src, byte_count);
|
||||
dest
|
||||
}
|
||||
|
||||
/// Provides the `libc` styled memory move (transfer between non-exclusive
|
||||
/// regions)
|
||||
#[inline]
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn memmove(
|
||||
dest: *mut u8, src: *const u8, byte_count: usize,
|
||||
) -> *mut u8 {
|
||||
__aeabi_memmove(dest, src, byte_count);
|
||||
dest
|
||||
}
|
||||
|
||||
/// Provides the `libc` styled memory set (assign `u8` in `byte` to the entire
|
||||
/// region)
|
||||
#[inline]
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn memset(
|
||||
dest: *mut u8, byte: i32, byte_count: usize,
|
||||
) -> *mut u8 {
|
||||
__aeabi_memset(dest, byte_count, byte);
|
||||
dest
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn __aeabi_memcpy(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
pub fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
pub fn __aeabi_memcpy8(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
|
||||
pub fn gba_sram_memcpy(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
|
||||
pub fn __aeabi_memmove(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
pub fn __aeabi_memmove4(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
pub fn __aeabi_memmove8(dest: *mut u8, src: *const u8, byte_count: usize);
|
||||
|
||||
pub fn __aeabi_memset(dest: *mut u8, byte_count: usize, byte: i32);
|
||||
pub fn __aeabi_memset4(dest: *mut u8, byte_count: usize, byte: i32);
|
||||
pub fn __aeabi_memset8(dest: *mut u8, byte_count: usize, byte: i32);
|
||||
|
||||
pub fn __aeabi_memclr(dest: *mut u8, byte_count: usize);
|
||||
pub fn __aeabi_memclr4(dest: *mut u8, byte_count: usize);
|
||||
pub fn __aeabi_memclr8(dest: *mut u8, byte_count: usize);
|
||||
}
|
||||
|
||||
core::arch::global_asm! {
|
||||
emit_a32_code!{
|
||||
put_code_in_section!(".iwram.aeabi.memory.copy.and.move", {
|
||||
"__aeabi_memmove8:",
|
||||
"__aeabi_memmove4:",
|
||||
"__aeabi_memmove:",
|
||||
"cmp r0, r1", // if d > s, reverse copy
|
||||
"bgt .L_r_copy_gain_align",
|
||||
// else fallthrough
|
||||
|
||||
"__aeabi_memcpy:",
|
||||
".L_f_copy_gain_align:",
|
||||
"eor r3, r0, r1",
|
||||
"lsls r3, r3, #31",
|
||||
"bmi .L_f_copy_max_coalign1",
|
||||
"bcs .L_f_copy_max_coalign2",
|
||||
// else fallthrough
|
||||
|
||||
".L_f_copy_max_coalign4:",
|
||||
"tst r0, #3",
|
||||
"bne .L_f_copy_fixup4",
|
||||
// else fallthrough
|
||||
|
||||
"__aeabi_memcpy8:",
|
||||
"__aeabi_memcpy4:",
|
||||
".L_f_copy_coalign4_assured:",
|
||||
"cmp r2, #32",
|
||||
"bge .L_f_copy_block",
|
||||
|
||||
".L_f_copy_post_block:",
|
||||
// copy 4 words, two at a time
|
||||
"tst r2, #0b10000",
|
||||
"ldmne r1!, {r3, r12}",
|
||||
"stmne r0!, {r3, r12}",
|
||||
"ldmne r1!, {r3, r12}",
|
||||
"stmne r0!, {r3, r12}",
|
||||
"bics r2, r2, #0b10000",
|
||||
"bxeq lr",
|
||||
|
||||
// copy 2 and/or 1 words
|
||||
"lsls r3, r2, #29",
|
||||
"ldmcs r1!, {r3, r12}",
|
||||
"stmcs r0!, {r3, r12}",
|
||||
"ldrmi r3, [r1], #4",
|
||||
"strmi r3, [r0], #4",
|
||||
"bics r2, r2, #0b1100",
|
||||
"bxeq lr",
|
||||
|
||||
// copy halfword and/or byte
|
||||
"lsls r3, r2, #31",
|
||||
"ldrhcs r3, [r1], #2",
|
||||
"strhcs r3, [r0], #2",
|
||||
"ldrbmi r3, [r1], #1",
|
||||
"strbmi r3, [r0], #1",
|
||||
"bx lr",
|
||||
|
||||
".L_f_copy_block:",
|
||||
with_pushed_registers!("{r4-r9}", {
|
||||
"1:",
|
||||
"subs r2, r2, #32",
|
||||
"ldmge r1!, {r3-r9, r12}",
|
||||
"stmge r0!, {r3-r9, r12}",
|
||||
"bgt 1b",
|
||||
}),
|
||||
"bxeq lr",
|
||||
"b .L_f_copy_post_block",
|
||||
|
||||
".L_f_copy_fixup4:",
|
||||
"cmp r2, #7", // if count <= (fix+word): just byte copy
|
||||
"ble .L_f_copy_max_coalign1",
|
||||
"lsls r3, r0, #31",
|
||||
"submi r2, r2, #1",
|
||||
"ldrbmi r3, [r1], #1",
|
||||
"strbmi r3, [r0], #1",
|
||||
"subcs r2, r2, #2",
|
||||
"ldrhcs r3, [r1], #2",
|
||||
"strhcs r3, [r0], #2",
|
||||
"b .L_f_copy_coalign4_assured",
|
||||
|
||||
".L_f_copy_max_coalign2:",
|
||||
"tst r0, #1",
|
||||
"bne .L_f_copy_fixup2",
|
||||
".L_f_copy_coalign2_assured:",
|
||||
"1:",
|
||||
"subs r2, r2, #2",
|
||||
"ldrhge r3, [r1], #2",
|
||||
"strhge r3, [r0], #2",
|
||||
"bgt 1b",
|
||||
"bxeq lr",
|
||||
"tst r2, #1",
|
||||
"ldrbne r3, [r1], #1",
|
||||
"strbne r3, [r0], #1",
|
||||
"bx lr",
|
||||
|
||||
".L_f_copy_fixup2:",
|
||||
"cmp r2, #3", // if count <= (fix+halfword): just byte copy
|
||||
"ble .L_f_copy_max_coalign1",
|
||||
"sub r2, r2, #1",
|
||||
"ldrb r3, [r1], #1",
|
||||
"strb r3, [r0], #1",
|
||||
"b .L_f_copy_coalign2_assured",
|
||||
|
||||
"gba_sram_memcpy:",
|
||||
".L_f_copy_max_coalign1:",
|
||||
"1:",
|
||||
"subs r2, r2, #1",
|
||||
"ldrbge r3, [r1], #1",
|
||||
"strbge r3, [r0], #1",
|
||||
"bgt 1b",
|
||||
"bx lr",
|
||||
|
||||
".L_r_copy_gain_align:",
|
||||
"add r0, r0, r2",
|
||||
"add r1, r1, r2",
|
||||
"eor r3, r0, r1",
|
||||
"lsls r3, r3, #31",
|
||||
"bmi .L_r_copy_max_coalign1",
|
||||
"bcs .L_r_copy_max_coalign2",
|
||||
// else fallthrough
|
||||
|
||||
".L_r_copy_max_coalign4:",
|
||||
"tst r0, #3",
|
||||
"bne .L_r_copy_fixup4",
|
||||
".L_r_copy_coalign4_assured:",
|
||||
"cmp r2, #32",
|
||||
"bge .L_r_copy_block",
|
||||
".L_r_copy_post_block:",
|
||||
// copy 4 words, two at a time
|
||||
"tst r2, #0b10000",
|
||||
"ldmdbne r1!, {r3, r12}",
|
||||
"stmdbne r0!, {r3, r12}",
|
||||
"ldmdbne r1!, {r3, r12}",
|
||||
"stmdbne r0!, {r3, r12}",
|
||||
"bics r2, r2, #0b10000",
|
||||
"bxeq lr",
|
||||
|
||||
// copy 2 and/or 1 words
|
||||
"lsls r3, r2, #29",
|
||||
"ldmdbcs r1!, {r3, r12}",
|
||||
"stmdbcs r0!, {r3, r12}",
|
||||
"ldrmi r3, [r1, #-4]!",
|
||||
"strmi r3, [r0, #-4]!",
|
||||
"bxeq lr",
|
||||
"lsls r2, r2, #31",
|
||||
"ldrhcs r3, [r1, #-2]!",
|
||||
"strhcs r3, [r0, #-2]!",
|
||||
"ldrbmi r3, [r1, #-1]!",
|
||||
"strbmi r3, [r0, #-1]!",
|
||||
"bx lr",
|
||||
|
||||
".L_r_copy_block:",
|
||||
with_pushed_registers!("{r4-r9}", {
|
||||
"1:",
|
||||
"subs r2, r2, #32",
|
||||
"ldmdbcs r1!, {r3-r9, r12}",
|
||||
"stmdbcs r0!, {r3-r9, r12}",
|
||||
"bgt 1b",
|
||||
}),
|
||||
"bxeq lr",
|
||||
"b .L_r_copy_post_block",
|
||||
|
||||
".L_r_copy_fixup4:",
|
||||
"cmp r2, #7", // if count <= (fix+word): just byte copy
|
||||
"ble .L_r_copy_max_coalign1",
|
||||
"lsls r3, r0, #31",
|
||||
"submi r2, r2, #1",
|
||||
"ldrbmi r3, [r1, #-1]!",
|
||||
"strbmi r3, [r0, #-1]!",
|
||||
"subcs r2, r2, #2",
|
||||
"ldrhcs r3, [r1, #-2]!",
|
||||
"strhcs r3, [r0, #-2]!",
|
||||
"b .L_r_copy_coalign4_assured",
|
||||
|
||||
".L_r_copy_max_coalign2:",
|
||||
"tst r0, #1",
|
||||
"bne .L_r_copy_fixup2",
|
||||
".L_r_copy_coalign2_assured:",
|
||||
"1:",
|
||||
"subs r2, r2, #2",
|
||||
"ldrhge r3, [r1, #-2]!",
|
||||
"strhge r3, [r0, #-2]!",
|
||||
"bgt 1b",
|
||||
"bxeq lr",
|
||||
"tst r2, #1",
|
||||
"ldrbne r3, [r1, #-1]!",
|
||||
"strbne r3, [r0, #-1]!",
|
||||
"bx lr",
|
||||
|
||||
".L_r_copy_fixup2:",
|
||||
"cmp r2, #3", // if count <= (fix+halfword): just byte copy
|
||||
"ble .L_r_copy_max_coalign1",
|
||||
"sub r2, r2, #1",
|
||||
"ldrb r3, [r1, #-1]!",
|
||||
"strb r3, [r0, #-1]!",
|
||||
"b .L_r_copy_coalign2_assured",
|
||||
|
||||
".L_r_copy_max_coalign1:",
|
||||
"1:",
|
||||
"subs r2, r2, #1",
|
||||
"ldrbge r3, [r1, #-1]!",
|
||||
"strbge r3, [r0, #-1]!",
|
||||
"bgt 1b",
|
||||
"bx lr",
|
||||
}),
|
||||
},
|
||||
options(raw)
|
||||
}
|
||||
|
||||
core::arch::global_asm! {
|
||||
emit_a32_code!{
|
||||
put_code_in_section!(".iwram.aeabi.memory.clear.and.set", {
|
||||
"__aeabi_memclr8:",
|
||||
"__aeabi_memclr4:",
|
||||
"mov r2, #0",
|
||||
"mov r3, #0",
|
||||
"b .L_memset_check_for_block_work",
|
||||
"__aeabi_memclr:",
|
||||
"mov r2, #0",
|
||||
"__aeabi_memset8:",
|
||||
"__aeabi_memset4:",
|
||||
"__aeabi_memset:", // r0(dest), r1(count), r2(byte)
|
||||
// duplicate the byte across all of r2 and r3
|
||||
"and r2, r2, #0xFF",
|
||||
"orr r2, r2, r2, lsl #8",
|
||||
"orr r2, r2, r2, lsl #16",
|
||||
"mov r3, r2",
|
||||
// for 'sets' too small to fixup we just byte loop
|
||||
"cmp r1, #3",
|
||||
"ble .L_memset_byte_loop",
|
||||
// carry/sign test on the address, then do fixup
|
||||
"lsls r12, r0, #31",
|
||||
"submi r1, r1, #1",
|
||||
"strbmi r2, [r0], #1",
|
||||
"subcs r1, r1, #2",
|
||||
"strhcs r2, [r0], #2",
|
||||
".L_memset_check_for_block_work:",
|
||||
"cmp r1, #32",
|
||||
"bge .L_memset_block_work",
|
||||
|
||||
".L_memset_post_block_work:",
|
||||
// set 4 words
|
||||
"tst r1, #0b10000",
|
||||
"stmne r0!, {r2, r3}",
|
||||
"stmne r0!, {r2, r3}",
|
||||
// set 2 and/or 1 words
|
||||
"lsls r12, r1, #29",
|
||||
"stmcs r0!, {r2, r3}",
|
||||
"strmi r2, [r0], #4",
|
||||
// set halfword and/or byte
|
||||
"lsls r12, r1, #31",
|
||||
"strhcs r2, [r0], #2",
|
||||
"strbmi r2, [r0], #1",
|
||||
"bx lr",
|
||||
|
||||
".L_memset_block_work:",
|
||||
with_pushed_registers!("{r4-r9}", {
|
||||
"mov r4, r2",
|
||||
"mov r5, r2",
|
||||
"mov r6, r2",
|
||||
"mov r7, r2",
|
||||
"mov r8, r2",
|
||||
"mov r9, r2",
|
||||
"1:",
|
||||
"subs r1, r1, #32",
|
||||
"stmge r0!, {r2-r9}",
|
||||
"bgt 1b",
|
||||
}),
|
||||
"bxeq lr",
|
||||
"b .L_memset_post_block_work",
|
||||
|
||||
".L_memset_byte_loop:",
|
||||
"1:",
|
||||
"subs r1, r1, #1",
|
||||
"strbcs r2, [r0], #1",
|
||||
"bgt 1b",
|
||||
"bx lr",
|
||||
}),
|
||||
},
|
||||
options(raw),
|
||||
}
|
189
src/bak/sio.rs
189
src/bak/sio.rs
|
@ -1,189 +0,0 @@
|
|||
//! Contains types and definitions for Serial IO registers.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Serial IO Control. Read/Write.
|
||||
pub const SIOCNT: VolAddress<SioControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0128) };
|
||||
|
||||
/// Serial IO Data. Read/Write.
|
||||
pub const SIODATA8: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_012A) };
|
||||
|
||||
/// General IO Control. Read/Write.
|
||||
pub const RCNT: VolAddress<IoControlSetting, Safe, Safe> = unsafe { VolAddress::new(0x400_0134) };
|
||||
|
||||
newtype!(
|
||||
/// Setting for the serial IO control register.
|
||||
///
|
||||
/// * 0-1: `BaudRate`
|
||||
/// * 2: Use hardware flow control
|
||||
/// * 3: Use odd parity instead of even
|
||||
/// * 4: TX buffer is full
|
||||
/// * 5: RX buffer is empty
|
||||
/// * 6: Error occurred
|
||||
/// * 7: Use 8-bit data length instead of 7-bit
|
||||
/// * 8: Use hardware FIFO
|
||||
/// * 9: Enable parity check
|
||||
/// * 10: Enable data receive
|
||||
/// * 11: Enable data transmit
|
||||
/// * 12-13: `SioMode`
|
||||
/// * 14: Trigger interrupt on RX
|
||||
SioControlSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl SioControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
baud_rate: 0-1=BaudRate<Bps9600,Bps38400,Bps57600,Bps115200>,
|
||||
flow_control: 2,
|
||||
parity_odd: 3,
|
||||
tx_full: 4,
|
||||
rx_empty: 5,
|
||||
error: 6,
|
||||
data_length_8bit: 7,
|
||||
fifo_enable:8,
|
||||
parity_enable: 9,
|
||||
tx_enable: 10,
|
||||
rx_enable: 11,
|
||||
mode: 12-13=SioMode<Normal8Bit,MultiPlayer,Normal32Bit,Uart>,
|
||||
irq_enable: 14,
|
||||
}
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// Supported baud rates.
|
||||
BaudRate = u16,
|
||||
/// * 9600 bps
|
||||
Bps9600 = 0,
|
||||
/// * 38400 bps
|
||||
Bps38400 = 1,
|
||||
/// * 57600 bps
|
||||
Bps57600 = 2,
|
||||
/// * 115200 bps
|
||||
Bps115200 = 3,
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// Serial IO modes.
|
||||
SioMode = u16,
|
||||
/// * Normal mode: 8-bit data
|
||||
Normal8Bit = 0,
|
||||
/// * Multiplayer mode: 16-bit data
|
||||
MultiPlayer = 1,
|
||||
/// * Normal mode: 32-bit data
|
||||
Normal32Bit = 2,
|
||||
/// * UART (RS232) mode: 7 or 8-bit data
|
||||
Uart = 3,
|
||||
}
|
||||
|
||||
newtype!(
|
||||
/// Setting for the general IO control register.
|
||||
///
|
||||
/// * 0: SC state
|
||||
/// * 1: SD state
|
||||
/// * 2: SI state
|
||||
/// * 3: SO state
|
||||
/// * 4: Set SC as output, instead of input
|
||||
/// * 5: Set SD as output, instead of input
|
||||
/// * 6: Set SI as output, instead of input
|
||||
/// * 7: Set SO as output, instead of input
|
||||
/// * 8: Trigger interrupt on SI change
|
||||
/// * 14-15: `IoMode`
|
||||
IoControlSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
newtype_enum! {
|
||||
/// General IO modes.
|
||||
IoMode = u16,
|
||||
/// * IO disabled
|
||||
Disabled = 0,
|
||||
/// * General Purpose IO
|
||||
GPIO = 2,
|
||||
/// * JoyBus mode
|
||||
JoyBus = 3,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl IoControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sc: 0,
|
||||
sd: 1,
|
||||
si: 2,
|
||||
so: 3,
|
||||
sc_output_enable: 4,
|
||||
sd_output_enable: 5,
|
||||
si_output_enable: 6,
|
||||
so_output_enable: 7,
|
||||
si_irq_enable: 8,
|
||||
mode: 14-15=IoMode<Disabled,GPIO,JoyBus>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty struct that implements embedded_hal traits.
|
||||
#[cfg(feature = "serial")]
|
||||
#[derive(Clone)]
|
||||
pub struct SioSerial;
|
||||
|
||||
#[cfg(feature = "serial")]
|
||||
impl SioSerial {
|
||||
/// Initialize SioSerial with provided baud rate and default 8N1 settings.
|
||||
pub fn init(baud: BaudRate) -> Self {
|
||||
RCNT.write(IoControlSetting::new());
|
||||
SIOCNT.write(
|
||||
// default settings: 8N1
|
||||
SioControlSetting::new()
|
||||
.with_baud_rate(baud)
|
||||
.with_data_length_8bit(true)
|
||||
.with_mode(SioMode::Uart)
|
||||
.with_fifo_enable(true)
|
||||
.with_rx_enable(true)
|
||||
.with_tx_enable(true),
|
||||
);
|
||||
|
||||
SioSerial
|
||||
}
|
||||
}
|
||||
|
||||
/// Serial IO error type.
|
||||
#[cfg(feature = "serial")]
|
||||
#[derive(Debug)]
|
||||
pub enum SioError {
|
||||
/// * Error bit in SIOCNT is set
|
||||
ErrorBitSet,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serial")]
|
||||
impl embedded_hal::serial::Read<u8> for SioSerial {
|
||||
type Error = SioError;
|
||||
|
||||
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
||||
match SIOCNT.read() {
|
||||
siocnt if siocnt.error() => Err(nb::Error::Other(SioError::ErrorBitSet)),
|
||||
siocnt if siocnt.rx_empty() => Err(nb::Error::WouldBlock),
|
||||
_ => Ok(SIODATA8.read() as u8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serial")]
|
||||
impl embedded_hal::serial::Write<u8> for SioSerial {
|
||||
type Error = SioError;
|
||||
|
||||
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
|
||||
self.flush()?;
|
||||
SIODATA8.write(word as u16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> nb::Result<(), Self::Error> {
|
||||
match SIOCNT.read() {
|
||||
siocnt if siocnt.error() => Err(nb::Error::Other(SioError::ErrorBitSet)),
|
||||
siocnt if siocnt.tx_full() => Err(nb::Error::WouldBlock),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
// _ Link Cable Pinout
|
||||
// ___/ \___ 1: VCC - 3.3V
|
||||
// / \ 2: SO - TX
|
||||
// | 1 3 5 | 3: SI - RX
|
||||
// | 2 4 6 | 4: SD
|
||||
// |_________| 5: SC
|
||||
// 6: GND
|
||||
|
||||
use embedded_hal::prelude::*;
|
||||
use gba::io::sio::{BaudRate, SioSerial};
|
||||
use nb::block;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let mut serial = SioSerial::init(BaudRate::Bps115200);
|
||||
|
||||
loop {
|
||||
if let Ok(c) = block!(serial.read()) {
|
||||
block!(serial.write(c)).ok();
|
||||
}
|
||||
}
|
||||
}
|
641
src/bios.rs
641
src/bios.rs
|
@ -1,631 +1,46 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
//! The BIOS includes several System Call Functions which can be accessed by SWI
|
||||
//! instructions.
|
||||
//!
|
||||
//! * All BIOS functions clobber `r0`, `r1`, and `r3`.
|
||||
//! * Some functions also use `r2` as an input register.
|
||||
//! * All other registers are unaffected.
|
||||
use crate::interrupts::IrqBits;
|
||||
|
||||
// Note(Lokathor): This makes intra-doc links work.
|
||||
#[allow(unused)]
|
||||
use crate::prelude::*;
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
/// (`swi 0x00`) Performs a "soft reset" of the device.
|
||||
/// `0x04` Waits for a specific interrupt type(s) to happen.
|
||||
///
|
||||
/// Loads `r14` based on the `u8` value at `0x0300_7FFA`:
|
||||
/// * zero: `0x0800_0000` (ROM)
|
||||
/// * non-zero: `0x0200_0000` (EWRAM)
|
||||
/// **Important:** This function forces [`IME`](crate::mmio::IME) on.
|
||||
///
|
||||
/// Then resets the following memory and registers:
|
||||
/// * `0x300_7E00` ..= `0x300_7FFF`: zeroed
|
||||
/// * `r0` ..= `r12`: zeroed
|
||||
/// * `sp_usr`: `0x300_7F00`
|
||||
/// * `sp_irq`: `0x300_7FA0`
|
||||
/// * `sp_svc`: `0x300_7FE0`
|
||||
/// * `lr_svc`, `lr_irq` : zeroed
|
||||
/// * `spsr_svc`, `spsr_irq`: zeroed
|
||||
/// Waits for *any* of the interrupt types set in `target_irqs` to occur. Your
|
||||
/// interrupt handler (if any) will be run before this function returns.
|
||||
///
|
||||
/// Then jumps to the `r14` address. This never returns.
|
||||
pub unsafe fn SoftReset() -> ! {
|
||||
asm!("swi 0x00", options(noreturn))
|
||||
}
|
||||
|
||||
/// (`swi 0x01`) Resets RAM and/or IO registers
|
||||
///
|
||||
/// * Note that if the IWRAM flag is used it doesn't reset the final `0x200`
|
||||
/// bytes of IWRAM. Instead, those bytes are reset during a call to the
|
||||
/// [`SoftReset`] function.
|
||||
/// * BIOS Bug: Data in `SIODATA32` is always destroyed, even if the `sio` flag
|
||||
/// is not set.
|
||||
/// This function uses a special BIOS variable to track what interrupts have
|
||||
/// occured recently.
|
||||
/// * If `ignore_existing` is set, then any previous interrupts (since
|
||||
/// `IntrWait` was last called) that match `target_irqs` are *ignored* and
|
||||
/// this function will wait for a new target interrupt to occur.
|
||||
/// * Otherwise, any previous interrupts that match `target_irqs` will cause the
|
||||
/// function to return immediately without waiting for a new interrupt.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn RegisterRamReset(flags: crate::mmio_types::ResetFlags) {
|
||||
asm!("swi 0x01",
|
||||
inlateout("r0") flags.0 => _,
|
||||
out("r1") _,
|
||||
pub fn IntrWait(ignore_existing: bool, target_irqs: IrqBits) {
|
||||
unsafe {
|
||||
core::arch::asm! {
|
||||
"swi #0x04",
|
||||
inout("r0") ignore_existing as u32 => _,
|
||||
inout("r1") target_irqs.to_u16() => _,
|
||||
out("r3") _,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
)
|
||||
options(preserves_flags),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// (`swi 0x02`) Halts the CPU until an interrupt request occurs.
|
||||
///
|
||||
/// The CPU is placed into low-power mode, while other parts (video, sound,
|
||||
/// timers, serial, keypad) continue to operate. This mode only terminates when
|
||||
/// one of the enabled interrupts is requested.
|
||||
///
|
||||
/// This halt state uses [`IE`] to determine what interrupts to allow, but the
|
||||
/// [`IME`] value is ignored (interrupts can occur even if `IME` is `false`).
|
||||
/// `0x05` Builtin shorthand for [`IntrWait(true, IrqBits::VBLANK)`](IntrWait)
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn Halt() {
|
||||
asm!("swi 0x02",
|
||||
pub fn VBlankIntrWait() {
|
||||
unsafe {
|
||||
core::arch::asm! {
|
||||
"swi #0x05",
|
||||
out("r0") _,
|
||||
out("r1") _,
|
||||
out("r3") _,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
)
|
||||
options(preserves_flags),
|
||||
}
|
||||
|
||||
/// (`swi 0x03`) Puts the CPU in a *very* low power state.
|
||||
///
|
||||
/// While stopped, the CPU, Sound, Video, SIO-shift-clock, DMA, and Timers are
|
||||
/// all disabled.
|
||||
///
|
||||
/// The system can return from this state only if there is an interrupt from the
|
||||
/// Keypad, Game Pak, or General-Purpose-SIO.
|
||||
///
|
||||
/// Before calling Stop you are advised to disable the Video to reduce battery
|
||||
/// usage, otherwise it just freezes.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn Stop() {
|
||||
asm!("swi 0x03",
|
||||
out("r0") _,
|
||||
out("r1") _,
|
||||
out("r3") _,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// (`swi 0x04`) "Interrupt Wait".
|
||||
///
|
||||
/// This is similar to [`Halt`], but when an interrupt does occur this function
|
||||
/// will automatically return the CPU to halt state unless the interrupt is one
|
||||
/// of the interrupt types specified by `flags`.
|
||||
///
|
||||
/// If you set `discard_current_flags` then any pending interrupts are cleared
|
||||
/// and this function will wait until a new flag is set. Otherwise the function
|
||||
/// will return immediately if you request a wait for an interrupt that's
|
||||
/// already pending.
|
||||
///
|
||||
/// When handling an interrupt through this function you must perform the normal
|
||||
/// acknowledgement using [`IRQ_ACKNOWLEDGE`] and **also** acknowledge using
|
||||
/// [`INTR_WAIT_ACKNOWLEDGE`].
|
||||
///
|
||||
/// **Caution:** This function automatically also sets [`IME`] to `true`.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn IntrWait(discard_current_flags: bool, flags: crate::mmio_types::InterruptFlags) {
|
||||
// Note(Lokathor): we don't mark this preserves_flags because the user's IRQ
|
||||
// handler gets called which might end up trashing the flags.
|
||||
asm!("swi 0x04",
|
||||
inlateout("r0") discard_current_flags as u8 => _,
|
||||
inlateout("r1") flags.0 => _,
|
||||
out("r3") _,
|
||||
options(nomem, nostack)
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x05`) "VBlank Interrupt Wait"
|
||||
///
|
||||
/// Waits for the next VBlank interrupt.
|
||||
///
|
||||
/// This function is just shorthand for the following:
|
||||
/// ```no_run
|
||||
/// # use crate::prelude::*;
|
||||
/// const VBLANK_IRQ: InterruptFlags = InterruptFlags::new().with_vblank(true);
|
||||
/// IntrWait(true, VBLANK_IRQ)
|
||||
/// ```
|
||||
/// See [`IntrWait`]
|
||||
///
|
||||
/// **Note:** Because this uses `IntrWait`, [`IME`] will be set to `true`
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn VBlankIntrWait() {
|
||||
// Note(Lokathor): we don't mark this preserves_flags because the user's IRQ
|
||||
// handler gets called which might end up trashing the flags.
|
||||
asm!(
|
||||
"swi 0x05",
|
||||
out("r0") _,
|
||||
out("r1") _,
|
||||
out("r3") _,
|
||||
options(nomem, nostack)
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x06`) Performs `i32` division.
|
||||
///
|
||||
/// **Outputs:** `(n/d, n%d, (n/d).unsigned_abs())`
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub fn Div(number: i32, denominator: core::num::NonZeroI32) -> (i32, i32, u32) {
|
||||
let d: i32;
|
||||
let m: i32;
|
||||
let abs_d: u32;
|
||||
unsafe {
|
||||
asm!("swi 0x06",
|
||||
inlateout("r0") number => d,
|
||||
inlateout("r1") denominator.get() => m,
|
||||
lateout("r3") abs_d,
|
||||
options(pure, nomem, nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
(d, m, abs_d)
|
||||
}
|
||||
|
||||
/// (`swi 0x08`) Square root of an integer value.
|
||||
///
|
||||
/// To obtain as much fraction as possible, shift the input left by 2N bits to
|
||||
/// get an output that is left shifted by N bits.
|
||||
/// * sqrt(2) => 0
|
||||
/// * sqrt(2 << 30) => 1.41421 << 15
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub fn Sqrt(number: u32) -> u16 {
|
||||
let output: u32;
|
||||
unsafe {
|
||||
asm!("swi 0x08",
|
||||
inlateout("r0") number => output,
|
||||
out("r1") _,
|
||||
out("r3") _,
|
||||
options(pure, nomem, nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
output as u16
|
||||
}
|
||||
|
||||
/// (`swi 0x09`) Arc tangent
|
||||
///
|
||||
/// The input and output have 14 fractional bits.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub fn ArcTan(tan: i16) -> i16 {
|
||||
let output;
|
||||
unsafe {
|
||||
asm!("swi 0x09",
|
||||
inlateout("r0") tan => output,
|
||||
out("r1") _,
|
||||
out("r3") _,
|
||||
options(pure, nomem, nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// (`swi 0x0A`) Arc tangent 2
|
||||
///
|
||||
/// * The inputs have 14 fractional bits.
|
||||
/// * The output range is `0 ..= u16::MAX`, reprisenting a portion of 2 PI.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub fn ArcTan2(x: i16, y: i16) -> u16 {
|
||||
let output;
|
||||
unsafe {
|
||||
asm!("swi 0x0A",
|
||||
inlateout("r0") x => output,
|
||||
in("r1") y,
|
||||
out("r3") _,
|
||||
options(pure, nomem, nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// (`swi 0x0B`) Quickly copy/fill some memory.
|
||||
///
|
||||
/// * `src`: points to either `u16` or `u32` data.
|
||||
/// * `dst`: points to the same type of data.
|
||||
/// * `len_mode`: bitfield value:
|
||||
/// * bits 0 ..= 20: the number of elements to copy/fill.
|
||||
/// * bit 24: enable for fill, otherwise this is a copy.
|
||||
/// * bit 26: enable for `u32` at a time, otherwise this uses `u16` at a time.
|
||||
///
|
||||
/// All pointers must be aligned to the appropriate type, and also valid for the
|
||||
/// appropriate element count.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn CpuSet(src: *const core::ffi::c_void, dst: *mut core::ffi::c_void, len_mode: u32) {
|
||||
asm!("swi 0x0B",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
in("r2") len_mode,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x0C`) Quickly copy/fill some memory (most faster!)
|
||||
///
|
||||
/// * `src` points to the data source.
|
||||
/// * `dst` points to the data destination.
|
||||
/// * `len_mode`: bitfield value:
|
||||
/// * bits 0 ..= 20: the number of `u32` to copy/fill.
|
||||
/// * bit 24: enable for fill, otherwise this is a copy.
|
||||
///
|
||||
/// All pointers must be aligned. The length must be a multiple of 8.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn CpuFastSet(src: *const u32, dst: *mut u32, len_mode: u32) {
|
||||
asm!("swi 0x0C",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
in("r2") len_mode,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BgAffineSetSrc {
|
||||
/// 8-bit fraction
|
||||
pub origin_center_x: i32,
|
||||
/// 8-bit fraction
|
||||
pub origin_center_y: i32,
|
||||
pub display_center_x: i16,
|
||||
pub display_center_y: i16,
|
||||
/// 8-bit fraction
|
||||
pub scale_ratio_x: i16,
|
||||
/// 8-bit fraction
|
||||
pub scale_ratio_y: i16,
|
||||
/// 8-bit fraction, range 0 to u16::MAX
|
||||
pub angle_of_rotation: u16,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct BgAffineSetDst {
|
||||
pub pa: i16,
|
||||
pub pb: i16,
|
||||
pub pc: i16,
|
||||
pub pd: i16,
|
||||
pub start_x_coordinate: i32,
|
||||
pub start_y_coordinate: i32,
|
||||
}
|
||||
|
||||
/// (`swi 0x0E`) Calculates BG affine data.
|
||||
///
|
||||
/// * `src`: Points to the start of a slice of [`BgAffineSetSrc`]
|
||||
/// * `dst`: Points to the start of a slice of [`BgAffineSetDst`]
|
||||
/// * `count`: The number of elements to process from `src` to `dst`.
|
||||
///
|
||||
/// Both pointers must be aligned and valid for the length given.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn BgAffineSet(src: *const BgAffineSetSrc, dst: *mut BgAffineSetDst, count: usize) {
|
||||
asm!("swi 0x0E",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
in("r2") count,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ObjAffineSetSrc {
|
||||
/// 8-bit fraction
|
||||
pub scale_ratio_x: i16,
|
||||
/// 8-bit fraction
|
||||
pub scale_ratio_y: i16,
|
||||
/// 8-bit fraction, range 0 to u16::MAX
|
||||
pub angle: u16,
|
||||
}
|
||||
|
||||
/// (`swi 0x0F`) Calculates OBJ affine data.
|
||||
///
|
||||
/// Unlike with [`BgAffineSet`], this can optionally write the output data
|
||||
/// directly into OAM (see below).
|
||||
///
|
||||
/// * `src`: points to the start of a slice of [`ObjAffineSetSrc`] values.
|
||||
/// * `dst`: points to the start of the output location (`pa`).
|
||||
/// * `count`: The number of `src` values to process to `dst`.
|
||||
/// * `out_param_offset`: the number of bytes between *each field* of the output
|
||||
/// data.
|
||||
/// * Specify 2 if you want to output to an `[i16; 4]` or similar.
|
||||
/// * Specify 8 if you want to output directly to OAM.
|
||||
///
|
||||
/// The pointers must be valid for the count given, and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn ObjAffineSet(
|
||||
src: *const ObjAffineSetSrc,
|
||||
dst: *mut i16,
|
||||
count: usize,
|
||||
out_param_offset: usize,
|
||||
) {
|
||||
asm!("swi 0x0F",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
in("r2") count,
|
||||
in("r3") out_param_offset,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct UnpackInfo {
|
||||
pub source_data_len_bytes: u16,
|
||||
/// Supports 1, 2, 4, or 8 bit source elements.
|
||||
pub source_unit_bit_width: u8,
|
||||
/// Supports 1, 2, 4, 8, 16, or 32 destination elements.
|
||||
pub destination_unit_bit_width: u8,
|
||||
/// This field combines two purposes:
|
||||
/// * bits 0 ..= 30: this value is added to all non-zero source units.
|
||||
/// * bit 31: if this is set, add the above to all zero source units.
|
||||
pub data_offset: u32,
|
||||
}
|
||||
|
||||
/// (`swi 0x10`) Used to undo bit packing.
|
||||
///
|
||||
/// * `src`: The start of the source bytes.
|
||||
/// * `dst`: The start of the destination.
|
||||
/// * `info`: Describes the unpacking to perform.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn BitUnPack(src: *const u8, dst: *mut u32, info: &UnpackInfo) {
|
||||
asm!("swi 0x10",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
in("r2") info,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x11`) LZ77 Decompression with 8-bit output.
|
||||
///
|
||||
/// Arguments
|
||||
/// * `src`: pointer to the source region. The source region is prefixed with a
|
||||
/// `u32` bitfield value that describes the decompression to perform. It's
|
||||
/// then followed by the byte sequence to decompress.
|
||||
/// * Prefix value: `output_data_size << 8 | (1 << 4) | (0)`
|
||||
/// * Flags: 1 byte that specifies the types of the next 8 blocks (MSB to
|
||||
/// LSB).
|
||||
/// * Blocks:
|
||||
/// * (0) Literal: Copy 1 byte from the source to the output.
|
||||
/// * (1) Back Reference: Repeat `N+3` bytes from `BACK+1` bytes earlier in
|
||||
/// the output. This uses the next two bytes from the source to describe
|
||||
/// the back reference:
|
||||
/// * first byte bits 0 ..= 3: most significant bits of `BACK`
|
||||
/// * first byte bits 4 ..= 7: `N`
|
||||
/// * second byte: least significant bits of `BACK`
|
||||
/// * (So each `N` is 3 bits, and each `BACK` is 12 bits.)
|
||||
/// * After 8 blocks there's another flag and then another 8 blocks.
|
||||
/// * The overall size of the source data should be a multiple of 4 (pad with
|
||||
/// 0 as necessary).
|
||||
/// * `dst`: pointer to the destination region.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn LZ77UnCompReadNormalWrite8bit(src: *const u32, dst: *mut u8) {
|
||||
asm!("swi 0x11",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x12`) LZ77 Decompression with 16-bit output.
|
||||
///
|
||||
/// This is largely as per [`LZ77UnCompReadNormalWrite8bit`], but each output is
|
||||
/// 16-bits, which means that `BACK` values of 0 will corrupt the process. This
|
||||
/// puts a small constraint on the data compressor, but doesn't really affect
|
||||
/// you when you're using this function to decompress some already-compressed data.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn LZ77UnCompReadNormalWrite16bit(src: *const u32, dst: *mut u16) {
|
||||
asm!("swi 0x12",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x13`) Decompresses Huffman-encoded data.
|
||||
///
|
||||
/// * `src`: The source buffer. There's a `u32` header, a huffman tree, and then
|
||||
/// a compressed bitstream.
|
||||
/// * header (4 bytes): `(output_byte_count << 8) | (2 << 4) |
|
||||
/// data_unit_bit_size`, the output bit size per data unit can be 4 or 8.
|
||||
/// * tree size (1 byte): the number of bytes in the tree table.
|
||||
/// * tree table (up to 255 bytes): a list of 8-bit nodes, starting with the
|
||||
/// root node.
|
||||
/// * root node and non-data child nodes (1 byte):
|
||||
/// * bits 0 ..= 5: offset to next child node.
|
||||
/// * next_child0: (CurrentAddr AND NOT 1)+Offset*2+2
|
||||
/// * next_child1: as above +1
|
||||
/// * bit 6: node1 end flag (1 = next node is data)
|
||||
/// * bit 7: node0 end flag (1 = next node is data)
|
||||
/// * data nodes (1 byte):
|
||||
/// * the literal value to output. If the output unit size is less than 8
|
||||
/// bits at a time the upper bits of the literal should be 0.
|
||||
/// * compressed bitstream (stored as a series of `u32` values). The node bits
|
||||
/// are stored in each `u32` starting from the high bit.
|
||||
/// * `dst`: The output buffer.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn HuffUnCompReadNormal(src: *const u32, dst: *mut u32) {
|
||||
asm!("swi 0x13",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x14`) Expands run-length compressed data, outputting as 8-bit units.
|
||||
///
|
||||
/// * `src`: The source buffer. There's a `u32` header, and then a loop of
|
||||
/// "flag" and then "data" bytes until the end of the stream.
|
||||
/// * header (4 bytes): `(output_byte_count << 8) | (3 << 4) | 0`
|
||||
/// * flag byte:
|
||||
/// * bits 0 ..= 6: expanded data length, uncompressed N-1, compressed N-3.
|
||||
/// * bit 7: 0=uncompressed, 1=compressed
|
||||
/// * data byte: N uncompressed bytes or 1 compressed byte repeated N times.
|
||||
/// * `dst`: The output buffer.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn RLUnCompReadNormalWrite8bit(src: *const u32, dst: *mut u8) {
|
||||
asm!("swi 0x14",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x15`) Expands run-length compressed data, outputting as 16-bit units.
|
||||
///
|
||||
/// This is like [`RLUnCompReadNormalWrite8bit`] but outputs in 16-bit units, so
|
||||
/// it's suitable for use with VRAM.
|
||||
///
|
||||
/// All pointers must be valid for the correct memory spans and aligned.
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn RLUnCompReadNormalWrite16bit(src: *const u32, dst: *mut u16) {
|
||||
asm!("swi 0x15",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x16`) Performs an "unfilter" on 8-bit data units.
|
||||
///
|
||||
/// An unfiltering converts a starting value and a series of delta values into
|
||||
/// the appropriate totals.
|
||||
/// * Filtered: 10, +1, +1, +1, +1, +5, +5, ...
|
||||
/// * Unfiltered: 10, 11, 12, 13, 14, 19, 24, ...
|
||||
///
|
||||
/// This is not itself a compression technique, but it's far easier to compress
|
||||
/// the filtered form of data in some cases, so this is often used in
|
||||
/// *combination* with other compression techniques.
|
||||
///
|
||||
/// Arguments
|
||||
/// * `src`: pointer to the source region. The source region is prefixed with a
|
||||
/// `u32` bitfield value that describes the unfiltering to perform. It's then
|
||||
/// followed by the bytes to unfilter.
|
||||
/// * Prefix value: `element_count << 8 | (8 << 4) | (1)`
|
||||
/// * `dst`: pointer to the destination region.
|
||||
///
|
||||
/// Note that, because this uses 8-bit writes, it cannot output correctly to
|
||||
/// VRAM.
|
||||
///
|
||||
/// The source pointer must be aligned to 4 (the header is read as a `u32`), and
|
||||
/// both pointers must be valid for the correct span:
|
||||
/// * `src`: `element_count` + 4 bytes
|
||||
/// * `dst`: `element_count` bytes
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn Diff8bitUnFilterWrite8bit(src: *const u8, dst: *mut u32) {
|
||||
asm!("swi 0x16",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x17`) Performs an "unfilter" on 8-bit data units, using 16-bit
|
||||
/// output.
|
||||
///
|
||||
/// This is *very close* to [`Diff8bitUnFilterWrite8bit`] except that the output
|
||||
/// is 16-bits per element.
|
||||
///
|
||||
/// Arguments
|
||||
/// * `src`: pointer to the source region. The source region is prefixed with a
|
||||
/// `u32` bitfield value that describes the unfiltering to perform. It's then
|
||||
/// followed by the bytes to unfilter.
|
||||
/// * Prefix value: `element_count << 8 | (8 << 4) | (1)`
|
||||
/// * `dst`: pointer to the destination region.
|
||||
///
|
||||
/// Because this outputs with 16-bit writes, it is suitable for use with VRAM.
|
||||
///
|
||||
/// The source pointer must be aligned to 4 (the header is read as a `u32`), and
|
||||
/// both pointers must be valid for the correct span:
|
||||
/// * `src`: `element_count` + 4 bytes
|
||||
/// * `dst`: `element_count` * 2 bytes
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn Diff8bitUnFilterWrite16bit(src: *const u8, dst: *mut u16) {
|
||||
asm!("swi 0x17",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
/// (`swi 0x18`) Performs an "unfilter" on 16-bit data units.
|
||||
///
|
||||
/// This is *very close* to [`Diff8bitUnFilterWrite8bit`] except that the output
|
||||
/// is 16-bits per element and the prefix is different.
|
||||
///
|
||||
/// Arguments
|
||||
/// * `src`: pointer to the source region. The source region is prefixed with a
|
||||
/// `u32` bitfield value that describes the unfiltering to perform. It's then
|
||||
/// followed by the bytes to unfilter.
|
||||
/// * Prefix value: `element_count << 8 | (8 << 4) | (2)`
|
||||
/// * `dst`: pointer to the destination region.
|
||||
///
|
||||
/// Because this outputs with 16-bit writes, it is suitable for use with VRAM.
|
||||
///
|
||||
/// The source pointer must be aligned to 4 (the header is read as a `u32`), and
|
||||
/// both pointers must be valid for the correct span:
|
||||
/// * `src`: (`element_count` * 2) + 4 bytes
|
||||
/// * `dst`: `element_count` * 2 bytes
|
||||
#[inline]
|
||||
#[instruction_set(arm::t32)]
|
||||
pub unsafe fn Diff16bitUnFilter(src: *const u16, dst: *mut u16) {
|
||||
asm!("swi 0x18",
|
||||
in("r0") src,
|
||||
in("r1") dst,
|
||||
out("r3") _,
|
||||
options(nostack, preserves_flags),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: MidiKey2Freq (1F)
|
||||
|
||||
// TODO: SoundBias (19)
|
||||
|
||||
// TODO: SoundChannelClear (1E)
|
||||
|
||||
// TODO: SoundDriverInit (1A)
|
||||
|
||||
// TODO: SoundDriverMain (1C)
|
||||
|
||||
// TODO: SoundDriverMode (1B)
|
||||
|
||||
// TODO: SoundDriverVSync (1D)
|
||||
|
||||
// TODO: MultiBoot (25)
|
||||
|
||||
// TODO: SoundDriverVSyncOff (28)
|
||||
|
||||
// TODO: SoundDriverVSyncOn (29)
|
||||
|
|
124
src/debugging.rs
124
src/debugging.rs
|
@ -1,124 +0,0 @@
|
|||
#![allow(unused_macros)]
|
||||
|
||||
//! Special utilities for debugging ROMs on various emulators.
|
||||
//!
|
||||
//! This is the underlying implementation behind the various print macros in
|
||||
//! the gba crate. It currently supports the latest versions of mGBA and NO$GBA.
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
sync::{InitOnce, RawMutex, Static},
|
||||
};
|
||||
use core::fmt::{Arguments, Error};
|
||||
use voladdress::*;
|
||||
|
||||
pub mod mgba;
|
||||
pub mod nocash;
|
||||
|
||||
/// A cross-emulator debug level.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DebugLevel {
|
||||
/// This causes the emulator (or debug interface) to halt!
|
||||
Fatal,
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Debug,
|
||||
}
|
||||
|
||||
/// An interface for debugging features.
|
||||
pub trait DebugInterface {
|
||||
/// Whether debugging is enabled.
|
||||
fn device_attached(&self) -> bool;
|
||||
|
||||
/// Prints a debug message to the emulator.
|
||||
fn debug_print(&self, debug: DebugLevel, args: &Arguments<'_>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// An lock to ensure interface changes go smoothly.
|
||||
static LOCK: RawMutex = RawMutex::new();
|
||||
/// An optimization to allow us to short circuit debugging early when there is no interface.
|
||||
static NO_DEBUG: Static<bool> = Static::new(false);
|
||||
/// The debugging interface in use.
|
||||
static INTERFACE: Static<Option<&'static dyn DebugInterface>> = Static::new(None);
|
||||
/// Debug interface detection only happens once.
|
||||
static DETECT_ONCE: InitOnce<()> = InitOnce::new();
|
||||
|
||||
/// Sets the debug interface in use manually.
|
||||
pub fn set_debug_interface(interface: &'static dyn DebugInterface) {
|
||||
let _lock = LOCK.lock();
|
||||
INTERFACE.write(Some(interface));
|
||||
NO_DEBUG.write(false);
|
||||
}
|
||||
|
||||
/// Disables debugging.
|
||||
pub fn set_debug_disabled() {
|
||||
let _lock = LOCK.lock();
|
||||
INTERFACE.write(None);
|
||||
NO_DEBUG.write(true);
|
||||
}
|
||||
|
||||
/// Prints a line to the debug interface, if there is any.
|
||||
#[inline(never)]
|
||||
pub fn debug_print(debug: DebugLevel, args: &Arguments<'_>) -> Result<(), Error> {
|
||||
if let Some(interface) = get_debug_interface() {
|
||||
interface.debug_print(debug, args)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current active debugging interface if there is one, or `None`
|
||||
/// if one isn't attached.
|
||||
#[inline(never)]
|
||||
pub fn get_debug_interface() -> Option<&'static dyn DebugInterface> {
|
||||
let mut interface = INTERFACE.read();
|
||||
if interface.is_none() {
|
||||
DETECT_ONCE.get(|| {
|
||||
let mut new_value: Option<&'static dyn DebugInterface> = None;
|
||||
if mgba::detect() {
|
||||
new_value = Some(&mgba::MGBADebugInterface);
|
||||
} else if nocash::detect() {
|
||||
new_value = Some(&nocash::NoCashDebugInterface);
|
||||
}
|
||||
if new_value.is_some() {
|
||||
INTERFACE.write(new_value);
|
||||
interface = new_value;
|
||||
}
|
||||
});
|
||||
}
|
||||
interface
|
||||
}
|
||||
|
||||
/// Whether debugging is disabled.
|
||||
///
|
||||
/// This should only be relied on for correctness. If this is false, there is no
|
||||
/// possible way any debugging calls will succeed, and it is better to simply
|
||||
/// skip the entire routine.
|
||||
#[inline(always)]
|
||||
pub fn is_debugging_disabled() -> bool {
|
||||
NO_DEBUG.read()
|
||||
}
|
||||
|
||||
/// Crashes the program by disabling interrupts and entering an infinite loop.
|
||||
///
|
||||
/// This is used to implement fatal errors outside of mGBA.
|
||||
#[inline(never)]
|
||||
pub fn crash() -> ! {
|
||||
unsafe {
|
||||
IME.write(false);
|
||||
// Stop all ongoing DMAs just in case.
|
||||
DMA0CNT_H.write(DmaControl::new());
|
||||
DMA1CNT_H.write(DmaControl::new());
|
||||
DMA2CNT_H.write(DmaControl::new());
|
||||
DMA3CNT_H.write(DmaControl::new());
|
||||
|
||||
// Writes the halt call back to memory
|
||||
//
|
||||
// we use an infinite loop in RAM just to make sure removing the
|
||||
// Game Pak doesn't break this crash loop.
|
||||
let target = VolAddress::<u16, Unsafe, Unsafe>::new(0x03000000);
|
||||
target.write(0xE7FE); // thumb assembly instruction: `loop: b loop`
|
||||
core::mem::transmute::<_, extern "C" fn() -> !>(0x03000001)()
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
//! Special utils for if you're running on the mGBA emulator.
|
||||
//!
|
||||
//! Note that this assumes that you're using the very latest version (0.7). If
|
||||
//! you've got some older version of things there might be any number of
|
||||
//! differences or problems.
|
||||
|
||||
use super::{DebugInterface, DebugLevel};
|
||||
use crate::sync::InitOnce;
|
||||
use core::fmt::{Arguments, Write};
|
||||
use voladdress::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum MGBADebugLevel {
|
||||
/// Warning! This causes the emulator to halt emulation!
|
||||
Fatal = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Debug = 4,
|
||||
}
|
||||
|
||||
// MGBADebug related addresses.
|
||||
const ENABLE_ADDRESS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x4fff780) };
|
||||
const ENABLE_ADDRESS_INPUT: u16 = 0xC0DE;
|
||||
const ENABLE_ADDRESS_OUTPUT: u16 = 0x1DEA;
|
||||
|
||||
const OUTPUT_BLOCK: VolBlock<u8, Safe, Safe, 256> = unsafe { VolBlock::new(0x4fff600) };
|
||||
|
||||
const SEND_ADDRESS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x4fff700) };
|
||||
const SEND_FLAG: u16 = 0x100;
|
||||
|
||||
// Only enable MGBA debugging once.
|
||||
static MGBA_DEBUGGING: InitOnce<bool> = InitOnce::new();
|
||||
|
||||
/// Returns whether we are running in mGBA.
|
||||
#[inline(never)]
|
||||
pub fn detect() -> bool {
|
||||
*MGBA_DEBUGGING.get(|| {
|
||||
ENABLE_ADDRESS.write(ENABLE_ADDRESS_INPUT);
|
||||
ENABLE_ADDRESS.read() == ENABLE_ADDRESS_OUTPUT
|
||||
})
|
||||
}
|
||||
|
||||
/// Allows writing to the `mGBA` debug output.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct MGBADebug {
|
||||
bytes_written: u8,
|
||||
}
|
||||
impl MGBADebug {
|
||||
/// Gives a new MGBADebug, if running within `mGBA`
|
||||
///
|
||||
/// # Fails
|
||||
///
|
||||
/// If you're not running in the `mGBA` emulator.
|
||||
pub fn new() -> Option<Self> {
|
||||
if detect() {
|
||||
Some(MGBADebug { bytes_written: 0 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Once output is buffered you must send it out with a level.
|
||||
///
|
||||
/// If the `Fatal` level is selected, the buffer is sent out as `Error`
|
||||
/// followed by a blank message being sent as `Error`. This is done because
|
||||
/// the `Fatal` message appears in a popup without showing up in the log, so
|
||||
/// it might accidentally be discarded.
|
||||
pub fn send(&mut self, level: MGBADebugLevel) {
|
||||
if level == MGBADebugLevel::Fatal {
|
||||
// Note(Lokathor): A Fatal send causes the emulator to halt!
|
||||
SEND_ADDRESS.write(SEND_FLAG | MGBADebugLevel::Fatal as u16);
|
||||
} else {
|
||||
SEND_ADDRESS.write(SEND_FLAG | level as u16);
|
||||
self.bytes_written = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Write for MGBADebug {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
|
||||
unsafe {
|
||||
let mut current = OUTPUT_BLOCK.index(self.bytes_written as usize);
|
||||
let mut str_iter = s.bytes();
|
||||
while self.bytes_written < 255 {
|
||||
match str_iter.next() {
|
||||
Some(byte) => {
|
||||
current.write(byte);
|
||||
current = current.offset(1);
|
||||
self.bytes_written += 1;
|
||||
}
|
||||
None => return Ok(()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`DebugInterface`] for MGBA.
|
||||
pub struct MGBADebugInterface;
|
||||
impl DebugInterface for MGBADebugInterface {
|
||||
fn device_attached(&self) -> bool {
|
||||
detect()
|
||||
}
|
||||
|
||||
fn debug_print(&self, debug: DebugLevel, args: &Arguments<'_>) -> Result<(), core::fmt::Error> {
|
||||
if let Some(mut out) = MGBADebug::new() {
|
||||
write!(out, "{}", args)?;
|
||||
out.send(match debug {
|
||||
DebugLevel::Fatal => MGBADebugLevel::Fatal,
|
||||
DebugLevel::Error => MGBADebugLevel::Error,
|
||||
DebugLevel::Warning => MGBADebugLevel::Warning,
|
||||
DebugLevel::Info => MGBADebugLevel::Info,
|
||||
DebugLevel::Debug => MGBADebugLevel::Debug,
|
||||
});
|
||||
if debug == DebugLevel::Fatal {
|
||||
super::crash();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
//! Special utils for if you're running on the NO$GBA emulator.
|
||||
//!
|
||||
//! Note that this assumes that you're using the very latest version (3.03). If
|
||||
//! you've got some older version of things there might be any number of
|
||||
//! differences or problems.
|
||||
|
||||
use super::{DebugInterface, DebugLevel};
|
||||
use crate::prelude::InitOnce;
|
||||
use core::fmt::{Arguments, Write};
|
||||
use voladdress::*;
|
||||
|
||||
const CHAR_OUT: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x04FFFA1C) };
|
||||
const SIGNATURE_ADDR: VolBlock<u8, Safe, Safe, 16> = unsafe { VolBlock::new(0x04FFFA00) };
|
||||
|
||||
const SIGNATURE: [u8; 7] = *b"no$gba ";
|
||||
static NO_CASH_DEBUGGING: InitOnce<bool> = InitOnce::new();
|
||||
|
||||
/// Returns whether we are running in `NO$GBA`.
|
||||
#[inline(never)]
|
||||
pub fn detect() -> bool {
|
||||
*NO_CASH_DEBUGGING.get(|| {
|
||||
for i in 0..7 {
|
||||
if SIGNATURE_ADDR.index(i).read() != SIGNATURE[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
/// Allows writing to the `NO$GBA` debug output.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct NoCashDebug(());
|
||||
impl NoCashDebug {
|
||||
/// Gives a new NoCashDebug, if running within `NO$GBA`
|
||||
///
|
||||
/// # Fails
|
||||
///
|
||||
/// If you're not running in the `NO$GBA` emulator.
|
||||
pub fn new() -> Option<Self> {
|
||||
if detect() {
|
||||
Some(NoCashDebug(()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl core::fmt::Write for NoCashDebug {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
|
||||
for b in s.bytes() {
|
||||
CHAR_OUT.write(b);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`DebugInterface`] for `NO$GBA`.
|
||||
pub struct NoCashDebugInterface;
|
||||
impl DebugInterface for NoCashDebugInterface {
|
||||
fn device_attached(&self) -> bool {
|
||||
detect()
|
||||
}
|
||||
|
||||
fn debug_print(&self, debug: DebugLevel, args: &Arguments<'_>) -> Result<(), core::fmt::Error> {
|
||||
if let Some(mut out) = NoCashDebug::new() {
|
||||
write!(out, "User: [{:?}] {}\n", debug, args)?;
|
||||
if debug == DebugLevel::Fatal {
|
||||
super::crash();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
58
src/dma.rs
Normal file
58
src/dma.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use crate::macros::{pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum DestAddrControl {
|
||||
#[default]
|
||||
Increment = 0 << 5,
|
||||
Decrement = 1 << 5,
|
||||
Fixed = 2 << 5,
|
||||
IncReload = 3 << 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum SrcAddrControl {
|
||||
#[default]
|
||||
Increment = 0 << 7,
|
||||
Decrement = 1 << 7,
|
||||
Fixed = 2 << 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum DmaStartTime {
|
||||
#[default]
|
||||
Immediate = 0 << 12,
|
||||
VBlank = 1 << 12,
|
||||
HBlank = 2 << 12,
|
||||
Special = 3 << 12,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct DmaControl(u16);
|
||||
impl DmaControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_enum_field!(
|
||||
5 - 6: DestAddrControl,
|
||||
dest_addr_control,
|
||||
with_dest_addr_control
|
||||
);
|
||||
u16_enum_field!(
|
||||
7 - 8: SrcAddrControl,
|
||||
src_addr_control,
|
||||
with_src_addr_control
|
||||
);
|
||||
u16_bool_field!(9, repeat, with_repeat);
|
||||
u16_bool_field!(10, transfer_32bit, with_transfer_32bit);
|
||||
u16_enum_field!(12 - 13: DmaStartTime, start_time, with_start_time);
|
||||
u16_bool_field!(14, irq_after, with_irq_after);
|
||||
u16_bool_field!(15, enabled, with_enabled);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
266
src/gba_cell.rs
Normal file
266
src/gba_cell.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
//! A GBA-specific "cell" type that allows safe global mutable data.
|
||||
//!
|
||||
//! Most importantly, data stored in a [`GbaCell`] can be safely shared between
|
||||
//! the main program and the interrupt handler.
|
||||
//!
|
||||
//! All you have to do is declare a static `GbaCell`:
|
||||
//!
|
||||
//! ```
|
||||
//! static THE_COLOR: GbaCell<Color> = GbaCell::new(Color::new());
|
||||
//! ```
|
||||
//!
|
||||
//! And then you can use the [`read`](GbaCell::read) and
|
||||
//! [`write`](GbaCell::write) methods to interact with the data:
|
||||
//!
|
||||
//! ```
|
||||
//! # static THE_COLOR: GbaCell<Color> = GbaCell::new(Color::new());
|
||||
//! # let some_new_color = Color::default();
|
||||
//! let old_color = THE_COLOR.read();
|
||||
//!
|
||||
//! THE_COLOR.write(some_new_color);
|
||||
//! ```
|
||||
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
fmt::Debug,
|
||||
num::{NonZeroI16, NonZeroI32, NonZeroI8, NonZeroU16, NonZeroU32, NonZeroU8},
|
||||
panic::RefUnwindSafe,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
interrupts::IrqFn,
|
||||
keys::{KeyControl, KeyInput},
|
||||
video::Color,
|
||||
};
|
||||
|
||||
/// Reads from a [`GbaCell`].
|
||||
///
|
||||
/// See the GbaCell docs for info on why you'd use this.
|
||||
#[macro_export]
|
||||
macro_rules! gba_cell_read {
|
||||
($elem:ty, $cell_ref:expr) => {
|
||||
{
|
||||
// Note(Lokathor): We assign the expression to a binding to avoid
|
||||
// evaluating any side-effecting expressions more than once. Also, we give
|
||||
// a type to the binding to ensure that the `transmute_copy` works right.
|
||||
let the_cell: &GbaCell<$elem> = $cell_ref;
|
||||
let out: $elem = match (::core::mem::size_of::<$elem>(), ::core::mem::align_of::<$elem>()) {
|
||||
(4, 4) => unsafe {
|
||||
let val: u32;
|
||||
core::arch::asm!(
|
||||
"ldr {r}, [{addr}]",
|
||||
r = out(reg) val,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(readonly, preserves_flags, nostack)
|
||||
);
|
||||
core::mem::transmute_copy(&val)
|
||||
},
|
||||
(2, 2) => unsafe {
|
||||
let val: u16;
|
||||
core::arch::asm!(
|
||||
"ldrh {r}, [{addr}]",
|
||||
r = out(reg) val,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(readonly, preserves_flags, nostack)
|
||||
);
|
||||
core::mem::transmute_copy(&val)
|
||||
},
|
||||
(1, 1) => unsafe {
|
||||
let val: u8;
|
||||
core::arch::asm!(
|
||||
"ldrb {r}, [{addr}]",
|
||||
r = out(reg) val,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(readonly, preserves_flags, nostack)
|
||||
);
|
||||
core::mem::transmute_copy(&val)
|
||||
},
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
};
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes to a [`GbaCell`].
|
||||
///
|
||||
/// See the GbaCell docs for info on why you'd use this.
|
||||
#[macro_export]
|
||||
macro_rules! gba_cell_write {
|
||||
($elem:ty, $cell_ref:expr, $val:expr) => {
|
||||
{
|
||||
// Note(Lokathor): We assign the expression to a binding to avoid
|
||||
// evaluating any side-effecting expressions more than once. Also, we give
|
||||
// a type to the binding to ensure that the `transmute_copy` works right.
|
||||
let the_cell: &GbaCell<$elem> = $cell_ref;
|
||||
let val: $elem = $val;
|
||||
match (::core::mem::size_of::<$elem>(), ::core::mem::align_of::<$elem>()) {
|
||||
(4, 4) => unsafe {
|
||||
let u: u32 = core::mem::transmute_copy(&val);
|
||||
core::arch::asm!(
|
||||
"str {val}, [{addr}]",
|
||||
val = in(reg) u,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(preserves_flags, nostack)
|
||||
)
|
||||
},
|
||||
(2, 2) => unsafe {
|
||||
let u: u16 = core::mem::transmute_copy(&val);
|
||||
core::arch::asm!(
|
||||
"strh {val}, [{addr}]",
|
||||
val = in(reg) u,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(preserves_flags, nostack)
|
||||
)
|
||||
},
|
||||
(1, 1) => unsafe {
|
||||
let u: u8 = core::mem::transmute_copy(&val);
|
||||
core::arch::asm!(
|
||||
"strb {val}, [{addr}]",
|
||||
val = in(reg) u,
|
||||
addr = in(reg) the_cell.get_ptr(),
|
||||
options(preserves_flags, nostack)
|
||||
)
|
||||
},
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A GBA-specific wrapper around Rust's [`UnsafeCell`](core::cell::UnsafeCell)
|
||||
/// type.
|
||||
///
|
||||
/// Supports any data type that implements the [`GbaCellSafe`] marker trait.
|
||||
///
|
||||
/// ## Safety Logic
|
||||
///
|
||||
/// * LLVM thinks that ARMv4T only supports atomic operations via special atomic
|
||||
/// support library functions (which is basically true).
|
||||
/// * If you directly write an Acquire/load, Release/store, or a Relaxed op with
|
||||
/// a `compiler_fence`, then LLVM does generate correct code. However, it will
|
||||
/// have sub-optimal performance. LLVM will generate calls to the mythical
|
||||
/// atomic support library when it should just directly use an `ldr` or `str`
|
||||
/// instruction.
|
||||
/// * In response to this LLVM nonsense, the `GbaCell` type just uses inline
|
||||
/// assembly to perform all accesses to the contained data.
|
||||
/// * When LLVM sees inline assembly, it is forced to defensively act as if the
|
||||
/// inline assembly might have done *anything* legally possible using the
|
||||
/// pointer and value provided to the inline assembly. This includes that the
|
||||
/// inline assembly *might* call the atomic support library to access the
|
||||
/// pointer's data.
|
||||
/// * So LLVM has to treat the inline assembly as an atomic sync point.
|
||||
///
|
||||
/// ## Macro Access
|
||||
/// Normally you can just use the `read` and `write` methods. However, register
|
||||
/// allocation for the `read` and `write` assembly blocks is performed *before*
|
||||
/// the methods are inlined into the caller. This means that `read` and `write`
|
||||
/// will always use `r0` and `r1` directly, and several calls to `read` and
|
||||
/// `write` will will put a lot of pressure on those two registers.
|
||||
///
|
||||
/// To reduce register pressure you can use the [gba_cell_read] and
|
||||
/// [gba_cell_write] macros. These will expand into inline assembly within your
|
||||
/// own code that *then* assigns registers based on your local function's
|
||||
/// register usage. This can potentially give some (very small) performance
|
||||
/// gains, at the cost of some ergonomics within the code.
|
||||
#[repr(transparent)]
|
||||
pub struct GbaCell<T>(UnsafeCell<T>);
|
||||
impl<T> Debug for GbaCell<T>
|
||||
where
|
||||
T: GbaCellSafe + Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
<T as Debug>::fmt(&self.read(), f)
|
||||
}
|
||||
}
|
||||
unsafe impl<T> Send for GbaCell<T> {}
|
||||
unsafe impl<T> Sync for GbaCell<T> {}
|
||||
impl<T> RefUnwindSafe for GbaCell<T> {}
|
||||
impl<T> GbaCell<T>
|
||||
where
|
||||
T: GbaCellSafe,
|
||||
{
|
||||
/// Wraps a value in a new `GbaCell`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new(val: T) -> Self {
|
||||
Self(UnsafeCell::new(val))
|
||||
}
|
||||
/// Gets a pointer to the inner data.
|
||||
///
|
||||
/// The rules for this pointer work just like with [`UnsafeCell`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn get_ptr(&self) -> *mut T {
|
||||
self.0.get()
|
||||
}
|
||||
/// Reads the value.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn read(&self) -> T {
|
||||
gba_cell_read!(T, self)
|
||||
}
|
||||
/// Writes a new value.
|
||||
#[inline]
|
||||
pub fn write(&self, val: T) {
|
||||
gba_cell_write!(T, self, val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait bound for the methods of [`GbaCell`].
|
||||
///
|
||||
/// When a type implements this trait it indicates that the type can be loaded
|
||||
/// from a pointer in a single instruction. Also it can be stored to a pointer
|
||||
/// in a single instruction.
|
||||
///
|
||||
/// The exact pair of load/store instructions used will depend on the type's
|
||||
/// size (`ldr`/`str`, `ldrh`/`strh`, or `ldrb`/`strb`).
|
||||
///
|
||||
/// ## Safety
|
||||
/// The type must fit in a single register and have an alignment equal to its
|
||||
/// size. Generally that means it should be one of:
|
||||
///
|
||||
/// * an 8, 16, or 32 bit integer
|
||||
/// * a function pointer
|
||||
/// * a data pointer to a sized type
|
||||
/// * an optional non-null pointer (to function or sized data)
|
||||
/// * a `repr(transparent)` newtype over one of the above
|
||||
pub unsafe trait GbaCellSafe: Copy {}
|
||||
|
||||
// Note(Lokathor): It would be nice if this impl list could be kept sorted, but
|
||||
// it's not necessary to do so.
|
||||
|
||||
// Note(Lokathor): This list is very incomplete! It's just what I thought would
|
||||
// be most useful right away. More types (eg: other fn pointer types) should be
|
||||
// added as necessary.
|
||||
|
||||
unsafe impl GbaCellSafe for bool {}
|
||||
unsafe impl GbaCellSafe for char {}
|
||||
unsafe impl GbaCellSafe for Color {}
|
||||
unsafe impl GbaCellSafe for i16 {}
|
||||
unsafe impl GbaCellSafe for i32 {}
|
||||
unsafe impl GbaCellSafe for i8 {}
|
||||
unsafe impl GbaCellSafe for KeyInput {}
|
||||
unsafe impl GbaCellSafe for KeyControl {}
|
||||
unsafe impl GbaCellSafe for NonZeroI16 {}
|
||||
unsafe impl GbaCellSafe for NonZeroI32 {}
|
||||
unsafe impl GbaCellSafe for NonZeroI8 {}
|
||||
unsafe impl GbaCellSafe for NonZeroU16 {}
|
||||
unsafe impl GbaCellSafe for NonZeroU32 {}
|
||||
unsafe impl GbaCellSafe for NonZeroU8 {}
|
||||
unsafe impl GbaCellSafe for Option<bool> {}
|
||||
unsafe impl GbaCellSafe for Option<char> {}
|
||||
unsafe impl GbaCellSafe for Option<IrqFn> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroI16> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroI32> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroI8> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroU16> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroU32> {}
|
||||
unsafe impl GbaCellSafe for Option<NonZeroU8> {}
|
||||
unsafe impl GbaCellSafe for u16 {}
|
||||
unsafe impl GbaCellSafe for u32 {}
|
||||
unsafe impl GbaCellSafe for u8 {}
|
49
src/interrupts.rs
Normal file
49
src/interrupts.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::macros::{pub_const_fn_new_zeroed, u16_bool_field};
|
||||
|
||||
/// A function you want called during an interrupt.
|
||||
pub type IrqFn = unsafe extern "C" fn(u16);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct IrqBits(u16);
|
||||
impl IrqBits {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, vblank, with_vblank);
|
||||
u16_bool_field!(1, hblank, with_hblank);
|
||||
u16_bool_field!(2, vcounter, with_vcounter);
|
||||
u16_bool_field!(3, timer0, with_timer0);
|
||||
u16_bool_field!(4, timer1, with_timer1);
|
||||
u16_bool_field!(5, timer2, with_timer2);
|
||||
u16_bool_field!(6, timer3, with_timer3);
|
||||
u16_bool_field!(7, serial, with_serial);
|
||||
u16_bool_field!(8, dma0, with_dma0);
|
||||
u16_bool_field!(9, dma1, with_dma1);
|
||||
u16_bool_field!(10, dma2, with_dma2);
|
||||
u16_bool_field!(11, dma3, with_dma3);
|
||||
u16_bool_field!(12, keypad, with_keypad);
|
||||
u16_bool_field!(13, gamepak, with_gamepak);
|
||||
|
||||
pub const VBLANK: Self = Self::new().with_vblank(true);
|
||||
pub const HBLANK: Self = Self::new().with_hblank(true);
|
||||
pub const VCOUNTER: Self = Self::new().with_vcounter(true);
|
||||
pub const TIMER0: Self = Self::new().with_timer0(true);
|
||||
pub const TIMER1: Self = Self::new().with_timer1(true);
|
||||
pub const TIMER2: Self = Self::new().with_timer2(true);
|
||||
pub const TIMER3: Self = Self::new().with_timer3(true);
|
||||
pub const SERIAL: Self = Self::new().with_serial(true);
|
||||
pub const DMA0: Self = Self::new().with_dma0(true);
|
||||
pub const DMA1: Self = Self::new().with_dma1(true);
|
||||
pub const DMA2: Self = Self::new().with_dma2(true);
|
||||
pub const DMA3: Self = Self::new().with_dma3(true);
|
||||
pub const KEYPAD: Self = Self::new().with_keypad(true);
|
||||
pub const GAMEPAK: Self = Self::new().with_gamepak(true);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: might want to support bit ops. But it's not super important right now
|
||||
// since they can't be implented as const traits yet anyway.
|
56
src/keys.rs
Normal file
56
src/keys.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::macros::{pub_const_fn_new_zeroed, u16_bool_field};
|
||||
|
||||
/// Key input data.
|
||||
///
|
||||
/// Internally this uses a "low-active" convention: A bit is 0 when the key is
|
||||
/// *pressed*, and 1 when a key is *released*. The accessor methods handle this
|
||||
/// automatically, you only need to consider this fact if you want to use the
|
||||
/// raw bit pattern for something (eg: as a randomness source).
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyInput(u16);
|
||||
impl KeyInput {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(inverted 0, a, with_a);
|
||||
u16_bool_field!(inverted 1, b, with_b);
|
||||
u16_bool_field!(inverted 2, select, with_select);
|
||||
u16_bool_field!(inverted 3, start, with_start);
|
||||
u16_bool_field!(inverted 4, right, with_right);
|
||||
u16_bool_field!(inverted 5, left, with_left);
|
||||
u16_bool_field!(inverted 6, up, with_up);
|
||||
u16_bool_field!(inverted 7, down, with_down);
|
||||
u16_bool_field!(inverted 8, r, with_r);
|
||||
u16_bool_field!(inverted 9, l, with_l);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyControl(u16);
|
||||
impl KeyControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, a, with_a);
|
||||
u16_bool_field!(1, b, with_b);
|
||||
u16_bool_field!(2, select, with_select);
|
||||
u16_bool_field!(3, start, with_start);
|
||||
u16_bool_field!(4, right, with_right);
|
||||
u16_bool_field!(5, left, with_left);
|
||||
u16_bool_field!(6, up, with_up);
|
||||
u16_bool_field!(7, down, with_down);
|
||||
u16_bool_field!(8, r, with_r);
|
||||
u16_bool_field!(9, l, with_l);
|
||||
|
||||
u16_bool_field!(14, irq_enabled, with_irq_enabled);
|
||||
u16_bool_field!(15, irq_all, with_irq_all);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
193
src/lib.rs
193
src/lib.rs
|
@ -1,184 +1,19 @@
|
|||
#![no_std]
|
||||
#![feature(asm_sym)]
|
||||
#![feature(asm_const)]
|
||||
#![feature(isa_attribute)]
|
||||
#![feature(naked_functions)]
|
||||
|
||||
//! This crate helps you write GBA ROMs.
|
||||
//!
|
||||
//! ## Safety
|
||||
//!
|
||||
//! This crate takes *minimal* precautions to avoid GBA specific code from being
|
||||
//! run on a standard desktop by accident by using `#[cfg(target_arch = "arm")]`
|
||||
//! in appropriate places. However, there are obviously many other ARM devices
|
||||
//! in the world. If you actually run the GBA specific code on something that
|
||||
//! isn't a GBA, then that's your fault.
|
||||
//!
|
||||
//! ## Docs.rs
|
||||
//!
|
||||
//! The docs on docs.rs are generated for the `thumbv6m-none-eabi` target
|
||||
//! because the docs.rs docker image isn't currently able to use the
|
||||
//! `-Zbuild-std=core` ability of cargo. Instead, we have it just build using a
|
||||
//! "close enough" Tier 2 target.
|
||||
//!
|
||||
//! When building your actual GBA games you should of course use the
|
||||
//! `thumbv4t-none-eabi` target.
|
||||
mod macros;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::mmio_types::*;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::bios::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::debugging::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::mmio_addresses::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::random::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::save::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::sync::*;
|
||||
}
|
||||
|
||||
pub mod mmio_types;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod mmio_addresses;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod asm_runtime;
|
||||
pub mod bios;
|
||||
|
||||
pub mod art;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod macros;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod sync;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod save;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod debugging;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod random;
|
||||
/*
|
||||
extern "C" {
|
||||
/// This marks the end of the `.data` and `.bss` sections in IWRAM.
|
||||
///
|
||||
/// Memory in IWRAM _before_ this location is not free to use, you'll trash
|
||||
/// your globals and stuff. Memory here or after is freely available for use
|
||||
/// (careful that you don't run into your own stack of course).
|
||||
///
|
||||
/// The actual value is unimportant, you just want to use the _address of_
|
||||
/// this location as the start of your IWRAM usage.
|
||||
pub static __bss_end: u8;
|
||||
}
|
||||
|
||||
TODO: math module for math functions you probably want on the GBA
|
||||
|
||||
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||
// TODO: const this? Requires const if
|
||||
if denom == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { divrem_u32_unchecked(numer, denom) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs divide and remainder, no check for 0 division.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If you call this with a denominator of 0 the result is implementation
|
||||
/// defined (not literal UB) including but not limited to: an infinite loop,
|
||||
/// panic on overflow, or incorrect output.
|
||||
pub unsafe fn divrem_u32_unchecked(numer: u32, denom: u32) -> (u32, u32) {
|
||||
// TODO: const this? Requires const if
|
||||
if (numer >> 5) < denom {
|
||||
divrem_u32_simple(numer, denom)
|
||||
} else {
|
||||
divrem_u32_non_restoring(numer, denom)
|
||||
}
|
||||
}
|
||||
|
||||
/// The simplest form of division. If N is too much larger than D this will be
|
||||
/// extremely slow. If N is close enough to D then it will likely be faster than
|
||||
/// the non_restoring form.
|
||||
fn divrem_u32_simple(mut numer: u32, denom: u32) -> (u32, u32) {
|
||||
// TODO: const this? Requires const if
|
||||
let mut quot = 0;
|
||||
while numer >= denom {
|
||||
numer -= denom;
|
||||
quot += 1;
|
||||
}
|
||||
(quot, numer)
|
||||
}
|
||||
|
||||
/// Takes a fixed quantity of time based on the bit width of the number (in this
|
||||
/// case 32).
|
||||
fn divrem_u32_non_restoring(numer: u32, denom: u32) -> (u32, u32) {
|
||||
// TODO: const this? Requires const if
|
||||
let mut r: i64 = numer as i64;
|
||||
let d: i64 = (denom as i64) << 32;
|
||||
let mut q: u32 = 0;
|
||||
let mut i = 1 << 31;
|
||||
while i > 0 {
|
||||
if r >= 0 {
|
||||
q |= i;
|
||||
r = 2 * r - d;
|
||||
} else {
|
||||
r = 2 * r + d;
|
||||
}
|
||||
i >>= 1;
|
||||
}
|
||||
q -= !q;
|
||||
if r < 0 {
|
||||
q -= 1;
|
||||
r += d;
|
||||
}
|
||||
r >>= 32;
|
||||
// TODO: remove this once we've done more checks here.
|
||||
debug_assert!(r >= 0);
|
||||
debug_assert!(r <= core::u32::MAX as i64);
|
||||
(q, r as u32)
|
||||
}
|
||||
|
||||
/// Performs signed divide and remainder, gives None if dividing by 0 or
|
||||
/// computing `MIN/-1`
|
||||
pub fn divrem_i32(numer: i32, denom: i32) -> Option<(i32, i32)> {
|
||||
if denom == 0 || (numer == core::i32::MIN && denom == -1) {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { divrem_i32_unchecked(numer, denom) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs signed divide and remainder, no check for 0 division or `MIN/-1`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * If you call this with a denominator of 0 the result is implementation
|
||||
/// defined (not literal UB) including but not limited to: an infinite loop,
|
||||
/// panic on overflow, or incorrect output.
|
||||
/// * If you call this with `MIN/-1` you'll get a panic in debug or just `MIN`
|
||||
/// in release (which is incorrect), because of how twos-compliment works.
|
||||
pub unsafe fn divrem_i32_unchecked(numer: i32, denom: i32) -> (i32, i32) {
|
||||
// TODO: const this? Requires const if
|
||||
let unsigned_numer = numer.abs() as u32;
|
||||
let unsigned_denom = denom.abs() as u32;
|
||||
let opposite_sign = (numer ^ denom) < 0;
|
||||
let (udiv, urem) = if (numer >> 5) < denom {
|
||||
divrem_u32_simple(unsigned_numer, unsigned_denom)
|
||||
} else {
|
||||
divrem_u32_non_restoring(unsigned_numer, unsigned_denom)
|
||||
};
|
||||
match (opposite_sign, numer < 0) {
|
||||
(true, true) => (-(udiv as i32), -(urem as i32)),
|
||||
(true, false) => (-(udiv as i32), urem as i32),
|
||||
(false, true) => (udiv as i32, -(urem as i32)),
|
||||
(false, false) => (udiv as i32, urem as i32),
|
||||
}
|
||||
}
|
||||
*/
|
||||
pub mod dma;
|
||||
pub mod gba_cell;
|
||||
pub mod interrupts;
|
||||
pub mod keys;
|
||||
pub mod mmio;
|
||||
pub mod prelude;
|
||||
pub mod sound;
|
||||
pub mod timers;
|
||||
pub mod video;
|
||||
|
|
201
src/macros.rs
201
src/macros.rs
|
@ -1,81 +1,144 @@
|
|||
/// Delivers a fatal message to the emulator debug output, and crashes
|
||||
/// the the game.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! fatal {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debugging;
|
||||
if !debugging::is_debugging_disabled() {
|
||||
debugging::debug_print(debugging::DebugLevel::Fatal, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
debugging::crash()
|
||||
}};
|
||||
}
|
||||
#![allow(unused_macros)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
/// Delivers a error message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debugging;
|
||||
if !debugging::is_debugging_disabled() {
|
||||
debugging::debug_print(debugging::DebugLevel::Error, &format_args!($($arg)*)).ok();
|
||||
macro_rules! pub_const_fn_new_zeroed {
|
||||
() => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}};
|
||||
};
|
||||
}
|
||||
pub(crate) use pub_const_fn_new_zeroed;
|
||||
|
||||
/// Delivers a warning message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! warning {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debugging;
|
||||
if !debugging::is_debugging_disabled() {
|
||||
debugging::debug_print(debugging::DebugLevel::Warning, &format_args!($($arg)*)).ok();
|
||||
macro_rules! u16_bool_field {
|
||||
($bit:expr, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> bool {
|
||||
bitfrob::u16_get_bit($bit, self.0)
|
||||
}
|
||||
}};
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, b: bool) -> Self {
|
||||
Self(bitfrob::u16_with_bit($bit, self.0, b))
|
||||
}
|
||||
};
|
||||
(inverted $bit:expr, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> bool {
|
||||
!bitfrob::u16_get_bit($bit, self.0)
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, b: bool) -> Self {
|
||||
Self(bitfrob::u16_with_bit($bit, self.0, !b))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u16_bool_field;
|
||||
|
||||
/// Delivers an info message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debugging;
|
||||
if !debugging::is_debugging_disabled() {
|
||||
debugging::debug_print(debugging::DebugLevel::Info, &format_args!($($arg)*)).ok();
|
||||
macro_rules! u16_enum_field {
|
||||
($low:literal - $high:literal : $t:ty, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> $t {
|
||||
unsafe {
|
||||
core::mem::transmute::<u16, $t>(bitfrob::u16_get_region(
|
||||
$low, $high, self.0,
|
||||
))
|
||||
}
|
||||
}};
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, val: $t) -> Self {
|
||||
Self(bitfrob::u16_with_region($low, $high, self.0, val as u16))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u16_enum_field;
|
||||
|
||||
/// Delivers a debug message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debugging;
|
||||
if !debugging::is_debugging_disabled() {
|
||||
debugging::debug_print(debugging::DebugLevel::Debug, &format_args!($($arg)*)).ok();
|
||||
macro_rules! u16_int_field {
|
||||
($low:literal - $high:literal, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> u16 {
|
||||
bitfrob::u16_get_value($low, $high, self.0)
|
||||
}
|
||||
}};
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, val: u16) -> Self {
|
||||
Self(bitfrob::u16_with_value($low, $high, self.0, val))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u16_int_field;
|
||||
|
||||
macro_rules! u8_bool_field {
|
||||
($bit:expr, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> bool {
|
||||
bitfrob::u8_get_bit($bit, self.0)
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, b: bool) -> Self {
|
||||
Self(bitfrob::u8_with_bit($bit, self.0, b))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u8_bool_field;
|
||||
|
||||
macro_rules! u8_enum_field {
|
||||
($low:literal - $high:literal : $t:ty, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> $t {
|
||||
unsafe {
|
||||
core::mem::transmute::<u8, $t>(bitfrob::u8_get_region(
|
||||
$low, $high, self.0,
|
||||
))
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, val: $t) -> Self {
|
||||
Self(bitfrob::u8_with_region($low, $high, self.0, val as u8))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u8_enum_field;
|
||||
|
||||
macro_rules! u8_int_field {
|
||||
($low:literal - $high:literal, $get:ident, $with:ident) => {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $get(self) -> u8 {
|
||||
bitfrob::u8_get_value($low, $high, self.0)
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(missing_docs)]
|
||||
pub const fn $with(self, val: u8) -> Self {
|
||||
Self(bitfrob::u8_with_value($low, $high, self.0, val))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use u8_int_field;
|
||||
|
|
253
src/mmio.rs
Normal file
253
src/mmio.rs
Normal file
|
@ -0,0 +1,253 @@
|
|||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
//! Contains all the MMIO address definitions for the GBA's components.
|
||||
//!
|
||||
//! This module contains *only* the MMIO addresses. The data type definitions
|
||||
//! for each MMIO control value are stored in the appropriate other modules such
|
||||
//! as [`video`](crate::video), [`interrupts`](crate::interrupts), etc.
|
||||
//!
|
||||
//! In general, the docs for each address are quite short. If you want to
|
||||
//! understand how a subsystem of the GBA works, you should read the docs for
|
||||
//! that system's module, and the data type used by the address.
|
||||
//!
|
||||
//! The GBATEK names (and thus mGBA names) are used for the MMIO addresses by
|
||||
//! default. However, in some cases (eg: sound) the GBATEK naming is excessively
|
||||
//! cryptic, and so new names have been created. Whenever a new name is used,
|
||||
//! the GBATEK name is still listed as a doc alias for that address. If
|
||||
//! necessary you can just search the GBATEK name in the rustdoc search bar and
|
||||
//! the search results will show you the new name.
|
||||
//!
|
||||
//! ## Safety
|
||||
//!
|
||||
//! While it's safe to use this crate's data type definitions anywhere (they're
|
||||
//! just wrappers for ints), the MMIO declarations in this module **must not**
|
||||
//! be used outside of a GBA. The read and write safety of each address are
|
||||
//! declared assuming that code is running on a GBA. On any other platform, the
|
||||
//! declarations are simply incorrect.
|
||||
|
||||
use core::{ffi::c_void, mem::size_of};
|
||||
use bitfrob::u8x2;
|
||||
use voladdress::{Safe, Unsafe, VolAddress, VolBlock, VolSeries};
|
||||
use crate::{
|
||||
interrupts::IrqBits,
|
||||
video::{
|
||||
BackgroundControl, Color, DisplayControl, DisplayStatus, WindowInside,
|
||||
WindowOutside, Mosaic, BlendControl, Tile4, ObjAttr0, ObjAttr1, ObjAttr2, Tile8, TextEntry
|
||||
},
|
||||
dma::DmaControl,
|
||||
sound::{
|
||||
SweepControl, TonePattern, ToneFrequency, WaveBank, WaveLenVolume, WaveFrequency, NoiseLenEnvelope, NoiseFrequency, LeftRightVolume, SoundMix, SoundEnable, SoundBias
|
||||
},
|
||||
timers::TimerControl, keys::{KeyInput, KeyControl},
|
||||
};
|
||||
|
||||
// Note(Lokathor): This macro lets us stick each address at the start of the
|
||||
// definition, which lets us easily keep each declaration in address order.
|
||||
macro_rules! def_mmio {
|
||||
($addr:literal = $name:ident : $t:ty $(; $comment:expr )?) => {
|
||||
// redirect a call **without** an alias list to just pass an empty alias list
|
||||
def_mmio!($addr = $name/[]: $t $(; $comment)? );
|
||||
};
|
||||
($addr:literal = $name:ident / [ $( $alias:literal ),* ]: $t:ty $(; $comment:expr )?) => {
|
||||
$(#[doc = $comment])?
|
||||
$(#[doc(alias = $alias)])*
|
||||
#[allow(missing_docs)]
|
||||
pub const $name: $t = unsafe { <$t>::new($addr) };
|
||||
};
|
||||
}
|
||||
|
||||
// Video
|
||||
|
||||
def_mmio!(0x0400_0000 = DISPCNT: VolAddress<DisplayControl, Safe, Safe>; "Display Control");
|
||||
def_mmio!(0x0400_0004 = DISPSTAT: VolAddress<DisplayStatus, Safe, Safe>; "Display Status");
|
||||
def_mmio!(0x0400_0006 = VCOUNT: VolAddress<u16, Safe, ()>; "Vertical Counter");
|
||||
|
||||
def_mmio!(0x0400_0008 = BG0CNT: VolAddress<BackgroundControl, Safe, Safe>; "Background 0 Control");
|
||||
def_mmio!(0x0400_000A = BG1CNT: VolAddress<BackgroundControl, Safe, Safe>; "Background 1 Control");
|
||||
def_mmio!(0x0400_000C = BG2CNT: VolAddress<BackgroundControl, Safe, Safe>; "Background 2 Control");
|
||||
def_mmio!(0x0400_000E = BG3CNT: VolAddress<BackgroundControl, Safe, Safe>; "Background 3 Control");
|
||||
|
||||
def_mmio!(0x0400_0010 = BG0HOFS: VolAddress<u16, (), Safe>; "Background 0 Horizontal Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_0012 = BG0VOFS: VolAddress<u16, (), Safe>; "Background 0 Vertical Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_0014 = BG1HOFS: VolAddress<u16, (), Safe>; "Background 1 Horizontal Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_0016 = BG1VOFS: VolAddress<u16, (), Safe>; "Background 1 Vertical Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_0018 = BG2HOFS: VolAddress<u16, (), Safe>; "Background 2 Horizontal Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_001A = BG2VOFS: VolAddress<u16, (), Safe>; "Background 2 Vertical Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_001C = BG3HOFS: VolAddress<u16, (), Safe>; "Background 3 Horizontal Offset, wrapped to `0..=511`, (text mode)");
|
||||
def_mmio!(0x0400_001E = BG3VOFS: VolAddress<u16, (), Safe>; "Background 3 Vertical Offset, wrapped to `0..=511`, (text mode)");
|
||||
|
||||
def_mmio!(0x0400_0020 = BG2PA: VolAddress<i16, (), Safe>; "Background 2 Param A (affine mode)");
|
||||
def_mmio!(0x0400_0022 = BG2PB: VolAddress<i16, (), Safe>; "Background 2 Param B (affine mode)");
|
||||
def_mmio!(0x0400_0024 = BG2PC: VolAddress<i16, (), Safe>; "Background 2 Param C (affine mode)");
|
||||
def_mmio!(0x0400_0026 = BG2PD: VolAddress<i16, (), Safe>; "Background 2 Param D (affine mode)");
|
||||
def_mmio!(0x0400_0028 = BG2X/["BG2X_L", "BG2X_H"]: VolAddress<i32, (), Safe>; "Background 2 X Reference Point, 8-bits fractional (affine/bitmap modes)");
|
||||
def_mmio!(0x0400_002C = BG2Y/["BG2Y_L", "BG2Y_H"]: VolAddress<i32, (), Safe>; "Background 2 Y Reference Point, 8-bits fractional (affine/bitmap modes)");
|
||||
|
||||
def_mmio!(0x0400_0030 = BG3PA: VolAddress<i16, (), Safe>; "Background 3 Param A (affine mode)");
|
||||
def_mmio!(0x0400_0032 = BG3PB: VolAddress<i16, (), Safe>; "Background 3 Param B (affine mode)");
|
||||
def_mmio!(0x0400_0034 = BG3PC: VolAddress<i16, (), Safe>; "Background 3 Param C (affine mode)");
|
||||
def_mmio!(0x0400_0036 = BG3PD: VolAddress<i16, (), Safe>; "Background 3 Param D (affine mode)");
|
||||
def_mmio!(0x0400_0038 = BG3X/["BG3X_L", "BG3X_H"]: VolAddress<i32, (), Safe>; "Background 3 X Reference Point, 8-bits fractional (affine/bitmap modes)");
|
||||
def_mmio!(0x0400_003C = BG3Y/["BG3Y_L", "BG3Y_H"]: VolAddress<i32, (), Safe>; "Background 3 Y Reference Point, 8-bits fractional (affine/bitmap modes)");
|
||||
|
||||
def_mmio!(0x0400_0040 = WIN0H: VolAddress<u8x2, (), Safe>; "Window 0 Horizontal: high=left, low=(right+1)");
|
||||
def_mmio!(0x0400_0042 = WIN1H: VolAddress<u8x2, (), Safe>; "Window 1 Horizontal: high=left, low=(right+1)");
|
||||
def_mmio!(0x0400_0044 = WIN0V: VolAddress<u8x2, (), Safe>; "Window 0 Vertical: high=top, low=(bottom+1)");
|
||||
def_mmio!(0x0400_0046 = WIN1V: VolAddress<u8x2, (), Safe>; "Window 1 Vertical: high=top, low=(bottom+1)");
|
||||
def_mmio!(0x0400_0048 = WININ: VolAddress<WindowInside, Safe, Safe>; "Controls the inside Windows 0 and 1");
|
||||
def_mmio!(0x0400_004A = WINOUT: VolAddress<WindowOutside, Safe, Safe>; "Controls inside the object window and outside of windows");
|
||||
|
||||
def_mmio!(0x0400_004C = MOSAIC: VolAddress<Mosaic, (), Safe>; "Sets the intensity of all mosaic effects");
|
||||
def_mmio!(0x0400_0050 = BLDCNT: VolAddress<BlendControl, Safe, Safe>; "Sets color blend effects");
|
||||
def_mmio!(0x0400_0052 = BLDALPHA: VolAddress<u8x2, Safe, Safe>;"Sets EVA(low) and EVB(high) alpha blend coefficients, allows `0..=16`, in 1/16th units");
|
||||
def_mmio!(0x0400_0054 = BLDY: VolAddress<u8, (), Safe>;"Sets EVY brightness blend coefficient, allows `0..=16`, in 1/16th units");
|
||||
|
||||
// Sound
|
||||
|
||||
def_mmio!(0x0400_0060 = TONE1_SWEEP/["SOUND1CNT_L","NR10"]: VolAddress<SweepControl, Safe, Safe>; "Tone 1 Sweep");
|
||||
def_mmio!(0x0400_0062 = TONE1_PATTERN/["SOUND1CNT_H","NR11","NR12"]: VolAddress<TonePattern, Safe, Safe>; "Tone 1 Duty/Len/Envelope");
|
||||
def_mmio!(0x0400_0064 = TONE1_FREQUENCY/["SOUND1CNT_X","NR13","NR14"]: VolAddress<ToneFrequency, Safe, Safe>; "Tone 1 Frequency/Control");
|
||||
|
||||
def_mmio!(0x0400_0068 = TONE2_PATTERN/["SOUND2CNT_L","NR21","NR22"]: VolAddress<TonePattern, Safe, Safe>; "Tone 2 Duty/Len/Envelope");
|
||||
def_mmio!(0x0400_006C = TONE2_FREQUENCY/["SOUND2CNT_H","NR23","NR24"]: VolAddress<ToneFrequency, Safe, Safe>; "Tone 2 Frequency/Control");
|
||||
|
||||
def_mmio!(0x0400_0070 = WAVE_BANK/["SOUND3CNT_L","NR30"]: VolAddress<WaveBank, Safe, Safe>; "Wave banking controls");
|
||||
def_mmio!(0x0400_0072 = WAVE_LEN_VOLUME/["SOUND3CNT_H","NR31","NR32"]: VolAddress<WaveLenVolume, Safe, Safe>; "Wave Length/Volume");
|
||||
def_mmio!(0x0400_0074 = WAVE_FREQ/["SOUND3CNT_X","NR33","NR34"]: VolAddress<WaveFrequency, Safe, Safe>; "Wave Frequency/Control");
|
||||
|
||||
def_mmio!(0x0400_0078 = NOISE_LEN_ENV/["SOUND4CNT_L","NR41","NR42"]: VolAddress<NoiseLenEnvelope, Safe, Safe>; "Noise Length/Envelope");
|
||||
def_mmio!(0x0400_007C = NOISE_FREQ/["SOUND4CNT_H","NR43","NR44"]: VolAddress<NoiseFrequency, Safe, Safe>; "Noise Frequency/Control");
|
||||
|
||||
def_mmio!(0x0400_0080 = LEFT_RIGHT_VOLUME/["SOUNDCNT_L","NR50","NR51"]: VolAddress<LeftRightVolume, Safe, Safe>;"Left/Right sound control (but GBAs only have one speaker each).");
|
||||
def_mmio!(0x0400_0082 = SOUND_MIX/["SOUNDCNT_H"]: VolAddress<SoundMix, Safe, Safe>;"Mixes sound sources out to the left and right");
|
||||
def_mmio!(0x0400_0084 = SOUND_ENABLED/["SOUNDCNT_X"]: VolAddress<SoundEnable, Safe, Safe>;"Sound active flags (r), as well as the sound primary enable (rw).");
|
||||
def_mmio!(0x0400_0088 = SOUNDBIAS: VolAddress<SoundBias, Safe, Safe>;"Provides a bias to set the 'middle point' of sound output.");
|
||||
|
||||
def_mmio!(0x0400_0090 = WAVE_RAM/["WAVE_RAM0_L","WAVE_RAM0_H","WAVE_RAM1_L","WAVE_RAM1_H","WAVE_RAM2_L","WAVE_RAM2_H","WAVE_RAM3_L","WAVE_RAM3_H"]: VolBlock<u32, Safe, Safe, 4>; "Wave memory, `u4`, plays MSB/LSB per byte.");
|
||||
def_mmio!(0x0400_00A0 = FIFO_A/["FIFO_A_L", "FIFO_A_H"]: VolAddress<u32, (), Safe>; "Pushes 4 `i8` samples into the Sound A buffer.\n\nThe buffer is 32 bytes max, playback is LSB first.");
|
||||
def_mmio!(0x0400_00A4 = FIFO_B/["FIFO_B_L", "FIFO_B_H"]: VolAddress<u32, (), Safe>; "Pushes 4 `i8` samples into the Sound B buffer.\n\nThe buffer is 32 bytes max, playback is LSB first.");
|
||||
|
||||
// DMA
|
||||
|
||||
def_mmio!(0x0400_00B0 = DMA0_SRC/["DMA0SAD"]: VolAddress<*const c_void, (), Unsafe>; "DMA0 Source Address (internal memory only)");
|
||||
def_mmio!(0x0400_00B4 = DMA0_DEST/["DMA0DAD"]: VolAddress<*mut c_void, (), Unsafe>; "DMA0 Destination Address (internal memory only)");
|
||||
def_mmio!(0x0400_00B8 = DMA0_COUNT/["DMA0CNT_L"]: VolAddress<u16, (), Unsafe>; "DMA0 Transfer Count (14-bit, 0=max)");
|
||||
def_mmio!(0x0400_00BA = DMA0_CONTROL/["DMA0_CNT_H"]: VolAddress<DmaControl, Safe, Unsafe>; "DMA0 Control Bits");
|
||||
|
||||
def_mmio!(0x0400_00BC = DMA1_SRC/["DMA1SAD"]: VolAddress<*const c_void, (), Unsafe>; "DMA1 Source Address (non-SRAM memory)");
|
||||
def_mmio!(0x0400_00C0 = DMA1_DEST/["DMA1DAD"]: VolAddress<*mut c_void, (), Unsafe>; "DMA1 Destination Address (internal memory only)");
|
||||
def_mmio!(0x0400_00C4 = DMA1_COUNT/["DMA1CNT_L"]: VolAddress<u16, (), Unsafe>; "DMA1 Transfer Count (14-bit, 0=max)");
|
||||
def_mmio!(0x0400_00C6 = DMA1_CONTROL/["DMA1_CNT_H"]: VolAddress<DmaControl, Safe, Unsafe>; "DMA1 Control Bits");
|
||||
|
||||
def_mmio!(0x0400_00C8 = DMA2_SRC/["DMA2SAD"]: VolAddress<*const c_void, (), Unsafe>; "DMA2 Source Address (non-SRAM memory)");
|
||||
def_mmio!(0x0400_00CC = DMA2_DEST/["DMA2DAD"]: VolAddress<*mut c_void, (), Unsafe>; "DMA2 Destination Address (internal memory only)");
|
||||
def_mmio!(0x0400_00D0 = DMA2_COUNT/["DMA2CNT_L"]: VolAddress<u16, (), Unsafe>; "DMA2 Transfer Count (14-bit, 0=max)");
|
||||
def_mmio!(0x0400_00D2 = DMA2_CONTROL/["DMA2_CNT_H"]: VolAddress<DmaControl, Safe, Unsafe>; "DMA2 Control Bits");
|
||||
|
||||
def_mmio!(0x0400_00D4 = DMA3_SRC/["DMA3SAD"]: VolAddress<*const c_void, (), Unsafe>; "DMA3 Source Address (non-SRAM memory)");
|
||||
def_mmio!(0x0400_00D8 = DMA3_DEST/["DMA3DAD"]: VolAddress<*mut c_void, (), Unsafe>; "DMA3 Destination Address (non-SRAM memory)");
|
||||
def_mmio!(0x0400_00DC = DMA3_COUNT/["DMA3CNT_L"]: VolAddress<u16, (), Unsafe>; "DMA3 Transfer Count (16-bit, 0=max)");
|
||||
def_mmio!(0x0400_00DE = DMA3_CONTROL/["DMA3_CNT_H"]: VolAddress<DmaControl, Safe, Unsafe>; "DMA3 Control Bits");
|
||||
|
||||
// Timers
|
||||
|
||||
def_mmio!(0x0400_0100 = TIMER0_COUNT/["TM0CNT_L"]: VolAddress<u16, Safe, ()>; "Timer 0 Count read");
|
||||
def_mmio!(0x0400_0100 = TIMER0_RELOAD/["TM0CNT_L"]: VolAddress<u16, (), Safe>; "Timer 0 Reload write");
|
||||
def_mmio!(0x0400_0102 = TIMER0_CONTROL/["TM0CNT_H"]: VolAddress<TimerControl, Safe, Safe>; "Timer 0 control");
|
||||
|
||||
def_mmio!(0x0400_0100 = TIMER1_COUNT/["TM1CNT_L"]: VolAddress<u16, Safe, ()>; "Timer 1 Count read");
|
||||
def_mmio!(0x0400_0100 = TIMER1_RELOAD/["TM1CNT_L"]: VolAddress<u16, (), Safe>; "Timer 1 Reload write");
|
||||
def_mmio!(0x0400_0102 = TIMER1_CONTROL/["TM1CNT_H"]: VolAddress<TimerControl, Safe, Safe>; "Timer 1 control");
|
||||
|
||||
def_mmio!(0x0400_0100 = TIMER2_COUNT/["TM2CNT_L"]: VolAddress<u16, Safe, ()>; "Timer 2 Count read");
|
||||
def_mmio!(0x0400_0100 = TIMER2_RELOAD/["TM2CNT_L"]: VolAddress<u16, (), Safe>; "Timer 2 Reload write");
|
||||
def_mmio!(0x0400_0102 = TIMER2_CONTROL/["TM2CNT_H"]: VolAddress<TimerControl, Safe, Safe>; "Timer 2 control");
|
||||
|
||||
def_mmio!(0x0400_0100 = TIMER3_COUNT/["TM3CNT_L"]: VolAddress<u16, Safe, ()>; "Timer 3 Count read");
|
||||
def_mmio!(0x0400_0100 = TIMER3_RELOAD/["TM3CNT_L"]: VolAddress<u16, (), Safe>; "Timer 3 Reload write");
|
||||
def_mmio!(0x0400_0102 = TIMER3_CONTROL/["TM3CNT_H"]: VolAddress<TimerControl, Safe, Safe>; "Timer 3 control");
|
||||
|
||||
// Serial (part 1)
|
||||
|
||||
def_mmio!(0x0400_0120 = SIODATA32: VolAddress<u32, Safe, Safe>);
|
||||
def_mmio!(0x0400_0120 = SIOMULTI0: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0122 = SIOMULTI1: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0124 = SIOMULTI2: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0126 = SIOMULTI3: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0128 = SIOCNT: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_012A = SIOMLT_SEND: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_012A = SIODATA8: VolAddress<u8, Safe, Safe>);
|
||||
|
||||
// Keys
|
||||
|
||||
def_mmio!(0x0400_0130 = KEYINPUT: VolAddress<KeyInput, Safe, ()>; "Key state data.");
|
||||
def_mmio!(0x0400_0132 = KEYCNT: VolAddress<KeyControl, Safe, Safe>; "Key control to configure the key interrupt.");
|
||||
|
||||
// Serial (part 2)
|
||||
|
||||
def_mmio!(0x0400_0134 = RCNT: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0140 = JOYCNT: VolAddress<u16, Safe, Safe>);
|
||||
def_mmio!(0x0400_0150 = JOY_RECV: VolAddress<u32, Safe, Safe>);
|
||||
def_mmio!(0x0400_0154 = JOY_TRANS: VolAddress<u32, Safe, Safe>);
|
||||
def_mmio!(0x0400_0158 = JOYSTAT: VolAddress<u8, Safe, Safe>);
|
||||
|
||||
// Interrupts
|
||||
|
||||
def_mmio!(0x0400_0200 = IE: VolAddress<IrqBits, Safe, Safe>; "Interrupts Enabled: sets which interrupts will be accepted when a subsystem fires an interrupt");
|
||||
def_mmio!(0x0400_0202 = IF: VolAddress<IrqBits, Safe, Safe>; "Interrupts Flagged: reads which interrupts are pending, writing bit(s) will clear a pending interrupt.");
|
||||
def_mmio!(0x0400_0204 = WAITCNT: VolAddress<u16, Safe, Unsafe>; "Wait state control for interfacing with the ROM (can make reading the ROM give garbage when it's mis-configured)");
|
||||
def_mmio!(0x0400_0208 = IME: VolAddress<bool, Safe, Safe>; "Interrupt Master Enable: Allows turning on/off all interrupts with a single access.");
|
||||
|
||||
// Palette RAM (PALRAM)
|
||||
|
||||
def_mmio!(0x0500_0000 = BACKDROP_COLOR: VolAddress<Color, Safe, Safe>; "Color that's shown when no BG or OBJ draws to a pixel");
|
||||
def_mmio!(0x0500_0000 = BG_PALETTE: VolBlock<Color, Safe, Safe, 256>; "Background tile palette entries.");
|
||||
def_mmio!(0x0500_2000 = OBJ_PALETTE: VolBlock<Color, Safe, Safe, 256>; "Object tile palette entries.");
|
||||
|
||||
// Video RAM (VRAM)
|
||||
|
||||
def_mmio!(0x0600_0000 = CHARBLOCK0_4BPP: VolBlock<Tile4, Safe, Safe, 512>; "Charblock 0, 4bpp view (512 tiles).");
|
||||
def_mmio!(0x0600_4000 = CHARBLOCK1_4BPP: VolBlock<Tile4, Safe, Safe, 512>; "Charblock 1, 4bpp view (512 tiles).");
|
||||
def_mmio!(0x0600_8000 = CHARBLOCK2_4BPP: VolBlock<Tile4, Safe, Safe, 512>; "Charblock 2, 4bpp view (512 tiles).");
|
||||
def_mmio!(0x0600_C000 = CHARBLOCK3_4BPP: VolBlock<Tile4, Safe, Safe, 512>; "Charblock 3, 4bpp view (512 tiles).");
|
||||
|
||||
def_mmio!(0x0600_0000 = CHARBLOCK0_8BPP: VolBlock<Tile8, Safe, Safe, 512>; "Charblock 0, 8bpp view (256 tiles).");
|
||||
def_mmio!(0x0600_4000 = CHARBLOCK1_8BPP: VolBlock<Tile8, Safe, Safe, 512>; "Charblock 1, 8bpp view (256 tiles).");
|
||||
def_mmio!(0x0600_8000 = CHARBLOCK2_8BPP: VolBlock<Tile8, Safe, Safe, 512>; "Charblock 2, 8bpp view (256 tiles).");
|
||||
def_mmio!(0x0600_C000 = CHARBLOCK3_8BPP: VolBlock<Tile8, Safe, Safe, 512>; "Charblock 3, 8bpp view (256 tiles).");
|
||||
|
||||
pub type TextScreenBlock = VolBlock<TextEntry, Safe, Safe, {8*8}>;
|
||||
/// ## Panics
|
||||
/// * Must be in range `0..=31`
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn text_screenblock(index: usize) -> TextScreenBlock {
|
||||
assert!(index < 32);
|
||||
unsafe { VolBlock::new(0x0600_0000 + index * size_of::<[TextEntry;8*8]>()) }
|
||||
}
|
||||
|
||||
pub type AffineScreenBlock0 = VolBlock<u8, Safe, Safe, {16*16}>;
|
||||
pub type AffineScreenBlock1 = VolBlock<u8, Safe, Safe, {32*32}>;
|
||||
pub type AffineScreenBlock2 = VolBlock<u8, Safe, Safe, {64*64}>;
|
||||
pub type AffineScreenBlock3 = VolBlock<u8, Safe, Safe, {128*128}>;
|
||||
|
||||
def_mmio!(0x0600_0000 = MODE3_BITMAP: VolBlock<Color, Safe, Safe, {240 * 160}>; "Mode 3 bitmap, 240x160.");
|
||||
|
||||
def_mmio!(0x0600_0000 = MODE4_FRAME0: VolBlock<u8x2, Safe, Safe, {(240/2) * 160}>; "Mode 4 indexmap, frame 0, (240/2)x160.");
|
||||
def_mmio!(0x0600_A000 = MODE4_FRAME1: VolBlock<u8x2, Safe, Safe, {(240/2) * 160}>; "Mode 4 indexmap, frame 1, (240/2)x160.");
|
||||
|
||||
def_mmio!(0x0600_0000 = MODE5_FRAME0: VolBlock<Color, Safe, Safe, {160 * 128}>; "Mode 5 bitmap, frame 0, 160x128.");
|
||||
def_mmio!(0x0600_A000 = MODE5_FRAME1: VolBlock<Color, Safe, Safe, {160 * 128}>; "Mode 5 bitmap, frame 1, 160x128.");
|
||||
|
||||
def_mmio!(0x0601_0000 = OBJ_TILES: VolBlock<Tile4, Safe, Safe, 1024>; "Object tiles. In bitmap modes, only indices 512..=1023 are available.");
|
||||
|
||||
// Object Attribute Memory (OAM)
|
||||
|
||||
def_mmio!(0x0700_0000 = OBJ_ATTR0: VolSeries<ObjAttr0, Safe, Safe, 128, {size_of::<[u16;4]>()}>);
|
||||
def_mmio!(0x0700_0002 = OBJ_ATTR1: VolSeries<ObjAttr1, Safe, Safe, 128, {size_of::<[u16;4]>()}>);
|
||||
def_mmio!(0x0700_0004 = OBJ_ATTR2: VolSeries<ObjAttr2, Safe, Safe, 128, {size_of::<[u16;4]>()}>);
|
||||
|
||||
def_mmio!(0x0700_0006 = AFFINE_PARAM_A: VolSeries<i16, Safe, Safe, 32, {size_of::<[u16;16]>()}>);
|
||||
def_mmio!(0x0700_000E = AFFINE_PARAM_B: VolSeries<i16, Safe, Safe, 32, {size_of::<[u16;16]>()}>);
|
||||
def_mmio!(0x0700_0016 = AFFINE_PARAM_C: VolSeries<i16, Safe, Safe, 32, {size_of::<[u16;16]>()}>);
|
||||
def_mmio!(0x0700_001E = AFFINE_PARAM_D: VolSeries<i16, Safe, Safe, 32, {size_of::<[u16;16]>()}>);
|
|
@ -1,323 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use voladdress::*;
|
||||
|
||||
pub mod mode3;
|
||||
|
||||
// TODO: modules for the other video modes
|
||||
|
||||
/// [DISPCNT](https://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||
pub const DISPCNT: VolAddress<DisplayControl, Safe, Safe> = unsafe { VolAddress::new(0x0400_0000) };
|
||||
|
||||
/// [DISPSTAT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||
pub const DISPSTAT: VolAddress<DisplayStatus, Safe, Safe> = unsafe { VolAddress::new(0x0400_0004) };
|
||||
|
||||
/// [VCOUNT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||
pub const VCOUNT: VolAddress<u8, Safe, ()> = unsafe { VolAddress::new(0x0400_0006) };
|
||||
|
||||
/// [BG0CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG0CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0008) };
|
||||
|
||||
/// [BG1CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG1CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000A) };
|
||||
|
||||
/// [BG2CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG2CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000C) };
|
||||
|
||||
/// [BG3CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG3CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000E) };
|
||||
|
||||
/// [BG0HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG0HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0010) };
|
||||
/// [BG0VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG0VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0012) };
|
||||
|
||||
/// [BG1HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG1HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0014) };
|
||||
/// [BG1VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG1VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0016) };
|
||||
|
||||
/// [BG2HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG2HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0018) };
|
||||
/// [BG2VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG2VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001A) };
|
||||
|
||||
/// [BG3HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG3HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001C) };
|
||||
/// [BG3VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG3VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001E) };
|
||||
|
||||
/// [BG2PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PA: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0020) };
|
||||
/// [BG2PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PB: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0022) };
|
||||
/// [BG2PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PC: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0024) };
|
||||
/// [BG2PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PD: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0026) };
|
||||
|
||||
/// [BG2X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2X: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_0028) };
|
||||
/// [BG2Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2Y: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_002C) };
|
||||
|
||||
/// [BG3PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PA: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0030) };
|
||||
/// [BG3PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PB: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0032) };
|
||||
/// [BG3PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PC: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0034) };
|
||||
/// [BG3PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PD: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0036) };
|
||||
|
||||
/// [BG3X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3X: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_0038) };
|
||||
/// [BG3Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3Y: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_003C) };
|
||||
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0H_RIGHT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0040) };
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0H_LEFT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0041) };
|
||||
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1H_RIGHT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0042) };
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1H_LEFT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0043) };
|
||||
|
||||
/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0V_BOTTOM: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0044) };
|
||||
/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0V_TOP: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0045) };
|
||||
|
||||
/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1V_BOTTOM: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0046) };
|
||||
/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1V_TOP: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0047) };
|
||||
|
||||
/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_0: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_0048) };
|
||||
/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_1: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_0049) };
|
||||
/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_OUT: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_004A) };
|
||||
/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_OBJ: VolAddress<WindowEnable, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_004B) };
|
||||
|
||||
/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction)
|
||||
pub const MOSAIC_BG: VolAddress<MosaicSize, (), Safe> = unsafe { VolAddress::new(0x0400_004C) };
|
||||
/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction)
|
||||
pub const MOSAIC_OBJ: VolAddress<MosaicSize, (), Safe> = unsafe { VolAddress::new(0x0400_004D) };
|
||||
|
||||
/// [BLDCNT](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDCNT: VolAddress<BlendControl, Safe, Safe> = unsafe { VolAddress::new(0x0400_0050) };
|
||||
|
||||
/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDALPHA_A: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0052) };
|
||||
|
||||
/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDALPHA_B: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0053) };
|
||||
|
||||
/// [BLDY](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDY: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0054) };
|
||||
|
||||
/// [SOUND1CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_SWEEP: VolAddress<ToneSweep, Safe, Safe> = unsafe { VolAddress::new(0x0400_0060) };
|
||||
/// [SOUND1CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_DUTY_LEN_ENV: VolAddress<ToneDutyLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0062) };
|
||||
/// [SOUND1CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_FREQ_CNT: VolAddress<ToneFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0064) };
|
||||
|
||||
/// [SOUND2CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone)
|
||||
pub const TONE2_DUTY_LEN_ENV: VolAddress<ToneDutyLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0068) };
|
||||
/// [SOUND2CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone)
|
||||
pub const TONE2_FREQ_CNT: VolAddress<ToneFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_006C) };
|
||||
|
||||
/// [SOUND3CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_CONTROL: VolAddress<WaveControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0070) };
|
||||
/// [SOUND3CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_LEN_VOLUME: VolAddress<WaveLenVolume, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0072) };
|
||||
/// [SOUND3CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_FREQ_CNT: VolAddress<WaveFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0074) };
|
||||
/// [WAVE_RAM](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_RAM: VolBlock<u32, Safe, Safe, 4> = unsafe { VolBlock::new(0x0400_0090) };
|
||||
|
||||
/// [SOUND4CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise)
|
||||
pub const NOISE_LEN_ENV: VolAddress<NoiseLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0078) };
|
||||
/// [SOUND4CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise)
|
||||
pub const NOISE_FREQ_CNT: VolAddress<NoiseFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_007C) };
|
||||
|
||||
/// [SOUNDCNT_L](https://problemkaputt.de/gbatek.htm#gbasoundcontrolregisters)
|
||||
pub const SOUND_CONTROL: VolAddress<SoundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0080) };
|
||||
/// [SOUNDCNT_X](https://problemkaputt.de/gbatek.htm#gbasoundcontrolregisters)
|
||||
pub const SOUND_STATUS: VolAddress<SoundStatus, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0084) };
|
||||
/// [SOUNDBIAS](https://problemkaputt.de/gbatek.htm#gbasoundcontrolregisters)
|
||||
pub const SOUND_BIAS: VolAddress<SoundBias, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0088) };
|
||||
|
||||
/// [SOUNDCNT_H](https://problemkaputt.de/gbatek.htm#gbasoundcontrolregisters) (R/W fields)
|
||||
pub const FIFO_CONTROL: VolAddress<FifoControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0082) };
|
||||
/// [SOUNDCNT_H](https://problemkaputt.de/gbatek.htm#gbasoundcontrolregisters) (write-only fields)
|
||||
pub const FIFO_RESET: VolAddress<FifoReset, (), Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0082) };
|
||||
/// [FIFO_A](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound)
|
||||
pub const FIFO_A: VolAddress<u32, (), Safe> = unsafe { VolAddress::new(0x0400_00A0) };
|
||||
/// [FIFO_B](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound)
|
||||
pub const FIFO_B: VolAddress<u32, (), Safe> = unsafe { VolAddress::new(0x0400_00A4) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Source Address (W) (internal memory)
|
||||
pub const DMA0SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Destination Address (W) (internal memory)
|
||||
pub const DMA0DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA0CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Control (R/W)
|
||||
pub const DMA0CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00BA) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Source Address (W) (any memory)
|
||||
pub const DMA1SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00BC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Destination Address (W) (internal memory)
|
||||
pub const DMA1DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA1CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Control (R/W)
|
||||
pub const DMA1CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00C6) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Source Address (W) (any memory)
|
||||
pub const DMA2SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Destination Address (W) (internal memory)
|
||||
pub const DMA2DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00CC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA2CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Control (R/W)
|
||||
pub const DMA2CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00D2) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Source Address (W) (any memory)
|
||||
pub const DMA3SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Destination Address (W) (any memory)
|
||||
pub const DMA3DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Word Count (W) (16 bit, 1..10000h)
|
||||
pub const DMA3CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00DC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Control (R/W)
|
||||
pub const DMA3CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00DE) };
|
||||
|
||||
/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0100) };
|
||||
/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0104) };
|
||||
/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0108) };
|
||||
/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_010C) };
|
||||
|
||||
/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0100) };
|
||||
/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0104) };
|
||||
/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0108) };
|
||||
/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_010C) };
|
||||
|
||||
/// [TM0CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0102) };
|
||||
/// [TM1CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0106) };
|
||||
/// [TM2CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_010A) };
|
||||
/// [TM3CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_010E) };
|
||||
|
||||
/// [SIOCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const SIOCNT: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0128) };
|
||||
/// [SIODATA8](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const SIODATA8: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x400_012A) };
|
||||
/// [RCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const RCNT: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x0400_0134) };
|
||||
|
||||
/// [KEYINPUT](https://problemkaputt.de/gbatek.htm#gbakeypadinput)
|
||||
pub const KEYINPUT: VolAddress<KeysLowActive, Safe, ()> = unsafe { VolAddress::new(0x0400_0130) };
|
||||
/// [KEYCNT](https://problemkaputt.de/gbatek.htm#gbakeypadinput)
|
||||
pub const KEYCNT: VolAddress<KeyInterruptControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0130) };
|
||||
|
||||
/// Points to the (A32) user interrupt handler function.
|
||||
pub const USER_IRQ_HANDLER: VolAddress<Option<unsafe extern "C" fn()>, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x0300_7FFC) };
|
||||
/// "Interrupt Master Enable", [IME](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol)
|
||||
pub const IME: VolAddress<bool, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_0208) };
|
||||
/// "Interrupts Enabled", [IE](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol)
|
||||
pub const IE: VolAddress<InterruptFlags, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_0200) };
|
||||
/// Shows which interrupts are pending.
|
||||
///
|
||||
/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (reading)
|
||||
pub const IRQ_PENDING: VolAddress<InterruptFlags, Safe, ()> =
|
||||
unsafe { VolAddress::new(0x0400_0202) };
|
||||
/// Acknowledges an interrupt as having been handled.
|
||||
///
|
||||
/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (writing)
|
||||
pub const IRQ_ACKNOWLEDGE: VolAddress<InterruptFlags, (), Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0202) };
|
||||
/// Use this during [`IntrWait`] and [`VBlankIntrWait`] interrupt handling.
|
||||
///
|
||||
/// You should:
|
||||
/// * read the current value
|
||||
/// * set any additional interrupt bits that you wish to mark as handled (do not
|
||||
/// clear any currently set bits!)
|
||||
/// * write the new value back to this register
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use crate::prelude::*;
|
||||
/// // to acknowledge a vblank interrupt
|
||||
/// let current = INTR_WAIT_ACKNOWLEDGE.read();
|
||||
/// unsafe { INTR_WAIT_ACKNOWLEDGE.write(current.with_vblank(true)) };
|
||||
/// ```
|
||||
///
|
||||
/// [GBATEK: IntrWait](https://problemkaputt.de/gbatek.htm#bioshaltfunctions)
|
||||
pub const INTR_WAIT_ACKNOWLEDGE: VolAddress<InterruptFlags, Safe, Unsafe> = unsafe {
|
||||
// Note(Lokathor): This uses a mirrored location that's closer to the main IO
|
||||
// Control memory region so that LLVM has a better chance of being able to
|
||||
// just do an offset read/write from an address that's already in a register.
|
||||
// The "base" address of this location is 0x0300_7FF8, so some documents may
|
||||
// refer to that value instead.
|
||||
VolAddress::new(0x0300_FFF8)
|
||||
};
|
||||
|
||||
pub const BACKDROP_COLOR: VolAddress<Color, Safe, Safe> = unsafe { VolAddress::new(0x0500_0000) };
|
||||
|
||||
pub const BG_PALETTE: VolBlock<Color, Safe, Safe, 256> = unsafe { VolBlock::new(0x0500_0000) };
|
||||
|
||||
pub const OBJ_PALETTE: VolBlock<Color, Safe, Safe, 256> = unsafe { VolBlock::new(0x0500_0200) };
|
||||
|
||||
pub const OAM_ATTR0: VolSeries<ObjAttr0, Safe, Safe, 128, 8> =
|
||||
unsafe { VolSeries::new(0x0700_0000) };
|
||||
pub const OAM_ATTR1: VolSeries<ObjAttr1, Safe, Safe, 128, 8> =
|
||||
unsafe { VolSeries::new(0x0700_0002) };
|
||||
pub const OAM_ATTR2: VolSeries<ObjAttr2, Safe, Safe, 128, 8> =
|
||||
unsafe { VolSeries::new(0x0700_0004) };
|
||||
|
||||
pub const OAM_PA: VolSeries<i16, Safe, Safe, 32, 0x20> = unsafe { VolSeries::new(0x0700_0006) };
|
||||
pub const OAM_PB: VolSeries<i16, Safe, Safe, 32, 0x20> = unsafe { VolSeries::new(0x0700_000E) };
|
||||
pub const OAM_PC: VolSeries<i16, Safe, Safe, 32, 0x20> = unsafe { VolSeries::new(0x0700_0016) };
|
||||
pub const OAM_PD: VolSeries<i16, Safe, Safe, 32, 0x20> = unsafe { VolSeries::new(0x0700_001E) };
|
|
@ -1,43 +0,0 @@
|
|||
use crate::prelude::Color;
|
||||
|
||||
use core::arch::asm;
|
||||
use voladdress::*;
|
||||
|
||||
pub const WIDTH: usize = 240;
|
||||
|
||||
pub const HEIGHT: usize = 160;
|
||||
|
||||
pub const BITMAP: VolBlock<Color, Safe, Safe, { WIDTH * HEIGHT }> =
|
||||
unsafe { VolBlock::new(0x0600_0000) };
|
||||
|
||||
pub const fn bitmap_xy(x: usize, y: usize) -> VolAddress<Color, Safe, Safe> {
|
||||
BITMAP.index(y * WIDTH + x)
|
||||
}
|
||||
|
||||
pub fn dma3_clear_to(color: Color) {
|
||||
use crate::prelude::{
|
||||
DestAddrControl, DmaControl, DmaStartTiming, SrcAddrControl, DMA3CNT_H, DMA3CNT_L, DMA3DAD,
|
||||
DMA3SAD,
|
||||
};
|
||||
let wide_color: u32 = color.0 as u32 | ((color.0 as u32) << 16);
|
||||
unsafe {
|
||||
DMA3SAD.write(&wide_color as *const _ as usize);
|
||||
DMA3DAD.write(0x0600_0000);
|
||||
const MODE3_WORD_COUNT: u16 = (WIDTH * HEIGHT * 2 / 4_usize) as u16;
|
||||
DMA3CNT_L.write(MODE3_WORD_COUNT);
|
||||
const CTRL: DmaControl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Fixed)
|
||||
.with_transfer_u32(true)
|
||||
.with_start_time(DmaStartTiming::Immediately)
|
||||
.with_enabled(true);
|
||||
DMA3CNT_H.write(CTRL);
|
||||
asm!(
|
||||
"
|
||||
nop
|
||||
nop
|
||||
",
|
||||
options(nostack),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
/// Sets up a constant new constructor for a zeroed value.
|
||||
macro_rules! const_new {
|
||||
() => {
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use const_new;
|
||||
|
||||
/// Sets up a bitfield integer
|
||||
macro_rules! bitfield_int {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
#[inline]
|
||||
pub const fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << ($high - $low + 1)) - 1) << $low;
|
||||
((self.0 & MASK) >> $low) as $nt
|
||||
}
|
||||
#[inline]
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << ($high - $low + 1)) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ (($get as $inner) << $low)) & MASK))
|
||||
}
|
||||
#[inline]
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_int;
|
||||
|
||||
/// Sets up a bitfield int wrapped newtype
|
||||
macro_rules! bitfield_newtype {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
#[inline]
|
||||
pub const fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << ($high - $low + 1)) - 1) << $low;
|
||||
$nt(self.0 & MASK)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << ($high - $low + 1)) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ $get.0) & MASK))
|
||||
}
|
||||
#[inline]
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_newtype;
|
||||
|
||||
/// Sets up a bitfield enum (CAUTION: misuse of this can cause UB!)
|
||||
macro_rules! bitfield_enum {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
// TODO: make this const when we have const transmute
|
||||
#[inline]
|
||||
pub fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
unsafe { core::mem::transmute(self.0 & MASK) }
|
||||
}
|
||||
#[inline]
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ $get as $inner) & MASK))
|
||||
}
|
||||
#[inline]
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_enum;
|
||||
|
||||
/// Sets up a bitfield bool
|
||||
macro_rules! bitfield_bool {
|
||||
($inner:ty; $bit:literal, $get:ident, $with:ident, $set:ident) => {
|
||||
#[inline]
|
||||
pub const fn $get(self) -> bool {
|
||||
(self.0 & (1 << $bit)) != 0
|
||||
}
|
||||
#[inline]
|
||||
pub const fn $with(self, $get: bool) -> Self {
|
||||
Self(self.0 ^ ((($get as $inner).wrapping_neg() ^ self.0) & (1 << $bit)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn $set(&mut self, $get: bool) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_bool;
|
||||
|
||||
/// Adds bitwise ops for this type
|
||||
macro_rules! impl_bitwise_ops {
|
||||
($outer:ty) => {
|
||||
impl core::ops::Not for $outer {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn not(self) -> Self {
|
||||
Self(!self.0)
|
||||
}
|
||||
}
|
||||
impl core::ops::BitAnd for $outer {
|
||||
type Output = $outer;
|
||||
#[inline]
|
||||
fn bitand(self, rhs: Self) -> Self {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
impl core::ops::BitOr for $outer {
|
||||
type Output = $outer;
|
||||
#[inline]
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
impl core::ops::BitXor for $outer {
|
||||
type Output = $outer;
|
||||
#[inline]
|
||||
fn bitxor(self, rhs: Self) -> Self {
|
||||
Self(self.0 ^ rhs.0)
|
||||
}
|
||||
}
|
||||
// // // // //
|
||||
impl core::ops::BitAndAssign for $outer {
|
||||
#[inline]
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.0 &= rhs.0
|
||||
}
|
||||
}
|
||||
impl core::ops::BitOrAssign for $outer {
|
||||
#[inline]
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0
|
||||
}
|
||||
}
|
||||
impl core::ops::BitXorAssign for $outer {
|
||||
#[inline]
|
||||
fn bitxor_assign(&mut self, rhs: Self) {
|
||||
self.0 ^= rhs.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use impl_bitwise_ops;
|
||||
|
||||
mod display_control;
|
||||
pub use display_control::*;
|
||||
|
||||
mod display_status;
|
||||
pub use display_status::*;
|
||||
|
||||
mod background_control;
|
||||
pub use background_control::*;
|
||||
|
||||
mod window_enable;
|
||||
pub use window_enable::*;
|
||||
|
||||
mod mosaic_size;
|
||||
pub use mosaic_size::*;
|
||||
|
||||
mod blend_control;
|
||||
pub use blend_control::*;
|
||||
|
||||
mod color;
|
||||
pub use color::*;
|
||||
|
||||
mod keys;
|
||||
pub use keys::*;
|
||||
|
||||
mod dma_control;
|
||||
pub use dma_control::*;
|
||||
|
||||
mod key_interrupt_control;
|
||||
pub use key_interrupt_control::*;
|
||||
|
||||
mod register_ram_reset_control;
|
||||
pub use register_ram_reset_control::*;
|
||||
|
||||
mod interrupt_flags;
|
||||
pub use interrupt_flags::*;
|
||||
|
||||
mod fifo_control;
|
||||
pub use fifo_control::*;
|
||||
|
||||
mod fifo_reset;
|
||||
pub use fifo_reset::*;
|
||||
|
||||
mod sound_control;
|
||||
pub use sound_control::*;
|
||||
|
||||
mod sound_status;
|
||||
pub use sound_status::*;
|
||||
|
||||
mod sound_bias;
|
||||
pub use sound_bias::*;
|
||||
|
||||
mod timer_control;
|
||||
pub use timer_control::*;
|
||||
|
||||
mod tone_duty_len_env;
|
||||
pub use tone_duty_len_env::*;
|
||||
|
||||
mod tone_frequency_control;
|
||||
pub use tone_frequency_control::*;
|
||||
|
||||
mod tone_sweep;
|
||||
pub use tone_sweep::*;
|
||||
|
||||
mod wave_control;
|
||||
pub use wave_control::*;
|
||||
|
||||
mod wave_len_volume;
|
||||
pub use wave_len_volume::*;
|
||||
|
||||
mod wave_frequency_control;
|
||||
pub use wave_frequency_control::*;
|
||||
|
||||
mod noise_len_env;
|
||||
pub use noise_len_env::*;
|
||||
|
||||
mod noise_frequency_control;
|
||||
pub use noise_frequency_control::*;
|
||||
|
||||
mod obj_attr0;
|
||||
pub use obj_attr0::*;
|
||||
|
||||
mod obj_attr1;
|
||||
pub use obj_attr1::*;
|
||||
|
||||
mod obj_attr2;
|
||||
pub use obj_attr2::*;
|
|
@ -1,15 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct BackgroundControl(u16);
|
||||
impl BackgroundControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=1: u8, priority, with_priority, set_priority);
|
||||
bitfield_int!(u16; 2..=3: u8, char_base_block, with_char_base_block, set_char_base_block);
|
||||
bitfield_bool!(u16; 6, mosaic, with_mosaic, set_mosaic);
|
||||
bitfield_bool!(u16; 7, is_8bpp, with_is_8bpp, set_is_8bpp);
|
||||
bitfield_int!(u16; 8..=12: u8, screen_base_block, with_screen_base_block, set_screen_base_block);
|
||||
bitfield_bool!(u16; 13, affine_overflow_wrapped, with_affine_overflow_wrapped, set_affine_overflow_wrapped);
|
||||
bitfield_int!(u16; 14..=15: u8, screen_size, with_screen_size, set_screen_size);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct BlendControl(u16);
|
||||
impl BlendControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, bg0_1st_target, with_bg0_1st_target, set_bg0_1st_target);
|
||||
bitfield_bool!(u16; 1, bg1_1st_target, with_bg1_1st_target, set_bg1_1st_target);
|
||||
bitfield_bool!(u16; 2, bg2_1st_target, with_bg2_1st_target, set_bg2_1st_target);
|
||||
bitfield_bool!(u16; 3, bg3_1st_target, with_bg3_1st_target, set_bg3_1st_target);
|
||||
bitfield_bool!(u16; 4, obj_1st_target, with_obj_1st_target, set_obj_1st_target);
|
||||
bitfield_bool!(u16; 5, backdrop_1st_target, with_backdrop_1st_target, set_backdrop_1st_target);
|
||||
bitfield_enum!(u16; 6..=7: ColorSpecialEffect, effect, with_effect, set_effect);
|
||||
bitfield_bool!(u16; 8, bg0_2nd_target, with_bg0_2nd_target, set_bg0_2nd_target);
|
||||
bitfield_bool!(u16; 9, bg1_2nd_target, with_bg1_2nd_target, set_bg1_2nd_target);
|
||||
bitfield_bool!(u16; 10, bg2_2nd_target, with_bg2_2nd_target, set_bg2_2nd_target);
|
||||
bitfield_bool!(u16; 11, bg3_2nd_target, with_bg3_2nd_target, set_bg3_2nd_target);
|
||||
bitfield_bool!(u16; 12, obj_2nd_target, with_obj_2nd_target, set_obj_2nd_target);
|
||||
bitfield_bool!(u16; 13, backdrop_2nd_target, with_backdrop_2nd_target, set_backdrop_2nd_target);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ColorSpecialEffect {
|
||||
NoEffect = 0 << 6,
|
||||
AlphaBlend = 1 << 6,
|
||||
BrightnessIncrease = 2 << 6,
|
||||
BrightnessDecrease = 3 << 6,
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Color(pub u16);
|
||||
impl Color {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=4: u8, red, with_red, set_red);
|
||||
bitfield_int!(u16; 5..=9: u8, green, with_green, set_green);
|
||||
bitfield_int!(u16; 10..=14: u8, blue, with_blue, set_blue);
|
||||
}
|
||||
impl Color {
|
||||
pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
|
||||
let r = red as u16;
|
||||
let g = green as u16;
|
||||
let b = blue as u16;
|
||||
Self(b << 10 | g << 5 | r)
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayControl(u16);
|
||||
impl DisplayControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=2: u16, display_mode, with_display_mode, set_display_mode);
|
||||
bitfield_bool!(u16; 4, display_frame1, with_display_frame1, set_display_frame1);
|
||||
bitfield_bool!(u16; 5, hblank_interval_free, with_hblank_interval_free, set_hblank_interval_free);
|
||||
bitfield_bool!(u16; 6, obj_vram_1d, with_obj_vram_1d, set_obj_vram_1d);
|
||||
bitfield_bool!(u16; 7, forced_blank, with_forced_blank, set_forced_blank);
|
||||
bitfield_bool!(u16; 8, display_bg0, with_display_bg0, set_display_bg0);
|
||||
bitfield_bool!(u16; 9, display_bg1, with_display_bg1, set_display_bg1);
|
||||
bitfield_bool!(u16; 10, display_bg2, with_display_bg2, set_display_bg2);
|
||||
bitfield_bool!(u16; 11, display_bg3, with_display_bg3, set_display_bg3);
|
||||
bitfield_bool!(u16; 12, display_obj, with_display_obj, set_display_obj);
|
||||
bitfield_bool!(u16; 13, display_win0, with_display_win0, set_display_win0);
|
||||
bitfield_bool!(u16; 14, display_win1, with_display_win1, set_display_win1);
|
||||
bitfield_bool!(u16; 15, display_obj_win, with_display_obj_win, set_display_obj_win);
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DisplayMode {
|
||||
_0 = 0,
|
||||
_1 = 1,
|
||||
_2 = 2,
|
||||
_3 = 3,
|
||||
_4 = 4,
|
||||
_5 = 5,
|
||||
_6 = 6,
|
||||
_7 = 7,
|
||||
}
|
||||
*/
|
|
@ -1,15 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayStatus(u16);
|
||||
impl DisplayStatus {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, is_vblank, with_is_vblank, set_is_vblank);
|
||||
bitfield_bool!(u16; 1, is_hblank, with_is_hblank, set_is_hblank);
|
||||
bitfield_bool!(u16; 2, is_vcount, with_is_vcount, set_is_vcount);
|
||||
bitfield_bool!(u16; 3, vblank_irq_enabled, with_vblank_irq_enabled, set_vblank_irq_enabled);
|
||||
bitfield_bool!(u16; 4, hblank_irq_enabled, with_hblank_irq_enabled, set_hblank_irq_enabled);
|
||||
bitfield_bool!(u16; 5, vcount_irq_enabled, with_vcount_irq_enabled, set_vcount_irq_enabled);
|
||||
bitfield_int!(u16; 8..=15: u16, vcount, with_vcount, set_vcount);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DmaControl(u16);
|
||||
impl DmaControl {
|
||||
const_new!();
|
||||
bitfield_enum!(u16; 5..=6: DestAddrControl, dest_addr, with_dest_addr, set_dest_addr);
|
||||
bitfield_enum!(u16; 7..=8: SrcAddrControl, src_addr, with_src_addr, set_src_addr);
|
||||
bitfield_bool!(u16; 9, dma_repeat, with_dma_repeat, set_dma_repeat);
|
||||
bitfield_bool!(u16; 10, transfer_u32, with_transfer_u32, set_transfer_u32);
|
||||
bitfield_bool!(u16; 11, drq_from_game_pak, with_drq_from_game_pak, set_drq_from_game_pak);
|
||||
bitfield_enum!(u16; 12..=13: DmaStartTiming, start_time, with_start_time, set_start_time);
|
||||
bitfield_bool!(u16; 14, irq_when_done, with_irq_when_done, set_irq_when_done);
|
||||
bitfield_bool!(u16; 15, enabled, with_enabled, set_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DestAddrControl {
|
||||
Increment = 0 << 5,
|
||||
Decrement = 1 << 5,
|
||||
Fixed = 2 << 5,
|
||||
IncrementReload = 3 << 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum SrcAddrControl {
|
||||
Increment = 0 << 7,
|
||||
Decrement = 1 << 7,
|
||||
Fixed = 2 << 7,
|
||||
/// Never use this value, it is only to guard against UB bit patterns
|
||||
Prohibited = 3 << 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DmaStartTiming {
|
||||
Immediately = 0 << 12,
|
||||
VBlank = 1 << 12,
|
||||
HBlank = 2 << 12,
|
||||
Special = 3 << 12,
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct FifoControl(u16);
|
||||
impl FifoControl {
|
||||
const_new!();
|
||||
bitfield_enum!(u16; 0..=1: MixVolume, mix_volume, with_mix_volume, set_mix_volume);
|
||||
bitfield_bool!(u16; 2, full_volume_a, with_full_volume_a, set_full_volume_a);
|
||||
bitfield_bool!(u16; 3, full_volume_b, with_full_volume_b, set_full_volume_b);
|
||||
bitfield_bool!(u16; 8, enable_right_a, with_enable_right_a, set_enable_right_a);
|
||||
bitfield_bool!(u16; 9, enable_left_a, with_enable_left_a, set_enable_left_a);
|
||||
bitfield_bool!(u16; 10, use_timer1_a, with_use_timer1_a, set_use_timer1_a);
|
||||
bitfield_bool!(u16; 12, enable_right_b, with_enable_right_b, set_enable_right_b);
|
||||
bitfield_bool!(u16; 13, enable_left_b, with_enable_left_b, set_enable_left_b);
|
||||
bitfield_bool!(u16; 14, use_timer1_b, with_use_timer1_b, set_use_timer1_b);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum MixVolume {
|
||||
_25 = 0,
|
||||
_50 = 1,
|
||||
_100 = 2,
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct FifoReset(u16);
|
||||
impl FifoReset {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 11, reset_a, with_reset_fifo_a, set_reset_fifo_a);
|
||||
bitfield_bool!(u16; 15, reset_b, with_reset_fifo_b, set_reset_fifo_b);
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct InterruptFlags(pub(crate) u16);
|
||||
impl InterruptFlags {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, vblank, with_vblank, set_vblank);
|
||||
bitfield_bool!(u16; 1, hblank, with_hblank, set_hblank);
|
||||
bitfield_bool!(u16; 2, vcount, with_vcount, set_vcount);
|
||||
bitfield_bool!(u16; 3, timer0, with_timer0, set_timer0);
|
||||
bitfield_bool!(u16; 4, timer1, with_timer1, set_timer1);
|
||||
bitfield_bool!(u16; 5, timer2, with_timer2, set_timer2);
|
||||
bitfield_bool!(u16; 6, timer3, with_timer3, set_timer3);
|
||||
bitfield_bool!(u16; 7, serial, with_serial, set_serial);
|
||||
bitfield_bool!(u16; 8, dma0, with_dma0, set_dma0);
|
||||
bitfield_bool!(u16; 9, dma1, with_dma1, set_dma1);
|
||||
bitfield_bool!(u16; 10, dma2, with_dma2, set_dma2);
|
||||
bitfield_bool!(u16; 11, dma3, with_dma3, set_dma3);
|
||||
bitfield_bool!(u16; 12, keypad, with_keypad, set_keypad);
|
||||
bitfield_bool!(u16; 13, gamepak, with_gamepak, set_gamepak);
|
||||
}
|
||||
impl_bitwise_ops!(InterruptFlags);
|
||||
impl core::fmt::Debug for InterruptFlags {
|
||||
#[inline(never)]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "InterruptFlags {{")?;
|
||||
if self.vblank() {
|
||||
write!(f, "vblank,")?;
|
||||
}
|
||||
if self.hblank() {
|
||||
write!(f, "hblank,")?;
|
||||
}
|
||||
if self.vcount() {
|
||||
write!(f, "vcount,")?;
|
||||
}
|
||||
if self.timer0() {
|
||||
write!(f, "timer0,")?;
|
||||
}
|
||||
if self.timer1() {
|
||||
write!(f, "timer1,")?;
|
||||
}
|
||||
if self.timer2() {
|
||||
write!(f, "timer2,")?;
|
||||
}
|
||||
if self.timer3() {
|
||||
write!(f, "timer3,")?;
|
||||
}
|
||||
if self.serial() {
|
||||
write!(f, "serial,")?;
|
||||
}
|
||||
if self.dma0() {
|
||||
write!(f, "dma0,")?;
|
||||
}
|
||||
if self.dma1() {
|
||||
write!(f, "dma1,")?;
|
||||
}
|
||||
if self.dma2() {
|
||||
write!(f, "dma2,")?;
|
||||
}
|
||||
if self.dma3() {
|
||||
write!(f, "dma3,")?;
|
||||
}
|
||||
if self.keypad() {
|
||||
write!(f, "keypad,")?;
|
||||
}
|
||||
if self.gamepak() {
|
||||
write!(f, "gamepak,")?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyInterruptControl(u16);
|
||||
impl KeyInterruptControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a, with_a, set_a);
|
||||
bitfield_bool!(u16; 1, b, with_b, set_b);
|
||||
bitfield_bool!(u16; 2, select, with_select, set_select);
|
||||
bitfield_bool!(u16; 3, start, with_start, set_start);
|
||||
bitfield_bool!(u16; 4, right, with_right, set_right);
|
||||
bitfield_bool!(u16; 5, left, with_left, set_left);
|
||||
bitfield_bool!(u16; 6, up, with_up, set_up);
|
||||
bitfield_bool!(u16; 7, down, with_down, set_down);
|
||||
bitfield_bool!(u16; 8, r, with_r, set_r);
|
||||
bitfield_bool!(u16; 9, l, with_l, set_l);
|
||||
//
|
||||
bitfield_bool!(u16; 14, enabled, with_enabled, set_enabled);
|
||||
bitfield_bool!(u16; 15, require_all, with_require_all, set_require_all);
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeysLowActive(u16);
|
||||
impl KeysLowActive {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a_released, with_a_released, set_a_released);
|
||||
bitfield_bool!(u16; 1, b_released, with_b_released, set_b_released);
|
||||
bitfield_bool!(u16; 2, select_released, with_select_released, set_select_released);
|
||||
bitfield_bool!(u16; 3, start_released, with_start_released, set_start_released);
|
||||
bitfield_bool!(u16; 4, right_released, with_right_released, set_right_released);
|
||||
bitfield_bool!(u16; 5, left_released, with_left_released, set_left_released);
|
||||
bitfield_bool!(u16; 6, up_released, with_up_released, set_up_released);
|
||||
bitfield_bool!(u16; 7, down_released, with_down_released, set_down_released);
|
||||
bitfield_bool!(u16; 8, r_released, with_r_released, set_r_released);
|
||||
bitfield_bool!(u16; 9, l_released, with_l_released, set_l_released);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Keys(u16);
|
||||
impl Keys {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a, with_a, set_a);
|
||||
bitfield_bool!(u16; 1, b, with_b, set_b);
|
||||
bitfield_bool!(u16; 2, select, with_select, set_select);
|
||||
bitfield_bool!(u16; 3, start, with_start, set_start);
|
||||
bitfield_bool!(u16; 4, right, with_right, set_right);
|
||||
bitfield_bool!(u16; 5, left, with_left, set_left);
|
||||
bitfield_bool!(u16; 6, up, with_up, set_up);
|
||||
bitfield_bool!(u16; 7, down, with_down, set_down);
|
||||
bitfield_bool!(u16; 8, r, with_r, set_r);
|
||||
bitfield_bool!(u16; 9, l, with_l, set_l);
|
||||
|
||||
pub const fn x_signum(self) -> i32 {
|
||||
if self.right() {
|
||||
1
|
||||
} else if self.left() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn y_signum(self) -> i32 {
|
||||
if self.down() {
|
||||
1
|
||||
} else if self.up() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: bit ops for keys
|
||||
|
||||
impl From<KeysLowActive> for Keys {
|
||||
fn from(low_active: KeysLowActive) -> Self {
|
||||
Self(low_active.0 ^ 0b11_1111_1111)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keys> for KeysLowActive {
|
||||
fn from(keys: Keys) -> Self {
|
||||
Self(keys.0 ^ 0b11_1111_1111)
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct MosaicSize(u8);
|
||||
impl MosaicSize {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=3: u8, horizontal, with_horizontal, set_horizontal);
|
||||
bitfield_int!(u8; 4..=7: u8, vertical, with_vertical, set_vertical);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseFrequencyControl(u16);
|
||||
impl NoiseFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=2: u16, div_ratio, with_div_ratio, set_div_ratio);
|
||||
bitfield_bool!(u16; 3, counter_width, with_counter_width, set_counter_width);
|
||||
bitfield_int!(u16; 4..=7: u16, shift_frequency, with_shift_frequency, set_shift_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseLenEnv(u16);
|
||||
impl NoiseLenEnv {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length);
|
||||
bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step);
|
||||
bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing);
|
||||
bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr0(u16);
|
||||
impl ObjAttr0 {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=7: u16, y_pos, with_y_pos, set_y_pos);
|
||||
bitfield_bool!(u16; 8, affine, with_affine, set_affine);
|
||||
bitfield_bool!(u16; 9, double_disabled, with_double_disabled, set_double_disabled);
|
||||
bitfield_int!(u16; 10..=11: u16, obj_mode, with_obj_mode, set_obj_mode);
|
||||
bitfield_bool!(u16; 12, mosaic, with_mosaic, set_mosaic);
|
||||
bitfield_bool!(u16; 13, use_palbank, with_use_palbank, set_use_palbank);
|
||||
bitfield_int!(u16; 14..=15: u16, obj_shape, with_obj_shape, set_obj_shape);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr1(u16);
|
||||
impl ObjAttr1 {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=8: u16, x_pos, with_x_pos, set_x_pos);
|
||||
bitfield_int!(u16; 9..=13: u16, affine_index, with_affine_index, set_affine_index);
|
||||
bitfield_bool!(u16; 12, hflip, with_hflip, set_hflip);
|
||||
bitfield_bool!(u16; 13, vflip, with_vflip, set_vflip);
|
||||
bitfield_int!(u16; 14..=15: u16, obj_size, with_obj_size, set_obj_size);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr2(u16);
|
||||
impl ObjAttr2 {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=9: u16, tile_index, with_tile_index, set_tile_index);
|
||||
bitfield_int!(u16; 10..=11: u16, priority, with_priority, set_priority);
|
||||
bitfield_int!(u16; 12..=15: u16, palbank_index, with_palbank_index, set_palbank_index);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ResetFlags(pub(crate) u8);
|
||||
impl ResetFlags {
|
||||
const_new!();
|
||||
//bitfield_bool!(u8; 0, ewram, with_ewram, set_ewram);
|
||||
//bitfield_bool!(u8; 1, iwram, with_iwram, set_iwram);
|
||||
bitfield_bool!(u8; 2, palram, with_palram, set_palram);
|
||||
bitfield_bool!(u8; 3, vram, with_vram, set_vram);
|
||||
bitfield_bool!(u8; 4, oam, with_oam, set_oam);
|
||||
bitfield_bool!(u8; 5, sio, with_sio, set_sio);
|
||||
bitfield_bool!(u8; 6, sound, with_sound, set_sound);
|
||||
bitfield_bool!(u8; 7, all_other_io, with_all_other_io, set_all_other_io);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundBias(u16);
|
||||
impl SoundBias {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 1..=9: u16, bias, with_bias, set_bias);
|
||||
bitfield_enum!(u16; 14..=15: SampleBits, sample_bits, with_sample_bits, set_sample_bits);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum SampleBits {
|
||||
_9 = 0 << 14,
|
||||
_8 = 1 << 14,
|
||||
_7 = 2 << 14,
|
||||
_6 = 3 << 14,
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundControl(u16);
|
||||
impl SoundControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=2: u16, right_volume, with_right_volume, set_right_volume);
|
||||
bitfield_int!(u16; 4..=6: u16, left_volume, with_left_volume, set_left_volume);
|
||||
bitfield_bool!(u16; 8, tone1_right, with_tone1_right, set_tone1_right);
|
||||
bitfield_bool!(u16; 9, tone2_right, with_tone2_right, set_tone2_right);
|
||||
bitfield_bool!(u16; 10, wave_right, with_wave_right, set_wave_right);
|
||||
bitfield_bool!(u16; 11, noise_right, with_noise_right, set_noise_right);
|
||||
bitfield_bool!(u16; 12, tone1_left, with_tone1_left, set_tone1_left);
|
||||
bitfield_bool!(u16; 13, tone2_left, with_tone2_left, set_tone2_left);
|
||||
bitfield_bool!(u16; 14, wave_left, with_wave_left, set_wave_left);
|
||||
bitfield_bool!(u16; 15, noise_left, with_noise_left, set_noise_left);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundStatus(u8);
|
||||
impl SoundStatus {
|
||||
const_new!();
|
||||
bitfield_bool!(u8; 0, tone1_playing, with_tone1_playing, set_tone1_playing);
|
||||
bitfield_bool!(u8; 1, tone2_playing, with_tone2_playing, set_tone2_playing);
|
||||
bitfield_bool!(u8; 2, wave_playing, with_wave_playing, set_wave_playing);
|
||||
bitfield_bool!(u8; 3, noise_playing, with_noise_playing, set_noise_playing);
|
||||
bitfield_bool!(u8; 7, enabled, with_enabled, set_enabled);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct TimerControl(u8);
|
||||
impl TimerControl {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=1: u8, prescaler_selection, with_prescaler_selection, set_prescaler_selection);
|
||||
bitfield_bool!(u8; 2, chained_counting, with_chained_counting, set_chained_counting);
|
||||
bitfield_bool!(u8; 6, irq_on_overflow, with_irq_on_overflow, set_irq_on_overflow);
|
||||
bitfield_bool!(u8; 7, enabled, with_enabled, set_enabled);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneDutyLenEnv(u16);
|
||||
impl ToneDutyLenEnv {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length);
|
||||
bitfield_int!(u16; 6..=7: u16, wave_pattern, with_wave_pattern, set_wave_pattern);
|
||||
bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step);
|
||||
bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing);
|
||||
bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneFrequencyControl(u16);
|
||||
impl ToneFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneSweep(u8);
|
||||
impl ToneSweep {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=2: u8, sweep_shift, with_sweep_shift, set_sweep_shift);
|
||||
bitfield_bool!(u8; 3, frequency_decreasing, with_frequency_decreasing, set_frequency_decreasing);
|
||||
bitfield_int!(u8; 4..=6: u8, sweep_time, with_sweep_time, set_sweep_time);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveControl(u8);
|
||||
impl WaveControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u8; 5, two_banks, with_two_banks, set_two_banks);
|
||||
bitfield_bool!(u8; 6, use_bank1, with_use_bank1, set_use_bank1);
|
||||
bitfield_bool!(u8; 7, playing, with_playing, set_playing);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveFrequencyControl(u16);
|
||||
impl WaveFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveLenVolume(u16);
|
||||
impl WaveLenVolume {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=7: u16, length, with_length, set_length);
|
||||
bitfield_int!(u16; 13..=14: u16, volume, with_volume, set_volume);
|
||||
bitfield_bool!(u16; 15, force75, with_force75, set_force75);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WindowEnable(u8);
|
||||
impl WindowEnable {
|
||||
const_new!();
|
||||
bitfield_bool!(u8; 0, bg0, with_bg0, set_bg0);
|
||||
bitfield_bool!(u8; 1, bg1, with_bg1, set_bg1);
|
||||
bitfield_bool!(u8; 2, bg2, with_bg2, set_bg2);
|
||||
bitfield_bool!(u8; 3, bg3, with_bg3, set_bg3);
|
||||
bitfield_bool!(u8; 4, obj, with_obj, set_obj);
|
||||
bitfield_bool!(u8; 5, effect, with_effect, set_effect);
|
||||
}
|
4
src/prelude.rs
Normal file
4
src/prelude.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub use crate::{
|
||||
asm_runtime::*, bios::*, dma::*, gba_cell::*, interrupts::*, keys::*,
|
||||
mmio::*, sound::*, timers::*, video::*,
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
|
||||
|
||||
//! Module for random number generation
|
||||
//!
|
||||
//! This module provides functions and utilites for randomly generated values
|
||||
//! # Usage
|
||||
//! ```rust
|
||||
//! use gba::random::RNG;
|
||||
//!
|
||||
//! let mut rng = RNG::seed(123, 321);
|
||||
//! let x = rng.next_u32();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
macro_rules! make_jump_lcgX {
|
||||
($(#[$attr:meta])* $f:ident, $u:ty) => {
|
||||
$(#[$attr])*
|
||||
/// Gives the state `delta` steps from now in `log(delta)` time.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
const fn $f(mut delta: $u, state: $u, mult: $u, inc: $u) -> $u {
|
||||
let mut cur_mult: $u = mult;
|
||||
let mut cur_plus: $u = inc;
|
||||
let mut acc_mult: $u = 1;
|
||||
let mut acc_plus: $u = 0;
|
||||
while delta > 0 {
|
||||
if (delta & 1) > 0 {
|
||||
acc_mult = acc_mult.wrapping_mul(cur_mult);
|
||||
acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus);
|
||||
}
|
||||
cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus);
|
||||
cur_mult = cur_mult.wrapping_mul(cur_mult);
|
||||
delta /= 2;
|
||||
}
|
||||
acc_mult.wrapping_mul(state).wrapping_add(acc_plus)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod gen32;
|
||||
pub use gen32::*;
|
||||
|
||||
mod pcg32;
|
||||
pub use pcg32::*;
|
||||
|
||||
mod bounded_rand;
|
||||
pub use bounded_rand::*;
|
||||
|
||||
mod algorithms;
|
|
@ -1,30 +0,0 @@
|
|||
/// Advances a PCG with 32 bits of state.
|
||||
macro_rules! pcg_core_state32 {
|
||||
($state:expr, $inc:expr) => {
|
||||
$state.wrapping_mul(PCG_MULTIPLIER_32).wrapping_add($inc)
|
||||
};
|
||||
}
|
||||
pub(crate) use pcg_core_state32;
|
||||
/// Generates u32 from u32 state
|
||||
macro_rules! pcg_rxs_m_xs_u32_to_u32 {
|
||||
($state: expr) => {{
|
||||
$state ^= ($state >> (4 + ($state >> 28) as u32)).wrapping_mul(277803737u32);
|
||||
$state ^ ($state >> 22)
|
||||
}};
|
||||
}
|
||||
pub(crate) use pcg_rxs_m_xs_u32_to_u32;
|
||||
|
||||
// Alternative for u32 to u16
|
||||
// macro_rules! pcg_xsh_rr_u32_to_u16 {
|
||||
// ($state: expr) => {
|
||||
// ((($state ^ ($state >> 18)) >> 11) as u16).rotate_right($state >> 27) as u16
|
||||
// };
|
||||
// }
|
||||
// pub(crate) use pcg_xsh_rr_u32_to_u16;
|
||||
/// Generates u16 from u32 state
|
||||
macro_rules! pcg_xsh_rs_u32_to_u16 {
|
||||
($state: expr) => {
|
||||
(($state ^ ($state >> 6)) >> (6 + ($state >> 29))) as u16
|
||||
};
|
||||
}
|
||||
pub(crate) use pcg_xsh_rs_u32_to_u16;
|
|
@ -1,73 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
/// Stores the values to sample a number in `0 .. N`
|
||||
///
|
||||
/// Making one of these performs a division operation. In comparison,
|
||||
/// [`Gen32::next_bounded`] will avoid needing to do a division much of the
|
||||
/// time. Thus, unless you need to sample repeatedly from a specific bounded
|
||||
/// range, simply calling `next_bounded` directly might be more efficient.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BoundedRandU16 {
|
||||
/// number of possible outputs. outputs will be in `0 .. count`
|
||||
count: u16,
|
||||
/// Multiplication threshold thing.
|
||||
///
|
||||
/// <https://arxiv.org/abs/1805.10941>
|
||||
threshold: u16,
|
||||
}
|
||||
impl BoundedRandU16 {
|
||||
/// Constructs a new `BoundedRandU32`.
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the count is 0.
|
||||
#[inline]
|
||||
pub const fn new(count: u16) -> Self {
|
||||
let threshold = count.wrapping_neg() % count;
|
||||
Self { count, threshold }
|
||||
}
|
||||
|
||||
/// Constructs a new `BoundedRandU32`, or `None` on failure.
|
||||
///
|
||||
/// ## Failure
|
||||
/// If the count is 0.
|
||||
#[inline]
|
||||
pub const fn try_new(count: u16) -> Option<Self> {
|
||||
if count > 0 {
|
||||
Some(Self::new(count))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of possible outputs.
|
||||
#[inline]
|
||||
pub const fn count(self) -> u16 {
|
||||
self.count
|
||||
}
|
||||
|
||||
/// Given a `u32`, place it into this bounded range.
|
||||
///
|
||||
/// ## Failure
|
||||
/// * If the value is such that it doesn't fit evenly it is rejected.
|
||||
#[inline]
|
||||
pub const fn place_in_range(self, val: u16) -> Option<u16> {
|
||||
let mul: u32 = (val as u32).wrapping_mul(self.count as u32);
|
||||
let low_part: u16 = mul as u16;
|
||||
if low_part < self.threshold {
|
||||
None
|
||||
} else {
|
||||
//debug_assert!(((mul >> 32) as u32) < self.count());
|
||||
Some((mul >> 16) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a gen, sample from the gen until `place_in_range` succeeds.
|
||||
#[inline]
|
||||
pub fn sample<G: Gen32 + ?Sized>(self, gen: &mut G) -> u16 {
|
||||
loop {
|
||||
if let Some(output) = self.place_in_range(gen.next_u16()) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
use core::convert::{TryFrom, TryInto};
|
||||
|
||||
use crate::mmio_types::Color;
|
||||
|
||||
/// A Generator with 32 bits of output per step.
|
||||
pub trait Gen32 {
|
||||
/// Generates the next 32 bits of output.
|
||||
fn next_u32(&mut self) -> u32;
|
||||
/// Generates the next 16-bits of output
|
||||
fn next_u16(&mut self) -> u16;
|
||||
|
||||
/// Produces a `Color`
|
||||
fn next_color(&mut self) -> Color {
|
||||
Color(self.next_u16() & 0b0111111111111111)
|
||||
}
|
||||
/// Produce a `bool`
|
||||
#[inline(always)]
|
||||
fn next_bool(&mut self) -> bool {
|
||||
(self.next_u32() as i32) < 0
|
||||
}
|
||||
|
||||
/// Produce a `u8`
|
||||
#[inline(always)]
|
||||
fn next_u8(&mut self) -> u8 {
|
||||
(self.next_u16() >> 8) as u8
|
||||
}
|
||||
|
||||
/// Produce a `u64`
|
||||
#[inline(always)]
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
let l = self.next_u32() as u64;
|
||||
let h = self.next_u32() as u64;
|
||||
h << 32 | l
|
||||
}
|
||||
|
||||
/// Gives a value within `0 .. B`
|
||||
///
|
||||
/// This is often more efficient than making a
|
||||
/// [`BoundedRandU32`](crate::random::BoundedRandU32) if you don't need to use a
|
||||
/// specific bound value more than once.
|
||||
///
|
||||
/// ## Panics
|
||||
/// * If the input is 0.
|
||||
#[inline]
|
||||
fn next_bounded(&mut self, b: u16) -> u16 {
|
||||
assert!(b != 0, "Gen32::next_bounded> Bound must be non-zero.");
|
||||
let mut x = self.next_u16() as u32;
|
||||
let mut mul = (b as u32).wrapping_mul(x);
|
||||
let mut low = mul as u16;
|
||||
if low < b {
|
||||
let threshold = b.wrapping_neg() % b;
|
||||
while low < threshold {
|
||||
x = self.next_u32() as u32;
|
||||
mul = (b as u32).wrapping_mul(x);
|
||||
low = mul as u16;
|
||||
}
|
||||
}
|
||||
let high = (mul >> 16) as u16;
|
||||
high
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by copy).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick<T>(&mut self, buf: &[T]) -> T
|
||||
where
|
||||
Self: Sized,
|
||||
T: Copy,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by shared ref).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick_ref<'b, T>(&mut self, buf: &'b [T]) -> &'b T
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
&buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by unique ref).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick_mut<'b, T>(&mut self, buf: &'b mut [T]) -> &'b mut T
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
&mut buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Shuffles a slice in `O(len)` time.
|
||||
///
|
||||
/// * The default impl shuffles only the first `u16::MAX` elements.
|
||||
#[inline]
|
||||
fn shuffle<T>(&mut self, buf: &mut [T])
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Note(Lokathor): The "standard" Fisher-Yates shuffle goes backward from
|
||||
// the end of the slice, but this version allows us to access memory forward
|
||||
// from the start to the end, so that we play more nicely with the
|
||||
// fetch-ahead of most modern CPUs.
|
||||
let mut possibility_count: u16 = buf.len().try_into().unwrap_or(u16::max_value());
|
||||
let mut this_index: usize = 0;
|
||||
let end = buf.len() - 1;
|
||||
while this_index < end {
|
||||
let offset = self.next_bounded(possibility_count) as usize;
|
||||
buf.swap(this_index, this_index + offset);
|
||||
possibility_count -= 1;
|
||||
this_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts that `Gen32` is an object-safe trait.
|
||||
const _: [&mut dyn Gen32; 0] = [];
|
||||
|
||||
/// Converts the `usize` into a `u16`, or gives `u16::MAX` if that wouldn't fit.
|
||||
#[inline(always)]
|
||||
const fn saturating_usize_as_u16(val: usize) -> u16 {
|
||||
if val <= u16::MAX as usize {
|
||||
val as u16
|
||||
} else {
|
||||
u16::MAX
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
use super::{algorithms::*, Gen32};
|
||||
|
||||
/// A default seed for any PCG.
|
||||
///
|
||||
/// Truncate to fit, as necessary.
|
||||
pub const DEFAULT_PCG_SEED: u128 = 201526561274146932589719779721328219291;
|
||||
|
||||
/// A default `inc` for any PCG.
|
||||
///
|
||||
/// Truncate to fit, as necessary.
|
||||
pub const DEFAULT_PCG_INC: u128 = 34172814569070222299;
|
||||
|
||||
// Other multipliers: 0xffffffff0e703b65 0xf2fc5985
|
||||
const PCG_MULTIPLIER_32: u32 = 0xf13283ad;
|
||||
|
||||
make_jump_lcgX!(jump_lcg32, u32);
|
||||
|
||||
/// A [permuted congruential
|
||||
/// generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator)
|
||||
/// with 32 bits of output per step.
|
||||
///
|
||||
/// * Generally you should create new generator values with the
|
||||
/// [`seed`](Self::seed) constructor. This will shuffle around the inputs
|
||||
/// somewhat, so it will work alright even with "boring" input values like
|
||||
/// `seed(0,0)` or whatever.
|
||||
/// * If you want to exactly save/restore a generator use the `Into` and `From`
|
||||
/// impls to convert the generator into and from a `[u32; 2]`.
|
||||
/// * The methods on this type are quite minimal. You're expected to use the
|
||||
/// [`Gen32`] trait to provide most of the useful operations.
|
||||
///
|
||||
/// Full list of methods can be found in the [`Gen32`] trait
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RNG {
|
||||
/// 32 bit state, which is used to generate the ouput
|
||||
state: u32,
|
||||
/// `inc` used when advancing the state forward
|
||||
inc: u32,
|
||||
}
|
||||
|
||||
impl RNG {
|
||||
/// Seed a new generator.
|
||||
/// Used to create a new random number generator.
|
||||
pub const fn seed(seed: u32, inc: u32) -> Self {
|
||||
let inc = (inc << 1) | 1;
|
||||
let mut state = pcg_core_state32!(0_u32, inc);
|
||||
state = state.wrapping_add(seed);
|
||||
state = pcg_core_state32!(state, inc);
|
||||
Self { state, inc }
|
||||
}
|
||||
|
||||
/// Gets the next 32-bits of output.
|
||||
#[inline]
|
||||
pub fn next_u32(&mut self) -> u32 {
|
||||
// LLVM do the instruction-level parallelism plz ;_;
|
||||
let out = pcg_rxs_m_xs_u32_to_u32!(self.state);
|
||||
self.state = pcg_core_state32!(self.state, self.inc);
|
||||
out
|
||||
}
|
||||
/// Gets the next 16-bits of output.
|
||||
#[inline]
|
||||
pub fn next_u16(&mut self) -> u16 {
|
||||
let out = pcg_xsh_rs_u32_to_u16!(self.state);
|
||||
self.state = pcg_core_state32!(self.state, self.inc);
|
||||
out
|
||||
}
|
||||
/// Jumps the generator by `delta` steps forward.
|
||||
///
|
||||
/// The generator sequence loops, so if you want to go "backwards" you can
|
||||
/// just subtract the number of steps you want to go back from `u32::MAX` and
|
||||
/// jump by that amount.
|
||||
#[inline]
|
||||
pub fn jump(&mut self, delta: u32) {
|
||||
self.state = jump_lcg32(delta, self.state, PCG_MULTIPLIER_32, self.inc);
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen32 for RNG {
|
||||
#[inline(always)]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
RNG::next_u32(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_u16(&mut self) -> u16 {
|
||||
RNG::next_u16(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RNG {
|
||||
fn default() -> Self {
|
||||
const THE_DEFAULT: RNG = RNG::seed(DEFAULT_PCG_SEED as _, DEFAULT_PCG_INC as _);
|
||||
THE_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u32; 2]> for RNG {
|
||||
fn from([state, inc]: [u32; 2]) -> Self {
|
||||
Self { state, inc }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RNG> for [u32; 2] {
|
||||
fn from(pcg: RNG) -> Self {
|
||||
[pcg.state, pcg.inc]
|
||||
}
|
||||
}
|
||||
|
||||
|
104
src/rsrt0.S
104
src/rsrt0.S
|
@ -1,104 +0,0 @@
|
|||
.arm
|
||||
.global __start
|
||||
|
||||
__start:
|
||||
b .Linit
|
||||
|
||||
@ this is replaced with correct header info by `gbafix`
|
||||
.space 188
|
||||
|
||||
.Linit:
|
||||
@ Set address of user IRQ handler
|
||||
@ldr r0, =MainIrqHandler
|
||||
@mov r1, #0x04000000
|
||||
@str r0, [r1, #-4]
|
||||
|
||||
@ set IRQ stack pointer
|
||||
mov r0, #0x12
|
||||
msr CPSR_c, r0
|
||||
ldr sp, =0x03007FA0
|
||||
|
||||
@ set user stack pointer
|
||||
mov r0, #0x1f
|
||||
msr CPSR_c, r0
|
||||
ldr sp, =0x03007F00
|
||||
|
||||
@ Sets WAITCNT to the default used by GBA games
|
||||
@
|
||||
@ See https://problemkaputt.de/gbatek.htm#gbasystemcontrol for reference.
|
||||
ldr r0, =0x04000204
|
||||
ldr r1, =0x4317
|
||||
strh r1, [r0]
|
||||
|
||||
@ copy .data and .text_iwram section to IWRAM
|
||||
ldr r0, =__iwram_lma @ source address
|
||||
ldr r1, =__iwram_start @ destination address
|
||||
ldr r2, =__iwram_end
|
||||
subs r2, r1 @ length
|
||||
@ these instructions are only executed if r2 is nonzero
|
||||
@ (i.e. don't bother copying an empty .iwram section)
|
||||
addne r2, #3
|
||||
asrne r2, #2
|
||||
addne r2, #0x04000000
|
||||
swine 0xB0000
|
||||
|
||||
@ jump to user code
|
||||
ldr r0, =main
|
||||
bx r0
|
||||
@ main should be `fn() -> !`, but it doesn't hurt to guard
|
||||
1: b 1b
|
||||
|
||||
/*
|
||||
.arm
|
||||
.global MainIrqHandler
|
||||
.align 4, 0
|
||||
MainIrqHandler:
|
||||
@ Load base I/O register address
|
||||
mov r2, #0x04000000
|
||||
add r2, r2, #0x200
|
||||
|
||||
@ Save IRQ stack pointer and IME
|
||||
mrs r0, spsr
|
||||
ldrh r1, [r2, #8]
|
||||
stmdb sp!, {r0-r2,lr}
|
||||
|
||||
@ Disable all interrupts by writing to IME
|
||||
@ r2 (0x4000200) can be used as we only care about bit 0 being unset
|
||||
strh r2, [r2, #8]
|
||||
|
||||
@ Acknowledge all received interrupts that were enabled in IE
|
||||
ldr r3, [r2, #0]
|
||||
and r0, r3, r3, lsr #16
|
||||
strh r0, [r2, #2]
|
||||
|
||||
@ Switch from IRQ mode to system mode
|
||||
@ cpsr_c = 0b000_10010u8 | 0b000_01101u8
|
||||
mrs r2, cpsr
|
||||
orr r2, r2, #0xD
|
||||
msr cpsr_c, r2
|
||||
|
||||
@ Jump to user specified IRQ handler
|
||||
ldr r2, =__IRQ_HANDLER
|
||||
ldr r1, [r2]
|
||||
stmdb sp!, {lr}
|
||||
adr lr, .Lreturn
|
||||
bx r1
|
||||
.Lreturn:
|
||||
ldmia sp!, {lr}
|
||||
|
||||
@ Switch from ??? mode to IRQ mode, disable IRQ
|
||||
@ cpsr_c = ( !0b000_01101u8 & cpsr_c ) | 0b100_10010u8
|
||||
mrs r2, cpsr
|
||||
bic r2, r2, #0xD
|
||||
orr r2, r2, #0x92
|
||||
msr cpsr_c, r2
|
||||
|
||||
@ Restore IRQ stack pointer and IME
|
||||
ldmia sp!, {r0-r2,lr}
|
||||
strh r1, [r2, #8]
|
||||
msr spsr_cf, r0
|
||||
|
||||
@ Return to BIOS IRQ handler
|
||||
bx lr
|
||||
.pool
|
||||
*/
|
336
src/save.rs
336
src/save.rs
|
@ -1,336 +0,0 @@
|
|||
//! Module for reading and writing to save media.
|
||||
//!
|
||||
//! This module provides both specific interfaces that directly access particular
|
||||
//! types of save media, and an abstraction layer that allows access to all kinds
|
||||
//! of save media using a shared interface.
|
||||
//!
|
||||
//! ## Save media types
|
||||
//!
|
||||
//! There are, broadly speaking, three different kinds of save media that can be
|
||||
//! found in official Game Carts:
|
||||
//!
|
||||
//! * Battery-Backed SRAM: The simplest kind of save media, which can be accessed
|
||||
//! like normal memory. You can have SRAM up to 32KiB, and while there exist a
|
||||
//! few variants this does not matter much for a game developer.
|
||||
//! * EEPROM: A kind of save media based on very cheap chips and slow chips.
|
||||
//! These are accessed using a serial interface based on reading/writing bit
|
||||
//! streams into IO registers. This memory comes in 8KiB and 512 byte versions,
|
||||
//! which unfortunately cannot be distinguished at runtime.
|
||||
//! * Flash: A kind of save media based on flash memory. Flash memory can be read
|
||||
//! like ordinary memory, but writing requires sending commands using multiple
|
||||
//! IO register spread across the address space. This memory comes in 64KiB
|
||||
//! and 128KiB variants, which can thankfully be distinguished using a chip ID.
|
||||
//!
|
||||
//! As these various types of save media cannot be easily distinguished at
|
||||
//! runtime, the kind of media in use should be set manually.
|
||||
//!
|
||||
//! ## Setting save media type
|
||||
//!
|
||||
//! To use save media in your game, you must set which type to use. This is done
|
||||
//! by calling one of the following functions at startup:
|
||||
//!
|
||||
//! * For 32 KiB battery-backed SRAM, call [`use_sram`].
|
||||
//! * For 64 KiB flash memory, call [`use_flash_64k`].
|
||||
//! * For 128 KiB flash memory, call [`use_flash_128k`].
|
||||
//! * For 512 byte EEPROM, call [`use_eeprom_512b`].
|
||||
//! * For 8 KiB EEPROM, call [`use_eeprom_8k`].
|
||||
//!
|
||||
//! Then, call [`set_timer_for_timeout`] to set the timer you intend to use to
|
||||
//! track the timeout that prevents errors with the save media from hanging your
|
||||
//! game. For more information on GBA timers, see the
|
||||
//! [`timers`](`crate::mmio_types::TimerControl`) module's documentation.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use gba::save;
|
||||
//! save::use_flash_128k();
|
||||
//! save::set_timer_for_timeout(3); // Uses timer 3 for save media timeouts.
|
||||
//! ```
|
||||
//!
|
||||
//! ## Using save media
|
||||
//!
|
||||
//! To access save media, use the [`SaveAccess::new`] method to create a new
|
||||
//! [`SaveAccess`] object. Its methods are used to read or write save media.
|
||||
//!
|
||||
//! Reading data from the savegame is simple. Use [`read`](`SaveAccess::read`)
|
||||
//! to copy data from an offset in the savegame into a buffer in memory.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use gba::{info, save::SaveAccess};
|
||||
//! let mut buf = [0; 1000];
|
||||
//! SaveAccess::new()?.read(1000, &mut buf)?;
|
||||
//! info!("Memory result: {:?}", buf);
|
||||
//! ```
|
||||
//!
|
||||
//! Writing to save media requires you to prepare the area for writing by calling
|
||||
//! the [`prepare_write`](`SaveAccess::prepare_write`) method before doing the
|
||||
//! actual write commands with the [`write`](`SaveAccess::write`) method.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use gba::{info, save::SaveAccess};
|
||||
//! let access = SaveAccess::new()?;
|
||||
//! access.prepare_write(500..600)?;
|
||||
//! access.write(500, &[10; 25])?;
|
||||
//! access.write(525, &[20; 25])?;
|
||||
//! access.write(550, &[30; 25])?;
|
||||
//! access.write(575, &[40; 25])?;
|
||||
//! ```
|
||||
//!
|
||||
//! The `prepare_write` method leaves everything in a sector that overlaps the
|
||||
//! range passed to it in an implementation defined state. On some devices it may
|
||||
//! do nothing, and on others, it may clear the entire range to `0xFF`.
|
||||
//!
|
||||
//! Because writes can only be prepared on a per-sector basis, a clear on a range
|
||||
//! of `4000..5000` on a device with 4096 byte sectors will actually clear a range
|
||||
//! of `0..8192`. Use [`sector_size`](`SaveAccess::sector_size`) to find the
|
||||
//! sector size, or [`align_range`](`SaveAccess::align_range`) to directly
|
||||
//! calculate the range of memory that will be affected by the clear.
|
||||
//!
|
||||
//! ## Performance and Other Details
|
||||
//!
|
||||
//! Because `prepare_write` does nothing on non-flash chips, it would not cause
|
||||
//! correctness issues to ignore it. Even so, it is recommend to write code to
|
||||
//! use the `prepare_write` function regardless of the save media, as it has
|
||||
//! minimal runtime cost on other save media types. If needed, you can check if
|
||||
//! `prepare_write` is required by calling the
|
||||
//! (`requires_prepare_write`)(`SaveAccess::requires_prepare_write`) method.
|
||||
//!
|
||||
//! Some memory types have a `sector_size` above `1`, but do not use
|
||||
//! `prepare_write`. This indicates that the media type has sectors that must
|
||||
//! be rewritten all at once, instead of supporting the separate erase/write
|
||||
//! cycles that flash media does. Writing non-sector aligned memory will be
|
||||
//! slower on such save media, as the implementation needs to read the old
|
||||
//! contents into a buffer before writing to avoid data loss.
|
||||
//!
|
||||
//! To summarize, for all supported media types:
|
||||
//!
|
||||
//! * SRAM does not require `prepare_write` and has no sectors to align to. Reads
|
||||
//! and writes at any alignment are efficient. Furthermore, it does not require
|
||||
//! a timer to be set with [`set_timer_for_timeout`].
|
||||
//! * Non-Atmel flash chips requires `prepare_write`, and have sectors of 4096
|
||||
//! bytes. Atmel flash chips instead do not require `prepare_write`, and instead
|
||||
//! have sectors of 128 bytes. You should generally try to use `prepare_write`
|
||||
//! regardless, and write in blocks of 128 bytes if at all possible.
|
||||
//! * EEPROM does not require `prepare_write` and has sectors of 8 bytes.
|
||||
|
||||
use crate::sync::Static;
|
||||
use core::ops::Range;
|
||||
|
||||
mod asm_utils;
|
||||
mod setup;
|
||||
mod utils;
|
||||
|
||||
pub use asm_utils::*;
|
||||
pub use setup::*;
|
||||
pub use utils::*;
|
||||
|
||||
pub mod eeprom;
|
||||
pub mod flash;
|
||||
pub mod sram;
|
||||
|
||||
/// A list of save media types.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
pub enum MediaType {
|
||||
/// 32KiB Battery-Backed SRAM or FRAM
|
||||
Sram32K,
|
||||
/// 8KiB EEPROM
|
||||
Eeprom8K,
|
||||
/// 512B EEPROM
|
||||
Eeprom512B,
|
||||
/// 64KiB flash chip
|
||||
Flash64K,
|
||||
/// 128KiB flash chip
|
||||
Flash128K,
|
||||
/// A user-defined save media type
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// The type used for errors encountered while reading or writing save media.
|
||||
#[derive(Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// There is no save media attached to this game cart.
|
||||
NoMedia,
|
||||
/// Failed to write the data to save media.
|
||||
WriteError,
|
||||
/// An operation on save media timed out.
|
||||
OperationTimedOut,
|
||||
/// An attempt was made to access save media at an invalid offset.
|
||||
OutOfBounds,
|
||||
/// The media is already in use.
|
||||
///
|
||||
/// This can generally only happen in an IRQ that happens during an ongoing
|
||||
/// save media operation.
|
||||
MediaInUse,
|
||||
/// This command cannot be used with the save media in use.
|
||||
IncompatibleCommand,
|
||||
}
|
||||
|
||||
/// Information about the save media used.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MediaInfo {
|
||||
/// The type of save media installed.
|
||||
pub media_type: MediaType,
|
||||
/// The power-of-two size of each sector. Zero represents a sector size of
|
||||
/// 0, implying sectors are not in use.
|
||||
///
|
||||
/// (For example, 512 byte sectors would return 9 here.)
|
||||
pub sector_shift: usize,
|
||||
/// The size of the save media, in sectors.
|
||||
pub sector_count: usize,
|
||||
/// Whether the save media type requires the use of the
|
||||
/// [`prepare_write`](`SaveAccess::prepare_write`) function before a block of
|
||||
/// memory can be overwritten.
|
||||
pub requires_prepare_write: bool,
|
||||
}
|
||||
|
||||
/// A trait allowing low-level saving and writing to save media.
|
||||
///
|
||||
/// It exposes an interface mostly based around the requirements of reading and
|
||||
/// writing flash memory, as those are the most restrictive.
|
||||
///
|
||||
/// This interface treats memory as a continuous block of bytes for purposes of
|
||||
/// reading, and as an array of sectors .
|
||||
pub trait RawSaveAccess: Sync {
|
||||
/// Returns information about the save media used.
|
||||
fn info(&self) -> Result<&'static MediaInfo, Error>;
|
||||
|
||||
/// Reads a slice of memory from save media.
|
||||
///
|
||||
/// This will attempt to fill `buffer` entirely, and will error if this is
|
||||
/// not possible. The contents of `buffer` are unpredictable if an error is
|
||||
/// returned.
|
||||
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error>;
|
||||
|
||||
/// Verifies that the save media has been successfully written, comparing
|
||||
/// it against the given buffer.
|
||||
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error>;
|
||||
|
||||
/// Prepares a given span of sectors for writing. This may permanently erase
|
||||
/// the current contents of the sector on some save media.
|
||||
fn prepare_write(&self, sector: usize, count: usize) -> Result<(), Error>;
|
||||
|
||||
/// Writes a buffer to the save media.
|
||||
///
|
||||
/// The sectors you are writing to must be prepared with a call to the
|
||||
/// `prepare_write` function beforehand, or else the contents of the save
|
||||
/// media may be unpredictable after writing.
|
||||
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Contains the current save media implementation.
|
||||
static CURRENT_SAVE_ACCESS: Static<Option<&'static dyn RawSaveAccess>> = Static::new(None);
|
||||
|
||||
/// Sets the save media implementation in use.
|
||||
pub fn set_save_implementation(access: Option<&'static dyn RawSaveAccess>) {
|
||||
CURRENT_SAVE_ACCESS.write(access)
|
||||
}
|
||||
|
||||
/// Gets the save media implementation in use.
|
||||
pub fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
|
||||
CURRENT_SAVE_ACCESS.read()
|
||||
}
|
||||
|
||||
/// Allows reading and writing of save media.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SaveAccess {
|
||||
access: &'static dyn RawSaveAccess,
|
||||
info: &'static MediaInfo,
|
||||
}
|
||||
impl SaveAccess {
|
||||
/// Creates a new save accessor around the current save implementaiton.
|
||||
pub fn new() -> Result<SaveAccess, Error> {
|
||||
match get_save_implementation() {
|
||||
Some(access) => Ok(SaveAccess { access, info: access.info()? }),
|
||||
None => Err(Error::NoMedia),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the media info underlying this accessor.
|
||||
pub fn media_info(&self) -> &'static MediaInfo {
|
||||
self.info
|
||||
}
|
||||
|
||||
/// Returns the save media type being used.
|
||||
pub fn media_type(&self) -> MediaType {
|
||||
self.info.media_type
|
||||
}
|
||||
|
||||
/// Returns the sector size of the save media. It is generally optimal to
|
||||
/// write data in blocks that are aligned to the sector size.
|
||||
pub fn sector_size(&self) -> usize {
|
||||
1 << self.info.sector_shift
|
||||
}
|
||||
|
||||
/// Returns the total length of this save media.
|
||||
pub fn len(&self) -> usize {
|
||||
self.info.sector_count << self.info.sector_shift
|
||||
}
|
||||
|
||||
/// Copies data from the save media to a buffer.
|
||||
pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
self.access.read(offset, buffer)
|
||||
}
|
||||
|
||||
/// Verifies that a given block of memory matches the save media.
|
||||
pub fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||
self.access.verify(offset, buffer)
|
||||
}
|
||||
|
||||
/// Returns whether this save media requires the use of [`SaveAccess::prepare_write`].
|
||||
pub fn requires_prepare_write(&self) -> bool {
|
||||
self.info.requires_prepare_write
|
||||
}
|
||||
|
||||
/// Returns a range that contains all sectors the input range overlaps.
|
||||
///
|
||||
/// This can be used to calculate which blocks would be erased by a call
|
||||
/// to [`prepare_write`](`SaveAccess::prepare_write`)
|
||||
pub fn align_range(&self, range: Range<usize>) -> Range<usize> {
|
||||
let shift = self.info.sector_shift;
|
||||
let mask = (1 << shift) - 1;
|
||||
(range.start & !mask)..((range.end + mask) & !mask)
|
||||
}
|
||||
|
||||
/// Prepares a given span of offsets for writing.
|
||||
///
|
||||
/// This will erase any data in any sector overlapping the input range. To
|
||||
/// calculate which offset ranges would be affected, use the
|
||||
/// [`align_range`](`SaveAccess::align_range`) function.
|
||||
pub fn prepare_write(&self, range: Range<usize>) -> Result<(), Error> {
|
||||
if self.info.requires_prepare_write {
|
||||
let range = self.align_range(range);
|
||||
let shift = self.info.sector_shift;
|
||||
self.access.prepare_write(range.start >> shift, range.len() >> shift)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a given buffer into the save media.
|
||||
///
|
||||
/// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns
|
||||
/// `true`, you must call [`prepare_write`](`SaveAccess::prepare_write`) on the
|
||||
/// range you intend to write for this to function correctly. The contents of
|
||||
/// the save media are unpredictable if you do not.
|
||||
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||
self.access.write(offset, buffer)
|
||||
}
|
||||
|
||||
/// Writes and validates a given buffer into the save media.
|
||||
///
|
||||
/// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns
|
||||
/// `true`, you must call [`prepare_write`](`SaveAccess::prepare_write`) on the
|
||||
/// range you intend to write for this to function correctly. The contents of
|
||||
/// the save media will be unpredictable if you do not.
|
||||
///
|
||||
/// This function will verify that the write has completed successfully, and
|
||||
/// return an error if it has not done so.
|
||||
pub fn write_and_verify(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||
self.write(offset, buffer)?;
|
||||
if !self.verify(offset, buffer)? {
|
||||
Err(Error::WriteError)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
@
|
||||
@ char WramReadByte(const char* offset);
|
||||
@
|
||||
@ A routine that reads a byte from a given memory offset.
|
||||
@
|
||||
.thumb
|
||||
.global WramReadByte
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramReadByte:
|
||||
ldr r1, =WramReadByteInner
|
||||
bx r1
|
||||
|
||||
.section .data
|
||||
|
||||
.thumb
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramReadByteInner:
|
||||
ldrb r0, [r0]
|
||||
mov pc, lr
|
||||
|
||||
.section .text
|
||||
|
||||
@
|
||||
@ bool WramVerifyBuf(const char* buf1, const char* buf2, int count);
|
||||
@
|
||||
@ A routine that compares two memory offsets.
|
||||
@
|
||||
.thumb
|
||||
.global WramVerifyBuf
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramVerifyBuf:
|
||||
push {{r4-r5, lr}}
|
||||
movs r5, r0 @ set up r5 to be r0, so we can use it immediately for the return result
|
||||
movs r0, #0 @ set up r0 so the default return result is false
|
||||
ldr r4, =WramVerifyBufInner
|
||||
bx r4 @ jump to the part in WRAM
|
||||
|
||||
.section .data
|
||||
|
||||
.thumb
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramVerifyBufInner:
|
||||
@ At this point, buf1 is actually in r5, so r0 can be used as a status return
|
||||
ldrb r3, [r5,r2]
|
||||
ldrb r4, [r1,r2]
|
||||
cmp r3, r4
|
||||
bne 0f
|
||||
subs r2, #1
|
||||
bpl WramVerifyBufInner
|
||||
|
||||
@ Returns from the function successfully
|
||||
movs r0, #1
|
||||
0: @ Jumps to here return the function unsuccessfully, because r0 contains 0 at this point
|
||||
pop {{r4-r5, pc}}
|
||||
|
||||
.section .text
|
||||
|
||||
@
|
||||
@ void WramXferBuf(const char* source, char* dest, int count);
|
||||
@
|
||||
@ A routine that copies one buffer into another.
|
||||
@
|
||||
.thumb
|
||||
.global WramXferBuf
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramXferBuf:
|
||||
ldr r3, =WramXferBufInner
|
||||
bx r3
|
||||
|
||||
.pool
|
||||
.section .data
|
||||
|
||||
.thumb
|
||||
.thumb_func
|
||||
.align 2
|
||||
WramXferBufInner:
|
||||
subs r2, #1
|
||||
ldrb r3, [r0,r2]
|
||||
strb r3, [r1,r2]
|
||||
bne WramXferBufInner
|
||||
mov pc, lr
|
||||
|
||||
.pool
|
||||
.section .text
|
|
@ -1,85 +0,0 @@
|
|||
//! A module containing low-level assembly functions that can be loaded into
|
||||
//! WRAM. Both flash media and battery-backed SRAM require reads to be
|
||||
//! performed via code in WRAM and cannot be accessed by DMA.
|
||||
|
||||
use core::arch::global_asm;
|
||||
|
||||
#[cfg_attr(not(target_arch = "arm"), allow(unused_variables, non_snake_case))]
|
||||
#[cfg(target_arch = "arm")]
|
||||
global_asm!(include_str!("asm_routines.s"));
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
extern "C" {
|
||||
fn WramXferBuf(src: *const u8, dst: *mut u8, count: usize);
|
||||
fn WramReadByte(src: *const u8) -> u8;
|
||||
fn WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
fn WramXferBuf(src: *const u8, dst: *mut u8, count: usize) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
fn WramReadByte(src: *const u8) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
fn WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Copies data from a given memory address into a buffer.
|
||||
///
|
||||
/// This should be used to access any data found in flash or battery-backed
|
||||
/// SRAM, as you must read those one byte at a time and from code stored
|
||||
/// in WRAM.
|
||||
///
|
||||
/// This uses raw addresses into the memory space. Use with care.
|
||||
#[inline(always)]
|
||||
pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) {
|
||||
if dst.len() != 0 {
|
||||
WramXferBuf(src as _, dst.as_mut_ptr(), dst.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies data from a buffer into a given memory address.
|
||||
///
|
||||
/// This is not strictly needed to write into save media, but reuses the
|
||||
/// optimized loop used in `read_raw_buf`, and will often be faster.
|
||||
///
|
||||
/// This uses raw addresses into the memory space. Use with care.
|
||||
#[inline(always)]
|
||||
pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) {
|
||||
if src.len() != 0 {
|
||||
WramXferBuf(src.as_ptr(), dst as _, src.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that the data in a buffer matches that in a given memory address.
|
||||
///
|
||||
/// This should be used to access any data found in flash or battery-backed
|
||||
/// SRAM, as you must read those one byte at a time and from code stored
|
||||
/// in WRAM.
|
||||
///
|
||||
/// This uses raw addresses into the memory space. Use with care.
|
||||
#[inline(always)]
|
||||
pub unsafe fn verify_raw_buf(buf1: &[u8], buf2: usize) -> bool {
|
||||
if buf1.len() != 0 {
|
||||
WramVerifyBuf(buf1.as_ptr(), buf2 as _, buf1.len() - 1)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a byte from a given memory address.
|
||||
///
|
||||
/// This should be used to access any data found in flash or battery-backed
|
||||
/// SRAM, as you must read those from code found in WRAM.
|
||||
///
|
||||
/// This uses raw addresses into the memory space. Use with care.
|
||||
#[inline(always)]
|
||||
pub unsafe fn read_raw_byte(src: usize) -> u8 {
|
||||
WramReadByte(src as _)
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
//! A module containing support for EEPROM.
|
||||
//!
|
||||
//! EEPROM requires using DMA to issue commands for both reading and writing.
|
||||
|
||||
use super::{Error, MediaType, RawSaveAccess};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
save::{lock_media, MediaInfo, Timeout},
|
||||
sync::with_irqs_disabled,
|
||||
};
|
||||
use core::cmp;
|
||||
use voladdress::*;
|
||||
|
||||
const PORT: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x0DFFFF00) };
|
||||
const SECTOR_SHIFT: usize = 3;
|
||||
const SECTOR_LEN: usize = 1 << SECTOR_SHIFT;
|
||||
const SECTOR_MASK: usize = SECTOR_LEN - 1;
|
||||
|
||||
/// Disable IRQs and DMAs during each read block.
|
||||
fn disable_dmas(func: impl FnOnce()) {
|
||||
with_irqs_disabled(|| unsafe {
|
||||
// Disable other DMAs. This avoids our read/write from being interrupted
|
||||
// by a higher priority DMA channel.
|
||||
let dma0_ctl = DMA0CNT_H.read();
|
||||
let dma1_ctl = DMA1CNT_H.read();
|
||||
let dma2_ctl = DMA2CNT_H.read();
|
||||
DMA0CNT_H.write(dma0_ctl.with_enabled(false));
|
||||
DMA1CNT_H.write(dma1_ctl.with_enabled(false));
|
||||
DMA2CNT_H.write(dma2_ctl.with_enabled(false));
|
||||
|
||||
// Executes the body of the function with DMAs and IRQs disabled.
|
||||
func();
|
||||
|
||||
// Continues higher priority DMAs if they were enabled before.
|
||||
DMA0CNT_H.write(dma0_ctl);
|
||||
DMA1CNT_H.write(dma1_ctl);
|
||||
DMA2CNT_H.write(dma2_ctl);
|
||||
});
|
||||
}
|
||||
|
||||
/// Sends a DMA command to EEPROM.
|
||||
fn dma_send(source: &[u32], ct: u16) {
|
||||
disable_dmas(|| unsafe {
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
DMA3SAD.write(source.as_ptr() as usize);
|
||||
DMA3DAD.write(0x0DFFFF00);
|
||||
DMA3CNT_L.write(ct);
|
||||
let dma3_ctl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Increment)
|
||||
.with_enabled(true);
|
||||
DMA3CNT_H.write(dma3_ctl);
|
||||
});
|
||||
}
|
||||
|
||||
/// Receives a DMA packet from EEPROM.
|
||||
fn dma_receive(source: &mut [u32], ct: u16) {
|
||||
disable_dmas(|| unsafe {
|
||||
DMA3SAD.write(0x0DFFFF00);
|
||||
DMA3DAD.write(source.as_mut_ptr() as usize);
|
||||
DMA3CNT_L.write(ct);
|
||||
let dma3_ctl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Increment)
|
||||
.with_enabled(true);
|
||||
DMA3CNT_H.write(dma3_ctl);
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
/// Union type to help build/receive commands.
|
||||
struct BufferData {
|
||||
idx: usize,
|
||||
data: BufferContents,
|
||||
}
|
||||
#[repr(align(4))]
|
||||
union BufferContents {
|
||||
uninit: (),
|
||||
bits: [u16; 82],
|
||||
words: [u32; 41],
|
||||
}
|
||||
impl BufferData {
|
||||
fn new() -> Self {
|
||||
BufferData { idx: 0, data: BufferContents { uninit: () } }
|
||||
}
|
||||
|
||||
/// Writes a bit to the output buffer.
|
||||
fn write_bit(&mut self, val: u8) {
|
||||
unsafe {
|
||||
self.data.bits[self.idx] = val as u16;
|
||||
self.idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a number to the output buffer
|
||||
fn write_num(&mut self, count: usize, num: u32) {
|
||||
for i in 0..count {
|
||||
self.write_bit(((num >> (count - 1 - i)) & 1) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a number from the input buffer.
|
||||
fn read_num(&mut self, off: usize, count: usize) -> u32 {
|
||||
let mut accum = 0;
|
||||
unsafe {
|
||||
for i in 0..count {
|
||||
accum <<= 1;
|
||||
accum |= self.data.bits[off + i] as u32;
|
||||
}
|
||||
}
|
||||
accum
|
||||
}
|
||||
|
||||
/// Receives a number of words into the input buffer.
|
||||
fn receive(&mut self, count: usize) {
|
||||
unsafe {
|
||||
dma_receive(&mut self.data.words, count as u16);
|
||||
}
|
||||
}
|
||||
|
||||
/// Submits the current buffer via DMA.
|
||||
fn submit(&self) {
|
||||
unsafe {
|
||||
dma_send(&self.data.words, self.idx as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The properties of a given EEPROM type.
|
||||
struct EepromProperties {
|
||||
addr_bits: usize,
|
||||
byte_len: usize,
|
||||
}
|
||||
impl EepromProperties {
|
||||
/// Reads a block from the save media.
|
||||
fn read_sector(&self, word: usize) -> [u8; 8] {
|
||||
// Set address command. The command is two one bits, followed by the
|
||||
// address, followed by a zero bit.
|
||||
//
|
||||
// 512B Command: [1 1|n n n n n n|0]
|
||||
// 8KiB Command: [1 1|n n n n n n n n n n n n n n|0]
|
||||
let mut buf = BufferData::new();
|
||||
buf.write_bit(1);
|
||||
buf.write_bit(1);
|
||||
buf.write_num(self.addr_bits, word as u32);
|
||||
buf.write_bit(0);
|
||||
buf.submit();
|
||||
|
||||
// Receive the buffer data. The EEPROM sends 3 irrelevant bits followed
|
||||
// by 64 data bits.
|
||||
buf.receive(68);
|
||||
let mut out = [0; 8];
|
||||
for i in 0..8 {
|
||||
out[i] = buf.read_num(4 + i * 8, 8) as u8;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Writes a sector directly.
|
||||
fn write_sector_raw(&self, word: usize, block: &[u8]) -> Result<(), Error> {
|
||||
// Write sector command. The command is a one bit, followed by a
|
||||
// zero bit, followed by the address, followed by 64 bits of data.
|
||||
//
|
||||
// 512B Command: [1 0|n n n n n n|v v v v ...]
|
||||
// 8KiB Command: [1 0|n n n n n n n n n n n n n n|v v v v ...]
|
||||
let mut buf = BufferData::new();
|
||||
buf.write_bit(1);
|
||||
buf.write_bit(0);
|
||||
buf.write_num(self.addr_bits, word as u32);
|
||||
for i in 0..8 {
|
||||
buf.write_num(8, block[i] as u32);
|
||||
}
|
||||
buf.write_bit(0);
|
||||
buf.submit();
|
||||
|
||||
// Wait for the sector to be written for 10 milliseconds.
|
||||
let timeout = Timeout::new()?;
|
||||
timeout.start();
|
||||
while PORT.read() & 1 != 1 {
|
||||
if timeout.is_timeout_met(10) {
|
||||
return Err(Error::OperationTimedOut);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a sector to the EEPROM, keeping any current contents outside the
|
||||
/// buffer's range.
|
||||
fn write_sector_safe(&self, word: usize, data: &[u8], start: usize) -> Result<(), Error> {
|
||||
let mut buf = self.read_sector(word);
|
||||
buf[start..start + data.len()].copy_from_slice(data);
|
||||
self.write_sector_raw(word, &buf)
|
||||
}
|
||||
|
||||
/// Writes a sector to the EEPROM.
|
||||
fn write_sector(&self, word: usize, data: &[u8], start: usize) -> Result<(), Error> {
|
||||
if data.len() == 8 && start == 0 {
|
||||
self.write_sector_raw(word, data)
|
||||
} else {
|
||||
self.write_sector_safe(word, data, start)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether an offset is in range.
|
||||
fn check_offset(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
if offset.checked_add(len).is_none() && (offset + len) > self.byte_len {
|
||||
Err(Error::OutOfBounds)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements EEPROM reads.
|
||||
fn read(&self, mut offset: usize, mut buf: &mut [u8]) -> Result<(), Error> {
|
||||
self.check_offset(offset, buf.len())?;
|
||||
let _guard = lock_media()?;
|
||||
while buf.len() != 0 {
|
||||
let start = offset & SECTOR_MASK;
|
||||
let end_len = cmp::min(SECTOR_LEN - start, buf.len());
|
||||
let sector = self.read_sector(offset >> SECTOR_SHIFT);
|
||||
buf[..end_len].copy_from_slice(§or[start..start + end_len]);
|
||||
buf = &mut buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Implements EEPROM verifies.
|
||||
fn verify(&self, mut offset: usize, mut buf: &[u8]) -> Result<bool, Error> {
|
||||
self.check_offset(offset, buf.len())?;
|
||||
let _guard = lock_media()?;
|
||||
while buf.len() != 0 {
|
||||
let start = offset & SECTOR_MASK;
|
||||
let end_len = cmp::min(SECTOR_LEN - start, buf.len());
|
||||
if &buf[..end_len] != &self.read_sector(offset >> SECTOR_SHIFT) {
|
||||
return Ok(false);
|
||||
}
|
||||
buf = &buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Implements EEPROM writes.
|
||||
fn write(&self, mut offset: usize, mut buf: &[u8]) -> Result<(), Error> {
|
||||
self.check_offset(offset, buf.len())?;
|
||||
let _guard = lock_media()?;
|
||||
while buf.len() != 0 {
|
||||
let start = offset & SECTOR_MASK;
|
||||
let end_len = cmp::min(SECTOR_LEN - start, buf.len());
|
||||
self.write_sector(offset >> SECTOR_SHIFT, &buf[..end_len], start)?;
|
||||
buf = &buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
const PROPS_512B: EepromProperties = EepromProperties { addr_bits: 6, byte_len: 512 };
|
||||
const PROPS_8K: EepromProperties = EepromProperties { addr_bits: 14, byte_len: 8 * 1024 };
|
||||
|
||||
/// The [`RawSaveAccess`] used for 512 byte EEPROM.
|
||||
pub struct Eeprom512B;
|
||||
impl RawSaveAccess for Eeprom512B {
|
||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||
Ok(&MediaInfo {
|
||||
media_type: MediaType::Eeprom512B,
|
||||
sector_shift: 3,
|
||||
sector_count: 64,
|
||||
requires_prepare_write: false,
|
||||
})
|
||||
}
|
||||
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
PROPS_512B.read(offset, buffer)
|
||||
}
|
||||
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||
PROPS_512B.verify(offset, buffer)
|
||||
}
|
||||
fn prepare_write(&self, _: usize, _: usize) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||
PROPS_512B.write(offset, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`RawSaveAccess`] used for 8 KiB EEPROM.
|
||||
pub struct Eeprom8K;
|
||||
impl RawSaveAccess for Eeprom8K {
|
||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||
Ok(&MediaInfo {
|
||||
media_type: MediaType::Eeprom8K,
|
||||
sector_shift: 3,
|
||||
sector_count: 1024,
|
||||
requires_prepare_write: false,
|
||||
})
|
||||
}
|
||||
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
PROPS_8K.read(offset, buffer)
|
||||
}
|
||||
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||
PROPS_8K.verify(offset, buffer)
|
||||
}
|
||||
fn prepare_write(&self, _: usize, _: usize) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||
PROPS_8K.write(offset, buffer)
|
||||
}
|
||||
}
|
|
@ -1,478 +0,0 @@
|
|||
//! Module for flash save media support.
|
||||
//!
|
||||
//! Flash may be read with ordinary read commands, but writing requires
|
||||
//! sending structured commands to the flash chip.
|
||||
|
||||
use super::{
|
||||
lock_media, read_raw_buf, read_raw_byte, verify_raw_buf, Error, MediaInfo, MediaType,
|
||||
RawSaveAccess,
|
||||
};
|
||||
use crate::sync::{with_irqs_disabled, InitOnce, Static};
|
||||
use core::cmp;
|
||||
use voladdress::*;
|
||||
|
||||
// Volatile address ports for flash
|
||||
const FLASH_PORT_BANK: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0E000000) };
|
||||
const FLASH_PORT_A: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0E005555) };
|
||||
const FLASH_PORT_B: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0E002AAA) };
|
||||
const FLASH_DATA: VolBlock<u8, Safe, Safe, 65536> = unsafe { VolBlock::new(0x0E000000) };
|
||||
|
||||
// Various constants related to sector sizes
|
||||
const BANK_SHIFT: usize = 16; // 64 KiB
|
||||
const BANK_LEN: usize = 1 << BANK_SHIFT;
|
||||
const BANK_MASK: usize = BANK_LEN - 1;
|
||||
|
||||
// Constants relating to flash commands.
|
||||
const CMD_SET_BANK: u8 = 0xB0;
|
||||
const CMD_READ_CHIP_ID: u8 = 0x90;
|
||||
const CMD_READ_CONTENTS: u8 = 0xF0;
|
||||
const CMD_WRITE: u8 = 0xA0;
|
||||
const CMD_ERASE_SECTOR_BEGIN: u8 = 0x80;
|
||||
const CMD_ERASE_SECTOR_CONFIRM: u8 = 0x30;
|
||||
const CMD_ERASE_SECTOR_ALL: u8 = 0x10;
|
||||
|
||||
/// Starts a command to the flash chip.
|
||||
fn start_flash_command() {
|
||||
FLASH_PORT_A.write(0xAA);
|
||||
FLASH_PORT_B.write(0x55);
|
||||
}
|
||||
|
||||
/// Helper function for issuing commands to the flash chip.
|
||||
fn issue_flash_command(c2: u8) {
|
||||
start_flash_command();
|
||||
FLASH_PORT_A.write(c2);
|
||||
}
|
||||
|
||||
/// A simple thing to avoid excessive bank switches
|
||||
static CURRENT_BANK: Static<u8> = Static::new(!0);
|
||||
fn set_bank(bank: u8) -> Result<(), Error> {
|
||||
if bank == 0xFF {
|
||||
Err(Error::OutOfBounds)
|
||||
} else if bank != CURRENT_BANK.read() {
|
||||
issue_flash_command(CMD_SET_BANK);
|
||||
FLASH_PORT_BANK.write(bank as u8);
|
||||
CURRENT_BANK.write(bank);
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a particular f
|
||||
/// lash chip in use by a Game Pak.
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum FlashChipType {
|
||||
/// 64KiB SST chip
|
||||
Sst64K,
|
||||
/// 64KiB Macronix chip
|
||||
Macronix64K,
|
||||
/// 64KiB Panasonic chip
|
||||
Panasonic64K,
|
||||
/// 64KiB Atmel chip
|
||||
Atmel64K,
|
||||
/// 128KiB Sanyo chip
|
||||
Sanyo128K,
|
||||
/// 128KiB Macronix chip
|
||||
Macronix128K,
|
||||
/// An unidentified chip
|
||||
Unknown,
|
||||
}
|
||||
impl FlashChipType {
|
||||
/// Returns the type of the flash chip currently in use.
|
||||
pub fn detect() -> Result<Self, Error> {
|
||||
Ok(Self::from_id(detect_chip_id()?))
|
||||
}
|
||||
|
||||
/// Determines the flash chip type from an ID.
|
||||
pub fn from_id(id: u16) -> Self {
|
||||
match id {
|
||||
0xD4BF => FlashChipType::Sst64K,
|
||||
0x1CC2 => FlashChipType::Macronix64K,
|
||||
0x1B32 => FlashChipType::Panasonic64K,
|
||||
0x3D1F => FlashChipType::Atmel64K,
|
||||
0x1362 => FlashChipType::Sanyo128K,
|
||||
0x09C2 => FlashChipType::Macronix128K,
|
||||
_ => FlashChipType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `u16` id of the chip, or `0` for `Unknown`.
|
||||
pub fn id(&self) -> u16 {
|
||||
match *self {
|
||||
FlashChipType::Sst64K => 0xD4BF,
|
||||
FlashChipType::Macronix64K => 0x1CC2,
|
||||
FlashChipType::Panasonic64K => 0x1B32,
|
||||
FlashChipType::Atmel64K => 0x3D1F,
|
||||
FlashChipType::Sanyo128K => 0x1362,
|
||||
FlashChipType::Macronix128K => 0x09C2,
|
||||
FlashChipType::Unknown => 0x0000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the raw ID of the flash chip currently in use.
|
||||
pub fn detect_chip_id() -> Result<u16, Error> {
|
||||
let _lock = lock_media()?;
|
||||
issue_flash_command(CMD_READ_CHIP_ID);
|
||||
let high = unsafe { read_raw_byte(0x0E000001) };
|
||||
let low = unsafe { read_raw_byte(0x0E000000) };
|
||||
let id = (high as u16) << 8 | low as u16;
|
||||
issue_flash_command(CMD_READ_CONTENTS);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Information relating to a particular flash chip that could be found in a
|
||||
/// Game Pak.
|
||||
#[allow(dead_code)]
|
||||
struct ChipInfo {
|
||||
/// The wait state required to read from the chip.
|
||||
read_wait: u8,
|
||||
/// The wait state required to write to the chip.
|
||||
write_wait: u8,
|
||||
|
||||
/// The timeout in milliseconds for writes to this chip.
|
||||
write_timeout: u16,
|
||||
/// The timeout in milliseconds for erasing a sector in this chip.
|
||||
erase_sector_timeout: u16,
|
||||
/// The timeout in milliseconds for erasing the entire chip.
|
||||
erase_chip_timeout: u16,
|
||||
|
||||
/// The number of 64KiB banks in this chip.
|
||||
bank_count: u8,
|
||||
/// Whether this is an Atmel chip, which has 128 byte sectors instead of 4K.
|
||||
uses_atmel_api: bool,
|
||||
/// Whether this is an Macronix chip, which requires an additional command
|
||||
/// to cancel the current action after a timeout.
|
||||
requires_cancel_command: bool,
|
||||
|
||||
/// The [`MediaInfo`] to return for this chip type.
|
||||
info: &'static MediaInfo,
|
||||
}
|
||||
|
||||
// Media info for the various chipsets.
|
||||
static INFO_64K: MediaInfo = MediaInfo {
|
||||
media_type: MediaType::Flash64K,
|
||||
sector_shift: 12, // 4 KiB
|
||||
sector_count: 16, // 4 KiB * 16 = 64 KiB
|
||||
requires_prepare_write: true,
|
||||
};
|
||||
static INFO_64K_ATMEL: MediaInfo = MediaInfo {
|
||||
media_type: MediaType::Flash64K,
|
||||
sector_shift: 7, // 128 bytes
|
||||
sector_count: 512, // 128 bytes * 512 = 64 KiB
|
||||
requires_prepare_write: false,
|
||||
};
|
||||
static INFO_128K: MediaInfo = MediaInfo {
|
||||
media_type: MediaType::Flash128K,
|
||||
sector_shift: 12,
|
||||
sector_count: 32, // 4 KiB * 32 = 128 KiB
|
||||
requires_prepare_write: true,
|
||||
};
|
||||
|
||||
// Chip info for the various chipsets.
|
||||
static CHIP_INFO_SST_64K: ChipInfo = ChipInfo {
|
||||
read_wait: 2, // 2 cycles
|
||||
write_wait: 1, // 3 cycles
|
||||
write_timeout: 10,
|
||||
erase_sector_timeout: 40,
|
||||
erase_chip_timeout: 200,
|
||||
bank_count: 1,
|
||||
uses_atmel_api: false,
|
||||
requires_cancel_command: false,
|
||||
info: &INFO_64K,
|
||||
};
|
||||
static CHIP_INFO_MACRONIX_64K: ChipInfo = ChipInfo {
|
||||
read_wait: 1, // 3 cycles
|
||||
write_wait: 3, // 8 cycles
|
||||
write_timeout: 10,
|
||||
erase_sector_timeout: 2000,
|
||||
erase_chip_timeout: 2000,
|
||||
bank_count: 1,
|
||||
uses_atmel_api: false,
|
||||
requires_cancel_command: true,
|
||||
info: &INFO_64K,
|
||||
};
|
||||
static CHIP_INFO_PANASONIC_64K: ChipInfo = ChipInfo {
|
||||
read_wait: 2, // 2 cycles
|
||||
write_wait: 0, // 4 cycles
|
||||
write_timeout: 10,
|
||||
erase_sector_timeout: 500,
|
||||
erase_chip_timeout: 500,
|
||||
bank_count: 1,
|
||||
uses_atmel_api: false,
|
||||
requires_cancel_command: false,
|
||||
info: &INFO_64K,
|
||||
};
|
||||
static CHIP_INFO_ATMEL_64K: ChipInfo = ChipInfo {
|
||||
read_wait: 3, // 8 cycles
|
||||
write_wait: 3, // 8 cycles
|
||||
write_timeout: 40,
|
||||
erase_sector_timeout: 40,
|
||||
erase_chip_timeout: 40,
|
||||
bank_count: 1,
|
||||
uses_atmel_api: true,
|
||||
requires_cancel_command: false,
|
||||
info: &INFO_64K_ATMEL,
|
||||
};
|
||||
static CHIP_INFO_GENERIC_64K: ChipInfo = ChipInfo {
|
||||
read_wait: 3, // 8 cycles
|
||||
write_wait: 3, // 8 cycles
|
||||
write_timeout: 40,
|
||||
erase_sector_timeout: 2000,
|
||||
erase_chip_timeout: 2000,
|
||||
bank_count: 1,
|
||||
uses_atmel_api: false,
|
||||
requires_cancel_command: true,
|
||||
info: &INFO_128K,
|
||||
};
|
||||
static CHIP_INFO_GENERIC_128K: ChipInfo = ChipInfo {
|
||||
read_wait: 1, // 3 cycles
|
||||
write_wait: 3, // 8 cycles
|
||||
write_timeout: 10,
|
||||
erase_sector_timeout: 2000,
|
||||
erase_chip_timeout: 2000,
|
||||
bank_count: 2,
|
||||
uses_atmel_api: false,
|
||||
requires_cancel_command: false,
|
||||
info: &INFO_128K,
|
||||
};
|
||||
|
||||
impl FlashChipType {
|
||||
/// Returns the internal info for this chip.
|
||||
fn chip_info(&self) -> &'static ChipInfo {
|
||||
match *self {
|
||||
FlashChipType::Sst64K => &CHIP_INFO_SST_64K,
|
||||
FlashChipType::Macronix64K => &CHIP_INFO_MACRONIX_64K,
|
||||
FlashChipType::Panasonic64K => &CHIP_INFO_PANASONIC_64K,
|
||||
FlashChipType::Atmel64K => &CHIP_INFO_ATMEL_64K,
|
||||
FlashChipType::Sanyo128K => &CHIP_INFO_GENERIC_128K,
|
||||
FlashChipType::Macronix128K => &CHIP_INFO_GENERIC_128K,
|
||||
FlashChipType::Unknown => &CHIP_INFO_GENERIC_64K,
|
||||
}
|
||||
}
|
||||
}
|
||||
static CHIP_INFO: InitOnce<&'static ChipInfo> = InitOnce::new();
|
||||
fn cached_chip_info() -> Result<&'static ChipInfo, Error> {
|
||||
CHIP_INFO
|
||||
.try_get(|| -> Result<_, Error> { Ok(FlashChipType::detect()?.chip_info()) })
|
||||
.map(Clone::clone)
|
||||
}
|
||||
|
||||
/// Actual implementation of the ChipInfo functions.
|
||||
impl ChipInfo {
|
||||
/// Returns the total length of this chip.
|
||||
fn total_len(&self) -> usize {
|
||||
self.info.sector_count << self.info.sector_shift
|
||||
}
|
||||
|
||||
// Checks whether a byte offset is in bounds.
|
||||
fn check_len(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
if offset.checked_add(len).is_some() && offset + len <= self.total_len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::OutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether a sector offset is in bounds.
|
||||
fn check_sector_len(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
if offset.checked_add(len).is_some() && offset + len <= self.info.sector_count {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::OutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the currently active bank.
|
||||
fn set_bank(&self, bank: usize) -> Result<(), Error> {
|
||||
if bank >= self.bank_count as usize {
|
||||
Err(Error::OutOfBounds)
|
||||
} else if self.bank_count > 1 {
|
||||
set_bank(bank as u8)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a buffer from save media into memory.
|
||||
fn read_buffer(&self, mut offset: usize, mut buf: &mut [u8]) -> Result<(), Error> {
|
||||
while buf.len() != 0 {
|
||||
self.set_bank(offset >> BANK_SHIFT)?;
|
||||
let start = offset & BANK_MASK;
|
||||
let end_len = cmp::min(BANK_LEN - start, buf.len());
|
||||
unsafe {
|
||||
read_raw_buf(&mut buf[..end_len], 0x0E000000 + start);
|
||||
}
|
||||
buf = &mut buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies that a buffer was properly stored into save media.
|
||||
fn verify_buffer(&self, mut offset: usize, mut buf: &[u8]) -> Result<bool, Error> {
|
||||
while buf.len() != 0 {
|
||||
self.set_bank(offset >> BANK_SHIFT)?;
|
||||
let start = offset & BANK_MASK;
|
||||
let end_len = cmp::min(BANK_LEN - start, buf.len());
|
||||
if !unsafe { verify_raw_buf(&buf[..end_len], 0x0E000000 + start) } {
|
||||
return Ok(false);
|
||||
}
|
||||
buf = &buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Waits for a timeout, or an operation to complete.
|
||||
fn wait_for_timeout(&self, offset: usize, val: u8, ms: u16) -> Result<(), Error> {
|
||||
let timeout = super::Timeout::new()?;
|
||||
timeout.start();
|
||||
let offset = 0x0E000000 + offset;
|
||||
|
||||
while unsafe { read_raw_byte(offset) != val } {
|
||||
if timeout.is_timeout_met(ms) {
|
||||
if self.requires_cancel_command {
|
||||
FLASH_PORT_A.write(0xF0);
|
||||
}
|
||||
return Err(Error::OperationTimedOut);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erases a sector to flash.
|
||||
fn erase_sector(&self, sector: usize) -> Result<(), Error> {
|
||||
let offset = sector << self.info.sector_shift;
|
||||
self.set_bank(offset >> BANK_SHIFT)?;
|
||||
issue_flash_command(CMD_ERASE_SECTOR_BEGIN);
|
||||
start_flash_command();
|
||||
FLASH_DATA.index(offset & BANK_MASK).write(CMD_ERASE_SECTOR_CONFIRM);
|
||||
self.wait_for_timeout(offset & BANK_MASK, 0xFF, self.erase_sector_timeout)
|
||||
}
|
||||
|
||||
/// Erases the entire chip.
|
||||
fn erase_chip(&self) -> Result<(), Error> {
|
||||
issue_flash_command(CMD_ERASE_SECTOR_BEGIN);
|
||||
issue_flash_command(CMD_ERASE_SECTOR_ALL);
|
||||
self.wait_for_timeout(0, 0xFF, 3000)
|
||||
}
|
||||
|
||||
/// Writes a byte to the save media.
|
||||
fn write_byte(&self, offset: usize, byte: u8) -> Result<(), Error> {
|
||||
issue_flash_command(CMD_WRITE);
|
||||
FLASH_DATA.index(offset).write(byte);
|
||||
self.wait_for_timeout(offset, byte, self.write_timeout)
|
||||
}
|
||||
|
||||
/// Writes an entire buffer to the save media.
|
||||
fn write_buffer(&self, offset: usize, buf: &[u8]) -> Result<(), Error> {
|
||||
self.set_bank(offset >> BANK_SHIFT)?;
|
||||
for i in 0..buf.len() {
|
||||
let byte_off = offset + i;
|
||||
if (byte_off & BANK_MASK) == 0 {
|
||||
self.set_bank(byte_off >> BANK_SHIFT)?;
|
||||
}
|
||||
self.write_byte(byte_off & BANK_MASK, buf[i])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erases and writes an entire 128b sector on Atmel devices.
|
||||
fn write_atmel_sector_raw(&self, offset: usize, buf: &[u8]) -> Result<(), Error> {
|
||||
with_irqs_disabled(|| {
|
||||
issue_flash_command(CMD_WRITE);
|
||||
for i in 0..128 {
|
||||
FLASH_DATA.index(offset + i).write(buf[i]);
|
||||
}
|
||||
self.wait_for_timeout(offset + 127, buf[127], self.erase_sector_timeout)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes an entire 128b sector on Atmel devices, copying existing data in
|
||||
/// case of non-sector aligned writes.
|
||||
#[inline(never)] // avoid allocating the 128 byte buffer for no reason.
|
||||
fn write_atmel_sector_safe(&self, offset: usize, buf: &[u8], start: usize) -> Result<(), Error> {
|
||||
let mut sector = [0u8; 128];
|
||||
self.read_buffer(offset, &mut sector[0..start])?;
|
||||
sector[start..start + buf.len()].copy_from_slice(buf);
|
||||
self.read_buffer(offset + start + buf.len(), &mut sector[start + buf.len()..128])?;
|
||||
self.write_atmel_sector_raw(offset, §or)
|
||||
}
|
||||
|
||||
/// Writes an entire 128b sector on Atmel devices, copying existing data in
|
||||
/// case of non-sector aligned writes.
|
||||
///
|
||||
/// This avoids allocating stack if there is no need to.
|
||||
fn write_atmel_sector(&self, offset: usize, buf: &[u8], start: usize) -> Result<(), Error> {
|
||||
if start == 0 && buf.len() == 128 {
|
||||
self.write_atmel_sector_raw(offset, buf)
|
||||
} else {
|
||||
self.write_atmel_sector_safe(offset, buf, start)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`RawSaveAccess`] used for flash save media.
|
||||
pub struct FlashAccess;
|
||||
impl RawSaveAccess for FlashAccess {
|
||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||
Ok(cached_chip_info()?.info)
|
||||
}
|
||||
|
||||
fn read(&self, offset: usize, buf: &mut [u8]) -> Result<(), Error> {
|
||||
let chip = cached_chip_info()?;
|
||||
chip.check_len(offset, buf.len())?;
|
||||
|
||||
let _lock = lock_media()?;
|
||||
chip.read_buffer(offset, buf)
|
||||
}
|
||||
|
||||
fn verify(&self, offset: usize, buf: &[u8]) -> Result<bool, Error> {
|
||||
let chip = cached_chip_info()?;
|
||||
chip.check_len(offset, buf.len())?;
|
||||
|
||||
let _lock = lock_media()?;
|
||||
chip.verify_buffer(offset, buf)
|
||||
}
|
||||
|
||||
fn prepare_write(&self, sector: usize, count: usize) -> Result<(), Error> {
|
||||
let chip = cached_chip_info()?;
|
||||
chip.check_sector_len(sector, count)?;
|
||||
|
||||
let _lock = lock_media()?;
|
||||
if chip.uses_atmel_api {
|
||||
Ok(())
|
||||
} else if count == chip.info.sector_count {
|
||||
chip.erase_chip()
|
||||
} else {
|
||||
for i in sector..sector + count {
|
||||
chip.erase_sector(i)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&self, mut offset: usize, mut buf: &[u8]) -> Result<(), Error> {
|
||||
let chip = cached_chip_info()?;
|
||||
chip.check_len(offset, buf.len())?;
|
||||
|
||||
let _lock = lock_media()?;
|
||||
if chip.uses_atmel_api {
|
||||
while buf.len() != 0 {
|
||||
let start = offset & 127;
|
||||
let end_len = cmp::min(128 - start, buf.len());
|
||||
chip.write_atmel_sector(offset & !127, &buf[..end_len], start)?;
|
||||
buf = &buf[end_len..];
|
||||
offset += end_len;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
// Write the bytes one by one.
|
||||
chip.write_buffer(offset, buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
//! A module that produces the marker strings used by emulators to determine
|
||||
//! which save media type a ROM uses.
|
||||
//!
|
||||
//! This takes advantage of the LLVM's usual dead code elimination. The
|
||||
//! functions that generate the markers use `volatile_mark_read` to force the
|
||||
//! LLVM to assume the statics used. Therefore, as long as one of these
|
||||
//! functions is called, the corresponding static is emitted with very little
|
||||
//! code actually generated.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[repr(align(4))]
|
||||
struct Align<T>(T);
|
||||
|
||||
static EEPROM: Align<[u8; 12]> = Align(*b"EEPROM_Vnnn\0");
|
||||
static SRAM: Align<[u8; 12]> = Align(*b"SRAM_Vnnn\0\0\0");
|
||||
static FLASH512K: Align<[u8; 16]> = Align(*b"FLASH512_Vnnn\0\0\0");
|
||||
static FLASH1M: Align<[u8; 16]> = Align(*b"FLASH1M_Vnnn\0\0\0\0");
|
||||
|
||||
#[inline(always)]
|
||||
fn emit_eeprom_marker() {
|
||||
crate::sync::memory_read_hint(&EEPROM);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn emit_sram_marker() {
|
||||
crate::sync::memory_read_hint(&SRAM);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn emit_flash_512k_marker() {
|
||||
crate::sync::memory_read_hint(&FLASH512K);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn emit_flash_1m_marker() {
|
||||
crate::sync::memory_read_hint(&FLASH1M);
|
||||
}
|
||||
|
||||
/// Declares that the ROM uses battery backed SRAM/FRAM.
|
||||
///
|
||||
/// This creates a marker in the ROM that allows emulators to understand what
|
||||
/// save type the Game Pak uses, and sets the accessor to one appropriate for
|
||||
/// memory type.
|
||||
///
|
||||
/// Battery Backed SRAM is generally very fast, but limited in size compared
|
||||
/// to flash chips.
|
||||
pub fn use_sram() {
|
||||
emit_sram_marker();
|
||||
set_save_implementation(Some(&sram::BatteryBackedAccess));
|
||||
}
|
||||
|
||||
/// Declares that the ROM uses 64KiB flash memory.
|
||||
///
|
||||
/// This creates a marker in the ROM that allows emulators to understand what
|
||||
/// save type the Game Pak uses, and sets the accessor to one appropriate for
|
||||
/// memory type.
|
||||
///
|
||||
/// Flash save media is generally very slow to write to and relatively fast
|
||||
/// to read from. It is the only real option if you need larger save data.
|
||||
pub fn use_flash_64k() {
|
||||
emit_flash_512k_marker();
|
||||
set_save_implementation(Some(&flash::FlashAccess));
|
||||
}
|
||||
|
||||
/// Declares that the ROM uses 128KiB flash memory.
|
||||
///
|
||||
/// This creates a marker in the ROM that allows emulators to understand what
|
||||
/// save type the Game Pak uses, and sets the accessor to one appropriate for
|
||||
/// memory type.
|
||||
///
|
||||
/// Flash save media is generally very slow to write to and relatively fast
|
||||
/// to read from. It is the only real option if you need larger save data.
|
||||
pub fn use_flash_128k() {
|
||||
emit_flash_1m_marker();
|
||||
set_save_implementation(Some(&flash::FlashAccess));
|
||||
}
|
||||
|
||||
/// Declares that the ROM uses 512 bytes EEPROM memory.
|
||||
///
|
||||
/// This creates a marker in the ROM that allows emulators to understand what
|
||||
/// save type the Game Pak uses, and sets the accessor to one appropriate for
|
||||
/// memory type.
|
||||
///
|
||||
/// EEPROM is generally pretty slow and also very small. It's mainly used in
|
||||
/// Game Paks because it's cheap.
|
||||
pub fn use_eeprom_512b() {
|
||||
emit_eeprom_marker();
|
||||
set_save_implementation(Some(&eeprom::Eeprom512B));
|
||||
}
|
||||
|
||||
/// Declares that the ROM uses 8 KiB EEPROM memory.
|
||||
///
|
||||
/// This creates a marker in the ROM that allows emulators to understand what
|
||||
/// save type the Game Pak uses, and sets the accessor to one appropriate for
|
||||
/// memory type.
|
||||
///
|
||||
/// EEPROM is generally pretty slow and also very small. It's mainly used in
|
||||
/// Game Paks because it's cheap.
|
||||
pub fn use_eeprom_8k() {
|
||||
emit_eeprom_marker();
|
||||
set_save_implementation(Some(&eeprom::Eeprom8K));
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
//! Module for battery backed SRAM save media support.
|
||||
//!
|
||||
//! SRAM acts as ordinary memory mapped into the memory space, and as such
|
||||
//! is accessed using normal memory read/write commands.
|
||||
|
||||
use super::{read_raw_buf, write_raw_buf, Error, MediaType, RawSaveAccess};
|
||||
use crate::save::{verify_raw_buf, MediaInfo};
|
||||
|
||||
const SRAM_SIZE: usize = 32 * 1024; // 32 KiB
|
||||
|
||||
/// Checks whether an offset is contained within the bounds of the SRAM.
|
||||
fn check_bounds(offset: usize, len: usize) -> Result<(), Error> {
|
||||
if offset.checked_add(len).is_none() || offset + len > SRAM_SIZE {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The [`RawSaveAccess`] used for battery backed SRAM.
|
||||
pub struct BatteryBackedAccess;
|
||||
impl RawSaveAccess for BatteryBackedAccess {
|
||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||
Ok(&MediaInfo {
|
||||
media_type: MediaType::Sram32K,
|
||||
sector_shift: 0,
|
||||
sector_count: SRAM_SIZE,
|
||||
requires_prepare_write: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
check_bounds(offset, buffer.len())?;
|
||||
unsafe {
|
||||
read_raw_buf(buffer, 0x0E000000 + offset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||
check_bounds(offset, buffer.len())?;
|
||||
let val = unsafe { verify_raw_buf(buffer, 0x0E000000 + offset) };
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn prepare_write(&self, _: usize, _: usize) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||
check_bounds(offset, buffer.len())?;
|
||||
unsafe {
|
||||
write_raw_buf(0x0E000000 + offset, buffer);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
//! A package containing useful utilities for writing save accessors. This is
|
||||
//! mainly used internally, although the types inside are exposed publicly.
|
||||
|
||||
use super::Error;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
sync::{RawMutex, RawMutexGuard, Static},
|
||||
};
|
||||
use voladdress::*;
|
||||
|
||||
/// Internal representation for our active timer.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[repr(u8)]
|
||||
enum TimerId {
|
||||
None,
|
||||
T0,
|
||||
T1,
|
||||
T2,
|
||||
T3,
|
||||
}
|
||||
|
||||
/// Stores the timer ID used for timeouts created by save accessors.
|
||||
static TIMER_ID: Static<TimerId> = Static::new(TimerId::None);
|
||||
|
||||
/// Sets the timer to use to implement timeouts for operations that may hang.
|
||||
///
|
||||
/// At any point where you call functions in a save accessor, this timer may be
|
||||
/// reset to a different value.
|
||||
pub fn set_timer_for_timeout(id: u8) {
|
||||
if id >= 4 {
|
||||
panic!("Timer ID must be 0-3.");
|
||||
} else {
|
||||
TIMER_ID.write([TimerId::T0, TimerId::T1, TimerId::T2, TimerId::T3][id as usize])
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables the timeout for operations that may hang.
|
||||
pub fn disable_timeout() {
|
||||
TIMER_ID.write(TimerId::None);
|
||||
}
|
||||
|
||||
/// A timeout type used to prevent hardware errors in save media from hanging
|
||||
/// the game.
|
||||
pub struct Timeout {
|
||||
_lock_guard: RawMutexGuard<'static>,
|
||||
active: bool,
|
||||
timer_l: VolAddress<u16, Safe, Safe>,
|
||||
timer_h: VolAddress<TimerControl, Safe, Safe>,
|
||||
}
|
||||
impl Timeout {
|
||||
/// Creates a new timeout from the timer passed to [`set_timer_for_timeout`].
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// If another timeout has already been created.
|
||||
#[inline(never)]
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
static TIMEOUT_LOCK: RawMutex = RawMutex::new();
|
||||
let _lock_guard = match TIMEOUT_LOCK.try_lock() {
|
||||
Some(x) => x,
|
||||
None => return Err(Error::MediaInUse),
|
||||
};
|
||||
let id = TIMER_ID.read();
|
||||
Ok(Timeout {
|
||||
_lock_guard,
|
||||
active: id != TimerId::None,
|
||||
timer_l: match id {
|
||||
TimerId::None => unsafe { VolAddress::new(0) },
|
||||
TimerId::T0 => unsafe { VolAddress::new(TIMER0_COUNTER.as_usize()) },
|
||||
TimerId::T1 => unsafe { VolAddress::new(TIMER1_COUNTER.as_usize()) },
|
||||
TimerId::T2 => unsafe { VolAddress::new(TIMER2_COUNTER.as_usize()) },
|
||||
TimerId::T3 => unsafe { VolAddress::new(TIMER3_COUNTER.as_usize()) },
|
||||
},
|
||||
timer_h: match id {
|
||||
TimerId::None => unsafe { VolAddress::new(0) },
|
||||
TimerId::T0 => TIMER0_CONTROL,
|
||||
TimerId::T1 => TIMER1_CONTROL,
|
||||
TimerId::T2 => TIMER2_CONTROL,
|
||||
TimerId::T3 => TIMER3_CONTROL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Starts this timeout.
|
||||
pub fn start(&self) {
|
||||
if self.active {
|
||||
self.timer_l.write(0);
|
||||
let timer_ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true);
|
||||
self.timer_h.write(TimerControl::new());
|
||||
self.timer_h.write(timer_ctl);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether a number of milliseconds has passed since the last call
|
||||
/// to [`Timeout::start()`].
|
||||
pub fn is_timeout_met(&self, check_ms: u16) -> bool {
|
||||
self.active && check_ms * 17 < self.timer_l.read()
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to obtain a lock on the global lock for save operations.
|
||||
///
|
||||
/// This is used to prevent problems with stateful save media.
|
||||
pub fn lock_media() -> Result<RawMutexGuard<'static>, Error> {
|
||||
static LOCK: RawMutex = RawMutex::new();
|
||||
match LOCK.try_lock() {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(Error::MediaInUse),
|
||||
}
|
||||
}
|
169
src/sound.rs
Normal file
169
src/sound.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
use crate::macros::{
|
||||
pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field, u16_int_field,
|
||||
u8_bool_field, u8_int_field,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SweepControl(u8);
|
||||
impl SweepControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u8_int_field!(0 - 2, sweep_num, with_sweep_num);
|
||||
u8_bool_field!(3, sweep_increasing, with_sweep_increasing);
|
||||
u8_int_field!(4 - 6, sweep_time, with_sweep_time);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct TonePattern(u16);
|
||||
impl TonePattern {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 5, length, with_length);
|
||||
u16_int_field!(6 - 7, duty, with_duty);
|
||||
u16_int_field!(8 - 10, step_time, with_step_time);
|
||||
u16_bool_field!(11, step_increasing, with_step_increasing);
|
||||
u16_int_field!(12 - 15, volume, with_volume);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneFrequency(u16);
|
||||
impl ToneFrequency {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 10, frequency, with_frequency);
|
||||
u16_bool_field!(14, stop_when_expired, with_stop_when_expired);
|
||||
u16_bool_field!(15, enabled, with_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveBank(u8);
|
||||
impl WaveBank {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u8_bool_field!(5, two_banks, with_two_banks);
|
||||
u8_bool_field!(6, bank1, with_bank1);
|
||||
u8_bool_field!(7, enabled, with_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveLenVolume(u16);
|
||||
impl WaveLenVolume {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 7, length, with_length);
|
||||
u16_int_field!(13 - 14, volume, with_volume);
|
||||
u16_bool_field!(15, force75, with_force75);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveFrequency(u16);
|
||||
impl WaveFrequency {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 10, sample_rate, with_length);
|
||||
u16_bool_field!(14, stop_when_expired, with_stop_when_expired);
|
||||
u16_bool_field!(15, enabled, with_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseLenEnvelope(u16);
|
||||
impl NoiseLenEnvelope {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 5, length, with_length);
|
||||
u16_int_field!(8 - 10, step_time, with_step_time);
|
||||
u16_bool_field!(11, step_increasing, with_step_increasing);
|
||||
u16_int_field!(12 - 15, volume, with_volume);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseFrequency(u16);
|
||||
impl NoiseFrequency {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 2, r, with_r);
|
||||
u16_bool_field!(3, counter7, with_counter7);
|
||||
u16_int_field!(4 - 7, s, with_s);
|
||||
u16_bool_field!(14, stop_when_expired, with_stop_when_expired);
|
||||
u16_bool_field!(15, enabled, with_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct LeftRightVolume(u16);
|
||||
impl LeftRightVolume {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 2, right_volume, with_right_volume);
|
||||
u16_int_field!(4 - 6, left_volume, with_left_volume);
|
||||
|
||||
u16_bool_field!(8, tone1_right, with_tone1_right);
|
||||
u16_bool_field!(9, tone2_right, with_tone2_right);
|
||||
u16_bool_field!(10, wave_right, with_wave_right);
|
||||
u16_bool_field!(11, noise_right, with_noise_right);
|
||||
|
||||
u16_bool_field!(12, tone1_left, with_tone1_left);
|
||||
u16_bool_field!(13, tone2_left, with_tone2_left);
|
||||
u16_bool_field!(14, wave_left, with_wave_left);
|
||||
u16_bool_field!(15, noise_left, with_noise_left);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum PsgMix {
|
||||
#[default]
|
||||
_25 = 0,
|
||||
_50 = 1,
|
||||
_100 = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundMix(u16);
|
||||
impl SoundMix {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_enum_field!(0 - 2: PsgMix, psg, with_psg);
|
||||
u16_bool_field!(2, sound_a_full, with_sound_a_full);
|
||||
u16_bool_field!(3, sound_b_full, with_sound_b_full);
|
||||
|
||||
u16_bool_field!(8, sound_a_right, with_sound_a_right);
|
||||
u16_bool_field!(9, sound_a_left, with_sound_a_left);
|
||||
u16_bool_field!(10, sound_a_timer, with_sound_a_timer);
|
||||
u16_bool_field!(11, sound_a_reset, with_sound_a_reset);
|
||||
|
||||
u16_bool_field!(12, sound_b_right, with_sound_b_right);
|
||||
u16_bool_field!(13, sound_b_left, with_sound_b_left);
|
||||
u16_bool_field!(14, sound_b_timer, with_sound_b_timer);
|
||||
u16_bool_field!(15, sound_b_reset, with_sound_b_reset);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundEnable(u8);
|
||||
impl SoundEnable {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u8_bool_field!(0, tone1_playing, with_tone1_playing);
|
||||
u8_bool_field!(1, tone2_playing, with_tone2_playing);
|
||||
u8_bool_field!(2, wave_playing, with_wave_playing);
|
||||
u8_bool_field!(3, noise_playing, with_noise_playing);
|
||||
|
||||
u8_bool_field!(7, enabled, with_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum SampleCycle {
|
||||
#[default]
|
||||
_9bit = 0 << 14,
|
||||
_8bit = 1 << 14,
|
||||
_7bit = 2 << 14,
|
||||
_6bit = 3 << 14,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SoundBias(u16);
|
||||
impl SoundBias {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(1 - 9, bias_level, with_bias_level);
|
||||
u16_enum_field!(14 - 15: SampleCycle, sample_cycle, with_sample_cycle);
|
||||
}
|
71
src/sync.rs
71
src/sync.rs
|
@ -1,71 +0,0 @@
|
|||
//! A module containing functions and utilities useful for synchronizing state.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
mod locks;
|
||||
mod statics;
|
||||
|
||||
pub use locks::*;
|
||||
pub use statics::*;
|
||||
|
||||
/// Marks that a pointer is read without actually reading from this.
|
||||
///
|
||||
/// This uses an [`asm!`] instruction that marks the parameter as being read,
|
||||
/// requiring the compiler to treat this function as if anything could be
|
||||
/// done to it.
|
||||
#[inline(always)]
|
||||
pub fn memory_read_hint<T>(val: *const T) {
|
||||
unsafe { asm!("/* {0} */", in(reg) val, options(readonly, nostack)) }
|
||||
}
|
||||
|
||||
/// Marks that a pointer is read or written to without actually writing to it.
|
||||
///
|
||||
/// This uses an [`asm!`] instruction that marks the parameter as being read
|
||||
/// and written, requiring the compiler to treat this function as if anything
|
||||
/// could be done to it.
|
||||
#[inline(always)]
|
||||
pub fn memory_write_hint<T>(val: *mut T) {
|
||||
unsafe { asm!("/* {0} */", in(reg) val, options(nostack)) }
|
||||
}
|
||||
|
||||
/// An internal function used as a temporary hack to get `compiler_fence`
|
||||
/// working. While this call is not properly inlined, working is better than not
|
||||
/// working at all.
|
||||
///
|
||||
/// This seems to be a problem caused by Rust issue #62256:
|
||||
/// <https://github.com/rust-lang/rust/issues/62256>
|
||||
///
|
||||
/// Not public API, obviously.
|
||||
///
|
||||
/// NOTE TO ANYONE WHO FINDS THIS: THIS FUNCTION SHOULD NOT NORMALLY BE BLANK.
|
||||
/// Having a blank version of this function is *only* correct because the GBA is
|
||||
/// so old that it doesn't actually have atomics to sync to begin with (just a
|
||||
/// main thread + interrupts). On any modern CPU, having this function be blank
|
||||
/// is extremely likely to be incorrect.
|
||||
#[doc(hidden)]
|
||||
#[deprecated]
|
||||
#[allow(dead_code)]
|
||||
#[no_mangle]
|
||||
#[inline(always)]
|
||||
pub unsafe extern "C" fn __sync_synchronize() {}
|
||||
|
||||
/// Runs a function with IRQs disabled.
|
||||
///
|
||||
/// This should not be done without good reason, as IRQs are usually important
|
||||
/// for game functionality.
|
||||
pub fn with_irqs_disabled<T>(mut func: impl FnOnce() -> T) -> T {
|
||||
let current_ime = IME.read();
|
||||
unsafe { IME.write(false) };
|
||||
// prevents the contents of the function from being reordered before IME is disabled.
|
||||
memory_write_hint(&mut func);
|
||||
|
||||
let mut result = func();
|
||||
|
||||
// prevents the contents of the function from being reordered after IME is re-enabled.
|
||||
memory_write_hint(&mut result);
|
||||
unsafe { IME.write(current_ime) };
|
||||
|
||||
result
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
use super::*;
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
mem::MaybeUninit,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr,
|
||||
sync::atomic::{compiler_fence, Ordering},
|
||||
};
|
||||
|
||||
#[inline(never)]
|
||||
fn already_locked() -> ! {
|
||||
panic!("This lock has already been locked by another thread.")
|
||||
}
|
||||
|
||||
/// A mutex that prevents code from running in both an IRQ and normal code at
|
||||
/// the same time.
|
||||
///
|
||||
/// Note that this does not support blocking like a typical mutex, and instead
|
||||
/// mainly exists for memory safety reasons.
|
||||
pub struct RawMutex(Static<bool>);
|
||||
impl RawMutex {
|
||||
/// Creates a new lock.
|
||||
pub const fn new() -> Self {
|
||||
RawMutex(Static::new(false))
|
||||
}
|
||||
|
||||
/// Locks the mutex and returns whether a lock was successfully acquired.
|
||||
fn raw_lock(&self) -> bool {
|
||||
if self.0.replace(true) {
|
||||
// value was already true, opps.
|
||||
false
|
||||
} else {
|
||||
// prevent any weird reordering, and continue
|
||||
compiler_fence(Ordering::Acquire);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks the mutex.
|
||||
fn raw_unlock(&self) {
|
||||
compiler_fence(Ordering::Release);
|
||||
if !self.0.replace(false) {
|
||||
panic!("Internal error: Attempt to unlock a `RawMutex` which is not locked.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a guard for this lock, or panics if there is another lock active.
|
||||
pub fn lock(&self) -> RawMutexGuard<'_> {
|
||||
self.try_lock().unwrap_or_else(|| already_locked())
|
||||
}
|
||||
|
||||
/// Returns a guard for this lock, or `None` if there is another lock active.
|
||||
pub fn try_lock(&self) -> Option<RawMutexGuard<'_>> {
|
||||
if self.raw_lock() {
|
||||
Some(RawMutexGuard(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl Send for RawMutex {}
|
||||
unsafe impl Sync for RawMutex {}
|
||||
|
||||
/// A guard representing an active lock on an [`RawMutex`].
|
||||
pub struct RawMutexGuard<'a>(&'a RawMutex);
|
||||
impl<'a> Drop for RawMutexGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.0.raw_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutex that protects an object from being accessed in both an IRQ and
|
||||
/// normal code at once.
|
||||
///
|
||||
/// Note that this does not support blocking like a typical mutex, and instead
|
||||
/// mainly exists for memory safety reasons.
|
||||
pub struct Mutex<T> {
|
||||
raw: RawMutex,
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
impl<T> Mutex<T> {
|
||||
/// Creates a new lock containing a given value.
|
||||
pub const fn new(t: T) -> Self {
|
||||
Mutex { raw: RawMutex::new(), data: UnsafeCell::new(t) }
|
||||
}
|
||||
|
||||
/// Returns a guard for this lock, or panics if there is another lock active.
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
self.try_lock().unwrap_or_else(|| already_locked())
|
||||
}
|
||||
|
||||
/// Returns a guard for this lock or `None` if there is another lock active.
|
||||
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
|
||||
if self.raw.raw_lock() {
|
||||
Some(MutexGuard { underlying: self, ptr: self.data.get() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl<T> Send for Mutex<T> {}
|
||||
unsafe impl<T> Sync for Mutex<T> {}
|
||||
|
||||
/// A guard representing an active lock on an [`Mutex`].
|
||||
pub struct MutexGuard<'a, T> {
|
||||
underlying: &'a Mutex<T>,
|
||||
ptr: *mut T,
|
||||
}
|
||||
impl<'a, T> Drop for MutexGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.underlying.raw.raw_unlock();
|
||||
}
|
||||
}
|
||||
impl<'a, T> Deref for MutexGuard<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
impl<'a, T> DerefMut for MutexGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
enum Void {}
|
||||
|
||||
/// A helper type that ensures a particular value is only initialized once.
|
||||
pub struct InitOnce<T> {
|
||||
is_initialized: Static<bool>,
|
||||
value: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
impl<T> InitOnce<T> {
|
||||
/// Creates a new uninitialized object.
|
||||
pub const fn new() -> Self {
|
||||
InitOnce {
|
||||
is_initialized: Static::new(false),
|
||||
value: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the contents of this state, or initializes it if it has not already
|
||||
/// been initialized.
|
||||
///
|
||||
/// The initializer function is guaranteed to only be called once.
|
||||
///
|
||||
/// This function disables IRQs while it is initializing the inner value.
|
||||
/// While this can cause audio skipping and other similar issues, it is
|
||||
/// not normally a problem as interrupts will only be disabled once per
|
||||
/// `InitOnce` during the life cycle of the program.
|
||||
pub fn get(&self, initializer: impl FnOnce() -> T) -> &T {
|
||||
match self.try_get(|| -> Result<T, Void> { Ok(initializer()) }) {
|
||||
Ok(v) => v,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the contents of this state, or initializes it if it has not already
|
||||
/// been initialized.
|
||||
///
|
||||
/// The initializer function is guaranteed to only be called once if it
|
||||
/// returns `Ok`. If it returns `Err`, it will be called again in the
|
||||
/// future until an attempt at initialization succeeds.
|
||||
///
|
||||
/// This function disables IRQs while it is initializing the inner value.
|
||||
/// While this can cause audio skipping and other similar issues, it is
|
||||
/// not normally a problem as interrupts will only be disabled once per
|
||||
/// `InitOnce` during the life cycle of the program.
|
||||
pub fn try_get<E>(&self, initializer: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
|
||||
unsafe {
|
||||
if !self.is_initialized.read() {
|
||||
// We disable interrupts to make this simpler, since this is likely to
|
||||
// only occur once in a program anyway.
|
||||
with_irqs_disabled(|| -> Result<(), E> {
|
||||
// We check again to make sure this function wasn't called in an
|
||||
// interrupt between the first check and when interrupts were
|
||||
// actually disabled.
|
||||
if !self.is_initialized.read() {
|
||||
// Do the actual initialization.
|
||||
ptr::write_volatile((*self.value.get()).as_mut_ptr(), initializer()?);
|
||||
self.is_initialized.write(true);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
compiler_fence(Ordering::Acquire);
|
||||
Ok(&*(*self.value.get()).as_mut_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Drop for InitOnce<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.is_initialized.read() {
|
||||
// drop the value inside the `MaybeUninit`
|
||||
unsafe {
|
||||
ptr::read((*self.value.get()).as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl<T: Send> Send for InitOnce<T> {}
|
||||
unsafe impl<T: Sync> Sync for InitOnce<T> {}
|
|
@ -1,277 +0,0 @@
|
|||
#![cfg_attr(not(target_arch = "arm"), allow(unused_variables))]
|
||||
|
||||
use crate::sync::with_irqs_disabled;
|
||||
use core::{
|
||||
arch::asm,
|
||||
cell::UnsafeCell,
|
||||
mem::{align_of, size_of, MaybeUninit},
|
||||
ptr,
|
||||
};
|
||||
|
||||
/// The internal function for replacing a `Copy` (really `!Drop`) value in a
|
||||
/// [`Static`]. This uses assembly to use an `stmia` instruction to ensure
|
||||
/// an IRQ cannot occur during the write operation.
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
|
||||
let align = align_of::<T>();
|
||||
let size = size_of::<T>();
|
||||
if size == 0 {
|
||||
// Do nothing with ZSTs. Obviously.
|
||||
} else if size <= 16 && align % 4 == 0 {
|
||||
// We can do an 4-byte aligned transfer up to 16 bytes.
|
||||
transfer_align4_thumb(dst, src);
|
||||
} else if size <= 36 && align % 4 == 0 {
|
||||
// // We can do the same up to 36 bytes, but we need to switch to ARM.
|
||||
// transfer_align4_arm(dst, src);
|
||||
// TODO(rust-console/gba#158) Cannot optimize larger transfers for now.
|
||||
with_irqs_disabled(|| ptr::write_volatile(dst, ptr::read_volatile(src)));
|
||||
} else if size <= 2 && align % 2 == 0 {
|
||||
// We can do a 2-byte aligned transfer up to 2 bytes.
|
||||
asm!(
|
||||
"ldrh {2},[{0}]",
|
||||
"strh {2},[{1}]",
|
||||
in(reg) src, in(reg) dst, out(reg) _,
|
||||
)
|
||||
} else if size == 1 {
|
||||
// We can do a simple byte copy.
|
||||
asm!(
|
||||
"ldrb {2},[{0}]",
|
||||
"strb {2},[{1}]",
|
||||
in(reg) src, in(reg) dst, out(reg) _,
|
||||
)
|
||||
} else {
|
||||
// When we don't have an optimized path, we just disable IRQs.
|
||||
with_irqs_disabled(|| ptr::write_volatile(dst, ptr::read_volatile(src)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[allow(unused_assignments)]
|
||||
unsafe fn transfer_align4_thumb<T: Copy>(mut dst: *mut T, mut src: *const T) {
|
||||
let size = size_of::<T>();
|
||||
if size <= 4 {
|
||||
// We use assembly here regardless to just do the word aligned copy. This
|
||||
// ensures it's done with a single ldr/str instruction.
|
||||
asm!(
|
||||
"ldr {2},[{0}]",
|
||||
"str {2},[{1}]",
|
||||
inout(reg) src, in(reg) dst, out(reg) _,
|
||||
)
|
||||
} else if size <= 8 {
|
||||
// Starting at size == 5, we begin using ldmia/stmia to load/save multiple
|
||||
// words in one instruction, avoiding IRQs from interrupting our operation.
|
||||
asm!(
|
||||
"ldmia {0}!, {{r2-r3}}",
|
||||
"stmia {1}!, {{r2-r3}}",
|
||||
inout(reg) src, inout(reg) dst,
|
||||
out("r2") _, out("r3") _,
|
||||
)
|
||||
} else if size <= 12 {
|
||||
asm!(
|
||||
"ldmia {0}!, {{r2-r4}}",
|
||||
"stmia {1}!, {{r2-r4}}",
|
||||
inout(reg) src, inout(reg) dst,
|
||||
out("r2") _, out("r3") _, out("r4") _,
|
||||
)
|
||||
} else if size <= 16 {
|
||||
asm!(
|
||||
"ldmia {0}!, {{r2-r5}}",
|
||||
"stmia {1}!, {{r2-r5}}",
|
||||
inout(reg) src, inout(reg) dst,
|
||||
out("r2") _, out("r3") _, out("r4") _, out("r5") _,
|
||||
)
|
||||
} else {
|
||||
unimplemented!("This should be done via transfer_arm.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(rust-console/gba#158) Un-comment out this fn when we can use higher registers.
|
||||
// #[cfg(target_arch = "arm")]
|
||||
// #[instruction_set(arm::a32)]
|
||||
// #[allow(unused_assignments)]
|
||||
// unsafe fn transfer_align4_arm<T: Copy>(mut dst: *mut T, mut src: *const T) {
|
||||
// let size = size_of::<T>();
|
||||
// if size <= 16 {
|
||||
// unimplemented!("This should be done via transfer_thumb.");
|
||||
// } else if size <= 20 {
|
||||
// // Starting at size == 20, we have to switch to ARM due to lack of
|
||||
// // accessible registers in THUMB mode.
|
||||
// asm!(
|
||||
// "ldmia {0}!, {{r2-r5,r8}}",
|
||||
// "stmia {1}!, {{r2-r5,r8}}",
|
||||
// inout(reg) src, inout(reg) dst,
|
||||
// out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||
// )
|
||||
// } else if size <= 24 {
|
||||
// asm!(
|
||||
// "push {{r9}}",
|
||||
// "ldmia {0}!, {{r2-r5,r8-r9}}",
|
||||
// "stmia {1}!, {{r2-r5,r8-r9}}",
|
||||
// "pop {{r9}}",
|
||||
// inout(reg) src, inout(reg) dst,
|
||||
// out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||
// )
|
||||
// } else if size <= 28 {
|
||||
// asm!(
|
||||
// "push {{r9}}",
|
||||
// "ldmia {0}!, {{r2-r5,r8-r10}}",
|
||||
// "stmia {1}!, {{r2-r5,r8-r10}}",
|
||||
// "pop {{r9}}",
|
||||
// inout(reg) src, inout(reg) dst,
|
||||
// out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||
// out("r10") _,
|
||||
// )
|
||||
// } else if size <= 32 {
|
||||
// asm!(
|
||||
// "push {{r9}}",
|
||||
// "ldmia {0}!, {{r2-r5,r8-r10,r12}}",
|
||||
// "stmia {1}!, {{r2-r5,r8-r10,r12}}",
|
||||
// "pop {{r9}}",
|
||||
// inout(reg) src, inout(reg) dst,
|
||||
// out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||
// out("r10") _, out("r12") _,
|
||||
// )
|
||||
// } else if size <= 36 {
|
||||
// asm!(
|
||||
// "push {{r9}}",
|
||||
// "ldmia {0}!, {{r2-r5,r8-r10,r12,r14}}",
|
||||
// "stmia {1}!, {{r2-r5,r8-r10,r12,r14}}",
|
||||
// "pop {{r9}}",
|
||||
// inout(reg) src, inout(reg) dst,
|
||||
// out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||
// out("r10") _, out("r12") _, out("r14") _,
|
||||
// )
|
||||
// } else {
|
||||
// unimplemented!("Copy too large for use of ldmia/stmia.");
|
||||
// }
|
||||
// }
|
||||
|
||||
/// The internal function for swapping the current value of a [`Static`] with
|
||||
/// another value.
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn exchange<T>(dst: *mut T, src: *const T) -> T {
|
||||
let align = align_of::<T>();
|
||||
let size = size_of::<T>();
|
||||
if size == 0 {
|
||||
// Do nothing with ZSTs.
|
||||
ptr::read(dst)
|
||||
} else if size <= 4 && align % 4 == 0 {
|
||||
// Swap a single word with the SWP instruction.
|
||||
let val = ptr::read(src as *const u32);
|
||||
let new_val = exchange_align4_arm(dst, val);
|
||||
ptr::read(&new_val as *const _ as *const T)
|
||||
} else if size == 1 {
|
||||
// Swap a byte with the SWPB instruction.
|
||||
let val = ptr::read(src as *const u8);
|
||||
let new_val = exchange_align1_arm(dst, val);
|
||||
ptr::read(&new_val as *const _ as *const T)
|
||||
} else {
|
||||
// fallback
|
||||
with_irqs_disabled(|| {
|
||||
let cur = ptr::read_volatile(dst);
|
||||
ptr::write_volatile(dst, ptr::read_volatile(src));
|
||||
cur
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[instruction_set(arm::a32)]
|
||||
unsafe fn exchange_align4_arm<T>(dst: *mut T, i: u32) -> u32 {
|
||||
let out;
|
||||
asm!("swp {2}, {1}, [{0}]", in(reg) dst, in(reg) i, lateout(reg) out);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[instruction_set(arm::a32)]
|
||||
unsafe fn exchange_align1_arm<T>(dst: *mut T, i: u8) -> u8 {
|
||||
let out;
|
||||
asm!("swpb {2}, {1}, [{0}]", in(reg) dst, in(reg) i, lateout(reg) out);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
unsafe fn exchange<T>(dst: *mut T, src: *const T) -> T {
|
||||
unimplemented!("This function is not supported on this target.")
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
|
||||
unimplemented!("This function is not supported on this target.")
|
||||
}
|
||||
|
||||
/// A helper that implements static variables.
|
||||
///
|
||||
/// It ensures that even if you use the same static variable in both an IRQ
|
||||
/// and normal code, the IRQ will never observe an invalid value of the
|
||||
/// variable.
|
||||
///
|
||||
/// This type only works with owned values. If you need to work with borrows,
|
||||
/// consider using [`super::Mutex`] instead.
|
||||
///
|
||||
/// ## Performance
|
||||
///
|
||||
/// Writing or reading from a static variable is efficient under the following
|
||||
/// conditions:
|
||||
///
|
||||
/// * The type is aligned to 4 bytes and can be stored in 36 bytes or less.
|
||||
/// * The type is aligned to 2 bytes and can be stored in 2 bytes.
|
||||
/// * The type is can be stored in a single byte.
|
||||
///
|
||||
/// Replacing the current value of the static variable is efficient under the
|
||||
/// following conditions:
|
||||
///
|
||||
/// * The type is aligned to 4 bytes and can be stored in 4 bytes or less.
|
||||
/// * The type is can be stored in a single byte.
|
||||
///
|
||||
/// When these conditions are not met, static variables are handled using a
|
||||
/// fallback routine that disables IRQs and does a normal copy. This can be
|
||||
/// dangerous as disabling IRQs can cause your program to miss out on important
|
||||
/// interrupts such as V-Blank.
|
||||
///
|
||||
/// Consider using [`super::Mutex`] instead if you need to use a large amount of
|
||||
/// operations that would cause IRQs to be disabled. Also consider using
|
||||
/// `#[repr(align(4))]` to force proper alignment for your type.
|
||||
pub struct Static<T> {
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
impl<T> Static<T> {
|
||||
/// Creates a new static variable.
|
||||
pub const fn new(val: T) -> Self {
|
||||
Static { data: UnsafeCell::new(val) }
|
||||
}
|
||||
|
||||
/// Replaces the current value of the static variable with another, and
|
||||
/// returns the old value.
|
||||
pub fn replace(&self, val: T) -> T {
|
||||
unsafe { exchange(self.data.get(), &val) }
|
||||
}
|
||||
|
||||
/// Extracts the interior value of the static variable.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.data.into_inner()
|
||||
}
|
||||
}
|
||||
impl<T: Copy> Static<T> {
|
||||
/// Writes a new value into this static variable.
|
||||
pub fn write(&self, val: T) {
|
||||
unsafe { transfer(self.data.get(), &val) }
|
||||
}
|
||||
|
||||
/// Reads a value from this static variable.
|
||||
pub fn read(&self) -> T {
|
||||
unsafe {
|
||||
let mut out: MaybeUninit<T> = MaybeUninit::uninit();
|
||||
transfer(out.as_mut_ptr(), self.data.get());
|
||||
out.assume_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Default> Default for Static<T> {
|
||||
fn default() -> Self {
|
||||
Static::new(T::default())
|
||||
}
|
||||
}
|
||||
unsafe impl<T> Send for Static<T> {}
|
||||
unsafe impl<T> Sync for Static<T> {}
|
22
src/timers.rs
Normal file
22
src/timers.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::macros::{pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum TimerPrescale {
|
||||
#[default]
|
||||
_1 = 0,
|
||||
_64 = 1,
|
||||
_256 = 2,
|
||||
_1024 = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct TimerControl(u16);
|
||||
impl TimerControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_enum_field!(0 - 1: TimerPrescale, prescale, with_prescale);
|
||||
u16_bool_field!(2, chained, with_chained);
|
||||
u16_bool_field!(6, overflow_irq, with_overflow_irq);
|
||||
u16_bool_field!(7, enabled, with_enabled);
|
||||
}
|
233
src/video.rs
Normal file
233
src/video.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use crate::macros::{
|
||||
pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field, u16_int_field,
|
||||
};
|
||||
|
||||
/// An RGB555 color value (packed into `u16`).
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Color(pub u16);
|
||||
impl Color {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 4, red, with_red);
|
||||
u16_int_field!(5 - 9, green, with_green);
|
||||
u16_int_field!(10 - 14, blue, with_blue);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum VideoMode {
|
||||
#[default]
|
||||
_0 = 0,
|
||||
_1 = 1,
|
||||
_2 = 2,
|
||||
_3 = 3,
|
||||
_4 = 4,
|
||||
_5 = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayControl(u16);
|
||||
impl DisplayControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_enum_field!(0 - 2: VideoMode, video_mode, with_video_mode);
|
||||
u16_bool_field!(4, show_frame1, with_show_frame1);
|
||||
u16_bool_field!(5, hblank_oam_free, with_hblank_oam_free);
|
||||
u16_bool_field!(6, obj_vram_1d, with_obj_vram_1d);
|
||||
u16_bool_field!(7, forced_blank, with_forced_blank);
|
||||
u16_bool_field!(8, show_bg0, with_show_bg0);
|
||||
u16_bool_field!(9, show_bg1, with_show_bg1);
|
||||
u16_bool_field!(10, show_bg2, with_show_bg2);
|
||||
u16_bool_field!(11, show_bg3, with_show_bg3);
|
||||
u16_bool_field!(12, show_obj, with_show_obj);
|
||||
u16_bool_field!(13, enable_win0, with_enable_win0);
|
||||
u16_bool_field!(14, enable_win1, with_enable_win1);
|
||||
u16_bool_field!(15, enable_obj_win, with_enable_obj_win);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayStatus(u16);
|
||||
impl DisplayStatus {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, currently_vblank, with_currently_vblank);
|
||||
u16_bool_field!(1, currently_hblank, with_currently_hblank);
|
||||
u16_bool_field!(2, currently_vcount, with_currently_vcount);
|
||||
u16_bool_field!(3, irq_vblank, with_irq_vblank);
|
||||
u16_bool_field!(4, irq_hblank, with_irq_hblank);
|
||||
u16_bool_field!(5, irq_vcount, with_irq_vcount);
|
||||
u16_int_field!(8 - 15, vcount_setting, with_vcount_setting);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct BackgroundControl(u16);
|
||||
impl BackgroundControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 1, priority, with_priority);
|
||||
u16_int_field!(2 - 3, charblock, with_charblock);
|
||||
u16_bool_field!(6, mosaic, with_mosaic);
|
||||
u16_bool_field!(7, bpp8, with_bpp8);
|
||||
u16_int_field!(8 - 12, screenblock, with_screenblock);
|
||||
u16_bool_field!(13, is_affine_wrapping, with_is_affine_wrapping);
|
||||
u16_int_field!(14 - 15, size, with_size);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct WindowInside(u16);
|
||||
impl WindowInside {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, win0_bg0, with_win0_bg0);
|
||||
u16_bool_field!(1, win0_bg1, with_win0_bg1);
|
||||
u16_bool_field!(2, win0_bg2, with_win0_bg2);
|
||||
u16_bool_field!(3, win0_bg3, with_win0_bg3);
|
||||
u16_bool_field!(4, win0_obj, with_win0_obj);
|
||||
u16_bool_field!(5, win0_effect, with_win0_effect);
|
||||
u16_bool_field!(8, win1_bg0, with_win1_bg0);
|
||||
u16_bool_field!(9, win1_bg1, with_win1_bg1);
|
||||
u16_bool_field!(10, win1_bg2, with_win1_bg2);
|
||||
u16_bool_field!(11, win1_bg3, with_win1_bg3);
|
||||
u16_bool_field!(12, win1_obj, with_win1_obj);
|
||||
u16_bool_field!(13, win1_effect, with_win1_effect);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct WindowOutside(u16);
|
||||
impl WindowOutside {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, outside_bg0, with_outside_bg0);
|
||||
u16_bool_field!(1, outside_bg1, with_outside_bg1);
|
||||
u16_bool_field!(2, outside_bg2, with_outside_bg2);
|
||||
u16_bool_field!(3, outside_bg3, with_outside_bg3);
|
||||
u16_bool_field!(4, outside_obj, with_outside_obj);
|
||||
u16_bool_field!(5, outside_effect, with_outside_effect);
|
||||
u16_bool_field!(8, obj_win_bg0, with_obj_win_bg0);
|
||||
u16_bool_field!(9, obj_win_bg1, with_obj_win_bg1);
|
||||
u16_bool_field!(10, obj_win_bg2, with_obj_win_bg2);
|
||||
u16_bool_field!(11, obj_win_bg3, with_obj_win_bg3);
|
||||
u16_bool_field!(12, obj_win_obj, with_obj_win_obj);
|
||||
u16_bool_field!(13, obj_win_effect, with_obj_win_effect);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Mosaic(u16);
|
||||
impl Mosaic {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 3, bg_h_extra, with_bg_h_extra);
|
||||
u16_int_field!(4 - 7, bg_v_extra, with_bg_v_extra);
|
||||
u16_int_field!(8 - 11, obj_h_extra, with_obj_h_extra);
|
||||
u16_int_field!(12 - 15, obj_v_extra, with_obj_v_extra);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum ColorEffectMode {
|
||||
#[default]
|
||||
NoEffect = 0 << 6,
|
||||
AlphaBlend = 1 << 6,
|
||||
Brighten = 2 << 6,
|
||||
Darken = 3 << 6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct BlendControl(u16);
|
||||
impl BlendControl {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_bool_field!(0, target1_bg0, with_target1_bg0);
|
||||
u16_bool_field!(1, target1_bg1, with_target1_bg1);
|
||||
u16_bool_field!(2, target1_bg2, with_target1_bg2);
|
||||
u16_bool_field!(3, target1_bg3, with_target1_bg3);
|
||||
u16_bool_field!(4, target1_obj, with_target1_obj);
|
||||
u16_bool_field!(5, target1_backdrop, with_target1_backdrop);
|
||||
u16_enum_field!(6 - 7: ColorEffectMode, mode, with_mode);
|
||||
u16_bool_field!(8, target2_bg0, with_target2_bg0);
|
||||
u16_bool_field!(9, target2_bg1, with_target2_bg1);
|
||||
u16_bool_field!(10, target2_bg2, with_target2_bg2);
|
||||
u16_bool_field!(11, target2_bg3, with_target2_bg3);
|
||||
u16_bool_field!(12, target2_obj, with_target2_obj);
|
||||
u16_bool_field!(13, target2_backdrop, with_target2_backdrop);
|
||||
}
|
||||
|
||||
/// Data for a 4-bit-per-pixel tile.
|
||||
pub type Tile4 = [u32; 8];
|
||||
|
||||
/// Data for an 8-bit-per-pixel tile.
|
||||
pub type Tile8 = [u32; 16];
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct TextEntry(u16);
|
||||
impl TextEntry {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 9, tile_id, with_tile_id);
|
||||
u16_bool_field!(10, hflip, with_hflip);
|
||||
u16_bool_field!(11, vflip, with_vflip);
|
||||
u16_int_field!(12 - 15, palbank, with_palbank);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjDisplayStyle {
|
||||
#[default]
|
||||
Normal = 0 << 8,
|
||||
Affine = 1 << 8,
|
||||
NotDisplayed = 2 << 8,
|
||||
DoubleSizeAffine = 3 << 8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjDisplayMode {
|
||||
#[default]
|
||||
Normal = 0 << 10,
|
||||
SemiTransparent = 1 << 10,
|
||||
Window = 2 << 10,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjShape {
|
||||
#[default]
|
||||
Square = 0 << 14,
|
||||
Horizontal = 1 << 14,
|
||||
Vertical = 2 << 14,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr0(u16);
|
||||
impl ObjAttr0 {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 7, y, with_y);
|
||||
u16_enum_field!(8 - 9: ObjDisplayStyle, style, with_style);
|
||||
u16_enum_field!(10 - 11: ObjDisplayMode, mode, with_mode);
|
||||
u16_bool_field!(12, mosaic, with_mosaic);
|
||||
u16_bool_field!(13, bpp8, with_bpp8);
|
||||
u16_enum_field!(14 - 15: ObjShape, shape, with_shape);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr1(u16);
|
||||
impl ObjAttr1 {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 8, x, with_x);
|
||||
u16_int_field!(9 - 13, affine_index, with_affine_index);
|
||||
u16_bool_field!(12, hflip, with_hflip);
|
||||
u16_bool_field!(13, vflip, with_vflip);
|
||||
u16_int_field!(14 - 15, size, with_size);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ObjAttr2(u16);
|
||||
impl ObjAttr2 {
|
||||
pub_const_fn_new_zeroed!();
|
||||
u16_int_field!(0 - 9, tile_id, with_tile_id);
|
||||
u16_int_field!(10 - 11, priority, with_priority);
|
||||
u16_int_field!(12 - 15, palbank, with_palbank);
|
||||
}
|
Loading…
Reference in a new issue