From 40ced658f33e08f0559b8e6752b336d1807d738f Mon Sep 17 00:00:00 2001 From: Alissa Rao Date: Sat, 27 Feb 2021 09:05:39 -0800 Subject: [PATCH] Properly finish documentation for the `save` module. (#119) --- src/save.rs | 93 ++++++++++++++++++++++++++++++++++++++++++---- src/save/eeprom.rs | 14 ++++++- src/save/flash.rs | 5 ++- src/save/sram.rs | 7 +++- 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src/save.rs b/src/save.rs index ab83ea0..b45a3a2 100644 --- a/src/save.rs +++ b/src/save.rs @@ -48,7 +48,69 @@ //! //! ## 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 core::ops::Range; @@ -115,6 +177,10 @@ pub struct MediaInfo { pub sector_shift: usize, /// The size of the save media, in sectors. 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. @@ -210,6 +276,11 @@ impl SaveAccess { 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. /// /// 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 /// [`align_range`](`SaveAccess::align_range`) function. pub fn prepare_write(&self, range: Range) -> Result<(), Error> { - let range = self.align_range(range); - let shift = self.info.sector_shift; - self.access.prepare_write(range.start >> shift, range.len() >> shift) + if self.info.requires_prepare_write { + let range = self.align_range(range); + 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. /// - /// You must call [`prepare_write`](`SaveAccess::prepare_write`) on the range - /// you intend to write for this to function correctly. + /// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns + /// `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> { self.access.write(offset, buffer) } /// Writes and validates a given buffer into the save media. /// - /// You must call [`prepare_write`](`SaveAccess::prepare_write`) on the range - /// you intend to write for this to function correctly. + /// If [`requires_prepare_write`](`SaveAccess::requires_prepare_write`) returns + /// `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 /// return an error if it has not done so. diff --git a/src/save/eeprom.rs b/src/save/eeprom.rs index e1022bb..9fa0c7c 100644 --- a/src/save/eeprom.rs +++ b/src/save/eeprom.rs @@ -262,7 +262,12 @@ const PROPS_8K: EepromProperties = EepromProperties { addr_bits: 14, byte_len: 8 pub struct Eeprom512B; impl RawSaveAccess for Eeprom512B { 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> { PROPS_512B.read(offset, buffer) @@ -282,7 +287,12 @@ impl RawSaveAccess for Eeprom512B { pub struct Eeprom8K; impl RawSaveAccess for Eeprom8K { 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> { PROPS_8K.read(offset, buffer) diff --git a/src/save/flash.rs b/src/save/flash.rs index a1cc624..fbff0b9 100644 --- a/src/save/flash.rs +++ b/src/save/flash.rs @@ -155,16 +155,19 @@ static INFO_64K: MediaInfo = MediaInfo { media_type: MediaType::Flash64K, sector_shift: 12, // 4 KiB sector_count: 16, // 4 KiB * 16 = 64 KiB + requires_prepare_write: true, }; static INFO_64K_ATMEL: MediaInfo = MediaInfo { media_type: MediaType::Flash64K, 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 { media_type: MediaType::Flash128K, sector_shift: 12, sector_count: 32, // 4 KiB * 32 = 128 KiB + requires_prepare_write: true, }; // Chip info for the various chipsets. diff --git a/src/save/sram.rs b/src/save/sram.rs index c7f05cf..954c112 100644 --- a/src/save/sram.rs +++ b/src/save/sram.rs @@ -20,7 +20,12 @@ fn check_bounds(offset: usize, len: usize) -> Result<(), Error> { 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 }) + 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> {