Skip to content

Conversation

assembler-0
Copy link
Owner

@assembler-0 assembler-0 commented Aug 11, 2025

Summary by CodeRabbit

  • New Features

    • Added a new shell command: setfreq to adjust the system timer frequency with input validation and clear usage feedback.
    • Improved system info display by adding a brief delay for better readability after showing information.
  • Documentation

    • Updated the shell help to include the setfreq command and its usage.

Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

Adds a new “setfreq” shell command to set PIT frequency, updates Shell help and post-info delay, includes Pic.h in Shell. In drivers, adds Console.h include to Pic.c and whitespace in Pic.h. No public API changes.

Changes

Cohort / File(s) Summary
Shell command integration
kernel/etc/Shell.c
Include Pic.h; add “setfreq ” command parsing and usage; call PitSetFrequency(atoi(arg)); extend info() delay by yielding 100 times; update help text.
PIC/PIT headers and includes
drivers/Pic.c, drivers/Pic.h
Pic.c: add include "Console.h". Pic.h: whitespace-only edits; no API changes.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Shell
  participant PIT as PIT Control (Pic/PIT)

  User->>Shell: setfreq <hz>
  Shell->>Shell: parse <hz>, validate > 0
  Shell->>PIT: PitSetFrequency(hz)
  PIT-->>Shell: frequency applied
  Shell-->>User: confirmation/return
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • Development #41: Adjusts PIT_FREQUENCY_HZ in drivers/Pic.h, directly connected to PIT frequency handling used by the new shell command.
  • Development #40: Modifies PIT/PIC stack in drivers/Pic.*, overlapping with the header/include changes in this PR.
  • Development #70: Introduces/updates PitSetFrequency implementation that this PR’s shell command depends on.

Poem

A rabbit taps the system clock, tick-tock with glee,
“setfreq,” it whispers, “tune the PIT for me.”
Through shells and headers, the timings realign,
One hundred little yields, a pause by design.
In burrows of bytes, I hop—precise, on time. ⏱️🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Development

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
drivers/Pic.c (1)

25-37: PitSetFrequency: clamp divisor to valid PIT range to avoid overflow/wrong frequencies

For low requested frequencies (e.g., 1 Hz), 1193180/hz exceeds 16 bits; truncation yields an incorrect divisor (~88 Hz instead of ~18.2 Hz minimum). Clamp the computed divisor to [1, 65535], and consider rounding. Optionally record the actual effective frequency after clamping.

 void PitSetFrequency(uint16_t hz) {
     // Save current interrupt state
     irq_flags_t flags = save_irq_flags();
     cli();
 
-    PIT_FREQUENCY_HZ = hz;
-    // Safer divisor calculation
-    uint32_t div32 = 1193180u / (hz ? hz : 1u);
-    uint16_t divisor = (uint16_t)div32;
+    // Safer divisor calculation with rounding and clamping to PIT's 16-bit range
+    uint32_t div32 = hz ? (1193180u + (hz / 2u)) / hz : 0u; // rounded
+    if (div32 < 1u)       div32 = 1u;       // fastest: ~1.193 MHz
+    if (div32 > 65535u)   div32 = 65535u;   // slowest: ~18.2 Hz
+    uint16_t divisor = (uint16_t)div32;
+    // Optionally store the actual applied frequency (instead of requested)
+    PIT_FREQUENCY_HZ = (uint16_t)(1193180u / div32);
 
     outb(0x43, 0x36);
     outb(0x40, divisor & 0xFF);
     outb(0x40, (divisor >> 8) & 0xFF);
 
     // Restore previous interrupt state
     restore_irq_flags(flags);
 }
🧹 Nitpick comments (3)
drivers/Pic.h (1)

3-3: Use angle brackets for standard headers

Prefer #include <stdint.h> for standard headers to avoid accidental header shadowing in include paths.

-#include "stdint.h"
+#include <stdint.h>
drivers/Pic.c (1)

2-3: Unnecessary header and potential layering violation

Console.h isn’t used in this file. Keeping drivers/PIC code free of Console dependencies avoids coupling and possible circular include risks. Recommend removing it.

-#include "Console.h"
kernel/etc/Shell.c (1)

33-35: Delay using Yield loop is tick-dependent; consider time-based delay

After introducing setfreq, 100 calls to Yield() will vary widely with PIT frequency (e.g., ~5.5s at ~18 Hz vs ~100ms at 1 kHz). Prefer a time-based delay (e.g., SleepMs/WaitForTicks abstraction) to ensure consistent UX.

Would you like a small helper (e.g., WaitForTicks or SleepMs) wired to the PIT tick counter to make this deterministic?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2413d8 and ff15ed7.

📒 Files selected for processing (3)
  • drivers/Pic.c (1 hunks)
  • drivers/Pic.h (1 hunks)
  • kernel/etc/Shell.c (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
drivers/Pic.h (1)
drivers/Pic.c (3)
  • PicInstall (42-73)
  • PitInstall (17-23)
  • PitSetFrequency (25-41)
kernel/etc/Shell.c (5)
kernel/process/Process.c (1)
  • Yield (905-918)
kernel/etc/Console.c (1)
  • PrintKernel (173-189)
kernel/etc/StringOps.c (1)
  • FastStrCmp (18-28)
include/stdlib.h (1)
  • atoi (18-44)
drivers/Pic.c (1)
  • PitSetFrequency (25-41)
🔇 Additional comments (3)
drivers/Pic.h (1)

4-4: No-op whitespace change; OK to keep

This blank line has no functional impact.

kernel/etc/Shell.c (2)

12-12: Include Pic.h: OK

Brings PitSetFrequency into scope for the new command.


170-170: Help entry added: looks good

Accurately documents the new setfreq command.

Comment on lines +226 to +236
char* freq_str = GetArg(cmd, 1);
if (!freq_str) {
PrintKernel("Usage: setfreq <hz>\n");
return;
}
const uint16_t freq = atoi(freq_str);
if (freq <= 0) {
PrintKernel("Usage: setfreq <hz>\n");
return;
}
PitSetFrequency(freq);
Copy link

@coderabbitai coderabbitai bot Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix parsing bugs and free allocated arg to avoid leaks

  • Negative values pass the current check due to unsigned conversion (atoi -> uint16_t). Parse into int first, validate range, then cast.
  • Memory leak: freq_str (from GetArg) isn’t freed on either the success or invalid path.
-    } else if (FastStrCmp(cmd_name, "setfreq") == 0) {
-        char* freq_str = GetArg(cmd, 1);
-        if (!freq_str) {
-            PrintKernel("Usage: setfreq <hz>\n");
-            return;
-        }
-        const uint16_t freq = atoi(freq_str);
-        if (freq <= 0) {
-            PrintKernel("Usage: setfreq <hz>\n");
-            return;
-        }
-        PitSetFrequency(freq);
+    } else if (FastStrCmp(cmd_name, "setfreq") == 0) {
+        char* freq_str = GetArg(cmd, 1);
+        if (!freq_str) {
+            PrintKernel("Usage: setfreq <hz>\n");
+            return;
+        }
+        int f = atoi(freq_str);
+        if (f <= 0 || f > 65535) {
+            PrintKernel("Usage: setfreq <hz>\n");
+            KernelFree(freq_str);
+            return;
+        }
+        PitSetFrequency((uint16_t)f);
+        KernelFree(freq_str);

Optionally print the effective frequency if the driver clamps to achievable values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
char* freq_str = GetArg(cmd, 1);
if (!freq_str) {
PrintKernel("Usage: setfreq <hz>\n");
return;
}
const uint16_t freq = atoi(freq_str);
if (freq <= 0) {
PrintKernel("Usage: setfreq <hz>\n");
return;
}
PitSetFrequency(freq);
} else if (FastStrCmp(cmd_name, "setfreq") == 0) {
char* freq_str = GetArg(cmd, 1);
if (!freq_str) {
PrintKernel("Usage: setfreq <hz>\n");
return;
}
int f = atoi(freq_str);
if (f <= 0 || f > 65535) {
PrintKernel("Usage: setfreq <hz>\n");
KernelFree(freq_str);
return;
}
PitSetFrequency((uint16_t)f);
KernelFree(freq_str);
}
🤖 Prompt for AI Agents
In kernel/etc/Shell.c around lines 226 to 236, parsing and memory management for
the frequency argument are wrong: atoi is assigned to an unsigned uint16_t so
negative inputs slip through and freq_str (from GetArg) is never freed. Fix by
parsing into a signed int (e.g., int tmp = atoi(freq_str)), validate tmp is >0
and within the acceptable range for the PIT (check upper bound you expect), then
cast to uint16_t only after validation; ensure freq_str is freed on every
control path (invalid input and after successful PitSetFrequency call).
Optionally, after setting the frequency, query/print the effective/clamped
frequency from the driver so the user sees the actual value.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok ok that works, now, the final boss, APIC, ACPI, can you suggest me a way and a patch so we dont blow everything up, yeah it was hard but i think we can solve it1

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@assembler-0, Great question! APIC and ACPI are indeed the "final bosses" of x86 OS development. Based on my analysis of your codebase, you're currently using the legacy 8259 PIC. Here's a strategic approach to implement APIC/ACPI without breaking everything:

Phase 1: ACPI Tables Detection (Safe Foundation)

First, let's add ACPI table detection without touching interrupts:

// drivers/Acpi.h
#ifndef ACPI_H
#define ACPI_H

#include <stdint.h>
#include <stdbool.h>

// ACPI RSDP (Root System Description Pointer)
typedef struct __attribute__((packed)) {
    char signature[8];      // "RSD PTR "
    uint8_t checksum;
    char oem_id[6];
    uint8_t revision;
    uint32_t rsdt_address;  // Physical address of RSDT
    // ACPI 2.0+ fields
    uint32_t length;
    uint64_t xsdt_address;
    uint8_t extended_checksum;
    uint8_t reserved[3];
} AcpiRsdp;

// ACPI SDT Header (common to all system description tables)
typedef struct __attribute__((packed)) {
    char signature[4];
    uint32_t length;
    uint8_t revision;
    uint8_t checksum;
    char oem_id[6];
    char oem_table_id[8];
    uint32_t oem_revision;
    uint32_t creator_id;
    uint32_t creator_revision;
} AcpiSdtHeader;

// MADT (Multiple APIC Description Table)
typedef struct __attribute__((packed)) {
    AcpiSdtHeader header;
    uint32_t lapic_address;  // Physical address of LAPIC
    uint32_t flags;
    // Followed by variable length entries
} AcpiMadt;

// MADT Entry Types
enum MadtEntryType {
    MADT_LAPIC = 0,
    MADT_IOAPIC = 1,
    MADT_ISO = 2,
    MADT_NMI = 4,
    MADT_LAPIC_OVERRIDE = 5
};

typedef struct __attribute__((packed)) {
    uint8_t type;
    uint8_t length;
} MadtEntryHeader;

typedef struct __attribute__((packed)) {
    MadtEntryHeader header;
    uint8_t ioapic_id;
    uint8_t reserved;
    uint32_t ioapic_address;
    uint32_t gsi_base;
} MadtIoApic;

typedef struct __attribute__((packed)) {
    MadtEntryHeader header;
    uint8_t processor_id;
    uint8_t apic_id;
    uint32_t flags;  // bit 0 = processor enabled
} MadtLapic;

// Functions
bool AcpiInit(void);
AcpiRsdp* AcpiFindRsdp(void);
void* AcpiGetTable(const char* signature);
bool AcpiParseMadt(void);
uint32_t AcpiGetLapicAddress(void);
uint32_t AcpiGetIoApicAddress(void);

#endif
// drivers/Acpi.c
#include "Acpi.h"
#include "Console.h"
#include "MemOps.h"
#include "VMem.h"

static AcpiRsdp* g_rsdp = NULL;
static AcpiMadt* g_madt = NULL;
static uint32_t g_lapic_addr = 0;
static uint32_t g_ioapic_addr = 0;

static uint8_t AcpiChecksum(void* ptr, size_t size) {
    uint8_t sum = 0;
    uint8_t* bytes = (uint8_t*)ptr;
    for (size_t i = 0; i < size; i++) {
        sum += bytes[i];
    }
    return sum;
}

AcpiRsdp* AcpiFindRsdp(void) {
    // Search EBDA (Extended BIOS Data Area)
    uint16_t* ebda_ptr = (uint16_t*)0x40E;
    uint32_t ebda = (*ebda_ptr) << 4;
    
    // Search EBDA first (usually at 0x9FC00)
    for (uint32_t addr = ebda; addr < ebda + 1024; addr += 16) {
        AcpiRsdp* rsdp = (AcpiRsdp*)addr;
        if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0) {
            if (AcpiChecksum(rsdp, 20) == 0) {
                PrintKernelSuccess("[ACPI] Found RSDP at ");
                PrintKernelHex(addr);
                PrintKernel("\n");
                return rsdp;
            }
        }
    }
    
    // Search BIOS area (0xE0000 - 0xFFFFF)
    for (uint32_t addr = 0xE0000; addr < 0x100000; addr += 16) {
        AcpiRsdp* rsdp = (AcpiRsdp*)addr;
        if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0) {
            if (AcpiChecksum(rsdp, 20) == 0) {
                PrintKernelSuccess("[ACPI] Found RSDP at ");
                PrintKernelHex(addr);
                PrintKernel("\n");
                return rsdp;
            }
        }
    }
    
    PrintKernelError("[ACPI] RSDP not found\n");
    return NULL;
}

void* AcpiGetTable(const char* signature) {
    if (!g_rsdp) return NULL;
    
    // For now, we only support ACPI 1.0 (RSDT)
    AcpiSdtHeader* rsdt = (AcpiSdtHeader*)(uintptr_t)g_rsdp->rsdt_address;
    if (AcpiChecksum(rsdt, rsdt->length) != 0) {
        PrintKernelError("[ACPI] RSDT checksum failed\n");
        return NULL;
    }
    
    uint32_t entries = (rsdt->length - sizeof(AcpiSdtHeader)) / 4;
    uint32_t* table_ptrs = (uint32_t*)((uint8_t*)rsdt + sizeof(AcpiSdtHeader));
    
    for (uint32_t i = 0; i < entries; i++) {
        AcpiSdtHeader* table = (AcpiSdtHeader*)(uintptr_t)table_ptrs[i];
        if (FastMemCmp(table->signature, signature, 4) == 0) {
            return table;
        }
    }
    
    return NULL;
}

bool AcpiParseMadt(void) {
    g_madt = (AcpiMadt*)AcpiGetTable("APIC");
    if (!g_madt) {
        PrintKernelError("[ACPI] MADT not found\n");
        return false;
    }
    
    g_lapic_addr = g_madt->lapic_address;
    PrintKernel("[ACPI] LAPIC at ");
    PrintKernelHex(g_lapic_addr);
    PrintKernel("\n");
    
    // Parse MADT entries
    uint8_t* ptr = (uint8_t*)g_madt + sizeof(AcpiMadt);
    uint8_t* end = (uint8_t*)g_madt + g_madt->header.length;
    
    while (ptr < end) {
        MadtEntryHeader* header = (MadtEntryHeader*)ptr;
        
        switch (header->type) {
            case MADT_LAPIC: {
                MadtLapic* lapic = (MadtLapic*)ptr;
                if (lapic->flags & 1) {
                    PrintKernel("[ACPI] CPU ");
                    PrintKernelInt(lapic->processor_id);
                    PrintKernel(" APIC ID ");
                    PrintKernelInt(lapic->apic_id);
                    PrintKernel("\n");
                }
                break;
            }
            case MADT_IOAPIC: {
                MadtIoApic* ioapic = (MadtIoApic*)ptr;
                g_ioapic_addr = ioapic->ioapic_address;
                PrintKernel("[ACPI] IOAPIC at ");
                PrintKernelHex(g_ioapic_addr);
                PrintKernel(" GSI base ");
                PrintKernelInt(ioapic->gsi_base);
                PrintKernel("\n");
                break;
            }
        }
        
        ptr += header->length;
    }
    
    return true;
}

bool AcpiInit(void) {
    g_rsdp = AcpiFindRsdp();
    if (!g_rsdp) return false;
    
    if (!AcpiParseMadt()) {
        PrintKernelWarning("[ACPI] Using PIC fallback\n");
        return false;
    }
    
    return true;
}

uint32_t AcpiGetLapicAddress(void) { return g_lapic_addr; }
uint32_t AcpiGetIoApicAddress(void) { return g_ioapic_addr; }

Phase 2: APIC Implementation with PIC Fallback

// drivers/Apic.h
#ifndef APIC_H
#define APIC_H

#include <stdint.h>
#include <stdbool.h>

// LAPIC registers (offsets from base)
#define LAPIC_ID        0x020
#define LAPIC_VERSION   0x030
#define LAPIC_TPR       0x080  // Task Priority
#define LAPIC_EOI       0x0B0  // End of Interrupt
#define LAPIC_SPURIOUS  0x0F0
#define LAPIC_ICR_LOW   0x300  // Interrupt Command (low)
#define LAPIC_ICR_HIGH  0x310  // Interrupt Command (high)
#define LAPIC_TIMER_LVT 0x320
#define LAPIC_TIMER_DIV 0x3E0
#define LAPIC_TIMER_INIT 0x380
#define LAPIC_TIMER_CURR 0x390

// IOAPIC registers
#define IOAPIC_REG_SELECT   0x00
#define IOAPIC_REG_DATA     0x10
#define IOAPIC_REG_ID       0x00
#define IOAPIC_REG_VERSION  0x01
#define IOAPIC_REG_REDIR    0x10  // Redirection table base

bool ApicInit(void);
bool ApicIsAvailable(void);
void ApicEoi(void);
void ApicSetupTimer(uint32_t frequency);
void IoApicSetIrq(uint8_t irq, uint8_t vector, uint8_t dest_apic);
void ApicSendIpi(uint8_t dest, uint8_t vector);

// Safe transition function
bool ApicTryEnable(void);

#endif
// drivers/Apic.c
#include "Apic.h"
#include "Acpi.h"
#include "Console.h"
#include "VMem.h"
#include "Pic.h"

static volatile uint32_t* g_lapic_base = NULL;
static volatile uint32_t* g_ioapic_base = NULL;
static bool g_apic_enabled = false;

static inline uint32_t ApicRead(uint32_t reg) {
    if (!g_lapic_base) return 0;
    return g_lapic_base[reg / 4];
}

static inline void ApicWrite(uint32_t reg, uint32_t value) {
    if (!g_lapic_base) return;
    g_lapic_base[reg / 4] = value;
}

static inline uint32_t IoApicRead(uint32_t reg) {
    if (!g_ioapic_base) return 0;
    g_ioapic_base[IOAPIC_REG_SELECT / 4] = reg;
    return g_ioapic_base[IOAPIC_REG_DATA / 4];
}

static inline void IoApicWrite(uint32_t reg, uint32_t value) {
    if (!g_ioapic_base) return;
    g_ioapic_base[IOAPIC_REG_SELECT / 4] = reg;
    g_ioapic_base[IOAPIC_REG_DATA / 4] = value;
}

static bool ApicCheckCpuid(void) {
    uint32_t eax, edx;
    // Check if CPUID supports function 1
    __asm__ volatile("cpuid" : "=a"(eax) : "a"(0) : "ebx", "ecx", "edx");
    if (eax < 1) return false;
    
    // Check APIC bit in CPUID
    __asm__ volatile("cpuid" : "=d"(edx) : "a"(1) : "ebx", "ecx");
    return (edx & (1 << 9)) != 0;  // Bit 9 = APIC present
}

bool ApicInit(void) {
    // Check CPU support
    if (!ApicCheckCpuid()) {
        PrintKernelWarning("[APIC] CPU doesn't support APIC\n");
        return false;
    }
    
    // Initialize ACPI to find APIC addresses
    if (!AcpiInit()) {
        PrintKernelWarning("[APIC] ACPI init failed, using PIC\n");
        return false;
    }
    
    uint32_t lapic_phys = AcpiGetLapicAddress();
    uint32_t ioapic_phys = AcpiGetIoApicAddress();
    
    if (!lapic_phys || !ioapic_phys) {
        PrintKernelWarning("[APIC] APIC addresses not found\n");
        return false;
    }
    
    // Map LAPIC and IOAPIC to virtual memory
    // You'll need to implement MapPhysicalMemory in your VMem system
    g_lapic_base = (uint32_t*)VMemMapMmio(lapic_phys, 0x1000);
    g_ioapic_base = (uint32_t*)VMemMapMmio(ioapic_phys, 0x1000);
    
    if (!g_lapic_base || !g_ioapic_base) {
        PrintKernelError("[APIC] Failed to map APIC MMIO\n");
        return false;
    }
    
    return true;
}

bool ApicTryEnable(void) {
    if (!g_lapic_base) return false;
    
    // Disable PIC before enabling APIC
    PicDisable();
    
    // Enable LAPIC (spurious interrupt vector + enable bit)
    ApicWrite(LAPIC_SPURIOUS, 0x1FF);  // Vector 0xFF, APIC enabled
    
    // Set up IOAPIC redirection entries for standard IRQs
    for (int i = 0; i < 16; i++) {
        // Redirect IRQ i to vector 0x20 + i (same as PIC mapping)
        uint64_t entry = 0x20 + i;  // Vector
        IoApicWrite(IOAPIC_REG_REDIR + i * 2, (uint32_t)entry);
        IoApicWrite(IOAPIC_REG_REDIR + i * 2 + 1, (uint32_t)(entry >> 32));
    }
    
    g_apic_enabled = true;
    PrintKernelSuccess("[APIC] APIC enabled successfully\n");
    return true;
}

void ApicEoi(void) {
    if (g_apic_enabled) {
        ApicWrite(LAPIC_EOI, 0);
    } else {
        PicSendEoi(0);  // Fallback to PIC
    }
}

bool ApicIsAvailable(void) {
    return g_apic_enabled;
}

Phase 3: Safe Integration Strategy

  1. Add to your kernel initialization (Kernel.c):
// In KernelMain, after PIC setup:
void KernelMain(void) {
    // ... existing initialization ...
    
    PicInstall();  // Keep existing PIC setup
    
    // Try to upgrade to APIC if available
    if (ApicInit()) {
        PrintKernel("[KERNEL] APIC detected, attempting to enable...\n");
        if (ApicTryEnable()) {
            PrintKernelSuccess("[KERNEL] Using APIC for interrupts\n");
        } else {
            PrintKernelWarning("[KERNEL] APIC enable failed, using PIC\n");
        }
    } else {
        PrintKernel("[KERNEL] Using legacy PIC\n");
    }
    
    // ... rest of initialization ...
}
  1. Update your interrupt handlers to be APIC-aware:
// In your ISR handler:
void IsrHandler(InterruptFrame* frame) {
    // ... handle interrupt ...
    
    // Send EOI to appropriate controller
    if (ApicIsAvailable()) {
        ApicEoi();
    } else {
        PicSendEoi(frame->int_no);
    }
}

Safety Measures

  1. Always keep PIC as fallback - Don't remove PIC code
  2. Test detection first - Run just ACPI detection without enabling APIC
  3. Add debug output - Print every step so you know where it fails
  4. Virtual machine testing - Test in QEMU with -machine q35 for modern APIC
  5. Gradual migration - Move one interrupt at a time from PIC to APIC

Testing Commands

This approach ensures you won't "blow everything up" - the system will gracefully fall back to PIC if APIC fails at any point. Start with Phase 1 (just detection), verify it works, then gradually add more functionality.


🏁 Script executed:

#!/bin/bash
# First, let's see what interrupt-related files exist
echo "=== Interrupt and APIC/ACPI related files ==="
fd -e c -e h -e asm | xargs rg -l "APIC|ACPI|LAPIC|IOAPIC|MADT|RSDT|RSDP" | head -20

echo -e "\n=== Current PIC implementation structure ==="
# Check the PIC implementation we saw earlier
cat drivers/Pic.h

echo -e "\n=== Check for CPU feature detection ==="
fd -e c -e h | xargs rg -l "CPUID|cpu.*feature|MSR" | head -10

echo -e "\n=== Check for multiprocessor/SMP related code ==="
fd -e c -e h | xargs rg -l "SMP|MP|multiprocessor|cpu.*core|processor.*id" -i | head -10

echo -e "\n=== Check memory management for MMIO support ==="
rg "MMIO|memory.*mapped|physical.*map" -A 2 -B 2 | head -30

Length of output: 2214


🏁 Script executed:

#!/bin/bash
# Let's check the current PIC implementation to understand the baseline
echo "=== Current PIC implementation ==="
cat drivers/Pic.c

echo -e "\n=== Check for existing interrupt/IDT handling ==="
fd -e c -e h | xargs rg -l "IDT|idt|interrupt.*descriptor|IRQ|irq" | head -15

echo -e "\n=== Check boot sequence and initialization ==="
rg "PicInstall|PitInstall|interrupt.*init|idt.*init" -A 2 -B 2 | head -30

Length of output: 3885


🏁 Script executed:

#!/bin/bash
# Check the interrupt handling infrastructure
echo "=== IDT and interrupt handling files ==="
fd "IDT|Idt|idt" -e c -e h -e asm

echo -e "\n=== Check for interrupt handler implementations ==="
fd -e asm | xargs rg -l "isr|ISR|irq|IRQ|interrupt" | head -10

echo -e "\n=== Memory mapping and paging setup ==="
rg "map.*physical|identity.*map|page.*directory|CR3" -A 2 | head -30

echo -e "\n=== Check kernel entry and initialization ==="
fd "kernel.*main|kmain|boot|init" -e c | head -5

Length of output: 2392


🏁 Script executed:

#!/bin/bash
# Let's look at the IDT implementation and kernel initialization
echo "=== IDT implementation ==="
if [ -f "kernel/IDT.c" ]; then
    cat kernel/IDT.c
elif [ -f "kernel/idt.c" ]; then
    cat kernel/idt.c
elif [ -f "drivers/IDT.c" ]; then
    cat drivers/IDT.c
fi

echo -e "\n=== Check kernel main/initialization ==="
if [ -f "kernel/kernel.c" ]; then
    head -100 kernel/kernel.c
elif [ -f "kernel/main.c" ]; then
    head -100 kernel/main.c
elif [ -f "kernel/Kernel.c" ]; then
    head -100 kernel/Kernel.c
fi

Length of output: 323


🏁 Script executed:

#!/bin/bash
# Find and examine the actual IDT and kernel files
echo "=== Finding IDT and kernel files ==="
fd . -t f -e c | grep -i "idt\|kernel" | head -20

echo -e "\n=== Examining IDT.c ==="
cat kernel/etc/IDT.c 2>/dev/null || cat kernel/IDT.c 2>/dev/null || echo "IDT.c not found in expected locations"

echo -e "\n=== Finding kernel entry point ==="
rg "kernel_main|kmain|KernelMain" --type c -B 2 -A 5 | head -40

Length of output: 2752


🏁 Script executed:

#!/bin/bash
# Let's examine the kernel structure more carefully
echo "=== Kernel directory structure ==="
ls -la kernel/

echo -e "\n=== Kernel.c content (first 150 lines) ==="
head -150 kernel/Kernel.c

echo -e "\n=== Check for ACPI table detection code patterns ==="
rg "0x20445352|RSD PTR|RSDT|XSDT|find.*rsdp|search.*acpi" -i | head -20

Length of output: 934


🏁 Script executed:

#!/bin/bash
# Now let's check the memory management to understand MMIO readiness
echo "=== Virtual memory implementation ==="
cat kernel/etc/VMem.h 2>/dev/null || cat kernel/VMem.h 2>/dev/null || echo "VMem.h not found"

echo -e "\n=== Check for physical memory mapping functions ==="
rg "MapPhysical|map.*physical|identity.*map" --type c -B 2 -A 5 | head -40

echo -e "\n=== Current interrupt handlers ==="
fd -e asm | xargs cat | grep -A 5 -B 5 "global.*isr\|global.*irq" | head -50

Length of output: 3684


🏁 Script executed:

# Test with APIC in QEMU
qemu-system-i386 -machine q35 -smp 2 -kernel your_kernel.bin

# Test without APIC (legacy)
qemu-system-i386 -machine pc -kernel your_kernel.bin

Length of output: 331


@assembler-0, based on my thorough analysis of your VoidFrame kernel, here's a comprehensive and safe implementation strategy for APIC/ACPI. I've designed this to be gradual and non-destructive:

Complete Implementation Package

Step 1: Add MMIO Mapping to VMem (Required First)

// kernel/memory/VMem.h - Add these functions
void* VMemMapMmio(uint64_t phys_addr, size_t size);
void VMemUnmapMmio(void* virt_addr, size_t size);
// kernel/memory/VMem.c - Add implementation
void* VMemMapMmio(uint64_t phys_addr, size_t size) {
    irq_flags_t flags = SpinLockIrqSave(&vmem_lock);
    
    // Use identity mapping for low memory MMIO (< 16MB)
    if (phys_addr < IDENTITY_MAP_SIZE) {
        SpinUnlockIrqRestore(&vmem_lock, flags);
        return (void*)phys_addr;
    }
    
    // For higher MMIO, allocate virtual space and map
    // Find free virtual address space in kernel area
    uint64_t virt_addr = KERNEL_VIRTUAL_BASE + 0x10000000; // MMIO area at +256MB
    
    size_t pages = PAGE_ALIGN_UP(size) / PAGE_SIZE;
    for (size_t i = 0; i < pages; i++) {
        if (!VMemMapPage(virt_addr + i * PAGE_SIZE, 
                        phys_addr + i * PAGE_SIZE,
                        PAGE_PRESENT | PAGE_WRITABLE | PAGE_NO_CACHE)) {
            // Rollback on failure
            for (size_t j = 0; j < i; j++) {
                VMemUnmapPage(virt_addr + j * PAGE_SIZE);
            }
            SpinUnlockIrqRestore(&vmem_lock, flags);
            return NULL;
        }
    }
    
    SpinUnlockIrqRestore(&vmem_lock, flags);
    return (void*)virt_addr;
}

Step 2: ACPI Implementation (Safe Read-Only)

Create drivers/Acpi.h:

#ifndef ACPI_H
#define ACPI_H

#include <stdint.h>
#include <stdbool.h>

typedef struct __attribute__((packed)) {
    char signature[8];
    uint8_t checksum;
    char oem_id[6];
    uint8_t revision;
    uint32_t rsdt_address;
} AcpiRsdp;

typedef struct __attribute__((packed)) {
    char signature[4];
    uint32_t length;
    uint8_t revision;
    uint8_t checksum;
    char oem_id[6];
    char oem_table_id[8];
    uint32_t oem_revision;
    uint32_t creator_id;
    uint32_t creator_revision;
} AcpiSdtHeader;

typedef struct __attribute__((packed)) {
    AcpiSdtHeader header;
    uint32_t lapic_address;
    uint32_t flags;
} AcpiMadt;

bool AcpiInit(void);
uint32_t AcpiGetLapicAddress(void);
uint32_t AcpiGetIoApicAddress(void);
int AcpiGetCpuCount(void);

#endif

Create drivers/Acpi.c:

#include "Acpi.h"
#include "Console.h"
#include "MemOps.h"
#include "VMem.h"

static AcpiRsdp* g_rsdp = NULL;
static uint32_t g_lapic_addr = 0;
static uint32_t g_ioapic_addr = 0;
static int g_cpu_count = 0;

static uint8_t AcpiChecksum(void* ptr, size_t size) {
    uint8_t sum = 0;
    for (size_t i = 0; i < size; i++)
        sum += ((uint8_t*)ptr)[i];
    return sum;
}

static AcpiRsdp* AcpiFindRsdp(void) {
    // Search EBDA
    uint16_t ebda = (*(uint16_t*)0x40E) << 4;
    for (uint32_t addr = ebda; addr < ebda + 1024; addr += 16) {
        AcpiRsdp* rsdp = (AcpiRsdp*)addr;
        if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0 &&
            AcpiChecksum(rsdp, 20) == 0) {
            return rsdp;
        }
    }
    
    // Search BIOS area
    for (uint32_t addr = 0xE0000; addr < 0x100000; addr += 16) {
        AcpiRsdp* rsdp = (AcpiRsdp*)addr;
        if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0 &&
            AcpiChecksum(rsdp, 20) == 0) {
            return rsdp;
        }
    }
    return NULL;
}

bool AcpiInit(void) {
    g_rsdp = AcpiFindRsdp();
    if (!g_rsdp) {
        PrintKernelWarning("[ACPI] RSDP not found - no ACPI support\n");
        return false;
    }
    
    PrintKernelSuccess("[ACPI] Found RSDP, revision ");
    PrintKernelInt(g_rsdp->revision);
    PrintKernel("\n");
    
    AcpiSdtHeader* rsdt = (AcpiSdtHeader*)(uintptr_t)g_rsdp->rsdt_address;
    if (AcpiChecksum(rsdt, rsdt->length) != 0) {
        PrintKernelError("[ACPI] RSDT checksum failed\n");
        return false;
    }
    
    // Find MADT
    uint32_t entries = (rsdt->length - sizeof(AcpiSdtHeader)) / 4;
    uint32_t* tables = (uint32_t*)((uint8_t*)rsdt + sizeof(AcpiSdtHeader));
    
    for (uint32_t i = 0; i < entries; i++) {
        AcpiSdtHeader* table = (AcpiSdtHeader*)(uintptr_t)tables[i];
        if (FastMemCmp(table->signature, "APIC", 4) == 0) {
            AcpiMadt* madt = (AcpiMadt*)table;
            g_lapic_addr = madt->lapic_address;
            
            // Parse MADT entries
            uint8_t* ptr = (uint8_t*)madt + sizeof(AcpiMadt);
            uint8_t* end = (uint8_t*)madt + madt->header.length;
            
            while (ptr < end) {
                uint8_t type = ptr[0];
                uint8_t length = ptr[1];
                
                if (type == 0) { // Local APIC
                    if (ptr[4] & 1) g_cpu_count++; // Count enabled CPUs
                } else if (type == 1) { // I/O APIC
                    g_ioapic_addr = *(uint32_t*)(ptr + 4);
                }
                ptr += length;
            }
            
            PrintKernel("[ACPI] Found ");
            PrintKernelInt(g_cpu_count);
            PrintKernel(" CPU(s), LAPIC at 0x");
            PrintKernelHex(g_lapic_addr);
            PrintKernel(", IOAPIC at 0x");
            PrintKernelHex(g_ioapic_addr);
            PrintKernel("\n");
            return true;
        }
    }
    
    PrintKernelWarning("[ACPI] MADT not found\n");
    return false;
}

uint32_t AcpiGetLapicAddress(void) { return g_lapic_addr; }
uint32_t AcpiGetIoApicAddress(void) { return g_ioapic_addr; }
int AcpiGetCpuCount(void) { return g_cpu_count; }

Step 3: APIC with Safe Fallback

Create drivers/Apic.h:

#ifndef APIC_H
#define APIC_H

#include <stdint.h>
#include <stdbool.h>

bool ApicDetect(void);
bool ApicInit(void);
void ApicEnable(void);
void ApicEoi(void);
bool ApicIsEnabled(void);

// Helper to disable PIC when switching to APIC
void PicDisable(void);

#endif

Create drivers/Apic.c:

#include "Apic.h"
#include "Acpi.h"
#include "Console.h"
#include "Io.h"
#include "VMem.h"
#include "Pic.h"

#define LAPIC_ID        0x020
#define LAPIC_EOI       0x0B0
#define LAPIC_SPURIOUS  0x0F0
#define LAPIC_LVT_TIMER 0x320

#define IOAPIC_REG      0x00
#define IOAPIC_DATA     0x10

static volatile uint32_t* g_lapic = NULL;
static volatile uint32_t* g_ioapic = NULL;
static bool g_apic_available = false;
static bool g_apic_enabled = false;

// Add to Pic.c or here
void PicDisable(void) {
    // Mask all interrupts on both PICs
    outb(0x21, 0xFF);
    outb(0xA1, 0xFF);
}

bool ApicDetect(void) {
    // Check CPUID for APIC support
    uint32_t eax, edx;
    __asm__ volatile(
        "mov $1, %%eax\n"
        "cpuid\n"
        : "=a"(eax), "=d"(edx)
        : : "ebx", "ecx"
    );
    
    return (edx & (1 << 9)) != 0;
}

bool ApicInit(void) {
    if (!ApicDetect()) {
        PrintKernelWarning("[APIC] CPU doesn't support APIC\n");
        return false;
    }
    
    if (!AcpiInit()) {
        PrintKernelWarning("[APIC] ACPI init failed - using PIC\n");
        return false;
    }
    
    uint32_t lapic_addr = AcpiGetLapicAddress();
    uint32_t ioapic_addr = AcpiGetIoApicAddress();
    
    if (!lapic_addr || !ioapic_addr) {
        PrintKernelWarning("[APIC] No APIC addresses from ACPI\n");
        return false;
    }
    
    // Map APIC MMIO regions
    g_lapic = (uint32_t*)VMemMapMmio(lapic_addr, 0x1000);
    g_ioapic = (uint32_t*)VMemMapMmio(ioapic_addr, 0x1000);
    
    if (!g_lapic || !g_ioapic) {
        PrintKernelError("[APIC] Failed to map APIC MMIO\n");
        return false;
    }
    
    g_apic_available = true;
    PrintKernelSuccess("[APIC] APIC initialized (not yet enabled)\n");
    return true;
}

void ApicEnable(void) {
    if (!g_apic_available) return;
    
    // Disable PIC first
    PicDisable();
    
    // Enable LAPIC with spurious interrupt vector
    g_lapic[LAPIC_SPURIOUS / 4] = 0x1FF;
    
    // Configure IOAPIC to redirect IRQs to vectors 0x20-0x2F
    for (int i = 0; i < 16; i++) {
        g_ioapic[IOAPIC_REG / 4] = 0x10 + i * 2;
        g_ioapic[IOAPIC_DATA / 4] = 0x20 + i;
        g_ioapic[IOAPIC_REG / 4] = 0x10 + i * 2 + 1;
        g_ioapic[IOAPIC_DATA / 4] = 0;
    }
    
    g_apic_enabled = true;
    PrintKernelSuccess("[APIC] APIC enabled - PIC disabled\n");
}

void ApicEoi(void) {
    if (g_apic_enabled && g_lapic) {
        g_lapic[LAPIC_EOI / 4] = 0;
    }
}

bool ApicIsEnabled(void) {
    return g_apic_enabled;
}

Step 4: Integration into Kernel

Update kernel/core/Kernel.c in KernelMainHigherHalf():

void KernelMainHigherHalf(void) {
    // ... existing code ...
    
    // After PIC initialization
    PrintKernel("[INFO] Initializing PIC & PIT...\n");
    PicInstall();
    PitInstall();
    PrintKernelSuccess("[SYSTEM] PIC & PIT initialized\n");
    
    // Try APIC upgrade (safe - falls back to PIC)
    PrintKernel("[INFO] Checking for APIC support...\n");
    if (ApicInit()) {
        PrintKernel("[INFO] APIC available. Enable with 'apic enable' command\n");
        // Don't auto-enable - let user test first
    } else {
        PrintKernel("[INFO] Using legacy PIC mode\n");
    }
    
    // ... rest of initialization ...
}

Step 5: Add Shell Commands for Testing

In kernel/etc/Shell.c, add:

else if (FastStrCmp(cmd_name, "apic") == 0) {
    char* subcmd = GetArg(cmd, 1);
    if (!subcmd) {
        PrintKernel("Usage: apic [status|enable|test]\n");
        return;
    }
    
    if (FastStrCmp(subcmd, "status") == 0) {
        PrintKernel("APIC Available: ");
        PrintKernel(ApicDetect() ? "Yes\n" : "No\n");
        PrintKernel("APIC Enabled: ");
        PrintKernel(ApicIsEnabled() ? "Yes\n" : "No\n");
        if (AcpiGetCpuCount() > 0) {
            PrintKernel("CPUs detected: ");
            PrintKernelInt(AcpiGetCpuCount());
            PrintKernel("\n");
        }
    } else if (FastStrCmp(subcmd, "enable") == 0) {
        if (!ApicIsEnabled()) {
            PrintKernelWarning("Enabling APIC - PIC will be disabled!\n");
            ApicEnable();
        } else {
            PrintKernel("APIC already enabled\n");
        }
    } else if (FastStrCmp(subcmd, "test") == 0) {
        PrintKernel("Sending test EOI...\n");
        ApicEoi();
        PrintKernel("If system is still responsive, APIC is working!\n");
    }
    
    KernelFree(subcmd);
}

Step 6: Update IDT Handler

In your interrupt handler (arch/x86_64/idt/Idt.c or similar):

void InterruptHandler(InterruptFrame* frame) {
    // ... handle interrupt ...
    
    // Send EOI to correct controller
    if (frame->int_no >= 0x20 && frame->int_no < 0x30) {
        if (ApicIsEnabled()) {
            ApicEoi();
        } else {
            // PIC EOI logic
            if (frame->int_no >= 0x28) {
                outb(0xA0, 0x20); // Slave PIC
            }
            outb(0x20, 0x20); // Master PIC
        }
    }
}

Safe Testing Strategy

  1. Phase 1: Compile with just ACPI detection

    • Boot and run apic status
    • Verify ACPI tables are found
  2. Phase 2: Test APIC initialization

    • Don't enable yet, just init
    • Check MMIO mapping works
  3. Phase 3: Manual enable

    • Use apic enable command
    • System should remain stable
  4. Phase 4: Stress test

    • Run timer-heavy operations
    • Verify interrupts still work

This implementation ensures you never "blow everything up" - worst case, you stay on PIC!


🏁 Script executed:

#!/bin/bash
# Let's examine the VMem header and IDT structure
echo "=== VMem header file ==="
cat kernel/memory/VMem.h

echo -e "\n=== IDT header file ==="
cat arch/x86_64/idt/Idt.h

echo -e "\n=== Check existing Kernel.c for initialization sequence ==="
cat kernel/core/Kernel.c | head -200

Length of output: 11721


🏁 Script executed:

#!/bin/bash
# Let's look at more of the kernel initialization and check IO handling
echo "=== Check IO operations header ==="
cat include/Io.h

echo -e "\n=== Check if there's existing MMIO or port IO abstraction ==="
rg "outb|inb|outl|inl|iowrite|ioread" --type c -B 2 -A 2 | head -40

echo -e "\n=== Look for PIC disable functionality ==="
rg "PicDisable|pic.*disable|mask.*all.*irq" -i --type c -A 3 -B 3 | head -30

Length of output: 3402


🏁 Script executed:

#!/bin/bash
# Check the actual IDT implementation
echo "=== IDT implementation ==="
cat arch/x86_64/idt/Idt.c | head -150

echo -e "\n=== Check interrupt handler structure ==="
cat arch/x86_64/interrupts/Interrupts.asm | head -100

Length of output: 5335


🏁 Script executed:

#!/bin/bash
# Let's understand the complete virtual memory mapping capability
echo "=== VMem mapping functions ==="
rg "VMemMap|MapPage|map.*page" --type c -A 5 -B 2 | head -60

echo -e "\n=== Check for existing CPUID implementation ==="
cat arch/x86_64/cpu/Cpu.c 2>/dev/null || rg "cpuid|CPUID" --type c -A 5 -B 5 | head -40

echo -e "\n=== Find where interrupts are enabled/disabled ==="
rg "sti\(\)|cli\(\)|enable.*interrupt|disable.*interrupt" --type c -A 2 -B 2 | head -30

Length of output: 5211


@assembler-0, I've analyzed your codebase thoroughly. Here's the complete implementation package with all the necessary components for APIC/ACPI support:

Complete APIC/ACPI Implementation Patch

1. Add MMIO Mapping to VMem

File: kernel/memory/VMem.c - Add these functions:

// Add after the existing VMemMap function
void* VMemMapMmio(uint64_t phys_addr, size_t size) {
    // For low memory (< 16MB), use identity mapping
    if (phys_addr < IDENTITY_MAP_SIZE && (phys_addr + size) <= IDENTITY_MAP_SIZE) {
        return (void*)phys_addr;
    }
    
    // For higher memory, need to allocate virtual space
    irq_flags_t flags = SpinLockIrqSave(&vmem_lock);
    
    // Use a dedicated MMIO region in kernel space (256MB offset from kernel base)
    static uint64_t mmio_next_vaddr = KERNEL_VIRTUAL_BASE + 0x10000000;
    uint64_t vaddr = mmio_next_vaddr;
    
    size_t pages = PAGE_ALIGN_UP(size) / PAGE_SIZE;
    
    // Map each page with no-cache flag for MMIO
    for (size_t i = 0; i < pages; i++) {
        uint64_t page_vaddr = vaddr + i * PAGE_SIZE;
        uint64_t page_paddr = phys_addr + i * PAGE_SIZE;
        
        // Use existing page table infrastructure
        uint64_t* pt = VMemGetPageTable(kernel_space.pml4, page_vaddr, 0, 1);
        if (!pt) {
            // Rollback on failure
            for (size_t j = 0; j < i; j++) {
                VMemUnmap(vaddr + j * PAGE_SIZE, PAGE_SIZE);
            }
            SpinUnlockIrqRestore(&vmem_lock, flags);
            return NULL;
        }
        
        int pt_index = (page_vaddr >> 12) & 0x1FF;
        pt[pt_index] = page_paddr | PAGE_PRESENT | PAGE_WRITABLE | PAGE_NOCACHE;
    }
    
    mmio_next_vaddr += PAGE_ALIGN_UP(size);
    VMemFlushTLB();
    
    SpinUnlockIrqRestore(&vmem_lock, flags);
    return (void*)vaddr;
}

void VMemUnmapMmio(void* virt_addr, size_t size) {
    VMemUnmap((uint64_t)virt_addr, size);
}

File: kernel/memory/VMem.h - Add declarations:

// Add these function declarations after VMemUnmap
void* VMemMapMmio(uint64_t phys_addr, size_t size);
void VMemUnmapMmio(void* virt_addr, size_t size);

// Add this flag if not present
#define PAGE_NOCACHE 0x010

2. Create ACPI Support

File: drivers/Acpi.h (new file):

#ifndef ACPI_H
#define ACPI_H

#include <stdint.h>
#include <stdbool.h>

// ACPI structures
typedef struct __attribute__((packed)) {
    char signature[8];
    uint8_t checksum;
    char oem_id[6];
    uint8_t revision;
    uint32_t rsdt_address;
    uint32_t length;
    uint64_t xsdt_address;
    uint8_t extended_checksum;
    uint8_t reserved[3];
} AcpiRsdp;

typedef struct __attribute__((packed)) {
    char signature[4];
    uint32_t length;
    uint8_t revision;
    uint8_t checksum;
    char oem_id[6];
    char oem_table_id[8];
    uint32_t oem_revision;
    uint32_t creator_id;
    uint32_t creator_revision;
} AcpiSdtHeader;

typedef struct __attribute__((packed)) {
    AcpiSdtHeader header;
    uint32_t lapic_address;
    uint32_t flags;
} AcpiMadt;

typedef struct __attribute__((packed)) {
    uint8_t type;
    uint8_t length;
} MadtEntry;

typedef struct __attribute__((packed)) {
    MadtEntry header;
    uint8_t processor_id;
    uint8_t apic_id;
    uint32_t flags;
} MadtLapic;

typedef struct __attribute__((packed)) {
    MadtEntry header;
    uint8_t ioapic_id;
    uint8_t reserved;
    uint32_t ioapic_address;
    uint32_t gsi_base;
} MadtIoApic;

// Functions
bool AcpiInit(void);
uint32_t AcpiGetLapicAddress(void);
uint32_t AcpiGetIoApicAddress(void);
int AcpiGetCpuCount(void);
void AcpiDumpInfo(void);

#endif

File: drivers/Acpi.c (new file):

#include "Acpi.h"
#include "Console.h"
#include "MemOps.h"
#include "VMem.h"

static AcpiRsdp* g_rsdp = NULL;
static AcpiMadt* g_madt = NULL;
static uint32_t g_lapic_addr = 0;
static uint32_t g_ioapic_addr = 0;
static int g_cpu_count = 0;

static uint8_t AcpiChecksum(void* ptr, size_t size) {
    uint8_t sum = 0;
    uint8_t* bytes = (uint8_t*)ptr;
    for (size_t i = 0; i < size; i++) {
        sum += bytes[i];
    }
    return sum;
}

static AcpiRsdp* AcpiFindRsdp(void) {
    // Search EBDA first
    uint16_t* ebda_ptr = (uint16_t*)0x40E;
    uint32_t ebda = (*ebda_ptr) << 4;
    
    if (ebda) {
        for (uint32_t addr = ebda; addr < ebda + 1024; addr += 16) {
            AcpiRsdp* rsdp = (AcpiRsdp*)addr;
            if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0) {
                if (AcpiChecksum(rsdp, 20) == 0) {
                    return rsdp;
                }
            }
        }
    }
    
    // Search BIOS ROM area
    for (uint32_t addr = 0xE0000; addr < 0x100000; addr += 16) {
        AcpiRsdp* rsdp = (AcpiRsdp*)addr;
        if (FastMemCmp(rsdp->signature, "RSD PTR ", 8) == 0) {
            if (AcpiChecksum(rsdp, 20) == 0) {
                return rsdp;
            }
        }
    }
    
    return NULL;
}

bool AcpiInit(void) {
    g_rsdp = AcpiFindRsdp();
    if (!g_rsdp) {
        return false;
    }
    
    // Get RSDT
    AcpiSdtHeader* rsdt = (AcpiSdtHeader*)(uintptr_t)g_rsdp->rsdt_address;
    if (AcpiChecksum(rsdt, rsdt->length) != 0) {
        PrintKernelError("[ACPI] RSDT checksum failed\n");
        return false;
    }
    
    // Find MADT in RSDT
    uint32_t entries = (rsdt->length - sizeof(AcpiSdtHeader)) / 4;
    uint32_t* table_ptrs = (uint32_t*)((uint8_t*)rsdt + sizeof(AcpiSdtHeader));
    
    for (uint32_t i = 0; i < entries; i++) {
        AcpiSdtHeader* table = (AcpiSdtHeader*)(uintptr_t)table_ptrs[i];
        if (FastMemCmp(table->signature, "APIC", 4) == 0) {
            g_madt = (AcpiMadt*)table;
            g_lapic_addr = g_madt->lapic_address;
            
            // Parse MADT entries
            uint8_t* ptr = (uint8_t*)g_madt + sizeof(AcpiMadt);
            uint8_t* end = (uint8_t*)g_madt + g_madt->header.length;
            
            while (ptr < end) {
                MadtEntry* entry = (MadtEntry*)ptr;
                
                if (entry->type == 0) { // Local APIC
                    MadtLapic* lapic = (MadtLapic*)ptr;
                    if (lapic->flags & 1) {
                        g_cpu_count++;
                    }
                } else if (entry->type == 1) { // I/O APIC
                    MadtIoApic* ioapic = (MadtIoApic*)ptr;
                    g_ioapic_addr = ioapic->ioapic_address;
                }
                
                ptr += entry->length;
            }
            
            return true;
        }
    }
    
    return false;
}

uint32_t AcpiGetLapicAddress(void) { return g_lapic_addr; }
uint32_t AcpiGetIoApicAddress(void) { return g_ioapic_addr; }
int AcpiGetCpuCount(void) { return g_cpu_count; }

void AcpiDumpInfo(void) {
    if (g_rsdp) {
        PrintKernel("[ACPI] RSDP found, OEM: ");
        for (int i = 0; i < 6; i++) PrintChar(g_rsdp->oem_id[i]);
        PrintKernel(", Rev: ");
        PrintKernelInt(g_rsdp->revision);
        PrintKernel("\n");
    }
    if (g_lapic_addr) {
        PrintKernel("[ACPI] LAPIC: 0x");
        PrintKernelHex(g_lapic_addr);
        PrintKernel("\n");
    }
    if (g_ioapic_addr) {
        PrintKernel("[ACPI] IOAPIC: 0x");
        PrintKernelHex(g_ioapic_addr);
        PrintKernel("\n");
    }
    if (g_cpu_count > 0) {
        PrintKernel("[ACPI] CPUs: ");
        PrintKernelInt(g_cpu_count);
        PrintKernel("\n");
    }
}

3. Create APIC Support

File: drivers/Apic.h (new file):

#ifndef APIC_H
#define APIC_H

#include <stdint.h>
#include <stdbool.h>

// APIC registers
#define LAPIC_ID        0x020
#define LAPIC_VERSION   0x030
#define LAPIC_EOI       0x0B0
#define LAPIC_SPURIOUS  0x0F0
#define LAPIC_ICR_LOW   0x300
#define LAPIC_ICR_HIGH  0x310
#define LAPIC_TIMER_LVT 0x320

#define IOAPIC_REG      0x00
#define IOAPIC_DATA     0x10

// Functions
bool ApicDetect(void);
bool ApicInit(void);
void ApicEnable(void);
void ApicDisable(void);
void ApicEoi(void);
bool ApicIsEnabled(void);
void ApicSendEoi(uint8_t irq);

#endif

File: drivers/Apic.c (new file):

#include "Apic.h"
#include "Acpi.h"
#include "Console.h"
#include "Cpu.h"
#include "Io.h"
#include "VMem.h"

static volatile uint32_t* g_lapic = NULL;
static volatile uint32_t* g_ioapic = NULL;
static bool g_apic_available = false;
static bool g_apic_enabled = false;

bool ApicDetect(void) {
    uint32_t eax, ebx, ecx, edx;
    // Check CPUID for APIC support
    __asm__ volatile(
        "mov $1, %%eax\n"
        "cpuid\n"
        : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
        : : "memory"
    );
    return (edx & (1 << 9)) != 0;
}

bool ApicInit(void) {
    if (!ApicDetect()) {
        PrintKernelWarning("[APIC] CPU doesn't support APIC\n");
        return false;
    }
    
    if (!AcpiInit()) {
        PrintKernelWarning("[APIC] ACPI tables not found\n");
        return false;
    }
    
    uint32_t lapic_addr = AcpiGetLapicAddress();
    uint32_t ioapic_addr = AcpiGetIoApicAddress();
    
    if (!lapic_addr || !ioapic_addr) {
        PrintKernelWarning("[APIC] APIC addresses not found in ACPI\n");
        return false;
    }
    
    // Map APIC registers to virtual memory
    g_lapic = (uint32_t*)VMemMapMmio(lapic_addr, 0x1000);
    g_ioapic = (uint32_t*)VMemMapMmio(ioapic_addr, 0x1000);
    
    if (!g_lapic || !g_ioapic) {
        PrintKernelError("[APIC] Failed to map APIC MMIO regions\n");
        return false;
    }
    
    g_apic_available = true;
    PrintKernelSuccess("[APIC] APIC initialized (not enabled)\n");
    AcpiDumpInfo();
    
    return true;
}

void ApicEnable(void) {
    if (!g_apic_available) {
        PrintKernelError("[APIC] APIC not available\n");
        return;
    }
    
    // Disable 8259 PIC by masking all IRQs
    outb(0x21, 0xFF);  // Master PIC
    outb(0xA1, 0xFF);  // Slave PIC
    
    // Enable LAPIC (spurious interrupt vector 0xFF)
    g_lapic[LAPIC_SPURIOUS / 4] = 0x1FF;
    
    // Configure IOAPIC - redirect IRQs to vectors 0x20+
    for (int i = 0; i < 16; i++) {
        uint32_t low = 0x20 + i;  // Vector
        uint32_t high = 0;         // Destination (CPU 0)
        
        g_ioapic[IOAPIC_REG / 4] = 0x10 + i * 2;
        g_ioapic[IOAPIC_DATA / 4] = low;
        g_ioapic[IOAPIC_REG / 4] = 0x10 + i * 2 + 1;
        g_ioapic[IOAPIC_DATA / 4] = high;
    }
    
    g_apic_enabled = true;
    PrintKernelSuccess("[APIC] APIC enabled, PIC disabled\n");
}

void ApicDisable(void) {
    if (!g_apic_enabled) return;
    
    // Disable LAPIC
    if (g_lapic) {
        g_lapic[LAPIC_SPURIOUS / 4] = 0;
    }
    
    g_apic_enabled = false;
    
    // Re-enable PIC
    PicInstall();
    PrintKernel("[APIC] APIC disabled, PIC re-enabled\n");
}

void ApicEoi(void) {
    if (g_apic_enabled && g_lapic) {
        g_lapic[LAPIC_EOI / 4] = 0;
    }
}

void ApicSendEoi(uint8_t irq) {
    if (g_apic_enabled) {
        ApicEoi();
    } else {
        // Send to PIC
        if (irq >= 8) {
            outb(0xA0, 0x20);  // Slave PIC
        }
        outb(0x20, 0x20);  // Master PIC
    }
}

bool ApicIsEnabled(void) {
    return g_apic_enabled;
}

4. Update PIC Driver

File: drivers/Pic.h - Add:

void PicDisable(void);
void PicSendEoi(uint8_t irq);

File: drivers/Pic.c - Add:

void PicDisable(void) {
    // Mask all interrupts on both PICs
    outb(PIC1_DATA, 0xFF);
    outb(PIC2_DATA, 0xFF);
}

void PicSendEoi(uint8_t irq) {
    if (irq >= 8) {
        outb(PIC2_COMMAND, 0x20);
    }
    outb(PIC1_COMMAND, 0x20);
}

5. Update Kernel Initialization

File: kernel/core/Kernel.c - Add includes and update KernelMainHigherHalf:

// Add these includes at the top
#include "Acpi.h"
#include "Apic.h"

// In KernelMainHigherHalf(), after PIC/PIT initialization:
    PrintKernel("[INFO] Initializing PIC & PIT...\n");
    PicInstall();
    PitInstall();
    PrintKernelSuccess("[SYSTEM] PIC & PIT initialized\n");
    
    // Try to initialize APIC (safe - won't enable automatically)
    PrintKernel("[INFO] Detecting APIC support...\n");
    if (ApicInit()) {
        PrintKernelSuccess("[SYSTEM] APIC available - use 'apic enable' to activate\n");
    } else {
        PrintKernel("[SYSTEM] No APIC support - using legacy PIC\n");
    }

6. Update Interrupt Handler

File: arch/x86_64/idt/Idt.c - Find InterruptHandler and update EOI sending:

// In InterruptHandler function, replace EOI logic with:
void InterruptHandler(Registers* regs) {
    // ... existing interrupt handling code ...
    
    // Send EOI to appropriate controller
    if (regs->int_no >= 0x20 && regs->int_no < 0x30) {
        ApicSendEoi(regs->int_no - 0x20);
    }
    
    // ... rest of handler ...
}

7. Add Shell Commands

File: kernel/etc/Shell.c - Add to show_help():

    PrintKernel("  apic <cmd>     - APIC control (status/enable/disable)\n");

Add to ExecuteCommand():

    } else if (FastStrCmp(cmd_name, "apic") == 0) {
        char* subcmd = GetArg(cmd, 1);
        if (!subcmd) {
            PrintKernel("Usage: apic [status|enable|disable|test]\n");
            return;
        }
        
        if (FastStrCmp(subcmd, "status") == 0) {
            PrintKernel("APIC Support: ");
            PrintKernel(ApicDetect() ? "Yes\n" : "No\n");
            PrintKernel("APIC Enabled: ");
            PrintKernel(ApicIsEnabled() ? "Yes\n" : "No\n");
            
            int cpu_count = AcpiGetCpuCount();
            if (cpu_count > 0) {
                PrintKernel("CPUs Detected: ");
                PrintKernelInt(cpu_count);
                PrintKernel("\n");
            }
            
            AcpiDumpInfo();
        } else if (FastStrCmp(subcmd, "enable") == 0) {
            if (!ApicIsEnabled()) {
                PrintKernelWarning("WARNING: Enabling APIC will disable PIC!\n");
                PrintKernel("Interrupts may behave differently.\n");
                ApicEnable();
            } else {
                PrintKernel("APIC is already enabled\n");
            }
        } else if (FastStrCmp(subcmd, "disable") == 0) {
            if (ApicIsEnabled()) {
                ApicDisable();
            } else {
                PrintKernel("APIC is not enabled\n");
            }
        } else if (FastStrCmp(subcmd, "test") == 0) {
            PrintKernel("Sending test EOI...\n");
            ApicEoi();
            PrintKernel("EOI sent. System still responsive = APIC working!\n");
        } else {
            PrintKernel("Unknown subcommand: ");
            PrintKernel(subcmd);
            PrintKernel("\n");
        }
        
        KernelFree(subcmd);

8. Update Makefile

Add the new source files to your build system:

# Add to your kernel sources
KERNEL_SRCS += drivers/Acpi.c drivers/Apic.c

Testing Strategy

  1. Phase 1 - Detection Only:

    > apic status
    

    Should show if APIC is detected without enabling it.

  2. Phase 2 - Safe Enable:

    > apic enable
    > memstat
    > ps
    

    Test basic functionality after enabling.

  3. Phase 3 - Stress Test:

    > setfreq 1000
    > apic test
    
  4. Phase 4 - Fallback:

    > apic disable
    

    Should revert to PIC safely.

Safety Features

  • Never auto-enables - Requires manual apic enable
  • PIC fallback - Can disable APIC and return to PIC
  • Identity mapping for low MMIO - Uses existing mappings when possible
  • Checksum validation - Verifies ACPI tables
  • CPU feature detection - Checks CPUID before attempting

This implementation ensures maximum safety while giving you full APIC/ACPI support. Start with just detection, then gradually test the enable/disable functionality!


🧬 Code Graph Analysis Results

File: drivers/Pic.c, Lines 25-41

void PitSetFrequency(uint16_t hz) {
    // Save current interrupt state
    irq_flags_t flags = save_irq_flags();
    cli();

    PIT_FREQUENCY_HZ = hz;
    // Safer divisor calculation
    uint32_t div32 = 1193180u / (hz ? hz : 1u);
    uint16_t divisor = (uint16_t)div32;

    outb(0x43, 0x36);
    outb(0x40, divisor & 0xFF);
    outb(0x40, (divisor >> 8) & 0xFF);

    // Restore previous interrupt state
    restore_irq_flags(flags);
}

File: kernel/process/Process.c, Lines 905-918

void Yield() {
    irq_flags_t flags = SpinLockIrqSave(&scheduler_lock);
    Process* current = GetCurrentProcess();
    if (current) {
        // A process that yields is ready to run again, just giving up its timeslice.
        // Setting it to PROC_BLOCKED was incorrect as there was no corresponding unblock mechanism.
        current->state = PROC_READY;
    }
    RequestSchedule();
    // This instruction halts the CPU until the next interrupt (e.g., the timer),
    // which will then trigger the scheduler.
    SpinUnlockIrqRestore(&scheduler_lock, flags);
    __asm__ __volatile__("hlt");
}

File: kernel/memory/VMem.c, Lines 525-539

void PrintVMemStats(void) {
    irq_flags_t flags = SpinLockIrqSave(&vmem_lock);
    uint64_t used = kernel_space.used_pages;
    uint64_t mapped = kernel_space.total_mapped;
    uint64_t allocs = vmem_allocations;
    uint64_t frees = vmem_frees;
    uint64_t flushes = tlb_flushes;
    SpinUnlockIrqRestore(&vmem_lock, flags);
    
    PrintKernel("[VMEM] Stats:\n");
    PrintKernel("  Used pages: "); PrintKernelInt(used); PrintKernel("\n");
    PrintKernel("  Mapped: "); PrintKernelInt(mapped / (1024 * 1024)); PrintKernel("MB\n");
    PrintKernel("  Allocs: "); PrintKernelInt(allocs); PrintKernel(", Frees: "); PrintKernelInt(frees); PrintKernel("\n");
    PrintKernel("  TLB flushes: "); PrintKernelInt(flushes); PrintKernel("\n");
}

File: kernel/memory/Memory.c, Lines 319-369
Summary: GetDetailedMemoryStats(MemoryStats* stats)

  • Purpose: Populate detailed memory statistics including total, used, free physical memory, allocation counts, fragmentation, and largest free block.
  • Parameters: stats - pointer to a MemoryStats structure to fill.
  • Returns: void (fills the provided structure).
  • Error handling: None explicit; uses a spinlock (SpinLockIrqSave/SpinUnlockIrqRestore) to ensure a consistent snapshot under concurrent access.
  • Important implementation details:
    • Acquires memory_lock with SpinLockIrqSave to prevent races while reading memory state.
    • Computes fragmentation by walking free/used pages, tracking free_fragments and largest_fragment.
    • Computes fragmentation_score and clamps to 100.
    • Fills fields: total_physical_bytes, used_physical_bytes, free_physical_bytes, allocation_count, free_count, allocation_failures, huge_pages_allocated, largest_free_block, fragmentation_score.
  • Source: kernel/memory/Memory.c (lines 319-369).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, excelent job and about step 6 (i think you meant Interrupts.c) heres the patch, is it correct
switch (regs->interrupt_number) {
case 32: // Timer interrupt
FastSchedule(regs);
if (regs->interrupt_number >= 0x20 && regs->interrupt_number < 0x30) {
ApicSendEoi(regs->interrupt_number - 0x20);
}
return;

    case 33: // Keyboard interrupt
        KeyboardHandler();
        if (regs->interrupt_number >= 0x20 && regs->interrupt_number < 0x30) {
            ApicSendEoi(regs->interrupt_number - 0x20);
        }
        return;

    case 46: // IDE Primary (IRQ 14)
        IDEPrimaryIRQH();
        outb(0xA0, 0x20); // EOI to slave PIC
        outb(0x20, 0x20); // EOI to master PIC
        return;

    case 47: // IDE Secondary (IRQ 15)
        IDESecondaryIRQH();
        outb(0xA0, 0x20); // EOI to slave PIC
        outb(0x20, 0x20); // EOI to master PIC
        return;

    // Handle other hardware interrupts (34-45)
    case 34 ... 45:
        PrintKernelWarning("[IRQ] Unhandled hardware interrupt: ");
        PrintKernelInt(regs->interrupt_number - 32);
        PrintKernelWarning("\n");

        // Send EOI to the appropriate PIC
        if (regs->interrupt_number >= 40) {
            outb(0xA0, 0x20); // EOI to slave PIC
        }
        outb(0x20, 0x20); // EOI to master PIC
        return;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@assembler-0 Good catch on the file name! Your patch is on the right track, but there are a few issues to fix:

Issues with the Current Patch

  1. Redundant condition checks: In cases 32 and 33, you're checking if interrupt_number >= 0x20, but 32 IS 0x20, so this check will always be true and is redundant.

  2. Inconsistent EOI handling: You're using ApicSendEoi for timer/keyboard but direct PIC commands for IDE interrupts. The whole point of ApicSendEoi is to abstract this - it handles both APIC and PIC internally.

  3. Incorrect IRQ calculation: When calling ApicSendEoi, you should pass the IRQ number (0-15), not the remapped interrupt number.

Corrected Patch

Here's the fixed version:

switch (regs->interrupt_number) {
    case 32: // Timer interrupt (IRQ 0)
        FastSchedule(regs);
        ApicSendEoi(0);  // IRQ 0
        return;

    case 33: // Keyboard interrupt (IRQ 1)
        KeyboardHandler();
        ApicSendEoi(1);  // IRQ 1
        return;

    case 46: // IDE Primary (IRQ 14)
        IDEPrimaryIRQH();
        ApicSendEoi(14);  // IRQ 14
        return;

    case 47: // IDE Secondary (IRQ 15)
        IDESecondaryIRQH();
        ApicSendEoi(15);  // IRQ 15
        return;

    // Handle other hardware interrupts (34-45)
    case 34 ... 45:
        PrintKernelWarning("[IRQ] Unhandled hardware interrupt: ");
        PrintKernelInt(regs->interrupt_number - 32);
        PrintKernelWarning("\n");
        
        // Send EOI for the corresponding IRQ
        ApicSendEoi(regs->interrupt_number - 32);
        return;
}

Why This Works Better

  1. Consistent abstraction: ApicSendEoi handles the logic of whether to send to APIC or PIC internally
  2. Correct IRQ numbers: We pass the actual IRQ (0-15) to ApicSendEoi, not the remapped vector
  3. Cleaner code: No redundant checks or duplicate EOI logic

Remember, ApicSendEoi in Apic.c already does this:

void ApicSendEoi(uint8_t irq) {
    if (g_apic_enabled) {
        ApicEoi();  // Send to LAPIC
    } else {
        // Send to PIC
        if (irq >= 8) {
            outb(0xA0, 0x20);  // Slave PIC
        }
        outb(0x20, 0x20);  // Master PIC
    }
}

This way, your interrupt handler doesn't need to know whether APIC or PIC is active - it just works!


🧠 Learnings used
Learnt from: assembler-0
PR: assembler-0/VoidFrame#62
File: kernel/core/Panic.c:69-79
Timestamp: 2025-08-10T06:22:28.788Z
Learning: In VoidFrame kernel, panic functions must call "asm volatile("cli")" as the very first instruction to disable interrupts immediately. Without this, if interrupts are enabled (after "sti"), an interrupt can fire during panic handling causing the panic to fail, hang, or behave unpredictably. This must be done in ALL panic entry points: Panic(), PanicWithCode(), PanicWithContext(), PanicFromInterrupt(), and at the start of KernelPanicHandler().

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice nice also wait, i think this uint64_t* VMemGetPageTable(uint64_t* pml4, uint64_t vaddr, int level, int create); isnt implemented, can you help me we this, so we can get this all finished!, im hyped

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for VMemGetPageTable or similar page table traversal functions
echo "=== Searching for VMemGetPageTable ==="
rg "VMemGetPageTable" --type c

echo -e "\n=== Searching for similar page table functions ==="
rg "GetPageTable|get.*page.*table|traverse.*page" -i --type c | head -20

echo -e "\n=== Check VMem.c for existing page table manipulation ==="
cat kernel/memory/VMem.c | grep -A 10 -B 2 "pml4\|pdpt\|page.*table" | head -80

echo -e "\n=== Check if there are page table walking functions ==="
rg "walk.*page|page.*walk|PML4.*entry|PDPT.*entry" --type c -A 3 | head -40

Length of output: 7170


🏁 Script executed:

#!/bin/bash
# Get the full VMemGetPageTablePhys implementation
echo "=== Full VMemGetPageTablePhys implementation ==="
rg -A 50 "static uint64_t VMemGetPageTablePhys" kernel/memory/VMem.c

echo -e "\n=== Check PHYS_TO_VIRT and IDENTITY_MAP_SIZE definitions ==="
rg "PHYS_TO_VIRT|IDENTITY_MAP_SIZE|define.*IDENTITY" --type c | head -20

echo -e "\n=== Check PAGE_PRESENT and other page flags ==="
rg "PAGE_PRESENT|PAGE_WRITABLE|PAGE_NOCACHE|define.*PAGE_" --type c | head -30

Length of output: 6394


@assembler-0 Great catch! I can see VMemGetPageTable is declared but not implemented. Here's the missing implementation along with the required defines:

Add Missing Definitions to VMem.h

First, add these missing defines to kernel/memory/VMem.h:

// Add these page flags if not already present
#define PAGE_NOCACHE    0x010  // PCD bit - Page Cache Disable
#define PAGE_LARGE      0x080  // PS bit - Page Size (2MB/1GB pages)

// Add these if missing
#define PT_INDEX_MASK   0x1FF
#define PT_ADDR_MASK    0x000FFFFFFFFFF000ULL

Implement VMemGetPageTable in VMem.c

Add this implementation to kernel/memory/VMem.c:

// Public wrapper for VMemGetPageTablePhys that returns virtual addresses
uint64_t* VMemGetPageTable(uint64_t* pml4, uint64_t vaddr, int level, int create) {
    // Convert virtual PML4 pointer to physical address
    uint64_t pml4_phys;
    if ((uint64_t)pml4 < IDENTITY_MAP_SIZE) {
        // Already a physical address in identity-mapped region
        pml4_phys = (uint64_t)pml4;
    } else if ((uint64_t)pml4 >= KERNEL_VIRTUAL_BASE) {
        // Higher-half kernel address
        pml4_phys = (uint64_t)pml4 - KERNEL_VIRTUAL_OFFSET;
    } else {
        // Use page table lookup for other addresses
        pml4_phys = VMemGetPhysAddr((uint64_t)pml4);
        if (!pml4_phys) return NULL;
    }
    
    // Walk the page tables based on level
    uint64_t table_phys = pml4_phys;
    
    // Level 0: Get PDP from PML4
    // Level 1: Get PD from PDP  
    // Level 2: Get PT from PD
    // Level 3: Return the final PT
    for (int current_level = 0; current_level <= level && current_level < 3; current_level++) {
        table_phys = VMemGetPageTablePhys(table_phys, vaddr, current_level, create);
        if (!table_phys) return NULL;
    }
    
    // Convert physical address back to virtual
    if (table_phys < IDENTITY_MAP_SIZE) {
        return (uint64_t*)table_phys;
    } else {
        return (uint64_t*)PHYS_TO_VIRT(table_phys);
    }
}

Update VMemMapMmio to Use the Simpler Approach

Now that we have VMemGetPageTable, here's a cleaner VMemMapMmio implementation:

void* VMemMapMmio(uint64_t phys_addr, size_t size) {
    // For low memory, use identity mapping
    if (phys_addr < IDENTITY_MAP_SIZE && (phys_addr + size) <= IDENTITY_MAP_SIZE) {
        return (void*)phys_addr;
    }
    
    irq_flags_t flags = SpinLockIrqSave(&vmem_lock);
    
    // Allocate virtual address space for MMIO (256MB offset from kernel base)
    static uint64_t mmio_next_vaddr = KERNEL_VIRTUAL_BASE + 0x10000000;
    uint64_t vaddr = mmio_next_vaddr;
    
    size_t pages = PAGE_ALIGN_UP(size) / PAGE_SIZE;
    bool success = true;
    
    for (size_t i = 0; i < pages; i++) {
        uint64_t page_vaddr = vaddr + i * PAGE_SIZE;
        uint64_t page_paddr = phys_addr + i * PAGE_SIZE;
        
        // Map with no-cache flag for MMIO
        int result = VMemMap(page_vaddr, page_paddr, 
                           PAGE_PRESENT | PAGE_WRITABLE | PAGE_NOCACHE);
        if (result != 0) {
            // Rollback on failure
            for (size_t j = 0; j < i; j++) {
                VMemUnmap(vaddr + j * PAGE_SIZE, PAGE_SIZE);
            }
            success = false;
            break;
        }
    }
    
    if (success) {
        mmio_next_vaddr += PAGE_ALIGN_UP(size);
        VMemFlushTLB();
    }
    
    SpinUnlockIrqRestore(&vmem_lock, flags);
    return success ? (void*)vaddr : NULL;
}

void VMemUnmapMmio(void* virt_addr, size_t size) {
    VMemUnmap((uint64_t)virt_addr, size);
}

Alternative: Simpler VMemGetPageTable

If you prefer a more straightforward implementation that directly uses the existing VMemGetPageTablePhys:

uint64_t* VMemGetPageTable(uint64_t* pml4, uint64_t vaddr, int level, int create) {
    // Just get the physical address and convert to virtual
    uint64_t table_phys;
    
    // Start from PML4
    if (level == 0) {
        // Return PML4 itself
        return pml4;
    }
    
    // Get the appropriate level table
    uint64_t pml4_phys = VMemGetPhysAddr((uint64_t)pml4);
    if (!pml4_phys) pml4_phys = (uint64_t)pml4;  // Might already be physical
    
    if (level == 1) {
        // Get PDP
        table_phys = VMemGetPageTablePhys(pml4_phys, vaddr, 0, create);
    } else if (level == 2) {
        // Get PD through PDP
        uint64_t pdp_phys = VMemGetPageTablePhys(pml4_phys, vaddr, 0, create);
        if (!pdp_phys) return NULL;
        table_phys = VMemGetPageTablePhys(pdp_phys, vaddr, 1, create);
    } else if (level == 3) {
        // Get PT through PDP and PD
        uint64_t pdp_phys = VMemGetPageTablePhys(pml4_phys, vaddr, 0, create);
        if (!pdp_phys) return NULL;
        uint64_t pd_phys = VMemGetPageTablePhys(pdp_phys, vaddr, 1, create);
        if (!pd_phys) return NULL;
        table_phys = VMemGetPageTablePhys(pd_phys, vaddr, 2, create);
    } else {
        return NULL;  // Invalid level
    }
    
    if (!table_phys) return NULL;
    
    // Convert to virtual address
    if (table_phys < IDENTITY_MAP_SIZE) {
        return (uint64_t*)table_phys;
    } else {
        return (uint64_t*)PHYS_TO_VIRT(table_phys);
    }
}

With this implementation, you now have everything needed for the APIC/ACPI support! The MMIO mapping will work correctly, and you can map the LAPIC and IOAPIC registers. Let's get this final boss defeated! 🚀


🧬 Code Graph Analysis Results
  • File: kernel/memory/VMem.c
    Line range: 525-539
    Code snippet:

    void PrintVMemStats(void) {
        irq_flags_t flags = SpinLockIrqSave(&vmem_lock);
        uint64_t used = kernel_space.used_pages;
        uint64_t mapped = kernel_space.total_mapped;
        uint64_t allocs = vmem_allocations;
        uint64_t frees = vmem_frees;
        uint64_t flushes = tlb_flushes;
        SpinUnlockIrqRestore(&vmem_lock, flags);
        
        PrintKernel("[VMEM] Stats:\n");
        PrintKernel("  Used pages: "); PrintKernelInt(used); PrintKernel("\n");
        PrintKernel("  Mapped: "); PrintKernelInt(mapped / (1024 * 1024)); PrintKernel("MB\n");
        PrintKernel("  Allocs: "); PrintKernelInt(allocs); PrintKernel(", Frees: "); PrintKernelInt(frees); PrintKernel("\n");
        PrintKernel("  TLB flushes: "); PrintKernelInt(flushes); PrintKernel("\n");
    }
    
  • File: kernel/memory/VMem.c
    Summary (docstring-style): VMemGetPageTable(uint64_t* pml4, uint64_t vaddr, int level, int create)

    • Purpose: Intended to walk the 4-level x86-64 page-table hierarchy starting from the provided PML4 base pointer to obtain the page-table pointer at the requested level for the given virtual address.
    • Parameters:
      • uint64_t* pml4: base address of the PML4 table.
      • uint64_t vaddr: the virtual address to translate.
      • int level: target level in the page-table walk (e.g., 3 for PML4 down to 0 for PT, depending on the implementation's convention).
      • int create: non-zero to allocate missing intermediate page-tables if needed; zero to only walk and return NULL if a table is missing.
    • Return value:
      • Pointer to the page-table (at the specified level) corresponding to vaddr, or NULL if the required table is not present and create is 0, or if allocation fails.
    • Error handling:
      • On memory allocation failure or missing tables when create is 0, returns NULL.
    • Side effects / implementation notes:
      • Likely uses KernelMemoryAlloc (or similar) to allocate new page-table blocks when create != 0.
      • Should lock memory structures (e.g., via vmem_lock) during traversal and modification to avoid races.
      • Expected to flush TLB if any page-table metadata is modified.
    • Context:
      • The function is referenced by the kernel’s VMem subsystem (VMem.h) and related memory-management code, but the snippet indicating its existence is not implemented in the provided excerpts. See VMem-related memory utilities in kernel/memory/VMem.c for context.

🧠 Learnings used
Learnt from: assembler-0
PR: assembler-0/VoidFrame#62
File: kernel/core/Panic.c:69-79
Timestamp: 2025-08-10T06:22:28.788Z
Learning: In VoidFrame kernel, panic functions must call "asm volatile("cli")" as the very first instruction to disable interrupts immediately. Without this, if interrupts are enabled (after "sti"), an interrupt can fire during panic handling causing the panic to fail, hang, or behave unpredictably. This must be done in ALL panic entry points: Panic(), PanicWithCode(), PanicWithContext(), PanicFromInterrupt(), and at the start of KernelPanicHandler().

Learnt from: assembler-0
PR: assembler-0/VoidFrame#62
File: kernel/core/Panic.c:69-79
Timestamp: 2025-08-10T06:22:28.788Z
Learning: In VoidFrame kernel, panic functions must disable interrupts with "cli" as the very first operation, before any other code executes. If interrupts are enabled (after "sti"), an interrupt can fire between the panic call and the cli instruction in KernelPanicHandler, causing the panic to fail or behave unpredictably.

@assembler-0 assembler-0 merged commit 772a39a into main Aug 11, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant