colonel.exe 🤑, refactor all include directives, AHCI is now interrupt driven, upgrades to IDE, implemented I/O Request Packets, fix numerous keyboard issues, change all for (;;) and while (1) to while (true), DbgPrint guarded by spinlock, fix KeSignalEvent, major KernelMain refactor, added SystemInitThread because of new blocking I/O, replace memcpy manouvers with strncpy and strncat, add: run for ide, runa for ahci, rund for debug and ide, runad for debug and ahci.

This commit is contained in:
maksw committed 2026-05-03 19:35:48 +02:00
1 parent 7f4052f41c
commit 9fa2a7e0db
61 files changed
+1311 -456

No files matched your search

+9 -8
View File
@@ -15,12 +15,13 @@
#include <efi.h>
#include <efilib.h>
#include <stdbool.h>
#include <intrin.h>
int _fltused = 0;
#include <boot_info.h>
#include <memory_map.h>
#include <ini_parser.h>
#include "../kernel/boot_info.h"
#include "../kernel/memory_map.h"
#include "ini_parser.h"
int _fltused = 0;
#define KERNEL_VIRT_BASE 0xFFFF800000000000ULL
#define PHYS_MAP_BASE 0xFFFF880000000000ULL
@@ -557,7 +558,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable
} MODULE_LOAD_INFO;
MODULE_LOAD_INFO core[] = {
{ L"kernel.exe", "kernel.exe", NULL, KERNEL_VIRT_BASE },
{ L"colonel.exe", "kernel.exe", NULL, KERNEL_VIRT_BASE },
{ L"hal.dll", "hal.dll", NULL, 0 /* Calculated dynamically */ },
};
@@ -567,7 +568,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable
UINT8* rawBuf = LoadFile(core[i].path, &rawSize);
if (!rawBuf) {
Print(L"MISSING: %s\n", core[i].path);
while(1);
while(true);
return EFI_NOT_FOUND;
}
@@ -576,7 +577,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable
EFI_STATUS status = LoadPeImage(rawBuf, virtCursor, &physBase);
if (EFI_ERROR(status)) {
Print(L"PE Load Error: %r\n", status);
while(1);
while(true);
return status;
}
@@ -609,7 +610,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable
if (EFI_ERROR(ResolveImports(core[0].physBase, core[1].physBase, core[1].virtBase))) {
Print(L"Failed to resolve imports. System halted.\n");
while(1);
while(true);
}
Print(L"5\n");
+316 -77
View File
@@ -1,4 +1,4 @@
// ahci.c - AHCI driver, polling mode.
// ahci.c - AHCI driver, interrupt-driven with per-drive async IRP queue.
// Supports SATA drives only (no ATAPI). Registers each found drive with the
// HAL disk subsystem via IoRegisterDisk().
//
@@ -7,17 +7,51 @@
// hal->PhysToVirt(phys) converts to kernel virtual address.
// DMA buffers are bounce-buffered (allocated via MmAllocateContiguous) to
// avoid needing a VirtToPhys conversion for arbitrary kernel pointers.
//
// Async I/O model (IRP path):
// Each drive has a dedicated kernel worker thread (AhciWorker) that blocks
// on a per-drive counting semaphore. Callers submit IRPs via AhciSubmitIrp,
// which enqueues the IRP and signals the semaphore. The worker dequeues
// one IRP at a time, runs the synchronous hardware transfer (blocking the
// worker, NOT the submitting thread), then writes irp->Status and fires
// irp->CompletionEvent / irp->CompletionFn.
//
// This gives true async I/O from the caller's perspective: IoSubmitIrp()
// returns STATUS_PENDING immediately. Multiple callers can have outstanding
// requests against different drives concurrently without stalling each other.
//
// NCQ extension (future work — see "NCQ TODO" comments):
// To saturate modern SSDs, replace the serialised single-slot model with
// Native Command Queuing:
// • Allocate per-slot bounce buffers (NUM_NCQ_SLOTS × SLOT_BOUNCE_BYTES).
// • Use ATA_CMD_READ_FPDMA_QUEUED / WRITE_FPDMA_QUEUED.
// • Encode the slot tag in FIS.countl bits 7:3.
// • Issue via port->sact (NCQ active) rather than port->ci.
// • In AhciIsr: completed slots = prev_sact ^ port->sact; look up
// slot_irp[slot] and fire each completion from within the ISR.
// • The worker becomes a pure dispatcher: grab a free slot, build the
// command, issue it, return immediately. No per-slot KeWaitForEvent.
//
// Interrupt model (unchanged):
// GHC.IE and per-port PORT_IE are enabled after each port is initialised.
// AhciIsr fires on the PCI IRQ line and signals the per-drive completionEvent.
// port_issue_cmd blocks the worker thread on KeWaitForEvent instead of
// spinning on CI. port_issue_cmd_poll is kept for early init (IDENTIFY)
// before the scheduler is live.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <io_driver.h>
#include <mm_phys.h>
#include <mm_virt.h>
#include <ke_debug.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ke_event.h>
#include <ke_semaphore.h>
#include <ke_thread.h>
#include <ke_irq.h>
#include <ke_irp.h>
// ---------------------------------------------------------------------------
// FIS types
// ---------------------------------------------------------------------------
typedef enum {
FIS_TYPE_REG_H2D = 0x27,
@@ -322,11 +356,14 @@ typedef volatile struct {
// ---------------------------------------------------------------------------
static const HAL_API *g_hal;
static HBA_MEM *g_hba; // HBA MMIO base (set during init)
static uint8_t g_irq; // PCI IRQ line for this HBA
// Per-drive context
typedef struct {
HBA_PORT *port; // MMIO port registers (virtual)
uint64_t sectorCount; // Total LBA sectors on device
uint8_t portNo; // Physical port index (for ISR dispatch)
// Structures block (command list + FIS + cmd tables)
uint64_t structPhys; // Physical base
@@ -335,6 +372,28 @@ typedef struct {
// Bounce buffer for DMA data
uint64_t bouncePhys;
void *bounceVirt;
// Signaled by the ISR when the port raises an interrupt.
// Auto-reset: one Wait consumes one completion.
EVENT *completionEvent;
// Last IS snapshot captured by the ISR (for error checking post-wait).
volatile uint32_t lastIs;
// -----------------------------------------------------------------------
// IRP queue — async dispatch
// -----------------------------------------------------------------------
// AhciWorker blocks here waiting for work. AhciSubmitIrp enqueues an IRP
// and signals the semaphore to wake the worker.
IRP_QUEUE irpQueue;
THREAD *workerThread;
// NCQ TODO: for multi-slot NCQ, add:
// uint32_t slotMask; // bitmask of in-flight slots
// IRP *slotIrp[NUM_CMD_SLOTS]; // slot → pending IRP
// uint64_t slotBouncePhys[NUM_CMD_SLOTS];
// void *slotBounceVirt[NUM_CMD_SLOTS];
// volatile uint32_t prevSact; // previous port->sact snapshot
} AHCI_DRIVE;
static AHCI_DRIVE g_drives[MAX_AHCI_DRIVES];
@@ -476,11 +535,12 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
}
// ---------------------------------------------------------------------------
// Issue a command (polling, always uses slot 0)
// Returns STATUS_SUCCESS or STATUS_DEVICE_ERROR / STATUS_TIMEOUT.
// Issue a command — polling fallback (used during early init / IDENTIFY only,
// before the scheduler and interrupt handler are live).
// Returns STATUS_SUCCESS, STATUS_DEVICE_ERROR, or STATUS_TIMEOUT.
// ---------------------------------------------------------------------------
static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) {
HBA_PORT *port = drv->port;
// Clear pending interrupts before issuing
@@ -499,19 +559,17 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
port->ci = (1u << 0);
// Poll until slot 0 clears
for (;;) {
while (true) {
if (!(port->ci & (1u << 0))) break;
if (port->is & (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)) {
DbgPrint("AHCI: error IS=0x%x TFD=0x%x SERR=0x%x\n",
port->is, port->tfd, port->serr);
// Reset error bits
port->serr = (uint32_t)-1;
port->is = (uint32_t)-1;
return STATUS_DEVICE_ERROR;
}
}
// One final error check after CI cleared
if (port->is & PORT_IS_TFES) {
DbgPrint("AHCI: TFES after completion TFD=0x%x\n", port->tfd);
port->is = (uint32_t)-1;
@@ -521,6 +579,77 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// ISR — called from KeIrqDispatch on the AHCI PCI IRQ line.
// Walks all registered drives, checks if their port raised an interrupt,
// captures IS, clears it, and signals the per-drive completion event.
// ---------------------------------------------------------------------------
static void AhciIsr(void) {
if (!g_hba) return;
// GHC.IS tells us which ports are pending.
uint32_t pending = g_hba->is;
if (!pending) return;
for (int i = 0; i < g_driveCount; i++) {
AHCI_DRIVE *drv = &g_drives[i];
uint32_t portBit = (1u << drv->portNo);
if (!(pending & portBit)) continue;
// Capture and clear port IS before signalling — the waiter reads
// lastIs to check for errors without racing the next command.
uint32_t portIs = drv->port->is;
drv->port->is = portIs; // W1C — write back to clear
drv->lastIs = portIs;
// Clear the HBA-level bit.
g_hba->is = portBit;
if (drv->completionEvent)
KeSignalEvent(drv->completionEvent);
}
}
// ---------------------------------------------------------------------------
// Issue a command — interrupt-driven (runtime path).
// Caller has already built slot 0 via build_rw_cmd / port_identify.
// Blocks the current thread until the ISR signals completionEvent.
// ---------------------------------------------------------------------------
static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
HBA_PORT *port = drv->port;
// Clear stale IS and snapshot before issuing so we don't mis-read a
// leftover value from a prior command.
port->is = (uint32_t)-1;
drv->lastIs = 0;
// Wait for port idle
int spin = 0;
while ((port->tfd & (ATA_SR_BUSY | ATA_SR_DRQ)) && spin < POLL_TIMEOUT)
spin++;
if (spin == POLL_TIMEOUT) {
DbgPrint("AHCI: port hung (pre-issue)\n");
return STATUS_TIMEOUT;
}
// Issue slot 0, then block on the event.
port->ci = (1u << 0);
KeWaitForEvent(drv->completionEvent);
// The ISR has cleared IS — check for errors in the snapshot.
uint32_t is = drv->lastIs;
if (is & (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)) {
DbgPrint("AHCI: error IS=0x%x TFD=0x%x SERR=0x%x\n",
is, port->tfd, port->serr);
port->serr = (uint32_t)-1;
return STATUS_DEVICE_ERROR;
}
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Build a standard DMA read or write command in slot 0
// ---------------------------------------------------------------------------
@@ -606,7 +735,8 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) {
fis->command = ATA_CMD_IDENTIFY;
fis->device = 0;
KSTATUS st = port_issue_cmd(drv);
// Poll: IDENTIFY runs before the scheduler and IRQ handler are live.
KSTATUS st = port_issue_cmd_poll(drv);
if (!KSUCCESS(st)) return st;
// Parse sector count from IDENTIFY data
@@ -677,31 +807,71 @@ static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba,
return STATUS_SUCCESS;
}
// Macro-generate per-slot trampoline pairs (read + write)
#define AHCI_TRAMPOLINE(N) \
static KSTATUS AhciRead##N(uint64_t lba, uint32_t n, void *b) \
{ return do_read (&g_drives[N], lba, n, b); } \
static KSTATUS AhciWrite##N(uint64_t lba, uint32_t n, const void *b) \
{ return do_write(&g_drives[N], lba, n, b); }
// ---------------------------------------------------------------------------
// HAL_DISK callbacks — context pointer replaces trampolines.
// Ctx is the AHCI_DRIVE* stored in DISK_DRIVER.Ctx; the dispatch layer in
// hal_disk.c passes it as the first argument to every call.
// ---------------------------------------------------------------------------
AHCI_TRAMPOLINE(0)
AHCI_TRAMPOLINE(1)
AHCI_TRAMPOLINE(2)
AHCI_TRAMPOLINE(3)
AHCI_TRAMPOLINE(4)
AHCI_TRAMPOLINE(5)
AHCI_TRAMPOLINE(6)
AHCI_TRAMPOLINE(7)
static KSTATUS AhciRead(void *ctx, uint64_t lba, uint32_t count, void *buf) {
return do_read((AHCI_DRIVE *)ctx, lba, count, buf);
}
typedef KSTATUS (*RW_FN_R)(uint64_t, uint32_t, void*);
typedef KSTATUS (*RW_FN_W)(uint64_t, uint32_t, const void*);
static KSTATUS AhciWrite(void *ctx, uint64_t lba, uint32_t count, const void *buf) {
return do_write((AHCI_DRIVE *)ctx, lba, count, buf);
}
static const RW_FN_R kReadFns[MAX_AHCI_DRIVES] = {
AhciRead0, AhciRead1, AhciRead2, AhciRead3,
AhciRead4, AhciRead5, AhciRead6, AhciRead7 };
static const RW_FN_W kWriteFns[MAX_AHCI_DRIVES] = {
AhciWrite0, AhciWrite1, AhciWrite2, AhciWrite3,
AhciWrite4, AhciWrite5, AhciWrite6, AhciWrite7 };
// ---------------------------------------------------------------------------
// Async IRP dispatch
// ---------------------------------------------------------------------------
// AhciSubmitIrp — called by IoSubmitIrp().
// Enqueues the IRP and wakes the per-drive worker thread.
// Returns STATUS_PENDING immediately; the caller waits on irp->CompletionEvent
// or irp->CompletionFn.
static KSTATUS AhciSubmitIrp(void *ctx, IRP *irp) {
AHCI_DRIVE *drv = (AHCI_DRIVE *)ctx;
irp->Status = STATUS_FAILURE; // sentinel: I/O has not completed yet
IrpQueueEnqueue(&drv->irpQueue, irp);
return STATUS_PENDING;
}
// AhciWorker — kernel thread, one per drive.
// Dequeues IRPs and executes them one at a time using the existing
// single-slot interrupt-driven path. Completion fires the IRP's event
// and/or callback so the original caller can observe the result without
// ever having blocked in a driver call.
//
// NCQ TODO: replace the serialised loop with a multi-slot dispatcher:
// 1. KeWaitSemaphore() to wait for a free slot AND a pending IRP.
// 2. Pick the oldest IRP from the queue.
// 3. Grab a free bit in drv->slotMask (under the slot spinlock).
// 4. Copy write data into slotBounce[slot], build the NCQ FIS (FPDMA),
// record slotIrp[slot] = irp, and issue via port->sact + port->ci.
// 5. Return immediately — the ISR drives all completions.
static void AhciWorker(uintptr_t arg) {
AHCI_DRIVE *drv = (AHCI_DRIVE *)arg;
while (true) {
// Block until AhciSubmitIrp signals a new IRP.
KeWaitSemaphore(drv->irpQueue.Semaphore);
IRP *irp = IrpQueueDequeue(&drv->irpQueue);
if (!irp) continue; // spurious wakeup — should not happen
bool write = !!(irp->Flags & IRP_FLAG_WRITE);
KSTATUS st;
if (write)
st = do_write(drv, irp->Lba, irp->SectorCount, irp->Buffer);
else
st = do_read (drv, irp->Lba, irp->SectorCount, irp->Buffer);
// Publish result then fire completion hooks.
irp->Status = st;
if (irp->CompletionEvent) KeSignalEvent(irp->CompletionEvent);
if (irp->CompletionFn) irp->CompletionFn(irp, irp->CompletionCtx);
}
}
// ---------------------------------------------------------------------------
// AHCI controller initialisation
@@ -734,10 +904,9 @@ static KSTATUS hba_reset(HBA_MEM *hba) {
return STATUS_TIMEOUT;
}
// Re-enable AHCI mode (reset cleared it)
hba->ghc |= GHC_AE;
// Leave GHC.IE clear - we use polling only
// Re-enable AHCI mode (reset cleared it), then enable global interrupts.
// GHC.IE lets the HBA assert its PCI interrupt line when a port fires.
hba->ghc |= GHC_AE | GHC_IE;
return STATUS_SUCCESS;
}
@@ -810,12 +979,25 @@ static KSTATUS port_comreset(AHCI_DRIVE *drv) {
return STATUS_SUCCESS;
}
// PORT_IE bits we care about:
// DHRS (bit 0) - D2H Register FIS received — normal completion
// PSS (bit 1) - PIO Setup FIS received
// DSS (bit 2) - DMA Setup FIS received
// SDBS (bit 3) - Set Device Bits FIS received
// TFES (bit 30) - Task File Error Status — error
// HBFS (bit 29) - Host Bus Fatal error
// HBDS (bit 28) - Host Bus Data error
// IFS (bit 27) - Interface Fatal error
#define PORT_IE_NORMAL (0x0000000Fu)
#define PORT_IE_ERRORS (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS)
#define PORT_IE_ALL (PORT_IE_NORMAL | PORT_IE_ERRORS)
KSTATUS AhciInitialize(const HAL_API *hal) {
g_hal = hal;
g_hal = hal;
g_driveCount = 0;
// -----------------------------------------------------------------------
// 1. Locate AHCI controller via PCI class scan
// Locate AHCI controller via PCI class scan
// -----------------------------------------------------------------------
uint8_t pciBus, pciDev, pciFn;
if (!hal->Pci.FindClass(PCI_AHCI_CLASS, PCI_AHCI_SUBCLASS,
@@ -826,54 +1008,60 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
DbgPrint("AHCI: controller at PCI %02x:%02x.%x\n", pciBus, pciDev, pciFn);
// -----------------------------------------------------------------------
// 2. Enable Bus Master + Memory Space in PCI command register
// Enable Bus Master + Memory Space in PCI command register
// -----------------------------------------------------------------------
uint32_t cmd = hal->Pci.ConfigRead32(pciBus, pciDev, pciFn, PCI_CMD_REG);
cmd |= (PCI_CMD_BUSMASTER | PCI_CMD_MEMSPACE);
hal->Pci.ConfigWrite32(pciBus, pciDev, pciFn, PCI_CMD_REG, cmd);
uint32_t cmd = hal->Pci.Read32(pciBus, pciDev, pciFn, PCI_CMD_REG);
cmd |= (PCI_CMD_BUSMASTER | PCI_CMD_MEMSPACE); cmd &= ~(1 << 10); cmd |= (1 << 2);
hal->Pci.Write32(pciBus, pciDev, pciFn, PCI_CMD_REG, cmd);
// -----------------------------------------------------------------------
// 3. Read ABAR (BAR5) and map as uncacheable
// The physical address is already identity-mapped via PhysToVirt.
// NOTE: real drivers should mark the mapping WC or UC; here we rely
// on the kernel not caching MMIO ranges (caller's responsibility).
// Read PCI IRQ line
// -----------------------------------------------------------------------
uint32_t bar5 = hal->Pci.ConfigRead32(pciBus, pciDev, pciFn, PCI_BAR5_REG);
uint64_t abarPhys = (uint64_t)(bar5 & ~0xFFFu); // strip flags
KSTATUS st = ahci_map_mmio(abarPhys); // establish PTEs
g_irq = hal->Pci.GetIrqLine(pciBus, pciDev, pciFn);
if (g_irq == 0xFF) {
DbgPrint("AHCI: no PCI IRQ wired — cannot use interrupt-driven mode\n");
return STATUS_NOT_SUPPORTED;
}
DbgPrint("AHCI: PCI IRQ line = %u\n", (unsigned)g_irq);
// -----------------------------------------------------------------------
// Read ABAR (BAR5) and map as uncacheable
// -----------------------------------------------------------------------
uint32_t bar5 = hal->Pci.Read32(pciBus, pciDev, pciFn, PCI_BAR5_REG);
uint64_t abarPhys = (uint64_t)(bar5 & ~0xFFFu);
KSTATUS st = ahci_map_mmio(abarPhys);
if (!KSUCCESS(st)) return st;
HBA_MEM *hba = (HBA_MEM *)hal->PhysToVirt(abarPhys);
g_hba = (HBA_MEM *)hal->PhysToVirt(abarPhys);
DbgPrint("AHCI: ABAR phys=0x%llx version=0x%08x\n",
(unsigned long long)abarPhys, hba->vs);
(unsigned long long)abarPhys, g_hba->vs);
// -----------------------------------------------------------------------
// 4. HBA reset + BIOS handoff
// HBA reset + BIOS handoff (also enables GHC.IE)
// -----------------------------------------------------------------------
st = hba_reset(hba);
st = hba_reset(g_hba);
if (!KSUCCESS(st)) return st;
// -----------------------------------------------------------------------
// 5. Log capability summary
// Log capability summary
// -----------------------------------------------------------------------
uint32_t cap = hba->cap;
uint32_t nPorts = (cap & 0x1F) + 1; // bits 4:0 + 1
uint32_t nSlots = ((cap >> 8) & 0x1F) + 1;
bool s64a = !!(cap & (1u << 31));
uint32_t cap = g_hba->cap;
uint32_t nPorts = (cap & 0x1F) + 1;
uint32_t nSlots = ((cap >> 8) & 0x1F) + 1;
bool s64a = !!(cap & (1u << 31));
DbgPrint("AHCI: cap=0x%08x ports=%u slots=%u 64bit=%d\n",
cap, nPorts, nSlots, (int)s64a);
// -----------------------------------------------------------------------
// 6. Enumerate implemented ports
// Enumerate implemented ports
// -----------------------------------------------------------------------
uint32_t pi = hba->pi;
uint32_t pi = g_hba->pi;
for (uint32_t portno = 0; portno < 32 && g_driveCount < MAX_AHCI_DRIVES; portno++) {
if (!(pi & (1u << portno))) continue;
HBA_PORT *port = &hba->ports[portno];
HBA_PORT *port = &g_hba->ports[portno];
// Phase 1: check physical link only — sig is garbage until after COMRESET
// Phase 1: physical link check
if (!port_link_up(port)) {
DbgPrint("AHCI: port %u: no link (ssts=0x%08x)\n", portno, port->ssts);
continue;
@@ -884,31 +1072,48 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
int driveIdx = g_driveCount;
AHCI_DRIVE *drv = &g_drives[driveIdx];
memset(drv, 0, sizeof(*drv));
drv->port = port;
drv->port = port;
drv->portNo = (uint8_t)portno;
// Phase 2: set up command list + FIS buffer (enables FRE)
// Phase 2: allocate command list, FIS buffer, bounce buffer
st = port_init_memory(drv);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: memory init failed (0x%x)\n", portno, st);
continue;
}
// Phase 3: COMRESET to flush stale HBA state and force device to
// re-send D2H FIS so the SIG register gets a valid value
// Phase 3: COMRESET — forces device to re-send D2H FIS for valid SIG
st = port_comreset(drv);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: COMRESET failed\n", portno);
continue;
}
// Phase 4: now sig is valid — filter to SATA disks only
// Phase 4: filter to SATA disks
if (!port_is_sata_disk(port)) {
DbgPrint("AHCI: port %u: not a SATA disk (sig=0x%08x), skipping\n",
portno, port->sig);
continue;
}
// Phase 5: IDENTIFY to get sector count
// Phase 5: create per-drive completion event (auto-reset, initially clear)
st = KeEventCreate(EventAutoReset, 0, &drv->completionEvent);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: event create failed (0x%x)\n", portno, st);
continue;
}
// Phase 5b: create the IRP queue semaphore and initialise the queue.
// The semaphore starts at 0; each submitted IRP increments it by one.
SEMAPHORE *irpSem = (SEMAPHORE *)0;
st = SemaphoreCreate(0, 0x7FFFFFFF, &irpSem);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: semaphore create failed (0x%x)\n", portno, st);
continue;
}
IrpQueueInit(&drv->irpQueue, irpSem);
// Phase 6: IDENTIFY (polled — scheduler not yet running)
st = port_identify(drv);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: IDENTIFY failed (0x%x)\n", portno, st);
@@ -920,22 +1125,56 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
(unsigned long long)drv->sectorCount,
(unsigned long long)(drv->sectorCount / 2048));
// Phase 7: enable per-port interrupts.
// Clear any stale IS first so we don't get a spurious signal immediately.
port->is = (uint32_t)-1;
port->ie = PORT_IE_ALL;
// Phase 8: register with disk subsystem using Ctx pointer
DISK_DRIVER dd = {
.ReadSectors = kReadFns[driveIdx],
.WriteSectors = kWriteFns[driveIdx],
.Ctx = drv,
.ReadSectors = AhciRead,
.WriteSectors = AhciWrite,
.SubmitIrp = AhciSubmitIrp, // async IRP path
.SectorCount = drv->sectorCount,
};
st = IoRegisterDisk((uint8_t)driveIdx, &dd);
if (KSUCCESS(st)) {
DbgPrint("AHCI: drive %d registered\n", driveIdx);
g_driveCount++; // only increment on full success
g_driveCount++;
} else {
DbgPrint("AHCI: IoRegisterDisk(%d) failed (0x%x)\n", driveIdx, st);
continue;
}
// Phase 9: spawn the per-drive IRP worker thread.
// The worker owns do_read / do_write exclusively once the scheduler
// is live; it is the only thread that may call port_issue_cmd().
st = KeCreateKernelThread((uintptr_t)AhciWorker, (uintptr_t)drv,
PriorityHigh, &drv->workerThread);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: drive %d: worker thread create failed (0x%x)\n",
driveIdx, st);
// Non-fatal: sync ReadSectors / WriteSectors still work via the
// IoSubmitIrpSync() fallback in hal_disk.c, but async callers
// will hang waiting on irp->CompletionEvent forever.
}
}
DbgPrint("AHCI: initialised %d drive(s)\n", g_driveCount);
return (g_driveCount > 0) ? STATUS_SUCCESS : STATUS_NOT_FOUND;
if (g_driveCount == 0) {
DbgPrint("AHCI: no drives found\n");
return STATUS_NOT_FOUND;
}
// -----------------------------------------------------------------------
// Register the ISR. All ports on this HBA share one PCI IRQ line.
// Do this after all drives are initialised so the ISR sees valid state.
// -----------------------------------------------------------------------
KeIrqRegister(g_irq, AhciIsr);
DbgPrint("AHCI: ISR registered on IRQ %u; initialised %d drive(s)\n",
(unsigned)g_irq, g_driveCount);
return STATUS_SUCCESS;
}
KSTATUS DriverEntry(const HAL_API *hal) {
+4 -4
View File
@@ -1,14 +1,14 @@
// fat32.c - FAT32 filesystem driver
// Registers itself via IoRegisterFilesystem after finding a FAT32 partition.
// Supports read-only access; write support is not implemented.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <io_driver.h>
#include <hal_api.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ke_debug.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
// ---------------------------------------------------------------------------
// On-disk structures
@@ -584,7 +584,7 @@ KSTATUS DriverEntry(const HAL_API* hal) {
// Pass the new volParams structure
KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams);
DbgPrint("FAT32: [DEBUG] pIoRegisterFilesystem returned: %d\n", status);
DbgPrint("FAT32: [DEBUG] IoRegisterFilesystem returned: %d\n", status);
DbgPrint("FAT32: Init complete.\n");
return STATUS_SUCCESS;
+109 -83
View File
@@ -1,19 +1,18 @@
// ide.c - PIO-mode IDE driver (legacy 28-bit LBA)
#include <io_driver.h>
#include <hal_api.h>
#include <ke_debug.h>
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <io_driver.h>
#include <hal_api.h>
#include <ke_debug.h>
#include <ke_sched.h>
// Primary channel I/O ports.
#define IDE_DATA 0x1F0
#define IDE_ERR 0x1F1
#define IDE_SECCNT 0x1F2
#define IDE_LBA0 0x1F3
#define IDE_LBA1 0x1F4
#define IDE_LBA2 0x1F5
#define IDE_DRIVE 0x1F6 // 0xE0=master 0xF0=slave
#define IDE_DRIVE 0x1F6
#define IDE_STATUS 0x1F7
#define IDE_CMD 0x1F7
#define IDE_ALT 0x3F6
@@ -23,69 +22,112 @@
#define IDE_ERR_F 0x01
#define IDE_DRQ 0x08
#define CMD_READ 0x20
#define CMD_WRITE 0x30
#define CMD_IDENT 0xEC
// 48-bit LBA Commands
#define CMD_READ_EXT 0x24
#define CMD_WRITE_EXT 0x34
#define CMD_IDENT 0xEC
static const HAL_API* gHal;
static DISK_DRIVER gDrv;
static uint64_t gSectorCount = 0;
static bool gSupportsLba48 = false;
// ---------------------------------------------------------
// Polling Upgrades: Yield instead of blocking the CPU
// ---------------------------------------------------------
static void IdeWait(void) {
while (__inbyte(IDE_STATUS) & IDE_BUSY) _mm_pause();
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
KeYield(); // <-- Let other threads run while the disk thinks!
}
}
static KSTATUS IdeWaitData(void) {
// Read the Alternate Status port 4 times to waste 400ns
for (int i = 0; i < 4; i++) {
__inbyte(IDE_ALT);
}
for (int i = 0; i < 4; i++) __inbyte(IDE_ALT);
// Wait until the drive is not busy AND data is requested (DRQ)
while (1) {
while (true) {
uint8_t st = __inbyte(IDE_STATUS);
if (st & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (!(st & IDE_BUSY) && (st & IDE_DRQ)) return STATUS_SUCCESS;
_mm_pause();
KeYield(); // <-- Be a good citizen
}
}
static KSTATUS IdeRead(uint64_t lba, uint32_t count, void* buf) {
// ---------------------------------------------------------
// 48-Bit LBA Read/Write
// ---------------------------------------------------------
static KSTATUS IdeRead(void *ctx, uint64_t lba, uint32_t count, void* buf) {
(void)ctx;
if (lba + count > gSectorCount) return STATUS_INVALID_PARAMETER;
uint16_t* p = (uint16_t *)buf;
// For simplicity, handle one sector at a time in this loop
for (uint32_t i = 0; i < count; i++, lba++) {
IdeWait(); // Wait for drive to be ready for commands
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_READ);
IdeWait();
if (gSupportsLba48) {
// LBA48 requires writing high bytes first, then low bytes
__outbyte(IDE_DRIVE, 0x40); // 0x40 = LBA mode, drive 0
__outbyte(IDE_SECCNT, 0); // Count High
__outbyte(IDE_LBA0, (lba >> 24) & 0xFF); // LBA 4
__outbyte(IDE_LBA1, (lba >> 32) & 0xFF); // LBA 5
__outbyte(IDE_LBA2, (lba >> 40) & 0xFF); // LBA 6
__outbyte(IDE_SECCNT, 1); // Count Low
__outbyte(IDE_LBA0, (uint8_t)lba); // LBA 1
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8)); // LBA 2
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));// LBA 3
__outbyte(IDE_CMD, CMD_READ_EXT);
} else {
// Fallback to 28-bit
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, 0x20); // CMD_READ (28-bit)
}
// Use the delay/DRQ wait
if (IdeWaitData() != STATUS_SUCCESS) return STATUS_DEVICE_ERROR;
for (int w = 0; w < 256; w++) *p++ = __inword(IDE_DATA);
}
return STATUS_SUCCESS;
}
static KSTATUS IdeWrite(uint64_t lba, uint32_t count, const void* buf) {
static KSTATUS IdeWrite(void *ctx, uint64_t lba, uint32_t count, const void* buf) {
(void)ctx;
if (lba + count > gSectorCount) return STATUS_INVALID_PARAMETER;
const uint16_t* p = (const uint16_t *)buf;
for (uint32_t i = 0; i < count; i++, lba++) {
IdeWait();
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_WRITE);
if (gSupportsLba48) {
__outbyte(IDE_DRIVE, 0x40);
__outbyte(IDE_SECCNT, 0);
__outbyte(IDE_LBA0, (lba >> 24) & 0xFF);
__outbyte(IDE_LBA1, (lba >> 32) & 0xFF);
__outbyte(IDE_LBA2, (lba >> 40) & 0xFF);
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, CMD_WRITE_EXT);
} else {
__outbyte(IDE_DRIVE, 0xE0 | ((lba >> 24) & 0xF));
__outbyte(IDE_SECCNT, 1);
__outbyte(IDE_LBA0, (uint8_t)lba);
__outbyte(IDE_LBA1, (uint8_t)(lba >> 8));
__outbyte(IDE_LBA2, (uint8_t)(lba >> 16));
__outbyte(IDE_CMD, 0x30); // CMD_WRITE (28-bit)
}
IdeWait();
for (int w = 0; w < 256; w++) __outword(IDE_DATA, *p++);
__outbyte(IDE_ALT, 0); // flush cache
__outbyte(IDE_ALT, 0); // flush cache
if (IdeWaitData() != STATUS_SUCCESS) return STATUS_DEVICE_ERROR;
}
return STATUS_SUCCESS;
@@ -94,83 +136,67 @@ static KSTATUS IdeWrite(uint64_t lba, uint32_t count, const void* buf) {
KSTATUS DriverEntry(const HAL_API* hal) {
DbgPrint("IDE: Init\n");
gHal = hal;
uint8_t status;
int timeout;
// 1. Select Primary Master Drive (0xA0)
// Select Primary Master (Drive 0)
__outbyte(IDE_DRIVE, 0xA0);
// Waste 400ns to let the drive selection settle on the bus
for(int i=0; i<4; i++) __inbyte(IDE_ALT);
// 2. Zero out the LBA and Sector Count registers
__outbyte(IDE_SECCNT, 0);
__outbyte(IDE_LBA0, 0);
__outbyte(IDE_LBA1, 0);
__outbyte(IDE_LBA2, 0);
// 3. Send the IDENTIFY command
DbgPrint("IDE: Sending CMD_IDENT (0xEC)...\n");
__outbyte(IDE_CMD, CMD_IDENT);
// 4. Read status immediately. If it's 0, the drive doesn't exist at all.
status = __inbyte(IDE_STATUS);
// DbgPrint("IDE: Initial status after IDENTIFY: 0x%02X\n", status);
if (status == 0) {
DbgPrint("IDE ERROR: Drive does not exist (Status 0).\n");
if (__inbyte(IDE_STATUS) == 0) {
// QEMU usually falls in here because it uses AHCI by default,
// leaving the legacy IDE ports floating/unconnected.
DbgPrint("IDE ERROR: Drive does not exist on legacy port 0x1F0.\n");
return STATUS_NOT_FOUND;
}
// 5. Wait for BSY (Busy) to clear, WITH A TIMEOUT
timeout = 100000;
// Wait for BSY to clear, yielding to scheduler
int timeout = 10000;
while (__inbyte(IDE_STATUS) & IDE_BUSY) {
if (--timeout == 0) {
DbgPrint("IDE ERROR: Timeout waiting for BSY to clear!\n");
return STATUS_DEVICE_ERROR;
}
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
}
// 6. Check for ATAPI (CD-ROM) or SATA signatures.
// If these registers aren't 0 after BSY clears, it's NOT a standard ATA disk!
uint8_t lba1 = __inbyte(IDE_LBA1);
uint8_t lba2 = __inbyte(IDE_LBA2);
if (lba1 != 0 || lba2 != 0) {
DbgPrint("IDE ERROR: Not an ATA drive! Signatures: LBA1=0x%02X, LBA2=0x%02X\n", lba1, lba2);
// (ATAPI CD-ROMs usually show LBA1=0x14, LBA2=0xEB)
if (__inbyte(IDE_LBA1) != 0 || __inbyte(IDE_LBA2) != 0) {
DbgPrint("IDE ERROR: Not a standard ATA drive (Possibly ATAPI).\n");
return STATUS_NOT_SUPPORTED;
}
// 7. Wait for DRQ (Data Request) or ERR (Error)
timeout = 100000;
while (1) {
status = __inbyte(IDE_STATUS);
if (status & IDE_ERR_F) {
DbgPrint("IDE ERROR: Drive returned an error! Status: 0x%02X\n", status);
return STATUS_DEVICE_ERROR;
}
if (status & IDE_DRQ) {
break; // Data is ready!
}
if (--timeout == 0) {
DbgPrint("IDE ERROR: Timeout waiting for DRQ! Status: 0x%02X\n", status);
return STATUS_DEVICE_ERROR;
}
timeout = 10000;
while (true) {
uint8_t status = __inbyte(IDE_STATUS);
if (status & IDE_ERR_F) return STATUS_DEVICE_ERROR;
if (status & IDE_DRQ) break;
if (--timeout == 0) return STATUS_DEVICE_ERROR;
KeYield();
}
// 8. Success! Read the 256 words (512 bytes) of IDENTIFY data
// DbgPrint("IDE: DRQ set! Reading 256 words...\n");
uint16_t ident[256];
for (int i = 0; i < 256; i++) {
ident[i] = __inword(IDE_DATA);
}
// 9. Extract and print the sector count (Words 60 and 61 for 28-bit LBA)
gSectorCount = ((uint32_t)ident[61] << 16) | ident[60];
DbgPrint("IDE: SUCCESS: Disk found! Sector count: %u\n", (uint32_t)gSectorCount);
// Word 83 Bit 10 determines LBA48 support
gSupportsLba48 = (ident[83] & (1 << 10)) != 0;
if (gSupportsLba48) {
// Words 100-103 contain the 64-bit LBA sector count
gSectorCount = *(uint64_t*)&ident[100];
DbgPrint("IDE: LBA48 Disk found! Sectors: %llu\n", gSectorCount);
} else {
// Fallback to 28-bit
gSectorCount = ((uint32_t)ident[61] << 16) | ident[60];
DbgPrint("IDE: LBA28 Disk found! Sectors: %u\n", (uint32_t)gSectorCount);
}
if (gSectorCount == 0) return STATUS_NOT_FOUND;
// Register driver globally (from the previous fix!)
gDrv.Ctx = NULL;
gDrv.ReadSectors = IdeRead;
gDrv.WriteSectors = IdeWrite;
gDrv.SectorCount = gSectorCount;
+7 -3
View File
@@ -3,7 +3,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "ke_status.h"
#include <ke_status.h>
typedef struct BOOT_INFO BOOT_INFO;
@@ -21,6 +21,7 @@ typedef struct HAL_KEYBOARD {
bool (*Poll)(void);
uint8_t (*ReadScancode)(void);
char (*ReadChar)(void);
uint32_t (*GetModifiers)(void);
void (*SetCallback)(void (*cb)(uint8_t scancode));
void (*Isr)(void);
uint8_t IrqNumber;
@@ -43,14 +44,17 @@ typedef struct HAL_TIMER {
} HAL_TIMER;
typedef struct HAL_PCI {
uint32_t (*ConfigRead32)(uint8_t bus, uint8_t dev,
uint32_t (*Read32)(uint8_t bus, uint8_t dev,
uint8_t fn, uint8_t reg);
void (*ConfigWrite32)(uint8_t bus, uint8_t dev,
void (*Write32)(uint8_t bus, uint8_t dev,
uint8_t fn, uint8_t reg, uint32_t val);
bool (*FindDevice)(uint16_t vendor, uint16_t device,
uint8_t* bus, uint8_t* dev, uint8_t* fn);
bool (*FindClass)(uint8_t classCode, uint8_t subclass,
uint8_t* bus, uint8_t* dev, uint8_t* fn);
// Returns the IRQ line from PCI config space (register 0x3C, byte 0).
// Returns 0xFF if no interrupt is wired.
uint8_t (*GetIrqLine)(uint8_t bus, uint8_t dev, uint8_t fn);
} HAL_PCI;
typedef struct HAL_INTC {
+2 -2
View File
@@ -1,6 +1,6 @@
// hal_apic.c - Local APIC + I/O APIC
#include "hal_apic.h"
#include "hal_api.h"
#include <hal_apic.h>
#include <hal_api.h>
#include <stdint.h>
extern HAL_API gApi;
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
// hal_debug.h - HAL debug output function
// Do not include outside HAL.
void HalDbgPrint(const char* fmt, ...);
+62 -12
View File
@@ -1,15 +1,26 @@
// hal_disk.c - drive registry; drivers register themselves via IoRegisterDisk
#include "hal_api.h"
#include "io_driver.h"
// hal_disk.c - drive registry; drivers register themselves via IoRegisterDisk.
//
// This file lives in the HAL layer and must NOT reference any kernel sync
// objects (KeEventCreate, KeSignalEvent, KeWaitForEvent, ObDereferenceObject).
// All IRP event signalling is done in kernel/ke_irp.c, which sits above us
// in the dependency graph.
//
// 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.
#include <stdint.h>
#include <stdbool.h>
#include <hal_api.h>
#include <io_driver.h>
#include <ke_irp.h>
#define MAX_DRIVES 8
static DISK_DRIVER gDrives[MAX_DRIVES];
static bool gPresent[MAX_DRIVES];
KSTATUS IoRegisterDisk(uint8_t index, const DISK_DRIVER* driver) {
KSTATUS IoRegisterDisk(uint8_t index, const DISK_DRIVER *driver) {
if (index >= MAX_DRIVES || !driver) return STATUS_INVALID_PARAMETER;
if (gPresent[index]) return STATUS_ALREADY_EXISTS;
gDrives[index] = *driver;
@@ -17,27 +28,66 @@ KSTATUS IoRegisterDisk(uint8_t index, const DISK_DRIVER* driver) {
return STATUS_SUCCESS;
}
// -------------------------------------------------------------------------
// 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.
// ke_irp.c must NOT touch irp->CompletionEvent
// yet; the driver will fire it.
// 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.
KSTATUS HalDiskDispatch(uint8_t drive, IRP *irp) {
if (drive >= MAX_DRIVES || !gPresent[drive]) return STATUS_NOT_FOUND;
DISK_DRIVER *drv = &gDrives[drive];
if (drv->SubmitIrp)
return drv->SubmitIrp(drv->Ctx, irp); // STATUS_PENDING
// Sync fallback: execute inline, write result, let caller fire hooks.
bool write = !!(irp->Flags & IRP_FLAG_WRITE);
if (write)
irp->Status = drv->WriteSectors(drv->Ctx, irp->Lba, irp->SectorCount, irp->Buffer);
else
irp->Status = drv->ReadSectors (drv->Ctx, irp->Lba, irp->SectorCount, irp->Buffer);
return STATUS_SUCCESS; // ran synchronously
}
// -------------------------------------------------------------------------
// Legacy HAL_DISK vtable — direct sync calls, no IRP involvement.
// Existing callers (FAT32, shell) use this path unchanged.
// -------------------------------------------------------------------------
static KSTATUS DiskRead(uint8_t drive, uint64_t lba,
uint32_t count, void* buf) {
uint32_t count, void *buf) {
if (drive >= MAX_DRIVES || !gPresent[drive]) return STATUS_NOT_FOUND;
return gDrives[drive].ReadSectors(lba, count, buf);
return gDrives[drive].ReadSectors(gDrives[drive].Ctx, lba, count, buf);
}
static KSTATUS DiskWrite(uint8_t drive, uint64_t lba,
uint32_t count, const void* buf) {
uint32_t count, const void *buf) {
if (drive >= MAX_DRIVES || !gPresent[drive]) return STATUS_NOT_FOUND;
return gDrives[drive].WriteSectors(lba, count, buf);
return gDrives[drive].WriteSectors(gDrives[drive].Ctx, lba, count, buf);
}
static bool DiskPresent(uint8_t drive) {
return drive < MAX_DRIVES && gPresent[drive];
}
static uint64_t DiskSectorCount(uint8_t drive) {
if (drive >= MAX_DRIVES || !gPresent[drive]) return 0;
return gDrives[drive].SectorCount;
}
void HalDiskInitialize(HAL_DISK* out) {
out->ReadSectors = DiskRead;
out->WriteSectors = DiskWrite;
out->DrivePresent = DiskPresent;
void HalDiskInitialize(HAL_DISK *out) {
out->ReadSectors = DiskRead;
out->WriteSectors = DiskWrite;
out->DrivePresent = DiskPresent;
out->DriveSectorCount = DiskSectorCount;
}
+260 -33
View File
@@ -1,71 +1,298 @@
// hal_keyboard.c - PS/2 keyboard, interrupt-driven, 256-entry ring buffer
#include "hal_api.h"
#include "hal_apic.h"
#include "hal_debug.h"
// hal_keyboard.c – PS/2 keyboard, interrupt-driven, 256-entry ring buffer
//
// Scan-code set 1 (XT-compatible), as output by the 8042's built-in
// translation layer. Supports make/break for all standard keys, Shift /
// Ctrl / Alt / CapsLock / NumLock modifiers, and the E0-prefixed extended
// key set (arrows, Home/End/PgUp/PgDn/Ins/Del, RCtrl, RAlt, numpad-Enter,
// numpad-divide).
//
// Threading model
// ---------------
// Single-producer (ISR on IRQ 1) / single-consumer ring buffer. All
// ISR-written state is declared volatile. On a uniprocessor, volatile
// plus the natural atomicity of aligned int reads/writes is sufficient.
// On SMP, add acquire/release memory barriers around gHead and gTail.
//
// Caller responsibilities
// -----------------------
// 1. Register KbdIsr with your interrupt dispatcher for IRQ 1.
// 2. Send the APIC/PIC EOI *after* KbdIsr returns, in the surrounding
// wrapper – not inside KbdIsr itself.
// 3. Unmask IRQ 1 in the APIC or 8259 before expecting any interrupts.
//
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <hal_api.h>
#include <hal_apic.h>
#include <hal_debug.h>
#define PS2_DATA 0x60
#define PS2_STAT 0x64
#define PS2_CMD 0x64
// -- Port addresses ------------------------------------------------------------
#define PS2_DATA 0x60u
#define PS2_STAT 0x64u
#define PS2_CMD 0x64u
#define KBD_BUF_SIZE 256
// -- Ring buffer ---------------------------------------------------------------
// KBD_BUF_SIZE must be a power of two; KBD_BUF_MASK replaces % with &.
#define KBD_BUF_SIZE 256u
#define KBD_BUF_MASK (KBD_BUF_SIZE - 1u)
static uint8_t gBuf[KBD_BUF_SIZE];
static volatile int gHead = 0, gTail = 0;
static void (*gCallback)(uint8_t) = (void *)0;
static uint8_t gBuf[KBD_BUF_SIZE];
static volatile int gHead = 0; // consumer read pointer (updated by reader)
static volatile int gTail = 0; // producer write pointer (updated by ISR)
// Minimal Set-2 to ASCII translation (unshifted only).
static const char gScanToAscii[128] = {
0,0,'1','2','3','4','5','6','7','8','9','0','-','=','\b',
'\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
0,'a','s','d','f','g','h','j','k','l',';','\'','`',
0,'\\','z','x','c','v','b','n','m',',','.','/',0,'*',
0,' ',0,0,0,0,0,0,0,0,0,0,0,0,0,
'7','8','9','-','4','5','6','+','1','2','3','0','.',0,0,0,
// -- Scan-code set 1 constants -------------------------------------------------
#define SC1_BREAK_BIT 0x80u // OR'd into every break (key-up) scan code
#define SC1_MAKE_MASK 0x7Fu // complement of SC1_BREAK_BIT; avoids ~uint cast
#define SC1_EXT_PREFIX 0xE0u // one-byte prefix for extended keys
// -- Modifier flags ------------------------------------------------------------
#define MOD_LSHIFT (1u << 0)
#define MOD_RSHIFT (1u << 1)
#define MOD_LCTRL (1u << 2)
#define MOD_RCTRL (1u << 3)
#define MOD_LALT (1u << 4)
#define MOD_RALT (1u << 5)
#define MOD_CAPS (1u << 6) // toggle, not a held key
#define MOD_NUMLOCK (1u << 7) // toggle, not a held key
#define MOD_SHIFT (MOD_LSHIFT | MOD_RSHIFT)
#define MOD_CTRL (MOD_LCTRL | MOD_RCTRL)
#define MOD_ALT (MOD_LALT | MOD_RALT)
static volatile uint32_t gModifiers = 0;
static volatile bool gNextExtended = false; // previous byte was 0xE0
// -- Raw-scancode callback (invoked from ISR context) --------------------------
// The pointer must be volatile so the compiler re-reads it on every ISR entry
// rather than caching the value seen at startup.
static void (* volatile gCallback)(uint8_t) = NULL;
// -- ASCII translation tables – scan-code set 1, non-extended keys only --------
// Index = 7-bit make code (SC1_BREAK_BIT cleared). 0 = no printable glyph.
// Numpad digits yield their digit characters; a NumLock-aware wrapper can
// substitute cursor-movement codes (e.g. HAL_KEY_HOME) when NumLock is clear.
//
// Layout verified against the IBM PC/AT scan-code set 1 reference.
static const char gUnshifted[128] = {
/*00*/ 0,
/*01*/ 0, // ESC
/*02*/ '1','2','3','4','5','6','7','8','9','0','-','=', // 02–0D
/*0E*/ '\b',
/*0F*/ '\t',
/*10*/ 'q','w','e','r','t','y','u','i','o','p','[',']', // 10–1B
/*1C*/ '\n',
/*1D*/ 0, // Left Ctrl
/*1E*/ 'a','s','d','f','g','h','j','k','l',';','\'','`', // 1E–29
/*2A*/ 0, // Left Shift
/*2B*/ '\\',
/*2C*/ 'z','x','c','v','b','n','m',',','.','/', // 2C–35
/*36*/ 0, // Right Shift
/*37*/ '*', // Numpad * (also PrtSc on some keyboards)
/*38*/ 0, // Left Alt
/*39*/ ' ',
/*3A*/ 0, // Caps Lock
/*3B*/ 0,0,0,0,0,0,0,0,0,0, // F1–F10 (3B–44)
/*45*/ 0, // Num Lock
/*46*/ 0, // Scroll Lock
/*47*/ '7','8','9', // Numpad 7–9
/*4A*/ '-', // Numpad –
/*4B*/ '4','5','6', // Numpad 4–6
/*4E*/ '+', // Numpad +
/*4F*/ '1','2','3', // Numpad 1–3
/*52*/ '0', // Numpad 0
/*53*/ '.', // Numpad .
/*54*/ 0,0,0,
/*57*/ 0, // F11
/*58*/ 0, // F12
/*59*/ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 59–68
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 69–78
0,0,0,0,0,0,0, // 79–7F
};
static const char gShifted[128] = {
/*00*/ 0,
/*01*/ 0, // ESC
/*02*/ '!','@','#','$','%','^','&','*','(',')','_','+', // 02–0D
/*0E*/ '\b',
/*0F*/ '\t',
/*10*/ 'Q','W','E','R','T','Y','U','I','O','P','{','}', // 10–1B
/*1C*/ '\n',
/*1D*/ 0, // Left Ctrl
/*1E*/ 'A','S','D','F','G','H','J','K','L',':','"','~', // 1E–29
/*2A*/ 0, // Left Shift
/*2B*/ '|',
/*2C*/ 'Z','X','C','V','B','N','M','<','>','?', // 2C–35
/*36*/ 0, // Right Shift
/*37*/ '*',
/*38*/ 0, // Left Alt
/*39*/ ' ',
/*3A*/ 0, // Caps Lock
/*3B*/ 0,0,0,0,0,0,0,0,0,0, // F1–F10
/*45*/ 0,
/*46*/ 0,
/*47*/ '7','8','9',
/*4A*/ '-',
/*4B*/ '4','5','6',
/*4E*/ '+',
/*4F*/ '1','2','3',
/*52*/ '0',
/*53*/ '.',
/*54*/ 0,0,0,
/*57*/ 0, // F11
/*58*/ 0, // F12
/*59*/ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,
};
// -- Interrupt service routine --------------------------------------------------
// Called with interrupts disabled (inside the hardware IRQ handler).
// The caller's wrapper must send the APIC/PIC EOI after this returns.
static void KbdIsr(void) {
uint8_t sc = __inbyte(PS2_DATA);
if (sc & 0x80) return; // key-up, ignore
int next = (gTail + 1) % KBD_BUF_SIZE;
if (next != gHead) {
gBuf[gTail] = sc;
gTail = next;
// E0 prefix: the following byte is an extended make/break code.
if (sc == SC1_EXT_PREFIX) {
gNextExtended = true;
return;
}
if (gCallback) gCallback(sc);
bool extended = gNextExtended;
bool is_break = (sc & SC1_BREAK_BIT) != 0;
uint8_t make = sc & SC1_MAKE_MASK;
gNextExtended = false;
// -- Update modifier state -------------------------------------------------
if (!extended) {
switch (make) {
case 0x2A: // Left Shift
gModifiers = is_break ? (gModifiers & ~MOD_LSHIFT)
: (gModifiers | MOD_LSHIFT);
return;
case 0x36: // Right Shift
gModifiers = is_break ? (gModifiers & ~MOD_RSHIFT)
: (gModifiers | MOD_RSHIFT);
return;
case 0x1D: // Left Ctrl
gModifiers = is_break ? (gModifiers & ~MOD_LCTRL)
: (gModifiers | MOD_LCTRL);
return;
case 0x38: // Left Alt
gModifiers = is_break ? (gModifiers & ~MOD_LALT)
: (gModifiers | MOD_LALT);
return;
case 0x3A: // Caps Lock – toggle on make only
if (!is_break) gModifiers ^= MOD_CAPS;
return;
case 0x45: // Num Lock – toggle on make only
if (!is_break) gModifiers ^= MOD_NUMLOCK;
return;
default:
break;
}
} else {
// Extended make codes that map to modifier keys.
switch (make) {
case 0x1D: // Right Ctrl (E0 1D)
gModifiers = is_break ? (gModifiers & ~MOD_RCTRL)
: (gModifiers | MOD_RCTRL);
return;
case 0x38: // Right Alt / AltGr (E0 38)
gModifiers = is_break ? (gModifiers & ~MOD_RALT)
: (gModifiers | MOD_RALT);
return;
default:
break;
}
// All other extended keys (arrows, Home/End, etc.) fall through
// and are enqueued as make codes for consumer-side interpretation.
}
// -- Discard break codes for non-modifier keys -----------------------------
if (is_break) return;
// -- Enqueue make code; silently drop if buffer is full --------------------
int next = (gTail + 1) & (int)KBD_BUF_MASK;
if (next != gHead) {
gBuf[gTail] = make;
gTail = next;
}
// -- Notify optional callback with the raw make code -----------------------
// Take a local snapshot so the compiler cannot reorder the NULL check and
// the call across a volatile barrier, and to avoid double-fetching.
void (*cb)(uint8_t) = (void (*)(uint8_t))gCallback;
if (cb) cb(make);
}
// -- Consumer API --------------------------------------------------------------
static bool KbdPoll(void) {
return gHead != gTail;
}
// Blocking read. Spins with PAUSE until a make code is available.
// On a preemptive kernel, replace the _mm_pause() loop with a
// condition variable or sti/hlt to avoid wasting CPU cycles.
static uint8_t KbdReadScancode(void) {
while (gHead == gTail) { _mm_pause(); }
while (gHead == gTail) {
_mm_pause();
}
uint8_t sc = gBuf[gHead];
gHead = (gHead + 1) % KBD_BUF_SIZE;
gHead = (gHead + 1) & (int)KBD_BUF_MASK;
return sc;
}
// Returns the ASCII character for the next keypress, applying the current
// Shift and CapsLock state. Returns 0 for keys with no ASCII mapping
// (function keys, arrows, unmodified Ctrl/Alt keys, etc.).
//
// CapsLock inverts shift only for alphabetic keys (a–z); digit and symbol
// keys are always governed solely by the physical Shift state.
static char KbdReadChar(void) {
uint8_t sc = KbdReadScancode();
if (sc < 128) return gScanToAscii[sc];
return 0;
uint8_t make = KbdReadScancode();
if (make >= 128) return 0;
uint32_t mods = gModifiers; // snapshot; safe outside ISR
bool shifted = (mods & MOD_SHIFT) != 0;
// CapsLock toggles the effective shift for alphabetic keys only.
if (mods & MOD_CAPS) {
char base = gUnshifted[make];
if (base >= 'a' && base <= 'z') {
shifted = !shifted;
}
}
return shifted ? gShifted[make] : gUnshifted[make];
}
static void KbdSetCallback(void (*cb)(uint8_t sc)) {
static void KbdSetCallback(void (*cb)(uint8_t)) {
gCallback = cb;
}
// Returns the current modifier bitmask (OR of MOD_* flags above).
// Snapshot is taken outside ISR and may lag by at most one interrupt.
static uint32_t KbdGetModifiers(void) {
return gModifiers;
}
// -- Initializer ---------------------------------------------------------------
void HalKeyboardInitialize(HAL_KEYBOARD *out) {
// Flush PS/2 output buffer
while (__inbyte(PS2_STAT) & 1) __inbyte(PS2_DATA);
// Drain any stale bytes sitting in the PS/2 output buffer before
// enabling interrupts, so the first ISR call sees a clean state.
while (__inbyte(PS2_STAT) & 1) {
__inbyte(PS2_DATA);
}
out->Poll = KbdPoll;
out->ReadScancode = KbdReadScancode;
out->ReadChar = KbdReadChar;
out->SetCallback = KbdSetCallback;
out->GetModifiers = KbdGetModifiers;
out->Isr = KbdIsr;
out->IrqNumber = 1;
}
+4 -4
View File
@@ -1,10 +1,10 @@
// hal_main.c - HAL initialisation and HalGetApi
#include "hal_api.h"
#include "hal_apic.h"
#include <hal_api.h>
#include <hal_apic.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include "printf.h"
#include <printf.h>
// Declared in other hal_*.c files:
void HalScreenInitialize(uintptr_t fb, int w, int h, int stride, HAL_SCREEN* out);
@@ -310,7 +310,7 @@ static void HalShutdown(void) {
if (gPm1bCntBlk) __outword(gPm1bCntBlk, valB);
// Firmware should have powered off by now. If it didn't, halt.
for (;;) __halt();
while (true) __halt();
}
// ---------------------------------------------------------------------------
+10 -3
View File
@@ -1,6 +1,6 @@
// hal_pci.c - PCI configuration space via Mechanism 1 port I/O
#include "hal_api.h"
#include <stdint.h>
#include <hal_api.h>
#define PCI_CONFIG_ADDR 0xCF8
#define PCI_CONFIG_DATA 0xCFC
@@ -55,9 +55,16 @@ static bool PciFindClass(uint8_t classCode, uint8_t subclass,
return false;
}
static uint8_t PciGetIrqLine(uint8_t bus, uint8_t dev, uint8_t fn) {
// PCI config register 0x3C: byte 0 = IRQ line, byte 1 = IRQ pin
uint32_t val = PciRead32(bus, dev, fn, 0x3C);
return (uint8_t)(val & 0xFF); // 0xFF = not connected
}
void HalPciInitialize(HAL_PCI* out) {
out->ConfigRead32 = PciRead32;
out->ConfigWrite32 = PciWrite32;
out->Read32 = PciRead32;
out->Write32 = PciWrite32;
out->FindDevice = PciFindDevice;
out->FindClass = PciFindClass;
out->GetIrqLine = PciGetIrqLine;
}
+1 -1
View File
@@ -3,10 +3,10 @@
// The 8x16 bitmap font is a standard VGA/PC font. We embed just the
// printable ASCII range (0x20–0x7E). Each glyph is 16 bytes; bit 7 of
// each byte is the leftmost pixel.
#include "hal_api.h"
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <hal_api.h>
static uint32_t* gFb = (uint32_t *)0;
static int gWidth = 0;
+4 -4
View File
@@ -1,9 +1,9 @@
// hal_timer.c - Local APIC timer, calibrated against PIT channel 2
#include "hal_api.h"
#include "hal_apic.h"
#include "hal_debug.h"
// hal_timer.c - Local APIC timer
#include <stdint.h>
#include <intrin.h>
#include <hal_api.h>
#include <hal_apic.h>
#include <hal_debug.h>
#define LAPIC_TIMER_LVT 0x320
#define LAPIC_TIMER_INIT 0x380
+30 -7
View File
@@ -1,17 +1,40 @@
#pragma once
// io_driver.h - driver-side interface for registering disk controllers
#include "hal_api.h"
#include "ke_status.h"
#include "../kernel/io_file.h"
// io_driver.h - driver-side interface for registering disk controllers
//
// DISK_DRIVER.SubmitIrp is the async path. IRP is forward-declared here so
// this header (and hal_disk.c) remain free of kernel sync-object dependencies.
// Drivers that implement SubmitIrp include ke_irp.h themselves.
#include <stdint.h>
#include <hal_api.h>
#include <ke_status.h>
#include <io_file.h>
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.
struct IRP;
typedef struct DISK_DRIVER {
KSTATUS (*ReadSectors)(uint64_t lba, uint32_t count, void* buf);
KSTATUS (*WriteSectors)(uint64_t lba, uint32_t count, const void* buf);
void *Ctx;
// --- Synchronous path (always present) ---------------------------------
KSTATUS (*ReadSectors) (void *ctx, uint64_t lba, uint32_t count, void *buf);
KSTATUS (*WriteSectors)(void *ctx, uint64_t lba, uint32_t count, const void *buf);
// --- Asynchronous path (optional) --------------------------------------
// NULL = driver has not implemented async; IoSubmitIrp() falls back to
// the sync path. Returns STATUS_PENDING when the IRP is accepted.
KSTATUS (*SubmitIrp)(void *ctx, struct IRP *irp);
uint64_t SectorCount;
} DISK_DRIVER;
KSTATUS IoRegisterDisk(uint8_t driveIndex, const DISK_DRIVER* driver);
KSTATUS IoRegisterDisk (uint8_t driveIndex, const DISK_DRIVER *driver);
// Internal dispatch used by ke_irp.c. Calls SubmitIrp if present, otherwise
// runs the sync callbacks inline and writes irp->Status; does NOT touch any
// kernel sync objects so it is safe to call from the HAL layer.
// Returns STATUS_PENDING (async path taken) or STATUS_SUCCESS (ran inline).
KSTATUS HalDiskDispatch (uint8_t drive, struct IRP *irp);
+1 -1
View File
@@ -1,8 +1,8 @@
// ini_parser.c - minimal [section] key=value parser
// Used by the bootloader to read boot.ini and drivers/drivers.ini.
// No heap; caller provides fixed-size output buffers.
#include "ini_parser.h"
#include <string.h>
#include <ini_parser.h>
static inline int IsWs(char c) { return c == ' ' || c == '\t' || c == '\r'; }
+1 -1
View File
@@ -2,7 +2,7 @@
// boot_info.h - shared between bootx64.efi and kernel.exe
// The bootloader fills this structure and passes a pointer to KernelMain.
#include <stdint.h>
#include "memory_map.h"
#include <memory_map.h>
#define BOOT_INFO_MAGIC 0x424F4F54494E464FULL /* 'BOOTINFO' */
#define MAX_BOOT_MODULES 32
+4 -4
View File
@@ -1,9 +1,9 @@
// io_file.c - VFS dispatch layer
#include "ke_list.h"
#include "io_file.h"
#include "mm_virt.h"
#include "ob_object.h"
#include <string.h>
#include <ke_list.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ob_object.h>
#define MAX_DRIVES 8
+2 -2
View File
@@ -2,10 +2,10 @@
// io_file.h - VFS layer
// Filesystem drivers register an IO_FILESYSTEM vtable via IoRegisterFilesystem.
// Kernel and user code open files through IoOpenFile.
#include "ke_status.h"
#include "ob_object.h"
#include <stdint.h>
#include <stddef.h>
#include <ke_status.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Filesystem driver vtable - filled in by fat32.sys (or any future fs driver)
+6 -2
View File
@@ -1,9 +1,10 @@
// ke_debug.c - debugging utilities
#include "hal_api.h"
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include "printf.h"
#include <hal_api.h>
#include <ke_spinlock.h>
#include <printf.h>
int _fltused; // fuckass
int gDebugEnabled = 0;
@@ -16,10 +17,13 @@ void kernel_putchar(char c) {
void DbgPrint(const char* fmt, ...) {
if (!gDebugEnabled) return;
static SPINLOCK sl = {0};
uintptr_t flags = KeAcquireSpinlock(&sl);
va_list va;
va_start(va, fmt);
vprintf(fmt, va);
va_end(va);
KeReleaseSpinlock(&sl, flags);
}
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v) {
+7 -6
View File
@@ -1,8 +1,9 @@
// ke_event.c - synchronisation events
#include "ke_event.h"
#include "ke_thread.h"
#include "ke_sched.h"
#include "ob_object.h"
#include <stdbool.h>
#include <ke_event.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ob_object.h>
static void EventDestroy(OBJECT* obj) { (void)obj; }
@@ -33,6 +34,7 @@ void KeSignalEvent(EVENT* event) {
flags = KeAcquireSpinlock(&event->Lock);
}
} else {
event->Signaled = 1;
if (!LIST_IS_EMPTY(&event->WaitList)) {
LIST_ENTRY* node = ListPopHead(&event->WaitList);
THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink);
@@ -40,7 +42,6 @@ void KeSignalEvent(EVENT* event) {
KeUnblockThread(t);
return;
}
event->Signaled = 1;
}
KeReleaseSpinlock(&event->Lock, flags);
}
@@ -56,7 +57,7 @@ KSTATUS KeWaitForEvent(EVENT* event) {
if (!event) return STATUS_INVALID_PARAMETER;
THREAD* self = KeCurrentThread();
for (;;) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&event->Lock);
if (event->Signaled) {
if (event->Type == EventAutoReset) event->Signaled = 0;
+4 -4
View File
@@ -1,10 +1,10 @@
#pragma once
// ke_event.h - auto-reset and manual-reset events
#include "ob_object.h"
#include "ke_spinlock.h"
#include "ke_list.h"
#include "ke_status.h"
#include <stdint.h>
#include <ob_object.h>
#include <ke_spinlock.h>
#include <ke_list.h>
#include <ke_status.h>
typedef enum EVENT_TYPE { EventAutoReset, EventManualReset } EVENT_TYPE;
+1 -1
View File
@@ -8,9 +8,9 @@
// 0x20 ring-3 data (DPL=3) - note: 0x1B/0x23 = 0x18|3 / 0x20|3
// 0x28 TSS low
// 0x30 TSS high
#include "ke_idt.h"
#include <stdint.h>
#include <string.h>
#include <ke_idt.h>
// External assembly procedures
extern void KeFlushGdt(void* pGdtr, uint16_t codeSel, uint16_t dataSel, uint16_t tssSel);
+112
View File
@@ -0,0 +1,112 @@
// ke_irp.c - I/O Request Packet dispatch and queue helpers
//
// This file lives in the kernel layer and is the ONLY place that bridges the
// IRP abstraction to kernel sync objects. All calls to KeEventCreate,
// KeSignalEvent, KeWaitForEvent, KeSignalSemaphore, and ObDereferenceObject
// are confined here; hal_disk.c and io_driver.h remain free of those symbols.
//
// Dependency graph (→ = includes / calls):
// ke_irp.c → ke_irp.h (IRP type, IRP_QUEUE, forward decls)
// → ke_event.h (KeEventCreate, KeSignalEvent, KeWaitForEvent)
// → ke_semaphore.h (KeSignalSemaphore)
// → ob_object.h (ObDereferenceObject)
// → io_driver.h (HalDiskDispatch)
// hal_disk.c → io_driver.h only (no ke_event.h, no ke_semaphore.h)
#include <ke_irp.h>
#include <ke_event.h>
#include <ke_semaphore.h>
#include <ob_object.h>
#include <io_driver.h>
// -------------------------------------------------------------------------
// IRP_QUEUE operations
// -------------------------------------------------------------------------
// These are defined here (not as static inlines in ke_irp.h) so that
// ke_irp.h can avoid including ke_semaphore.h, keeping the header safe
// for inclusion from the HAL layer.
void IrpQueueInit(IRP_QUEUE *q, struct SEMAPHORE *sem) {
q->Lock = (SPINLOCK)SPINLOCK_INIT;
q->Semaphore = sem;
LIST_INIT(&q->Head);
}
void IrpQueueEnqueue(IRP_QUEUE *q, IRP *irp) {
uintptr_t flags = KeAcquireSpinlock(&q->Lock);
if (irp->Flags & IRP_FLAG_PRIORITY)
ListInsertHead(&q->Head, &irp->Link);
else
ListInsertTail(&q->Head, &irp->Link);
KeReleaseSpinlock(&q->Lock, flags);
// Signal outside the spinlock — safe, and avoids priority-inversion risk.
KeSignalSemaphore((SEMAPHORE *)q->Semaphore);
}
IRP *IrpQueueDequeue(IRP_QUEUE *q) {
uintptr_t flags = KeAcquireSpinlock(&q->Lock);
LIST_ENTRY *node = ListPopHead(&q->Head);
KeReleaseSpinlock(&q->Lock, flags);
if (!node) return (IRP *)0;
return CONTAINING_RECORD(node, IRP, Link);
}
// -------------------------------------------------------------------------
// IoSubmitIrp
// -------------------------------------------------------------------------
// Calls HalDiskDispatch() which is HAL-layer safe (no event references).
// If the dispatch ran inline (sync fallback), we fire the completion hooks
// here in the kernel layer where we can legally call KeSignalEvent.
KSTATUS IoSubmitIrp(IRP *irp) {
if (!irp) return STATUS_INVALID_PARAMETER;
KSTATUS st = HalDiskDispatch(irp->DriveIndex, irp);
if (st == STATUS_NOT_FOUND)
return STATUS_NOT_FOUND;
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.
if (irp->CompletionEvent)
KeSignalEvent((EVENT *)irp->CompletionEvent);
if (irp->CompletionFn)
irp->CompletionFn(irp, irp->CompletionCtx);
}
// Return STATUS_PENDING in both cases so callers have a uniform contract:
// "completion is signalled via the event/callback, not this return value."
return STATUS_PENDING;
}
// -------------------------------------------------------------------------
// IoSubmitIrpSync
// -------------------------------------------------------------------------
// Creates a stack-allocated auto-reset event, attaches it to the IRP,
// submits, and blocks. Callers that already provided a CompletionEvent
// should use IoSubmitIrp() directly and wait on their own event instead.
KSTATUS IoSubmitIrpSync(IRP *irp) {
if (!irp) return STATUS_INVALID_PARAMETER;
EVENT *ev = (EVENT *)0;
KSTATUS st = KeEventCreate(EventAutoReset, 0, &ev);
if (!KSUCCESS(st)) return st;
// Attach our event; preserve any callback the caller may have set.
irp->CompletionEvent = ev;
st = IoSubmitIrp(irp);
// IoSubmitIrp always returns STATUS_PENDING on the non-error paths.
// The event is signalled either by the driver worker (async) or by
// IoSubmitIrp itself (sync fallback), so KeWaitForEvent will always
// return promptly in the sync-fallback case.
if (st == STATUS_PENDING)
KeWaitForEvent(ev);
// Release our reference to the temporary event object.
ObDereferenceObject(&ev->Header);
return irp->Status;
}
+95
View File
@@ -0,0 +1,95 @@
#pragma once
// 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
// 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.
//
// Files that actually call KeEventCreate / KeSignalEvent / KeWaitForEvent
// (ke_irp.c, ahci.c) include ke_event.h and ke_semaphore.h themselves.
#include <stdint.h>
#include <stdbool.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_status.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;
// -------------------------------------------------------------------------
// IRP flags
// -------------------------------------------------------------------------
#define IRP_FLAG_WRITE (1u << 0) // direction: write to device
#define IRP_FLAG_PRIORITY (1u << 1) // insert at head of queue (elevated)
// -------------------------------------------------------------------------
// Forward declaration (IRP is self-referential via CompletionFn)
// -------------------------------------------------------------------------
typedef struct IRP IRP;
// Completion callback: invoked in driver worker context; must not block.
typedef void (*IRP_COMPLETION_FN)(IRP *irp, void *ctx);
// -------------------------------------------------------------------------
// I/O Request Packet
// -------------------------------------------------------------------------
typedef struct IRP {
// Embedded list node — driver-internal; do not touch after submit.
LIST_ENTRY Link;
// ---- Request parameters (caller fills before IoSubmitIrp) ------------
uint8_t DriveIndex;
uint32_t Flags; // IRP_FLAG_*
uint64_t Lba;
uint32_t SectorCount;
void *Buffer; // kernel VA; valid until completion
// ---- Completion (caller fills before submit; driver writes Status) ----
volatile KSTATUS Status;
// Forward-declared pointer: callers that create events include ke_event.h.
struct EVENT *CompletionEvent;
IRP_COMPLETION_FN CompletionFn;
void *CompletionCtx;
// ---- Driver-internal --------------------------------------------------
uint8_t HwSlot;
void *DriverCtx;
} IRP;
// -------------------------------------------------------------------------
// IRP_QUEUE (per-device, driver-internal)
// -------------------------------------------------------------------------
typedef struct IRP_QUEUE {
SPINLOCK Lock;
LIST_ENTRY Head;
struct SEMAPHORE *Semaphore; // forward-declared pointer
} IRP_QUEUE;
// Non-inline: defined in ke_irp.c, which can pull in ke_semaphore.h safely.
void IrpQueueInit (IRP_QUEUE *q, struct SEMAPHORE *sem);
void IrpQueueEnqueue (IRP_QUEUE *q, IRP *irp);
IRP *IrpQueueDequeue (IRP_QUEUE *q);
// -------------------------------------------------------------------------
// Public dispatch API — implemented in kernel/ke_irp.c
// -------------------------------------------------------------------------
// Submit asynchronously. Returns STATUS_PENDING on success.
KSTATUS IoSubmitIrp (IRP *irp);
// Submit and block until complete. Returns irp->Status.
KSTATUS IoSubmitIrpSync(IRP *irp);
+2 -2
View File
@@ -1,6 +1,6 @@
// ke_irq.c — IRQ dispatch, kernel-side
#include "ke_irq.h"
#include "hal_api.h"
#include <ke_irq.h>
#include <hal_api.h>
#define MAX_IRQS 24
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "hal_api.h"
#include <stdint.h>
#include <hal_api.h>
void KeIrqInitialize(const HAL_API *hal);
void KeIrqRegister(uint8_t irq, void (*handler)(void));
+7 -6
View File
@@ -1,9 +1,10 @@
// ke_mutex.c - binary ownership mutex
#include "ke_mutex.h"
#include "ke_thread.h"
#include "ke_sched.h"
#include "mm_virt.h"
#include "ob_object.h"
#include <stdbool.h>
#include <ke_mutex.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <mm_virt.h>
#include <ob_object.h>
static void MutexDestroy(OBJECT* obj) { (void)obj; }
@@ -23,7 +24,7 @@ KSTATUS KeAcquireMutex(MUTEX* mutex) {
if (!mutex) return STATUS_INVALID_PARAMETER;
THREAD* self = KeCurrentThread();
for (;;) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&mutex->Lock);
if (!mutex->Owner) {
mutex->Owner = self;
+4 -4
View File
@@ -1,9 +1,9 @@
#pragma once
// ke_mutex.h - binary ownership mutex
#include "ob_object.h"
#include "ke_spinlock.h"
#include "ke_list.h"
#include "ke_status.h"
#include <ob_object.h>
#include <ke_spinlock.h>
#include <ke_list.h>
#include <ke_status.h>
typedef struct MUTEX {
OBJECT Header;
+5 -5
View File
@@ -1,11 +1,11 @@
// ke_process.c - process model
#include "ke_process.h"
#include "ke_thread.h"
#include "ke_event.h"
#include "mm_virt.h"
#include "ob_object.h"
#include <string.h>
#include <intrin.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_event.h>
#include <mm_virt.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Global process list
+4 -4
View File
@@ -2,10 +2,10 @@
// ke_process.h - process model
// A process owns a handle table and a list of threads.
// There is one shared address space - processes do not own one.
#include "ob_object.h"
#include "ke_list.h"
#include "ke_spinlock.h"
#include "ke_event.h"
#include <ob_object.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_event.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
+6 -6
View File
@@ -1,9 +1,9 @@
// ke_queue.c - ring-buffer message queue
#include "ke_queue.h"
#include "ke_thread.h"
#include "ke_sched.h"
#include "ob_object.h"
#include <string.h>
#include <ke_queue.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ob_object.h>
static void QueueDestroy(OBJECT* obj) { (void)obj; }
@@ -24,7 +24,7 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking) {
if (!queue || !msg) return STATUS_INVALID_PARAMETER;
THREAD* self = KeCurrentThread();
for (;;) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
if (queue->Count < QUEUE_DEPTH) {
queue->Ring[queue->Tail] = *msg;
@@ -56,7 +56,7 @@ KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking) {
if (!queue || !msg) return STATUS_INVALID_PARAMETER;
THREAD* self = KeCurrentThread();
for (;;) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
if (queue->Count > 0) {
*msg = queue->Ring[queue->Head];
+4 -4
View File
@@ -2,12 +2,12 @@
// ke_queue.h - fixed-depth message queue
// Processes share an address space, so a pointer in Param[] is valid
// on both sides. The queue is only a synchronisation and ordering tool.
#include "ob_object.h"
#include "ke_spinlock.h"
#include "ke_list.h"
#include "ke_status.h"
#include <stdint.h>
#include <stdbool.h>
#include <ob_object.h>
#include <ke_spinlock.h>
#include <ke_list.h>
#include <ke_status.h>
#define QUEUE_MESSAGE_SIZE 64 /* bytes, fixed */
#define QUEUE_DEPTH 256
+7 -6
View File
@@ -1,10 +1,11 @@
// ke_sched.c - MLFQ scheduler
#include "ke_sched.h"
#include "ke_thread.h"
#include "ke_spinlock.h"
#include "ke_list.h"
#include "mm_virt.h"
#include <stddef.h>
#include <stdbool.h>
#include <ke_sched.h>
#include <ke_thread.h>
#include <ke_spinlock.h>
#include <ke_list.h>
#include <mm_virt.h>
#define DEMOTION_QUANTA 4 /* ticks at a level before demotion */
@@ -20,7 +21,7 @@ THREAD* KeCurrentThread(void) { return gCurrentThread; }
void KeSetCurrentThread(THREAD* t){ gCurrentThread = t; }
static void IdleLoop(void) {
for (;;) { __halt(); }
while (true) { __halt(); }
}
void KeInitializeScheduler(void) {
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
// ke_sched.h - Multi-Level Feedback Queue scheduler
// Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick.
#include "ke_thread.h"
#include <ke_thread.h>
#define QUANTUM_MS 10 /* base time slice in milliseconds */
+6 -5
View File
@@ -1,8 +1,9 @@
// ke_semaphore.c - counting semaphore
#include "ke_semaphore.h"
#include "ke_thread.h"
#include "ke_sched.h"
#include "ob_object.h"
#include <stdbool.h>
#include <ke_semaphore.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ob_object.h>
static void SemDestroy(OBJECT* obj) { (void)obj; }
@@ -24,7 +25,7 @@ KSTATUS KeWaitSemaphore(SEMAPHORE* sem) {
if (!sem) return STATUS_INVALID_PARAMETER;
THREAD* self = KeCurrentThread();
for (;;) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&sem->Lock);
if (sem->Count > 0) {
sem->Count--;
+4 -4
View File
@@ -1,10 +1,10 @@
#pragma once
// ke_semaphore.h - counting semaphore
#include "ob_object.h"
#include "ke_spinlock.h"
#include "ke_list.h"
#include "ke_status.h"
#include <stdint.h>
#include <ob_object.h>
#include <ke_spinlock.h>
#include <ke_list.h>
#include <ke_status.h>
typedef struct SEMAPHORE {
OBJECT Header;
+1
View File
@@ -11,6 +11,7 @@ typedef uint32_t KSTATUS;
#define STATUS_FAILURE ((KSTATUS)0x0001)
#define STATUS_NOT_SUPPORTED ((KSTATUS)0x0002)
#define STATUS_TIMEOUT ((KSTATUS)0x0003)
#define STATUS_PENDING ((KSTATUS)0x0004)
// --- Validation & Parameters (0x0010) ---
#define STATUS_INVALID_PARAMETER ((KSTATUS)0x0010)
+16 -16
View File
@@ -30,22 +30,22 @@
// call: the object model already handled it; the dedicated syscall was only
// adding a footgun where callers could bypass the unified refcount path.
#include "ke_thread.h"
#include "ke_sched.h"
#include "ke_process.h"
#include "ke_mutex.h"
#include "ke_semaphore.h"
#include "ke_event.h"
#include "ke_queue.h"
#include "ke_debug.h"
#include "mm_virt.h"
#include "ldr_image.h"
#include "syscall_nr.h"
#include "hal_api.h"
#include "io_file.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_process.h>
#include <ke_mutex.h>
#include <ke_semaphore.h>
#include <ke_event.h>
#include <ke_queue.h>
#include <ke_debug.h>
#include <mm_virt.h>
#include <ldr_image.h>
#include <syscall_nr.h>
#include <hal_api.h>
#include <io_file.h>
// ---------------------------------------------------------------------------
// HAL pointer - set once at boot by KeSyscallSetHal()
@@ -106,18 +106,18 @@ static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2,
PROCESS* proc = t ? t->Owner : NULL;
if (proc) KeExitProcess(proc, (int)a0);
if (t) KeExitThread(t, (int)a0);
for (;;) ; // unreachable - KeExitThread does not return
while (true) ; // unreachable - KeExitThread does not return
}
// ---------------------------------------------------------------------------
// SYS_EXIT_THREAD (1) a0 = exitCode
// SYS_EXIT_THREAD (true) a0 = exitCode
// ---------------------------------------------------------------------------
static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
THREAD* t = KeCurrentThread();
if (t) KeExitThread(t, (int)a0);
for (;;) ;
while (true) ;
}
// ---------------------------------------------------------------------------
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include <stdint.h>
#include "hal_api.h"
#include <hal_api.h>
// Set the HAL API pointer for syscalls
void KeSyscallSetHal(const HAL_API* hal);
+4 -4
View File
@@ -1,10 +1,10 @@
// ke_thread.c - thread model
#include "ke_thread.h"
#include "ke_sched.h"
#include "mm_virt.h"
#include "ob_object.h"
#include <string.h>
#include <intrin.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <mm_virt.h>
#include <ob_object.h>
static uint32_t gNextThreadId = 1;
extern void KeThreadEntry(void);
+3 -3
View File
@@ -1,10 +1,10 @@
#pragma once
// ke_thread.h - thread model
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
#include "ob_object.h"
#include "ke_list.h"
#include "ke_process.h"
#include <stdint.h>
#include <ob_object.h>
#include <ke_list.h>
#include <ke_process.h>
#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */
#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */
+114 -83
View File
@@ -1,22 +1,22 @@
// main.c - kernel entry point
#include "boot_info.h"
#include "mm_phys.h"
#include "mm_virt.h"
#include "ob_object.h"
#include "ke_process.h"
#include "ke_thread.h"
#include "ke_sched.h"
#include "ke_idt.h"
#include "ke_irq.h"
#include "ke_syscall.h"
#include "ke_debug.h"
#include "ldr_image.h"
#include "hal_api.h"
#include "io_file.h"
#include "ini_parser.h"
// kmain.c - kernel entry point
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <boot_info.h>
#include <mm_phys.h>
#include <mm_virt.h>
#include <ob_object.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_idt.h>
#include <ke_irq.h>
#include <ke_syscall.h>
#include <ke_debug.h>
#include <ldr_image.h>
#include <hal_api.h>
#include <io_file.h>
#include <ini_parser.h>
// dynamic arrays bitch
typedef struct {
@@ -72,6 +72,11 @@ typedef struct {
#define arr_free(arr) MmFreePool((Header*)(arr) - 1)
typedef struct {
BOOT_INFO* bootInfo;
const HAL_API* hal;
} SYSTEM_INIT_ARGS;
typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *);
// HAL pointer cached at boot so kernel threads can reach it without
@@ -107,7 +112,7 @@ static void DriversIniCb(const char* section, const char* key,
}
static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
DbgPrint("KernelMain: Initializing drivers...\n");
DbgPrint("InitializeDrivers: Initializing drivers...\n");
// boot drivers (provided by the bootloader)
for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) {
@@ -117,13 +122,13 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
size_t len = strlen(name);
if (len < 4 || strcmp(name + len - 4, ".sys")) continue;
DbgPrint("KernelMain: Executing DriverEntry for %s\n", name);
DbgPrint("InitializeDrivers: Executing DriverEntry for %s\n", name);
DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(bootInfo->Modules[i].VirtBase));
KSTATUS ds = entry(hal);
if (!KSUCCESS(ds)) {
DbgPrint("KernelMain: Driver %s failed to init!\n", name);
DbgPrint("InitializeDrivers: Driver %s failed to init!\n", name);
}
}
@@ -133,17 +138,17 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
s = IoOpenFile("drivers\\drivers.ini", &f);
if (s != STATUS_SUCCESS) {
DbgPrint("KernelMain: Failed to open drivers.ini\n");
_disable(); while(1) __halt();
DbgPrint("InitializeDrivers: Failed to open drivers.ini\n");
_disable(); while(true) __halt();
}
uint64_t size = IoGetFileSize(f) + 1;
char* mem = MmAllocatePool(size);
size_t got;
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
DbgPrint("KernelMain: Failed to read drivers.ini\n");
DbgPrint("InitializeDrivers: Failed to read drivers.ini\n");
IoCloseFile(f);
_disable(); while(1) __halt();
_disable(); while(true) __halt();
}
mem[got] = '\0';
@@ -159,17 +164,18 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
size_t len = strlen(runtimeKeys[i]);
char* path = MmAllocatePool(len + 10);
if (!path) {
DbgPrint("KernelMain: Failed to allocate memory for driver path. How?\n");
DbgPrint("InitializeDrivers: Failed to allocate memory for driver path. How?\n");
continue;
}
// memcpy manouvers
memcpy(path, "drivers\\", 8);
memcpy(path + 8, runtimeKeys[i], len + 1);
// Assemble the path
strncpy(path, "drivers\\", sizeof(path) - 1);
path[sizeof(path) - 1] = '\0';
strncat(path, runtimeKeys[i], sizeof(path) - strlen(path) - 1);
s = IoOpenFile(path, &f);
if (s != STATUS_SUCCESS) {
DbgPrint("KernelMain: Failed to open runtime driver: %s\n", runtimeKeys[i]);
DbgPrint("InitializeDrivers: Failed to open runtime driver: %s\n", runtimeKeys[i]);
continue; // Skip this driver but keep going with others
}
@@ -178,7 +184,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
uint64_t drvSize = IoGetFileSize(f);
void* drvMem = MmAllocatePool(drvSize);
if (!KSUCCESS(IoReadFile(f, drvMem, drvSize, &got))) {
DbgPrint("KernelMain: Failed to read runtime driver: %s\n", runtimeKeys[i]);
DbgPrint("InitializeDrivers: Failed to read runtime driver: %s\n", runtimeKeys[i]);
IoCloseFile(f);
continue;
}
@@ -186,7 +192,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
uintptr_t mappedBase = 0;
if (!KSUCCESS(LdrLoadImage(drvMem, drvSize, &mappedBase, false))) { // Load into kernel space
DbgPrint("KernelMain: Failed to load runtime driver: %s\n", runtimeKeys[i]);
DbgPrint("InitializeDrivers: Failed to load runtime driver: %s\n", runtimeKeys[i]);
continue;
}
@@ -194,7 +200,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
KSTATUS ds = entry(hal);
if (!KSUCCESS(ds)) {
DbgPrint("KernelMain: Driver %s failed to init!\n", runtimeKeys[i]);
DbgPrint("InitializeDrivers: Driver %s failed to init!\n", runtimeKeys[i]);
}
}
@@ -223,7 +229,7 @@ static void LoadAndRegisterDll(const char* filename) {
// Read the DLL from disk
s = IoOpenFile(filename, &f);
if (s != STATUS_SUCCESS) {
DbgPrint("KernelMain: Failed to open DLL: %s\n", filename);
DbgPrint("LoadAndRegisterDll: Failed to open DLL: %s\n", filename);
return;
}
@@ -231,7 +237,7 @@ static void LoadAndRegisterDll(const char* filename) {
void* mem = MmAllocatePool(size); // Temporary pool for the raw file
size_t got;
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
DbgPrint("KernelMain: Failed to read %s\n", filename);
DbgPrint("LoadAndRegisterDll: Failed to read %s\n", filename);
IoCloseFile(f);
return;
}
@@ -242,7 +248,7 @@ static void LoadAndRegisterDll(const char* filename) {
uintptr_t mappedBase = 0;
s = LdrLoadImage(mem, size, &mappedBase, true);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: Failed to map %s\n", filename);
DbgPrint("LoadAndRegisterDll: Failed to map %s\n", filename);
return;
}
@@ -262,23 +268,23 @@ static PROCESS* LaunchUserProcess(const char* filePath) {
// Read the executable file into a raw buffer
s = IoOpenFile(filePath, &f);
DbgPrint("KernelMain: IoOpenFile: %d\n", s);
DbgPrint("LaunchUserProcess: IoOpenFile: %d\n", s);
if (s != STATUS_SUCCESS) {
DbgPrint("Failed to load file: \"%s\"!\n", filePath);
return (PROCESS*)0;
}
uint64_t size = IoGetFileSize(f);
DbgPrint("KernelMain: IoGetFileSize: %llu\n", size);
DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size);
void* mem = MmAllocateUserMemory(size);
DbgPrint("KernelMain: MmAllocateUserMemory: 0x%llX\n", mem);
DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem);
size_t got;
s = IoReadFile(f, mem, size, &got);
DbgPrint("KernelMain: IoReadFile: %d\n", s);
DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s);
IoCloseFile(f);
DbgPrint("KernelMain: IoCloseFile\n");
DbgPrint("LaunchUserProcess: IoCloseFile\n");
// Create the process object
PROCESS* proc;
@@ -295,17 +301,17 @@ static PROCESS* LaunchUserProcess(const char* filePath) {
uintptr_t imageBase = 0;
s = LdrLoadImage(mem, size, &imageBase, true);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: LdrLoadImage failed with status: %d\n", s);
DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s);
}
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
DbgPrint("KernelMain: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint);
DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint);
// Create the main thread, pointing it to the PE Entry Point
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: Failed to create thread. Status: %d\n", s);
DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s);
}
DbgPrint("KernelMain: Thread created and queued!\n");
DbgPrint("LaunchUserProcess: Thread created and queued!\n");
// Caller owns the reference returned by KeCreateProcess.
return proc;
@@ -318,11 +324,11 @@ static PROCESS* LaunchUserProcess(const char* filePath) {
static void ProcessWatcherThread(void* arg) {
PROCESS* proc = (PROCESS*)arg;
KeWaitForEvent(proc->ExitEvent);
DbgPrint("KernelMain: Process \"%s\" (PID %u) exited with code %d.\n",
DbgPrint("ProcessWatcherThread: Process \"%s\" (PID %u) exited with code %d.\n",
proc->Name, proc->ProcessId, proc->ExitCode);
ObDereferenceObject(&proc->Header);
DbgPrint("KernelMain: Shutting down.\n");
DbgPrint("ProcessWatcherThread: Shutting down.\n");
gKmainHal->Power.Shutdown(); // does not return
}
@@ -345,6 +351,47 @@ static void PrintProcessList() {
}
}
void SystemInitThread(void* arg) {
SYSTEM_INIT_ARGS* args = (SYSTEM_INIT_ARGS*)arg;
DbgPrint("SystemInitThread: Thread started. Igniting the system...\n");
InitializeDrivers(args->bootInfo, args->hal);
ResolveSystemImports(args->bootInfo);
DbgPrint("SystemInitThread: Loading system DLLs...\n");
LoadAndRegisterDll("syscall.dll");
LoadAndRegisterDll("win32.dll");
LoadAndRegisterDll("bink2w64.dll");
// Now that they are in memory and registered, we link them together
// (e.g., win32.dll resolving its imports against syscall.dll)
ResolveSystemImports(args->bootInfo);
// Print memory status now that the system is fully alive
DbgPrint("Memory Status:\n");
DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f));
DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f));
DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f));
args->hal->Screen.Clear(0x000000);
// Launch User-Mode Shell (this will trigger AHCI & FAT32 which CAN safely block now)
PROCESS* shellProc = LaunchUserProcess(args->bootInfo->CommandLine);
if (shellProc) {
KSTATUS ws = KeCreateKernelThread((uintptr_t)ProcessWatcherThread,
(uintptr_t)shellProc,
PriorityNormal, (THREAD**)0);
if (!KSUCCESS(ws)) {
DbgPrint("SystemInitThread: Failed to create watcher thread (%d) -- leaking proc ref.\n", ws);
}
}
MmFreePool(args);
}
// ---------------------------------------------------------------------------
// Main Kernel Entry
// ---------------------------------------------------------------------------
@@ -381,7 +428,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
.DebugEnabled = bootInfo->DebugEnabled
};
KSTATUS s = HalInitialize(&halParams);
if (!KSUCCESS(s)) for (;;) __halt(); // there's nothing we can do.
if (!KSUCCESS(s)) while (true) __halt(); // there's nothing we can do.
const HAL_API* hal = HalGetApi();
gKmainHal = hal;
@@ -400,53 +447,37 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
// Dynamic Linking & Driver Execution
//
ResolveSystemImports(bootInfo);
InitializeDrivers(bootInfo, hal);
//InitializeDrivers(bootInfo, hal);
//
// Scheduler Prep & Status Update
//
KeInitializeScheduler();
hal->Screen.Clear(0x000000);
DbgPrint("KernelMain: Loading system DLLs...\n");
LoadAndRegisterDll("syscall.dll");
LoadAndRegisterDll("win32.dll");
LoadAndRegisterDll("bink2w64.dll");
// Pack the arguments for the init thread
SYSTEM_INIT_ARGS* initArgs = MmAllocatePool(sizeof(SYSTEM_INIT_ARGS));
initArgs->bootInfo = bootInfo;
initArgs->hal = hal;
// Now that they are in memory and registered, we link them together
// (e.g., win32.dll resolving its imports against syscall.dll)
ResolveSystemImports(bootInfo);
//
// User-Mode Launch
//
PROCESS* shellProc = LaunchUserProcess(bootInfo->CommandLine);
// Spawn a kernel thread that blocks on the process exit event and prints
// when it fires. The watcher takes ownership of the LaunchUserProcess
// reference so we do not hold a dangling pointer in KernelMain.
if (shellProc) {
KSTATUS ws = KeCreateKernelThread((uintptr_t)ProcessWatcherThread,
(uintptr_t)shellProc,
PriorityNormal, (THREAD**)0);
if (!KSUCCESS(ws)) {
DbgPrint("KernelMain: Failed to create watcher thread (%d) -- leaking proc ref.\n", ws);
}
// Ownership of shellProc transferred to watcher; do not use it below.
// Spawn the Initialization Thread
THREAD* initThread = (THREAD*)0;
KSTATUS st = KeCreateKernelThread((uintptr_t)SystemInitThread,
(uintptr_t)initArgs,
PriorityNormal, &initThread);
if (!KSUCCESS(st)) {
DbgPrint("KernelMain: PANIC - Failed to create SystemInitThread! (0x%x)\n", st);
_disable();
for(;;) __halt();
}
//
// Ignite the System
//
DbgPrint("KernelMain: Starting scheduler... Handing over control.\n");
DbgPrint("KernelMain: Handing over to scheduler...\n");
// Ignite the system
_enable();
DbgPrint("Memory Status:\n");
DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f));
DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f));
DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f));
while (1) {
KeYield(); // nothing to do
// The Idle Loop: The CPU rests here whenever all other threads are waiting on events.
while (true) {
__halt();
}
}
+5 -9
View File
@@ -1,14 +1,10 @@
// ldr_image.c - PE32+ loader and export resolver
//
// At boot all images are already mapped by the bootloader; LdrLoadImage is
// used for any image loaded later (currently unused - all modules come in via
// BOOT_INFO). LdrGetExport scans the PE export directory of a registered module.
#include "ldr_image.h"
#include "boot_info.h"
#include "mm_virt.h"
#include "ke_debug.h"
#include <string.h>
#include <stdint.h>
#include <string.h>
#include <ldr_image.h>
#include <boot_info.h>
#include <mm_virt.h>
#include <ke_debug.h>
// ---------------------------------------------------------------------------
// PE32+ structures (just what we need)
+1 -1
View File
@@ -1,10 +1,10 @@
#pragma once
// ldr_image.h - minimal PE32+ loader
// Loads an already-mapped PE image and resolves its imports.
#include "ke_status.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <ke_status.h>
// Map a PE32+ image from 'buffer' into the kernel virtual address space.
// On success, *baseOut receives the virtual base address.
+4 -3
View File
@@ -6,10 +6,11 @@
// bootloader at PhysicalWindowBase (always 0xFFFF880000000000).
//
// Lock: a single spinlock guards Push/Pop. O(1) for both operations.
#include "mm_phys.h"
#include "ke_spinlock.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <mm_phys.h>
#include <ke_spinlock.h>
// ---------------------------------------------------------------------------
// Physical-to-virtual translation via the direct map window.
@@ -70,7 +71,7 @@ void MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) {
}
if (!stackPhys) {
for (;;) { __halt(); }
while (true) { __halt(); }
}
gFrameStack.Stack = (PHYSICAL_ADDRESS *)PhysToVirt(stackPhys);
+1 -1
View File
@@ -3,7 +3,7 @@
// Stack allocator over 4 KB frames, seeded from MEMORY_MAP.
#include <stdint.h>
#include <stddef.h>
#include "memory_map.h"
#include <memory_map.h>
#define PAGE_SIZE 4096ULL
#define PAGE_MASK (~(PAGE_SIZE - 1))
+3 -3
View File
@@ -12,10 +12,10 @@
// User heap:
// A single best-fit free list in the lower half.
// Backed on demand from the PMM. No per-process heaps.
#include "mm_virt.h"
#include "ke_spinlock.h"
#include <string.h>
#include <stddef.h>
#include <string.h>
#include <mm_virt.h>
#include <ke_spinlock.h>
// ---------------------------------------------------------------------------
// Helpers - physical window access
+2 -2
View File
@@ -3,8 +3,8 @@
// Manages the single shared PML4, kernel pool, and user heap.
#include <stdint.h>
#include <stddef.h>
#include "mm_phys.h"
#include "ke_status.h"
#include <mm_phys.h>
#include <ke_status.h>
// ---------------------------------------------------------------------------
// Page flags (x86-64 PTE bits)
+4 -4
View File
@@ -1,8 +1,8 @@
// ob_object.c - executive object manager
#include "ob_object.h"
#include "mm_virt.h" // MmAllocatePool / MmFreePool
#include <string.h>
#include <intrin.h>
#include <ob_object.h>
#include <mm_virt.h>
OBJECT* ObAllocateObject(OBJECT_TYPE type, size_t size) {
if (size < sizeof(OBJECT)) size = sizeof(OBJECT);
@@ -13,7 +13,7 @@ OBJECT* ObAllocateObject(OBJECT_TYPE type, size_t size) {
memset(obj, 0, size);
obj->Type = type;
obj->RefCount = 1;
obj->Destroy = (void *)0; /* caller fills this in */
obj->Destroy = (void *)0; // caller fills this in
return obj;
}
@@ -26,7 +26,7 @@ void ObDereferenceObject(OBJECT* object) {
if (!object) return;
long prev = _InterlockedDecrement(&object->RefCount);
if (prev == 0) {
/* Reference count hit zero - run the destructor then free. */
// Reference count hit zero - run the destructor then free.
if (object->Destroy) {
object->Destroy(object);
}
+2 -2
View File
@@ -4,7 +4,7 @@
// The OBJECT header must be the first member of any derived struct.
#include <stdint.h>
#include <stddef.h>
#include "ke_status.h"
#include <ke_status.h>
typedef enum OBJECT_TYPE {
ObjectTypeProcess = 1,
@@ -18,7 +18,7 @@ typedef enum OBJECT_TYPE {
typedef struct OBJECT {
OBJECT_TYPE Type;
volatile long RefCount; /* interlocked; long = 4 bytes on MSVC x64 */
volatile long RefCount; // interlocked; long = 4 bytes on MSVC x64
void (*Destroy)(struct OBJECT* self);
} OBJECT;
+30 -1
View File
@@ -1,6 +1,6 @@
// everyone needs a little libc
#include <stddef.h>
#include <stdint.h>
#include <stddef.h>
char* strcpy(char* dest, const char* src) {
char* d = dest;
@@ -16,6 +16,35 @@ char* strncpy(char* dest, const char* src, size_t n) {
return dest;
}
char* strcat(char* dest, const char* src) {
char* ptr = dest;
while (*ptr != '\0') {
ptr++;
}
while (*src != '\0') {
*ptr = *src;
ptr++;
src++;
}
*ptr = '\0';
return dest;
}
char* strncat(char* dest, const char* src, size_t n) {
char* ptr = dest;
while (*ptr != '\0') {
ptr++;
}
while (n > 0 && *src != '\0') {
*ptr = *src;
ptr++;
src++;
n--;
}
*ptr = '\0';
return dest;
}
int strcmp(const char* s1, const char* s2) {
while (*s1 && *s1 == *s2) s1++, s2++;
return (unsigned char)*s1 - (unsigned char)*s2;
+3 -2
View File
@@ -1,5 +1,6 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
// ---------------------------------------------------------------------------
@@ -76,7 +77,7 @@ static void printUInt(uint32_t n) {
static void readLine(HANDLE hIn, char* buf, int len) {
int pos = 0;
while (1) {
while (true) {
char c;
DWORD nRead;
ReadConsoleA(hIn, &c, 1, &nRead, 0);
@@ -165,7 +166,7 @@ int main() {
print("shell: ready\n");
while (1) {
while (true) {
print("> ");
readLine(hIn, lineBuf, MAX_CMD);
if (runCommand(trim(lineBuf)))
+1 -1
View File
@@ -1329,7 +1329,7 @@
#define RAD_STATEMENT_END_TRUE \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
} while(1) \
} while(true) \
__pragma(warning(pop))
#else
+4 -4
View File
@@ -103,7 +103,7 @@ __declspec(dllexport) void SetLastError(DWORD err) {
// ---------------------------------------------------------------------------
__declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) {
SysExitProcess((int)code);
for (;;) ; // unreachable
while (true) ; // unreachable
}
// ---------------------------------------------------------------------------
@@ -382,7 +382,7 @@ __declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule,
__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) {
(void)hProcess;
SysExitProcess((int)uExitCode);
for (;;) ;
while (true) ;
}
// STARTUPINFOA – only cb matters for our stub.
@@ -839,7 +839,7 @@ __declspec(dllexport) void RaiseException(DWORD dwExceptionCode, DWORD dwExcepti
(void)dwExceptionCode; (void)dwExceptionFlags;
(void)nNumberOfArguments; (void)lpArguments;
// Best-effort: nothing to unwind; just halt.
for (;;) ;
while (true) ;
}
typedef struct _EXCEPTION_POINTERS { void* ExceptionRecord; void* ContextRecord; } EXCEPTION_POINTERS;
@@ -910,7 +910,7 @@ __declspec(dllexport) void RtlUnwindEx(void* TargetFrame, void* TargetIp,
(void)TargetFrame; (void)TargetIp; (void)ExceptionRecord;
(void)ReturnValue; (void)ContextRecord; (void)HistoryTable;
// No real unwind possible; halt rather than corrupt the stack silently.
for (;;) ;
while (true) ;
}
// ---------------------------------------------------------------------------