22 Commits

Author SHA1 Message Date
AI
313aeb5872 Implement VGA text-mode driver with memory statistics display (AI)
- Created VGA driver that writes to the 0xB8000 text-mode framebuffer.
  Supports 80x25 display with 16 foreground/background colors, scrolling,
  hardware cursor updates, and special characters (\n, \r, \t, \b).
- Provides vga_puts, vga_putchar, vga_put_hex, vga_put_dec, vga_set_color.
- Displays boot banner ("ClaudeOS v0.1 booting...") on screen clear.
- vga_show_mem_stats() prints total RAM, kernel start/end addresses, and
  kernel size on the VGA display during boot.
- Registered as the first driver using REGISTER_DRIVER, proving the
  driver framework works end-to-end (probe -> init lifecycle).

Tested: driver loads successfully, debug port confirms vga probe/init.
2026-02-23 11:10:48 +00:00
AI
bb09de6a6d Implement driver architecture with linker-section registration (AI)
- Created driver framework with probe/init lifecycle. Drivers register via
  REGISTER_DRIVER macro which places pointers in a .drivers linker section.
- During boot, init_drivers() iterates the section, probes each driver
  (checking if hardware is present), and initializes those that respond OK.
- Added .drivers section to linker.ld with __drivers_start/__drivers_end
  symbols for iteration.
- Also added .rodata.* pattern to the .rodata section for string literals
  placed in sub-sections by the compiler.
- No drivers are registered yet; the VGA driver will be the first.

Tested: boots cleanly with driver scan completing (0 registered, 0 loaded).
2026-02-23 11:08:59 +00:00
AI
f63cd9eb3f Implement kernel memory allocator (kmalloc/kfree) and freestanding string library (AI)
- Added first-fit free-list allocator with block splitting and coalescing.
  Provides kmalloc(), kfree(), and kcalloc() for kernel-space dynamic memory.
- Each block carries an inline header with a magic value (0xCAFEBABE) for
  heap corruption detection, plus double-free checking.
- Memory is obtained from the paging subsystem in 4 KiB page increments.
  All allocations are 8-byte aligned with a 16-byte minimum block size.
- Created freestanding string.c with memset, memcpy, memmove, memcmp,
  strlen, strcmp, strncmp, strcpy, strncpy — replacing the unavailable
  libc implementations.
- Added documentation in docs/kmalloc.md.

Tested: kmalloc(64) returns 0xD0001010 (in kernel heap) and kfree succeeds.
Works with both 4 MiB and 128 MiB RAM.
2026-02-23 11:06:52 +00:00
AI
fb61ab7c15 Implement paging subsystem with identity mapping and kernel heap (AI)
- Created two-level x86 paging (page directory + page tables) with 4 KiB pages.
- Identity maps all detected physical memory in two phases:
  1) Static: first 16 MiB using 4 BSS-allocated page tables (avoids
     chicken-and-egg with PMM bitmap in BSS).
  2) Dynamic: memory above 16 MiB using PMM-allocated page tables,
     created before paging is enabled so physical addresses still work.
- Provides kernel heap at 0xD0000000–0xF0000000 for virtual page allocation.
- API: paging_map_page, paging_unmap_page, paging_alloc_page, paging_free_page,
  paging_get_physical.
- Added pmm_get_memory_size() to expose detected RAM for paging init.
- Kernel tests paging by allocating a virtual page, writing 0xDEADBEEF, and
  reading it back, then freeing it.
- Added documentation in docs/paging.md.

Tested: boots and passes paging test with both 4 MiB and 128 MiB RAM in QEMU.
2026-02-23 11:03:27 +00:00
AI
f2e7d6c5d7 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).
2026-02-23 10:57:56 +00:00
AI
cf3059747a Implement physical memory allocator with zone support (AI)
- Added bitmap-based physical memory manager (PMM) that parses the Multiboot2
  memory map to discover available RAM regions.
- Supports two allocation zones: PMM_ZONE_DMA (below 16MB) and PMM_ZONE_NORMAL
  (above 16MB), with automatic fallback from NORMAL to DMA when the preferred
  zone is exhausted.
- Marks kernel memory region and multiboot info structure as reserved using
  _kernel_start/_kernel_end linker symbols.
- Page 0 is always marked as used to prevent returning NULL as a valid address.
- Added linker script symbols (_kernel_start, _kernel_end) to track kernel
  memory boundaries for the allocator.
- Kernel now initializes PMM after PIC and performs test allocations to verify
  the subsystem works.
2026-02-23 10:52:06 +00:00
AI
f1923fdbcf Implement ISR stubs and PIC driver for hardware interrupt handling (AI)
- Reworked IDT initialization to register all 32 CPU exception handlers (ISR 0-31)
  and 16 hardware interrupt handlers (IRQ 0-15, mapped to IDT entries 32-47).
- Created assembly stubs in interrupts.S using macros for ISRs with and without
  error codes, plus IRQ stubs. All route through a common stub that saves
  registers, loads kernel data segment, and calls the C handler.
- Added isr.c with a unified interrupt dispatcher that handles both exceptions
  (halts on fault) and hardware IRQs (sends EOI via PIC).
- Implemented PIC (8259) driver in pic.c with full initialization sequence that
  remaps IRQ 0-7 to IDT 32-39 and IRQ 8-15 to IDT 40-47. Includes mask/unmask
  and EOI support.
- Extracted port I/O primitives (inb, outb, io_wait) into port_io.h header for
  reuse across drivers.
- Kernel now initializes PIC after IDT and enables interrupts with STI.
2026-02-23 10:51:45 +00:00
3909a1f581 Add copilot instructions (human) 2026-02-23 11:48:40 +01:00
7c45406c23 Add PIC handling (human) 2026-02-23 11:05:41 +01:00
AI
e4cc638a8d Implement GDT and basic IDT setup
This commit adds GDT initialization with proper code/data segments and reloads CS.

It also adds the initial IDT structure and loads an empty IDT.

Build configuration updated to disable SSE/MMX to prevent compiler generation of unsupported instructions in early boot.
2026-02-23 10:05:17 +00:00
c7e9833819 Remove output file (human) 2026-02-23 09:56:22 +01:00
AI
6b00cf3154 Disable floppy generation for now, focus on CDROM (AI) 2026-02-23 08:54:39 +00:00
AI
f2e84924f5 Update gitignore (AI) 2026-02-23 08:53:59 +00:00
6d91edc897 Revert "Add script to merge boot sector and patch GRUB for floppy (AI)"
This reverts commit 7068176d91.
2026-02-23 09:52:40 +01:00
AI
7068176d91 Add script to merge boot sector and patch GRUB for floppy (AI) 2026-02-23 08:46:03 +00:00
AI
436aeceb10 Remove build artifacts from repo 2026-02-23 08:33:09 +00:00
AI
aa954045c1 Implement ISO and Floppy image generation (AI) 2026-02-23 08:32:48 +00:00
AI
a048764a3d Setup simple kernel with Hello World output via debugcon (AI) 2026-02-23 08:24:50 +00:00
AI
34382babb3 Create directory structure and initial build system (AI) 2026-02-23 08:21:49 +00:00
ffb8b02762 Update instructions based on things learned from attempt-1 2026-02-23 08:35:16 +01:00
94b0297b28 Include attempt-1 devcontainer Dockerfile (AI+human) 2026-02-23 08:34:13 +01:00
76f8b9d4dd Include attempt-1 devcontainer json (AI) 2026-02-23 08:34:01 +01:00
39 changed files with 3186 additions and 14 deletions

42
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
# ClaudeOS build environment
#
# Provides everything needed to build and test a bare-metal i386 kernel:
# - Clang + LLD for cross-compilation to i386-elf
# - GRUB 2 tools for generating bootable ISO and floppy images
# - QEMU (i386) for running integration tests
# - CMake + Ninja for the build system
# - xorriso and mtools used internally by grub-mkrescue / grub-mkimage
FROM --platform=linux/amd64 alpine:3.23
RUN apk add --no-cache \
# Core build tools
clang \
lld \
cmake \
ninja \
make \
git \
# GRUB image generation
grub \
grub-bios \
xorriso \
mtools \
# QEMU for running the OS
qemu-system-i386 \
# Handy utilities
file \
ca-certificates
# Verify the cross-compilation toolchain can produce an i386-elf binary.
# Compile and link separately so ld.lld is invoked directly, avoiding the
# host gcc linker driver (which rejects -m32 on non-x86 hosts such as aarch64).
RUN echo 'void _start(void){for(;;)__asm__("hlt");}' > /tmp/test.c \
&& clang -v -target i386-elf -march=i386 -ffreestanding -fno-stack-protector \
-c /tmp/test.c -o /tmp/test.o \
&& ld.lld -m elf_i386 -e _start --nostdlib /tmp/test.o -o /tmp/test.elf \
&& file /tmp/test.elf | grep -q "ELF 32-bit.*386" \
&& echo "Toolchain OK" \
&& rm /tmp/test.c /tmp/test.o /tmp/test.elf
WORKDIR /workspaces/claude-os

View File

@@ -0,0 +1,24 @@
{
"name": "ClaudeOS Dev",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack"
],
"settings": {
"cmake.generator": "Ninja",
"cmake.configureSettings": {
"CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/cmake/toolchain-i386-clang.cmake"
}
}
}
},
"postCreateCommand": "echo 'ClaudeOS devcontainer ready!'",
"remoteUser": "root"
}

1
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1 @@
See `README.md` for a general overview on how to work and for a list of tasks to perform.

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
build/
release/
*.img
*.iso
debug_grub/
*_output.txt
snippet.*
qemu.log
iso_output.txt

