From ccfff311238c6377e75d2acb56aa95e7cc0fb7d9 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 16 Nov 2019 18:16:13 +0200 Subject: [PATCH] Improve DMA According to GBATEK: The SAD, DAD, and CNT_L registers are holding the initial start addresses, and initial length. The hardware does NOT change the content of these registers during or after the transfer. The actual transfer takes place by using internal pointer/counter registers. The initial values are copied into internal regs under the following circumstances: Upon DMA Enable (Bit 15) changing from 0 to 1: Reloads SAD, DAD, CNT_L. Upon Repeat: Reloads CNT_L, and optionally DAD (Increment+Reload). Former-commit-id: 1e606dc88603a4600a79a341ef17fe8ccb482871 --- src/core/dma.rs | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/core/dma.rs b/src/core/dma.rs index 9999ba6..6ac5efc 100644 --- a/src/core/dma.rs +++ b/src/core/dma.rs @@ -21,12 +21,22 @@ pub struct DmaChannel { pub wc: u32, pub ctrl: DmaChannelCtrl, + // These are "latched" when the dma is enabled. + internal: DmaInternalRegs, + running: bool, cycles: usize, start_cycles: usize, irq: Interrupt, } +#[derive(Debug, Default)] +struct DmaInternalRegs { + src_addr: u32, + dst_addr: u32, + count: u32, +} + impl DmaChannel { pub fn new(id: usize) -> DmaChannel { if id > 3 { @@ -42,6 +52,7 @@ impl DmaChannel { ctrl: DmaChannelCtrl(0), cycles: 0, start_cycles: 0, + internal: Default::default(), } } @@ -78,12 +89,13 @@ impl DmaChannel { pub fn write_dma_ctrl(&mut self, value: u16) -> bool { let ctrl = DmaChannelCtrl(value); let mut start_immediately = false; - if ctrl.is_enabled() { + if ctrl.is_enabled() && !self.ctrl.is_enabled() { self.start_cycles = self.cycles; self.running = true; - if ctrl.timing() == 0 { - start_immediately = true; - } + start_immediately = ctrl.timing() == 0; + self.internal.src_addr = self.src; + self.internal.dst_addr = self.dst; + self.internal.count = self.wc; } self.ctrl = ctrl; return start_immediately; @@ -91,25 +103,30 @@ impl DmaChannel { fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) { let word_size = if self.ctrl.is_32bit() { 4 } else { 2 }; - let dst_rld = self.dst; - for word in 0..self.wc { + let count = match self.internal.count { + 0 => match self.id { + 3 => 0x1_0000, + _ => 0x0_4000, + }, + _ => self.internal.count, + }; + for _ in 0..count { if word_size == 4 { - let w = sb.read_32(self.src); - sb.write_32(self.dst, w) + let w = sb.read_32(self.internal.src_addr); + sb.write_32(self.internal.dst_addr, w) } else { - let hw = sb.read_16(self.src); - // println!("src {:x} dst {:x}", self.src, self.dst); - sb.write_16(self.dst, hw) + let hw = sb.read_16(self.internal.src_addr); + sb.write_16(self.internal.dst_addr, hw) } match self.ctrl.src_adj() { - /* Increment */ 0 => self.src += word_size, - /* Decrement */ 1 => self.src -= word_size, + /* Increment */ 0 => self.internal.src_addr += word_size, + /* Decrement */ 1 => self.internal.src_addr -= word_size, /* Fixed */ 2 => {} _ => panic!("forbidden DMA source address adjustment"), } match self.ctrl.dst_adj() { - /* Increment[+Reload] */ 0 | 3 => self.dst += word_size, - /* Decrement */ 1 => self.dst -= word_size, + /* Increment[+Reload] */ 0 | 3 => self.internal.dst_addr += word_size, + /* Decrement */ 1 => self.internal.dst_addr -= word_size, /* Fixed */ 2 => {} _ => panic!("forbidden DMA dest address adjustment"), } @@ -121,7 +138,7 @@ impl DmaChannel { self.start_cycles = self.cycles; /* reload */ if 3 == self.ctrl.dst_adj() { - self.dst = dst_rld; + self.internal.dst_addr = self.dst; } } else { self.running = false;