all repos — mgba @ e88d1775827589a15eb41511802a0b6fb16dec1e

mGBA Game Boy Advance Emulator

Copy GBA.js DMA implementation
Jeffrey Pfau jeffrey@endrift.com
Tue, 16 Apr 2013 23:13:52 -0700
commit

e88d1775827589a15eb41511802a0b6fb16dec1e

parent

20622b6135056581a6fc8ed2d274eca8cf6daf84

6 files changed, 314 insertions(+), 5 deletions(-)

jump to
M src/gba/gba-io.csrc/gba/gba-io.c

@@ -7,6 +7,31 @@ switch (address) {

case REG_DISPSTAT: GBAVideoWriteDISPSTAT(&gba->video, value); break; + case REG_DMA0CNT_LO: + GBAMemoryWriteDMACNT_LO(&gba->memory, 0, value); + break; + case REG_DMA0CNT_HI: + GBAMemoryWriteDMACNT_HI(&gba->memory, 0, value); + break; + case REG_DMA1CNT_LO: + GBAMemoryWriteDMACNT_LO(&gba->memory, 1, value); + break; + case REG_DMA1CNT_HI: + GBAMemoryWriteDMACNT_HI(&gba->memory, 1, value); + break; + case REG_DMA2CNT_LO: + GBAMemoryWriteDMACNT_LO(&gba->memory, 2, value); + break; + case REG_DMA2CNT_HI: + GBAMemoryWriteDMACNT_HI(&gba->memory, 2, value); + break; + case REG_DMA3CNT_LO: + GBAMemoryWriteDMACNT_LO(&gba->memory, 3, value); + break; + case REG_DMA3CNT_HI: + GBAMemoryWriteDMACNT_HI(&gba->memory, 3, value); + break; + case REG_WAITCNT: GBAAdjustWaitstates(&gba->memory, value); break;

@@ -21,6 +46,39 @@ GBALog(GBA_LOG_STUB, "Stub I/O register write: %03x", address);

break; } gba->memory.io[address >> 1] = value; +} + +void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) { + switch (address) { + case REG_DMA0SAD_LO: + GBAMemoryWriteDMASAD(&gba->memory, 0, value); + break; + case REG_DMA0DAD_LO: + GBAMemoryWriteDMADAD(&gba->memory, 0, value); + break; + case REG_DMA1SAD_LO: + GBAMemoryWriteDMASAD(&gba->memory, 1, value); + break; + case REG_DMA1DAD_LO: + GBAMemoryWriteDMADAD(&gba->memory, 1, value); + break; + case REG_DMA2SAD_LO: + GBAMemoryWriteDMASAD(&gba->memory, 2, value); + break; + case REG_DMA2DAD_LO: + GBAMemoryWriteDMADAD(&gba->memory, 2, value); + break; + case REG_DMA3SAD_LO: + GBAMemoryWriteDMASAD(&gba->memory, 3, value); + break; + case REG_DMA3DAD_LO: + GBAMemoryWriteDMADAD(&gba->memory, 3, value); + break; + default: + GBAIOWrite(gba, address, value & 0xFFFF); + GBAIOWrite(gba, address | 2, value >> 16); + break; + } } uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
M src/gba/gba-io.hsrc/gba/gba-io.h

@@ -142,6 +142,7 @@ REG_HALTCNT = 0x301

}; void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value); +void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value); uint16_t GBAIORead(struct GBA* gba, uint32_t address); #endif
M src/gba/gba-memory.csrc/gba/gba-memory.c

@@ -2,6 +2,7 @@ #include "gba-memory.h"

#include "gba-io.h" +#include <limits.h> #include <string.h> #include <sys/mman.h>

@@ -13,6 +14,7 @@ static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 };

