From 08248e3f3c1d8764064135bb5850d7b8f4442740 Mon Sep 17 00:00:00 2001 From: karina Date: Sun, 3 May 2026 15:49:40 +0400 Subject: [PATCH] WIP: modules --- Bootloader/Source/main.c | 186 +++++++++++++++++++++++++++-- Common/bootinfo.h | 2 + Kernel/Include/OS/Modules.h | 5 + Kernel/Include/OS/Scheduler.h | 4 +- Kernel/Source/Arch/Exceptions.c | 4 + Kernel/Source/KernelMain.c | 7 ++ Kernel/Source/OS/Modules.c | 48 ++++++++ Runtime/System/Init/CMakeLists.txt | 40 +++++++ Runtime/System/Init/Source/entry.S | 21 ++++ Runtime/System/Init/Source/main.c | 9 ++ Runtime/cmake/aarch64.cmake | 58 +++++++++ Runtime/justfile | 124 +++++++++++++++++++ justfile | 93 +++++++-------- 13 files changed, 539 insertions(+), 62 deletions(-) create mode 100644 Kernel/Include/OS/Modules.h create mode 100644 Kernel/Source/OS/Modules.c create mode 100644 Runtime/System/Init/CMakeLists.txt create mode 100644 Runtime/System/Init/Source/entry.S create mode 100644 Runtime/System/Init/Source/main.c create mode 100644 Runtime/cmake/aarch64.cmake create mode 100644 Runtime/justfile diff --git a/Bootloader/Source/main.c b/Bootloader/Source/main.c index e86ea1b..cae6f87 100644 --- a/Bootloader/Source/main.c +++ b/Bootloader/Source/main.c @@ -7,8 +7,10 @@ #define PAGE_SIZE 0x1000 #define WSTR(str) ((wchar_t*)L##str) +#define MAX_MODULES 16 static wchar_t* kernel_path = WSTR("ksOSKernel.elf"); +static wchar_t* system_dir_path = WSTR("System"); static efi_guid_t dtb_guid = { 0xb1b621d5, 0xf19c, 0x41a5, {0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0} }; @@ -79,6 +81,8 @@ static efi_status_t open_root_volume(efi_file_handle_t** root) { return fs->OpenVolume(fs, root); } +static efi_status_t validate_elf(efi_physical_address_t addr); + static efi_status_t read_file_info(efi_file_handle_t* file, efi_file_info_t** file_info) { efi_guid_t file_info_guid = EFI_FILE_INFO_GUID; uintn_t file_info_size = 0; @@ -95,6 +99,157 @@ static efi_status_t read_file_info(efi_file_handle_t* file, efi_file_info_t** fi return file->GetInfo(file, &file_info_guid, &file_info_size, *file_info); } +static efi_status_t load_modules(efi_file_handle_t* root, Bootinfo* boot_info) { + efi_file_handle_t* sys_dir = NULL; + efi_status_t status = root->Open(root, &sys_dir, system_dir_path, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) { + boot_info->moduleCount = 0; + return EFI_SUCCESS; + } + + boot_info->moduleCount = 0; + + uint8_t dir_buffer[8192]; + uintn_t dir_buffer_size; + boolean_t first_entry = 1; + + while (1) { + dir_buffer_size = sizeof(dir_buffer); + status = sys_dir->Read(sys_dir, &dir_buffer_size, dir_buffer); + if (EFI_ERROR(status) || dir_buffer_size == 0) break; + + efi_file_info_t* entry = (efi_file_info_t*)dir_buffer; + + if (first_entry) { first_entry = 0; continue; } + + // Only process subdirectories (skip bare files) + if (!(entry->Attribute & 0x10)) continue; + + // Skip . and .. entries + if (entry->FileName[0] == L'.') continue; + + // Build path: System\DirName\DirName (module file matches directory name) + wchar_t file_path[FILENAME_MAX * 2 + 16]; + wchar_t* p = file_path; + wchar_t* prefix = system_dir_path; + while (*prefix) *p++ = *prefix++; + *p++ = L'\\'; + wchar_t* dir_name_start = p; + wchar_t* name = entry->FileName; + while (*name) *p++ = *name++; + *p++ = L'\\'; + // Copy directory name again as the inner file name + wchar_t* inner = dir_name_start; + while (*inner != L'\\') *p++ = *inner++; + *p = L'\0'; + + efi_file_handle_t* module_file = NULL; + status = root->Open(root, &module_file, file_path, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) continue; + + efi_file_info_t* mod_info = NULL; + status = read_file_info(module_file, &mod_info); + if (EFI_ERROR(status)) { + module_file->Close(module_file); + continue; + } + + uint64_t file_size = mod_info->FileSize; + gBS->FreePool(mod_info); + + if (file_size == 0) { + module_file->Close(module_file); + continue; + } + + uintn_t pages = (file_size + PAGE_SIZE - 1) / PAGE_SIZE; + efi_physical_address_t module_addr = 0; + status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, &module_addr); + if (EFI_ERROR(status)) { + module_file->Close(module_file); + continue; + } + + uintn_t bytes_to_read = (uintn_t)file_size; + status = module_file->Read(module_file, &bytes_to_read, (void*)module_addr); + module_file->Close(module_file); + + if (EFI_ERROR(status)) { + gBS->FreePages(module_addr, pages); + continue; + } + + if (!EFI_ERROR(validate_elf(module_addr))) { + Elf64_Ehdr* mod_elf = (Elf64_Ehdr*)module_addr; + Elf64_Phdr* phdrs = (Elf64_Phdr*)(module_addr + mod_elf->e_phoff); + + uint64_t min_vaddr = (uint64_t)-1; + uint64_t max_vaddr = 0; + + for (int i = 0; i < mod_elf->e_phnum; i++) { + if (phdrs[i].p_type == PT_LOAD) { + if (phdrs[i].p_vaddr < min_vaddr) min_vaddr = phdrs[i].p_vaddr; + uint64_t end = phdrs[i].p_vaddr + phdrs[i].p_memsz; + if (end > max_vaddr) max_vaddr = end; + } + } + + min_vaddr &= ~0xFFFULL; + max_vaddr = (max_vaddr + 0xFFFULL) & ~0xFFFULL; + uint64_t total_vsize = max_vaddr - min_vaddr; + + uintn_t final_pages = total_vsize / PAGE_SIZE; + efi_physical_address_t final_phys = 0; + status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, final_pages, &final_phys); + + if (EFI_ERROR(status)) { + gBS->FreePages(module_addr, pages); + continue; + } + + memset((void*)final_phys, 0, total_vsize); + + for (int i = 0; i < mod_elf->e_phnum; i++) { + if (phdrs[i].p_type == PT_LOAD && phdrs[i].p_filesz > 0) { + void* dest = (void*)(final_phys + (phdrs[i].p_vaddr - min_vaddr)); + void* src = (void*)(module_addr + phdrs[i].p_offset); + + uint8_t* d = dest; + uint8_t* s = src; + for (uintn_t j = 0; j < phdrs[i].p_filesz; j++) d[j] = s[j]; + } + } + + BootModule* mod = &boot_info->modules[boot_info->moduleCount]; + mod->physicalBase = final_phys; + mod->virtualBase = min_vaddr; + mod->size = total_vsize; + mod->entry = (void*)mod_elf->e_entry; + mod->capabilities = 0; + + for (int i = 0; i < 31; i++) { + wchar_t wc = entry->FileName[i]; + if (wc == L'\0') { mod->name[i] = '\0'; break; } + mod->name[i] = (wc < 128) ? (char)wc : '?'; + } + mod->name[31] = '\0'; + + print(WSTR(" [MODULE] ")); + print(entry->FileName); + print(WSTR("\r\n")); + + boot_info->moduleCount++; + } + + gBS->FreePages(module_addr, pages); + + if (boot_info->moduleCount >= MAX_MODULES) break; + } + + sys_dir->Close(sys_dir); + return EFI_SUCCESS; +} + static efi_status_t load_kernel(efi_file_handle_t* root, efi_physical_address_t* kernel_addr, uint64_t* kernel_size, efi_file_handle_t** out_handle) { efi_file_handle_t* kernel_file = NULL; efi_status_t status = root->Open(root, &kernel_file, kernel_path, EFI_FILE_MODE_READ, 0); @@ -118,17 +273,25 @@ static efi_status_t load_kernel(efi_file_handle_t* root, efi_physical_address_t* return EFI_SUCCESS; } +static efi_status_t validate_elf(efi_physical_address_t addr) { + Elf64_Ehdr* elf = (Elf64_Ehdr*)addr; + + if (elf->e_ident[0] != 0x7f || elf->e_ident[1] != 'E' || + elf->e_ident[2] != 'L' || elf->e_ident[3] != 'F') { + return EFI_UNSUPPORTED; + } + if (elf->e_machine != 0xB7) { + return EFI_UNSUPPORTED; + } + + return EFI_SUCCESS; +} + static efi_status_t parse_elf_headers(efi_physical_address_t kernel_addr) { - Elf64_Ehdr* elf_header = (Elf64_Ehdr*)kernel_addr; - - if (elf_header->e_ident[0] != 0x7f || elf_header->e_ident[1] != 'E' || - elf_header->e_ident[2] != 'L' || elf_header->e_ident[3] != 'F') { - return fail(WSTR("Invalid ELF header\r\n")); + efi_status_t status = validate_elf(kernel_addr); + if (EFI_ERROR(status)) { + return fail(WSTR("Invalid kernel ELF\r\n")); } - if (elf_header->e_machine != 0xB7) { // AArch64 - return fail(WSTR("Invalid ELF machine\r\n")); - } - return EFI_SUCCESS; } @@ -246,8 +409,6 @@ efi_status_t bootloader_main(void) { return fail(WSTR("Failed to load ksOSKernel.bin\r\n")); } - Elf64_Ehdr* elf_header = (Elf64_Ehdr*)kernel_addr; - status = parse_elf_headers(kernel_addr); if (EFI_ERROR(status)) return status; @@ -271,6 +432,9 @@ efi_status_t bootloader_main(void) { boot_info->framebuffer.pitch = gop->Mode->Information->PixelsPerScanLine; boot_info->dtb = dtb_address; + print(WSTR("Loading System modules...\r\n")); + status = load_modules(root, boot_info); + print(WSTR("Almost ready to jump. Reading memory map\r\n")); status = populate_memory_map(boot_info); if (EFI_ERROR(status)) { diff --git a/Common/bootinfo.h b/Common/bootinfo.h index 7ac07f6..3e2bb32 100644 --- a/Common/bootinfo.h +++ b/Common/bootinfo.h @@ -29,6 +29,8 @@ typedef struct { typedef struct { BIUInt64 physicalBase; + BIUInt64 virtualBase; + void* entry; BIUInt64 size; char name[32]; BIUInt64 capabilities; diff --git a/Kernel/Include/OS/Modules.h b/Kernel/Include/OS/Modules.h new file mode 100644 index 0000000..4aca698 --- /dev/null +++ b/Kernel/Include/OS/Modules.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "../Common/bootinfo.h" + +void ModuleLoad(BootModule* module); diff --git a/Kernel/Include/OS/Scheduler.h b/Kernel/Include/OS/Scheduler.h index 18c74d0..4eee84d 100644 --- a/Kernel/Include/OS/Scheduler.h +++ b/Kernel/Include/OS/Scheduler.h @@ -39,7 +39,9 @@ enum { kOSSchedulerExceptionNumber = 0xF0F0 }; +extern UInt32 gOSSchedulerNextProcessID; + void SchedulerInitialize(); OSTask* SchedulerSpawn(void(*entryPoint)(), OSProcess* owner, Boolean isUser, Address fixedUserStackAddress); Address SchedulerNext(Address stackPointer); -void SchedulerYield(UInt64 ticks); \ No newline at end of file +void SchedulerYield(UInt64 ticks); diff --git a/Kernel/Source/Arch/Exceptions.c b/Kernel/Source/Arch/Exceptions.c index 1c07f50..32da766 100644 --- a/Kernel/Source/Arch/Exceptions.c +++ b/Kernel/Source/Arch/Exceptions.c @@ -18,6 +18,10 @@ Address ExceptionsHandler(ExceptionsContext* frame, ExceptionsType type) { return SchedulerNext((Address)frame); } } + + if (class == 0x15 && syndrome == 0) { + OSPanic("Wow! We are inside EL0! Syscall caught successfully! :D"); + } } OSPanicException(frame); } diff --git a/Kernel/Source/KernelMain.c b/Kernel/Source/KernelMain.c index 31cb0a7..e017abd 100644 --- a/Kernel/Source/KernelMain.c +++ b/Kernel/Source/KernelMain.c @@ -13,6 +13,7 @@ #include #include #include +#include void KernelMain(Bootinfo* bootinfo) { OSLog("Kernel started.\n"); @@ -26,6 +27,7 @@ void KernelMain(Bootinfo* bootinfo) { DTBParse(bootinfo->dtb, &bootMap); PMMInitialize(&bootMap); VMMInitialize(&bootMap, bootinfo); + bootinfo = (Bootinfo*)VMPhysToHHDM((Address)bootinfo); SerialUpdate(VMPhysToHHDM(bootMap.UART.base)); HeapInitialize(); @@ -37,5 +39,10 @@ void KernelMain(Bootinfo* bootinfo) { CPUEnableInterrupts(); SchedulerInitialize(); + for (UInt32 i = 0; i < bootinfo->moduleCount; i++) { + ModuleLoad(&bootinfo->modules[i]); // TODO: make some sort of priority of loading modules + // like init first then uart then fb ... + } + OSLog("Kernel initialized.\n"); } diff --git a/Kernel/Source/OS/Modules.c b/Kernel/Source/OS/Modules.c new file mode 100644 index 0000000..17b3fa1 --- /dev/null +++ b/Kernel/Source/OS/Modules.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../Common/bootinfo.h" + +void ModuleLoad(BootModule* module) { + OSProcess* userProc = HeapAllocate(sizeof(OSProcess)); + MemorySet(userProc, 0, sizeof(OSProcess)); + userProc->id = 1; // TODO: id gen + userProc->state = OSTaskStateRunning; + StringCopy(userProc->name, module->name); + + Address userL0Phys = (Address)PMMAllocatePage(); + Address* userL0Virt = (Address*)VMPhysToHHDM(userL0Phys); + MemorySet(userL0Virt, 0, kVMPageSize); + userProc->l0Table = userL0Virt; + + Address physBase = module->physicalBase; + Address virtBase = module->virtualBase; + Size modSize = module->size; + + UInt64 codeFlags = kPTENormalMem | kPTEUser | kPTEAccessRW | kPTEPrivNX; // TODO: kill RWX + + for (Size i = 0; i < modSize; i += kVMPageSize) { + VMMMapPage((Address*)userL0Phys, physBase + i, virtBase + i, codeFlags); + } + + CPUCleanAndInvalidateCode((void*)VMPhysToHHDM(physBase), modSize); + + Address stackPhys = (Address)PMMAllocatePage(); + Address userStackVirt = 0x80000000; + UInt64 stackFlags = kPTENormalMem | kPTEUser | kPTEAccessRW | kPTEPrivNX | kPTEUserNX; + + VMMMapPage((Address*)userL0Phys, stackPhys, userStackVirt, stackFlags); + + void (*userEntryPoint)() = (void(*)())module->entry; + + if ((Address)userEntryPoint == 0x0) { + OSLog("Skipping module %s: entry point is 0x0.\n", module->name); + return; + } + + SchedulerSpawn(userEntryPoint, userProc, true, userStackVirt + kVMPageSize); +} diff --git a/Runtime/System/Init/CMakeLists.txt b/Runtime/System/Init/CMakeLists.txt new file mode 100644 index 0000000..2099edd --- /dev/null +++ b/Runtime/System/Init/CMakeLists.txt @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (c) 2026 0xKSor + +cmake_minimum_required(VERSION 3.20) +project(ksOS_init LANGUAGES C) + +file(GLOB_RECURSE INIT_SOURCES CMAKE_CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/Source/*.S + ${CMAKE_CURRENT_SOURCE_DIR}/Source/*.c +) + +add_executable(init ${INIT_SOURCES}) + +target_include_directories(init PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Source + ${CMAKE_CURRENT_SOURCE_DIR}/../../Common +) + +target_compile_options(init PRIVATE + -std=c23 + -ffreestanding + -fno-stack-protector + -fno-builtin + -Wall -Wextra + -g + -mgeneral-regs-only +) + +# Передаем "голые" аргументы напрямую для ld.lld +target_link_options(init PRIVATE + "-Ttext=0x400000" + "-e" "_start" + "-z" "max-page-size=0x1000" + "--no-dynamic-linker" +) + +set_target_properties(init PROPERTIES + OUTPUT_NAME "Init" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +) diff --git a/Runtime/System/Init/Source/entry.S b/Runtime/System/Init/Source/entry.S new file mode 100644 index 0000000..67c5a32 --- /dev/null +++ b/Runtime/System/Init/Source/entry.S @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKSor + +.section .text.entry, "ax" +.global _start +_start: + // Set up stack pointer (8KB stack) + adrp x0, stack_top + add sp, x0, :lo12:stack_top + + // Jump to C main + bl main + + // If main returns, loop forever + b . + +.section .bss, "aw", @nobits +.align 12 // 4KB alignment +stack_bottom: + .space 8192 // 8KB stack +stack_top: diff --git a/Runtime/System/Init/Source/main.c b/Runtime/System/Init/Source/main.c new file mode 100644 index 0000000..fa9b93e --- /dev/null +++ b/Runtime/System/Init/Source/main.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKSor + +void main(void) { + __asm__ volatile( + "svc #0\n" + "b .\n" + ); +} diff --git a/Runtime/cmake/aarch64.cmake b/Runtime/cmake/aarch64.cmake new file mode 100644 index 0000000..72781e5 --- /dev/null +++ b/Runtime/cmake/aarch64.cmake @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (c) 2026 0xKSor + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR aarch64) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +if(NOT LLVM_BIN) + find_program(_CLANG + NAMES clang + HINTS + /opt/homebrew/opt/llvm/bin + /usr/local/opt/llvm/bin + ) + + if(NOT _CLANG AND APPLE) + execute_process( + COMMAND brew --prefix llvm + OUTPUT_VARIABLE LLVM_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE BREW_PREFIX_RESULT + ) + if(BREW_PREFIX_RESULT EQUAL 0 AND EXISTS "${LLVM_PREFIX}/bin/clang") + set(_CLANG "${LLVM_PREFIX}/bin/clang") + endif() + endif() + + if(NOT _CLANG) + message(FATAL_ERROR "clang not found. Set LLVM_BIN or add clang to PATH.") + endif() + + get_filename_component(LLVM_BIN "${_CLANG}" DIRECTORY) +endif() + +set(CMAKE_C_COMPILER "${LLVM_BIN}/clang") +set(CMAKE_ASM_COMPILER "${LLVM_BIN}/clang") + +set(TARGET_TRIPLE aarch64-none-elf) +set(CMAKE_C_COMPILER_TARGET ${TARGET_TRIPLE}) +set(CMAKE_ASM_COMPILER_TARGET ${TARGET_TRIPLE}) + +if(APPLE) + find_program(TERMOS_LD_LLD NAMES ld.lld HINTS /usr/local/bin /opt/homebrew/bin REQUIRED) + set(CMAKE_C_LINK_FLAGS "") + set(CMAKE_C_LINK_EXECUTABLE + "${TERMOS_LD_LLD} -o ") +endif() + +find_program(LLVM_OBJCOPY NAMES llvm-objcopy objcopy + HINTS + "${LLVM_BIN}" + /usr/local/opt/llvm/bin + /opt/homebrew/opt/llvm/bin + /usr/local/bin + /opt/homebrew/bin + REQUIRED +) diff --git a/Runtime/justfile b/Runtime/justfile new file mode 100644 index 0000000..88203b3 --- /dev/null +++ b/Runtime/justfile @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (c) 2026 0xKSor + +set quiet := true + +ROOT_BUILD_DIR := env_var_or_default("BUILD_DIR", justfile_directory() + "/../.build") +RUNTIME_BUILD_DIR := ROOT_BUILD_DIR + "/Runtime" +TEMP_DIR := env_var_or_default("TEMP_DIR", ROOT_BUILD_DIR + "/temp") + +_default: + just --list + +@_build_cmake project_dir target_subpath: + #!/usr/bin/env bash + set -e + PROJECT_DIR="{{ project_dir }}" + TARGET_SUBPATH="{{ target_subpath }}" + TEMP_DIR="{{ TEMP_DIR }}/Runtime/${TARGET_SUBPATH}" + OUT_DIR="{{ RUNTIME_BUILD_DIR }}/${TARGET_SUBPATH}" + + echo " 🛠️ CMake build: ${TARGET_SUBPATH}" + + TOOLCHAIN_FILE="{{ justfile_directory() }}/cmake/aarch64.cmake" + TOOLCHAIN="" + if [ -f "${TOOLCHAIN_FILE}" ]; then + TOOLCHAIN="-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}" + else + echo " ⚠️ Warning: Toolchain file not found at ${TOOLCHAIN_FILE}" + fi + + if ! OUT=$(cmake -B "${TEMP_DIR}" -S "${PROJECT_DIR}" -DCMAKE_BUILD_TYPE=Release ${TOOLCHAIN} -G Ninja 2>&1); then + echo "$OUT" + exit 1 + fi + + if ! OUT=$(cmake --build "${TEMP_DIR}" 2>&1); then + echo "$OUT" + exit 1 + fi + + mkdir -p "${OUT_DIR}" + # Copy everything from build dir except CMake junk + rsync -a --exclude='CMakeFiles' --exclude='CMakeCache.txt' --exclude='*.ninja*' --exclude='.ninja*' \ + "${TEMP_DIR}"/ "${OUT_DIR}"/ 2>/dev/null || \ + cp -R "${TEMP_DIR}"/* "${OUT_DIR}"/ 2>/dev/null || true + + echo " ✅ CMake done: ${TARGET_SUBPATH}" + +@_build_swiftpm project_dir target_subpath: + #!/usr/bin/env bash + set -e + PROJECT_DIR="{{ project_dir }}" + TARGET_SUBPATH="{{ target_subpath }}" + TEMP_DIR="{{ TEMP_DIR }}/Runtime/${TARGET_SUBPATH}" + OUT_DIR="{{ RUNTIME_BUILD_DIR }}/${TARGET_SUBPATH}" + + echo " 🛠️ SwiftPM build: ${TARGET_SUBPATH}" + + cd "${PROJECT_DIR}" + + if ! OUT=$(swift build -c release --build-path "${TEMP_DIR}" 2>&1); then + echo "$OUT" + exit 1 + fi + + mkdir -p "${OUT_DIR}" + if [ -d "${TEMP_DIR}/release" ]; then + rsync -a "${TEMP_DIR}/release"/ "${OUT_DIR}"/ 2>/dev/null || \ + cp -R "${TEMP_DIR}/release"/* "${OUT_DIR}"/ 2>/dev/null || true + elif [ -d "${TEMP_DIR}/Release" ]; then + rsync -a "${TEMP_DIR}/Release"/ "${OUT_DIR}"/ 2>/dev/null || \ + cp -R "${TEMP_DIR}/Release"/* "${OUT_DIR}"/ 2>/dev/null || true + fi + + echo " ✅ SwiftPM done: ${TARGET_SUBPATH}" + +@_discover_and_build category: + #!/usr/bin/env bash + set -e + CATEGORY="{{ category }}" + RUNTIME_DIR="{{ justfile_directory() }}" + SRC_DIR="${RUNTIME_DIR}/${CATEGORY}" + + if [ ! -d "${SRC_DIR}" ]; then + echo " 📁 ${CATEGORY}/ not found, skipping" + exit 0 + fi + + shopt -s nullglob + projects=("${SRC_DIR}"/*/) + shopt -u nullglob + + if [ ${#projects[@]} -eq 0 ]; then + echo " 📁 ${CATEGORY}/ is empty, skipping" + exit 0 + fi + + for dir in "${projects[@]}"; do + name=$(basename "${dir}") + echo "" + echo " 📦 ${CATEGORY}/${name}" + + if [ -f "${dir}/CMakeLists.txt" ]; then + just --quiet _build_cmake "${dir}" "${CATEGORY}/${name}" + elif [ -f "${dir}/Package.swift" ]; then + just --quiet _build_swiftpm "${dir}" "${CATEGORY}/${name}" + else + echo " ⚠️ Skipping ${CATEGORY}/${name}: no CMakeLists.txt or Package.swift found" + fi + done + +@build: + @echo "🛠️ Building Runtime..." + @mkdir -p {{ RUNTIME_BUILD_DIR }}/System + @mkdir -p {{ RUNTIME_BUILD_DIR }}/Apps + @just --quiet _discover_and_build System + @just --quiet _discover_and_build Apps + @echo "" + @echo "✅ Runtime ready at: {{ RUNTIME_BUILD_DIR }}" + +@clean: + @rm -rf {{ TEMP_DIR }}/Runtime + @rm -rf {{ RUNTIME_BUILD_DIR }} + @echo "🧹 Runtime cleaned" diff --git a/justfile b/justfile index 83a2f0e..23a796c 100644 --- a/justfile +++ b/justfile @@ -3,57 +3,38 @@ set quiet := true -OS_NAME := os() +OS_NAME := os() ARCH_NAME := arch() - HB_PREFIX := if ARCH_NAME == "aarch64" { "/opt/homebrew" } else { "/usr/local" } export PATH := HB_PREFIX + "/bin:" + HB_PREFIX + "/sbin:" + env_var("PATH") - -ACCEL := if OS_NAME == "macos" { - if ARCH_NAME == "aarch64" { "-accel hvf" } else { "" } -} else { - if ARCH_NAME == "aarch64" { "-accel kvm" } else { "" } -} - +ACCEL := if OS_NAME == "macos" { if ARCH_NAME == "aarch64" { "-accel hvf" } else { "" } } else if ARCH_NAME == "aarch64" { "-accel kvm" } else { "" } CPU := if ARCH_NAME == "aarch64" { "host" } else { "max" } - -OVMF_ARM := if OS_NAME == "macos" { - HB_PREFIX + "/share/qemu/edk2-aarch64-code.fd" -} else { - BUILD_DIR + "/edk2/edk2-aarch64-code.fd" -} - - -DISPLAY_FLAGS := if OS_NAME == "macos" { - "-display cocoa,show-cursor=on" -} else { - env_var_or_default("QEMU_DISPLAY", "-display sdl") -} - +OVMF_ARM := if OS_NAME == "macos" { HB_PREFIX + "/share/qemu/edk2-aarch64-code.fd" } else { BUILD_DIR + "/edk2/edk2-aarch64-code.fd" } +DISPLAY_FLAGS := if OS_NAME == "macos" { "-display cocoa,show-cursor=on" } else { env_var_or_default("QEMU_DISPLAY", "-display sdl") } ACCEL_INFO := if ACCEL == "" { "none (TCG)" } else { ACCEL } RAM := "2G" - export BUILD_DIR := justfile_directory() + "/.build" -export TEMP_DIR := BUILD_DIR + "/temp" -export BOOT_BIN := BUILD_DIR + "/Bootloader/BOOTAA64.EFI" -export IMG_FILE := BUILD_DIR + "/ksOS.img" +export TEMP_DIR := BUILD_DIR + "/temp" +export BOOT_BIN := BUILD_DIR + "/Bootloader/BOOTAA64.EFI" +export IMG_FILE := BUILD_DIR + "/ksOS.img" mod Bootloader mod Kernel +mod Runtime _default: just --list _prep: - @mkdir -p {{BUILD_DIR}}/Bootloader - @mkdir -p {{TEMP_DIR}}/Bootloader - @mkdir -p {{BUILD_DIR}}/Kernel - @if [ "{{OS_NAME}}" != "macos" ]; then \ - mkdir -p {{BUILD_DIR}}/edk2; \ - if [ ! -f "{{OVMF_ARM}}" ]; then \ + @mkdir -p {{ BUILD_DIR }}/Bootloader + @mkdir -p {{ TEMP_DIR }}/Bootloader + @mkdir -p {{ BUILD_DIR }}/Kernel + @if [ "{{ OS_NAME }}" != "macos" ]; then \ + mkdir -p {{ BUILD_DIR }}/edk2; \ + if [ ! -f "{{ OVMF_ARM }}" ]; then \ echo "⬇️ Downloading vanilla EDK2 for Linux..."; \ - curl -sL -o "{{OVMF_ARM}}.bz2" "https://github.com/qemu/qemu/raw/master/pc-bios/edk2-aarch64-code.fd.bz2"; \ - bzip2 -d "{{OVMF_ARM}}.bz2"; \ + curl -sL -o "{{ OVMF_ARM }}.bz2" "https://github.com/qemu/qemu/raw/master/pc-bios/edk2-aarch64-code.fd.bz2"; \ + bzip2 -d "{{ OVMF_ARM }}.bz2"; \ fi \ fi @@ -61,21 +42,32 @@ _prep: @echo "🛠️ Building everything..." just Bootloader build just Kernel build + just Runtime build @_image: build @echo "💾 Creating image..." - @dd if=/dev/zero of={{IMG_FILE}} bs=1M count=64 status=none - @mkfs.fat -F 32 {{IMG_FILE}} > /dev/null - @mmd -i {{IMG_FILE}} ::/EFI ::/EFI/BOOT - @mcopy -i {{IMG_FILE}} {{BOOT_BIN}} ::/EFI/BOOT/BOOTAA64.EFI - @mcopy -i {{IMG_FILE}} {{BUILD_DIR}}/Kernel/ksOSKernel.elf ::/ksOSKernel.elf - + @dd if=/dev/zero of={{ IMG_FILE }} bs=1M count=128 status=none + @mkfs.fat -F 32 {{ IMG_FILE }} > /dev/null + @mmd -i {{ IMG_FILE }} ::/EFI ::/EFI/BOOT + @mmd -i {{ IMG_FILE }} ::/System + @mmd -i {{ IMG_FILE }} ::/Apps + @mcopy -i {{ IMG_FILE }} {{ BOOT_BIN }} ::/EFI/BOOT/BOOTAA64.EFI + @mcopy -i {{ IMG_FILE }} {{ BUILD_DIR }}/Kernel/ksOSKernel.elf ::/ksOSKernel.elf + # Pack Runtime into ISO + @if [ -d {{ BUILD_DIR }}/Runtime/System ] &&[ "$$(ls {{ BUILD_DIR }}/Runtime/System 2>/dev/null)" ]; then \ + echo " 📦 Packing System runtime..."; \ + mcopy -s -i {{ IMG_FILE }} {{ BUILD_DIR }}/Runtime/System/* ::/System/ || true; \ + fi + @if [ -d {{ BUILD_DIR }}/Runtime/Apps ] &&[ "$$(ls {{ BUILD_DIR }}/Runtime/Apps 2>/dev/null)" ]; then \ + echo " 📦 Packing Apps runtime..."; \ + mcopy -s -i {{ IMG_FILE }} {{ BUILD_DIR }}/Runtime/Apps/* ::/Apps/ || true; \ + fi run *FLAGS: #!/usr/bin/env bash set -e - FLAGS=" {{FLAGS}} " + FLAGS=" {{ FLAGS }} " if [[ "$FLAGS" == *" -clean "* ]]; then echo "🧹 Cleaning..." @@ -85,7 +77,7 @@ run *FLAGS: just _image DEBUG_ARGS="" - DISPLAY_ARGS="{{DISPLAY_FLAGS}}" + DISPLAY_ARGS="{{ DISPLAY_FLAGS }}" STATE_MSG="Launching" if [[ "$FLAGS" == *" -debug "* ]]; then @@ -97,16 +89,16 @@ run *FLAGS: DISPLAY_ARGS="-nographic" fi - echo "🚀 $STATE_MSG (accel: {{ACCEL_INFO}})..." + echo "🚀 $STATE_MSG (accel: {{ ACCEL_INFO }})..." - qemu-system-aarch64 {{ACCEL}} \ + qemu-system-aarch64 {{ ACCEL }} \ -machine virt,acpi=off \ - -cpu {{CPU}} \ - -m {{RAM}} \ + -cpu {{ CPU }} \ + -m {{ RAM }} \ -device ramfb \ $DISPLAY_ARGS \ - -drive if=pflash,format=raw,readonly=on,file={{OVMF_ARM}} \ - -drive file={{IMG_FILE}},format=raw,if=none,id=hd0 \ + -drive if=pflash,format=raw,readonly=on,file={{ OVMF_ARM }} \ + -drive file={{ IMG_FILE }},format=raw,if=none,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -serial stdio \ -monitor telnet:127.0.0.1:5555,server,nowait \ @@ -115,6 +107,7 @@ run *FLAGS: @clean: just Bootloader clean just Kernel clean - rm -rf {{BUILD_DIR}} + just Runtime clean + rm -rf {{ BUILD_DIR }} rm -f compile_commands.json rm -f ide-swift-toolchain.txt