Files

454 lines
15 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKSor
#include "modules/uefi/uefi.h"
#include "modules/elf/elf.h"
#include "../../Common/bootinfo.h"
#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}
};
static void print(const wchar_t* msg) {
ST->ConOut->OutputString(ST->ConOut, (wchar_t*)msg);
}
void* memset(void* destination, int value, uint64_t count) {
uint8_t* ptr = (uint8_t*)destination;
while (count--) {
*ptr++ = (uint8_t)value;
}
return destination;
}
static efi_status_t die(void) {
while (1) {
__asm__ volatile("wfi");
}
return EFI_ABORTED;
}
static efi_status_t fail(const wchar_t* msg) {
print(msg);
return die();
}
static int guid_equal(const efi_guid_t* lhs, const efi_guid_t* rhs) {
const uint8_t* lhs_bytes = (const uint8_t*)lhs;
const uint8_t* rhs_bytes = (const uint8_t*)rhs;
for (int i = 0; i < 16; ++i) {
if (lhs_bytes[i] != rhs_bytes[i]) {
return 0;
}
}
return 1;
}
static void* find_configuration_table(const efi_guid_t* guid) {
for (uintn_t i = 0; i < ST->NumberOfTableEntries; ++i) {
if (guid_equal(&ST->ConfigurationTable[i].VendorGuid, guid)) {
return ST->ConfigurationTable[i].VendorTable;
}
}
return NULL;
}
static efi_status_t open_root_volume(efi_file_handle_t** root) {
efi_loaded_image_protocol_t* loaded_image = NULL;
efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
efi_status_t status = gBS->HandleProtocol(IM, &loaded_image_guid, (void**)&loaded_image);
if (EFI_ERROR(status)) {
return status;
}
efi_simple_file_system_protocol_t* fs = NULL;
efi_guid_t filesystem_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
status = gBS->HandleProtocol(loaded_image->DeviceHandle, &filesystem_guid, (void**)&fs);
if (EFI_ERROR(status)) {
return status;
}
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;
efi_status_t status = file->GetInfo(file, &file_info_guid, &file_info_size, NULL);
if (status != EFI_BUFFER_TOO_SMALL) {
return status;
}
status = gBS->AllocatePool(EfiLoaderData, file_info_size, (void**)file_info);
if (EFI_ERROR(status)) {
return status;
}
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);
BootModule* mod = &boot_info->modules[boot_info->moduleCount];
mod->entry = (void*)mod_elf->e_entry;
mod->capabilities = 0;
mod->segmentCount = 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';
int alloc_failed = 0;
for (int i = 0; i < mod_elf->e_phnum && mod->segmentCount < BOOT_MODULE_MAX_SEGMENTS; i++) {
if (phdrs[i].p_type != PT_LOAD) continue;
uint64_t vaddr_page = phdrs[i].p_vaddr & ~0xFFFULL;
uint64_t offset_in_page = phdrs[i].p_vaddr & 0xFFFULL;
uint64_t total_size = offset_in_page + phdrs[i].p_memsz;
uint64_t aligned_size = (total_size + 0xFFFULL) & ~0xFFFULL;
uintn_t seg_pages = aligned_size / PAGE_SIZE;
efi_physical_address_t seg_phys = 0;
status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, seg_pages, &seg_phys);
if (EFI_ERROR(status)) {
alloc_failed = 1;
break;
}
memset((void*)seg_phys, 0, aligned_size);
if (phdrs[i].p_filesz > 0) {
uint8_t* dest = (uint8_t*)(seg_phys + offset_in_page);
uint8_t* src = (uint8_t*)(module_addr + phdrs[i].p_offset);
for (uintn_t j = 0; j < phdrs[i].p_filesz; j++) dest[j] = src[j];
}
BootModuleSegment* seg = &mod->segments[mod->segmentCount];
seg->physicalBase = seg_phys;
seg->virtualBase = vaddr_page;
seg->size = aligned_size;
seg->flags = phdrs[i].p_flags;
mod->segmentCount++;
}
if (alloc_failed) {
for (int i = 0; i < (int)mod->segmentCount; i++) {
uint64_t sz = mod->segments[i].size;
uintn_t pg = sz / PAGE_SIZE;
gBS->FreePages(mod->segments[i].physicalBase, pg);
}
} else {
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);
if (EFI_ERROR(status)) return status;
efi_file_info_t* kernel_info = NULL;
status = read_file_info(kernel_file, &kernel_info);
if (EFI_ERROR(status)) return status;
*kernel_size = kernel_info->FileSize;
uintn_t kernel_pages = (*kernel_size + PAGE_SIZE - 1) / PAGE_SIZE;
status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, kernel_pages, kernel_addr);
if (EFI_ERROR(status)) return status;
uintn_t bytes_to_read = (uintn_t)*kernel_size;
status = kernel_file->Read(kernel_file, &bytes_to_read, (void*)*kernel_addr);
if (EFI_ERROR(status)) return status;
*out_handle = kernel_file;
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) {
efi_status_t status = validate_elf(kernel_addr);
if (EFI_ERROR(status)) {
return fail(WSTR("Invalid kernel ELF\r\n"));
}
return EFI_SUCCESS;
}
static efi_status_t load_elf_segments(efi_physical_address_t kernel_addr, efi_file_handle_t* kernel_file, efi_physical_address_t* out_phys_entry) {
Elf64_Ehdr* elf_header = (Elf64_Ehdr*)kernel_addr;
*out_phys_entry = 0;
for (int i = 0; i < elf_header->e_phnum; i++) {
Elf64_Phdr* phdr = (Elf64_Phdr*)(kernel_addr + elf_header->e_phoff + i * elf_header->e_phentsize);
if (phdr->p_type != PT_LOAD) continue;
uintn_t pages = (phdr->p_memsz + 0xFFF) / 0x1000;
efi_physical_address_t segment_addr = phdr->p_paddr;
efi_status_t status = gBS->AllocatePages(AllocateAddress, EfiLoaderData, pages, &segment_addr);
if (EFI_ERROR(status)) {
if (status == EFI_NOT_FOUND) print(WSTR(" (Range not in RAM) "));
if (status == EFI_OUT_OF_RESOURCES) print(WSTR(" (Occupied) "));
return fail(WSTR("Address conflict!\r\n"));
}
memset((void*)segment_addr, 0, phdr->p_memsz);
kernel_file->SetPosition(kernel_file, phdr->p_offset);
uintn_t size_to_read = phdr->p_filesz;
status = kernel_file->Read(kernel_file, &size_to_read, (void*)segment_addr);
if (EFI_ERROR(status) || size_to_read != phdr->p_filesz) {
return fail(WSTR("File read error\r\n"));
}
if (elf_header->e_entry >= phdr->p_vaddr && elf_header->e_entry < phdr->p_vaddr + phdr->p_memsz) {
uint64_t entry_offset = elf_header->e_entry - phdr->p_vaddr;
*out_phys_entry = segment_addr + entry_offset;
}
}
if (*out_phys_entry == 0) {
return fail(WSTR("Entry point not found in PT_LOAD segments\r\n"));
}
return EFI_SUCCESS;
}
static efi_status_t populate_memory_map(Bootinfo* boot_info) {
uintn_t map_size = 0;
efi_memory_descriptor_t* map = NULL;
uintn_t map_key = 0;
uintn_t descriptor_size = 0;
uint32_t descriptor_version = 0;
efi_status_t status = gBS->GetMemoryMap(&map_size, NULL, &map_key, &descriptor_size, &descriptor_version);
if (status != EFI_BUFFER_TOO_SMALL) {
return status;
}
map_size += PAGE_SIZE;
status = gBS->AllocatePool(EfiLoaderData, map_size, (void**)&map);
if (EFI_ERROR(status)) {
return status;
}
while (1) {
status = gBS->GetMemoryMap(&map_size, map, &map_key, &descriptor_size, &descriptor_version);
if (EFI_ERROR(status)) {
gBS->FreePool(map);
return status;
}
boot_info->memoryMap.descriptorSize = descriptor_size;
boot_info->memoryMap.descriptorVersion = descriptor_version;
boot_info->memoryMap.mapSize = map_size;
boot_info->memoryMap.mapKey = map_key;
boot_info->memoryMap.map = map;
status = gBS->ExitBootServices(IM, map_key);
if (status == EFI_SUCCESS) {
return EFI_SUCCESS;
}
gBS->FreePool(map);
map_size += 2 * descriptor_size;
status = gBS->AllocatePool(EfiLoaderData, map_size, (void**)&map);
if (EFI_ERROR(status)) {
return status;
}
}
}
efi_status_t bootloader_main(void) {
efi_gop_t* gop = NULL;
efi_guid_t gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
efi_status_t status = ST->BootServices->LocateProtocol(&gop_guid, 0, (void**)&gop);
if (EFI_ERROR(status) || gop == NULL) {
return fail(WSTR("Failed to locate GOP\r\n"));
}
void* dtb_address = find_configuration_table(&dtb_guid);
if (dtb_address == NULL) {
return fail(WSTR("Failed to find DTB\r\n"));
}
efi_file_handle_t* root = NULL;
status = open_root_volume(&root);
if (EFI_ERROR(status)) {
return fail(WSTR("Failed to open boot volume\r\n"));
}
efi_physical_address_t kernel_addr = 0;
uint64_t kernel_size = 0;
efi_file_handle_t* kernel_file = NULL;
status = load_kernel(root, &kernel_addr, &kernel_size, &kernel_file);
if (EFI_ERROR(status)) {
return fail(WSTR("Failed to load ksOSKernel.bin\r\n"));
}
status = parse_elf_headers(kernel_addr);
if (EFI_ERROR(status)) return status;
efi_physical_address_t phys_entry = 0;
status = load_elf_segments(kernel_addr, kernel_file, &phys_entry);
if (EFI_ERROR(status)) return status;
Bootinfo* boot_info = NULL;
status = gBS->AllocatePool(EfiLoaderData, sizeof(Bootinfo), (void**)&boot_info);
if (EFI_ERROR(status) || boot_info == NULL) {
return fail(WSTR("Failed to allocate boot info\r\n"));
}
boot_info->magic = BOOTINFO_MAGIC;
boot_info->kernelInfo.kernelAddress = (void*)kernel_addr;
boot_info->kernelInfo.kernelSize = kernel_size;
boot_info->framebuffer.base = (BIUInt32*)gop->Mode->FrameBufferBase;
boot_info->framebuffer.baseSize = gop->Mode->FrameBufferSize;
boot_info->framebuffer.height = gop->Mode->Information->VerticalResolution;
boot_info->framebuffer.width = gop->Mode->Information->HorizontalResolution;
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)) {
return fail(WSTR("Failed to exit boot services\r\n"));
}
typedef void (*kernel_entry_t)(Bootinfo*);
kernel_entry_t kernel_main = (kernel_entry_t)phys_entry;
kernel_main(boot_info);
return EFI_SUCCESS;
}