From 6aa8ab4d69c929d00df9be60b1733bc14f89fcbf Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Fri, 21 Aug 2026 21:49:24 -0700 Subject: [PATCH 1/2] dbdma: Interpret command lists until STOP With #214 we started to advertise the IDE DMA support from #192 to guests. That appeared to break booting Mac OS 8.x, at least on the Beige G3: the startup disk was not even detected. Mac OS 8.1 sets up an IDE DMA read as INPUT_MORE, then a NOP with its i field set to always, then STOP, which clears the channel's ACTIVE bit. The emulated transfer completion path interpreted only one following descriptor, so it stopped at NOP. NOP raised the interrupt, but STOP was never reached and the channel stayed active. The Mac OS X driver is more tolerant of this, which is why it was not an issue there. The DBDMA Specification, section 1.4, says: "The target fetches command entries and performs the command-entry-specified data-transfer operation, processing command entries until a STOP entry is reached". Keep interpreting ready commands until a transfer blocks or the channel stops, as start() and resume() already did. This is closer to the hardware behavior, and empirically lets Mac OS 8.1 boot again. --- devices/common/dbdma.cpp | 39 ++++++++++++++++----------------------- devices/common/dbdma.h | 1 + 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/devices/common/dbdma.cpp b/devices/common/dbdma.cpp index 63077c8876..0b32c16b3b 100644 --- a/devices/common/dbdma.cpp +++ b/devices/common/dbdma.cpp @@ -145,6 +145,15 @@ void DMAChannel::interpret_cmd() { } } +void DMAChannel::interpret_until_blocked() { + // Execute ready commands until a transfer is queued or the channel becomes idle/dead. + while (this->is_active()) { + this->interpret_cmd(); + if (this->cmd_in_progress) + break; + } +} + void DMAChannel::update_cmd() { if (this->cur_is_writable) { if (this->cur_cmd < DBDMA_Cmd::STOP) @@ -471,7 +480,7 @@ void DMAChannel::xfer_from_device() { this->get_name().c_str()); } - this->interpret_cmd(); + this->interpret_until_blocked(); } void DMAChannel::xfer_to_device() { @@ -488,7 +497,7 @@ void DMAChannel::xfer_to_device() { this->finish_cmd(); } - this->interpret_cmd(); + this->interpret_until_blocked(); } void DMAChannel::xfer_retry() { @@ -521,9 +530,7 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8 } // interpret DBDMA program until we get data or become idle - while ((this->ch_stat & CH_STAT_ACTIVE) && !this->queue_len) { - this->interpret_cmd(); - } + this->interpret_until_blocked(); // dequeue data if any if (this->queue_len) { @@ -556,9 +563,7 @@ DmaPushResult DMAChannel::push_data(const char* src_ptr, int len) { } // 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(); - } + this->interpret_until_blocked(); if (this->queue_len) { len = std::min((int)this->queue_len, len); @@ -570,7 +575,7 @@ DmaPushResult DMAChannel::push_data(const char* src_ptr, int len) { // proceed with the DBDMA program if the buffer became exhausted if (!this->queue_len) { - this->interpret_cmd(); + this->interpret_until_blocked(); } return DmaPushResult::PushedData; @@ -646,13 +651,7 @@ void DMAChannel::start() { if (this->start_cb) this->start_cb(); - // some DBDMA programs contain commands that don't transfer data - // between a device and memory (LOAD_QUAD, STORE_QUAD, NOP and STOP). - // We thus interprete the DBDMA program until a data transfer between - // a device and memory is queued or the channel becomes idle/dead. - while (!this->cmd_in_progress && this->is_active()) { - this->interpret_cmd(); - } + this->interpret_until_blocked(); } void DMAChannel::resume() { @@ -664,13 +663,7 @@ void DMAChannel::resume() { LOG_F(INFO, "%s: Resuming DMA channel", this->get_name().c_str()); - // some DBDMA programs contain commands that don't transfer data - // between a device and memory (LOAD_QUAD, STORE_QUAD, NOP and STOP). - // We thus interprete the DBDMA program until a data transfer between - // a device and memory is queued or the channel becomes idle/dead. - while (!this->cmd_in_progress && this->is_active()) { - this->interpret_cmd(); - } + this->interpret_until_blocked(); } void DMAChannel::abort() { diff --git a/devices/common/dbdma.h b/devices/common/dbdma.h index 1e48e90c11..578e553a19 100644 --- a/devices/common/dbdma.h +++ b/devices/common/dbdma.h @@ -141,6 +141,7 @@ class DMAChannel : public DmaBidirChannel, public DmaChannel { protected: DMACmd* fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd, bool *is_writable); void interpret_cmd(void); + void interpret_until_blocked(); void update_cmd(); void finish_cmd(); void xfer_quad(bool is_store); From a930d0ed3c5d81e7629925e268b7dee045379091 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Fri, 21 Aug 2026 22:32:04 -0700 Subject: [PATCH 2/2] dma: Remove the old pull/push completion API b63b6583 removed the old ESCC DMA implementation, taking the last users of end_pull_data(), end_push_data(), and get_push_data_remaining() with it. 9fb34efc later removed the last get_pull_data_remaining() call from the sound server. Nothing has called these hooks since. Remove them from DmaOutChannel, DmaInChannel, and DMAChannel instead of carrying dead completion paths alongside the DMA interfaces that are still in use. --- devices/common/dbdma.cpp | 35 ----------------------------------- devices/common/dbdma.h | 4 ---- devices/common/dmacore.h | 4 ---- 3 files changed, 43 deletions(-) diff --git a/devices/common/dbdma.cpp b/devices/common/dbdma.cpp index 0b32c16b3b..a99ae788a6 100644 --- a/devices/common/dbdma.cpp +++ b/devices/common/dbdma.cpp @@ -581,41 +581,6 @@ DmaPushResult DMAChannel::push_data(const char* src_ptr, int len) { return DmaPushResult::PushedData; } -void DMAChannel::end_pull_data() { - if (!this->is_active()) { - // dead or idle channel? -> no more data - LOG_F(WARNING, "%s: Ending Dead/idle channel -> no more data", - this->get_name().c_str()); - return; - } - - if (this->queue_len) { - this->queue_len = 0; - } else { - this->ch_stat &= ~CH_STAT_FLUSH; - } - - // proceed with the DBDMA program - this->interpret_cmd(); -} - -void DMAChannel::end_push_data() { - if (!this->is_active()) { - LOG_F(WARNING, "%s: Attempt to end push data to dead/idle channel", - this->get_name().c_str()); - return; - } - - if (this->queue_len) { - this->queue_len = 0; - } else { - this->ch_stat &= ~CH_STAT_FLUSH; - } - - // proceed with the DBDMA program - this->interpret_cmd(); -} - bool DMAChannel::is_out_active() { return this->is_active(); } diff --git a/devices/common/dbdma.h b/devices/common/dbdma.h index 578e553a19..f6991f939e 100644 --- a/devices/common/dbdma.h +++ b/devices/common/dbdma.h @@ -125,10 +125,6 @@ class DMAChannel : public DmaBidirChannel, public DmaChannel { bool is_in_active() override; DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data) override; DmaPushResult push_data(const char* src_ptr, int len) override; - int get_pull_data_remaining() override { return this->queue_len; } - int get_push_data_remaining() override { return this->queue_len; } - void end_pull_data() override; - void end_push_data() override; bool dma_is_ready() override; void xfer_retry() override; diff --git a/devices/common/dmacore.h b/devices/common/dmacore.h index 60695102a7..12c3fca495 100644 --- a/devices/common/dmacore.h +++ b/devices/common/dmacore.h @@ -45,8 +45,6 @@ class DmaOutChannel { virtual bool is_out_active() { return true; } virtual DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data) = 0; - virtual int get_pull_data_remaining() { return 1; } - virtual void end_pull_data() {} std::string get_name(void) { return this->name; } @@ -60,8 +58,6 @@ class DmaInChannel { virtual bool is_in_active() { return true; } virtual DmaPushResult push_data(const char* src_ptr, int len) = 0; - virtual int get_push_data_remaining() { return 1; } - virtual void end_push_data() {} std::string get_name(void) { return this->name; }