diff --git a/Makefile b/Makefile index de40e2f..ab29e04 100644 --- a/Makefile +++ b/Makefile @@ -1,45 +1,56 @@ -# Makefile for UEFI Operating System +# ============================================================================== +# Makefile for Simple UEFI Operating System +# ============================================================================== -# Architecture +# ---- Architecture ----------------------------------------------------------- ARCH = x86_64 -# Compiler and tools -CC = gcc -LD = ld +# ---- Tools ------------------------------------------------------------------ +CC = gcc +LD = ld OBJCOPY = objcopy -# Directories -SRC_DIR = . +# ---- Directories ------------------------------------------------------------ +SRC_DIR = . BUILD_DIR = build -EFI_DIR = $(BUILD_DIR)/EFI/BOOT +EFI_DIR = $(BUILD_DIR)/EFI/BOOT IMAGE_DIR = $(BUILD_DIR)/image -# GNU-EFI paths (common locations) -EFI_INC = /usr/include/efi -EFI_INCLUDES = -I$(EFI_INC) -I$(EFI_INC)/$(ARCH) -I$(EFI_INC)/protocol -EFI_LDS = /usr/lib/elf_$(ARCH)_efi.lds -EFI_CRT_OBJS = /usr/lib/crt0-efi-$(ARCH).o +# ---- GNU-EFI paths --------------------------------------------------------- +EFI_INC = /usr/include/efi +EFI_INCLUDES = -I$(EFI_INC) -I$(EFI_INC)/$(ARCH) -I$(EFI_INC)/protocol +EFI_LDS = /usr/lib/elf_$(ARCH)_efi.lds +EFI_CRT_OBJS = /usr/lib/crt0-efi-$(ARCH).o EFI_LIB_PATHS = -L/usr/lib -# Compiler flags -CFLAGS = -ffreestanding -fno-stack-protector -fpic \ - -fshort-wchar -mno-red-zone -Wall -Wextra \ - $(EFI_INCLUDES) -DEFI_FUNCTION_WRAPPER +# ---- Compiler / linker flags ------------------------------------------------ + +# Flags shared by both the UEFI loader and the kernel +COMMON_CFLAGS = -ffreestanding -fno-stack-protector -fshort-wchar \ + -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) + +# UEFI loader: position-independent (shared object → PE32+ conversion) +LOADER_CFLAGS = $(COMMON_CFLAGS) -fpic -DEFI_FUNCTION_WRAPPER + +# Kernel: position-dependent static ELF +KERNEL_CFLAGS = $(COMMON_CFLAGS) -fno-pic -# Linker flags LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \ -Bsymbolic $(EFI_LIB_PATHS) $(EFI_CRT_OBJS) -# Libraries LIBS = -lefi -lgnuefi -# Target -TARGET = BOOTX64.EFI -TARGET_SO = bootx64.so -OBJ = $(BUILD_DIR)/main.o +# ---- Targets / objects ------------------------------------------------------ +TARGET = BOOTX64.EFI +TARGET_SO = bootx64.so +LOADER_OBJ = $(BUILD_DIR)/main.o + KERNEL_TARGET = kernel.elf -KERNEL_OBJS = $(BUILD_DIR)/kernel.o $(BUILD_DIR)/string_utils.o $(BUILD_DIR)/commands.o $(BUILD_DIR)/idt.o $(BUILD_DIR)/isr.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/task.o $(BUILD_DIR)/context_switch.o -KERNEL_LD = kernel.ld +KERNEL_LD = kernel.ld +KERNEL_C_SRCS = kernel.c string_utils.c commands.c idt.c memory.c task.c +KERNEL_S_SRCS = isr.S context_switch.S +KERNEL_OBJS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(KERNEL_C_SRCS)) \ + $(patsubst %.S,$(BUILD_DIR)/%.o,$(KERNEL_S_SRCS)) # QEMU settings QEMU = qemu-system-x86_64 @@ -76,17 +87,23 @@ QEMU_FLAGS = -machine q35 \ -drive if=pflash,format=raw,file=$(BUILD_DIR)/OVMF_VARS.fd \ -drive format=raw,file=fat:rw:$(BUILD_DIR) \ -net none \ - -no-reboot + -no-reboot - # Phony targets -.PHONY: all clean run setup install-deps iso runiso +# ============================================================================== +# Phony targets +# ============================================================================== + +.PHONY: all clean run setup install-deps iso runiso help + +# ---- Default ---------------------------------------------------------------- - # Default target all: setup $(EFI_DIR)/$(TARGET) $(BUILD_DIR)/$(KERNEL_TARGET) -# ISO target -ISO_FILE = $(IMAGE_DIR)/os.iso +# ============================================================================== +# ISO image +# ============================================================================== -ESP_IMG = $(IMAGE_DIR)/staging/esp.img +ISO_FILE = $(IMAGE_DIR)/os.iso +ESP_IMG = $(IMAGE_DIR)/staging/esp.img iso: all @echo "Creating FAT ESP image for UEFI boot..." @@ -107,6 +124,10 @@ iso: all $(IMAGE_DIR)/staging @echo "ISO image created: $(ISO_FILE)" +# ============================================================================== +# Run targets +# ============================================================================== + # Run from ISO with QEMU runiso: iso @echo "Starting QEMU from ISO..." @@ -124,77 +145,62 @@ runiso: iso -nographic \ -no-reboot -# Create necessary directories +# ============================================================================== +# Directory setup +# ============================================================================== + setup: @mkdir -p $(BUILD_DIR) @mkdir -p $(EFI_DIR) @mkdir -p $(IMAGE_DIR) -# Compile source files +# ============================================================================== +# Compilation rules +# ============================================================================== + +# ---- UEFI loader object (PIC, linked as shared object → PE32+) ---- + +$(BUILD_DIR)/main.o: $(SRC_DIR)/main.c + @echo " CC $<" + @$(CC) $(LOADER_CFLAGS) -c $< -o $@ + +# ---- Kernel objects (position-dependent, linked into static ELF) ---- +# Pattern rules replace the per-file rules for every kernel source. + $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c - @echo "Compiling $<..." - $(CC) $(CFLAGS) -c $< -o $@ + @echo " CC $<" + @$(CC) $(KERNEL_CFLAGS) -c $< -o $@ -# Compile kernel source -$(BUILD_DIR)/kernel.o: $(SRC_DIR)/kernel.c - @echo "Compiling kernel.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.S + @echo " AS $<" + @$(CC) $(KERNEL_CFLAGS) -c $< -o $@ -$(BUILD_DIR)/string_utils.o: $(SRC_DIR)/string_utils.c - @echo "Compiling string_utils.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ +# ============================================================================== +# Linking +# ============================================================================== -$(BUILD_DIR)/commands.o: $(SRC_DIR)/commands.c - @echo "Compiling commands.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ +# ---- Kernel ELF ---- -$(BUILD_DIR)/idt.o: $(SRC_DIR)/idt.c - @echo "Compiling idt.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ - -$(BUILD_DIR)/isr.o: $(SRC_DIR)/isr.S - @echo "Compiling isr.S..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ - -$(BUILD_DIR)/memory.o: $(SRC_DIR)/memory.c - @echo "Compiling memory.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ - -$(BUILD_DIR)/task.o: $(SRC_DIR)/task.c - @echo "Compiling task.c..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ - -$(BUILD_DIR)/context_switch.o: $(SRC_DIR)/context_switch.S - @echo "Assembling context_switch.S..." - $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ - -mno-red-zone -Wall -Wextra -c $< -o $@ - -# Link kernel ELF $(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD) - @echo "Linking kernel ELF..." - $(LD) -nostdlib -T $(KERNEL_LD) $(KERNEL_OBJS) -o $@ + @echo " LD $@" + @$(LD) -nostdlib -T $(KERNEL_LD) $(KERNEL_OBJS) -o $@ -# Link to create shared object -$(BUILD_DIR)/$(TARGET_SO): $(OBJ) - @echo "Linking $@..." - $(LD) $(LDFLAGS) $(OBJ) -o $@ $(LIBS) +# ---- UEFI loader shared object ---- + +$(BUILD_DIR)/$(TARGET_SO): $(LOADER_OBJ) + @echo " LD $@" + @$(LD) $(LDFLAGS) $(LOADER_OBJ) -o $@ $(LIBS) + +# ---- Convert to PE32+ EFI application ---- -# Convert to EFI application $(EFI_DIR)/$(TARGET): $(BUILD_DIR)/$(TARGET_SO) - @echo "Creating EFI application..." - $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ - -j .dynsym -j .rel -j .rela -j .reloc \ - --target=efi-app-$(ARCH) $< $@ + @echo " OBJCOPY $@" + @$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc \ + --target=efi-app-$(ARCH) $< $@ @echo "Build complete: $@" -# Run with QEMU +# Run with QEMU (FAT directory) run: all @echo "Starting QEMU..." @echo "Using OVMF firmware: $(OVMF_CODE)" @@ -210,20 +216,27 @@ run: all @echo "================================================" $(QEMU) $(QEMU_FLAGS) +# ============================================================================== +# Maintenance +# ============================================================================== + # Clean build artifacts clean: @echo "Cleaning build directory..." rm -rf $(BUILD_DIR) @echo "Clean complete." -# Install dependencies (for Debian/Ubuntu) +# Install dependencies (Debian/Ubuntu) install-deps: @echo "Installing dependencies..." sudo apt-get update sudo apt-get install -y gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools @echo "Dependencies installed." - # Help target +# ============================================================================== +# Help +# ============================================================================== + help: @echo "UEFI Operating System Makefile" @echo "" diff --git a/README.md b/README.md index 1434193..aa23ea1 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,30 @@ A minimal bootable UEFI operating system written in C that demonstrates UEFI boo ## Features -- **UEFI Boot**: Boots directly on UEFI firmware via a PE32+ loader -- **ELF64 Kernel Loader**: Reads and maps a standalone kernel from the EFI partition -- **Console I/O**: Interactive keyboard input and screen output -- **Interrupt Handling**: IDT setup with CPU exception handlers (vectors 0–31) and hardware IRQ dispatch -- **Memory Management**: Bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing -- **Cooperative Multitasking**: Process control blocks, assembly context switching, and a round-robin task scheduler -- **Interactive Shell**: Command registry with `help`, `man`, built-in commands, and test utilities +- **UEFI Boot** — boots directly on UEFI firmware via a PE32+ loader +- **ELF64 Kernel Loader** — reads and maps a standalone kernel from the EFI partition +- **Console I/O** — interactive keyboard input and screen output +- **Interrupt Handling** — IDT setup with CPU exception handlers (vectors 0–31) and hardware IRQ dispatch +- **Memory Management** — bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing +- **Cooperative Multitasking** — process control blocks, assembly context switching, and a round-robin task scheduler +- **Interactive Shell** — command registry with `help`, `man`, built-in commands, and test utilities -## Requirements +--- -- GCC compiler -- GNU-EFI library +## Getting Started + +### Requirements + +- GCC cross-compiler (or native x86-64 GCC) +- GNU-EFI library and headers - QEMU with OVMF firmware -- Make +- GNU Make +- `xorriso` and `mtools` (for ISO builds only) -## Installation +### Installation -### On Debian/Ubuntu: +
+Debian / Ubuntu ```bash make install-deps @@ -33,57 +39,78 @@ Or manually: sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools ``` -### On Arch Linux: +
+ +
+Arch Linux ```bash sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools ``` -### On Fedora/RHEL: +
+ +
+Fedora / RHEL ```bash sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools ``` -## Building +
-Build the UEFI loader and kernel: +### Building ```bash -make +make # build both UEFI loader and kernel ``` -This creates `build/EFI/BOOT/BOOTX64.EFI` (UEFI loader) and `build/kernel.elf` (kernel binary). +This produces two binaries: -## Running +| Output | Description | +|--------|-------------| +| `build/EFI/BOOT/BOOTX64.EFI` | UEFI loader (PE32+) | +| `build/kernel.elf` | Kernel binary (ELF64) | -Test with QEMU (no graphics mode): +### Running ```bash -make run -``` - -To build and run from an ISO image: - -```bash -make runiso +make run # build and run with QEMU (FAT directory, nographic) +make runiso # build, create ISO, and run with QEMU ``` The loader expects `kernel.elf` at the root of the EFI partition (next to the `EFI/` directory). -### QEMU Controls: -- **Exit QEMU**: Press `Ctrl+A`, then `X` -- **Shutdown OS**: Type `shutdown` in the OS +| Key / Command | Action | +|---------------|--------| +| `Ctrl+A`, then `X` | Exit QEMU | +| `shutdown` (in shell) | Power off the OS | -## Commands +### Make Targets -Once the OS boots, you can use these commands: +| Target | Description | +|--------|-------------| +| `all` | Build the UEFI loader and kernel (default) | +| `run` | Build and run with QEMU | +| `iso` | Create a bootable ISO image | +| `runiso` | Build, create ISO, and run in QEMU | +| `clean` | Remove all build artifacts | +| `install-deps` | Install required packages (Debian/Ubuntu) | +| `help` | Print target summary | + +--- + +## Usage + +### Shell Commands + +Once the OS boots, an interactive prompt (`->`) is displayed. The following commands are available: | Command | Description | |---------|-------------| -| `help` | Display all available commands | -| `man ` | Display detailed help for a command | -| `shutdown` | Shutdown the system | +| `help` | List all available commands | +| `man ` | Show detailed help for a command | +| `shutdown` | Shut down the system | | `clear` | Clear the screen | | `about` | Display system information | | `mem` | Display memory statistics (PMM, heap, paging) | @@ -108,10 +135,10 @@ Once the OS boots, you can use these commands: ### Testing Memory Management -The `memtest` command runs through four test phases: +The `memtest` command runs four phases: -1. **Heap allocation** — allocates 8 blocks of increasing size (16 to 4096 bytes) via `kmalloc()` -2. **Heap free** — frees all allocated blocks via `kfree()` and verifies coalescing +1. **Heap allocation** — allocates 8 blocks of increasing size (16–4096 bytes) via `kmalloc()` +2. **Heap free** — frees all blocks via `kfree()` and verifies coalescing 3. **PMM single page** — allocates and frees a single 4 KB page 4. **PMM multi-page** — allocates and frees 4 contiguous pages @@ -126,7 +153,7 @@ The `tasktest` command: 3. The shell yields to let workers run in round-robin order 4. Displays the task list after completion -You can also manually test with `spawn` and `ps`: +You can also test manually with `spawn` and `ps`: ``` -> spawn myTask @@ -142,140 +169,154 @@ Created task 'myTask' (PID 1) Background tasks run whenever the shell yields (which happens automatically while waiting for keyboard input). -## Project Structure - -``` -. -├── main.c # UEFI loader (reads and loads kernel ELF) -├── kernel.c # Kernel entry point and interactive shell loop -├── kernel.ld # Kernel linker script (base address 0x100000) -├── boot_info.h # Shared boot interface (BootInfo struct) -├── commands.c # Command registry and handler implementations -├── commands.h # Command interface definitions -├── string_utils.c # String utility functions (trim, compare, etc.) -├── string_utils.h # String utility declarations -├── idt.c # IDT setup, exception handlers, PIC EOI -├── idt.h # IDT types (ISRFrame) and init declaration -├── isr.S # ISR stub table (vectors 0–255) with common handler -├── memory.c # PMM (bitmap), paging (4-level), heap (first-fit) -├── memory.h # Memory subsystem declarations and constants -├── task.c # Task manager: PCB pool, scheduler, yield/exit -├── task.h # Task types (Task, TaskState) and API -├── context_switch.S # Assembly cooperative context switch (x86-64) -├── Makefile # Build configuration -└── README.md # This file -``` +--- ## Architecture ### Boot Flow -1. **UEFI Entry** — `efi_main()` in `main.c` is called by firmware -2. **GNU-EFI Init** — library initialized with the system table -3. **Kernel Load** — `kernel.elf` is read from the EFI partition, ELF64 PT_LOAD segments are mapped into memory -4. **BootInfo Setup** — function pointers for console I/O, shutdown, and page allocation are packaged into a `BootInfo` struct -5. **Kernel Entry** — `kmain()` is called with the `BootInfo` pointer - -### Kernel Initialization - -1. **IDT** — CPU exception vectors (0–31) are installed; firmware IRQ handlers are preserved -2. **Memory** — PMM allocates a 16 MB page pool via UEFI, bitmap tracks free/used pages; paging reads CR3; heap carves 256 KB from the PMM -3. **Tasks** — scheduler initializes; the running kernel becomes task 0 -4. **Shell** — interactive loop with non-blocking input and cooperative yielding +``` +UEFI Firmware + └─ efi_main() [main.c] PE32+ UEFI application + ├─ InitializeLib() GNU-EFI setup + ├─ read_file_to_buffer() read kernel.elf from ESP + ├─ load_elf_kernel() parse ELF64, map PT_LOAD segments + ├─ populate BootInfo wrap UEFI services as fn pointers + └─ kmain(&Boot) [kernel.c] jump to kernel + ├─ idt_init() install exception handlers + ├─ memory_init() PMM → paging → heap + ├─ task_init() scheduler + task 0 + └─ shell loop read-eval-print with cooperative yield +``` ### Memory Management -| Component | Description | -|-----------|-------------| -| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages); supports single and contiguous multi-page allocation | -| **Paging** | Walks/creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT); supports map, unmap, and virtual-to-physical translation | -| **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing; 16-byte alignment; magic number corruption detection | +| Layer | Description | +|-------|-------------| +| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages). Supports single and contiguous multi-page allocation. | +| **Paging** | Walks and creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT). Supports map, unmap, and virtual-to-physical translation for 4 KB, 2 MB, and 1 GB page sizes. | +| **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing. 16-byte alignment. Magic-number corruption detection. | ### Task System -| Component | Description | -|-----------|-------------| -| **PCB** | `Task` struct with PID, state, name, saved RSP, stack, entry function, and scheduling metadata | +| Aspect | Detail | +|--------|--------| +| **PCB** | `Task` struct: PID, state, name, saved RSP, stack base, entry function, scheduling metadata | | **States** | `FREE` → `READY` → `RUNNING` → `TERMINATED` → `FREE` | -| **Scheduler** | Round-robin selection among `READY` tasks via `task_yield()` | -| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12`–`r15`) and `RFLAGS`, swaps RSP | -| **Stack** | Each task gets 32 KB (8 pages) allocated from PMM; freed on `task_exit()` | +| **Scheduler** | Round-robin among `READY` tasks via `task_yield()` | +| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12`–`r15`) and `RFLAGS`, then swaps RSP | +| **Stack** | 32 KB (8 pages) per task, allocated from PMM, freed on `task_exit()` | | **Limits** | Up to 32 concurrent tasks; cooperative (voluntary yield) only | ### Command System -- Commands are registered in a static array with metadata (name, description, usage, handler function) -- `execute_command()` parses input, splits command and arguments, and dispatches to the matching handler -- `man` provides detailed help by looking up command metadata -- New commands are added by writing a handler and appending to the `commands[]` array +Commands are registered in a static array in `commands.c`: -## Building for Real Hardware +```c +static Command commands[] = { + { L"name", L"description", L"usage", handler_fn }, + ... + { NULL, NULL, NULL, NULL } /* sentinel */ +}; +``` -To create a bootable USB drive: +`execute_command()` splits user input into command + arguments and dispatches to the matching handler. `man` provides detailed help by looking up command metadata. + +--- + +## Project Structure + +``` +. +├── main.c # UEFI loader — reads kernel.elf, sets up BootInfo +├── kernel.c # Kernel entry point (kmain) and interactive shell +├── kernel.ld # Linker script — kernel loaded at 0x100000 +├── boot_info.h # BootInfo struct shared between loader and kernel +│ +├── commands.c # Command registry and all handler implementations +├── commands.h # Command type and dispatch API +├── string_utils.c # CHAR16 string helpers (trim, compare) +├── string_utils.h # String utility declarations +│ +├── idt.c # IDT setup, exception handlers, PIC EOI +├── idt.h # ISRFrame layout and idt_init() declaration +├── isr.S # ISR stub table (vectors 0–255) and common handler +│ +├── memory.c # PMM (bitmap), paging (4-level), heap (first-fit) +├── memory.h # Memory subsystem constants, types, and API +│ +├── task.c # PCB pool, round-robin scheduler, yield/exit +├── task.h # Task types (Task, TaskState) and task API +├── context_switch.S # Cooperative context switch (x86-64 assembly) +│ +├── Makefile # Build system +├── README.md # This file +└── build/ # Build output (generated) + ├── EFI/BOOT/BOOTX64.EFI + ├── kernel.elf + └── image/ # ISO staging area +``` + +--- + +## Development + +### Adding a New Command + +1. Open `commands.c` +2. Add a forward declaration: + ```c + static void cmd_foo(BootInfo *Boot, CHAR16 *Args); + ``` +3. Append an entry to the `commands[]` array (before the `NULL` sentinel): + ```c + { L"foo", L"Short description", L"Usage: foo [args]\n\r Details.", cmd_foo }, + ``` +4. Implement `cmd_foo` +5. Rebuild with `make` + +### Building for Real Hardware 1. Build the project: `make` -2. Format a USB drive with GPT and create an EFI partition (FAT32) -3. Mount the EFI partition -4. Copy `build/EFI/BOOT/BOOTX64.EFI` to `/EFI/BOOT/` on the USB drive -5. Copy `build/kernel.elf` to the root of the EFI partition -6. Boot from the USB drive in UEFI mode +2. Format a USB drive with GPT and a FAT32 EFI System Partition +3. Mount the ESP +4. Copy `build/EFI/BOOT/BOOTX64.EFI` → `/EFI/BOOT/BOOTX64.EFI` +5. Copy `build/kernel.elf` → `/kernel.elf` +6. Boot from the USB in UEFI mode -**Warning**: Always backup your data before creating bootable media! +> **Warning:** Always back up your data before creating bootable media. -## Cleaning - -Remove build artifacts: - -```bash -make clean -``` +--- ## Troubleshooting -### GNU-EFI headers not found +| Problem | Solution | +|---------|----------| +| **GNU-EFI headers not found** | Install GNU-EFI. Headers are typically at `/usr/include/efi`. Update `EFI_INC` in the Makefile if installed elsewhere. | +| **OVMF firmware not found** | Check `/usr/share/OVMF/`, `/usr/share/qemu/`, or `/usr/share/edk2-ovmf/`. The Makefile auto-detects common paths. | +| **QEMU crashes or won't start** | Ensure `qemu-system-x86_64` and OVMF are installed. Verify with `qemu-system-x86_64 --version`. | -Make sure GNU-EFI is installed. The headers are typically in `/usr/include/efi`. - -If installed in a different location, update the `EFI_INC` variable in the Makefile. - -### OVMF firmware not found - -OVMF files might be in different locations depending on your distribution: -- `/usr/share/OVMF/` -- `/usr/share/qemu/` -- `/usr/share/edk2-ovmf/` - -Update the paths in the Makefile if needed. - -### QEMU crashes or fails to start - -Ensure you have QEMU with x86_64 support and OVMF firmware installed. +--- ## Technical Details -- **Architecture**: x86_64 -- **Firmware Interface**: UEFI 2.x -- **Development Library**: GNU-EFI -- **Loader Format**: PE32+ (Portable Executable for EFI) -- **Kernel Format**: ELF64 -- **Boot Protocol**: UEFI Boot Services -- **Memory Model**: Identity-mapped (UEFI), 4-level paging (kernel) -- **Scheduling**: Cooperative round-robin multitasking - -## License - -This is a minimal educational example. Feel free to use and modify as needed. - -## Adding New Commands - -1. Open `commands.c` -2. Add a forward declaration: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args);` -3. Add an entry to the `commands[]` array (before the sentinel) -4. Implement the handler function -5. Rebuild with `make` +| Property | Value | +|----------|-------| +| Architecture | x86-64 (long mode) | +| Firmware Interface | UEFI 2.x | +| Development Library | GNU-EFI | +| Loader Format | PE32+ (EFI application) | +| Kernel Format | ELF64 (static, loaded at 0x100000) | +| Boot Protocol | UEFI Boot Services | +| Memory Model | Identity-mapped (UEFI), 4-level paging (kernel) | +| Scheduling | Cooperative round-robin multitasking | ## Resources - [UEFI Specification](https://uefi.org/specifications) - [GNU-EFI Documentation](https://sourceforge.net/projects/gnu-efi/) - [OSDev Wiki](https://wiki.osdev.org/) + +## License + +This is a minimal educational example. Feel free to use and modify as needed. diff --git a/boot_info.h b/boot_info.h index 219bccb..0f6e64f 100644 --- a/boot_info.h +++ b/boot_info.h @@ -1,22 +1,45 @@ +/* + * boot_info.h – Shared interface between the UEFI loader and the kernel. + * + * The BootInfo struct is populated by the loader (main.c) and passed to + * the kernel entry point (kmain). It provides function pointers that + * abstract UEFI Boot/Runtime Services so the kernel can use console I/O, + * memory allocation, and system control without linking against GNU-EFI. + */ + #ifndef BOOT_INFO_H #define BOOT_INFO_H #include +/* Printf-style print function (backed by UEFI ConOut). */ typedef UINTN (*KernelPrintFn)(const CHAR16 *Format, ...); +/* + * BootInfo – everything the kernel needs from the UEFI environment. + * + * All function pointers are optional: the kernel must NULL-check before + * calling. If a service is unavailable the corresponding field is NULL. + */ typedef struct { - EFI_SYSTEM_TABLE *SystemTable; - KernelPrintFn print; - EFI_STATUS (*clear_screen)(void); - EFI_STATUS (*set_attribute)(UINTN Attribute); - EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key); - EFI_STATUS (*try_read_key)(EFI_INPUT_KEY *Key); /* non-blocking */ - void (*shutdown)(void); + EFI_SYSTEM_TABLE *SystemTable; /* UEFI System Table */ + + /* Console I/O */ + KernelPrintFn print; /* formatted text output */ + EFI_STATUS (*clear_screen)(void); /* clear the console */ + EFI_STATUS (*set_attribute)(UINTN Attribute); /* set text colour/attr */ + EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key); /* blocking key read */ + EFI_STATUS (*try_read_key)(EFI_INPUT_KEY *Key); /* non-blocking key read */ + + /* System control */ + void (*shutdown)(void); /* power-off the machine */ + + /* Physical memory */ EFI_STATUS (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr); EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages); } BootInfo; +/* Kernel entry point signature (called by the UEFI loader). */ typedef void (*KernelEntryFn)(BootInfo *Boot); -#endif +#endif /* BOOT_INFO_H */ diff --git a/commands.c b/commands.c index 8ce2ebd..e3f762f 100644 --- a/commands.c +++ b/commands.c @@ -1,9 +1,23 @@ +/* + * commands.c – Shell command registry and handler implementations. + * + * Each command is a { name, description, usage, handler } entry in the + * static `commands[]` table. execute_command() splits user input into + * command + arguments and dispatches to the matching handler. + * + * To add a new command: + * 1. Write a static handler cmd_foo(BootInfo *Boot, CHAR16 *Args) + * 2. Add a forward declaration above the table + * 3. Append an entry to commands[] (before the sentinel) + */ + #include #include "commands.h" #include "string_utils.h" #include "memory.h" #include "task.h" +/* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ do { \ if ((Boot) != NULL && (Boot)->print != NULL) { \ @@ -11,7 +25,10 @@ } \ } while (0) -// Forward declarations +/* ================================================================ + * Forward declarations + * ================================================================ */ + static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args); static void cmd_help(BootInfo *Boot, CHAR16 *Args); static void cmd_man(BootInfo *Boot, CHAR16 *Args); @@ -23,7 +40,10 @@ static void cmd_spawn(BootInfo *Boot, CHAR16 *Args); static void cmd_memtest(BootInfo *Boot, CHAR16 *Args); static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args); -// Command registry +/* ================================================================ + * Command registry + * ================================================================ */ + static Command commands[] = { { L"shutdown", @@ -85,9 +105,17 @@ static Command commands[] = { L"Usage: tasktest\n\r Spawns several concurrent tasks that run cooperatively\n\r and report their progress, demonstrating context switching.", cmd_tasktest }, - {NULL, NULL, NULL, NULL} // Sentinel + {NULL, NULL, NULL, NULL} /* sentinel */ }; +/* ================================================================ + * System control + * ================================================================ */ + +/* + * Try Boot->shutdown first, then fall back to RuntimeServices, then + * print an error if neither is available. + */ static void request_shutdown(BootInfo *Boot) { if (Boot == NULL) { @@ -110,16 +138,20 @@ static void request_shutdown(BootInfo *Boot) SAFE_PRINT(Boot, L"Shutdown service unavailable.\n\r"); } +/* ================================================================ + * Built-in command handlers + * ================================================================ */ + static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args) { - (void)Args; // Unused + (void)Args; SAFE_PRINT(Boot, L"Shutting down...\n\r"); request_shutdown(Boot); } static void cmd_help(BootInfo *Boot, CHAR16 *Args) { - (void)Args; // Unused + (void)Args; show_help(Boot); } @@ -154,7 +186,7 @@ static void cmd_man(BootInfo *Boot, CHAR16 *Args) static void cmd_clear(BootInfo *Boot, CHAR16 *Args) { EFI_STATUS Status; - (void)Args; // Unused + (void)Args; if (Boot != NULL && Boot->clear_screen != NULL) { Status = Boot->clear_screen(); @@ -168,8 +200,8 @@ static void cmd_clear(BootInfo *Boot, CHAR16 *Args) static void cmd_about(BootInfo *Boot, CHAR16 *Args) { - (void)Args; // Unused - + (void)Args; + SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"================================================\n\r"); SAFE_PRINT(Boot, L" Simple UEFI Operating System\n\r"); @@ -378,6 +410,11 @@ static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args) SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r"); } +/* ================================================================ + * Public API + * ================================================================ */ + +/* Print a formatted table of all registered commands. */ void show_help(BootInfo *Boot) { UINTN i = 0; @@ -389,7 +426,7 @@ void show_help(BootInfo *Boot) SAFE_PRINT(Boot, L"Available commands:\n\r"); SAFE_PRINT(Boot, L"\n\r"); - // Find longest command name for formatting + /* Find the longest command name for column alignment */ for (i = 0; commands[i].name != NULL; i++) { name_len = 0; while (commands[i].name[name_len] != L'\0') { @@ -403,7 +440,7 @@ void show_help(BootInfo *Boot) for (i = 0; commands[i].name != NULL; i++) { SAFE_PRINT(Boot, L" %s", commands[i].name); - // Add padding + /* Pad to align descriptions */ name_len = 0; while (commands[i].name[name_len] != L'\0') { name_len++; @@ -420,6 +457,10 @@ void show_help(BootInfo *Boot) SAFE_PRINT(Boot, L"\n\r"); } +/* + * Parse a line of user input into command + arguments and dispatch + * to the matching handler. Unknown commands print an error. + */ void execute_command(BootInfo *Boot, CHAR16 *Input) { CHAR16 *cmd_start = NULL; @@ -436,27 +477,27 @@ void execute_command(BootInfo *Boot, CHAR16 *Input) return; } - // Split command and arguments + /* Split input into command and argument strings */ cmd_start = Input; args_start = Input; - - // Find first space + + /* Advance past the command keyword */ while (*args_start != L'\0' && !is_space16(*args_start)) { args_start++; } - // If we found a space, null-terminate command and advance to args + /* NUL-terminate the command and skip leading whitespace in args */ if (*args_start != L'\0') { *args_start = L'\0'; args_start++; - // Skip leading spaces in args + /* skip leading whitespace in args */ while (*args_start != L'\0' && is_space16(*args_start)) { args_start++; } } - // Search for command + /* Look up and dispatch the command */ for (i = 0; commands[i].name != NULL; i++) { if (ascii_streq_ci(cmd_start, commands[i].name)) { commands[i].handler(Boot, args_start); @@ -464,7 +505,7 @@ void execute_command(BootInfo *Boot, CHAR16 *Input) } } - // Command not found + /* Command not found */ SAFE_PRINT(Boot, L"Unknown command: %s\n\r", cmd_start); SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r"); } diff --git a/commands.h b/commands.h index 5e35f97..77135ac 100644 --- a/commands.h +++ b/commands.h @@ -1,18 +1,35 @@ +/* + * commands.h – Interactive shell command interface. + * + * Defines the Command structure used by the command registry and the + * public API for command execution and help display. + */ + #ifndef COMMANDS_H #define COMMANDS_H #include "boot_info.h" +/* Handler function signature: receives BootInfo and any argument text. */ typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args); +/* + * Command – describes a single shell command. + * + * An array of these (terminated by a sentinel with name == NULL) forms + * the command registry in commands.c. + */ typedef struct { - const CHAR16 *name; - const CHAR16 *description; - const CHAR16 *usage; - CommandHandlerFn handler; + const CHAR16 *name; /* command keyword (e.g. L"help") */ + const CHAR16 *description; /* one-line summary for `help` */ + const CHAR16 *usage; /* detailed text shown by `man` */ + CommandHandlerFn handler; /* function that executes the cmd */ } Command; +/* Parse and dispatch a line of user input. */ void execute_command(BootInfo *Boot, CHAR16 *Input); + +/* Print a formatted list of all registered commands. */ void show_help(BootInfo *Boot); -#endif +#endif /* COMMANDS_H */ diff --git a/context_switch.S b/context_switch.S index dfafcd7..93c6539 100644 --- a/context_switch.S +++ b/context_switch.S @@ -1,19 +1,27 @@ /* - * context_switch.S – cooperative context switch for x86_64 + * context_switch.S – Cooperative context switch for x86-64. * * void context_switch(UINT64 *old_rsp, UINT64 new_rsp); * * %rdi = pointer to save current RSP (old task's PCB field) * %rsi = RSP value to restore (new task's saved RSP) * - * Saves callee-saved registers and RFLAGS on the current stack, - * stores RSP, loads the new stack, restores registers, and returns - * into the new task's execution context. + * Saves callee-saved registers (rbp, rbx, r12-r15) and RFLAGS on + * the current stack, stores RSP into *old_rsp, loads new_rsp into + * RSP, restores registers, and returns into the new task's context. + * + * This is a standard cooperative switch: only callee-saved state + * needs preserving because the C calling convention guarantees that + * caller-saved registers are already handled by the compiler. */ .text .global context_switch +/* ---------------------------------------------------------------- + * Entry: save old context, load new context, return + * ---------------------------------------------------------------- */ + context_switch: /* Save callee-saved registers and flags on the current stack */ pushq %rbp @@ -24,10 +32,10 @@ context_switch: pushq %r15 pushfq - /* Save current stack pointer into *old_rsp */ + /* Store current RSP into *old_rsp */ movq %rsp, (%rdi) - /* Load new task's stack pointer */ + /* Switch to the new task's stack */ movq %rsi, %rsp /* Restore callee-saved registers and flags from the new stack */ diff --git a/idt.c b/idt.c index 89c1168..75a7b03 100644 --- a/idt.c +++ b/idt.c @@ -1,28 +1,55 @@ +/* + * idt.c – Interrupt Descriptor Table setup and exception handling. + * + * On initialisation the kernel copies the firmware's IDT (preserving + * its hardware IRQ handlers for vectors 32-47), then overrides CPU + * exception vectors 0-31 with our own stubs from isr.S. + * + * Hardware IRQs receive an EOI and return. CPU exceptions print + * diagnostic information (including CR2 for page faults) and halt. + */ + #include "idt.h" -#define IDT_SIZE 256 -#define IDT_TYPE_INTERRUPT 0x8E +/* ================================================================ + * Constants and internal types + * ================================================================ */ +#define IDT_SIZE 256 +#define IDT_TYPE_INTERRUPT 0x8E /* P=1, DPL=0, 64-bit interrupt gate */ + +/* Single 16-byte IDT entry (x86-64 long mode). */ typedef struct { - UINT16 offset_low; - UINT16 selector; - UINT8 ist; - UINT8 type_attr; - UINT16 offset_mid; - UINT32 offset_high; - UINT32 zero; + UINT16 offset_low; /* bits 0-15 of handler address */ + UINT16 selector; /* code segment selector */ + UINT8 ist; /* interrupt stack table index */ + UINT8 type_attr; /* type and attributes */ + UINT16 offset_mid; /* bits 16-31 of handler address */ + UINT32 offset_high; /* bits 32-63 of handler address */ + UINT32 zero; /* reserved, must be zero */ } __attribute__((packed)) IdtEntry; +/* IDTR register layout for the LIDT instruction. */ typedef struct { UINT16 limit; UINT64 base; } __attribute__((packed)) IdtPtr; -static IdtEntry idt[IDT_SIZE]; +/* ================================================================ + * Module state + * ================================================================ */ + +static IdtEntry idt[IDT_SIZE]; static BootInfo *gBoot = NULL; +/* Defined in isr.S – one stub function per vector (0-255). */ extern void (*isr_stub_table[])(void); +/* ================================================================ + * IDT gate helpers + * ================================================================ */ + +/* Install a single IDT gate pointing at `handler`. */ static void idt_set_gate(UINTN index, void (*handler)(void)) { UINT64 addr = (UINT64)(UINTN)handler; @@ -39,11 +66,17 @@ static void idt_set_gate(UINTN index, void (*handler)(void)) idt[index].zero = 0; } +/* Load a new IDT register value. */ static void lidt(const IdtPtr *idtr) { __asm__ __volatile__("lidt (%0)" :: "r"(idtr)); } +/* ================================================================ + * Exception name lookup + * ================================================================ */ + +/* Return a human-readable name for CPU exception vectors 0-31. */ static const CHAR16 *exception_name(UINTN vector) { switch (vector) { @@ -83,11 +116,17 @@ static const CHAR16 *exception_name(UINTN vector) } } +/* ================================================================ + * PIC and low-level helpers + * ================================================================ */ + +/* Write a byte to an I/O port. */ static inline void outb(UINT16 port, UINT8 value) { __asm__ __volatile__("outb %0, %1" :: "a"(value), "Nd"(port)); } +/* Send End-Of-Interrupt to the 8259 PIC(s). */ static void pic_eoi(UINTN vector) { if (vector >= 40) { @@ -96,6 +135,7 @@ static void pic_eoi(UINTN vector) outb(0x20, 0x20); /* EOI to master PIC */ } +/* Disable interrupts and halt forever (unrecoverable fault). */ static void halt_forever(void) { for (;;) { @@ -103,6 +143,10 @@ static void halt_forever(void) } } +/* ================================================================ + * ISR dispatcher (called from isr_common in isr.S) + * ================================================================ */ + void isr_handler(ISRFrame *frame) { UINT64 cr2 = 0; @@ -132,6 +176,10 @@ void isr_handler(ISRFrame *frame) halt_forever(); } +/* ================================================================ + * IDT initialisation + * ================================================================ */ + void idt_init(BootInfo *Boot) { IdtPtr old_idtr; diff --git a/idt.h b/idt.h index 4c4fd06..c5bce38 100644 --- a/idt.h +++ b/idt.h @@ -1,10 +1,22 @@ +/* + * idt.h – Interrupt Descriptor Table interface. + * + * Declares the ISR stack frame layout (pushed by isr_common in isr.S) + * and the IDT initialisation function. + */ + #ifndef IDT_H #define IDT_H #include #include "boot_info.h" +/* + * ISRFrame – register state saved by isr_common before calling + * isr_handler(). The order must match the push sequence in isr.S. + */ typedef struct { + /* General-purpose registers (pushed by isr_common) */ UINT64 r15; UINT64 r14; UINT64 r13; @@ -20,13 +32,21 @@ typedef struct { UINT64 rcx; UINT64 rbx; UINT64 rax; + + /* Pushed by the ISR stub macros */ UINT64 vector; UINT64 error_code; + + /* Pushed by the CPU on interrupt entry */ UINT64 rip; UINT64 cs; UINT64 rflags; } ISRFrame; +/* + * Install our IDT: copies firmware entries for vectors 32+, + * overrides vectors 0-31 with kernel exception handlers. + */ void idt_init(BootInfo *Boot); -#endif +#endif /* IDT_H */ diff --git a/isr.S b/isr.S index 5b9b68d..ab19f69 100644 --- a/isr.S +++ b/isr.S @@ -1,8 +1,28 @@ +/* + * isr.S – Interrupt Service Routine stubs for x86-64. + * + * Provides 256 thin stub functions (isr0 – isr255) and a common body + * (isr_common) that saves all general-purpose registers, calls the C + * handler isr_handler(ISRFrame *), restores registers, and returns + * via IRETQ. + * + * Two macros generate the stubs: + * ISR_NOERR – pushes a dummy error code (0) for vectors that don't + * ISR_ERR – the CPU already pushed an error code + * + * A pointer table (isr_stub_table) is emitted at the end so that + * idt.c can look up the stub address for each vector by index. + */ + .text .global isr_handler .global isr_stub_table +/* ---------------------------------------------------------------- + * Stub macros + * ---------------------------------------------------------------- */ + .macro ISR_NOERR num .global isr\num isr\num: @@ -18,6 +38,10 @@ isr\num: jmp isr_common .endm +/* ---------------------------------------------------------------- + * Common ISR body – saves state, calls C handler, restores state + * ---------------------------------------------------------------- */ + isr_common: cld @@ -59,6 +83,15 @@ isr_common: addq $16, %rsp iretq +/* ---------------------------------------------------------------- + * Stub instantiation (vectors 0-255) + * + * Vectors 0-31: CPU exceptions (some push error codes) + * Vectors 32-47: legacy 8259 PIC hardware IRQs + * Vectors 48+: available for software / APIC use + * ---------------------------------------------------------------- */ + +/* CPU exceptions */ ISR_NOERR 0 ISR_NOERR 1 ISR_NOERR 2 @@ -316,6 +349,11 @@ ISR_NOERR 253 ISR_NOERR 254 ISR_NOERR 255 +/* ---------------------------------------------------------------- + * Stub pointer table – indexed by vector number (0-255) + * Used by idt_set_gate() in idt.c to populate the IDT. + * ---------------------------------------------------------------- */ + isr_stub_table: .quad isr0 .quad isr1 diff --git a/kernel.c b/kernel.c index dadcbc7..d8bf497 100644 --- a/kernel.c +++ b/kernel.c @@ -1,3 +1,15 @@ +/* + * kernel.c – Kernel entry point and interactive shell loop. + * + * kmain() is called by the UEFI loader (main.c) after the ELF kernel + * has been mapped into memory. It initialises subsystems (IDT, memory, + * tasks), prints a welcome banner, and enters an interactive read- + * eval-print loop that dispatches typed commands via commands.c. + * + * While waiting for keyboard input, the shell yields to the cooperative + * scheduler so that background tasks can make progress. + */ + #include #include "boot_info.h" @@ -6,6 +18,7 @@ #include "memory.h" #include "task.h" +/* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ do { \ if ((Boot) != NULL && (Boot)->print != NULL) { \ @@ -13,6 +26,10 @@ } \ } while (0) +/* ================================================================ + * Kernel entry point + * ================================================================ */ + void kmain(BootInfo *Boot) { EFI_INPUT_KEY Key; @@ -37,11 +54,12 @@ void kmain(BootInfo *Boot) } } + /* ---- Subsystem initialisation ---- */ idt_init(Boot); memory_init(Boot); task_init(Boot); - SAFE_PRINT(Boot, L"================================================\n\r"); + /* ---- Welcome banner ---- */ SAFE_PRINT(Boot, L" Welcome to Simple UEFI Operating System!\n\r"); SAFE_PRINT(Boot, L"================================================\n\r"); SAFE_PRINT(Boot, L"\n\r"); @@ -69,7 +87,7 @@ void kmain(BootInfo *Boot) SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"Type 'help' for a list of commands.\n\r\n\r"); - // Simple line buffer + /* ---- Interactive shell loop ---- */ CHAR16 line[128]; UINTN len = 0; @@ -100,21 +118,22 @@ void kmain(BootInfo *Boot) read_errors = 0; if (Key.UnicodeChar == L'\r' || Key.UnicodeChar == L'\n') { - // Enter pressed: terminate string and handle + /* Enter pressed: execute the buffered command */ line[len] = L'\0'; SAFE_PRINT(Boot, L"\n\r"); execute_command(Boot, line); - // Reset for next command + /* Reset for next command */ len = 0; SAFE_PRINT(Boot, L"-> "); } else if (Key.ScanCode == 0x08 || Key.UnicodeChar == L'\b' || Key.UnicodeChar == 0x7F) { - // Backspace (ScanCode 0x08 is backspace in UEFI) + /* Backspace */ if (len > 0) { len--; SAFE_PRINT(Boot, L"\b \b"); } } else if (Key.UnicodeChar >= 32 && Key.UnicodeChar < 127) { + /* Printable ASCII */ if (len < (sizeof(line) / sizeof(line[0]) - 1)) { line[len++] = Key.UnicodeChar; SAFE_PRINT(Boot, L"%c", Key.UnicodeChar); diff --git a/kernel.ld b/kernel.ld index 66829c4..506a4b1 100644 --- a/kernel.ld +++ b/kernel.ld @@ -1,25 +1,33 @@ +/* + * kernel.ld – Linker script for the ELF64 kernel. + * + * The kernel is loaded at physical address 0x100000 (1 MB) by the + * UEFI loader. Each section is page-aligned (4 KB) so that page- + * level permissions can be applied later if desired. + */ + ENTRY(kmain) SECTIONS { - . = 0x100000; + . = 0x100000; /* load address: 1 MB */ - .text : ALIGN(0x1000) + .text : ALIGN(0x1000) /* executable code */ { *(.text*) } - .rodata : ALIGN(0x1000) + .rodata : ALIGN(0x1000) /* read-only data / string literals */ { *(.rodata*) } - .data : ALIGN(0x1000) + .data : ALIGN(0x1000) /* initialised read-write data */ { *(.data*) } - .bss : ALIGN(0x1000) + .bss : ALIGN(0x1000) /* zero-initialised data */ { *(COMMON) *(.bss*) diff --git a/main.c b/main.c index e67ef89..e40cf16 100644 --- a/main.c +++ b/main.c @@ -1,45 +1,70 @@ +/* + * main.c – UEFI boot loader. + * + * This is the first code that runs. efi_main() is called by UEFI + * firmware via the PE32+ entry point. It: + * 1. Reads kernel.elf from the EFI System Partition + * 2. Parses the ELF64 headers and maps PT_LOAD segments into memory + * 3. Populates a BootInfo struct with UEFI service wrappers + * 4. Jumps to the kernel entry point (kmain) + */ + #include #include #include "boot_info.h" -#define ELF_MAGIC 0x464c457f -#define PT_LOAD 1 +/* ================================================================ + * ELF64 constants and types + * ================================================================ */ +#define ELF_MAGIC 0x464c457f /* "\x7fELF" as a little-endian UINT32 */ +#define PT_LOAD 1 /* loadable program segment */ + +/* Minimal ELF64 file header (enough to locate program headers). */ typedef struct { - UINT32 e_magic; - UINT8 e_class; - UINT8 e_data; - UINT8 e_version; - UINT8 e_osabi; - UINT8 e_abiversion; - UINT8 e_pad[7]; - UINT16 e_type; - UINT16 e_machine; + UINT32 e_magic; /* must be ELF_MAGIC */ + UINT8 e_class; /* 2 = 64-bit */ + UINT8 e_data; /* 1 = little-endian */ + UINT8 e_version; + UINT8 e_osabi; + UINT8 e_abiversion; + UINT8 e_pad[7]; + UINT16 e_type; /* ET_EXEC = 2 */ + UINT16 e_machine; /* EM_X86_64 = 62 */ UINT32 e_version2; - UINT64 e_entry; - UINT64 e_phoff; + UINT64 e_entry; /* virtual address of entry point */ + UINT64 e_phoff; /* file offset to program header table */ UINT64 e_shoff; UINT32 e_flags; UINT16 e_ehsize; - UINT16 e_phentsize; - UINT16 e_phnum; + UINT16 e_phentsize; /* size of one program header entry */ + UINT16 e_phnum; /* number of program header entries */ UINT16 e_shentsize; UINT16 e_shnum; UINT16 e_shstrndx; } Elf64_Ehdr; +/* ELF64 program header (describes one segment). */ typedef struct { - UINT32 p_type; - UINT32 p_flags; - UINT64 p_offset; - UINT64 p_vaddr; - UINT64 p_paddr; - UINT64 p_filesz; - UINT64 p_memsz; - UINT64 p_align; + UINT32 p_type; /* segment type (PT_LOAD, etc.) */ + UINT32 p_flags; /* segment flags (R/W/X) */ + UINT64 p_offset; /* file offset of segment data */ + UINT64 p_vaddr; /* virtual address to map at */ + UINT64 p_paddr; /* physical address (usually == vaddr) */ + UINT64 p_filesz; /* bytes of segment data in file */ + UINT64 p_memsz; /* total bytes in memory (>= filesz) */ + UINT64 p_align; /* alignment requirement */ } Elf64_Phdr; +/* ================================================================ + * EFI file-system helpers + * ================================================================ */ + +/* + * Open the root directory of the volume from which this loader was + * loaded (i.e. the EFI System Partition). + */ static EFI_STATUS open_root_volume(EFI_HANDLE ImageHandle, EFI_FILE_PROTOCOL **Root) { EFI_STATUS Status; @@ -61,6 +86,10 @@ static EFI_STATUS open_root_volume(EFI_HANDLE ImageHandle, EFI_FILE_PROTOCOL **R return uefi_call_wrapper(FileSystem->OpenVolume, 2, FileSystem, Root); } +/* + * Read an entire file from the EFI System Partition into a pool buffer. + * Caller must free *Buffer with BS->FreePool() when done. + */ static EFI_STATUS read_file_to_buffer(EFI_HANDLE ImageHandle, CHAR16 *Path, VOID **Buffer, UINTN *Size) { @@ -127,6 +156,15 @@ static EFI_STATUS read_file_to_buffer(EFI_HANDLE ImageHandle, CHAR16 *Path, return EFI_SUCCESS; } +/* ================================================================ + * ELF64 kernel loader + * ================================================================ */ + +/* + * Parse an ELF64 image in memory and load all PT_LOAD segments to + * their specified virtual (== physical) addresses. On success, + * *EntryOut is set to the kernel entry point. + */ static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut) { Elf64_Ehdr *Ehdr = (Elf64_Ehdr *)Image; @@ -185,6 +223,10 @@ static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut) return EFI_SUCCESS; } +/* ================================================================ + * UEFI service wrappers (passed to the kernel via BootInfo) + * ================================================================ */ + static EFI_STATUS loader_clear_screen(void) { return uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); @@ -223,6 +265,10 @@ static EFI_STATUS loader_free_pages(EFI_PHYSICAL_ADDRESS addr, UINTN pages) return uefi_call_wrapper(BS->FreePages, 2, addr, pages); } +/* ================================================================ + * UEFI application entry point + * ================================================================ */ + EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) @@ -233,10 +279,10 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) UINT64 KernelEntry = 0; BootInfo Boot; KernelEntryFn EntryFn = NULL; - - // Initialize the GNU-EFI library + + /* Initialise the GNU-EFI library */ InitializeLib(ImageHandle, SystemTable); - + Print(L"Loading kernel...\n\r"); Status = read_file_to_buffer(ImageHandle, L"\\kernel.elf", &KernelImage, &KernelSize); @@ -251,16 +297,18 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) return Status; } - Boot.SystemTable = ST; - Boot.print = Print; + /* Populate the BootInfo struct with UEFI service wrappers */ + Boot.SystemTable = ST; + Boot.print = Print; Boot.clear_screen = loader_clear_screen; Boot.set_attribute = loader_set_attribute; - Boot.read_key = loader_read_key; + Boot.read_key = loader_read_key; Boot.try_read_key = loader_try_read_key; - Boot.shutdown = loader_shutdown; - Boot.alloc_pages = loader_alloc_pages; - Boot.free_pages = loader_free_pages; + Boot.shutdown = loader_shutdown; + Boot.alloc_pages = loader_alloc_pages; + Boot.free_pages = loader_free_pages; + /* Jump to the kernel – this should not return */ EntryFn = (KernelEntryFn)(UINTN)KernelEntry; EntryFn(&Boot); diff --git a/memory.c b/memory.c index 658df7e..26b54d7 100644 --- a/memory.c +++ b/memory.c @@ -1,5 +1,18 @@ +/* + * memory.c – Kernel memory management. + * + * Implements three layers: + * PMM – bitmap-based physical page-frame allocator backed by a + * 16 MB pool obtained from UEFI at boot. + * Paging – walks and creates 4-level x86-64 page tables; supports + * map, unmap, and virtual-to-physical translation. + * Heap – first-fit free-list allocator with block splitting and + * bidirectional coalescing; 16-byte aligned. + */ + #include "memory.h" +/* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ do { \ if ((Boot) != NULL && (Boot)->print != NULL) { \ @@ -17,21 +30,36 @@ static UINTN pmm_free_count = 0; static UINT8 pmm_bitmap[PMM_POOL_PAGES / 8]; static BOOLEAN pmm_ready = FALSE; +/* ================================================================ + * PMM – bitmap helpers + * ================================================================ */ + +/* Mark page `idx` as allocated. */ static void pmm_set_bit(UINTN idx) { pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8)); } +/* Mark page `idx` as free. */ static void pmm_clear_bit(UINTN idx) { pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8)); } +/* Return TRUE if page `idx` is currently allocated. */ static BOOLEAN pmm_test_bit(UINTN idx) { return (pmm_bitmap[idx / 8] & (1U << (idx % 8))) != 0; } +/* ---------------------------------------------------------------- + * PMM – public interface + * ---------------------------------------------------------------- */ + +/* + * Initialise the PMM: request PMM_POOL_PAGES from UEFI and set up + * the bitmap with all pages marked free. + */ void pmm_init(BootInfo *Boot) { EFI_STATUS Status; @@ -66,6 +94,7 @@ void pmm_init(BootInfo *Boot) pmm_pool_base); } +/* Allocate a single 4 KB page. Returns physical address or 0. */ UINT64 pmm_alloc_page(void) { UINTN i; @@ -85,6 +114,7 @@ UINT64 pmm_alloc_page(void) return 0; } +/* Free a single page previously returned by pmm_alloc_page(). */ void pmm_free_page(UINT64 phys_addr) { UINTN idx; @@ -100,6 +130,7 @@ void pmm_free_page(UINT64 phys_addr) pmm_free_count++; } +/* Allocate `count` physically contiguous pages (first-fit). */ UINT64 pmm_alloc_pages(UINTN count) { UINTN i, j; @@ -131,6 +162,7 @@ UINT64 pmm_alloc_pages(UINTN count) return 0; } +/* Free `count` contiguous pages starting at phys_addr. */ void pmm_free_pages(UINT64 phys_addr, UINTN count) { UINTN i; @@ -146,6 +178,11 @@ UINTN pmm_get_total_pages(void) { return pmm_total_pages; } * Paging – manipulate the live 4-level x86-64 page tables * ================================================================ */ +/* ================================================================ + * Paging – low-level helpers + * ================================================================ */ + +/* Read the CR3 register (physical address of PML4). */ static UINT64 read_cr3(void) { UINT64 cr3; @@ -153,11 +190,13 @@ static UINT64 read_cr3(void) return cr3; } +/* Invalidate the TLB entry for virtual address `addr`. */ static void invlpg(UINT64 addr) { __asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory"); } +/* Return a pointer to the current PML4 table. */ static UINT64 *get_pml4(void) { return (UINT64 *)(UINTN)(read_cr3() & PTE_ADDR_MASK); @@ -197,12 +236,22 @@ static UINT64 *paging_walk_level(UINT64 *table, UINTN index, BOOLEAN create) return next; } +/* ---------------------------------------------------------------- + * Paging – public interface + * ---------------------------------------------------------------- */ + +/* Log the current CR3 value (identity-mapped by UEFI). */ void paging_init(BootInfo *Boot) { SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (UEFI identity-mapped)\n\r", read_cr3()); } +/* + * Map a single 4 KB page: virt → phys with the given flags. + * Returns TRUE on success, FALSE if a huge page is in the way or + * page-table allocation failed. + */ BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags) { UINT64 *pml4, *pdpt, *pd, *pt; @@ -235,6 +284,7 @@ BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags) return TRUE; } +/* Remove the mapping for a single 4 KB page and flush the TLB. */ void paging_unmap_page(UINT64 virt) { UINT64 *pml4, *pdpt, *pd, *pt; @@ -262,6 +312,10 @@ void paging_unmap_page(UINT64 virt) invlpg(virt); } +/* + * Translate a virtual address to its physical counterpart. + * Handles 4 KB, 2 MB, and 1 GB page sizes. Returns 0 if unmapped. + */ UINT64 paging_get_phys(UINT64 virt) { UINT64 *pml4, *pdpt, *pd, *pt; @@ -302,11 +356,16 @@ UINT64 paging_get_phys(UINT64 virt) static HeapBlock *heap_start = NULL; static BOOLEAN heap_ready = FALSE; +/* Round `val` up to the next multiple of `align`. */ static UINTN align_up(UINTN val, UINTN align) { return (val + align - 1) & ~(align - 1); } +/* + * Initialise the heap: allocate HEAP_INITIAL_PAGES from the PMM + * and set up a single free block spanning the entire region. + */ void heap_init(BootInfo *Boot) { UINT64 phys; @@ -333,6 +392,11 @@ void heap_init(BootInfo *Boot) heap_size / 1024, phys); } +/* + * Allocate `size` bytes from the heap (first-fit). + * The returned pointer is aligned to HEAP_ALIGN. Returns NULL on + * failure or heap corruption. + */ void *kmalloc(UINTN size) { HeapBlock *block, *split; @@ -377,6 +441,10 @@ void *kmalloc(UINTN size) return NULL; /* out of heap memory */ } +/* + * Free a previously kmalloc'd pointer. Coalesces adjacent free + * blocks to reduce fragmentation. + */ void kfree(void *ptr) { HeapBlock *block; @@ -416,6 +484,7 @@ void kfree(void *ptr) } } +/* Gather aggregate heap statistics. */ void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, UINTN *num_blocks) { @@ -441,6 +510,7 @@ void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, * Top-level helpers * ================================================================ */ +/* Initialise all memory subsystems in order. */ void memory_init(BootInfo *Boot) { SAFE_PRINT(Boot, L"Initializing memory management...\n\r"); @@ -450,6 +520,7 @@ void memory_init(BootInfo *Boot) SAFE_PRINT(Boot, L"Memory management ready.\n\r\n\r"); } +/* Print a summary of PMM, heap, and paging state to the console. */ void memory_print_stats(BootInfo *Boot) { UINTN h_total, h_used, h_free, h_blocks; diff --git a/memory.h b/memory.h index 1453014..534563f 100644 --- a/memory.h +++ b/memory.h @@ -1,41 +1,51 @@ +/* + * memory.h – Memory management subsystem declarations. + * + * Three layers: + * PMM – bitmap-based physical page-frame allocator (16 MB pool) + * Paging – walks / creates 4-level x86-64 page tables + * Heap – first-fit free-list allocator with block coalescing + */ + #ifndef MEMORY_H #define MEMORY_H #include #include "boot_info.h" -/* Page size constants */ +/* ================================================================ + * Page-level constants + * ================================================================ */ + #define PAGE_SIZE 4096 #define PAGE_SHIFT 12 #define PAGE_MASK (~(UINT64)(PAGE_SIZE - 1)) -/* Page table entry flags */ -#define PTE_PRESENT (1ULL << 0) -#define PTE_WRITABLE (1ULL << 1) -#define PTE_USER (1ULL << 2) +/* ================================================================ + * Page-table entry flags (x86-64 4-level paging) + * ================================================================ */ + +#define PTE_PRESENT (1ULL << 0) +#define PTE_WRITABLE (1ULL << 1) +#define PTE_USER (1ULL << 2) #define PTE_WRITETHROUGH (1ULL << 3) -#define PTE_NOCACHE (1ULL << 4) -#define PTE_ACCESSED (1ULL << 5) -#define PTE_DIRTY (1ULL << 6) -#define PTE_HUGE (1ULL << 7) -#define PTE_GLOBAL (1ULL << 8) -#define PTE_NX (1ULL << 63) +#define PTE_NOCACHE (1ULL << 4) +#define PTE_ACCESSED (1ULL << 5) +#define PTE_DIRTY (1ULL << 6) +#define PTE_HUGE (1ULL << 7) /* 2 MB or 1 GB page */ +#define PTE_GLOBAL (1ULL << 8) +#define PTE_NX (1ULL << 63) /* No-Execute */ -#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ULL +#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ULL -/* PMM pool: 16 MB managed by the kernel */ -#define PMM_POOL_SIZE (16ULL * 1024 * 1024) +/* ================================================================ + * Physical Memory Manager (PMM) + * ================================================================ */ + +/* Size of the PMM-managed pool (allocated from UEFI at boot). */ +#define PMM_POOL_SIZE (16ULL * 1024 * 1024) /* 16 MB */ #define PMM_POOL_PAGES (PMM_POOL_SIZE / PAGE_SIZE) -/* Heap constants */ -#define HEAP_INITIAL_PAGES 64 /* 256 KB initial heap */ -#define HEAP_BLOCK_MAGIC 0xDEADBEEFU -#define HEAP_BLOCK_FREE 0 -#define HEAP_BLOCK_USED 1 -#define HEAP_ALIGN 16 - -/* -------- Physical Memory Manager -------- */ - void pmm_init(BootInfo *Boot); UINT64 pmm_alloc_page(void); void pmm_free_page(UINT64 phys_addr); @@ -44,19 +54,33 @@ void pmm_free_pages(UINT64 phys_addr, UINTN count); UINTN pmm_get_free_pages(void); UINTN pmm_get_total_pages(void); -/* -------- Paging -------- */ +/* ================================================================ + * Paging + * ================================================================ */ void paging_init(BootInfo *Boot); BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags); void paging_unmap_page(UINT64 virt); UINT64 paging_get_phys(UINT64 virt); -/* -------- Heap Allocator -------- */ +/* ================================================================ + * Heap allocator + * ================================================================ */ +#define HEAP_INITIAL_PAGES 64 /* 256 KB initial heap */ +#define HEAP_BLOCK_MAGIC 0xDEADBEEFU /* corruption-detection canary */ +#define HEAP_BLOCK_FREE 0 +#define HEAP_BLOCK_USED 1 +#define HEAP_ALIGN 16 /* minimum allocation alignment */ + +/* + * HeapBlock – header placed immediately before each allocation. + * Blocks form a doubly-linked free-list. + */ typedef struct HeapBlock { - UINT32 magic; - UINT32 state; - UINTN size; /* usable bytes (excludes header) */ + UINT32 magic; /* must be HEAP_BLOCK_MAGIC */ + UINT32 state; /* HEAP_BLOCK_FREE / _USED */ + UINTN size; /* usable bytes (excludes hdr) */ struct HeapBlock *next; struct HeapBlock *prev; } HeapBlock; @@ -67,9 +91,14 @@ void kfree(void *ptr); void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, UINTN *num_blocks); -/* -------- Top-level helpers -------- */ +/* ================================================================ + * Top-level helpers + * ================================================================ */ +/* Initialise all memory subsystems (PMM → paging → heap). */ void memory_init(BootInfo *Boot); + +/* Print PMM, heap, and paging statistics to the console. */ void memory_print_stats(BootInfo *Boot); -#endif +#endif /* MEMORY_H */ diff --git a/string_utils.c b/string_utils.c index 377a0a2..2ed3b63 100644 --- a/string_utils.c +++ b/string_utils.c @@ -1,5 +1,16 @@ +/* + * string_utils.c – Wide-character (CHAR16) string helpers. + * + * Provides basic string operations used throughout the kernel, + * particularly by the command parser in commands.c. + */ + #include "string_utils.h" +/* ---------------------------------------------------------------- + * Character classification / conversion + * ---------------------------------------------------------------- */ + BOOLEAN is_space16(CHAR16 Ch) { return (Ch == L' ' || Ch == L'\t'); @@ -13,6 +24,10 @@ CHAR16 ascii_lower16(CHAR16 Ch) return Ch; } +/* ---------------------------------------------------------------- + * String comparison + * ---------------------------------------------------------------- */ + BOOLEAN ascii_streq_ci(const CHAR16 *A, const CHAR16 *B) { if (A == NULL || B == NULL) { @@ -30,6 +45,10 @@ BOOLEAN ascii_streq_ci(const CHAR16 *A, const CHAR16 *B) return (*A == L'\0' && *B == L'\0'); } +/* ---------------------------------------------------------------- + * In-place trimming + * ---------------------------------------------------------------- */ + void trim_spaces_inplace(CHAR16 *Cmd) { UINTN start = 0; @@ -40,19 +59,23 @@ void trim_spaces_inplace(CHAR16 *Cmd) return; } + /* Find first non-space character */ while (Cmd[start] != L'\0' && is_space16(Cmd[start])) { start++; } + /* Find end of string */ end = start; while (Cmd[end] != L'\0') { end++; } + /* Trim trailing spaces */ while (end > start && is_space16(Cmd[end - 1])) { end--; } + /* Shift trimmed content to the beginning */ while (i < (end - start)) { Cmd[i] = Cmd[start + i]; i++; diff --git a/string_utils.h b/string_utils.h index bb89c3d..21481b5 100644 --- a/string_utils.h +++ b/string_utils.h @@ -1,11 +1,25 @@ +/* + * string_utils.h – Wide-character (CHAR16) string helpers. + * + * Utility functions for the kernel's interactive shell: whitespace + * detection, case-insensitive comparison, and in-place trimming. + */ + #ifndef STRING_UTILS_H #define STRING_UTILS_H #include +/* Return TRUE if Ch is a space or horizontal tab. */ BOOLEAN is_space16(CHAR16 Ch); + +/* Return the ASCII lower-case equivalent of Ch (A-Z only). */ CHAR16 ascii_lower16(CHAR16 Ch); + +/* Case-insensitive equality test for two NUL-terminated CHAR16 strings. */ BOOLEAN ascii_streq_ci(const CHAR16 *A, const CHAR16 *B); + +/* Strip leading and trailing whitespace from Cmd in place. */ void trim_spaces_inplace(CHAR16 *Cmd); -#endif +#endif /* STRING_UTILS_H */ diff --git a/task.c b/task.c index 4b74231..f08e239 100644 --- a/task.c +++ b/task.c @@ -1,6 +1,21 @@ +/* + * task.c – Cooperative multitasking: PCB pool, scheduler, yield/exit. + * + * Task 0 is the kernel/shell thread (uses the boot stack). + * Additional tasks are created with task_create(), which allocates a + * stack from the PMM and sets up a fake context-switch frame so that + * context_switch() can "return" into a trampoline that calls the real + * entry function. + * + * Scheduling is purely cooperative: tasks must call task_yield() to + * let others run. The scheduler uses round-robin selection among + * READY tasks. + */ + #include "task.h" #include "memory.h" +/* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ do { \ if ((Boot) != NULL && (Boot)->print != NULL) { \ @@ -9,14 +24,14 @@ } while (0) /* ================================================================ - * Task / Process Control Block pool + * Module state * ================================================================ */ -static Task tasks[TASK_MAX]; +static Task tasks[TASK_MAX]; /* PCB pool (static array) */ static Task *current_task = NULL; -static UINT32 next_pid = 0; -static BootInfo *task_boot = NULL; -static BOOLEAN task_ready = FALSE; +static UINT32 next_pid = 0; +static BootInfo *task_boot = NULL; +static BOOLEAN task_ready = FALSE; /* Forward declaration */ static void task_trampoline(void); @@ -25,6 +40,7 @@ static void task_trampoline(void); * Helpers * ---------------------------------------------------------------- */ +/* Copy a wide string with a maximum length (including NUL). */ static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max) { UINTN i = 0; @@ -39,6 +55,10 @@ static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max) * Initialisation – make the current (kernel) thread task 0 * ---------------------------------------------------------------- */ +/* + * Initialise the scheduler: clear all PCB slots and register the + * currently running kernel thread as task 0. + */ void task_init(BootInfo *Boot) { UINTN i; diff --git a/task.h b/task.h index 1c80949..e700793 100644 --- a/task.h +++ b/task.h @@ -1,56 +1,80 @@ +/* + * task.h – Cooperative multitasking interface. + * + * Provides process control blocks (PCBs), a round-robin scheduler, + * and voluntary yield/exit primitives. Context switching is performed + * by the assembly routine in context_switch.S. + */ + #ifndef TASK_H #define TASK_H #include #include "boot_info.h" -/* Maximum number of concurrent tasks */ -#define TASK_MAX 32 +/* ================================================================ + * Configuration + * ================================================================ */ -/* Stack size per task: 32 KB (8 pages) */ -#define TASK_STACK_PAGES 8 +#define TASK_MAX 32 /* max concurrent tasks */ +#define TASK_STACK_PAGES 8 /* 32 KB stack per task */ #define TASK_STACK_SIZE (TASK_STACK_PAGES * 4096) +#define TASK_NAME_LEN 32 /* max name length (CHAR16) */ -/* Maximum task name length */ -#define TASK_NAME_LEN 32 +/* ================================================================ + * Task states + * ================================================================ */ -/* Task states */ typedef enum { - TASK_STATE_FREE = 0, /* PCB slot is unused */ - TASK_STATE_READY, /* Runnable, waiting for CPU */ - TASK_STATE_RUNNING, /* Currently executing on CPU */ - TASK_STATE_TERMINATED /* Finished execution */ + TASK_STATE_FREE = 0, /* PCB slot is unused */ + TASK_STATE_READY, /* runnable, waiting to be scheduled */ + TASK_STATE_RUNNING, /* currently executing on the CPU */ + TASK_STATE_TERMINATED /* finished; slot will be recycled */ } TaskState; -/* Task entry function signature */ +/* ================================================================ + * Task entry function + * ================================================================ */ + +/* Signature for the function a new task begins executing. */ typedef void (*TaskEntryFn)(void *arg); /* ================================================================ * Process Control Block (PCB) * ================================================================ */ + typedef struct Task { - UINT32 pid; /* Process ID */ - TaskState state; /* Current state */ - CHAR16 name[TASK_NAME_LEN]; /* Human-readable name */ - UINT64 saved_rsp; /* Saved stack pointer (context switch) */ - UINT64 stack_base; /* Base address of allocated stack */ - UINTN stack_pages; /* Stack size in pages */ - TaskEntryFn entry; /* Entry function pointer */ - void *arg; /* Argument passed to entry */ - UINTN switches; /* Number of times scheduled */ + UINT32 pid; /* unique process ID */ + TaskState state; /* current lifecycle state */ + CHAR16 name[TASK_NAME_LEN]; /* human-readable label */ + + /* Context switch state */ + UINT64 saved_rsp; /* RSP saved by context_switch() */ + UINT64 stack_base; /* base phys addr of stack alloc */ + UINTN stack_pages; /* stack size in pages */ + + /* Entry point */ + TaskEntryFn entry; /* function pointer */ + void *arg; /* argument passed to entry() */ + + /* Scheduling metadata */ + UINTN switches; /* number of times scheduled */ } Task; -/* -------- Task API -------- */ +/* ================================================================ + * Public API + * ================================================================ */ -void task_init(BootInfo *Boot); -Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg); -void task_yield(void); -void task_exit(void); -Task *task_current(void); -UINTN task_count(void); -void task_print_list(BootInfo *Boot); +void task_init(BootInfo *Boot); /* initialise scheduler */ +Task *task_create(const CHAR16 *name, /* spawn a new task */ + TaskEntryFn entry, void *arg); +void task_yield(void); /* voluntarily give up the CPU */ +void task_exit(void); /* terminate the current task */ +Task *task_current(void); /* return the running task's PCB */ +UINTN task_count(void); /* number of non-FREE tasks */ +void task_print_list(BootInfo *Boot); /* print task table (for `ps`) */ -/* Assembly context switch (defined in context_switch.S) */ +/* Assembly context switch (defined in context_switch.S). */ extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp); -#endif +#endif /* TASK_H */