GBA DMA: Move DMAs to using absolute timing

This commit is contained in:
Jeffrey Pfau 2016-12-13 19:11:06 -08:00
parent ad85acab75
commit 1c93b75b7e
6 changed files with 41 additions and 68 deletions

View File

@ -68,6 +68,7 @@ Misc:
- Debugger: Add functions for read- or write-only watchpoints - Debugger: Add functions for read- or write-only watchpoints
- GB Memory: Reset ROM bank when loading a ROM - GB Memory: Reset ROM bank when loading a ROM
- GBA DMA: Refactor DMA out of memory.c - GBA DMA: Refactor DMA out of memory.c
- GBA DMA: Move DMAs to using absolute timing
0.5.1: (2016-10-05) 0.5.1: (2016-10-05)
Bugfixes: Bugfixes:

View File

@ -109,6 +109,7 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA*
return; return;
} }
info->reg = GBADMARegisterSetDestControl(info->reg, DMA_FIXED); info->reg = GBADMARegisterSetDestControl(info->reg, DMA_FIXED);
info->reg = GBADMARegisterSetWidth(info->reg, 1);
} }
void GBAAudioWriteSOUND1CNT_LO(struct GBAAudio* audio, uint16_t value) { void GBAAudioWriteSOUND1CNT_LO(struct GBAAudio* audio, uint16_t value) {
@ -232,11 +233,9 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) {
struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource];
if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) {
dma->when = mTimingCurrentTime(&audio->p->timing) - cycles;
dma->nextCount = 4; dma->nextCount = 4;
dma->nextEvent = 0; GBADMASchedule(audio->p, channel->dmaSource, dma);
dma->reg = GBADMARegisterSetWidth(dma->reg, 1);
dma->reg = GBADMARegisterSetDestControl(dma->reg, 2);
GBADMAUpdate(audio->p, -cycles);
} else { } else {
channel->dmaSource = 0; channel->dmaSource = 0;
} }

View File

@ -26,7 +26,6 @@ void GBADMAReset(struct GBA* gba) {
int i; int i;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
gba->memory.dma[i].count = 0x4000; gba->memory.dma[i].count = 0x4000;
gba->memory.dma[i].nextEvent = INT_MAX;
} }
gba->memory.dma[3].count = 0x10000; gba->memory.dma[3].count = 0x10000;
gba->memory.activeDMA = -1; gba->memory.activeDMA = -1;
@ -83,7 +82,6 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) {
if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) { if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) {
currentDma->nextSource = currentDma->source; currentDma->nextSource = currentDma->source;
currentDma->nextDest = currentDma->dest; currentDma->nextDest = currentDma->dest;
currentDma->nextCount = currentDma->count;
GBADMASchedule(gba, dma, currentDma); GBADMASchedule(gba, dma, currentDma);
} }
// If the DMA has already occurred, this value might have changed since the function started // If the DMA has already occurred, this value might have changed since the function started
@ -91,27 +89,20 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) {
}; };
void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) { void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) {
info->hasStarted = 0;
switch (GBADMARegisterGetTiming(info->reg)) { switch (GBADMARegisterGetTiming(info->reg)) {
case DMA_TIMING_NOW: case DMA_TIMING_NOW:
info->nextEvent = 2 + 1; // XXX: Account for I cycle when writing info->when = mTimingCurrentTime(&gba->timing) + 2 + 1; // XXX: Account for I cycle when writing
info->scheduledAt = mTimingCurrentTime(&gba->timing); info->nextCount = info->count;
GBADMAUpdate(gba, 0);
break; break;
case DMA_TIMING_HBLANK: case DMA_TIMING_HBLANK:
// Handled implicitly
info->nextEvent = INT_MAX;
break;
case DMA_TIMING_VBLANK: case DMA_TIMING_VBLANK:
// Handled implicitly // Handled implicitly
info->nextEvent = INT_MAX; return;
break;
case DMA_TIMING_CUSTOM: case DMA_TIMING_CUSTOM:
info->nextEvent = INT_MAX;
switch (number) { switch (number) {
case 0: case 0:
mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling"); mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling");
break; return;
case 1: case 1:
case 2: case 2:
GBAAudioScheduleFifoDma(&gba->audio, number, info); GBAAudioScheduleFifoDma(&gba->audio, number, info);
@ -121,76 +112,64 @@ void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) {
break; break;
} }
} }
GBADMAUpdate(gba);
} }
void GBADMARunHblank(struct GBA* gba, int32_t cycles) { void GBADMARunHblank(struct GBA* gba, int32_t cycles) {
struct GBAMemory* memory = &gba->memory; struct GBAMemory* memory = &gba->memory;
struct GBADMA* dma; struct GBADMA* dma;
bool dmaSeen = false;
if (memory->activeDMA >= 0) {
GBADMAUpdate(gba, mTimingCurrentTime(&gba->timing) - memory->dma[memory->activeDMA].scheduledAt);
}
int i; int i;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
dma = &memory->dma[i]; dma = &memory->dma[i];
if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_HBLANK) { if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_HBLANK && !dma->hasStarted) {
dma->nextEvent = 2 + cycles; dma->when = mTimingCurrentTime(&gba->timing) + 2 + cycles;
dma->scheduledAt = mTimingCurrentTime(&gba->timing); dma->nextCount = dma->count;
dmaSeen = true;
} }
} }
if (dmaSeen) { GBADMAUpdate(gba);
GBADMAUpdate(gba, 0);
}
} }
void GBADMARunVblank(struct GBA* gba, int32_t cycles) { void GBADMARunVblank(struct GBA* gba, int32_t cycles) {
struct GBAMemory* memory = &gba->memory; struct GBAMemory* memory = &gba->memory;
struct GBADMA* dma; struct GBADMA* dma;
bool dmaSeen = false;
if (memory->activeDMA >= 0) {
GBADMAUpdate(gba, mTimingCurrentTime(&gba->timing) - memory->dma[memory->activeDMA].scheduledAt);
}
int i; int i;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
dma = &memory->dma[i]; dma = &memory->dma[i];
if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_VBLANK) { if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_VBLANK && !dma->hasStarted) {
dma->nextEvent = 2 + cycles; dma->when = mTimingCurrentTime(&gba->timing) + 2 + cycles;
dma->scheduledAt = mTimingCurrentTime(&gba->timing); dma->nextCount = dma->count;
dmaSeen = true;
} }
} }
if (dmaSeen) { GBADMAUpdate(gba);
GBADMAUpdate(gba, 0);
}
} }
void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) {
UNUSED(timing); UNUSED(timing);
UNUSED(cyclesLate);
struct GBA* gba = context; struct GBA* gba = context;
struct GBAMemory* memory = &gba->memory; struct GBAMemory* memory = &gba->memory;
struct GBADMA* dma = &memory->dma[memory->activeDMA]; struct GBADMA* dma = &memory->dma[memory->activeDMA];
dma->nextEvent = -cyclesLate;
GBADMAService(gba, memory->activeDMA, dma); GBADMAService(gba, memory->activeDMA, dma);
} }
void GBADMAUpdate(struct GBA* gba, int32_t cycles) { void GBADMAUpdate(struct GBA* gba) {
int i; int i;
struct GBAMemory* memory = &gba->memory; struct GBAMemory* memory = &gba->memory;
memory->activeDMA = -1; memory->activeDMA = -1;
uint32_t currentTime = mTimingCurrentTime(&gba->timing);
for (i = 3; i >= 0; --i) { for (i = 3; i >= 0; --i) {
struct GBADMA* dma = &memory->dma[i]; struct GBADMA* dma = &memory->dma[i];
if (dma->nextEvent != INT_MAX) { if (GBADMARegisterIsEnable(dma->reg) && dma->nextCount) {
dma->nextEvent -= cycles; if (dma->when < currentTime) {
if (GBADMARegisterIsEnable(dma->reg)) { dma->when = currentTime;
memory->activeDMA = i;
} }
memory->activeDMA = i;
} }
} }
if (memory->activeDMA >= 0) { if (memory->activeDMA >= 0) {
mTimingDeschedule(&gba->timing, &memory->dmaEvent); mTimingDeschedule(&gba->timing, &memory->dmaEvent);
mTimingSchedule(&gba->timing, &memory->dmaEvent, memory->dma[memory->activeDMA].nextEvent); mTimingSchedule(&gba->timing, &memory->dmaEvent, memory->dma[memory->activeDMA].when - currentTime);
} else { } else {
gba->cpuBlocked = false; gba->cpuBlocked = false;
} }
@ -218,10 +197,9 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
cycles += memory->waitstatesNonseq16[sourceRegion] + memory->waitstatesNonseq16[destRegion]; cycles += memory->waitstatesNonseq16[sourceRegion] + memory->waitstatesNonseq16[destRegion];
} }
if (info->hasStarted < 1) { if (info->hasStarted < 1) {
info->hasStarted = wordsRemaining; info->hasStarted = info->count << 1;
info->nextEvent = 0; info->when = mTimingCurrentTime(&gba->timing) + cycles;
info->scheduledAt = mTimingCurrentTime(&gba->timing); GBADMAUpdate(gba);
GBADMAUpdate(gba, -cycles);
return; return;
} }
info->hasStarted = 2; info->hasStarted = 2;
@ -234,7 +212,7 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]; cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion];
} }
} }
info->nextEvent += cycles; info->when += cycles;
gba->performingDMA = 1 | (number << 1); gba->performingDMA = 1 | (number << 1);
uint32_t word; uint32_t word;
@ -267,27 +245,23 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
gba->performingDMA = 0; gba->performingDMA = 0;
if (!wordsRemaining) { if (!wordsRemaining) {
info->hasStarted = 0;
if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) { if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) {
info->reg = GBADMARegisterClearEnable(info->reg); info->reg = GBADMARegisterClearEnable(info->reg);
info->nextEvent = INT_MAX;
// Clear the enable bit in memory // Clear the enable bit in memory
memory->io[(REG_DMA0CNT_HI + number * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0; memory->io[(REG_DMA0CNT_HI + number * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0;
} else { }
info->nextCount = info->count;
if (GBADMARegisterGetDestControl(info->reg) == DMA_INCREMENT_RELOAD) { if (GBADMARegisterGetDestControl(info->reg) == DMA_INCREMENT_RELOAD) {
info->nextDest = info->dest; info->nextDest = info->dest;
} }
GBADMASchedule(gba, number, info);
}
if (GBADMARegisterIsDoIRQ(info->reg)) { if (GBADMARegisterIsDoIRQ(info->reg)) {
GBARaiseIRQ(gba, IRQ_DMA0 + number); GBARaiseIRQ(gba, IRQ_DMA0 + number);
} }
} else { } else {
info->nextDest = dest; info->nextDest = dest;
info->nextCount = wordsRemaining;
info->scheduledAt = mTimingCurrentTime(&gba->timing);
} }
info->nextCount = wordsRemaining;
info->nextSource = source; info->nextSource = source;
GBADMAUpdate(gba, 0); GBADMAUpdate(gba);
} }