40
CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.16)
project(ClaudeOS C ASM)
set(CMAKE_C_STANDARD 99)
# We are building a kernel, so we don't want standard libraries
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -g -O2 -Wall -Wextra")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -fno-pie -fno-pic")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
# Define build output directory
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(src)
# Create output directories
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\" { multiboot2 /boot/kernel.bin }")
# ISO Generation
add_custom_target(iso ALL
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernel ${CMAKE_BINARY_DIR}/isodir/boot/kernel.bin
COMMAND grub-mkrescue -o ${CMAKE_SOURCE_DIR}/release/claude-os.iso ${CMAKE_BINARY_DIR}/isodir
DEPENDS kernel
COMMENT "Generating bootable ISO image"
)
# Test target
add_custom_target(test_images
COMMAND sh ${CMAKE_SOURCE_DIR}/test_images.sh
DEPENDS iso
COMMENT "Testing generated images in QEMU"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

View File

@@ -3,12 +3,14 @@ This document describes which files are written fully by humans, which had human
## Fully Human
The following files were written completely by humans:
- HUMAN.md
- README.md
- `HUMAN.md` (except for the checkmarks in the tasklist)
- `README.md`
- Everything in `vendor/`. While we don't actually know if it was written by humans, certainly the AI used for this project has not touched it.
## Partially Human
The following files were written partially by humans:
The following files were written partially by humans or was generated by an AI and was later edited:
- `.devcontainer/Dockerfile`
## Fully AI
Any file not in the above two categories were completely written by an AI without a human manually editing the file afterwards.

View File

@@ -32,20 +32,23 @@ The source code uses the following directory structure:
# Task list
An AI implementing this operating system should implement features in this order.
After each feature, it should create a new Git commit.
After each feature, it should create a new Git commit and push the commit.
The git commit should describe what is done and why certain choices were made.
The title of the commit should always end with (AI), to indicate that it was written by an AI.
Once a task is completed, it should be checked off.
- [ ] Create directory structure
- [ ] Create initial build system
- [ ] Setup a simple kernel that writes `Hello, world` to Qemu debug port
- [ ] Update the build system to create both ISO and Floppy images. Verify these work using a test script.
- [ ] Update the kernel to correctly setup the GDT
- [ ] 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.
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
- [x] Create directory structure
- [x] Create initial build system
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
- [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.
- [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.
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
- [x] 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.
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
- [ ] Create subsystem for loading new processes in Ring 3.
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
- [ ] Write a VFS subsystem.

62
docs/interrupts.md Normal file
View File

@@ -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 0255 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 |
|---|---|
| 031 | CPU exceptions (Division by Zero, Page Fault, GPF, etc.) |
| 3247 | Hardware IRQs (remapped from default 015) |
| 48255 | 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 3247), `isr_handler` sends an End-of-Interrupt (EOI) to the PIC.
6. For CPU exceptions (vectors 031), 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 07 mapped to vectors 815, which collide with CPU exceptions. During initialization, we remap:
- **Master PIC** (IRQ 07) → vectors 3239
- **Slave PIC** (IRQ 815) → vectors 4047
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 031 and IRQs 015.
- `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`.

61
docs/kmalloc.md Normal file
View File

@@ -0,0 +1,61 @@
# Kernel Memory Allocator (kmalloc)
## Overview
The kernel memory allocator provides `kmalloc()` and `kfree()` for dynamic memory allocation within the kernel. It uses the paging subsystem to obtain physical memory on demand.
## Architecture
### Free-List Allocator
The allocator maintains a singly-linked free list of available memory blocks, ordered by address. Each block carries an inline header:
```c
typedef struct block_header {
uint32_t size; // Usable bytes (excludes header)
uint32_t magic; // 0xCAFEBABE for integrity checking
struct block_header *next; // Next free block (free list only)
uint8_t is_free; // 1 = free, 0 = allocated
} block_header_t;
```
### Allocation Strategy
1. **First-fit search:** Walk the free list for the first block with `size >= requested`.
2. **Block splitting:** If the found block is significantly larger than needed, it is split into the allocated portion and a new free block.
3. **Page expansion:** If no suitable block exists, a new 4 KiB page is obtained from `paging_alloc_page()`.
All allocations are 8-byte aligned. Minimum block size is 16 bytes to limit fragmentation.
### Deallocation
1. The block header is located by subtracting `sizeof(block_header_t)` from the user pointer.
2. The block's magic number is verified to detect corruption.
3. The block is inserted back into the free list in address order.
4. **Coalescing:** Adjacent free blocks are merged to reduce fragmentation.
### Integrity Checks
- A magic value (`0xCAFEBABE`) in each block header detects heap corruption.
- Double-free is detected by checking the `is_free` flag.
## API
```c
void init_kmalloc(void);
void *kmalloc(size_t size);
void kfree(void *ptr);
void *kcalloc(size_t count, size_t size);
```
## Key Files
- `src/kmalloc.c` / `src/kmalloc.h` — Allocator implementation.
- `src/string.c` — Freestanding `memset`, `memcpy`, `strlen`, etc.
- `src/paging.c` — Provides physical page backing.
## Limitations
- Maximum single allocation is ~4080 bytes (one page minus header).
- No multi-page allocations for large objects.
- Free virtual addresses are not reused after `paging_free_page()`.

75
docs/paging.md Normal file
View File

@@ -0,0 +1,75 @@
# Paging Subsystem
## Overview
The paging subsystem manages virtual memory using the x86 two-level paging scheme (no PAE). It provides identity mapping for all physical memory and a kernel heap region for dynamic virtual page allocation.
## Architecture
### Page Table Structure
x86 32-bit paging uses two levels:
| Level | Entries | Each Entry Maps | Total Coverage |
|---|---|---|---|
| Page Directory | 1024 | 4 MiB (one page table) | 4 GiB |
| Page Table | 1024 | 4 KiB (one page) | 4 MiB |
Each entry is a 32-bit value containing a 20-bit physical page frame number and 12 bits of flags.
### Identity Mapping
During initialization, all detected physical memory is identity-mapped (virtual address = physical address). This is done in two phases:
1. **Static mapping (first 16 MiB):** Four page tables are statically allocated in BSS. This avoids a chicken-and-egg problem since the PMM bitmap itself resides in this region.
2. **Dynamic mapping (above 16 MiB):** Additional page tables are allocated from the PMM *before* paging is enabled (so physical addresses are still directly accessible). These cover all remaining detected physical memory.
### Kernel Heap
The kernel heap region occupies virtual addresses `0xD0000000` through `0xF0000000` (768 MiB).
When `paging_alloc_page()` is called:
1. A physical page is allocated from the PMM.
2. A page table entry is created mapping the next free virtual address to the physical page.
3. The virtual address is returned.
When `paging_free_page()` is called:
1. The physical address is looked up via the page table entry.
2. The virtual mapping is removed.
3. The physical page is returned to the PMM.
### TLB Management
- Single-page invalidations use `invlpg`.
- Full TLB flushes use CR3 reload.
## API
```c
void init_paging(void);
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
void paging_unmap_page(uint32_t vaddr);
void *paging_alloc_page(void);
void paging_free_page(void *vaddr);
uint32_t paging_get_physical(uint32_t vaddr);
```
### Flags
| Flag | Value | Meaning |
|---|---|---|
| `PAGE_PRESENT` | 0x001 | Page is present in memory |
| `PAGE_WRITE` | 0x002 | Page is writable |
| `PAGE_USER` | 0x004 | Page is user-accessible (ring 3) |
## Key Files
- `src/paging.c` / `src/paging.h` — Implementation and API.
- `src/pmm.c` / `src/pmm.h` — Physical page allocation backing.
## Design Decisions
- **No higher-half kernel yet:** The kernel runs at its physical load address (1 MiB) with identity mapping. Higher-half mapping (0xC0000000) can be added later without changing the paging API.
- **Static + dynamic page tables:** The first 16 MiB uses BSS-allocated tables to bootstrap, while memory above 16 MiB uses PMM-allocated tables. This keeps BSS usage bounded at ~16 KiB regardless of total RAM.
- **Sequential heap allocation:** The heap grows upward linearly. No free-list reuse of freed virtual addresses is implemented yet.

63
docs/pmm.md Normal file
View File

@@ -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.

26
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.16)
add_executable(kernel
boot.S
gdt_flush.S
gdt.c
idt.c
isr.c
pic.c
pmm.c
paging.c
kmalloc.c
string.c
driver.c
vga.c
interrupts.S
kernel.c
)
# Use our custom linker script
target_link_options(kernel PRIVATE -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
target_include_directories(kernel PRIVATE
${CMAKE_SOURCE_DIR}/vendor
${CMAKE_SOURCE_DIR}/include # If created later
)

65
src/boot.S Normal file
View File

@@ -0,0 +1,65 @@
#define ASM_FILE
#include <multiboot2.h>
/* Multiboot 1 header constants */
.set ALIGN, 1<<0 /* align loaded modules on page boundaries */
.set MEMINFO, 1<<1 /* provide memory map */
.set FLAGS, ALIGN | MEMINFO /* this is the Multiboot 'flag' field */
.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */
.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */
.section .multiboot
.align 4
multiboot1_header:
.long MAGIC
.long FLAGS
.long CHECKSUM
.align 8
multiboot_header:
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* architecture: 0 (protected mode i386) */
.long MULTIBOOT_ARCHITECTURE_I386
/* header length */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
/* Tags here */
/* End tag */
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long 8
multiboot_header_end:
.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:
.section .text
.global _start
.type _start, @function
_start:
/* Set up stack */
mov $stack_top, %esp
/* Reset EFLAGS */
push $0
popf
/* Push magic and multiboot info pointer onto stack for kernel_main */
/* Multiboot2 puts magic in EAX, pointer in EBX */
push %ebx
push %eax
call kernel_main
cli
1: hlt
jmp 1b
.size _start, . - _start

72
src/driver.c Normal file
View File

@@ -0,0 +1,72 @@
/**
* @file driver.c
* @brief Driver framework implementation.
*
* Iterates over all driver descriptors placed in the .drivers linker section
* by the REGISTER_DRIVER macro. Each driver is probed and, if the probe
* succeeds, initialized.
*/
#include "driver.h"
#include "port_io.h"
#include <stddef.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/**
* Linker-provided symbols marking the start and end of the .drivers section.
* Each entry is a pointer to a driver_t.
*/
extern const driver_t *__drivers_start[];
extern const driver_t *__drivers_end[];
void init_drivers(void) {
const driver_t **drv;
int loaded = 0;
int skipped = 0;
int failed = 0;
offset_print(" DRIVERS: scanning registered drivers...\n");
for (drv = __drivers_start; drv < __drivers_end; drv++) {
const driver_t *d = *drv;
if (!d || !d->name) {
continue;
}
offset_print(" DRIVERS: probing ");
offset_print(d->name);
offset_print("... ");
/* Run probe function if provided */
if (d->probe) {
driver_probe_result_t result = d->probe();
if (result == DRIVER_PROBE_NOT_FOUND) {
offset_print("not found\n");
skipped++;
continue;
} else if (result == DRIVER_PROBE_ERROR) {
offset_print("probe error\n");
failed++;
continue;
}
}
/* Run init function */
if (d->init) {
int ret = d->init();
if (ret != 0) {
offset_print("init failed\n");
failed++;
continue;
}
}
offset_print("loaded\n");
loaded++;
}
offset_print(" DRIVERS: done\n");
}

56
src/driver.h Normal file
View File

@@ -0,0 +1,56 @@
/**
* @file driver.h
* @brief Kernel driver architecture.
*
* Provides a simple framework for registering and initializing kernel drivers.
* Each driver provides a probe function that returns whether it should load,
* and an init function that performs the actual initialization.
*
* Drivers are registered at compile time using the REGISTER_DRIVER macro,
* which places driver descriptors in a special linker section. The kernel
* iterates over all registered drivers during boot.
*/
#ifndef DRIVER_H
#define DRIVER_H
#include <stdint.h>
/** Driver probe result codes. */
typedef enum {
DRIVER_PROBE_OK = 0, /**< Driver should be loaded. */
DRIVER_PROBE_NOT_FOUND = 1, /**< Hardware not found, skip this driver. */
DRIVER_PROBE_ERROR = 2 /**< Error during probing. */
} driver_probe_result_t;
/**
* Driver descriptor structure.
*
* Each driver provides a name, a probe function (to test if hardware is
* present), and an init function (to set up the driver).
*/
typedef struct driver {
const char *name; /**< Human-readable driver name. */
driver_probe_result_t (*probe)(void); /**< Probe function. Returns DRIVER_PROBE_OK if driver should load. */
int (*init)(void); /**< Init function. Returns 0 on success, non-zero on failure. */
} driver_t;
/**
* Register a driver.
*
* Places the driver descriptor in the .drivers linker section so it
* is automatically discovered during boot.
*/
#define REGISTER_DRIVER(drv) \
static const driver_t * __attribute__((used, section(".drivers"))) \
_driver_##drv = &(drv)
/**
* Initialize all registered drivers.
*
* Iterates over the .drivers section, probes each driver, and initializes
* those that respond positively to probing.
*/
void init_drivers(void);
#endif /* DRIVER_H */

68
src/gdt.c Normal file
View File

@@ -0,0 +1,68 @@
#include "gdt.h"
#include <stdint.h>
/* GDT Pointer Structure */
struct gdt_ptr gp;
/* GDT entries */
struct gdt_entry gdt[5];
extern void gdt_flush(uint32_t);
/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
{
/* Setup the descriptor base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
/* Setup the descriptor limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
/* Should be called by main. This will setup the special GDT
* pointer, set up the first 3 entries in our GDT, and then
* call gdt_flush() in our assembler file in order to tell
* the processor where the new GDT is and update the
* internal registers */
void init_gdt()
{
/* Setup the GDT pointer and limit */
gp.limit = (sizeof(struct gdt_entry) * 5) - 1;
gp.base = (uint32_t)&gdt;
/* Our NULL descriptor */
gdt_set_gate(0, 0, 0, 0, 0);
/* The second entry is our Code Segment. The base address
* is 0, the limit is 4GBytes, it uses 4KByte granularity,
* uses 32-bit opcodes, and is a Code Segment descriptor.
* Please check the table above in the tutorial in order
* to see exactly what each value means */
/* 0x9A = 1001 1010
P=1, DPL=00, S=1 (Code/Data), Type=1010 (Code, Exec/Read) */
/* 0xCF = 1100 1111
G=1 (4k), DB=1 (32bit), L=0, AVL=0, LimitHigh=0xF */
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* The third entry is our Data Segment. It's EXACTLY the
* same as our code segment, but the descriptor type in
* this entry's access byte says it's a Data Segment */
/* 0x92 = 1001 0010
P=1, DPL=00, S=1, Type=0010 (Data, Read/Write) */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* User mode code segment */
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
/* User mode data segment */
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
/* Flush out the old GDT and install the new changes! */
gdt_flush((uint32_t)&gp);
}

25
src/gdt.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef GDT_H
#define GDT_H
#include <stdint.h>
/* GDT Entry Structure */
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
/* GDT Pointer Structure */
struct gdt_ptr {
uint16_t limit;
uint32_t base;
} __attribute__((packed, aligned(1)));
/* Initialize GDT */
void init_gdt(void);
#endif // GDT_H

18
src/gdt_flush.S Normal file
View File

@@ -0,0 +1,18 @@
.section .text
.global gdt_flush
.type gdt_flush, @function
gdt_flush:
mov 4(%esp), %eax
lgdt (%eax)
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
ljmp $0x08, $flush
flush:
ret

136
src/idt.c Normal file
View File

@@ -0,0 +1,136 @@
#include "idt.h"
#include <string.h> // For memset
// The IDT itself
idt_entry_t idt[256];
idt_ptr_t idt_ptr;
// Helper to set a gate in the IDT
static void set_idt_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
idt[num].base_lo = base & 0xFFFF;
idt[num].base_hi = (base >> 16) & 0xFFFF;
idt[num].sel = sel;
idt[num].always0 = 0;
// flags: 0x8E = 10001110 (Present, Ring0, 32-bit Interrupt Gate)
idt[num].flags = flags;
}
// Exception Handlers (ISRs)
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
// Hardware Interrupt Handlers (IRQs)
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();
void init_idt() {
// 1. Set up the IDT pointer
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
idt_ptr.base = (uint32_t)&idt;
// 2. Clear the IDT
memset(&idt, 0, sizeof(idt_entry_t) * 256);
// 3. Set the ISRs (Exceptions 0-31)
// Code Selector is 0x08 usually (defined in GDT)
// Flags: 0x8E = Present(1), DPL(00), Storage(0), GateType(1110 = 32-bit Int)
set_idt_gate( 0, (uint32_t)isr0, 0x08, 0x8E);
set_idt_gate( 1, (uint32_t)isr1, 0x08, 0x8E);
set_idt_gate( 2, (uint32_t)isr2, 0x08, 0x8E);
set_idt_gate( 3, (uint32_t)isr3, 0x08, 0x8E);
set_idt_gate( 4, (uint32_t)isr4, 0x08, 0x8E);
set_idt_gate( 5, (uint32_t)isr5, 0x08, 0x8E);
set_idt_gate( 6, (uint32_t)isr6, 0x08, 0x8E);
set_idt_gate( 7, (uint32_t)isr7, 0x08, 0x8E);
set_idt_gate( 8, (uint32_t)isr8, 0x08, 0x8E);
set_idt_gate( 9, (uint32_t)isr9, 0x08, 0x8E);
set_idt_gate(10, (uint32_t)isr10, 0x08, 0x8E);
set_idt_gate(11, (uint32_t)isr11, 0x08, 0x8E);
set_idt_gate(12, (uint32_t)isr12, 0x08, 0x8E);
set_idt_gate(13, (uint32_t)isr13, 0x08, 0x8E);
set_idt_gate(14, (uint32_t)isr14, 0x08, 0x8E);
set_idt_gate(15, (uint32_t)isr15, 0x08, 0x8E);
set_idt_gate(16, (uint32_t)isr16, 0x08, 0x8E);
set_idt_gate(17, (uint32_t)isr17, 0x08, 0x8E);
set_idt_gate(18, (uint32_t)isr18, 0x08, 0x8E);
set_idt_gate(19, (uint32_t)isr19, 0x08, 0x8E);
set_idt_gate(20, (uint32_t)isr20, 0x08, 0x8E);
set_idt_gate(21, (uint32_t)isr21, 0x08, 0x8E);
set_idt_gate(22, (uint32_t)isr22, 0x08, 0x8E);
set_idt_gate(23, (uint32_t)isr23, 0x08, 0x8E);
set_idt_gate(24, (uint32_t)isr24, 0x08, 0x8E);
set_idt_gate(25, (uint32_t)isr25, 0x08, 0x8E);
set_idt_gate(26, (uint32_t)isr26, 0x08, 0x8E);
set_idt_gate(27, (uint32_t)isr27, 0x08, 0x8E);
set_idt_gate(28, (uint32_t)isr28, 0x08, 0x8E);
set_idt_gate(29, (uint32_t)isr29, 0x08, 0x8E);
set_idt_gate(30, (uint32_t)isr30, 0x08, 0x8E);
set_idt_gate(31, (uint32_t)isr31, 0x08, 0x8E);
// 4. Set the IRQs (Remapped to 32-47)
set_idt_gate(32, (uint32_t)irq0, 0x08, 0x8E);
set_idt_gate(33, (uint32_t)irq1, 0x08, 0x8E);
set_idt_gate(34, (uint32_t)irq2, 0x08, 0x8E);
set_idt_gate(35, (uint32_t)irq3, 0x08, 0x8E);
set_idt_gate(36, (uint32_t)irq4, 0x08, 0x8E);
set_idt_gate(37, (uint32_t)irq5, 0x08, 0x8E);
set_idt_gate(38, (uint32_t)irq6, 0x08, 0x8E);
set_idt_gate(39, (uint32_t)irq7, 0x08, 0x8E);
set_idt_gate(40, (uint32_t)irq8, 0x08, 0x8E);
set_idt_gate(41, (uint32_t)irq9, 0x08, 0x8E);
set_idt_gate(42, (uint32_t)irq10, 0x08, 0x8E);
set_idt_gate(43, (uint32_t)irq11, 0x08, 0x8E);
set_idt_gate(44, (uint32_t)irq12, 0x08, 0x8E);
set_idt_gate(45, (uint32_t)irq13, 0x08, 0x8E);
set_idt_gate(46, (uint32_t)irq14, 0x08, 0x8E);
set_idt_gate(47, (uint32_t)irq15, 0x08, 0x8E);
// 5. Load the IDT using assembly instruction 'lidt'
// We can use inline assembly or a helper function.
// Assuming lid is available via inline asm or similar to gdt_flush
__asm__ volatile("lidt (%0)" : : "r" (&idt_ptr));
}

33
src/idt.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef IDT_H
#define IDT_H
#include <stdint.h>
/* A struct describing an interrupt gate. */
struct idt_entry_struct {
uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.
uint16_t sel; // Kernel segment selector.
uint8_t always0; // This must always be zero.
uint8_t flags; // More flags. See documentation.
uint16_t base_hi; // The upper 16 bits of the address to jump to.
} __attribute__((packed));
typedef struct idt_entry_struct idt_entry_t;
/* A struct describing a pointer to an array of interrupt handlers.
* This is in a format suitable for giving to 'lidt'. */
struct idt_ptr_struct {
uint16_t limit;
uint32_t base; // The address of the first element in our idt_entry_t array.
} __attribute__((packed));
typedef struct idt_ptr_struct idt_ptr_t;
// These extern directives let us access the addresses of our ASM ISR handlers.
extern void isr0();
extern void isr1();
// ... we will add more later ...
void init_idt();
#endif

120
src/interrupts.S Normal file
View File

@@ -0,0 +1,120 @@
.section .text
.global idt_load
.type idt_load, @function
idt_load:
mov 4(%esp), %eax
lidt (%eax)
ret
/* Common ISR stub */
isr_common_stub:
pusha
/* Save segment registers */
mov %ds, %ax
push %eax
/* Load kernel data segment */
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
/* Push pointer to stack structure as argument for C handler */
push %esp
call isr_handler
add $4, %esp /* Clean up pushed pointer */
/* Restore segment registers */
pop %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
popa
add $8, %esp /* Cleans up error code and ISR number */
iret
/* Macro for exceptions with NO Error Code */
.macro ISR_NOERRCODE num
.global isr\num
.type isr\num, @function
isr\num:
cli
push $0
push $\num
jmp isr_common_stub
.endm
/* Macro for exceptions WITH Error Code */
.macro ISR_ERRCODE num
.global isr\num
.type isr\num, @function
isr\num:
cli
push $\num
jmp isr_common_stub
.endm
ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6
ISR_NOERRCODE 7
ISR_ERRCODE 8
ISR_NOERRCODE 9
ISR_ERRCODE 10
ISR_ERRCODE 11
ISR_ERRCODE 12
ISR_ERRCODE 13
ISR_ERRCODE 14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_ERRCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_ERRCODE 30
ISR_NOERRCODE 31
/* Macro for IRQs (Hardware Interrupts) */
.macro ISR_IRQ num, idt_index
.global irq\num
.type irq\num, @function
irq\num:
cli
push $0
push $\idt_index
jmp isr_common_stub
.endm
/* Hardware Interrupts */
ISR_IRQ 0, 32
ISR_IRQ 1, 33
ISR_IRQ 2, 34
ISR_IRQ 3, 35
ISR_IRQ 4, 36
ISR_IRQ 5, 37
ISR_IRQ 6, 38
ISR_IRQ 7, 39
ISR_IRQ 8, 40
ISR_IRQ 9, 41
ISR_IRQ 10, 42
ISR_IRQ 11, 43
ISR_IRQ 12, 44
ISR_IRQ 13, 45
ISR_IRQ 14, 46
ISR_IRQ 15, 47

75
src/isr.c Normal file
View File

@@ -0,0 +1,75 @@
#include "isr.h"
#include "pic.h"
#include <stdint.h>
/* Forward declaration for kernel panic or similar */
void offset_print(const char *str);
void print_hex(uint32_t val);
/* Exception messages */
char *exception_messages[] = {
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
void isr_handler(registers_t *regs)
{
// If it's a hardware interrupt (IRQ), we must acknowledge it
if (regs->int_no >= 32 && regs->int_no < 48) {
// Send EOI to PIC (IRQ number 0-15)
pic_send_eoi(regs->int_no - 32);
// Here we would call the registered handler for this IRQ
// For now, just print something for the timer tick so we know it works,
// but limit it to avoid flooding the log.
if (regs->int_no == 32) {
// Timer tick - do nothing verbose
// offset_print(".");
} else if (regs->int_no == 33) {
// Keyboard
offset_print("Keyboard IRQ!\n");
}
return;
}
offset_print("received interrupt: ");
print_hex(regs->int_no);
offset_print("\n");
if (regs->int_no < 32)
{
offset_print(exception_messages[regs->int_no]);
offset_print(" Exception. System Halted!\n");
for (;;) ;
}
}

18
src/isr.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef ISR_H
#define ISR_H
#include <stdint.h>
typedef struct registers
{
uint32_t ds; // Data segment selector
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha
uint32_t int_no, err_code; // Interrupt number and error code (if applicable)
uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically
} registers_t;
typedef void (*isr_t)(registers_t*);
void isr_handler(registers_t* regs);
#endif

96
src/kernel.c Normal file
View File

@@ -0,0 +1,96 @@
#include <multiboot2.h>
#include <stdint.h>
#include <stddef.h>
#include "gdt.h"
#include "idt.h"
#include "pic.h"
#include "port_io.h"
#include "pmm.h"
#include "paging.h"
#include "kmalloc.h"
#include "driver.h"
#include "vga.h"
void offset_print(const char *str)
{
while (*str) {
outb(0xE9, *str);
str++;
}
}
void print_hex(uint32_t val)
{
const char *hex = "0123456789ABCDEF";
outb(0xE9, '0');
outb(0xE9, 'x');
for (int i = 28; i >= 0; i -= 4) {
outb(0xE9, hex[(val >> i) & 0xF]);
}
outb(0xE9, '\n');
}
void kernel_main(uint32_t magic, uint32_t addr) {
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
offset_print("Invalid magic number: ");
print_hex(magic);
return;
}
offset_print("Booting...\n");
init_gdt();
offset_print("GDT initialized\n");
init_idt();
offset_print("IDT initialized\n");
init_pic();
offset_print("PIC initialized\n");
init_pmm(addr);
offset_print("PMM initialized\n");
init_paging();
offset_print("Paging initialized\n");
/* Test paging: allocate a page and write to it */
void *test_page = paging_alloc_page();
if (test_page) {
offset_print("Allocated virtual page at: ");
print_hex((uint32_t)test_page);
*((volatile uint32_t *)test_page) = 0xDEADBEEF;
offset_print("Virtual page write/read OK\n");
paging_free_page(test_page);
} else {
offset_print("FAILED to allocate virtual page\n");
}
init_kmalloc();
offset_print("Memory allocator initialized\n");
init_drivers();
offset_print("Drivers initialized\n");
/* Show memory statistics and boot progress on VGA */
vga_show_mem_stats();
vga_puts("Boot complete.\n\n");
/* Test kmalloc/kfree */
uint32_t *test_alloc = (uint32_t *)kmalloc(64);
if (test_alloc) {
*test_alloc = 42;
offset_print("kmalloc(64) = ");
print_hex((uint32_t)test_alloc);
kfree(test_alloc);
offset_print("kfree OK\n");
} else {
offset_print("FAILED to kmalloc\n");
}
/* Enable interrupts */
asm volatile("sti");
offset_print("Interrupts enabled\n");
offset_print("Hello, world\n");
}

253
src/kmalloc.c Normal file
View File

@@ -0,0 +1,253 @@
/**
* @file kmalloc.c
* @brief Kernel memory allocator implementation.
*
* A simple first-fit free-list allocator. Memory is obtained from the paging
* subsystem in 4 KiB page increments. The allocator maintains a linked list
* of free blocks. On allocation, the first block large enough is split if
* needed. On free, adjacent blocks are coalesced to reduce fragmentation.
*
* The allocator metadata (block headers) are stored inline at the beginning
* of each block, so the minimum allocation overhead is sizeof(block_header_t).
*/
#include "kmalloc.h"
#include "paging.h"
#include <stdint.h>
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/**
* Block header stored at the start of every allocated or free block.
* The usable memory starts immediately after this header.
*/
typedef struct block_header {
uint32_t size; /**< Size of the usable area (excludes header). */
uint32_t magic; /**< Magic number for integrity checking. */
struct block_header *next; /**< Next block in the free list (free blocks only). */
uint8_t is_free; /**< 1 if block is free, 0 if allocated. */
} block_header_t;
/** Magic value to detect heap corruption. */
#define BLOCK_MAGIC 0xCAFEBABE
/** Minimum block size to avoid excessive fragmentation. */
#define MIN_BLOCK_SIZE 16
/** Alignment for all allocations (8-byte aligned). */
#define ALIGNMENT 8
/** Round up to alignment boundary. */
#define ALIGN_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
/** Head of the free list. */
static block_header_t *free_list = NULL;
/** Number of pages currently allocated for the heap. */
static uint32_t heap_pages = 0;
/**
* Request a new page from the paging subsystem and add it to the free list.
*
* @return Pointer to the new block header, or NULL on failure.
*/
static block_header_t *request_page(void) {
void *page = paging_alloc_page();
if (!page) {
return NULL;
}
heap_pages++;
block_header_t *block = (block_header_t *)page;
block->size = 4096 - sizeof(block_header_t);
block->magic = BLOCK_MAGIC;
block->next = NULL;
block->is_free = 1;
return block;
}
/**
* Insert a block into the free list, maintaining address order.
* Then attempt to coalesce with adjacent blocks.
*
* @param block The block to insert.
*/
static void insert_free_block(block_header_t *block) {
block->is_free = 1;
/* Find insertion point (maintain address order for coalescing) */
block_header_t *prev = NULL;
block_header_t *curr = free_list;
while (curr && curr < block) {
prev = curr;
curr = curr->next;
}
/* Insert between prev and curr */
block->next = curr;
if (prev) {
prev->next = block;
} else {
free_list = block;
}
/* Coalesce with next block if adjacent */
if (block->next) {
uint8_t *block_end = (uint8_t *)block + sizeof(block_header_t) + block->size;
if (block_end == (uint8_t *)block->next) {
block->size += sizeof(block_header_t) + block->next->size;
block->next = block->next->next;
}
}
/* Coalesce with previous block if adjacent */
if (prev) {
uint8_t *prev_end = (uint8_t *)prev + sizeof(block_header_t) + prev->size;
if (prev_end == (uint8_t *)block) {
prev->size += sizeof(block_header_t) + block->size;
prev->next = block->next;
}
}
}
/**
* Split a block if it is large enough to hold the requested size
* plus a new block header and minimum block.
*
* @param block The block to potentially split.
* @param size The requested allocation size (aligned).
*/
static void split_block(block_header_t *block, uint32_t size) {
uint32_t remaining = block->size - size - sizeof(block_header_t);
if (remaining >= MIN_BLOCK_SIZE) {
block_header_t *new_block = (block_header_t *)((uint8_t *)block + sizeof(block_header_t) + size);
new_block->size = remaining;
new_block->magic = BLOCK_MAGIC;
new_block->is_free = 1;
new_block->next = block->next;
block->size = size;
block->next = new_block;
}
}
void init_kmalloc(void) {
/* Allocate the initial heap page */
block_header_t *initial = request_page();
if (!initial) {
offset_print(" KMALLOC: FATAL - could not allocate initial heap page\n");
return;
}
free_list = initial;
offset_print(" KMALLOC: initialized with 1 page\n");
}
void *kmalloc(size_t size) {
if (size == 0) {
return NULL;
}
/* Align the requested size */
size = ALIGN_UP(size, ALIGNMENT);
/* First-fit search through free list */
block_header_t *prev = NULL;
block_header_t *curr = free_list;
while (curr) {
if (curr->magic != BLOCK_MAGIC) {
offset_print(" KMALLOC: HEAP CORRUPTION detected!\n");
return NULL;
}
if (curr->is_free && curr->size >= size) {
/* Found a suitable block */
split_block(curr, size);
/* Remove from free list */
if (prev) {
prev->next = curr->next;
} else {
free_list = curr->next;
}
curr->is_free = 0;
curr->next = NULL;
/* Return pointer past the header */
return (void *)((uint8_t *)curr + sizeof(block_header_t));
}
prev = curr;
curr = curr->next;
}
/* No suitable block found; request a new page */
block_header_t *new_block = request_page();
if (!new_block) {
offset_print(" KMALLOC: out of memory\n");
return NULL;
}
/* If the new page is large enough, use it directly */
if (new_block->size >= size) {
split_block(new_block, size);
/* If there's a remainder, add it to the free list */
if (new_block->next && new_block->next->is_free) {
insert_free_block(new_block->next);
}
new_block->is_free = 0;
new_block->next = NULL;
return (void *)((uint8_t *)new_block + sizeof(block_header_t));
}
/* Page too small (shouldn't happen for reasonable sizes) */
offset_print(" KMALLOC: requested size too large for single page\n");
insert_free_block(new_block);
return NULL;
}
void kfree(void *ptr) {
if (!ptr) {
return;
}
/* Get the block header */
block_header_t *block = (block_header_t *)((uint8_t *)ptr - sizeof(block_header_t));
if (block->magic != BLOCK_MAGIC) {
offset_print(" KMALLOC: kfree() invalid pointer or corruption!\n");
return;
}
if (block->is_free) {
offset_print(" KMALLOC: kfree() double free detected!\n");
return;
}
insert_free_block(block);
}
void *kcalloc(size_t count, size_t size) {
size_t total = count * size;
/* Overflow check */
if (count != 0 && total / count != size) {
return NULL;
}
void *ptr = kmalloc(total);
if (ptr) {
memset(ptr, 0, total);
}
return ptr;
}

46
src/kmalloc.h Normal file
View File

@@ -0,0 +1,46 @@
/**
* @file kmalloc.h
* @brief Kernel memory allocator.
*
* Provides malloc/free for the kernel. Internally uses the paging subsystem
* to ensure returned addresses are backed by physical RAM. Uses a simple
* first-fit free-list allocator with block splitting and coalescing.
*/
#ifndef KMALLOC_H
#define KMALLOC_H
#include <stddef.h>
/**
* Initialize the kernel memory allocator.
*
* Must be called after init_paging(). Allocates the initial heap pages.
*/
void init_kmalloc(void);
/**
* Allocate a block of kernel memory.
*
* @param size Number of bytes to allocate.
* @return Pointer to the allocated memory, or NULL on failure.
*/
void *kmalloc(size_t size);
/**
* Free a previously allocated block of kernel memory.
*
* @param ptr Pointer returned by kmalloc. NULL is safely ignored.
*/
void kfree(void *ptr);
/**
* Allocate a block of zero-initialized kernel memory.
*
* @param count Number of elements.
* @param size Size of each element in bytes.
* @return Pointer to the allocated and zeroed memory, or NULL on failure.
*/
void *kcalloc(size_t count, size_t size);
#endif /* KMALLOC_H */

40
src/linker.ld Normal file
View File

@@ -0,0 +1,40 @@
ENTRY(_start)
SECTIONS
{
. = 1M;
_kernel_start = .;
.text BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.multiboot))
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
*(.rodata.*)
}
.drivers BLOCK(4K) : ALIGN(4K)
{
__drivers_start = .;
KEEP(*(.drivers))
__drivers_end = .;
}
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
_kernel_end = .;
}

278
src/paging.c Normal file
View File

@@ -0,0 +1,278 @@
/**
* @file paging.c
* @brief Virtual memory paging subsystem implementation.
*
* Implements two-level x86 paging (page directory + page tables) with 4 KiB
* pages. At initialization, all detected physical memory is identity-mapped
* so that physical addresses equal virtual addresses. Drivers and the kernel
* can then allocate additional virtual pages as needed.
*
* The kernel heap region starts at KERNEL_HEAP_START (0xD0000000) and grows
* upward as pages are requested through paging_alloc_page().
*/
#include "paging.h"
#include "pmm.h"
#include "port_io.h"
#include <stddef.h>
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/** Kernel heap starts at 0xD0000000 (above the 0xC0000000 higher-half region). */
#define KERNEL_HEAP_START 0xD0000000
/** Kernel heap ends at 0xF0000000 (768 MiB of virtual space for kernel heap). */
#define KERNEL_HEAP_END 0xF0000000
/**
* The page directory. Must be page-aligned (4 KiB).
* Each entry either points to a page table or is zero (not present).
*/
static uint32_t page_directory[PAGE_ENTRIES] __attribute__((aligned(4096)));
/**
* Storage for page tables. We pre-allocate enough for identity mapping.
* For a system with up to 4 GiB, we'd need 1024 page tables, but we
* only use these for the first 16 MiB during early boot. Additional page
* tables are allocated from the PMM as needed.
*
* The first 16 MiB must be statically allocated because the PMM bitmap
* itself lives in BSS within this region.
*/
#define STATIC_PT_COUNT 4
static uint32_t static_page_tables[STATIC_PT_COUNT][PAGE_ENTRIES] __attribute__((aligned(4096)));
/**
* Dynamically allocated page tables for memory above 16 MiB.
* Before paging is enabled, we allocate these from the PMM and store
* their physical addresses here so we can access them after paging.
*/
#define MAX_DYNAMIC_PT 256
static uint32_t *dynamic_page_tables[MAX_DYNAMIC_PT];
static uint32_t dynamic_pt_count = 0;
/** Next virtual address to hand out from the kernel heap. */
static uint32_t heap_next = KERNEL_HEAP_START;
/**
* Flush a single TLB entry for the given virtual address.
*
* @param vaddr The virtual address whose TLB entry to invalidate.
*/
static inline void tlb_flush_single(uint32_t vaddr) {
__asm__ volatile("invlpg (%0)" : : "r"(vaddr) : "memory");
}
/**
* Reload CR3 to flush the entire TLB.
*/
static inline void tlb_flush_all(void) {
uint32_t cr3;
__asm__ volatile("mov %%cr3, %0" : "=r"(cr3));
__asm__ volatile("mov %0, %%cr3" : : "r"(cr3) : "memory");
}
/**
* Get a page table for a given page directory index.
*
* If the page directory entry is not present, allocate a new page table
* from the PMM and install it.
*
* @param pd_idx Page directory index (01023).
* @param create If non-zero, create the page table if it doesn't exist.
* @return Pointer to the page table, or NULL if not present and !create.
*/
static uint32_t *get_page_table(uint32_t pd_idx, int create) {
if (page_directory[pd_idx] & PAGE_PRESENT) {
return (uint32_t *)(page_directory[pd_idx] & 0xFFFFF000);
}
if (!create) {
return NULL;
}
/* Allocate a new page table from the PMM */
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
if (pt_phys == 0) {
offset_print(" PAGING: FATAL - could not allocate page table\n");
return NULL;
}
/* Zero the new page table */
memset((void *)pt_phys, 0, 4096);
/* Install it in the page directory */
page_directory[pd_idx] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
return (uint32_t *)pt_phys;
}
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags) {
uint32_t pd_idx = PD_INDEX(vaddr);
uint32_t pt_idx = PT_INDEX(vaddr);
uint32_t *pt = get_page_table(pd_idx, 1);
if (!pt) {
return;
}
pt[pt_idx] = (paddr & 0xFFFFF000) | (flags & 0xFFF);
tlb_flush_single(vaddr);
}
void paging_unmap_page(uint32_t vaddr) {
uint32_t pd_idx = PD_INDEX(vaddr);
uint32_t pt_idx = PT_INDEX(vaddr);
uint32_t *pt = get_page_table(pd_idx, 0);
if (!pt) {
return;
}
pt[pt_idx] = 0;
tlb_flush_single(vaddr);
}
uint32_t paging_get_physical(uint32_t vaddr) {
uint32_t pd_idx = PD_INDEX(vaddr);
uint32_t pt_idx = PT_INDEX(vaddr);
uint32_t *pt = get_page_table(pd_idx, 0);
if (!pt) {
return 0;
}
if (!(pt[pt_idx] & PAGE_PRESENT)) {
return 0;
}
return (pt[pt_idx] & 0xFFFFF000) | (vaddr & 0xFFF);
}
void *paging_alloc_page(void) {
if (heap_next >= KERNEL_HEAP_END) {
offset_print(" PAGING: kernel heap exhausted\n");
return NULL;
}
/* Allocate a physical page */
phys_addr_t paddr = pmm_alloc_page(PMM_ZONE_NORMAL);
if (paddr == 0) {
offset_print(" PAGING: out of physical memory\n");
return NULL;
}
/* Map it into the kernel heap */
uint32_t vaddr = heap_next;
paging_map_page(vaddr, paddr, PAGE_PRESENT | PAGE_WRITE);
heap_next += 4096;
return (void *)vaddr;
}
void paging_free_page(void *vaddr) {
uint32_t va = (uint32_t)vaddr;
/* Look up the physical address before unmapping */
uint32_t paddr = paging_get_physical(va);
if (paddr == 0) {
return;
}
/* Unmap the virtual page */
paging_unmap_page(va);
/* Return the physical page to the PMM */
pmm_free_page(paddr & 0xFFFFF000);
}
void init_paging(void) {
/* 1. Zero the page directory */
memset(page_directory, 0, sizeof(page_directory));
/* 2. Identity map the first 16 MiB using static page tables.
* This covers the kernel (loaded at 1 MiB), the PMM bitmap (in BSS),
* the stack, and typical BIOS/device regions.
* Each page table maps 4 MiB (1024 entries × 4 KiB).
*/
for (uint32_t i = 0; i < STATIC_PT_COUNT; i++) {
memset(static_page_tables[i], 0, sizeof(static_page_tables[i]));
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
static_page_tables[i][j] = paddr | PAGE_PRESENT | PAGE_WRITE;
}
page_directory[i] = (uint32_t)static_page_tables[i] | PAGE_PRESENT | PAGE_WRITE;
}
offset_print(" PAGING: identity mapped first 16 MiB\n");
/* 3. Identity map memory above 16 MiB using dynamically allocated page
* tables. We do this BEFORE enabling paging, so physical addresses
* are still directly accessible.
*
* mem_upper is in KiB and starts at 1 MiB, so total memory is
* approximately (mem_upper + 1024) KiB.
*/
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total memory in KiB */
uint32_t total_bytes = mem_kb * 1024;
uint32_t pd_entries_needed = (total_bytes + (4 * 1024 * 1024 - 1)) / (4 * 1024 * 1024);
if (pd_entries_needed > PAGE_ENTRIES) {
pd_entries_needed = PAGE_ENTRIES;
}
dynamic_pt_count = 0;
for (uint32_t i = STATIC_PT_COUNT; i < pd_entries_needed; i++) {
if (dynamic_pt_count >= MAX_DYNAMIC_PT) {
break;
}
/* Allocate a page for this page table from the DMA zone,
* since we need it to be accessible before paging is enabled
* (i.e., within the first 16 MiB identity map won't help for
* the page table itself, but we haven't enabled paging yet so
* ALL physical memory is accessible). */
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_DMA);
if (pt_phys == 0) {
pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
}
if (pt_phys == 0) {
offset_print(" PAGING: WARNING - could not alloc page table\n");
break;
}
uint32_t *pt = (uint32_t *)pt_phys;
dynamic_page_tables[dynamic_pt_count++] = pt;
/* Fill the page table with identity mappings */
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
pt[j] = paddr | PAGE_PRESENT | PAGE_WRITE;
}
page_directory[i] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
}
if (dynamic_pt_count > 0) {
offset_print(" PAGING: identity mapped ");
print_hex(pd_entries_needed * 4);
offset_print(" PAGING: MiB total using ");
print_hex(dynamic_pt_count);
offset_print(" PAGING: additional page tables\n");
}
/* 4. Load the page directory into CR3 */
__asm__ volatile("mov %0, %%cr3" : : "r"(page_directory) : "memory");
/* 5. Enable paging by setting bit 31 (PG) of CR0 */
uint32_t cr0;
__asm__ volatile("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x80000000;
__asm__ volatile("mov %0, %%cr0" : : "r"(cr0) : "memory");
offset_print(" PAGING: enabled\n");
}

86
src/paging.h Normal file
View File

@@ -0,0 +1,86 @@
/**
* @file paging.h
* @brief Virtual memory paging subsystem.
*
* Provides page directory and page table management for the x86 two-level
* paging scheme (no PAE). Allows mapping and unmapping individual 4 KiB pages,
* as well as allocating virtual pages backed by physical memory.
*/
#ifndef PAGING_H
#define PAGING_H
#include <stdint.h>
/** Page table entry flags. */
#define PAGE_PRESENT 0x001 /**< Page is present in memory. */
#define PAGE_WRITE 0x002 /**< Page is writable. */
#define PAGE_USER 0x004 /**< Page is accessible from ring 3. */
#define PAGE_WRITETHROUGH 0x008 /**< Write-through caching. */
#define PAGE_NOCACHE 0x010 /**< Disable caching for this page. */
#define PAGE_ACCESSED 0x020 /**< CPU has read from this page. */
#define PAGE_DIRTY 0x040 /**< CPU has written to this page. */
#define PAGE_SIZE_4MB 0x080 /**< 4 MiB page (page directory only). */
/** Number of entries in a page directory or page table. */
#define PAGE_ENTRIES 1024
/** Extract the page directory index from a virtual address. */
#define PD_INDEX(vaddr) (((uint32_t)(vaddr) >> 22) & 0x3FF)
/** Extract the page table index from a virtual address. */
#define PT_INDEX(vaddr) (((uint32_t)(vaddr) >> 12) & 0x3FF)
/**
* Initialize the paging subsystem.
*
* Sets up a page directory, identity-maps all detected physical memory,
* and enables paging by writing to CR3 and CR0.
*/
void init_paging(void);
/**
* Map a virtual address to a physical address with the given flags.
*
* If no page table exists for the virtual address range, one is allocated
* from the PMM automatically.
*
* @param vaddr Virtual address (must be page-aligned).
* @param paddr Physical address (must be page-aligned).
* @param flags Page flags (PAGE_PRESENT | PAGE_WRITE | ...).
*/
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
/**
* Unmap a virtual address, freeing the mapping but not the physical page.
*
* @param vaddr Virtual address to unmap (must be page-aligned).
*/
void paging_unmap_page(uint32_t vaddr);
/**
* Allocate a new virtual page backed by physical memory.
*
* Finds a free virtual address, allocates a physical page from the PMM,
* and creates a mapping.
*
* @return Virtual address of the allocated page, or NULL on failure.
*/
void *paging_alloc_page(void);
/**
* Free a virtual page, unmapping it and returning the physical page to the PMM.
*
* @param vaddr Virtual address of the page to free.
*/
void paging_free_page(void *vaddr);
/**
* Look up the physical address mapped to a virtual address.
*
* @param vaddr Virtual address to translate.
* @return Physical address, or 0 if the page is not mapped.
*/
uint32_t paging_get_physical(uint32_t vaddr);
#endif /* PAGING_H */

95
src/pic.c Normal file
View File

@@ -0,0 +1,95 @@
#include "pic.h"
#include "port_io.h"
#define PIC1_COMMAND 0x20
#define PIC1_DATA 0x21
#define PIC2_COMMAND 0xA0
#define PIC2_DATA 0xA1
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
#define PIC_EOI 0x20
void pic_send_eoi(uint8_t irq)
{
if(irq >= 8)
outb(PIC2_COMMAND, PIC_EOI);
outb(PIC1_COMMAND, PIC_EOI);
}
/*
reinitialize the PIC controllers, giving them specified vector offsets
rather than 8h and 70h, as configured by default
*/
#define PIC1_OFFSET 0x20
#define PIC2_OFFSET 0x28
void init_pic(void)
{
unsigned char a1, a2;
a1 = inb(PIC1_DATA); // save masks
a2 = inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode)
io_wait();
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
io_wait();
outb(PIC1_DATA, PIC1_OFFSET); // ICW2: Master PIC vector offset
io_wait();
outb(PIC2_DATA, PIC2_OFFSET); // ICW2: Slave PIC vector offset
io_wait();
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
io_wait();
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
outb(PIC1_DATA, a1); // restore saved masks.
outb(PIC2_DATA, a2);
}
void pic_clear_mask(uint8_t irq) {
uint16_t port;
uint8_t value;
if(irq < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
irq -= 8;
}
value = inb(port) & ~(1 << irq);
outb(port, value);
}
void pic_set_mask(uint8_t irq) {
uint16_t port;
uint8_t value;
if(irq < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
irq -= 8;
}
value = inb(port) | (1 << irq);
outb(port, value);
}

