diff --git a/src/gba/gba-io.c b/src/gba/gba-io.c index 93d161f1e..e3739c3c5 100644 --- a/src/gba/gba-io.c +++ b/src/gba/gba-io.c @@ -7,6 +7,31 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { 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; @@ -23,6 +48,39 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { 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) { switch (address) { case REG_DISPSTAT: diff --git a/src/gba/gba-io.h b/src/gba/gba-io.h index 9cb319b24..4712d32f4 100644 --- a/src/gba/gba-io.h +++ b/src/gba/gba-io.h @@ -142,6 +142,7 @@ enum GBAIORegisters { }; 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 diff --git a/src/gba/gba-memory.c b/src/gba/gba-memory.c index 26ff6554d..eaaa4a1ad 100644 --- a/src/gba/gba-memory.c +++ b/src/gba/gba-memory.c @@ -2,6 +2,7 @@ #include "gba-io.h" +#include #include #include @@ -13,6 +14,7 @@ static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 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 @@ void GBAMemoryInit(struct GBAMemory* memory) { 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 @@ void GBAStore32(struct ARMMemory* memory, uint32_t address, int32_t value) { 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 @@ void GBAAdjustWaitstates(struct GBAMemory* memory, uint16_t parameters) { 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); + } +} diff --git a/src/gba/gba-memory.h b/src/gba/gba-memory.h index 4ab73a498..bd2814484 100644 --- a/src/gba/gba-memory.h +++ b/src/gba/gba-memory.h @@ -59,6 +59,45 @@ enum { 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,8 +113,12 @@ struct GBAMemory { 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); uint16_t GBALoadU16(struct ARMMemory* memory, uint32_t address); @@ -88,4 +131,14 @@ 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 diff --git a/src/gba/gba.c b/src/gba/gba.c index 0033e3490..b2b18a63d 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -75,6 +75,11 @@ static void GBAProcessEvents(struct ARMBoard* board) { nextEvent = testEvent; } + testEvent = GBAMemoryProcessEvents(&gbaBoard->p->memory, cycles); + if (testEvent < nextEvent) { + nextEvent = testEvent; + } + board->cpu->cycles = 0; board->cpu->nextEvent = nextEvent; } @@ -98,10 +103,6 @@ void GBAWriteIE(struct GBA* gba, uint16_t value) { 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)) { GBALog(GBA_LOG_STUB, "Keypad interrupts not implemented"); } diff --git a/src/gba/gba.h b/src/gba/gba.h index c8c460ba4..9579c58a1 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -29,7 +29,8 @@ enum GBAError { }; enum GBALogLevel { - GBA_LOG_STUB + GBA_LOG_STUB, + GBA_LOG_WARN }; struct GBABoard {