Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["std"]
# `std` brings in the Vec-backed `Bus` and the `start` run loop. Without it the
# CPU core is `no_std` so it can be embedded on bare-metal hosts (see `gba/`).
std = []

[dependencies]
device_interfaces = { path = "../device_interfaces" }

Expand Down
6 changes: 3 additions & 3 deletions core/src/bus_interface.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::error::Error;
use core::error::Error;

#[derive(Debug, Clone, Copy)]
pub enum BusException {
Expand All @@ -8,8 +8,8 @@ pub enum BusException {
StoreAccessFault,
}

impl std::fmt::Display for BusException {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Display for BusException {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LoadAddressMisaligned => write!(f, "LoadAddressMisaligned,"),
Self::LoadAccessFault => write!(f, "LoadAccessFault,"),
Expand Down
16 changes: 10 additions & 6 deletions core/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ pub struct Cpu<B> {
wait_for_interrupt: bool,
/// Previous privilege mode.
previous_mode: PrivilegeMode,
/// This is used to reserve addresses for LR/SC
reserved_load_addresses: std::collections::HashMap<u32, u32>,
/// Address and value reserved by the last LR.W, consumed by SC.W.
reservation: Option<(u32, u32)>,
/// It is used to record exception reason for mtval
cause: u32,
}
Expand All @@ -74,7 +74,7 @@ impl<B: BusController + BusReader + BusWriter> Cpu<B> {
exception: 0,
wait_for_interrupt: false,
previous_mode: PrivilegeMode::Machine,
reserved_load_addresses: std::collections::HashMap::new(),
reservation: None,
cause: 0,
}
}
Expand Down Expand Up @@ -222,6 +222,10 @@ impl<B: BusController + BusWriter + BusReader> Cpu<B> {
&self.bus
}

pub fn bus_mut(&mut self) -> &mut B {
&mut self.bus
}

pub fn step(&mut self) -> CpuState {
// Drive bus state
self.bus.step(&mut self.mip);
Expand Down Expand Up @@ -470,14 +474,14 @@ impl<B: BusController + BusWriter + BusReader> Cpu<B> {
// LR.W
// Load-Reserved Word
0b00010 => {
self.reserved_load_addresses.insert(rs1, v);
self.reservation = Some((rs1, v));
self.write_back(rd, v)
}
// SC.W
// Store-Conditional Word
0b00011 => {
if let Some(val) = self.reserved_load_addresses.get(&rs1) {
if *val == v {
if let Some((addr, val)) = self.reservation.take() {
if addr == rs1 && val == v {
self.bus
.write32(rs1, rs2)
.unwrap_or_else(|e| self.record_exception(e.into(), rs1));
Expand Down
6 changes: 6 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "std")]
pub mod bus;
pub mod bus_interface;
pub mod clint;
pub mod cpu;

#[cfg(feature = "std")]
use bus_interface::{BusController, BusReader, BusWriter};
#[cfg(feature = "std")]
use cpu::{Cpu, CpuState};

#[cfg(feature = "std")]
pub fn start<B: BusController + BusReader + BusWriter>(
bus: B,
pc: u32,
Expand Down
2 changes: 2 additions & 0 deletions device_interfaces/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![no_std]

mod serial;
mod timer;

Expand Down
9 changes: 9 additions & 0 deletions gba/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[build]
target = "armv4t-none-eabi"

[unstable]
build-std = ["core"]
build-std-features = ["compiler-builtins-mem"]

[target.armv4t-none-eabi]
rustflags = ["-Clink-arg=-Tgba.ld"]
27 changes: 27 additions & 0 deletions gba/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "r2-gba"
version = "0.1.0"
edition = "2021"

# Runs the r2 RISC-V core on a Game Boy Advance and boots Linux on it.
# Built for `thumbv4t-none-eabi` (see .cargo/config.toml), so it lives outside
# the host workspace.
[workspace]

[features]
# Boot the tiny bare-metal test program instead of Linux.
baremetal = []

[dependencies]
r2_core = { path = "../core", package = "core", default-features = false }

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
debug = false

[profile.dev]
opt-level = 1
panic = "abort"
19 changes: 19 additions & 0 deletions gba/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Build the GBA ROM: `make` (Linux) or `make baremetal`.
SYSROOT := $(shell rustc --print sysroot)
OBJCOPY := $(wildcard $(SYSROOT)/lib/rustlib/*/bin/llvm-objcopy)
ELF := target/armv4t-none-eabi/release/r2-gba

.PHONY: all baremetal clean FORCE

all: target/r2-gba.gba

baremetal: FORCE
cargo build --release --features baremetal --target-dir target/baremetal
$(OBJCOPY) -O binary target/baremetal/armv4t-none-eabi/release/r2-gba target/r2-gba-baremetal.gba

target/r2-gba.gba: FORCE
cargo build --release
$(OBJCOPY) -O binary $(ELF) $@

clean:
cargo clean
60 changes: 60 additions & 0 deletions gba/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# r2-gba

Runs the r2 RISC-V core on a Game Boy Advance and boots Linux on it
(the same `fixtures/linux.bin` + `default.dtb` as the other frontends).

![Linux shell on the GBA (rgba)](screenshot.png)

## How it fits in 288KB of RAM

It doesn't. Flash carts that run games out of PSRAM (EZ-Flash, EverDrive, ...)
expose the 32MB GamePak ROM window as writable memory, so:

| GBA address | Use |
| ------------------------- | ----------------------------------------- |
| `0x08000000-0x08FFFFFF` | this program + embedded kernel image/DTB |
| `0x09000000-0x09FFFFFF` | guest RAM (16MB, guest `0x80000000-`) |
| IWRAM | the interpreter loop (ARM code) and stack |
| VRAM (Mode 3) | 60x26 text console (4x6 font) |

At boot the kernel is copied into guest RAM and the DTB is placed at its end
(its memory size is patched like mini-rv32ima does).

The guest clock is derived from the instruction count (16 instructions per
µs), so the guest sees a 16 MIPS machine and timing is deterministic.
WFI fast-forwards the clock to the next timer interrupt.

## Build

Needs a nightly toolchain with `rust-src` (picked up from `rust-toolchain.toml`).

```sh
make # target/r2-gba.gba
make baremetal # target/r2-gba-baremetal.gba (tiny test program)
```

## Run

On [rgba](https://github.com/bokuweb/rgba) with PSRAM emulation:

```sh
RGBA_PSRAM=1 rgba target/r2-gba.gba
```

The console is also mirrored to the mGBA debug log. The login prompt is
answered automatically (`root`), and `uname -a` runs at the first prompt. Each button types a command:

| Button | Command |
| ------ | ------------------- |
| A | `uname -a` |
| B | `ls /` |
| L | `cat /proc/cpuinfo` |
| R | `free` |
| START | `uptime` |
| SELECT | `ps` |

## Speed

About 70 ARM instructions per RISC-V instruction, roughly 110K guest
instructions per second on a 16.78MHz GBA. Reaching the login prompt takes
about 40M guest instructions, i.e. several minutes of GBA time.
11 changes: 11 additions & 0 deletions gba/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::{env, fs, path::PathBuf};

fn main() {
// Put the linker script where rust-lld can find it.
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::copy("gba.ld", out.join("gba.ld")).unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=gba.ld");
println!("cargo:rerun-if-changed=../fixtures/linux.bin");
println!("cargo:rerun-if-changed=../fixtures/default.dtb");
}
50 changes: 50 additions & 0 deletions gba/gba.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* GBA memory map: code and payload in GamePak ROM, hot code and data in IWRAM. */
ENTRY(_start)

MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 31K /* top 1K: stacks + BIOS area */
rom (rx) : ORIGIN = 0x08000000, LENGTH = 16M /* upper 16M is guest RAM */
}

SECTIONS {
.text : {
KEEP(*(.text.crt0))
*(.text .text.*)
. = ALIGN(4);
} >rom

.rodata : {
*(.rodata .rodata.*)
. = ALIGN(4);
} >rom

/* Copied from ROM to IWRAM by crt0: the emulator loop and initialized data. */
.iwram : {
. = ALIGN(4);
__iwram_start = .;
*(.iwram .iwram.*)
*(.data .data.*)
. = ALIGN(4);
__iwram_end = .;
} >iwram AT>rom
__iwram_lma = LOADADDR(.iwram);

.bss (NOLOAD) : {
. = ALIGN(4);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end = .;
} >iwram

.ewram (NOLOAD) : {
*(.ewram .ewram.*)
} >ewram

/DISCARD/ : {
*(.ARM.exidx .ARM.exidx.*)
*(.ARM.extab .ARM.extab.*)
}
}
3 changes: 3 additions & 0 deletions gba/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]
Binary file added gba/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading