From f2e7d6c5d7850fe39efad126b1852bdf7b069c80 Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 10:57:56 +0000 Subject: [PATCH] Fix PMM: switch to Multiboot2 boot protocol and add documentation (AI) - Changed grub.cfg from 'multiboot' to 'multiboot2' command. The PMM parses Multiboot2 tag structures, but GRUB was booting with Multiboot1 protocol, causing the memory map parsing to silently fail (all memory stayed marked as used, leading to OOM on every allocation). - Fixed BITMAP_SIZE calculation to properly round up instead of truncating, ensuring the last few pages of the address space are covered. - Fixed sign comparison warning in bitmap init loop. - Added debug output to PMM init (mem_upper, region count) for diagnostics. - Removed stale Multiboot1 magic constant and (void)addr cast from kernel.c. - Added documentation for the interrupt subsystem and PMM in docs/. - Checked off 'Implement a PIC handler' and 'Create a physical memory allocator' in the task list. Tested: kernel boots in QEMU with both 4MB and 128MB RAM, PMM correctly allocates from NORMAL zone (0x01000000) and DMA zone (0x00001000). --- CMakeLists.txt | 2 +- README.md | 4 +-- docs/interrupts.md | 62 +++++++++++++++++++++++++++++++++++++++++++++ docs/pmm.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++ src/kernel.c | 6 +---- src/pmm.c | 21 +++++++++++++--- 6 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 docs/interrupts.md create mode 100644 docs/pmm.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d919c..d089cc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub) # Create grub.cfg for ISO -file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot /boot/kernel.bin }") +file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin }") # ISO Generation diff --git a/README.md b/README.md index 1359432..ffcd187 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ Once a task is completed, it should be checked off. - [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now) - [x] Update the kernel to correctly setup the GDT - [x] Create an interrupt handler. -- [ ] Implement a PIC handler -- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone. +- [x] Implement a PIC handler +- [x] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone. - [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will. - [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them. - [ ] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not. diff --git a/docs/interrupts.md b/docs/interrupts.md new file mode 100644 index 0000000..5df6a92 --- /dev/null +++ b/docs/interrupts.md @@ -0,0 +1,62 @@ +# Interrupt Subsystem + +## Overview + +The interrupt subsystem handles both CPU exceptions (faults, traps, aborts) and hardware interrupts (IRQs) from external devices. It consists of three cooperating components: + +1. **IDT (Interrupt Descriptor Table)** — Maps interrupt vectors 0–255 to handler entry points. +2. **ISR (Interrupt Service Routines)** — Assembly stubs and a C dispatcher that routes interrupts. +3. **PIC (Programmable Interrupt Controller)** — Manages the 8259A PIC chips that multiplex hardware IRQs onto CPU interrupt lines. + +## Architecture + +### Interrupt Vector Layout + +| Vector Range | Purpose | +|---|---| +| 0–31 | CPU exceptions (Division by Zero, Page Fault, GPF, etc.) | +| 32–47 | Hardware IRQs (remapped from default 0–15) | +| 48–255 | Available for software interrupts / future use | + +### Flow of an Interrupt + +1. CPU or device raises an interrupt. +2. CPU looks up the vector in the IDT and jumps to the assembly stub. +3. The stub pushes a dummy error code (if the CPU didn't push one), the interrupt number, and all general-purpose registers onto the stack. +4. The stub loads the kernel data segment (0x10) and calls `isr_handler()` in C. +5. For hardware interrupts (vectors 32–47), `isr_handler` sends an End-of-Interrupt (EOI) to the PIC. +6. For CPU exceptions (vectors 0–31), the handler prints the exception name and halts. +7. On return, the stub restores all registers and executes `iret`. + +### PIC Remapping + +The 8259A PIC ships with IRQ 0–7 mapped to vectors 8–15, which collide with CPU exceptions. During initialization, we remap: + +- **Master PIC** (IRQ 0–7) → vectors 32–39 +- **Slave PIC** (IRQ 8–15) → vectors 40–47 + +The PIC is initialized in cascade mode with ICW4 (8086 mode). Original IRQ masks are saved and restored after remapping. + +## Key Files + +- `src/idt.c` / `src/idt.h` — IDT setup and gate registration. +- `src/interrupts.S` — Assembly stubs for ISRs 0–31 and IRQs 0–15. +- `src/isr.c` / `src/isr.h` — C interrupt dispatcher and `registers_t` structure. +- `src/pic.c` / `src/pic.h` — PIC initialization, EOI, mask/unmask. +- `src/port_io.h` — Inline `inb`, `outb`, `io_wait` helpers. + +## Register Save Frame + +When an interrupt fires, the following is pushed onto the stack (from high to low address): + +``` +ss, useresp (only on privilege change) +eflags +cs, eip (pushed by CPU) +err_code (pushed by CPU or stub as 0) +int_no (pushed by stub) +eax..edi (pushed by pusha) +ds (pushed by stub) +``` + +This matches the `registers_t` struct in `isr.h`. diff --git a/docs/pmm.md b/docs/pmm.md new file mode 100644 index 0000000..a463e2c --- /dev/null +++ b/docs/pmm.md @@ -0,0 +1,63 @@ +# Physical Memory Manager (PMM) + +## Overview + +The PMM manages physical page frames using a bitmap allocator. It tracks which 4 KiB pages of physical RAM are free or in use, and supports allocating pages from different memory zones. + +## Architecture + +### Bitmap Allocator + +Each bit in a global bitmap corresponds to one 4 KiB physical page frame: +- **Bit = 1** → page is in use (allocated or reserved) +- **Bit = 0** → page is free + +The bitmap covers the entire 32-bit physical address space (up to 4 GiB), requiring 128 KiB of storage in BSS. + +### Memory Zones + +The allocator supports zone-based allocation to satisfy constraints from different subsystems: + +| Zone | Range | Purpose | +|---|---|---| +| `PMM_ZONE_DMA` | 0 – 16 MiB | ISA DMA-compatible memory | +| `PMM_ZONE_NORMAL` | 16 MiB – 4 GiB | General-purpose allocation | + +When `PMM_ZONE_NORMAL` is requested but no pages are available above 16 MiB (e.g., on systems with less than 16 MiB of RAM), the allocator falls back to `PMM_ZONE_DMA`. + +Page 0 (address 0x00000000) is always marked as used to prevent returning NULL as a valid physical address. + +### Initialization + +1. The entire bitmap is initialized to "all used" (0xFFFFFFFF). +2. The Multiboot2 memory map is parsed to discover available RAM regions. +3. Available regions are marked as free in the bitmap. +4. The kernel's own memory (between `_kernel_start` and `_kernel_end` linker symbols) is re-marked as used. +5. The Multiboot2 info structure itself is marked as used. + +### Linker Symbols + +The linker script exports `_kernel_start` and `_kernel_end` symbols so the PMM knows which physical pages the kernel occupies and must not allocate. + +## API + +```c +void init_pmm(uint32_t multiboot_addr); +phys_addr_t pmm_alloc_page(pmm_zone_t zone); +void pmm_free_page(phys_addr_t addr); +``` + +- `init_pmm` — Parse Multiboot2 info and build the free-page bitmap. +- `pmm_alloc_page` — Allocate a single 4 KiB page from the specified zone. Returns 0 on OOM. +- `pmm_free_page` — Return a page to the free pool. + +## Key Files + +- `src/pmm.c` / `src/pmm.h` — Allocator implementation and zone definitions. +- `src/linker.ld` — Exports `_kernel_start` / `_kernel_end`. + +## Limitations + +- First-fit allocation (linear scan) — O(n) per allocation. +- No per-zone free counts or statistics yet. +- Does not handle memory hot-plug or ACPI reclaim regions. diff --git a/src/kernel.c b/src/kernel.c index 842ac93..3c1b579 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -15,8 +15,6 @@ void offset_print(const char *str) } } -#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 - void print_hex(uint32_t val) { const char *hex = "0123456789ABCDEF"; @@ -29,9 +27,7 @@ void print_hex(uint32_t val) } void kernel_main(uint32_t magic, uint32_t addr) { - (void)addr; // Unused for now - - if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) { + if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) { offset_print("Invalid magic number: "); print_hex(magic); return; diff --git a/src/pmm.c b/src/pmm.c index 1cadbbc..0637fd9 100644 --- a/src/pmm.c +++ b/src/pmm.c @@ -1,12 +1,18 @@ #include "pmm.h" +#include "port_io.h" #include extern uint32_t _kernel_start; extern uint32_t _kernel_end; +/* Printing helpers (defined in kernel.c) */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + #define MAX_PHYSICAL_MEMORY 0xFFFFFFFF // 4GB -#define BITMAP_SIZE (MAX_PHYSICAL_MEMORY / PAGE_SIZE / 32) +#define TOTAL_FRAMES (MAX_PHYSICAL_MEMORY / PAGE_SIZE + 1) #define FRAMES_PER_INDEX 32 +#define BITMAP_SIZE ((TOTAL_FRAMES + FRAMES_PER_INDEX - 1) / FRAMES_PER_INDEX) static uint32_t memory_bitmap[BITMAP_SIZE]; static uint32_t memory_size = 0; @@ -42,17 +48,18 @@ void init_pmm(uint32_t multiboot_addr) { uint32_t addr = multiboot_addr; /* Initialize all memory as used (1) initially */ - for (int i = 0; i < BITMAP_SIZE; i++) { + for (uint32_t i = 0; i < BITMAP_SIZE; i++) { memory_bitmap[i] = 0xFFFFFFFF; } if (addr & 7) { - // offset_print("Multiboot alignment error\n"); // Need offset_print here? No, kernel handles prints + offset_print(" PMM: multiboot alignment error\n"); return; // Alignment issue } uint32_t size = *(uint32_t *)addr; + uint32_t regions_found = 0; for (tag = (struct multiboot_tag *)(addr + 8); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7))) { @@ -60,6 +67,8 @@ void init_pmm(uint32_t multiboot_addr) { if (tag->type == MULTIBOOT_TAG_TYPE_BASIC_MEMINFO) { struct multiboot_tag_basic_meminfo *meminfo = (struct multiboot_tag_basic_meminfo *)tag; memory_size = meminfo->mem_upper; + offset_print(" PMM: mem_upper = "); + print_hex(memory_size); } else if (tag->type == MULTIBOOT_TAG_TYPE_MMAP) { multiboot_memory_map_t *mmap; struct multiboot_tag_mmap *tag_mmap = (struct multiboot_tag_mmap *)tag; @@ -73,6 +82,8 @@ void init_pmm(uint32_t multiboot_addr) { uint64_t len = mmap->len; uint64_t end = start + len; + regions_found++; + if (start % PAGE_SIZE != 0) { start = (start + PAGE_SIZE) & ~(PAGE_SIZE - 1); } @@ -109,6 +120,10 @@ void init_pmm(uint32_t multiboot_addr) { // Mark first page as used to avoid returning 0 (NULL) set_frame(0); + + offset_print(" PMM: found "); + print_hex(regions_found); + offset_print(" PMM: available memory regions\n"); } phys_addr_t pmm_alloc_page(pmm_zone_t zone) {