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) {