11
src/pic.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef PIC_H
#define PIC_H
#include <stdint.h>
void init_pic(void);
void pic_send_eoi(uint8_t irq);
void pic_clear_mask(uint8_t irq);
void pic_set_mask(uint8_t irq);
#endif

165
src/pmm.c Normal file
View File

@@ -0,0 +1,165 @@
#include "pmm.h"
#include "port_io.h"
#include <multiboot2.h>
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 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;
// static uint32_t used_frames = 0; // Unused for now
static void set_frame(phys_addr_t frame_addr) {
uint32_t frame = frame_addr / PAGE_SIZE;
uint32_t idx = frame / FRAMES_PER_INDEX;
uint32_t off = frame % FRAMES_PER_INDEX;
memory_bitmap[idx] |= (1 << off);
}
static void clear_frame(phys_addr_t frame_addr) {
uint32_t frame = frame_addr / PAGE_SIZE;
uint32_t idx = frame / FRAMES_PER_INDEX;
uint32_t off = frame % FRAMES_PER_INDEX;
memory_bitmap[idx] &= ~(1 << off);
}
static uint32_t first_free_frame(size_t start_frame, size_t end_frame) {
for (size_t i = start_frame; i < end_frame; i++) {
uint32_t idx = i / FRAMES_PER_INDEX;
uint32_t off = i % FRAMES_PER_INDEX;
if (!(memory_bitmap[idx] & (1 << off))) {
return i;
}
}
return (uint32_t)-1;
}
void init_pmm(uint32_t multiboot_addr) {
struct multiboot_tag *tag;
uint32_t addr = multiboot_addr;
/* Initialize all memory as used (1) initially */
for (uint32_t i = 0; i < BITMAP_SIZE; i++) {
memory_bitmap[i] = 0xFFFFFFFF;
}
if (addr & 7) {
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))) {
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;
for (mmap = tag_mmap->entries;
(multiboot_uint8_t *)mmap < (multiboot_uint8_t *)tag + tag->size;
mmap = (multiboot_memory_map_t *)((unsigned long)mmap + tag_mmap->entry_size)) {
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
uint64_t start = mmap->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);
}
for (uint64_t addr_iter = start; addr_iter < end; addr_iter += PAGE_SIZE) {
if (addr_iter + PAGE_SIZE <= end && addr_iter < MAX_PHYSICAL_MEMORY) {
clear_frame((phys_addr_t)addr_iter);
}
}
}
}
}
}
uint32_t kstart = (uint32_t)&_kernel_start;
uint32_t kend = (uint32_t)&_kernel_end;
kstart &= ~(PAGE_SIZE - 1);
kend = (kend + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
for (uint32_t i = kstart; i < kend; i += PAGE_SIZE) {
set_frame(i);
}
// Mark multiboot structure
uint32_t mb_start = multiboot_addr;
uint32_t mb_end = multiboot_addr + size;
mb_start &= ~(PAGE_SIZE - 1);
mb_end = (mb_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
for (uint32_t i = mb_start; i < mb_end; i += PAGE_SIZE) {
set_frame(i);
}
// 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) {
uint32_t start_frame = 0;
uint32_t end_frame = BITMAP_SIZE * FRAMES_PER_INDEX;
uint32_t frame = (uint32_t)-1;
// 16MB boundary is at 16*1024*1024 / 4096 = 4096 frames
uint32_t dma_limit = 4096;
if (zone == PMM_ZONE_DMA) {
end_frame = dma_limit;
frame = first_free_frame(start_frame, end_frame);
} else {
start_frame = dma_limit;
frame = first_free_frame(start_frame, end_frame);
// Fallback to DMA if NORMAL failed
if (frame == (uint32_t)-1) {
frame = first_free_frame(0, dma_limit);
}
}
if (frame != (uint32_t)-1) {
phys_addr_t addr = frame * PAGE_SIZE;
set_frame(addr);
return addr;
}
return 0; // OOM
}
void pmm_free_page(phys_addr_t addr) {
clear_frame(addr);
}
uint32_t pmm_get_memory_size(void) {
return memory_size;
}

42
src/pmm.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef PMM_H
#define PMM_H
#include <stdint.h>
#include <stddef.h>
#define PAGE_SIZE 4096
typedef uintptr_t phys_addr_t;
typedef enum {
PMM_ZONE_DMA, // < 16 MB
PMM_ZONE_NORMAL, // Any other memory
PMM_ZONE_HIGHMEM // > 4 GB (if 64-bit or PAE, but we are 32-bit for now) - actually sticking to DMA and NORMAL is enough for 32-bit typically
} pmm_zone_t;
/*
* Initialize the physical memory manager.
* @param multiboot_addr Address of the multiboot info structure
*/
void init_pmm(uint32_t multiboot_addr);
/*
* Allocate a physical page.
* @param zone The preferred zone to allocate from.
* @return Physical address of the allocated page, or 0 if out of memory.
*/
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
/**
* Free a physical page.
* @param addr Physical address of the page to free.
*/
void pmm_free_page(phys_addr_t addr);
/**
* Get the total detected upper memory in KiB.
* @return Upper memory size in KiB as reported by Multiboot.
*/
uint32_t pmm_get_memory_size(void);
#endif

25
src/port_io.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef PORT_IO_H
#define PORT_IO_H
#include <stdint.h>
static inline void outb(uint16_t port, uint8_t val)
{
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
}
static inline uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb %w1, %b0" : "=a"(ret) : "Nd"(port) );
return ret;
}
static inline void io_wait(void)
{
/* Port 0x80 is used for 'checkpoints' during POST. */
/* The Linux kernel seems to think it is free for use :-/ */
asm volatile ( "outb %%al, $0x80" : : "a"(0) );
}
#endif

