// AHCI port helpers. This file owns port reset, command-table setup, and per-port command execution. #include "ahci.h" // Implements the ahci map mmio operation. KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length) { if (length == 0) length = AHCI_MMIO_PAGES * 4096ull; uint64_t pageBase = physBase & ~(uint64_t)(PAGE_SIZE - 1u); uint64_t pageEnd = (physBase + length + PAGE_SIZE - 1u) & ~(uint64_t)(PAGE_SIZE - 1u); int pageIndex = 0; for (uint64_t phys = pageBase; phys < pageEnd; phys += PAGE_SIZE, pageIndex++) { uintptr_t virt = (uintptr_t)MmDdkPhysToVirt(phys); uint64_t flags = PAGE_PRESENT | PAGE_WRITE | PAGE_CACHE_UC; KSTATUS st = MmDdkMapPage(virt, phys, flags, NtlDdkMapReplaceExisting); if (!KSUCCESS(st)) { DbgPrint("AHCI: MMIO map failed for page %d (phys=0x%016llX, st=0x%x)\n", pageIndex, (unsigned long long)phys, st); return st; } } return NTLSTATUS_SUCCESS; } // Implements the port stop operation. void port_stop(HBA_PORT* port) { // Clear ST (bit 0). port->cmd &= ~PORT_CMD_ST; // Wait for CR (bit 15) to clear (command list DMA stops). for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(port->cmd & PORT_CMD_CR)) break; } // Clear FRE (bit 4). port->cmd &= ~PORT_CMD_FRE; // Wait for FR (bit 14) to clear. for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(port->cmd & PORT_CMD_FR)) break; } } // Implements the port start operation. void port_start(HBA_PORT* port) { // Wait until CR is clear before setting ST. for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(port->cmd & PORT_CMD_CR)) break; } port->cmd |= PORT_CMD_FRE; port->cmd |= PORT_CMD_ST; } // Implements the hba reset operation. KSTATUS hba_reset(HBA_MEM* hba) { // BIOS/OS handoff (spec §10.6.3). if (hba->cap2 & CAP2_BOH) { hba->bohc |= BOHC_OOS; // Wait for BIOS to clear BOS. for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(hba->bohc & BOHC_BOS)) break; } // Give the BIOS another moment to clear Busy. for (int i = 0; i < 800000; i++) { if (!(hba->bohc & BOHC_BB)) break; } } // Enable AHCI mode before reset so the reset actually takes effect. hba->ghc |= GHC_AE; // HBA reset. hba->ghc |= GHC_HR; for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(hba->ghc & GHC_HR)) break; } if (hba->ghc & GHC_HR) { DbgPrint("AHCI: HBA reset timed out\n"); return NTLSTATUS_TIMEOUT; } // Re-enable AHCI mode (reset cleared it), then enable global interrupts. // GHC.IE lets the HBA assert its PCI interrupt line when a port fires. hba->ghc |= GHC_AE | GHC_IE; return NTLSTATUS_SUCCESS; } // Implements the port link up operation. bool port_link_up(HBA_PORT* port) { uint32_t det = port->ssts & 0x0F; uint32_t ipm = (port->ssts >> 8) & 0x0F; return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE); } // Implements the port is sata disk operation. bool port_is_sata_disk(HBA_PORT* port) { return port->sig == SATA_SIG_ATA; } // Implements the ahci delay ms operation. void ahci_delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 1000; i++) __outbyte(0x80, 0); } // Implements the port comreset operation. KSTATUS port_comreset(AHCI_DRIVE* drv) { HBA_PORT* port = drv->port; port->cmd &= ~PORT_CMD_ST; for (int i = 0; i < POLL_TIMEOUT; i++) if (!(port->cmd & PORT_CMD_CR)) break; // Assert COMRESET: SControl.DET = 1, hold for ≥ 1ms. port->sctl = (port->sctl & ~0x0Fu) | 0x1u; ahci_delay_ms(1); // Deassert: DET = 0. port->sctl = port->sctl & ~0x0Fu; // Poll for DET = 3, up to 1 second. bool link_ok = false; for (int i = 0; i < 1000; i++) { if ((port->ssts & 0x0Fu) == PORT_DET_PRESENT) { link_ok = true; break; } ahci_delay_ms(1); } if (!link_ok) { DbgPrint("AHCI: COMRESET: link did not come back (ssts=0x%x)\n", port->ssts); return NTLSTATUS_TIMEOUT; } port->serr = (uint32_t)-1; port->is = (uint32_t)-1; port->cmd |= PORT_CMD_ST; // Wait up to 500ms for a valid signature (D2H FIS from device). for (int i = 0; i < 500; i++) { uint32_t sig = port->sig; if (sig != 0xFFFFFFFFu && sig != 0x00000000u) break; ahci_delay_ms(1); } DbgPrint("AHCI: port COMRESET done: sig=0x%08x ssts=0x%08x\n", port->sig, port->ssts); return NTLSTATUS_SUCCESS; }