gameboy-rust/docs/hardware/dma.md
0xrsydn c836944c2e feat(power): implement keypad-wake STOP and BIOS Stop service
Separate Running, Halt, and Stop states. Complete the entering instruction before freezing CPU, DMA, timers, display, and externally supplied clock advances. Wake STOP through the live enabled keypad condition without creating a clock-off IF request.

Implement original ARM SWI 0x03, preserve caller state, and expose zero-cycle StopIdle plus stopped frame-runner results. Retain DMA state, timer phase, and captured frames across sleep. Document keypad-only wake and unverified oscillator/edge timing.

Add core, BIOS, and demo regression tests; preserve BIOS-only access and failed-store isolation. Validated on Darwin arm64 with debug/release workspace and core/demo suites, Clippy, rustdoc, ten native window smoke tests, and terminal demos.
2026-10-05 18:28:23 +07:00

3.9 KiB
Raw Blame History

Direct memory access (DMA)

Four DMA channels occupy 0x040000b0..0x040000df. Each channel has a 12-byte register block:

Offset Register Behavior
+0 SAD Write-only initial source address
+4 DAD Write-only initial destination address
+8 CNT_L Write-only halfword/word count
+10 CNT_H Read/write address controls, repeat, width, timing, IRQ, and enable

Byte, halfword, and word register writes are supported. A combined count/control write sets the count before enabling DMA. Control masks are 0xf7e0 for DMA0–2 and 0xffe0 for DMA3. Source addresses use 27 bits on DMA0 and 28 bits on DMA1–3. Destination addresses use 27 bits on DMA0–2 and 28 bits on DMA3. Initial addresses align down to the selected halfword or word width. DMA0–2 use 14-bit counts; DMA3 uses 16-bit counts. Zero means 16,384 or 65,536 data units, respectively. Write-only register reads return zero as a placeholder, not hardware open-bus behavior.

A rising enable bit copies the programmed addresses and count into internal transfer state. Register writes alone do not move data. Machine::step() services one halfword or word at a time. Source modes are increment, decrement, or fixed. Destination modes also include increment/reload. Game Pak source addresses increment regardless of the selected source mode. Writes to programmed addresses/count do not alter an active block's internal pointers or remaining count. Disable the channel before changing its width, address modes, or start timing; live reconfiguration is not hardware-validated.

Supported triggers:

  • Immediate: ready after the enabling instruction; repeat does not keep it running.
  • VBlank: ready on entry to line 160.
  • HBlank: ready at HBlank entry on visible lines 0–159 only.

Blanking triggers do not depend on DISPSTAT IRQ enables or forced blank. Enabling during an existing blank period waits for the next edge. A busy channel ignores additional edges. Pending requests are not a queue. On repeated blanking transfers, completion reloads the programmed count and optionally the destination. The source continues from its current position. Non-repeated completion clears the enable bit. Clearing enable manually cancels a waiting or active transfer.

DMA0 has highest priority, followed by DMA1, DMA2, and DMA3. A higher-priority request can interrupt a lower-priority transfer between data units. While the system clock runs, the CPU remains paused while any channel is ready; display and timer clocks continue. STOP freezes DMA before transfer selection, including diagnostics, and retains pending transfer state until wake-up. Completion with local IRQ enable latches IF bits 8–11, independently of IE, IME, and CPSR.I. CPU delivery waits until no DMA is ready. IRQ entry does not acknowledge IF.

Timing uses independent source and destination bus costs, including WAITCNT and 128 KiB ROM boundaries. The first unit uses non-sequential accesses plus two internal cycles. Later units use sequential accesses. Each step reports zero code cycles. Overlapping transfers read each unit after the preceding write; they are not bulk host copies. Startup scheduling delays, channel-resumption costs, CPU fetch-state effects, and display-bus contention remain unmodeled.

Special timing modes (sound FIFO/video capture), Game Pak DRQ, and prohibited source mode 3 return DmaError::UnsupportedControl. DMA reads below work RAM, including BIOS reads, return DmaError::UnsupportedSource rather than exposing BIOS bytes. DMA writes to DMA registers return DmaError::RegisterDestination; self-modifying transfers are not supported. Other accesses use the existing memory map, alignment checks, mirrors, and I/O write rules. Unmapped memory and cartridge writes remain diagnostics, not hardware open-bus/latch behavior. A failed unit changes no memory, device state, or clock. Earlier units remain committed; the failed unit remains pending.