From e649be94f5399a7c29d023fbdb0b336834fc9860 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 8 Jun 2016 09:43:56 -0700 Subject: [PATCH] DS: Add timers and start interrupts --- src/ds/ds.c | 64 ++++++++++++++++++++++------ src/ds/ds.h | 9 ++++ src/ds/io.c | 74 ++++++++++++++++++++++++++++++-- src/ds/memory.c | 15 +++++++ src/ds/timer.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ src/ds/timer.h | 34 +++++++++++++++ 6 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 src/ds/timer.c create mode 100644 src/ds/timer.h diff --git a/src/ds/ds.c b/src/ds/ds.c index 30262d511..c8f0837f9 100644 --- a/src/ds/ds.c +++ b/src/ds/ds.c @@ -80,6 +80,10 @@ static void DSInit(void* cpu, struct mCPUComponent* component) { 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 @@ static void DSProcessEvents(struct ARMCore* 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 @@ void DS9WriteCP15(struct ARMCore* cpu, int crn, int crm, int opcode1, int opcode 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); + } +} diff --git a/src/ds/ds.h b/src/ds/ds.h index c45b0c32c..e2de69d23 100644 --- a/src/ds/ds.h +++ b/src/ds/ds.h @@ -12,6 +12,7 @@ #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 DS { struct ARMCore* arm9; struct DSMemory memory; struct DSVideo video; + int timersEnabled7; + int timersEnabled9; + struct DSTimer timers7[4]; + struct DSTimer timers9[4]; struct mCoreSync* sync; @@ -131,4 +136,8 @@ 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 diff --git a/src/ds/io.c b/src/ds/io.c index d0c2d8abe..518126b49 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -24,11 +24,53 @@ void DS7IOInit(struct DS* ds) { 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); if (address >= DS7_REG_MAX) { @@ -50,11 +92,28 @@ void DS7IOWrite8(struct DS* ds, uint32_t address, uint8_t 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 @@ void DS9IOWrite8(struct DS* ds, uint32_t address, uint8_t 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) { diff --git a/src/ds/memory.c b/src/ds/memory.c index b1b92f88a..1354dfa0f 100644 --- a/src/ds/memory.c +++ b/src/ds/memory.c @@ -248,6 +248,9 @@ uint32_t DS7Load32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } 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; @@ -347,6 +350,9 @@ void DS7Store32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle } 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); break; @@ -455,6 +461,9 @@ uint32_t DS7LoadMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum L 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 @@ uint32_t DS9Load32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } 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; @@ -682,6 +694,9 @@ void DS9Store32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle } 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); break; diff --git a/src/ds/timer.c b/src/ds/timer.c new file mode 100644 index 000000000..a395bd6d1 --- /dev/null +++ b/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; +} diff --git a/src/ds/timer.h b/src/ds/timer.h new file mode 100644 index 000000000..c43a82774 --- /dev/null +++ b/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