1#![allow(
14 clippy::doc_markdown,
15 reason = "save-backend prose names FlashRAM/SRAM/EEPROM/DMA/CIR constantly"
16)]
17
18use alloc::boxed::Box;
19use alloc::vec;
20
21use serde::{Deserialize, Serialize};
22
23use crate::SaveType;
24
25pub const SAVE_PI_BASE: u32 = 0x0800_0000;
27pub const FLASH_CIR: u32 = 0x0801_0000;
29
30const FLASH_SIZE: usize = 128 * 1024;
32const FLASH_PAGE: usize = 128;
33const FLASH_SECTOR_PAGES: usize = 128;
34const FLASH_SECTOR: usize = FLASH_SECTOR_PAGES * FLASH_PAGE;
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
38enum FlashMode {
39 #[default]
41 ReadArray,
42 Status,
44 SiliconId,
46 LoadPage,
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct FlashRam {
53 array: Box<[u8]>,
54 #[serde(with = "serde_big_array::BigArray")]
55 page_buffer: [u8; FLASH_PAGE],
56 mode: FlashMode,
57 erase_sector: Option<usize>,
60 chip_erase: bool,
61 status: u8,
63}
64
65const FLASH_STATUS_ERASE_OK: u8 = 0x08;
69const FLASH_STATUS_PROGRAM_OK: u8 = 0x04;
70
71impl FlashRam {
72 fn new() -> Self {
73 Self {
74 array: vec![0xFF; FLASH_SIZE].into_boxed_slice(),
76 page_buffer: [0xFF; FLASH_PAGE],
77 mode: FlashMode::default(),
78 erase_sector: None,
79 chip_erase: false,
80 status: 0,
81 }
82 }
83
84 fn write_cir(&mut self, cmd: u32) {
86 match cmd >> 24 {
87 0xF0 => self.mode = FlashMode::ReadArray,
88 0xE1 => self.mode = FlashMode::SiliconId,
89 0xB4 => {
90 self.mode = FlashMode::LoadPage;
91 self.page_buffer = [0xFF; FLASH_PAGE];
92 }
93 0x3C => {
94 self.chip_erase = true;
95 self.erase_sector = None;
96 }
97 0x4B => {
98 self.chip_erase = false;
102 let page = (cmd & 0xFFFF) as usize;
103 let sector = (page / FLASH_SECTOR_PAGES).min(FLASH_SIZE / FLASH_SECTOR - 1);
104 self.erase_sector = Some(sector * FLASH_SECTOR);
105 }
106 0x78 => {
107 if self.chip_erase {
109 self.array.fill(0xFF);
110 } else if let Some(base) = self.erase_sector {
111 let end = (base + FLASH_SECTOR).min(self.array.len());
112 self.array[base..end].fill(0xFF);
113 }
114 self.chip_erase = false;
115 self.erase_sector = None;
116 self.status = FLASH_STATUS_ERASE_OK;
117 self.mode = FlashMode::Status;
118 }
119 0xA5 => {
120 let page = (cmd & 0xFFFF) as usize;
122 let base = page * FLASH_PAGE;
123 if base + FLASH_PAGE <= self.array.len() {
124 for (a, &b) in self.array[base..base + FLASH_PAGE]
127 .iter_mut()
128 .zip(self.page_buffer.iter())
129 {
130 *a &= b;
131 }
132 }
133 self.status = FLASH_STATUS_PROGRAM_OK;
134 self.mode = FlashMode::Status;
135 }
136 _ => {}
137 }
138 }
139
140 fn read(&self, off: usize) -> u8 {
142 match self.mode {
143 FlashMode::ReadArray => self.array.get(off).copied().unwrap_or(0xFF),
144 FlashMode::Status => {
145 if off & 1 == 1 { self.status } else { 0 }
149 }
150 FlashMode::SiliconId => [0x11, 0x11, 0x80, 0x00, 0xC2, 0x00, 0x1D, 0x00]
153 .get(off & 7)
154 .copied()
155 .unwrap_or(0),
156 FlashMode::LoadPage => self
157 .page_buffer
158 .get(off & (FLASH_PAGE - 1))
159 .copied()
160 .unwrap_or(0xFF),
161 }
162 }
163
164 fn write(&mut self, off: usize, val: u8) {
167 if self.mode == FlashMode::LoadPage {
168 self.page_buffer[off & (FLASH_PAGE - 1)] = val;
169 }
170 }
171}
172
173#[derive(Clone, Debug, Default, Serialize, Deserialize)]
175pub enum SaveDevice {
176 #[default]
178 None,
179 Eeprom(Box<[u8]>),
181 Sram(Box<[u8]>),
183 Flash(FlashRam),
185 ControllerPak(Box<[u8]>),
187}
188
189impl SaveDevice {
190 #[must_use]
192 pub fn new(save_type: SaveType) -> Self {
193 let flat = |n: usize| vec![0u8; n].into_boxed_slice();
194 match save_type {
195 SaveType::None => Self::None,
196 SaveType::Eeprom4k => Self::Eeprom(flat(512)),
197 SaveType::Eeprom16k => Self::Eeprom(flat(2 * 1024)),
198 SaveType::Sram => Self::Sram(flat(32 * 1024)),
199 SaveType::FlashRam => Self::Flash(FlashRam::new()),
200 SaveType::ControllerPak => Self::ControllerPak(flat(32 * 1024)),
201 }
202 }
203
204 #[must_use]
207 pub fn pi_read(&self, addr: u32) -> Option<u8> {
208 let off = addr.wrapping_sub(SAVE_PI_BASE) as usize;
209 match self {
210 Self::Sram(store) => Some(store.get(off).copied().unwrap_or(0)),
211 Self::Flash(flash) => Some(flash.read(off)),
212 _ => None,
213 }
214 }
215
216 pub fn pi_write(&mut self, addr: u32, val: u8) {
219 match self {
220 Self::Sram(store) => {
221 let off = addr.wrapping_sub(SAVE_PI_BASE) as usize;
222 if let Some(b) = store.get_mut(off) {
223 *b = val;
224 }
225 }
226 Self::Flash(flash) => flash.write(addr.wrapping_sub(SAVE_PI_BASE) as usize, val),
227 _ => {}
228 }
229 }
230
231 pub fn flash_cir(&mut self, cmd: u32) {
233 if let Self::Flash(flash) = self {
234 flash.write_cir(cmd);
235 }
236 }
237
238 pub fn eeprom_read_block(&self, block: u8, out: &mut [u8; 8]) {
240 if let Self::Eeprom(store) = self {
241 let base = block as usize * 8;
242 for (i, o) in out.iter_mut().enumerate() {
243 *o = store.get(base + i).copied().unwrap_or(0);
244 }
245 }
246 }
247
248 pub fn eeprom_write_block(&mut self, block: u8, data: &[u8; 8]) {
250 if let Self::Eeprom(store) = self {
251 let base = block as usize * 8;
252 for (i, &d) in data.iter().enumerate() {
253 if let Some(b) = store.get_mut(base + i) {
254 *b = d;
255 }
256 }
257 }
258 }
259
260 pub fn cpak_read(&self, addr: u16, out: &mut [u8; 32]) {
262 if let Self::ControllerPak(store) = self {
263 let base = addr as usize;
264 for (i, o) in out.iter_mut().enumerate() {
265 *o = store.get(base + i).copied().unwrap_or(0);
266 }
267 }
268 }
269
270 pub fn cpak_write(&mut self, addr: u16, data: &[u8; 32]) {
272 if let Self::ControllerPak(store) = self {
273 let base = addr as usize;
274 for (i, &d) in data.iter().enumerate() {
275 if let Some(b) = store.get_mut(base + i) {
276 *b = d;
277 }
278 }
279 }
280 }
281
282 #[must_use]
285 pub fn backing(&self) -> &[u8] {
286 match self {
287 Self::None => &[],
288 Self::Eeprom(s) | Self::Sram(s) | Self::ControllerPak(s) => s,
289 Self::Flash(f) => &f.array,
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn sram_round_trips() {
300 let mut d = SaveDevice::new(SaveType::Sram);
301 d.pi_write(SAVE_PI_BASE + 0x1234, 0xAB);
302 d.pi_write(SAVE_PI_BASE + 0x1235, 0xCD);
303 assert_eq!(d.pi_read(SAVE_PI_BASE + 0x1234), Some(0xAB));
304 assert_eq!(d.pi_read(SAVE_PI_BASE + 0x1235), Some(0xCD));
305 assert_eq!(d.backing()[0x1234], 0xAB);
306 }
307
308 #[test]
309 fn eeprom_block_round_trips() {
310 let mut d = SaveDevice::new(SaveType::Eeprom4k);
311 let data = [1, 2, 3, 4, 5, 6, 7, 8];
312 d.eeprom_write_block(3, &data);
313 let mut out = [0u8; 8];
314 d.eeprom_read_block(3, &mut out);
315 assert_eq!(out, data);
316 let mut other = [0xFFu8; 8];
318 d.eeprom_read_block(4, &mut other);
319 assert_eq!(other, [0; 8]);
320 }
321
322 #[test]
323 fn controller_pak_block_round_trips() {
324 let mut d = SaveDevice::new(SaveType::ControllerPak);
325 let data = [0x5A; 32];
326 d.cpak_write(0x0100, &data);
327 let mut out = [0u8; 32];
328 d.cpak_read(0x0100, &mut out);
329 assert_eq!(out, data);
330 }
331
332 #[test]
336 fn flashram_erase_load_program_round_trips() {
337 let mut d = SaveDevice::new(SaveType::FlashRam);
338 assert_eq!(d.pi_read(SAVE_PI_BASE), Some(0xFF));
340
341 d.flash_cir(0x4B00_0000);
343 d.flash_cir(0x7800_0000);
344 assert_eq!(d.pi_read(SAVE_PI_BASE + 3), Some(FLASH_STATUS_ERASE_OK));
346
347 d.flash_cir(0xB400_0000);
349 for i in 0..FLASH_PAGE {
350 d.pi_write(SAVE_PI_BASE + i as u32, i as u8);
351 }
352 d.flash_cir(0xA500_0000);
353 assert_eq!(d.pi_read(SAVE_PI_BASE + 3), Some(FLASH_STATUS_PROGRAM_OK));
354
355 d.flash_cir(0xF000_0000);
357 assert_eq!(d.pi_read(SAVE_PI_BASE), Some(0));
358 assert_eq!(d.pi_read(SAVE_PI_BASE + 5), Some(5));
359 assert_eq!(d.backing()[7], 7);
360 }
361
362 #[test]
365 fn flashram_program_only_clears_bits() {
366 let mut d = SaveDevice::new(SaveType::FlashRam);
367 d.flash_cir(0xB400_0000);
368 d.pi_write(SAVE_PI_BASE, 0x0F); d.flash_cir(0xA500_0000);
370 d.flash_cir(0xF000_0000);
371 assert_eq!(d.pi_read(SAVE_PI_BASE), Some(0x0F));
372 d.flash_cir(0xB400_0000);
374 d.pi_write(SAVE_PI_BASE, 0xF0);
375 d.flash_cir(0xA500_0000);
376 d.flash_cir(0xF000_0000);
377 assert_eq!(
378 d.pi_read(SAVE_PI_BASE),
379 Some(0x00),
380 "program only clears bits"
381 );
382 }
383}