colonel.exe 🤑, refactor all include directives, AHCI is now interrupt driven, upgrades to IDE, implemented I/O Request Packets, fix numerous keyboard issues, change all for (;;) and while (1) to while (true), DbgPrint guarded by spinlock, fix KeSignalEvent, major KernelMain refactor, added SystemInitThread because of new blocking I/O, replace memcpy manouvers with strncpy and strncat, add: run for ide, runa for ahci, rund for debug and ide, runad for debug and ahci.
This commit is contained in:
1 parent
7f4052f41c
commit
9fa2a7e0db
61 files changed
+1311
-456
No files matched your search
+316
-77
@@ -1,4 +1,4 @@
|
||||
// ahci.c - AHCI driver, polling mode.
|
||||
// ahci.c - AHCI driver, interrupt-driven with per-drive async IRP queue.
|
||||
// Supports SATA drives only (no ATAPI). Registers each found drive with the
|
||||
// HAL disk subsystem via IoRegisterDisk().
|
||||
//
|
||||
@@ -7,17 +7,51 @@
|
||||
// hal->PhysToVirt(phys) converts to kernel virtual address.
|
||||
// DMA buffers are bounce-buffered (allocated via MmAllocateContiguous) to
|
||||
// avoid needing a VirtToPhys conversion for arbitrary kernel pointers.
|
||||
//
|
||||
// Async I/O model (IRP path):
|
||||
// Each drive has a dedicated kernel worker thread (AhciWorker) that blocks
|
||||
// on a per-drive counting semaphore. Callers submit IRPs via AhciSubmitIrp,
|
||||
// which enqueues the IRP and signals the semaphore. The worker dequeues
|
||||
// one IRP at a time, runs the synchronous hardware transfer (blocking the
|
||||
// worker, NOT the submitting thread), then writes irp->Status and fires
|
||||
// irp->CompletionEvent / irp->CompletionFn.
|
||||
//
|
||||
// This gives true async I/O from the caller's perspective: IoSubmitIrp()
|
||||
// returns STATUS_PENDING immediately. Multiple callers can have outstanding
|
||||
// requests against different drives concurrently without stalling each other.
|
||||
//
|
||||
// NCQ extension (future work — see "NCQ TODO" comments):
|
||||
// To saturate modern SSDs, replace the serialised single-slot model with
|
||||
// Native Command Queuing:
|
||||
// • Allocate per-slot bounce buffers (NUM_NCQ_SLOTS × SLOT_BOUNCE_BYTES).
|
||||
// • Use ATA_CMD_READ_FPDMA_QUEUED / WRITE_FPDMA_QUEUED.
|
||||
// • Encode the slot tag in FIS.countl bits 7:3.
|
||||
// • Issue via port->sact (NCQ active) rather than port->ci.
|
||||
// • In AhciIsr: completed slots = prev_sact ^ port->sact; look up
|
||||
// slot_irp[slot] and fire each completion from within the ISR.
|
||||
// • The worker becomes a pure dispatcher: grab a free slot, build the
|
||||
// command, issue it, return immediately. No per-slot KeWaitForEvent.
|
||||
//
|
||||
// Interrupt model (unchanged):
|
||||
// GHC.IE and per-port PORT_IE are enabled after each port is initialised.
|
||||
// AhciIsr fires on the PCI IRQ line and signals the per-drive completionEvent.
|
||||
// port_issue_cmd blocks the worker thread on KeWaitForEvent instead of
|
||||
// spinning on CI. port_issue_cmd_poll is kept for early init (IDENTIFY)
|
||||
// before the scheduler is live.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <io_driver.h>
|
||||
#include <mm_phys.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ke_debug.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ke_event.h>
|
||||
#include <ke_semaphore.h>
|
||||
#include <ke_thread.h>
|
||||
#include <ke_irq.h>
|
||||
#include <ke_irp.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FIS types
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef enum {
|
||||
FIS_TYPE_REG_H2D = 0x27,
|
||||
@@ -322,11 +356,14 @@ typedef volatile struct {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const HAL_API *g_hal;
|
||||
static HBA_MEM *g_hba; // HBA MMIO base (set during init)
|
||||
static uint8_t g_irq; // PCI IRQ line for this HBA
|
||||
|
||||
// Per-drive context
|
||||
typedef struct {
|
||||
HBA_PORT *port; // MMIO port registers (virtual)
|
||||
uint64_t sectorCount; // Total LBA sectors on device
|
||||
uint8_t portNo; // Physical port index (for ISR dispatch)
|
||||
|
||||
// Structures block (command list + FIS + cmd tables)
|
||||
uint64_t structPhys; // Physical base
|
||||
@@ -335,6 +372,28 @@ typedef struct {
|
||||
// Bounce buffer for DMA data
|
||||
uint64_t bouncePhys;
|
||||
void *bounceVirt;
|
||||
|
||||
// Signaled by the ISR when the port raises an interrupt.
|
||||
// Auto-reset: one Wait consumes one completion.
|
||||
EVENT *completionEvent;
|
||||
|
||||
// Last IS snapshot captured by the ISR (for error checking post-wait).
|
||||
volatile uint32_t lastIs;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// IRP queue — async dispatch
|
||||
// -----------------------------------------------------------------------
|
||||
// AhciWorker blocks here waiting for work. AhciSubmitIrp enqueues an IRP
|
||||
// and signals the semaphore to wake the worker.
|
||||
IRP_QUEUE irpQueue;
|
||||
THREAD *workerThread;
|
||||
|
||||
// NCQ TODO: for multi-slot NCQ, add:
|
||||
// uint32_t slotMask; // bitmask of in-flight slots
|
||||
// IRP *slotIrp[NUM_CMD_SLOTS]; // slot → pending IRP
|
||||
// uint64_t slotBouncePhys[NUM_CMD_SLOTS];
|
||||
// void *slotBounceVirt[NUM_CMD_SLOTS];
|
||||
// volatile uint32_t prevSact; // previous port->sact snapshot
|
||||
} AHCI_DRIVE;
|
||||
|
||||
static AHCI_DRIVE g_drives[MAX_AHCI_DRIVES];
|
||||
@@ -476,11 +535,12 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Issue a command (polling, always uses slot 0)
|
||||
// Returns STATUS_SUCCESS or STATUS_DEVICE_ERROR / STATUS_TIMEOUT.
|
||||
// Issue a command — polling fallback (used during early init / IDENTIFY only,
|
||||
// before the scheduler and interrupt handler are live).
|
||||
// Returns STATUS_SUCCESS, STATUS_DEVICE_ERROR, or STATUS_TIMEOUT.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
|
||||
static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) {
|
||||
HBA_PORT *port = drv->port;
|
||||
|
||||
// Clear pending interrupts before issuing
|
||||
@@ -499,19 +559,17 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
|
||||
port->ci = (1u << 0);
|
||||
|
||||
// Poll until slot 0 clears
|
||||
for (;;) {
|
||||
while (true) {
|
||||
if (!(port->ci & (1u << 0))) break;
|
||||
if (port->is & (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)) {
|
||||
DbgPrint("AHCI: error IS=0x%x TFD=0x%x SERR=0x%x\n",
|
||||
port->is, port->tfd, port->serr);
|
||||
// Reset error bits
|
||||
port->serr = (uint32_t)-1;
|
||||
port->is = (uint32_t)-1;
|
||||
return STATUS_DEVICE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// One final error check after CI cleared
|
||||
if (port->is & PORT_IS_TFES) {
|
||||
DbgPrint("AHCI: TFES after completion TFD=0x%x\n", port->tfd);
|
||||
port->is = (uint32_t)-1;
|
||||
@@ -521,6 +579,77 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ISR — called from KeIrqDispatch on the AHCI PCI IRQ line.
|
||||
// Walks all registered drives, checks if their port raised an interrupt,
|
||||
// captures IS, clears it, and signals the per-drive completion event.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void AhciIsr(void) {
|
||||
if (!g_hba) return;
|
||||
|
||||
// GHC.IS tells us which ports are pending.
|
||||
uint32_t pending = g_hba->is;
|
||||
if (!pending) return;
|
||||
|
||||
for (int i = 0; i < g_driveCount; i++) {
|
||||
AHCI_DRIVE *drv = &g_drives[i];
|
||||
uint32_t portBit = (1u << drv->portNo);
|
||||
if (!(pending & portBit)) continue;
|
||||
|
||||
// Capture and clear port IS before signalling — the waiter reads
|
||||
// lastIs to check for errors without racing the next command.
|
||||
uint32_t portIs = drv->port->is;
|
||||
drv->port->is = portIs; // W1C — write back to clear
|
||||
drv->lastIs = portIs;
|
||||
|
||||
// Clear the HBA-level bit.
|
||||
g_hba->is = portBit;
|
||||
|
||||
if (drv->completionEvent)
|
||||
KeSignalEvent(drv->completionEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Issue a command — interrupt-driven (runtime path).
|
||||
// Caller has already built slot 0 via build_rw_cmd / port_identify.
|
||||
// Blocks the current thread until the ISR signals completionEvent.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
|
||||
HBA_PORT *port = drv->port;
|
||||
|
||||
// Clear stale IS and snapshot before issuing so we don't mis-read a
|
||||
// leftover value from a prior command.
|
||||
port->is = (uint32_t)-1;
|
||||
drv->lastIs = 0;
|
||||
|
||||
// Wait for port idle
|
||||
int spin = 0;
|
||||
while ((port->tfd & (ATA_SR_BUSY | ATA_SR_DRQ)) && spin < POLL_TIMEOUT)
|
||||
spin++;
|
||||
if (spin == POLL_TIMEOUT) {
|
||||
DbgPrint("AHCI: port hung (pre-issue)\n");
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
// Issue slot 0, then block on the event.
|
||||
port->ci = (1u << 0);
|
||||
KeWaitForEvent(drv->completionEvent);
|
||||
|
||||
// The ISR has cleared IS — check for errors in the snapshot.
|
||||
uint32_t is = drv->lastIs;
|
||||
if (is & (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)) {
|
||||
DbgPrint("AHCI: error IS=0x%x TFD=0x%x SERR=0x%x\n",
|
||||
is, port->tfd, port->serr);
|
||||
port->serr = (uint32_t)-1;
|
||||
return STATUS_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Build a standard DMA read or write command in slot 0
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -606,7 +735,8 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) {
|
||||
fis->command = ATA_CMD_IDENTIFY;
|
||||
fis->device = 0;
|
||||
|
||||
KSTATUS st = port_issue_cmd(drv);
|
||||
// Poll: IDENTIFY runs before the scheduler and IRQ handler are live.
|
||||
KSTATUS st = port_issue_cmd_poll(drv);
|
||||
if (!KSUCCESS(st)) return st;
|
||||
|
||||
// Parse sector count from IDENTIFY data
|
||||
@@ -677,31 +807,71 @@ static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba,
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Macro-generate per-slot trampoline pairs (read + write)
|
||||
#define AHCI_TRAMPOLINE(N) \
|
||||
static KSTATUS AhciRead##N(uint64_t lba, uint32_t n, void *b) \
|
||||
{ return do_read (&g_drives[N], lba, n, b); } \
|
||||
static KSTATUS AhciWrite##N(uint64_t lba, uint32_t n, const void *b) \
|
||||
{ return do_write(&g_drives[N], lba, n, b); }
|
||||
// ---------------------------------------------------------------------------
|
||||
// HAL_DISK callbacks — context pointer replaces trampolines.
|
||||
// Ctx is the AHCI_DRIVE* stored in DISK_DRIVER.Ctx; the dispatch layer in
|
||||
// hal_disk.c passes it as the first argument to every call.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AHCI_TRAMPOLINE(0)
|
||||
AHCI_TRAMPOLINE(1)
|
||||
AHCI_TRAMPOLINE(2)
|
||||
AHCI_TRAMPOLINE(3)
|
||||
AHCI_TRAMPOLINE(4)
|
||||
AHCI_TRAMPOLINE(5)
|
||||
AHCI_TRAMPOLINE(6)
|
||||
AHCI_TRAMPOLINE(7)
|
||||
static KSTATUS AhciRead(void *ctx, uint64_t lba, uint32_t count, void *buf) {
|
||||
return do_read((AHCI_DRIVE *)ctx, lba, count, buf);
|
||||
}
|
||||
|
||||
typedef KSTATUS (*RW_FN_R)(uint64_t, uint32_t, void*);
|
||||
typedef KSTATUS (*RW_FN_W)(uint64_t, uint32_t, const void*);
|
||||
static KSTATUS AhciWrite(void *ctx, uint64_t lba, uint32_t count, const void *buf) {
|
||||
return do_write((AHCI_DRIVE *)ctx, lba, count, buf);
|
||||
}
|
||||
|
||||
static const RW_FN_R kReadFns[MAX_AHCI_DRIVES] = {
|
||||
AhciRead0, AhciRead1, AhciRead2, AhciRead3,
|
||||
AhciRead4, AhciRead5, AhciRead6, AhciRead7 };
|
||||
static const RW_FN_W kWriteFns[MAX_AHCI_DRIVES] = {
|
||||
AhciWrite0, AhciWrite1, AhciWrite2, AhciWrite3,
|
||||
AhciWrite4, AhciWrite5, AhciWrite6, AhciWrite7 };
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async IRP dispatch
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// AhciSubmitIrp — called by IoSubmitIrp().
|
||||
// Enqueues the IRP and wakes the per-drive worker thread.
|
||||
// Returns STATUS_PENDING immediately; the caller waits on irp->CompletionEvent
|
||||
// or irp->CompletionFn.
|
||||
static KSTATUS AhciSubmitIrp(void *ctx, IRP *irp) {
|
||||
AHCI_DRIVE *drv = (AHCI_DRIVE *)ctx;
|
||||
irp->Status = STATUS_FAILURE; // sentinel: I/O has not completed yet
|
||||
IrpQueueEnqueue(&drv->irpQueue, irp);
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
// AhciWorker — kernel thread, one per drive.
|
||||
// Dequeues IRPs and executes them one at a time using the existing
|
||||
// single-slot interrupt-driven path. Completion fires the IRP's event
|
||||
// and/or callback so the original caller can observe the result without
|
||||
// ever having blocked in a driver call.
|
||||
//
|
||||
// NCQ TODO: replace the serialised loop with a multi-slot dispatcher:
|
||||
// 1. KeWaitSemaphore() to wait for a free slot AND a pending IRP.
|
||||
// 2. Pick the oldest IRP from the queue.
|
||||
// 3. Grab a free bit in drv->slotMask (under the slot spinlock).
|
||||
// 4. Copy write data into slotBounce[slot], build the NCQ FIS (FPDMA),
|
||||
// record slotIrp[slot] = irp, and issue via port->sact + port->ci.
|
||||
// 5. Return immediately — the ISR drives all completions.
|
||||
static void AhciWorker(uintptr_t arg) {
|
||||
AHCI_DRIVE *drv = (AHCI_DRIVE *)arg;
|
||||
|
||||
while (true) {
|
||||
// Block until AhciSubmitIrp signals a new IRP.
|
||||
KeWaitSemaphore(drv->irpQueue.Semaphore);
|
||||
|
||||
IRP *irp = IrpQueueDequeue(&drv->irpQueue);
|
||||
if (!irp) continue; // spurious wakeup — should not happen
|
||||
|
||||
bool write = !!(irp->Flags & IRP_FLAG_WRITE);
|
||||
KSTATUS st;
|
||||
if (write)
|
||||
st = do_write(drv, irp->Lba, irp->SectorCount, irp->Buffer);
|
||||
else
|
||||
st = do_read (drv, irp->Lba, irp->SectorCount, irp->Buffer);
|
||||
|
||||
// Publish result then fire completion hooks.
|
||||
irp->Status = st;
|
||||
if (irp->CompletionEvent) KeSignalEvent(irp->CompletionEvent);
|
||||
if (irp->CompletionFn) irp->CompletionFn(irp, irp->CompletionCtx);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AHCI controller initialisation
|
||||
@@ -734,10 +904,9 @@ static KSTATUS hba_reset(HBA_MEM *hba) {
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
// Re-enable AHCI mode (reset cleared it)
|
||||
hba->ghc |= GHC_AE;
|
||||
|
||||
// Leave GHC.IE clear - we use polling only
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -810,12 +979,25 @@ static KSTATUS port_comreset(AHCI_DRIVE *drv) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// PORT_IE bits we care about:
|
||||
// DHRS (bit 0) - D2H Register FIS received — normal completion
|
||||
// PSS (bit 1) - PIO Setup FIS received
|
||||
// DSS (bit 2) - DMA Setup FIS received
|
||||
// SDBS (bit 3) - Set Device Bits FIS received
|
||||
// TFES (bit 30) - Task File Error Status — error
|
||||
// HBFS (bit 29) - Host Bus Fatal error
|
||||
// HBDS (bit 28) - Host Bus Data error
|
||||
// IFS (bit 27) - Interface Fatal error
|
||||
#define PORT_IE_NORMAL (0x0000000Fu)
|
||||
#define PORT_IE_ERRORS (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)
|
||||
#define PORT_IE_ALL (PORT_IE_NORMAL | PORT_IE_ERRORS)
|
||||
|
||||
KSTATUS AhciInitialize(const HAL_API *hal) {
|
||||
g_hal = hal;
|
||||
g_hal = hal;
|
||||
g_driveCount = 0;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 1. Locate AHCI controller via PCI class scan
|
||||
// Locate AHCI controller via PCI class scan
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t pciBus, pciDev, pciFn;
|
||||
if (!hal->Pci.FindClass(PCI_AHCI_CLASS, PCI_AHCI_SUBCLASS,
|
||||
@@ -826,54 +1008,60 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
|
||||
DbgPrint("AHCI: controller at PCI %02x:%02x.%x\n", pciBus, pciDev, pciFn);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 2. Enable Bus Master + Memory Space in PCI command register
|
||||
// Enable Bus Master + Memory Space in PCI command register
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t cmd = hal->Pci.ConfigRead32(pciBus, pciDev, pciFn, PCI_CMD_REG);
|
||||
cmd |= (PCI_CMD_BUSMASTER | PCI_CMD_MEMSPACE);
|
||||
hal->Pci.ConfigWrite32(pciBus, pciDev, pciFn, PCI_CMD_REG, cmd);
|
||||
uint32_t cmd = hal->Pci.Read32(pciBus, pciDev, pciFn, PCI_CMD_REG);
|
||||
cmd |= (PCI_CMD_BUSMASTER | PCI_CMD_MEMSPACE); cmd &= ~(1 << 10); cmd |= (1 << 2);
|
||||
hal->Pci.Write32(pciBus, pciDev, pciFn, PCI_CMD_REG, cmd);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 3. Read ABAR (BAR5) and map as uncacheable
|
||||
// The physical address is already identity-mapped via PhysToVirt.
|
||||
// NOTE: real drivers should mark the mapping WC or UC; here we rely
|
||||
// on the kernel not caching MMIO ranges (caller's responsibility).
|
||||
// Read PCI IRQ line
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t bar5 = hal->Pci.ConfigRead32(pciBus, pciDev, pciFn, PCI_BAR5_REG);
|
||||
uint64_t abarPhys = (uint64_t)(bar5 & ~0xFFFu); // strip flags
|
||||
KSTATUS st = ahci_map_mmio(abarPhys); // establish PTEs
|
||||
g_irq = hal->Pci.GetIrqLine(pciBus, pciDev, pciFn);
|
||||
if (g_irq == 0xFF) {
|
||||
DbgPrint("AHCI: no PCI IRQ wired — cannot use interrupt-driven mode\n");
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
DbgPrint("AHCI: PCI IRQ line = %u\n", (unsigned)g_irq);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Read ABAR (BAR5) and map as uncacheable
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t bar5 = hal->Pci.Read32(pciBus, pciDev, pciFn, PCI_BAR5_REG);
|
||||
uint64_t abarPhys = (uint64_t)(bar5 & ~0xFFFu);
|
||||
KSTATUS st = ahci_map_mmio(abarPhys);
|
||||
if (!KSUCCESS(st)) return st;
|
||||
|
||||
HBA_MEM *hba = (HBA_MEM *)hal->PhysToVirt(abarPhys);
|
||||
|
||||
g_hba = (HBA_MEM *)hal->PhysToVirt(abarPhys);
|
||||
DbgPrint("AHCI: ABAR phys=0x%llx version=0x%08x\n",
|
||||
(unsigned long long)abarPhys, hba->vs);
|
||||
(unsigned long long)abarPhys, g_hba->vs);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 4. HBA reset + BIOS handoff
|
||||
// HBA reset + BIOS handoff (also enables GHC.IE)
|
||||
// -----------------------------------------------------------------------
|
||||
st = hba_reset(hba);
|
||||
st = hba_reset(g_hba);
|
||||
if (!KSUCCESS(st)) return st;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 5. Log capability summary
|
||||
// Log capability summary
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t cap = hba->cap;
|
||||
uint32_t nPorts = (cap & 0x1F) + 1; // bits 4:0 + 1
|
||||
uint32_t nSlots = ((cap >> 8) & 0x1F) + 1;
|
||||
bool s64a = !!(cap & (1u << 31));
|
||||
uint32_t cap = g_hba->cap;
|
||||
uint32_t nPorts = (cap & 0x1F) + 1;
|
||||
uint32_t nSlots = ((cap >> 8) & 0x1F) + 1;
|
||||
bool s64a = !!(cap & (1u << 31));
|
||||
DbgPrint("AHCI: cap=0x%08x ports=%u slots=%u 64bit=%d\n",
|
||||
cap, nPorts, nSlots, (int)s64a);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 6. Enumerate implemented ports
|
||||
// Enumerate implemented ports
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t pi = hba->pi;
|
||||
uint32_t pi = g_hba->pi;
|
||||
for (uint32_t portno = 0; portno < 32 && g_driveCount < MAX_AHCI_DRIVES; portno++) {
|
||||
if (!(pi & (1u << portno))) continue;
|
||||
|
||||
HBA_PORT *port = &hba->ports[portno];
|
||||
HBA_PORT *port = &g_hba->ports[portno];
|
||||
|
||||
// Phase 1: check physical link only — sig is garbage until after COMRESET
|
||||
// Phase 1: physical link check
|
||||
if (!port_link_up(port)) {
|
||||
DbgPrint("AHCI: port %u: no link (ssts=0x%08x)\n", portno, port->ssts);
|
||||
continue;
|
||||
@@ -884,31 +1072,48 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
|
||||
int driveIdx = g_driveCount;
|
||||
AHCI_DRIVE *drv = &g_drives[driveIdx];
|
||||
memset(drv, 0, sizeof(*drv));
|
||||
drv->port = port;
|
||||
drv->port = port;
|
||||
drv->portNo = (uint8_t)portno;
|
||||
|
||||
// Phase 2: set up command list + FIS buffer (enables FRE)
|
||||
// Phase 2: allocate command list, FIS buffer, bounce buffer
|
||||
st = port_init_memory(drv);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: port %u: memory init failed (0x%x)\n", portno, st);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 3: COMRESET to flush stale HBA state and force device to
|
||||
// re-send D2H FIS so the SIG register gets a valid value
|
||||
// Phase 3: COMRESET — forces device to re-send D2H FIS for valid SIG
|
||||
st = port_comreset(drv);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: port %u: COMRESET failed\n", portno);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 4: now sig is valid — filter to SATA disks only
|
||||
// Phase 4: filter to SATA disks
|
||||
if (!port_is_sata_disk(port)) {
|
||||
DbgPrint("AHCI: port %u: not a SATA disk (sig=0x%08x), skipping\n",
|
||||
portno, port->sig);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 5: IDENTIFY to get sector count
|
||||
// Phase 5: create per-drive completion event (auto-reset, initially clear)
|
||||
st = KeEventCreate(EventAutoReset, 0, &drv->completionEvent);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: port %u: event create failed (0x%x)\n", portno, st);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 5b: create the IRP queue semaphore and initialise the queue.
|
||||
// The semaphore starts at 0; each submitted IRP increments it by one.
|
||||
SEMAPHORE *irpSem = (SEMAPHORE *)0;
|
||||
st = SemaphoreCreate(0, 0x7FFFFFFF, &irpSem);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: port %u: semaphore create failed (0x%x)\n", portno, st);
|
||||
continue;
|
||||
}
|
||||
IrpQueueInit(&drv->irpQueue, irpSem);
|
||||
|
||||
// Phase 6: IDENTIFY (polled — scheduler not yet running)
|
||||
st = port_identify(drv);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: port %u: IDENTIFY failed (0x%x)\n", portno, st);
|
||||
@@ -920,22 +1125,56 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
|
||||
(unsigned long long)drv->sectorCount,
|
||||
(unsigned long long)(drv->sectorCount / 2048));
|
||||
|
||||
// Phase 7: enable per-port interrupts.
|
||||
// Clear any stale IS first so we don't get a spurious signal immediately.
|
||||
port->is = (uint32_t)-1;
|
||||
port->ie = PORT_IE_ALL;
|
||||
|
||||
// Phase 8: register with disk subsystem using Ctx pointer
|
||||
DISK_DRIVER dd = {
|
||||
.ReadSectors = kReadFns[driveIdx],
|
||||
.WriteSectors = kWriteFns[driveIdx],
|
||||
.Ctx = drv,
|
||||
.ReadSectors = AhciRead,
|
||||
.WriteSectors = AhciWrite,
|
||||
.SubmitIrp = AhciSubmitIrp, // async IRP path
|
||||
.SectorCount = drv->sectorCount,
|
||||
};
|
||||
st = IoRegisterDisk((uint8_t)driveIdx, &dd);
|
||||
if (KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: drive %d registered\n", driveIdx);
|
||||
g_driveCount++; // only increment on full success
|
||||
g_driveCount++;
|
||||
} else {
|
||||
DbgPrint("AHCI: IoRegisterDisk(%d) failed (0x%x)\n", driveIdx, st);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 9: spawn the per-drive IRP worker thread.
|
||||
// The worker owns do_read / do_write exclusively once the scheduler
|
||||
// is live; it is the only thread that may call port_issue_cmd().
|
||||
st = KeCreateKernelThread((uintptr_t)AhciWorker, (uintptr_t)drv,
|
||||
PriorityHigh, &drv->workerThread);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: drive %d: worker thread create failed (0x%x)\n",
|
||||
driveIdx, st);
|
||||
// Non-fatal: sync ReadSectors / WriteSectors still work via the
|
||||
// IoSubmitIrpSync() fallback in hal_disk.c, but async callers
|
||||
// will hang waiting on irp->CompletionEvent forever.
|
||||
}
|
||||
}
|
||||
|
||||
DbgPrint("AHCI: initialised %d drive(s)\n", g_driveCount);
|
||||
return (g_driveCount > 0) ? STATUS_SUCCESS : STATUS_NOT_FOUND;
|
||||
if (g_driveCount == 0) {
|
||||
DbgPrint("AHCI: no drives found\n");
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Register the ISR. All ports on this HBA share one PCI IRQ line.
|
||||
// Do this after all drives are initialised so the ISR sees valid state.
|
||||
// -----------------------------------------------------------------------
|
||||
KeIrqRegister(g_irq, AhciIsr);
|
||||
DbgPrint("AHCI: ISR registered on IRQ %u; initialised %d drive(s)\n",
|
||||
(unsigned)g_irq, g_driveCount);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
KSTATUS DriverEntry(const HAL_API *hal) {
|
||||
|
||||
Reference in new issue
Block a user