1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
/**
* @file z_std_dma.c
*
* This file implements a system for structuring the ROM image and retrieving data. It is designed to have the same
* external interface regardless of whether the ROM segments are compressed or not.
*
* The ROM image is partitioned into regions that are entered into the DMA data table `gDmaDataTable`. External code
* does not directly address locations on the ROM image directly, instead a "Virtual ROM" addressing is used. Virtual
* ROM is defined to be the ROM address of a segment in a totally uncompressed ROM. For uncompressed ROMs, "physical"
* ROM and VROM addresses coincide. The DMA data table converts VROM to ROM addresses so that code may exclusively use
* VROM addresses even if the ROM is compressed.
*
* External code typically submits requests to the DMA Manager asking for a transfer in terms of Virtual ROM; the DMA
* Manager translates this to the physical ROM address, performs the transfer to RAM and decompresses the data if
* required.
* Requests are processed in the order they are received and may be submitted both synchronously and asynchronously.
*
* There are some additional provisions to ensure that audio DMA is particularly high-speed, the audio data is assumed
* to be uncompressed and the request queue and address translation is skipped.
*/
#include "z64dma.h"
#include "carthandle.h"
#include "fault.h"
#include "macros.h"
#include "segment_symbols.h"
#include "stack.h"
#include "libu64/stackcheck.h"
#include "yaz0.h"
#include "z64thread.h"
size_t gDmaMgrDmaBuffSize = DMAMGR_DEFAULT_BUFSIZE;
StackEntry sDmaMgrStackInfo;
u16 sNumDmaEntries;
OSMesgQueue sDmaMgrMsgQueue;
OSMesg sDmaMgrMsgBuf[32];
OSThread sDmaMgrThread;
STACK(sDmaMgrStack, 0x500);
/**
* Transfer `size` bytes from physical ROM address `rom` to `ram`.
*
* This function is intended for internal use only, however it is possible to use this function externally in which
* case it behaves as a synchronous transfer, data is available as soon as this function returns.
*
* Transfers are divided into chunks based on the current value of `gDmaMgrDmaBuffSize` to avoid congestion of the PI
* so that higher priority transfers can still be carried out in a timely manner. The transfers are sent in a queue to
* the OS PI Manager which performs the transfer.
*
* @return 0 if successful, -1 if the DMA could not be queued with the PI Manager.
*/
s32 DmaMgr_DmaRomToRam(uintptr_t rom, void* ram, size_t size) {
OSIoMesg ioMsg;
OSMesgQueue queue;
OSMesg msg[1];
s32 ret;
size_t buffSize = gDmaMgrDmaBuffSize;
osInvalDCache(ram, size);
osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg));
if (buffSize != 0) {
while (buffSize < size) {
ioMsg.hdr.pri = OS_MESG_PRI_NORMAL;
ioMsg.hdr.retQueue = &queue;
ioMsg.devAddr = rom;
ioMsg.dramAddr = ram;
ioMsg.size = buffSize;
ret = osEPiStartDma(gCartHandle, &ioMsg, OS_READ);
if (ret != 0) {
goto end;
}
osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
size -= buffSize;
rom += buffSize;
ram = (u8*)ram + buffSize;
}
}
ioMsg.hdr.pri = OS_MESG_PRI_NORMAL;
ioMsg.hdr.retQueue = &queue;
ioMsg.devAddr = rom;
ioMsg.dramAddr = ram;
ioMsg.size = size;
ret = osEPiStartDma(gCartHandle, &ioMsg, OS_READ);
if (ret != 0) {
goto end;
}
osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
osInvalDCache(ram, size);
end:
return ret;
}
/**
* Callback function to facilitate audio DMA. Audio DMA does not use the request queue as audio data is often needed
* very soon after the request is sent, requiring a higher priority method for enqueueing a DMA on the OS PI command
* queue.
*
* @param pihandle Cartridge ROM PI Handle.
* @param mb IO Message describing the transfer.
* @param direction Read or write.
* @return 0 if the IO Message was successfully put on the OS PI command queue, < 0 otherwise
*/
s32 DmaMgr_AudioDmaHandler(OSPiHandle* pihandle, OSIoMesg* mb, s32 direction) {
s32 ret = osEPiStartDma(pihandle, mb, direction);
if (ret != 0) {
PRINTF("OOPS!!\n");
}
return ret;
}
DmaEntry* DmaMgr_FindDmaEntry(uintptr_t vrom) {
DmaEntry* entry;
for (entry = gDmaDataTable; entry->file.vromEnd != 0; entry++) {
if ((vrom >= entry->file.vromStart) && (vrom < entry->file.vromEnd)) {
return entry;
}
}
return NULL;
}
s32 DmaMgr_TranslateVromToRom(uintptr_t vrom) {
DmaEntry* entry = DmaMgr_FindDmaEntry(vrom);
if (entry != NULL) {
if (entry->romEnd == 0) {
return vrom + entry->romStart - entry->file.vromStart;
}
if (vrom == entry->file.vromStart) {
return entry->romStart;
}
return -1;
}
return -1;
}
s32 DmaMgr_FindDmaIndex(uintptr_t vrom) {
DmaEntry* entry = DmaMgr_FindDmaEntry(vrom);
if (entry != NULL) {
return entry - gDmaDataTable;
}
return -1;
}
const char* func_800809F4(uintptr_t vrom) {
return "??";
}
void DmaMgr_ProcessRequest(DmaRequest* req) {
uintptr_t vrom = req->vromAddr;
void* ram = req->dramAddr;
size_t size = req->size;
uintptr_t romStart;
size_t romSize;
DmaEntry* entry;
s32 index = DmaMgr_FindDmaIndex(vrom);
if ((index >= 0) && (index < sNumDmaEntries)) {
entry = &gDmaDataTable[index];
if (entry->romEnd == 0) {
// romEnd of 0 indicates that the file is uncompressed. Files that are stored uncompressed can have
// only part of their content loaded into RAM, so DMA only the requested region.
if (entry->file.vromEnd < (vrom + size)) {
// Error, vrom + size ends up in a different file than it started in
#if MM_VERSION >= N64_US
Fault_AddHungupAndCrash("../z_std_dma.c", 499);
#else
Fault_AddHungupAndCrash("../z_std_dma.c", 496);
#endif
}
DmaMgr_DmaRomToRam((entry->romStart + vrom) - entry->file.vromStart, ram, size);
} else {
// File is compressed. Files that are stored compressed must be loaded into RAM all at once.
romSize = entry->romEnd - entry->romStart;
romStart = entry->romStart;
if (vrom != entry->file.vromStart) {
// Error, requested vrom is not the start of a file
#if MM_VERSION >= N64_US
Fault_AddHungupAndCrash("../z_std_dma.c", 518);
#else
Fault_AddHungupAndCrash("../z_std_dma.c", 515);
#endif
}
if (size != (entry->file.vromEnd - entry->file.vromStart)) {
// Error, only part of the file was requested
#if MM_VERSION >= N64_US
Fault_AddHungupAndCrash("../z_std_dma.c", 525);
#else
Fault_AddHungupAndCrash("../z_std_dma.c", 522);
#endif
}
// Reduce the thread priority and decompress the file, the decompression routine handles the DMA
// in chunks. Restores the thread priority when done.
osSetThreadPri(NULL, Z_PRIORITY_DMAMGR_LOW);
Yaz0_Decompress(romStart, ram, romSize);
osSetThreadPri(NULL, Z_PRIORITY_DMAMGR);
}
} else {
// Error, invalid index
#if MM_VERSION >= N64_US
Fault_AddHungupAndCrash("../z_std_dma.c", 558);
#else
Fault_AddHungupAndCrash("../z_std_dma.c", 555);
#endif
}
}
void DmaMgr_ThreadEntry(void* arg) {
OSMesg msg;
DmaRequest* req;
PRINTF(T("DMAマネージャスレッド実行開始\n", "DMA manager thread execution start\n"));
while (true) {
// Wait for DMA Requests to arrive from other threads
osRecvMesg(&sDmaMgrMsgQueue, &msg, OS_MESG_BLOCK);
if (msg == NULL) {
break;
}
req = (DmaRequest*)msg;
DmaMgr_ProcessRequest(req);
// Notify the sender that the request has been processed
if (req->notifyQueue != NULL) {
osSendMesg(req->notifyQueue, req->notifyMsg, OS_MESG_NOBLOCK);
}
}
PRINTF(T("DMAマネージャスレッド実行終了\n", "DMA manager thread execution end\n"));
}
/**
* Submit an asynchronous DMA request. Unlike other DMA requests, this will not block the
* current thread. Data arrival is not immediate however, ensure that the request has completed by awaiting a message
* sent to `queue` when the DMA operation has completed.
*
* @param req DMA request, filled out internally.
* @param ram Location in DRAM for data to be written.
* @param vrom Virtual ROM location for data to be read.
* @param size Transfer size.
* @param queue Message queue to notify with `msg` once the transfer is complete.
* @param msg Message to send to `queue` once the transfer is complete.
* @return -2 if Irq is in NMI reset state, 0 otherwise
*/
s32 DmaMgr_RequestAsync(DmaRequest* req, void* ram, uintptr_t vrom, size_t size, UNK_TYPE4 unused, OSMesgQueue* queue,
OSMesg msg) {
if (gIrqMgrResetStatus >= IRQ_RESET_STATUS_NMI) {
return -2;
}
req->vromAddr = vrom;
req->dramAddr = ram;
req->size = size;
req->unk14 = 0;
req->notifyQueue = queue;
req->notifyMsg = msg;
osSendMesg(&sDmaMgrMsgQueue, (OSMesg)req, OS_MESG_BLOCK);
return 0;
}
/**
* Submit a synchronous DMA request. This will block the current thread until the requested transfer is complete. Data
* is immediately available as soon as this function returns.
*
* @param ram Location in DRAM for data to be written.
* @param vrom Virtual ROM location for data to be read.
* @param size Transfer size.
* @return 0
*/
s32 DmaMgr_RequestSync(void* ram, uintptr_t vrom, size_t size) {
DmaRequest req;
OSMesgQueue queue;
OSMesg msg[1];
s32 ret;
osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg));
ret = DmaMgr_RequestAsync(&req, ram, vrom, size, 0, &queue, NULL);
if (ret == -1) { // DmaMgr_RequestAsync does not return -1
return ret;
}
osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
return 0;
}
void DmaMgr_Init(void) {
DmaMgr_DmaRomToRam(SEGMENT_ROM_START(dmadata), gDmaDataTable, SEGMENT_ROM_SIZE(dmadata));
{
DmaEntry* entry = gDmaDataTable;
s32 index = 0;
while (entry->file.vromEnd != 0) {
entry++;
index++;
}
sNumDmaEntries = index;
}
osCreateMesgQueue(&sDmaMgrMsgQueue, sDmaMgrMsgBuf, ARRAY_COUNT(sDmaMgrMsgBuf));
StackCheck_Init(&sDmaMgrStackInfo, sDmaMgrStack, STACK_TOP(sDmaMgrStack), 0, 0x100, "dmamgr");
osCreateThread(&sDmaMgrThread, Z_THREAD_ID_DMAMGR, DmaMgr_ThreadEntry, NULL, STACK_TOP(sDmaMgrStack),
Z_PRIORITY_DMAMGR);
osStartThread(&sDmaMgrThread);
}
void DmaMgr_Stop(void) {
osSendMesg(&sDmaMgrMsgQueue, NULL, OS_MESG_BLOCK);
}
|