Better docs and structure

This commit is contained in:
2026-02-26 21:33:16 +00:00
parent d449150169
commit 13a281fa4f
18 changed files with 892 additions and 387 deletions

191
Makefile
View File

@@ -1,45 +1,56 @@
# Makefile for UEFI Operating System # ==============================================================================
# Makefile for Simple UEFI Operating System
# ==============================================================================
# Architecture # ---- Architecture -----------------------------------------------------------
ARCH = x86_64 ARCH = x86_64
# Compiler and tools # ---- Tools ------------------------------------------------------------------
CC = gcc CC = gcc
LD = ld LD = ld
OBJCOPY = objcopy OBJCOPY = objcopy
# Directories # ---- Directories ------------------------------------------------------------
SRC_DIR = . SRC_DIR = .
BUILD_DIR = build BUILD_DIR = build
EFI_DIR = $(BUILD_DIR)/EFI/BOOT EFI_DIR = $(BUILD_DIR)/EFI/BOOT
IMAGE_DIR = $(BUILD_DIR)/image IMAGE_DIR = $(BUILD_DIR)/image
# GNU-EFI paths (common locations) # ---- GNU-EFI paths ---------------------------------------------------------
EFI_INC = /usr/include/efi EFI_INC = /usr/include/efi
EFI_INCLUDES = -I$(EFI_INC) -I$(EFI_INC)/$(ARCH) -I$(EFI_INC)/protocol EFI_INCLUDES = -I$(EFI_INC) -I$(EFI_INC)/$(ARCH) -I$(EFI_INC)/protocol
EFI_LDS = /usr/lib/elf_$(ARCH)_efi.lds EFI_LDS = /usr/lib/elf_$(ARCH)_efi.lds
EFI_CRT_OBJS = /usr/lib/crt0-efi-$(ARCH).o EFI_CRT_OBJS = /usr/lib/crt0-efi-$(ARCH).o
EFI_LIB_PATHS = -L/usr/lib EFI_LIB_PATHS = -L/usr/lib
# Compiler flags # ---- Compiler / linker flags ------------------------------------------------
CFLAGS = -ffreestanding -fno-stack-protector -fpic \
-fshort-wchar -mno-red-zone -Wall -Wextra \ # Flags shared by both the UEFI loader and the kernel
$(EFI_INCLUDES) -DEFI_FUNCTION_WRAPPER 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 \ LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \
-Bsymbolic $(EFI_LIB_PATHS) $(EFI_CRT_OBJS) -Bsymbolic $(EFI_LIB_PATHS) $(EFI_CRT_OBJS)
# Libraries
LIBS = -lefi -lgnuefi LIBS = -lefi -lgnuefi
# Target # ---- Targets / objects ------------------------------------------------------
TARGET = BOOTX64.EFI TARGET = BOOTX64.EFI
TARGET_SO = bootx64.so TARGET_SO = bootx64.so
OBJ = $(BUILD_DIR)/main.o LOADER_OBJ = $(BUILD_DIR)/main.o
KERNEL_TARGET = kernel.elf 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 settings
QEMU = qemu-system-x86_64 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 if=pflash,format=raw,file=$(BUILD_DIR)/OVMF_VARS.fd \
-drive format=raw,file=fat:rw:$(BUILD_DIR) \ -drive format=raw,file=fat:rw:$(BUILD_DIR) \
-net none \ -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) 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 iso: all
@echo "Creating FAT ESP image for UEFI boot..." @echo "Creating FAT ESP image for UEFI boot..."
@@ -107,6 +124,10 @@ iso: all
$(IMAGE_DIR)/staging $(IMAGE_DIR)/staging
@echo "ISO image created: $(ISO_FILE)" @echo "ISO image created: $(ISO_FILE)"
# ==============================================================================
# Run targets
# ==============================================================================
# Run from ISO with QEMU # Run from ISO with QEMU
runiso: iso runiso: iso
@echo "Starting QEMU from ISO..." @echo "Starting QEMU from ISO..."
@@ -124,77 +145,62 @@ runiso: iso
-nographic \ -nographic \
-no-reboot -no-reboot
# Create necessary directories # ==============================================================================
# Directory setup
# ==============================================================================
setup: setup:
@mkdir -p $(BUILD_DIR) @mkdir -p $(BUILD_DIR)
@mkdir -p $(EFI_DIR) @mkdir -p $(EFI_DIR)
@mkdir -p $(IMAGE_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 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@echo "Compiling $<..." @echo " CC $<"
$(CC) $(CFLAGS) -c $< -o $@ @$(CC) $(KERNEL_CFLAGS) -c $< -o $@
# Compile kernel source $(BUILD_DIR)/%.o: $(SRC_DIR)/%.S
$(BUILD_DIR)/kernel.o: $(SRC_DIR)/kernel.c @echo " AS $<"
@echo "Compiling kernel.c..." @$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
$(BUILD_DIR)/string_utils.o: $(SRC_DIR)/string_utils.c # ==============================================================================
@echo "Compiling string_utils.c..." # Linking
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ # ==============================================================================
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
$(BUILD_DIR)/commands.o: $(SRC_DIR)/commands.c # ---- Kernel ELF ----
@echo "Compiling commands.c..."
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
$(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) $(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD)
@echo "Linking kernel ELF..." @echo " LD $@"
$(LD) -nostdlib -T $(KERNEL_LD) $(KERNEL_OBJS) -o $@ @$(LD) -nostdlib -T $(KERNEL_LD) $(KERNEL_OBJS) -o $@
# Link to create shared object # ---- UEFI loader shared object ----
$(BUILD_DIR)/$(TARGET_SO): $(OBJ)
@echo "Linking $@..." $(BUILD_DIR)/$(TARGET_SO): $(LOADER_OBJ)
$(LD) $(LDFLAGS) $(OBJ) -o $@ $(LIBS) @echo " LD $@"
@$(LD) $(LDFLAGS) $(LOADER_OBJ) -o $@ $(LIBS)
# ---- Convert to PE32+ EFI application ----
# Convert to EFI application
$(EFI_DIR)/$(TARGET): $(BUILD_DIR)/$(TARGET_SO) $(EFI_DIR)/$(TARGET): $(BUILD_DIR)/$(TARGET_SO)
@echo "Creating EFI application..." @echo " OBJCOPY $@"
$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ @$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \
-j .dynsym -j .rel -j .rela -j .reloc \ -j .dynsym -j .rel -j .rela -j .reloc \
--target=efi-app-$(ARCH) $< $@ --target=efi-app-$(ARCH) $< $@
@echo "Build complete: $@" @echo "Build complete: $@"
# Run with QEMU # Run with QEMU (FAT directory)
run: all run: all
@echo "Starting QEMU..." @echo "Starting QEMU..."
@echo "Using OVMF firmware: $(OVMF_CODE)" @echo "Using OVMF firmware: $(OVMF_CODE)"
@@ -210,20 +216,27 @@ run: all
@echo "================================================" @echo "================================================"
$(QEMU) $(QEMU_FLAGS) $(QEMU) $(QEMU_FLAGS)
# ==============================================================================
# Maintenance
# ==============================================================================
# Clean build artifacts # Clean build artifacts
clean: clean:
@echo "Cleaning build directory..." @echo "Cleaning build directory..."
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
@echo "Clean complete." @echo "Clean complete."
# Install dependencies (for Debian/Ubuntu) # Install dependencies (Debian/Ubuntu)
install-deps: install-deps:
@echo "Installing dependencies..." @echo "Installing dependencies..."
sudo apt-get update sudo apt-get update
sudo apt-get install -y gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools sudo apt-get install -y gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
@echo "Dependencies installed." @echo "Dependencies installed."
# Help target # ==============================================================================
# Help
# ==============================================================================
help: help:
@echo "UEFI Operating System Makefile" @echo "UEFI Operating System Makefile"
@echo "" @echo ""

325
README.md
View File

@@ -4,24 +4,30 @@ A minimal bootable UEFI operating system written in C that demonstrates UEFI boo
## Features ## Features
- **UEFI Boot**: Boots directly on UEFI firmware via a PE32+ loader - **UEFI Boot** — boots directly on UEFI firmware via a PE32+ loader
- **ELF64 Kernel Loader**: Reads and maps a standalone kernel from the EFI partition - **ELF64 Kernel Loader** — reads and maps a standalone kernel from the EFI partition
- **Console I/O**: Interactive keyboard input and screen output - **Console I/O** — interactive keyboard input and screen output
- **Interrupt Handling**: IDT setup with CPU exception handlers (vectors 031) and hardware IRQ dispatch - **Interrupt Handling** IDT setup with CPU exception handlers (vectors 031) 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 - **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 - **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 - **Interactive Shell** — command registry with `help`, `man`, built-in commands, and test utilities
## Requirements ---
- GCC compiler ## Getting Started
- GNU-EFI library
### Requirements
- GCC cross-compiler (or native x86-64 GCC)
- GNU-EFI library and headers
- QEMU with OVMF firmware - QEMU with OVMF firmware
- Make - GNU Make
- `xorriso` and `mtools` (for ISO builds only)
## Installation ### Installation
### On Debian/Ubuntu: <details>
<summary><strong>Debian / Ubuntu</strong></summary>
```bash ```bash
make install-deps make install-deps
@@ -33,57 +39,78 @@ Or manually:
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
``` ```
### On Arch Linux: </details>
<details>
<summary><strong>Arch Linux</strong></summary>
```bash ```bash
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
``` ```
### On Fedora/RHEL: </details>
<details>
<summary><strong>Fedora / RHEL</strong></summary>
```bash ```bash
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
``` ```
## Building </details>
Build the UEFI loader and kernel: ### Building
```bash ```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 ```bash
make run make run # build and run with QEMU (FAT directory, nographic)
``` make runiso # build, create ISO, and run with QEMU
To build and run from an ISO image:
```bash
make runiso
``` ```
The loader expects `kernel.elf` at the root of the EFI partition (next to the `EFI/` directory). The loader expects `kernel.elf` at the root of the EFI partition (next to the `EFI/` directory).
### QEMU Controls: | Key / Command | Action |
- **Exit QEMU**: Press `Ctrl+A`, then `X` |---------------|--------|
- **Shutdown OS**: Type `shutdown` in the OS | `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 | | Command | Description |
|---------|-------------| |---------|-------------|
| `help` | Display all available commands | | `help` | List all available commands |
| `man <command>` | Display detailed help for a command | | `man <command>` | Show detailed help for a command |
| `shutdown` | Shutdown the system | | `shutdown` | Shut down the system |
| `clear` | Clear the screen | | `clear` | Clear the screen |
| `about` | Display system information | | `about` | Display system information |
| `mem` | Display memory statistics (PMM, heap, paging) | | `mem` | Display memory statistics (PMM, heap, paging) |
@@ -108,10 +135,10 @@ Once the OS boots, you can use these commands:
### Testing Memory Management ### 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()` 1. **Heap allocation** — allocates 8 blocks of increasing size (164096 bytes) via `kmalloc()`
2. **Heap free** — frees all allocated blocks via `kfree()` and verifies coalescing 2. **Heap free** — frees all blocks via `kfree()` and verifies coalescing
3. **PMM single page** — allocates and frees a single 4 KB page 3. **PMM single page** — allocates and frees a single 4 KB page
4. **PMM multi-page** — allocates and frees 4 contiguous pages 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 3. The shell yields to let workers run in round-robin order
4. Displays the task list after completion 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 -> 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). 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 0255) 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 ## Architecture
### Boot Flow ### Boot Flow
1. **UEFI Entry**`efi_main()` in `main.c` is called by firmware ```
2. **GNU-EFI Init** — library initialized with the system table UEFI Firmware
3. **Kernel Load**`kernel.elf` is read from the EFI partition, ELF64 PT_LOAD segments are mapped into memory └─ efi_main() [main.c] PE32+ UEFI application
4. **BootInfo Setup** — function pointers for console I/O, shutdown, and page allocation are packaged into a `BootInfo` struct ├─ InitializeLib() GNU-EFI setup
5. **Kernel Entry**`kmain()` is called with the `BootInfo` pointer ├─ read_file_to_buffer() read kernel.elf from ESP
├─ load_elf_kernel() parse ELF64, map PT_LOAD segments
### Kernel Initialization ├─ populate BootInfo wrap UEFI services as fn pointers
└─ kmain(&Boot) [kernel.c] jump to kernel
1. **IDT** — CPU exception vectors (031) are installed; firmware IRQ handlers are preserved ├─ idt_init() install exception handlers
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 ├─ memory_init() PMM → paging → heap
3. **Tasks** — scheduler initializes; the running kernel becomes task 0 ├─ task_init() scheduler + task 0
4. **Shell** — interactive loop with non-blocking input and cooperative yielding └─ shell loop read-eval-print with cooperative yield
```
### Memory Management ### Memory Management
| Component | Description | | Layer | Description |
|-----------|-------------| |-------|-------------|
| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages); supports single and contiguous multi-page allocation | | **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 | | **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 | | **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing. 16-byte alignment. Magic-number corruption detection. |
### Task System ### Task System
| Component | Description | | Aspect | Detail |
|-----------|-------------| |--------|--------|
| **PCB** | `Task` struct with PID, state, name, saved RSP, stack, entry function, and scheduling metadata | | **PCB** | `Task` struct: PID, state, name, saved RSP, stack base, entry function, scheduling metadata |
| **States** | `FREE``READY``RUNNING``TERMINATED``FREE` | | **States** | `FREE``READY``RUNNING``TERMINATED``FREE` |
| **Scheduler** | Round-robin selection among `READY` tasks via `task_yield()` | | **Scheduler** | Round-robin among `READY` tasks via `task_yield()` |
| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12``r15`) and `RFLAGS`, swaps RSP | | **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12``r15`) and `RFLAGS`, then swaps RSP |
| **Stack** | Each task gets 32 KB (8 pages) allocated from PMM; freed on `task_exit()` | | **Stack** | 32 KB (8 pages) per task, allocated from PMM, freed on `task_exit()` |
| **Limits** | Up to 32 concurrent tasks; cooperative (voluntary yield) only | | **Limits** | Up to 32 concurrent tasks; cooperative (voluntary yield) only |
### Command System ### Command System
- Commands are registered in a static array with metadata (name, description, usage, handler function) Commands are registered in a static array in `commands.c`:
- `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
## 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 0255) 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` 1. Build the project: `make`
2. Format a USB drive with GPT and create an EFI partition (FAT32) 2. Format a USB drive with GPT and a FAT32 EFI System Partition
3. Mount the EFI partition 3. Mount the ESP
4. Copy `build/EFI/BOOT/BOOTX64.EFI` to `/EFI/BOOT/` on the USB drive 4. Copy `build/EFI/BOOT/BOOTX64.EFI` → `<mount>/EFI/BOOT/BOOTX64.EFI`
5. Copy `build/kernel.elf` to the root of the EFI partition 5. Copy `build/kernel.elf` → `<mount>/kernel.elf`
6. Boot from the USB drive in UEFI mode 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 ## 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 ## Technical Details
- **Architecture**: x86_64 | Property | Value |
- **Firmware Interface**: UEFI 2.x |----------|-------|
- **Development Library**: GNU-EFI | Architecture | x86-64 (long mode) |
- **Loader Format**: PE32+ (Portable Executable for EFI) | Firmware Interface | UEFI 2.x |
- **Kernel Format**: ELF64 | Development Library | GNU-EFI |
- **Boot Protocol**: UEFI Boot Services | Loader Format | PE32+ (EFI application) |
- **Memory Model**: Identity-mapped (UEFI), 4-level paging (kernel) | Kernel Format | ELF64 (static, loaded at 0x100000) |
- **Scheduling**: Cooperative round-robin multitasking | Boot Protocol | UEFI Boot Services |
| Memory Model | Identity-mapped (UEFI), 4-level paging (kernel) |
## License | Scheduling | Cooperative round-robin multitasking |
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`
## Resources ## Resources
- [UEFI Specification](https://uefi.org/specifications) - [UEFI Specification](https://uefi.org/specifications)
- [GNU-EFI Documentation](https://sourceforge.net/projects/gnu-efi/) - [GNU-EFI Documentation](https://sourceforge.net/projects/gnu-efi/)
- [OSDev Wiki](https://wiki.osdev.org/) - [OSDev Wiki](https://wiki.osdev.org/)
## License
This is a minimal educational example. Feel free to use and modify as needed.

View File

@@ -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 #ifndef BOOT_INFO_H
#define BOOT_INFO_H #define BOOT_INFO_H
#include <efi.h> #include <efi.h>
/* Printf-style print function (backed by UEFI ConOut). */
typedef UINTN (*KernelPrintFn)(const CHAR16 *Format, ...); 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 { typedef struct {
EFI_SYSTEM_TABLE *SystemTable; EFI_SYSTEM_TABLE *SystemTable; /* UEFI System Table */
KernelPrintFn print;
EFI_STATUS (*clear_screen)(void); /* Console I/O */
EFI_STATUS (*set_attribute)(UINTN Attribute); KernelPrintFn print; /* formatted text output */
EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key); EFI_STATUS (*clear_screen)(void); /* clear the console */
EFI_STATUS (*try_read_key)(EFI_INPUT_KEY *Key); /* non-blocking */ EFI_STATUS (*set_attribute)(UINTN Attribute); /* set text colour/attr */
void (*shutdown)(void); 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 (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr);
EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages); EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages);
} BootInfo; } BootInfo;
/* Kernel entry point signature (called by the UEFI loader). */
typedef void (*KernelEntryFn)(BootInfo *Boot); typedef void (*KernelEntryFn)(BootInfo *Boot);
#endif #endif /* BOOT_INFO_H */

View File

@@ -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 <efi.h> #include <efi.h>
#include "commands.h" #include "commands.h"
#include "string_utils.h" #include "string_utils.h"
#include "memory.h" #include "memory.h"
#include "task.h" #include "task.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \ #define SAFE_PRINT(Boot, ...) \
do { \ do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \ if ((Boot) != NULL && (Boot)->print != NULL) { \
@@ -11,7 +25,10 @@
} \ } \
} while (0) } while (0)
// Forward declarations /* ================================================================
* Forward declarations
* ================================================================ */
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args); static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args);
static void cmd_help(BootInfo *Boot, CHAR16 *Args); static void cmd_help(BootInfo *Boot, CHAR16 *Args);
static void cmd_man(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_memtest(BootInfo *Boot, CHAR16 *Args);
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args); static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
// Command registry /* ================================================================
* Command registry
* ================================================================ */
static Command commands[] = { static Command commands[] = {
{ {
L"shutdown", 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.", L"Usage: tasktest\n\r Spawns several concurrent tasks that run cooperatively\n\r and report their progress, demonstrating context switching.",
cmd_tasktest 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) static void request_shutdown(BootInfo *Boot)
{ {
if (Boot == NULL) { if (Boot == NULL) {
@@ -110,16 +138,20 @@ static void request_shutdown(BootInfo *Boot)
SAFE_PRINT(Boot, L"Shutdown service unavailable.\n\r"); SAFE_PRINT(Boot, L"Shutdown service unavailable.\n\r");
} }
/* ================================================================
* Built-in command handlers
* ================================================================ */
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args) static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args)
{ {
(void)Args; // Unused (void)Args;
SAFE_PRINT(Boot, L"Shutting down...\n\r"); SAFE_PRINT(Boot, L"Shutting down...\n\r");
request_shutdown(Boot); request_shutdown(Boot);
} }
static void cmd_help(BootInfo *Boot, CHAR16 *Args) static void cmd_help(BootInfo *Boot, CHAR16 *Args)
{ {
(void)Args; // Unused (void)Args;
show_help(Boot); show_help(Boot);
} }
@@ -154,7 +186,7 @@ static void cmd_man(BootInfo *Boot, CHAR16 *Args)
static void cmd_clear(BootInfo *Boot, CHAR16 *Args) static void cmd_clear(BootInfo *Boot, CHAR16 *Args)
{ {
EFI_STATUS Status; EFI_STATUS Status;
(void)Args; // Unused (void)Args;
if (Boot != NULL && Boot->clear_screen != NULL) { if (Boot != NULL && Boot->clear_screen != NULL) {
Status = Boot->clear_screen(); Status = Boot->clear_screen();
@@ -168,8 +200,8 @@ static void cmd_clear(BootInfo *Boot, CHAR16 *Args)
static void cmd_about(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"================================================\n\r"); SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L" Simple UEFI Operating System\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"); 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) void show_help(BootInfo *Boot)
{ {
UINTN i = 0; UINTN i = 0;
@@ -389,7 +426,7 @@ void show_help(BootInfo *Boot)
SAFE_PRINT(Boot, L"Available commands:\n\r"); SAFE_PRINT(Boot, L"Available commands:\n\r");
SAFE_PRINT(Boot, L"\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++) { for (i = 0; commands[i].name != NULL; i++) {
name_len = 0; name_len = 0;
while (commands[i].name[name_len] != L'\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++) { for (i = 0; commands[i].name != NULL; i++) {
SAFE_PRINT(Boot, L" %s", commands[i].name); SAFE_PRINT(Boot, L" %s", commands[i].name);
// Add padding /* Pad to align descriptions */
name_len = 0; name_len = 0;
while (commands[i].name[name_len] != L'\0') { while (commands[i].name[name_len] != L'\0') {
name_len++; name_len++;
@@ -420,6 +457,10 @@ void show_help(BootInfo *Boot)
SAFE_PRINT(Boot, L"\n\r"); 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) void execute_command(BootInfo *Boot, CHAR16 *Input)
{ {
CHAR16 *cmd_start = NULL; CHAR16 *cmd_start = NULL;
@@ -436,27 +477,27 @@ void execute_command(BootInfo *Boot, CHAR16 *Input)
return; return;
} }
// Split command and arguments /* Split input into command and argument strings */
cmd_start = Input; cmd_start = Input;
args_start = Input; args_start = Input;
// Find first space /* Advance past the command keyword */
while (*args_start != L'\0' && !is_space16(*args_start)) { while (*args_start != L'\0' && !is_space16(*args_start)) {
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') { if (*args_start != L'\0') {
*args_start = L'\0'; *args_start = L'\0';
args_start++; args_start++;
// Skip leading spaces in args /* skip leading whitespace in args */
while (*args_start != L'\0' && is_space16(*args_start)) { while (*args_start != L'\0' && is_space16(*args_start)) {
args_start++; args_start++;
} }
} }
// Search for command /* Look up and dispatch the command */
for (i = 0; commands[i].name != NULL; i++) { for (i = 0; commands[i].name != NULL; i++) {
if (ascii_streq_ci(cmd_start, commands[i].name)) { if (ascii_streq_ci(cmd_start, commands[i].name)) {
commands[i].handler(Boot, args_start); 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"Unknown command: %s\n\r", cmd_start);
SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r"); SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r");
} }

View File

@@ -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 #ifndef COMMANDS_H
#define COMMANDS_H #define COMMANDS_H
#include "boot_info.h" #include "boot_info.h"
/* Handler function signature: receives BootInfo and any argument text. */
typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args); 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 { typedef struct {
const CHAR16 *name; const CHAR16 *name; /* command keyword (e.g. L"help") */
const CHAR16 *description; const CHAR16 *description; /* one-line summary for `help` */
const CHAR16 *usage; const CHAR16 *usage; /* detailed text shown by `man` */
CommandHandlerFn handler; CommandHandlerFn handler; /* function that executes the cmd */
} Command; } Command;
/* Parse and dispatch a line of user input. */
void execute_command(BootInfo *Boot, CHAR16 *Input); void execute_command(BootInfo *Boot, CHAR16 *Input);
/* Print a formatted list of all registered commands. */
void show_help(BootInfo *Boot); void show_help(BootInfo *Boot);
#endif #endif /* COMMANDS_H */

View File

@@ -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); * void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
* *
* %rdi = pointer to save current RSP (old task's PCB field) * %rdi = pointer to save current RSP (old task's PCB field)
* %rsi = RSP value to restore (new task's saved RSP) * %rsi = RSP value to restore (new task's saved RSP)
* *
* Saves callee-saved registers and RFLAGS on the current stack, * Saves callee-saved registers (rbp, rbx, r12-r15) and RFLAGS on
* stores RSP, loads the new stack, restores registers, and returns * the current stack, stores RSP into *old_rsp, loads new_rsp into
* into the new task's execution context. * 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 .text
.global context_switch .global context_switch
/* ----------------------------------------------------------------
* Entry: save old context, load new context, return
* ---------------------------------------------------------------- */
context_switch: context_switch:
/* Save callee-saved registers and flags on the current stack */ /* Save callee-saved registers and flags on the current stack */
pushq %rbp pushq %rbp
@@ -24,10 +32,10 @@ context_switch:
pushq %r15 pushq %r15
pushfq pushfq
/* Save current stack pointer into *old_rsp */ /* Store current RSP into *old_rsp */
movq %rsp, (%rdi) movq %rsp, (%rdi)
/* Load new task's stack pointer */ /* Switch to the new task's stack */
movq %rsi, %rsp movq %rsi, %rsp
/* Restore callee-saved registers and flags from the new stack */ /* Restore callee-saved registers and flags from the new stack */

68
idt.c
View File

@@ -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" #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 { typedef struct {
UINT16 offset_low; UINT16 offset_low; /* bits 0-15 of handler address */
UINT16 selector; UINT16 selector; /* code segment selector */
UINT8 ist; UINT8 ist; /* interrupt stack table index */
UINT8 type_attr; UINT8 type_attr; /* type and attributes */
UINT16 offset_mid; UINT16 offset_mid; /* bits 16-31 of handler address */
UINT32 offset_high; UINT32 offset_high; /* bits 32-63 of handler address */
UINT32 zero; UINT32 zero; /* reserved, must be zero */
} __attribute__((packed)) IdtEntry; } __attribute__((packed)) IdtEntry;
/* IDTR register layout for the LIDT instruction. */
typedef struct { typedef struct {
UINT16 limit; UINT16 limit;
UINT64 base; UINT64 base;
} __attribute__((packed)) IdtPtr; } __attribute__((packed)) IdtPtr;
static IdtEntry idt[IDT_SIZE]; /* ================================================================
* Module state
* ================================================================ */
static IdtEntry idt[IDT_SIZE];
static BootInfo *gBoot = NULL; static BootInfo *gBoot = NULL;
/* Defined in isr.S one stub function per vector (0-255). */
extern void (*isr_stub_table[])(void); 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)) static void idt_set_gate(UINTN index, void (*handler)(void))
{ {
UINT64 addr = (UINT64)(UINTN)handler; UINT64 addr = (UINT64)(UINTN)handler;
@@ -39,11 +66,17 @@ static void idt_set_gate(UINTN index, void (*handler)(void))
idt[index].zero = 0; idt[index].zero = 0;
} }
/* Load a new IDT register value. */
static void lidt(const IdtPtr *idtr) static void lidt(const IdtPtr *idtr)
{ {
__asm__ __volatile__("lidt (%0)" :: "r"(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) static const CHAR16 *exception_name(UINTN vector)
{ {
switch (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) static inline void outb(UINT16 port, UINT8 value)
{ {
__asm__ __volatile__("outb %0, %1" :: "a"(value), "Nd"(port)); __asm__ __volatile__("outb %0, %1" :: "a"(value), "Nd"(port));
} }
/* Send End-Of-Interrupt to the 8259 PIC(s). */
static void pic_eoi(UINTN vector) static void pic_eoi(UINTN vector)
{ {
if (vector >= 40) { if (vector >= 40) {
@@ -96,6 +135,7 @@ static void pic_eoi(UINTN vector)
outb(0x20, 0x20); /* EOI to master PIC */ outb(0x20, 0x20); /* EOI to master PIC */
} }
/* Disable interrupts and halt forever (unrecoverable fault). */
static void halt_forever(void) static void halt_forever(void)
{ {
for (;;) { for (;;) {
@@ -103,6 +143,10 @@ static void halt_forever(void)
} }
} }
/* ================================================================
* ISR dispatcher (called from isr_common in isr.S)
* ================================================================ */
void isr_handler(ISRFrame *frame) void isr_handler(ISRFrame *frame)
{ {
UINT64 cr2 = 0; UINT64 cr2 = 0;
@@ -132,6 +176,10 @@ void isr_handler(ISRFrame *frame)
halt_forever(); halt_forever();
} }
/* ================================================================
* IDT initialisation
* ================================================================ */
void idt_init(BootInfo *Boot) void idt_init(BootInfo *Boot)
{ {
IdtPtr old_idtr; IdtPtr old_idtr;

22
idt.h
View File

@@ -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 #ifndef IDT_H
#define IDT_H #define IDT_H
#include <efi.h> #include <efi.h>
#include "boot_info.h" #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 { typedef struct {
/* General-purpose registers (pushed by isr_common) */
UINT64 r15; UINT64 r15;
UINT64 r14; UINT64 r14;
UINT64 r13; UINT64 r13;
@@ -20,13 +32,21 @@ typedef struct {
UINT64 rcx; UINT64 rcx;
UINT64 rbx; UINT64 rbx;
UINT64 rax; UINT64 rax;
/* Pushed by the ISR stub macros */
UINT64 vector; UINT64 vector;
UINT64 error_code; UINT64 error_code;
/* Pushed by the CPU on interrupt entry */
UINT64 rip; UINT64 rip;
UINT64 cs; UINT64 cs;
UINT64 rflags; UINT64 rflags;
} ISRFrame; } ISRFrame;
/*
* Install our IDT: copies firmware entries for vectors 32+,
* overrides vectors 0-31 with kernel exception handlers.
*/
void idt_init(BootInfo *Boot); void idt_init(BootInfo *Boot);
#endif #endif /* IDT_H */

38
isr.S
View File

@@ -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 .text
.global isr_handler .global isr_handler
.global isr_stub_table .global isr_stub_table
/* ----------------------------------------------------------------
* Stub macros
* ---------------------------------------------------------------- */
.macro ISR_NOERR num .macro ISR_NOERR num
.global isr\num .global isr\num
isr\num: isr\num:
@@ -18,6 +38,10 @@ isr\num:
jmp isr_common jmp isr_common
.endm .endm
/* ----------------------------------------------------------------
* Common ISR body saves state, calls C handler, restores state
* ---------------------------------------------------------------- */
isr_common: isr_common:
cld cld
@@ -59,6 +83,15 @@ isr_common:
addq $16, %rsp addq $16, %rsp
iretq 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 0
ISR_NOERR 1 ISR_NOERR 1
ISR_NOERR 2 ISR_NOERR 2
@@ -316,6 +349,11 @@ ISR_NOERR 253
ISR_NOERR 254 ISR_NOERR 254
ISR_NOERR 255 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: isr_stub_table:
.quad isr0 .quad isr0
.quad isr1 .quad isr1

View File

@@ -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 <efi.h> #include <efi.h>
#include "boot_info.h" #include "boot_info.h"
@@ -6,6 +18,7 @@
#include "memory.h" #include "memory.h"
#include "task.h" #include "task.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \ #define SAFE_PRINT(Boot, ...) \
do { \ do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \ if ((Boot) != NULL && (Boot)->print != NULL) { \
@@ -13,6 +26,10 @@
} \ } \
} while (0) } while (0)
/* ================================================================
* Kernel entry point
* ================================================================ */
void kmain(BootInfo *Boot) void kmain(BootInfo *Boot)
{ {
EFI_INPUT_KEY Key; EFI_INPUT_KEY Key;
@@ -37,11 +54,12 @@ void kmain(BootInfo *Boot)
} }
} }
/* ---- Subsystem initialisation ---- */
idt_init(Boot); idt_init(Boot);
memory_init(Boot); memory_init(Boot);
task_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" Welcome to Simple UEFI Operating System!\n\r");
SAFE_PRINT(Boot, L"================================================\n\r"); SAFE_PRINT(Boot, L"================================================\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"\n\r");
SAFE_PRINT(Boot, L"Type 'help' for a list of commands.\n\r\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]; CHAR16 line[128];
UINTN len = 0; UINTN len = 0;
@@ -100,21 +118,22 @@ void kmain(BootInfo *Boot)
read_errors = 0; read_errors = 0;
if (Key.UnicodeChar == L'\r' || Key.UnicodeChar == L'\n') { 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'; line[len] = L'\0';
SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"\n\r");
execute_command(Boot, line); execute_command(Boot, line);
// Reset for next command /* Reset for next command */
len = 0; len = 0;
SAFE_PRINT(Boot, L"-> "); SAFE_PRINT(Boot, L"-> ");
} else if (Key.ScanCode == 0x08 || Key.UnicodeChar == L'\b' || Key.UnicodeChar == 0x7F) { } else if (Key.ScanCode == 0x08 || Key.UnicodeChar == L'\b' || Key.UnicodeChar == 0x7F) {
// Backspace (ScanCode 0x08 is backspace in UEFI) /* Backspace */
if (len > 0) { if (len > 0) {
len--; len--;
SAFE_PRINT(Boot, L"\b \b"); SAFE_PRINT(Boot, L"\b \b");
} }
} else if (Key.UnicodeChar >= 32 && Key.UnicodeChar < 127) { } else if (Key.UnicodeChar >= 32 && Key.UnicodeChar < 127) {
/* Printable ASCII */
if (len < (sizeof(line) / sizeof(line[0]) - 1)) { if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
line[len++] = Key.UnicodeChar; line[len++] = Key.UnicodeChar;
SAFE_PRINT(Boot, L"%c", Key.UnicodeChar); SAFE_PRINT(Boot, L"%c", Key.UnicodeChar);

View File

@@ -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) ENTRY(kmain)
SECTIONS SECTIONS
{ {
. = 0x100000; . = 0x100000; /* load address: 1 MB */
.text : ALIGN(0x1000) .text : ALIGN(0x1000) /* executable code */
{ {
*(.text*) *(.text*)
} }
.rodata : ALIGN(0x1000) .rodata : ALIGN(0x1000) /* read-only data / string literals */
{ {
*(.rodata*) *(.rodata*)
} }
.data : ALIGN(0x1000) .data : ALIGN(0x1000) /* initialised read-write data */
{ {
*(.data*) *(.data*)
} }
.bss : ALIGN(0x1000) .bss : ALIGN(0x1000) /* zero-initialised data */
{ {
*(COMMON) *(COMMON)
*(.bss*) *(.bss*)

112
main.c
View File

@@ -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 <efi.h> #include <efi.h>
#include <efilib.h> #include <efilib.h>
#include "boot_info.h" #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 { typedef struct {
UINT32 e_magic; UINT32 e_magic; /* must be ELF_MAGIC */
UINT8 e_class; UINT8 e_class; /* 2 = 64-bit */
UINT8 e_data; UINT8 e_data; /* 1 = little-endian */
UINT8 e_version; UINT8 e_version;
UINT8 e_osabi; UINT8 e_osabi;
UINT8 e_abiversion; UINT8 e_abiversion;
UINT8 e_pad[7]; UINT8 e_pad[7];
UINT16 e_type; UINT16 e_type; /* ET_EXEC = 2 */
UINT16 e_machine; UINT16 e_machine; /* EM_X86_64 = 62 */
UINT32 e_version2; UINT32 e_version2;
UINT64 e_entry; UINT64 e_entry; /* virtual address of entry point */
UINT64 e_phoff; UINT64 e_phoff; /* file offset to program header table */
UINT64 e_shoff; UINT64 e_shoff;
UINT32 e_flags; UINT32 e_flags;
UINT16 e_ehsize; UINT16 e_ehsize;
UINT16 e_phentsize; UINT16 e_phentsize; /* size of one program header entry */
UINT16 e_phnum; UINT16 e_phnum; /* number of program header entries */
UINT16 e_shentsize; UINT16 e_shentsize;
UINT16 e_shnum; UINT16 e_shnum;
UINT16 e_shstrndx; UINT16 e_shstrndx;
} Elf64_Ehdr; } Elf64_Ehdr;
/* ELF64 program header (describes one segment). */
typedef struct { typedef struct {
UINT32 p_type; UINT32 p_type; /* segment type (PT_LOAD, etc.) */
UINT32 p_flags; UINT32 p_flags; /* segment flags (R/W/X) */
UINT64 p_offset; UINT64 p_offset; /* file offset of segment data */
UINT64 p_vaddr; UINT64 p_vaddr; /* virtual address to map at */
UINT64 p_paddr; UINT64 p_paddr; /* physical address (usually == vaddr) */
UINT64 p_filesz; UINT64 p_filesz; /* bytes of segment data in file */
UINT64 p_memsz; UINT64 p_memsz; /* total bytes in memory (>= filesz) */
UINT64 p_align; UINT64 p_align; /* alignment requirement */
} Elf64_Phdr; } 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) static EFI_STATUS open_root_volume(EFI_HANDLE ImageHandle, EFI_FILE_PROTOCOL **Root)
{ {
EFI_STATUS Status; 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); 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, static EFI_STATUS read_file_to_buffer(EFI_HANDLE ImageHandle, CHAR16 *Path,
VOID **Buffer, UINTN *Size) VOID **Buffer, UINTN *Size)
{ {
@@ -127,6 +156,15 @@ static EFI_STATUS read_file_to_buffer(EFI_HANDLE ImageHandle, CHAR16 *Path,
return EFI_SUCCESS; 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) static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut)
{ {
Elf64_Ehdr *Ehdr = (Elf64_Ehdr *)Image; 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; return EFI_SUCCESS;
} }
/* ================================================================
* UEFI service wrappers (passed to the kernel via BootInfo)
* ================================================================ */
static EFI_STATUS loader_clear_screen(void) static EFI_STATUS loader_clear_screen(void)
{ {
return uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); 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); return uefi_call_wrapper(BS->FreePages, 2, addr, pages);
} }
/* ================================================================
* UEFI application entry point
* ================================================================ */
EFI_STATUS EFI_STATUS
EFIAPI EFIAPI
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) 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; UINT64 KernelEntry = 0;
BootInfo Boot; BootInfo Boot;
KernelEntryFn EntryFn = NULL; KernelEntryFn EntryFn = NULL;
// Initialize the GNU-EFI library /* Initialise the GNU-EFI library */
InitializeLib(ImageHandle, SystemTable); InitializeLib(ImageHandle, SystemTable);
Print(L"Loading kernel...\n\r"); Print(L"Loading kernel...\n\r");
Status = read_file_to_buffer(ImageHandle, L"\\kernel.elf", &KernelImage, &KernelSize); 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; return Status;
} }
Boot.SystemTable = ST; /* Populate the BootInfo struct with UEFI service wrappers */
Boot.print = Print; Boot.SystemTable = ST;
Boot.print = Print;
Boot.clear_screen = loader_clear_screen; Boot.clear_screen = loader_clear_screen;
Boot.set_attribute = loader_set_attribute; 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.try_read_key = loader_try_read_key;
Boot.shutdown = loader_shutdown; Boot.shutdown = loader_shutdown;
Boot.alloc_pages = loader_alloc_pages; Boot.alloc_pages = loader_alloc_pages;
Boot.free_pages = loader_free_pages; Boot.free_pages = loader_free_pages;
/* Jump to the kernel this should not return */
EntryFn = (KernelEntryFn)(UINTN)KernelEntry; EntryFn = (KernelEntryFn)(UINTN)KernelEntry;
EntryFn(&Boot); EntryFn(&Boot);

View File

@@ -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" #include "memory.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \ #define SAFE_PRINT(Boot, ...) \
do { \ do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \ 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 UINT8 pmm_bitmap[PMM_POOL_PAGES / 8];
static BOOLEAN pmm_ready = FALSE; static BOOLEAN pmm_ready = FALSE;
/* ================================================================
* PMM bitmap helpers
* ================================================================ */
/* Mark page `idx` as allocated. */
static void pmm_set_bit(UINTN idx) static void pmm_set_bit(UINTN idx)
{ {
pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8)); pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8));
} }
/* Mark page `idx` as free. */
static void pmm_clear_bit(UINTN idx) static void pmm_clear_bit(UINTN idx)
{ {
pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8)); pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8));
} }
/* Return TRUE if page `idx` is currently allocated. */
static BOOLEAN pmm_test_bit(UINTN idx) static BOOLEAN pmm_test_bit(UINTN idx)
{ {
return (pmm_bitmap[idx / 8] & (1U << (idx % 8))) != 0; 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) void pmm_init(BootInfo *Boot)
{ {
EFI_STATUS Status; EFI_STATUS Status;
@@ -66,6 +94,7 @@ void pmm_init(BootInfo *Boot)
pmm_pool_base); pmm_pool_base);
} }
/* Allocate a single 4 KB page. Returns physical address or 0. */
UINT64 pmm_alloc_page(void) UINT64 pmm_alloc_page(void)
{ {
UINTN i; UINTN i;
@@ -85,6 +114,7 @@ UINT64 pmm_alloc_page(void)
return 0; return 0;
} }
/* Free a single page previously returned by pmm_alloc_page(). */
void pmm_free_page(UINT64 phys_addr) void pmm_free_page(UINT64 phys_addr)
{ {
UINTN idx; UINTN idx;
@@ -100,6 +130,7 @@ void pmm_free_page(UINT64 phys_addr)
pmm_free_count++; pmm_free_count++;
} }
/* Allocate `count` physically contiguous pages (first-fit). */
UINT64 pmm_alloc_pages(UINTN count) UINT64 pmm_alloc_pages(UINTN count)
{ {
UINTN i, j; UINTN i, j;
@@ -131,6 +162,7 @@ UINT64 pmm_alloc_pages(UINTN count)
return 0; return 0;
} }
/* Free `count` contiguous pages starting at phys_addr. */
void pmm_free_pages(UINT64 phys_addr, UINTN count) void pmm_free_pages(UINT64 phys_addr, UINTN count)
{ {
UINTN i; 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 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) static UINT64 read_cr3(void)
{ {
UINT64 cr3; UINT64 cr3;
@@ -153,11 +190,13 @@ static UINT64 read_cr3(void)
return cr3; return cr3;
} }
/* Invalidate the TLB entry for virtual address `addr`. */
static void invlpg(UINT64 addr) static void invlpg(UINT64 addr)
{ {
__asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory"); __asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory");
} }
/* Return a pointer to the current PML4 table. */
static UINT64 *get_pml4(void) static UINT64 *get_pml4(void)
{ {
return (UINT64 *)(UINTN)(read_cr3() & PTE_ADDR_MASK); 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; return next;
} }
/* ----------------------------------------------------------------
* Paging public interface
* ---------------------------------------------------------------- */
/* Log the current CR3 value (identity-mapped by UEFI). */
void paging_init(BootInfo *Boot) void paging_init(BootInfo *Boot)
{ {
SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (UEFI identity-mapped)\n\r", SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (UEFI identity-mapped)\n\r",
read_cr3()); 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) BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags)
{ {
UINT64 *pml4, *pdpt, *pd, *pt; UINT64 *pml4, *pdpt, *pd, *pt;
@@ -235,6 +284,7 @@ BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags)
return TRUE; return TRUE;
} }
/* Remove the mapping for a single 4 KB page and flush the TLB. */
void paging_unmap_page(UINT64 virt) void paging_unmap_page(UINT64 virt)
{ {
UINT64 *pml4, *pdpt, *pd, *pt; UINT64 *pml4, *pdpt, *pd, *pt;
@@ -262,6 +312,10 @@ void paging_unmap_page(UINT64 virt)
invlpg(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 paging_get_phys(UINT64 virt)
{ {
UINT64 *pml4, *pdpt, *pd, *pt; UINT64 *pml4, *pdpt, *pd, *pt;
@@ -302,11 +356,16 @@ UINT64 paging_get_phys(UINT64 virt)
static HeapBlock *heap_start = NULL; static HeapBlock *heap_start = NULL;
static BOOLEAN heap_ready = FALSE; static BOOLEAN heap_ready = FALSE;
/* Round `val` up to the next multiple of `align`. */
static UINTN align_up(UINTN val, UINTN align) static UINTN align_up(UINTN val, UINTN align)
{ {
return (val + align - 1) & ~(align - 1); 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) void heap_init(BootInfo *Boot)
{ {
UINT64 phys; UINT64 phys;
@@ -333,6 +392,11 @@ void heap_init(BootInfo *Boot)
heap_size / 1024, phys); 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) void *kmalloc(UINTN size)
{ {
HeapBlock *block, *split; HeapBlock *block, *split;
@@ -377,6 +441,10 @@ void *kmalloc(UINTN size)
return NULL; /* out of heap memory */ return NULL; /* out of heap memory */
} }
/*
* Free a previously kmalloc'd pointer. Coalesces adjacent free
* blocks to reduce fragmentation.
*/
void kfree(void *ptr) void kfree(void *ptr)
{ {
HeapBlock *block; 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, void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
UINTN *num_blocks) UINTN *num_blocks)
{ {
@@ -441,6 +510,7 @@ void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
* Top-level helpers * Top-level helpers
* ================================================================ */ * ================================================================ */
/* Initialise all memory subsystems in order. */
void memory_init(BootInfo *Boot) void memory_init(BootInfo *Boot)
{ {
SAFE_PRINT(Boot, L"Initializing memory management...\n\r"); 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"); 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) void memory_print_stats(BootInfo *Boot)
{ {
UINTN h_total, h_used, h_free, h_blocks; UINTN h_total, h_used, h_free, h_blocks;

View File

@@ -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 #ifndef MEMORY_H
#define MEMORY_H #define MEMORY_H
#include <efi.h> #include <efi.h>
#include "boot_info.h" #include "boot_info.h"
/* Page size constants */ /* ================================================================
* Page-level constants
* ================================================================ */
#define PAGE_SIZE 4096 #define PAGE_SIZE 4096
#define PAGE_SHIFT 12 #define PAGE_SHIFT 12
#define PAGE_MASK (~(UINT64)(PAGE_SIZE - 1)) #define PAGE_MASK (~(UINT64)(PAGE_SIZE - 1))
/* Page table entry flags */ /* ================================================================
#define PTE_PRESENT (1ULL << 0) * Page-table entry flags (x86-64 4-level paging)
#define PTE_WRITABLE (1ULL << 1) * ================================================================ */
#define PTE_USER (1ULL << 2)
#define PTE_PRESENT (1ULL << 0)
#define PTE_WRITABLE (1ULL << 1)
#define PTE_USER (1ULL << 2)
#define PTE_WRITETHROUGH (1ULL << 3) #define PTE_WRITETHROUGH (1ULL << 3)
#define PTE_NOCACHE (1ULL << 4) #define PTE_NOCACHE (1ULL << 4)
#define PTE_ACCESSED (1ULL << 5) #define PTE_ACCESSED (1ULL << 5)
#define PTE_DIRTY (1ULL << 6) #define PTE_DIRTY (1ULL << 6)
#define PTE_HUGE (1ULL << 7) #define PTE_HUGE (1ULL << 7) /* 2 MB or 1 GB page */
#define PTE_GLOBAL (1ULL << 8) #define PTE_GLOBAL (1ULL << 8)
#define PTE_NX (1ULL << 63) #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) #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); void pmm_init(BootInfo *Boot);
UINT64 pmm_alloc_page(void); UINT64 pmm_alloc_page(void);
void pmm_free_page(UINT64 phys_addr); 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_free_pages(void);
UINTN pmm_get_total_pages(void); UINTN pmm_get_total_pages(void);
/* -------- Paging -------- */ /* ================================================================
* Paging
* ================================================================ */
void paging_init(BootInfo *Boot); void paging_init(BootInfo *Boot);
BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags); BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags);
void paging_unmap_page(UINT64 virt); void paging_unmap_page(UINT64 virt);
UINT64 paging_get_phys(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 { typedef struct HeapBlock {
UINT32 magic; UINT32 magic; /* must be HEAP_BLOCK_MAGIC */
UINT32 state; UINT32 state; /* HEAP_BLOCK_FREE / _USED */
UINTN size; /* usable bytes (excludes header) */ UINTN size; /* usable bytes (excludes hdr) */
struct HeapBlock *next; struct HeapBlock *next;
struct HeapBlock *prev; struct HeapBlock *prev;
} HeapBlock; } HeapBlock;
@@ -67,9 +91,14 @@ void kfree(void *ptr);
void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
UINTN *num_blocks); UINTN *num_blocks);
/* -------- Top-level helpers -------- */ /* ================================================================
* Top-level helpers
* ================================================================ */
/* Initialise all memory subsystems (PMM → paging → heap). */
void memory_init(BootInfo *Boot); void memory_init(BootInfo *Boot);
/* Print PMM, heap, and paging statistics to the console. */
void memory_print_stats(BootInfo *Boot); void memory_print_stats(BootInfo *Boot);
#endif #endif /* MEMORY_H */

View File

@@ -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" #include "string_utils.h"
/* ----------------------------------------------------------------
* Character classification / conversion
* ---------------------------------------------------------------- */
BOOLEAN is_space16(CHAR16 Ch) BOOLEAN is_space16(CHAR16 Ch)
{ {
return (Ch == L' ' || Ch == L'\t'); return (Ch == L' ' || Ch == L'\t');
@@ -13,6 +24,10 @@ CHAR16 ascii_lower16(CHAR16 Ch)
return Ch; return Ch;
} }
/* ----------------------------------------------------------------
* String comparison
* ---------------------------------------------------------------- */
BOOLEAN ascii_streq_ci(const CHAR16 *A, const CHAR16 *B) BOOLEAN ascii_streq_ci(const CHAR16 *A, const CHAR16 *B)
{ {
if (A == NULL || B == NULL) { 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'); return (*A == L'\0' && *B == L'\0');
} }
/* ----------------------------------------------------------------
* In-place trimming
* ---------------------------------------------------------------- */
void trim_spaces_inplace(CHAR16 *Cmd) void trim_spaces_inplace(CHAR16 *Cmd)
{ {
UINTN start = 0; UINTN start = 0;
@@ -40,19 +59,23 @@ void trim_spaces_inplace(CHAR16 *Cmd)
return; return;
} }
/* Find first non-space character */
while (Cmd[start] != L'\0' && is_space16(Cmd[start])) { while (Cmd[start] != L'\0' && is_space16(Cmd[start])) {
start++; start++;
} }
/* Find end of string */
end = start; end = start;
while (Cmd[end] != L'\0') { while (Cmd[end] != L'\0') {
end++; end++;
} }
/* Trim trailing spaces */
while (end > start && is_space16(Cmd[end - 1])) { while (end > start && is_space16(Cmd[end - 1])) {
end--; end--;
} }
/* Shift trimmed content to the beginning */
while (i < (end - start)) { while (i < (end - start)) {
Cmd[i] = Cmd[start + i]; Cmd[i] = Cmd[start + i];
i++; i++;

View File

@@ -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 #ifndef STRING_UTILS_H
#define STRING_UTILS_H #define STRING_UTILS_H
#include <efi.h> #include <efi.h>
/* Return TRUE if Ch is a space or horizontal tab. */
BOOLEAN is_space16(CHAR16 Ch); BOOLEAN is_space16(CHAR16 Ch);
/* Return the ASCII lower-case equivalent of Ch (A-Z only). */
CHAR16 ascii_lower16(CHAR16 Ch); 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); 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); void trim_spaces_inplace(CHAR16 *Cmd);
#endif #endif /* STRING_UTILS_H */

30
task.c
View File

@@ -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 "task.h"
#include "memory.h" #include "memory.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \ #define SAFE_PRINT(Boot, ...) \
do { \ do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \ if ((Boot) != NULL && (Boot)->print != NULL) { \
@@ -9,14 +24,14 @@
} while (0) } 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 Task *current_task = NULL;
static UINT32 next_pid = 0; static UINT32 next_pid = 0;
static BootInfo *task_boot = NULL; static BootInfo *task_boot = NULL;
static BOOLEAN task_ready = FALSE; static BOOLEAN task_ready = FALSE;
/* Forward declaration */ /* Forward declaration */
static void task_trampoline(void); static void task_trampoline(void);
@@ -25,6 +40,7 @@ static void task_trampoline(void);
* Helpers * Helpers
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
/* Copy a wide string with a maximum length (including NUL). */
static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max) static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max)
{ {
UINTN i = 0; 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 * 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) void task_init(BootInfo *Boot)
{ {
UINTN i; UINTN i;

86
task.h
View File

@@ -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 #ifndef TASK_H
#define TASK_H #define TASK_H
#include <efi.h> #include <efi.h>
#include "boot_info.h" #include "boot_info.h"
/* Maximum number of concurrent tasks */ /* ================================================================
#define TASK_MAX 32 * Configuration
* ================================================================ */
/* Stack size per task: 32 KB (8 pages) */ #define TASK_MAX 32 /* max concurrent tasks */
#define TASK_STACK_PAGES 8 #define TASK_STACK_PAGES 8 /* 32 KB stack per task */
#define TASK_STACK_SIZE (TASK_STACK_PAGES * 4096) #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 { typedef enum {
TASK_STATE_FREE = 0, /* PCB slot is unused */ TASK_STATE_FREE = 0, /* PCB slot is unused */
TASK_STATE_READY, /* Runnable, waiting for CPU */ TASK_STATE_READY, /* runnable, waiting to be scheduled */
TASK_STATE_RUNNING, /* Currently executing on CPU */ TASK_STATE_RUNNING, /* currently executing on the CPU */
TASK_STATE_TERMINATED /* Finished execution */ TASK_STATE_TERMINATED /* finished; slot will be recycled */
} TaskState; } TaskState;
/* Task entry function signature */ /* ================================================================
* Task entry function
* ================================================================ */
/* Signature for the function a new task begins executing. */
typedef void (*TaskEntryFn)(void *arg); typedef void (*TaskEntryFn)(void *arg);
/* ================================================================ /* ================================================================
* Process Control Block (PCB) * Process Control Block (PCB)
* ================================================================ */ * ================================================================ */
typedef struct Task { typedef struct Task {
UINT32 pid; /* Process ID */ UINT32 pid; /* unique process ID */
TaskState state; /* Current state */ TaskState state; /* current lifecycle state */
CHAR16 name[TASK_NAME_LEN]; /* Human-readable name */ CHAR16 name[TASK_NAME_LEN]; /* human-readable label */
UINT64 saved_rsp; /* Saved stack pointer (context switch) */
UINT64 stack_base; /* Base address of allocated stack */ /* Context switch state */
UINTN stack_pages; /* Stack size in pages */ UINT64 saved_rsp; /* RSP saved by context_switch() */
TaskEntryFn entry; /* Entry function pointer */ UINT64 stack_base; /* base phys addr of stack alloc */
void *arg; /* Argument passed to entry */ UINTN stack_pages; /* stack size in pages */
UINTN switches; /* Number of times scheduled */
/* Entry point */
TaskEntryFn entry; /* function pointer */
void *arg; /* argument passed to entry() */
/* Scheduling metadata */
UINTN switches; /* number of times scheduled */
} Task; } Task;
/* -------- Task API -------- */ /* ================================================================
* Public API
* ================================================================ */
void task_init(BootInfo *Boot); void task_init(BootInfo *Boot); /* initialise scheduler */
Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg); Task *task_create(const CHAR16 *name, /* spawn a new task */
void task_yield(void); TaskEntryFn entry, void *arg);
void task_exit(void); void task_yield(void); /* voluntarily give up the CPU */
Task *task_current(void); void task_exit(void); /* terminate the current task */
UINTN task_count(void); Task *task_current(void); /* return the running task's PCB */
void task_print_list(BootInfo *Boot); 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); extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
#endif #endif /* TASK_H */