1#![allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
21
22use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
23
24use crate::dma_bus::DmaBus;
25
26const MODE_LENGTHS: [u8; 8] = [1, 2, 2, 4, 4, 4, 2, 4];
29
30#[derive(Debug, Clone, Copy)]
32pub struct Channel {
33 pub dmap: u8,
36 pub target: u8,
38 pub source_addr: u16,
40 pub source_bank: u8,
42 pub count_or_indirect: u16,
44 pub indirect_bank: u8,
46 pub hdma_addr: u16,
48 pub line_counter: u8,
50 pub hdma_completed: bool,
52 pub hdma_do_transfer: bool,
54}
55
56impl Default for Channel {
57 fn default() -> Self {
58 Self {
59 dmap: 0xFF,
60 target: 0xFF,
61 source_addr: 0xFFFF,
62 source_bank: 0xFF,
63 count_or_indirect: 0xFFFF,
64 indirect_bank: 0xFF,
65 hdma_addr: 0xFFFF,
66 line_counter: 0xFF,
67 hdma_completed: false,
68 hdma_do_transfer: false,
69 }
70 }
71}
72
73impl Channel {
74 const fn direction_b_to_a(self) -> bool {
75 self.dmap & 0x80 != 0
76 }
77 const fn indirect(self) -> bool {
78 self.dmap & 0x40 != 0
79 }
80 const fn reverse(self) -> bool {
81 self.dmap & 0x10 != 0
82 }
83 const fn fixed(self) -> bool {
84 self.dmap & 0x08 != 0
85 }
86 const fn mode(self) -> u8 {
87 self.dmap & 0x07
88 }
89
90 fn save_state(self, s: &mut SaveWriter) {
91 s.write_u8(self.dmap);
92 s.write_u8(self.target);
93 s.write_u16(self.source_addr);
94 s.write_u8(self.source_bank);
95 s.write_u16(self.count_or_indirect);
96 s.write_u8(self.indirect_bank);
97 s.write_u16(self.hdma_addr);
98 s.write_u8(self.line_counter);
99 s.write_bool(self.hdma_completed);
100 s.write_bool(self.hdma_do_transfer);
101 }
102
103 fn load_state(&mut self, s: &mut SaveReader) -> Result<(), SaveStateError> {
104 self.dmap = s.read_u8()?;
105 self.target = s.read_u8()?;
106 self.source_addr = s.read_u16()?;
107 self.source_bank = s.read_u8()?;
108 self.count_or_indirect = s.read_u16()?;
109 self.indirect_bank = s.read_u8()?;
110 self.hdma_addr = s.read_u16()?;
111 self.line_counter = s.read_u8()?;
112 self.hdma_completed = s.read_bool()?;
113 self.hdma_do_transfer = s.read_bool()?;
114 Ok(())
115 }
116
117 const fn b_address(self, index: u8) -> u8 {
119 let bump = match self.mode() {
120 1 | 5 => index & 1,
121 3 | 7 => (index >> 1) & 1,
122 4 => index,
123 _ => 0, };
125 self.target.wrapping_add(bump)
126 }
127}
128
129#[derive(Debug, Clone, Default)]
131pub struct Dma {
132 pub channels: [Channel; 8],
134 pub gp_enable: u8,
136 pub hdma_enable: u8,
138}
139
140impl Dma {
141 #[must_use]
143 pub fn new() -> Self {
144 Self::default()
145 }
146
147 pub fn write_reg(&mut self, ch: usize, reg: u8, val: u8) {
150 let c = &mut self.channels[ch & 7];
151 match reg {
152 0x0 => c.dmap = val,
153 0x1 => c.target = val,
154 0x2 => c.source_addr = (c.source_addr & 0xFF00) | u16::from(val),
155 0x3 => c.source_addr = (c.source_addr & 0x00FF) | (u16::from(val) << 8),
156 0x4 => c.source_bank = val,
157 0x5 => c.count_or_indirect = (c.count_or_indirect & 0xFF00) | u16::from(val),
158 0x6 => c.count_or_indirect = (c.count_or_indirect & 0x00FF) | (u16::from(val) << 8),
159 0x7 => c.indirect_bank = val,
160 0x8 => c.hdma_addr = (c.hdma_addr & 0xFF00) | u16::from(val),
161 0x9 => c.hdma_addr = (c.hdma_addr & 0x00FF) | (u16::from(val) << 8),
162 0xA => c.line_counter = val,
163 _ => {}
164 }
165 }
166
167 pub fn save_state(&self, w: &mut SaveWriter) {
169 w.section(*b"DMA0", |s| {
170 for &c in &self.channels {
171 c.save_state(s);
172 }
173 s.write_u8(self.gp_enable);
174 s.write_u8(self.hdma_enable);
175 });
176 }
177
178 pub fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
184 let mut s = r.expect_section(*b"DMA0")?;
185 for c in &mut self.channels {
186 c.load_state(&mut s)?;
187 }
188 self.gp_enable = s.read_u8()?;
189 self.hdma_enable = s.read_u8()?;
190 if s.remaining() != 0 {
191 return Err(SaveStateError::Invalid(alloc::format!(
192 "DMA0 section has {} trailing byte(s)",
193 s.remaining()
194 )));
195 }
196 Ok(())
197 }
198
199 #[must_use]
201 pub const fn read_reg(&self, ch: usize, reg: u8) -> u8 {
202 let c = &self.channels[ch & 7];
203 match reg {
204 0x0 => c.dmap,
205 0x1 => c.target,
206 0x2 => c.source_addr as u8,
207 0x3 => (c.source_addr >> 8) as u8,
208 0x4 => c.source_bank,
209 0x5 => c.count_or_indirect as u8,
210 0x6 => (c.count_or_indirect >> 8) as u8,
211 0x7 => c.indirect_bank,
212 0x8 => c.hdma_addr as u8,
213 0x9 => (c.hdma_addr >> 8) as u8,
214 0xA => c.line_counter,
215 _ => 0,
216 }
217 }
218
219 #[must_use]
223 pub fn run_gp(&mut self, mask: u8, bus: &mut impl DmaBus) -> u32 {
224 let mut cost: u32 = 0;
225 if mask == 0 {
226 return 0;
227 }
228 let mut last_line = bus.hdma_last_line();
238 bus.step(8);
239 cost += 8;
240 cost += self.service_hdma_during_gp(&mut last_line, bus);
241 for ch in 0..8 {
242 if mask & (1 << ch) == 0 {
243 continue;
244 }
245 bus.step(8); cost += 8;
247 cost += self.service_hdma_during_gp(&mut last_line, bus);
248 let channel = self.channels[ch];
249 let mut src = channel.source_addr;
250 let mut remaining = channel.count_or_indirect;
252 let mut index: u8 = 0;
253 loop {
254 let a = (u32::from(channel.source_bank) << 16) | u32::from(src);
255 let b = channel.b_address(index);
256 bus.step(4);
260 if channel.direction_b_to_a() {
261 let data = bus.read_b(b);
262 bus.step(4);
263 bus.write_a(a, data);
264 } else {
265 let data = bus.read_a(a);
266 bus.step(4);
267 bus.write_b(b, data);
268 }
269 cost += 8; cost += self.service_hdma_during_gp(&mut last_line, bus);
272 if !channel.fixed() {
273 src = if channel.reverse() {
274 src.wrapping_sub(1)
275 } else {
276 src.wrapping_add(1)
277 };
278 }
279 index = index.wrapping_add(1);
280 remaining = remaining.wrapping_sub(1);
281 if remaining == 0 {
282 break;
283 }
284 }
285 self.channels[ch].source_addr = src;
287 self.channels[ch].count_or_indirect = 0;
288 }
289 self.gp_enable = 0;
291 cost
292 }
293
294 fn transfer_unit(channel: Channel, a_addr: u32, b_addr: u8, bus: &mut impl DmaBus) {
297 if channel.direction_b_to_a() {
298 let data = bus.read_b(b_addr);
299 bus.write_a(a_addr, data);
300 } else {
301 let data = bus.read_a(a_addr);
302 bus.write_b(b_addr, data);
303 }
304 }
305
306 #[must_use]
314 pub fn service_hdma_line(&mut self, line: u16, vh: u16, bus: &mut impl DmaBus) -> u32 {
315 if line == 0 {
320 self.hdma_reset();
321 return self.hdma_setup(bus);
322 }
323 if self.hdma_enable == 0 {
324 return 0;
325 }
326 if line <= vh { self.hdma_run(bus) } else { 0 }
327 }
328
329 fn service_hdma_during_gp(&mut self, last_line: &mut u16, bus: &mut impl DmaBus) -> u32 {
336 if self.hdma_enable == 0 {
337 return 0;
338 }
339 let line = bus.scanline();
340 if line == *last_line {
341 return 0;
342 }
343 *last_line = line;
344 bus.set_hdma_last_line(line);
345 let vh = bus.visible_height();
346 let cost = self.service_hdma_line(line, vh, bus);
347 if cost > 0 {
348 bus.step(cost);
349 }
350 cost
351 }
352
353 pub fn hdma_reset(&mut self) {
355 for c in &mut self.channels {
356 c.hdma_completed = false;
357 c.hdma_do_transfer = false;
358 }
359 }
360
361 #[must_use]
364 pub fn hdma_setup(&mut self, bus: &mut impl DmaBus) -> u32 {
365 if self.hdma_enable == 0 {
366 return 0;
367 }
368 let mut cost: u32 = 8;
369 for ch in 0..8 {
370 self.channels[ch].hdma_do_transfer = true;
376 if self.hdma_enable & (1 << ch) == 0 {
377 continue;
378 }
379 self.channels[ch].hdma_addr = self.channels[ch].source_addr;
380 self.channels[ch].line_counter = 0;
381 cost += self.hdma_reload(ch, bus);
382 }
383 cost
384 }
385
386 fn hdma_reload(&mut self, ch: usize, bus: &mut impl DmaBus) -> u32 {
389 let mut cost = 0;
390 let bank = self.channels[ch].source_bank;
391 let mut addr = self.channels[ch].hdma_addr;
392
393 if self.channels[ch].line_counter.trailing_zeros() >= 7 {
395 let data = bus.read_a((u32::from(bank) << 16) | u32::from(addr));
396 cost += 8;
397 self.channels[ch].line_counter = data;
398 addr = addr.wrapping_add(1);
399
400 let completed = self.channels[ch].line_counter == 0;
401 self.channels[ch].hdma_completed = completed;
402 self.channels[ch].hdma_do_transfer = !completed;
403
404 if self.channels[ch].indirect() {
405 let lo = bus.read_a((u32::from(bank) << 16) | u32::from(addr));
406 cost += 8;
407 addr = addr.wrapping_add(1);
408 let indirect = if completed && self.hdma_finished(ch) {
411 u16::from(lo)
412 } else {
413 let hi = bus.read_a((u32::from(bank) << 16) | u32::from(addr));
414 cost += 8;
415 addr = addr.wrapping_add(1);
416 (u16::from(hi) << 8) | u16::from(lo)
417 };
418 self.channels[ch].count_or_indirect = indirect;
419 }
420 }
421 self.channels[ch].hdma_addr = addr;
422 cost
423 }
424
425 fn hdma_finished(&self, ch: usize) -> bool {
427 ((ch + 1)..8).all(|i| self.hdma_enable & (1 << i) == 0 || self.channels[i].hdma_completed)
428 }
429
430 const fn hdma_active(&self, ch: usize) -> bool {
431 self.hdma_enable & (1 << ch) != 0 && !self.channels[ch].hdma_completed
432 }
433
434 #[must_use]
437 pub fn hdma_run(&mut self, bus: &mut impl DmaBus) -> u32 {
438 if self.hdma_enable == 0 {
439 return 0;
440 }
441 let mut cost: u32 = 8; for ch in 0..8 {
444 if !self.hdma_active(ch) || !self.channels[ch].hdma_do_transfer {
445 continue;
446 }
447 let channel = self.channels[ch];
448 let len = MODE_LENGTHS[channel.mode() as usize];
449 let indirect = channel.indirect();
450 for index in 0..len {
451 let ptr = if indirect {
454 self.channels[ch].count_or_indirect
455 } else {
456 self.channels[ch].hdma_addr
457 };
458 let a_addr = (u32::from(if indirect {
459 channel.indirect_bank
460 } else {
461 channel.source_bank
462 }) << 16)
463 | u32::from(ptr);
464 let b = channel.b_address(index);
465 Self::transfer_unit(channel, a_addr, b, bus);
466 cost += 8;
467 let next = ptr.wrapping_add(1);
468 if indirect {
469 self.channels[ch].count_or_indirect = next;
470 } else {
471 self.channels[ch].hdma_addr = next;
472 }
473 }
474 }
475 for ch in 0..8 {
477 if !self.hdma_active(ch) {
478 continue;
479 }
480 self.channels[ch].line_counter = self.channels[ch].line_counter.wrapping_sub(1);
481 self.channels[ch].hdma_do_transfer = self.channels[ch].line_counter & 0x80 != 0;
482 cost += self.hdma_reload(ch, bus);
483 }
484 cost
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use alloc::vec;
492 use alloc::vec::Vec;
493
494 struct TestBus {
496 a: Vec<u8>,
497 b: [u8; 256],
498 }
499 impl DmaBus for TestBus {
500 fn read_a(&mut self, addr: u32) -> u8 {
501 *self.a.get((addr & 0xFFFF) as usize).unwrap_or(&0)
502 }
503 fn write_a(&mut self, addr: u32, val: u8) {
504 let i = (addr & 0xFFFF) as usize;
505 if i < self.a.len() {
506 self.a[i] = val;
507 }
508 }
509 fn read_b(&mut self, addr: u8) -> u8 {
510 self.b[addr as usize]
511 }
512 fn write_b(&mut self, addr: u8, val: u8) {
513 self.b[addr as usize] = val;
514 }
515 }
516
517 #[test]
518 fn gp_dma_mode0_copies_block_to_b_bus() {
519 let mut bus = TestBus {
520 a: vec![0; 0x10000],
521 b: [0; 256],
522 };
523 for i in 0..4u32 {
524 bus.a[(0x1000 + i) as usize] = (0xA0 + i) as u8;
525 }
526 let mut dma = Dma::new();
527 dma.write_reg(0, 0x0, 0x00); dma.write_reg(0, 0x1, 0x18); dma.write_reg(0, 0x2, 0x00); dma.write_reg(0, 0x3, 0x10); dma.write_reg(0, 0x4, 0x00); dma.write_reg(0, 0x5, 0x04); dma.write_reg(0, 0x6, 0x00); let cost = dma.run_gp(0x01, &mut bus);
536 assert_eq!(bus.b[0x18], 0xA3);
538 assert!(cost >= 8 + 8 + 4 * 8); }
540
541 #[test]
542 fn gp_dma_mode1_alternates_two_b_regs() {
543 let mut bus = TestBus {
544 a: vec![0; 0x10000],
545 b: [0; 256],
546 };
547 for i in 0..4u32 {
548 bus.a[(0x2000 + i) as usize] = (0x10 + i) as u8;
549 }
550 let mut dma = Dma::new();
551 dma.write_reg(0, 0x0, 0x01); dma.write_reg(0, 0x1, 0x18); dma.write_reg(0, 0x2, 0x00);
554 dma.write_reg(0, 0x3, 0x20); dma.write_reg(0, 0x4, 0x00);
556 dma.write_reg(0, 0x5, 0x04);
557 dma.write_reg(0, 0x6, 0x00);
558 let _ = dma.run_gp(0x01, &mut bus);
559 assert_eq!(bus.b[0x18], 0x12);
561 assert_eq!(bus.b[0x19], 0x13);
562 }
563
564 #[test]
565 fn gp_dma_enable_is_one_shot() {
566 let mut bus = TestBus {
567 a: vec![0; 0x10000],
568 b: [0; 256],
569 };
570 let mut dma = Dma::new();
571 dma.write_reg(0, 0x5, 0x01);
572 dma.gp_enable = 0x01;
573 let _ = dma.run_gp(0x01, &mut bus);
574 assert_eq!(dma.gp_enable, 0);
575 }
576
577 #[test]
578 fn all_channels_round_trip_through_save_state() {
579 let mut dma = Dma::new();
580 dma.write_reg(3, 0x0, 0x81);
581 dma.write_reg(3, 0x1, 0x18);
582 dma.gp_enable = 0x08;
583 dma.hdma_enable = 0x01;
584
585 let mut w = SaveWriter::new();
586 dma.save_state(&mut w);
587 let bytes = w.into_bytes();
588
589 let mut fresh = Dma::new();
590 let mut r = SaveReader::new(&bytes);
591 fresh.load_state(&mut r).unwrap();
592
593 assert_eq!(fresh.channels[3].dmap, 0x81);
594 assert_eq!(fresh.channels[3].target, 0x18);
595 assert_eq!(fresh.gp_enable, 0x08);
596 assert_eq!(fresh.hdma_enable, 0x01);
597 assert_eq!(r.remaining(), 0);
598 }
599}