refactored SH4 DMA

added PIO CD_READ support to GDROM
updated Holly / GDROM DMA to work with new DMA code
This commit is contained in:
Anthony Pesch 2016-03-24 19:59:54 -07:00
parent 8069a76100
commit 9efdb02b63
10 changed files with 450 additions and 224 deletions

View File

@ -8,7 +8,7 @@ namespace re {
namespace hw {
namespace gdrom {
enum { SECTOR_SIZE = 2352 };
static const int SECTOR_SIZE = 2352;
struct Track {
Track()

View File

@ -1,3 +1,4 @@
#include "core/memory.h"
#include "hw/gdrom/gdrom.h"
#include "hw/gdrom/gdrom_replies.inc"
#include "hw/holly/holly.h"
@ -24,15 +25,12 @@ GDROM::GDROM(Dreamcast &dc)
sectnum_{0},
byte_count_{0},
status_{0},
pio_idx_(0),
pio_head_(0),
pio_size_(0),
dma_head_(0),
dma_size_(0),
state_(STATE_STANDBY),
current_disc_(nullptr) {
dma_buffer_ = new uint8_t[0x1000000];
}
GDROM::~GDROM() { delete[] dma_buffer_; }
current_disc_(nullptr) {}
bool GDROM::Init() {
memory_ = dc_.memory;
@ -57,6 +55,25 @@ void GDROM::SetDisc(std::unique_ptr<Disc> disc) {
status_.BSY = 0;
}
void GDROM::BeginDMA() {
}
int GDROM::ReadDMA(uint8_t *data, int data_size) {
int remaining = dma_size_ - dma_head_;
int n = std::min(remaining, data_size);
memcpy(data, &dma_buffer_[dma_head_], n);
dma_head_ += n;
return n;
}
void GDROM::EndDMA() {
// reset DMA write state
dma_size_ = 0;
// CD_READ command is now done
TriggerEvent(EV_SPI_CMD_DONE);
}
template uint8_t GDROM::ReadRegister(uint32_t addr);
template uint16_t GDROM::ReadRegister(uint32_t addr);
template uint32_t GDROM::ReadRegister(uint32_t addr);
@ -71,17 +88,15 @@ T GDROM::ReadRegister(uint32_t addr) {
}
switch (offset) {
// gdrom regs
case GD_ALTSTAT_DEVCTRL_OFFSET:
// this register is the same as the status register, but it does not
// clear DMA status information when it is accessed
return status_.full;
case GD_DATA_OFFSET: {
// TODO add ReadData function
uint16_t v = *(uint16_t *)&pio_buffer_[pio_idx_];
pio_idx_ += 2;
if (pio_idx_ == pio_size_) {
uint16_t v = re::load<uint16_t>(&pio_buffer_[pio_head_]);
pio_head_ += 2;
if (pio_head_ == pio_size_) {
TriggerEvent(EV_SPI_WRITE_END);
}
return static_cast<T>(v);
@ -110,8 +125,6 @@ T GDROM::ReadRegister(uint32_t addr) {
case GD_STATUS_COMMAND_OFFSET:
holly_->UnrequestInterrupt(HOLLY_INTC_G1GDINT);
return status_.full;
// g1 bus regs
}
return reg.value;
@ -130,21 +143,21 @@ void GDROM::WriteRegister(uint32_t addr, T value) {
return;
}
uint32_t old_value = reg.value;
reg.value = static_cast<uint32_t>(value);
switch (offset) {
// gdrom regs
case GD_ALTSTAT_DEVCTRL_OFFSET:
// LOG_INFO("GD_DEVCTRL 0x%x", (uint32_t)value);
break;
case GD_DATA_OFFSET: {
// TODO add WriteData function
*(uint16_t *)(&pio_buffer_[pio_idx_]) = reg.value & 0xffff;
pio_idx_ += 2;
if ((state_ == STATE_SPI_READ_CMD && pio_idx_ == 12) ||
(state_ == STATE_SPI_READ_DATA && pio_idx_ == pio_size_)) {
re::store(&pio_buffer_[pio_head_],
static_cast<uint16_t>(reg.value & 0xffff));
pio_head_ += 2;
// check if we've finished reading a command / the remaining data
if ((state_ == STATE_SPI_READ_CMD && pio_head_ == SPI_CMD_SIZE) ||
(state_ == STATE_SPI_READ_DATA && pio_head_ == pio_size_)) {
TriggerEvent(EV_SPI_READ_END);
}
} break;
@ -176,48 +189,6 @@ void GDROM::WriteRegister(uint32_t addr, T value) {
case GD_STATUS_COMMAND_OFFSET:
ProcessATACommand((ATACommand)reg.value);
break;
// g1 bus regs
case SB_GDEN_OFFSET:
// NOTE for when this is made asynchtonous
// This register can also be used to forcibly terminate such a DMA
// transfer that is in progress, by writing a "0" to this register.
break;
case SB_GDST_OFFSET:
// if a "0" is written to this register, it is ignored
reg.value |= old_value;
if (reg.value) {
// TODO add DMAStart function
// NOTE for when this is made asynchronous
// Cautions during DMA operations: If the SB_GDAPRO, SB_G1GDRC,
// SB_GDSTAR, SB_GDLEN, or SB_GDDIR register is overwritten while a DMA
// operation is in progress, the new setting has no effect on the
// current DMA operation.
CHECK_EQ(dc_.SB_GDEN, 1); // dma enabled
CHECK_EQ(dc_.SB_GDDIR, 1); // gd-rom -> system memory
CHECK_EQ(dc_.SB_GDLEN, (uint32_t)dma_size_);
int transfer_size = dc_.SB_GDLEN;
uint32_t start = dc_.SB_GDSTAR;
LOG_INFO("GD DMA START 0x%x -> 0x%x, 0x%x bytes", start,
start + transfer_size, transfer_size);
memory_->Memcpy(start, dma_buffer_, transfer_size);
// done
dc_.SB_GDSTARD = start + transfer_size;
dc_.SB_GDLEND = transfer_size;
dc_.SB_GDST = 0;
holly_->RequestInterrupt(HOLLY_INTC_G1DEINT);
// finish off CD_READ command
TriggerEvent(EV_SPI_CMD_DONE);
}
break;
}
}
@ -239,7 +210,7 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
case EV_SPI_WAIT_CMD: {
CHECK_EQ(state_, STATE_STANDBY);
pio_idx_ = 0;
pio_head_ = 0;
intreason_.CoD = 1;
intreason_.IO = 0;
@ -256,9 +227,9 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
int size = static_cast<int>(arg1);
CHECK_NE(size, 0);
pio_idx_ = 0;
pio_head_ = 0;
pio_size_ = size;
spi_read_offset_ = offset;
pio_read_offset_ = offset;
byte_count_.full = size;
intreason_.IO = 1;
@ -275,12 +246,10 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
CHECK(state_ == STATE_SPI_READ_CMD || state_ == STATE_SPI_READ_DATA);
if (state_ == STATE_SPI_READ_CMD) {
CHECK_EQ(pio_head_, SPI_CMD_SIZE);
ProcessSPICommand(pio_buffer_);
} else if (state_ == STATE_SPI_READ_DATA) {
// only SPI_SET_MODE uses this
memcpy(reinterpret_cast<uint8_t *>(&reply_11[spi_read_offset_ >> 1]),
pio_buffer_, pio_size_);
TriggerEvent(EV_SPI_CMD_DONE);
ProcessSetMode(pio_read_offset_, pio_buffer_, pio_head_);
}
} break;
@ -289,13 +258,13 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
uint8_t *data = reinterpret_cast<uint8_t *>(arg0);
int size = static_cast<int>(arg1);
CHECK_NE(size, 0);
CHECK(size && size < static_cast<int>(sizeof(pio_buffer_)));
memcpy(pio_buffer_, data, size);
pio_size_ = size;
pio_idx_ = 0;
pio_head_ = 0;
byte_count_.full = size;
byte_count_.full = pio_size_;
intreason_.IO = 1;
intreason_.CoD = 0;
status_.DRQ = 1;
@ -306,15 +275,66 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
state_ = STATE_SPI_WRITE_DATA;
} break;
case EV_SPI_WRITE_END: {
CHECK_EQ(state_, STATE_SPI_WRITE_DATA);
case EV_SPI_WRITE_SECTORS: {
CHECK(state_ == STATE_SPI_READ_CMD || state_ == STATE_SPI_WRITE_SECTORS);
TriggerEvent(EV_SPI_CMD_DONE);
if (cdreq_.dma) {
int max_dma_size = cdreq_.num_sectors * SECTOR_SIZE;
// reserve the worst case size
dma_buffer_.Reserve(max_dma_size);
// read to DMA buffer
dma_size_ = ReadSectors(cdreq_.first_sector, cdreq_.sector_format,
cdreq_.sector_mask, cdreq_.num_sectors,
dma_buffer_.data(), dma_buffer_.capacity());
dma_head_ = 0;
// gdrom state won't be updated until DMA transfer is completed
} else {
int max_pio_sectors = sizeof(pio_buffer_) / SECTOR_SIZE;
// fill PIO buffer with as many sectors as possible
int num_sectors = std::min(cdreq_.num_sectors, max_pio_sectors);
pio_size_ = ReadSectors(cdreq_.first_sector, cdreq_.sector_format,
cdreq_.sector_mask, num_sectors, pio_buffer_,
sizeof(pio_buffer_));
pio_head_ = 0;
// update sector read state
cdreq_.first_sector += num_sectors;
cdreq_.num_sectors -= num_sectors;
// update gdrom state
byte_count_.full = pio_size_;
intreason_.IO = 1;
intreason_.CoD = 0;
status_.DRQ = 1;
status_.BSY = 0;
holly_->RequestInterrupt(HOLLY_INTC_G1GDINT);
}
state_ = STATE_SPI_WRITE_SECTORS;
} break;
case EV_SPI_WRITE_END: {
CHECK(state_ == STATE_SPI_WRITE_DATA ||
state_ == STATE_SPI_WRITE_SECTORS);
// if there are still sectors remaining to be written out to the PIO
// buffer, continue doing so
if (state_ == STATE_SPI_WRITE_SECTORS && cdreq_.num_sectors) {
TriggerEvent(EV_SPI_WRITE_SECTORS);
} else {
TriggerEvent(EV_SPI_CMD_DONE);
}
} break;
case EV_SPI_CMD_DONE: {
CHECK(state_ == STATE_SPI_READ_CMD || state_ == STATE_SPI_READ_DATA ||
state_ == STATE_SPI_WRITE_DATA);
state_ == STATE_SPI_WRITE_DATA ||
state_ == STATE_SPI_WRITE_SECTORS);
intreason_.IO = 1;
intreason_.CoD = 1;
@ -334,13 +354,13 @@ void GDROM::ProcessATACommand(ATACommand cmd) {
status_.BSY = 1;
switch (cmd) {
// case ATA_NOP:
// // Setting "abort" in the error register
// // Setting "error" in the status register
// // Clearing BUSY in the status register
// // Asserting the INTRQ signal
// TriggerEvent(EV_ATA_CMD_DONE);
// break;
case ATA_NOP:
// Setting "abort" in the error register
// Setting "error" in the status register
// Clearing BUSY in the status register
// Asserting the INTRQ signal
TriggerEvent(EV_ATA_CMD_DONE);
break;
case ATA_SOFT_RESET:
SetDisc(std::move(current_disc_));
@ -381,9 +401,23 @@ void GDROM::ProcessSPICommand(uint8_t *data) {
//
// Packet Command Flow For PIO DATA To Host
//
// case SPI_REQ_STAT:
// LOG_FATAL("Unhandled");
// break;
case SPI_REQ_STAT: {
int addr = data[2];
int sz = data[4];
uint8_t stat[10];
stat[0] = sectnum_.status;
stat[1] = sectnum_.format << 4;
stat[2] = 0x4;
stat[3] = 2;
stat[4] = 0;
stat[5] = 0;
stat[6] = 0;
stat[7] = 0;
stat[8] = 0;
stat[9] = 0;
TriggerEvent(EV_SPI_WRITE_START, reinterpret_cast<intptr_t>(&stat[addr]),
sz);
} break;
case SPI_REQ_MODE: {
int addr = data[2];
@ -392,7 +426,6 @@ void GDROM::ProcessSPICommand(uint8_t *data) {
} break;
// case SPI_REQ_ERROR:
// LOG_FATAL("Unhandled");
// break;
case SPI_GET_TOC: {
@ -420,42 +453,20 @@ void GDROM::ProcessSPICommand(uint8_t *data) {
} break;
case SPI_CD_READ: {
auto GetFAD = [](uint8_t a, uint8_t b, uint8_t c, bool msf) {
if (msf) {
// MSF mode
// Byte 2 - Start time: minutes (binary 0 - 255)
// Byte 3 - Start time: seconds (binary 0 - 59)
// Byte 4 - Start time: frames (binary 0 - 74)
return (a * 60 * 75) + (b * 75) + c;
}
bool msf = (data[1] & 0x1);
// FAD mode
// Byte 2 - Start frame address (MSB)
// Byte 3 - Start frame address
// Byte 4- Start frame address (LSB)
return (a << 16) | (b << 8) | c;
};
cdreq_.dma = features_.dma;
cdreq_.sector_format = (SectorFormat)((data[1] & 0xe) >> 1);
cdreq_.sector_mask = (SectorMask)((data[1] >> 4) & 0xff);
cdreq_.first_sector = GetFAD(data[2], data[3], data[4], msf);
cdreq_.num_sectors = (data[8] << 16) | (data[9] << 8) | data[10];
bool use_dma = features_.dma;
bool use_msf = (data[1] & 0x1);
SectorFormat expected_format = (SectorFormat)((data[1] & 0xe) >> 1);
DataMask data_mask = (DataMask)((data[1] >> 4) & 0xff);
int start_addr = GetFAD(data[2], data[3], data[4], use_msf);
int num_sectors = (data[8] << 16) | (data[9] << 8) | data[10];
CHECK_EQ(cdreq_.sector_format, SECTOR_M1);
CHECK_EQ(expected_format, SECTOR_M1);
if (use_dma) {
int r = ReadSectors(start_addr, expected_format, data_mask, num_sectors,
dma_buffer_);
dma_size_ = r;
} else {
LOG_FATAL("Unsupported non-dma CD read");
}
TriggerEvent(EV_SPI_WRITE_SECTORS);
} break;
// case SPI_CD_READ2:
// LOG_FATAL("Unhandled");
// break;
//
@ -495,6 +506,12 @@ void GDROM::ProcessSPICommand(uint8_t *data) {
}
}
void GDROM::ProcessSetMode(int offset, uint8_t *data, int data_size) {
memcpy(reinterpret_cast<uint8_t *>(&reply_11[offset >> 1]), data, data_size);
TriggerEvent(EV_SPI_CMD_DONE);
}
void GDROM::GetTOC(AreaType area_type, TOC *toc) {
CHECK(current_disc_);
@ -566,8 +583,24 @@ void GDROM::GetSubcode(int format, uint8_t *data) {
LOG_INFO("GetSubcode not fully implemented");
}
int GDROM::ReadSectors(int fad, SectorFormat format, DataMask mask,
int num_sectors, uint8_t *dst) {
int GDROM::GetFAD(uint8_t a, uint8_t b, uint8_t c, bool msf) {
if (msf) {
// MSF mode
// Byte 2 - Start time: minutes (binary 0 - 255)
// Byte 3 - Start time: seconds (binary 0 - 59)
// Byte 4 - Start time: frames (binary 0 - 74)
return (a * 60 * 75) + (b * 75) + c;
}
// FAD mode
// Byte 2 - Start frame address (MSB)
// Byte 3 - Start frame address
// Byte 4- Start frame address (LSB)
return (a << 16) | (b << 8) | c;
}
int GDROM::ReadSectors(int fad, SectorFormat format, SectorMask mask,
int num_sectors, uint8_t *dst, int dst_size) {
CHECK(current_disc_);
int total = 0;
@ -580,7 +613,7 @@ int GDROM::ReadSectors(int fad, SectorFormat format, DataMask mask,
CHECK_EQ(r, 1);
if (format == SECTOR_M1 && mask == MASK_DATA) {
CHECK_LT(total + 2048, 0x1000000);
CHECK_LT(total + 2048, dst_size);
memcpy(dst, data + 16, 2048);
dst += 2048;
total += 2048;

View File

@ -2,6 +2,7 @@
#define GDROM_H
#include <memory>
#include "core/array.h"
#include "hw/gdrom/disc.h"
#include "hw/machine.h"
@ -17,25 +18,29 @@ class Memory;
namespace gdrom {
enum GDState { //
STATE_STANDBY,
STATE_SPI_READ_CMD,
STATE_SPI_READ_DATA,
STATE_SPI_WRITE_DATA
};
// internal gdrom state machine
enum GDEvent {
EV_ATA_CMD_DONE,
// each incomming SPI command will either:
// a.) have no additional data and immediately fire EV_SPI_CMD_DONE
// b.) read additional data over PIO with EV_SPI_READ_START
// c.) write additional data over PIO with EV_SPI_WRITE_START
// d.) write additional data over DMA / PIO with EV_SPI_WRITE_SECTORS
EV_SPI_WAIT_CMD,
EV_SPI_READ_START,
EV_SPI_READ_END,
EV_SPI_WRITE_START,
EV_SPI_WRITE_SECTORS,
EV_SPI_WRITE_END,
EV_SPI_CMD_DONE
EV_SPI_CMD_DONE,
};
enum GDState {
STATE_STANDBY,
STATE_SPI_READ_CMD,
STATE_SPI_READ_DATA,
STATE_SPI_WRITE_DATA,
STATE_SPI_WRITE_SECTORS,
};
// ata / spi commands
@ -45,7 +50,7 @@ enum ATACommand {
ATA_EXEC_DIAG = 0x90,
ATA_PACKET = 0xa0,
ATA_IDENTIFY_DEV = 0xa1,
ATA_SET_FEATURES = 0xef
ATA_SET_FEATURES = 0xef,
};
enum SPICommand {
@ -64,12 +69,12 @@ enum SPICommand {
SPI_CD_READ2 = 0x31, // CD read (pre-read position)
SPI_GET_SCD = 0x40, // Get subcode
SPI_UNKNOWN_70 = 0x70,
SPI_UNKNOWN_71 = 0x71
SPI_UNKNOWN_71 = 0x71,
};
enum AreaType { //
enum AreaType {
AREA_SINGLE_DENSITY,
AREA_DOUBLE_DENSITY
AREA_DOUBLE_DENSITY,
};
enum AudioStatus {
@ -78,7 +83,24 @@ enum AudioStatus {
AUDIO_PAUSED = 0x12,
AUDIO_ENDED = 0x13,
AUDIO_ERROR = 0x14,
AUDIO_NOSTATUS = 0x15
AUDIO_NOSTATUS = 0x15,
};
enum SectorMask {
MASK_HEADER = 0x8,
MASK_SUBHEADER = 0x4,
MASK_DATA = 0x2,
MASK_OTHER = 0x1
};
enum SectorFormat {
SECTOR_ANY,
SECTOR_CDDA,
SECTOR_M1,
SECTOR_M2,
SECTOR_M2F1,
SECTOR_M2F2,
SECTOR_M2_NOXA
};
union TOCEntry {
@ -90,6 +112,14 @@ union TOCEntry {
};
};
struct CDReadRequest {
bool dma;
SectorFormat sector_format;
SectorMask sector_mask;
int first_sector;
int num_sectors;
};
struct TOC {
TOCEntry entries[99];
TOCEntry start;
@ -104,7 +134,8 @@ struct Session {
uint32_t start_fad : 24;
};
enum { SUBCODE_SIZE = 100 };
static const int SPI_CMD_SIZE = 12;
static const int SUBCODE_SIZE = 100;
// register types
enum DriveStatus {
@ -186,34 +217,20 @@ union GD_BYTECT_T {
};
};
enum SectorFormat {
SECTOR_ANY,
SECTOR_CDDA,
SECTOR_M1,
SECTOR_M2,
SECTOR_M2F1,
SECTOR_M2F2,
SECTOR_M2_NOXA
};
enum DataMask {
MASK_HEADER = 0x8,
MASK_SUBHEADER = 0x4,
MASK_DATA = 0x2,
MASK_OTHER = 0x1
};
class GDROM : public Device {
friend class holly::Holly;
public:
GDROM(Dreamcast &dc);
~GDROM();
bool Init() final;
void SetDisc(std::unique_ptr<Disc> disc);
void BeginDMA();
int ReadDMA(uint8_t *data, int data_size);
void EndDMA();
private:
template <typename T>
T ReadRegister(uint32_t addr);
@ -224,12 +241,14 @@ class GDROM : public Device {
void TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1);
void ProcessATACommand(ATACommand cmd);
void ProcessSPICommand(uint8_t *data);
void ProcessSetMode(int offset, uint8_t *data, int data_size);
void GetTOC(AreaType area_type, TOC *toc);
void GetSession(int session, Session *ses);
void GetSubcode(int format, uint8_t *data);
int ReadSectors(int fad, SectorFormat format, DataMask mask, int num_sectors,
uint8_t *dst);
int GetFAD(uint8_t a, uint8_t b, uint8_t c, bool msf);
int ReadSectors(int fad, SectorFormat format, SectorMask mask,
int num_sectors, uint8_t *dst, int dst_size);
Dreamcast &dc_;
Memory *memory_;
@ -242,15 +261,18 @@ class GDROM : public Device {
GD_BYTECT_T byte_count_;
GD_STATUS_T status_;
uint8_t pio_buffer_[0xfa00];
int pio_idx_;
uint8_t pio_buffer_[0x10000];
int pio_head_;
int pio_size_;
uint8_t *dma_buffer_;
int dma_size_;
GDState state_;
int spi_read_offset_;
int pio_read_offset_;
re::array<uint8_t> dma_buffer_;
int dma_head_;
int dma_size_;
GDState state_;
std::unique_ptr<Disc> current_disc_;
CDReadRequest cdreq_;
};
}
}

View File

@ -107,7 +107,8 @@ T Holly::ReadRegister(uint32_t addr) {
if (offset >= SB_MDSTAR_OFFSET && offset <= SB_MRXDBD_OFFSET) {
return maple_->ReadRegister<T>(addr);
}
if (offset >= GD_ALTSTAT_DEVCTRL_OFFSET && offset <= SB_GDLEND_OFFSET) {
if (offset >= GD_ALTSTAT_DEVCTRL_OFFSET &&
offset <= GD_STATUS_COMMAND_OFFSET) {
return gdrom_->ReadRegister<T>(addr);
}
@ -145,7 +146,8 @@ void Holly::WriteRegister(uint32_t addr, T value) {
maple_->WriteRegister<T>(addr, value);
return;
}
if (offset >= GD_ALTSTAT_DEVCTRL_OFFSET && offset <= SB_GDLEND_OFFSET) {
if (offset >= GD_ALTSTAT_DEVCTRL_OFFSET &&
offset <= GD_STATUS_COMMAND_OFFSET) {
gdrom_->WriteRegister<T>(addr, value);
return;
}
@ -155,7 +157,7 @@ void Holly::WriteRegister(uint32_t addr, T value) {
return;
}
uint32_t old = reg.value;
uint32_t old_value = reg.value;
reg.value = static_cast<uint32_t>(value);
switch (offset) {
@ -163,7 +165,7 @@ void Holly::WriteRegister(uint32_t addr, T value) {
case SB_ISTEXT_OFFSET:
case SB_ISTERR_OFFSET: {
// writing a 1 clears the interrupt
reg.value = old & ~value;
reg.value = old_value & ~value;
ForwardRequestInterrupts();
} break;
@ -181,16 +183,75 @@ void Holly::WriteRegister(uint32_t addr, T value) {
case SB_C2DST_OFFSET:
if (value) {
CH2DMATransfer();
// FIXME what are SB_LMMODE0 / SB_LMMODE1
DTR dtr = {};
dtr.channel = 2;
dtr.rw = false;
dtr.addr = dc_.SB_C2DSTAT;
sh4_->DDT(dtr);
dc_.SB_C2DLEN = 0;
dc_.SB_C2DST = 0;
RequestInterrupt(HOLLY_INTC_DTDE2INT);
}
break;
case SB_SDST_OFFSET:
if (value) {
SortDMATransfer();
LOG_FATAL("Sort DMA not supported");
}
break;
// g1 bus regs
case SB_GDEN_OFFSET:
// NOTE for when this is made asynchtonous
// This register can also be used to forcibly terminate such a DMA
// transfer that is in progress, by writing a "0" to this register.
break;
case SB_GDST_OFFSET:
// if a "0" is written to this register, it is ignored
reg.value |= old_value;
if (reg.value) {
CHECK_EQ(dc_.SB_GDEN, 1); // dma enabled
CHECK_EQ(dc_.SB_GDDIR, 1); // gd-rom -> system memory
int transfer_size = dc_.SB_GDLEN;
uint32_t start = dc_.SB_GDSTAR;
int remaining = transfer_size;
uint32_t addr = start;
gdrom_->BeginDMA();
while (remaining) {
// read a single sector at a time from the gdrom
uint8_t sector_data[SECTOR_SIZE];
int n = gdrom_->ReadDMA(sector_data, sizeof(sector_data));
DTR dtr = {};
dtr.channel = 0;
dtr.rw = true;
dtr.data = sector_data;
dtr.addr = addr;
dtr.size = n;
sh4_->DDT(dtr);
remaining -= n;
addr += n;
}
gdrom_->EndDMA();
dc_.SB_GDSTARD = start + transfer_size;
dc_.SB_GDLEND = transfer_size;
dc_.SB_GDST = 0;
RequestInterrupt(HOLLY_INTC_G1DEINT);
}
break;
// g2 bus regs
case SB_ADEN_OFFSET:
case SB_ADST_OFFSET:
case SB_E1EN_OFFSET:
@ -217,20 +278,6 @@ void Holly::WriteRegister(uint32_t addr, T value) {
}
}
// FIXME what are SB_LMMODE0 / SB_LMMODE1
void Holly::CH2DMATransfer() {
sh4_->DDT(2, DDT_W, dc_.SB_C2DSTAT);
dc_.SB_C2DLEN = 0;
dc_.SB_C2DST = 0;
RequestInterrupt(HOLLY_INTC_DTDE2INT);
}
void Holly::SortDMATransfer() {
dc_.SB_SDST = 0;
RequestInterrupt(HOLLY_INTC_DTDESINT);
}
void Holly::ForwardRequestInterrupts() {
// trigger the respective level-encoded interrupt on the sh4 interrupt
// controller

View File

@ -174,8 +174,6 @@ class Holly : public Device, public MemoryInterface {
template <typename T>
void WriteRegister(uint32_t addr, T value);
void CH2DMATransfer();
void SortDMATransfer();
void ForwardRequestInterrupts();
Dreamcast &dc_;

View File

@ -190,23 +190,38 @@ void Memory::W64(uint32_t addr, uint64_t value) {
WriteBytes<uint64_t, &MemoryRegion::w64>(addr, value);
}
void Memory::Memcpy(uint32_t virtual_dest, const void *ptr, uint32_t size) {
uint8_t *src = (uint8_t *)ptr;
uint32_t end = virtual_dest + size;
while (virtual_dest < end) {
W8(virtual_dest, *src);
virtual_dest++;
src++;
void Memory::Memcpy(uint32_t virtual_dst, const void *ptr, uint32_t size) {
CHECK(size % 4 == 0);
const uint8_t *src = reinterpret_cast<const uint8_t *>(ptr);
uint32_t end = virtual_dst + size;
while (virtual_dst < end) {
W32(virtual_dst, re::load<uint32_t>(src));
virtual_dst += 4;
src += 4;
}
}
void Memory::Memcpy(void *ptr, uint32_t virtual_src, uint32_t size) {
uint8_t *dest = (uint8_t *)ptr;
uint8_t *end = dest + size;
while (dest < end) {
*dest = R32(virtual_src);
virtual_src++;
dest++;
CHECK(size % 4 == 0);
uint8_t *dst = reinterpret_cast<uint8_t *>(ptr);
uint8_t *end = dst + size;
while (dst < end) {
re::store(dst, R32(virtual_src));
virtual_src += 4;
dst += 4;
}
}
void Memory::Memcpy(uint32_t virtual_dst, uint32_t virtual_src, uint32_t size) {
CHECK(size % 4 == 0);
uint32_t end = virtual_dst + size;
while (virtual_dst < end) {
W32(virtual_dst, R32(virtual_src));
virtual_src += 4;
virtual_dst += 4;
}
}

View File

@ -183,6 +183,7 @@ class Memory {
void W64(uint32_t addr, uint64_t value);
void Memcpy(uint32_t virtual_dest, const void *ptr, uint32_t size);
void Memcpy(void *ptr, uint32_t virtual_src, uint32_t size);
void Memcpy(uint32_t virtual_dest, uint32_t virtual_src, uint32_t size);
void Lookup(uint32_t virtual_addr, uint8_t **ptr, MemoryRegion **region,
uint32_t *offset);

View File

@ -129,30 +129,76 @@ void SH4::Run(const std::chrono::nanoseconds &delta) {
s_current_cpu = nullptr;
}
void SH4::DDT(int channel, DDTRW rw, uint32_t addr) {
CHECK_EQ(2, channel);
uint32_t src_addr, dst_addr;
if (rw == DDT_R) {
src_addr = addr;
dst_addr = DAR2;
void SH4::DDT(const DTR &dtr) {
if (dtr.data) {
// single address mode transfer
if (dtr.rw) {
memory_->Memcpy(dtr.addr, dtr.data, dtr.size);
} else {
memory_->Memcpy(dtr.data, dtr.addr, dtr.size);
}
} else {
src_addr = SAR2;
dst_addr = addr;
}
// dual address mode transfer
// NOTE this should be made asynchronous, at which point the significance
// of the registers / interrupts should be more obvious
uint32_t *sar;
uint32_t *dar;
uint32_t *dmatcr;
CHCR_T *chcr;
Interrupt dmte;
uint32_t transfer_size = DMATCR2 * 32;
for (size_t i = 0; i < transfer_size / 4; i++) {
memory_->W32(dst_addr, memory_->R32(src_addr));
dst_addr += 4;
src_addr += 4;
}
switch (dtr.channel) {
case 0:
sar = &SAR0;
dar = &DAR0;
dmatcr = &DMATCR0;
chcr = &CHCR0;
dmte = SH4_INTC_DMTE0;
break;
case 1:
sar = &SAR1;
dar = &DAR1;
dmatcr = &DMATCR1;
chcr = &CHCR1;
dmte = SH4_INTC_DMTE1;
break;
case 2:
sar = &SAR2;
dar = &DAR2;
dmatcr = &DMATCR2;
chcr = &CHCR2;
dmte = SH4_INTC_DMTE2;
break;
case 3:
sar = &SAR3;
dar = &DAR3;
dmatcr = &DMATCR3;
chcr = &CHCR3;
dmte = SH4_INTC_DMTE3;
break;
default:
LOG_FATAL("Unexpected DMA channel");
break;
}
SAR2 = src_addr;
DAR2 = dst_addr;
DMATCR2 = 0;
CHCR2.TE = 1;
RequestInterrupt(SH4_INTC_DMTE2);
uint32_t src = dtr.rw ? dtr.addr : *sar;
uint32_t dst = dtr.rw ? *dar : dtr.addr;
int size = *dmatcr * 32;
memory_->Memcpy(dst, src, size);
// update src / addresses as well as remaining count
*sar = src + size;
*dar = dst + size;
*dmatcr = 0;
// signal transfer end
chcr->TE = 1;
// raise interrupt if requested
if (chcr->IE) {
RequestInterrupt(dmte);
}
}
}
void SH4::RequestInterrupt(Interrupt intr) {
@ -530,14 +576,35 @@ void SH4::WriteRegister(uint32_t addr, T value) {
}
} break;
// it seems the only aspect of the cache control register that needs to be
// emulated is the instruction cache invalidation
case CCR_OFFSET: {
if (CCR.ICI) {
ResetCache();
}
} break;
case CHCR0_OFFSET: {
CheckDMA(0);
} break;
case CHCR1_OFFSET: {
CheckDMA(1);
} break;
case CHCR2_OFFSET: {
CheckDMA(2);
} break;
case CHCR3_OFFSET: {
CheckDMA(3);
} break;
case DMAOR_OFFSET: {
CheckDMA(0);
CheckDMA(1);
CheckDMA(2);
CheckDMA(3);
} break;
case IPRA_OFFSET:
case IPRB_OFFSET:
case IPRC_OFFSET: {
@ -615,6 +682,33 @@ void SH4::ResetCache() {
code_cache_->UnlinkBlocks();
}
//
// DMAC
//
void SH4::CheckDMA(int channel) {
CHCR_T *chcr = nullptr;
switch (channel) {
case 0:
chcr = &CHCR0;
break;
case 1:
chcr = &CHCR1;
break;
case 2:
chcr = &CHCR2;
break;
case 3:
chcr = &CHCR3;
break;
default:
LOG_FATAL("Unexpected DMA channel");
break;
}
CHECK(DMAOR.DDT || !DMAOR.DME || !chcr->DE, "Non-DDT DMA not supported");
}
//
// INTC
//

View File

@ -19,9 +19,21 @@ namespace sh4 {
static const int MAX_MIPS_SAMPLES = 10;
enum DDTRW {
DDT_R,
DDT_W,
// data transfer request
struct DTR {
int channel;
// when rw is true, addr is the dst address
// when rw is false, addr is the src address
bool rw;
// when data is non-null, a single address mode transfer is performed between
// the external device memory at data, and the memory at addr for
// when data is null, a dual address mode transfer is performed between addr
// and SARn / DARn
uint8_t *data;
uint32_t addr;
// size is only valid for single address mode transfers, dual address mode
// transfers honor DMATCR
int size;
};
class SH4 : public Device,
@ -42,7 +54,7 @@ class SH4 : public Device,
void Run(const std::chrono::nanoseconds &delta) final;
// DMAC
void DDT(int channel, DDTRW rw, uint32_t addr);
void DDT(const DTR &dtr);
// INTC
void RequestInterrupt(Interrupt intr);
@ -94,6 +106,9 @@ class SH4 : public Device,
// CCN
void ResetCache();
// DMAC
void CheckDMA(int channel);
// INTC
void ReprioritizeInterrupts();
void UpdatePendingInterrupts();

View File

@ -217,7 +217,8 @@ Instr *IRBuilder::FTrunc(Value *v, ValueType dest_type) {
}
Instr *IRBuilder::Select(Value *cond, Value *t, Value *f) {
CHECK(IsIntType(cond->type()) && IsIntType(t->type()) && t->type() == f->type());
CHECK(IsIntType(cond->type()) && IsIntType(t->type()) &&
t->type() == f->type());
Instr *instr = AppendInstr(OP_SELECT, t->type());
instr->set_arg0(t);