From a589c94352f644394d54a1fb99515113eb55c807 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 5 Jul 2026 09:19:27 -0700 Subject: [PATCH 1/3] ata: Implement hard disk DMA transfers Handle ATA DMA reads and writes for hard disks. Reads prepare data from the backing image and writes accept pushed data from the DBDMA channel until the programmed sector count is exhausted. The `data_ptr` in `AtaBaseDevice` needs to be advanced after each DMA pull so one request can be split across multiple DBDMA descriptors. --- devices/common/ata/atabasedevice.cpp | 5 ++- devices/common/ata/atahd.cpp | 58 ++++++++++++++++++++++++++++ devices/common/ata/atahd.h | 1 + 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/devices/common/ata/atabasedevice.cpp b/devices/common/ata/atabasedevice.cpp index 7ed8da4929..88bbfac06e 100644 --- a/devices/common/ata/atabasedevice.cpp +++ b/devices/common/ata/atabasedevice.cpp @@ -205,10 +205,13 @@ int AtaBaseDevice::pull_data(uint8_t *buf, int len) { // this->data_ptr[i] = BYTESWAP_16(this->data_ptr[i]); std::memcpy(buf, this->data_ptr, xfer_size); + this->data_ptr = (uint16_t *)((uint8_t *)this->data_ptr + xfer_size); this->xfer_cnt -= xfer_size; if (!this->xfer_cnt) { - TimerManager::get_instance()->add_oneshot_timer(500, [this]() { + this->is_dma_xfer = false; + this->data_ptr = nullptr; + TimerManager::get_instance()->add_immediate_timer([this]() { this->r_status &= ~(BSY | DRQ); this->update_intrq(1); }); diff --git a/devices/common/ata/atahd.cpp b/devices/common/ata/atahd.cpp index 8a28a92cff..abb988025a 100644 --- a/devices/common/ata/atahd.cpp +++ b/devices/common/ata/atahd.cpp @@ -22,12 +22,14 @@ along with this program. If not, see . /** @file ATA hard disk emulation. */ #include +#include #include #include #include #include #include +#include #include #include #include @@ -120,6 +122,38 @@ int AtaHardDisk::perform_command() { this->signal_data_ready(); } break; + case READ_DMA: + case READ_DMA_NR: { + uint16_t sec_count = this->r_sect_count ? this->r_sect_count : 256; + int xfer_size = sec_count * ATA_HD_SEC_SIZE; + uint64_t offset = this->get_lba() * ATA_HD_SEC_SIZE; + + hdd_img.read(buffer, offset, xfer_size); + this->data_ptr = (uint16_t *)this->buffer; + this->is_dma_xfer = true; + this->prepare_xfer(xfer_size, xfer_size); + this->r_status &= ~(BSY | DRQ | ERR); + this->r_status |= DRDY | DSC; + // TODO: Model ATA DMAREQ as a latched signal instead of relying + // on this delay to avoid racing DBDMA command startup. + this->host_obj->assert_dmareq(500); + } + break; + case WRITE_DMA: + case WRITE_DMA_NR: { + uint16_t sec_count = this->r_sect_count ? this->r_sect_count : 256; + int xfer_size = sec_count * ATA_HD_SEC_SIZE; + + this->cur_fpos = this->get_lba() * ATA_HD_SEC_SIZE; + this->is_dma_xfer = true; + this->prepare_xfer(xfer_size, xfer_size); + this->r_status &= ~(BSY | DRQ | ERR); + this->r_status |= DRDY | DSC; + // TODO: Model ATA DMAREQ as a latched signal instead of relying + // on this delay to avoid racing DBDMA command startup. + this->host_obj->assert_dmareq(500); + } + break; case WRITE_MULTIPLE: case WRITE_SECTOR: case WRITE_SECTOR_NR: { @@ -256,6 +290,30 @@ int AtaHardDisk::perform_command() { return 0; } +int AtaHardDisk::push_data(uint8_t *buf, int len) { + if (!this->xfer_cnt || !this->is_dma_xfer) + return 0; + + int xfer_size = std::min(this->xfer_cnt, len); + this->hdd_img.write(buf, this->cur_fpos, xfer_size); + this->cur_fpos += xfer_size; + this->xfer_cnt -= xfer_size; + + if (!this->xfer_cnt) { + this->is_dma_xfer = false; + TimerManager::get_instance()->add_immediate_timer([this]() { + this->r_status &= ~(BSY | DRQ | ERR); + this->r_status |= DRDY | DSC; + this->update_intrq(1); + }); + } else { + this->r_status &= ~BSY; + this->r_status |= DRQ; + } + + return xfer_size; +} + void AtaHardDisk::prepare_identify_info() { uint16_t *buf_ptr = (uint16_t *)this->data_buf; diff --git a/devices/common/ata/atahd.h b/devices/common/ata/atahd.h index 35bd96b4e5..475b7aa47d 100644 --- a/devices/common/ata/atahd.h +++ b/devices/common/ata/atahd.h @@ -53,6 +53,7 @@ class AtaHardDisk : public AtaBaseDevice void insert_image(std::string filename); int perform_command() override; + int push_data(uint8_t *buf, int len) override; protected: void prepare_identify_info(); From e239ad425943de5a700cff4192206779d12a3b46 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 5 Jul 2026 09:19:47 -0700 Subject: [PATCH 2/3] dbdma: Report residual descriptor byte counts We were treating resCount as a "result" count, but it's actually a "residual" count that reports how many bytes from a descriptor were not transferred when processing a command. We now correctly handle partial descriptors. This ended up being necessary for the 10.0 public beta bringup too (the `AppleUltra66ATA::stopDma` implementation in xnu-103 computes the number of bytes transferred by subtracting `resCount` from `reqCount`). --- devices/common/dbdma.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/devices/common/dbdma.cpp b/devices/common/dbdma.cpp index b3b59e358b..970e81c215 100644 --- a/devices/common/dbdma.cpp +++ b/devices/common/dbdma.cpp @@ -91,7 +91,7 @@ uint8_t DMAChannel::interpret_cmd() { if (cmd_struct.req_count) { res = mmu_map_dma_mem(cmd_struct.address, cmd_struct.req_count, false); this->queue_data = res.host_va; - this->res_count = 0; + this->res_count = cmd_struct.req_count; this->queue_len = cmd_struct.req_count; // don't set queue_len until all the other fields are set this->cmd_in_progress = true; switch (this->cur_cmd) { @@ -468,7 +468,8 @@ void DMAChannel::xfer_from_device() { this->xfer_dir = DMA_DIR_FROM_DEV; int got_bytes = this->dev_obj->xfer_from(this, this->queue_data, this->queue_len); - this->res_count += got_bytes; + this->queue_data += got_bytes; + this->res_count -= got_bytes; this->queue_len -= got_bytes; if (!this->queue_len) { this->finish_cmd(); @@ -487,7 +488,8 @@ void DMAChannel::xfer_to_device() { this->xfer_dir = DMA_DIR_TO_DEV; int got_bytes = this->dev_obj->xfer_to(this, this->queue_data, this->queue_len); - this->res_count += got_bytes; + this->queue_data += got_bytes; + this->res_count -= got_bytes; this->queue_len -= got_bytes; if (!this->queue_len) { this->finish_cmd(); @@ -536,14 +538,14 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8 *p_data = this->queue_data; *avail_len = req_len; this->queue_len -= req_len; - this->res_count += req_len; + this->res_count -= req_len; this->queue_data += req_len; } else { // return less data than req_len LOG_F(9, "%s: Return queue_len = %d data", this->get_name().c_str(), this->queue_len); *p_data = this->queue_data; *avail_len = this->queue_len; - this->res_count += this->queue_len; + this->res_count -= this->queue_len; this->queue_len = 0; } return DmaPullResult::MoreData; // tell the caller there is more data @@ -568,7 +570,7 @@ DmaPushResult DMAChannel::push_data(const char* src_ptr, int len) { len = std::min((int)this->queue_len, len); std::memcpy(this->queue_data, src_ptr, len); this->queue_data += len; - this->res_count += len; + this->res_count -= len; this->queue_len -= len; } From d29153ff99c5267747c20f30fb2dcdc69fdc78d0 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sun, 5 Jul 2026 09:20:03 -0700 Subject: [PATCH 3/3] ata: Demote split DMA pull diagnostic Mac OS X Public Beta commonly drives one ATA DMA transfer through multiple DBDMA descriptors, so a single ATA pull can be smaller or larger than the current descriptor request without indicating an error. Reduce the logging level to avoid logspam during boot. --- devices/common/ata/atabasedevice.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/devices/common/ata/atabasedevice.cpp b/devices/common/ata/atabasedevice.cpp index 88bbfac06e..13704cd982 100644 --- a/devices/common/ata/atabasedevice.cpp +++ b/devices/common/ata/atabasedevice.cpp @@ -198,8 +198,10 @@ int AtaBaseDevice::pull_data(uint8_t *buf, int len) { int xfer_size = std::min(this->xfer_cnt, len); - if (this->xfer_cnt != len) - LOG_F(WARNING, "%s: xfer_cnt != len", this->name.c_str()); + if (this->xfer_cnt != len) { + LOG_F(9, "%s: DMA pull size differs from descriptor, remaining=%d requested=%d", + this->name.c_str(), this->xfer_cnt, len); + } //for (int i = 0; i < xfer_size >> 1; i++) // this->data_ptr[i] = BYTESWAP_16(this->data_ptr[i]);