ref: 7bb7bf23e83e2c3f870b92d31d25ddc917aeb04f
dir: /snes/dma.c/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #include <stddef.h> #include "dma.h" #include "snes.h" #include "snes_regs.h" static const int bAdrOffsets[8][4] = { {0, 0, 0, 0}, {0, 1, 0, 1}, {0, 0, 0, 0}, {0, 0, 1, 1}, {0, 1, 2, 3}, {0, 1, 0, 1}, {0, 0, 0, 0}, {0, 0, 1, 1} }; static const int transferLength[8] = { 1, 2, 2, 4, 4, 4, 2, 4 }; static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB); Dma* dma_init(Snes* snes) { Dma* dma = (Dma*)malloc(sizeof(Dma)); dma->snes = snes; return dma; } void dma_free(Dma* dma) { free(dma); } void dma_reset(Dma* dma) { for(int i = 0; i < 8; i++) { dma->channel[i].bAdr = 0xff; dma->channel[i].aAdr = 0xffff; dma->channel[i].aBank = 0xff; dma->channel[i].size = 0xffff; dma->channel[i].indBank = 0xff; dma->channel[i].tableAdr = 0xffff; dma->channel[i].repCount = 0xff; dma->channel[i].unusedByte = 0xff; dma->channel[i].dmaActive = false; dma->channel[i].hdmaActive = false; dma->channel[i].mode = 7; dma->channel[i].fixed = true; dma->channel[i].decrement = true; dma->channel[i].indirect = true; dma->channel[i].fromB = true; dma->channel[i].unusedBit = true; dma->channel[i].doTransfer = false; dma->channel[i].terminated = false; dma->channel[i].offIndex = 0; } dma->hdmaTimer = 0; dma->dmaTimer = 0; dma->dmaBusy = false; } void dma_saveload(Dma *dma, SaveLoadFunc *func, void *ctx) { func(ctx, &dma->channel, sizeof(Dma) - offsetof(Dma, channel)); } uint8_t dma_read(Dma* dma, uint16_t adr) { uint8_t c = (adr & 0x70) >> 4; switch(adr & 0xf) { case 0x0: { uint8_t val = dma->channel[c].mode; val |= dma->channel[c].fixed << 3; val |= dma->channel[c].decrement << 4; val |= dma->channel[c].unusedBit << 5; val |= dma->channel[c].indirect << 6; val |= dma->channel[c].fromB << 7; return val; } case 0x1: { return dma->channel[c].bAdr; } case 0x2: { return dma->channel[c].aAdr & 0xff; } case 0x3: { return dma->channel[c].aAdr >> 8; } case 0x4: { return dma->channel[c].aBank; } case 0x5: { return dma->channel[c].size & 0xff; } case 0x6: { return dma->channel[c].size >> 8; } case 0x7: { return dma->channel[c].indBank; } case 0x8: { return dma->channel[c].tableAdr & 0xff; } case 0x9: { return dma->channel[c].tableAdr >> 8; } case 0xa: { return dma->channel[c].repCount; } case 0xb: case 0xf: { return dma->channel[c].unusedByte; } default: { return dma->snes->openBus; } } } void dma_write(Dma* dma, uint16_t adr, uint8_t val) { uint8_t c = (adr & 0x70) >> 4; switch(adr & 0xf) { case 0x0: { dma->channel[c].mode = val & 0x7; dma->channel[c].fixed = val & 0x8; dma->channel[c].decrement = val & 0x10; dma->channel[c].unusedBit = val & 0x20; dma->channel[c].indirect = val & 0x40; dma->channel[c].fromB = val & 0x80; break; } case 0x1: { dma->channel[c].bAdr = val; break; } case 0x2: { dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff00) | val; break; } case 0x3: { dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff) | (val << 8); break; } case 0x4: { dma->channel[c].aBank = val; break; } case 0x5: { dma->channel[c].size = (dma->channel[c].size & 0xff00) | val; break; } case 0x6: { dma->channel[c].size = (dma->channel[c].size & 0xff) | (val << 8); break; } case 0x7: { dma->channel[c].indBank = val; break; } case 0x8: { dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff00) | val; break; } case 0x9: { dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff) | (val << 8); break; } case 0xa: { dma->channel[c].repCount = val; break; } case 0xb: case 0xf: { dma->channel[c].unusedByte = val; break; } default: { break; } } } void dma_doDma(Dma* dma) { /*if(dma->dmaTimer > 0) { dma->dmaTimer -= 2; return; }*/ // figure out first channel that is active int i = 0; for(i = 0; i < 8; i++) { if(dma->channel[i].dmaActive) { break; } } if(i == 8) { // no active channels dma->dmaBusy = false; return; } // do channel i dma_transferByte( dma, dma->channel[i].aAdr, dma->channel[i].aBank, dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][dma->channel[i].offIndex++], dma->channel[i].fromB ); dma->channel[i].offIndex &= 3; dma->dmaTimer += 6; // 8 cycles for each byte taken, -2 for this cycle if(!dma->channel[i].fixed) { dma->channel[i].aAdr += dma->channel[i].decrement ? -1 : 1; } dma->channel[i].size--; if(dma->channel[i].size == 0) { dma->channel[i].offIndex = 0; // reset offset index dma->channel[i].dmaActive = false; dma->dmaTimer += 8; // 8 cycle overhead per channel } } void dma_initHdma(Dma* dma) { dma->hdmaTimer = 0; bool hdmaHappened = false; for(int i = 0; i < 8; i++) { if(dma->channel[i].hdmaActive) { hdmaHappened = true; // terminate any dma dma->channel[i].dmaActive = false; dma->channel[i].offIndex = 0; // load address, repCount, and indirect address if needed dma->channel[i].tableAdr = dma->channel[i].aAdr; dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); dma->hdmaTimer += 8; // 8 cycle overhead for each active channel if(dma->channel[i].indirect) { dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8; dma->hdmaTimer += 16; // another 16 cycles for indirect (total 24) } dma->channel[i].doTransfer = true; } else { dma->channel[i].doTransfer = false; } dma->channel[i].terminated = false; } if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle } void dma_doHdma(Dma* dma) { dma->hdmaTimer = 0; bool hdmaHappened = false; for(int i = 0; i < 8; i++) { if(dma->channel[i].hdmaActive && !dma->channel[i].terminated) { hdmaHappened = true; // terminate any dma dma->channel[i].dmaActive = false; dma->channel[i].offIndex = 0; // do the hdma dma->hdmaTimer += 8; // 8 cycles overhead for each active channel if(dma->channel[i].doTransfer) { for(int j = 0; j < transferLength[dma->channel[i].mode]; j++) { dma->hdmaTimer += 8; // 8 cycles for each byte transferred if(dma->channel[i].indirect) { dma_transferByte( dma, dma->channel[i].size++, dma->channel[i].indBank, dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB ); } else { dma_transferByte( dma, dma->channel[i].tableAdr++, dma->channel[i].aBank, dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB ); } } } dma->channel[i].repCount--; dma->channel[i].doTransfer = dma->channel[i].repCount & 0x80; if((dma->channel[i].repCount & 0x7f) == 0) { dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); if(dma->channel[i].indirect) { // TODO: oddness with not fetching high byte if last active channel and reCount is 0 dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8; dma->hdmaTimer += 16; // 16 cycles for new indirect address } if(dma->channel[i].repCount == 0) dma->channel[i].terminated = true; dma->channel[i].doTransfer = true; } } } if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle } static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB) { // TODO: invalid writes: // accesing b-bus via a-bus gives open bus, // $2180-$2183 while accessing ram via a-bus open busses $2180-$2183 // cannot access $4300-$437f (dma regs), or $420b / $420c if(fromB) { snes_write(dma->snes, (aBank << 16) | aAdr, snes_readBBus(dma->snes, bAdr)); } else { uint8_t data = snes_read(dma->snes, (aBank << 16) | aAdr); snes_writeBBus(dma->snes, bAdr, data); } } bool dma_cycle(Dma* dma) { if(dma->hdmaTimer > 0) { dma->hdmaTimer -= 2; return true; } else if(dma->dmaBusy) { dma_doDma(dma); return true; } return false; } void dma_startDma(Dma* dma, uint8_t val, bool hdma) { for(int i = 0; i < 8; i++) { if(hdma) { dma->channel[i].hdmaActive = val & (1 << i); } else { dma->channel[i].dmaActive = val & (1 << i); } } if(!hdma) { dma->dmaBusy = val; dma->dmaTimer += dma->dmaBusy ? 16 : 0; // 12-24 cycle overhead for entire dma transfer } }