ref: d44f55b337b4b6e6e00ef698f656e17655694d24
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
}
}