static const char GBA_BASE_WAITSTATES_SEQ[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 }; static const char GBA_ROM_WAITSTATES[] = { 4, 3, 2, 8 }; static const char GBA_ROM_WAITSTATES_SEQ[] = { 2, 1, 4, 1, 8, 1 }; +static const int DMA_OFFSET[] = { 1, -1, 0, 1 }; void GBAMemoryInit(struct GBAMemory* memory) { memory->d.load32 = GBALoad32;

@@ -29,6 +31,7 @@ memory->wram = mmap(0, SIZE_WORKING_RAM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);

memory->iwram = mmap(0, SIZE_WORKING_IRAM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); memory->rom = 0; memset(memory->io, 0, sizeof(memory->io)); + memset(memory->dma, 0, sizeof(memory->dma)); if (!memory->wram || !memory->iwram) { GBAMemoryDeinit(memory);

@@ -281,6 +284,7 @@ case BASE_WORKING_IRAM:

gbaMemory->iwram[(address & (SIZE_WORKING_IRAM - 1)) >> 2] = value; break; case BASE_IO: + GBAIOWrite32(gbaMemory->p, address & (SIZE_IO - 1), value); break; case BASE_PALETTE_RAM: break;

@@ -392,3 +396,194 @@

memory->d.activePrefetchCycles32 = memory->waitstates32[memory->activeRegion]; memory->d.activePrefetchCycles16 = memory->waitstates16[memory->activeRegion]; } + +int32_t GBAMemoryProcessEvents(struct GBAMemory* memory, int32_t cycles) { + struct GBADMA* dma; + int32_t test = INT_MAX; + + dma = &memory->dma[0]; + dma->nextIRQ -= cycles; + if (dma->enable && dma->doIrq && dma->nextIRQ) { + if (dma->nextIRQ <= 0) { + dma->nextIRQ = INT_MAX; + GBARaiseIRQ(memory->p, IRQ_DMA0); + } else if (dma->nextIRQ < test) { + test = dma->nextIRQ; + } + } + + dma = &memory->dma[1]; + dma->nextIRQ -= cycles; + if (dma->enable && dma->doIrq && dma->nextIRQ) { + if (dma->nextIRQ <= 0) { + dma->nextIRQ = INT_MAX; + GBARaiseIRQ(memory->p, IRQ_DMA1); + } else if (dma->nextIRQ < test) { + test = dma->nextIRQ; + } + } + + dma = &memory->dma[2]; + dma->nextIRQ -= cycles; + if (dma->enable && dma->doIrq && dma->nextIRQ) { + if (dma->nextIRQ <= 0) { + dma->nextIRQ = INT_MAX; + GBARaiseIRQ(memory->p, IRQ_DMA2); + } else if (dma->nextIRQ < test) { + test = dma->nextIRQ; + } + } + + dma = &memory->dma[3]; + dma->nextIRQ -= cycles; + if (dma->enable && dma->doIrq && dma->nextIRQ) { + if (dma->nextIRQ <= 0) { + dma->nextIRQ = INT_MAX; + GBARaiseIRQ(memory->p, IRQ_DMA3); + } else if (dma->nextIRQ < test) { + test = dma->nextIRQ; + } + } + + return test; +} + +void GBAMemoryWriteDMASAD(struct GBAMemory* memory, int dma, uint32_t address) { + memory->dma[dma].source = address & 0xFFFFFFFE; +} + +void GBAMemoryWriteDMADAD(struct GBAMemory* memory, int dma, uint32_t address) { + memory->dma[dma].dest = address & 0xFFFFFFFE; +} + +void GBAMemoryWriteDMACNT_LO(struct GBAMemory* memory, int dma, uint16_t count) { + memory->dma[dma].count = count ? count : (dma == 3 ? 0x10000 : 0x4000); +} + +void GBAMemoryWriteDMACNT_HI(struct GBAMemory* memory, int dma, uint16_t control) { + struct GBADMA* currentDma = &memory->dma[dma]; + int wasEnabled = currentDma->enable; + currentDma->packed = control; + currentDma->nextIRQ = 0; + + if (currentDma->drq) { + GBALog(GBA_LOG_STUB, "DRQ not implemented"); + } + + if (!wasEnabled && currentDma->enable) { + currentDma->nextSource = currentDma->source; + currentDma->nextDest = currentDma->dest; + currentDma->nextCount = currentDma->count; + GBAMemoryScheduleDMA(memory, dma, currentDma); + } +}; + +void GBAMemoryScheduleDMA(struct GBAMemory* memory, int number, struct GBADMA* info) { + switch (info->timing) { + case DMA_TIMING_NOW: + GBAMemoryServiceDMA(memory, number, info); + break; + case DMA_TIMING_HBLANK: + // Handled implicitly + break; + case DMA_TIMING_VBLANK: + // Handled implicitly + break; + case DMA_TIMING_CUSTOM: + switch (number) { + case 0: + GBALog(GBA_LOG_WARN, "Discarding invalid DMA0 scheduling"); + break; + case 1: + case 2: + //this.cpu.irq.audio.scheduleFIFODma(number, info); + break; + case 3: + //this.cpu.irq.video.scheduleVCaptureDma(dma, info); + break; + } + } +} + +void GBAMemoryRunHblankDMAs(struct GBAMemory* memory) { + struct GBADMA* dma; + int i; + for (i = 0; i < 4; ++i) { + dma = &memory->dma[i]; + if (dma->enable && dma->timing == DMA_TIMING_HBLANK) { + GBAMemoryServiceDMA(memory, i, dma); + } + } +} + +void GBAMemoryRunVblankDMAs(struct GBAMemory* memory) { + struct GBADMA* dma; + int i; + for (i = 0; i < 4; ++i) { + dma = &memory->dma[i]; + if (dma->enable && dma->timing == DMA_TIMING_VBLANK) { + GBAMemoryServiceDMA(memory, i, dma); + } + } +} + +void GBAMemoryServiceDMA(struct GBAMemory* memory, int number, struct GBADMA* info) { + if (!info->enable) { + // There was a DMA scheduled that got canceled + return; + } + + uint32_t width = info->width ? 4 : 2; + int sourceOffset = DMA_OFFSET[info->srcControl] * width; + int destOffset = DMA_OFFSET[info->dstControl] * width; + int32_t wordsRemaining = info->nextCount; + uint32_t source = info->nextSource; + uint32_t dest = info->nextDest; + uint32_t sourceRegion = source >> BASE_OFFSET; + uint32_t destRegion = dest >> BASE_OFFSET; + + if (width == 4) { + int32_t word; + source &= 0xFFFFFFFC; + dest &= 0xFFFFFFFC; + while (wordsRemaining--) { + word = GBALoad32(&memory->d, source); + GBAStore32(&memory->d, dest, word); + source += sourceOffset; + dest += destOffset; + } + } else { + uint16_t word; + while (wordsRemaining--) { + word = GBALoadU16(&memory->d, source); + GBAStore16(&memory->d, dest, word); + source += sourceOffset; + dest += destOffset; + } + } + + if (info->doIrq) { + info->nextIRQ = memory->p->cpu.cycles + 2; + info->nextIRQ += (width == 4 ? memory->waitstates32[sourceRegion] + memory->waitstates32[destRegion] + : memory->waitstates16[sourceRegion] + memory->waitstates16[destRegion]); + info->nextIRQ += (info->count - 1) * (width == 4 ? memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion] + : memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]); + } + + info->nextSource = source; + info->nextDest = dest; + info->nextCount = wordsRemaining; + + if (!info->repeat) { + info->enable = 0; + + // Clear the enable bit in memory + memory->io[(REG_DMA0CNT_HI + number * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0; + } else { + info->nextCount = info->count; + if (info->dstControl == DMA_INCREMENT_RELOAD) { + info->nextDest = info->dest; + } + GBAMemoryScheduleDMA(memory, number, info); + } +}
M src/gba/gba-memory.hsrc/gba/gba-memory.h

@@ -59,6 +59,45 @@ OFFSET_MASK = 0x00FFFFFF,

BASE_OFFSET = 24 }; +enum DMAControl { + DMA_INCREMENT = 0, + DMA_DECREMENT = 1, + DMA_FIXED = 2, + DMA_INCREMENT_RELOAD = 3 +}; + +enum DMATiming { + DMA_TIMING_NOW = 0, + DMA_TIMING_VBLANK = 1, + DMA_TIMING_HBLANK = 2, + DMA_TIMING_CUSTOM = 3 +}; + +struct GBADMA { + union { + struct { + int : 5; + enum DMAControl dstControl : 2; + enum DMAControl srcControl : 2; + unsigned repeat : 1; + unsigned width : 1; + unsigned drq : 1; + enum DMATiming timing : 2; + unsigned doIrq : 1; + unsigned enable : 1; + }; + uint16_t packed; + }; + + uint32_t source; + uint32_t dest; + int32_t count; + uint32_t nextSource; + uint32_t nextDest; + int32_t nextCount; + int32_t nextIRQ; +}; + struct GBAMemory { struct ARMMemory d; struct GBA* p;

@@ -74,7 +113,11 @@ char waitstates16[256];

char waitstatesSeq32[256]; char waitstatesSeq16[256]; int activeRegion; + + struct GBADMA dma[4]; }; + +int32_t GBAMemoryProcessEvents(struct GBAMemory* memory, int32_t cycles); int32_t GBALoad32(struct ARMMemory* memory, uint32_t address); int16_t GBALoad16(struct ARMMemory* memory, uint32_t address);

@@ -87,5 +130,15 @@ void GBAStore16(struct ARMMemory* memory, uint32_t address, int16_t value);

void GBAStore8(struct ARMMemory* memory, uint32_t address, int8_t value); void GBAAdjustWaitstates(struct GBAMemory* memory, uint16_t parameters); + +void GBAMemoryWriteDMASAD(struct GBAMemory* memory, int dma, uint32_t address); +void GBAMemoryWriteDMADAD(struct GBAMemory* memory, int dma, uint32_t address); +void GBAMemoryWriteDMACNT_LO(struct GBAMemory* memory, int dma, uint16_t count); +void GBAMemoryWriteDMACNT_HI(struct GBAMemory* memory, int dma, uint16_t control); + +void GBAMemoryScheduleDMA(struct GBAMemory* memory, int number, struct GBADMA* info); +void GBAMemoryServiceDMA(struct GBAMemory* memory, int number, struct GBADMA* info); +void GBAMemoryRunHblankDMAs(struct GBAMemory* memory); +void GBAMemoryRunVblankDMAs(struct GBAMemory* memory); #endif
M src/gba/gba.csrc/gba/gba.c

@@ -75,6 +75,11 @@ if (testEvent < nextEvent) {

nextEvent = testEvent; } + testEvent = GBAMemoryProcessEvents(&gbaBoard->p->memory, cycles); + if (testEvent < nextEvent) { + nextEvent = testEvent; + } + board->cpu->cycles = 0; board->cpu->nextEvent = nextEvent; }

@@ -96,10 +101,6 @@ }

if (value & (1 << IRQ_SIO)) { GBALog(GBA_LOG_STUB, "SIO interrupts not implemented"); - } - - if (value & ((1 << IRQ_DMA0) | (1 << IRQ_DMA1) | (1 << IRQ_DMA2) | (1 << IRQ_DMA3))) { - GBALog(GBA_LOG_STUB, "DMA interrupts not implemented"); } if (value & (1 << IRQ_KEYPAD)) {
M src/gba/gba.hsrc/gba/gba.h

@@ -29,7 +29,8 @@ GBA_OUT_OF_MEMORY = -1

}; enum GBALogLevel { - GBA_LOG_STUB + GBA_LOG_STUB, + GBA_LOG_WARN }; struct GBABoard {