From 9efdb02b63b3e83ebd47c1a768a429233593fe2d Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Thu, 24 Mar 2016 19:59:54 -0700 Subject: [PATCH] refactored SH4 DMA added PIO CD_READ support to GDROM updated Holly / GDROM DMA to work with new DMA code --- src/hw/gdrom/disc.h | 2 +- src/hw/gdrom/gdrom.cc | 273 ++++++++++++++++++++++----------------- src/hw/gdrom/gdrom.h | 102 +++++++++------ src/hw/holly/holly.cc | 87 ++++++++++--- src/hw/holly/holly.h | 2 - src/hw/memory.cc | 41 ++++-- src/hw/memory.h | 1 + src/hw/sh4/sh4.cc | 140 ++++++++++++++++---- src/hw/sh4/sh4.h | 23 +++- src/jit/ir/ir_builder.cc | 3 +- 10 files changed, 450 insertions(+), 224 deletions(-) diff --git a/src/hw/gdrom/disc.h b/src/hw/gdrom/disc.h index ab10366f..f387c198 100644 --- a/src/hw/gdrom/disc.h +++ b/src/hw/gdrom/disc.h @@ -8,7 +8,7 @@ namespace re { namespace hw { namespace gdrom { -enum { SECTOR_SIZE = 2352 }; +static const int SECTOR_SIZE = 2352; struct Track { Track() diff --git a/src/hw/gdrom/gdrom.cc b/src/hw/gdrom/gdrom.cc index 47b5cd23..b0740cd4 100644 --- a/src/hw/gdrom/gdrom.cc +++ b/src/hw/gdrom/gdrom.cc @@ -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) { 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(&pio_buffer_[pio_head_]); + pio_head_ += 2; + if (pio_head_ == pio_size_) { TriggerEvent(EV_SPI_WRITE_END); } return static_cast(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(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(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(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(&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(arg0); int size = static_cast(arg1); - CHECK_NE(size, 0); + CHECK(size && size < static_cast(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(&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(&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; diff --git a/src/hw/gdrom/gdrom.h b/src/hw/gdrom/gdrom.h index dc39072f..8d96c39a 100644 --- a/src/hw/gdrom/gdrom.h +++ b/src/hw/gdrom/gdrom.h @@ -2,6 +2,7 @@ #define GDROM_H #include +#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); + void BeginDMA(); + int ReadDMA(uint8_t *data, int data_size); + void EndDMA(); + private: template 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 dma_buffer_; + int dma_head_; + int dma_size_; + + GDState state_; std::unique_ptr current_disc_; + CDReadRequest cdreq_; }; } } diff --git a/src/hw/holly/holly.cc b/src/hw/holly/holly.cc index a1a1b524..c1ec0fb7 100644 --- a/src/hw/holly/holly.cc +++ b/src/hw/holly/holly.cc @@ -107,7 +107,8 @@ T Holly::ReadRegister(uint32_t addr) { if (offset >= SB_MDSTAR_OFFSET && offset <= SB_MRXDBD_OFFSET) { return maple_->ReadRegister(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(addr); } @@ -145,7 +146,8 @@ void Holly::WriteRegister(uint32_t addr, T value) { maple_->WriteRegister(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(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(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 diff --git a/src/hw/holly/holly.h b/src/hw/holly/holly.h index 461ce45f..312a1381 100644 --- a/src/hw/holly/holly.h +++ b/src/hw/holly/holly.h @@ -174,8 +174,6 @@ class Holly : public Device, public MemoryInterface { template void WriteRegister(uint32_t addr, T value); - void CH2DMATransfer(); - void SortDMATransfer(); void ForwardRequestInterrupts(); Dreamcast &dc_; diff --git a/src/hw/memory.cc b/src/hw/memory.cc index 0ef0bef7..e26d6974 100644 --- a/src/hw/memory.cc +++ b/src/hw/memory.cc @@ -190,23 +190,38 @@ void Memory::W64(uint32_t addr, uint64_t value) { WriteBytes(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(ptr); + uint32_t end = virtual_dst + size; + while (virtual_dst < end) { + W32(virtual_dst, re::load(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(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; } } diff --git a/src/hw/memory.h b/src/hw/memory.h index 9a387e03..be7d3c53 100644 --- a/src/hw/memory.h +++ b/src/hw/memory.h @@ -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); diff --git a/src/hw/sh4/sh4.cc b/src/hw/sh4/sh4.cc index c09a86dc..8430396a 100644 --- a/src/hw/sh4/sh4.cc +++ b/src/hw/sh4/sh4.cc @@ -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 // diff --git a/src/hw/sh4/sh4.h b/src/hw/sh4/sh4.h index 8e8c3e16..a6f26d47 100644 --- a/src/hw/sh4/sh4.h +++ b/src/hw/sh4/sh4.h @@ -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(); diff --git a/src/jit/ir/ir_builder.cc b/src/jit/ir/ir_builder.cc index 9c643391..e9fb61bd 100644 --- a/src/jit/ir/ir_builder.cc +++ b/src/jit/ir/ir_builder.cc @@ -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);