mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
Properly finish documentation for the save
module. (#119)
This commit is contained in:
parent
a1e1b4d320
commit
40ced658f3
93
src/save.rs
93
src/save.rs
|
@ -48,7 +48,69 @@
|
||||||
//!
|
//!
|
||||||
//! ## Using save media
|
//! ## 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 crate::sync::Static;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
@ -115,6 +177,10 @@ pub struct MediaInfo {
|
||||||
pub sector_shift: usize,
|
pub sector_shift: usize,
|
||||||
/// The size of the save media, in sectors.
|
/// The size of the save media, in sectors.
|
||||||
pub sector_count: usize,
|
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.
|
/// A trait allowing low-level saving and writing to save media.
|
||||||
|
@ -210,6 +276,11 @@ impl SaveAccess {
|
||||||
self.access.verify(offset, buffer)
|
self.access.verify(offset, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether this save media requires the use of [`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.
|
/// 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
|
/// This can be used to calculate which blocks would be erased by a call
|
||||||
|
@ -226,23 +297,31 @@ impl SaveAccess {
|
||||||
/// calculate which offset ranges would be affected, use the
|
/// calculate which offset ranges would be affected, use the
|
||||||
/// [`align_range`](`SaveAccess::align_range`) function.
|
/// [`align_range`](`SaveAccess::align_range`) function.
|
||||||
pub fn prepare_write(&self, range: Range<usize>) -> Result<(), Error> {
|
pub fn prepare_write(&self, range: Range<usize>) -> Result<(), Error> {
|
||||||
let range = self.align_range(range);
|
if self.info.requires_prepare_write {
|
||||||
let shift = self.info.sector_shift;
|
let range = self.align_range(range);
|
||||||
self.access.prepare_write(range.start >> shift, range.len() >> shift)
|
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.
|
/// Writes a given buffer into the save media.
|
||||||
///
|
///
|
||||||
/// You must call [`prepare_write`](`SaveAccess::prepare_write`) on the range
|
/// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns
|
||||||
/// you intend to write for this to function correctly.
|
/// `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> {
|
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||||
self.access.write(offset, buffer)
|
self.access.write(offset, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes and validates a given buffer into the save media.
|
/// Writes and validates a given buffer into the save media.
|
||||||
///
|
///
|
||||||
/// You must call [`prepare_write`](`SaveAccess::prepare_write`) on the range
|
/// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns
|
||||||
/// you intend to write for this to function correctly.
|
/// `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
|
/// This function will verify that the write has completed successfully, and
|
||||||
/// return an error if it has not done so.
|
/// return an error if it has not done so.
|
||||||
|
|
|
@ -262,7 +262,12 @@ const PROPS_8K: EepromProperties = EepromProperties { addr_bits: 14, byte_len: 8
|
||||||
pub struct Eeprom512B;
|
pub struct Eeprom512B;
|
||||||
impl RawSaveAccess for Eeprom512B {
|
impl RawSaveAccess for Eeprom512B {
|
||||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||||
Ok(&MediaInfo { media_type: MediaType::Eeprom512B, sector_shift: 3, sector_count: 64 })
|
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> {
|
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
PROPS_512B.read(offset, buffer)
|
PROPS_512B.read(offset, buffer)
|
||||||
|
@ -282,7 +287,12 @@ impl RawSaveAccess for Eeprom512B {
|
||||||
pub struct Eeprom8K;
|
pub struct Eeprom8K;
|
||||||
impl RawSaveAccess for Eeprom8K {
|
impl RawSaveAccess for Eeprom8K {
|
||||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||||
Ok(&MediaInfo { media_type: MediaType::Eeprom8K, sector_shift: 3, sector_count: 1024 })
|
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> {
|
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
PROPS_8K.read(offset, buffer)
|
PROPS_8K.read(offset, buffer)
|
||||||
|
|
|
@ -155,16 +155,19 @@ static INFO_64K: MediaInfo = MediaInfo {
|
||||||
media_type: MediaType::Flash64K,
|
media_type: MediaType::Flash64K,
|
||||||
sector_shift: 12, // 4 KiB
|
sector_shift: 12, // 4 KiB
|
||||||
sector_count: 16, // 4 KiB * 16 = 64 KiB
|
sector_count: 16, // 4 KiB * 16 = 64 KiB
|
||||||
|
requires_prepare_write: true,
|
||||||
};
|
};
|
||||||
static INFO_64K_ATMEL: MediaInfo = MediaInfo {
|
static INFO_64K_ATMEL: MediaInfo = MediaInfo {
|
||||||
media_type: MediaType::Flash64K,
|
media_type: MediaType::Flash64K,
|
||||||
sector_shift: 7, // 128 bytes
|
sector_shift: 7, // 128 bytes
|
||||||
sector_count: 512, // 128 bytes * 512 = 64 KIB
|
sector_count: 512, // 128 bytes * 512 = 64 KiB
|
||||||
|
requires_prepare_write: false,
|
||||||
};
|
};
|
||||||
static INFO_128K: MediaInfo = MediaInfo {
|
static INFO_128K: MediaInfo = MediaInfo {
|
||||||
media_type: MediaType::Flash128K,
|
media_type: MediaType::Flash128K,
|
||||||
sector_shift: 12,
|
sector_shift: 12,
|
||||||
sector_count: 32, // 4 KiB * 32 = 128 KiB
|
sector_count: 32, // 4 KiB * 32 = 128 KiB
|
||||||
|
requires_prepare_write: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Chip info for the various chipsets.
|
// Chip info for the various chipsets.
|
||||||
|
|
|
@ -20,7 +20,12 @@ fn check_bounds(offset: usize, len: usize) -> Result<(), Error> {
|
||||||
pub struct BatteryBackedAccess;
|
pub struct BatteryBackedAccess;
|
||||||
impl RawSaveAccess for BatteryBackedAccess {
|
impl RawSaveAccess for BatteryBackedAccess {
|
||||||
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||||
Ok(&MediaInfo { media_type: MediaType::Sram32K, sector_shift: 0, sector_count: SRAM_SIZE })
|
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> {
|
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
|
Loading…
Reference in a new issue