mirror of
https://github.com/snesrev/zelda3.git
synced 2025-12-25 02:14:49 -05:00
329 lines
9.6 KiB
C
329 lines
9.6 KiB
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
|
|
}
|
|
}
|