src/gba/dma.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "dma.h"
7
8#include "gba/gba.h"
9#include "gba/io.h"
10
11static void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate);
12
13static void GBADMAService(struct GBA* gba, int number, struct GBADMA* info);
14
15static const int DMA_OFFSET[] = { 1, -1, 0, 1 };
16
17void GBADMAInit(struct GBA* gba) {
18 gba->memory.dmaEvent.name = "GBA DMA";
19 gba->memory.dmaEvent.callback = _dmaEvent;
20 gba->memory.dmaEvent.context = gba;
21 gba->memory.dmaEvent.priority = 0x40;
22}
23
24void GBADMAReset(struct GBA* gba) {
25 memset(gba->memory.dma, 0, sizeof(gba->memory.dma));
26 int i;
27 for (i = 0; i < 4; ++i) {
28 gba->memory.dma[i].count = 0x4000;
29 gba->memory.dma[i].nextEvent = INT_MAX;
30 }
31 gba->memory.dma[3].count = 0x10000;
32 gba->memory.activeDMA = -1;
33}
34static bool _isValidDMASAD(int dma, uint32_t address) {
35 if (dma == 0 && address >= BASE_CART0 && address < BASE_CART_SRAM) {
36 return false;
37 }
38 return address >= BASE_WORKING_RAM;
39}
40
41static bool _isValidDMADAD(int dma, uint32_t address) {
42 return dma == 3 || address < BASE_CART0;
43}
44
45uint32_t GBADMAWriteSAD(struct GBA* gba, int dma, uint32_t address) {
46 struct GBAMemory* memory = &gba->memory;
47 address &= 0x0FFFFFFE;
48 if (_isValidDMASAD(dma, address)) {
49 memory->dma[dma].source = address;
50 }
51 return memory->dma[dma].source;
52}
53
54uint32_t GBADMAWriteDAD(struct GBA* gba, int dma, uint32_t address) {
55 struct GBAMemory* memory = &gba->memory;
56 address &= 0x0FFFFFFE;
57 if (_isValidDMADAD(dma, address)) {
58 memory->dma[dma].dest = address;
59 }
60 return memory->dma[dma].dest;
61}
62
63void GBADMAWriteCNT_LO(struct GBA* gba, int dma, uint16_t count) {
64 struct GBAMemory* memory = &gba->memory;
65 memory->dma[dma].count = count ? count : (dma == 3 ? 0x10000 : 0x4000);
66}
67
68uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) {
69 struct GBAMemory* memory = &gba->memory;
70 struct GBADMA* currentDma = &memory->dma[dma];
71 int wasEnabled = GBADMARegisterIsEnable(currentDma->reg);
72 if (dma < 3) {
73 control &= 0xF7E0;
74 } else {
75 control &= 0xFFE0;
76 }
77 currentDma->reg = control;
78
79 if (GBADMARegisterIsDRQ(currentDma->reg)) {
80 mLOG(GBA_MEM, STUB, "DRQ not implemented");
81 }
82
83 if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) {
84 currentDma->nextSource = currentDma->source;
85 currentDma->nextDest = currentDma->dest;
86 currentDma->nextCount = currentDma->count;
87 GBADMASchedule(gba, dma, currentDma);
88 }
89 // If the DMA has already occurred, this value might have changed since the function started
90 return currentDma->reg;
91};
92
93void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) {
94 info->hasStarted = 0;
95 switch (GBADMARegisterGetTiming(info->reg)) {
96 case DMA_TIMING_NOW:
97 info->nextEvent = 2 + 1; // XXX: Account for I cycle when writing
98 info->scheduledAt = mTimingCurrentTime(&gba->timing);
99 GBADMAUpdate(gba, 0);
100 break;
101 case DMA_TIMING_HBLANK:
102 // Handled implicitly
103 info->nextEvent = INT_MAX;
104 break;
105 case DMA_TIMING_VBLANK:
106 // Handled implicitly
107 info->nextEvent = INT_MAX;
108 break;
109 case DMA_TIMING_CUSTOM:
110 info->nextEvent = INT_MAX;
111 switch (number) {
112 case 0:
113 mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling");
114 break;
115 case 1:
116 case 2:
117 GBAAudioScheduleFifoDma(&gba->audio, number, info);
118 break;
119 case 3:
120 // GBAVideoScheduleVCaptureDma(dma, info);
121 break;
122 }
123 }
124}
125
126void GBADMARunHblank(struct GBA* gba, int32_t cycles) {
127 struct GBAMemory* memory = &gba->memory;
128 struct GBADMA* dma;
129 bool dmaSeen = false;
130 if (memory->activeDMA >= 0) {
131 GBADMAUpdate(gba, mTimingCurrentTime(&gba->timing) - memory->dma[memory->activeDMA].scheduledAt);
132 }
133 int i;
134 for (i = 0; i < 4; ++i) {
135 dma = &memory->dma[i];
136 if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_HBLANK) {
137 dma->nextEvent = 2 + cycles;
138 dma->scheduledAt = mTimingCurrentTime(&gba->timing);
139 dmaSeen = true;
140 }
141 }
142 if (dmaSeen) {
143 GBADMAUpdate(gba, 0);
144 }
145}
146
147void GBADMARunVblank(struct GBA* gba, int32_t cycles) {
148 struct GBAMemory* memory = &gba->memory;
149 struct GBADMA* dma;
150 bool dmaSeen = false;
151 if (memory->activeDMA >= 0) {
152 GBADMAUpdate(gba, mTimingCurrentTime(&gba->timing) - memory->dma[memory->activeDMA].scheduledAt);
153 }
154 int i;
155 for (i = 0; i < 4; ++i) {
156 dma = &memory->dma[i];
157 if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_VBLANK) {
158 dma->nextEvent = 2 + cycles;
159 dma->scheduledAt = mTimingCurrentTime(&gba->timing);
160 dmaSeen = true;
161 }
162 }
163 if (dmaSeen) {
164 GBADMAUpdate(gba, 0);
165 }
166}
167
168void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) {
169 UNUSED(timing);
170 struct GBA* gba = context;
171 struct GBAMemory* memory = &gba->memory;
172 struct GBADMA* dma = &memory->dma[memory->activeDMA];
173 dma->nextEvent = -cyclesLate;
174 GBADMAService(gba, memory->activeDMA, dma);
175}
176
177void GBADMAUpdate(struct GBA* gba, int32_t cycles) {
178 int i;
179 struct GBAMemory* memory = &gba->memory;
180 memory->activeDMA = -1;
181 for (i = 3; i >= 0; --i) {
182 struct GBADMA* dma = &memory->dma[i];
183 if (dma->nextEvent != INT_MAX) {
184 dma->nextEvent -= cycles;
185 if (GBADMARegisterIsEnable(dma->reg)) {
186 memory->activeDMA = i;
187 }
188 }
189 }
190
191 if (memory->activeDMA >= 0) {
192 mTimingDeschedule(&gba->timing, &memory->dmaEvent);
193 mTimingSchedule(&gba->timing, &memory->dmaEvent, memory->dma[memory->activeDMA].nextEvent);
194 } else {
195 gba->cpuBlocked = false;
196 }
197}
198
199void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
200 struct GBAMemory* memory = &gba->memory;
201 struct ARMCore* cpu = gba->cpu;
202 uint32_t width = 2 << GBADMARegisterGetWidth(info->reg);
203 int32_t wordsRemaining = info->nextCount;
204 uint32_t source = info->nextSource;
205 uint32_t dest = info->nextDest;
206 uint32_t sourceRegion = source >> BASE_OFFSET;
207 uint32_t destRegion = dest >> BASE_OFFSET;
208 int32_t cycles = 2;
209
210 gba->cpuBlocked = true;
211 if (info->hasStarted < 2) {
212 if (sourceRegion < REGION_CART0 || destRegion < REGION_CART0) {
213 cycles += 2;
214 }
215 if (width == 4) {
216 cycles += memory->waitstatesNonseq32[sourceRegion] + memory->waitstatesNonseq32[destRegion];
217 } else {
218 cycles += memory->waitstatesNonseq16[sourceRegion] + memory->waitstatesNonseq16[destRegion];
219 }
220 if (info->hasStarted < 1) {
221 info->hasStarted = wordsRemaining;
222 info->nextEvent = 0;
223 info->scheduledAt = mTimingCurrentTime(&gba->timing);
224 GBADMAUpdate(gba, -cycles);
225 return;
226 }
227 info->hasStarted = 2;
228 source &= -width;
229 dest &= -width;
230 } else {
231 if (width == 4) {
232 cycles += memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion];
233 } else {
234 cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion];
235 }
236 }
237 info->nextEvent += cycles;
238
239 gba->performingDMA = 1 | (number << 1);
240 uint32_t word;
241 if (width == 4) {
242 word = cpu->memory.load32(cpu, source, 0);
243 gba->bus = word;
244 cpu->memory.store32(cpu, dest, word, 0);
245 } else {
246 if (sourceRegion == REGION_CART2_EX && memory->savedata.type == SAVEDATA_EEPROM) {
247 word = GBASavedataReadEEPROM(&memory->savedata);
248 cpu->memory.store16(cpu, dest, word, 0);
249 } else if (destRegion == REGION_CART2_EX) {
250 if (memory->savedata.type == SAVEDATA_AUTODETECT) {
251 mLOG(GBA_MEM, INFO, "Detected EEPROM savegame");
252 GBASavedataInitEEPROM(&memory->savedata, gba->realisticTiming);
253 }
254 word = cpu->memory.load16(cpu, source, 0);
255 GBASavedataWriteEEPROM(&memory->savedata, word, wordsRemaining);
256 } else {
257 word = cpu->memory.load16(cpu, source, 0);
258 cpu->memory.store16(cpu, dest, word, 0);
259 }
260 gba->bus = word | (word << 16);
261 }
262 int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width;
263 int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width;
264 source += sourceOffset;
265 dest += destOffset;
266 --wordsRemaining;
267 gba->performingDMA = 0;
268
269 if (!wordsRemaining) {
270 if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) {
271 info->reg = GBADMARegisterClearEnable(info->reg);
272 info->nextEvent = INT_MAX;
273
274 // Clear the enable bit in memory
275 memory->io[(REG_DMA0CNT_HI + number * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0;
276 } else {
277 info->nextCount = info->count;
278 if (GBADMARegisterGetDestControl(info->reg) == DMA_INCREMENT_RELOAD) {
279 info->nextDest = info->dest;
280 }
281 GBADMASchedule(gba, number, info);
282 }
283 if (GBADMARegisterIsDoIRQ(info->reg)) {
284 GBARaiseIRQ(gba, IRQ_DMA0 + number);
285 }
286 } else {
287 info->nextDest = dest;
288 info->nextCount = wordsRemaining;
289 info->scheduledAt = mTimingCurrentTime(&gba->timing);
290 }
291 info->nextSource = source;
292 GBADMAUpdate(gba, 0);
293}