Better docs and structure
This commit is contained in:
191
Makefile
191
Makefile
@@ -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
325
README.md
@@ -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 0–31) and hardware IRQ dispatch
|
- **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
|
- **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 (16–4096 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 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
|
## 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 (0–31) 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 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`
|
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.
|
||||||
|
|||||||
39
boot_info.h
39
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
|
#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 */
|
||||||
|
|||||||
75
commands.c
75
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 <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");
|
||||||
}
|
}
|
||||||
|
|||||||
27
commands.h
27
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
|
#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 */
|
||||||
|
|||||||
@@ -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
68
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"
|
#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
22
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
|
#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
38
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
|
.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
|
||||||
|
|||||||
29
kernel.c
29
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 <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);
|
||||||
|
|||||||
18
kernel.ld
18
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)
|
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
112
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 <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);
|
||||||
|
|
||||||
|
|||||||
71
memory.c
71
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"
|
#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;
|
||||||
|
|||||||
89
memory.h
89
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
|
#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 */
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
|||||||
@@ -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
30
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 "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
86
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
|
#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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user