mirror of https://github.com/inolen/redream.git
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:
parent
8069a76100
commit
9efdb02b63
|
@ -8,7 +8,7 @@ namespace re {
|
|||
namespace hw {
|
||||
namespace gdrom {
|
||||
|
||||
enum { SECTOR_SIZE = 2352 };
|
||||
static const int SECTOR_SIZE = 2352;
|
||||
|
||||
struct Track {
|
||||
Track()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue