Skip to content

Commit

Permalink
Files save on baremetal RPI (#137)
Browse files Browse the repository at this point in the history
Implement FAT32 save mechanism with caching and optimizations to improve performance

Still does not support folders/directories
  • Loading branch information
alloncm authored Jan 5, 2025
1 parent 48c7340 commit 95e436c
Show file tree
Hide file tree
Showing 21 changed files with 888 additions and 353 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ members = [
version = "4.0.0"
authors = ["alloncm <alloncm@gmail.com>"]
rust-version = "1.73" # cause of cargo-ndk used to build for android platform
edition = "2021"
edition = "2021"

[profile.release]
lto = true # Samller binaries and faster code
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ Choose a game with the Joypad bindings (Dpad and A to confirm)
### Raspberry Pi Baremetal

Currently only Raspberry Pi 4 is supported using the following instructions:
* Format a sd card and make a single `FAT32` partition called `boot`
* Copy the file `config.txt` to the root dir of the sd card
* Copy the following files from the [Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/master/boot) onto the SD card:
- [fixup4.dat](https://github.com/raspberrypi/firmware/raw/master/boot/fixup4.dat)
- [start4.elf](https://github.com/raspberrypi/firmware/raw/master/boot/start4.elf)
- [bcm2711-rpi-4-b.dtb](https://github.com/raspberrypi/firmware/raw/master/boot/bcm2711-rpi-4-b.dtb)
* Copy `kernel7.img` onto the SD card
* Format a sd card to MBR (not GPT) and create a single `FAT32` partition (On windows you can use Rufus)
* Copy the following files from the [Raspberry Pi firmware repo](https://github.com/raspberrypi/firmware/tree/191360eaf2e5933eaa0ed76ac0d62722b6f9a58f/boot) onto the SD card:
- [fixup4.dat](https://github.com/raspberrypi/firmware/raw/191360eaf2e5933eaa0ed76ac0d62722b6f9a58f/boot/fixup4.dat)
- [start4.elf](https://github.com/raspberrypi/firmware/raw/191360eaf2e5933eaa0ed76ac0d62722b6f9a58f/boot/start4.elf)
- [bcm2711-rpi-4-b.dtb](https://github.com/raspberrypi/firmware/raw/191360eaf2e5933eaa0ed76ac0d62722b6f9a58f/boot/bcm2711-rpi-4-b.dtb)

_**Notice**: This is a specific revision, for some reason it broke on the latest version of those files_
* Copy `kernel7.img` and `config.txt` to the SD card
* Connect all the peripherals (ili9341 display and gpio buttons)
* Insert the SD card to the RPI4 and boot it

Expand Down
8 changes: 4 additions & 4 deletions core/src/mmu/carts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ pub const MBC_RAM_SIZE_LOCATION:usize = 0x149;
pub fn get_ram_size(ram_size_register:u8)->usize{
match ram_size_register{
0x0=>0,
0x1=>0x800,
0x1=>0x800, // Unofficial - Undefined according to official docs
0x2=>0x4000,
0x3=>0x8000,
0x4=>0x20000,
0x5=>0x10000,
0x4=>0x2_0000,
0x5=>0x1_0000,
_=>core::panic!("invalid ram size register {:#X}", ram_size_register)
}
}
Expand All @@ -34,7 +34,7 @@ pub fn init_ram(ram_reg:u8, external_ram:Option<&'static mut[u8]>)->&'static mut
match external_ram{
Some(ram)=>{
if ram.len() != ram_size{
core::panic!("external rom is not in the correct size for the cartridge");
core::panic!("External ram is not in the correct size for the cartridge, the save seems corrupted, either fix or delete it and try again");
}

return ram;
Expand Down
12 changes: 6 additions & 6 deletions core/src/mmu/gb_mmu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G
return self.io_bus.ppu.vram.read_current_bank(address-0x8000);
}
else{
log::warn!("bad vram read");
log::trace!("bad vram read");
return BAD_READ_VALUE;
}
},
Expand All @@ -50,7 +50,7 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G
return self.io_bus.ppu.oam[(address-0xFE00) as usize];
}
else{
log::warn!("bad oam read");
log::trace!("bad oam read");
return BAD_READ_VALUE;
}
}
Expand Down Expand Up @@ -80,15 +80,15 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G
self.io_bus.ppu.vram.write_current_bank(address-0x8000, value);
}
else{
log::warn!("bad vram write: address - {:#X}, value - {:#X}, bank - {}", address, value, self.io_bus.ppu.vram.get_bank_reg());
log::trace!("bad vram write: address - {:#X}, value - {:#X}, bank - {}", address, value, self.io_bus.ppu.vram.get_bank_reg());
}
},
0xFE00..=0xFE9F=>{
if self.is_oam_ready_for_io(){
self.io_bus.ppu.oam[(address-0xFE00) as usize] = value;
}
else{
log::warn!("bad oam write")
log::trace!("bad oam write")
}
},
_=>self.write_unprotected(address, value)
Expand Down Expand Up @@ -249,12 +249,12 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{
}

fn bad_dma_read(address:u16)->u8{
log::warn!("bad memory read during dma. {:#X}", address);
log::trace!("bad memory read during dma. {:#X}", address);
return BAD_READ_VALUE;
}

fn bad_dma_write(address:u16){
log::warn!("bad memory write during dma. {:#X}", address)
log::trace!("bad memory write during dma. {:#X}", address)
}

fn write_color_ram(&mut self, address:u16, color: Color){
Expand Down
2 changes: 1 addition & 1 deletion core/src/ppu/gb_ppu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ impl<GFX:GfxDevice> GbPpu<GFX>{
color_ram[(*pallete_index_register & 0b11_1111) as usize] = value;
}
else{
log::warn!("bad color ram write: index - {:#X}, value: - {:#X}", pallete_index_register, value);
log::trace!("bad color ram write: index - {:#X}, value: - {:#X}", pallete_index_register, value);
}

// if bit 7 is set inderement the dest adderess after write
Expand Down
4 changes: 2 additions & 2 deletions rpi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ magenboy_core = {path = "../core"}
magenboy_common = {path = "../common"}
log = "0.4"
cfg-if = "1"
bitfield-struct = "0.5"
bitfield-struct = {version = "0.9", optional = true}
libc = {version = "0.2", optional = true}
nix = {version = "0.24", optional = true}
crossbeam-channel = {version = "0.5", optional = true}
Expand All @@ -20,7 +20,7 @@ arrayvec = {version = "0.7.6", default-features = false, optional = true}

[features]
os = ["magenboy_common/std", "libc", "nix/ioctl", "crossbeam-channel", "rppal"]
bm = ["arrayvec"]
bm = ["arrayvec", "bitfield-struct"]

[[bin]]
name = "rpios"
Expand Down
6 changes: 0 additions & 6 deletions rpi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,5 @@ fn main(){
let rpi_version = std::env::var(config::RPI_ENV_VAR_NAME)
.expect(std::format!("{} env must be set", config::RPI_ENV_VAR_NAME).as_str());
println!("cargo:rustc-cfg=rpi=\"{}\"", rpi_version);

// Silent warnings for this cfg
println!("cargo:rustc-check-cfg=cfg(rpi, values(\"4\", \"2\"))");
}

// Silent warnings for this cfg
println!("cargo:rustc-check-cfg=cfg(rpi)");
}
2 changes: 1 addition & 1 deletion rpi/src/bin/baremetal/link.ld
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Place _start procedure at the entry address for RPI */
__rpi_32_phys_binary_load_addr = 0x8000;
__isr_table_addr = 0;
__stack_size = 0x100000; /* 1MB stack */
__stack_size = 0x300000; /* 3MB stack */
ENTRY(__rpi_32_phys_binary_load_addr) /* enry point */

SECTIONS
Expand Down
95 changes: 71 additions & 24 deletions rpi/src/bin/baremetal/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ use core::panic::PanicInfo;
use arrayvec::ArrayString;

use magenboy_common::{joypad_menu::{joypad_gfx_menu::{self, GfxDeviceMenuRenderer}, JoypadMenu, }, menu::*, VERSION};
use magenboy_core::machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc};
use magenboy_rpi::{drivers::*, peripherals::{PERIPHERALS, GpioPull}, configuration::{display::*, joypad::button_to_bcm_pin, emulation::*}, MENU_PIN_BCM, delay};
use magenboy_core::{machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, mmu::carts::Mbc};
use magenboy_rpi::{drivers::*, peripherals::{PERIPHERALS, GpioPull, ResetMode, Power}, configuration::{display::*, joypad::button_to_bcm_pin, emulation::*}, MENU_PIN_BCM, delay};

#[panic_handler]
fn panic(info:&PanicInfo)->!{
log::error!("An error has occurred!: \r\n{}", info);

// SAFETY: Defined in boot asm code, no params passed and this function does not returned so no calling convention is even needed
unsafe{boot::hang_led()};
}

const MAX_ROM_SIZE:usize = 0x80_0000; // 8 MiB, Max size of MBC5 rom
const MAX_RAM_SIZE:usize = 0x2_0000; // 128 KiB

// Allocating as static buffer (on the .bss) because it is a very large buffer and
// I dont want to cause problems in stack making it overflow and shit (I can increase it when needed but I afraid Id forget)
// I don't want to cause problems in stack making it overflow and shit (I can increase it when needed but I afraid Id forget)
static mut ROM_BUFFER:[u8; MAX_ROM_SIZE] = [0;MAX_ROM_SIZE];
static mut RAM_BUFFER:[u8; MAX_RAM_SIZE] = [0;MAX_RAM_SIZE];

// This function is no regular main.
// It will not return and will be jumped to from the _start proc in the boot code
Expand All @@ -28,14 +38,14 @@ pub extern "C" fn main()->!{
log::info!("Initialized logger");
log::info!("running at exec mode: {:#X}", boot::get_cpu_execution_mode());

let mut power_manager = unsafe{PERIPHERALS.take_power()};
let power_manager = unsafe{PERIPHERALS.take_power()};

let mut fs = Fat32::new();
let mut fs = Fat32Fs::new();
let mut gfx = Ili9341GfxDevice::new(RESET_PIN_BCM, LED_PIN_BCM, TURBO, FRAME_LIMITER);
let mut pause_menu_gfx = gfx.clone();
let mut joypad_provider = GpioJoypadProvider::new(button_to_bcm_pin);
let mut pause_menu_joypad_provider = joypad_provider.clone();
log::info!("Initialize all drivers succesfully");
log::info!("Initialize all drivers successfully");

let menu_renderer = joypad_gfx_menu::GfxDeviceMenuRenderer::new(&mut gfx);

Expand All @@ -46,16 +56,18 @@ pub extern "C" fn main()->!{
let selected_rom = menu.get_menu_selection(&mut joypad_provider);
log::info!("Selected ROM: {}", selected_rom.get_name());

// SAFETY: Only ref to this static mut var
let rom = unsafe{&mut ROM_BUFFER};
fs.read_file(selected_rom, rom);
let mbc = initialize_mbc(&rom[0..selected_rom.size as usize], None);
let save_data = try_read_save_file(selected_rom, &mut fs);
let mbc = initialize_mbc(&rom[0..selected_rom.size as usize], save_data);
let mode = mbc.detect_preferred_mode();

let mut gameboy = GameBoy::new_with_mode(mbc, joypad_provider, magenboy_rpi::BlankAudioDevice, gfx, mode);
log::info!("Initialized gameboy!");

let menu_pin = unsafe {PERIPHERALS.get_gpio().take_pin(MENU_PIN_BCM).into_input(GpioPull::PullUp)};
let pause_menu_header:ArrayString<30> = ArrayString::try_from(format_args!("MagenBoy bm v{}", VERSION)).unwrap();
let pause_menu_header:ArrayString<30> = ArrayString::try_from(format_args!("MagenBoy v{}", VERSION)).unwrap();
let pause_menu_renderer = GfxDeviceMenuRenderer::new(&mut pause_menu_gfx);
let mut pause_menu = JoypadMenu::new(&GAME_MENU_OPTIONS, pause_menu_header.as_str(), pause_menu_renderer);
loop{
Expand All @@ -64,45 +76,80 @@ pub extern "C" fn main()->!{
match pause_menu.get_menu_selection(&mut pause_menu_joypad_provider){
EmulatorMenuOption::Resume => {},
EmulatorMenuOption::Restart => {
log::info!("Reseting system");
delay::wait_ms(100);
power_manager.reset(magenboy_rpi::peripherals::ResetMode::Partition0)
log::info!("Resetting system");
reset_system(mbc, fs, power_manager, ResetMode::Partition0, selected_rom);
}
EmulatorMenuOption::Shutdown => {
log::info!("Shuting down system");
delay::wait_ms(100);
power_manager.reset(magenboy_rpi::peripherals::ResetMode::Halt)
reset_system(mbc, fs, power_manager, ResetMode::Halt, selected_rom);
}
}
}
gameboy.cycle_frame();
}
}

fn read_menu_options(fs: &mut Fat32, menu_options: &mut [MenuOption<FileEntry, ArrayString<{FileEntry::FILENAME_SIZE}>>; 255]) -> usize {
fn reset_system<'a>(mbc: &'a mut dyn Mbc, mut fs: Fat32Fs, mut power_manager: Power, mode: ResetMode, selected_rom: &FileEntry)->!{
let filename = get_save_filename(selected_rom);
fs.write_file(filename.as_str(), mbc.get_ram());

// delaying the reset operation so other low level tasks will have enough time to finish (like uart transmission)
delay::wait_ms(100);
power_manager.reset(mode);
}

fn try_read_save_file(selected_rom: &FileEntry, mut fs: &mut Fat32Fs) -> Option<&'static [u8]> {
let save_filename = get_save_filename(selected_rom);
let file = search_file(&mut fs, save_filename.as_str())?;

// SAFETY: The only reference to this static mut var
let ram = unsafe{&mut RAM_BUFFER[0..file.size as usize]};
fs.read_file(&file, ram);
log::info!("Found save file for selected rom: {}", file.get_name());
return Some(ram);
}

fn get_save_filename(selected_rom: &FileEntry) -> ArrayString<11> {
ArrayString::try_from(format_args!("{}SAV",&selected_rom.get_name()[..8])).unwrap()
}

fn read_menu_options(fs: &mut Fat32Fs, menu_options: &mut [MenuOption<FileEntry, ArrayString<{FileEntry::FILENAME_SIZE}>>; 255]) -> usize {
const FILES_PER_LIST:usize = 20;

let mut menu_options_size = 0;
let mut root_dir_offset = 0;
const FILES_PER_LIST:usize = 20;
'search_dir_loop: loop{
loop{
let dir_entries = fs.root_dir_list::<FILES_PER_LIST>(root_dir_offset);
for entry in dir_entries{
let Some(entry) = entry else {break 'search_dir_loop};
for entry in &dir_entries{
let extension = entry.get_extension();
if extension.eq_ignore_ascii_case("gb") || extension.eq_ignore_ascii_case("gbc"){
menu_options[menu_options_size] = MenuOption{ value: entry.clone(), prompt: ArrayString::from(entry.get_name()).unwrap() };
menu_options_size += 1;
log::debug!("Detected ROM: {}", entry.get_name());
}
}
// The fact that its not completely full indicates that there are no more unread entries left
if dir_entries.remaining_capacity() != 0{
break;
}
root_dir_offset += FILES_PER_LIST;
}
return menu_options_size;
}

#[panic_handler]
fn panic(info:&PanicInfo)->!{
log::error!("An error has occoured!");
log::error!("{}", info);

unsafe{boot::hang_led()};
fn search_file(fs:&mut Fat32Fs, filename: &str)->Option<FileEntry>{
let mut root_dir_offset = 0;
const FILES_PER_LIST:usize = 20;
loop{
let dir_entries = fs.root_dir_list::<FILES_PER_LIST>(root_dir_offset);
for entry in &dir_entries{
if entry.get_name() == filename{
return Some(entry.clone());
}
}
if dir_entries.remaining_capacity() != 0{
return None;
}
root_dir_offset += FILES_PER_LIST;
}
}
Loading

0 comments on commit 95e436c

Please sign in to comment.