From 9f4c248e4cf084e58e0d3f02f41218d1644523e8 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Thu, 20 Oct 2022 13:20:06 +0200 Subject: [PATCH] Rework DBDMA logic for bidirectional channels. --- cpu/ppc/ppcmmu.cpp | 7 ++- cpu/ppc/ppcmmu.h | 2 +- devices/common/dbdma.cpp | 113 +++++++++++++++++++++++------------ devices/common/dbdma.h | 32 +++++----- devices/common/dmacore.h | 12 ++-- devices/ioctrl/amic.cpp | 9 ++- devices/video/pdmonboard.cpp | 2 +- 7 files changed, 112 insertions(+), 65 deletions(-) diff --git a/cpu/ppc/ppcmmu.cpp b/cpu/ppc/ppcmmu.cpp index dc29fb6..d03ccc4 100644 --- a/cpu/ppc/ppcmmu.cpp +++ b/cpu/ppc/ppcmmu.cpp @@ -309,9 +309,11 @@ static PATResult page_address_translation(uint32_t la, bool is_instr_fetch, }; } -uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size) +uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size, bool* is_writable) { if (addr >= last_dma_area.start && (addr + size) <= last_dma_area.end) { + if (is_writable) + *is_writable = last_dma_area.type & RT_RAM; return last_dma_area.mem_ptr + (addr - last_dma_area.start); } else { AddressMapEntry* entry = mem_ctrl_instance->find_range(addr); @@ -319,6 +321,9 @@ uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size) last_dma_area.start = entry->start; last_dma_area.end = entry->end; last_dma_area.mem_ptr = entry->mem_ptr; + last_dma_area.type = entry->type; + if (is_writable) + *is_writable = entry->type & RT_RAM; return last_dma_area.mem_ptr + (addr - last_dma_area.start); } else { ABORT_F("SOS: DMA access to unmapped memory %08X!\n", addr); diff --git a/cpu/ppc/ppcmmu.h b/cpu/ppc/ppcmmu.h index 1770027..914a9b7 100644 --- a/cpu/ppc/ppcmmu.h +++ b/cpu/ppc/ppcmmu.h @@ -102,7 +102,7 @@ enum TLBFlags : uint16_t { extern std::function ibat_update; extern std::function dbat_update; -extern uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size); +extern uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size, bool* is_writable); extern void mmu_change_mode(void); extern void mmu_pat_ctx_changed(); diff --git a/devices/common/dbdma.cpp b/devices/common/dbdma.cpp index e9d7bba..9494bc7 100644 --- a/devices/common/dbdma.cpp +++ b/devices/common/dbdma.cpp @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-21 divingkatae and maximum +Copyright (C) 2018-22 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -25,6 +25,7 @@ along with this program. If not, see . #include #include #include +#include #include #include @@ -36,21 +37,51 @@ void DMAChannel::set_callbacks(DbdmaCallback start_cb, DbdmaCallback stop_cb) this->stop_cb = stop_cb; } -void DMAChannel::get_next_cmd(uint32_t cmd_addr, DMACmd* p_cmd) { +void DMAChannel::fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd) { /* load DMACmd from physical memory */ - memcpy((uint8_t*)p_cmd, mmu_get_dma_mem(cmd_addr, 16), 16); + memcpy((uint8_t*)p_cmd, mmu_get_dma_mem(cmd_addr, 16, nullptr), 16); } uint8_t DMAChannel::interpret_cmd() { DMACmd cmd_struct; + bool is_writable; + int cmd; - get_next_cmd(this->cmd_ptr, &cmd_struct); + if (this->cmd_in_progress) { + // return current command if there is data to transfer + if (this->queue_len) + return cmd; + + // obtain real pointer to the descriptor of the completed command + uint8_t *curr_cmd = mmu_get_dma_mem(this->cmd_ptr - 16, 16, &is_writable); + + // get command code + cmd = curr_cmd[3] >> 4; + + // all commands except STOP update cmd.xferStatus + if (cmd < 7 && is_writable) { + WRITE_WORD_LE_A(&curr_cmd[14], this->ch_stat | CH_STAT_ACTIVE); + } + + // all INPUT and OUTPUT commands update cmd.resCount + if (cmd < 4 && is_writable) { + WRITE_WORD_LE_A(&curr_cmd[12], this->queue_len & 0xFFFFUL); + } + + this->cmd_in_progress = false; + } + + fetch_cmd(this->cmd_ptr, &cmd_struct); + + cmd = cmd_struct.cmd_key >> 4; this->ch_stat &= ~CH_STAT_WAKE; /* clear wake bit (DMA spec, 5.5.3.4) */ switch (cmd_struct.cmd_key >> 4) { - case 0: - LOG_F(9, "Executing DMA Command OUTPUT_MORE"); + case 0: // OUTPUT_MORE + case 1: // OUTPUT_LAST + case 2: // INPUT_MORE + case 3: // INPUT_LAST if (cmd_struct.cmd_key & 7) { LOG_F(ERROR, "Key > 0 not implemented"); break; @@ -59,35 +90,10 @@ uint8_t DMAChannel::interpret_cmd() { LOG_F(ERROR, "non-zero i/b/w not implemented"); break; } - // this->dma_cb->dma_push( - // mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count), - // cmd_struct.req_count); - this->queue_data = mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count); + this->queue_data = mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count, &is_writable); this->queue_len = cmd_struct.req_count; this->cmd_ptr += 16; - break; - case 1: - LOG_F(9, "Executing DMA Command OUTPUT_LAST"); - if (cmd_struct.cmd_key & 7) { - LOG_F(ERROR, "Key > 0 not implemented"); - break; - } - if (cmd_struct.cmd_bits & 0x3F) { - LOG_F(ERROR, "non-zero i/b/w not implemented"); - break; - } - // this->dma_cb->dma_push( - // mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count), - // cmd_struct.req_count); - this->queue_data = mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count); - this->queue_len = cmd_struct.req_count; - this->cmd_ptr += 16; - break; - case 2: - LOG_F(ERROR, "Unsupported DMA Command INPUT_MORE"); - break; - case 3: - LOG_F(ERROR, "Unsupported DMA Command INPUT_LAST"); + this->cmd_in_progress = true; break; case 4: LOG_F(ERROR, "Unsupported DMA Command STORE_QUAD"); @@ -96,11 +102,11 @@ uint8_t DMAChannel::interpret_cmd() { LOG_F(ERROR, "Unsupported DMA Command LOAD_QUAD"); break; case 6: - LOG_F(INFO, "Unsupported DMA Command NOP"); + LOG_F(ERROR, "Unsupported DMA Command NOP"); break; case 7: - LOG_F(INFO, "DMA Command: 7 (STOP)"); this->ch_stat &= ~CH_STAT_ACTIVE; + this->cmd_in_progress = false; break; default: LOG_F(ERROR, "Unsupported DMA command 0x%X", cmd_struct.cmd_key >> 4); @@ -149,7 +155,7 @@ void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) { case DMAReg::CH_CTRL: mask = value >> 16; new_stat = (value & mask & 0xF0FFU) | (old_stat & ~mask); - LOG_F(INFO, "New ChannelStatus value = 0x%X", new_stat); + LOG_F(9, "New ChannelStatus value = 0x%X", new_stat); if ((new_stat & CH_STAT_RUN) != (old_stat & CH_STAT_RUN)) { if (new_stat & CH_STAT_RUN) { @@ -184,7 +190,7 @@ void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) { case DMAReg::CMD_PTR_LO: if (!(this->ch_stat & CH_STAT_RUN) && !(this->ch_stat & CH_STAT_ACTIVE)) { this->cmd_ptr = value; - LOG_F(INFO, "CommandPtrLo set to 0x%X", this->cmd_ptr); + LOG_F(9, "CommandPtrLo set to 0x%X", this->cmd_ptr); } break; default: @@ -227,6 +233,33 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8 return DmaPullResult::NoMoreData; /* tell the caller there is no more data */ } +int DMAChannel::push_data(const char* src_ptr, int len) +{ + if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) { + LOG_F(WARNING, "DBDMA: attempt to push data to dead/idle channel"); + return -1; + } + + // interpret DBDMA program until we get buffer to fill in or become idle + while ((this->ch_stat & CH_STAT_ACTIVE) && !this->queue_len) { + this->interpret_cmd(); + } + + if (this->queue_len) { + len = std::min((int)this->queue_len, len); + std::memcpy(this->queue_data, src_ptr, len); + this->queue_data += len; + this->queue_len -= len; + } + + // proceed with the DBDMA program if the buffer became exhausted + if (!this->queue_len) { + this->interpret_cmd(); + } + + return 0; +} + bool DMAChannel::is_active() { if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) { @@ -248,7 +281,8 @@ void DMAChannel::start() this->queue_len = 0; - this->start_cb(); + if (this->start_cb) + this->start_cb(); } void DMAChannel::resume() { @@ -266,5 +300,6 @@ void DMAChannel::abort() { void DMAChannel::pause() { LOG_F(INFO, "Pausing DMA channel"); - this->stop_cb(); + if (this->stop_cb) + this->stop_cb(); } diff --git a/devices/common/dbdma.h b/devices/common/dbdma.h index 4e46a22..28c4eba 100644 --- a/devices/common/dbdma.h +++ b/devices/common/dbdma.h @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-21 divingkatae and maximum +Copyright (C) 2018-22 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -42,7 +42,7 @@ enum DMAReg : uint32_t { }; /** Channel Status bits (DBDMA spec, 5.5.3) */ -enum { +enum : uint16_t { CH_STAT_ACTIVE = 0x400, CH_STAT_DEAD = 0x800, CH_STAT_WAKE = 0x1000, @@ -53,18 +53,18 @@ enum { /** DBDMA command (DBDMA spec, 5.6.1) - all fields are little-endian! */ typedef struct DMACmd { - uint16_t req_count; - uint8_t cmd_bits; - uint8_t cmd_key; - uint32_t address; - uint32_t cmd_arg; - uint16_t res_count; - uint16_t xfer_stat; + uint16_t req_count; + uint8_t cmd_bits; + uint8_t cmd_key; + uint32_t address; + uint32_t cmd_arg; + uint16_t res_count; + uint16_t xfer_stat; } DMACmd; typedef std::function DbdmaCallback; -class DMAChannel : public DmaOutChannel { +class DMAChannel : public DmaBidirChannel { public: DMAChannel() = default; ~DMAChannel() = default; @@ -75,9 +75,10 @@ public: bool is_active(); DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data); + int push_data(const char* src_ptr, int len); protected: - void get_next_cmd(uint32_t cmd_addr, DMACmd* p_cmd); + void fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd); uint8_t interpret_cmd(void); void start(void); @@ -86,14 +87,15 @@ protected: void pause(void); private: - std::function start_cb; // DMA channel start callback - std::function stop_cb; // DMA channel stop callback + std::function start_cb = nullptr; // DMA channel start callback + std::function stop_cb = nullptr; // DMA channel stop callback uint16_t ch_stat = 0; uint32_t cmd_ptr = 0; + uint32_t queue_len = 0; + uint8_t* queue_data = 0; - uint32_t queue_len; - uint8_t* queue_data; + bool cmd_in_progress = false; }; #endif /* DB_DMA_H */ diff --git a/devices/common/dmacore.h b/devices/common/dmacore.h index 5737cd2..44de35e 100644 --- a/devices/common/dmacore.h +++ b/devices/common/dmacore.h @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-21 divingkatae and maximum +Copyright (C) 2018-22 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -38,13 +38,13 @@ public: uint8_t **p_data) = 0; }; -// Base class for bidirectional DMA channels. -class DmaBidirChannel { +class DmaInChannel { public: - virtual bool is_active() { return true; }; virtual int push_data(const char* src_ptr, int len) = 0; - virtual DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, - uint8_t **p_data) = 0; +}; + +// Base class for bidirectional DMA channels. +class DmaBidirChannel : public DmaOutChannel, public DmaInChannel { }; #endif // DMA_CORE_H diff --git a/devices/ioctrl/amic.cpp b/devices/ioctrl/amic.cpp index 4da35c8..558f1d4 100644 --- a/devices/ioctrl/amic.cpp +++ b/devices/ioctrl/amic.cpp @@ -491,7 +491,7 @@ DmaPullResult AmicSndOutDma::pull_data(uint32_t req_len, uint32_t *avail_len, *p_data = mmu_get_dma_mem( (this->snd_buf_num ? this->out_buf1 : this->out_buf0) + this->cur_buf_pos, - len); + len, nullptr); this->cur_buf_pos += len; *avail_len = len; return DmaPullResult::MoreData; @@ -524,9 +524,14 @@ void AmicFloppyDma::write_ctrl(uint8_t value) int AmicFloppyDma::push_data(const char* src_ptr, int len) { + bool is_writable; + len = std::min((int)this->byte_count, len); - uint8_t *p_data = mmu_get_dma_mem(this->addr_ptr, len); + uint8_t *p_data = mmu_get_dma_mem(this->addr_ptr, len, &is_writable); + if (!is_writable) { + ABORT_F("AMIC: attempting DMA write to read-only memory"); + } std::memcpy(p_data, src_ptr, len); this->addr_ptr += len; diff --git a/devices/video/pdmonboard.cpp b/devices/video/pdmonboard.cpp index fff66c7..c28a924 100644 --- a/devices/video/pdmonboard.cpp +++ b/devices/video/pdmonboard.cpp @@ -204,7 +204,7 @@ void PdmOnboardVideo::enable_video_internal() LOG_F(INFO, "PDM-Video: framebuffer phys base addr = 0x%X", fb_base_phys); // set framebuffer address and pitch - this->fb_ptr = mmu_get_dma_mem(fb_base_phys, PDM_FB_SIZE_MAX); + this->fb_ptr = mmu_get_dma_mem(fb_base_phys, PDM_FB_SIZE_MAX, nullptr); this->active_width = new_width; this->active_height = new_height;