Skip to main content

gb_pak/
sram.rs

1//! The cartridge's own RAM, usually kept alive by a battery.
2//!
3//! SRAM is only readable and writable while the controller has it switched on, so
4//! every access happens inside [`Sram::open`], which switches it on for the
5//! closure and off again after. Leaving it off otherwise protects the contents
6//! while the console powers down or the cartridge is pulled.
7//!
8//! # Declaring
9//!
10//! ```ignore
11//! use core::cell::Cell;
12//! use gb_pak::{CriticalSection, sram};
13//! use zerocopy::FromBytes;
14//!
15//! #[repr(C)]
16//! #[derive(FromBytes)]
17//! struct Save {
18//!     magic:   Cell<[u8; 4]>,
19//!     version: Cell<u8>,
20//!     hp:      Cell<u8>,
21//!     gold:    Cell<u16>,
22//! }
23//!
24//! /// The save file.
25//! #[sram(0)]
26//! static FILE: Save;
27//!
28//! fn hurt(cs: CriticalSection<'_>) {
29//!     FILE.open(cs, |f| f.hp.set(f.hp.get().saturating_sub(1)));
30//! }
31//! ```
32//!
33//! A static has no initializer: the bytes are the cartridge's already. A bank
34//! holds one of them, at its start (`0xA000`), and the attribute says which bank.
35//!
36//! Turned down at compile time: a missing bank number, a bank the cartridge does
37//! not have, any bank when it has no SRAM, a value larger than one 8 KiB bank,
38//! and an initializer.
39//!
40//! # Editing in place
41//!
42//! The closure is handed a shared reference, so every field is written as a
43//! [`Cell`](core::cell::Cell), the way a peripheral crate writes a register
44//! block. A field that is not one can never be written.
45//!
46//! ```ignore
47//! unsafe { gb::interrupt::free(|cs| FILE.open(cs, |f| f.gold.set(f.gold.get() + 10))) };
48//! ```
49//!
50//! # Trusting the contents
51//!
52//! A cartridge that has never been written holds noise, so the value is bounded
53//! on [`FromBytes`]: no byte pattern may be invalid for it, which rules out
54//! `bool`, `char`, enums, and references.
55//!
56//! `repr(C)` is recommended: a `repr(Rust)` layout is unspecified and may differ
57//! between toolchain versions, so a save an earlier build wrote could be read
58//! back wrong.
59//!
60//! That bounds the type, not the contents. The bytes are still whatever survived:
61//! noise on a new cartridge, decay on a failing battery, another build's layout
62//! after an update. Check a magic number, a schema version, and a checksum before
63//! believing a save.
64
65use zerocopy::FromBytes;
66
67use crate::{CriticalSection, WINDOW, WINDOW_LEN, reg};
68
69/// A value at the base of SRAM bank `BANK`.
70///
71/// Declared by [`#[sram]`](macro@crate::sram).
72pub struct Sram<T: FromBytes, const BANK: u8> {
73    _value: core::marker::PhantomData<T>,
74}
75
76// The handle holds no `T`; the bytes are the cartridge's and are reached only
77// inside `open`.
78unsafe impl<T: FromBytes, const BANK: u8> Sync for Sram<T, BANK> {}
79
80impl<T: FromBytes, const BANK: u8> Sram<T, BANK> {
81    /// # Safety
82    ///
83    /// The cartridge must have bank `BANK`. The attribute checks that against
84    /// `header.toml`, which is why this module is reachable on a cartridge with
85    /// no SRAM at all: the check belongs at the declaration, not at the module.
86    #[doc(hidden)]
87    pub const unsafe fn declare() -> Self {
88        Sram { _value: core::marker::PhantomData }
89    }
90
91    /// Switch the RAM on, run `f`, switch it off.
92    ///
93    /// The [`CriticalSection`] keeps an interrupt handler from reaching the same
94    /// bytes while `f` runs.
95    ///
96    /// The window holds one thing at a time, so `f` must not open another scope
97    /// or read another peripheral: the rest of `f` would look at whatever that
98    /// left selected.
99    #[inline]
100    pub fn open<R>(&self, _cs: CriticalSection<'_>, f: impl FnOnce(&T) -> R) -> R {
101        reg::select(BANK);
102        reg::enable();
103        let r = f(unsafe { &*(WINDOW as *const T) });
104        reg::disable();
105        r
106    }
107}
108
109/// Bytes one bank holds.
110pub const BANK_LEN: usize = WINDOW_LEN;
111
112/// SRAM banks this cartridge has, from `ram_size` in `header.toml`.
113pub const BANKS: u8 = reg::BANKS;