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:
maksw committed 2026-05-03 19:35:48 +02:00
1 parent 7f4052f41c
commit 9fa2a7e0db
61 files changed
+1311 -456

No files matched your search

+316 -77
View File
@@ -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) {
+4 -4
View File
@@ -1,14 +1,14 @@
// fat32.c - FAT32 filesystem driver
// Registers itself via IoRegisterFilesystem after finding a FAT32 partition.
// Supports read-only access; write support is not implemented.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <io_driver.h>
#include <hal_api.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ke_debug.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
// ---------------------------------------------------------------------------
// On-disk structures
@@ -584,7 +584,7 @@ KSTATUS DriverEntry(const HAL_API* hal) {
// Pass the new volParams structure
KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams);
DbgPrint("FAT32: [DEBUG] pIoRegisterFilesystem returned: %d\n", status);
DbgPrint("FAT32: [DEBUG] IoRegisterFilesystem returned: %d\n", status);
DbgPrint("FAT32: Init complete.\n");
return STATUS_SUCCESS;
+109 -83
View File
@@ -1,19 +1,18 @@
// ide.c - PIO-mode IDE driver (legacy 28-bit LBA)
#include <io_driver.h>
#include <hal_api.h>
#include <ke_debug.h>
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <io_driver.h>
#include <hal_api.h>
#include <ke_debug.h>
#include <ke_sched.h>
// Primary channel I/O ports.
#define IDE_DATA 0x1F0
#define IDE_ERR 0x1F1
#define IDE_SECCNT 0x1F2
#define IDE_LBA0 0x1F3
#define IDE_LBA1 0x1F4
#define IDE_LBA2 0x1F5
#define IDE_DRIVE 0x1F6 // 0xE0=master 0xF0=slave
#define IDE_DRIVE 0x1F6
#define IDE_STATUS 0x1F7
#define IDE_CMD 0x1F7
#define IDE_ALT 0x3F6
@@ -23,69 +22,112 @@
#define IDE_ERR_F 0x01
#define IDE_DRQ 0x08
#define CMD_READ 0x20
#define CMD_WRITE 0x30
#define CMD_IDENT 0xEC
// 48-bit LBA Commands
#define CMD_READ_EXT 0x24
#define CMD_WRITE_EXT 0x34
#define CMD_IDENT 0xEC
static const HAL_API* gHal;
static DISK_DRIVER gDrv;
static uint64_t gSectorCount = 0;
static bool gSupportsLba48 = false;
// ---------------------------------------------------------
// Polling Upgrades: Yield instead of blocking the CPU
// ---------------------------------------------------------
static void IdeWait(void) {
while (__inbyte(IDE_STATUS) & IDE_BUSY) _mm_pause();
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
KeYield(); // <-- Let other threads run while the disk thinks!
}
}
static KSTATUS IdeWaitData(void) {
// Read the Alternate Status port 4 times to waste 400ns
for (int i = 0; i < 4; i++) {
__inbyte(IDE_ALT);
}
for (int i = 0; i < 4; i++) __inbyte(IDE_ALT);
// Wait until the drive is not busy AND data is requested (DRQ)
while (1) {
while (true) {
uint8_t st = __inbyte(IDE_STATUS);
if (st & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (!(st & IDE_BUSY) && (st & IDE_DRQ)) return STATUS_SUCCESS;
_mm_pause();
KeYield(); // <-- Be a good citizen
}
}
static KSTATUS IdeRead(uint64_t lba, uint32_t count, void* buf) {
// ---------------------------------------------------------
// 48-Bit LBA Read/Write
// ---------------------------------------------------------
static KSTATUS IdeRead(void *ctx, uint64_t lba, uint32_t count, void* buf) {
(void)ctx;
if (lba + count > gSectorCount) return STATUS_INVALID_PARAMETER;
uint16_t* p = (uint16_t *)buf;
// For simplicity, handle one sector at a time in this loop
for (uint32_t i = 0; i < count; i++, lba++) {
IdeWait(); // Wait for drive to be ready for commands
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_READ);
IdeWait();
if (gSupportsLba48) {
// LBA48 requires writing high bytes first, then low bytes
__outbyte(IDE_DRIVE, 0x40); // 0x40 = LBA mode, drive 0
__outbyte(IDE_SECCNT, 0); // Count High
__outbyte(IDE_LBA0, (lba >> 24) & 0xFF); // LBA 4
__outbyte(IDE_LBA1, (lba >> 32) & 0xFF); // LBA 5
__outbyte(IDE_LBA2, (lba >> 40) & 0xFF); // LBA 6
__outbyte(IDE_SECCNT, 1); // Count Low
__outbyte(IDE_LBA0, (uint8_t)lba); // LBA 1
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8)); // LBA 2
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));// LBA 3
__outbyte(IDE_CMD, CMD_READ_EXT);
} else {
// Fallback to 28-bit
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, 0x20); // CMD_READ (28-bit)
}
// Use the delay/DRQ wait
if (IdeWaitData() != STATUS_SUCCESS) return STATUS_DEVICE_ERROR;
for (int w = 0; w < 256; w++) *p++ = __inword(IDE_DATA);
}
return STATUS_SUCCESS;
}
static KSTATUS IdeWrite(uint64_t lba, uint32_t count, const void* buf) {
static KSTATUS IdeWrite(void *ctx, uint64_t lba, uint32_t count, const void* buf) {
(void)ctx;
if (lba + count > gSectorCount) return STATUS_INVALID_PARAMETER;
const uint16_t* p = (const uint16_t *)buf;
for (uint32_t i = 0; i < count; i++, lba++) {
IdeWait();
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_WRITE);
if (gSupportsLba48) {
__outbyte(IDE_DRIVE, 0x40);
__outbyte(IDE_SECCNT, 0);
__outbyte(IDE_LBA0, (lba >> 24) & 0xFF);
__outbyte(IDE_LBA1, (lba >> 32) & 0xFF);
__outbyte(IDE_LBA2, (lba >> 40) & 0xFF);
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_WRITE_EXT);
} else {
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, 0x30); // CMD_WRITE (28-bit)
}
IdeWait();
for (int w = 0; w < 256; w++) __outword(IDE_DATA, *p++);
__outbyte(IDE_ALT, 0); // flush cache
__outbyte(IDE_ALT, 0); // flush cache
if (IdeWaitData() != STATUS_SUCCESS) return STATUS_DEVICE_ERROR;
}
return STATUS_SUCCESS;
@@ -94,83 +136,67 @@ static KSTATUS IdeWrite(uint64_t lba, uint32_t count, const void* buf) {
KSTATUS DriverEntry(const HAL_API* hal) {
DbgPrint("IDE: Init\n");
gHal = hal;
uint8_t status;
int timeout;
// 1. Select Primary Master Drive (0xA0)
// Select Primary Master (Drive 0)
__outbyte(IDE_DRIVE, 0xA0);
// Waste 400ns to let the drive selection settle on the bus
for(int i=0; i<4; i++) __inbyte(IDE_ALT);
// 2. Zero out the LBA and Sector Count registers
__outbyte(IDE_SECCNT, 0);
__outbyte(IDE_LBA0, 0);
__outbyte(IDE_LBA1, 0);
__outbyte(IDE_LBA2, 0);
// 3. Send the IDENTIFY command
DbgPrint("IDE: Sending CMD_IDENT (0xEC)...\n");
__outbyte(IDE_CMD, CMD_IDENT);
// 4. Read status immediately. If it's 0, the drive doesn't exist at all.
status = __inbyte(IDE_STATUS);
// DbgPrint("IDE: Initial status after IDENTIFY: 0x%02X\n", status);
if (status == 0) {
DbgPrint("IDE ERROR: Drive does not exist (Status 0).\n");
if (__inbyte(IDE_STATUS) == 0) {
// QEMU usually falls in here because it uses AHCI by default,
// leaving the legacy IDE ports floating/unconnected.
DbgPrint("IDE ERROR: Drive does not exist on legacy port 0x1F0.\n");
return STATUS_NOT_FOUND;
}
// 5. Wait for BSY (Busy) to clear, WITH A TIMEOUT
timeout = 100000;
// Wait for BSY to clear, yielding to scheduler
int timeout = 10000;
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
if (--timeout == 0) {
DbgPrint("IDE ERROR: Timeout waiting for BSY to clear!\n");
return STATUS_DEVICE_ERROR;
}
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
}
// 6. Check for ATAPI (CD-ROM) or SATA signatures.
// If these registers aren't 0 after BSY clears, it's NOT a standard ATA disk!
uint8_t lba1 = __inbyte(IDE_LBA1);
uint8_t lba2 = __inbyte(IDE_LBA2);
if (lba1 != 0 || lba2 != 0) {
DbgPrint("IDE ERROR: Not an ATA drive! Signatures: LBA1=0x%02X, LBA2=0x%02X\n", lba1, lba2);
// (ATAPI CD-ROMs usually show LBA1=0x14, LBA2=0xEB)
if (__inbyte(IDE_LBA1) != 0 || __inbyte(IDE_LBA2) != 0) {
DbgPrint("IDE ERROR: Not a standard ATA drive (Possibly ATAPI).\n");
return STATUS_NOT_SUPPORTED;
}
// 7. Wait for DRQ (Data Request) or ERR (Error)
timeout = 100000;
while (1) {
status = __inbyte(IDE_STATUS);
if (status & IDE_ERR_F) {
DbgPrint("IDE ERROR: Drive returned an error! Status: 0x%02X\n", status);
return STATUS_DEVICE_ERROR;
}
if (status & IDE_DRQ) {
break; // Data is ready!
}
if (--timeout == 0) {
DbgPrint("IDE ERROR: Timeout waiting for DRQ! Status: 0x%02X\n", status);
return STATUS_DEVICE_ERROR;
}
timeout = 10000;
while (true) {
uint8_t status = __inbyte(IDE_STATUS);
if (status & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (status & IDE_DRQ) break;
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
}
// 8. Success! Read the 256 words (512 bytes) of IDENTIFY data
// DbgPrint("IDE: DRQ set! Reading 256 words...\n");
uint16_t ident[256];
for (int i = 0; i < 256; i++) {
ident[i] = __inword(IDE_DATA);
}
// 9. Extract and print the sector count (Words 60 and 61 for 28-bit LBA)
gSectorCount = ((uint32_t)ident[61] << 16) | ident[60];
DbgPrint("IDE: SUCCESS: Disk found! Sector count: %u\n", (uint32_t)gSectorCount);
// Word 83 Bit 10 determines LBA48 support
gSupportsLba48 = (ident[83] & (1 << 10)) != 0;
if (gSupportsLba48) {
// Words 100-103 contain the 64-bit LBA sector count
gSectorCount = *(uint64_t*)&ident[100];
DbgPrint("IDE: LBA48 Disk found! Sectors: %llu\n", gSectorCount);
} else {
// Fallback to 28-bit
gSectorCount = ((uint32_t)ident[61] << 16) | ident[60];
DbgPrint("IDE: LBA28 Disk found! Sectors: %u\n", (uint32_t)gSectorCount);
}
if (gSectorCount == 0) return STATUS_NOT_FOUND;
// Register driver globally (from the previous fix!)
gDrv.Ctx = NULL;
gDrv.ReadSectors = IdeRead;
gDrv.WriteSectors = IdeWrite;
gDrv.SectorCount = gSectorCount;