add KePanic instead of splattering _disable __halt everywhere, implement a DMA + interrupts path to the IDE driver, move the dynamic arrays from kmain.c to ke_utils.h, remove weird unicode chars

This commit is contained in:
maksw committed 2026-05-03 22:45:31 +02:00
1 parent 83d48e09bb
commit 4b57738fe3
15 files changed
+673 -219

No files matched your search

Binary file not shown.
+19 -19
View File
@@ -20,7 +20,7 @@
// 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):
// 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).
@@ -381,7 +381,7 @@ typedef struct {
volatile uint32_t lastIs;
// -----------------------------------------------------------------------
// IRP queue — async dispatch
// IRP queue - async dispatch
// -----------------------------------------------------------------------
// AhciWorker blocks here waiting for work. AhciSubmitIrp enqueues an IRP
// and signals the semaphore to wake the worker.
@@ -413,7 +413,7 @@ static KSTATUS ahci_map_mmio(uint64_t physBase) {
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
// 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) {
@@ -535,7 +535,7 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
}
// ---------------------------------------------------------------------------
// Issue a command — polling fallback (used during early init / IDENTIFY only,
// 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.
// ---------------------------------------------------------------------------
@@ -580,7 +580,7 @@ static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) {
}
// ---------------------------------------------------------------------------
// ISR — called from KeIrqDispatch on the AHCI PCI IRQ line.
// 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.
// ---------------------------------------------------------------------------
@@ -597,10 +597,10 @@ static void AhciIsr(void) {
uint32_t portBit = (1u << drv->portNo);
if (!(pending & portBit)) continue;
// Capture and clear port IS before signalling — the waiter reads
// 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->port->is = portIs; // W1C - write back to clear
drv->lastIs = portIs;
// Clear the HBA-level bit.
@@ -612,7 +612,7 @@ static void AhciIsr(void) {
}
// ---------------------------------------------------------------------------
// Issue a command — interrupt-driven (runtime path).
// 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.
// ---------------------------------------------------------------------------
@@ -638,7 +638,7 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
port->ci = (1u << 0);
KeWaitForEvent(drv->completionEvent);
// The ISR has cleared IS — check for errors in the snapshot.
// 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",
@@ -808,7 +808,7 @@ static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba,
}
// ---------------------------------------------------------------------------
// HAL_DISK callbacks — context pointer replaces trampolines.
// 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.
// ---------------------------------------------------------------------------
@@ -825,7 +825,7 @@ static KSTATUS AhciWrite(void *ctx, uint64_t lba, uint32_t count, const void *bu
// Async IRP dispatch
// ---------------------------------------------------------------------------
// AhciSubmitIrp — called by IoSubmitIrp().
// 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.
@@ -836,7 +836,7 @@ static KSTATUS AhciSubmitIrp(void *ctx, IRP *irp) {
return STATUS_PENDING;
}
// AhciWorker — kernel thread, one per drive.
// 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
@@ -848,7 +848,7 @@ static KSTATUS AhciSubmitIrp(void *ctx, IRP *irp) {
// 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.
// 5. Return immediately - the ISR drives all completions.
static void AhciWorker(uintptr_t arg) {
AHCI_DRIVE *drv = (AHCI_DRIVE *)arg;
@@ -857,7 +857,7 @@ static void AhciWorker(uintptr_t arg) {
KeWaitSemaphore(drv->irpQueue.Semaphore);
IRP *irp = IrpQueueDequeue(&drv->irpQueue);
if (!irp) continue; // spurious wakeup — should not happen
if (!irp) KePanic("AhciWorker: semaphore fired but queue is empty - IRP accounting bug\n");
bool write = !!(irp->Flags & IRP_FLAG_WRITE);
KSTATUS st;
@@ -980,11 +980,11 @@ static KSTATUS port_comreset(AHCI_DRIVE *drv) {
}
// PORT_IE bits we care about:
// DHRS (bit 0) - D2H Register FIS received — normal completion
// 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
// 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
@@ -1019,7 +1019,7 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
// -----------------------------------------------------------------------
g_irq = hal->Pci.GetIrqLine(pciBus, pciDev, pciFn);
if (g_irq == 0xFF) {
DbgPrint("AHCI: no PCI IRQ wired — cannot use interrupt-driven mode\n");
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);
@@ -1082,7 +1082,7 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
continue;
}
// Phase 3: COMRESET — forces device to re-send D2H FIS for valid SIG
// 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);
@@ -1113,7 +1113,7 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
}
IrpQueueInit(&drv->irpQueue, irpSem);
// Phase 6: IDENTIFY (polled — scheduler not yet running)
// 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);
+486 -99
View File
@@ -1,3 +1,30 @@
// ide.c — IDE/ATA driver: PIO (sync fallback) + Bus Master DMA (async)
//
// Architecture
// ------------
// The DMA path mirrors AHCI exactly:
// SubmitIrp → enqueues IRP + signals gIrpSemaphore → returns STATUS_PENDING
// IdeWorker → dequeues IRP, calls IdeDmaRw(), fires completion hooks
// IdeIsr → clears BM status, signals gDmaEvent to unblock the worker
//
// Graceful degradation: if Bus Master init fails (no PCI IDE controller,
// BAR4 not assigned, PRDT allocation above 4 GB, etc.) SubmitIrp is left
// NULL. HalDiskDispatch sees NULL and falls back to the synchronous PIO
// path with no changes at the call sites.
//
// PRDT layout
// -----------
// One page allocated via MmAllocateContiguous. We build up to PRDT_MAX_ENTRIES
// entries by walking the transfer buffer in page-sized chunks and looking up
// each page's physical address via MmGetPhysicalAddress. Adjacent physical
// pages are coalesced into a single PRD entry where possible.
//
// LBA48 vs LBA28
// --------------
// DMA commands exist in both modes: CMD_READ/WRITE_DMA_EXT (0x25/0x35) for
// LBA48 and CMD_READ/WRITE_DMA (0xC8/0xCA) for LBA28. The existing
// gSupportsLba48 flag picks the right variant.
#include <stdint.h>
#include <string.h>
#include <intrin.h>
@@ -5,115 +32,173 @@
#include <hal_api.h>
#include <ke_debug.h>
#include <ke_sched.h>
#include <ke_irp.h>
#include <ke_event.h>
#include <ke_semaphore.h>
#include <ke_thread.h>
#include <ke_irq.h>
#include <mm_phys.h>
#include <mm_virt.h>
// ---------------------------------------------------------------------------
// IDE register map (primary channel, base 0x1F0)
// ---------------------------------------------------------------------------
#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
#define IDE_DRIVE 0x1F6
#define IDE_STATUS 0x1F7
#define IDE_CMD 0x1F7
#define IDE_ALT 0x3F6
#define IDE_BUSY 0x80
#define IDE_READY 0x40
#define IDE_ERR_F 0x01
#define IDE_DRQ 0x08
#define IDE_BUSY 0x80
#define IDE_READY 0x40
#define IDE_ERR_F 0x01
#define IDE_DRQ 0x08
// 48-bit LBA Commands
#define CMD_READ_EXT 0x24
#define CMD_WRITE_EXT 0x34
#define CMD_IDENT 0xEC
// ATA commands
#define CMD_READ_EXT 0x24 // PIO 48-bit read
#define CMD_WRITE_EXT 0x34 // PIO 48-bit write
#define CMD_READ_DMA_EXT 0x25 // DMA 48-bit read
#define CMD_WRITE_DMA_EXT 0x35 // DMA 48-bit write
#define CMD_READ_DMA 0xC8 // DMA 28-bit read
#define CMD_WRITE_DMA 0xCA // DMA 28-bit write
#define CMD_IDENT 0xEC
static const HAL_API* gHal;
static DISK_DRIVER gDrv;
static uint64_t gSectorCount = 0;
static bool gSupportsLba48 = false;
// IDE primary IRQ
#define IDE_IRQ_PRIMARY 14
// ---------------------------------------------------------
// Polling Upgrades: Yield instead of blocking the CPU
// ---------------------------------------------------------
// ---------------------------------------------------------------------------
// Bus Master IDE register offsets (relative to gBmBase, primary channel)
// ---------------------------------------------------------------------------
#define BM_CMD_REG 0x00 // command
#define BM_STATUS_REG 0x02 // status
#define BM_PRDT_REG 0x04 // PRDT physical address (32-bit)
// BM_CMD_REG bits
#define BM_CMD_START 0x01 // start DMA transfer
#define BM_CMD_READ 0x08 // 1 = disk→memory (read), 0 = memory→disk (write)
// BM_STATUS_REG bits
#define BM_STATUS_ACTIVE 0x01 // DMA active
#define BM_STATUS_ERROR 0x02 // DMA error (write 1 to clear)
#define BM_STATUS_IRQ 0x04 // interrupt pending (write 1 to clear)
// ---------------------------------------------------------------------------
// Physical Region Descriptor (PRDT entry, 8 bytes)
// The PRDT itself must not cross a 64 KB boundary and must be 4-byte aligned.
// We allocate a full page so both constraints are trivially satisfied.
// ---------------------------------------------------------------------------
#pragma pack(push, 1)
typedef struct {
uint32_t physAddr; // physical base of this memory region
uint16_t byteCount; // byte count; 0 means 64 KB
uint16_t flags; // bit 15: End Of Table
} PRD;
#pragma pack(pop)
#define PRD_EOT 0x8000u
// 512 entries × 8 bytes = 4096 bytes = exactly one page; supports 512×64 KB = 32 MB
#define PRDT_MAX_ENTRIES 512
// ---------------------------------------------------------------------------
// Module state
// ---------------------------------------------------------------------------
static const HAL_API *gHal;
static DISK_DRIVER gDrv;
static uint64_t gSectorCount = 0;
static bool gSupportsLba48 = false;
// DMA hardware state
static uint16_t gBmBase = 0; // Bus Master I/O base port (BAR4 & ~3)
static PRD *gPrdtVirt = NULL; // kernel VA of PRDT page
static uint32_t gPrdtPhys = 0; // 32-bit physical address of PRDT
// Async worker synchronisation
static IRP_QUEUE gIrpQueue;
static SEMAPHORE *gIrpSemaphore = NULL;
static EVENT *gDmaEvent = NULL; // auto-reset; ISR signals after each xfer
// ---------------------------------------------------------------------------
// Bus Master I/O helpers
// ---------------------------------------------------------------------------
static inline uint8_t BmRead8 (uint8_t reg) { return __inbyte (gBmBase + reg); }
static inline void BmWrite8 (uint8_t reg, uint8_t v) { __outbyte (gBmBase + reg, v); }
static inline void BmWrite32(uint8_t reg, uint32_t v) { __outdword(gBmBase + reg, v); }
// ---------------------------------------------------------------------------
// PIO polling helpers (unchanged)
// ---------------------------------------------------------------------------
static void IdeWait(void) {
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
KeYield(); // <-- Let other threads run while the disk thinks!
}
while (__inbyte(IDE_STATUS) & IDE_BUSY)
KeYield();
}
static KSTATUS IdeWaitData(void) {
for (int i = 0; i < 4; i++) __inbyte(IDE_ALT);
for (int i = 0; i < 4; i++) __inbyte(IDE_ALT); // 400 ns delay
while (true) {
uint8_t st = __inbyte(IDE_STATUS);
if (st & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (st & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (!(st & IDE_BUSY) && (st & IDE_DRQ)) return STATUS_SUCCESS;
KeYield(); // <-- Be a good citizen
KeYield();
}
}
// ---------------------------------------------------------
// 48-Bit LBA Read/Write
// ---------------------------------------------------------
static KSTATUS IdeRead(void *ctx, uint64_t lba, uint32_t count, void* buf) {
// ---------------------------------------------------------------------------
// PIO read/write — sync fallback when DMA is unavailable
// ---------------------------------------------------------------------------
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();
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)
}
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(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;
uint16_t *p = (uint16_t *)buf;
for (uint32_t i = 0; i < count; i++, lba++) {
IdeWait();
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_READ_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, 0x20);
}
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(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();
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));
@@ -121,85 +206,387 @@ static KSTATUS IdeWrite(void *ctx, uint64_t lba, uint32_t count, const void* buf
__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)
__outbyte(IDE_CMD, 0x30);
}
IdeWait();
for (int w = 0; w < 256; w++) __outword(IDE_DATA, *p++);
__outbyte(IDE_ALT, 0); // flush cache
__outbyte(IDE_ALT, 0); // flush write cache
if (IdeWaitData() != STATUS_SUCCESS) return STATUS_DEVICE_ERROR;
}
return STATUS_SUCCESS;
}
KSTATUS DriverEntry(const HAL_API* hal) {
// ---------------------------------------------------------------------------
// BuildPrdt — fill gPrdtVirt[] for a transfer.
//
// Walks the virtual buffer in page-aligned chunks, looks up each chunk's
// physical address via MmGetPhysicalAddress, and coalesces adjacent physical
// pages into a single PRD entry where possible. This is correct regardless of
// whether the pool gave us physically contiguous pages.
//
// Constraints enforced:
// - Each PRD entry covers at most one 4 KB physical page (never crosses
// a page boundary), so the physical address is valid for the whole chunk.
// - Total byte count per entry is ≤ 64 KB (the PRD limit).
// - Physical address must fit in 32 bits (BM IDE hardware limit).
//
// Returns the number of PRD entries written, or 0 on error.
// ---------------------------------------------------------------------------
static uint32_t BuildPrdt(void *virtBuf, uint32_t byteLen) {
uint32_t nPrd = 0;
uintptr_t va = (uintptr_t)virtBuf;
uint32_t remain = byteLen;
while (remain > 0) {
if (nPrd >= PRDT_MAX_ENTRIES) {
DbgPrint("IDE DMA: transfer too large for PRDT (%u entries)\n", nPrd);
return 0;
}
// Bytes from va to the end of its 4 KB page (never straddles a page).
uint32_t pageOff = (uint32_t)(va & 0xFFF);
uint32_t inPage = 0x1000 - pageOff;
uint32_t chunk = (remain < inPage) ? remain : inPage;
PHYSICAL_ADDRESS phys = MmGetPhysicalAddress(va);
if (phys >> 32) {
// Physical address above 4 GB — BM IDE's 32-bit PRDT can't reach it.
DbgPrint("IDE DMA: buffer page at VA 0x%llx has phys 0x%llx > 4 GB\n",
(uint64_t)va, (uint64_t)phys);
return 0;
}
uint32_t phys32 = (uint32_t)phys;
// Try to coalesce with the previous PRD entry if the physical regions
// are adjacent and the combined size stays within the 64 KB limit.
if (nPrd > 0) {
PRD *prev = &gPrdtVirt[nPrd - 1];
uint32_t prevSz = prev->byteCount ? prev->byteCount : 0x10000u;
uint32_t prevEnd = prev->physAddr + prevSz;
uint32_t combined = prevSz + chunk;
if (prevEnd == phys32 && combined <= 0x10000u) {
prev->byteCount = (uint16_t)(combined == 0x10000u ? 0u : combined);
va += chunk;
remain -= chunk;
continue;
}
}
gPrdtVirt[nPrd].physAddr = phys32;
gPrdtVirt[nPrd].byteCount = (uint16_t)(chunk == 0x10000u ? 0u : chunk);
gPrdtVirt[nPrd].flags = 0;
nPrd++;
va += chunk;
remain -= chunk;
}
if (nPrd > 0)
gPrdtVirt[nPrd - 1].flags |= PRD_EOT;
return nPrd;
}
// ---------------------------------------------------------------------------
// IDE ISR (IRQ 14 — primary channel)
//
// Must be fast — runs with interrupts disabled. We only clear the hardware
// interrupt bits here and signal gDmaEvent to wake the worker thread.
// The worker reads the final status and fires IRP completion hooks.
// ---------------------------------------------------------------------------
static void IdeIsr(void) {
uint8_t bmSt = BmRead8(BM_STATUS_REG);
if (!(bmSt & BM_STATUS_IRQ))
return; // not our interrupt (spurious or secondary channel)
// Stop the Bus Master engine.
BmWrite8(BM_CMD_REG, 0x00);
// Clear interrupt and error sticky bits (write-1-to-clear).
BmWrite8(BM_STATUS_REG, BM_STATUS_IRQ | BM_STATUS_ERROR);
// Read IDE status and alt-status to fully acknowledge the device interrupt.
(void)__inbyte(IDE_ALT);
(void)__inbyte(IDE_STATUS);
// Wake the worker thread; it will read the final BM status and complete
// the IRP. gDmaEvent is auto-reset so exactly one waiter is released.
KeSignalEvent(gDmaEvent);
}
// ---------------------------------------------------------------------------
// IdeDmaRw — program the hardware and block until the ISR completes it.
//
// Called exclusively from IdeWorker. Has full control of the IDE/BM
// registers; no other thread touches them while the worker is running.
// Falls back to PIO inline if BuildPrdt fails (address space issue).
// ---------------------------------------------------------------------------
static KSTATUS IdeDmaRw(uint64_t lba, uint32_t count, void *buf, bool write) {
uint32_t byteLen = count * 512u;
uint32_t nPrd = BuildPrdt(buf, byteLen);
if (nPrd == 0) {
// Graceful inline PIO fallback for awkward buffers.
DbgPrint("IDE DMA: BuildPrdt failed — falling back to PIO\n");
return write ? IdeWrite(NULL, lba, count, buf)
: IdeRead (NULL, lba, count, buf);
}
// ---- Program Bus Master -----------------------------------------------
// Load PRDT physical address.
BmWrite32(BM_PRDT_REG, gPrdtPhys);
// Clear any leftover interrupt/error bits from the previous transfer.
BmWrite8(BM_STATUS_REG, BM_STATUS_IRQ | BM_STATUS_ERROR);
// Set direction: BM_CMD_READ = disk→memory; 0 = memory→disk.
// Do NOT set BM_CMD_START yet — spec says program ATA regs first.
BmWrite8(BM_CMD_REG, write ? 0x00 : BM_CMD_READ);
// ---- Program ATA registers -------------------------------------------
IdeWait();
if (gSupportsLba48) {
__outbyte(IDE_DRIVE, 0x40);
__outbyte(IDE_SECCNT, (uint8_t)(count >> 8)); // sector count high byte
__outbyte(IDE_LBA0, (uint8_t)(lba >> 24));
__outbyte(IDE_LBA1, (uint8_t)(lba >> 32));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 40));
__outbyte(IDE_SECCNT, (uint8_t)count); // sector count low byte
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, write ? CMD_WRITE_DMA_EXT : CMD_READ_DMA_EXT);
} else {
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, (uint8_t)count);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, write ? CMD_WRITE_DMA : CMD_READ_DMA);
}
// ---- Start DMA --------------------------------------------------------
// Set Start bit. Direction bit is already set from above.
BmWrite8(BM_CMD_REG, (write ? 0x00 : BM_CMD_READ) | BM_CMD_START);
// Block the worker thread until the ISR fires.
KeWaitForEvent(gDmaEvent);
// ---- Read result -------------------------------------------------------
// ISR already stopped the BM engine and cleared BM_STATUS_IRQ.
// We still need to check BM_STATUS_ERROR and IDE_ERR_F.
uint8_t bmSt = BmRead8(BM_STATUS_REG);
uint8_t ideSt = __inbyte(IDE_STATUS);
if (bmSt & BM_STATUS_ERROR) {
DbgPrint("IDE DMA: BM error (bmStatus=0x%02x, ideStatus=0x%02x, ideErr=0x%02x)\n",
bmSt, ideSt, __inbyte(IDE_ERR));
return STATUS_DEVICE_ERROR;
}
if (ideSt & IDE_ERR_F) {
DbgPrint("IDE DMA: ATA error (ideStatus=0x%02x, ideErr=0x%02x)\n",
ideSt, __inbyte(IDE_ERR));
return STATUS_DEVICE_ERROR;
}
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IdeWorker — kernel thread, one per IDE controller.
//
// Blocks on gIrpSemaphore. Each IrpQueueEnqueue increments it by one, so
// the worker wakes once per submitted IRP. Mirrors AhciWorker exactly.
// ---------------------------------------------------------------------------
static void IdeWorker(uintptr_t arg) {
(void)arg;
while (true) {
KeWaitSemaphore(gIrpSemaphore);
IRP *irp = IrpQueueDequeue(&gIrpQueue);
if (!irp)
KePanic("IdeWorker: semaphore fired but IRP queue is empty — accounting bug\n");
if (irp->Lba + irp->SectorCount > gSectorCount) {
irp->Status = STATUS_INVALID_PARAMETER;
} else {
bool write = !!(irp->Flags & IRP_FLAG_WRITE);
irp->Status = IdeDmaRw(irp->Lba, irp->SectorCount, irp->Buffer, write);
}
if (irp->CompletionEvent) KeSignalEvent(irp->CompletionEvent);
if (irp->CompletionFn) irp->CompletionFn(irp, irp->CompletionCtx);
}
}
// ---------------------------------------------------------------------------
// IdeSubmitIrp — async entry point wired into DISK_DRIVER.SubmitIrp.
// Enqueues the IRP and immediately returns STATUS_PENDING.
// ---------------------------------------------------------------------------
static KSTATUS IdeSubmitIrp(void *ctx, IRP *irp) {
(void)ctx;
IrpQueueEnqueue(&gIrpQueue, irp);
return STATUS_PENDING;
}
// ---------------------------------------------------------------------------
// IdeDmaInit — locate the PCI IDE controller, grab BAR4, allocate the PRDT,
// create sync objects, and register IRQ 14.
//
// Returns true on success. On any failure, logs a message and returns false;
// DriverEntry then leaves DISK_DRIVER.SubmitIrp = NULL so the dispatch layer
// falls back to the synchronous PIO path automatically.
// ---------------------------------------------------------------------------
static bool IdeDmaInit(void) {
// ---- Find PCI IDE controller (class 0x01, subclass 0x01) --------------
uint8_t bus, dev, fn;
if (!gHal->Pci.FindClass(0x01, 0x01, &bus, &dev, &fn)) {
DbgPrint("IDE DMA: No PCI IDE controller found — using PIO\n");
return false;
}
// ---- Read BAR4 (Bus Master base, I/O space) ---------------------------
// PCI config offset 0x20. Bit 0 must be 1 for I/O BAR.
uint32_t bar4 = gHal->Pci.Read32(bus, dev, fn, 0x20);
if (!(bar4 & 0x1)) {
DbgPrint("IDE DMA: BAR4 is a memory BAR (0x%08x) — unexpected, using PIO\n", bar4);
return false;
}
uint16_t bmBase = (uint16_t)(bar4 & 0xFFFC);
if (bmBase == 0) {
DbgPrint("IDE DMA: BAR4 is zero (firmware didn't assign it) — using PIO\n");
return false;
}
DbgPrint("IDE DMA: Bus Master base = 0x%04X (bus %u, dev %u, fn %u)\n",
bmBase, bus, dev, fn);
// ---- Enable Bus Mastering in PCI Command register (bit 2) ------------
uint32_t pciCmd = gHal->Pci.Read32(bus, dev, fn, 0x04);
gHal->Pci.Write32(bus, dev, fn, 0x04, pciCmd | (1u << 2));
// ---- Allocate PRDT page (must be 32-bit physical, 4-byte aligned) ----
PHYSICAL_ADDRESS prdtPhys = MmAllocateContiguous(1);
if (!prdtPhys) {
DbgPrint("IDE DMA: Failed to allocate PRDT page\n");
return false;
}
if (prdtPhys >> 32) {
// MmAllocateContiguous returned memory above 4 GB — BM IDE can't
// address it. This shouldn't happen on normal x86 systems with early
// boot allocations but we check defensively.
DbgPrint("IDE DMA: PRDT page is above 4 GB (0x%llx) — using PIO\n",
(uint64_t)prdtPhys);
return false;
}
void *prdtVirt = gHal->PhysToVirt(prdtPhys);
DbgPrint("IDE DMA: PRDT at phys 0x%08x, virt %p\n", (uint32_t)prdtPhys, prdtVirt);
// ---- Create synchronisation objects ------------------------------------
SEMAPHORE *sem = NULL;
if (!KSUCCESS(KeSemaphoreCreate(0, 0x7FFFFFFF, &sem))) {
DbgPrint("IDE DMA: Failed to create IRP semaphore\n");
return false;
}
EVENT *dmaEv = NULL;
if (!KSUCCESS(KeEventCreate(EventAutoReset, 0, &dmaEv))) {
DbgPrint("IDE DMA: Failed to create DMA completion event\n");
return false;
}
// ---- Commit module state -----------------------------------------------
gBmBase = bmBase;
gPrdtPhys = (uint32_t)prdtPhys;
gPrdtVirt = (PRD *)prdtVirt;
gIrpSemaphore = sem;
gDmaEvent = dmaEv;
IrpQueueInit(&gIrpQueue, sem);
// ---- Register IRQ 14 --------------------------------------------------
KeIrqRegister(IDE_IRQ_PRIMARY, IdeIsr);
DbgPrint("IDE DMA: IRQ %u registered\n", IDE_IRQ_PRIMARY);
// ---- Spawn worker thread -----------------------------------------------
KSTATUS st = KeCreateKernelThread((uintptr_t)IdeWorker, 0,
PriorityHigh, (THREAD **)NULL);
if (!KSUCCESS(st)) {
DbgPrint("IDE DMA: Worker thread creation failed (0x%x)\n", st);
// Not fatal for init — the PRDT/IRQ are live but async callers will
// never get completions. Better to surface the failure clearly.
return false;
}
DbgPrint("IDE DMA: Init complete\n");
return true;
}
// ---------------------------------------------------------------------------
// DriverEntry
// ---------------------------------------------------------------------------
KSTATUS DriverEntry(const HAL_API *hal) {
DbgPrint("IDE: Init\n");
gHal = hal;
// Select Primary Master (Drive 0)
// ---- IDENTIFY drive (same as before) ----------------------------------
__outbyte(IDE_DRIVE, 0xA0);
for(int i=0; i<4; i++) __inbyte(IDE_ALT);
for (int i = 0; i < 4; i++) __inbyte(IDE_ALT);
__outbyte(IDE_SECCNT, 0);
__outbyte(IDE_LBA0, 0);
__outbyte(IDE_LBA1, 0);
__outbyte(IDE_LBA2, 0);
__outbyte(IDE_CMD, CMD_IDENT);
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");
DbgPrint("IDE: No drive on primary channel\n");
return STATUS_NOT_FOUND;
}
// Wait for BSY to clear, yielding to scheduler
int timeout = 10000;
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
KeYield();
}
if (__inbyte(IDE_LBA1) != 0 || __inbyte(IDE_LBA2) != 0) {
DbgPrint("IDE ERROR: Not a standard ATA drive (Possibly ATAPI).\n");
DbgPrint("IDE: Not a standard ATA drive (ATAPI?)\n");
return STATUS_NOT_SUPPORTED;
}
timeout = 10000;
while (true) {
uint8_t status = __inbyte(IDE_STATUS);
if (status & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (status & IDE_DRQ) break;
uint8_t st = __inbyte(IDE_STATUS);
if (st & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (st & IDE_DRQ) break;
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
}
uint16_t ident[256];
for (int i = 0; i < 256; i++) {
ident[i] = __inword(IDE_DATA);
}
for (int i = 0; i < 256; i++) ident[i] = __inword(IDE_DATA);
// 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);
gSectorCount = *(uint64_t *)&ident[100];
DbgPrint("IDE: LBA48, %llu sectors\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);
DbgPrint("IDE: LBA28, %u sectors\n", (uint32_t)gSectorCount);
}
if (gSectorCount == 0) return STATUS_NOT_FOUND;
// ---- Try to initialise Bus Master DMA ---------------------------------
// On success, wire in IdeSubmitIrp. On failure, leave it NULL so
// HalDiskDispatch falls back to the synchronous PIO callbacks.
bool dmaOk = IdeDmaInit();
gDrv.Ctx = NULL;
gDrv.ReadSectors = IdeRead;
gDrv.WriteSectors = IdeWrite;
gDrv.SubmitIrp = dmaOk ? IdeSubmitIrp : NULL;
gDrv.SectorCount = gSectorCount;
DbgPrint("IDE: Registered (%s)\n", dmaOk ? "DMA" : "PIO fallback");
return IoRegisterDisk(0, &gDrv);
}
+6 -6
View File
@@ -7,7 +7,7 @@
//
// HalDiskDispatch is the one bridge: it calls the driver's SubmitIrp (async)
// or runs the sync callbacks inline and writes irp->Status. It does not
// touch irp->CompletionEvent — that is ke_irp.c's responsibility.
// touch irp->CompletionEvent - that is ke_irp.c's responsibility.
#include <stdint.h>
#include <stdbool.h>
@@ -29,18 +29,18 @@ __declspec(dllexport) KSTATUS IoRegisterDisk(uint8_t index, const DISK_DRIVER *d
}
// -------------------------------------------------------------------------
// HalDiskDispatch — called exclusively by ke_irp.c::IoSubmitIrp()
// HalDiskDispatch - called exclusively by ke_irp.c::IoSubmitIrp()
// -------------------------------------------------------------------------
// Separating dispatch here keeps hal_disk.c free of event primitives while
// still allowing ke_irp.c (kernel layer) to fire completion hooks after we
// return.
//
// Returns STATUS_PENDING — driver accepted the IRP asynchronously.
// Returns STATUS_PENDING - driver accepted the IRP asynchronously.
// ke_irp.c must NOT touch irp->CompletionEvent
// yet; the driver will fire it.
// Returns STATUS_SUCCESS — sync fallback ran; irp->Status is already
// Returns STATUS_SUCCESS - sync fallback ran; irp->Status is already
// written. ke_irp.c should fire completion hooks.
// Returns STATUS_NOT_FOUND — drive not registered.
// Returns STATUS_NOT_FOUND - drive not registered.
__declspec(dllexport) KSTATUS HalDiskDispatch(uint8_t drive, IRP *irp) {
if (drive >= MAX_DRIVES || !gPresent[drive]) return STATUS_NOT_FOUND;
@@ -60,7 +60,7 @@ __declspec(dllexport) KSTATUS HalDiskDispatch(uint8_t drive, IRP *irp) {
}
// -------------------------------------------------------------------------
// Legacy HAL_DISK vtable — direct sync calls, no IRP involvement.
// Legacy HAL_DISK vtable - direct sync calls, no IRP involvement.
// Existing callers (FAT32, shell) use this path unchanged.
// -------------------------------------------------------------------------
+1 -1
View File
@@ -13,7 +13,7 @@
typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API* hal);
typedef void (*DRIVER_UNLOAD_FN)(void);
// Forward declaration only — avoids dragging ke_event.h into the HAL layer.
// Forward declaration only - avoids dragging ke_event.h into the HAL layer.
struct IRP;
typedef struct DISK_DRIVER {
+26 -4
View File
@@ -14,6 +14,12 @@ void kernel_putchar(char c) {
PrintChar(c, 0x00FF00, 0x000000);
}
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v) {
gDebugEnabled = v;
PrintChar = PrintPtr;
printf_set_putchar(kernel_putchar);
}
__declspec(dllexport) void DbgPrint(const char* fmt, ...) {
if (!gDebugEnabled) return;
@@ -26,8 +32,24 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) {
KeReleaseSpinlock(&sl, flags);
}
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v) {
gDebugEnabled = v;
PrintChar = PrintPtr;
printf_set_putchar(kernel_putchar);
// KePanic - Forces output even if debug was compiled out, then the macro halts.
__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) {
// We are done, go away
_disable();
// Print a bright-red banner so the framebuffer is unmistakable.
// We call PrintChar directly so we can set a red foreground.
// (DbgPrint uses green; we want this visually distinct.)
static const char banner[] = "\n*** KERNEL PANIC ***\n";
for (const char *p = banner; *p; p++)
PrintChar(*p, 0xFF2222, 0x000000);
// Now print the caller's message through the normal vprintf path.
va_list va;
va_start(va, fmt);
vprintf(fmt, va);
va_end(va);
// Halt it!
while (true) __halt();
}
+2 -1
View File
@@ -5,5 +5,6 @@
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v);
__declspec(dllexport) void DbgPrint(const char* fmt, ...);
__declspec(dllexport) void KePanic(const char *fmt, ...);
#endif // KE_DEBUG_H
#endif // KE_DEBUG_H
+2 -2
View File
@@ -39,7 +39,7 @@ __declspec(dllexport) void IrpQueueEnqueue(IRP_QUEUE *q, IRP *irp) {
else
ListInsertTail(&q->Head, &irp->Link);
KeReleaseSpinlock(&q->Lock, flags);
// Signal outside the spinlock — safe, and avoids priority-inversion risk.
// Signal outside the spinlock - safe, and avoids priority-inversion risk.
KeSignalSemaphore((SEMAPHORE *)q->Semaphore);
}
@@ -68,7 +68,7 @@ __declspec(dllexport) KSTATUS IoSubmitIrp(IRP *irp) {
if (st == STATUS_SUCCESS) {
// Sync fallback ran; irp->Status is already written.
// Fire completion hooks now — we are in kernel context so this is safe.
// Fire completion hooks now - we are in kernel context so this is safe.
if (irp->CompletionEvent)
KeSignalEvent((EVENT *)irp->CompletionEvent);
if (irp->CompletionFn)
+4 -4
View File
@@ -2,7 +2,7 @@
// ke_irp.h - I/O Request Packet
//
// This header intentionally includes only ke_list.h, ke_spinlock.h, and
// ke_status.h — all of which are pure-inline / macro headers with no
// ke_status.h - all of which are pure-inline / macro headers with no
// external symbol references. ke_event.h and ke_semaphore.h are forward-
// declared here so the HAL layer (hal_disk.c, io_driver.h) can include this
// header without dragging in kernel sync-object link dependencies.
@@ -16,7 +16,7 @@
#include <ke_spinlock.h>
#include <ke_status.h>
// Forward declarations — full types live in ke_event.h / ke_semaphore.h.
// Forward declarations - full types live in ke_event.h / ke_semaphore.h.
// We only ever hold pointers in IRP and IRP_QUEUE, so a forward decl suffices.
struct EVENT;
struct SEMAPHORE;
@@ -42,7 +42,7 @@ typedef void (*IRP_COMPLETION_FN)(IRP *irp, void *ctx);
// -------------------------------------------------------------------------
typedef struct IRP {
// Embedded list node — driver-internal; do not touch after submit.
// Embedded list node - driver-internal; do not touch after submit.
LIST_ENTRY Link;
// ---- Request parameters (caller fills before IoSubmitIrp) ------------
@@ -85,7 +85,7 @@ __declspec(dllexport) void IrpQueueEnqueue (IRP_QUEUE *q, IRP *irp);
__declspec(dllexport) IRP *IrpQueueDequeue (IRP_QUEUE *q);
// -------------------------------------------------------------------------
// Public dispatch API — implemented in kernel/ke_irp.c
// Public dispatch API - implemented in kernel/ke_irp.c
// -------------------------------------------------------------------------
// Submit asynchronously. Returns STATUS_PENDING on success.
+1 -1
View File
@@ -1,4 +1,4 @@
// ke_irq.c — IRQ dispatch, kernel-side
// ke_irq.c - IRQ dispatch, kernel-side
#include <ke_irq.h>
#include <hal_api.h>
+1 -1
View File
@@ -174,7 +174,7 @@ __declspec(dllexport) void KeYield(void) {
// Only re-queue the outgoing thread if it is still in the running state.
// If KeBlockThread set State = ThreadWaiting, SchedulerLink was already
// inserted into the sync object's wait list — inserting it here too would
// inserted into the sync object's wait list - inserting it here too would
// corrupt both lists simultaneously (same intrusive node in two lists).
// If KeExitThread set State = ThreadDead, the thread must not be re-queued.
if (cur != gIdleThread && cur->State == ThreadRunning) {
+76
View File
@@ -0,0 +1,76 @@
// ke_util.h - kernel utility macros (dynamic arrays, etc.)
#pragma once
#include <string.h>
#include <mm_virt.h>
// ---------------------------------------------------------------------------
// Dynamic array (stretchy-buffer style)
//
// T *buf = NULL;
// arr_push(buf, value); // appends; grows automatically
// arr_push_front(buf, value); // prepends
// arr_remove(buf, i); // removes index i, shifts tail left
// size_t n = arr_len(buf); // element count (0 if buf == NULL)
// arr_free(buf); // frees header + data
//
// The allocation layout is:
// [ Header (count, capacity) ][ T[0] ][ T[1] ] ...
// buf always points at T[0]; the Header lives one slot before it.
// ---------------------------------------------------------------------------
typedef struct {
size_t count;
size_t capacity;
} _ArrHeader;
#define _ARR_INIT_CAPACITY 2
#define _arr_header(arr) ((_ArrHeader *)(arr) - 1)
#define arr_len(arr) ((arr) ? _arr_header(arr)->count : 0u)
#define _arr_maybe_grow(arr) \
do { \
if (!(arr)) { \
_ArrHeader *_h = MmAllocatePool(sizeof(_ArrHeader) + \
sizeof(*(arr)) * _ARR_INIT_CAPACITY); \
_h->count = 0; \
_h->capacity = _ARR_INIT_CAPACITY; \
(arr) = (void *)(_h + 1); \
} \
_ArrHeader *_h = _arr_header(arr); \
if (_h->count >= _h->capacity) { \
_h->capacity *= 2; \
_h = MmReallocatePool(_h, sizeof(_ArrHeader) + \
sizeof(*(arr)) * _h->capacity); \
(arr) = (void *)(_h + 1); \
} \
} while (0)
#define arr_push(arr, x) \
do { \
_arr_maybe_grow(arr); \
(arr)[_arr_header(arr)->count++] = (x); \
} while (0)
#define arr_push_front(arr, x) \
do { \
_arr_maybe_grow(arr); \
_ArrHeader *_h = _arr_header(arr); \
memmove(&(arr)[1], &(arr)[0], sizeof(*(arr)) * _h->count); \
(arr)[0] = (x); \
_h->count++; \
} while (0)
#define arr_remove(arr, index) \
do { \
if ((arr) && (size_t)(index) < arr_len(arr)) { \
_ArrHeader *_h = _arr_header(arr); \
memmove(&(arr)[index], &(arr)[(index) + 1], \
sizeof(*(arr)) * (_h->count - (size_t)(index) - 1)); \
_h->count--; \
} \
} while (0)
#define arr_free(arr) MmFreePool(_arr_header(arr))
+20 -76
View File
@@ -13,65 +13,12 @@
#include <ke_irq.h>
#include <ke_syscall.h>
#include <ke_debug.h>
#include <ke_util.h>
#include <ldr_image.h>
#include <hal_api.h>
#include <io_file.h>
#include <ini_parser.h>
// dynamic arrays bitch
typedef struct {
size_t count;
size_t capacity;
} Header;
#define ARR_INIT_CAPACITY 2
#define _arr_header(arr) ((Header*)(arr) - 1)
#define _arr_maybe_grow(arr) \
do { \
if (!(arr)) { \
Header* h = MmAllocatePool(sizeof(Header) + sizeof(*(arr)) * ARR_INIT_CAPACITY); \
h->count = 0; \
h->capacity = ARR_INIT_CAPACITY; \
(arr) = (void*)(h + 1); \
} \
Header* h = _arr_header(arr); \
if (h->count >= h->capacity) { \
h->capacity *= 2; \
h = MmReallocatePool(h, sizeof(Header) + sizeof(*(arr)) * h->capacity); \
(arr) = (void*)(h + 1); \
} \
} while (0)
#define arr_push(arr, x) \
do { \
_arr_maybe_grow(arr); \
(arr)[_arr_header(arr)->count++] = (x); \
} while (0)
#define arr_push_front(arr, x) \
do { \
_arr_maybe_grow(arr); \
Header* h = _arr_header(arr); \
memmove(&(arr)[1], &(arr)[0], sizeof(*(arr)) * h->count); \
(arr)[0] = (x); \
h->count++; \
} while (0)
#define arr_remove(arr, index) \
do { \
if ((arr) && (index) < arr_len(arr)) { \
Header* h = _arr_header(arr); \
memmove(&(arr)[index], &(arr)[index + 1], sizeof(*(arr)) * (h->count - (index) - 1)); \
h->count--; \
} \
} while (0)
#define arr_len(arr) ((Header*)(arr) - 1)->count
#define arr_free(arr) MmFreePool((Header*)(arr) - 1)
typedef struct {
BOOT_INFO* bootInfo;
const HAL_API* hal;
@@ -138,17 +85,15 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
s = IoOpenFile("drivers\\drivers.ini", &f);
if (s != STATUS_SUCCESS) {
DbgPrint("InitializeDrivers: Failed to open drivers.ini\n");
_disable(); while(true) __halt();
KePanic("InitializeDrivers: Failed to open drivers.ini (status %d)\n", s);
}
uint64_t size = IoGetFileSize(f) + 1;
char* mem = MmAllocatePool(size);
size_t got;
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
DbgPrint("InitializeDrivers: Failed to read drivers.ini\n");
IoCloseFile(f);
_disable(); while(true) __halt();
KePanic("InitializeDrivers: Failed to read drivers.ini\n");
}
mem[got] = '\0';
@@ -162,16 +107,17 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
// Load the driver
size_t len = strlen(runtimeKeys[i]);
char* path = MmAllocatePool(len + 10);
if (!path) {
DbgPrint("InitializeDrivers: Failed to allocate memory for driver path. How?\n");
continue;
}
// Assemble the path
strncpy(path, "drivers\\", sizeof(path) - 1);
path[sizeof(path) - 1] = '\0';
strncat(path, runtimeKeys[i], sizeof(path) - strlen(path) - 1);
// "drivers\" is 8 chars; +1 for the NUL terminator already in len.
size_t pathLen = 8 + len + 1; // len already includes the NUL from strlen+1 above
char *path = MmAllocatePool(pathLen);
if (!path) {
DbgPrint("InitializeDrivers: OOM building driver path\n");
continue;
}
memcpy(path, "drivers\\", 8);
memcpy(path + 8, runtimeKeys[i], len); // len includes the NUL
s = IoOpenFile(path, &f);
if (s != STATUS_SUCCESS) {
@@ -229,17 +175,15 @@ static void LoadAndRegisterDll(const char* filename) {
// Read the DLL from disk
s = IoOpenFile(filename, &f);
if (s != STATUS_SUCCESS) {
DbgPrint("LoadAndRegisterDll: Failed to open DLL: %s\n", filename);
return;
KePanic("LoadAndRegisterDll: Failed to open DLL: %s\n", filename);
}
uint64_t size = IoGetFileSize(f);
void* mem = MmAllocatePool(size); // Temporary pool for the raw file
size_t got;
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
DbgPrint("LoadAndRegisterDll: Failed to read %s\n", filename);
KePanic("LoadAndRegisterDll: Failed to read %s\n", filename);
IoCloseFile(f);
return;
}
IoCloseFile(f);
@@ -248,8 +192,7 @@ static void LoadAndRegisterDll(const char* filename) {
uintptr_t mappedBase = 0;
s = LdrLoadImage(mem, size, &mappedBase, true);
if (!KSUCCESS(s)) {
DbgPrint("LoadAndRegisterDll: Failed to map %s\n", filename);
return;
KePanic("LoadAndRegisterDll: Failed to map %s\n", filename);
}
MmFreePool(mem); // Free the raw file buffer, we only need the mapped image now
@@ -428,7 +371,10 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
.DebugEnabled = bootInfo->DebugEnabled
};
KSTATUS s = HalInitialize(&halParams);
if (!KSUCCESS(s)) while (true) __halt(); // there's nothing we can do.
if (!KSUCCESS(s)) {
_disable();
while (true) __halt(); // there's nothing we can do.
}
const HAL_API* hal = HalGetApi();
gKmainHal = hal;
@@ -466,9 +412,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
PriorityNormal, &initThread);
if (!KSUCCESS(st)) {
DbgPrint("KernelMain: PANIC - Failed to create SystemInitThread! (0x%x)\n", st);
_disable();
for(;;) __halt();
KePanic("KernelMain: Failed to create SystemInitThread (status 0x%x)\n", st);
}
DbgPrint("KernelMain: Handing over to scheduler...\n");
+1 -1
View File
@@ -120,7 +120,7 @@ void MmFreeFrame(PHYSICAL_ADDRESS frame) {
uintptr_t flags = KeAcquireSpinlock(&gFrameStack.Lock);
if (gFrameStack.Top < gFrameStack.Capacity)
gFrameStack.Stack[gFrameStack.Top++] = frame;
// else: stack full — leak rather than corrupt
// else: stack full - leak rather than corrupt
KeReleaseSpinlock(&gFrameStack.Lock, flags);
}
+28 -4
View File
@@ -17,10 +17,10 @@ It boots into a shell.
- HAL (screen, keyboard, timer, disk, PCI, APIC)
- preemptive scheduler with priority levels
- processes and threads
- VFS
- VFS with IRPs
- drive-letter paths (`0:\apps\shell.exe`)
- INI-based boot and driver configuration
- partial Win32 layer (kernel32 and user32, far from finished but capable of running simple apps)
- partial Win32 layer (kernel32 and user32, capable of running simple windows apps)
- PE import resolution and dynamic driver loading
## how 2 build
@@ -28,11 +28,33 @@ It boots into a shell.
* open vs x64 build tools
* cd into this directory
* `build.bat`
* open `run.bat`/`rund.bat`
* open `run.bat`/`rund.bat`/`runa.bat`/`runad.bat`
* replace `\\.\PhysicalDrive7` with your thumbdrive partition
* `run.bat`/`rund.bat`
* `run.bat`/`rund.bat`/`runa.bat`/`runad.bat`
* enjoy
## how it looks when built
I have some strong opinions on OS design.
```
efi/boot/
bootx64.efi
boot.ini
hal.dll
colonel.exe
drivers/
drivers.ini
ide.sys
ahci.sys
fat32.sys
syscall.dll
win32.dll
posix.dll
shell.exe
video.exe
```
## driver model
drivers are PE32+ `.sys` files with an entry point preferably named DriverEntry accepting a constant pointer to HAL_API.
@@ -42,6 +64,8 @@ drivers to be loaded have their path stored in `drivers.ini`, split into essenti
a disk driver fills in a `DISK_DRIVER` vtable and calls `IoRegisterDisk(index, &driver)`.
a filesystem driver fills in an `IO_FILESYSTEM` vtable and calls `IoRegisterFilesystem(index, &fs, &vol)`.
currently shipping: AHCI (interrupts), IDE (PIO/DMA), FAT32 (read-only).
## how it boots
1. UEFI loads `boot.ini`, reads `drivers.ini`