| | 1 | /*************************************************************************** |
| | 2 | * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team * |
| | 3 | * * |
| | 4 | * This program is free software; you can redistribute it and/or modify * |
| | 5 | * it under the terms of the GNU General Public License as published by * |
| | 6 | * the Free Software Foundation; either version 2 of the License, or * |
| | 7 | * (at your option) any later version. * |
| | 8 | * * |
| | 9 | * This program is distributed in the hope that it will be useful, * |
| | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
| | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
| | 12 | * GNU General Public License for more details. * |
| | 13 | * * |
| | 14 | * You should have received a copy of the GNU General Public License * |
| | 15 | * along with this program; if not, write to the * |
| | 16 | * Free Software Foundation, Inc., * |
| | 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA. * |
| | 18 | ***************************************************************************/ |
| | 19 | |
| | 20 | /* |
| | 21 | * Handles PSX DMA functions. |
| | 22 | */ |
| | 23 | |
| | 24 | #include "psxdma.h" |
| | 25 | #include "gpu.h" |
| | 26 | |
| | 27 | #ifndef min |
| | 28 | #define min(a, b) ((b) < (a) ? (b) : (a)) |
| | 29 | #endif |
| | 30 | #ifndef PSXDMA_LOG |
| | 31 | #define PSXDMA_LOG(...) |
| | 32 | #endif |
| | 33 | |
| | 34 | // Dma0/1 in Mdec.c |
| | 35 | // Dma3 in CdRom.c |
| | 36 | |
| | 37 | void spuInterrupt() { |
| | 38 | if (HW_DMA4_CHCR & SWAP32(0x01000000)) |
| | 39 | { |
| | 40 | HW_DMA4_CHCR &= SWAP32(~0x01000000); |
| | 41 | DMA_INTERRUPT(4); |
| | 42 | } |
| | 43 | } |
| | 44 | |
| | 45 | void psxDma4(u32 madr, u32 bcr, u32 chcr) { // SPU |
| | 46 | u32 words, words_max = 0, words_copy; |
| | 47 | u16 *ptr; |
| | 48 | |
| | 49 | madr &= ~3; |
| | 50 | ptr = getDmaRam(madr, &words_max); |
| | 51 | if (ptr == INVALID_PTR) |
| | 52 | log_unhandled("bad dma4 madr %x\n", madr); |
| | 53 | |
| | 54 | words = words_copy = (bcr >> 16) * (bcr & 0xffff); |
| | 55 | if (words_copy > words_max) { |
| | 56 | log_unhandled("bad dma4 madr %x bcr %x\n", madr, bcr); |
| | 57 | words_copy = words_max; |
| | 58 | } |
| | 59 | |
| | 60 | switch (chcr) { |
| | 61 | case 0x01000201: //cpu to spu transfer |
| | 62 | PSXDMA_LOG("*** DMA4 SPU - mem2spu *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 63 | if (ptr == INVALID_PTR) |
| | 64 | break; |
| | 65 | SPU_writeDMAMem(ptr, words_copy * 2, psxRegs.cycle); |
| | 66 | HW_DMA4_MADR = SWAPu32(madr + words_copy * 2); |
| | 67 | // This should be much slower, like 12+ cycles/byte, it's like |
| | 68 | // that because the CPU runs too fast and fifo is not emulated. |
| | 69 | // See also set_dma_end(). |
| | 70 | set_event(PSXINT_SPUDMA, words * 4 * 4); |
| | 71 | return; |
| | 72 | |
| | 73 | case 0x01000200: //spu to cpu transfer |
| | 74 | PSXDMA_LOG("*** DMA4 SPU - spu2mem *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 75 | if (ptr == INVALID_PTR) |
| | 76 | break; |
| | 77 | SPU_readDMAMem(ptr, words_copy * 2, psxRegs.cycle); |
| | 78 | psxCpu->Clear(madr, words_copy); |
| | 79 | |
| | 80 | HW_DMA4_MADR = SWAPu32(madr + words_copy * 4); |
| | 81 | set_event(PSXINT_SPUDMA, words * 4 * 4); |
| | 82 | return; |
| | 83 | |
| | 84 | default: |
| | 85 | log_unhandled("*** DMA4 SPU - unknown *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 86 | break; |
| | 87 | } |
| | 88 | |
| | 89 | HW_DMA4_CHCR &= SWAP32(~0x01000000); |
| | 90 | DMA_INTERRUPT(4); |
| | 91 | } |
| | 92 | |
| | 93 | #if 0 |
| | 94 | // Taken from PEOPS SOFTGPU |
| | 95 | static inline boolean CheckForEndlessLoop(u32 laddr, u32 *lUsedAddr) { |
| | 96 | if (laddr == lUsedAddr[1]) return TRUE; |
| | 97 | if (laddr == lUsedAddr[2]) return TRUE; |
| | 98 | |
| | 99 | if (laddr < lUsedAddr[0]) lUsedAddr[1] = laddr; |
| | 100 | else lUsedAddr[2] = laddr; |
| | 101 | |
| | 102 | lUsedAddr[0] = laddr; |
| | 103 | |
| | 104 | return FALSE; |
| | 105 | } |
| | 106 | |
| | 107 | static u32 gpuDmaChainSize(u32 addr) { |
| | 108 | u32 size; |
| | 109 | u32 DMACommandCounter = 0; |
| | 110 | u32 lUsedAddr[3]; |
| | 111 | |
| | 112 | lUsedAddr[0] = lUsedAddr[1] = lUsedAddr[2] = 0xffffff; |
| | 113 | |
| | 114 | // initial linked list ptr (word) |
| | 115 | size = 1; |
| | 116 | |
| | 117 | do { |
| | 118 | addr &= 0x1ffffc; |
| | 119 | |
| | 120 | if (DMACommandCounter++ > 2000000) break; |
| | 121 | if (CheckForEndlessLoop(addr, lUsedAddr)) break; |
| | 122 | |
| | 123 | // # 32-bit blocks to transfer |
| | 124 | size += psxMu8( addr + 3 ); |
| | 125 | |
| | 126 | // next 32-bit pointer |
| | 127 | addr = psxMu32( addr & ~0x3 ) & 0xffffff; |
| | 128 | size += 1; |
| | 129 | } while (!(addr & 0x800000)); // contrary to some documentation, the end-of-linked-list marker is not actually 0xFF'FFFF |
| | 130 | // any pointer with bit 23 set will do. |
| | 131 | |
| | 132 | return size; |
| | 133 | } |
| | 134 | #endif |
| | 135 | |
| | 136 | void psxDma2(u32 madr, u32 bcr, u32 chcr) { // GPU |
| | 137 | u32 *ptr, madr_next, *madr_next_p; |
| | 138 | u32 words, words_left, words_max, words_copy; |
| | 139 | s32 cycles_last_cmd = 0; |
| | 140 | int do_walking; |
| | 141 | long cycles_sum; |
| | 142 | |
| | 143 | madr &= ~3; |
| | 144 | switch (chcr) { |
| | 145 | case 0x01000200: // vram2mem |
| | 146 | PSXDMA_LOG("*** DMA2 GPU - vram2mem *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 147 | ptr = getDmaRam(madr, &words_max); |
| | 148 | if (ptr == INVALID_PTR) { |
| | 149 | log_unhandled("bad dma2 madr %x\n", madr); |
| | 150 | break; |
| | 151 | } |
| | 152 | // BA blocks * BS words (word = 32-bits) |
| | 153 | words = words_copy = (bcr >> 16) * (bcr & 0xffff); |
| | 154 | if (words > words_max) { |
| | 155 | log_unhandled("bad dma2 madr %x bcr %x\n", madr, bcr); |
| | 156 | words_copy = words_max; |
| | 157 | } |
| | 158 | GPU_readDataMem(ptr, words_copy); |
| | 159 | psxCpu->Clear(madr, words_copy); |
| | 160 | |
| | 161 | HW_DMA2_MADR = SWAPu32(madr + words_copy * 4); |
| | 162 | |
| | 163 | // careful: gpu_state_change() also messes with this |
| | 164 | psxRegs.gpuIdleAfter = psxRegs.cycle + words / 4 + 16; |
| | 165 | // already 32-bit word size ((size * 4) / 4) |
| | 166 | set_event(PSXINT_GPUDMA, words / 4); |
| | 167 | return; |
| | 168 | |
| | 169 | case 0x01000201: // mem2vram |
| | 170 | PSXDMA_LOG("*** DMA 2 - GPU mem2vram *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 171 | words = words_left = (bcr >> 16) * (bcr & 0xffff); |
| | 172 | while (words_left > 0) { |
| | 173 | ptr = getDmaRam(madr, &words_max); |
| | 174 | if (ptr == INVALID_PTR) { |
| | 175 | log_unhandled("bad2 dma madr %x\n", madr); |
| | 176 | break; |
| | 177 | } |
| | 178 | words_copy = min(words_left, words_max); |
| | 179 | GPU_writeDataMem(ptr, words_copy); |
| | 180 | words_left -= words_copy; |
| | 181 | madr += words_copy * 4; |
| | 182 | } |
| | 183 | |
| | 184 | HW_DMA2_MADR = SWAPu32(madr); |
| | 185 | |
| | 186 | // careful: gpu_state_change() also messes with this |
| | 187 | psxRegs.gpuIdleAfter = psxRegs.cycle + words / 4 + 16; |
| | 188 | // already 32-bit word size ((size * 4) / 4) |
| | 189 | set_event(PSXINT_GPUDMA, words / 4); |
| | 190 | return; |
| | 191 | |
| | 192 | case 0x01000401: // dma chain |
| | 193 | PSXDMA_LOG("*** DMA 2 - GPU dma chain *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 194 | // when not emulating walking progress, end immediately |
| | 195 | // (some games abort the dma and read madr so break out of that logic) |
| | 196 | madr_next = 0xffffff; |
| | 197 | |
| | 198 | do_walking = Config.GpuListWalking; |
| | 199 | if (do_walking < 0) |
| | 200 | do_walking = Config.hacks.gpu_slow_list_walking; |
| | 201 | madr_next_p = do_walking ? &madr_next : NULL; |
| | 202 | |
| | 203 | cycles_sum = GPU_dmaChain((u32 *)psxM, madr & 0x1fffff, |
| | 204 | madr_next_p, &cycles_last_cmd); |
| | 205 | |
| | 206 | HW_DMA2_MADR = SWAPu32(madr_next); |
| | 207 | |
| | 208 | // timing hack with some lame heuristics |
| | 209 | if (Config.gpu_timing_override && (do_walking || cycles_sum > 64) |
| | 210 | && !(HW_GPU_STATUS & SWAP32(PSXGPU_DHEIGHT | PSXGPU_RGB24))) |
| | 211 | cycles_sum = Config.gpu_timing_override; |
| | 212 | |
| | 213 | psxRegs.gpuIdleAfter = psxRegs.cycle + cycles_sum + cycles_last_cmd; |
| | 214 | set_event(PSXINT_GPUDMA, cycles_sum); |
| | 215 | //printf("%u dma2cf: %6ld,%4d %08x %08x %08x %08x\n", psxRegs.cycle, |
| | 216 | // cycles_sum, cycles_last_cmd, madr, bcr, chcr, HW_DMA2_MADR); |
| | 217 | return; |
| | 218 | |
| | 219 | default: |
| | 220 | log_unhandled("*** DMA 2 - GPU unknown *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 221 | break; |
| | 222 | } |
| | 223 | |
| | 224 | HW_DMA2_CHCR &= SWAP32(~0x01000000); |
| | 225 | DMA_INTERRUPT(2); |
| | 226 | } |
| | 227 | |
| | 228 | void gpuInterrupt() { |
| | 229 | if (HW_DMA2_CHCR == SWAP32(0x01000401) && !(HW_DMA2_MADR & SWAP32(0x800000))) |
| | 230 | { |
| | 231 | u32 madr_next = SWAPu32(HW_DMA2_MADR); |
| | 232 | s32 cycles_sum = psxRegs.gpuIdleAfter - psxRegs.cycle; |
| | 233 | s32 cycles_last_cmd = 0; |
| | 234 | |
| | 235 | do { |
| | 236 | cycles_sum += cycles_last_cmd; |
| | 237 | cycles_sum += GPU_dmaChain((u32 *)psxM, madr_next & 0x1fffff, |
| | 238 | &madr_next, &cycles_last_cmd); |
| | 239 | } |
| | 240 | while (cycles_sum <= 0 && !(madr_next & 0x800000)); |
| | 241 | HW_DMA2_MADR = SWAPu32(madr_next); |
| | 242 | psxRegs.gpuIdleAfter = psxRegs.cycle + cycles_sum + cycles_last_cmd; |
| | 243 | set_event(PSXINT_GPUDMA, cycles_sum); |
| | 244 | //printf("%u dma2cn: %6d,%4d %08x\n", psxRegs.cycle, cycles_sum, |
| | 245 | // cycles_last_cmd, HW_DMA2_MADR); |
| | 246 | return; |
| | 247 | } |
| | 248 | if (HW_DMA2_CHCR & SWAP32(0x01000000)) |
| | 249 | { |
| | 250 | HW_DMA2_CHCR &= SWAP32(~0x01000000); |
| | 251 | DMA_INTERRUPT(2); |
| | 252 | } |
| | 253 | } |
| | 254 | |
| | 255 | void psxAbortDma2() { |
| | 256 | psxRegs.gpuIdleAfter = psxRegs.cycle + 32; |
| | 257 | } |
| | 258 | |
| | 259 | void psxDma6(u32 madr, u32 bcr, u32 chcr) { |
| | 260 | u32 words, words_max; |
| | 261 | u32 *mem; |
| | 262 | |
| | 263 | PSXDMA_LOG("*** DMA6 OT *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 264 | |
| | 265 | if (chcr == 0x11000002) { |
| | 266 | madr &= ~3; |
| | 267 | mem = getDmaRam(madr, &words_max); |
| | 268 | if (mem == INVALID_PTR) { |
| | 269 | log_unhandled("bad6 dma madr %x\n", madr); |
| | 270 | HW_DMA6_CHCR &= SWAP32(~0x11000000); |
| | 271 | DMA_INTERRUPT(6); |
| | 272 | return; |
| | 273 | } |
| | 274 | |
| | 275 | // already 32-bit size |
| | 276 | words = bcr; |
| | 277 | |
| | 278 | while (bcr-- && mem > (u32 *)psxM) { |
| | 279 | *mem-- = SWAP32((madr - 4) & 0xffffff); |
| | 280 | madr -= 4; |
| | 281 | } |
| | 282 | *++mem = SWAP32(0xffffff); |
| | 283 | |
| | 284 | // halted |
| | 285 | psxRegs.cycle += words; |
| | 286 | set_event(PSXINT_GPUOTCDMA, 16); |
| | 287 | return; |
| | 288 | } |
| | 289 | else { |
| | 290 | // Unknown option |
| | 291 | log_unhandled("*** DMA6 OT - unknown *** %x addr = %x size = %x\n", chcr, madr, bcr); |
| | 292 | } |
| | 293 | |
| | 294 | HW_DMA6_CHCR &= SWAP32(~0x11000000); |
| | 295 | DMA_INTERRUPT(6); |
| | 296 | } |
| | 297 | |
| | 298 | void gpuotcInterrupt() |
| | 299 | { |
| | 300 | if (HW_DMA6_CHCR & SWAP32(0x01000000)) |
| | 301 | { |
| | 302 | HW_DMA6_CHCR &= SWAP32(~0x11000000); |
| | 303 | DMA_INTERRUPT(6); |
| | 304 | } |
| | 305 | } |