Reintegrate SRAM save media reader.

This commit is contained in:
Alissa Rao 2022-08-17 00:42:34 -07:00
parent d50413a3cc
commit ec41db2fc9
No known key found for this signature in database
GPG key ID: 9314D8F6745E881E
4 changed files with 302 additions and 195 deletions

View file

@ -224,6 +224,8 @@ pub struct Gba {
pub sound: sound::dmg::Sound,
/// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files.
pub mixer: sound::mixer::MixerController,
/// Manages access to the Game Boy Advance cartridge's save chip.
pub save: save::SaveManager,
/// Manages access to the Game Boy Advance's 4 timers.
pub timers: timer::TimerController,
}
@ -240,6 +242,7 @@ impl Gba {
display: display::Display::new(),
sound: sound::dmg::Sound::new(),
mixer: sound::mixer::MixerController::new(),
save: save::SaveManager::new(),
timers: timer::TimerController::new(),
}
}

View file

@ -107,18 +107,14 @@ use crate::sync::{Mutex, RawMutexGuard};
use crate::timer::Timer;
mod asm_utils;
//mod setup;
mod utils;
//pub use asm_utils::*;
//pub use setup::*;
//pub mod eeprom;
//pub mod flash;
//pub mod sram;
mod sram;
mod utils;
/// A list of save media types.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
#[non_exhaustive]
pub enum MediaType {
/// 32KiB Battery-Backed SRAM or FRAM
Sram32K,
@ -130,8 +126,6 @@ pub enum MediaType {
Flash64K,
/// 128KiB flash chip
Flash128K,
/// A user-defined save media type
Custom,
}
/// The type used for errors encountered while reading or writing save media.
@ -194,7 +188,6 @@ fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
}
/// Allows reading and writing of save media.
#[derive(Copy, Clone)]
pub struct SaveData {
lock: RawMutexGuard<'static>,
access: &'static dyn RawSaveAccess,
@ -332,12 +325,67 @@ impl<'a> SavePreparedBlock<'a> {
}
}
mod marker {
#[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)]
pub fn emit_eeprom_marker() {
crate::sync::memory_read_hint(&EEPROM);
}
#[inline(always)]
pub fn emit_sram_marker() {
crate::sync::memory_read_hint(&SRAM);
}
#[inline(always)]
pub fn emit_flash_512k_marker() {
crate::sync::memory_read_hint(&FLASH512K);
}
#[inline(always)]
pub fn emit_flash_1m_marker() {
crate::sync::memory_read_hint(&FLASH1M);
}
}
/// Allows access to the cartridge's save data.
pub struct SaveManager;
#[non_exhaustive]
pub struct SaveManager {}
impl SaveManager {
pub(crate) const fn new() -> Self {
SaveManager {}
}
/// Declares that the ROM uses battery backed SRAM/FRAM.
///
/// Battery Backed SRAM is generally very fast, but limited in size compared
/// to flash chips.
///
/// This creates a marker in the ROM that allows emulators to understand what
/// save type the Game Pak uses, and configures the save manager to use the
/// given save type.
///
/// Only one `init_*` function may be called in the lifetime of the program.
pub fn init_sram() {
marker::emit_sram_marker();
set_save_implementation(&sram::BatteryBackedAccess);
}
/// Creates a new accessor to the save data.
///
/// You must have initialized the save manager beforehand to use a specific
/// type of media before calling this method.
pub fn access() -> Result<SaveData, Error> {
SaveData::new(None)
}
/// Creates a new accessor to the save data that uses the given timer for timeouts.
///
/// You must have initialized the save manager beforehand to use a specific
/// type of media before calling this method.
pub fn access_with_timer(timer: Timer) -> Result<SaveData, Error> {
SaveData::new(Some(timer))
}

56
agb/src/save/sram.rs Normal file
View file

@ -0,0 +1,56 @@
//! 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 crate::save::{Error, MediaInfo, MediaType, RawSaveAccess};
use crate::save::asm_utils::*;
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,
uses_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(())
}
}