From 090c163466c244d582952e863bd1fd0e01868bae Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 10:20:02 +0200 Subject: [PATCH 1/9] Rename DmaCon to ApbDma Related to #105 --- clicky-core/src/devices/platform/pp.rs | 4 ++-- .../platform/pp/{dma.rs => apb_dma.rs} | 20 +++++++++---------- clicky-core/src/devices/platform/pp/soc.rs | 14 ++++++------- clicky-core/src/sys/ipod4g/mod.rs | 2 +- clicky-core/src/sys/ipodmini1g/mod.rs | 2 +- clicky-core/src/sys/ipodphoto/mod.rs | 2 +- 6 files changed, 22 insertions(+), 22 deletions(-) rename clicky-core/src/devices/platform/pp/{dma.rs => apb_dma.rs} (95%) diff --git a/clicky-core/src/devices/platform/pp.rs b/clicky-core/src/devices/platform/pp.rs index d221cc5e..1e73039a 100644 --- a/clicky-core/src/devices/platform/pp.rs +++ b/clicky-core/src/devices/platform/pp.rs @@ -2,13 +2,13 @@ mod mmap; +mod apb_dma; mod cachecon; mod cfg_timer; mod clcd; mod cpucon; mod cpuid; mod devcon; -mod dma; mod eide; mod evp; mod firewire; @@ -35,7 +35,7 @@ pub use clcd::*; pub use cpucon::*; pub use cpuid::*; pub use devcon::*; -pub use dma::*; +pub use apb_dma::*; pub use eide::*; pub use evp::*; pub use firewire::*; diff --git a/clicky-core/src/devices/platform/pp/dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs similarity index 95% rename from clicky-core/src/devices/platform/pp/dma.rs rename to clicky-core/src/devices/platform/pp/apb_dma.rs index 51b216fd..1b8ec03d 100644 --- a/clicky-core/src/devices/platform/pp/dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -1,7 +1,7 @@ use crate::devices::prelude::*; #[derive(Debug, Default)] -struct Dma { +struct DmaChannel { label: Option<&'static str>, cmd: u32, @@ -12,9 +12,9 @@ struct Dma { incr: u32, } -impl Device for Dma { +impl Device for DmaChannel { fn kind(&self) -> &'static str { - "" + "APB-DMA channel" } fn label(&self) -> Option<&'static str> { @@ -37,10 +37,10 @@ impl Device for Dma { } #[derive(Debug)] -pub struct DmaCon { +pub struct ApbDma { label: &'static str, - dma: [Dma; 8], + dma: [DmaChannel; 8], master_control: u32, master_status: u32, req_status: u32, @@ -57,9 +57,9 @@ pub struct DmaCon { ide_dmarq: Option, } -impl DmaCon { - pub fn new(label: &'static str, ide_dmarq: Option) -> DmaCon { - let mut dma = DmaCon { +impl ApbDma { + pub fn new(label: &'static str, ide_dmarq: Option) -> ApbDma { + let mut dma = ApbDma { label, dma: Default::default(), @@ -92,7 +92,7 @@ impl DmaCon { } } -impl Device for DmaCon { +impl Device for ApbDma { fn kind(&self) -> &'static str { "DMA Engine" } @@ -117,7 +117,7 @@ impl Device for DmaCon { } } -impl Memory for DmaCon { +impl Memory for ApbDma { fn r32(&mut self, offset: u32) -> MemResult { match offset { 0x0 => Err(StubRead(Error, self.master_control)), diff --git a/clicky-core/src/devices/platform/pp/soc.rs b/clicky-core/src/devices/platform/pp/soc.rs index 38c7bc21..411b19db 100644 --- a/clicky-core/src/devices/platform/pp/soc.rs +++ b/clicky-core/src/devices/platform/pp/soc.rs @@ -36,8 +36,8 @@ pub struct Pp502x { pub cachecon: CacheCon, pub i2s: I2SCon, pub mailbox: Mailbox, - pub dmacon0: DmaCon, - pub dmacon1: DmaCon, + pub apb_dma: ApbDma, + pub dmacon1: ApbDma, pub serial0: Serial, pub serial1: Serial, pub evp: Evp, @@ -95,9 +95,9 @@ impl Pp502x { // .register(37, ser1_irq_rx) .register(40, i2c_irq_rx); - let dmacon0 = DmaCon::new("0", Some(ide_dmarq_rx)); + let apb_dma = ApbDma::new("0", Some(ide_dmarq_rx)); // the undocumented second engine -- nothing routes DMA requests to it yet - let dmacon1 = DmaCon::new("1", None); + let dmacon1 = ApbDma::new("1", None); Pp502x { fastram: AsanRam::new(96 * 1024, true), // 96 KB @@ -128,7 +128,7 @@ impl Pp502x { cachecon: CacheCon::new(), i2s: I2SCon::new(), mailbox: Mailbox::new(mbx_cpu_irq_tx, mbx_cop_irq_tx), - dmacon0, + apb_dma, dmacon1, serial0: Serial::new("0"), serial1: Serial::new("1"), @@ -216,9 +216,9 @@ soc_mmap! { 0x6000_5014..=0x6000_5017 => rtc, 0x6000_6000..=0x6000_6fff => devcon, 0x6000_7000..=0x6000_7fff => cpucon, - // Memory accesses to dmacon1 are suspiciously similar to dmacon0 + // Memory accesses to dmacon1 are suspiciously similar to apb_dma 0x6000_8000..=0x6000_9fff => dmacon1, - 0x6000_a000..=0x6000_bfff => dmacon0, + 0x6000_a000..=0x6000_bfff => apb_dma, 0x6000_c000..=0x6000_cfff => cachecon, 0x6000_d000..=0x6000_d07f => gpio_abcd, 0x6000_d080..=0x6000_d0ff => gpio_efgh, diff --git a/clicky-core/src/sys/ipod4g/mod.rs b/clicky-core/src/sys/ipod4g/mod.rs index 05802986..77c8a2dd 100644 --- a/clicky-core/src/sys/ipod4g/mod.rs +++ b/clicky-core/src/sys/ipod4g/mod.rs @@ -267,7 +267,7 @@ impl Ipod4g { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.dmacon0.do_ide_dma() { + if devices.soc.apb_dma.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), diff --git a/clicky-core/src/sys/ipodmini1g/mod.rs b/clicky-core/src/sys/ipodmini1g/mod.rs index 40d6a7d2..482952b0 100644 --- a/clicky-core/src/sys/ipodmini1g/mod.rs +++ b/clicky-core/src/sys/ipodmini1g/mod.rs @@ -272,7 +272,7 @@ impl IpodMini1g { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.dmacon0.do_ide_dma() { + if devices.soc.apb_dma.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), diff --git a/clicky-core/src/sys/ipodphoto/mod.rs b/clicky-core/src/sys/ipodphoto/mod.rs index e82c3e64..58b5b8c7 100644 --- a/clicky-core/src/sys/ipodphoto/mod.rs +++ b/clicky-core/src/sys/ipodphoto/mod.rs @@ -241,7 +241,7 @@ impl IpodPhoto { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.dmacon0.do_ide_dma() { + if devices.soc.apb_dma.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), From 2329edb046cf07fe36b35cd3a6c4b6368367936b Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 10:25:28 +0200 Subject: [PATCH 2/9] Duplicate ApbDma into AhbDma Related to #105 --- clicky-core/src/devices/platform/pp.rs | 4 +- .../src/devices/platform/pp/ahb_dma.rs | 164 ++++++++++++++++++ clicky-core/src/devices/platform/pp/soc.rs | 10 +- 3 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 clicky-core/src/devices/platform/pp/ahb_dma.rs diff --git a/clicky-core/src/devices/platform/pp.rs b/clicky-core/src/devices/platform/pp.rs index 1e73039a..f38f0893 100644 --- a/clicky-core/src/devices/platform/pp.rs +++ b/clicky-core/src/devices/platform/pp.rs @@ -2,6 +2,7 @@ mod mmap; +mod ahb_dma; mod apb_dma; mod cachecon; mod cfg_timer; @@ -29,13 +30,14 @@ mod usb; mod usec_timer; mod pwm; +pub use ahb_dma::*; +pub use apb_dma::*; pub use cachecon::*; pub use cfg_timer::*; pub use clcd::*; pub use cpucon::*; pub use cpuid::*; pub use devcon::*; -pub use apb_dma::*; pub use eide::*; pub use evp::*; pub use firewire::*; diff --git a/clicky-core/src/devices/platform/pp/ahb_dma.rs b/clicky-core/src/devices/platform/pp/ahb_dma.rs new file mode 100644 index 00000000..2981eacb --- /dev/null +++ b/clicky-core/src/devices/platform/pp/ahb_dma.rs @@ -0,0 +1,164 @@ +use crate::devices::prelude::*; + +#[derive(Debug, Default)] +struct DmaChannel { + label: Option<&'static str>, + + cmd: u32, + status: u32, + ram_addr: u32, + flags: u32, + per_addr: u32, + incr: u32, +} + +impl Device for DmaChannel { + fn kind(&self) -> &'static str { + "AHB-DMA channel" + } + + fn label(&self) -> Option<&'static str> { + self.label + } + + fn probe(&self, offset: u32) -> Probe { + let reg = match offset { + 0x00 => "Cmd", + 0x04 => "Status", + 0x10 => "Ram Addr", + 0x14 => "Flags", + 0x18 => "Per Addr", + 0x1c => "Incr", + _ => return Probe::Unmapped, + }; + + Probe::Register(reg) + } +} + +#[derive(Debug)] +pub struct AhbDma { + label: &'static str, + + dma: [DmaChannel; 8], + master_control: u32, + master_status: u32, + req_status: u32, + + // HACK: IDE DMA doesn't actually go through the DMA controller + // that said, to keep things simple in the emulator, we route IDE DMA through the main DMA + // engine... + // + // As per the pp5020 spec sheet: "A dedicated, high-performance ATA-66IDE controller with its + // own DMA engine frees the processors from mundane management tasks." + // + // Only the engine the IDE controller was wired to carries this; the other + // one gets `None`. + ide_dmarq: Option, +} + +impl AhbDma { + pub fn new(label: &'static str, ide_dmarq: Option) -> AhbDma { + let mut dma = AhbDma { + label, + + dma: Default::default(), + master_control: 0, + master_status: 0, + req_status: 0, + + ide_dmarq, + }; + + dma.dma[0].label = Some("0"); + dma.dma[1].label = Some("1"); + dma.dma[2].label = Some("2"); + dma.dma[3].label = Some("3"); + dma.dma[4].label = Some("4"); + dma.dma[5].label = Some("5"); + dma.dma[6].label = Some("6"); + dma.dma[7].label = Some("7"); + + dma + } + + /// XXX: remove this once DMA is properly sorted out + pub fn do_ide_dma(&self) -> bool { + if let Some(ref dmarq) = self.ide_dmarq { + dmarq.asserted() + } else { + false + } + } +} + +impl Device for AhbDma { + fn kind(&self) -> &'static str { + "DMA Engine" + } + + fn label(&self) -> Option<&'static str> { + Some(self.label) + } + + fn probe(&self, offset: u32) -> Probe { + let reg = match offset { + 0x0 => "Master Control", + 0x4 => "Master Status", + 0x8 => "Req Status", + 0x1000..=0x10ff => { + let id = (offset - 0x1000) / 0x20; + return Probe::from_device(&self.dma[id as usize], offset % 0x20); + } + _ => return Probe::Unmapped, + }; + + Probe::Register(reg) + } +} + +impl Memory for AhbDma { + fn r32(&mut self, offset: u32) -> MemResult { + match offset { + 0x0 => Err(StubRead(Error, self.master_control)), + 0x4 => Err(StubRead(Error, self.master_status)), + 0x8 => Err(StubRead(Error, self.req_status)), + 0x1000..=0x10ff => { + let id = (offset - 0x1000) / 0x20; + let dma = &mut self.dma[id as usize]; + match offset % 0x20 { + 0x00 => Err(StubRead(Error, dma.cmd)), + 0x04 => Err(StubRead(Error, dma.status)), + 0x10 => Err(StubRead(Error, dma.ram_addr)), + 0x14 => Err(StubRead(Error, dma.flags)), + 0x18 => Err(StubRead(Error, dma.per_addr)), + 0x1c => Err(StubRead(Error, dma.incr)), + _ => Err(Unexpected), + } + } + _ => Err(Unexpected), + } + } + + fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { + match offset { + 0x0 => Err(StubWrite(Error, self.master_control = val)), + 0x4 => Err(StubWrite(Error, self.master_status = val)), + 0x8 => Err(StubWrite(Error, self.req_status = val)), + 0x1000..=0x10ff => { + let id = (offset - 0x1000) / 0x20; + let dma = &mut self.dma[id as usize]; + match offset % 0x20 { + 0x00 => Err(StubWrite(Error, dma.cmd = val)), + 0x04 => Err(StubWrite(Error, dma.status = val)), + 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), + 0x14 => Err(StubWrite(Error, dma.flags = val)), + 0x18 => Err(StubWrite(Error, dma.per_addr = val)), + 0x1c => Err(StubWrite(Error, dma.incr = val)), + _ => Err(Unexpected), + } + } + _ => Err(Unexpected), + } + } +} diff --git a/clicky-core/src/devices/platform/pp/soc.rs b/clicky-core/src/devices/platform/pp/soc.rs index 411b19db..36a0e96a 100644 --- a/clicky-core/src/devices/platform/pp/soc.rs +++ b/clicky-core/src/devices/platform/pp/soc.rs @@ -36,8 +36,8 @@ pub struct Pp502x { pub cachecon: CacheCon, pub i2s: I2SCon, pub mailbox: Mailbox, + pub ahb_dma: AhbDma, pub apb_dma: ApbDma, - pub dmacon1: ApbDma, pub serial0: Serial, pub serial1: Serial, pub evp: Evp, @@ -95,9 +95,8 @@ impl Pp502x { // .register(37, ser1_irq_rx) .register(40, i2c_irq_rx); + let ahb_dma = AhbDma::new("1", None); let apb_dma = ApbDma::new("0", Some(ide_dmarq_rx)); - // the undocumented second engine -- nothing routes DMA requests to it yet - let dmacon1 = ApbDma::new("1", None); Pp502x { fastram: AsanRam::new(96 * 1024, true), // 96 KB @@ -128,8 +127,8 @@ impl Pp502x { cachecon: CacheCon::new(), i2s: I2SCon::new(), mailbox: Mailbox::new(mbx_cpu_irq_tx, mbx_cop_irq_tx), + ahb_dma, apb_dma, - dmacon1, serial0: Serial::new("0"), serial1: Serial::new("1"), evp: Evp::new(), @@ -216,8 +215,7 @@ soc_mmap! { 0x6000_5014..=0x6000_5017 => rtc, 0x6000_6000..=0x6000_6fff => devcon, 0x6000_7000..=0x6000_7fff => cpucon, - // Memory accesses to dmacon1 are suspiciously similar to apb_dma - 0x6000_8000..=0x6000_9fff => dmacon1, + 0x6000_8000..=0x6000_9fff => ahb_dma, 0x6000_a000..=0x6000_bfff => apb_dma, 0x6000_c000..=0x6000_cfff => cachecon, 0x6000_d000..=0x6000_d07f => gpio_abcd, From 3514dc0a865c8b60191f88c893c937ea3f9308c7 Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 10:28:33 +0200 Subject: [PATCH 3/9] ahb_dma: Remove EIDE IRQ Related to #105 --- .../src/devices/platform/pp/ahb_dma.rs | 24 +------------------ clicky-core/src/devices/platform/pp/soc.rs | 2 +- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/ahb_dma.rs b/clicky-core/src/devices/platform/pp/ahb_dma.rs index 2981eacb..72f12daa 100644 --- a/clicky-core/src/devices/platform/pp/ahb_dma.rs +++ b/clicky-core/src/devices/platform/pp/ahb_dma.rs @@ -44,21 +44,10 @@ pub struct AhbDma { master_control: u32, master_status: u32, req_status: u32, - - // HACK: IDE DMA doesn't actually go through the DMA controller - // that said, to keep things simple in the emulator, we route IDE DMA through the main DMA - // engine... - // - // As per the pp5020 spec sheet: "A dedicated, high-performance ATA-66IDE controller with its - // own DMA engine frees the processors from mundane management tasks." - // - // Only the engine the IDE controller was wired to carries this; the other - // one gets `None`. - ide_dmarq: Option, } impl AhbDma { - pub fn new(label: &'static str, ide_dmarq: Option) -> AhbDma { + pub fn new(label: &'static str) -> AhbDma { let mut dma = AhbDma { label, @@ -66,8 +55,6 @@ impl AhbDma { master_control: 0, master_status: 0, req_status: 0, - - ide_dmarq, }; dma.dma[0].label = Some("0"); @@ -81,15 +68,6 @@ impl AhbDma { dma } - - /// XXX: remove this once DMA is properly sorted out - pub fn do_ide_dma(&self) -> bool { - if let Some(ref dmarq) = self.ide_dmarq { - dmarq.asserted() - } else { - false - } - } } impl Device for AhbDma { diff --git a/clicky-core/src/devices/platform/pp/soc.rs b/clicky-core/src/devices/platform/pp/soc.rs index 36a0e96a..7cc371ed 100644 --- a/clicky-core/src/devices/platform/pp/soc.rs +++ b/clicky-core/src/devices/platform/pp/soc.rs @@ -95,7 +95,7 @@ impl Pp502x { // .register(37, ser1_irq_rx) .register(40, i2c_irq_rx); - let ahb_dma = AhbDma::new("1", None); + let ahb_dma = AhbDma::new("1"); let apb_dma = ApbDma::new("0", Some(ide_dmarq_rx)); Pp502x { From bc1768fac4c31524c3cffb8a95cf422ea4405fe7 Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 10:30:20 +0200 Subject: [PATCH 4/9] apb_dma, ahb_dma: Remove controller label PP5020 has one instance of each, no need to label them --- clicky-core/src/devices/platform/pp/ahb_dma.rs | 10 +--------- clicky-core/src/devices/platform/pp/apb_dma.rs | 10 +--------- clicky-core/src/devices/platform/pp/soc.rs | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/ahb_dma.rs b/clicky-core/src/devices/platform/pp/ahb_dma.rs index 72f12daa..280c69f9 100644 --- a/clicky-core/src/devices/platform/pp/ahb_dma.rs +++ b/clicky-core/src/devices/platform/pp/ahb_dma.rs @@ -38,8 +38,6 @@ impl Device for DmaChannel { #[derive(Debug)] pub struct AhbDma { - label: &'static str, - dma: [DmaChannel; 8], master_control: u32, master_status: u32, @@ -47,10 +45,8 @@ pub struct AhbDma { } impl AhbDma { - pub fn new(label: &'static str) -> AhbDma { + pub fn new() -> AhbDma { let mut dma = AhbDma { - label, - dma: Default::default(), master_control: 0, master_status: 0, @@ -75,10 +71,6 @@ impl Device for AhbDma { "DMA Engine" } - fn label(&self) -> Option<&'static str> { - Some(self.label) - } - fn probe(&self, offset: u32) -> Probe { let reg = match offset { 0x0 => "Master Control", diff --git a/clicky-core/src/devices/platform/pp/apb_dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs index 1b8ec03d..de4bb078 100644 --- a/clicky-core/src/devices/platform/pp/apb_dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -38,8 +38,6 @@ impl Device for DmaChannel { #[derive(Debug)] pub struct ApbDma { - label: &'static str, - dma: [DmaChannel; 8], master_control: u32, master_status: u32, @@ -58,10 +56,8 @@ pub struct ApbDma { } impl ApbDma { - pub fn new(label: &'static str, ide_dmarq: Option) -> ApbDma { + pub fn new(ide_dmarq: Option) -> ApbDma { let mut dma = ApbDma { - label, - dma: Default::default(), master_control: 0, master_status: 0, @@ -97,10 +93,6 @@ impl Device for ApbDma { "DMA Engine" } - fn label(&self) -> Option<&'static str> { - Some(self.label) - } - fn probe(&self, offset: u32) -> Probe { let reg = match offset { 0x0 => "Master Control", diff --git a/clicky-core/src/devices/platform/pp/soc.rs b/clicky-core/src/devices/platform/pp/soc.rs index 7cc371ed..b5fd134c 100644 --- a/clicky-core/src/devices/platform/pp/soc.rs +++ b/clicky-core/src/devices/platform/pp/soc.rs @@ -95,8 +95,8 @@ impl Pp502x { // .register(37, ser1_irq_rx) .register(40, i2c_irq_rx); - let ahb_dma = AhbDma::new("1"); - let apb_dma = ApbDma::new("0", Some(ide_dmarq_rx)); + let ahb_dma = AhbDma::new(); + let apb_dma = ApbDma::new(Some(ide_dmarq_rx)); Pp502x { fastram: AsanRam::new(96 * 1024, true), // 96 KB From ea161db2917e8372ef9d4dbc235a6b147ddcb11a Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 20:05:32 +0200 Subject: [PATCH 5/9] apb_dma: Remove unused channels --- clicky-core/src/devices/platform/pp/apb_dma.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/apb_dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs index de4bb078..c352c584 100644 --- a/clicky-core/src/devices/platform/pp/apb_dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -70,10 +70,6 @@ impl ApbDma { dma.dma[1].label = Some("1"); dma.dma[2].label = Some("2"); dma.dma[3].label = Some("3"); - dma.dma[4].label = Some("4"); - dma.dma[5].label = Some("5"); - dma.dma[6].label = Some("6"); - dma.dma[7].label = Some("7"); dma } From 3b3738040991b8b0e60dfa2506921c4343838513 Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 20:18:22 +0200 Subject: [PATCH 6/9] apb_dma: Move EIDE DMA IRQ into eide Related to #105 --- .../src/devices/platform/pp/apb_dma.rs | 24 +------------------ clicky-core/src/devices/platform/pp/eide.rs | 10 ++++++-- clicky-core/src/devices/platform/pp/soc.rs | 4 ++-- clicky-core/src/sys/ipod4g/mod.rs | 2 +- clicky-core/src/sys/ipodmini1g/mod.rs | 2 +- clicky-core/src/sys/ipodphoto/mod.rs | 2 +- 6 files changed, 14 insertions(+), 30 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/apb_dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs index c352c584..d8fa2b61 100644 --- a/clicky-core/src/devices/platform/pp/apb_dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -42,28 +42,15 @@ pub struct ApbDma { master_control: u32, master_status: u32, req_status: u32, - - // HACK: IDE DMA doesn't actually go through the DMA controller - // that said, to keep things simple in the emulator, we route IDE DMA through the main DMA - // engine... - // - // As per the pp5020 spec sheet: "A dedicated, high-performance ATA-66IDE controller with its - // own DMA engine frees the processors from mundane management tasks." - // - // Only the engine the IDE controller was wired to carries this; the other - // one gets `None`. - ide_dmarq: Option, } impl ApbDma { - pub fn new(ide_dmarq: Option) -> ApbDma { + pub fn new() -> ApbDma { let mut dma = ApbDma { dma: Default::default(), master_control: 0, master_status: 0, req_status: 0, - - ide_dmarq, }; dma.dma[0].label = Some("0"); @@ -73,15 +60,6 @@ impl ApbDma { dma } - - /// XXX: remove this once DMA is properly sorted out - pub fn do_ide_dma(&self) -> bool { - if let Some(ref dmarq) = self.ide_dmarq { - dmarq.asserted() - } else { - false - } - } } impl Device for ApbDma { diff --git a/clicky-core/src/devices/platform/pp/eide.rs b/clicky-core/src/devices/platform/pp/eide.rs index 31e50842..4245fb52 100644 --- a/clicky-core/src/devices/platform/pp/eide.rs +++ b/clicky-core/src/devices/platform/pp/eide.rs @@ -21,6 +21,7 @@ pub struct EIDECon { ide0_cfg: IdeDriveCfg, ide1_cfg: IdeDriveCfg, ide: IdeController, + dmarq: irq::Receiver, // NOTE: since no pp devices ever used two IDE devices at once, I have no idea if the DMA is // per disk or per controller... @@ -38,11 +39,12 @@ pub struct EIDECon { pub struct DmaErr; impl EIDECon { - pub fn new(irq: irq::Sender, dmarq: irq::Sender) -> EIDECon { + pub fn new(irq: irq::Sender, dmarq_tx: irq::Sender, dmarq_rx: irq::Receiver) -> EIDECon { EIDECon { ide0_cfg: Default::default(), ide1_cfg: Default::default(), - ide: IdeController::new(irq, dmarq), + ide: IdeController::new(irq, dmarq_tx), + dmarq: dmarq_rx, dma_control: 0, dma_length: 0, @@ -55,6 +57,10 @@ impl EIDECon { &mut self.ide } + pub fn do_ide_dma(&self) -> bool { + self.dmarq.asserted() + } + pub fn do_dma(&mut self) -> Result<(crate::memory::MemAccessKind, u32), DmaErr> { if self.dma_length == 0 { return Err(DmaErr); diff --git a/clicky-core/src/devices/platform/pp/soc.rs b/clicky-core/src/devices/platform/pp/soc.rs index b5fd134c..0df834f8 100644 --- a/clicky-core/src/devices/platform/pp/soc.rs +++ b/clicky-core/src/devices/platform/pp/soc.rs @@ -96,7 +96,7 @@ impl Pp502x { .register(40, i2c_irq_rx); let ahb_dma = AhbDma::new(); - let apb_dma = ApbDma::new(Some(ide_dmarq_rx)); + let apb_dma = ApbDma::new(); Pp502x { fastram: AsanRam::new(96 * 1024, true), // 96 KB @@ -122,7 +122,7 @@ impl Pp502x { ppcon: PPCon::new(), devcon: DevCon::new(), intcon, - eidecon: EIDECon::new(ide_irq_tx, ide_dmarq_tx), + eidecon: EIDECon::new(ide_irq_tx, ide_dmarq_tx, ide_dmarq_rx), memcon: MemCon::new(), cachecon: CacheCon::new(), i2s: I2SCon::new(), diff --git a/clicky-core/src/sys/ipod4g/mod.rs b/clicky-core/src/sys/ipod4g/mod.rs index 77c8a2dd..31cbc6eb 100644 --- a/clicky-core/src/sys/ipod4g/mod.rs +++ b/clicky-core/src/sys/ipod4g/mod.rs @@ -267,7 +267,7 @@ impl Ipod4g { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.apb_dma.do_ide_dma() { + if devices.soc.eidecon.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), diff --git a/clicky-core/src/sys/ipodmini1g/mod.rs b/clicky-core/src/sys/ipodmini1g/mod.rs index 482952b0..eae353a8 100644 --- a/clicky-core/src/sys/ipodmini1g/mod.rs +++ b/clicky-core/src/sys/ipodmini1g/mod.rs @@ -272,7 +272,7 @@ impl IpodMini1g { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.apb_dma.do_ide_dma() { + if devices.soc.eidecon.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), diff --git a/clicky-core/src/sys/ipodphoto/mod.rs b/clicky-core/src/sys/ipodphoto/mod.rs index 58b5b8c7..8e9ce67b 100644 --- a/clicky-core/src/sys/ipodphoto/mod.rs +++ b/clicky-core/src/sys/ipodphoto/mod.rs @@ -241,7 +241,7 @@ impl IpodPhoto { // reorganized, and moved somewhere more appropriate. if self.dma_pending.check() { self.dma_pending.clear(); - if devices.soc.apb_dma.do_ide_dma() { + if devices.soc.eidecon.do_ide_dma() { let (kind, addr) = match (devices.soc.eidecon).do_dma() { Ok(tup) => tup, Err(_) => panic!("asd"), From c2a7b06b33e3f72e59fe03620be258f31fa66969 Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 22:53:48 +0200 Subject: [PATCH 7/9] ahb_dma, apb_dma: Fix aliasing As tested on ppdeluxe, DMA channels are aliased every 0x80. For AHB-DMA only 2 channels are available, the gap in 0x40-0x7F is "emulated" by reading out zeros. Related to #105 --- .../src/devices/platform/pp/ahb_dma.rs | 62 ++++++++++--------- .../src/devices/platform/pp/apb_dma.rs | 20 +++--- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/ahb_dma.rs b/clicky-core/src/devices/platform/pp/ahb_dma.rs index 280c69f9..0ad0a363 100644 --- a/clicky-core/src/devices/platform/pp/ahb_dma.rs +++ b/clicky-core/src/devices/platform/pp/ahb_dma.rs @@ -76,10 +76,12 @@ impl Device for AhbDma { 0x0 => "Master Control", 0x4 => "Master Status", 0x8 => "Req Status", - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - return Probe::from_device(&self.dma[id as usize], offset % 0x20); - } + 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { + offset @ 0x00..=0x3f => { + return Probe::from_device(&self.dma[(offset / 0x20) as usize], offset % 0x20); + } + _ => "Reserved (reads zero)", + }, _ => return Probe::Unmapped, }; @@ -93,19 +95,21 @@ impl Memory for AhbDma { 0x0 => Err(StubRead(Error, self.master_control)), 0x4 => Err(StubRead(Error, self.master_status)), 0x8 => Err(StubRead(Error, self.req_status)), - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - let dma = &mut self.dma[id as usize]; - match offset % 0x20 { - 0x00 => Err(StubRead(Error, dma.cmd)), - 0x04 => Err(StubRead(Error, dma.status)), - 0x10 => Err(StubRead(Error, dma.ram_addr)), - 0x14 => Err(StubRead(Error, dma.flags)), - 0x18 => Err(StubRead(Error, dma.per_addr)), - 0x1c => Err(StubRead(Error, dma.incr)), - _ => Err(Unexpected), + 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { + offset @ 0x00..=0x3f => { + let dma = &mut self.dma[(offset / 0x20) as usize]; + match offset % 0x20 { + 0x00 => Err(StubRead(Error, dma.cmd)), + 0x04 => Err(StubRead(Error, dma.status)), + 0x10 => Err(StubRead(Error, dma.ram_addr)), + 0x14 => Err(StubRead(Error, dma.flags)), + 0x18 => Err(StubRead(Error, dma.per_addr)), + 0x1c => Err(StubRead(Error, dma.incr)), + _ => Err(Unexpected), + } } - } + _ => Ok(0), + }, _ => Err(Unexpected), } } @@ -115,19 +119,21 @@ impl Memory for AhbDma { 0x0 => Err(StubWrite(Error, self.master_control = val)), 0x4 => Err(StubWrite(Error, self.master_status = val)), 0x8 => Err(StubWrite(Error, self.req_status = val)), - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - let dma = &mut self.dma[id as usize]; - match offset % 0x20 { - 0x00 => Err(StubWrite(Error, dma.cmd = val)), - 0x04 => Err(StubWrite(Error, dma.status = val)), - 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), - 0x14 => Err(StubWrite(Error, dma.flags = val)), - 0x18 => Err(StubWrite(Error, dma.per_addr = val)), - 0x1c => Err(StubWrite(Error, dma.incr = val)), - _ => Err(Unexpected), + 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { + offset @ 0x00..=0x3f => { + let dma = &mut self.dma[(offset / 0x20) as usize]; + match offset % 0x20 { + 0x00 => Err(StubWrite(Error, dma.cmd = val)), + 0x04 => Err(StubWrite(Error, dma.status = val)), + 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), + 0x14 => Err(StubWrite(Error, dma.flags = val)), + 0x18 => Err(StubWrite(Error, dma.per_addr = val)), + 0x1c => Err(StubWrite(Error, dma.incr = val)), + _ => Err(Unexpected), + } } - } + _ => Ok(()), + }, _ => Err(Unexpected), } } diff --git a/clicky-core/src/devices/platform/pp/apb_dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs index d8fa2b61..86c68d05 100644 --- a/clicky-core/src/devices/platform/pp/apb_dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -38,7 +38,7 @@ impl Device for DmaChannel { #[derive(Debug)] pub struct ApbDma { - dma: [DmaChannel; 8], + dma: [DmaChannel; 4], master_control: u32, master_status: u32, req_status: u32, @@ -72,9 +72,9 @@ impl Device for ApbDma { 0x0 => "Master Control", 0x4 => "Master Status", 0x8 => "Req Status", - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - return Probe::from_device(&self.dma[id as usize], offset % 0x20); + 0x1000..=0x1fff => { + let offset = (offset - 0x1000) % 0x80; + return Probe::from_device(&self.dma[(offset / 0x20) as usize], offset % 0x20); } _ => return Probe::Unmapped, }; @@ -89,9 +89,9 @@ impl Memory for ApbDma { 0x0 => Err(StubRead(Error, self.master_control)), 0x4 => Err(StubRead(Error, self.master_status)), 0x8 => Err(StubRead(Error, self.req_status)), - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - let dma = &mut self.dma[id as usize]; + 0x1000..=0x1fff => { + let offset = (offset - 0x1000) % 0x80; + let dma = &mut self.dma[(offset / 0x20) as usize]; match offset % 0x20 { 0x00 => Err(StubRead(Error, dma.cmd)), 0x04 => Err(StubRead(Error, dma.status)), @@ -111,9 +111,9 @@ impl Memory for ApbDma { 0x0 => Err(StubWrite(Error, self.master_control = val)), 0x4 => Err(StubWrite(Error, self.master_status = val)), 0x8 => Err(StubWrite(Error, self.req_status = val)), - 0x1000..=0x10ff => { - let id = (offset - 0x1000) / 0x20; - let dma = &mut self.dma[id as usize]; + 0x1000..=0x1fff => { + let offset = (offset - 0x1000) % 0x80; + let dma = &mut self.dma[(offset / 0x20) as usize]; match offset % 0x20 { 0x00 => Err(StubWrite(Error, dma.cmd = val)), 0x04 => Err(StubWrite(Error, dma.status = val)), From 16906861ee76c53288f278676ae1da14ac12e619 Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Tue, 20 Oct 2026 20:05:44 +0200 Subject: [PATCH 8/9] ahb_dma: Implement CSR based on Tegra spec --- .../src/devices/platform/pp/ahb_dma.rs | 529 +++++++++++++++--- 1 file changed, 464 insertions(+), 65 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/ahb_dma.rs b/clicky-core/src/devices/platform/pp/ahb_dma.rs index 0ad0a363..b43967ce 100644 --- a/clicky-core/src/devices/platform/pp/ahb_dma.rs +++ b/clicky-core/src/devices/platform/pp/ahb_dma.rs @@ -1,15 +1,222 @@ +#![allow(non_camel_case_types)] + use crate::devices::prelude::*; +use bitfields::{bitfield, bitflag}; + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum TriggerSelection { + #[base] + SMP_30_same_as_bit_14 = 0, + SMP_31_same_as_bit_15 = 1, + HRQ2_XRQ_C = 2, + HRQ3_XRQ_D = 3, + HRQ4_N_A = 4, + HRQ5_N_A = 5, + HRQ6_TMR1 = 6, + HRQ7_TMR2 = 7, + AHB_0 = 8, + AHB_1 = 9, + AHB_2 = 10, + AHB_3 = 11, + SMP_28 = 12, + SMP_29 = 13, + SMP_30 = 14, + SMP_31 = 15, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum RequestSelection { + #[base] + CNTR_REQ = 0, + SMP_17 = 1, + SMP_18 = 2, + SMP_19 = 3, + SMP_20 = 4, + SMP_21 = 5, + SMP_22 = 6, + SMP_23 = 7, + SMP_24 = 8, + SMP_25 = 9, + SMP_26 = 10, + Host1x = 11, + SRQ0_N_A = 12, + SRQ1_N_A = 13, + SRQ0_XRQ_A = 14, + SRQ1_XRQ_B = 15, +} + +// Tegra 2 TRM 10.5.4.1 "AHBDMACHAN_CHANNEL_0_CSR_0" +#[bitfield(u32, order = msb)] +struct ChannelControl { + enb: bool, + + /// Interrupt Enable on transfer complete + ie_eoc: bool, + + /// Flush + fl_btwn: bool, + + hold: bool, + + /// 0 = XMB -> AHB + dir: bool, + + /// 0 = Multiple Block Transfer + once: bool, + + trig: bool, + + flow: bool, + + #[bits(4)] + trig_sel: TriggerSelection, + + #[bits(4)] + req_sel: RequestSelection, + + /// Number of words to transfer, minus one + #[bits(14)] + wcount: u16, + + #[bits(2)] + _reserved: u8, +} + +// Tegra 2 TRM 10.5.4.2 "AHBDMACHAN_CHANNEL_0_STA_0" +#[bitfield(u32, order = msb)] +struct ChannelStatus { + bsy: bool, + + /// End of transfer interrupt, write 1 to clear + is_eoc: bool, + + _reserved_29: bool, + + halt: bool, + + #[bits(12)] + _reserved_16: u16, + + /// Remaining words + #[bits(14)] + count: u16, + + #[bits(2)] + _reserved_0: u8, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum StrideSize { + #[base] + DISABLE = 0, + WORDS_4 = 1, + WORDS_8 = 2, + WORDS_16 = 3, + WORDS_32 = 4, + WORDS_64 = 5, + WORDS_128 = 6, + WORDS_256 = 7, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum AhbBurst { + #[base] + RSVD_0 = 0, + RSVD_1 = 1, + WORD = 2, + FOUR_WORD = 3, + EIGHT_WORD = 4, + RSVD_5 = 5, + RSVD_6 = 6, + RSVD_7 = 7, +} + +// Tegra 2 TRM 10.5.4.4 "AHBDMACHAN_CHANNEL_0_AHB_SEQ_0" +#[bitfield(u32, order = msb)] +struct AhbAddressSequencer { + /// Send interrupt to CPU (0 = send to COP) + intr_enb: bool, + + #[bits(3)] + _reserved_28: u8, + + /// Reverse endianness + ahb_data_swap: bool, + + #[bits(3, default = AhbBurst::WORD)] + ahb_burst: AhbBurst, + + #[bits(3)] + ahb_stride_size: StrideSize, + + _reserved_20: bool, + + /// Reload base address every time + dbl_buf: bool, + + ahb_addr_wrap: bool, + + /// Words skipped between strides + ahb_stride: u16, + + #[bits(2)] + _reserved_0: u8, +} + +// Tegra 2 TRM 10.5.4.6 "AHBDMACHAN_CHANNEL_0_XMB_SEQ_0" +#[bitfield(u32, order = msb)] +struct XmbAddressSequencer { + #[bits(4)] + _reserved_28: u8, + + /// Reverse endianness + xmb_data_swap: bool, + + #[bits(7)] + _reserved_20: u8, + + /// Reload base address every time + dbl_buf: bool, + + xmb_addr_wrap: bool, + + /// Words skipped between strides + xmb_stride: u16, + + #[bits(2)] + _reserved_0: u8, +} + #[derive(Debug, Default)] struct DmaChannel { label: Option<&'static str>, - cmd: u32, - status: u32, - ram_addr: u32, - flags: u32, - per_addr: u32, - incr: u32, + control: ChannelControl, + status: ChannelStatus, + ahb_ptr: u32, + ahb_seq: AhbAddressSequencer, + xmb_ptr: u32, + xmb_seq: XmbAddressSequencer, +} + +impl DmaChannel { + fn new(label: Option<&'static str>) -> DmaChannel { + DmaChannel { + label, + + control: ChannelControl::new(), + status: ChannelStatus::new(), + ahb_ptr: 0, + ahb_seq: AhbAddressSequencer::new(), + xmb_ptr: 0, + xmb_seq: XmbAddressSequencer::new(), + } + } } impl Device for DmaChannel { @@ -23,12 +230,12 @@ impl Device for DmaChannel { fn probe(&self, offset: u32) -> Probe { let reg = match offset { - 0x00 => "Cmd", - 0x04 => "Status", - 0x10 => "Ram Addr", - 0x14 => "Flags", - 0x18 => "Per Addr", - 0x1c => "Incr", + 0x00 => "CSR", + 0x04 => "STA", + 0x10 => "AHB_PTR", + 0x14 => "AHB_SEQ", + 0x18 => "XMB_PTR", + 0x1c => "XMB_SEQ", _ => return Probe::Unmapped, }; @@ -36,46 +243,241 @@ impl Device for DmaChannel { } } +impl Memory for DmaChannel { + fn r32(&mut self, offset: u32) -> MemResult { + match offset { + 0x00 => Ok(self.control.into_bits()), + 0x04 => Ok(self.status.into_bits()), + 0x10 => Ok(self.ahb_ptr), + 0x14 => Ok(self.ahb_seq.into_bits()), + 0x18 => Ok(self.xmb_ptr), + 0x1c => Ok(self.xmb_seq.into_bits()), + _ => Err(Unexpected), + } + } + + fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { + match offset { + 0x00 => { + self.control.write_bits(val); + if self.control.enb() { + return Err(StubWrite(Warn, ())); + } + Ok(()) + } + 0x04 => { + if ChannelStatus::from_bits(val).is_eoc() { + self.status.set_is_eoc(false); + } + Ok(()) + } + 0x10 => Ok(self.ahb_ptr = val), + 0x14 => Ok(self.ahb_seq.write_bits(val)), + 0x18 => Ok(self.xmb_ptr = val), + 0x1c => Ok(self.xmb_seq.write_bits(val)), + _ => Err(Unexpected), + } + } +} + +// Tegra 2 TRM 10.5.3.1 "AHBDMA_CMD_0" +#[bitfield(u32, order = msb)] +struct CommandRegister { + /// Global enable + gen: bool, + + #[bits(31)] + _reserved: u32, +} + +// Tegra 2 TRM 10.5.3.2 "AHBDMA_STA_0" +#[bitfield(u32, order = msb)] +struct StatusRegister { + #[bits(6)] + _reserved_26: u8, + + ch1_busy: bool, + ch0_busy: bool, + + #[bits(24)] + _reserved_0: u32, +} + +// Tegra 2 TRM 10.5.3.3 "AHBDMA_TX_REQ_0" +#[bitfield(u32, order = msb)] +struct TxRequestors { + smp_31: bool, + smp_30: bool, + smp_29: bool, + smp_28: bool, + + #[bits(2)] + _reserved_26: u8, + + /// End of transfer on channel 1 + ahb_1: bool, + /// End of transfer on channel 0 + ahb_0: bool, + + hrq7_tmr2: bool, + hrq6_tmr1: bool, + + #[bits(2)] + _reserved_20: u8, + + hrq3_xrq_d: bool, + hrq2_xrq_c: bool, + smp_31_same_as_31_bit: bool, + smp_30_same_as_30_bit: bool, + srq1_xrq_b: bool, + srq0_xrq_a: bool, + + #[bits(2)] + _reserved_12: u8, + + host1x: bool, + smp_26: bool, + smp_25: bool, + smp_24: bool, + smp_23: bool, + smp_22: bool, + smp_21: bool, + smp_20: bool, + smp_19: bool, + smp_18: bool, + smp_17: bool, + cntr_req: bool, +} + +// Tegra 2 TRM 10.5.3.4 "AHBDMA_COUNTER_0" +#[bitfield(u32, order = msb)] +struct CounterUsage { + #[bits(12)] + _reserved: u16, + + /// Throttle channel 1 on the counter + ch1_fl_cnt: bool, + /// Throttle channel 0 on the counter + ch0_fl_cnt: bool, + + cntr_en: bool, + + /// Qualify counter reload with the last word of a burst + prd_en: bool, + + /// Counter init/reload value + count_value: u16, +} + +// Tegra 2 TRM 10.5.3.5 "AHBDMA_IRQ_STA_CPU_0", 10.5.3.6 "AHBDMA_IRQ_STA_COP_0" +#[bitfield(u32, order = msb)] +struct IrqStatus { + #[bits(30)] + _reserved: u32, + + ch1: bool, + ch0: bool, +} + +// Tegra 2 TRM 10.5.3.7 "AHBDMA_IRQ_MASK_0", 10.5.3.8 "AHBDMA_IRQ_MASK_SET_0", +// 10.5.3.9 "AHBDMA_IRQ_MASK_CLR_0" +#[bitfield(u32, order = msb)] +struct IrqMask { + #[bits(30)] + _reserved: u32, + + ch1: bool, + ch0: bool, +} + +// Tegra 2 TRM 10.5.3.10 "AHBDMA_RDWR_COHERENCY_0" +#[bitfield(u32, order = msb)] +struct RdWrCoherency { + /// No AHB writes in flight + #[bits(default = true)] + mst_gizmo_wrque_empty: bool, + + #[bits(13)] + _reserved_18: u16, + + ch1_rds_active: bool, + ch0_rds_active: bool, + + #[bits(14)] + _reserved_2: u16, + + ch1_wrs_active: bool, + ch0_wrs_active: bool, +} + +// Tegra 2 TRM 10.5.3.11 "AHBDMA_TEST_BUS_0" +#[bitfield(u32, order = msb)] +struct TestBus { + #[bits(28)] + _reserved: u32, + + mstgizmo_wrque_ch1_pong_status: bool, + mstgizmo_wrque_ch1_ping_status: bool, + mstgizmo_wrque_ch0_pong_status: bool, + mstgizmo_wrque_ch0_ping_status: bool, +} + #[derive(Debug)] pub struct AhbDma { - dma: [DmaChannel; 8], - master_control: u32, - master_status: u32, - req_status: u32, + dma: [DmaChannel; 2], + command: CommandRegister, + status: StatusRegister, + tx_req: TxRequestors, + counter: CounterUsage, + irq_mask: IrqMask, + rdwr_coherency: RdWrCoherency, + test_bus: TestBus, } impl AhbDma { pub fn new() -> AhbDma { - let mut dma = AhbDma { - dma: Default::default(), - master_control: 0, - master_status: 0, - req_status: 0, - }; + AhbDma { + dma: [DmaChannel::new(Some("0")), DmaChannel::new(Some("1"))], + command: CommandRegister::new(), + status: StatusRegister::new(), + tx_req: TxRequestors::new(), + counter: CounterUsage::new(), + irq_mask: IrqMask::new(), + rdwr_coherency: RdWrCoherency::new(), + test_bus: TestBus::new(), + } + } - dma.dma[0].label = Some("0"); - dma.dma[1].label = Some("1"); - dma.dma[2].label = Some("2"); - dma.dma[3].label = Some("3"); - dma.dma[4].label = Some("4"); - dma.dma[5].label = Some("5"); - dma.dma[6].label = Some("6"); - dma.dma[7].label = Some("7"); + /// Masked end-of-transfer interrupts routed to the CPU (or the COP). + fn irq_status(&self, cpu: bool) -> IrqStatus { + let pending = + |ch: &DmaChannel, unmasked| unmasked && ch.status.is_eoc() && ch.ahb_seq.intr_enb() == cpu; - dma + let mut status = IrqStatus::new(); + status.set_ch0(pending(&self.dma[0], self.irq_mask.ch0())); + status.set_ch1(pending(&self.dma[1], self.irq_mask.ch1())); + status } } impl Device for AhbDma { fn kind(&self) -> &'static str { - "DMA Engine" + "AHB-DMA" } fn probe(&self, offset: u32) -> Probe { let reg = match offset { - 0x0 => "Master Control", - 0x4 => "Master Status", - 0x8 => "Req Status", + 0x00 => "AHBDMA_CMD", + 0x04 => "AHBDMA_STA", + 0x08 => "AHBDMA_TX_REQ", + 0x10 => "AHBDMA_COUNTER", + 0x14 => "AHBDMA_IRQ_STA_CPU", + 0x18 => "AHBDMA_IRQ_STA_COP", + 0x1c => "AHBDMA_IRQ_MASK", + 0x20 => "AHBDMA_IRQ_MASK_SET", + 0x24 => "AHBDMA_IRQ_MASK_CLR", + 0x28 => "AHBDMA_RDWR_COHERENCY", + 0x2c => "AHBDMA_TEST_BUS", 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { offset @ 0x00..=0x3f => { return Probe::from_device(&self.dma[(offset / 0x20) as usize], offset % 0x20); @@ -92,22 +494,18 @@ impl Device for AhbDma { impl Memory for AhbDma { fn r32(&mut self, offset: u32) -> MemResult { match offset { - 0x0 => Err(StubRead(Error, self.master_control)), - 0x4 => Err(StubRead(Error, self.master_status)), - 0x8 => Err(StubRead(Error, self.req_status)), + 0x00 => Ok(self.command.into_bits()), + 0x04 => Ok(self.status.into_bits()), + 0x08 => Ok(self.tx_req.into_bits()), + 0x10 => Ok(self.counter.into_bits()), + 0x14 => Ok(self.irq_status(true).into_bits()), + 0x18 => Ok(self.irq_status(false).into_bits()), + 0x1c => Ok(self.irq_mask.into_bits()), + 0x20 | 0x24 => Err(InvalidAccess), + 0x28 => Ok(self.rdwr_coherency.into_bits()), + 0x2c => Ok(self.test_bus.into_bits()), 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { - offset @ 0x00..=0x3f => { - let dma = &mut self.dma[(offset / 0x20) as usize]; - match offset % 0x20 { - 0x00 => Err(StubRead(Error, dma.cmd)), - 0x04 => Err(StubRead(Error, dma.status)), - 0x10 => Err(StubRead(Error, dma.ram_addr)), - 0x14 => Err(StubRead(Error, dma.flags)), - 0x18 => Err(StubRead(Error, dma.per_addr)), - 0x1c => Err(StubRead(Error, dma.incr)), - _ => Err(Unexpected), - } - } + offset @ 0x00..=0x3f => self.dma[(offset / 0x20) as usize].r32(offset % 0x20), _ => Ok(0), }, _ => Err(Unexpected), @@ -116,22 +514,23 @@ impl Memory for AhbDma { fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { match offset { - 0x0 => Err(StubWrite(Error, self.master_control = val)), - 0x4 => Err(StubWrite(Error, self.master_status = val)), - 0x8 => Err(StubWrite(Error, self.req_status = val)), + 0x00 => Ok(self.command.write_bits(val)), + 0x04 | 0x08 | 0x14 | 0x18 | 0x1c | 0x28 | 0x2c => Err(InvalidAccess), + 0x10 => Ok(self.counter.write_bits(val)), + 0x20 => { + let set = IrqMask::from_bits(val); + self.irq_mask.set_ch0(self.irq_mask.ch0() || set.ch0()); + self.irq_mask.set_ch1(self.irq_mask.ch1() || set.ch1()); + Ok(()) + } + 0x24 => { + let clr = IrqMask::from_bits(val); + self.irq_mask.set_ch0(self.irq_mask.ch0() && !clr.ch0()); + self.irq_mask.set_ch1(self.irq_mask.ch1() && !clr.ch1()); + Ok(()) + } 0x1000..=0x1fff => match (offset - 0x1000) % 0x80 { - offset @ 0x00..=0x3f => { - let dma = &mut self.dma[(offset / 0x20) as usize]; - match offset % 0x20 { - 0x00 => Err(StubWrite(Error, dma.cmd = val)), - 0x04 => Err(StubWrite(Error, dma.status = val)), - 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), - 0x14 => Err(StubWrite(Error, dma.flags = val)), - 0x18 => Err(StubWrite(Error, dma.per_addr = val)), - 0x1c => Err(StubWrite(Error, dma.incr = val)), - _ => Err(Unexpected), - } - } + offset @ 0x00..=0x3f => self.dma[(offset / 0x20) as usize].w32(offset % 0x20, val), _ => Ok(()), }, _ => Err(Unexpected), From 29da27600d7d7a4459fa6918eed8a871093debdd Mon Sep 17 00:00:00 2001 From: Jean THOMAS Date: Wed, 21 Oct 2026 08:59:35 +0200 Subject: [PATCH 9/9] apb_dma: Implement CSR based on Tegra spec and experiments --- .../src/devices/platform/pp/apb_dma.rs | 394 +++++++++++++++--- 1 file changed, 336 insertions(+), 58 deletions(-) diff --git a/clicky-core/src/devices/platform/pp/apb_dma.rs b/clicky-core/src/devices/platform/pp/apb_dma.rs index 86c68d05..7adab0c2 100644 --- a/clicky-core/src/devices/platform/pp/apb_dma.rs +++ b/clicky-core/src/devices/platform/pp/apb_dma.rs @@ -1,15 +1,217 @@ +#![allow(non_camel_case_types)] + use crate::devices::prelude::*; +use bitfields::{bitfield, bitflag}; + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum RequestSelection { + #[base] + REQ_0 = 0, + REQ_1 = 1, + IIS = 2, + REQ_3 = 3, + REQ_4 = 4, + REQ_5 = 5, + REQ_6 = 6, + REQ_7 = 7, + REQ_8 = 8, + REQ_9 = 9, + REQ_10 = 10, + LCD2 = 11, + REQ_12 = 12, + SDHC = 13, + REQ_14 = 14, + REQ_15 = 15, +} + +// Tegra 2 TRM 10.5.4.1 "AHBDMACHAN_CHANNEL_0_CSR_0" +#[bitfield(u32, order = msb)] +struct ChannelControl { + enb: bool, + + /// Interrupt Enable on transfer complete + ie_eoc: bool, + + /// Flush, doesn't stick + #[bits(access = ro)] + fl_btwn: bool, + + /// Doesn't stick + #[bits(access = ro)] + hold: bool, + + /// 0 = APB -> AHB + dir: bool, + + /// 0 = Multiple Block Transfer + once: bool, + + trig: bool, + + /// Only move data while the request line is asserted + flow: bool, + + /// Trigger sources are unknown on PP5020 + #[bits(4)] + trig_sel: u8, + + #[bits(4)] + req_sel: RequestSelection, + + /// Number of words to transfer, minus one + #[bits(14)] + wcount: u16, + + #[bits(2)] + _reserved: u8, +} + +// Tegra 2 TRM 10.5.4.2 "AHBDMACHAN_CHANNEL_0_STA_0" +#[bitfield(u32, order = msb)] +struct ChannelStatus { + bsy: bool, + + /// End of transfer interrupt, cleared by reading the register + is_eoc: bool, + + _reserved_29: bool, + + halt: bool, + + #[bits(12)] + _reserved_16: u16, + + /// Remaining words + #[bits(14)] + count: u16, + + #[bits(2)] + _reserved_0: u8, +} + +/// Reserved encodings transfer 8 words +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum AhbBurst { + #[base] + RSVD_0 = 0, + RSVD_1 = 1, + RSVD_2 = 2, + RSVD_3 = 3, + DMA_BURST_1WORDS = 4, + DMA_BURST_4WORDS = 5, + DMA_BURST_8WORDS = 6, + RSVD_7 = 7, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum AhbWrap { + #[base] + NO_WRAP = 0, + WRAP_ON_32WORDS = 1, + WRAP_ON_64WORDS = 2, + WRAP_ON_128WORDS = 3, + WRAP_ON_256WORDS = 4, + WRAP_ON_512WORDS = 5, + WRAP_ON_1024WORDS = 6, + WRAP_ON_2048WORDS = 7, +} + +// Tegra 2 TRM 11.2.4.4 "APBDMACHAN_CHANNEL_0_AHB_SEQ_0" +#[bitfield(u32, order = msb)] +struct AhbSequencer { + /// INTR_ENB, AHB_BUS_WIDTH and AHB_DATA_SWAP on Tegra, none of them stick + #[bits(5)] + _reserved_27: u8, + + #[bits(3)] + ahb_burst: AhbBurst, + + #[bits(4)] + _reserved_20: u8, + + /// Reload base address every other block + dbl_buf: bool, + + #[bits(3)] + ahb_addr_wrap: AhbWrap, + + _reserved_0: u16, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum BusWidth { + #[base] + BUS_WIDTH_8 = 0, + BUS_WIDTH_16 = 1, + BUS_WIDTH_32 = 2, + BUS_WIDTH_64 = 3, + BUS_WIDTH_128 = 4, + RSVD_5 = 5, + RSVD_6 = 6, + RSVD_7 = 7, +} + +#[bitflag(u8)] +#[derive(Debug, PartialEq)] +enum ApbWrap { + #[base] + NO_WRAP = 0, + WRAP_ON_1WORDS = 1, + WRAP_ON_2WORDS = 2, + WRAP_ON_4WORDS = 3, + WRAP_ON_8WORDS = 4, + WRAP_ON_16WORDS = 5, + WRAP_ON_32WORDS = 6, + WRAP_ON_64WORDS = 7, +} + +// Tegra 2 TRM 11.2.4.6 "APBDMACHAN_CHANNEL_0_APB_SEQ_0" +#[bitfield(u32, order = msb)] +struct ApbSequencer { + _reserved_31: bool, + + #[bits(3, default = BusWidth::BUS_WIDTH_32)] + apb_bus_width: BusWidth, + + #[bits(9)] + _reserved_19: u16, + + #[bits(3, default = ApbWrap::WRAP_ON_1WORDS)] + apb_addr_wrap: ApbWrap, + + _reserved_0: u16, +} + #[derive(Debug, Default)] struct DmaChannel { label: Option<&'static str>, - cmd: u32, - status: u32, - ram_addr: u32, - flags: u32, - per_addr: u32, - incr: u32, + control: ChannelControl, + status: ChannelStatus, + ahb_ptr: u32, + ahb_seq: AhbSequencer, + apb_ptr: u32, + apb_seq: ApbSequencer, +} + +impl DmaChannel { + fn new(label: Option<&'static str>) -> DmaChannel { + DmaChannel { + label, + + control: ChannelControl::new(), + status: ChannelStatus::new(), + ahb_ptr: 0, + ahb_seq: AhbSequencer::new(), + apb_ptr: 0x7000_0000, + apb_seq: ApbSequencer::new(), + } + } } impl Device for DmaChannel { @@ -23,12 +225,13 @@ impl Device for DmaChannel { fn probe(&self, offset: u32) -> Probe { let reg = match offset { - 0x00 => "Cmd", - 0x04 => "Status", - 0x10 => "Ram Addr", - 0x14 => "Flags", - 0x18 => "Per Addr", - 0x1c => "Incr", + 0x00 => "CSR", + 0x04 => "STA", + 0x08 | 0x0c => "APB_SEQ", + 0x10 => "AHB_PTR", + 0x14 => "AHB_SEQ", + 0x18 => "APB_PTR", + 0x1c => "APB_SEQ", _ => return Probe::Unmapped, }; @@ -36,42 +239,130 @@ impl Device for DmaChannel { } } +impl Memory for DmaChannel { + fn r32(&mut self, offset: u32) -> MemResult { + match offset { + 0x00 => Ok(self.control.into_bits()), + 0x04 => { + let val = self.status.into_bits(); + self.status.set_is_eoc(false); + Ok(val) + } + 0x08 | 0x0c | 0x1c => Ok(self.apb_seq.into_bits()), + 0x10 => Ok(self.ahb_ptr), + 0x14 => Ok(self.ahb_seq.into_bits()), + 0x18 => Ok(self.apb_ptr), + _ => Err(Unexpected), + } + } + + fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { + match offset { + 0x00 => { + self.control.write_bits(val); + if self.control.enb() { + return Err(StubWrite(Warn, ())); + } + Ok(()) + } + 0x04 => Ok(()), + 0x08 | 0x0c | 0x1c => Ok(self.apb_seq.write_bits(val)), + 0x10 => Ok(self.ahb_ptr = val & !0b11), + 0x14 => Ok(self.ahb_seq.write_bits(val)), + 0x18 => Ok(self.apb_ptr = (val & 0xfffc) | 0x7000_0000), + _ => Err(Unexpected), + } + } +} + +// Tegra 2 TRM 10.5.3.1 "AHBDMA_CMD_0" +#[bitfield(u32, order = msb)] +struct CommandRegister { + /// Global enable + gen: bool, + + #[bits(31)] + _reserved: u32, +} + +// Tegra 2 TRM 10.5.3.2 "AHBDMA_STA_0" +#[bitfield(u32, order = msb)] +struct StatusRegister { + #[bits(4)] + _reserved_28: u8, + + ch3_busy: bool, + ch2_busy: bool, + ch1_busy: bool, + ch0_busy: bool, + + #[bits(24)] + _reserved_0: u32, +} + +#[bitfield(u32, order = msb)] +struct RequestStatus { + _reserved: u16, + + req_15: bool, + req_14: bool, + sdhc: bool, + req_12: bool, + #[bits(default = true)] + lcd2: bool, + req_10: bool, + req_9: bool, + req_8: bool, + req_7: bool, + req_6: bool, + req_5: bool, + #[bits(default = true)] + req_4: bool, + #[bits(default = true)] + req_3: bool, + #[bits(default = true)] + iis: bool, + req_1: bool, + req_0: bool, +} + #[derive(Debug)] pub struct ApbDma { dma: [DmaChannel; 4], - master_control: u32, - master_status: u32, - req_status: u32, + command: CommandRegister, + status: StatusRegister, + req_status: RequestStatus, } impl ApbDma { pub fn new() -> ApbDma { - let mut dma = ApbDma { - dma: Default::default(), - master_control: 0, - master_status: 0, - req_status: 0, - }; - - dma.dma[0].label = Some("0"); - dma.dma[1].label = Some("1"); - dma.dma[2].label = Some("2"); - dma.dma[3].label = Some("3"); - - dma + ApbDma { + dma: [ + DmaChannel::new(Some("0")), + DmaChannel::new(Some("1")), + DmaChannel::new(Some("2")), + DmaChannel::new(Some("3")), + ], + command: CommandRegister::new(), + status: StatusRegister::new(), + req_status: RequestStatus::new(), + } } } impl Device for ApbDma { fn kind(&self) -> &'static str { - "DMA Engine" + "APB-DMA" } fn probe(&self, offset: u32) -> Probe { let reg = match offset { - 0x0 => "Master Control", - 0x4 => "Master Status", - 0x8 => "Req Status", + 0x0000..=0x0fff => match offset % 0x10 { + 0x0 => "CMD", + 0x4 => "STA", + 0x8 => "REQ_STATUS", + _ => return Probe::Unmapped, + }, 0x1000..=0x1fff => { let offset = (offset - 0x1000) % 0x80; return Probe::from_device(&self.dma[(offset / 0x20) as usize], offset % 0x20); @@ -86,21 +377,15 @@ impl Device for ApbDma { impl Memory for ApbDma { fn r32(&mut self, offset: u32) -> MemResult { match offset { - 0x0 => Err(StubRead(Error, self.master_control)), - 0x4 => Err(StubRead(Error, self.master_status)), - 0x8 => Err(StubRead(Error, self.req_status)), + 0x0000..=0x0fff => match offset % 0x10 { + 0x0 => Ok(self.command.into_bits()), + 0x4 => Ok(self.status.into_bits()), + 0x8 => Ok(self.req_status.into_bits()), + _ => Err(Unexpected), + }, 0x1000..=0x1fff => { let offset = (offset - 0x1000) % 0x80; - let dma = &mut self.dma[(offset / 0x20) as usize]; - match offset % 0x20 { - 0x00 => Err(StubRead(Error, dma.cmd)), - 0x04 => Err(StubRead(Error, dma.status)), - 0x10 => Err(StubRead(Error, dma.ram_addr)), - 0x14 => Err(StubRead(Error, dma.flags)), - 0x18 => Err(StubRead(Error, dma.per_addr)), - 0x1c => Err(StubRead(Error, dma.incr)), - _ => Err(Unexpected), - } + self.dma[(offset / 0x20) as usize].r32(offset % 0x20) } _ => Err(Unexpected), } @@ -108,21 +393,14 @@ impl Memory for ApbDma { fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { match offset { - 0x0 => Err(StubWrite(Error, self.master_control = val)), - 0x4 => Err(StubWrite(Error, self.master_status = val)), - 0x8 => Err(StubWrite(Error, self.req_status = val)), + 0x0000..=0x0fff => match offset % 0x10 { + 0x0 => Ok(self.command.write_bits(val)), + 0x4 | 0x8 => Err(InvalidAccess), + _ => Err(Unexpected), + }, 0x1000..=0x1fff => { let offset = (offset - 0x1000) % 0x80; - let dma = &mut self.dma[(offset / 0x20) as usize]; - match offset % 0x20 { - 0x00 => Err(StubWrite(Error, dma.cmd = val)), - 0x04 => Err(StubWrite(Error, dma.status = val)), - 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), - 0x14 => Err(StubWrite(Error, dma.flags = val)), - 0x18 => Err(StubWrite(Error, dma.per_addr = val)), - 0x1c => Err(StubWrite(Error, dma.incr = val)), - _ => Err(Unexpected), - } + self.dma[(offset / 0x20) as usize].w32(offset % 0x20, val) } _ => Err(Unexpected), }