Compare commits
10 Commits
c2dd9d1e89
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 08cb1db571 | |||
| de161801c4 | |||
| 9b1a70e3a5 | |||
| 7ecf26cbd9 | |||
| d17daf7aac | |||
| a3edb854f4 | |||
| 13a281fa4f | |||
| d449150169 | |||
| 6658e4314b | |||
| 770526efae |
216
Makefile
216
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
|
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,70 +87,120 @@ 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
|
# 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 image
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
ISO_FILE = $(IMAGE_DIR)/os.iso
|
||||||
|
ESP_IMG = $(IMAGE_DIR)/staging/esp.img
|
||||||
|
|
||||||
|
iso: all
|
||||||
|
@echo "Creating FAT ESP image for UEFI boot..."
|
||||||
|
@mkdir -p $(IMAGE_DIR)/staging
|
||||||
|
@# Calculate size: EFI binary + kernel + overhead, rounded up to MiB
|
||||||
|
@SIZE_KB=$$(( ($$(stat -c%s $(EFI_DIR)/$(TARGET)) + $$(stat -c%s $(BUILD_DIR)/$(KERNEL_TARGET)) + 1048576) / 1024 )); \
|
||||||
|
dd if=/dev/zero of=$(ESP_IMG) bs=1K count=$$SIZE_KB 2>/dev/null
|
||||||
|
mkfs.fat -F 12 $(ESP_IMG) >/dev/null
|
||||||
|
mmd -i $(ESP_IMG) ::EFI
|
||||||
|
mmd -i $(ESP_IMG) ::EFI/BOOT
|
||||||
|
mcopy -i $(ESP_IMG) $(EFI_DIR)/$(TARGET) ::EFI/BOOT/BOOTX64.EFI
|
||||||
|
mcopy -i $(ESP_IMG) $(BUILD_DIR)/$(KERNEL_TARGET) ::kernel.elf
|
||||||
|
@echo "Creating ISO image..."
|
||||||
|
xorriso -as mkisofs \
|
||||||
|
-o $(ISO_FILE) \
|
||||||
|
-e esp.img \
|
||||||
|
-no-emul-boot \
|
||||||
|
$(IMAGE_DIR)/staging
|
||||||
|
@echo "ISO image created: $(ISO_FILE)"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Run targets
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Run from ISO with QEMU
|
||||||
|
runiso: iso
|
||||||
|
@echo "Starting QEMU from ISO..."
|
||||||
|
@echo "Using OVMF firmware: $(OVMF_CODE)"
|
||||||
|
@if [ ! -f $(BUILD_DIR)/OVMF_VARS.fd ]; then \
|
||||||
|
echo "Creating OVMF_VARS.fd from template..."; \
|
||||||
|
cp $(OVMF_VARS_TEMPLATE) $(BUILD_DIR)/OVMF_VARS.fd 2>/dev/null || \
|
||||||
|
dd if=/dev/zero of=$(BUILD_DIR)/OVMF_VARS.fd bs=1M count=64 2>/dev/null; \
|
||||||
|
fi
|
||||||
|
$(QEMU) -machine q35 \
|
||||||
|
-drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \
|
||||||
|
-drive if=pflash,format=raw,file=$(BUILD_DIR)/OVMF_VARS.fd \
|
||||||
|
-cdrom $(ISO_FILE) \
|
||||||
|
-net none \
|
||||||
|
-nographic \
|
||||||
|
-no-reboot
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Directory setup
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
# Create necessary directories
|
|
||||||
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 $@
|
|
||||||
|
|
||||||
# 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)"
|
||||||
@@ -155,26 +216,35 @@ 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
|
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 ""
|
||||||
@echo "Targets:"
|
@echo "Targets:"
|
||||||
@echo " all - Build the UEFI application (default)"
|
@echo " all - Build the UEFI application (default)"
|
||||||
@echo " run - Build and run with QEMU"
|
@echo " run - Build and run with QEMU"
|
||||||
|
@echo " iso - Create a bootable ISO image"
|
||||||
|
@echo " runiso - Build, create ISO, and run in QEMU from ISO"
|
||||||
@echo " clean - Remove build artifacts"
|
@echo " clean - Remove build artifacts"
|
||||||
@echo " install-deps - Install required dependencies (Debian/Ubuntu)"
|
@echo " install-deps - Install required dependencies (Debian/Ubuntu)"
|
||||||
@echo " help - Display this help message"
|
@echo " help - Display this help message"
|
||||||
@@ -182,5 +252,7 @@ help:
|
|||||||
@echo "Usage:"
|
@echo "Usage:"
|
||||||
@echo " make # Build the OS"
|
@echo " make # Build the OS"
|
||||||
@echo " make run # Build and run in QEMU"
|
@echo " make run # Build and run in QEMU"
|
||||||
|
@echo " make iso # Create ISO image"
|
||||||
|
@echo " make runiso # Build, create ISO, and run in QEMU from ISO"
|
||||||
@echo " make clean # Clean build files"
|
@echo " make clean # Clean build files"
|
||||||
@echo " make install-deps # Install dependencies"
|
@echo " make install-deps # Install dependencies"
|
||||||
|
|||||||
371
README.md
371
README.md
@@ -1,25 +1,33 @@
|
|||||||
# Simple UEFI Operating System
|
# Simple UEFI Operating System
|
||||||
|
|
||||||
A minimal bootable UEFI operating system written in C that demonstrates basic UEFI functionality.
|
A minimal bootable UEFI operating system written in C that demonstrates UEFI boot loading, memory management, interrupt handling, and cooperative multitasking.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **UEFI Boot**: Boots directly on UEFI firmware
|
- **UEFI Boot** — boots directly on UEFI firmware via a PE32+ loader
|
||||||
- **Console I/O**: Interactive keyboard input and screen output
|
- **ELF64 Kernel Loader** — reads and maps a standalone kernel from the EFI partition
|
||||||
- **System Information**: Displays firmware details
|
- **Console I/O** — interactive keyboard input and screen output
|
||||||
- **Kernel Loader**: Loads an ELF64 kernel from the EFI partition
|
- **Interrupt Handling** — IDT setup with CPU exception handlers (vectors 0–31) and hardware IRQ dispatch
|
||||||
- **Simple Commands**: Minimal interactive command interface
|
- **Memory Management** — bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing
|
||||||
|
- **Cooperative Multitasking** — process control blocks, assembly context switching, and a round-robin task scheduler
|
||||||
|
- **Interactive Shell** — command registry with `help`, `man`, built-in commands, and test utilities
|
||||||
|
|
||||||
## Requirements
|
---
|
||||||
|
|
||||||
- GCC compiler
|
## 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
|
||||||
@@ -28,171 +36,294 @@ make install-deps
|
|||||||
Or manually:
|
Or manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make
|
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
|
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
|
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building
|
</details>
|
||||||
|
|
||||||
Build the UEFI application and kernel:
|
### Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make
|
make # build both UEFI loader and kernel
|
||||||
```
|
```
|
||||||
|
|
||||||
This will create `build/EFI/BOOT/BOOTX64.EFI` and `build/kernel.elf`.
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
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 in the OS
|
### 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 |
|
||||||
|
|
||||||
- `help` - Display all available commands
|
---
|
||||||
- `man <command>` - Display detailed help for a command
|
|
||||||
- `shutdown` - Shutdown the system
|
## Usage
|
||||||
- `clear` - Clear the screen
|
|
||||||
- `about` - Display system information
|
### Shell Commands
|
||||||
|
|
||||||
|
Once the OS boots, the Starling Terminal displays an interactive prompt (`starling>`). The following built-in commands are available:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `help` | List all available commands |
|
||||||
|
| `man <command>` | Show detailed help for a command |
|
||||||
|
| `shutdown` | Shut down the system |
|
||||||
|
| `clear` | Clear the screen |
|
||||||
|
| `about` | Display system information |
|
||||||
|
| `mem` | Display memory statistics (PMM, heap, paging) |
|
||||||
|
| `ps` | List all active tasks with PID, state, and name |
|
||||||
|
| `spawn [name]` | Create a demo background task (optional argument sets the task name) |
|
||||||
|
| `memtest` | Run memory allocation/deallocation and PMM tests |
|
||||||
|
| `tasktest` | Spawn multiple concurrent tasks to test the scheduler |
|
||||||
|
|
||||||
|
In addition, the Starling Terminal itself recognises:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `starling` | Spawn a nested Starling Terminal at increased depth |
|
||||||
|
| `exit` | Exit the current nested Starling Terminal (not allowed at depth 0) |
|
||||||
|
|
||||||
|
### Example Session
|
||||||
|
|
||||||
Example usage:
|
|
||||||
```
|
```
|
||||||
-> help
|
-> help
|
||||||
-> man shutdown
|
|
||||||
-> clear
|
|
||||||
-> about
|
-> about
|
||||||
|
-> mem
|
||||||
|
-> memtest
|
||||||
|
-> spawn worker1
|
||||||
|
-> spawn worker2
|
||||||
|
-> ps
|
||||||
|
-> tasktest
|
||||||
-> shutdown
|
-> shutdown
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing Memory Management
|
||||||
|
|
||||||
|
The `memtest` command runs four phases:
|
||||||
|
|
||||||
|
1. **Heap allocation** — allocates 8 blocks of increasing size (16–4096 bytes) via `kmalloc()`
|
||||||
|
2. **Heap free** — frees all blocks via `kfree()` and verifies coalescing
|
||||||
|
3. **PMM single page** — allocates and frees a single 4 KB page
|
||||||
|
4. **PMM multi-page** — allocates and frees 4 contiguous pages
|
||||||
|
|
||||||
|
Each phase reports addresses, success/failure, and usage statistics.
|
||||||
|
|
||||||
|
### Testing Task Scheduling
|
||||||
|
|
||||||
|
The `tasktest` command:
|
||||||
|
|
||||||
|
1. Spawns 3 worker tasks (`worker-A`, `worker-B`, `worker-C`)
|
||||||
|
2. Each worker prints 3 progress steps, yielding between each
|
||||||
|
3. The shell yields to let workers run in round-robin order
|
||||||
|
4. Displays the task list after completion
|
||||||
|
|
||||||
|
You can also test manually with `spawn` and `ps`:
|
||||||
|
|
||||||
|
```
|
||||||
|
-> spawn myTask
|
||||||
|
Created task 'myTask' (PID 1)
|
||||||
|
-> ps
|
||||||
|
PID STATE SWITCHES NAME
|
||||||
|
--- ---------- -------- ----
|
||||||
|
0 RUNNING 5 kernel
|
||||||
|
1 READY 0 myTask
|
||||||
|
|
||||||
|
Active tasks: 2 / 32
|
||||||
|
```
|
||||||
|
|
||||||
|
Background tasks run whenever the shell yields (which happens automatically while waiting for keyboard input).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Boot Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
UEFI Firmware
|
||||||
|
└─ efi_main() [main.c] PE32+ UEFI application
|
||||||
|
├─ InitializeLib() GNU-EFI setup
|
||||||
|
├─ read_file_to_buffer() read kernel.elf from ESP
|
||||||
|
├─ load_elf_kernel() parse ELF64, map PT_LOAD segments
|
||||||
|
├─ populate BootInfo wrap UEFI services as fn pointers
|
||||||
|
└─ kmain(&Boot) [kernel.c] jump to kernel
|
||||||
|
├─ idt_init() install exception handlers
|
||||||
|
├─ memory_init() PMM → paging → heap
|
||||||
|
├─ task_init() scheduler + task 0
|
||||||
|
└─ Starling Terminal task interactive read-eval-print loop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
|
||||||
|
| Layer | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages). Supports single and contiguous multi-page allocation. |
|
||||||
|
| **Paging** | Walks and creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT). Supports map, unmap, and virtual-to-physical translation for 4 KB, 2 MB, and 1 GB page sizes. |
|
||||||
|
| **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing. 16-byte alignment. Magic-number corruption detection. |
|
||||||
|
|
||||||
|
### Task System
|
||||||
|
|
||||||
|
| Aspect | Detail |
|
||||||
|
|--------|--------|
|
||||||
|
| **PCB** | `Task` struct: PID, state, name, saved RSP, stack base, entry function, scheduling metadata |
|
||||||
|
| **States** | `FREE` → `READY` → `RUNNING` → `TERMINATED` → `FREE` |
|
||||||
|
| **Scheduler** | Round-robin among `READY` tasks via `task_yield()` |
|
||||||
|
| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12`–`r15`) and `RFLAGS`, then swaps RSP |
|
||||||
|
| **Stack** | 32 KB (8 pages) per task, allocated from PMM, freed on `task_exit()` |
|
||||||
|
| **Limits** | Up to 32 concurrent tasks; cooperative (voluntary yield) only |
|
||||||
|
|
||||||
|
### Command System
|
||||||
|
|
||||||
|
Commands are registered in a static array in `commands.c`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static Command commands[] = {
|
||||||
|
{ L"name", L"description", L"usage", handler_fn },
|
||||||
|
...
|
||||||
|
{ NULL, NULL, NULL, NULL } /* sentinel */
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`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
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── main.c # UEFI loader (reads and loads kernel ELF)
|
├── main.c # UEFI loader — reads kernel.elf, sets up BootInfo
|
||||||
├── kernel.c # Kernel entry point and main loop
|
├── kernel.c # Kernel entry point (kmain) and interactive shell
|
||||||
├── commands.c # Command registry and handlers
|
├── kernel.ld # Linker script — kernel loaded at 0x100000
|
||||||
├── commands.h # Command interface definitions
|
├── boot_info.h # BootInfo struct shared between loader and kernel
|
||||||
├── string_utils.c # String utility functions
|
│
|
||||||
├── string_utils.h # String utility declarations
|
├── commands.c # Command registry and all handler implementations
|
||||||
├── kernel.ld # Kernel linker script
|
├── commands.h # Command type and dispatch API
|
||||||
├── boot_info.h # Shared boot interface
|
├── string_utils.c # CHAR16 string helpers (trim, compare)
|
||||||
├── Makefile # Build configuration
|
├── string_utils.h # String utility declarations
|
||||||
└── README.md # This file
|
│
|
||||||
|
├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
## How It Works
|
---
|
||||||
|
|
||||||
1. **UEFI Entry Point**: The `efi_main` function is the entry point called by UEFI firmware
|
## Development
|
||||||
2. **Initialization**: GNU-EFI library is initialized with the system table
|
|
||||||
3. **Console Setup**: Output is configured and screen is cleared
|
|
||||||
4. **Kernel Load**: The loader reads `kernel.elf` and maps its loadable segments
|
|
||||||
5. **Main Loop**: The kernel enters a loop waiting for keyboard input
|
|
||||||
6. **Command Processing**: User commands are parsed and dispatched to registered handlers
|
|
||||||
7. **Command Execution**: Each command is executed via its handler function
|
|
||||||
8. **Shutdown**: UEFI runtime services are used to shutdown the system
|
|
||||||
|
|
||||||
**Command System Architecture:**
|
### Adding a New Command
|
||||||
- Commands are registered in a static array with metadata (name, description, usage, handler)
|
|
||||||
- The `execute_command()` function parses input, splits command and arguments, and dispatches to handlers
|
|
||||||
- The `man` command provides detailed help by looking up command metadata
|
|
||||||
- New commands can be easily added by implementing a handler and registering it
|
|
||||||
|
|
||||||
## Building for Real Hardware
|
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`
|
||||||
|
|
||||||
To create a bootable USB drive:
|
### 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) |
|
||||||
- **Executable Format**: PE32+ (Portable Executable for EFI)
|
| Firmware Interface | UEFI 2.x |
|
||||||
- **Boot Protocol**: UEFI Boot Services
|
| Development Library | GNU-EFI |
|
||||||
|
| Loader Format | PE32+ (EFI application) |
|
||||||
## License
|
| Kernel Format | ELF64 (static, loaded at 0x100000) |
|
||||||
|
| Boot Protocol | UEFI Boot Services |
|
||||||
This is a minimal educational example. Feel free to use and modify as needed.
|
| Memory Model | Identity-mapped (UEFI), 4-level paging (kernel) |
|
||||||
|
| Scheduling | Cooperative round-robin multitasking |
|
||||||
## Further Development
|
|
||||||
|
|
||||||
The codebase is now modular and easy to extend:
|
|
||||||
|
|
||||||
**Adding New Commands:**
|
|
||||||
1. Open `commands.c`
|
|
||||||
2. Create a new handler function: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args)`
|
|
||||||
3. Add your command to the `commands[]` array with name, description, usage, and handler
|
|
||||||
4. Rebuild with `make`
|
|
||||||
|
|
||||||
**Other Ideas for expansion:**
|
|
||||||
- File system support
|
|
||||||
- Memory management
|
|
||||||
- Graphics output
|
|
||||||
- Network stack
|
|
||||||
- Device drivers
|
|
||||||
- Multi-tasking
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
64
boot_info.h
64
boot_info.h
@@ -1,19 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* boot_info.h – Shared interface between the firmware loader and kernel.
|
||||||
|
*
|
||||||
|
* The BootInfo struct is populated by the platform-specific loader
|
||||||
|
* (currently the UEFI loader in main.c) and passed to the kernel entry
|
||||||
|
* point (kmain). It exposes only generic types and function pointers so
|
||||||
|
* that the kernel remains independent of any particular firmware API.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef BOOT_INFO_H
|
#ifndef BOOT_INFO_H
|
||||||
#define BOOT_INFO_H
|
#define BOOT_INFO_H
|
||||||
|
|
||||||
#include <efi.h>
|
/*
|
||||||
|
* Status code returned by loader-provided services (0 = success).
|
||||||
|
* The underlying integer type (UINT64) must be provided by including
|
||||||
|
* either the firmware headers (e.g. <efi.h>) or the kernel type
|
||||||
|
* header before including this file.
|
||||||
|
*/
|
||||||
|
typedef UINT64 KSTATUS;
|
||||||
|
|
||||||
|
/* Simple key event used by the kernel's input path. */
|
||||||
|
typedef struct {
|
||||||
|
UINT16 scan_code;
|
||||||
|
CHAR16 unicode_char;
|
||||||
|
} KeyEvent;
|
||||||
|
|
||||||
|
/* Printf-style print function (typically backed by firmware console). */
|
||||||
typedef UINTN (*KernelPrintFn)(const CHAR16 *Format, ...);
|
typedef UINTN (*KernelPrintFn)(const CHAR16 *Format, ...);
|
||||||
|
|
||||||
|
/* Console output helpers. */
|
||||||
|
typedef KSTATUS (*ConsoleClearFn)(void);
|
||||||
|
typedef KSTATUS (*ConsoleSetAttrFn)(UINTN Attribute);
|
||||||
|
|
||||||
|
/* Keyboard input helpers. */
|
||||||
|
typedef KSTATUS (*KeyReadFn)(KeyEvent *Key);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BootInfo – everything the kernel needs from the loader at boot time.
|
||||||
|
*
|
||||||
|
* 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;
|
/* Console I/O */
|
||||||
KernelPrintFn print;
|
KernelPrintFn print; /* formatted text output */
|
||||||
EFI_STATUS (*clear_screen)(void);
|
ConsoleClearFn clear_screen; /* clear the console */
|
||||||
EFI_STATUS (*set_attribute)(UINTN Attribute);
|
ConsoleSetAttrFn set_attribute; /* set text colour/attr */
|
||||||
EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key);
|
KeyReadFn read_key; /* blocking key read */
|
||||||
void (*shutdown)(void);
|
KeyReadFn try_read_key; /* non-blocking key read */
|
||||||
|
|
||||||
|
/* System control */
|
||||||
|
void (*shutdown)(void); /* power-off the machine */
|
||||||
|
|
||||||
|
/* Physical memory */
|
||||||
|
KSTATUS (*alloc_pages)(UINTN pages, UINT64 *addr);
|
||||||
|
KSTATUS (*free_pages)(UINT64 addr, UINTN pages);
|
||||||
|
|
||||||
|
/* Firmware metadata (for informational commands only). */
|
||||||
|
const CHAR16 *firmware_vendor;
|
||||||
|
UINT32 firmware_major;
|
||||||
|
UINT32 firmware_minor;
|
||||||
} BootInfo;
|
} BootInfo;
|
||||||
|
|
||||||
|
/* Kernel entry point signature (called by the loader). */
|
||||||
typedef void (*KernelEntryFn)(BootInfo *Boot);
|
typedef void (*KernelEntryFn)(BootInfo *Boot);
|
||||||
|
|
||||||
#endif
|
#endif /* BOOT_INFO_H */
|
||||||
|
|||||||
489
commands.c
489
commands.c
@@ -1,7 +1,23 @@
|
|||||||
#include <efi.h>
|
/*
|
||||||
|
* 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 "kernel_types.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "string_utils.h"
|
#include "string_utils.h"
|
||||||
|
#include "memory.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) { \
|
||||||
@@ -9,80 +25,185 @@
|
|||||||
} \
|
} \
|
||||||
} 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);
|
||||||
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
|
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
|
||||||
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
|
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_mem(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_ps(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_kusr(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
|
||||||
|
/* Small helper struct used to pass arguments into per-command tasks. */
|
||||||
|
typedef struct {
|
||||||
|
BootInfo *Boot;
|
||||||
|
CommandHandlerFn handler;
|
||||||
|
CHAR16 args[128];
|
||||||
|
} CommandTaskContext;
|
||||||
|
|
||||||
|
static void command_task_entry(void *arg);
|
||||||
|
|
||||||
|
/* Local string copy helper (wide-char, bounded). */
|
||||||
|
static void wstrcpy16_local(CHAR16 *dst, const CHAR16 *src, UINTN max)
|
||||||
|
{
|
||||||
|
UINTN i = 0;
|
||||||
|
|
||||||
|
if (dst == NULL || max == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src != NULL && src[i] != L'\0' && i < (max - 1)) {
|
||||||
|
dst[i] = src[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[i] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Command registry
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
// Command registry
|
|
||||||
static Command commands[] = {
|
static Command commands[] = {
|
||||||
{
|
{
|
||||||
L"shutdown",
|
L"shutdown",
|
||||||
L"Shutdown the system",
|
L"Shutdown the system",
|
||||||
L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.",
|
L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.",
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
cmd_shutdown
|
cmd_shutdown
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
L"help",
|
L"help",
|
||||||
L"Display available commands",
|
L"Display available commands",
|
||||||
L"Usage: help\n\r Lists all available commands with brief descriptions.",
|
L"Usage: help\n\r Lists all available commands with brief descriptions.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
cmd_help
|
cmd_help
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
L"man",
|
L"man",
|
||||||
L"Display manual page for a command",
|
L"Display manual page for a command",
|
||||||
L"Usage: man <command>\n\r Shows detailed help for the specified command.",
|
L"Usage: man <command>\n\r Shows detailed help for the specified command.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
cmd_man
|
cmd_man
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
L"clear",
|
L"clear",
|
||||||
L"Clear the screen",
|
L"Clear the screen",
|
||||||
L"Usage: clear\n\r Clears the console screen.",
|
L"Usage: clear\n\r Clears the console screen.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
cmd_clear
|
cmd_clear
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
L"about",
|
L"about",
|
||||||
L"Display system information",
|
L"Display system information",
|
||||||
L"Usage: about\n\r Shows information about this operating system.",
|
L"Usage: about\n\r Shows information about this operating system.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
cmd_about
|
cmd_about
|
||||||
},
|
},
|
||||||
{NULL, NULL, NULL, NULL} // Sentinel
|
{
|
||||||
|
L"mem",
|
||||||
|
L"Display memory statistics",
|
||||||
|
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
|
cmd_mem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"ps",
|
||||||
|
L"List running tasks",
|
||||||
|
L"Usage: ps\n\r Displays all active tasks with PID, state, and name.",
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_ps
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"spawn",
|
||||||
|
L"Spawn a demo background task",
|
||||||
|
L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.",
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_spawn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"memtest",
|
||||||
|
L"Test memory allocation and deallocation",
|
||||||
|
L"Usage: memtest\n\r"
|
||||||
|
L" Run memory tests in four phases:\n\r"
|
||||||
|
L" 1) Heap allocation of 8 blocks (16–4096 bytes) via kmalloc()\n\r"
|
||||||
|
L" 2) Heap free and coalescing verification via kfree()\n\r"
|
||||||
|
L" 3) Single-page PMM allocate/free via pmm_alloc_page()/pmm_free_page()\n\r"
|
||||||
|
L" 4) Multi-page (4-page) PMM allocate/free via pmm_alloc_pages()/pmm_free_pages()\n\r",
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
|
cmd_memtest
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"tasktest",
|
||||||
|
L"Test task scheduler with multiple tasks",
|
||||||
|
L"Usage: tasktest\n\r"
|
||||||
|
L" Spawns three worker tasks (worker-A/B/C) that run cooperatively,\n\r"
|
||||||
|
L" each printing three progress steps and yielding between them.\n\r"
|
||||||
|
L" After the workers finish, prints the final task list to\n\r"
|
||||||
|
L" demonstrate the cooperative round-robin scheduler.",
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_tasktest
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"kusr",
|
||||||
|
L"Run a command with kernel privilege",
|
||||||
|
L"Usage: kusr <command> [args...]\n\r"
|
||||||
|
L" Temporarily elevates the current task to kernel privilege,\n\r"
|
||||||
|
L" executes the given command, then restores the original level.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_kusr
|
||||||
|
},
|
||||||
|
{NULL, NULL, NULL, 0, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* System control
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
static void request_shutdown(BootInfo *Boot)
|
static void request_shutdown(BootInfo *Boot)
|
||||||
{
|
{
|
||||||
|
Task *caller;
|
||||||
|
|
||||||
if (Boot == NULL) {
|
if (Boot == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: shutdown requires KERNEL. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: shutdown requires kernel privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Boot->shutdown != NULL) {
|
if (Boot->shutdown != NULL) {
|
||||||
Boot->shutdown();
|
Boot->shutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Boot->SystemTable != NULL &&
|
|
||||||
Boot->SystemTable->RuntimeServices != NULL &&
|
|
||||||
Boot->SystemTable->RuntimeServices->ResetSystem != NULL) {
|
|
||||||
Boot->SystemTable->RuntimeServices->ResetSystem(
|
|
||||||
EfiResetShutdown, EFI_SUCCESS, 0, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +237,14 @@ 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;
|
KSTATUS 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();
|
||||||
if (EFI_ERROR(Status)) {
|
if (Status != 0) {
|
||||||
SAFE_PRINT(Boot, L"Failed to clear screen: %r\n\r", Status);
|
SAFE_PRINT(Boot, L"Failed to clear screen (status=%ld)\n\r",
|
||||||
|
(UINT64)Status);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SAFE_PRINT(Boot, L"Clear screen function unavailable.\n\r");
|
SAFE_PRINT(Boot, L"Clear screen function unavailable.\n\r");
|
||||||
@@ -131,25 +253,263 @@ 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 64-bit 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");
|
||||||
SAFE_PRINT(Boot, L"A minimal bootable UEFI operating system written in C.\n\r");
|
SAFE_PRINT(Boot, L"A minimal bootable operating system written in C.\n\r");
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
SAFE_PRINT(Boot, L"Features:\n\r");
|
SAFE_PRINT(Boot, L"Features:\n\r");
|
||||||
SAFE_PRINT(Boot, L" - UEFI Boot\n\r");
|
SAFE_PRINT(Boot, L" - Flexible bootloader interface\n\r");
|
||||||
SAFE_PRINT(Boot, L" - Console I/O\n\r");
|
SAFE_PRINT(Boot, L" - Console I/O\n\r");
|
||||||
SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\n\r");
|
SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" - Memory Management (PMM + Heap)\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" - Cooperative Multitasking\n\r");
|
||||||
SAFE_PRINT(Boot, L" - Interactive Command Interface\n\r");
|
SAFE_PRINT(Boot, L" - Interactive Command Interface\n\r");
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
SAFE_PRINT(Boot, L"Type 'help' for available commands.\n\r");
|
SAFE_PRINT(Boot, L"Type 'help' for available commands.\n\r");
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cmd_mem(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
(void)Args;
|
||||||
|
memory_print_stats(Boot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Demo task – runs cooperatively, prints progress, then exits
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void demo_task_fn(void *arg)
|
||||||
|
{
|
||||||
|
BootInfo *Boot = (BootInfo *)arg;
|
||||||
|
Task *self = task_current();
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[%s:%d] started\n\r", self->name, self->pid);
|
||||||
|
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
SAFE_PRINT(Boot, L"[%s:%d] tick %d/5\n\r",
|
||||||
|
self->name, self->pid, i + 1);
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[%s:%d] finished\n\r", self->name, self->pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_ps(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
(void)Args;
|
||||||
|
task_print_list(Boot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
Task *t;
|
||||||
|
const CHAR16 *name;
|
||||||
|
|
||||||
|
if (Args != NULL && Args[0] != L'\0') {
|
||||||
|
trim_spaces_inplace(Args);
|
||||||
|
name = Args;
|
||||||
|
} else {
|
||||||
|
name = L"demo";
|
||||||
|
}
|
||||||
|
|
||||||
|
t = task_create(name, demo_task_fn, Boot);
|
||||||
|
if (t == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to create task (out of slots or memory).\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Created task '%s' (PID %d)\n\r", t->name, t->pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* memtest – exercise the heap and PMM allocators
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
void *ptrs[8];
|
||||||
|
UINTN sizes[] = { 16, 64, 128, 256, 512, 1024, 2048, 4096 };
|
||||||
|
UINTN i;
|
||||||
|
UINT64 page;
|
||||||
|
UINTN h_total, h_used, h_free, h_blocks;
|
||||||
|
Task *caller;
|
||||||
|
(void)Args;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: memtest requires KERNEL. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: memtest requires kernel privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"Memory Test\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
|
/* --- Heap allocation test --- */
|
||||||
|
SAFE_PRINT(Boot, L"[1] Heap allocation test\n\r");
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
ptrs[i] = kmalloc(sizes[i]);
|
||||||
|
if (ptrs[i] != NULL) {
|
||||||
|
SAFE_PRINT(Boot, L" kmalloc(%4d) = 0x%lx OK\n\r",
|
||||||
|
sizes[i], (UINT64)(UINTN)ptrs[i]);
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L" kmalloc(%4d) = NULL FAIL\n\r", sizes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
|
||||||
|
SAFE_PRINT(Boot, L" Heap after alloc: used=%d free=%d blocks=%d\n\r",
|
||||||
|
h_used, h_free, h_blocks);
|
||||||
|
|
||||||
|
/* --- Heap free test --- */
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[2] Heap free test\n\r");
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if (ptrs[i] != NULL) {
|
||||||
|
kfree(ptrs[i]);
|
||||||
|
SAFE_PRINT(Boot, L" kfree(0x%lx) OK\n\r",
|
||||||
|
(UINT64)(UINTN)ptrs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
|
||||||
|
SAFE_PRINT(Boot, L" Heap after free: used=%d free=%d blocks=%d\n\r",
|
||||||
|
h_used, h_free, h_blocks);
|
||||||
|
|
||||||
|
/* --- PMM page allocation test --- */
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[3] PMM page allocation test\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Free pages before: %d\n\r", pmm_get_free_pages());
|
||||||
|
|
||||||
|
page = pmm_alloc_page();
|
||||||
|
if (page != 0) {
|
||||||
|
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0x%lx OK\n\r", page);
|
||||||
|
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
|
||||||
|
pmm_free_page(page);
|
||||||
|
SAFE_PRINT(Boot, L" pmm_free_page() OK\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0 FAIL (out of pages)\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Multi-page allocation test --- */
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[4] PMM multi-page allocation test (4 pages)\n\r");
|
||||||
|
page = pmm_alloc_pages(4);
|
||||||
|
if (page != 0) {
|
||||||
|
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0x%lx OK\n\r", page);
|
||||||
|
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
|
||||||
|
pmm_free_pages(page, 4);
|
||||||
|
SAFE_PRINT(Boot, L" pmm_free_pages() OK\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0 FAIL\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\rAll memory tests completed.\n\r\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* tasktest – spawn multiple concurrent tasks to exercise scheduler
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void worker_task_fn(void *arg)
|
||||||
|
{
|
||||||
|
BootInfo *Boot = (BootInfo *)arg;
|
||||||
|
Task *self = task_current();
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
SAFE_PRINT(Boot, L" [%s:%d] working... step %d/3\n\r",
|
||||||
|
self->name, self->pid, i + 1);
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" [%s:%d] done\n\r", self->name, self->pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
Task *t1, *t2, *t3;
|
||||||
|
UINTN i;
|
||||||
|
(void)Args;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"Task Scheduler Test\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Spawning 3 worker tasks...\n\r\n\r");
|
||||||
|
|
||||||
|
t1 = task_create(L"worker-A", worker_task_fn, Boot);
|
||||||
|
t2 = task_create(L"worker-B", worker_task_fn, Boot);
|
||||||
|
t3 = task_create(L"worker-C", worker_task_fn, Boot);
|
||||||
|
|
||||||
|
if (t1 == NULL || t2 == NULL || t3 == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to create one or more tasks.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Created: %s (PID %d), %s (PID %d), %s (PID %d)\n\r",
|
||||||
|
t1->name, t1->pid, t2->name, t2->pid, t3->name, t3->pid);
|
||||||
|
SAFE_PRINT(Boot, L"\n\rYielding to let workers run:\n\r\n\r");
|
||||||
|
|
||||||
|
/* Yield enough times for all workers to complete (3 tasks x 3 steps) */
|
||||||
|
for (i = 0; i < 12; i++) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\rTask list after test:\n\r");
|
||||||
|
task_print_list(Boot);
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* kusr – run a command with escalated privilege
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void cmd_kusr(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
Task *self;
|
||||||
|
TaskPrivilege saved_priv;
|
||||||
|
|
||||||
|
if (Args == NULL || Args[0] == L'\0') {
|
||||||
|
SAFE_PRINT(Boot, L"Usage: kusr <command> [args...]\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self = task_current();
|
||||||
|
if (self == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"kusr: no task context available.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Elevate, dispatch, restore. */
|
||||||
|
saved_priv = task_get_privilege(self);
|
||||||
|
task_set_privilege(self, TASK_PRIV_KERNEL);
|
||||||
|
|
||||||
|
{
|
||||||
|
Task *cmd_task = execute_command(Boot, Args, TASK_PRIV_KERNEL);
|
||||||
|
if (cmd_task != NULL) {
|
||||||
|
task_wait(cmd_task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task_set_privilege(self, saved_priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 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;
|
||||||
@@ -161,7 +521,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') {
|
||||||
@@ -175,7 +535,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++;
|
||||||
@@ -192,51 +552,110 @@ void show_help(BootInfo *Boot)
|
|||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
void execute_command(BootInfo *Boot, CHAR16 *Input)
|
/*
|
||||||
|
* Parse a line of user input into command + arguments and dispatch
|
||||||
|
* by spawning a dedicated task for the matching handler. Unknown
|
||||||
|
* commands print an error.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* - Pointer to the spawned Task for the command, or NULL if the
|
||||||
|
* command was not found or had to run synchronously.
|
||||||
|
*/
|
||||||
|
Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv)
|
||||||
{
|
{
|
||||||
CHAR16 *cmd_start = NULL;
|
CHAR16 *cmd_start = NULL;
|
||||||
CHAR16 *args_start = NULL;
|
CHAR16 *args_start = NULL;
|
||||||
UINTN i = 0;
|
UINTN i = 0;
|
||||||
|
CommandTaskContext *ctx;
|
||||||
|
Task *t;
|
||||||
|
|
||||||
if (Boot == NULL || Input == NULL) {
|
if (Boot == NULL || Input == NULL) {
|
||||||
return;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
trim_spaces_inplace(Input);
|
trim_spaces_inplace(Input);
|
||||||
|
|
||||||
if (Input[0] == L'\0') {
|
if (Input[0] == L'\0') {
|
||||||
return;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
/* Allocate a context block for the command task. */
|
||||||
return;
|
ctx = (CommandTaskContext *)kmalloc(sizeof(CommandTaskContext));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to allocate command context; running in core thread.\n\r");
|
||||||
|
commands[i].handler(Boot, args_start);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->Boot = Boot;
|
||||||
|
ctx->handler = commands[i].handler;
|
||||||
|
wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0]));
|
||||||
|
|
||||||
|
t = task_create_with_priv(commands[i].name,
|
||||||
|
command_task_entry,
|
||||||
|
ctx,
|
||||||
|
caller_priv);
|
||||||
|
if (t == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r",
|
||||||
|
commands[i].name);
|
||||||
|
kfree(ctx);
|
||||||
|
commands[i].handler(Boot, args_start);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[starling] spawned '%s' as PID %d\n\r", t->name, t->pid);
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Command task entry – executes one command in its own task context
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void command_task_entry(void *arg)
|
||||||
|
{
|
||||||
|
CommandTaskContext *ctx = (CommandTaskContext *)arg;
|
||||||
|
BootInfo *Boot = NULL;
|
||||||
|
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot = ctx->Boot;
|
||||||
|
|
||||||
|
if (ctx->handler != NULL) {
|
||||||
|
ctx->handler(Boot, ctx->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context was heap-allocated in execute_command. */
|
||||||
|
kfree(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
36
commands.h
36
commands.h
@@ -1,18 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
#include "task.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;
|
TaskPrivilege min_priv; /* minimum privilege required */
|
||||||
|
CommandHandlerFn handler; /* function that executes the cmd */
|
||||||
} Command;
|
} Command;
|
||||||
|
|
||||||
void execute_command(BootInfo *Boot, CHAR16 *Input);
|
/* Parse and dispatch a line of user input.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* - Pointer to a Task representing the spawned command process,
|
||||||
|
* or NULL if the command was not found or ran synchronously.
|
||||||
|
*/
|
||||||
|
Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv);
|
||||||
|
|
||||||
|
/* Print a formatted list of all registered commands. */
|
||||||
void show_help(BootInfo *Boot);
|
void show_help(BootInfo *Boot);
|
||||||
|
|
||||||
#endif
|
#endif /* COMMANDS_H */
|
||||||
|
|||||||
52
context_switch.S
Normal file
52
context_switch.S
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* context_switch.S – Cooperative context switch for x86-64.
|
||||||
|
*
|
||||||
|
* void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
|
||||||
|
*
|
||||||
|
* %rdi = pointer to save current RSP (old task's PCB field)
|
||||||
|
* %rsi = RSP value to restore (new task's saved RSP)
|
||||||
|
*
|
||||||
|
* Saves callee-saved registers (rbp, rbx, r12-r15) and RFLAGS on
|
||||||
|
* the current stack, stores RSP into *old_rsp, loads new_rsp into
|
||||||
|
* RSP, restores registers, and returns into the new task's context.
|
||||||
|
*
|
||||||
|
* This is a standard cooperative switch: only callee-saved state
|
||||||
|
* needs preserving because the C calling convention guarantees that
|
||||||
|
* caller-saved registers are already handled by the compiler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.text
|
||||||
|
.global context_switch
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Entry: save old context, load new context, return
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
context_switch:
|
||||||
|
/* Save callee-saved registers and flags on the current stack */
|
||||||
|
pushq %rbp
|
||||||
|
pushq %rbx
|
||||||
|
pushq %r12
|
||||||
|
pushq %r13
|
||||||
|
pushq %r14
|
||||||
|
pushq %r15
|
||||||
|
pushfq
|
||||||
|
|
||||||
|
/* Store current RSP into *old_rsp */
|
||||||
|
movq %rsp, (%rdi)
|
||||||
|
|
||||||
|
/* Switch to the new task's stack */
|
||||||
|
movq %rsi, %rsp
|
||||||
|
|
||||||
|
/* Restore callee-saved registers and flags from the new stack */
|
||||||
|
popfq
|
||||||
|
popq %r15
|
||||||
|
popq %r14
|
||||||
|
popq %r13
|
||||||
|
popq %r12
|
||||||
|
popq %rbx
|
||||||
|
popq %rbp
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
.section .note.GNU-stack,"",@progbits
|
||||||
528
docs/commands-and-terminal.md
Normal file
528
docs/commands-and-terminal.md
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
## Starling Terminal and command pipeline
|
||||||
|
|
||||||
|
User interaction with the kernel is mediated by the **Starling Terminal** and a command registry implemented in `kernel.c` and `commands.c`.
|
||||||
|
|
||||||
|
- The **Starling Terminal** is a task that runs a read–eval–print loop, reading keystrokes via `BootInfo` services and dispatching commands.
|
||||||
|
- The **command subsystem** maintains a table of commands, each with a name, description, usage string, minimum privilege level, and handler function.
|
||||||
|
- Each command typically runs in its own task so that long-running work does not block the terminal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Starling Terminal task
|
||||||
|
|
||||||
|
The Starling Terminal is implemented as a task entry function in `kernel.c`:
|
||||||
|
|
||||||
|
```47:163:/home/lochlan/Documents/Coding/c/os/kernel.c
|
||||||
|
static void starling_terminal_task(void *arg)
|
||||||
|
{
|
||||||
|
StarlingContext *ctx = (StarlingContext *)arg;
|
||||||
|
BootInfo *Boot = NULL;
|
||||||
|
KeyEvent Key;
|
||||||
|
KSTATUS Status;
|
||||||
|
UINTN read_errors = 0;
|
||||||
|
CHAR16 line[128];
|
||||||
|
UINTN len = 0;
|
||||||
|
UINTN depth = 0;
|
||||||
|
TaskPrivilege shell_priv;
|
||||||
|
|
||||||
|
if (ctx == NULL || ctx->Boot == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot = ctx->Boot;
|
||||||
|
depth = ctx->depth;
|
||||||
|
shell_priv = ctx->shell_priv;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r",
|
||||||
|
depth, (INT32)shell_priv);
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
/* Try non-blocking read first; yield to other tasks while idle */
|
||||||
|
if (Boot->try_read_key != NULL) {
|
||||||
|
Status = Boot->try_read_key(&Key);
|
||||||
|
if (Status != 0) {
|
||||||
|
task_yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (Boot->read_key != NULL) {
|
||||||
|
Status = Boot->read_key(&Key);
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Status != 0) {
|
||||||
|
read_errors++;
|
||||||
|
if (read_errors == 1 || (read_errors % 64) == 0) {
|
||||||
|
SAFE_PRINT(Boot, L"read_key failed (status=%ld)\n\r",
|
||||||
|
(UINT64)Status);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
read_errors = 0;
|
||||||
|
|
||||||
|
if (Key.unicode_char == L'\r' || Key.unicode_char == L'\n') {
|
||||||
|
/* Enter pressed: execute the buffered command */
|
||||||
|
line[len] = L'\0';
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
trim_spaces_inplace(line);
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
Task *cmd_task = execute_command(Boot, line, shell_priv);
|
||||||
|
|
||||||
|
/* If a command task was spawned, wait for it to finish. */
|
||||||
|
if (cmd_task != NULL) {
|
||||||
|
task_wait(cmd_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset for next command */
|
||||||
|
len = 0;
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
}
|
||||||
|
} else if (Key.scan_code == 0x08 || Key.unicode_char == L'\b' || Key.unicode_char == 0x7F) {
|
||||||
|
/* Backspace */
|
||||||
|
if (len > 0) {
|
||||||
|
len--;
|
||||||
|
SAFE_PRINT(Boot, L"\b \b");
|
||||||
|
}
|
||||||
|
} else if (Key.unicode_char >= 32 && Key.unicode_char < 127) {
|
||||||
|
/* Printable ASCII */
|
||||||
|
if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
|
||||||
|
line[len++] = Key.unicode_char;
|
||||||
|
SAFE_PRINT(Boot, L"%c", Key.unicode_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free our context on exit (allocated by the spawner). */
|
||||||
|
kfree(ctx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable design choices:
|
||||||
|
|
||||||
|
- **Non-blocking polling**: When `Boot->try_read_key` is available, the terminal uses it and calls `task_yield` if no key is present. This avoids monopolising the CPU while idle.
|
||||||
|
- **Line editing**: A fixed-size buffer `line[128]` accumulates ASCII characters. Backspace decrements `len` and erases the last character on screen.
|
||||||
|
- **Command execution**: When the user presses Enter, the line is trimmed and either:
|
||||||
|
- Handled as a built-in shell control command (`exit`, `starling`).
|
||||||
|
- Sent to `execute_command` for lookup and execution.
|
||||||
|
- **Nested shells**: The `starling` command spawns a nested Starling Terminal task with increased `depth`, allowing recursive shells.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spawning the terminal
|
||||||
|
|
||||||
|
`kmain` spawns the initial Starling Terminal as its own task and then turns the core thread into an idle loop:
|
||||||
|
|
||||||
|
```221:253:/home/lochlan/Documents/Coding/c/os/kernel.c
|
||||||
|
ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to allocate Starling Terminal context; starting inline.\n\r");
|
||||||
|
StarlingContext inline_ctx;
|
||||||
|
inline_ctx.Boot = Boot;
|
||||||
|
inline_ctx.depth = 0;
|
||||||
|
inline_ctx.shell_priv = TASK_PRIV_USER;
|
||||||
|
starling_terminal_task(&inline_ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->Boot = Boot;
|
||||||
|
ctx->depth = 0;
|
||||||
|
ctx->shell_priv = TASK_PRIV_USER;
|
||||||
|
|
||||||
|
terminal_task = task_create_with_priv(L"starling-term",
|
||||||
|
starling_terminal_task,
|
||||||
|
ctx,
|
||||||
|
TASK_PRIV_USER);
|
||||||
|
if (terminal_task == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to start Starling Terminal task; falling back to kernel loop.\n\r");
|
||||||
|
...
|
||||||
|
starling_terminal_task(Boot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[core] Started Starling Terminal (PID %d).\n\r", terminal_task->pid);
|
||||||
|
|
||||||
|
/* Core thread becomes an idle loop, yielding to the terminal and others. */
|
||||||
|
while (TRUE) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures that:
|
||||||
|
|
||||||
|
- The terminal runs as a **regular task** managed by the cooperative scheduler.
|
||||||
|
- The core thread remains available to run other tasks or future subsystems, rather than being permanently blocked in terminal I/O.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command registry (`commands[]`)
|
||||||
|
|
||||||
|
The command registry is defined in `commands.c` as a static array:
|
||||||
|
|
||||||
|
```73:164:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static Command commands[] = {
|
||||||
|
{
|
||||||
|
L"shutdown",
|
||||||
|
L"Shutdown the system",
|
||||||
|
L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.",
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
|
cmd_shutdown
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"help",
|
||||||
|
L"Display available commands",
|
||||||
|
L"Usage: help\n\r Lists all available commands with brief descriptions.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_help
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"man",
|
||||||
|
L"Display manual page for a command",
|
||||||
|
L"Usage: man <command>\n\r Shows detailed help for the specified command.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_man
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"clear",
|
||||||
|
L"Clear the screen",
|
||||||
|
L"Usage: clear\n\r Clears the console screen.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_clear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"about",
|
||||||
|
L"Display system information",
|
||||||
|
L"Usage: about\n\r Shows information about this operating system.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_about
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"mem",
|
||||||
|
L"Display memory statistics",
|
||||||
|
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
|
cmd_mem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"ps",
|
||||||
|
L"List running tasks",
|
||||||
|
L"Usage: ps\n\r Displays all active tasks with PID, state, and name.",
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_ps
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"spawn",
|
||||||
|
L"Spawn a demo background task",
|
||||||
|
L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.",
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_spawn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"memtest",
|
||||||
|
L"Test memory allocation and deallocation",
|
||||||
|
...
|
||||||
|
TASK_PRIV_KERNEL,
|
||||||
|
cmd_memtest
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"tasktest",
|
||||||
|
L"Test task scheduler with multiple tasks",
|
||||||
|
...
|
||||||
|
TASK_PRIV_DRIVER,
|
||||||
|
cmd_tasktest
|
||||||
|
},
|
||||||
|
{
|
||||||
|
L"kusr",
|
||||||
|
L"Run a command with kernel privilege",
|
||||||
|
L"Usage: kusr <command> [args...]\n\r"
|
||||||
|
L" Temporarily elevates the current task to kernel privilege,\n\r"
|
||||||
|
L" executes the given command, then restores the original level.",
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_kusr
|
||||||
|
},
|
||||||
|
{NULL, NULL, NULL, 0, NULL} /* sentinel */
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Each `Command` entry includes:
|
||||||
|
|
||||||
|
- `name` – the token typed at the prompt.
|
||||||
|
- `description` – a short summary used by `help`.
|
||||||
|
- `usage` – a longer description and usage details for `man`.
|
||||||
|
- `min_priv` – the minimum `TaskPrivilege` required to run the command (see `task.h`).
|
||||||
|
- `handler` – a function of type:
|
||||||
|
|
||||||
|
```14:19:/home/lochlan/Documents/Coding/c/os/commands.h
|
||||||
|
typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
```
|
||||||
|
|
||||||
|
To add a new command, follow the guide in the file header:
|
||||||
|
|
||||||
|
```8:12:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
* 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command execution pipeline
|
||||||
|
|
||||||
|
The central function that processes a line of user input is `execute_command`:
|
||||||
|
|
||||||
|
```493:557:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv)
|
||||||
|
{
|
||||||
|
CHAR16 *cmd_start = NULL;
|
||||||
|
CHAR16 *args_start = NULL;
|
||||||
|
UINTN i = 0;
|
||||||
|
CommandTaskContext *ctx;
|
||||||
|
Task *t;
|
||||||
|
|
||||||
|
if (Boot == NULL || Input == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
trim_spaces_inplace(Input);
|
||||||
|
|
||||||
|
if (Input[0] == L'\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split input into command and argument strings */
|
||||||
|
cmd_start = Input;
|
||||||
|
args_start = Input;
|
||||||
|
|
||||||
|
/* Advance past the command keyword */
|
||||||
|
while (*args_start != L'\0' && !is_space16(*args_start)) {
|
||||||
|
args_start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NUL-terminate the command and skip leading whitespace in args */
|
||||||
|
if (*args_start != L'\0') {
|
||||||
|
*args_start = L'\0';
|
||||||
|
args_start++;
|
||||||
|
|
||||||
|
/* skip leading whitespace in args */
|
||||||
|
while (*args_start != L'\0' && is_space16(*args_start)) {
|
||||||
|
args_start++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look up and dispatch the command */
|
||||||
|
for (i = 0; commands[i].name != NULL; i++) {
|
||||||
|
if (ascii_streq_ci(cmd_start, commands[i].name)) {
|
||||||
|
/* Allocate a context block for the command task. */
|
||||||
|
ctx = (CommandTaskContext *)kmalloc(sizeof(CommandTaskContext));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to allocate command context; running in core thread.\n\r");
|
||||||
|
commands[i].handler(Boot, args_start);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->Boot = Boot;
|
||||||
|
ctx->handler = commands[i].handler;
|
||||||
|
wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0]));
|
||||||
|
|
||||||
|
t = task_create_with_priv(commands[i].name,
|
||||||
|
command_task_entry,
|
||||||
|
ctx,
|
||||||
|
caller_priv);
|
||||||
|
if (t == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r",
|
||||||
|
commands[i].name);
|
||||||
|
kfree(ctx);
|
||||||
|
commands[i].handler(Boot, args_start);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[starling] spawned '%s' as PID %d\n\r", t->name, t->pid);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Command not found */
|
||||||
|
SAFE_PRINT(Boot, L"Unknown command: %s\n\r", cmd_start);
|
||||||
|
SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline stages:
|
||||||
|
|
||||||
|
1. **Normalisation**:
|
||||||
|
- `trim_spaces_inplace` removes leading/trailing spaces.
|
||||||
|
- Empty lines are ignored.
|
||||||
|
2. **Tokenisation**:
|
||||||
|
- `cmd_start` points to the command token.
|
||||||
|
- `args_start` is advanced past the command; the first whitespace is replaced with `L'\0'`, splitting the string in-place.
|
||||||
|
- Leading whitespace in `args_start` is skipped.
|
||||||
|
3. **Lookup**:
|
||||||
|
- `commands[]` is scanned for a name that matches `cmd_start` using case-insensitive `ascii_streq_ci`.
|
||||||
|
4. **Dispatch**:
|
||||||
|
- On match, a `CommandTaskContext` is allocated via `kmalloc` and filled with:
|
||||||
|
- `Boot` pointer.
|
||||||
|
- Handler function.
|
||||||
|
- A bounded copy of the argument string.
|
||||||
|
- A new task is created via `task_create_with_priv` with:
|
||||||
|
- Task name = command name.
|
||||||
|
- Entry = `command_task_entry`.
|
||||||
|
- Argument = pointer to the context.
|
||||||
|
- Privilege = `caller_priv` (inherited from the calling shell).
|
||||||
|
- If task creation fails, the command handler is run synchronously in the current thread as a fallback.
|
||||||
|
|
||||||
|
The terminal then optionally `task_wait`s on the returned `Task *`, serialising command execution from the user's perspective while still letting the scheduler run other tasks (e.g., background demos).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command task entry and context
|
||||||
|
|
||||||
|
Command handlers are executed in a dedicated task, whose entry function is `command_task_entry`:
|
||||||
|
|
||||||
|
```44:50:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
typedef struct {
|
||||||
|
BootInfo *Boot;
|
||||||
|
CommandHandlerFn handler;
|
||||||
|
CHAR16 args[128];
|
||||||
|
} CommandTaskContext;
|
||||||
|
|
||||||
|
static void command_task_entry(void *arg);
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
|
||||||
|
```570:587:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void command_task_entry(void *arg)
|
||||||
|
{
|
||||||
|
CommandTaskContext *ctx = (CommandTaskContext *)arg;
|
||||||
|
BootInfo *Boot = NULL;
|
||||||
|
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot = ctx->Boot;
|
||||||
|
|
||||||
|
if (ctx->handler != NULL) {
|
||||||
|
ctx->handler(Boot, ctx->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context was heap-allocated in execute_command. */
|
||||||
|
kfree(ctx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This design gives each command:
|
||||||
|
|
||||||
|
- Its own stack, independent of the terminal.
|
||||||
|
- Its own argument buffer, isolated from the terminal's input buffer.
|
||||||
|
- Automatic cleanup of the context when the command finishes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in commands
|
||||||
|
|
||||||
|
Some notable built-in handlers:
|
||||||
|
|
||||||
|
- **System control**:
|
||||||
|
- `shutdown` (KERNEL) → `cmd_shutdown` calls `Boot->shutdown` via `request_shutdown` to power off the machine. `request_shutdown` enforces kernel privilege.
|
||||||
|
- `clear` (USER) → `cmd_clear` uses `Boot->clear_screen` to wipe the console.
|
||||||
|
- **Information and help**:
|
||||||
|
- `help` (USER) → `cmd_help` calls `show_help` to print a formatted table of available commands.
|
||||||
|
- `man` (USER) → `cmd_man` prints the `usage` field for a specific command.
|
||||||
|
- `about` (USER) → `cmd_about` prints OS information and feature list.
|
||||||
|
- **Diagnostics**:
|
||||||
|
- `mem` (KERNEL) → `cmd_mem` calls `memory_print_stats` to show PMM and heap state. The callee enforces kernel privilege.
|
||||||
|
- `ps` (DRIVER) → `cmd_ps` calls `task_print_list` to show current tasks. The callee enforces driver privilege.
|
||||||
|
- `memtest` (KERNEL) → `cmd_memtest` exercises heap and PMM allocations. The handler enforces kernel privilege.
|
||||||
|
- `tasktest` (DRIVER) → `cmd_tasktest` spawns multiple worker tasks to demonstrate cooperative scheduling.
|
||||||
|
- **Tasking demo**:
|
||||||
|
- `spawn` (DRIVER) → `cmd_spawn` creates a demonstration task using `demo_task_fn`, which yields in a loop and reports progress.
|
||||||
|
- **Privilege escalation**:
|
||||||
|
- `kusr` (USER) → `cmd_kusr` temporarily elevates the calling task to `TASK_PRIV_KERNEL`, dispatches the given sub-command, then restores the original privilege level.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```248:252:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_mem(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
(void)Args;
|
||||||
|
memory_print_stats(Boot);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```275:279:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_ps(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
(void)Args;
|
||||||
|
task_print_list(Boot);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From a user's perspective:
|
||||||
|
|
||||||
|
- Commands are discoverable via `help` and `man`.
|
||||||
|
- Many commands provide deep insight into internal subsystems (memory, tasks) without requiring external tooling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding new commands
|
||||||
|
|
||||||
|
To add a new command `foo`:
|
||||||
|
|
||||||
|
1. **Declare the handler** near the top of `commands.c`:
|
||||||
|
|
||||||
|
```32:42:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_help(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
...
|
||||||
|
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
static void cmd_kusr(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
/* Add: */
|
||||||
|
static void cmd_foo(BootInfo *Boot, CHAR16 *Args);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Implement the handler**:
|
||||||
|
|
||||||
|
```200:207:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
(void)Args;
|
||||||
|
SAFE_PRINT(Boot, L"Shutting down...\n\r");
|
||||||
|
request_shutdown(Boot);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this as a template for `cmd_foo`, replacing the body with your logic and using `SAFE_PRINT` for output.
|
||||||
|
|
||||||
|
3. **Register the command** in `commands[]` before the sentinel:
|
||||||
|
|
||||||
|
```140:164:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
{
|
||||||
|
L"kusr",
|
||||||
|
...
|
||||||
|
TASK_PRIV_USER,
|
||||||
|
cmd_kusr
|
||||||
|
},
|
||||||
|
{NULL, NULL, NULL, 0, NULL} /* sentinel */
|
||||||
|
```
|
||||||
|
|
||||||
|
Insert a new block above the sentinel:
|
||||||
|
|
||||||
|
```c
|
||||||
|
{
|
||||||
|
L"foo",
|
||||||
|
L"One-line description",
|
||||||
|
L"Usage: foo [args]\n\r Detailed explanation...",
|
||||||
|
TASK_PRIV_USER, /* minimum privilege required */
|
||||||
|
cmd_foo
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Rebuild and run. Typing `foo` at the `starling>` prompt will now execute your handler in its own task.
|
||||||
|
|
||||||
|
This extensible design makes it straightforward to grow the OS with additional diagnostics, demos, or experimental subsystems, all accessible from the interactive shell.
|
||||||
|
|
||||||
269
docs/interrupts-and-exceptions.md
Normal file
269
docs/interrupts-and-exceptions.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
## Interrupt and exception handling overview
|
||||||
|
|
||||||
|
The kernel's interrupt/exception handling is implemented in `idt.c` and a companion assembly file `isr.S` (not shown here). The design goals are:
|
||||||
|
|
||||||
|
- Preserve the firmware's existing hardware interrupt handlers where possible.
|
||||||
|
- Override CPU **exception vectors 0–31** with kernel stubs that route into a C dispatcher.
|
||||||
|
- Print detailed diagnostics for exceptions and halt on unrecoverable faults.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IDT representation
|
||||||
|
|
||||||
|
The x86-64 Interrupt Descriptor Table (IDT) is represented as an array of packed 16-byte entries:
|
||||||
|
|
||||||
|
```22:37:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
typedef struct {
|
||||||
|
UINT16 offset_low; /* bits 0-15 of handler address */
|
||||||
|
UINT16 selector; /* code segment selector */
|
||||||
|
UINT8 ist; /* interrupt stack table index */
|
||||||
|
UINT8 type_attr; /* type and attributes */
|
||||||
|
UINT16 offset_mid; /* bits 16-31 of handler address */
|
||||||
|
UINT32 offset_high; /* bits 32-63 of handler address */
|
||||||
|
UINT32 zero; /* reserved, must be zero */
|
||||||
|
} __attribute__((packed)) IdtEntry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UINT16 limit;
|
||||||
|
UINT64 base;
|
||||||
|
} __attribute__((packed)) IdtPtr;
|
||||||
|
```
|
||||||
|
|
||||||
|
Module state:
|
||||||
|
|
||||||
|
```42:47:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static IdtEntry idt[IDT_SIZE];
|
||||||
|
static BootInfo *gBoot = NULL;
|
||||||
|
|
||||||
|
/* Defined in isr.S – one stub function per vector (0-255). */
|
||||||
|
extern void (*isr_stub_table[])(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
Each element of `isr_stub_table` is a low-level assembly stub that:
|
||||||
|
|
||||||
|
- Saves CPU state into an `ISRFrame`.
|
||||||
|
- Calls the common C dispatcher `isr_handler`.
|
||||||
|
- Restores state and performs `iretq` (for IRQs) or loops into a halt (for fatal faults).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installing IDT entries
|
||||||
|
|
||||||
|
`idt_set_gate` encodes a handler function pointer into an IDT entry:
|
||||||
|
|
||||||
|
```53:67:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static void idt_set_gate(UINTN index, void (*handler)(void))
|
||||||
|
{
|
||||||
|
UINT64 addr = (UINT64)(UINTN)handler;
|
||||||
|
UINT16 selector = 0;
|
||||||
|
|
||||||
|
__asm__ __volatile__("mov %%cs, %0" : "=r"(selector));
|
||||||
|
|
||||||
|
idt[index].offset_low = (UINT16)(addr & 0xFFFF);
|
||||||
|
idt[index].selector = selector;
|
||||||
|
idt[index].ist = 0;
|
||||||
|
idt[index].type_attr = IDT_TYPE_INTERRUPT;
|
||||||
|
idt[index].offset_mid = (UINT16)((addr >> 16) & 0xFFFF);
|
||||||
|
idt[index].offset_high = (UINT32)((addr >> 32) & 0xFFFFFFFF);
|
||||||
|
idt[index].zero = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`lidt` loads a new IDTR:
|
||||||
|
|
||||||
|
```69:73:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static void lidt(const IdtPtr *idtr)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__("lidt (%0)" :: "r"(idtr));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exception names
|
||||||
|
|
||||||
|
For better diagnostics, the kernel maps exception vectors to human-readable names:
|
||||||
|
|
||||||
|
```80:116:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static const CHAR16 *exception_name(UINTN vector)
|
||||||
|
{
|
||||||
|
switch (vector) {
|
||||||
|
case 0: return L"Divide Error";
|
||||||
|
case 1: return L"Debug";
|
||||||
|
...
|
||||||
|
case 14: return L"Page Fault";
|
||||||
|
...
|
||||||
|
case 30: return L"Security";
|
||||||
|
case 31: return L"Reserved";
|
||||||
|
default: return L"Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These strings are used by `isr_handler` when printing exception banners.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PIC helpers and halting
|
||||||
|
|
||||||
|
The kernel provides minimal support for acknowledging legacy PIC interrupts and halting the CPU in fatal cases:
|
||||||
|
|
||||||
|
```124:136:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static inline void outb(UINT16 port, UINT8 value)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__("outb %0, %1" :: "a"(value), "Nd"(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pic_eoi(UINTN vector)
|
||||||
|
{
|
||||||
|
if (vector >= 40) {
|
||||||
|
outb(0xA0, 0x20); /* EOI to slave PIC */
|
||||||
|
}
|
||||||
|
outb(0x20, 0x20); /* EOI to master PIC */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`halt_forever` disables interrupts and executes `hlt` in an infinite loop:
|
||||||
|
|
||||||
|
```138:144:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
static void halt_forever(void)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
__asm__ __volatile__("cli; hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ISR dispatcher (`isr_handler`)
|
||||||
|
|
||||||
|
`isr_handler` is the central C function that receives control from all exception and interrupt stubs:
|
||||||
|
|
||||||
|
```150:177:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
void isr_handler(ISRFrame *frame)
|
||||||
|
{
|
||||||
|
UINT64 cr2 = 0;
|
||||||
|
|
||||||
|
/* Hardware IRQs (vectors 32-47): send EOI and return */
|
||||||
|
if (frame->vector >= 32 && frame->vector <= 47) {
|
||||||
|
pic_eoi(frame->vector);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CPU exceptions (vectors 0-31): print diagnostics and halt */
|
||||||
|
if (gBoot != NULL && gBoot->print != NULL) {
|
||||||
|
gBoot->print(L"\n\rEXCEPTION: %d (%s)\n\r", frame->vector,
|
||||||
|
exception_name(frame->vector));
|
||||||
|
gBoot->print(L" Error Code: 0x%lx\n\r", frame->error_code);
|
||||||
|
gBoot->print(L" RIP: 0x%lx CS: 0x%lx RFLAGS: 0x%lx\n\r",
|
||||||
|
frame->rip, frame->cs, frame->rflags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame->vector == 14) {
|
||||||
|
__asm__ __volatile__("mov %%cr2, %0" : "=r"(cr2));
|
||||||
|
if (gBoot != NULL && gBoot->print != NULL) {
|
||||||
|
gBoot->print(L" CR2: 0x%lx\n\r", cr2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
halt_forever();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ISRFrame` structure (defined in `idt.h`) contains the state saved by the assembly stubs, including:
|
||||||
|
|
||||||
|
- Exception/interrupt vector number.
|
||||||
|
- Error code (if applicable).
|
||||||
|
- `RIP`, `CS`, and `RFLAGS` at the time of the fault.
|
||||||
|
|
||||||
|
The handler's behaviour is:
|
||||||
|
|
||||||
|
- For **hardware IRQs** (vectors 32–47):
|
||||||
|
- Send an **End Of Interrupt** (EOI) to the PIC.
|
||||||
|
- Return to the interrupted context.
|
||||||
|
- For **CPU exceptions** (vectors 0–31):
|
||||||
|
- Print a diagnostic header with the vector number and name.
|
||||||
|
- Show error code and execution context (`RIP`, `CS`, `RFLAGS`).
|
||||||
|
- For page faults (vector 14), read and print CR2 (faulting virtual address).
|
||||||
|
- Halt the system via `halt_forever`.
|
||||||
|
|
||||||
|
This makes debugging faults significantly easier when running under QEMU or on real hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IDT initialisation (`idt_init`)
|
||||||
|
|
||||||
|
`idt_init` is called from `kmain` early during boot:
|
||||||
|
|
||||||
|
```183:214:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||||
|
void idt_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
IdtPtr old_idtr;
|
||||||
|
IdtPtr idtr;
|
||||||
|
IdtEntry *old_idt = NULL;
|
||||||
|
UINTN i = 0;
|
||||||
|
|
||||||
|
gBoot = Boot;
|
||||||
|
|
||||||
|
/* Read the firmware's existing IDT so we can preserve its entries */
|
||||||
|
__asm__ __volatile__("sidt %0" : "=m"(old_idtr));
|
||||||
|
old_idt = (IdtEntry *)(UINTN)old_idtr.base;
|
||||||
|
|
||||||
|
/* Copy the entire existing IDT first (preserves firmware IRQ handlers) */
|
||||||
|
for (i = 0; i < IDT_SIZE; i++) {
|
||||||
|
if (old_idt != NULL && (i * sizeof(IdtEntry)) < (UINTN)(old_idtr.limit + 1)) {
|
||||||
|
idt[i] = old_idt[i];
|
||||||
|
} else {
|
||||||
|
idt_set_gate(i, isr_stub_table[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override only CPU exception vectors (0-31) with our handlers */
|
||||||
|
for (i = 0; i < 32; i++) {
|
||||||
|
idt_set_gate(i, isr_stub_table[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
idtr.limit = (UINT16)(sizeof(idt) - 1);
|
||||||
|
idtr.base = (UINT64)(UINTN)idt;
|
||||||
|
|
||||||
|
lidt(&idtr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The process is:
|
||||||
|
|
||||||
|
1. **Capture firmware IDT**:
|
||||||
|
- `sidt` reads the current IDTR into `old_idtr`.
|
||||||
|
- `old_idt` is set to the base of the firmware's IDT.
|
||||||
|
2. **Copy firmware entries**:
|
||||||
|
- For all indices `i` where the address lies within the firmware's IDT limit, copy the existing entry into the kernel's `idt` array.
|
||||||
|
- For indices beyond the firmware's limit, install the kernel's own stub from `isr_stub_table`.
|
||||||
|
3. **Override CPU exceptions**:
|
||||||
|
- For vectors 0–31, call `idt_set_gate` with the kernel's stubs, ensuring that exceptions are always handled by `isr_handler`.
|
||||||
|
4. **Activate new IDT**:
|
||||||
|
- Populate `idtr` with the address and size of the kernel's `idt`.
|
||||||
|
- Call `lidt` to load the new IDT.
|
||||||
|
|
||||||
|
This approach preserves any firmware-installed handlers for higher interrupt vectors (e.g., hardware IRQs or system-specific events), while guaranteeing full control over CPU exception handling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interaction with the rest of the kernel
|
||||||
|
|
||||||
|
The IDT/ISR subsystem interacts with other parts of the kernel in the following ways:
|
||||||
|
|
||||||
|
- **BootInfo access**:
|
||||||
|
- `idt_init` stores `Boot` in `gBoot` so that `isr_handler` can safely use `Boot->print` for diagnostics.
|
||||||
|
- **Memory subsystem**:
|
||||||
|
- `isr_handler` reads CR2 for page faults; combined with `paging_get_phys` from `memory.c`, this can be used to inspect paging state.
|
||||||
|
- **Tasks and scheduler**:
|
||||||
|
- The current implementation is **non-preemptive**: context switches happen only through explicit calls to `task_yield`, not timer interrupts. Exceptions still interrupt tasks asynchronously, but there is no timer tick driving the scheduler.
|
||||||
|
- **User-level diagnostics**:
|
||||||
|
- When an exception occurs, the on-screen diagnostics provide enough context to identify the type of fault and its location in the kernel, especially when used alongside a symbol-enabled build and external debugger.
|
||||||
|
|
||||||
|
Future extensions might include:
|
||||||
|
|
||||||
|
- Installing a periodic timer IRQ handler that calls `task_yield` to add preemptive scheduling.
|
||||||
|
- Extending `ISRFrame` and `isr_handler` with richer diagnostics or a kernel debugger stub.
|
||||||
|
|
||||||
638
docs/memory-and-allocation.md
Normal file
638
docs/memory-and-allocation.md
Normal file
@@ -0,0 +1,638 @@
|
|||||||
|
## Memory management overview
|
||||||
|
|
||||||
|
The kernel's memory subsystem is implemented in `memory.c` and exposes three layers:
|
||||||
|
|
||||||
|
- **Physical Memory Manager (PMM)** – a bitmap-based page-frame allocator over a fixed-size pool obtained from the loader at boot.
|
||||||
|
- **Paging helpers** – routines to walk and extend the live 4-level x86-64 page tables, map/unmap virtual addresses, and translate virtual to physical.
|
||||||
|
- **Heap allocator** – a first-fit free-list allocator with block splitting and bidirectional coalescing, backed by pages from the PMM.
|
||||||
|
|
||||||
|
All three layers are wired together and brought up by `memory_init`:
|
||||||
|
|
||||||
|
```515:522:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void memory_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
SAFE_PRINT(Boot, L"Initializing memory management...\n\r");
|
||||||
|
pmm_init(Boot);
|
||||||
|
paging_init(Boot);
|
||||||
|
heap_init(Boot);
|
||||||
|
SAFE_PRINT(Boot, L"Memory management ready.\n\r\n\r");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Physical Memory Manager (PMM)
|
||||||
|
|
||||||
|
### Design
|
||||||
|
|
||||||
|
The PMM manages a pool of 4 KiB physical page frames acquired from the loader via `BootInfo->alloc_pages`. It uses a simple **bitmap** to track free vs. allocated pages:
|
||||||
|
|
||||||
|
```27:33:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
static UINT64 pmm_pool_base = 0;
|
||||||
|
static UINTN pmm_total_pages = 0;
|
||||||
|
static UINTN pmm_free_count = 0;
|
||||||
|
static UINT8 pmm_bitmap[PMM_POOL_PAGES / 8];
|
||||||
|
static BOOLEAN pmm_ready = FALSE;
|
||||||
|
```
|
||||||
|
|
||||||
|
Each bit in `pmm_bitmap` corresponds to a single page in the pool:
|
||||||
|
|
||||||
|
- **0** – page is free.
|
||||||
|
- **1** – page is allocated.
|
||||||
|
|
||||||
|
Helper functions manipulate these bits:
|
||||||
|
|
||||||
|
```37:53:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
static void pmm_set_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pmm_clear_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOLEAN pmm_test_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
return (pmm_bitmap[idx / 8] & (1U << (idx % 8))) != 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Initialisation
|
||||||
|
|
||||||
|
`pmm_init` obtains the underlying page pool from the loader and prepares the bitmap:
|
||||||
|
|
||||||
|
```64:96:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void pmm_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
KSTATUS Status;
|
||||||
|
UINT64 pool_addr = 0;
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
/* Zero the bitmap – all pages start free */
|
||||||
|
for (i = 0; i < sizeof(pmm_bitmap); i++) {
|
||||||
|
pmm_bitmap[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boot == NULL || Boot->alloc_pages == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"PMM: page allocator unavailable\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = Boot->alloc_pages(PMM_POOL_PAGES, &pool_addr);
|
||||||
|
...
|
||||||
|
|
||||||
|
pmm_pool_base = (UINT64)pool_addr;
|
||||||
|
pmm_total_pages = PMM_POOL_PAGES;
|
||||||
|
pmm_free_count = PMM_POOL_PAGES;
|
||||||
|
pmm_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" PMM : %d pages (%d KB) at 0x%lx\n\r",
|
||||||
|
pmm_total_pages,
|
||||||
|
(pmm_total_pages * PAGE_SIZE) / 1024,
|
||||||
|
pmm_pool_base);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of parsing the firmware's memory map, this OS delegates low-level page allocation to the loader via `BootInfo->alloc_pages`. The PMM then **sub-allocates** from this contiguous pool using its own bitmap.
|
||||||
|
|
||||||
|
### Single-page allocation
|
||||||
|
|
||||||
|
`pmm_alloc_page` scans the bitmap for the first free page, marks it allocated, and returns the physical address:
|
||||||
|
|
||||||
|
```98:116:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
UINT64 pmm_alloc_page(void)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
if (!pmm_ready || pmm_free_count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < pmm_total_pages; i++) {
|
||||||
|
if (!pmm_test_bit(i)) {
|
||||||
|
pmm_set_bit(i);
|
||||||
|
pmm_free_count--;
|
||||||
|
return pmm_pool_base + ((UINT64)i * PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The corresponding free operation validates the address and clears the bit:
|
||||||
|
|
||||||
|
```119:132:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void pmm_free_page(UINT64 phys_addr)
|
||||||
|
{
|
||||||
|
UINTN idx;
|
||||||
|
|
||||||
|
if (!pmm_ready) return;
|
||||||
|
if (phys_addr < pmm_pool_base) return;
|
||||||
|
|
||||||
|
idx = (UINTN)((phys_addr - pmm_pool_base) / PAGE_SIZE);
|
||||||
|
if (idx >= pmm_total_pages) return;
|
||||||
|
if (!pmm_test_bit(idx)) return; /* already free */
|
||||||
|
|
||||||
|
pmm_clear_bit(idx);
|
||||||
|
pmm_free_count++;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contiguous allocation
|
||||||
|
|
||||||
|
For multi-page allocations, `pmm_alloc_pages` performs a **first-fit** search for a run of `count` consecutive free bits:
|
||||||
|
|
||||||
|
```134:163:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
UINT64 pmm_alloc_pages(UINTN count)
|
||||||
|
{
|
||||||
|
UINTN i, j;
|
||||||
|
BOOLEAN found;
|
||||||
|
|
||||||
|
if (!pmm_ready || count == 0 || count > pmm_total_pages
|
||||||
|
|| pmm_free_count < count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i + count <= pmm_total_pages; i++) {
|
||||||
|
found = TRUE;
|
||||||
|
for (j = 0; j < count; j++) {
|
||||||
|
if (pmm_test_bit(i + j)) {
|
||||||
|
found = FALSE;
|
||||||
|
i += j; /* skip past the used page */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
for (j = 0; j < count; j++) {
|
||||||
|
pmm_set_bit(i + j);
|
||||||
|
}
|
||||||
|
pmm_free_count -= count;
|
||||||
|
return pmm_pool_base + ((UINT64)i * PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`pmm_free_pages` simply calls `pmm_free_page` for each page in the range.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Paging helpers
|
||||||
|
|
||||||
|
The paging layer operates directly on the current CR3 page table hierarchy and uses the PMM to allocate new page-table pages on demand.
|
||||||
|
|
||||||
|
### Reading CR3 and locating the PML4
|
||||||
|
|
||||||
|
```186:204:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
static UINT64 read_cr3(void)
|
||||||
|
{
|
||||||
|
UINT64 cr3;
|
||||||
|
__asm__ __volatile__("mov %%cr3, %0" : "=r"(cr3));
|
||||||
|
return cr3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void invlpg(UINT64 addr)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static UINT64 *get_pml4(void)
|
||||||
|
{
|
||||||
|
return (UINT64 *)(UINTN)(read_cr3() & PTE_ADDR_MASK);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `read_cr3` returns the physical address of the current PML4.
|
||||||
|
- `get_pml4` masks off flag bits using `PTE_ADDR_MASK` and casts the result to a pointer, assuming identity mapping of low physical memory (as set up by the loader).
|
||||||
|
|
||||||
|
`paging_init` logs the initial CR3 value for diagnostic purposes:
|
||||||
|
|
||||||
|
```244:249:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void paging_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (identity-mapped by loader)\n\r",
|
||||||
|
read_cr3());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Walking page-table levels
|
||||||
|
|
||||||
|
`paging_walk_level` abstracts a single step down the PML4 → PDPT → PD → PT hierarchy:
|
||||||
|
|
||||||
|
```211:238:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
static UINT64 *paging_walk_level(UINT64 *table, UINTN index, BOOLEAN create)
|
||||||
|
{
|
||||||
|
UINT64 *next;
|
||||||
|
UINTN i;
|
||||||
|
UINT64 page;
|
||||||
|
|
||||||
|
if (table[index] & PTE_PRESENT) {
|
||||||
|
return (UINT64 *)(UINTN)(table[index] & PTE_ADDR_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!create) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
page = pmm_alloc_page();
|
||||||
|
if (page == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero the freshly-allocated page table */
|
||||||
|
next = (UINT64 *)(UINTN)page;
|
||||||
|
for (i = 0; i < PAGE_SIZE / sizeof(UINT64); i++) {
|
||||||
|
next[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[index] = page | PTE_PRESENT | PTE_WRITABLE;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `create` is true and the entry is missing, it:
|
||||||
|
|
||||||
|
- Allocates a fresh page with `pmm_alloc_page`.
|
||||||
|
- Clears it.
|
||||||
|
- Installs it as the next-level table with base address + default flags (`PTE_PRESENT | PTE_WRITABLE`).
|
||||||
|
|
||||||
|
### Mapping and unmapping pages
|
||||||
|
|
||||||
|
To map a single 4 KiB page, the kernel:
|
||||||
|
|
||||||
|
1. Decomposes the virtual address into PML4/PDPT/PD/PT indices.
|
||||||
|
2. Walks or creates intermediate tables.
|
||||||
|
3. Installs a PTE with the desired flags.
|
||||||
|
4. Invalidates the TLB entry with `invlpg`.
|
||||||
|
|
||||||
|
```256:285:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
pdpt = paging_walk_level(pml4, pml4i, TRUE);
|
||||||
|
if (pdpt == NULL) return FALSE;
|
||||||
|
|
||||||
|
/* 1 GB huge page – cannot carve a 4 KB mapping inside it */
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) return FALSE;
|
||||||
|
|
||||||
|
pd = paging_walk_level(pdpt, pdpti, TRUE);
|
||||||
|
if (pd == NULL) return FALSE;
|
||||||
|
|
||||||
|
/* 2 MB huge page – cannot carve a 4 KB mapping inside it */
|
||||||
|
if (pd[pdi] & PTE_HUGE) return FALSE;
|
||||||
|
|
||||||
|
pt = paging_walk_level(pd, pdi, TRUE);
|
||||||
|
if (pt == NULL) return FALSE;
|
||||||
|
|
||||||
|
pt[pti] = (phys & PTE_ADDR_MASK) | flags | PTE_PRESENT;
|
||||||
|
invlpg(virt);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unmapping follows the same index computation but stops early if an intermediate table or mapping is missing or a huge-page mapping is in place:
|
||||||
|
|
||||||
|
```288:314:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void paging_unmap_page(UINT64 virt)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
pdpt = paging_walk_level(pml4, pml4i, FALSE);
|
||||||
|
if (pdpt == NULL) return;
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) return;
|
||||||
|
|
||||||
|
pd = paging_walk_level(pdpt, pdpti, FALSE);
|
||||||
|
if (pd == NULL) return;
|
||||||
|
if (pd[pdi] & PTE_HUGE) return;
|
||||||
|
|
||||||
|
pt = paging_walk_level(pd, pdi, FALSE);
|
||||||
|
if (pt == NULL) return;
|
||||||
|
|
||||||
|
pt[pti] = 0;
|
||||||
|
invlpg(virt);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Virtual-to-physical translation
|
||||||
|
|
||||||
|
`paging_get_phys` walks the existing hierarchy without allocating anything, and supports 4 KiB, 2 MiB, and 1 GiB mappings:
|
||||||
|
|
||||||
|
```320:351:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
UINT64 paging_get_phys(UINT64 virt)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
if (!(pml4[pml4i] & PTE_PRESENT)) return 0;
|
||||||
|
pdpt = (UINT64 *)(UINTN)(pml4[pml4i] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pdpt[pdpti] & PTE_PRESENT)) return 0;
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) {
|
||||||
|
/* 1 GB page */
|
||||||
|
return (pdpt[pdpti] & 0x000FFFFFC0000000ULL) | (virt & 0x3FFFFFFFULL);
|
||||||
|
}
|
||||||
|
pd = (UINT64 *)(UINTN)(pdpt[pdpti] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pd[pdi] & PTE_PRESENT)) return 0;
|
||||||
|
if (pd[pdi] & PTE_HUGE) {
|
||||||
|
/* 2 MB page */
|
||||||
|
return (pd[pdi] & 0x000FFFFFFFE00000ULL) | (virt & 0x1FFFFFULL);
|
||||||
|
}
|
||||||
|
pt = (UINT64 *)(UINTN)(pd[pdi] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pt[pti] & PTE_PRESENT)) return 0;
|
||||||
|
return (pt[pti] & PTE_ADDR_MASK) | (virt & 0xFFFULL);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is useful for diagnostics and for checking assumptions about how the firmware identity-mapped memory before entering the kernel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Heap allocator
|
||||||
|
|
||||||
|
The heap allocator builds on top of the PMM to provide `kmalloc`/`kfree` semantics. It uses a singly linked list of **heap blocks** (`HeapBlock`), each containing metadata and a `size` field describing the payload.
|
||||||
|
|
||||||
|
### Initialisation
|
||||||
|
|
||||||
|
`heap_init` obtains an initial contiguous region of heap memory and seeds the free list with a single large free block:
|
||||||
|
|
||||||
|
```370:394:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void heap_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINT64 phys;
|
||||||
|
UINTN heap_size;
|
||||||
|
|
||||||
|
phys = pmm_alloc_pages(HEAP_INITIAL_PAGES);
|
||||||
|
if (phys == 0) {
|
||||||
|
SAFE_PRINT(Boot, L" Heap: failed to allocate pages\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_size = HEAP_INITIAL_PAGES * PAGE_SIZE;
|
||||||
|
heap_start = (HeapBlock *)(UINTN)phys;
|
||||||
|
|
||||||
|
heap_start->magic = HEAP_BLOCK_MAGIC;
|
||||||
|
heap_start->state = HEAP_BLOCK_FREE;
|
||||||
|
heap_start->size = heap_size - sizeof(HeapBlock);
|
||||||
|
heap_start->next = NULL;
|
||||||
|
heap_start->prev = NULL;
|
||||||
|
|
||||||
|
heap_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" Heap: %d KB at 0x%lx\n\r",
|
||||||
|
heap_size / 1024, phys);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The allocator assumes that the physical address returned by `pmm_alloc_pages` is accessible via identity mapping, so it can cast it directly to a `HeapBlock *`.
|
||||||
|
|
||||||
|
### Alignment helper
|
||||||
|
|
||||||
|
Allocations are rounded up to a fixed alignment (e.g., 16 bytes) using `align_up`:
|
||||||
|
|
||||||
|
```361:364:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
static UINTN align_up(UINTN val, UINTN align)
|
||||||
|
{
|
||||||
|
return (val + align - 1) & ~(align - 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allocation (`kmalloc`)
|
||||||
|
|
||||||
|
`kmalloc` performs a **first-fit** search of the free list:
|
||||||
|
|
||||||
|
```401:440:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void *kmalloc(UINTN size)
|
||||||
|
{
|
||||||
|
HeapBlock *block, *split;
|
||||||
|
UINTN aligned;
|
||||||
|
|
||||||
|
if (!heap_ready || size == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
aligned = align_up(size, HEAP_ALIGN);
|
||||||
|
|
||||||
|
for (block = heap_start; block != NULL; block = block->next) {
|
||||||
|
if (block->magic != HEAP_BLOCK_MAGIC) {
|
||||||
|
return NULL; /* heap corruption */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->state != HEAP_BLOCK_FREE || block->size < aligned) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to split if there is room for another header + 16 bytes */
|
||||||
|
if (block->size >= aligned + sizeof(HeapBlock) + HEAP_ALIGN) {
|
||||||
|
split = (HeapBlock *)((UINT8 *)block + sizeof(HeapBlock) + aligned);
|
||||||
|
split->magic = HEAP_BLOCK_MAGIC;
|
||||||
|
split->state = HEAP_BLOCK_FREE;
|
||||||
|
split->size = block->size - aligned - sizeof(HeapBlock);
|
||||||
|
split->next = block->next;
|
||||||
|
split->prev = block;
|
||||||
|
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = split;
|
||||||
|
}
|
||||||
|
|
||||||
|
block->next = split;
|
||||||
|
block->size = aligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
block->state = HEAP_BLOCK_USED;
|
||||||
|
return (void *)((UINT8 *)block + sizeof(HeapBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL; /* out of heap memory */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable details:
|
||||||
|
|
||||||
|
- **Corruption detection** – checks `HEAP_BLOCK_MAGIC` for each block; any mismatch aborts with `NULL`.
|
||||||
|
- **Splitting** – if the free block is large enough, it is split into:
|
||||||
|
- An allocated block of exactly `aligned` bytes.
|
||||||
|
- A new trailing free block (`split`) with its own header.
|
||||||
|
- **Alignment** – the returned pointer is `sizeof(HeapBlock)` bytes after the header and aligned according to `HEAP_ALIGN`.
|
||||||
|
|
||||||
|
### Freeing (`kfree`) and coalescing
|
||||||
|
|
||||||
|
`kfree` marks a block as free and then attempts to coalesce with neighboring free blocks to combat fragmentation:
|
||||||
|
|
||||||
|
```449:486:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void kfree(void *ptr)
|
||||||
|
{
|
||||||
|
HeapBlock *block;
|
||||||
|
|
||||||
|
if (ptr == NULL || !heap_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = (HeapBlock *)((UINT8 *)ptr - sizeof(HeapBlock));
|
||||||
|
|
||||||
|
if (block->magic != HEAP_BLOCK_MAGIC || block->state != HEAP_BLOCK_USED) {
|
||||||
|
return; /* bad pointer or double-free */
|
||||||
|
}
|
||||||
|
|
||||||
|
block->state = HEAP_BLOCK_FREE;
|
||||||
|
|
||||||
|
/* Coalesce with next neighbour */
|
||||||
|
if (block->next != NULL
|
||||||
|
&& block->next->magic == HEAP_BLOCK_MAGIC
|
||||||
|
&& block->next->state == HEAP_BLOCK_FREE) {
|
||||||
|
block->size += sizeof(HeapBlock) + block->next->size;
|
||||||
|
block->next = block->next->next;
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coalesce with previous neighbour */
|
||||||
|
if (block->prev != NULL
|
||||||
|
&& block->prev->magic == HEAP_BLOCK_MAGIC
|
||||||
|
&& block->prev->state == HEAP_BLOCK_FREE) {
|
||||||
|
block->prev->size += sizeof(HeapBlock) + block->size;
|
||||||
|
block->prev->next = block->next;
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = block->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The allocator never returns memory to the PMM; all heap pages remain reserved for heap use for the lifetime of the kernel.
|
||||||
|
|
||||||
|
### Heap statistics
|
||||||
|
|
||||||
|
`heap_get_stats` walks the free list and aggregates total, used, and free bytes as well as block count:
|
||||||
|
|
||||||
|
```488:508:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
|
||||||
|
UINTN *num_blocks)
|
||||||
|
{
|
||||||
|
HeapBlock *b;
|
||||||
|
|
||||||
|
*total = 0; *used = 0; *free_mem = 0; *num_blocks = 0;
|
||||||
|
|
||||||
|
if (!heap_ready) return;
|
||||||
|
|
||||||
|
for (b = heap_start; b != NULL && b->magic == HEAP_BLOCK_MAGIC;
|
||||||
|
b = b->next) {
|
||||||
|
(*num_blocks)++;
|
||||||
|
*total += b->size;
|
||||||
|
if (b->state == HEAP_BLOCK_USED) {
|
||||||
|
*used += b->size;
|
||||||
|
} else {
|
||||||
|
*free_mem += b->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These statistics are surfaced to the user via the `mem` and `memtest` commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Runtime memory diagnostics (`mem` and `memtest`)
|
||||||
|
|
||||||
|
The `mem` command (in `commands.c`) prints a snapshot of PMM and heap state by calling `memory_print_stats`. Access requires `TASK_PRIV_KERNEL`:
|
||||||
|
|
||||||
|
```525:572:/home/lochlan/Documents/Coding/c/os/memory.c
|
||||||
|
void memory_print_stats(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN h_total, h_used, h_free, h_blocks;
|
||||||
|
UINTN p_total, p_free, p_used;
|
||||||
|
Task *caller;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: memory stats require KERNEL. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: memory stats require kernel privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_total = pmm_get_total_pages();
|
||||||
|
p_free = pmm_get_free_pages();
|
||||||
|
p_used = p_total - p_free;
|
||||||
|
|
||||||
|
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"Memory Statistics\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
|
...
|
||||||
|
SAFE_PRINT(Boot, L"Paging:\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" CR3: 0x%lx\n\r", read_cr3());
|
||||||
|
SAFE_PRINT(Boot, L" Mode: 4-level (PML4)\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `memtest` command runs a scripted set of tests that exercise heap allocation, heap free/coalescing, and PMM single- and multi-page allocation. It also enforces `TASK_PRIV_KERNEL`:
|
||||||
|
|
||||||
|
```306:379:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
void *ptrs[8];
|
||||||
|
UINTN sizes[] = { 16, 64, 128, 256, 512, 1024, 2048, 4096 };
|
||||||
|
UINTN i;
|
||||||
|
UINT64 page;
|
||||||
|
UINTN h_total, h_used, h_free, h_blocks;
|
||||||
|
Task *caller;
|
||||||
|
(void)Args;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: memtest requires KERNEL. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: memtest requires kernel privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"Memory Test\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
|
...
|
||||||
|
/* --- Heap allocation test --- */
|
||||||
|
...
|
||||||
|
/* --- Heap free test --- */
|
||||||
|
...
|
||||||
|
/* --- PMM page allocation test --- */
|
||||||
|
...
|
||||||
|
/* --- Multi-page allocation test --- */
|
||||||
|
...
|
||||||
|
SAFE_PRINT(Boot, L"\n\rAll memory tests completed.\n\r\n\r");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These commands provide a convenient way to validate memory subsystem behaviour from the Starling Terminal without needing an external debugger.
|
||||||
|
|
||||||
308
docs/overview.md
Normal file
308
docs/overview.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
## Overview
|
||||||
|
|
||||||
|
This document explains the high-level control flow of the operating system – from firmware entry through to the interactive shell – and introduces the major subsystems that the rest of the documentation explores in depth.
|
||||||
|
|
||||||
|
- **Boot loader**: a UEFI application implemented in `main.c` that reads `kernel.elf`, maps its segments, prepares the `BootInfo` interface, and jumps into the kernel.
|
||||||
|
- **Kernel entry**: `kmain` in `kernel.c`, which initialises the IDT, memory, and tasking subsystems, then spawns the Starling Terminal task.
|
||||||
|
- **Subsystems**: memory management (`memory.c`), cooperative multitasking (`task.c`), interrupt/exception handling (`idt.c`), and the command/terminal layer (`commands.c` + `kernel.c`).
|
||||||
|
|
||||||
|
The remaining sections walk through this path step by step and show how these modules interact.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Firmware and boot loader (`main.c`)
|
||||||
|
|
||||||
|
The system starts execution inside the UEFI firmware, which invokes the PE32+ entry point of the loader. GNU-EFI arranges for this to be the `efi_main` function in `main.c`:
|
||||||
|
|
||||||
|
```298:345:/home/lochlan/Documents/Coding/c/os/main.c
|
||||||
|
EFI_STATUS
|
||||||
|
EFIAPI
|
||||||
|
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
||||||
|
{
|
||||||
|
EFI_STATUS Status;
|
||||||
|
VOID *KernelImage = NULL;
|
||||||
|
UINTN KernelSize = 0;
|
||||||
|
UINT64 KernelEntry = 0;
|
||||||
|
BootInfo Boot;
|
||||||
|
KernelEntryFn EntryFn = NULL;
|
||||||
|
|
||||||
|
/* Initialise the GNU-EFI library */
|
||||||
|
InitializeLib(ImageHandle, SystemTable);
|
||||||
|
|
||||||
|
Print(L"Loading kernel...\n\r");
|
||||||
|
|
||||||
|
Status = read_file_to_buffer(ImageHandle, L"\\kernel.elf", &KernelImage, &KernelSize);
|
||||||
|
...
|
||||||
|
Status = load_elf_kernel(KernelImage, KernelSize, &KernelEntry);
|
||||||
|
...
|
||||||
|
/* Populate the BootInfo struct with generic UEFI-backed services */
|
||||||
|
Boot.print = Print;
|
||||||
|
Boot.clear_screen = loader_clear_screen;
|
||||||
|
...
|
||||||
|
Boot.free_pages = loader_free_pages;
|
||||||
|
Boot.firmware_vendor = SystemTable->FirmwareVendor;
|
||||||
|
...
|
||||||
|
|
||||||
|
/* Jump to the kernel – this should not return */
|
||||||
|
EntryFn = (KernelEntryFn)(UINTN)KernelEntry;
|
||||||
|
EntryFn(&Boot);
|
||||||
|
|
||||||
|
Print(L"Kernel returned. Halting.\n\r");
|
||||||
|
return EFI_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key steps performed by `efi_main`:
|
||||||
|
|
||||||
|
- **Load kernel image**: `read_file_to_buffer` opens `\\kernel.elf` from the EFI System Partition and reads it into a pool buffer.
|
||||||
|
- **Parse ELF64**: `load_elf_kernel` verifies ELF headers and iterates PT\_LOAD segments, mapping each to its requested virtual/physical address via UEFI `AllocatePages`, then zero-fills `.bss`:
|
||||||
|
|
||||||
|
```168:223:/home/lochlan/Documents/Coding/c/os/main.c
|
||||||
|
static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut)
|
||||||
|
{
|
||||||
|
Elf64_Ehdr *Ehdr = (Elf64_Ehdr *)Image;
|
||||||
|
...
|
||||||
|
for (Index = 0; Index < Ehdr->e_phnum; Index++) {
|
||||||
|
Elf64_Phdr *Segment = (Elf64_Phdr *)((UINT8 *)Phdr + (Index * Ehdr->e_phentsize));
|
||||||
|
...
|
||||||
|
Address = (EFI_PHYSICAL_ADDRESS)SegmentStart;
|
||||||
|
if (EFI_ERROR(uefi_call_wrapper(BS->AllocatePages, 4, AllocateAddress,
|
||||||
|
EfiLoaderData, SegmentPages, &Address))) {
|
||||||
|
return EFI_OUT_OF_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyMem((VOID *)(UINTN)Segment->p_vaddr,
|
||||||
|
(UINT8 *)Image + Segment->p_offset,
|
||||||
|
(UINTN)Segment->p_filesz);
|
||||||
|
if (Segment->p_memsz > Segment->p_filesz) {
|
||||||
|
SetMem((VOID *)(UINTN)(Segment->p_vaddr + Segment->p_filesz),
|
||||||
|
(UINTN)(Segment->p_memsz - Segment->p_filesz), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*EntryOut = Ehdr->e_entry;
|
||||||
|
return EFI_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Prepare the kernel ABI**: `BootInfo` is a compact struct of function pointers and metadata shared between loader and kernel:
|
||||||
|
|
||||||
|
```21:62:/home/lochlan/Documents/Coding/c/os/boot_info.h
|
||||||
|
typedef struct {
|
||||||
|
/* Console I/O */
|
||||||
|
KernelPrintFn print;
|
||||||
|
ConsoleClearFn clear_screen;
|
||||||
|
ConsoleSetAttrFn set_attribute;
|
||||||
|
KeyReadFn read_key;
|
||||||
|
KeyReadFn try_read_key;
|
||||||
|
|
||||||
|
/* System control */
|
||||||
|
void (*shutdown)(void);
|
||||||
|
|
||||||
|
/* Physical memory */
|
||||||
|
KSTATUS (*alloc_pages)(UINTN pages, UINT64 *addr);
|
||||||
|
KSTATUS (*free_pages)(UINT64 addr, UINTN pages);
|
||||||
|
|
||||||
|
/* Firmware metadata (for informational commands only). */
|
||||||
|
const CHAR16 *firmware_vendor;
|
||||||
|
UINT32 firmware_major;
|
||||||
|
UINT32 firmware_minor;
|
||||||
|
} BootInfo;
|
||||||
|
```
|
||||||
|
|
||||||
|
UEFI-specific calls (console I/O, page allocation, shutdown) are wrapped in small adapter functions (`loader_clear_screen`, `loader_alloc_pages`, etc.) and stored in this struct. The kernel never calls firmware entry points directly; instead it depends only on `BootInfo`.
|
||||||
|
|
||||||
|
Finally, the loader:
|
||||||
|
|
||||||
|
- Casts the ELF entry point to `KernelEntryFn`.
|
||||||
|
- Invokes `EntryFn(&Boot)`, transferring control to the kernel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kernel entry and subsystem initialisation (`kernel.c`)
|
||||||
|
|
||||||
|
The C-level kernel entry point is `kmain` in `kernel.c`. It receives a single `BootInfo *` argument from the loader:
|
||||||
|
|
||||||
|
```169:254:/home/lochlan/Documents/Coding/c/os/kernel.c
|
||||||
|
void kmain(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
KSTATUS Status;
|
||||||
|
Task *terminal_task = NULL;
|
||||||
|
StarlingContext *ctx = NULL;
|
||||||
|
|
||||||
|
if (Boot == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boot->clear_screen != NULL) {
|
||||||
|
Status = Boot->clear_screen();
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boot->set_attribute != NULL) {
|
||||||
|
Status = Boot->set_attribute(TEXT_ATTR(COLOR_LIGHTGREEN, COLOR_BLACK));
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Subsystem initialisation ---- */
|
||||||
|
idt_init(Boot);
|
||||||
|
memory_init(Boot);
|
||||||
|
task_init(Boot);
|
||||||
|
|
||||||
|
/* ---- Welcome banner ---- */
|
||||||
|
SAFE_PRINT(Boot, L" Welcome to Simple 64-bit Operating System!\n\r");
|
||||||
|
...
|
||||||
|
SAFE_PRINT(Boot, L"Type 'help' for a list of commands.\n\r\n\r");
|
||||||
|
|
||||||
|
/* ---- Spawn Starling Terminal as its own task ---- */
|
||||||
|
ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
|
||||||
|
...
|
||||||
|
ctx->Boot = Boot;
|
||||||
|
ctx->depth = 0;
|
||||||
|
ctx->shell_priv = TASK_PRIV_USER;
|
||||||
|
|
||||||
|
terminal_task = task_create_with_priv(L"starling-term",
|
||||||
|
starling_terminal_task,
|
||||||
|
ctx,
|
||||||
|
TASK_PRIV_USER);
|
||||||
|
if (terminal_task == NULL) {
|
||||||
|
...
|
||||||
|
starling_terminal_task(Boot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[core] Started Starling Terminal (PID %d).\n\r", terminal_task->pid);
|
||||||
|
|
||||||
|
/* Core thread becomes an idle loop, yielding to the terminal and others. */
|
||||||
|
while (TRUE) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`kmain` performs three major duties:
|
||||||
|
|
||||||
|
1. **Console setup** – clears the screen and sets a green-on-black colour scheme using firmware-backed services from `BootInfo`.
|
||||||
|
2. **Subsystem initialisation** – calls:
|
||||||
|
- `idt_init(Boot)` to install the kernel's Interrupt Descriptor Table and exception handlers.
|
||||||
|
- `memory_init(Boot)` to bring up the physical allocator, paging helpers, and heap.
|
||||||
|
- `task_init(Boot)` to bootstrap the cooperative scheduler and register the current thread as task 0.
|
||||||
|
3. **User interface** – prints a banner and spawns the Starling Terminal as a separate task via `task_create_with_priv` with `TASK_PRIV_USER` privilege, then turns the core thread into an idle loop that continuously `task_yield`s to allow other tasks to run.
|
||||||
|
|
||||||
|
At this point, the system has:
|
||||||
|
|
||||||
|
- A working IDT for CPU exceptions and IRQs.
|
||||||
|
- A memory stack providing page allocation, virtual mappings, and heap.
|
||||||
|
- A cooperative scheduler with at least two tasks: the core thread (task 0) and the terminal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Starling Terminal and command dispatch
|
||||||
|
|
||||||
|
Interactive user input is handled by the Starling Terminal task in `kernel.c`. It runs a read–eval–print loop that delegates command execution to `commands.c`:
|
||||||
|
|
||||||
|
```47:163:/home/lochlan/Documents/Coding/c/os/kernel.c
|
||||||
|
static void starling_terminal_task(void *arg)
|
||||||
|
{
|
||||||
|
StarlingContext *ctx = (StarlingContext *)arg;
|
||||||
|
BootInfo *Boot = NULL;
|
||||||
|
KeyEvent Key;
|
||||||
|
...
|
||||||
|
|
||||||
|
if (ctx == NULL || ctx->Boot == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot = ctx->Boot;
|
||||||
|
depth = ctx->depth;
|
||||||
|
shell_priv = ctx->shell_priv;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r",
|
||||||
|
depth, (INT32)shell_priv);
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
/* Try non-blocking read first; yield to other tasks while idle */
|
||||||
|
if (Boot->try_read_key != NULL) {
|
||||||
|
Status = Boot->try_read_key(&Key);
|
||||||
|
if (Status != 0) {
|
||||||
|
task_yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (Boot->read_key != NULL) {
|
||||||
|
Status = Boot->read_key(&Key);
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
if (Key.unicode_char == L'\r' || Key.unicode_char == L'\n') {
|
||||||
|
/* Enter pressed: execute the buffered command */
|
||||||
|
line[len] = L'\0';
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
trim_spaces_inplace(line);
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
Task *cmd_task = execute_command(Boot, line, shell_priv);
|
||||||
|
|
||||||
|
/* If a command task was spawned, wait for it to finish. */
|
||||||
|
if (cmd_task != NULL) {
|
||||||
|
task_wait(cmd_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset for next command */
|
||||||
|
len = 0;
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free our context on exit (allocated by the spawner). */
|
||||||
|
kfree(ctx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key points:
|
||||||
|
|
||||||
|
- **Non-blocking idle**: when `try_read_key` returns no key, the terminal calls `task_yield()` so other tasks can run while the user is idle.
|
||||||
|
- **Line editing**: handles printable ASCII and backspace to maintain a simple line buffer (`line[128]`).
|
||||||
|
- **Command execution**: on Enter, the line is trimmed and passed to `execute_command(Boot, line, shell_priv)` in `commands.c`, propagating the shell's privilege level. If that function spawns a dedicated command task, the terminal waits for it via `task_wait`.
|
||||||
|
- **Nested terminals**: entering `starling` recursively spawns another Starling Terminal task with increased `depth`, demonstrating multi-level shells.
|
||||||
|
|
||||||
|
The command registry and dispatch path are documented in detail in `commands-and-terminal.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Subsystem overview
|
||||||
|
|
||||||
|
The kernel is organised into focused subsystems, each in its own translation unit:
|
||||||
|
|
||||||
|
- **Type layer** (`kernel_types.h`): defines fixed-width and utility types such as `UINT8`, `UINT64`, `UINTN`, and `CHAR16`, deliberately avoiding firmware headers so that core kernel code remains decoupled from UEFI.
|
||||||
|
- **Boot ABI** (`boot_info.h`): defines `BootInfo`, `KeyEvent`, and function pointer types (`KernelPrintFn`, `ConsoleClearFn`, etc.) forming the sole contract between loader and kernel.
|
||||||
|
- **Memory management** (`memory.c` + `memory.h`):
|
||||||
|
- PMM – bitmap-based page-frame allocator over a 16 MB pool obtained from the loader.
|
||||||
|
- Paging – helpers to walk and extend the 4-level x86-64 page tables, map/unmap pages, and translate virtual to physical addresses.
|
||||||
|
- Heap – first-fit free-list allocator with block splitting and coalescing, backed by PMM pages.
|
||||||
|
- **Tasks and scheduler** (`task.c` + `task.h`):
|
||||||
|
- Static process control block (PCB) pool.
|
||||||
|
- Cooperative round-robin scheduler.
|
||||||
|
- Software privilege levels (`TASK_PRIV_USER`, `TASK_PRIV_DRIVER`, `TASK_PRIV_KERNEL`) for access control.
|
||||||
|
- Stack management and context switch support (via an external `context_switch` assembly routine).
|
||||||
|
- **Interrupts and exceptions** (`idt.c` + `idt.h`):
|
||||||
|
- IDT mirroring of firmware entries.
|
||||||
|
- Replacement of CPU exception vectors 0–31 with kernel stubs.
|
||||||
|
- Central `isr_handler` that prints diagnostics and halts on unrecoverable faults.
|
||||||
|
- **Commands and shell** (`commands.c` + `commands.h`):
|
||||||
|
- Command registry with per-command minimum privilege levels and help/man system.
|
||||||
|
- System control commands (`shutdown`, `about`, `mem`, `ps`).
|
||||||
|
- Test commands (`memtest`, `tasktest`, `spawn`) that exercise memory and scheduler subsystems in isolation.
|
||||||
|
- Privilege escalation command (`kusr`) for running commands with elevated privilege.
|
||||||
|
|
||||||
|
Each of these subsystems is covered in a dedicated document:
|
||||||
|
|
||||||
|
- `memory-and-allocation.md` – PMM, paging, and heap internals.
|
||||||
|
- `tasks-and-scheduler.md` – task lifecycle, stacks, context switching, and scheduling.
|
||||||
|
- `interrupts-and-exceptions.md` – IDT construction, ISRs, and fault handling.
|
||||||
|
- `commands-and-terminal.md` – command pipeline from user input to handler execution.
|
||||||
|
|
||||||
508
docs/tasks-and-scheduler.md
Normal file
508
docs/tasks-and-scheduler.md
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
## Cooperative tasking overview
|
||||||
|
|
||||||
|
The kernel uses a **cooperative** multitasking model implemented in `task.c`. Tasks (lightweight threads) must call `task_yield` explicitly to let others run; there is no preemptive timer interrupt that forces context switches.
|
||||||
|
|
||||||
|
The key components are:
|
||||||
|
|
||||||
|
- A fixed-size **PCB pool** (`Task tasks[TASK_MAX]`).
|
||||||
|
- A per-task **stack** allocated from the PMM.
|
||||||
|
- A **scheduler** that performs round-robin selection among READY tasks.
|
||||||
|
- A `context_switch` assembly routine that saves/restores callee-saved registers and the stack pointer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module state and initialisation
|
||||||
|
|
||||||
|
The scheduler's global state is:
|
||||||
|
|
||||||
|
```30:35:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
static Task tasks[TASK_MAX]; /* PCB pool (static array) */
|
||||||
|
static Task *current_task = NULL;
|
||||||
|
static UINT32 next_pid = 0;
|
||||||
|
static BootInfo *task_boot = NULL;
|
||||||
|
static BOOLEAN task_ready = FALSE;
|
||||||
|
```
|
||||||
|
|
||||||
|
`task_init` is called from `kmain` after memory and IDT initialisation:
|
||||||
|
|
||||||
|
```62:97:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
void task_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
task_boot = Boot;
|
||||||
|
|
||||||
|
/* Clear all PCB slots */
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
tasks[i].state = TASK_STATE_FREE;
|
||||||
|
tasks[i].pid = 0;
|
||||||
|
tasks[i].privilege = TASK_PRIV_USER;
|
||||||
|
tasks[i].saved_rsp = 0;
|
||||||
|
tasks[i].stack_base = 0;
|
||||||
|
tasks[i].stack_pages = 0;
|
||||||
|
tasks[i].entry = NULL;
|
||||||
|
tasks[i].arg = NULL;
|
||||||
|
tasks[i].switches = 0;
|
||||||
|
tasks[i].name[0] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task 0 = the currently running kernel core thread.
|
||||||
|
* It already has a stack (the kernel's boot stack), so we don't
|
||||||
|
* allocate one. Its saved_rsp will be filled in during the
|
||||||
|
* first context_switch call in task_yield().
|
||||||
|
*/
|
||||||
|
tasks[0].pid = next_pid++;
|
||||||
|
tasks[0].state = TASK_STATE_RUNNING;
|
||||||
|
tasks[0].privilege = TASK_PRIV_KERNEL;
|
||||||
|
tasks[0].switches = 1;
|
||||||
|
wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN);
|
||||||
|
|
||||||
|
current_task = &tasks[0];
|
||||||
|
task_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" Tasks: scheduler ready (max %d tasks)\n\r",
|
||||||
|
(UINTN)TASK_MAX);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Important points:
|
||||||
|
|
||||||
|
- Task 0 represents the **kernel core thread**, which uses the boot-time stack provided by the loader. It receives `TASK_PRIV_KERNEL` privilege.
|
||||||
|
- No stack is allocated for task 0; its `saved_rsp` is populated the first time a context switch occurs.
|
||||||
|
- All other PCBs begin in `TASK_STATE_FREE` with `TASK_PRIV_USER`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task creation and stack layout
|
||||||
|
|
||||||
|
New tasks are created via `task_create_with_priv` (or its wrapper `task_create`), which:
|
||||||
|
|
||||||
|
1. Checks that the caller is not escalating privilege beyond its own level.
|
||||||
|
2. Finds a free PCB slot.
|
||||||
|
3. Allocates a stack from the PMM.
|
||||||
|
4. Sets up an initial stack frame so that `context_switch` can "return" into a C trampoline function.
|
||||||
|
|
||||||
|
```121:211:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
Task *task_create_with_priv(const CHAR16 *name,
|
||||||
|
TaskEntryFn entry,
|
||||||
|
void *arg,
|
||||||
|
TaskPrivilege privilege)
|
||||||
|
{
|
||||||
|
Task *t = NULL;
|
||||||
|
UINTN i;
|
||||||
|
UINT64 stack_phys;
|
||||||
|
UINT64 *sp;
|
||||||
|
...
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: prevent privilege escalation. */
|
||||||
|
{
|
||||||
|
Task *caller = task_current();
|
||||||
|
if (caller != NULL && privilege > task_get_privilege(caller)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find a free PCB slot */
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
if (tasks[i].state == TASK_STATE_FREE) {
|
||||||
|
t = &tasks[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
/* Allocate stack pages from the physical memory manager */
|
||||||
|
stack_phys = pmm_alloc_pages(TASK_STACK_PAGES);
|
||||||
|
if (stack_phys == 0) {
|
||||||
|
return NULL; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill in the PCB */
|
||||||
|
t->pid = next_pid++;
|
||||||
|
t->state = TASK_STATE_READY;
|
||||||
|
t->privilege = privilege;
|
||||||
|
t->entry = entry;
|
||||||
|
t->arg = arg;
|
||||||
|
t->switches = 0;
|
||||||
|
t->stack_base = stack_phys;
|
||||||
|
t->stack_pages = TASK_STACK_PAGES;
|
||||||
|
wstrcpy16(t->name, name != NULL ? name : L"unnamed", TASK_NAME_LEN);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the initial stack frame so that context_switch() can
|
||||||
|
* "return" into task_trampoline().
|
||||||
|
*
|
||||||
|
* context_switch saves/restores (low → high on stack):
|
||||||
|
* flags, r15, r14, r13, r12, rbx, rbp (pushes)
|
||||||
|
* then `ret` pops the return address (→ trampoline)
|
||||||
|
*
|
||||||
|
* Above the return address we place a safety-net address
|
||||||
|
* (task_exit) so that if the trampoline or entry function does
|
||||||
|
* a bare `ret`, it lands in task_exit().
|
||||||
|
*/
|
||||||
|
sp = (UINT64 *)(stack_phys + TASK_STACK_SIZE);
|
||||||
|
|
||||||
|
/* Align stack top to 16 bytes */
|
||||||
|
sp = (UINT64 *)((UINT64)sp & ~0xFULL);
|
||||||
|
|
||||||
|
/* Safety-net return address for the trampoline */
|
||||||
|
*(--sp) = (UINT64)(UINTN)task_exit;
|
||||||
|
|
||||||
|
/* Return address for context_switch's `ret` → trampoline */
|
||||||
|
*(--sp) = (UINT64)(UINTN)task_trampoline;
|
||||||
|
|
||||||
|
/* Callee-saved registers – all zero for fresh task */
|
||||||
|
*(--sp) = 0; /* rbp */
|
||||||
|
*(--sp) = 0; /* rbx */
|
||||||
|
*(--sp) = 0; /* r12 */
|
||||||
|
*(--sp) = 0; /* r13 */
|
||||||
|
*(--sp) = 0; /* r14 */
|
||||||
|
*(--sp) = 0; /* r15 */
|
||||||
|
|
||||||
|
/* RFLAGS – interrupts enabled (IF = bit 9) */
|
||||||
|
*(--sp) = 0x202; /* flags */
|
||||||
|
|
||||||
|
t->saved_rsp = (UINT64)(UINTN)sp;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The convenience wrapper `task_create` inherits the calling task's privilege level:
|
||||||
|
|
||||||
|
```213:220:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg)
|
||||||
|
{
|
||||||
|
/* Inherit privilege from the calling task (kernel if no task context). */
|
||||||
|
Task *caller = task_current();
|
||||||
|
TaskPrivilege priv = (caller != NULL) ? task_get_privilege(caller)
|
||||||
|
: TASK_PRIV_KERNEL;
|
||||||
|
return task_create_with_priv(name, entry, arg, priv);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The effective stack layout (low to high addresses) after `task_create` is:
|
||||||
|
|
||||||
|
- Saved `flags`, `r15`, `r14`, `r13`, `r12`, `rbx`, `rbp` (pushed by `context_switch` semantics).
|
||||||
|
- Return address to `task_trampoline`.
|
||||||
|
- Safety-net return address to `task_exit`.
|
||||||
|
|
||||||
|
This design guarantees that:
|
||||||
|
|
||||||
|
- The first time the scheduler chooses this task, restoring registers and issuing `ret` will jump to `task_trampoline`.
|
||||||
|
- If the trampoline or entry function ever returns normally, execution will fall into `task_exit` rather than running off the end of the stack.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trampoline and task entry
|
||||||
|
|
||||||
|
The trampoline is a small C function that calls the user-supplied entry point and then terminates the task cleanly:
|
||||||
|
|
||||||
|
```105:116:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
static void task_trampoline(void)
|
||||||
|
{
|
||||||
|
Task *t = task_current();
|
||||||
|
if (t != NULL && t->entry != NULL) {
|
||||||
|
t->entry(t->arg);
|
||||||
|
}
|
||||||
|
task_exit();
|
||||||
|
/* Should never reach here, but just in case: */
|
||||||
|
for (;;) {
|
||||||
|
__asm__ __volatile__("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The entry function signature is:
|
||||||
|
|
||||||
|
```12:17:/home/lochlan/Documents/Coding/c/os/task.h
|
||||||
|
typedef void (*TaskEntryFn)(void *arg);
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes a task analogous to a `pthread`:
|
||||||
|
|
||||||
|
- It receives an opaque `void *arg`.
|
||||||
|
- It runs arbitrary kernel code.
|
||||||
|
- On completion it returns to `task_trampoline`, which calls `task_exit`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scheduling and `task_yield`
|
||||||
|
|
||||||
|
The scheduler is purely cooperative and uses a simple **round-robin** algorithm implemented by `schedule_next`:
|
||||||
|
|
||||||
|
```203:230:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
static Task *schedule_next(void)
|
||||||
|
{
|
||||||
|
UINTN start, idx, i;
|
||||||
|
|
||||||
|
if (current_task == NULL) {
|
||||||
|
return &tasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find current task's index in the array */
|
||||||
|
start = (UINTN)(current_task - tasks);
|
||||||
|
|
||||||
|
/* Round-robin: scan from (current+1) wrapping around */
|
||||||
|
for (i = 1; i <= TASK_MAX; i++) {
|
||||||
|
idx = (start + i) % TASK_MAX;
|
||||||
|
if (tasks[idx].state == TASK_STATE_READY) {
|
||||||
|
return &tasks[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No other ready task – stay with current if still runnable */
|
||||||
|
if (current_task->state == TASK_STATE_RUNNING ||
|
||||||
|
current_task->state == TASK_STATE_READY) {
|
||||||
|
return current_task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback to task 0 (kernel / shell) */
|
||||||
|
return &tasks[0];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`task_yield` is the public API that tasks call to give up the CPU:
|
||||||
|
|
||||||
|
```236:266:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
void task_yield(void)
|
||||||
|
{
|
||||||
|
Task *prev, *next;
|
||||||
|
|
||||||
|
if (!task_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current_task;
|
||||||
|
next = schedule_next();
|
||||||
|
|
||||||
|
if (next == prev) {
|
||||||
|
return; /* nothing else to switch to */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the previous task as READY (still runnable) */
|
||||||
|
if (prev->state == TASK_STATE_RUNNING) {
|
||||||
|
prev->state = TASK_STATE_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
next->state = TASK_STATE_RUNNING;
|
||||||
|
next->switches++;
|
||||||
|
current_task = next;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* context_switch saves callee-saved regs + flags on prev's stack,
|
||||||
|
* stores prev's RSP into prev->saved_rsp, loads next->saved_rsp
|
||||||
|
* into RSP, restores regs + flags, and `ret`s into next's code.
|
||||||
|
*/
|
||||||
|
context_switch(&prev->saved_rsp, next->saved_rsp);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The actual register-level state transition is performed by an external assembly function:
|
||||||
|
|
||||||
|
```18:22:/home/lochlan/Documents/Coding/c/os/task.h
|
||||||
|
void context_switch(UINT64 *prev_rsp, UINT64 next_rsp);
|
||||||
|
```
|
||||||
|
|
||||||
|
Conceptually, `context_switch`:
|
||||||
|
|
||||||
|
- Pushes callee-saved registers and FLAGS on the current stack.
|
||||||
|
- Stores the resulting stack pointer in `*prev_rsp`.
|
||||||
|
- Loads `next_rsp` into RSP.
|
||||||
|
- Pops registers and FLAGS from the new stack.
|
||||||
|
- Issues `ret`, returning into the next task's code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task termination (`task_exit`)
|
||||||
|
|
||||||
|
Tasks terminate by calling `task_exit`, typically via the trampoline:
|
||||||
|
|
||||||
|
```272:305:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
void task_exit(void)
|
||||||
|
{
|
||||||
|
Task *prev, *next;
|
||||||
|
|
||||||
|
if (!task_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current_task;
|
||||||
|
prev->state = TASK_STATE_TERMINATED;
|
||||||
|
|
||||||
|
/* Free the stack memory back to the PMM */
|
||||||
|
if (prev->stack_base != 0 && prev->stack_pages != 0) {
|
||||||
|
pmm_free_pages(prev->stack_base, prev->stack_pages);
|
||||||
|
prev->stack_base = 0;
|
||||||
|
prev->stack_pages = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the PCB slot as free for reuse */
|
||||||
|
prev->state = TASK_STATE_FREE;
|
||||||
|
|
||||||
|
next = schedule_next();
|
||||||
|
if (next == prev) {
|
||||||
|
/* Shouldn't happen if task 0 (kernel) is always alive */
|
||||||
|
next = &tasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
next->state = TASK_STATE_RUNNING;
|
||||||
|
next->switches++;
|
||||||
|
current_task = next;
|
||||||
|
|
||||||
|
/* One-way switch: we never return to the exited task */
|
||||||
|
context_switch(&prev->saved_rsp, next->saved_rsp);
|
||||||
|
|
||||||
|
/* Should never reach here */
|
||||||
|
for (;;) {
|
||||||
|
__asm__ __volatile__("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key behaviours:
|
||||||
|
|
||||||
|
- The task's stack pages are returned to the PMM via `pmm_free_pages`.
|
||||||
|
- The PCB slot is recycled back to `TASK_STATE_FREE`.
|
||||||
|
- The subsequent `context_switch` is **one-way**: control never returns to the exited task.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Waiting for tasks
|
||||||
|
|
||||||
|
Certain parts of the kernel (e.g., the Starling Terminal and some commands) need to wait for a worker task to finish. This is done cooperatively via `task_wait`:
|
||||||
|
|
||||||
|
```336:348:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
void task_wait(Task *t)
|
||||||
|
{
|
||||||
|
if (!task_ready || t == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Busy-wait cooperatively until the target task's PCB slot has
|
||||||
|
* been recycled back to FREE by task_exit().
|
||||||
|
*/
|
||||||
|
while (t->state != TASK_STATE_FREE) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the scheduler is cooperative, this **busy-wait** loop is benign: it yields on each iteration, allowing the waited-on task to make progress and eventually call `task_exit`.
|
||||||
|
|
||||||
|
Example usage from the Starling Terminal:
|
||||||
|
|
||||||
|
```135:140:/home/lochlan/Documents/Coding/c/os/kernel.c
|
||||||
|
Task *cmd_task = execute_command(Boot, line, shell_priv);
|
||||||
|
|
||||||
|
/* If a command task was spawned, wait for it to finish. */
|
||||||
|
if (cmd_task != NULL) {
|
||||||
|
task_wait(cmd_task);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task inspection (`ps` and `tasktest`)
|
||||||
|
|
||||||
|
The `ps` command uses `task_print_list` to show current tasks. Access requires at least `TASK_PRIV_DRIVER`:
|
||||||
|
|
||||||
|
```407:439:/home/lochlan/Documents/Coding/c/os/task.c
|
||||||
|
void task_print_list(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
Task *caller;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: task list requires DRIVER. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_DRIVER) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: task list requires driver privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" PID STATE PRIV SWITCHES NAME\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" --- ---------- ---- -------- ----\n\r");
|
||||||
|
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
if (tasks[i].state == TASK_STATE_FREE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" %3d %-10s %4d %8d %s\n\r",
|
||||||
|
tasks[i].pid,
|
||||||
|
state_str(tasks[i].state),
|
||||||
|
(INT32)tasks[i].privilege,
|
||||||
|
tasks[i].switches,
|
||||||
|
tasks[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Active tasks: %d / %d\n\r",
|
||||||
|
task_count(), (UINTN)TASK_MAX);
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `tasktest` command in `commands.c` programmatically exercises the scheduler:
|
||||||
|
|
||||||
|
```400:435:/home/lochlan/Documents/Coding/c/os/commands.c
|
||||||
|
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args)
|
||||||
|
{
|
||||||
|
Task *t1, *t2, *t3;
|
||||||
|
UINTN i;
|
||||||
|
(void)Args;
|
||||||
|
...
|
||||||
|
t1 = task_create(L"worker-A", worker_task_fn, Boot);
|
||||||
|
t2 = task_create(L"worker-B", worker_task_fn, Boot);
|
||||||
|
t3 = task_create(L"worker-C", worker_task_fn, Boot);
|
||||||
|
...
|
||||||
|
SAFE_PRINT(Boot, L"\n\rYielding to let workers run:\n\r\n\r");
|
||||||
|
|
||||||
|
/* Yield enough times for all workers to complete (3 tasks x 3 steps) */
|
||||||
|
for (i = 0; i < 12; i++) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\rTask list after test:\n\r");
|
||||||
|
task_print_list(Boot);
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each worker task:
|
||||||
|
|
||||||
|
- Prints a progress message.
|
||||||
|
- Calls `task_yield`.
|
||||||
|
- Repeats three times, then finishes.
|
||||||
|
|
||||||
|
This demonstrates how cooperative tasks interleave output and how `task_yield` drives scheduling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Privilege system
|
||||||
|
|
||||||
|
Each task carries a `TaskPrivilege` level defined in `task.h`:
|
||||||
|
|
||||||
|
```47:52:/home/lochlan/Documents/Coding/c/os/task.h
|
||||||
|
typedef enum {
|
||||||
|
TASK_PRIV_USER = 0,
|
||||||
|
TASK_PRIV_DRIVER = 1,
|
||||||
|
TASK_PRIV_KERNEL = 2,
|
||||||
|
} TaskPrivilege;
|
||||||
|
```
|
||||||
|
|
||||||
|
All tasks still execute in CPU ring 0; this is a **software-only** hierarchy used for access control decisions:
|
||||||
|
|
||||||
|
- `task_create_with_priv` prevents a caller from creating a task with a higher privilege than its own.
|
||||||
|
- Subsystem functions like `memory_print_stats`, `task_print_list`, and `request_shutdown` check the calling task's privilege before proceeding.
|
||||||
|
- The `kusr` command (`commands.c`) temporarily elevates a task to `TASK_PRIV_KERNEL` to run a privileged sub-command, then restores the original level.
|
||||||
|
|
||||||
|
Accessors:
|
||||||
|
|
||||||
|
- `task_get_privilege(Task *t)` – returns the task's current privilege level.
|
||||||
|
- `task_set_privilege(Task *t, TaskPrivilege p)` – changes it (no enforcement; callers are responsible).
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
24
idt.h
24
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 "kernel_types.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
|
||||||
|
|||||||
283
kernel.c
283
kernel.c
@@ -1,9 +1,25 @@
|
|||||||
#include <efi.h>
|
/*
|
||||||
|
* kernel.c – Kernel entry point and Starling Terminal task.
|
||||||
|
*
|
||||||
|
* kmain() is called by the loader (main.c) after the ELF kernel has been
|
||||||
|
* mapped into memory. It initialises subsystems (IDT, memory, tasks),
|
||||||
|
* prints a welcome banner, and then spawns the Starling Terminal as a
|
||||||
|
* dedicated task. The terminal task runs the interactive read-eval-print
|
||||||
|
* loop that dispatches typed commands via commands.c.
|
||||||
|
*
|
||||||
|
* While the terminal waits for keyboard input it yields to the cooperative
|
||||||
|
* scheduler so that background tasks can make progress.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kernel_types.h"
|
||||||
#include "boot_info.h"
|
#include "boot_info.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "string_utils.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,11 +27,158 @@
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
/* Simple text attribute helper (modelled after UEFI TEXT_ATTR). */
|
||||||
|
#define TEXT_ATTR(fg, bg) (UINTN)(((fg) & 0x0F) | (((bg) & 0x0F) << 4))
|
||||||
|
|
||||||
|
/* Basic colour constants (compatible with UEFI's palette). */
|
||||||
|
#define COLOR_BLACK 0x0
|
||||||
|
#define COLOR_LIGHTGREEN 0xA
|
||||||
|
|
||||||
|
/* Simple context passed to each Starling Terminal instance. */
|
||||||
|
typedef struct {
|
||||||
|
BootInfo *Boot;
|
||||||
|
UINTN depth; /* 0 = top-level, 1+ = nested shells */
|
||||||
|
TaskPrivilege shell_priv; /* logical privilege for this shell */
|
||||||
|
} StarlingContext;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Starling Terminal task – interactive command loop
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void starling_terminal_task(void *arg)
|
||||||
|
{
|
||||||
|
StarlingContext *ctx = (StarlingContext *)arg;
|
||||||
|
BootInfo *Boot = NULL;
|
||||||
|
KeyEvent Key;
|
||||||
|
KSTATUS Status;
|
||||||
|
UINTN read_errors = 0;
|
||||||
|
CHAR16 line[128];
|
||||||
|
UINTN len = 0;
|
||||||
|
UINTN depth = 0;
|
||||||
|
TaskPrivilege shell_priv;
|
||||||
|
|
||||||
|
if (ctx == NULL || ctx->Boot == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot = ctx->Boot;
|
||||||
|
depth = ctx->depth;
|
||||||
|
shell_priv = ctx->shell_priv;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r",
|
||||||
|
depth, (INT32)shell_priv);
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
/* Try non-blocking read first; yield to other tasks while idle */
|
||||||
|
if (Boot->try_read_key != NULL) {
|
||||||
|
Status = Boot->try_read_key(&Key);
|
||||||
|
if (Status != 0) {
|
||||||
|
task_yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (Boot->read_key != NULL) {
|
||||||
|
Status = Boot->read_key(&Key);
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Status != 0) {
|
||||||
|
read_errors++;
|
||||||
|
if (read_errors == 1 || (read_errors % 64) == 0) {
|
||||||
|
SAFE_PRINT(Boot, L"read_key failed (status=%ld)\n\r",
|
||||||
|
(UINT64)Status);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
read_errors = 0;
|
||||||
|
|
||||||
|
if (Key.unicode_char == L'\r' || Key.unicode_char == L'\n') {
|
||||||
|
/* Enter pressed: execute the buffered command */
|
||||||
|
line[len] = L'\0';
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
trim_spaces_inplace(line);
|
||||||
|
|
||||||
|
/* Built-in terminal controls: exit / spawn nested Starling. */
|
||||||
|
if (ascii_streq_ci(line, L"exit")) {
|
||||||
|
if (depth == 0) {
|
||||||
|
SAFE_PRINT(Boot, L"Starling: cannot exit top-level terminal.\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L"Exiting Starling Terminal depth %d...\n\r", depth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (ascii_streq_ci(line, L"starling")) {
|
||||||
|
StarlingContext *child_ctx;
|
||||||
|
Task *child_task;
|
||||||
|
|
||||||
|
child_ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
|
||||||
|
if (child_ctx == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Starling: failed to allocate nested terminal context.\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
} else {
|
||||||
|
child_ctx->Boot = Boot;
|
||||||
|
child_ctx->depth = depth + 1;
|
||||||
|
child_ctx->shell_priv = shell_priv;
|
||||||
|
|
||||||
|
child_task = task_create_with_priv(L"starling-term",
|
||||||
|
starling_terminal_task,
|
||||||
|
child_ctx,
|
||||||
|
shell_priv);
|
||||||
|
if (child_task == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Starling: failed to spawn nested terminal.\n\r");
|
||||||
|
kfree(child_ctx);
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
} else {
|
||||||
|
SAFE_PRINT(Boot, L"[starling] spawned nested terminal (PID %d, depth %d)\n\r",
|
||||||
|
child_task->pid, child_ctx->depth);
|
||||||
|
/* Block this shell until the child terminal exits. */
|
||||||
|
task_wait(child_task);
|
||||||
|
SAFE_PRINT(Boot, L"[starling] returned from nested terminal (depth %d)\n\r", depth);
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Task *cmd_task = execute_command(Boot, line, shell_priv);
|
||||||
|
|
||||||
|
/* If a command task was spawned, wait for it to finish. */
|
||||||
|
if (cmd_task != NULL) {
|
||||||
|
task_wait(cmd_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset for next command */
|
||||||
|
len = 0;
|
||||||
|
SAFE_PRINT(Boot, L"starling> ");
|
||||||
|
}
|
||||||
|
} else if (Key.scan_code == 0x08 || Key.unicode_char == L'\b' || Key.unicode_char == 0x7F) {
|
||||||
|
/* Backspace */
|
||||||
|
if (len > 0) {
|
||||||
|
len--;
|
||||||
|
SAFE_PRINT(Boot, L"\b \b");
|
||||||
|
}
|
||||||
|
} else if (Key.unicode_char >= 32 && Key.unicode_char < 127) {
|
||||||
|
/* Printable ASCII */
|
||||||
|
if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
|
||||||
|
line[len++] = Key.unicode_char;
|
||||||
|
SAFE_PRINT(Boot, L"%c", Key.unicode_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free our context on exit (allocated by the spawner). */
|
||||||
|
kfree(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Kernel entry point
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
void kmain(BootInfo *Boot)
|
void kmain(BootInfo *Boot)
|
||||||
{
|
{
|
||||||
EFI_INPUT_KEY Key;
|
KSTATUS Status;
|
||||||
EFI_STATUS Status;
|
Task *terminal_task = NULL;
|
||||||
UINTN read_errors = 0;
|
StarlingContext *ctx = NULL;
|
||||||
|
|
||||||
if (Boot == NULL) {
|
if (Boot == NULL) {
|
||||||
return;
|
return;
|
||||||
@@ -23,90 +186,82 @@ void kmain(BootInfo *Boot)
|
|||||||
|
|
||||||
if (Boot->clear_screen != NULL) {
|
if (Boot->clear_screen != NULL) {
|
||||||
Status = Boot->clear_screen();
|
Status = Boot->clear_screen();
|
||||||
if (EFI_ERROR(Status)) {
|
if (Status != 0) {
|
||||||
SAFE_PRINT(Boot, L"clear_screen failed: %r\n\r", Status);
|
SAFE_PRINT(Boot, L"clear_screen failed (status=%ld)\n\r",
|
||||||
|
(UINT64)Status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Boot->set_attribute != NULL) {
|
if (Boot->set_attribute != NULL) {
|
||||||
Status = Boot->set_attribute(EFI_TEXT_ATTR(EFI_LIGHTGREEN, EFI_BLACK));
|
Status = Boot->set_attribute(TEXT_ATTR(COLOR_LIGHTGREEN, COLOR_BLACK));
|
||||||
if (EFI_ERROR(Status)) {
|
if (Status != 0) {
|
||||||
SAFE_PRINT(Boot, L"set_attribute failed: %r\n\r", Status);
|
SAFE_PRINT(Boot, L"set_attribute failed (status=%ld)\n\r",
|
||||||
|
(UINT64)Status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Subsystem initialisation ---- */
|
||||||
idt_init(Boot);
|
idt_init(Boot);
|
||||||
|
memory_init(Boot);
|
||||||
|
task_init(Boot);
|
||||||
|
|
||||||
SAFE_PRINT(Boot, L"================================================\n\r");
|
/* ---- Welcome banner ---- */
|
||||||
SAFE_PRINT(Boot, L" Welcome to Simple UEFI Operating System!\n\r");
|
SAFE_PRINT(Boot, L" Welcome to Simple 64-bit 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");
|
||||||
SAFE_PRINT(Boot, L"System Information:\n\r");
|
SAFE_PRINT(Boot, L"System Information:\n\r");
|
||||||
if (Boot->SystemTable != NULL) {
|
if (Boot->firmware_vendor != NULL) {
|
||||||
SAFE_PRINT(Boot, L" UEFI Firmware Vendor: %s\n\r",
|
SAFE_PRINT(Boot, L" Firmware Vendor: %s\n\r", Boot->firmware_vendor);
|
||||||
Boot->SystemTable->FirmwareVendor);
|
SAFE_PRINT(Boot, L" Firmware Revision: %d.%d\n\r",
|
||||||
SAFE_PRINT(Boot, L" UEFI Firmware Revision: %d.%d\n\r",
|
Boot->firmware_major, Boot->firmware_minor);
|
||||||
Boot->SystemTable->FirmwareRevision >> 16,
|
|
||||||
Boot->SystemTable->FirmwareRevision & 0xFFFF);
|
|
||||||
} else {
|
} else {
|
||||||
SAFE_PRINT(Boot, L" UEFI System Table: Unavailable\n\r");
|
SAFE_PRINT(Boot, L" Firmware information: Unavailable\n\r");
|
||||||
}
|
}
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
SAFE_PRINT(Boot, L"Available Services:\n\r");
|
SAFE_PRINT(Boot, L"Available Services:\n\r");
|
||||||
SAFE_PRINT(Boot, L" - Console Input/Output: %s\n\r",
|
SAFE_PRINT(Boot, L" - Console Input/Output: %s\n\r",
|
||||||
(Boot->read_key != NULL && Boot->print != NULL) ? L"Active" : L"Unavailable");
|
(Boot->read_key != NULL && Boot->print != NULL) ? L"Active" : L"Unavailable");
|
||||||
SAFE_PRINT(Boot, L" - Runtime Services: %s\n\r",
|
SAFE_PRINT(Boot, L" - Terminal: Starling Terminal\n\r");
|
||||||
(Boot->SystemTable != NULL &&
|
|
||||||
Boot->SystemTable->RuntimeServices != NULL) ? L"Active" : L"Unavailable");
|
|
||||||
SAFE_PRINT(Boot, L" - Boot Services: %s\n\r",
|
|
||||||
(Boot->SystemTable != NULL &&
|
|
||||||
Boot->SystemTable->BootServices != NULL) ? L"Active" : L"Unavailable");
|
|
||||||
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
|
/* ---- Spawn Starling Terminal as its own task ---- */
|
||||||
CHAR16 line[128];
|
ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
|
||||||
UINTN len = 0;
|
if (ctx == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to allocate Starling Terminal context; starting inline.\n\r");
|
||||||
|
StarlingContext inline_ctx;
|
||||||
|
inline_ctx.Boot = Boot;
|
||||||
|
inline_ctx.depth = 0;
|
||||||
|
inline_ctx.shell_priv = TASK_PRIV_USER;
|
||||||
|
starling_terminal_task(&inline_ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SAFE_PRINT(Boot, L"-> ");
|
ctx->Boot = Boot;
|
||||||
|
ctx->depth = 0;
|
||||||
|
ctx->shell_priv = TASK_PRIV_USER;
|
||||||
|
|
||||||
|
terminal_task = task_create_with_priv(L"starling-term",
|
||||||
|
starling_terminal_task,
|
||||||
|
ctx,
|
||||||
|
TASK_PRIV_USER);
|
||||||
|
if (terminal_task == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"Failed to start Starling Terminal task; falling back to kernel loop.\n\r");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fall back to running the terminal loop directly in the core
|
||||||
|
* thread so the system remains usable even if task creation
|
||||||
|
* fails for some reason.
|
||||||
|
*/
|
||||||
|
starling_terminal_task(Boot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"[core] Started Starling Terminal (PID %d).\n\r", terminal_task->pid);
|
||||||
|
|
||||||
|
/* Core thread becomes an idle loop, yielding to the terminal and others. */
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
if (Boot->read_key == NULL) {
|
task_yield();
|
||||||
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = Boot->read_key(&Key);
|
|
||||||
if (EFI_ERROR(Status)) {
|
|
||||||
read_errors++;
|
|
||||||
if (read_errors == 1 || (read_errors % 64) == 0) {
|
|
||||||
SAFE_PRINT(Boot, L"read_key failed: %r\n\r", Status);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
read_errors = 0;
|
|
||||||
|
|
||||||
if (Key.UnicodeChar == L'\r' || Key.UnicodeChar == L'\n') {
|
|
||||||
// Enter pressed: terminate string and handle
|
|
||||||
line[len] = L'\0';
|
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
|
||||||
execute_command(Boot, line);
|
|
||||||
|
|
||||||
// Reset for next command
|
|
||||||
len = 0;
|
|
||||||
SAFE_PRINT(Boot, L"-> ");
|
|
||||||
} else if (Key.ScanCode == 0x08 || Key.UnicodeChar == L'\b' || Key.UnicodeChar == 0x7F) {
|
|
||||||
// Backspace (ScanCode 0x08 is backspace in UEFI)
|
|
||||||
if (len > 0) {
|
|
||||||
len--;
|
|
||||||
SAFE_PRINT(Boot, L"\b \b");
|
|
||||||
}
|
|
||||||
} else if (Key.UnicodeChar >= 32 && Key.UnicodeChar < 127) {
|
|
||||||
if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
|
|
||||||
line[len++] = 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*)
|
||||||
|
|||||||
40
kernel_types.h
Normal file
40
kernel_types.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* kernel_types.h – Common fixed-width and utility types for the kernel.
|
||||||
|
*
|
||||||
|
* This header deliberately avoids pulling in any firmware- or
|
||||||
|
* platform-specific headers (such as UEFI's <efi.h>). All core kernel
|
||||||
|
* code should include this instead of <efi.h>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KERNEL_TYPES_H
|
||||||
|
#define KERNEL_TYPES_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint8_t UINT8;
|
||||||
|
typedef uint16_t UINT16;
|
||||||
|
typedef uint32_t UINT32;
|
||||||
|
typedef uint64_t UINT64;
|
||||||
|
|
||||||
|
typedef int32_t INT32;
|
||||||
|
|
||||||
|
typedef size_t UINTN;
|
||||||
|
|
||||||
|
#ifndef BOOLEAN
|
||||||
|
typedef bool BOOLEAN;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef uint16_t CHAR16;
|
||||||
|
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE ((BOOLEAN)true)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FALSE
|
||||||
|
#define FALSE ((BOOLEAN)false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* KERNEL_TYPES_H */
|
||||||
|
|
||||||
167
main.c
167
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 generic 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,21 +223,48 @@ static EFI_STATUS load_elf_kernel(VOID *Image, UINTN Size, UINT64 *EntryOut)
|
|||||||
return EFI_SUCCESS;
|
return EFI_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EFI_STATUS loader_clear_screen(void)
|
/* ================================================================
|
||||||
|
* UEFI service wrappers (passed to the kernel via BootInfo)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static KSTATUS loader_clear_screen(void)
|
||||||
{
|
{
|
||||||
return uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
|
EFI_STATUS Status = uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
|
||||||
|
return (KSTATUS)Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EFI_STATUS loader_set_attribute(UINTN Attribute)
|
static KSTATUS loader_set_attribute(UINTN Attribute)
|
||||||
{
|
{
|
||||||
return uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, Attribute);
|
EFI_STATUS Status = uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, Attribute);
|
||||||
|
return (KSTATUS)Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EFI_STATUS loader_read_key(EFI_INPUT_KEY *Key)
|
static KSTATUS loader_read_key(KeyEvent *Key)
|
||||||
{
|
{
|
||||||
|
EFI_STATUS Status;
|
||||||
|
EFI_INPUT_KEY EfiKey;
|
||||||
UINTN Index = 0;
|
UINTN Index = 0;
|
||||||
|
|
||||||
uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
|
uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
|
||||||
return uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, Key);
|
Status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &EfiKey);
|
||||||
|
if (!EFI_ERROR(Status) && Key != NULL) {
|
||||||
|
Key->scan_code = EfiKey.ScanCode;
|
||||||
|
Key->unicode_char = EfiKey.UnicodeChar;
|
||||||
|
}
|
||||||
|
return (KSTATUS)Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static KSTATUS loader_try_read_key(KeyEvent *Key)
|
||||||
|
{
|
||||||
|
EFI_STATUS Status;
|
||||||
|
EFI_INPUT_KEY EfiKey;
|
||||||
|
|
||||||
|
Status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &EfiKey);
|
||||||
|
if (!EFI_ERROR(Status) && Key != NULL) {
|
||||||
|
Key->scan_code = EfiKey.ScanCode;
|
||||||
|
Key->unicode_char = EfiKey.UnicodeChar;
|
||||||
|
}
|
||||||
|
return (KSTATUS)Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loader_shutdown(void)
|
static void loader_shutdown(void)
|
||||||
@@ -207,6 +272,29 @@ static void loader_shutdown(void)
|
|||||||
uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
|
uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static KSTATUS loader_alloc_pages(UINTN pages, UINT64 *addr)
|
||||||
|
{
|
||||||
|
EFI_PHYSICAL_ADDRESS Phys = 0;
|
||||||
|
EFI_STATUS Status = uefi_call_wrapper(BS->AllocatePages, 4,
|
||||||
|
AllocateAnyPages, EfiLoaderData,
|
||||||
|
pages, &Phys);
|
||||||
|
if (!EFI_ERROR(Status) && addr != NULL) {
|
||||||
|
*addr = (UINT64)Phys;
|
||||||
|
}
|
||||||
|
return (KSTATUS)Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static KSTATUS loader_free_pages(UINT64 addr, UINTN pages)
|
||||||
|
{
|
||||||
|
EFI_STATUS Status = uefi_call_wrapper(BS->FreePages, 2,
|
||||||
|
(EFI_PHYSICAL_ADDRESS)addr, pages);
|
||||||
|
return (KSTATUS)Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 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)
|
||||||
@@ -218,7 +306,7 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
|||||||
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");
|
||||||
@@ -235,13 +323,20 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
|||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
Boot.SystemTable = ST;
|
/* Populate the BootInfo struct with generic UEFI-backed services */
|
||||||
Boot.print = Print;
|
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.shutdown = loader_shutdown;
|
Boot.try_read_key = loader_try_read_key;
|
||||||
|
Boot.shutdown = loader_shutdown;
|
||||||
|
Boot.alloc_pages = loader_alloc_pages;
|
||||||
|
Boot.free_pages = loader_free_pages;
|
||||||
|
Boot.firmware_vendor = SystemTable->FirmwareVendor;
|
||||||
|
Boot.firmware_major = (SystemTable->FirmwareRevision >> 16) & 0xFFFF;
|
||||||
|
Boot.firmware_minor = SystemTable->FirmwareRevision & 0xFFFF;
|
||||||
|
|
||||||
|
/* Jump to the kernel – this should not return */
|
||||||
EntryFn = (KernelEntryFn)(UINTN)KernelEntry;
|
EntryFn = (KernelEntryFn)(UINTN)KernelEntry;
|
||||||
EntryFn(&Boot);
|
EntryFn(&Boot);
|
||||||
|
|
||||||
|
|||||||
571
memory.c
Normal file
571
memory.c
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
/*
|
||||||
|
* memory.c – Kernel memory management.
|
||||||
|
*
|
||||||
|
* Implements three layers:
|
||||||
|
* PMM – bitmap-based physical page-frame allocator backed by a
|
||||||
|
* 16 MB pool obtained from the loader 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 "task.h"
|
||||||
|
|
||||||
|
/* Null-safe print helper used throughout the kernel. */
|
||||||
|
#define SAFE_PRINT(Boot, ...) \
|
||||||
|
do { \
|
||||||
|
if ((Boot) != NULL && (Boot)->print != NULL) { \
|
||||||
|
(Boot)->print(__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Physical Memory Manager – bitmap-based page-frame allocator
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static UINT64 pmm_pool_base = 0;
|
||||||
|
static UINTN pmm_total_pages = 0;
|
||||||
|
static UINTN pmm_free_count = 0;
|
||||||
|
static UINT8 pmm_bitmap[PMM_POOL_PAGES / 8];
|
||||||
|
static BOOLEAN pmm_ready = FALSE;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* PMM – bitmap helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* Mark page `idx` as allocated. */
|
||||||
|
static void pmm_set_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark page `idx` as free. */
|
||||||
|
static void pmm_clear_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return TRUE if page `idx` is currently allocated. */
|
||||||
|
static BOOLEAN pmm_test_bit(UINTN idx)
|
||||||
|
{
|
||||||
|
return (pmm_bitmap[idx / 8] & (1U << (idx % 8))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* PMM – public interface
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise the PMM: request PMM_POOL_PAGES from the loader via
|
||||||
|
* BootInfo->alloc_pages() and set up the bitmap with all pages
|
||||||
|
* marked free.
|
||||||
|
*/
|
||||||
|
void pmm_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
KSTATUS Status;
|
||||||
|
UINT64 pool_addr = 0;
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
/* Zero the bitmap – all pages start free */
|
||||||
|
for (i = 0; i < sizeof(pmm_bitmap); i++) {
|
||||||
|
pmm_bitmap[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boot == NULL || Boot->alloc_pages == NULL) {
|
||||||
|
SAFE_PRINT(Boot, L"PMM: page allocator unavailable\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = Boot->alloc_pages(PMM_POOL_PAGES, &pool_addr);
|
||||||
|
if (Status != 0) {
|
||||||
|
SAFE_PRINT(Boot, L"PMM: failed to allocate pool (%d pages), status=%ld\n\r",
|
||||||
|
(UINTN)PMM_POOL_PAGES, (UINT64)Status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pmm_pool_base = (UINT64)pool_addr;
|
||||||
|
pmm_total_pages = PMM_POOL_PAGES;
|
||||||
|
pmm_free_count = PMM_POOL_PAGES;
|
||||||
|
pmm_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" PMM : %d pages (%d KB) at 0x%lx\n\r",
|
||||||
|
pmm_total_pages,
|
||||||
|
(pmm_total_pages * PAGE_SIZE) / 1024,
|
||||||
|
pmm_pool_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate a single 4 KB page. Returns physical address or 0. */
|
||||||
|
UINT64 pmm_alloc_page(void)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
if (!pmm_ready || pmm_free_count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < pmm_total_pages; i++) {
|
||||||
|
if (!pmm_test_bit(i)) {
|
||||||
|
pmm_set_bit(i);
|
||||||
|
pmm_free_count--;
|
||||||
|
return pmm_pool_base + ((UINT64)i * PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free a single page previously returned by pmm_alloc_page(). */
|
||||||
|
void pmm_free_page(UINT64 phys_addr)
|
||||||
|
{
|
||||||
|
UINTN idx;
|
||||||
|
|
||||||
|
if (!pmm_ready) return;
|
||||||
|
if (phys_addr < pmm_pool_base) return;
|
||||||
|
|
||||||
|
idx = (UINTN)((phys_addr - pmm_pool_base) / PAGE_SIZE);
|
||||||
|
if (idx >= pmm_total_pages) return;
|
||||||
|
if (!pmm_test_bit(idx)) return; /* already free */
|
||||||
|
|
||||||
|
pmm_clear_bit(idx);
|
||||||
|
pmm_free_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate `count` physically contiguous pages (first-fit). */
|
||||||
|
UINT64 pmm_alloc_pages(UINTN count)
|
||||||
|
{
|
||||||
|
UINTN i, j;
|
||||||
|
BOOLEAN found;
|
||||||
|
|
||||||
|
if (!pmm_ready || count == 0 || count > pmm_total_pages
|
||||||
|
|| pmm_free_count < count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i + count <= pmm_total_pages; i++) {
|
||||||
|
found = TRUE;
|
||||||
|
for (j = 0; j < count; j++) {
|
||||||
|
if (pmm_test_bit(i + j)) {
|
||||||
|
found = FALSE;
|
||||||
|
i += j; /* skip past the used page */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
for (j = 0; j < count; j++) {
|
||||||
|
pmm_set_bit(i + j);
|
||||||
|
}
|
||||||
|
pmm_free_count -= count;
|
||||||
|
return pmm_pool_base + ((UINT64)i * PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free `count` contiguous pages starting at phys_addr. */
|
||||||
|
void pmm_free_pages(UINT64 phys_addr, UINTN count)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
pmm_free_page(phys_addr + ((UINT64)i * PAGE_SIZE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UINTN pmm_get_free_pages(void) { return pmm_free_count; }
|
||||||
|
UINTN pmm_get_total_pages(void) { return pmm_total_pages; }
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Paging – manipulate the live 4-level x86-64 page tables
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Paging – low-level helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* Read the CR3 register (physical address of PML4). */
|
||||||
|
static UINT64 read_cr3(void)
|
||||||
|
{
|
||||||
|
UINT64 cr3;
|
||||||
|
__asm__ __volatile__("mov %%cr3, %0" : "=r"(cr3));
|
||||||
|
return cr3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invalidate the TLB entry for virtual address `addr`. */
|
||||||
|
static void invlpg(UINT64 addr)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return a pointer to the current PML4 table. */
|
||||||
|
static UINT64 *get_pml4(void)
|
||||||
|
{
|
||||||
|
return (UINT64 *)(UINTN)(read_cr3() & PTE_ADDR_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walk one level of the page table hierarchy.
|
||||||
|
* If `create` is TRUE and the entry is missing, a fresh zeroed page is
|
||||||
|
* allocated from the PMM and installed.
|
||||||
|
*/
|
||||||
|
static UINT64 *paging_walk_level(UINT64 *table, UINTN index, BOOLEAN create)
|
||||||
|
{
|
||||||
|
UINT64 *next;
|
||||||
|
UINTN i;
|
||||||
|
UINT64 page;
|
||||||
|
|
||||||
|
if (table[index] & PTE_PRESENT) {
|
||||||
|
return (UINT64 *)(UINTN)(table[index] & PTE_ADDR_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!create) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
page = pmm_alloc_page();
|
||||||
|
if (page == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero the freshly-allocated page table */
|
||||||
|
next = (UINT64 *)(UINTN)page;
|
||||||
|
for (i = 0; i < PAGE_SIZE / sizeof(UINT64); i++) {
|
||||||
|
next[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[index] = page | PTE_PRESENT | PTE_WRITABLE;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Paging – public interface
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Log the current CR3 value (identity-mapped by UEFI). */
|
||||||
|
void paging_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (identity-mapped by loader)\n\r",
|
||||||
|
read_cr3());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map a single 4 KB page: virt → phys with the given flags.
|
||||||
|
* Returns TRUE on success, FALSE if a huge page is in the way or
|
||||||
|
* page-table allocation failed.
|
||||||
|
*/
|
||||||
|
BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
pdpt = paging_walk_level(pml4, pml4i, TRUE);
|
||||||
|
if (pdpt == NULL) return FALSE;
|
||||||
|
|
||||||
|
/* 1 GB huge page – cannot carve a 4 KB mapping inside it */
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) return FALSE;
|
||||||
|
|
||||||
|
pd = paging_walk_level(pdpt, pdpti, TRUE);
|
||||||
|
if (pd == NULL) return FALSE;
|
||||||
|
|
||||||
|
/* 2 MB huge page – cannot carve a 4 KB mapping inside it */
|
||||||
|
if (pd[pdi] & PTE_HUGE) return FALSE;
|
||||||
|
|
||||||
|
pt = paging_walk_level(pd, pdi, TRUE);
|
||||||
|
if (pt == NULL) return FALSE;
|
||||||
|
|
||||||
|
pt[pti] = (phys & PTE_ADDR_MASK) | flags | PTE_PRESENT;
|
||||||
|
invlpg(virt);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the mapping for a single 4 KB page and flush the TLB. */
|
||||||
|
void paging_unmap_page(UINT64 virt)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
pdpt = paging_walk_level(pml4, pml4i, FALSE);
|
||||||
|
if (pdpt == NULL) return;
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) return;
|
||||||
|
|
||||||
|
pd = paging_walk_level(pdpt, pdpti, FALSE);
|
||||||
|
if (pd == NULL) return;
|
||||||
|
if (pd[pdi] & PTE_HUGE) return;
|
||||||
|
|
||||||
|
pt = paging_walk_level(pd, pdi, FALSE);
|
||||||
|
if (pt == NULL) return;
|
||||||
|
|
||||||
|
pt[pti] = 0;
|
||||||
|
invlpg(virt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Translate a virtual address to its physical counterpart.
|
||||||
|
* Handles 4 KB, 2 MB, and 1 GB page sizes. Returns 0 if unmapped.
|
||||||
|
*/
|
||||||
|
UINT64 paging_get_phys(UINT64 virt)
|
||||||
|
{
|
||||||
|
UINT64 *pml4, *pdpt, *pd, *pt;
|
||||||
|
UINTN pml4i, pdpti, pdi, pti;
|
||||||
|
|
||||||
|
pml4i = (virt >> 39) & 0x1FF;
|
||||||
|
pdpti = (virt >> 30) & 0x1FF;
|
||||||
|
pdi = (virt >> 21) & 0x1FF;
|
||||||
|
pti = (virt >> 12) & 0x1FF;
|
||||||
|
|
||||||
|
pml4 = get_pml4();
|
||||||
|
|
||||||
|
if (!(pml4[pml4i] & PTE_PRESENT)) return 0;
|
||||||
|
pdpt = (UINT64 *)(UINTN)(pml4[pml4i] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pdpt[pdpti] & PTE_PRESENT)) return 0;
|
||||||
|
if (pdpt[pdpti] & PTE_HUGE) {
|
||||||
|
/* 1 GB page */
|
||||||
|
return (pdpt[pdpti] & 0x000FFFFFC0000000ULL) | (virt & 0x3FFFFFFFULL);
|
||||||
|
}
|
||||||
|
pd = (UINT64 *)(UINTN)(pdpt[pdpti] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pd[pdi] & PTE_PRESENT)) return 0;
|
||||||
|
if (pd[pdi] & PTE_HUGE) {
|
||||||
|
/* 2 MB page */
|
||||||
|
return (pd[pdi] & 0x000FFFFFFFE00000ULL) | (virt & 0x1FFFFFULL);
|
||||||
|
}
|
||||||
|
pt = (UINT64 *)(UINTN)(pd[pdi] & PTE_ADDR_MASK);
|
||||||
|
|
||||||
|
if (!(pt[pti] & PTE_PRESENT)) return 0;
|
||||||
|
return (pt[pti] & PTE_ADDR_MASK) | (virt & 0xFFFULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Heap Allocator – first-fit free-list with coalescing
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static HeapBlock *heap_start = NULL;
|
||||||
|
static BOOLEAN heap_ready = FALSE;
|
||||||
|
|
||||||
|
/* Round `val` up to the next multiple of `align`. */
|
||||||
|
static UINTN align_up(UINTN val, UINTN align)
|
||||||
|
{
|
||||||
|
return (val + align - 1) & ~(align - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise the heap: allocate HEAP_INITIAL_PAGES from the PMM
|
||||||
|
* and set up a single free block spanning the entire region.
|
||||||
|
*/
|
||||||
|
void heap_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINT64 phys;
|
||||||
|
UINTN heap_size;
|
||||||
|
|
||||||
|
phys = pmm_alloc_pages(HEAP_INITIAL_PAGES);
|
||||||
|
if (phys == 0) {
|
||||||
|
SAFE_PRINT(Boot, L" Heap: failed to allocate pages\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_size = HEAP_INITIAL_PAGES * PAGE_SIZE;
|
||||||
|
heap_start = (HeapBlock *)(UINTN)phys;
|
||||||
|
|
||||||
|
heap_start->magic = HEAP_BLOCK_MAGIC;
|
||||||
|
heap_start->state = HEAP_BLOCK_FREE;
|
||||||
|
heap_start->size = heap_size - sizeof(HeapBlock);
|
||||||
|
heap_start->next = NULL;
|
||||||
|
heap_start->prev = NULL;
|
||||||
|
|
||||||
|
heap_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" Heap: %d KB at 0x%lx\n\r",
|
||||||
|
heap_size / 1024, phys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate `size` bytes from the heap (first-fit).
|
||||||
|
* The returned pointer is aligned to HEAP_ALIGN. Returns NULL on
|
||||||
|
* failure or heap corruption.
|
||||||
|
*/
|
||||||
|
void *kmalloc(UINTN size)
|
||||||
|
{
|
||||||
|
HeapBlock *block, *split;
|
||||||
|
UINTN aligned;
|
||||||
|
|
||||||
|
if (!heap_ready || size == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
aligned = align_up(size, HEAP_ALIGN);
|
||||||
|
|
||||||
|
for (block = heap_start; block != NULL; block = block->next) {
|
||||||
|
if (block->magic != HEAP_BLOCK_MAGIC) {
|
||||||
|
return NULL; /* heap corruption */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->state != HEAP_BLOCK_FREE || block->size < aligned) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to split if there is room for another header + 16 bytes */
|
||||||
|
if (block->size >= aligned + sizeof(HeapBlock) + HEAP_ALIGN) {
|
||||||
|
split = (HeapBlock *)((UINT8 *)block + sizeof(HeapBlock) + aligned);
|
||||||
|
split->magic = HEAP_BLOCK_MAGIC;
|
||||||
|
split->state = HEAP_BLOCK_FREE;
|
||||||
|
split->size = block->size - aligned - sizeof(HeapBlock);
|
||||||
|
split->next = block->next;
|
||||||
|
split->prev = block;
|
||||||
|
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = split;
|
||||||
|
}
|
||||||
|
|
||||||
|
block->next = split;
|
||||||
|
block->size = aligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
block->state = HEAP_BLOCK_USED;
|
||||||
|
return (void *)((UINT8 *)block + sizeof(HeapBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL; /* out of heap memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free a previously kmalloc'd pointer. Coalesces adjacent free
|
||||||
|
* blocks to reduce fragmentation.
|
||||||
|
*/
|
||||||
|
void kfree(void *ptr)
|
||||||
|
{
|
||||||
|
HeapBlock *block;
|
||||||
|
|
||||||
|
if (ptr == NULL || !heap_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = (HeapBlock *)((UINT8 *)ptr - sizeof(HeapBlock));
|
||||||
|
|
||||||
|
if (block->magic != HEAP_BLOCK_MAGIC || block->state != HEAP_BLOCK_USED) {
|
||||||
|
return; /* bad pointer or double-free */
|
||||||
|
}
|
||||||
|
|
||||||
|
block->state = HEAP_BLOCK_FREE;
|
||||||
|
|
||||||
|
/* Coalesce with next neighbour */
|
||||||
|
if (block->next != NULL
|
||||||
|
&& block->next->magic == HEAP_BLOCK_MAGIC
|
||||||
|
&& block->next->state == HEAP_BLOCK_FREE) {
|
||||||
|
block->size += sizeof(HeapBlock) + block->next->size;
|
||||||
|
block->next = block->next->next;
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coalesce with previous neighbour */
|
||||||
|
if (block->prev != NULL
|
||||||
|
&& block->prev->magic == HEAP_BLOCK_MAGIC
|
||||||
|
&& block->prev->state == HEAP_BLOCK_FREE) {
|
||||||
|
block->prev->size += sizeof(HeapBlock) + block->size;
|
||||||
|
block->prev->next = block->next;
|
||||||
|
if (block->next != NULL) {
|
||||||
|
block->next->prev = block->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gather aggregate heap statistics. */
|
||||||
|
void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
|
||||||
|
UINTN *num_blocks)
|
||||||
|
{
|
||||||
|
HeapBlock *b;
|
||||||
|
|
||||||
|
*total = 0; *used = 0; *free_mem = 0; *num_blocks = 0;
|
||||||
|
|
||||||
|
if (!heap_ready) return;
|
||||||
|
|
||||||
|
for (b = heap_start; b != NULL && b->magic == HEAP_BLOCK_MAGIC;
|
||||||
|
b = b->next) {
|
||||||
|
(*num_blocks)++;
|
||||||
|
*total += b->size;
|
||||||
|
if (b->state == HEAP_BLOCK_USED) {
|
||||||
|
*used += b->size;
|
||||||
|
} else {
|
||||||
|
*free_mem += b->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Top-level helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* Initialise all memory subsystems in order. */
|
||||||
|
void memory_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
SAFE_PRINT(Boot, L"Initializing memory management...\n\r");
|
||||||
|
pmm_init(Boot);
|
||||||
|
paging_init(Boot);
|
||||||
|
heap_init(Boot);
|
||||||
|
SAFE_PRINT(Boot, L"Memory management ready.\n\r\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print a summary of PMM, heap, and paging state to the console. */
|
||||||
|
void memory_print_stats(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN h_total, h_used, h_free, h_blocks;
|
||||||
|
UINTN p_total, p_free, p_used;
|
||||||
|
Task *caller;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: memory stats require KERNEL. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: memory stats require kernel privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_total = pmm_get_total_pages();
|
||||||
|
p_free = pmm_get_free_pages();
|
||||||
|
p_used = p_total - p_free;
|
||||||
|
|
||||||
|
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"Memory Statistics\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Physical Memory Manager:\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Pool Base: 0x%lx\n\r", pmm_pool_base);
|
||||||
|
SAFE_PRINT(Boot, L" Total Pages: %d (%d KB)\n\r",
|
||||||
|
p_total, (p_total * PAGE_SIZE) / 1024);
|
||||||
|
SAFE_PRINT(Boot, L" Used Pages: %d (%d KB)\n\r",
|
||||||
|
p_used, (p_used * PAGE_SIZE) / 1024);
|
||||||
|
SAFE_PRINT(Boot, L" Free Pages: %d (%d KB)\n\r",
|
||||||
|
p_free, (p_free * PAGE_SIZE) / 1024);
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Heap Allocator:\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Total: %d bytes\n\r", h_total);
|
||||||
|
SAFE_PRINT(Boot, L" Used: %d bytes\n\r", h_used);
|
||||||
|
SAFE_PRINT(Boot, L" Free: %d bytes\n\r", h_free);
|
||||||
|
SAFE_PRINT(Boot, L" Blocks: %d\n\r", h_blocks);
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"Paging:\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" CR3: 0x%lx\n\r", read_cr3());
|
||||||
|
SAFE_PRINT(Boot, L" Mode: 4-level (PML4)\n\r");
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
}
|
||||||
104
memory.h
Normal file
104
memory.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* memory.h – Memory management subsystem declarations.
|
||||||
|
*
|
||||||
|
* Three layers:
|
||||||
|
* PMM – bitmap-based physical page-frame allocator (16 MB pool)
|
||||||
|
* Paging – walks / creates 4-level x86-64 page tables
|
||||||
|
* Heap – first-fit free-list allocator with block coalescing
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MEMORY_H
|
||||||
|
#define MEMORY_H
|
||||||
|
|
||||||
|
#include "kernel_types.h"
|
||||||
|
#include "boot_info.h"
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Page-level constants
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
#define PAGE_SIZE 4096
|
||||||
|
#define PAGE_SHIFT 12
|
||||||
|
#define PAGE_MASK (~(UINT64)(PAGE_SIZE - 1))
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Page-table entry flags (x86-64 4-level paging)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
#define PTE_PRESENT (1ULL << 0)
|
||||||
|
#define PTE_WRITABLE (1ULL << 1)
|
||||||
|
#define PTE_USER (1ULL << 2)
|
||||||
|
#define PTE_WRITETHROUGH (1ULL << 3)
|
||||||
|
#define PTE_NOCACHE (1ULL << 4)
|
||||||
|
#define PTE_ACCESSED (1ULL << 5)
|
||||||
|
#define PTE_DIRTY (1ULL << 6)
|
||||||
|
#define PTE_HUGE (1ULL << 7) /* 2 MB or 1 GB page */
|
||||||
|
#define PTE_GLOBAL (1ULL << 8)
|
||||||
|
#define PTE_NX (1ULL << 63) /* No-Execute */
|
||||||
|
|
||||||
|
#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ULL
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
void pmm_init(BootInfo *Boot);
|
||||||
|
UINT64 pmm_alloc_page(void);
|
||||||
|
void pmm_free_page(UINT64 phys_addr);
|
||||||
|
UINT64 pmm_alloc_pages(UINTN count);
|
||||||
|
void pmm_free_pages(UINT64 phys_addr, UINTN count);
|
||||||
|
UINTN pmm_get_free_pages(void);
|
||||||
|
UINTN pmm_get_total_pages(void);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Paging
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void paging_init(BootInfo *Boot);
|
||||||
|
BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags);
|
||||||
|
void paging_unmap_page(UINT64 virt);
|
||||||
|
UINT64 paging_get_phys(UINT64 virt);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Heap allocator
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
#define HEAP_INITIAL_PAGES 64 /* 256 KB initial heap */
|
||||||
|
#define HEAP_BLOCK_MAGIC 0xDEADBEEFU /* corruption-detection canary */
|
||||||
|
#define HEAP_BLOCK_FREE 0
|
||||||
|
#define HEAP_BLOCK_USED 1
|
||||||
|
#define HEAP_ALIGN 16 /* minimum allocation alignment */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HeapBlock – header placed immediately before each allocation.
|
||||||
|
* Blocks form a doubly-linked free-list.
|
||||||
|
*/
|
||||||
|
typedef struct HeapBlock {
|
||||||
|
UINT32 magic; /* must be HEAP_BLOCK_MAGIC */
|
||||||
|
UINT32 state; /* HEAP_BLOCK_FREE / _USED */
|
||||||
|
UINTN size; /* usable bytes (excludes hdr) */
|
||||||
|
struct HeapBlock *next;
|
||||||
|
struct HeapBlock *prev;
|
||||||
|
} HeapBlock;
|
||||||
|
|
||||||
|
void heap_init(BootInfo *Boot);
|
||||||
|
void *kmalloc(UINTN size);
|
||||||
|
void kfree(void *ptr);
|
||||||
|
void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem,
|
||||||
|
UINTN *num_blocks);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Top-level helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* Initialise all memory subsystems (PMM → paging → heap). */
|
||||||
|
void memory_init(BootInfo *Boot);
|
||||||
|
|
||||||
|
/* Print PMM, heap, and paging statistics to the console. */
|
||||||
|
void memory_print_stats(BootInfo *Boot);
|
||||||
|
|
||||||
|
#endif /* 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 "kernel_types.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 */
|
||||||
|
|||||||
438
task.c
Normal file
438
task.c
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
/*
|
||||||
|
* task.c – Cooperative multitasking: PCB pool, scheduler, yield/exit.
|
||||||
|
*
|
||||||
|
* Task 0 is the always-present kernel core thread (uses the boot stack).
|
||||||
|
* Additional tasks are created with task_create(), which allocates a
|
||||||
|
* stack from the PMM and sets up a fake context-switch frame so that
|
||||||
|
* context_switch() can "return" into a trampoline that calls the real
|
||||||
|
* entry function.
|
||||||
|
*
|
||||||
|
* Scheduling is purely cooperative: tasks must call task_yield() to
|
||||||
|
* let others run. The scheduler uses round-robin selection among
|
||||||
|
* READY tasks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "task.h"
|
||||||
|
#include "memory.h"
|
||||||
|
|
||||||
|
/* Null-safe print helper used throughout the kernel. */
|
||||||
|
#define SAFE_PRINT(Boot, ...) \
|
||||||
|
do { \
|
||||||
|
if ((Boot) != NULL && (Boot)->print != NULL) { \
|
||||||
|
(Boot)->print(__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Module state
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static Task tasks[TASK_MAX]; /* PCB pool (static array) */
|
||||||
|
static Task *current_task = NULL;
|
||||||
|
static UINT32 next_pid = 0;
|
||||||
|
static BootInfo *task_boot = NULL;
|
||||||
|
static BOOLEAN task_ready = FALSE;
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
static void task_trampoline(void);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Helpers
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Copy a wide string with a maximum length (including NUL). */
|
||||||
|
static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max)
|
||||||
|
{
|
||||||
|
UINTN i = 0;
|
||||||
|
while (i < max - 1 && src != NULL && src[i] != L'\0') {
|
||||||
|
dst[i] = src[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
dst[i] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Initialisation – make the current (kernel) thread task 0
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise the scheduler: clear all PCB slots and register the
|
||||||
|
* currently running kernel core thread as task 0.
|
||||||
|
*/
|
||||||
|
void task_init(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
|
||||||
|
task_boot = Boot;
|
||||||
|
|
||||||
|
/* Clear all PCB slots */
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
tasks[i].state = TASK_STATE_FREE;
|
||||||
|
tasks[i].pid = 0;
|
||||||
|
tasks[i].privilege = TASK_PRIV_USER;
|
||||||
|
tasks[i].saved_rsp = 0;
|
||||||
|
tasks[i].stack_base = 0;
|
||||||
|
tasks[i].stack_pages = 0;
|
||||||
|
tasks[i].entry = NULL;
|
||||||
|
tasks[i].arg = NULL;
|
||||||
|
tasks[i].switches = 0;
|
||||||
|
tasks[i].name[0] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task 0 = the currently running kernel core thread.
|
||||||
|
* It already has a stack (the kernel's boot stack), so we don't
|
||||||
|
* allocate one. Its saved_rsp will be filled in during the
|
||||||
|
* first context_switch call in task_yield().
|
||||||
|
*/
|
||||||
|
tasks[0].pid = next_pid++;
|
||||||
|
tasks[0].state = TASK_STATE_RUNNING;
|
||||||
|
tasks[0].privilege = TASK_PRIV_KERNEL;
|
||||||
|
tasks[0].switches = 1;
|
||||||
|
wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN);
|
||||||
|
|
||||||
|
current_task = &tasks[0];
|
||||||
|
task_ready = TRUE;
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" Tasks: scheduler ready (max %d tasks)\n\r",
|
||||||
|
(UINTN)TASK_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Trampoline – first code a new task executes after context_switch
|
||||||
|
* returns into it. It calls the real entry function and then
|
||||||
|
* performs a clean task_exit().
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void task_trampoline(void)
|
||||||
|
{
|
||||||
|
Task *t = task_current();
|
||||||
|
if (t != NULL && t->entry != NULL) {
|
||||||
|
t->entry(t->arg);
|
||||||
|
}
|
||||||
|
task_exit();
|
||||||
|
/* Should never reach here, but just in case: */
|
||||||
|
for (;;) {
|
||||||
|
__asm__ __volatile__("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Create a new task
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
Task *task_create_with_priv(const CHAR16 *name,
|
||||||
|
TaskEntryFn entry,
|
||||||
|
void *arg,
|
||||||
|
TaskPrivilege privilege)
|
||||||
|
{
|
||||||
|
Task *t = NULL;
|
||||||
|
UINTN i;
|
||||||
|
UINT64 stack_phys;
|
||||||
|
UINT64 *sp;
|
||||||
|
|
||||||
|
if (!task_ready || entry == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: prevent privilege escalation. */
|
||||||
|
{
|
||||||
|
Task *caller = task_current();
|
||||||
|
if (caller != NULL && privilege > task_get_privilege(caller)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find a free PCB slot */
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
if (tasks[i].state == TASK_STATE_FREE) {
|
||||||
|
t = &tasks[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t == NULL) {
|
||||||
|
return NULL; /* out of slots */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate stack pages from the physical memory manager */
|
||||||
|
stack_phys = pmm_alloc_pages(TASK_STACK_PAGES);
|
||||||
|
if (stack_phys == 0) {
|
||||||
|
return NULL; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill in the PCB */
|
||||||
|
t->pid = next_pid++;
|
||||||
|
t->state = TASK_STATE_READY;
|
||||||
|
t->privilege = privilege;
|
||||||
|
t->entry = entry;
|
||||||
|
t->arg = arg;
|
||||||
|
t->switches = 0;
|
||||||
|
t->stack_base = stack_phys;
|
||||||
|
t->stack_pages = TASK_STACK_PAGES;
|
||||||
|
wstrcpy16(t->name, name != NULL ? name : L"unnamed", TASK_NAME_LEN);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the initial stack frame so that context_switch() can
|
||||||
|
* "return" into task_trampoline().
|
||||||
|
*
|
||||||
|
* context_switch saves/restores (low → high on stack):
|
||||||
|
* flags, r15, r14, r13, r12, rbx, rbp (pushes)
|
||||||
|
* then `ret` pops the return address (→ trampoline)
|
||||||
|
*
|
||||||
|
* Above the return address we place a safety-net address
|
||||||
|
* (task_exit) so that if the trampoline or entry function does
|
||||||
|
* a bare `ret`, it lands in task_exit().
|
||||||
|
*/
|
||||||
|
sp = (UINT64 *)(stack_phys + TASK_STACK_SIZE);
|
||||||
|
|
||||||
|
/* Align stack top to 16 bytes */
|
||||||
|
sp = (UINT64 *)((UINT64)sp & ~0xFULL);
|
||||||
|
|
||||||
|
/* Safety-net return address for the trampoline */
|
||||||
|
*(--sp) = (UINT64)(UINTN)task_exit;
|
||||||
|
|
||||||
|
/* Return address for context_switch's `ret` → trampoline */
|
||||||
|
*(--sp) = (UINT64)(UINTN)task_trampoline;
|
||||||
|
|
||||||
|
/* Callee-saved registers – all zero for fresh task */
|
||||||
|
*(--sp) = 0; /* rbp */
|
||||||
|
*(--sp) = 0; /* rbx */
|
||||||
|
*(--sp) = 0; /* r12 */
|
||||||
|
*(--sp) = 0; /* r13 */
|
||||||
|
*(--sp) = 0; /* r14 */
|
||||||
|
*(--sp) = 0; /* r15 */
|
||||||
|
|
||||||
|
/* RFLAGS – interrupts enabled (IF = bit 9) */
|
||||||
|
*(--sp) = 0x202; /* flags */
|
||||||
|
|
||||||
|
t->saved_rsp = (UINT64)(UINTN)sp;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg)
|
||||||
|
{
|
||||||
|
/* Inherit privilege from the calling task (kernel if no task context). */
|
||||||
|
Task *caller = task_current();
|
||||||
|
TaskPrivilege priv = (caller != NULL) ? task_get_privilege(caller)
|
||||||
|
: TASK_PRIV_KERNEL;
|
||||||
|
return task_create_with_priv(name, entry, arg, priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Schedule – pick the next READY task (round-robin)
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static Task *schedule_next(void)
|
||||||
|
{
|
||||||
|
UINTN start, idx, i;
|
||||||
|
|
||||||
|
if (current_task == NULL) {
|
||||||
|
return &tasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find current task's index in the array */
|
||||||
|
start = (UINTN)(current_task - tasks);
|
||||||
|
|
||||||
|
/* Round-robin: scan from (current+1) wrapping around */
|
||||||
|
for (i = 1; i <= TASK_MAX; i++) {
|
||||||
|
idx = (start + i) % TASK_MAX;
|
||||||
|
if (tasks[idx].state == TASK_STATE_READY) {
|
||||||
|
return &tasks[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No other ready task – stay with current if still runnable */
|
||||||
|
if (current_task->state == TASK_STATE_RUNNING ||
|
||||||
|
current_task->state == TASK_STATE_READY) {
|
||||||
|
return current_task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback to task 0 (kernel / shell) */
|
||||||
|
return &tasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Yield – voluntarily give up the CPU to the next ready task
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void task_yield(void)
|
||||||
|
{
|
||||||
|
Task *prev, *next;
|
||||||
|
|
||||||
|
if (!task_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current_task;
|
||||||
|
next = schedule_next();
|
||||||
|
|
||||||
|
if (next == prev) {
|
||||||
|
return; /* nothing else to switch to */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the previous task as READY (still runnable) */
|
||||||
|
if (prev->state == TASK_STATE_RUNNING) {
|
||||||
|
prev->state = TASK_STATE_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
next->state = TASK_STATE_RUNNING;
|
||||||
|
next->switches++;
|
||||||
|
current_task = next;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* context_switch saves callee-saved regs + flags on prev's stack,
|
||||||
|
* stores prev's RSP into prev->saved_rsp, loads next->saved_rsp
|
||||||
|
* into RSP, restores regs + flags, and `ret`s into next's code.
|
||||||
|
*/
|
||||||
|
context_switch(&prev->saved_rsp, next->saved_rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Exit – terminate the current task and switch away
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void task_exit(void)
|
||||||
|
{
|
||||||
|
Task *prev, *next;
|
||||||
|
|
||||||
|
if (!task_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current_task;
|
||||||
|
prev->state = TASK_STATE_TERMINATED;
|
||||||
|
|
||||||
|
/* Free the stack memory back to the PMM */
|
||||||
|
if (prev->stack_base != 0 && prev->stack_pages != 0) {
|
||||||
|
pmm_free_pages(prev->stack_base, prev->stack_pages);
|
||||||
|
prev->stack_base = 0;
|
||||||
|
prev->stack_pages = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the PCB slot as free for reuse */
|
||||||
|
prev->state = TASK_STATE_FREE;
|
||||||
|
|
||||||
|
next = schedule_next();
|
||||||
|
if (next == prev) {
|
||||||
|
/* Shouldn't happen if task 0 (kernel) is always alive */
|
||||||
|
next = &tasks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
next->state = TASK_STATE_RUNNING;
|
||||||
|
next->switches++;
|
||||||
|
current_task = next;
|
||||||
|
|
||||||
|
/* One-way switch: we never return to the exited task */
|
||||||
|
context_switch(&prev->saved_rsp, next->saved_rsp);
|
||||||
|
|
||||||
|
/* Should never reach here */
|
||||||
|
for (;;) {
|
||||||
|
__asm__ __volatile__("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Accessors
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
Task *task_current(void)
|
||||||
|
{
|
||||||
|
return current_task;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskPrivilege task_get_privilege(Task *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) {
|
||||||
|
return TASK_PRIV_KERNEL;
|
||||||
|
}
|
||||||
|
return t->privilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
void task_set_privilege(Task *t, TaskPrivilege privilege)
|
||||||
|
{
|
||||||
|
if (t == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t->privilege = privilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINTN task_count(void)
|
||||||
|
{
|
||||||
|
UINTN i, count = 0;
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
if (tasks[i].state != TASK_STATE_FREE) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Wait for another task to finish
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void task_wait(Task *t)
|
||||||
|
{
|
||||||
|
if (!task_ready || t == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Busy-wait cooperatively until the target task's PCB slot has
|
||||||
|
* been recycled back to FREE by task_exit().
|
||||||
|
*/
|
||||||
|
while (t->state != TASK_STATE_FREE) {
|
||||||
|
task_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Print task list (implements the `ps` command)
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static const CHAR16 *state_str(TaskState s)
|
||||||
|
{
|
||||||
|
switch (s) {
|
||||||
|
case TASK_STATE_FREE: return L"FREE";
|
||||||
|
case TASK_STATE_READY: return L"READY";
|
||||||
|
case TASK_STATE_RUNNING: return L"RUNNING";
|
||||||
|
case TASK_STATE_TERMINATED: return L"ENDED";
|
||||||
|
default: return L"???";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void task_print_list(BootInfo *Boot)
|
||||||
|
{
|
||||||
|
UINTN i;
|
||||||
|
Task *caller;
|
||||||
|
|
||||||
|
/* Subsystem-level privilege enforcement: task list requires DRIVER. */
|
||||||
|
caller = task_current();
|
||||||
|
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_DRIVER) {
|
||||||
|
SAFE_PRINT(Boot, L"Permission denied: task list requires driver privilege.\n\r");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" PID STATE PRIV SWITCHES NAME\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" --- ---------- ---- -------- ----\n\r");
|
||||||
|
|
||||||
|
for (i = 0; i < TASK_MAX; i++) {
|
||||||
|
if (tasks[i].state == TASK_STATE_FREE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L" %3d %-10s %4d %8d %s\n\r",
|
||||||
|
tasks[i].pid,
|
||||||
|
state_str(tasks[i].state),
|
||||||
|
(INT32)tasks[i].privilege,
|
||||||
|
tasks[i].switches,
|
||||||
|
tasks[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
SAFE_PRINT(Boot, L" Active tasks: %d / %d\n\r",
|
||||||
|
task_count(), (UINTN)TASK_MAX);
|
||||||
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
|
}
|
||||||
111
task.h
Normal file
111
task.h
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* task.h – Cooperative multitasking interface.
|
||||||
|
*
|
||||||
|
* Provides process control blocks (PCBs), a round-robin scheduler,
|
||||||
|
* and voluntary yield/exit primitives. Context switching is performed
|
||||||
|
* by the assembly routine in context_switch.S.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TASK_H
|
||||||
|
#define TASK_H
|
||||||
|
|
||||||
|
#include "kernel_types.h"
|
||||||
|
#include "boot_info.h"
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Configuration
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
#define TASK_MAX 32 /* max concurrent tasks */
|
||||||
|
#define TASK_STACK_PAGES 8 /* 32 KB stack per task */
|
||||||
|
#define TASK_STACK_SIZE (TASK_STACK_PAGES * 4096)
|
||||||
|
#define TASK_NAME_LEN 32 /* max name length (CHAR16) */
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Task states
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TASK_STATE_FREE = 0, /* PCB slot is unused */
|
||||||
|
TASK_STATE_READY, /* runnable, waiting to be scheduled */
|
||||||
|
TASK_STATE_RUNNING, /* currently executing on the CPU */
|
||||||
|
TASK_STATE_TERMINATED /* finished; slot will be recycled */
|
||||||
|
} TaskState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Logical privilege level for a task.
|
||||||
|
*
|
||||||
|
* Higher numeric values are more privileged:
|
||||||
|
* USER (0) – least privileged
|
||||||
|
* DRIVER (1) – mid-level, can talk to hardware-facing subsystems
|
||||||
|
* KERNEL (2) – most privileged, core kernel and management threads
|
||||||
|
*
|
||||||
|
* All tasks still execute in ring 0 today; this is a software
|
||||||
|
* hierarchy used for access control decisions and future paging work.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
TASK_PRIV_USER = 0,
|
||||||
|
TASK_PRIV_DRIVER = 1,
|
||||||
|
TASK_PRIV_KERNEL = 2,
|
||||||
|
} TaskPrivilege;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Task entry function
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/* Signature for the function a new task begins executing. */
|
||||||
|
typedef void (*TaskEntryFn)(void *arg);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Process Control Block (PCB)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
typedef struct Task {
|
||||||
|
UINT32 pid; /* unique process ID */
|
||||||
|
TaskState state; /* current lifecycle state */
|
||||||
|
TaskPrivilege privilege; /* logical privilege level */
|
||||||
|
CHAR16 name[TASK_NAME_LEN]; /* human-readable label */
|
||||||
|
|
||||||
|
/* Context switch state */
|
||||||
|
UINT64 saved_rsp; /* RSP saved by context_switch() */
|
||||||
|
UINT64 stack_base; /* base phys addr of stack alloc */
|
||||||
|
UINTN stack_pages; /* stack size in pages */
|
||||||
|
|
||||||
|
/* Entry point */
|
||||||
|
TaskEntryFn entry; /* function pointer */
|
||||||
|
void *arg; /* argument passed to entry() */
|
||||||
|
|
||||||
|
/* Scheduling metadata */
|
||||||
|
UINTN switches; /* number of times scheduled */
|
||||||
|
} Task;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public API
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void task_init(BootInfo *Boot); /* initialise scheduler */
|
||||||
|
|
||||||
|
/* Spawn a new task with a specific privilege level. */
|
||||||
|
Task *task_create_with_priv(const CHAR16 *name,
|
||||||
|
TaskEntryFn entry,
|
||||||
|
void *arg,
|
||||||
|
TaskPrivilege privilege);
|
||||||
|
|
||||||
|
/* Backwards-compatible helper: creates a kernel-privileged task. */
|
||||||
|
Task *task_create(const CHAR16 *name,
|
||||||
|
TaskEntryFn entry, void *arg);
|
||||||
|
void task_yield(void); /* voluntarily give up the CPU */
|
||||||
|
void task_exit(void); /* terminate the current task */
|
||||||
|
Task *task_current(void); /* return the running task's PCB */
|
||||||
|
TaskPrivilege task_get_privilege(Task *t);
|
||||||
|
void task_set_privilege(Task *t, TaskPrivilege privilege);
|
||||||
|
UINTN task_count(void); /* number of non-FREE tasks */
|
||||||
|
void task_print_list(BootInfo *Boot); /* print task table (for `ps`) */
|
||||||
|
|
||||||
|
/* Block the current task until the target task has finished. */
|
||||||
|
void task_wait(Task *t);
|
||||||
|
|
||||||
|
/* Assembly context switch (defined in context_switch.S). */
|
||||||
|
extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
|
||||||
|
|
||||||
|
#endif /* TASK_H */
|
||||||
Reference in New Issue
Block a user