144 lines
4.2 KiB
C
144 lines
4.2 KiB
C
// ahci_port.c - AHCI MMIO mapping, HBA reset, and port link bring-up helpers.
|
|
#include "ahci.h"
|
|
|
|
KSTATUS ahci_map_mmio(uint64_t physBase) {
|
|
for (int i = 0; i < AHCI_MMIO_PAGES; i++) {
|
|
uint64_t phys = physBase + (uint64_t)i * 4096;
|
|
uintptr_t virt = (uintptr_t)g_hal->PhysToVirt(phys);
|
|
uint64_t flags = PAGE_PRESENT | PAGE_WRITE | PAGE_NOCACHE;
|
|
KSTATUS st = MmMapPage(virt, phys, flags);
|
|
// Ignore STATUS_ALREADY_EXISTS - a prior mapping with different flags
|
|
// is possible for the first page if the bootloader was overly generous.
|
|
// Use MmSetPageProtect to force NOCACHE in that case.
|
|
if (st == STATUS_ALREADY_EXISTS) {
|
|
st = MmSetPageProtect(virt, 4096, flags);
|
|
}
|
|
if (!KSUCCESS(st)) {
|
|
DbgPrint("AHCI: MmMapPage failed for page %d (phys=0x%016llX, st=0x%x)\n",
|
|
i, (unsigned long long)phys, st);
|
|
return st;
|
|
}
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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 STATUS_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 STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
bool port_is_sata_disk(HBA_PORT *port) {
|
|
return port->sig == SATA_SIG_ATA;
|
|
}
|
|
|
|
|
|
void ahci_delay_ms(uint32_t ms) {
|
|
for (uint32_t i = 0; i < ms * 1000; i++)
|
|
__outbyte(0x80, 0);
|
|
}
|
|
|
|
|
|
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 STATUS_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 STATUS_SUCCESS;
|
|
}
|