GBA flash ROM code

Comes in 64KB and 128KB variants; only difference is that 128KB supports a bank switch command

5use bincode::{Decode, Encode};
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
8enum Command {
9    EnterIdMode,  // 0x90
10    ExitIdMode,   // 0xF0
11    PrepareErase, // 0x80
12    EraseChip,    // 0x10
13    EraseSector,  // 0x30
14    WriteByte,    // 0xA0
15    SwitchBank,   // 0xB0
16}
17
18impl Command {
19    fn from_byte(byte: u8) -> Option<Self> {
20        match byte {
21            0x10 => Some(Self::EraseChip),
22            0x30 => Some(Self::EraseSector),
23            0x80 => Some(Self::PrepareErase),
24            0x90 => Some(Self::EnterIdMode),
25            0xA0 => Some(Self::WriteByte),
26            0xB0 => Some(Self::SwitchBank),
27            0xF0 => Some(Self::ExitIdMode),
28            _ => None,
29        }
30    }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
34enum State {
35    Ready,
36    ReceivingCommand1,      // Received $E005555 = 0xAA
37    ReceivingCommand2,      // Received $E002AAA = 0x55
38    EraseReady,             // Received erase command 0x80
39    EraseReceivingCommand1, // Received $E005555 = 0xAA
40    EraseReceivingCommand2, // Received $E002AAA = 0x55
41    WriteByteReady,         // Received write byte command 0xA0
42    BankSwitchReady,        // Received bank switch command 0xB0
43}
44
45#[derive(Debug, Clone, Encode, Decode)]
46pub struct FlashRom<const BANKED: bool> {
47    memory: Box<[u8]>,
48    state: State,
49    bank_offset: u32,
50    id_mode: bool,
51}
52
53pub type FlashRom64K = FlashRom<false>;
54pub type FlashRom128K = FlashRom<true>;

Arbitrary choices that are not Atmel or Macronix (which support custom commands)

57const PANASONIC_64K_ID: u16 = 0x1B32;
58const SANYO_128K_ID: u16 = 0x1362;
60impl<const BANKED: bool> FlashRom<BANKED> {
61    pub fn new(initial_save: Option<&Vec<u8>>) -> Self {
62        let len = if BANKED { 128 * 1024 } else { 64 * 1024 };
63        let mut memory = vec![0xFF; len].into_boxed_slice();
64
65        if let Some(initial_save) = initial_save
66            && initial_save.len() >= len
67        {
68            memory.copy_from_slice(&initial_save[..len]);
69        }
70
71        Self { memory, state: State::Ready, bank_offset: 0, id_mode: false }
72    }
73
74    pub fn read(&self, address: u32) -> u8 {
75        log::trace!("Flash ROM read {address:08X}, current state {:?}", self.state);
76
77        let address = address & 0xFFFF;
78
79        if self.id_mode && address <= 0x0001 {
80            let id = if BANKED { SANYO_128K_ID } else { PANASONIC_64K_ID };
81            return id.to_le_bytes()[address as usize];
82        }
83
84        self.memory[(self.bank_offset | address) as usize]
85    }
86
87    pub fn write(&mut self, address: u32, value: u8) {
88        log::trace!("Flash ROM write {address:08X} {value:02X}, current state {:?}", self.state);
89
90        let address = address & 0xFFFF;
91
92        self.state = match self.state {
93            State::Ready => {
94                if address == 0x5555 && value == 0xAA {
95                    State::ReceivingCommand1
96                } else {
97                    State::Ready
98                }
99            }
100            State::ReceivingCommand1 => {
101                if address == 0x2AAA && value == 0x55 {
102                    State::ReceivingCommand2
103                } else {
104                    State::Ready
105                }
106            }
107            State::ReceivingCommand2 => {
108                if address == 0x5555 {
109                    match Command::from_byte(value) {
110                        Some(Command::EnterIdMode) => {
111                            self.id_mode = true;
112                            State::Ready
113                        }
114                        Some(Command::ExitIdMode) => {
115                            self.id_mode = false;
116                            State::Ready
117                        }
118                        Some(Command::PrepareErase) => State::EraseReady,
119                        Some(Command::WriteByte) => State::WriteByteReady,
120                        Some(Command::SwitchBank) => {
121                            if BANKED {
122                                State::BankSwitchReady
123                            } else {
124                                State::Ready
125                            }
126                        }
127                        Some(Command::EraseChip | Command::EraseSector) | None => State::Ready,
128                    }
129                } else {
130                    State::Ready
131                }
132            }
133            State::EraseReady => {
134                if address == 0x5555 && value == 0xAA {
135                    State::EraseReceivingCommand1
136                } else {
137                    State::Ready
138                }
139            }
140            State::EraseReceivingCommand1 => {
141                if address == 0x2AAA && value == 0x55 {
142                    State::EraseReceivingCommand2
143                } else {
144                    State::Ready
145                }
146            }
147            State::EraseReceivingCommand2 => match Command::from_byte(value) {
148                Some(Command::EraseChip) if address == 0x5555 => {
149                    self.memory.fill(0xFF);
150                    State::Ready
151                }
152                Some(Command::EraseSector) => {
153                    let sector_addr = (self.bank_offset | (address & 0xF000)) as usize;
154                    self.memory[sector_addr..sector_addr + 0x1000].fill(0xFF);
155                    State::Ready
156                }
157                _ => State::Ready,
158            },
159            State::WriteByteReady => {
160                self.memory[(self.bank_offset | address) as usize] = value;
161                State::Ready
162            }
163            State::BankSwitchReady => {
164                self.bank_offset = u32::from(value & 1) << 16;
165                State::Ready
166            }
167        };
168
169        log::trace!("  New state {:?}", self.state);
170    }
171
172    pub fn memory(&self) -> &[u8] {
173        &self.memory
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn write_then_read() {
183        let mut flash_rom = FlashRom64K::new(None);
184
185        assert_eq!(0xFF, flash_rom.read(0xE001357));
186
187        flash_rom.write(0xE005555, 0xAA);
188        flash_rom.write(0xE002AAA, 0x55);
189        flash_rom.write(0xE005555, 0xA0);
190        flash_rom.write(0xE001357, 0x42);
191
192        assert_eq!(0x42, flash_rom.read(0xE001357));
193    }
194
195    #[test]
196    fn erase_chip() {
197        let mut flash_rom = FlashRom64K::new(None);
198        flash_rom.memory.fill(0x12);
199
200        flash_rom.write(0xE005555, 0xAA);
201        flash_rom.write(0xE002AAA, 0x55);
202        flash_rom.write(0xE005555, 0x80);
203        flash_rom.write(0xE005555, 0xAA);
204        flash_rom.write(0xE002AAA, 0x55);
205        flash_rom.write(0xE005555, 0x10);
206
207        for address in 0xE000000..=0xE00FFFF {
208            assert_eq!(0xFF, flash_rom.read(address));
209        }
210    }
211
212    #[test]
213    fn erase_sector() {
214        let mut flash_rom = FlashRom64K::new(None);
215        flash_rom.memory.fill(0x12);
216
217        flash_rom.write(0xE005555, 0xAA);
218        flash_rom.write(0xE002AAA, 0x55);
219        flash_rom.write(0xE005555, 0x80);
220        flash_rom.write(0xE005555, 0xAA);
221        flash_rom.write(0xE002AAA, 0x55);
222        flash_rom.write(0xE007000, 0x30);
223
224        for address in 0xE000000..=0xE006FFF {
225            assert_eq!(0x12, flash_rom.read(address));
226        }
227
228        for address in 0xE007000..=0xE007FFF {
229            assert_eq!(0xFF, flash_rom.read(address));
230        }
231
232        for address in 0xE008000..=0xE00FFFF {
233            assert_eq!(0x12, flash_rom.read(address));
234        }
235    }
236
237    #[test]
238    fn bank_switch() {
239        let mut flash_rom = FlashRom128K::new(None);
240
241        // Write 0x42 to $1357
242        flash_rom.write(0xE005555, 0xAA);
243        flash_rom.write(0xE002AAA, 0x55);
244        flash_rom.write(0xE005555, 0xA0);
245        flash_rom.write(0xE001357, 0x42);
246
247        assert_eq!(0x42, flash_rom.read(0xE001357));
248
249        // Switch to bank 1
250        flash_rom.write(0xE005555, 0xAA);
251        flash_rom.write(0xE002AAA, 0x55);
252        flash_rom.write(0xE005555, 0xB0);
253        flash_rom.write(0xE000000, 1);
254
255        assert_eq!(0xFF, flash_rom.read(0xE001357));
256
257        // Write 0xBC to $1357
258        flash_rom.write(0xE005555, 0xAA);
259        flash_rom.write(0xE002AAA, 0x55);
260        flash_rom.write(0xE005555, 0xA0);
261        flash_rom.write(0xE001357, 0xBC);
262
263        assert_eq!(0xBC, flash_rom.read(0xE001357));
264
265        // Switch to bank 0
266        flash_rom.write(0xE005555, 0xAA);
267        flash_rom.write(0xE002AAA, 0x55);
268        flash_rom.write(0xE005555, 0xB0);
269        flash_rom.write(0xE000000, 0);
270
271        assert_eq!(0x42, flash_rom.read(0xE001357));
272
273        assert_eq!(0x42, flash_rom.memory[0x01357]);
274        assert_eq!(0xBC, flash_rom.memory[0x11357]);
275    }
276}