From 4b57738fe3fb00d23f9120e582336d5af4b75c7f Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Sun, 3 May 2026 22:42:36 +0200 Subject: [PATCH] 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 --- OVMF/OVMF_VARS_4M.fd | Bin 540672 -> 540672 bytes code/drivers/ahci/ahci.c | 38 +-- code/drivers/ide/ide.c | 585 ++++++++++++++++++++++++++++++++------- code/hal/hal_disk.c | 12 +- code/hal/io_driver.h | 2 +- code/kernel/ke_debug.c | 30 +- code/kernel/ke_debug.h | 3 +- code/kernel/ke_irp.c | 4 +- code/kernel/ke_irp.h | 8 +- code/kernel/ke_irq.c | 2 +- code/kernel/ke_sched.c | 2 +- code/kernel/ke_util.h | 76 +++++ code/kernel/kmain.c | 96 ++----- code/kernel/mm_phys.c | 2 +- readme.md | 32 ++- 15 files changed, 673 insertions(+), 219 deletions(-) create mode 100644 code/kernel/ke_util.h diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index 88cb33122859ea44ab046fb512ca0af8eb2de260..91d56fa414375a131b00773f55a1c429c54170d3 100644 GIT binary patch delta 77 zcmZo@P;3CAEsT8&Ci?|BZNIR9@e=6JTo-U|%M{VE_Q!gdGq7 delta 57 zcmZo@P;3CAEsT8&Ca0+?ZQrnf@e^0O+g(N=X4>v{mwA`b_6v^~ml(DQu(S!Vwh6Gc L39v5{;4lCHw}2I; diff --git a/code/drivers/ahci/ahci.c b/code/drivers/ahci/ahci.c index 8c0ac2a..e9a02a7 100644 --- a/code/drivers/ahci/ahci.c +++ b/code/drivers/ahci/ahci.c @@ -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); diff --git a/code/drivers/ide/ide.c b/code/drivers/ide/ide.c index 54d82a7..9265b31 100644 --- a/code/drivers/ide/ide.c +++ b/code/drivers/ide/ide.c @@ -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 #include #include @@ -5,115 +32,173 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +// --------------------------------------------------------------------------- +// 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); } diff --git a/code/hal/hal_disk.c b/code/hal/hal_disk.c index 6bb7b43..53e04a5 100644 --- a/code/hal/hal_disk.c +++ b/code/hal/hal_disk.c @@ -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 #include @@ -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. // ------------------------------------------------------------------------- diff --git a/code/hal/io_driver.h b/code/hal/io_driver.h index 41355e9..e86d808 100644 --- a/code/hal/io_driver.h +++ b/code/hal/io_driver.h @@ -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 { diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index ccf21c6..a436a30 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -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(); } diff --git a/code/kernel/ke_debug.h b/code/kernel/ke_debug.h index cd4f726..8b7ba6b 100644 --- a/code/kernel/ke_debug.h +++ b/code/kernel/ke_debug.h @@ -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 \ No newline at end of file +#endif // KE_DEBUG_H diff --git a/code/kernel/ke_irp.c b/code/kernel/ke_irp.c index 718d9ad..c7ab7d8 100644 --- a/code/kernel/ke_irp.c +++ b/code/kernel/ke_irp.c @@ -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) diff --git a/code/kernel/ke_irp.h b/code/kernel/ke_irp.h index 12f057c..411374d 100644 --- a/code/kernel/ke_irp.h +++ b/code/kernel/ke_irp.h @@ -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 #include -// 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. diff --git a/code/kernel/ke_irq.c b/code/kernel/ke_irq.c index c6f8591..2a8a4b8 100644 --- a/code/kernel/ke_irq.c +++ b/code/kernel/ke_irq.c @@ -1,4 +1,4 @@ -// ke_irq.c — IRQ dispatch, kernel-side +// ke_irq.c - IRQ dispatch, kernel-side #include #include diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index cde5f98..b71f426 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -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) { diff --git a/code/kernel/ke_util.h b/code/kernel/ke_util.h new file mode 100644 index 0000000..538542b --- /dev/null +++ b/code/kernel/ke_util.h @@ -0,0 +1,76 @@ +// ke_util.h - kernel utility macros (dynamic arrays, etc.) +#pragma once + +#include +#include + +// --------------------------------------------------------------------------- +// 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)) \ No newline at end of file diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index 411ac75..ba410c3 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -13,65 +13,12 @@ #include #include #include +#include #include #include #include #include -// 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"); diff --git a/code/kernel/mm_phys.c b/code/kernel/mm_phys.c index 42f7d21..8280406 100644 --- a/code/kernel/mm_phys.c +++ b/code/kernel/mm_phys.c @@ -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); } diff --git a/readme.md b/readme.md index d29111b..acac5c5 100644 --- a/readme.md +++ b/readme.md @@ -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`