From: Tulio A M Mendes Date: Tue, 10 Feb 2026 10:37:38 +0000 (-0300) Subject: feat: Fase 9 — ATA Bus Master IDE DMA for read/write X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=237463d3063093fdd07fbaa61ed76ac041a40f6a;p=AdrOS.git feat: Fase 9 — ATA Bus Master IDE DMA for read/write Implement Bus Master IDE DMA as a transparent upgrade over PIO: - New ata_dma.c: Bus Master IDE DMA driver for PIIX3 IDE controller - Finds IDE controller via PCI class 0x01:0x01 - Reads BAR4 for Bus Master I/O base, enables PCI bus mastering - Allocates PRDT and bounce buffer pages at dedicated VAs (0xC0220000/0xC0221000) to avoid heap VA collisions - Polling-based DMA completion (BSY clear + BM Active clear) - IRQ 14 handler with dma_active flag to prevent race between IRQ handler and polling loop on ATA status register - spin_lock (not irqsave) for serialization — PIIX3 requires interrupt delivery for DMA completion signaling - Modified ata_pio.c: transparent DMA upgrade - ata_pio_init_primary_master registers IRQ 14 handler early (before IDENTIFY) to prevent INTRQ storm - Calls ata_dma_init after IDENTIFY to probe for DMA capability - ata_pio_read28/write28 delegate to DMA when available, fall back to PIO if DMA init failed - New include/ata_dma.h: public API header Key bugs fixed during development: - hal_mm_phys_to_virt can map PMM pages into heap VA range (phys 0x10000000 -> virt 0xD0000000 = heap base) — use dedicated VAs instead - ATA INTRQ must be deasserted by reading status register; without IRQ handler, unacknowledged INTRQ causes interrupt storm - nIEN must be cleared before DMA so device asserts INTRQ - DMA direction bit must be set before Start bit per ATA spec Tested: 4-CPU (25s) and 1-CPU (45s) pass all init.elf tests including diskfs getdents and /persist/counter. --- diff --git a/include/ata_dma.h b/include/ata_dma.h new file mode 100644 index 0000000..5f4b384 --- /dev/null +++ b/include/ata_dma.h @@ -0,0 +1,23 @@ +#ifndef ATA_DMA_H +#define ATA_DMA_H + +#include + +/* Try to initialize ATA Bus Master DMA on the primary channel. + * Returns 0 on success, negative errno on failure (no PCI IDE, etc.). + * If DMA init fails, PIO mode remains available. */ +int ata_dma_init(void); + +/* Returns 1 if DMA is available and initialized. */ +int ata_dma_available(void); + +/* DMA read: read one sector (512 bytes) at LBA into buf. + * buf must be a kernel virtual address (will be translated to physical). + * Returns 0 on success, negative errno on failure. */ +int ata_dma_read28(uint32_t lba, uint8_t* buf512); + +/* DMA write: write one sector (512 bytes) from buf to LBA. + * Returns 0 on success, negative errno on failure. */ +int ata_dma_write28(uint32_t lba, const uint8_t* buf512); + +#endif diff --git a/src/hal/x86/ata_dma.c b/src/hal/x86/ata_dma.c new file mode 100644 index 0000000..6d87fa6 --- /dev/null +++ b/src/hal/x86/ata_dma.c @@ -0,0 +1,314 @@ +#include "ata_dma.h" +#include "pci.h" +#include "io.h" +#include "pmm.h" +#include "vmm.h" +#include "arch/x86/idt.h" +#include "spinlock.h" +#include "uart_console.h" +#include "utils.h" +#include "errno.h" + +#include +#include + +/* ATA I/O ports (primary channel) */ +#define ATA_IO_BASE 0x1F0 +#define ATA_CTRL_BASE 0x3F6 + +#define ATA_REG_DATA 0x00 +#define ATA_REG_ERROR 0x01 +#define ATA_REG_SECCOUNT0 0x02 +#define ATA_REG_LBA0 0x03 +#define ATA_REG_LBA1 0x04 +#define ATA_REG_LBA2 0x05 +#define ATA_REG_HDDEVSEL 0x06 +#define ATA_REG_COMMAND 0x07 +#define ATA_REG_STATUS 0x07 + +#define ATA_CMD_READ_DMA 0xC8 +#define ATA_CMD_WRITE_DMA 0xCA +#define ATA_CMD_CACHE_FLUSH 0xE7 + +#define ATA_SR_BSY 0x80 +#define ATA_SR_DRDY 0x40 +#define ATA_SR_DF 0x20 +#define ATA_SR_DRQ 0x08 +#define ATA_SR_ERR 0x01 + +/* Bus Master IDE register offsets (from BAR4) */ +#define BM_CMD 0x00 /* Command register (byte) */ +#define BM_STATUS 0x02 /* Status register (byte) */ +#define BM_PRDT 0x04 /* PRDT address (dword) */ + +/* BM_CMD bits */ +#define BM_CMD_START 0x01 +#define BM_CMD_READ 0x08 /* 1 = device-to-memory (read), 0 = memory-to-device (write) */ + +/* BM_STATUS bits */ +#define BM_STATUS_ACTIVE 0x01 /* Bus master active */ +#define BM_STATUS_ERR 0x02 /* Error */ +#define BM_STATUS_IRQ 0x04 /* Interrupt (write 1 to clear) */ + +/* Physical Region Descriptor (PRD) entry */ +struct prd_entry { + uint32_t phys_addr; /* Physical buffer address */ + uint16_t byte_count; /* Byte count (0 = 64KB) */ + uint16_t flags; /* Bit 15 = end of table */ +} __attribute__((packed)); + +/* State */ +static int dma_available = 0; +static uint16_t bm_base = 0; /* Bus Master I/O base port */ +static struct prd_entry* prdt = NULL; /* PRDT (kernel virtual) */ +static uint32_t prdt_phys = 0; /* PRDT physical address */ +static uint8_t* dma_buf = NULL; /* 512-byte DMA bounce buffer (kernel virtual) */ +static uint32_t dma_buf_phys = 0; /* DMA bounce buffer physical address */ + +static volatile int dma_active = 0; /* Set during DMA polling to prevent IRQ handler race */ +static spinlock_t dma_lock = {0}; + +static inline void io_wait_400ns(void) { + (void)inb((uint16_t)ATA_CTRL_BASE); + (void)inb((uint16_t)ATA_CTRL_BASE); + (void)inb((uint16_t)ATA_CTRL_BASE); + (void)inb((uint16_t)ATA_CTRL_BASE); +} + +static int ata_wait_not_busy(void) { + for (int i = 0; i < 1000000; i++) { + uint8_t st = inb((uint16_t)(ATA_IO_BASE + ATA_REG_STATUS)); + if ((st & ATA_SR_BSY) == 0) return 0; + } + return -EIO; +} + +/* IRQ 14 handler (vector 46): acknowledge device interrupt. + * During active DMA polling, only clear BM status (polling loop handles ATA status). + * During PIO operations, read ATA status to deassert INTRQ. */ +static void ata_irq14_handler(struct registers* regs) { + (void)regs; + if (dma_active) { + /* DMA polling loop is handling completion — just clear BM IRQ bit */ + uint8_t bm_stat = inb((uint16_t)(bm_base + BM_STATUS)); + outb((uint16_t)(bm_base + BM_STATUS), bm_stat | BM_STATUS_IRQ); + } else { + /* PIO mode — read ATA status to deassert INTRQ */ + (void)inb((uint16_t)(ATA_IO_BASE + ATA_REG_STATUS)); + } +} + +int ata_dma_init(void) { + if (dma_available) return 0; /* Already initialized */ + + /* Find IDE controller: PCI class 0x01 (Mass Storage), subclass 0x01 (IDE) */ + const struct pci_device* ide = pci_find_class(0x01, 0x01); + if (!ide) { + uart_print("[ATA-DMA] No PCI IDE controller found.\n"); + return -ENODEV; + } + + /* BAR4 contains the Bus Master IDE I/O base */ + uint32_t bar4 = ide->bar[4]; + if ((bar4 & 1) == 0) { + uart_print("[ATA-DMA] BAR4 is not I/O space.\n"); + return -ENODEV; + } + bm_base = (uint16_t)(bar4 & 0xFFFC); + + if (bm_base == 0) { + uart_print("[ATA-DMA] BAR4 I/O base is zero.\n"); + return -ENODEV; + } + + /* Enable PCI Bus Mastering (bit 2 of command register) + I/O space (bit 0) */ + uint32_t cmd_reg = pci_config_read(ide->bus, ide->slot, ide->func, 0x04); + cmd_reg |= (1U << 0) | (1U << 2); /* I/O Space Enable + Bus Master Enable */ + pci_config_write(ide->bus, ide->slot, ide->func, 0x04, cmd_reg); + + /* Allocate PRDT: one page, physically contiguous, aligned. + * We only need 1 PRD entry (8 bytes) but allocate a full page. */ + void* prdt_page = pmm_alloc_page(); + if (!prdt_page) { + uart_print("[ATA-DMA] Failed to allocate PRDT page.\n"); + return -ENOMEM; + } + prdt_phys = (uint32_t)(uintptr_t)prdt_page; + /* Map PRDT at a dedicated VA to avoid collisions with the heap. + * 0xC0220000 is above LAPIC(0xC0200000), IOAPIC(0xC0201000), + * and ACPI temp window (0xC0202000-0xC0212000). */ + uintptr_t prdt_virt = 0xC0220000U; + vmm_map_page((uint64_t)prdt_phys, (uint64_t)prdt_virt, + VMM_FLAG_PRESENT | VMM_FLAG_RW); + prdt = (struct prd_entry*)prdt_virt; + memset((void*)prdt, 0, PAGE_SIZE); + + /* Allocate DMA bounce buffer: one page for sector transfers */ + void* buf_page = pmm_alloc_page(); + if (!buf_page) { + uart_print("[ATA-DMA] Failed to allocate DMA buffer page.\n"); + pmm_free_page(prdt_page); + return -ENOMEM; + } + dma_buf_phys = (uint32_t)(uintptr_t)buf_page; + uintptr_t buf_virt = 0xC0221000U; + vmm_map_page((uint64_t)dma_buf_phys, (uint64_t)buf_virt, + VMM_FLAG_PRESENT | VMM_FLAG_RW); + dma_buf = (uint8_t*)buf_virt; + + /* Set up the single PRD entry: 512 bytes, end-of-table */ + prdt[0].phys_addr = dma_buf_phys; + prdt[0].byte_count = 512; + prdt[0].flags = 0x8000; /* EOT bit */ + + /* Register IRQ 14 handler to acknowledge device interrupts */ + register_interrupt_handler(46, ata_irq14_handler); + + /* Stop any in-progress DMA and clear status */ + outb((uint16_t)(bm_base + BM_CMD), 0); + outb((uint16_t)(bm_base + BM_STATUS), BM_STATUS_IRQ | BM_STATUS_ERR); + + dma_available = 1; + + char tmp[12]; + uart_print("[ATA-DMA] Initialized, BM I/O base="); + itoa_hex(bm_base, tmp); + uart_print(tmp); + uart_print("\n"); + + return 0; +} + +int ata_dma_available(void) { + return dma_available; +} + +/* Common DMA transfer setup and wait */ +static int ata_dma_transfer(uint32_t lba, int is_write) { + if (lba & 0xF0000000U) return -EINVAL; + + /* Ensure nIEN is cleared so the device asserts INTRQ on completion. + * The Bus Master IRQ bit won't be set without INTRQ. */ + outb((uint16_t)ATA_CTRL_BASE, 0x00); + + /* Read ATA status to clear any pending interrupt */ + (void)inb((uint16_t)(ATA_IO_BASE + ATA_REG_STATUS)); + + /* Set PRDT address */ + outl((uint16_t)(bm_base + BM_PRDT), prdt_phys); + + /* Clear status bits */ + outb((uint16_t)(bm_base + BM_STATUS), BM_STATUS_IRQ | BM_STATUS_ERR); + + /* Wait for drive not busy */ + if (ata_wait_not_busy() < 0) return -EIO; + + /* Select drive + LBA */ + outb((uint16_t)(ATA_IO_BASE + ATA_REG_HDDEVSEL), + (uint8_t)(0xE0 | ((lba >> 24) & 0x0F))); + io_wait_400ns(); + + /* Sector count and LBA */ + outb((uint16_t)(ATA_IO_BASE + ATA_REG_SECCOUNT0), 1); + outb((uint16_t)(ATA_IO_BASE + ATA_REG_LBA0), (uint8_t)(lba & 0xFF)); + outb((uint16_t)(ATA_IO_BASE + ATA_REG_LBA1), (uint8_t)((lba >> 8) & 0xFF)); + outb((uint16_t)(ATA_IO_BASE + ATA_REG_LBA2), (uint8_t)((lba >> 16) & 0xFF)); + + /* Mark DMA as active so IRQ handler doesn't race on ATA status */ + __atomic_store_n(&dma_active, 1, __ATOMIC_SEQ_CST); + + /* Step 3: Set direction bit in BM Command (without Start bit yet) */ + uint8_t bm_dir = is_write ? 0x00 : BM_CMD_READ; + outb((uint16_t)(bm_base + BM_CMD), bm_dir); + + /* Step 4: Issue ATA DMA command to the device */ + outb((uint16_t)(ATA_IO_BASE + ATA_REG_COMMAND), + is_write ? ATA_CMD_WRITE_DMA : ATA_CMD_READ_DMA); + + /* Step 5: Set Start bit to begin the DMA transfer */ + outb((uint16_t)(bm_base + BM_CMD), bm_dir | BM_CMD_START); + + /* Poll for DMA completion: + * 1. Wait for ATA BSY to clear (device finished processing) + * 2. Check BM status for Active bit clear (DMA engine done) + * 3. Check for errors */ + int completed = 0; + for (int i = 0; i < 2000000; i++) { + uint8_t ata_stat = inb((uint16_t)(ATA_IO_BASE + ATA_REG_STATUS)); + uint8_t bm_stat = inb((uint16_t)(bm_base + BM_STATUS)); + + /* Check for Bus Master error */ + if (bm_stat & BM_STATUS_ERR) { + outb((uint16_t)(bm_base + BM_CMD), 0); + outb((uint16_t)(bm_base + BM_STATUS), BM_STATUS_IRQ | BM_STATUS_ERR); + uart_print("[ATA-DMA] Bus master error!\n"); + return -EIO; + } + + /* Check for ATA error */ + if (ata_stat & ATA_SR_ERR) { + outb((uint16_t)(bm_base + BM_CMD), 0); + outb((uint16_t)(bm_base + BM_STATUS), BM_STATUS_IRQ | BM_STATUS_ERR); + uart_print("[ATA-DMA] ATA error during DMA!\n"); + return -EIO; + } + + /* DMA complete when: BSY clear AND BM Active clear */ + if (!(ata_stat & ATA_SR_BSY) && !(bm_stat & BM_STATUS_ACTIVE)) { + completed = 1; + break; + } + } + + /* Stop Bus Master */ + outb((uint16_t)(bm_base + BM_CMD), 0); + + /* Clear status bits */ + outb((uint16_t)(bm_base + BM_STATUS), BM_STATUS_IRQ | BM_STATUS_ERR); + + __atomic_store_n(&dma_active, 0, __ATOMIC_SEQ_CST); + + if (!completed) { + uart_print("[ATA-DMA] Transfer timeout!\n"); + return -EIO; + } + + return 0; +} + +int ata_dma_read28(uint32_t lba, uint8_t* buf512) { + if (!buf512) return -EFAULT; + if (!dma_available) return -ENOSYS; + + spin_lock(&dma_lock); + + int ret = ata_dma_transfer(lba, 0); + if (ret == 0) { + /* Copy from DMA bounce buffer to caller's buffer */ + memcpy(buf512, dma_buf, 512); + } + + spin_unlock(&dma_lock); + return ret; +} + +int ata_dma_write28(uint32_t lba, const uint8_t* buf512) { + if (!buf512) return -EFAULT; + if (!dma_available) return -ENOSYS; + + spin_lock(&dma_lock); + + /* Copy caller's data into DMA bounce buffer */ + memcpy(dma_buf, buf512, 512); + + int ret = ata_dma_transfer(lba, 1); + + if (ret == 0) { + /* Flush cache after write */ + outb((uint16_t)(ATA_IO_BASE + ATA_REG_COMMAND), ATA_CMD_CACHE_FLUSH); + (void)ata_wait_not_busy(); + } + + spin_unlock(&dma_lock); + return ret; +} diff --git a/src/hal/x86/ata_pio.c b/src/hal/x86/ata_pio.c index 4406a47..1115dd0 100644 --- a/src/hal/x86/ata_pio.c +++ b/src/hal/x86/ata_pio.c @@ -1,7 +1,17 @@ #include "ata_pio.h" +#include "ata_dma.h" #include "errno.h" #include "io.h" +#include "arch/x86/idt.h" +#include "uart_console.h" + +/* Basic IRQ 14 handler: read ATA status to deassert INTRQ. + * Registered early so PIO IDENTIFY doesn't cause an IRQ storm. */ +static void ata_pio_irq14_handler(struct registers* regs) { + (void)regs; + (void)inb((uint16_t)(0x1F0 + 0x07)); /* Read ATA status */ +} // Primary ATA bus I/O ports #define ATA_IO_BASE 0x1F0 @@ -59,6 +69,9 @@ uint32_t ata_pio_sector_size(void) { } int ata_pio_init_primary_master(void) { + // Register IRQ 14 handler early to prevent INTRQ storm + register_interrupt_handler(46, ata_pio_irq14_handler); + // Select drive: 0xA0 = master, CHS mode bits set, LBA bit cleared outb((uint16_t)(ATA_IO_BASE + ATA_REG_HDDEVSEL), 0xA0); io_wait_400ns(); @@ -82,6 +95,13 @@ int ata_pio_init_primary_master(void) { (void)inw((uint16_t)(ATA_IO_BASE + ATA_REG_DATA)); } + // Try to upgrade to DMA mode + if (ata_dma_init() == 0) { + uart_print("[ATA] Using DMA mode.\n"); + } else { + uart_print("[ATA] Using PIO mode (DMA unavailable).\n"); + } + return 0; } @@ -89,6 +109,11 @@ int ata_pio_read28(uint32_t lba, uint8_t* buf512) { if (!buf512) return -EFAULT; if (lba & 0xF0000000U) return -EINVAL; + // Use DMA if available + if (ata_dma_available()) { + return ata_dma_read28(lba, buf512); + } + if (ata_wait_not_busy() < 0) return -EIO; outb((uint16_t)(ATA_IO_BASE + ATA_REG_HDDEVSEL), (uint8_t)(0xE0 | ((lba >> 24) & 0x0F))); @@ -115,6 +140,11 @@ int ata_pio_write28(uint32_t lba, const uint8_t* buf512) { if (!buf512) return -EFAULT; if (lba & 0xF0000000U) return -EINVAL; + // Use DMA if available + if (ata_dma_available()) { + return ata_dma_write28(lba, buf512); + } + if (ata_wait_not_busy() < 0) return -EIO; outb((uint16_t)(ATA_IO_BASE + ATA_REG_HDDEVSEL), (uint8_t)(0xE0 | ((lba >> 24) & 0x0F)));