172
src/string.c Normal file
View File

@@ -0,0 +1,172 @@
/**
* @file string.c
* @brief Minimal C string/memory functions for the freestanding kernel.
*
* These implementations replace the standard library versions since the
* kernel is compiled with -ffreestanding and does not link against libc.
*/
#include <stddef.h>
#include <stdint.h>
/**
* Fill a region of memory with a byte value.
*
* @param s Destination pointer.
* @param c Byte value to fill (only low 8 bits used).
* @param n Number of bytes to fill.
* @return The destination pointer.
*/
void *memset(void *s, int c, size_t n) {
unsigned char *p = (unsigned char *)s;
while (n--) {
*p++ = (unsigned char)c;
}
return s;
}
/**
* Copy a region of memory (non-overlapping).
*
* @param dest Destination pointer.
* @param src Source pointer.
* @param n Number of bytes to copy.
* @return The destination pointer.
*/
void *memcpy(void *dest, const void *src, size_t n) {
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
while (n--) {
*d++ = *s++;
}
return dest;
}
/**
* Copy a region of memory, handling overlaps correctly.
*
* @param dest Destination pointer.
* @param src Source pointer.
* @param n Number of bytes to copy.
* @return The destination pointer.
*/
void *memmove(void *dest, const void *src, size_t n) {
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
if (d < s) {
while (n--) {
*d++ = *s++;
}
} else {
d += n;
s += n;
while (n--) {
*--d = *--s;
}
}
return dest;
}
/**
* Compare two regions of memory.
*
* @param s1 First memory region.
* @param s2 Second memory region.
* @param n 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 n) {
const unsigned char *a = (const unsigned char *)s1;
const unsigned char *b = (const unsigned char *)s2;
while (n--) {
if (*a != *b) {
return *a - *b;
}
a++;
b++;
}
return 0;
}
/**
* Calculate the length of a null-terminated string.
*
* @param s The string.
* @return Number of characters before the null terminator.
*/
size_t strlen(const char *s) {
size_t len = 0;
while (*s++) {
len++;
}
return len;
}
/**
* Compare two null-terminated strings.
*
* @param s1 First string.
* @param s2 Second string.
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
*/
int strcmp(const char *s1, const char *s2) {
while (*s1 && *s1 == *s2) {
s1++;
s2++;
}
return *(unsigned char *)s1 - *(unsigned char *)s2;
}
/**
* Compare at most n characters of two strings.
*
* @param s1 First string.
* @param s2 Second string.
* @param n Maximum number of characters to compare.
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
*/
int strncmp(const char *s1, const char *s2, size_t n) {
while (n && *s1 && *s1 == *s2) {
s1++;
s2++;
n--;
}
if (n == 0) {
return 0;
}
return *(unsigned char *)s1 - *(unsigned char *)s2;
}
/**
* Copy a null-terminated string.
*
* @param dest Destination buffer (must be large enough).
* @param src Source string.
* @return The destination pointer.
*/
char *strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++))
;
return dest;
}
/**
* Copy at most n characters of a string, null-padding if shorter.
*
* @param dest Destination buffer.
* @param src Source string.
* @param n Maximum characters to copy.
* @return The destination pointer.
*/
char *strncpy(char *dest, const char *src, size_t n) {
char *d = dest;
while (n && (*d++ = *src++)) {
n--;
}
while (n--) {
*d++ = '\0';
}
return dest;
}

230
src/vga.c Normal file
View File

@@ -0,0 +1,230 @@
/**
* @file vga.c
* @brief VGA text-mode driver implementation.
*
* Drives the standard VGA text-mode framebuffer at 0xB8000. The buffer
* is an array of 80×25 16-bit values, where the low byte is the ASCII
* character and the high byte encodes foreground and background color.
*
* This driver registers itself via the REGISTER_DRIVER macro and is
* automatically discovered during boot.
*/
#include "vga.h"
#include "driver.h"
#include "port_io.h"
#include "pmm.h"
/** Base address of the VGA text-mode framebuffer. */
#define VGA_BUFFER 0xB8000
/** Pointer to the VGA framebuffer, treated as an array of uint16_t. */
static uint16_t *vga_buffer = (uint16_t *)VGA_BUFFER;
/** Current cursor row (0-based). */
static uint8_t cursor_row = 0;
/** Current cursor column (0-based). */
static uint8_t cursor_col = 0;
/** Current text attribute byte (foreground | background << 4). */
static uint8_t text_attr = 0;
/**
* Create a VGA entry (character + attribute) for the framebuffer.
*
* @param c ASCII character.
* @param attr Color attribute byte.
* @return 16-bit VGA entry.
*/
static inline uint16_t vga_entry(char c, uint8_t attr) {
return (uint16_t)c | ((uint16_t)attr << 8);
}
/**
* Create a color attribute byte from foreground and background colors.
*
* @param fg Foreground color (015).
* @param bg Background color (015).
* @return Attribute byte.
*/
static inline uint8_t vga_color(vga_color_t fg, vga_color_t bg) {
return (uint8_t)fg | ((uint8_t)bg << 4);
}
/**
* Update the hardware cursor position via VGA I/O ports.
*/
static void update_cursor(void) {
uint16_t pos = cursor_row * VGA_WIDTH + cursor_col;
outb(0x3D4, 0x0F);
outb(0x3D5, (uint8_t)(pos & 0xFF));
outb(0x3D4, 0x0E);
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
}
/**
* Scroll the screen up by one line.
*
* The top line is discarded, all other lines move up, and the bottom
* line is filled with spaces.
*/
static void scroll(void) {
/* Move all lines up by one */
for (int i = 0; i < (VGA_HEIGHT - 1) * VGA_WIDTH; i++) {
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
}
/* Clear the last line */
uint16_t blank = vga_entry(' ', text_attr);
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
vga_buffer[i] = blank;
}
cursor_row = VGA_HEIGHT - 1;
}
void vga_clear(void) {
uint16_t blank = vga_entry(' ', text_attr);
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
vga_buffer[i] = blank;
}
cursor_row = 0;
cursor_col = 0;
update_cursor();
}
void vga_set_color(vga_color_t fg, vga_color_t bg) {
text_attr = vga_color(fg, bg);
}
void vga_putchar(char c) {
if (c == '\n') {
cursor_col = 0;
cursor_row++;
} else if (c == '\r') {
cursor_col = 0;
} else if (c == '\t') {
cursor_col = (cursor_col + 8) & ~7;
if (cursor_col >= VGA_WIDTH) {
cursor_col = 0;
cursor_row++;
}
} else if (c == '\b') {
if (cursor_col > 0) {
cursor_col--;
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(' ', text_attr);
}
} else {
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(c, text_attr);
cursor_col++;
if (cursor_col >= VGA_WIDTH) {
cursor_col = 0;
cursor_row++;
}
}
if (cursor_row >= VGA_HEIGHT) {
scroll();
}
update_cursor();
}
void vga_puts(const char *str) {
while (*str) {
vga_putchar(*str);
str++;
}
}
void vga_put_hex(uint32_t val) {
const char *hex = "0123456789ABCDEF";
vga_putchar('0');
vga_putchar('x');
for (int i = 28; i >= 0; i -= 4) {
vga_putchar(hex[(val >> i) & 0xF]);
}
}
void vga_put_dec(uint32_t val) {
if (val == 0) {
vga_putchar('0');
return;
}
char buf[12];
int pos = 0;
while (val > 0) {
buf[pos++] = '0' + (val % 10);
val /= 10;
}
/* Print in reverse */
while (pos > 0) {
vga_putchar(buf[--pos]);
}
}
void vga_show_mem_stats(void) {
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total including lower */
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
vga_puts("=== ClaudeOS Memory Statistics ===\n");
vga_set_color(VGA_WHITE, VGA_BLACK);
vga_puts(" Total RAM: ");
vga_put_dec(mem_kb);
vga_puts(" KiB (");
vga_put_dec(mem_kb / 1024);
vga_puts(" MiB)\n");
vga_puts(" Kernel start: ");
extern uint32_t _kernel_start;
vga_put_hex((uint32_t)&_kernel_start);
vga_puts("\n");
vga_puts(" Kernel end: ");
extern uint32_t _kernel_end;
vga_put_hex((uint32_t)&_kernel_end);
vga_puts("\n");
uint32_t kernel_size = (uint32_t)&_kernel_end - (uint32_t)&_kernel_start;
vga_puts(" Kernel size: ");
vga_put_dec(kernel_size / 1024);
vga_puts(" KiB\n");
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
vga_puts("==================================\n");
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
}
/* --- Driver registration --- */
/**
* VGA probe: always succeeds since VGA text mode is always available
* on the target platform (i386).
*/
static driver_probe_result_t vga_probe(void) {
return DRIVER_PROBE_OK;
}
int vga_init(void) {
text_attr = vga_color(VGA_LIGHT_GREY, VGA_BLACK);
vga_clear();
vga_set_color(VGA_LIGHT_GREEN, VGA_BLACK);
vga_puts("ClaudeOS v0.1 booting...\n\n");
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
return 0;
}
/** VGA driver descriptor. */
static const driver_t vga_driver = {
.name = "vga",
.probe = vga_probe,
.init = vga_init,
};
REGISTER_DRIVER(vga_driver);

97
src/vga.h Normal file
View File

@@ -0,0 +1,97 @@
/**
* @file vga.h
* @brief VGA text-mode driver interface.
*
* Provides functions to write text to the VGA text-mode framebuffer
* (typically at 0xB8000). Supports an 80x25 character display with
* 16 foreground and 16 background colors.
*/
#ifndef VGA_H
#define VGA_H
#include <stdint.h>
#include <stddef.h>
/** VGA color constants. */
typedef enum {
VGA_BLACK = 0,
VGA_BLUE = 1,
VGA_GREEN = 2,
VGA_CYAN = 3,
VGA_RED = 4,
VGA_MAGENTA = 5,
VGA_BROWN = 6,
VGA_LIGHT_GREY = 7,
VGA_DARK_GREY = 8,
VGA_LIGHT_BLUE = 9,
VGA_LIGHT_GREEN = 10,
VGA_LIGHT_CYAN = 11,
VGA_LIGHT_RED = 12,
VGA_LIGHT_MAGENTA = 13,
VGA_YELLOW = 14,
VGA_WHITE = 15,
} vga_color_t;
/** VGA screen dimensions. */
#define VGA_WIDTH 80
#define VGA_HEIGHT 25
/**
* Initialize the VGA driver.
*
* Clears the screen and sets the default colors.
*
* @return 0 on success.
*/
int vga_init(void);
/**
* Clear the VGA screen.
*/
void vga_clear(void);
/**
* Set the foreground and background color for subsequent writes.
*
* @param fg Foreground color.
* @param bg Background color.
*/
void vga_set_color(vga_color_t fg, vga_color_t bg);
/**
* Write a single character at the current cursor position.
*
* @param c Character to write.
*/
void vga_putchar(char c);
/**
* Write a null-terminated string at the current cursor position.
*
* @param str String to write.
*/
void vga_puts(const char *str);
/**
* Write a 32-bit value in hexadecimal to the VGA display.
*
* @param val Value to display.
*/
void vga_put_hex(uint32_t val);
/**
* Write a 32-bit value in decimal to the VGA display.
*
* @param val Value to display.
*/
void vga_put_dec(uint32_t val);
/**
* Display boot memory statistics on VGA.
*
* Shows detected memory, kernel size, and free pages.
*/
void vga_show_mem_stats(void);
#endif /* VGA_H */

25
test_images.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
set -e
# Build directory
BUILD_DIR=build
BIN_DIR=$BUILD_DIR/bin
RELEASE_DIR=release
# Check if images exist
if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
echo "Error: claude-os.iso not found!"
exit 1
fi
echo "Testing ISO image..."
timeout 5s qemu-system-i386 -cdrom "$RELEASE_DIR/claude-os.iso" -debugcon file:iso_output.txt -display none -no-reboot || true
if grep -q "Hello, world" iso_output.txt; then
echo "ISO Test Passed!"
else
echo "ISO Test Failed!"
cat iso_output.txt
exit 1
fi
echo "All tests passed!"

417
vendor/multiboot2.h vendored Normal file
View File

@@ -0,0 +1,417 @@
/* multiboot2.h - Multiboot 2 header file. */
/* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY
* DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MULTIBOOT_HEADER
#define MULTIBOOT_HEADER 1
/* How many bytes from the start of the file we search for the header. */
#define MULTIBOOT_SEARCH 32768
#define MULTIBOOT_HEADER_ALIGN 8
/* The magic field should contain this. */
#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6
/* This should be in %eax. */
#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289
/* Alignment of multiboot modules. */
#define MULTIBOOT_MOD_ALIGN 0x00001000
/* Alignment of the multiboot info structure. */
#define MULTIBOOT_INFO_ALIGN 0x00000008
/* Flags set in the 'flags' member of the multiboot header. */
#define MULTIBOOT_TAG_ALIGN 8
#define MULTIBOOT_TAG_TYPE_END 0
#define MULTIBOOT_TAG_TYPE_CMDLINE 1
#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2
#define MULTIBOOT_TAG_TYPE_MODULE 3
#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4
#define MULTIBOOT_TAG_TYPE_BOOTDEV 5
#define MULTIBOOT_TAG_TYPE_MMAP 6
#define MULTIBOOT_TAG_TYPE_VBE 7
#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8
#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9
#define MULTIBOOT_TAG_TYPE_APM 10
#define MULTIBOOT_TAG_TYPE_EFI32 11
#define MULTIBOOT_TAG_TYPE_EFI64 12
#define MULTIBOOT_TAG_TYPE_SMBIOS 13
#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14
#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15
#define MULTIBOOT_TAG_TYPE_NETWORK 16
#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17
#define MULTIBOOT_TAG_TYPE_EFI_BS 18
#define MULTIBOOT_TAG_TYPE_EFI32_IH 19
#define MULTIBOOT_TAG_TYPE_EFI64_IH 20
#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21
#define MULTIBOOT_HEADER_TAG_END 0
#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1
#define MULTIBOOT_HEADER_TAG_ADDRESS 2
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3
#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4
#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5
#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6
#define MULTIBOOT_HEADER_TAG_EFI_BS 7
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9
#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10
#define MULTIBOOT_ARCHITECTURE_I386 0
#define MULTIBOOT_ARCHITECTURE_MIPS32 4
#define MULTIBOOT_HEADER_TAG_OPTIONAL 1
#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2
#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2
#ifndef ASM_FILE
typedef unsigned char multiboot_uint8_t;
typedef unsigned short multiboot_uint16_t;
typedef unsigned int multiboot_uint32_t;
typedef unsigned long long multiboot_uint64_t;
struct multiboot_header
{
/* Must be MULTIBOOT_MAGIC - see above. */
multiboot_uint32_t magic;
/* ISA */
multiboot_uint32_t architecture;
/* Total header length. */
multiboot_uint32_t header_length;
/* The above fields plus this one must equal 0 mod 2^32. */
multiboot_uint32_t checksum;
};
struct multiboot_header_tag
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
};
struct multiboot_header_tag_information_request
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t requests[0];
};
struct multiboot_header_tag_address
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t header_addr;
multiboot_uint32_t load_addr;
multiboot_uint32_t load_end_addr;
multiboot_uint32_t bss_end_addr;
};
struct multiboot_header_tag_entry_address
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t entry_addr;
};
struct multiboot_header_tag_console_flags
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t console_flags;
};
struct multiboot_header_tag_framebuffer
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t width;
multiboot_uint32_t height;
multiboot_uint32_t depth;
};
struct multiboot_header_tag_module_align
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
};
struct multiboot_header_tag_relocatable
{
multiboot_uint16_t type;
multiboot_uint16_t flags;
multiboot_uint32_t size;
multiboot_uint32_t min_addr;
multiboot_uint32_t max_addr;
multiboot_uint32_t align;
multiboot_uint32_t preference;
};
struct multiboot_color
{
multiboot_uint8_t red;
multiboot_uint8_t green;
multiboot_uint8_t blue;
};
struct multiboot_mmap_entry
{
multiboot_uint64_t addr;
multiboot_uint64_t len;
#define MULTIBOOT_MEMORY_AVAILABLE 1
#define MULTIBOOT_MEMORY_RESERVED 2
#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3
#define MULTIBOOT_MEMORY_NVS 4
#define MULTIBOOT_MEMORY_BADRAM 5
multiboot_uint32_t type;
multiboot_uint32_t zero;
};
typedef struct multiboot_mmap_entry multiboot_memory_map_t;
struct multiboot_tag
{
multiboot_uint32_t type;
multiboot_uint32_t size;
};
struct multiboot_tag_string
{
multiboot_uint32_t type;
multiboot_uint32_t size;
char string[0];
};
struct multiboot_tag_module
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t mod_start;
multiboot_uint32_t mod_end;
char cmdline[0];
};
struct multiboot_tag_basic_meminfo
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t mem_lower;
multiboot_uint32_t mem_upper;
};
struct multiboot_tag_bootdev
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t biosdev;
multiboot_uint32_t slice;
multiboot_uint32_t part;
};
struct multiboot_tag_mmap
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t entry_size;
multiboot_uint32_t entry_version;
struct multiboot_mmap_entry entries[0];
};
struct multiboot_vbe_info_block
{
multiboot_uint8_t external_specification[512];
};
struct multiboot_vbe_mode_info_block
{
multiboot_uint8_t external_specification[256];
};
struct multiboot_tag_vbe
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint16_t vbe_mode;
multiboot_uint16_t vbe_interface_seg;
multiboot_uint16_t vbe_interface_off;
multiboot_uint16_t vbe_interface_len;
struct multiboot_vbe_info_block vbe_control_info;
struct multiboot_vbe_mode_info_block vbe_mode_info;
};
struct multiboot_tag_framebuffer_common
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t framebuffer_addr;
multiboot_uint32_t framebuffer_pitch;
multiboot_uint32_t framebuffer_width;
multiboot_uint32_t framebuffer_height;
multiboot_uint8_t framebuffer_bpp;
#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0
#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1
#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2
multiboot_uint8_t framebuffer_type;
multiboot_uint16_t reserved;
};
struct multiboot_tag_framebuffer
{
struct multiboot_tag_framebuffer_common common;
union
{
struct
{
multiboot_uint16_t framebuffer_palette_num_colors;
struct multiboot_color framebuffer_palette[0];
};
struct
{
multiboot_uint8_t framebuffer_red_field_position;
multiboot_uint8_t framebuffer_red_mask_size;
multiboot_uint8_t framebuffer_green_field_position;
multiboot_uint8_t framebuffer_green_mask_size;
multiboot_uint8_t framebuffer_blue_field_position;
multiboot_uint8_t framebuffer_blue_mask_size;
};
};
};
struct multiboot_tag_elf_sections
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t num;
multiboot_uint32_t entsize;
multiboot_uint32_t shndx;
char sections[0];
};
struct multiboot_tag_apm
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint16_t version;
multiboot_uint16_t cseg;
multiboot_uint32_t offset;
multiboot_uint16_t cseg_16;
multiboot_uint16_t dseg;
multiboot_uint16_t flags;
multiboot_uint16_t cseg_len;
multiboot_uint16_t cseg_16_len;
multiboot_uint16_t dseg_len;
};
struct multiboot_tag_efi32
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t pointer;
};
struct multiboot_tag_efi64
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t pointer;
};
struct multiboot_tag_smbios
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t major;
multiboot_uint8_t minor;
multiboot_uint8_t reserved[6];
multiboot_uint8_t tables[0];
};
struct multiboot_tag_old_acpi
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t rsdp[0];
};
struct multiboot_tag_new_acpi
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t rsdp[0];
};
struct multiboot_tag_network
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint8_t dhcpack[0];
};
struct multiboot_tag_efi_mmap
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t descr_size;
multiboot_uint32_t descr_vers;
multiboot_uint8_t efi_mmap[0];
};
struct multiboot_tag_efi32_ih
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t pointer;
};
struct multiboot_tag_efi64_ih
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint64_t pointer;
};
struct multiboot_tag_load_base_addr
{
multiboot_uint32_t type;
multiboot_uint32_t size;
multiboot_uint32_t load_base_addr;
};
#endif /* ! ASM_FILE */
#endif /* ! MULTIBOOT_HEADER */