Skip to content

Commit

Permalink
Add reading roms from the SD card for rpi baremetal
Browse files Browse the repository at this point in the history
* Add stack strings to core
* Port the rom menu to the baremetal target
  • Loading branch information
alloncm committed Aug 19, 2023
1 parent ed5bb7f commit a8ed722
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 40 deletions.
27 changes: 26 additions & 1 deletion common/src/emulation_menu.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::sync::{atomic::AtomicBool, Mutex};
use std::{sync::{atomic::AtomicBool, Mutex}, path::PathBuf};
use magenboy_core::{ppu::gfx_device::GfxDevice, keypad::joypad_provider::JoypadProvider};

use crate::joypad_menu::MenuRenderer;

use super::joypad_menu::{MenuOption, MenuJoypadProvider, joypad_gfx_menu, JoypadMenu};

enum EmulatorMenuOption{
Expand Down Expand Up @@ -70,4 +72,27 @@ impl<JP:JoypadProvider + MenuJoypadProvider> MagenBoyMenu<JP> {
}
}

}

pub fn get_rom_selection<MR:MenuRenderer<PathBuf, String>, JP:MenuJoypadProvider + JoypadProvider>(roms_path:&str, menu_renderer:MR, jp:&mut JP)->String{
let mut menu_options = Vec::new();
let dir_entries = std::fs::read_dir(roms_path).expect(std::format!("Error openning the roms directory: {}",roms_path).as_str());
for entry in dir_entries{
let entry = entry.unwrap();
let path = entry.path();
if let Some(extension) = path.as_path().extension().and_then(std::ffi::OsStr::to_str){
match extension {
"gb" | "gbc"=>{
let filename = String::from(path.file_name().expect("Error should be a file").to_str().unwrap());
let option = MenuOption{value: path, prompt: filename};
menu_options.push(option);
},
_=>{}
}
}
}
let mut menu = JoypadMenu::new(&menu_options, String::from("Choose ROM"), menu_renderer);
let result = menu.get_menu_selection(jp);

return String::from(result.to_str().unwrap());
}
2 changes: 1 addition & 1 deletion common/src/joypad_menu/joypad_gfx_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl<'a, GFX: GfxDevice, T, S:AsRef<str>> MenuRenderer<T, S> for GfxDeviceMenuRe
// Calculate the range of the visible menu
let mut start_index = 0;
let screen_max_options = (SCREEN_HEIGHT / GLYPH_HEIGHT) - 1; // -1 for the header
let mut end_index = std::cmp::min(menu.len(), screen_max_options);
let mut end_index = core::cmp::min(menu.len(), screen_max_options);
if selection >= end_index{
end_index = selection + 1;
start_index = end_index - screen_max_options;
Expand Down
30 changes: 3 additions & 27 deletions common/src/joypad_menu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
mod font;
pub mod joypad_gfx_menu;

use std::path::PathBuf;

use magenboy_core::keypad::{button::Button, joypad::Joypad, joypad_provider::JoypadProvider};

#[derive(Default, Clone, Copy)]
pub struct MenuOption<T, S:AsRef<str>>{
pub prompt:S,
pub value:T
pub value:T,
pub prompt:S
}

pub trait MenuRenderer<T, S:AsRef<str>>{
Expand Down Expand Up @@ -63,27 +62,4 @@ impl<'a, T, S: AsRef<str>, MR:MenuRenderer<T, S>> JoypadMenu<'a, T, S, MR>{
}
return &self.options[self.selection].value;
}
}

pub fn get_rom_selection<MR:MenuRenderer<PathBuf, String>, JP:MenuJoypadProvider + JoypadProvider>(roms_path:&str, menu_renderer:MR, jp:&mut JP)->String{
let mut menu_options = Vec::new();
let dir_entries = std::fs::read_dir(roms_path).expect(std::format!("Error openning the roms directory: {}",roms_path).as_str());
for entry in dir_entries{
let entry = entry.unwrap();
let path = entry.path();
if let Some(extension) = path.as_path().extension().and_then(std::ffi::OsStr::to_str){
match extension {
"gb" | "gbc"=>{
let filename = String::from(path.file_name().expect("Error should be a file").to_str().unwrap());
let option = MenuOption{value: path, prompt: filename};
menu_options.push(option);
},
_=>{}
}
}
}
let mut menu = JoypadMenu::new(&menu_options, String::from("Choose ROM"), menu_renderer);
let result = menu.get_menu_selection(jp);

return String::from(result.to_str().unwrap());
}
2 changes: 1 addition & 1 deletion common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

cfg_if::cfg_if!{ if #[cfg(feature = "std")] {
pub mod emulation_menu;
pub mod joypad_menu;
pub mod mbc_handler;
pub mod mpmc_gfx_device;
pub mod logging;
}}

pub mod joypad_menu;
pub mod interpolation;

pub const VERSION:&str = env!("CARGO_PKG_VERSION");
1 change: 1 addition & 0 deletions core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod bit_masks;
pub mod fixed_size_queue;
pub mod static_allocator;
pub mod global_static_alloctor;
pub mod stack_string;

// Frequency in m_cycles (m_cycle = 4 t_cycles)
pub const GB_FREQUENCY:u32 = 4_194_304 / 4;
Expand Down
120 changes: 120 additions & 0 deletions core/src/utils/stack_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use core::{convert::From, fmt::Write};

#[derive(Clone, Copy)]
pub struct StackString<const MAX_SIZE:usize>{
data:[u8; MAX_SIZE],
size: usize
}

impl<const SIZE:usize> Default for StackString<SIZE>{
fn default() -> Self {
Self { data: [0;SIZE], size: 0 }
}
}

impl<const SIZE:usize> From<&str> for StackString<SIZE>{
fn from(value: &str) -> Self {
let bytes = value.as_bytes();
if bytes.len() > SIZE{
core::panic!("Data is too large for the string");
}
let mut str = Self::default();
str.append(bytes);
return str;
}
}

impl<const SIZE:usize> StackString<SIZE>{
pub fn append(&mut self, data_to_append:&[u8]){
if self.size + data_to_append.len() > SIZE{
core::panic!("Error!, trying to append to stack string with too much data");
}
self.data[self.size .. self.size + data_to_append.len()].copy_from_slice(data_to_append);
self.size += data_to_append.len();
}

pub fn as_str<'a>(&'a self)->&'a str{
return core::str::from_utf8(&self.data[0..self.size]).unwrap();
}
}

impl<const SIZE:usize> Write for StackString<SIZE>{
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let bytes = s.as_bytes();
if self.size + s.len() > SIZE{
return core::fmt::Result::Err(core::fmt::Error);
}
self.append(bytes);
return core::fmt::Result::Ok(());
}
}

impl<const SIZE:usize> AsRef<str> for StackString<SIZE>{
fn as_ref(&self) -> &str {self.as_str()}
}

#[cfg(test)]
mod tests{
use super::*;

#[test]
fn test_append(){
let mut str = StackString::<10>::default();
str.append(b"hello");
assert_eq!(str.as_str(), "hello");
}

#[test]
fn test_append_u8(){
let mut str = StackString::<10>::default();
str.append(&[b'h']);
assert_eq!(str.as_str(), "h");
}

#[test]
fn test_from(){
let str = "hello world";

let _: StackString<11> = StackString::from(str);
let _: StackString<12> = StackString::from(str);
let _: StackString<20> = StackString::from(str);
}

#[test]
#[should_panic]
fn test_from_panic(){
let str = "hello world";

let _: StackString<10> = StackString::from(str);
}

#[test]
fn test_as_str(){
let data = b"hello fucker";
let ss = StackString{ data:data.clone(), size: data.len()};

assert_eq!(ss.as_str(), "hello fucker");
}

#[test]
fn test_write_str(){
let bstr = b"hello";
let mut data = [0;20];
data[0..bstr.len()].copy_from_slice(bstr);
let mut ss: StackString<20> = StackString{ data, size: bstr.len()};

ss.write_str(" fucker").unwrap();
assert_eq!(&ss.data[0..ss.size], b"hello fucker")
}

#[test]
fn test_write_str_error(){
let bstr = b"hello";
let mut data = [0;20];
data[0..bstr.len()].copy_from_slice(bstr);
let mut ss: StackString<20> = StackString{ data, size: bstr.len()};

let res: Result<(), core::fmt::Error> = ss.write_str(" fucker djakdjaslkdjskl");
assert_eq!(res, Result::Err(core::fmt::Error));
}
}
2 changes: 1 addition & 1 deletion rpi/src/bin/baremetal/boot_armv7a.s
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ init_mmu_map_section:
.global hang_led
hang_led:
ldr r3, =PERIPHERALS_BASE_ADDRESS_PTR
ldr r0, [r3] // Deref the ptr to get the pperipherals address
ldr r0, [r3] // Deref the ptr to get the peripherals address
ldr r2, =0x200008 // GPFSEL2 MMIO register offset from base address
add r2, r2, r0 // add both to create GPFSEL2 address
mov r1, #1<<3 // Pin 21 as output mode
Expand Down
46 changes: 40 additions & 6 deletions rpi/src/bin/baremetal/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ mod logging;

use core::panic::PanicInfo;

use magenboy_core::{machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, mmu::external_memory_bus::Bootrom};
use magenboy_common::joypad_menu::{joypad_gfx_menu, JoypadMenu, MenuOption};
use magenboy_core::{machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, mmu::external_memory_bus::Bootrom, utils::stack_string::StackString};
use magenboy_rpi::{drivers::{GpioJoypadProvider, Ili9341GfxDevice, Fat32, FileEntry}, peripherals::PERIPHERALS, configuration::{display::*, joypad::button_to_bcm_pin, emulation::*}};

use magenboy_rpi::{drivers::{GpioJoypadProvider, Ili9341GfxDevice}, peripherals::PERIPHERALS, configuration::{display::*, joypad::button_to_bcm_pin, emulation::*}};
const MAX_ROM_SIZE:usize = 0x80_0000; // 8 MiB, Max size of MBC5 rom

// 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)
static mut ROM_BUFFER:[u8; MAX_ROM_SIZE] = [0;MAX_ROM_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 @@ -20,11 +26,39 @@ pub extern "C" fn main()->!{
log::info!("Initialized logger");
log::info!("running at exec mode: {:#X}", boot::get_cpu_execution_mode());

let mbc = initialize_mbc(ROM, None, None);
let joypad_provider = GpioJoypadProvider::new(button_to_bcm_pin);
let mut fs = Fat32::new();
let mut gfx = Ili9341GfxDevice::new(RESET_PIN_BCM, LED_PIN_BCM, TURBO, FRAME_LIMITER);
let mut joypad_provider = GpioJoypadProvider::new(button_to_bcm_pin);
log::info!("Initialize all drivers succesfully");

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

let mut menu_options:[MenuOption<FileEntry, StackString<{FileEntry::FILENAME_SIZE}>>; 255] = [Default::default(); 255];
let mut menu_options_size = 0;
let mut root_dir_offset = 0;
const FILES_PER_LIST:usize = 20;
'search_dir_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};
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: StackString::from(entry.get_name()) };
menu_options_size += 1;
log::debug!("Detected ROM: {}", entry.get_name());
}
}
root_dir_offset += FILES_PER_LIST;
}

let mut menu = JoypadMenu::new(&menu_options[0..menu_options_size], StackString::from("Choose ROM"), menu_renderer);
let selected_rom = menu.get_menu_selection(&mut joypad_provider);
log::info!("Selected ROM: {}", selected_rom.get_name());

let rom = unsafe{&mut ROM_BUFFER};
fs.read_file(selected_rom, rom);
let mbc = initialize_mbc(&rom[0..selected_rom.size as usize], None, None);

let gfx = Ili9341GfxDevice::new(RESET_PIN_BCM, LED_PIN_BCM, TURBO, FRAME_LIMITER);
log::info!("Init joypad");
let mut gameboy = GameBoy::new(mbc, joypad_provider, magenboy_rpi::BlankAudioDevice, gfx, Bootrom::None, None);
log::info!("Initialized gameboy!");
loop{
Expand Down
2 changes: 1 addition & 1 deletion rpi/src/bin/rpios/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{env, fs};

use magenboy_common::{emulation_menu::{MagenBoyMenu, MagenBoyState}, joypad_menu::*, mpmc_gfx_device::MpmcGfxDevice, mbc_handler::{initialize_mbc, release_mbc}};
use magenboy_common::{emulation_menu::*, joypad_menu::*, mpmc_gfx_device::MpmcGfxDevice, mbc_handler::{initialize_mbc, release_mbc}};
use magenboy_core::{ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_WIDTH, SCREEN_HEIGHT}, gfx_device::{GfxDevice, Pixel}}, mmu::{GBC_BOOT_ROM_SIZE, external_memory_bus::Bootrom, GB_BOOT_ROM_SIZE}, machine::{Mode, gameboy::GameBoy}, keypad::joypad_provider::JoypadProvider};
use log::info;
use magenboy_rpi::{configuration::{emulation::*, display::*, joypad::*}, drivers::*, peripherals::PERIPHERALS};
Expand Down
1 change: 0 additions & 1 deletion rpi/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ pub mod peripherals{
}

pub mod emulation{
pub const ROM:&'static [u8] = include_bytes!("../../Dependencies/TetrisDX.gbc"); // Path is relative to the build
pub const TURBO:u8 = 1; // Will speed up the emulation * X
pub const FRAME_LIMITER:u32 = 0; // Will filter every frame X frames
}
1 change: 0 additions & 1 deletion rpi/src/drivers/gpio_joypad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ impl JoypadProvider for GpioJoypadProvider{
}
}

#[cfg(feature = "os")]
impl magenboy_common::joypad_menu::MenuJoypadProvider for GpioJoypadProvider {
fn poll(&mut self, joypad:&mut Joypad) {
let gpio = unsafe{PERIPHERALS.get_gpio()};
Expand Down

0 comments on commit a8ed722

Please sign in to comment.