From bf1c9654c441d1fcb88fb96fe3e59ad7a853dfcc Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sat, 11 Jul 2026 17:44:48 -0700 Subject: [PATCH 1/2] ata: Implement reusable DMA data transfers Implement push_data in AtaBaseDevice using the device transfer buffer and post-transfer callback. Advance the read data pointer across DMA pulls so a transfer can span multiple DBDMA descriptors, and clear the DMA state when transfers finish. --- devices/common/ata/atabasedevice.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/devices/common/ata/atabasedevice.cpp b/devices/common/ata/atabasedevice.cpp index e26acb5f65..16bdac0886 100644 --- a/devices/common/ata/atabasedevice.cpp +++ b/devices/common/ata/atabasedevice.cpp @@ -207,9 +207,12 @@ 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) { + this->is_dma_xfer = false; + this->data_ptr = nullptr; TimerManager::get_instance()->add_oneshot_timer(500, [this]() { this->r_status &= ~(BSY | DRQ); this->update_intrq(1); @@ -223,7 +226,30 @@ int AtaBaseDevice::pull_data(uint8_t *buf, int len) { } int AtaBaseDevice::push_data(uint8_t *buf, int len) { - return 0; + if (!this->xfer_cnt || !this->is_dma_xfer) + return 0; + + int xfer_size = std::min(this->xfer_cnt, len); + + std::memcpy(this->cur_data_ptr, buf, xfer_size); + this->cur_data_ptr = (uint16_t *)((uint8_t *)this->cur_data_ptr + xfer_size); + + this->xfer_cnt -= xfer_size; + if (!this->xfer_cnt) { + this->post_xfer_action(); + this->is_dma_xfer = false; + this->data_ptr = nullptr; + this->cur_data_ptr = nullptr; + TimerManager::get_instance()->add_oneshot_timer(500, [this]() { + this->r_status &= ~(BSY | DRQ); + this->update_intrq(1); + }); + } else { + this->r_status &= ~BSY; + this->r_status |= DRQ; + } + + return xfer_size; } void AtaBaseDevice::update_intrq(uint8_t new_intrq_state) { From 017a4a48b1dae38f0d7d96e6e8a36b3158bd3189 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Sat, 11 Jul 2026 17:45:41 -0700 Subject: [PATCH 2/2] atahd: Implement hard disk DMA commands Add read/write DMA command support to ATA hard disks (can mostly reuse the PIO paths). --- devices/common/ata/atahd.cpp | 49 ++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/devices/common/ata/atahd.cpp b/devices/common/ata/atahd.cpp index 8a28a92cff..3a625ac8c4 100644 --- a/devices/common/ata/atahd.cpp +++ b/devices/common/ata/atahd.cpp @@ -99,11 +99,15 @@ int AtaHardDisk::perform_command() { break; case READ_MULTIPLE: case READ_SECTOR: - case READ_SECTOR_NR: { + case READ_SECTOR_NR: + 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; - uint32_t ints_size = ATA_HD_SEC_SIZE; + bool is_dma_cmd = this->r_command == READ_DMA || + this->r_command == READ_DMA_NR; + uint32_t ints_size = is_dma_cmd ? xfer_size : ATA_HD_SEC_SIZE; if (this->r_command == READ_MULTIPLE) { if (!this->sectors_per_int) { LOG_F(ERROR, "%s: READ_MULTIPLE disabled", this->name.c_str()); @@ -115,20 +119,34 @@ int AtaHardDisk::perform_command() { } hdd_img.read(buffer, offset, xfer_size); this->data_ptr = (uint16_t *)this->buffer; - // those commands should generate IRQ for each sector this->prepare_xfer(xfer_size, ints_size); - this->signal_data_ready(); + if (is_dma_cmd) { + this->is_dma_xfer = true; + 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); + } else { + // PIO commands generate IRQ for each sector or multiple block. + this->signal_data_ready(); + } } break; case WRITE_MULTIPLE: case WRITE_SECTOR: - case WRITE_SECTOR_NR: { + case WRITE_SECTOR_NR: + case WRITE_DMA: + case WRITE_DMA_NR: { uint16_t sec_count = this->r_sect_count ? this->r_sect_count : 256; - this->cur_fpos = this->get_lba() * ATA_HD_SEC_SIZE; - this->data_ptr = (uint16_t *)this->buffer; - this->cur_data_ptr = this->data_ptr; uint32_t xfer_size = sec_count * ATA_HD_SEC_SIZE; - uint32_t ints_size = ATA_HD_SEC_SIZE; + bool is_dma_cmd = this->r_command == WRITE_DMA || + this->r_command == WRITE_DMA_NR; + uint32_t ints_size = is_dma_cmd ? xfer_size : ATA_HD_SEC_SIZE; + + this->cur_fpos = this->get_lba() * ATA_HD_SEC_SIZE; + this->data_ptr = (uint16_t *)this->buffer; + this->cur_data_ptr = this->data_ptr; if (this->r_command == WRITE_MULTIPLE) { if (!this->sectors_per_int) { LOG_F(ERROR, "%s: WRITE_MULTIPLE disabled", this->name.c_str()); @@ -144,8 +162,17 @@ int AtaHardDisk::perform_command() { this->hdd_img.write(this->data_ptr, this->cur_fpos, write_len); this->cur_fpos += write_len; }; - this->r_status |= DRQ; - this->r_status &= ~BSY; + if (is_dma_cmd) { + this->is_dma_xfer = true; + 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); + } else { + this->r_status |= DRQ; + this->r_status &= ~BSY; + } } break; case CHECK_POWER_MODE: