Add detailed docstrings to bootloader and kernel header files

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-28 22:48:09 +00:00
parent d0ada8628b
commit 2b2dafef02
9 changed files with 1047 additions and 89 deletions

View File

@@ -18,7 +18,23 @@
static EFI_SYSTEM_TABLE* gST = NULL;
static EFI_BOOT_SERVICES* gBS = NULL;
// Helper: Compare GUIDs
/**
* @brief Compares two UEFI GUIDs for equality
*
* GUIDs (Globally Unique Identifiers) are 128-bit values used by UEFI to identify
* protocols, tables, and other system resources. This function performs a field-by-field
* comparison of two GUID structures.
*
* @param a Pointer to the first GUID to compare
* @param b Pointer to the second GUID to compare
* @return 1 if the GUIDs are equal, 0 if they differ
*
* @note The GUID structure consists of:
* - Data1: 32-bit value
* - Data2: 16-bit value
* - Data3: 16-bit value
* - Data4: 8-byte array
*/
static int guid_compare(const EFI_GUID* a, const EFI_GUID* b) {
if (a->Data1 != b->Data1) return 0;
if (a->Data2 != b->Data2) return 0;
@@ -29,14 +45,42 @@ static int guid_compare(const EFI_GUID* a, const EFI_GUID* b) {
return 1;
}
// Helper: Memory set
/**
* @brief Sets a block of memory to a specified value
*
* This is a simple implementation of memset that works in the UEFI environment.
* It fills the first n bytes of the memory area pointed to by s with the constant
* byte c. This is used for initializing structures and buffers.
*
* @param s Pointer to the memory block to fill
* @param c Value to set (converted to unsigned char)
* @param n Number of bytes to set
* @return Pointer to the memory block s
*
* @note This function operates byte-by-byte, so it's not optimized for large blocks.
* However, for bootloader usage with small structures, this is sufficient.
*/
static VOID* efi_memset(VOID* s, int c, UINTN n) {
unsigned char* p = s;
while (n--) *p++ = (unsigned char)c;
return s;
}
// Helper: Memory copy
/**
* @brief Copies a block of memory from source to destination
*
* This is a simple implementation of memcpy that works in the UEFI environment.
* It copies n bytes from memory area src to memory area dest. The memory areas
* must not overlap (use memmove if they might overlap).
*
* @param dest Pointer to the destination memory block
* @param src Pointer to the source memory block
* @param n Number of bytes to copy
* @return Pointer to the destination memory block dest
*
* @warning The memory areas must not overlap. If they do, the behavior is undefined.
* @note This function operates byte-by-byte for simplicity and UEFI compatibility.
*/
static VOID* efi_memcpy(VOID* dest, const VOID* src, UINTN n) {
unsigned char* d = dest;
const unsigned char* s = src;
@@ -44,8 +88,18 @@ static VOID* efi_memcpy(VOID* dest, const VOID* src, UINTN n) {
return dest;
}
/*
* Print a string to the UEFI console
/**
* @brief Prints a UTF-16 string to the UEFI console output
*
* Uses the UEFI Simple Text Output Protocol to display a string on the console.
* This is the primary method for user feedback during the boot process.
*
* @param str Pointer to a null-terminated UTF-16 string to display
*
* @note UEFI uses UTF-16 encoding (CHAR16*) rather than ASCII or UTF-8.
* Literal strings should be prefixed with 'u' (e.g., u"Hello").
* @note This function does nothing if the console output protocol is unavailable,
* which may happen in headless systems.
*/
void print_string(const CHAR16* str) {
if (gST && gST->ConOut) {
@@ -53,8 +107,18 @@ void print_string(const CHAR16* str) {
}
}
/*
* Print operation status
/**
* @brief Prints an operation description followed by its status result
*
* This helper function displays the result of a UEFI operation in a consistent
* format: the operation description followed by either " [OK]" or " [FAILED]"
* depending on the status code.
*
* @param operation UTF-16 string describing the operation that was performed
* @param status UEFI status code returned by the operation (EFI_SUCCESS or error)
*
* @note EFI_SUCCESS (0) indicates success; any other value indicates failure.
* @see print_string() for the underlying output mechanism
*/
void print_status(const CHAR16* operation, EFI_STATUS status) {
print_string(operation);
@@ -65,8 +129,28 @@ void print_status(const CHAR16* operation, EFI_STATUS status) {
}
}
/*
* Initialize graphics output protocol
/**
* @brief Initializes the graphics output and retrieves framebuffer information
*
* This function locates the UEFI Graphics Output Protocol (GOP) and extracts
* framebuffer details needed by the kernel for direct graphics rendering.
* The GOP provides a linear framebuffer that can be used for pixel-based graphics.
*
* The function stores the following information in boot_info:
* - framebuffer_base: Physical address of the framebuffer in memory
* - framebuffer_width: Horizontal resolution in pixels
* - framebuffer_height: Vertical resolution in pixels
* - framebuffer_pitch: Bytes per scanline (width * bytes_per_pixel, may include padding)
* - framebuffer_bpp: Bits per pixel (assumed 32-bit BGRA format)
*
* @param ImageHandle Handle to the bootloader image (currently unused)
* @param boot_info Pointer to BootInfo structure to receive framebuffer information
* @return EFI_SUCCESS if GOP was located and framebuffer info was retrieved,
* or an error code if GOP is unavailable
*
* @note This function uses the current graphics mode without attempting to change it.
* The resolution is whatever UEFI firmware has already configured.
* @note The framebuffer format is assumed to be 32-bit (4 bytes per pixel).
*/
EFI_STATUS initialize_graphics(EFI_HANDLE ImageHandle, BootInfo* boot_info) {
(void)ImageHandle;
@@ -93,8 +177,28 @@ EFI_STATUS initialize_graphics(EFI_HANDLE ImageHandle, BootInfo* boot_info) {
return EFI_SUCCESS;
}
/*
* Load kernel from disk
/**
* @brief Loads the kernel binary from the boot disk into memory
*
* This function performs the following steps to load the kernel:
* 1. Obtains the Loaded Image Protocol to identify the boot device
* 2. Opens the Simple File System Protocol on the boot device
* 3. Opens the root directory of the file system
* 4. Reads the kernel file "metalos.bin" from the root directory
* 5. Allocates a temporary buffer and reads the kernel into it
* 6. Copies the kernel to its final load address (KERNEL_LOAD_ADDRESS = 0x100000 = 1MB)
* 7. Stores kernel location and size in boot_info for the kernel's use
*
* @param ImageHandle Handle to the bootloader image, used to find the boot device
* @param boot_info Pointer to BootInfo structure to receive kernel location and size
* @return EFI_SUCCESS if kernel was loaded successfully, or an error code on failure
*
* @note The kernel file must be named "metalos.bin" and located in the root directory
* of the boot device (typically the EFI System Partition).
* @note The kernel is copied to physical address 0x100000 (1MB), which is a standard
* location above the legacy BIOS area and below the 16MB mark.
* @note This function allocates memory using UEFI's AllocatePool, which is only valid
* until ExitBootServices is called. The kernel is copied to a permanent location.
*/
EFI_STATUS load_kernel(EFI_HANDLE ImageHandle, BootInfo* boot_info) {
EFI_STATUS status;
@@ -181,8 +285,26 @@ EFI_STATUS load_kernel(EFI_HANDLE ImageHandle, BootInfo* boot_info) {
return EFI_SUCCESS;
}
/*
* Get ACPI RSDP (Root System Description Pointer)
/**
* @brief Retrieves the ACPI RSDP (Root System Description Pointer) from UEFI
*
* The RSDP is the entry point to ACPI (Advanced Configuration and Power Interface)
* tables, which provide information about the system hardware, including:
* - Multiple APIC Description Table (MADT) for SMP initialization
* - PCI routing tables
* - Power management configuration
* - Hardware description
*
* This function searches the UEFI Configuration Table for the ACPI 2.0+ table GUID
* and returns a pointer to the RSDP structure if found.
*
* @return Pointer to the RSDP structure if found, NULL if not available
*
* @note ACPI 2.0+ is preferred over ACPI 1.0 because it uses 64-bit addresses.
* @note The RSDP pointer remains valid after ExitBootServices is called since it
* points to firmware-provided tables in reserved memory.
* @note The kernel can use this to locate ACPI tables for multicore initialization
* and hardware discovery.
*/
void* get_rsdp(void) {
EFI_GUID acpi_20_guid = EFI_ACPI_20_TABLE_GUID;
@@ -197,8 +319,37 @@ void* get_rsdp(void) {
return NULL;
}
/*
* Main entry point for bootloader
/**
* @brief Main entry point for the UEFI bootloader
*
* This is the entry point called by UEFI firmware when the bootloader is loaded.
* It performs the following steps in order:
*
* 1. Initialize UEFI services and boot_info structure
* 2. Display boot banner
* 3. Initialize graphics and get framebuffer information
* 4. Load the kernel binary from disk
* 5. Get ACPI RSDP for hardware information
* 6. Get UEFI memory map
* 7. Exit UEFI boot services (point of no return - transfers control from firmware)
* 8. Jump to the kernel entry point
*
* After ExitBootServices is called:
* - UEFI Boot Services are no longer available
* - UEFI Runtime Services remain available
* - The kernel takes full control of the system
* - No more firmware calls can be made except runtime services
*
* @param ImageHandle Handle to this bootloader image, used for protocol access
* @param SystemTable Pointer to UEFI System Table containing all UEFI services
* @return EFI_SUCCESS on successful boot (should never return),
* or an error code if boot fails
*
* @note If ExitBootServices fails on the first attempt, the memory map may have
* changed. This function automatically retries once with an updated memory map.
* @note The kernel entry point is assumed to be at KERNEL_LOAD_ADDRESS (0x100000).
* @note The kernel receives a pointer to the BootInfo structure containing all
* information needed to initialize the system.
*/
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) {
EFI_STATUS status;

View File

@@ -20,32 +20,131 @@
#define APIC_IPI_STARTUP 0x600
#ifdef __cplusplus
// C++ APIC class
/**
* @class APIC
* @brief Advanced Programmable Interrupt Controller manager for multicore systems
*
* The APIC (Advanced Programmable Interrupt Controller) replaces the legacy 8259 PIC
* in modern x86-64 systems. Each CPU core has its own Local APIC that:
* - Receives interrupts from devices and other CPUs
* - Sends End-Of-Interrupt (EOI) signals
* - Sends Inter-Processor Interrupts (IPIs) to other cores
* - Manages per-core timer and performance monitoring interrupts
*
* The Local APIC is memory-mapped at physical address 0xFEE00000 by default.
* All APIC operations are performed by reading/writing 32-bit registers at
* specific offsets from this base address.
*
* Key APIC concepts:
* - Local APIC: One per CPU core, handles that core's interrupts
* - APIC ID: Unique identifier for each Local APIC (each CPU core)
* - IPI (Inter-Processor Interrupt): Interrupt sent from one CPU to another
* - EOI (End-Of-Interrupt): Signal that interrupt handling is complete
* - ICR (Interrupt Command Register): Used to send IPIs to other cores
*/
class APIC {
private:
volatile uint32_t* apicBase;
volatile uint32_t* apicBase; ///< Pointer to APIC memory-mapped registers (0xFEE00000)
/**
* @brief Read a 32-bit value from an APIC register
* @param offset Register offset from APIC base (e.g., APIC_REG_ID)
* @return 32-bit register value
*/
uint32_t read(uint32_t offset) const;
/**
* @brief Write a 32-bit value to an APIC register
* @param offset Register offset from APIC base
* @param value 32-bit value to write
*/
void write(uint32_t offset, uint32_t value);
public:
/** @brief Constructor - initializes APIC base address to 0xFEE00000 */
APIC();
/**
* @brief Check if APIC is available on this CPU
* @return true if APIC is present and can be used, false otherwise
* @note Uses CPUID instruction to check for APIC support (bit 9 of EDX)
*/
bool isAvailable() const;
/**
* @brief Initialize the Local APIC for this CPU core
* @note Enables the APIC by setting bit 8 of the Spurious Interrupt Vector Register
* @note Sets Task Priority Register to 0 to accept all interrupts
* @note Must be called on each CPU core that will use the APIC
*/
void init();
/**
* @brief Get the APIC ID of the current CPU core
* @return 8-bit APIC ID (unique identifier for this core)
* @note APIC IDs may not be sequential (e.g., 0, 2, 4, 6 on hyperthreaded systems)
*/
uint8_t getId() const;
/**
* @brief Send End-Of-Interrupt signal to acknowledge interrupt completion
* @note Must be called at the end of every interrupt handler that uses the APIC
* @note Writing any value to the EOI register acknowledges the interrupt
*/
void sendEOI();
/**
* @brief Send an Inter-Processor Interrupt to another CPU core
* @param destApicId APIC ID of the destination CPU core
* @param vector Interrupt vector number to deliver (0-255)
* @param deliveryMode Delivery mode flags (e.g., APIC_IPI_INIT, APIC_IPI_STARTUP)
*
* IPIs are used for:
* - Starting application processors (APs) during SMP initialization
* - TLB shootdowns when changing page tables
* - Sending signals between cores
* - Emergency core wakeup or halt
*
* @note Waits for previous IPI to complete before sending new one
*/
void sendIPI(uint8_t destApicId, uint8_t vector, uint32_t deliveryMode);
};
extern "C" {
#endif
// C-compatible APIC functions
/* APIC - C-compatible interface */
/**
* @brief Check if Local APIC is available on this system
* @return true if APIC is present, false otherwise
*/
bool apic_is_available(void);
/**
* @brief Initialize the Local APIC for the current CPU core
* @note Must be called on each core before using APIC functionality
*/
void apic_init(void);
/**
* @brief Get APIC ID of the current CPU core
* @return 8-bit APIC ID
*/
uint8_t apic_get_id(void);
/**
* @brief Send End-Of-Interrupt signal
* @note Call at end of interrupt handlers that use APIC
*/
void apic_send_eoi(void);
/**
* @brief Send Inter-Processor Interrupt to another core
* @param dest_apic_id Target core's APIC ID
* @param vector Interrupt vector number
* @param delivery_mode IPI delivery mode (APIC_IPI_INIT, APIC_IPI_STARTUP, etc.)
*/
void apic_send_ipi(uint8_t dest_apic_id, uint8_t vector, uint32_t delivery_mode);
#ifdef __cplusplus

View File

@@ -3,7 +3,25 @@
#include <stdint.h>
// GDT Entry structure
/**
* @struct gdt_entry_t
* @brief Global Descriptor Table entry structure (8 bytes)
*
* The GDT defines memory segments in protected/long mode. Each entry describes
* a segment with base address, limit (size), and access rights. In 64-bit long mode,
* segmentation is mostly disabled, but the GDT is still required for:
* - Code/Data segment selectors
* - Privilege level enforcement (Ring 0 kernel, Ring 3 user)
* - System call/interrupt handling
*
* Each GDT entry is 8 bytes with the following layout:
* - limit_low (16 bits): Lower 16 bits of segment limit
* - base_low (16 bits): Lower 16 bits of base address
* - base_middle (8 bits): Middle 8 bits of base address
* - access (8 bits): Access flags (present, privilege, type)
* - granularity (8 bits): Upper 4 bits of limit + flags (granularity, size)
* - base_high (8 bits): Upper 8 bits of base address
*/
typedef struct {
uint16_t limit_low;
uint16_t base_low;
@@ -13,31 +31,77 @@ typedef struct {
uint8_t base_high;
} __attribute__((packed)) gdt_entry_t;
// GDT Pointer structure
/**
* @struct gdt_ptr_t
* @brief GDT pointer structure used by LGDT instruction
*
* This structure is loaded into the CPU using the LGDT (Load GDT) instruction.
* It tells the CPU where the GDT is located in memory and how large it is.
*
* - limit: Size of GDT in bytes minus 1
* - base: 64-bit linear address of the GDT
*/
typedef struct {
uint16_t limit;
uint64_t base;
} __attribute__((packed)) gdt_ptr_t;
#ifdef __cplusplus
// C++ GDT class
/**
* @class GDT
* @brief Global Descriptor Table manager for x86-64 long mode
*
* The GDT (Global Descriptor Table) is a data structure used by x86 processors
* to define memory segments. Although segmentation is mostly flat in 64-bit mode,
* the GDT is still required for:
* - Defining code and data segments
* - Setting privilege levels (CPL 0 for kernel, CPL 3 for user)
* - Enabling system calls and interrupts
*
* MetalOS uses a minimal 5-entry GDT:
* 0. Null descriptor (required by CPU, never used)
* 1. Kernel code segment (64-bit, ring 0, executable)
* 2. Kernel data segment (64-bit, ring 0, writable)
* 3. User code segment (64-bit, ring 3, executable)
* 4. User data segment (64-bit, ring 3, writable)
*
* In 64-bit mode, segment bases and limits are mostly ignored, but the access
* rights (privilege level, executable flag) are still enforced.
*/
class GDT {
private:
gdt_entry_t entries[5];
gdt_ptr_t gdtPtr;
gdt_entry_t entries[5]; ///< Array of 5 GDT entries
gdt_ptr_t gdtPtr; ///< GDT pointer for LGDT instruction
/**
* @brief Set a GDT entry with specified parameters
* @param num Entry index (0-4)
* @param base Base address (mostly ignored in 64-bit mode)
* @param limit Segment limit (mostly ignored in 64-bit mode)
* @param access Access byte (present, DPL, type flags)
* @param gran Granularity byte (upper limit bits + flags)
*/
void setGate(int num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran);
public:
/** @brief Constructor - initializes GDT pointer structure */
GDT();
/**
* @brief Initialize the GDT and load it into the CPU
* @note Sets up 5 descriptors: null, kernel code/data, user code/data
* @note Calls gdt_flush() assembly function to load GDT using LGDT
*/
void init();
};
extern "C" {
#endif
// C-compatible GDT function
/**
* @brief Initialize the Global Descriptor Table
* @note Must be called early in kernel initialization before enabling interrupts
*/
void gdt_init(void);
#ifdef __cplusplus

View File

@@ -3,53 +3,140 @@
#include <stdint.h>
// IDT Entry structure
/**
* @struct idt_entry_t
* @brief Interrupt Descriptor Table entry structure (16 bytes in 64-bit mode)
*
* Each IDT entry describes how to handle a specific interrupt or exception.
* The IDT contains 256 entries for interrupt vectors 0-255:
* - Vectors 0-31: CPU exceptions (divide by zero, page fault, etc.)
* - Vectors 32-47: Hardware IRQs (after PIC remap)
* - Vectors 48-255: Available for software interrupts
*
* In 64-bit mode, IDT entries are 16 bytes and point to interrupt handler functions.
*/
typedef struct {
uint16_t offset_low; // Offset bits 0-15
uint16_t selector; // Code segment selector
uint8_t ist; // Interrupt Stack Table offset
uint8_t type_attr; // Type and attributes
uint16_t offset_mid; // Offset bits 16-31
uint32_t offset_high; // Offset bits 32-63
uint32_t zero; // Reserved
uint16_t offset_low; ///< Lower 16 bits of handler function address
uint16_t selector; ///< Code segment selector (typically 0x08 for kernel code)
uint8_t ist; ///< Interrupt Stack Table offset (0 = use current stack)
uint8_t type_attr; ///< Type and attributes (present, DPL, gate type)
uint16_t offset_mid; ///< Middle 16 bits of handler function address
uint32_t offset_high; ///< Upper 32 bits of handler function address
uint32_t zero; ///< Reserved, must be zero
} __attribute__((packed)) idt_entry_t;
// IDT Pointer structure
/**
* @struct idt_ptr_t
* @brief IDT pointer structure used by LIDT instruction
*
* This structure is loaded into the CPU using the LIDT (Load IDT) instruction.
* It tells the CPU where the IDT is located in memory and how large it is.
*/
typedef struct {
uint16_t limit;
uint64_t base;
uint16_t limit; ///< Size of IDT in bytes minus 1
uint64_t base; ///< 64-bit linear address of the IDT
} __attribute__((packed)) idt_ptr_t;
// CPU registers state (for interrupt handlers)
/**
* @struct registers_t
* @brief CPU register state saved during interrupt handling
*
* This structure represents the complete CPU state at the time an interrupt occurred.
* It is pushed onto the stack by the interrupt handler assembly code and passed to
* the C interrupt handler. It includes:
* - General purpose registers (rax, rbx, rcx, etc.)
* - Interrupt number and error code
* - Execution state (rip, rsp, rflags) automatically pushed by CPU
*/
typedef struct {
uint64_t r15, r14, r13, r12, r11, r10, r9, r8;
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
uint64_t int_no, err_code;
uint64_t rip, cs, rflags, rsp, ss;
uint64_t r15, r14, r13, r12, r11, r10, r9, r8; ///< Extended GP registers
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax; ///< Standard GP registers
uint64_t int_no, err_code; ///< Interrupt number and error code
uint64_t rip, cs, rflags, rsp, ss; ///< CPU state (pushed by CPU)
} __attribute__((packed)) registers_t;
#ifdef __cplusplus
// C++ InterruptManager class
/**
* @class InterruptManager
* @brief Manages the Interrupt Descriptor Table and interrupt handling
*
* The InterruptManager class is responsible for:
* - Setting up the IDT with 256 interrupt vectors
* - Installing interrupt handler functions
* - Remapping the legacy 8259 PIC (Programmable Interrupt Controller)
* - Dispatching interrupts to appropriate handlers
* - Sending End-Of-Interrupt signals to PIC or APIC
*
* Key concepts:
* - ISR (Interrupt Service Routine): Handles CPU exceptions (0-31)
* - IRQ (Interrupt Request): Handles hardware interrupts (32-47 after remap)
* - PIC (Programmable Interrupt Controller): Legacy 8259 chip that manages IRQs
* - APIC (Advanced PIC): Modern interrupt controller for multicore systems
*
* The PIC is remapped because the default IRQ vectors (0-15) conflict with
* CPU exception vectors. We remap IRQs to vectors 32-47.
*/
class InterruptManager {
private:
idt_entry_t idt[256];
idt_ptr_t idtPtr;
idt_entry_t idt[256]; ///< Array of 256 IDT entries
idt_ptr_t idtPtr; ///< IDT pointer for LIDT instruction
/**
* @brief Set an IDT entry to point to an interrupt handler
* @param num Interrupt vector number (0-255)
* @param handler Address of interrupt handler function
* @param selector Code segment selector (0x08 for kernel code)
* @param flags Type and attribute flags (present, DPL, gate type)
*/
void setGate(uint8_t num, uint64_t handler, uint16_t selector, uint8_t flags);
/**
* @brief Remap the 8259 PIC to use different IRQ vectors
* @note Remaps IRQ 0-7 to vectors 32-39, IRQ 8-15 to vectors 40-47
* @note Masks all IRQs initially; they must be explicitly unmasked to receive
*/
void remapPIC();
public:
/** @brief Constructor - initializes IDT pointer structure */
InterruptManager();
/**
* @brief Initialize the IDT and enable interrupts
* @note Sets up all 256 IDT entries
* @note Installs exception handlers (ISR 0-31)
* @note Remaps and configures the PIC
* @note Installs IRQ handlers (IRQ 0-15 → vectors 32-47)
* @note Loads IDT using LIDT instruction
* @note Enables interrupts using STI instruction
*/
void init();
/**
* @brief Handle an interrupt by dispatching to appropriate handler
* @param regs Pointer to saved CPU register state
* @note Calls specific handlers based on interrupt number
* @note Sends EOI to PIC or APIC after handling
*/
void handleInterrupt(registers_t* regs);
};
extern "C" {
#endif
// C-compatible functions
/* Interrupt Management - C-compatible interface */
/**
* @brief Initialize the Interrupt Descriptor Table and enable interrupts
* @note Must be called after GDT initialization
*/
void idt_init(void);
/**
* @brief Main interrupt handler dispatcher (called from assembly ISRs)
* @param regs Pointer to saved CPU register state
* @note Should only be called from interrupt context
*/
void interrupt_handler(registers_t* regs);
#ifdef __cplusplus

View File

@@ -9,58 +9,230 @@
#define PAGE_SIZE 4096
#ifdef __cplusplus
// C++ PhysicalMemoryManager class
/**
* @class PhysicalMemoryManager
* @brief Manages physical memory pages using a bitmap-based allocator
*
* This class implements a simple physical memory manager that tracks available
* 4KB pages using a bitmap. Each bit in the bitmap represents one page:
* - 0 = page is free and available for allocation
* - 1 = page is in use
*
* The bitmap supports up to 128MB of physical memory (32768 bytes * 8 bits/byte
* * 4KB per page = 128MB). Memory is assumed to start at physical address 0x01000000
* (16MB) to avoid conflicts with legacy hardware and the kernel itself.
*
* This is a very simple allocator suitable for a minimal kernel. It does not:
* - Handle memory regions with different properties
* - Support allocation of multiple contiguous pages at once
* - Track memory usage per process (single application OS)
*/
class PhysicalMemoryManager {
private:
uint8_t pageBitmap[32768]; // Supports up to 128MB with 4KB pages
uint64_t totalPages;
uint64_t usedPages;
uint8_t pageBitmap[32768]; ///< Bitmap tracking page allocation (128MB / 4KB pages)
uint64_t totalPages; ///< Total number of pages managed
uint64_t usedPages; ///< Number of pages currently allocated
public:
/** @brief Constructor - initializes bitmap to all zeros (all pages free) */
PhysicalMemoryManager();
/**
* @brief Initialize the physical memory manager with boot information
* @param bootInfo Boot information from bootloader (currently unused, assumes 128MB)
* @note Currently hardcoded to manage 128MB starting at 16MB physical address
*/
void init(BootInfo* bootInfo);
/**
* @brief Allocate a single 4KB page from physical memory
* @return Physical address of allocated page, or nullptr if out of memory
* @note Returns first available page found in bitmap (no specific allocation strategy)
*/
void* allocPage();
/**
* @brief Free a previously allocated page, returning it to the available pool
* @param page Physical address of the page to free
* @note Does nothing if the page address is invalid or out of managed range
*/
void freePage(void* page);
/**
* @brief Get total amount of physical memory managed (in bytes)
* @return Total memory in bytes (totalPages * PAGE_SIZE)
*/
uint64_t getTotalMemory() const;
/**
* @brief Get amount of free physical memory available (in bytes)
* @return Free memory in bytes ((totalPages - usedPages) * PAGE_SIZE)
*/
uint64_t getFreeMemory() const;
};
// C++ HeapAllocator class
/**
* @class HeapAllocator
* @brief Simple bump allocator for kernel heap memory
*
* This class implements a very simple "bump" or "arena" allocator. Memory is
* allocated by simply incrementing a pointer (heapCurrent) forward. This is
* extremely fast but has limitations:
* - Cannot free individual allocations (free() is a no-op)
* - Memory is only reclaimed when the entire heap is reset
* - Can fragment if many small allocations are made
*
* For a single-application OS that doesn't need complex memory management,
* this simple allocator is sufficient and has minimal code size.
*
* All allocations are aligned to 16-byte boundaries for performance and
* compatibility with SSE/AVX instructions.
*/
class HeapAllocator {
private:
uint8_t* heapStart;
uint8_t* heapCurrent;
uint8_t* heapEnd;
uint8_t* heapStart; ///< Start address of the heap region
uint8_t* heapCurrent; ///< Current allocation pointer (bump pointer)
uint8_t* heapEnd; ///< End address of the heap region (exclusive)
public:
/** @brief Constructor - initializes all pointers to null */
HeapAllocator();
/**
* @brief Initialize the heap allocator with a memory region
* @param start Starting address of heap memory region
* @param size Size of heap region in bytes
* @note The memory region should be obtained from the physical memory manager
*/
void init(void* start, size_t size);
/**
* @brief Allocate memory from the heap
* @param size Number of bytes to allocate
* @return Pointer to allocated memory, or nullptr if out of heap space
* @note Allocation is aligned to 16-byte boundaries
* @note This is a bump allocator - simply moves heapCurrent forward
*/
void* alloc(size_t size);
/**
* @brief Allocate and zero-initialize an array of objects
* @param num Number of elements
* @param size Size of each element in bytes
* @return Pointer to allocated and zeroed memory, or nullptr if out of space
* @note Equivalent to alloc(num * size) followed by memset to zero
*/
void* calloc(size_t num, size_t size);
/**
* @brief Free allocated memory (no-op in bump allocator)
* @param ptr Pointer to memory to free (ignored)
* @note This function does nothing - bump allocators cannot free individual allocations
* @todo Replace with a proper allocator if individual free() is needed
*/
void free(void* ptr);
};
extern "C" {
#endif
// C-compatible physical memory manager
/* Physical Memory Manager - C-compatible interface */
/**
* @brief Initialize the physical memory manager
* @param boot_info Boot information from bootloader (contains memory map)
* @note This must be called early in kernel initialization, before any memory allocation
*/
void pmm_init(BootInfo* boot_info);
/**
* @brief Allocate a single 4KB physical memory page
* @return Physical address of allocated page, or NULL if out of memory
* @note Returns first available page from the page bitmap
*/
void* pmm_alloc_page(void);
/**
* @brief Free a previously allocated physical memory page
* @param page Physical address of the page to free
*/
void pmm_free_page(void* page);
/**
* @brief Get total amount of physical memory managed by PMM
* @return Total memory in bytes
*/
uint64_t pmm_get_total_memory(void);
/**
* @brief Get amount of free physical memory currently available
* @return Free memory in bytes
*/
uint64_t pmm_get_free_memory(void);
// C-compatible kernel heap allocator
/* Kernel Heap Allocator - C-compatible interface */
/**
* @brief Initialize the kernel heap allocator
* @param start Starting address of heap memory region
* @param size Size of heap region in bytes
* @note The memory region should be allocated from physical memory manager first
*/
void heap_init(void* start, size_t size);
/**
* @brief Allocate memory from kernel heap (like malloc)
* @param size Number of bytes to allocate
* @return Pointer to allocated memory, or NULL if out of heap space
* @note Memory is 16-byte aligned
* @note Cannot be freed individually (bump allocator limitation)
*/
void* kmalloc(size_t size);
/**
* @brief Allocate and zero-initialize array memory from kernel heap (like calloc)
* @param num Number of elements
* @param size Size of each element in bytes
* @return Pointer to allocated and zeroed memory, or NULL if out of space
*/
void* kcalloc(size_t num, size_t size);
/**
* @brief Free kernel heap memory (no-op in current implementation)
* @param ptr Pointer to memory to free
* @note This function currently does nothing - bump allocator cannot free individual allocations
*/
void kfree(void* ptr);
// Memory utility functions
/* Memory Utility Functions */
/**
* @brief Fill memory with a constant byte value
* @param dest Pointer to memory block to fill
* @param val Value to set (converted to unsigned char)
* @param count Number of bytes to set
* @return Pointer to dest
* @note Simple byte-by-byte implementation
*/
void* memset(void* dest, int val, size_t count);
/**
* @brief Copy memory from source to destination
* @param dest Pointer to destination buffer
* @param src Pointer to source buffer
* @param count Number of bytes to copy
* @return Pointer to dest
* @warning Memory regions must not overlap
*/
void* memcpy(void* dest, const void* src, size_t count);
/**
* @brief Compare two memory blocks
* @param s1 Pointer to first memory block
* @param s2 Pointer to second memory block
* @param count Number of bytes to compare
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2
*/
int memcmp(const void* s1, const void* s2, size_t count);
#ifdef __cplusplus

View File

@@ -10,22 +10,42 @@
// Maximum devices we'll track
#define MAX_PCI_DEVICES 256
// PCI Device Structure
/**
* @struct pci_device_t
* @brief Represents a PCI device discovered during bus enumeration
*
* PCI (Peripheral Component Interconnect) is the standard bus for connecting
* hardware devices like GPUs, network cards, storage controllers, etc.
*
* Each PCI device is identified by:
* - Bus, device, function numbers (BDF): Physical location
* - Vendor ID and Device ID: Identifies manufacturer and model
* - Class code, subclass, prog_if: Type of device (GPU, storage, etc.)
* - Base Address Registers (BARs): Memory/IO regions used by device
*
* Example vendor IDs:
* - 0x1002: AMD/ATI (used by Radeon GPUs)
* - 0x10DE: NVIDIA
* - 0x8086: Intel
*/
typedef struct {
uint8_t bus;
uint8_t device;
uint8_t function;
uint16_t vendor_id;
uint16_t device_id;
uint8_t class_code;
uint8_t subclass;
uint8_t prog_if;
uint8_t revision_id;
uint32_t bar[6]; // Base Address Registers
uint8_t bus; ///< PCI bus number (0-255)
uint8_t device; ///< Device number on bus (0-31)
uint8_t function; ///< Function number within device (0-7)
uint16_t vendor_id; ///< Vendor identifier (e.g., 0x1002 for AMD)
uint16_t device_id; ///< Device identifier (specific model)
uint8_t class_code; ///< Device class (0x03 = display controller)
uint8_t subclass; ///< Device subclass (0x00 = VGA compatible)
uint8_t prog_if; ///< Programming interface
uint8_t revision_id; ///< Device revision
uint32_t bar[6]; ///< Base Address Registers (memory/IO regions)
} pci_device_t;
#ifdef __cplusplus
// C++ PCIDevice class-compatible structure
/**
* @struct PCIDevice
* @brief C++ equivalent of pci_device_t for type compatibility
*/
struct PCIDevice {
uint8_t bus;
uint8_t device;
@@ -39,32 +59,133 @@ struct PCIDevice {
uint32_t bar[6];
};
// C++ PCIManager class
/**
* @class PCIManager
* @brief Manages PCI bus enumeration and device configuration
*
* The PCIManager scans the PCI bus hierarchy to discover all connected devices.
* PCI configuration is done through two I/O ports:
* - 0xCF8 (CONFIG_ADDRESS): Specifies which device and register to access
* - 0xCFC (CONFIG_DATA): Reads/writes the configuration register
*
* PCI devices are organized hierarchically:
* - Up to 256 buses
* - Up to 32 devices per bus
* - Up to 8 functions per device (most devices have only function 0)
*
* Each device has 256 bytes of configuration space containing:
* - Device identification (vendor/device ID)
* - Command and status registers
* - Base Address Registers (BARs) for memory-mapped I/O
* - Interrupt configuration
* - Device-specific registers
*/
class PCIManager {
private:
PCIDevice devices[MAX_PCI_DEVICES];
uint32_t deviceCount;
PCIDevice devices[MAX_PCI_DEVICES]; ///< Array of discovered devices
uint32_t deviceCount; ///< Number of devices found
/**
* @brief Probe a specific PCI function and add to device list
* @param bus Bus number
* @param device Device number
* @param function Function number
* @note Reads device identification and stores in devices array
*/
void probeDevice(uint8_t bus, uint8_t device, uint8_t function);
public:
/** @brief Constructor - initializes device count to 0 */
PCIManager();
/**
* @brief Scan all PCI buses and enumerate devices
* @note Scans all 256 buses, 32 devices per bus, up to 8 functions per device
* @note Skips non-existent devices (vendor ID 0xFFFF)
*/
void init();
/**
* @brief Read a 32-bit value from PCI configuration space
* @param bus Bus number
* @param device Device number
* @param function Function number
* @param offset Register offset (must be 4-byte aligned)
* @return 32-bit configuration register value
*/
uint32_t readConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
/**
* @brief Write a 32-bit value to PCI configuration space
* @param bus Bus number
* @param device Device number
* @param function Function number
* @param offset Register offset (must be 4-byte aligned)
* @param value Value to write
*/
void writeConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
/**
* @brief Find a PCI device by vendor and device ID
* @param vendor_id Vendor identifier (e.g., 0x1002 for AMD)
* @param device_id Device identifier
* @return Pointer to PCIDevice if found, nullptr if not found
* @note Useful for finding specific hardware (e.g., "find AMD RX 6600 GPU")
*/
PCIDevice* findDevice(uint16_t vendor_id, uint16_t device_id);
/**
* @brief Enable bus mastering for a PCI device
* @param dev Pointer to PCI device
* @note Bus mastering allows device to perform DMA (Direct Memory Access)
* @note Required for most devices to function properly
*/
void enableBusMastering(PCIDevice* dev);
};
extern "C" {
#endif
// C-compatible PCI functions
/* PCI - C-compatible interface */
/**
* @brief Initialize PCI subsystem and enumerate all devices
* @note Should be called during kernel initialization
*/
void pci_init(void);
/**
* @brief Read from PCI configuration space
* @param bus Bus number
* @param device Device number
* @param function Function number
* @param offset Register offset
* @return 32-bit register value
*/
uint32_t pci_read_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
/**
* @brief Write to PCI configuration space
* @param bus Bus number
* @param device Device number
* @param function Function number
* @param offset Register offset
* @param value Value to write
*/
void pci_write_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
/**
* @brief Find a PCI device by vendor and device ID
* @param vendor_id Vendor identifier
* @param device_id Device identifier
* @return Pointer to device structure if found, NULL otherwise
*/
pci_device_t* pci_find_device(uint16_t vendor_id, uint16_t device_id);
/**
* @brief Enable bus mastering for a PCI device
* @param dev Pointer to PCI device structure
*/
void pci_enable_bus_mastering(pci_device_t* dev);
#ifdef __cplusplus

View File

@@ -7,46 +7,158 @@
// Maximum number of CPUs we support
#define MAX_CPUS 16
// Per-CPU data structure
/**
* @struct cpu_info_t
* @brief Per-CPU information structure
*
* Stores information about each CPU core in a multicore system.
*/
typedef struct {
uint8_t cpu_id;
uint8_t apic_id;
bool online;
uint64_t kernel_stack;
uint8_t cpu_id; ///< Logical CPU ID (0, 1, 2, ...)
uint8_t apic_id; ///< Physical APIC ID (may not be sequential)
bool online; ///< True if CPU is initialized and running
uint64_t kernel_stack; ///< Kernel stack pointer for this CPU (future use)
} cpu_info_t;
#ifdef __cplusplus
// C++ SMPManager class
/**
* @class SMPManager
* @brief Symmetric Multi-Processing manager for multicore CPU initialization
*
* SMP (Symmetric Multi-Processing) allows an operating system to use multiple
* CPU cores. In x86-64 systems:
* - One core (BSP - Bootstrap Processor) starts first and initializes the system
* - Other cores (APs - Application Processors) must be explicitly started by the BSP
*
* The SMP initialization process:
* 1. BSP initializes its own Local APIC
* 2. BSP discovers other cores (from ACPI tables or by probing)
* 3. BSP sends INIT IPI to each AP (reset the core)
* 4. BSP sends SIPI (Startup IPI) with entry point address to each AP
* 5. AP starts executing at entry point, initializes itself
* 6. AP marks itself as online
*
* The SIPI protocol requires:
* - A small "trampoline" code in low memory (below 1MB) in real mode
* - Two SIPI messages sent with specific timing (per Intel spec)
* - Waiting for AP to signal it's online
*
* Once all cores are initialized, they can be used for parallel processing,
* though MetalOS currently only uses the BSP for application execution.
*/
class SMPManager {
private:
cpu_info_t cpuInfo[MAX_CPUS];
uint8_t cpuCount;
bool smpEnabled;
cpu_info_t cpuInfo[MAX_CPUS]; ///< Information for each CPU core
uint8_t cpuCount; ///< Total number of CPU cores detected
bool smpEnabled; ///< True if multiple cores are available
/**
* @brief Initialize CPU info structure for a core
* @param cpuId Logical CPU ID
* @param apicId Physical APIC ID
*/
void initCPU(uint8_t cpuId, uint8_t apicId);
/**
* @brief Busy-wait delay (approximate)
* @param microseconds Delay duration in microseconds
* @note Not precise, used for timing during AP startup
*/
void delay(uint32_t microseconds);
/**
* @brief Start an Application Processor using SIPI protocol
* @param apicId APIC ID of the AP to start
* @return true if AP started successfully, false on timeout
* @note Sends INIT IPI followed by two SIPI messages
*/
bool startAP(uint8_t apicId);
public:
/** @brief Constructor - initializes BSP as first CPU */
SMPManager();
/**
* @brief Initialize SMP subsystem and start all available CPU cores
* @note Checks for APIC availability
* @note Initializes BSP's APIC
* @note Attempts to start up to MAX_CPUS cores
* @note Falls back to single-core mode if APIC unavailable
*/
void init();
/**
* @brief Get total number of CPU cores detected
* @return Number of CPU cores (at least 1)
*/
uint8_t getCPUCount() const;
/**
* @brief Get logical ID of the currently executing CPU core
* @return CPU ID (0 for BSP)
*/
uint8_t getCurrentCPU() const;
/**
* @brief Check if SMP is enabled (multiple cores available)
* @return true if multiple cores detected, false if single-core
*/
bool isEnabled() const;
/**
* @brief Get information structure for a specific CPU
* @param cpuId Logical CPU ID
* @return Pointer to cpu_info_t structure, or nullptr if invalid ID
*/
cpu_info_t* getCPUInfo(uint8_t cpuId);
/**
* @brief Mark a CPU as online (called by AP during initialization)
* @param cpuId Logical CPU ID
*/
void markCPUOnline(uint8_t cpuId);
};
extern "C" {
#endif
// C-compatible SMP functions
/* SMP - C-compatible interface */
/**
* @brief Initialize SMP subsystem and start all CPU cores
* @note Should be called after APIC initialization
*/
void smp_init(void);
/**
* @brief Get number of CPU cores detected
* @return Number of CPU cores
*/
uint8_t smp_get_cpu_count(void);
/**
* @brief Get ID of current CPU core
* @return Logical CPU ID
*/
uint8_t smp_get_current_cpu(void);
/**
* @brief Check if SMP is enabled
* @return true if multiple cores available, false if single-core
*/
bool smp_is_enabled(void);
/**
* @brief Get CPU information structure
* @param cpu_id Logical CPU ID
* @return Pointer to cpu_info_t structure or NULL
*/
cpu_info_t* smp_get_cpu_info(uint8_t cpu_id);
/**
* @brief Mark CPU as online (called by AP during startup)
* @param cpu_id Logical CPU ID
*/
void smp_cpu_online(uint8_t cpu_id);
#ifdef __cplusplus

View File

@@ -5,34 +5,120 @@
#include <stdbool.h>
#ifdef __cplusplus
// C++ Spinlock class
/**
* @class Spinlock
* @brief Simple spinlock for multicore synchronization
*
* A spinlock is a synchronization primitive used to protect shared data in
* multicore systems. Unlike a mutex that blocks (sleeps), a spinlock "spins"
* in a tight loop waiting for the lock to become available.
*
* Spinlocks are appropriate when:
* - Critical sections are very short (a few instructions)
* - You're in interrupt context (can't sleep)
* - Lock contention is rare
*
* Spinlocks should NOT be used when:
* - Critical section is long (wastes CPU cycles)
* - Lock might be held for unpredictable time
* - You can use a sleeping lock instead
*
* Implementation uses x86 XCHG instruction which is:
* - Atomic (indivisible operation)
* - Automatically locked (works across multiple cores)
* - Sequentially consistent (no memory reordering issues)
*
* The PAUSE instruction in the spin loop:
* - Reduces power consumption while spinning
* - Improves performance on hyperthreaded CPUs
* - Signals to CPU that we're in a spin-wait loop
*/
class Spinlock {
private:
volatile uint32_t lock;
volatile uint32_t lock; ///< Lock state: 0 = unlocked, 1 = locked
public:
/** @brief Constructor - initializes lock to unlocked state */
Spinlock();
/**
* @brief Initialize spinlock (set to unlocked)
* @note Can be used to re-initialize an existing spinlock
*/
void init();
/**
* @brief Acquire the spinlock, spinning until available
* @note Will loop indefinitely if lock is never released
* @note Disables interrupts on this CPU while spinning (not implemented here)
*/
void acquire();
/**
* @brief Try to acquire spinlock without blocking
* @return true if lock was acquired, false if already locked
* @note Returns immediately without spinning
*/
bool tryAcquire();
/**
* @brief Release the spinlock
* @note Must only be called by the CPU that acquired the lock
* @note Includes memory barrier to ensure all writes are visible
*/
void release();
/**
* @brief Check if spinlock is currently locked
* @return true if locked, false if unlocked
* @note Result may be stale immediately after checking
*/
bool isLocked() const;
};
extern "C" {
#endif
// C-compatible spinlock structure
/**
* @struct spinlock_t
* @brief C-compatible spinlock structure
*/
typedef struct {
volatile uint32_t lock;
volatile uint32_t lock; ///< Lock state: 0 = unlocked, 1 = locked
} spinlock_t;
// C-compatible functions
/* Spinlock - C-compatible interface */
/**
* @brief Initialize a spinlock
* @param lock Pointer to spinlock structure
*/
void spinlock_init(spinlock_t* lock);
/**
* @brief Acquire a spinlock (spin until available)
* @param lock Pointer to spinlock structure
*/
void spinlock_acquire(spinlock_t* lock);
/**
* @brief Try to acquire spinlock without blocking
* @param lock Pointer to spinlock structure
* @return true if acquired, false if already locked
*/
bool spinlock_try_acquire(spinlock_t* lock);
/**
* @brief Release a spinlock
* @param lock Pointer to spinlock structure
*/
void spinlock_release(spinlock_t* lock);
/**
* @brief Check if spinlock is locked
* @param lock Pointer to spinlock structure
* @return true if locked, false if unlocked
*/
bool spinlock_is_locked(spinlock_t* lock);
#ifdef __cplusplus

View File

@@ -7,27 +7,93 @@
#define TIMER_FREQUENCY 1000 // 1ms per tick
#ifdef __cplusplus
// C++ Timer class
/**
* @class Timer
* @brief Programmable Interval Timer (PIT) manager for system timing
*
* The PIT (Programmable Interval Timer), also known as the 8253/8254 chip,
* generates periodic timer interrupts at a configurable frequency. It's used for:
* - System timekeeping (tracking elapsed time)
* - Scheduling and preemption (in multi-tasking systems)
* - Delays and timeouts
*
* The PIT has a base frequency of 1.193182 MHz. By programming a divisor,
* we can generate interrupts at lower frequencies. For example:
* - Divisor 1193 → ~1000 Hz (1ms per tick) ← MetalOS uses this
* - Divisor 119318 → ~10 Hz (100ms per tick)
*
* Timer interrupts are delivered as IRQ0, which is mapped to interrupt vector 32
* after the PIC is remapped.
*
* @note The PIT is legacy hardware but still commonly available. Modern systems
* may use HPET (High Precision Event Timer) or APIC timer instead.
*/
class Timer {
private:
volatile uint64_t ticks;
volatile uint64_t ticks; ///< Number of timer interrupts received since initialization
public:
/** @brief Constructor - initializes tick counter to 0 */
Timer();
/**
* @brief Initialize the PIT with specified interrupt frequency
* @param frequency Desired interrupt frequency in Hz (e.g., 1000 for 1ms ticks)
* @note Calculates divisor = 1193182 / frequency and programs the PIT
* @note Unmasks IRQ0 in the PIC to enable timer interrupts
*/
void init(uint32_t frequency);
/**
* @brief Get the current tick count
* @return Number of timer ticks since initialization
* @note At 1000 Hz, each tick represents 1 millisecond
*/
uint64_t getTicks() const;
/**
* @brief Wait for a specified number of timer ticks
* @param waitTicks Number of ticks to wait
* @note Uses HLT instruction to save power while waiting
* @note Blocking wait - CPU will be idle during this time
*/
void wait(uint32_t waitTicks) const;
/**
* @brief Handle timer interrupt (called from IRQ0 handler)
* @note Increments the tick counter
* @note Should be called from interrupt context only
*/
void handleInterrupt();
};
extern "C" {
#endif
// C-compatible timer functions
/* Timer - C-compatible interface */
/**
* @brief Initialize the Programmable Interval Timer
* @param frequency Interrupt frequency in Hz (typically 1000 for 1ms ticks)
*/
void timer_init(uint32_t frequency);
/**
* @brief Get current timer tick count
* @return Number of ticks since timer initialization
*/
uint64_t timer_get_ticks(void);
/**
* @brief Wait for specified number of timer ticks
* @param ticks Number of ticks to wait
*/
void timer_wait(uint32_t ticks);
/**
* @brief Timer interrupt handler (called from IRQ0)
* @note Should only be called from interrupt context
*/
void timer_handler(void);
#ifdef __cplusplus