DS: Add timers and start interrupts
Jeffrey Pfau jeffrey@endrift.com
Wed, 08 Jun 2016 09:43:56 -0700
6 files changed,
290 insertions(+),
16 deletions(-)
M
src/ds/ds.c
→
src/ds/ds.c
@@ -80,6 +80,10 @@ ds->video.p = ds;
ds->springIRQ7 = 0; ds->springIRQ9 = 0; + ds->timersEnabled7 = 0; + ds->timersEnabled9 = 0; + memset(ds->timers7, 0, sizeof(ds->timers7)); + memset(ds->timers9, 0, sizeof(ds->timers9)); ds->keySource = NULL; ds->rtcSource = NULL; ds->rumble = NULL;@@ -196,22 +200,26 @@ ARMRaiseIRQ(cpu);
ds->springIRQ7 = 0; } - do { - int32_t cycles = cpu->nextEvent; - int32_t nextEvent = INT_MAX; + int32_t cycles = cpu->nextEvent; + int32_t nextEvent = INT_MAX; + int32_t testEvent; #ifndef NDEBUG - if (cycles < 0) { - mLOG(DS, FATAL, "Negative cycles passed: %i", cycles); - } + if (cycles < 0) { + mLOG(DS, FATAL, "Negative cycles passed: %i", cycles); + } #endif - cpu->cycles -= cycles; - cpu->nextEvent = nextEvent; + testEvent = DSTimersProcessEvents(ds, cycles); + if (testEvent < nextEvent) { + nextEvent = testEvent; + } - if (cpu->halted) { - cpu->cycles = cpu->nextEvent; - } - } while (cpu->cycles >= cpu->nextEvent); + cpu->cycles -= cycles; + cpu->nextEvent = nextEvent; + + if (cpu->halted) { + cpu->cycles = cpu->nextEvent; + } } void DSAttachDebugger(struct DS* ds, struct mDebugger* debugger) {@@ -454,4 +462,34 @@ break;
case 9: _writeTCMControl(cpu, crm, opcode2, value); break; - }} + } +} + +void DSWriteIE(struct ARMCore* cpu, uint16_t* io, uint32_t value) { + if (io[DS7_REG_IME >> 1] && (value & io[DS7_REG_IF_LO >> 1] || (value >> 16) & io[DS7_REG_IF_HI >> 1])) { + ARMRaiseIRQ(cpu); + } +} +void DSWriteIME(struct ARMCore* cpu, uint16_t* io, uint16_t value) { + if (value && (io[DS7_REG_IE_LO >> 1] & io[DS7_REG_IF_LO >> 1] || io[DS7_REG_IE_HI >> 1] & io[DS7_REG_IF_HI >> 1])) { + ARMRaiseIRQ(cpu); + } +} + +void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq) { + if (irq < 16) { + io[DS7_REG_IF_LO >> 1] |= 1 << irq; + } else { + io[DS7_REG_IF_HI >> 1] |= 1 << (irq - 16); + } + cpu->halted = 0; + + if (!io[DS7_REG_IME >> 1]) { + return; + } + if (irq < 16 && (io[DS7_REG_IE_LO >> 1] & 1 << irq)) { + ARMRaiseIRQ(cpu); + } else if (io[DS7_REG_IE_HI >> 1] & 1 << (irq - 16)) { + ARMRaiseIRQ(cpu); + } +}
M
src/ds/ds.h
→
src/ds/ds.h
@@ -12,6 +12,7 @@ #include "arm/arm.h"
#include "core/log.h" #include "ds/memory.h" +#include "ds/timer.h" #include "ds/video.h" extern const uint32_t DS_ARM946ES_FREQUENCY;@@ -57,6 +58,10 @@ struct ARMCore* arm7;
struct ARMCore* arm9; struct DSMemory memory; struct DSVideo video; + int timersEnabled7; + int timersEnabled9; + struct DSTimer timers7[4]; + struct DSTimer timers9[4]; struct mCoreSync* sync;@@ -130,5 +135,9 @@
bool DSIsROM(struct VFile* vf); void DSGetGameCode(struct DS* ds, char* out); void DSGetGameTitle(struct DS* ds, char* out); + +void DSWriteIME(struct ARMCore* cpu, uint16_t* io, uint16_t value); +void DSWriteIE(struct ARMCore* cpu, uint16_t* io, uint32_t value); +void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq); #endif
M
src/ds/io.c
→
src/ds/io.c
@@ -24,10 +24,52 @@ }
void DS7IOWrite(struct DS* ds, uint32_t address, uint16_t value) { switch (address) { - case DS9_REG_IPCSYNC: + // Timers + case DS7_REG_TM0CNT_LO: + DSTimerWriteTMCNT_LO(&ds->timers7[0], value); + return; + case DS7_REG_TM1CNT_LO: + DSTimerWriteTMCNT_LO(&ds->timers7[1], value); + return; + case DS7_REG_TM2CNT_LO: + DSTimerWriteTMCNT_LO(&ds->timers7[2], value); + return; + case DS7_REG_TM3CNT_LO: + DSTimerWriteTMCNT_LO(&ds->timers7[3], value); + return; + + case DS7_REG_TM0CNT_HI: + value &= 0x00C7; + DSTimerWriteTMCNT_HI(&ds->timers7[0], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value); + ds->timersEnabled7 &= ~1; + ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[0].flags); + break; + case DS7_REG_TM1CNT_HI: + value &= 0x00C7; + DSTimerWriteTMCNT_HI(&ds->timers7[1], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value); + ds->timersEnabled7 &= ~2; + ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[1].flags) << 1; + break; + case DS7_REG_TM2CNT_HI: + value &= 0x00C7; + DSTimerWriteTMCNT_HI(&ds->timers7[2], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value); + ds->timersEnabled7 &= ~4; + ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[2].flags) << 2; + break; + case DS7_REG_TM3CNT_HI: + value &= 0x00C7; + DSTimerWriteTMCNT_HI(&ds->timers7[3], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value); + ds->timersEnabled7 &= ~8; + ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[3].flags) << 3; + break; + + case DS7_REG_IPCSYNC: value &= 0x6F00; value |= ds->memory.io7[address >> 1] & 0x000F; _writeIPCSync(ds->arm9, ds->memory.io9, value); + break; + case DS7_REG_IME: + DSWriteIME(ds->arm7, ds->memory.io7, value); break; default: mLOG(DS_IO, STUB, "Stub DS7 I/O register write: %06X:%04X", address, value);@@ -50,11 +92,28 @@ mLOG(DS, STUB, "Writing to unknown DS7 register: %08X:%02X", address, value);
} } -void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value); +void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value) { + switch (address) { + case DS7_REG_IE_LO: + DSWriteIE(ds->arm7, ds->memory.io7, value); + break; + default: + DS7IOWrite(ds, address, value & 0xFFFF); + DS7IOWrite(ds, address | 2, value >> 16); + return; + } + ds->memory.io7[address >> 1] = value; + ds->memory.io7[(address >> 1) + 1] = value >> 16; +} uint16_t DS7IORead(struct DS* ds, uint32_t address) { switch (address) { case DS7_REG_IPCSYNC: + case DS7_REG_IME: + case DS7_REG_IE_LO: + case DS7_REG_IE_HI: + case DS7_REG_IF_LO: + case DS7_REG_IF_HI: // Handled transparently by the registers break; default:@@ -98,7 +157,16 @@ mLOG(DS, STUB, "Writing to unknown DS9 register: %08X:%02X", address, value);
} } -void DS9IOWrite32(struct DS* ds, uint32_t address, uint32_t value); +void DS9IOWrite32(struct DS* ds, uint32_t address, uint32_t value) { + switch (address) { + default: + DS9IOWrite(ds, address, value & 0xFFFF); + DS9IOWrite(ds, address | 2, value >> 16); + return; + } + ds->memory.io9[address >> 1] = value; + ds->memory.io9[(address >> 1) + 1] = value >> 16; +} uint16_t DS9IORead(struct DS* ds, uint32_t address) { switch (address) {
M
src/ds/memory.c
→
src/ds/memory.c
@@ -248,6 +248,9 @@ break;
} mLOG(DS_MEM, STUB, "Unimplemented DS7 Load32: %08X", address); break; + case DS_REGION_IO: + value = DS7IORead(ds, address & 0x00FFFFFC) | (DS7IORead(ds, (address & 0x00FFFFFC) | 2) << 16); + break; default: mLOG(DS_MEM, STUB, "Unimplemented DS7 Load32: %08X", address); break;@@ -346,6 +349,9 @@ STORE_32(value, address & (DS_SIZE_RAM - 1), memory->ram);
break; } mLOG(DS_MEM, STUB, "Unimplemented DS7 Store32: %08X:%08X", address, value); + break; + case DS_REGION_IO: + DS7IOWrite32(ds, address & 0x00FFFFFF, value); break; default: mLOG(DS_MEM, STUB, "Unimplemented DS7 Store32: %08X:%08X", address, value);@@ -455,6 +461,9 @@ } else {
mLOG(DS_MEM, STUB, "Unimplemented DS7 LDM: %08X", address); }); break; + case DS_REGION_IO: + LDM_LOOP(value = DS7IORead(ds, address & 0x00FFFFFC) | (DS7IORead(ds, (address & 0x00FFFFFC) | 2) << 16)); + break; default: mLOG(DS_MEM, STUB, "Unimplemented DS7 LDM: %08X", address); LDM_LOOP(value = 0);@@ -592,6 +601,9 @@ break;
} mLOG(DS_MEM, STUB, "Unimplemented DS9 Load32: %08X", address); break; + case DS_REGION_IO: + value = DS9IORead(ds, address & 0x00FFFFFC) | (DS9IORead(ds, (address & 0x00FFFFFC) | 2) << 16); + break; default: mLOG(DS_MEM, STUB, "Unimplemented DS9 Load32: %08X", address); break;@@ -681,6 +693,9 @@ STORE_32(value, address & (DS_SIZE_RAM - 1), memory->ram);
break; } mLOG(DS_MEM, STUB, "Unimplemented DS9 Store32: %08X:%08X", address, value); + break; + case DS_REGION_IO: + DS9IOWrite32(ds, address & 0x00FFFFFF, value); break; default: mLOG(DS_MEM, STUB, "Unimplemented DS9 Store32: %08X:%08X", address, value);
A
src/ds/timer.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "timer.h" + +#include "arm/arm.h" +#include "ds/ds.h" + +void DSTimerUpdateRegister(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io) { + if (DSTimerFlagsIsEnable(timer->flags) && !DSTimerFlagsIsCountUp(timer->flags)) { + // Reading this takes two cycles (1N+1I), so let's remove them preemptively + *io = timer->oldReload + ((cpu->cycles - timer->lastEvent - 2) >> DSTimerFlagsGetPrescaleBits(timer->flags)); + } +} + +void DSTimerWriteTMCNT_LO(struct DSTimer* timer, uint16_t reload) { + timer->reload = reload; + timer->overflowInterval = (0x10000 - timer->reload) << DSTimerFlagsGetPrescaleBits(timer->flags); +} + +void DSTimerWriteTMCNT_HI(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io, uint16_t control) { + DSTimerUpdateRegister(timer, cpu, io); + + unsigned oldPrescale = DSTimerFlagsGetPrescaleBits(timer->flags); + switch (control & 0x0003) { + case 0x0000: + timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 0); + break; + case 0x0001: + timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 6); + break; + case 0x0002: + timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 8); + break; + case 0x0003: + timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 10); + break; + } + timer->flags = DSTimerFlagsTestFillCountUp(timer->flags, control & 0x0004); + timer->flags = DSTimerFlagsTestFillDoIrq(timer->flags, control & 0x0040); + timer->overflowInterval = (0x10000 - timer->reload) << DSTimerFlagsGetPrescaleBits(timer->flags); + bool wasEnabled = DSTimerFlagsIsEnable(timer->flags); + timer->flags = DSTimerFlagsTestFillEnable(timer->flags, control & 0x0080); + if (!wasEnabled && DSTimerFlagsIsEnable(timer->flags)) { + if (!DSTimerFlagsIsCountUp(timer->flags)) { + timer->nextEvent = cpu->cycles + timer->overflowInterval; + } else { + timer->nextEvent = INT_MAX; + } + *io = timer->reload; + timer->oldReload = timer->reload; + timer->lastEvent = cpu->cycles; + } else if (wasEnabled && !DSTimerFlagsIsEnable(timer->flags)) { + if (!DSTimerFlagsIsCountUp(timer->flags)) { + *io = timer->oldReload + ((cpu->cycles - timer->lastEvent) >> oldPrescale); + } + } else if (DSTimerFlagsGetPrescaleBits(timer->flags) != oldPrescale && !DSTimerFlagsIsCountUp(timer->flags)) { + // FIXME: this might be before present + timer->nextEvent = timer->lastEvent + timer->overflowInterval; + } + + if (timer->nextEvent < cpu->nextEvent) { + cpu->nextEvent = timer->nextEvent; + } +} + +int32_t DSTimersProcessEvents(struct DS* ds, int32_t cycles) { + int32_t nextEvent = INT_MAX; + if (!ds->timersEnabled7) { + return nextEvent; + } + + struct DSTimer* timer; + struct DSTimer* nextTimer; + + int t; + for (t = 0; t < 4; ++t) { + timer = &ds->timers7[t]; + if (DSTimerFlagsIsEnable(timer->flags)) { + timer->nextEvent -= cycles; + timer->lastEvent -= cycles; + while (timer->nextEvent <= 0) { + timer->lastEvent = timer->nextEvent; + timer->nextEvent += timer->overflowInterval; + ds->memory.io7[(DS7_REG_TM0CNT_LO + (t << 2)) >> 1] = timer->reload; + timer->oldReload = timer->reload; + + if (DSTimerFlagsIsDoIrq(timer->flags)) { + DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER0); + } + + if (t == 3) { + break; + } + + nextTimer = &ds->timers7[t + 1]; + if (DSTimerFlagsIsCountUp(nextTimer->flags)) { + ++ds->memory.io7[(DS7_REG_TM1CNT_LO + (t << 2)) >> 1]; + if (!ds->memory.io7[(DS7_REG_TM1CNT_LO + (t << 2)) >> 1]) { + nextTimer->nextEvent = 0; + } + } + } + nextEvent = timer->nextEvent; + } + } + return nextEvent; +}
A
src/ds/timer.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef DS_TIMER_H +#define DS_TIMER_H + +#include "util/common.h" + +DECL_BITFIELD(DSTimerFlags, uint32_t); +DECL_BITS(DSTimerFlags, PrescaleBits, 0, 4); +DECL_BIT(DSTimerFlags, CountUp, 4); +DECL_BIT(DSTimerFlags, DoIrq, 5); +DECL_BIT(DSTimerFlags, Enable, 6); + +struct DSTimer { + uint16_t reload; + uint16_t oldReload; + int32_t lastEvent; + int32_t nextEvent; + int32_t overflowInterval; + DSTimerFlags flags; +}; + +// TODO: Merge back into GBATimer +struct ARMCore; +void DSTimerUpdateRegister(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io); +void DSTimerWriteTMCNT_LO(struct DSTimer* timer, uint16_t reload); +void DSTimerWriteTMCNT_HI(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io, uint16_t control); + +struct DS; +int32_t DSTimersProcessEvents(struct DS* ds, int32_t cycles); +#endif