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 <mgba/internal/gba/dma.h>
7
8#include <mgba/internal/gba/gba.h>
9#include <mgba/internal/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 }
30 gba->memory.dma[3].count = 0x10000;
31 gba->memory.activeDMA = -1;
32}
33static bool _isValidDMASAD(int dma, uint32_t address) {
34 if (dma == 0 && address >= BASE_CART0 && address < BASE_CART_SRAM) {
35 return false;
36 }
37 return address >= BASE_WORKING_RAM;
38}
39
40static bool _isValidDMADAD(int dma, uint32_t address) {
41 return dma == 3 || address < BASE_CART0;
42}
43
44uint32_t GBADMAWriteSAD(struct GBA* gba, int dma, uint32_t address) {
45 struct GBAMemory* memory = &gba->memory;
46 address &= 0x0FFFFFFE;
47 if (_isValidDMASAD(dma, address)) {
48 memory->dma[dma].source = address;
49 } else {
50 memory->dma[dma].source = 0;
51 }
52 return memory->dma[dma].source;
53}
54
55uint32_t GBADMAWriteDAD(struct GBA* gba, int dma, uint32_t address) {
56 struct GBAMemory* memory = &gba->memory;
57 address &= 0x0FFFFFFE;
58 if (_isValidDMADAD(dma, address)) {
59 memory->dma[dma].dest = address;
60 }
61 return memory->dma[dma].dest;
62}
63
64void GBADMAWriteCNT_LO(struct GBA* gba, int dma, uint16_t count) {
65 struct GBAMemory* memory = &gba->memory;
66 memory->dma[dma].count = count ? count : (dma == 3 ? 0x10000 : 0x4000);
67}
68
69uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) {
70 struct GBAMemory* memory = &gba->memory;
71 struct GBADMA* currentDma = &memory->dma[dma];
72 int wasEnabled = GBADMARegisterIsEnable(currentDma->reg);
73 if (dma < 3) {
74 control &= 0xF7E0;
75 } else {
76 control &= 0xFFE0;
77 }
78 currentDma->reg = control;
79
80 if (GBADMARegisterIsDRQ(currentDma->reg)) {
81 mLOG(GBA_MEM, STUB, "DRQ not implemented");
82 }
83
84 if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) {
85 currentDma->nextSource = currentDma->source;
86 if (currentDma->nextSource >= BASE_CART0 && currentDma->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(currentDma->reg) < 3) {
87 currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg);
88 }
89 currentDma->nextDest = currentDma->dest;
90
91 uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg);
92 if (currentDma->nextSource & (width - 1)) {
93 mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA source address: 0x%08X", currentDma->nextSource);
94 }
95 if (currentDma->nextDest & (width - 1)) {
96 mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA destination address: 0x%08X", currentDma->nextDest);
97 }
98 currentDma->nextSource &= -width;
99 currentDma->nextDest &= -width;
100
101 GBADMASchedule(gba, dma, currentDma);
102 }
103 // If the DMA has already occurred, this value might have changed since the function started
104 return currentDma->reg;
105};
106
107void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) {
108 switch (GBADMARegisterGetTiming(info->reg)) {
109 case GBA_DMA_TIMING_NOW:
110 info->when = mTimingCurrentTime(&gba->timing) + 3; // DMAs take 3 cycles to start
111 info->nextCount = info->count;
112 break;
113 case GBA_DMA_TIMING_HBLANK:
114 case GBA_DMA_TIMING_VBLANK:
115 // Handled implicitly
116 return;
117 case GBA_DMA_TIMING_CUSTOM:
118 switch (number) {
119 case 0:
120 mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling");
121 return;
122 case 1:
123 case 2:
124 GBAAudioScheduleFifoDma(&gba->audio, number, info);
125 break;
126 case 3:
127 // Handled implicitly
128 break;
129 }
130 }
131 GBADMAUpdate(gba);
132}
133
134void GBADMARunHblank(struct GBA* gba, int32_t cycles) {
135 struct GBAMemory* memory = &gba->memory;
136 struct GBADMA* dma;
137 bool found = false;
138 int i;
139 for (i = 0; i < 4; ++i) {
140 dma = &memory->dma[i];
141 if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_HBLANK && !dma->nextCount) {
142 dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles;
143 dma->nextCount = dma->count;
144 found = true;
145 }
146 }
147 if (found) {
148 GBADMAUpdate(gba);
149 }
150}
151
152void GBADMARunVblank(struct GBA* gba, int32_t cycles) {
153 struct GBAMemory* memory = &gba->memory;
154 struct GBADMA* dma;
155 bool found = false;
156 int i;
157 for (i = 0; i < 4; ++i) {
158 dma = &memory->dma[i];
159 if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_VBLANK && !dma->nextCount) {
160 dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles;
161 dma->nextCount = dma->count;
162 found = true;
163 }
164 }
165 if (found) {
166 GBADMAUpdate(gba);
167 }
168}
169
170void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles) {
171 struct GBAMemory* memory = &gba->memory;
172 struct GBADMA* dma = &memory->dma[3];
173 if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM && !dma->nextCount) {
174 dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles;
175 dma->nextCount = dma->count;
176 GBADMAUpdate(gba);
177 }
178}
179
180void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) {
181 UNUSED(timing);
182 UNUSED(cyclesLate);
183 struct GBA* gba = context;
184 struct GBAMemory* memory = &gba->memory;
185 struct GBADMA* dma = &memory->dma[memory->activeDMA];
186 if (dma->nextCount == dma->count) {
187 dma->when = mTimingCurrentTime(&gba->timing);
188 }
189 if (dma->nextCount & 0xFFFFF) {
190 GBADMAService(gba, memory->activeDMA, dma);
191 } else {
192 dma->nextCount = 0;
193 bool noRepeat = !GBADMARegisterIsRepeat(dma->reg);
194 noRepeat |= GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_NOW;
195 noRepeat |= memory->activeDMA == 3 && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM && gba->video.vcount == GBA_VIDEO_VERTICAL_PIXELS + 1;
196 if (noRepeat) {
197 dma->reg = GBADMARegisterClearEnable(dma->reg);
198
199 // Clear the enable bit in memory
200 memory->io[(REG_DMA0CNT_HI + memory->activeDMA * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0;
201 }
202 if (GBADMARegisterGetDestControl(dma->reg) == GBA_DMA_INCREMENT_RELOAD) {
203 dma->nextDest = dma->dest;
204 }
205 if (GBADMARegisterIsDoIRQ(dma->reg)) {
206 GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA, cyclesLate);
207 }
208 GBADMAUpdate(gba);
209 }
210}
211
212void GBADMAUpdate(struct GBA* gba) {
213 int i;
214 struct GBAMemory* memory = &gba->memory;
215 uint32_t currentTime = mTimingCurrentTime(&gba->timing);
216 int32_t leastTime = INT_MAX;
217 memory->activeDMA = -1;
218 for (i = 0; i < 4; ++i) {
219 struct GBADMA* dma = &memory->dma[i];
220 if (GBADMARegisterIsEnable(dma->reg) && dma->nextCount) {
221 int32_t time = dma->when - currentTime;
222 if (memory->activeDMA == -1 || time < leastTime) {
223 leastTime = time;
224 memory->activeDMA = i;
225 }
226 }
227 }
228
229 if (memory->activeDMA >= 0) {
230 gba->dmaPC = gba->cpu->gprs[ARM_PC];
231 mTimingDeschedule(&gba->timing, &memory->dmaEvent);
232 mTimingSchedule(&gba->timing, &memory->dmaEvent, memory->dma[memory->activeDMA].when - currentTime);
233 } else {
234 gba->cpuBlocked = false;
235 }
236}
237
238void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
239 struct GBAMemory* memory = &gba->memory;
240 struct ARMCore* cpu = gba->cpu;
241 uint32_t width = 2 << GBADMARegisterGetWidth(info->reg);
242 int32_t wordsRemaining = info->nextCount;
243 uint32_t source = info->nextSource;
244 uint32_t dest = info->nextDest;
245 uint32_t sourceRegion = source >> BASE_OFFSET;
246 uint32_t destRegion = dest >> BASE_OFFSET;
247 int32_t cycles = 2;
248
249 gba->cpuBlocked = true;
250 if (info->count == info->nextCount) {
251 if (width == 4) {
252 cycles += memory->waitstatesNonseq32[sourceRegion] + memory->waitstatesNonseq32[destRegion];
253 } else {
254 cycles += memory->waitstatesNonseq16[sourceRegion] + memory->waitstatesNonseq16[destRegion];
255 }
256 } else {
257 if (width == 4) {
258 cycles += memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion];
259 } else {
260 cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion];
261 }
262 }
263 info->when += cycles;
264
265 gba->performingDMA = 1 | (number << 1);
266 if (width == 4) {
267 if (source) {
268 memory->dmaTransferRegister = cpu->memory.load32(cpu, source, 0);
269 }
270 gba->bus = memory->dmaTransferRegister;
271 cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0);
272 } else {
273 if (sourceRegion == REGION_CART2_EX && (memory->savedata.type == SAVEDATA_EEPROM || memory->savedata.type == SAVEDATA_EEPROM512)) {
274 memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata);
275 memory->dmaTransferRegister |= memory->dmaTransferRegister << 16;
276 } else if (source) {
277 memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0);
278 memory->dmaTransferRegister |= memory->dmaTransferRegister << 16;
279 }
280 if (destRegion == REGION_CART2_EX) {
281 if (memory->savedata.type == SAVEDATA_AUTODETECT) {
282 mLOG(GBA_MEM, INFO, "Detected EEPROM savegame");
283 GBASavedataInitEEPROM(&memory->savedata);
284 }
285 if (memory->savedata.type == SAVEDATA_EEPROM512 || memory->savedata.type == SAVEDATA_EEPROM) {
286 GBASavedataWriteEEPROM(&memory->savedata, memory->dmaTransferRegister, wordsRemaining);
287 }
288 } else {
289 cpu->memory.store16(cpu, dest, memory->dmaTransferRegister, 0);
290
291 }
292 gba->bus = memory->dmaTransferRegister;
293 }
294 int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width;
295 int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width;
296 if (source) {
297 source += sourceOffset;
298 }
299 dest += destOffset;
300 --wordsRemaining;
301 gba->performingDMA = 0;
302
303 info->nextCount = wordsRemaining;
304 info->nextSource = source;
305 info->nextDest = dest;
306
307 int i;
308 for (i = 0; i < 4; ++i) {
309 struct GBADMA* dma = &memory->dma[i];
310 int32_t time = dma->when - info->when;
311 if (time < 0 && GBADMARegisterIsEnable(dma->reg) && dma->nextCount) {
312 dma->when = info->when;
313 }
314 }
315
316 if (!wordsRemaining) {
317 info->nextCount |= 0x80000000;
318 if (sourceRegion < REGION_CART0 || destRegion < REGION_CART0) {
319 info->when += 2;
320 }
321 }
322 GBADMAUpdate(gba);
323}