From 5c8cf64442ca45519112d08d34c6f930b0e644c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 20:03:46 +0000 Subject: [PATCH] Add CMake, Ninja, and Conan support with kernel modules (GDT, interrupts) Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- .gitignore | 20 ++ CMakeLists.txt | 147 ++++++++++++++ bootloader/CMakeLists.txt | 67 +++++++ conan_profile | 13 ++ conanfile.py | 62 ++++++ docs/BUILD_SYSTEMS.md | 307 +++++++++++++++++++++++++++++ kernel/CMakeLists.txt | 79 ++++++++ kernel/include/kernel/gdt.h | 25 +++ kernel/include/kernel/interrupts.h | 37 ++++ kernel/src/gdt.c | 56 ++++++ kernel/src/gdt_flush.asm | 26 +++ kernel/src/interrupts.c | 160 +++++++++++++++ kernel/src/interrupts_asm.asm | 120 +++++++++++ kernel/src/main.c | 14 +- tests/CMakeLists.txt | 10 + tests/unit/CMakeLists.txt | 23 +++ userspace/CMakeLists.txt | 10 + 17 files changed, 1170 insertions(+), 6 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 bootloader/CMakeLists.txt create mode 100644 conan_profile create mode 100644 conanfile.py create mode 100644 docs/BUILD_SYSTEMS.md create mode 100644 kernel/CMakeLists.txt create mode 100644 kernel/include/kernel/gdt.h create mode 100644 kernel/include/kernel/interrupts.h create mode 100644 kernel/src/gdt.c create mode 100644 kernel/src/gdt_flush.asm create mode 100644 kernel/src/interrupts.c create mode 100644 kernel/src/interrupts_asm.asm create mode 100644 tests/CMakeLists.txt create mode 100644 tests/unit/CMakeLists.txt create mode 100644 userspace/CMakeLists.txt diff --git a/.gitignore b/.gitignore index 4dc89a3..9431e41 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,26 @@ build/ bootloader/build/ kernel/build/ +# CMake +CMakeCache.txt +CMakeFiles/ +cmake-build-*/ +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps/ + +# Ninja +.ninja_deps +.ninja_log + +# Conan +conan.lock +conaninfo.txt +conanbuildinfo.* +graph_info.json + # Test binaries tests/unit/test_* !tests/unit/*.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6d69c9e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,147 @@ +cmake_minimum_required(VERSION 3.16) + +project(MetalOS + VERSION 0.1.0 + DESCRIPTION "Minimal OS for QT6 on AMD64 + RX 6600" + LANGUAGES C CXX ASM_NASM +) + +# Enable assembly with NASM +enable_language(ASM_NASM) + +# Set C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Set C++ standard (for QT6 app later) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# Platform-specific flags +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +# Global compile options for bare metal +add_compile_options( + -Wall + -Wextra + -Werror + -ffreestanding + -fno-stack-protector + -mno-red-zone +) + +# NASM flags for assembly files +set(CMAKE_ASM_NASM_OBJECT_FORMAT elf64) +set(CMAKE_ASM_NASM_COMPILE_OBJECT " -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o ") + +# Add subdirectories +add_subdirectory(bootloader) +add_subdirectory(kernel) +add_subdirectory(userspace) + +# Testing +enable_testing() +add_subdirectory(tests) + +# Custom target to create bootable image +add_custom_target(image + COMMAND ${CMAKE_SOURCE_DIR}/scripts/create_image.sh + DEPENDS bootloader kernel + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Creating bootable disk image" +) + +# QEMU targets for testing +set(QEMU_DISPLAY "none" CACHE STRING "QEMU display mode (none, gtk, sdl)") + +# Find OVMF firmware +find_file(OVMF_FIRMWARE + NAMES OVMF_CODE.fd OVMF.fd ovmf-x86_64.bin + PATHS + /usr/share/OVMF + /usr/share/ovmf + /usr/share/edk2-ovmf/x64 + /usr/share/qemu + DOC "UEFI firmware for QEMU" +) + +if(OVMF_FIRMWARE) + message(STATUS "Found OVMF firmware: ${OVMF_FIRMWARE}") + + # QEMU run target + add_custom_target(qemu + COMMAND qemu-system-x86_64 + -drive if=pflash,format=raw,readonly=on,file=${OVMF_FIRMWARE} + -drive format=raw,file=${CMAKE_BINARY_DIR}/metalos.img + -m 512M + -serial stdio + -display ${QEMU_DISPLAY} + -net none + DEPENDS image + COMMENT "Running MetalOS in QEMU with UEFI" + ) + + # QEMU debug target + add_custom_target(qemu-debug + COMMAND qemu-system-x86_64 + -drive if=pflash,format=raw,readonly=on,file=${OVMF_FIRMWARE} + -drive format=raw,file=${CMAKE_BINARY_DIR}/metalos.img + -m 512M + -serial stdio + -display ${QEMU_DISPLAY} + -net none + -d int,cpu_reset + DEPENDS image + COMMENT "Running MetalOS in QEMU with debug output" + ) + + # QEMU GDB target + add_custom_target(qemu-gdb + COMMAND qemu-system-x86_64 + -drive if=pflash,format=raw,readonly=on,file=${OVMF_FIRMWARE} + -drive format=raw,file=${CMAKE_BINARY_DIR}/metalos.img + -m 512M + -serial stdio + -display ${QEMU_DISPLAY} + -net none + -s -S + DEPENDS image + COMMENT "Running MetalOS in QEMU with GDB server on port 1234" + ) + + # QEMU UEFI test (no OS image) + add_custom_target(qemu-uefi-test + COMMAND qemu-system-x86_64 + -drive if=pflash,format=raw,readonly=on,file=${OVMF_FIRMWARE} + -m 512M + -nographic + -net none + COMMENT "Testing QEMU UEFI boot (no OS image)" + ) +else() + message(WARNING "OVMF firmware not found. QEMU targets will not be available.") + message(WARNING "Install OVMF:") + message(WARNING " Ubuntu/Debian: sudo apt-get install ovmf") + message(WARNING " Arch Linux: sudo pacman -S edk2-ovmf") + message(WARNING " Fedora: sudo dnf install edk2-ovmf") +endif() + +# Print build configuration +message(STATUS "MetalOS Build Configuration:") +message(STATUS " Version: ${PROJECT_VERSION}") +message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}") +message(STATUS " C Compiler: ${CMAKE_C_COMPILER}") +message(STATUS " C++ Compiler: ${CMAKE_CXX_COMPILER}") +message(STATUS " NASM: ${CMAKE_ASM_NASM_COMPILER}") +message(STATUS " QEMU Display: ${QEMU_DISPLAY}") diff --git a/bootloader/CMakeLists.txt b/bootloader/CMakeLists.txt new file mode 100644 index 0000000..a5810d6 --- /dev/null +++ b/bootloader/CMakeLists.txt @@ -0,0 +1,67 @@ +# MetalOS Bootloader CMakeLists + +# Bootloader-specific compiler flags +set(BOOTLOADER_CFLAGS + -fshort-wchar + -fno-stack-check + -DEFI_FUNCTION_WRAPPER +) + +# Bootloader-specific linker flags +set(BOOTLOADER_LDFLAGS + -shared + -Bsymbolic + -nostdlib + -znocombreloc +) + +# Source files +set(BOOTLOADER_SOURCES + src/main.c +) + +# Include directories +include_directories(include) + +# Create bootloader object files +add_library(bootloader_objs OBJECT + ${BOOTLOADER_SOURCES} +) + +target_compile_options(bootloader_objs PRIVATE ${BOOTLOADER_CFLAGS}) + +# Link bootloader as shared object first +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bootx64.so + COMMAND ${CMAKE_LINKER} + ${BOOTLOADER_LDFLAGS} + -T ${CMAKE_CURRENT_SOURCE_DIR}/uefi.lds + $ + -o ${CMAKE_CURRENT_BINARY_DIR}/bootx64.so + DEPENDS bootloader_objs ${CMAKE_CURRENT_SOURCE_DIR}/uefi.lds + COMMENT "Linking bootloader shared object" + VERBATIM +) + +# Convert to EFI binary +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bootx64.efi + COMMAND ${CMAKE_OBJCOPY} + -j .text -j .sdata -j .data -j .dynamic + -j .dynsym -j .rel -j .rela -j .reloc + --target=efi-app-x86_64 + ${CMAKE_CURRENT_BINARY_DIR}/bootx64.so + ${CMAKE_CURRENT_BINARY_DIR}/bootx64.efi + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bootx64.so + COMMENT "Creating EFI bootloader binary" + VERBATIM +) + +add_custom_target(bootloader ALL + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bootx64.efi +) + +# Install bootloader binary +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bootx64.efi + DESTINATION bin +) diff --git a/conan_profile b/conan_profile new file mode 100644 index 0000000..06ac1cb --- /dev/null +++ b/conan_profile @@ -0,0 +1,13 @@ +[settings] +os=Linux +arch=x86_64 +compiler=gcc +compiler.version=13 +compiler.libcxx=libstdc++11 +build_type=Release + +[options] + +[build_requires] + +[env] diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..d6e5105 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,62 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout + +class MetalOSConan(ConanFile): + name = "metalos" + version = "0.1.0" + + # Project metadata + license = "MIT" + author = "MetalOS Contributors" + url = "https://github.com/johndoe6345789/MetalOS" + description = "Minimal OS for QT6 on AMD64 + RX 6600" + topics = ("os", "kernel", "uefi", "qt6", "minimal") + + # Build settings + settings = "os", "compiler", "build_type", "arch" + + # Build options + options = { + "with_tests": [True, False], + "qemu_display": ["none", "gtk", "sdl"] + } + + default_options = { + "with_tests": True, + "qemu_display": "none" + } + + # Sources are in the same repo + exports_sources = ( + "CMakeLists.txt", + "bootloader/*", + "kernel/*", + "userspace/*", + "tests/*", + "scripts/*", + "docs/*" + ) + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.variables["BUILD_TESTING"] = self.options.with_tests + tc.variables["QEMU_DISPLAY"] = str(self.options.qemu_display) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + if self.options.with_tests: + cmake.test() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["metalos"] diff --git a/docs/BUILD_SYSTEMS.md b/docs/BUILD_SYSTEMS.md new file mode 100644 index 0000000..9ae10a4 --- /dev/null +++ b/docs/BUILD_SYSTEMS.md @@ -0,0 +1,307 @@ +# MetalOS Build Instructions + +This document describes how to build MetalOS using various build systems. + +## Prerequisites + +### Required Tools + +- **Compiler**: GCC 11+ or Clang 13+ +- **Assembler**: NASM 2.14+ +- **CMake**: 3.16+ +- **Ninja** (optional): 1.10+ +- **Conan** (optional): 2.0+ +- **QEMU**: For testing (with OVMF firmware) + +### Install on Ubuntu/Debian + +```bash +# Basic build tools +sudo apt-get update +sudo apt-get install -y build-essential cmake nasm + +# Ninja build system (optional but faster) +sudo apt-get install -y ninja-build + +# QEMU and UEFI firmware for testing +sudo apt-get install -y qemu-system-x86 ovmf + +# Conan package manager (optional) +pip3 install conan +``` + +### Install on Arch Linux + +```bash +sudo pacman -S base-devel cmake nasm ninja qemu edk2-ovmf +pip install conan +``` + +### Install on Fedora + +```bash +sudo dnf install gcc gcc-c++ cmake nasm ninja-build qemu edk2-ovmf +pip install conan +``` + +## Build Methods + +### Method 1: CMake with Make (Traditional) + +```bash +# Configure +mkdir build && cd build +cmake .. + +# Build +make -j$(nproc) + +# Build specific targets +make bootloader +make kernel + +# Test in QEMU +make image +make qemu +``` + +### Method 2: CMake with Ninja (Faster) + +```bash +# Configure with Ninja +mkdir build && cd build +cmake -G Ninja .. + +# Build (much faster than make) +ninja + +# Build specific targets +ninja bootloader +ninja kernel + +# Test in QEMU +ninja image +ninja qemu +``` + +### Method 3: Conan + CMake (Modern) + +```bash +# Install dependencies (if any are added in future) +conan install . --build=missing + +# Build with Conan +conan build . + +# Or build with specific profile +conan create . --build=missing +``` + +### Method 4: Conan + Ninja (Recommended) + +```bash +# Configure Conan to use Ninja +conan install . --build=missing -c tools.cmake.cmaketoolchain:generator=Ninja + +# Build +conan build . +``` + +## Build Configuration + +### CMake Options + +```bash +# Debug build +cmake -DCMAKE_BUILD_TYPE=Debug .. + +# Release build (default) +cmake -DCMAKE_BUILD_TYPE=Release .. + +# Set QEMU display mode +cmake -DQEMU_DISPLAY=gtk .. +``` + +### Conan Options + +```bash +# With tests (default) +conan build . -o with_tests=True + +# Without tests +conan build . -o with_tests=False + +# Set QEMU display +conan build . -o qemu_display=gtk +``` + +## Quick Start + +### Fastest Build (Recommended) + +```bash +# One-time setup +pip3 install conan +sudo apt-get install -y cmake ninja-build nasm + +# Build and test +mkdir build && cd build +cmake -G Ninja .. +ninja +ninja qemu +``` + +### Simple Build (No extra tools) + +```bash +# Just CMake and Make +mkdir build && cd build +cmake .. +make -j$(nproc) +make qemu +``` + +## Build Targets + +### Main Targets + +- **all**: Build bootloader and kernel (default) +- **bootloader**: Build UEFI bootloader only +- **kernel**: Build kernel only +- **image**: Create bootable disk image +- **clean**: Clean build artifacts + +### Testing Targets + +- **test**: Run unit tests +- **qemu**: Run in QEMU with UEFI +- **qemu-debug**: Run with debug output +- **qemu-gdb**: Run with GDB debugging +- **qemu-uefi-test**: Test UEFI boot without OS + +## Build Output + +``` +build/ +├── bootloader/ +│ └── bootx64.efi # UEFI bootloader +├── kernel/ +│ └── metalos.bin # Kernel binary +└── metalos.img # Bootable disk image +``` + +## Troubleshooting + +### NASM not found + +```bash +sudo apt-get install nasm +# Or download from https://www.nasm.us/ +``` + +### OVMF firmware not found + +```bash +# Ubuntu/Debian +sudo apt-get install ovmf + +# Arch Linux +sudo pacman -S edk2-ovmf + +# Fedora +sudo dnf install edk2-ovmf +``` + +### Ninja not found + +```bash +sudo apt-get install ninja-build +# Or use make instead: cmake .. (without -G Ninja) +``` + +### Conan not found + +```bash +pip3 install conan +# Or use system package manager +``` + +## Performance Tips + +### Parallel Builds + +```bash +# Make (use all CPU cores) +make -j$(nproc) + +# Ninja (automatically uses all cores) +ninja + +# Conan +conan build . -c tools.cmake.cmake_toolchain:jobs=$(nproc) +``` + +### Incremental Builds + +- Ninja is faster for incremental builds +- Use `ccache` for faster recompilation: + ```bash + sudo apt-get install ccache + export CC="ccache gcc" + export CXX="ccache g++" + cmake .. + ``` + +### Clean Builds + +```bash +# Full clean +rm -rf build/ +mkdir build && cd build +cmake .. + +# Or use target +make clean # Make +ninja clean # Ninja +``` + +## Cross-Compilation + +MetalOS is built for x86_64 bare metal. If you need a cross-compiler: + +```bash +# Install x86_64 bare metal toolchain +sudo apt-get install gcc-x86-64-linux-gnu + +# Configure CMake to use it +cmake -DCMAKE_C_COMPILER=x86_64-linux-gnu-gcc .. +``` + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +- name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build nasm ovmf qemu-system-x86 + +- name: Build with Ninja + run: | + mkdir build && cd build + cmake -G Ninja .. + ninja + +- name: Run tests + run: | + cd build + ctest --output-on-failure +``` + +## Additional Resources + +- [CMake Documentation](https://cmake.org/documentation/) +- [Ninja Build System](https://ninja-build.org/) +- [Conan Documentation](https://docs.conan.io/) +- [NASM Documentation](https://www.nasm.us/doc/) diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt new file mode 100644 index 0000000..a744978 --- /dev/null +++ b/kernel/CMakeLists.txt @@ -0,0 +1,79 @@ +# MetalOS Kernel CMakeLists + +# Include directories +include_directories(include) + +# Source files +set(KERNEL_C_SOURCES + src/main.c + src/gdt.c + src/interrupts.c +) + +set(KERNEL_ASM_SOURCES + src/gdt_flush.asm + src/interrupts_asm.asm +) + +# Kernel-specific C compiler flags +set(KERNEL_CFLAGS + -mcmodel=large + -O2 +) + +# Create kernel C object files +add_library(kernel_c_objs OBJECT + ${KERNEL_C_SOURCES} +) + +target_compile_options(kernel_c_objs PRIVATE ${KERNEL_CFLAGS}) +target_include_directories(kernel_c_objs PRIVATE include) + +# Create kernel ASM object files separately +# We need to avoid CMake adding C flags to NASM +foreach(asm_file ${KERNEL_ASM_SOURCES}) + get_filename_component(asm_name ${asm_file} NAME_WE) + set(asm_obj ${CMAKE_CURRENT_BINARY_DIR}/${asm_name}.o) + + add_custom_command( + OUTPUT ${asm_obj} + COMMAND ${CMAKE_ASM_NASM_COMPILER} + -f elf64 + -I ${CMAKE_CURRENT_SOURCE_DIR}/include + -o ${asm_obj} + ${CMAKE_CURRENT_SOURCE_DIR}/${asm_file} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${asm_file} + COMMENT "Assembling ${asm_file}" + VERBATIM + ) + + list(APPEND KERNEL_ASM_OBJS ${asm_obj}) +endforeach() + +# Custom target for ASM objects +add_custom_target(kernel_asm_objs + DEPENDS ${KERNEL_ASM_OBJS} +) + +# Link kernel binary +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin + COMMAND ${CMAKE_LINKER} + -nostdlib + -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld + $ + ${KERNEL_ASM_OBJS} + -o ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin + DEPENDS kernel_c_objs kernel_asm_objs ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld + COMMENT "Linking kernel binary" + COMMAND_EXPAND_LISTS +) + +add_custom_target(kernel ALL + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin +) + +# Install kernel binary +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin + DESTINATION bin +) diff --git a/kernel/include/kernel/gdt.h b/kernel/include/kernel/gdt.h new file mode 100644 index 0000000..b170611 --- /dev/null +++ b/kernel/include/kernel/gdt.h @@ -0,0 +1,25 @@ +#ifndef METALOS_KERNEL_GDT_H +#define METALOS_KERNEL_GDT_H + +#include + +// GDT Entry structure +typedef struct { + uint16_t limit_low; + uint16_t base_low; + uint8_t base_middle; + uint8_t access; + uint8_t granularity; + uint8_t base_high; +} __attribute__((packed)) gdt_entry_t; + +// GDT Pointer structure +typedef struct { + uint16_t limit; + uint64_t base; +} __attribute__((packed)) gdt_ptr_t; + +// Initialize the Global Descriptor Table +void gdt_init(void); + +#endif // METALOS_KERNEL_GDT_H diff --git a/kernel/include/kernel/interrupts.h b/kernel/include/kernel/interrupts.h new file mode 100644 index 0000000..d29dcd6 --- /dev/null +++ b/kernel/include/kernel/interrupts.h @@ -0,0 +1,37 @@ +#ifndef METALOS_KERNEL_INTERRUPTS_H +#define METALOS_KERNEL_INTERRUPTS_H + +#include + +// IDT Entry structure +typedef struct { + uint16_t offset_low; // Offset bits 0-15 + uint16_t selector; // Code segment selector + uint8_t ist; // Interrupt Stack Table offset + uint8_t type_attr; // Type and attributes + uint16_t offset_mid; // Offset bits 16-31 + uint32_t offset_high; // Offset bits 32-63 + uint32_t zero; // Reserved +} __attribute__((packed)) idt_entry_t; + +// IDT Pointer structure +typedef struct { + uint16_t limit; + uint64_t base; +} __attribute__((packed)) idt_ptr_t; + +// CPU registers state (for interrupt handlers) +typedef struct { + uint64_t r15, r14, r13, r12, r11, r10, r9, r8; + uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax; + uint64_t int_no, err_code; + uint64_t rip, cs, rflags, rsp, ss; +} __attribute__((packed)) registers_t; + +// Initialize Interrupt Descriptor Table +void idt_init(void); + +// Generic interrupt handler (called from assembly) +void interrupt_handler(registers_t* regs); + +#endif // METALOS_KERNEL_INTERRUPTS_H diff --git a/kernel/src/gdt.c b/kernel/src/gdt.c new file mode 100644 index 0000000..e5729d9 --- /dev/null +++ b/kernel/src/gdt.c @@ -0,0 +1,56 @@ +/* + * MetalOS Kernel - Global Descriptor Table (GDT) + * + * Minimal GDT setup for x86_64 long mode + * Only what's needed for our single-app OS + */ + +#include "kernel/gdt.h" + +// GDT entries (minimal for x86_64) +// In long mode, most segmentation is ignored, but we still need a valid GDT +static gdt_entry_t gdt[5]; +static gdt_ptr_t gdt_ptr; + +// Set a GDT entry +static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) { + gdt[num].base_low = (base & 0xFFFF); + gdt[num].base_middle = (base >> 16) & 0xFF; + gdt[num].base_high = (base >> 24) & 0xFF; + + gdt[num].limit_low = (limit & 0xFFFF); + gdt[num].granularity = (limit >> 16) & 0x0F; + gdt[num].granularity |= gran & 0xF0; + gdt[num].access = access; +} + +// Load GDT (assembly) +extern void gdt_flush(uint64_t); + +// Initialize GDT +void gdt_init(void) { + gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1; + gdt_ptr.base = (uint64_t)&gdt; + + // Null descriptor + gdt_set_gate(0, 0, 0, 0, 0); + + // Kernel code segment (64-bit) + // Access: Present, Ring 0, Code, Executable, Readable + gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xA0); + + // Kernel data segment (64-bit) + // Access: Present, Ring 0, Data, Writable + gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xC0); + + // User code segment (64-bit) + // Access: Present, Ring 3, Code, Executable, Readable + gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xA0); + + // User data segment (64-bit) + // Access: Present, Ring 3, Data, Writable + gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xC0); + + // Flush GDT + gdt_flush((uint64_t)&gdt_ptr); +} diff --git a/kernel/src/gdt_flush.asm b/kernel/src/gdt_flush.asm new file mode 100644 index 0000000..cbeaea0 --- /dev/null +++ b/kernel/src/gdt_flush.asm @@ -0,0 +1,26 @@ +; GDT flush assembly routine +; Load new GDT and update segment registers + +global gdt_flush +extern gdt_ptr + +section .text +bits 64 + +gdt_flush: + lgdt [rdi] ; Load GDT pointer (first argument in rdi) + + ; Reload segments + mov ax, 0x10 ; Kernel data segment offset + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Far return to reload CS + pop rdi ; Get return address + mov rax, 0x08 ; Kernel code segment offset + push rax + push rdi + retfq ; Far return diff --git a/kernel/src/interrupts.c b/kernel/src/interrupts.c new file mode 100644 index 0000000..52eb3a8 --- /dev/null +++ b/kernel/src/interrupts.c @@ -0,0 +1,160 @@ +/* + * MetalOS Kernel - Interrupt Handling + * + * Minimal IDT and interrupt handlers + * Only essential interrupts for QT6 app + */ + +#include "kernel/interrupts.h" + +// IDT entries (256 interrupts in x86_64) +static idt_entry_t idt[256]; +static idt_ptr_t idt_ptr; + +// External ISR handlers (defined in interrupts_asm.asm) +extern void isr0(void); +extern void isr1(void); +extern void isr2(void); +extern void isr3(void); +extern void isr4(void); +extern void isr5(void); +extern void isr6(void); +extern void isr7(void); +extern void isr8(void); +extern void isr9(void); +extern void isr10(void); +extern void isr11(void); +extern void isr12(void); +extern void isr13(void); +extern void isr14(void); +extern void isr15(void); +extern void isr16(void); +extern void isr17(void); +extern void isr18(void); +extern void isr19(void); +extern void isr20(void); +extern void isr21(void); +extern void isr22(void); +extern void isr23(void); +extern void isr24(void); +extern void isr25(void); +extern void isr26(void); +extern void isr27(void); +extern void isr28(void); +extern void isr29(void); +extern void isr30(void); +extern void isr31(void); + +// IRQ handlers +extern void irq0(void); +extern void irq1(void); + +// Set an IDT entry +static void idt_set_gate(uint8_t num, uint64_t handler, uint16_t selector, uint8_t flags) { + idt[num].offset_low = handler & 0xFFFF; + idt[num].offset_mid = (handler >> 16) & 0xFFFF; + idt[num].offset_high = (handler >> 32) & 0xFFFFFFFF; + idt[num].selector = selector; + idt[num].ist = 0; + idt[num].type_attr = flags; + idt[num].zero = 0; +} + +// Remap PIC (Programmable Interrupt Controller) +static void pic_remap(void) { + // ICW1: Initialize PIC + __asm__ volatile("outb %0, $0x20" : : "a"((uint8_t)0x11)); // Master PIC + __asm__ volatile("outb %0, $0xA0" : : "a"((uint8_t)0x11)); // Slave PIC + + // ICW2: Set interrupt vector offsets + __asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x20)); // Master offset to 0x20 + __asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x28)); // Slave offset to 0x28 + + // ICW3: Set up cascade + __asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x04)); // Tell master about slave + __asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x02)); // Tell slave its cascade + + // ICW4: Set mode + __asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x01)); + __asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x01)); + + // Mask all interrupts initially + __asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0xFF)); + __asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0xFF)); +} + +// Initialize IDT +void idt_init(void) { + idt_ptr.limit = (sizeof(idt_entry_t) * 256) - 1; + idt_ptr.base = (uint64_t)&idt; + + // Clear IDT + for (int i = 0; i < 256; i++) { + idt_set_gate(i, 0, 0, 0); + } + + // Install exception handlers (ISRs 0-31) + idt_set_gate(0, (uint64_t)isr0, 0x08, 0x8E); + idt_set_gate(1, (uint64_t)isr1, 0x08, 0x8E); + idt_set_gate(2, (uint64_t)isr2, 0x08, 0x8E); + idt_set_gate(3, (uint64_t)isr3, 0x08, 0x8E); + idt_set_gate(4, (uint64_t)isr4, 0x08, 0x8E); + idt_set_gate(5, (uint64_t)isr5, 0x08, 0x8E); + idt_set_gate(6, (uint64_t)isr6, 0x08, 0x8E); + idt_set_gate(7, (uint64_t)isr7, 0x08, 0x8E); + idt_set_gate(8, (uint64_t)isr8, 0x08, 0x8E); + idt_set_gate(9, (uint64_t)isr9, 0x08, 0x8E); + idt_set_gate(10, (uint64_t)isr10, 0x08, 0x8E); + idt_set_gate(11, (uint64_t)isr11, 0x08, 0x8E); + idt_set_gate(12, (uint64_t)isr12, 0x08, 0x8E); + idt_set_gate(13, (uint64_t)isr13, 0x08, 0x8E); + idt_set_gate(14, (uint64_t)isr14, 0x08, 0x8E); + idt_set_gate(15, (uint64_t)isr15, 0x08, 0x8E); + idt_set_gate(16, (uint64_t)isr16, 0x08, 0x8E); + idt_set_gate(17, (uint64_t)isr17, 0x08, 0x8E); + idt_set_gate(18, (uint64_t)isr18, 0x08, 0x8E); + idt_set_gate(19, (uint64_t)isr19, 0x08, 0x8E); + idt_set_gate(20, (uint64_t)isr20, 0x08, 0x8E); + idt_set_gate(21, (uint64_t)isr21, 0x08, 0x8E); + idt_set_gate(22, (uint64_t)isr22, 0x08, 0x8E); + idt_set_gate(23, (uint64_t)isr23, 0x08, 0x8E); + idt_set_gate(24, (uint64_t)isr24, 0x08, 0x8E); + idt_set_gate(25, (uint64_t)isr25, 0x08, 0x8E); + idt_set_gate(26, (uint64_t)isr26, 0x08, 0x8E); + idt_set_gate(27, (uint64_t)isr27, 0x08, 0x8E); + idt_set_gate(28, (uint64_t)isr28, 0x08, 0x8E); + idt_set_gate(29, (uint64_t)isr29, 0x08, 0x8E); + idt_set_gate(30, (uint64_t)isr30, 0x08, 0x8E); + idt_set_gate(31, (uint64_t)isr31, 0x08, 0x8E); + + // Remap PIC + pic_remap(); + + // Install IRQ handlers (IRQs 0-15 mapped to 32-47) + idt_set_gate(32, (uint64_t)irq0, 0x08, 0x8E); // Timer + idt_set_gate(33, (uint64_t)irq1, 0x08, 0x8E); // Keyboard + + // Load IDT + __asm__ volatile("lidt %0" : : "m"(idt_ptr)); + + // Enable interrupts + __asm__ volatile("sti"); +} + +// Generic interrupt handler +void interrupt_handler(registers_t* regs) { + // Handle interrupt based on interrupt number + (void)regs; // Suppress unused warning for now + + // TODO: Dispatch to specific handlers based on regs->int_no + + // Send EOI (End of Interrupt) to PIC if this was an IRQ + if (regs->int_no >= 32 && regs->int_no < 48) { + if (regs->int_no >= 40) { + // Slave PIC + __asm__ volatile("outb %0, $0xA0" : : "a"((uint8_t)0x20)); + } + // Master PIC + __asm__ volatile("outb %0, $0x20" : : "a"((uint8_t)0x20)); + } +} diff --git a/kernel/src/interrupts_asm.asm b/kernel/src/interrupts_asm.asm new file mode 100644 index 0000000..473f752 --- /dev/null +++ b/kernel/src/interrupts_asm.asm @@ -0,0 +1,120 @@ +; Interrupt Service Routines (ISRs) for x86_64 +; Assembly stubs that save state and call C handler + +global isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7 +global isr8, isr9, isr10, isr11, isr12, isr13, isr14, isr15 +global isr16, isr17, isr18, isr19, isr20, isr21, isr22, isr23 +global isr24, isr25, isr26, isr27, isr28, isr29, isr30, isr31 +global irq0, irq1 + +extern interrupt_handler + +section .text +bits 64 + +; Macro for ISRs without error code +%macro ISR_NOERRCODE 1 +isr%1: + push qword 0 ; Dummy error code + push qword %1 ; Interrupt number + jmp isr_common_stub +%endmacro + +; Macro for ISRs with error code +%macro ISR_ERRCODE 1 +isr%1: + push qword %1 ; Interrupt number + jmp isr_common_stub +%endmacro + +; Macro for IRQs +%macro IRQ 2 +irq%1: + push qword 0 ; Dummy error code + push qword %2 ; Interrupt number + jmp isr_common_stub +%endmacro + +; CPU Exceptions (0-31) +ISR_NOERRCODE 0 ; Divide by zero +ISR_NOERRCODE 1 ; Debug +ISR_NOERRCODE 2 ; Non-maskable interrupt +ISR_NOERRCODE 3 ; Breakpoint +ISR_NOERRCODE 4 ; Overflow +ISR_NOERRCODE 5 ; Bound range exceeded +ISR_NOERRCODE 6 ; Invalid opcode +ISR_NOERRCODE 7 ; Device not available +ISR_ERRCODE 8 ; Double fault +ISR_NOERRCODE 9 ; Coprocessor segment overrun +ISR_ERRCODE 10 ; Invalid TSS +ISR_ERRCODE 11 ; Segment not present +ISR_ERRCODE 12 ; Stack-segment fault +ISR_ERRCODE 13 ; General protection fault +ISR_ERRCODE 14 ; Page fault +ISR_NOERRCODE 15 ; Reserved +ISR_NOERRCODE 16 ; x87 floating-point exception +ISR_ERRCODE 17 ; Alignment check +ISR_NOERRCODE 18 ; Machine check +ISR_NOERRCODE 19 ; SIMD floating-point exception +ISR_NOERRCODE 20 ; Virtualization exception +ISR_NOERRCODE 21 ; Reserved +ISR_NOERRCODE 22 ; Reserved +ISR_NOERRCODE 23 ; Reserved +ISR_NOERRCODE 24 ; Reserved +ISR_NOERRCODE 25 ; Reserved +ISR_NOERRCODE 26 ; Reserved +ISR_NOERRCODE 27 ; Reserved +ISR_NOERRCODE 28 ; Reserved +ISR_NOERRCODE 29 ; Reserved +ISR_ERRCODE 30 ; Security exception +ISR_NOERRCODE 31 ; Reserved + +; IRQs (32-47) +IRQ 0, 32 ; Timer +IRQ 1, 33 ; Keyboard + +; Common ISR stub - saves state and calls C handler +isr_common_stub: + ; Save all general purpose registers + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Call C handler with pointer to register state + mov rdi, rsp + call interrupt_handler + + ; Restore all registers + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + ; Clean up error code and interrupt number + add rsp, 16 + + ; Return from interrupt + iretq diff --git a/kernel/src/main.c b/kernel/src/main.c index 59f1897..8e2664f 100644 --- a/kernel/src/main.c +++ b/kernel/src/main.c @@ -7,6 +7,8 @@ */ #include "kernel/kernel.h" +#include "kernel/gdt.h" +#include "kernel/interrupts.h" /* * Kernel main entry point @@ -19,13 +21,13 @@ void kernel_main(BootInfo* boot_info) { // Suppress unused parameter warning (void)boot_info; - // TODO: Set up minimal page tables (identity mapped or simple offset) + // Initialize GDT (Global Descriptor Table) + gdt_init(); - // TODO: Set up IDT with only interrupts we need: - // - Keyboard/mouse (USB or PS/2) - // - Timer (for QT event loop) - // - GPU (if needed) - // That's it! Maybe 5 interrupt handlers total. + // Initialize IDT (Interrupt Descriptor Table) + idt_init(); + + // TODO: Set up minimal page tables (identity mapped or simple offset) // TODO: Simple memory allocator (bump allocator is fine) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a46b451 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +# MetalOS Tests CMakeLists + +# Include test framework +include_directories(include) + +# Unit tests subdirectory +add_subdirectory(unit) + +# Integration tests can be added here later +# add_subdirectory(integration) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt new file mode 100644 index 0000000..939866d --- /dev/null +++ b/tests/unit/CMakeLists.txt @@ -0,0 +1,23 @@ +# MetalOS Unit Tests CMakeLists + +# Test sources +set(TEST_SOURCES + test_bootloader.c +) + +# Create test executable for each test file +foreach(test_src ${TEST_SOURCES}) + get_filename_component(test_name ${test_src} NAME_WE) + + add_executable(${test_name} ${test_src}) + + # Link with test framework (if needed) + target_include_directories(${test_name} PRIVATE + ${CMAKE_SOURCE_DIR}/tests/include + ${CMAKE_SOURCE_DIR}/bootloader/include + ${CMAKE_SOURCE_DIR}/kernel/include + ) + + # Add as test + add_test(NAME ${test_name} COMMAND ${test_name}) +endforeach() diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt new file mode 100644 index 0000000..9a454d9 --- /dev/null +++ b/userspace/CMakeLists.txt @@ -0,0 +1,10 @@ +# MetalOS Userspace CMakeLists + +# Placeholder for userspace components +# Will be expanded when implementing user applications + +message(STATUS "Userspace build configuration (placeholder)") + +# Add subdirectories when ready +# add_subdirectory(apps) +# add_subdirectory(libs)