View File

@ -21,6 +21,6 @@ struct GBADMA;
void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info); void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info);
void GBADMARunHblank(struct GBA* gba, int32_t cycles); void GBADMARunHblank(struct GBA* gba, int32_t cycles);
void GBADMARunVblank(struct GBA* gba, int32_t cycles); void GBADMARunVblank(struct GBA* gba, int32_t cycles);
void GBADMAUpdate(struct GBA* gba, int32_t cycles); void GBADMAUpdate(struct GBA* gba);
#endif #endif

View File

@ -918,7 +918,7 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
STORE_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount); STORE_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount);
STORE_32(gba->memory.dma[i].nextEvent, 0, &state->dma[i].nextEvent); STORE_32(gba->memory.dma[i].when, 0, &state->dma[i].nextEvent);
} }
GBAHardwareSerialize(&gba->memory.hw, state); GBAHardwareSerialize(&gba->memory.hw, state);
@ -951,12 +951,12 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
LOAD_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); LOAD_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
LOAD_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount); LOAD_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount);
LOAD_32(gba->memory.dma[i].nextEvent, 0, &state->dma[i].nextEvent); LOAD_32(gba->memory.dma[i].when, 0, &state->dma[i].nextEvent);
if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) { if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) {
GBADMASchedule(gba, i, &gba->memory.dma[i]); GBADMASchedule(gba, i, &gba->memory.dma[i]);
} }
} }
GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]); GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]);
GBADMAUpdate(gba, 0); GBADMAUpdate(gba);
GBAHardwareDeserialize(&gba->memory.hw, state); GBAHardwareDeserialize(&gba->memory.hw, state);
} }

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau /* Copyright (c) 2013-2016 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -108,8 +108,7 @@ struct GBADMA {
uint32_t nextSource; uint32_t nextSource;
uint32_t nextDest; uint32_t nextDest;
int32_t nextCount; int32_t nextCount;
int32_t nextEvent; uint32_t when;
uint32_t scheduledAt;
int32_t hasStarted; int32_t hasStarted;
}; };