From 196543c3d3a0387e7b4e6116b43961e75caa617c Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Tue, 27 Oct 2026 22:50:43 +0100 Subject: [PATCH] boot: Add EFISTUB and refactor boot protocols --- Kconfig | 1 + arch/x86_64/Kconfig | 1 + arch/x86_64/include/bits/pmap.h | 17 ++ arch/x86_64/include/x86_64/defs.h | 1 + arch/x86_64/include/x86_64/pmap.h | 17 -- boot/Kconfig | 19 ++ boot/efi/efi.h | 249 +++++++++++++++++++++++ boot/efi/header.c | 97 +++++++++ boot/efi/main.c | 325 ++++++++++++++++++++++++++++++ boot/efi/meson.build | 42 ++++ boot/efi/pe.h | 164 +++++++++++++++ boot/efi/reloc.c | 61 ++++++ boot/efi/stub.h | 13 ++ boot/efi/stub.ld | 100 +++++++++ boot/efi/x86_64/efi_arch.h | 9 + boot/efi/x86_64/entry.S | 48 +++++ boot/efi/x86_64/meson.build | 1 + boot/kernel.ld | 5 + boot/limine/entry.c | 135 ------------- boot/limine/loader.ld | 46 +++++ boot/limine/main.c | 192 ++++++++++++++++++ boot/limine/meson.build | 26 ++- boot/loader/elf.c | 120 +++++++++++ boot/loader/lib.c | 53 +++++ boot/loader/loader.c | 81 ++++++++ boot/loader/loader.h | 41 ++++ boot/loader/x86_64/enter.S | 13 ++ boot/loader/x86_64/loader_arch.h | 8 + boot/loader/x86_64/meson.build | 11 + boot/loader/x86_64/paging.c | 45 +++++ boot/meson.build | 46 ++++- include/zinnia/alloc.h | 14 +- include/zinnia/boot.h | 60 ++++++ include/zinnia/elf.h | 3 +- include/zinnia/init.h | 36 +--- {boot => lib}/cmdline.c | 0 lib/entry.c | 22 ++ {boot => lib}/main.c | 1 + lib/meson.build | 3 + meson.build | 5 +- 40 files changed, 1923 insertions(+), 208 deletions(-) create mode 100644 boot/Kconfig create mode 100644 boot/efi/efi.h create mode 100644 boot/efi/header.c create mode 100644 boot/efi/main.c create mode 100644 boot/efi/meson.build create mode 100644 boot/efi/pe.h create mode 100644 boot/efi/reloc.c create mode 100644 boot/efi/stub.h create mode 100644 boot/efi/stub.ld create mode 100644 boot/efi/x86_64/efi_arch.h create mode 100644 boot/efi/x86_64/entry.S create mode 100644 boot/efi/x86_64/meson.build delete mode 100644 boot/limine/entry.c create mode 100644 boot/limine/loader.ld create mode 100644 boot/limine/main.c create mode 100644 boot/loader/elf.c create mode 100644 boot/loader/lib.c create mode 100644 boot/loader/loader.c create mode 100644 boot/loader/loader.h create mode 100644 boot/loader/x86_64/enter.S create mode 100644 boot/loader/x86_64/loader_arch.h create mode 100644 boot/loader/x86_64/meson.build create mode 100644 boot/loader/x86_64/paging.c create mode 100644 include/zinnia/boot.h rename {boot => lib}/cmdline.c (100%) create mode 100644 lib/entry.c rename {boot => lib}/main.c (98%) diff --git a/Kconfig b/Kconfig index 6bdfd47..84b897e 100644 --- a/Kconfig +++ b/Kconfig @@ -5,6 +5,7 @@ config KERNEL_FLAVOR default "vanilla" source "arch/$(ARCH)/Kconfig" +source "boot/Kconfig" source "mem/Kconfig" source "sched/Kconfig" source "dev/Kconfig" diff --git a/arch/x86_64/Kconfig b/arch/x86_64/Kconfig index aabf5bd..8286856 100644 --- a/arch/x86_64/Kconfig +++ b/arch/x86_64/Kconfig @@ -4,6 +4,7 @@ config X86_64 bool default y select ACPI + select HAVE_EFISTUB select HAVE_MMU select HAVE_MMU_GENERIC select HAVE_TLB_SHOOTDOWN_IPI diff --git a/arch/x86_64/include/bits/pmap.h b/arch/x86_64/include/bits/pmap.h index 8e17972..fc768ff 100644 --- a/arch/x86_64/include/bits/pmap.h +++ b/arch/x86_64/include/bits/pmap.h @@ -5,3 +5,20 @@ #include typedef uint64_t pte_t; + +enum : pte_t { + ARCH_FLAG_PRESENT = 1 << 0, + ARCH_FLAG_READ_WRITE = 1 << 1, + ARCH_FLAG_USER_MODE = 1 << 2, + ARCH_FLAG_WRITE_THROUGH = 1 << 3, + ARCH_FLAG_CACHE_DISABLE = 1 << 4, + ARCH_FLAG_ACCESSED = 1 << 5, + ARCH_FLAG_DIRTY = 1 << 6, + ARCH_FLAG_SIZE = 1 << 7, + ARCH_FLAG_GLOBAL = 1 << 8, + ARCH_FLAG_AVAILABLE = 1 << 9, + ARCH_FLAG_ATTRIBUTE_TABLE = 1 << 7, + ARCH_FLAG_EXECUTE_DISABLE = 1LU << 63, + + ARCH_PTE_ADDR_MASK = 0x000F'FFFF'FFFF'F000, +}; diff --git a/arch/x86_64/include/x86_64/defs.h b/arch/x86_64/include/x86_64/defs.h index 70c457c..f77cd00 100644 --- a/arch/x86_64/include/x86_64/defs.h +++ b/arch/x86_64/include/x86_64/defs.h @@ -187,6 +187,7 @@ enum : uint64_t { CR4_OSFXSR = 1ull << 9, CR4_OSXMMEXCPT = 1ull << 10, CR4_UMIP = 1ull << 11, + CR4_LA57 = 1ull << 12, CR4_VMXE = 1ull << 13, CR4_SMXE = 1ull << 14, CR4_FSGSBASE = 1ull << 16, diff --git a/arch/x86_64/include/x86_64/pmap.h b/arch/x86_64/include/x86_64/pmap.h index 191ad75..dcf309d 100644 --- a/arch/x86_64/include/x86_64/pmap.h +++ b/arch/x86_64/include/x86_64/pmap.h @@ -2,20 +2,3 @@ #pragma once #include - -enum : pte_t { - ARCH_FLAG_PRESENT = 1 << 0, - ARCH_FLAG_READ_WRITE = 1 << 1, - ARCH_FLAG_USER_MODE = 1 << 2, - ARCH_FLAG_WRITE_THROUGH = 1 << 3, - ARCH_FLAG_CACHE_DISABLE = 1 << 4, - ARCH_FLAG_ACCESSED = 1 << 5, - ARCH_FLAG_DIRTY = 1 << 6, - ARCH_FLAG_SIZE = 1 << 7, - ARCH_FLAG_GLOBAL = 1 << 8, - ARCH_FLAG_AVAILABLE = 1 << 9, - ARCH_FLAG_ATTRIBUTE_TABLE = 1 << 7, - ARCH_FLAG_EXECUTE_DISABLE = 1LU << 63, - - ARCH_PTE_ADDR_MASK = 0x000F'FFFF'FFFF'F000, -}; diff --git a/boot/Kconfig b/boot/Kconfig new file mode 100644 index 0000000..d00afaa --- /dev/null +++ b/boot/Kconfig @@ -0,0 +1,19 @@ +config HAVE_EFISTUB + bool + +menu "Boot loaders" + +config BOOT_EFISTUB + bool "EFI stub" + depends on HAVE_EFISTUB + default y + help + Kernel loader for UEFI systems. + +config BOOT_LIMINE + bool "Limine" + default n + help + ELF loader with the Limine protocol. + +endmenu diff --git a/boot/efi/efi.h b/boot/efi/efi.h new file mode 100644 index 0000000..b552dce --- /dev/null +++ b/boot/efi/efi.h @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include "efi_arch.h" + +typedef uint64_t efi_status; +typedef void* efi_handle; +typedef uint16_t char16; + +constexpr efi_status EFI_SUCCESS = 0; +constexpr efi_status EFI_ERROR = 1ull << 63; +constexpr efi_status EFI_LOAD_ERROR = EFI_ERROR | 1; +constexpr efi_status EFI_INVALID_PARAMETER = EFI_ERROR | 2; +constexpr efi_status EFI_UNSUPPORTED = EFI_ERROR | 3; +constexpr efi_status EFI_BUFFER_TOO_SMALL = EFI_ERROR | 5; +constexpr efi_status EFI_OUT_OF_RESOURCES = EFI_ERROR | 9; +constexpr efi_status EFI_NOT_FOUND = EFI_ERROR | 14; + +struct efi_guid { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +}; + +enum efi_memory_type : uint32_t { + EFI_RESERVED_MEMORY_TYPE, + EFI_LOADER_CODE, + EFI_LOADER_DATA, + EFI_BOOT_SERVICES_CODE, + EFI_BOOT_SERVICES_DATA, + EFI_RUNTIME_SERVICES_CODE, + EFI_RUNTIME_SERVICES_DATA, + EFI_CONVENTIONAL_MEMORY, + EFI_UNUSABLE_MEMORY, + EFI_ACPI_RECLAIM_MEMORY, + EFI_ACPI_MEMORY_NVS, + EFI_MEMORY_MAPPED_IO, + EFI_MEMORY_MAPPED_IO_PORT_SPACE, + EFI_PAL_CODE, + EFI_PERSISTENT_MEMORY, + EFI_UNACCEPTED_MEMORY, +}; + +enum efi_allocate_type : uint32_t { + EFI_ALLOCATE_ANY_PAGES, + EFI_ALLOCATE_MAX_ADDRESS, + EFI_ALLOCATE_ADDRESS, +}; + +enum efi_locate_search_type : uint32_t { + EFI_LOCATE_ALL_HANDLES, + EFI_LOCATE_BY_REGISTER_NOTIFY, + EFI_LOCATE_BY_PROTOCOL, +}; + +struct efi_memory_descriptor { + uint32_t type; + uint64_t physical_start; + uint64_t virtual_start; + uint64_t number_of_pages; + uint64_t attribute; +}; + +struct efi_table_header { + uint64_t signature; + uint32_t revision; + uint32_t header_size; + uint32_t crc32; + uint32_t reserved; +}; + +struct efi_simple_text_output_protocol { + void* reset; + efi_status(EFIAPI* output_string)(struct efi_simple_text_output_protocol* self, const char16* string); +}; + +struct efi_boot_services { + struct efi_table_header hdr; + void* raise_tpl; + void* restore_tpl; + efi_status(EFIAPI* allocate_pages)( + enum efi_allocate_type type, + enum efi_memory_type memory_type, + size_t pages, + uint64_t* memory + ); + efi_status(EFIAPI* free_pages)(uint64_t memory, size_t pages); + efi_status(EFIAPI* get_memory_map)( + size_t* map_size, + struct efi_memory_descriptor* map, + size_t* map_key, + size_t* descriptor_size, + uint32_t* descriptor_version + ); + efi_status(EFIAPI* allocate_pool)(enum efi_memory_type pool_type, size_t size, void** buffer); + efi_status(EFIAPI* free_pool)(void* buffer); + void* create_event; + void* set_timer; + void* wait_for_event; + void* signal_event; + void* close_event; + void* check_event; + void* install_protocol_interface; + void* reinstall_protocol_interface; + void* uninstall_protocol_interface; + efi_status(EFIAPI* handle_protocol)(efi_handle handle, const struct efi_guid* protocol, void** interface); + void* reserved; + void* register_protocol_notify; + void* locate_handle; + void* locate_device_path; + void* install_configuration_table; + void* load_image; + void* start_image; + void* exit; + void* unload_image; + efi_status(EFIAPI* exit_boot_services)(efi_handle image_handle, size_t map_key); + void* get_next_monotonic_count; + void* stall; + void* set_watchdog_timer; + void* connect_controller; + void* disconnect_controller; + void* open_protocol; + void* close_protocol; + void* open_protocol_information; + void* protocols_per_handle; + efi_status(EFIAPI* locate_handle_buffer)( + enum efi_locate_search_type search_type, + const struct efi_guid* protocol, + void* search_key, + size_t* no_handles, + efi_handle** buffer + ); + efi_status(EFIAPI* locate_protocol)(const struct efi_guid* protocol, void* registration, void** interface); +}; + +struct efi_configuration_table { + struct efi_guid vendor_guid; + void* vendor_table; +}; + +struct efi_system_table { + struct efi_table_header hdr; + char16* firmware_vendor; + uint32_t firmware_revision; + efi_handle console_in_handle; + void* con_in; + efi_handle console_out_handle; + struct efi_simple_text_output_protocol* con_out; + efi_handle standard_error_handle; + void* std_err; + void* runtime_services; + struct efi_boot_services* boot_services; + size_t number_of_table_entries; + struct efi_configuration_table* configuration_table; +}; + +struct efi_loaded_image_protocol { + uint32_t revision; + efi_handle parent_handle; + struct efi_system_table* system_table; + efi_handle device_handle; + void* file_path; + void* reserved; + uint32_t load_options_size; + void* load_options; + void* image_base; + uint64_t image_size; + enum efi_memory_type image_code_type; + enum efi_memory_type image_data_type; + void* unload; +}; + +constexpr uint64_t EFI_FILE_MODE_READ = 1; + +struct efi_file_protocol { + uint64_t revision; + efi_status(EFIAPI* open)( + struct efi_file_protocol* self, + struct efi_file_protocol** new_handle, + const char16* file_name, + uint64_t open_mode, + uint64_t attributes + ); + efi_status(EFIAPI* close)(struct efi_file_protocol* self); + void* delete; + efi_status(EFIAPI* read)(struct efi_file_protocol* self, size_t* buffer_size, void* buffer); + void* write; + efi_status(EFIAPI* get_position)(struct efi_file_protocol* self, uint64_t* position); + efi_status(EFIAPI* set_position)(struct efi_file_protocol* self, uint64_t position); +}; + +struct efi_simple_file_system_protocol { + uint64_t revision; + efi_status(EFIAPI* open_volume)(struct efi_simple_file_system_protocol* self, struct efi_file_protocol** root); +}; + +enum efi_pixel_format : uint32_t { + EFI_PIXEL_RGBX_8, + EFI_PIXEL_BGRX_8, + EFI_PIXEL_BIT_MASK, + EFI_PIXEL_BLT_ONLY, +}; + +struct efi_pixel_bitmask { + uint32_t red; + uint32_t green; + uint32_t blue; + uint32_t reserved; +}; + +struct efi_gop_mode_info { + uint32_t version; + uint32_t horizontal_resolution; + uint32_t vertical_resolution; + enum efi_pixel_format pixel_format; + struct efi_pixel_bitmask pixel_information; + uint32_t pixels_per_scan_line; +}; + +struct efi_gop_mode { + uint32_t max_mode; + uint32_t mode; + struct efi_gop_mode_info* info; + size_t size_of_info; + uint64_t frame_buffer_base; + size_t frame_buffer_size; +}; + +struct efi_graphics_output_protocol { + void* query_mode; + void* set_mode; + void* blt; + struct efi_gop_mode* mode; +}; + +static const struct efi_guid EFI_LOADED_IMAGE_PROTOCOL_GUID = + {0x5b1b'31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}}; +static const struct efi_guid EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID = + {0x964e'5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}}; +static const struct efi_guid EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID = + {0x9042'a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}}; +static const struct efi_guid EFI_ACPI_20_TABLE_GUID = + {0x8868'e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81}}; +static const struct efi_guid EFI_ACPI_10_TABLE_GUID = + {0xeb9d'2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d}}; diff --git a/boot/efi/header.c b/boot/efi/header.c new file mode 100644 index 0000000..f20a818 --- /dev/null +++ b/boot/efi/header.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include "efi_arch.h" +#include "pe.h" + +extern const char __pe_code_size[]; +extern const char __pe_data_sizes[]; +extern const char __pe_entry[]; +extern const char __pe_image_sizes[]; +extern const char __pe_base_relocs[]; +extern const char __pe_text_virtual[]; +extern const char __pe_text_raw[]; +extern const char __pe_reloc_virtual[]; +extern const char __pe_reloc_raw[]; +extern const char __pe_rodata_virtual[]; +extern const char __pe_rodata_raw[]; +extern const char __pe_data_virtual[]; +extern const char __pe_data_raw[]; + +struct efistub_pe_header { + struct pe_dos_header dos; + uint32_t signature; + struct pe_coff_header coff; + struct pe_optional_header64 optional; + struct pe_section_header sections[4]; +}; + +static_assert(offsetof(struct efistub_pe_header, optional) % 8 == 0); +static_assert(offsetof(struct efistub_pe_header, sections) % 8 == 0); + +[[__used, __section(".header")]] +const struct efistub_pe_header efistub_pe_header = { + .dos = + { + .e_magic = PE_DOS_MAGIC, + .e_lfanew = offsetof(struct efistub_pe_header, signature), + }, + .signature = PE_SIGNATURE, + .coff = + { + .machine = EFI_ARCH_PE_MACHINE, + .number_of_sections = 4, + .size_of_optional_header = sizeof(struct pe_optional_header64), + .characteristics = PE_FILE_EXECUTABLE_IMAGE | PE_FILE_LINE_NUMS_STRIPPED | PE_FILE_DEBUG_STRIPPED, + }, + .optional = + { + .magic_and_code_size = (uintptr_t)__pe_code_size + PE_OPTIONAL_MAGIC_PE32_PLUS, + .data_sizes = (uintptr_t)__pe_data_sizes, + .entry_and_code_base = (uintptr_t)__pe_entry, + .section_alignment = 0x1000, + .file_alignment = 0x1000, + .image_and_header_sizes = (uintptr_t)__pe_image_sizes, + .subsystem = PE_SUBSYSTEM_EFI_APPLICATION, + .dll_characteristics = PE_DLL_NX_COMPAT, + .number_of_rva_and_sizes = PE_NUM_DATA_DIRECTORIES, + .data_directories = + { + [PE_DIRECTORY_BASE_RELOC] = {.layout = (uintptr_t)__pe_base_relocs}, + }, + }, + .sections = { + { + .name = ".text", + .virtual_layout = (uintptr_t)__pe_text_virtual, + .raw_layout = (uintptr_t)__pe_text_raw, + .characteristics = PE_SCN_CNT_CODE | PE_SCN_MEM_EXECUTE | PE_SCN_MEM_READ, + }, + { + .name = ".reloc", + .virtual_layout = (uintptr_t)__pe_reloc_virtual, + .raw_layout = (uintptr_t)__pe_reloc_raw, + .characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_DISCARDABLE | PE_SCN_MEM_READ, + }, + { + .name = ".rdata", + .virtual_layout = (uintptr_t)__pe_rodata_virtual, + .raw_layout = (uintptr_t)__pe_rodata_raw, + .characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_READ, + }, + { + .name = ".data", + .virtual_layout = (uintptr_t)__pe_data_virtual, + .raw_layout = (uintptr_t)__pe_data_raw, + .characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_READ | PE_SCN_MEM_WRITE, + }, + }, +}; + +[[__used, __section(".reloc")]] +const struct pe_base_reloc_block efistub_base_relocs = { + .page_rva = 0, + .block_size = sizeof(struct pe_base_reloc_block), +}; diff --git a/boot/efi/main.c b/boot/efi/main.c new file mode 100644 index 0000000..9251fbe --- /dev/null +++ b/boot/efi/main.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: ISC + +#include +#include "stub.h" + +constexpr size_t MEM_MAP_SLACK = 64; + +struct efi_boot_services* bs; + +bool loader_alloc(enum phys_mem_usage usage, size_t size, uint64_t* out) { + enum efi_memory_type efi_type = EFI_LOADER_DATA; + if (usage == PHYS_STATIC) + efi_type = MEM_STATIC; + else if (usage == PHYS_RECLAIMABLE) + efi_type = MEM_RECLAIMABLE; + + const size_t pages = ALIGN_UP(size, PAGE_SIZE) / PAGE_SIZE; + if (bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, efi_type, pages, out)) + return false; + memset((void*)*out, 0, pages * PAGE_SIZE); + return true; +} + +void* loader_ptr(uint64_t phys) { + return (void*)phys; +} + +static efi_status open_file_on(efi_handle handle, const char16* path, struct efi_file_protocol** out) { + struct efi_simple_file_system_protocol* fs; + efi_status status = bs->handle_protocol(handle, &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, (void**)&fs); + if (status) + return status; + + struct efi_file_protocol* root; + status = fs->open_volume(fs, &root); + if (status) + return status; + + status = root->open(root, out, path, EFI_FILE_MODE_READ, 0); + if (status && path[0] == '\\') + status = root->open(root, out, path + 1, EFI_FILE_MODE_READ, 0); + root->close(root); + return status; +} + +static efi_status read_file(struct efi_file_protocol* file, enum phys_mem_usage usage, uint64_t* base, uint64_t* size) { + uint64_t total; + uint64_t done = 0; + efi_status status = file->set_position(file, UINT64_MAX); + if (!status) + status = file->get_position(file, &total); + if (!status) + status = file->set_position(file, 0); + if (!status && !loader_alloc(usage, total ? total : 1, base)) + status = EFI_OUT_OF_RESOURCES; + while (!status && done < total) { + size_t chunk = total - done; + status = file->read(file, &chunk, (void*)(*base + done)); + if (!chunk) + break; + done += chunk; + } + file->close(file); + *size = done; + return status; +} + +static void color_from_mask(uint32_t mask, struct boot_color* out) { + if (!mask) + return; + out->shift = __builtin_ctz(mask); + out->size = __builtin_ctzll(~(uint64_t)(mask >> out->shift)); +} + +static enum phys_mem_usage convert_mem_type(uint32_t type) { + switch (type) { + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + case EFI_CONVENTIONAL_MEMORY: + return PHYS_USABLE; + case EFI_ACPI_RECLAIM_MEMORY: + case MEM_RECLAIMABLE: + return PHYS_RECLAIMABLE; + case MEM_STATIC: + return PHYS_STATIC; + default: + return PHYS_RESERVED; + } +} + +efi_status efi_main(efi_handle image_handle, struct efi_system_table* st) { + bs = st->boot_services; + + // Check if we support this configuration. + if (!arch_check_cpu()) + return EFI_UNSUPPORTED; + + struct efi_loaded_image_protocol* image; + efi_status status = bs->handle_protocol(image_handle, &EFI_LOADED_IMAGE_PROTOCOL_GUID, (void**)&image); + if (status) + return status; + + struct kernel_image kernel; + if (!loader_load_kernel(&kernel, "EFISTUB")) + return EFI_LOAD_ERROR; + struct boot_info* info = kernel.info; + + // Reserve room for the memory map. + size_t map_size = 0; + size_t map_key; + size_t desc_size; + uint32_t desc_version; + status = bs->get_memory_map(&map_size, nullptr, &map_key, &desc_size, &desc_version); + if (status != EFI_BUFFER_TOO_SMALL) + return status; + + const size_t map_capacity = map_size + MEM_MAP_SLACK * desc_size; + uint64_t map; + if (!loader_alloc(PHYS_USABLE, map_capacity, &map)) + return EFI_OUT_OF_RESOURCES; + + uint64_t mem; + if (!loader_alloc(PHYS_USABLE, map_capacity / desc_size * sizeof(struct phys_mem), &mem)) + return EFI_OUT_OF_RESOURCES; + + // Use the load options as the command line. + const char16* opts = image->load_options; + const size_t opts_len = image->load_options_size / sizeof(char16); + size_t cmdline_len = 0; + for (size_t i = 0; opts && i < opts_len && opts[i]; i++) { + if (opts[i] < 0x20 || opts[i] > 0x7e || cmdline_len + 1 >= sizeof(info->cmdline)) { + cmdline_len = 0; + break; + } + info->cmdline[cmdline_len++] = (char)opts[i]; + } + info->cmdline[cmdline_len] = 0; + + // Without load options, fall back to a command line file on the boot device. + struct efi_file_protocol* cmdline_file; + if (!cmdline_len && !open_file_on(image->device_handle, u"\\EFI\\zinnia\\cmdline", &cmdline_file)) { + uint64_t base; + uint64_t size; + status = read_file(cmdline_file, PHYS_USABLE, &base, &size); + if (status) + return status; + + const char* text = (const char*)base; + while (cmdline_len < size && text[cmdline_len] && text[cmdline_len] != '\n' && text[cmdline_len] != '\r') { + if (text[cmdline_len] < 0x20 || text[cmdline_len] > 0x7e) + return EFI_INVALID_PARAMETER; + if (cmdline_len + 1 >= sizeof(info->cmdline)) + return EFI_BUFFER_TOO_SMALL; + info->cmdline[cmdline_len] = text[cmdline_len]; + cmdline_len++; + } + while (cmdline_len && info->cmdline[cmdline_len - 1] == ' ') + cmdline_len--; + info->cmdline[cmdline_len] = 0; + } + + // Load every initrd= file. + const char* cmdline = info->cmdline; + for (size_t i = 0; cmdline[i];) { + if (cmdline[i] == ' ') { + i++; + continue; + } + + const size_t start = i; + bool quoted = false; + while (cmdline[i] && (quoted || cmdline[i] != ' ')) { + if (cmdline[i] == '"') + quoted = !quoted; + i++; + } + const char* token = cmdline + start; + const size_t len = i - start; + if (len <= 7 || memcmp(token, "initrd=", 7)) + continue; + + const size_t path_len = len - 7; + if (path_len >= BOOT_PATH_MAX) + return EFI_INVALID_PARAMETER; + char path[BOOT_PATH_MAX]; + char16 wide[BOOT_PATH_MAX]; + for (size_t j = 0; j < path_len; j++) { + path[j] = token[7 + j]; + wide[j] = path[j] == '/' ? '\\' : (char16)path[j]; + } + path[path_len] = 0; + wide[path_len] = 0; + + // Try the boot device first, then all other file systems. + struct efi_file_protocol* file; + status = open_file_on(image->device_handle, wide, &file); + size_t num_handles; + efi_handle* handles; + if (status && + !bs->locate_handle_buffer( + EFI_LOCATE_BY_PROTOCOL, + &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, + nullptr, + &num_handles, + &handles + )) { + for (size_t j = 0; j < num_handles && status; j++) { + if (handles[j] != image->device_handle) + status = open_file_on(handles[j], wide, &file); + } + bs->free_pool(handles); + } + if (status) + return status; + + uint64_t base; + uint64_t size; + status = read_file(file, PHYS_STATIC, &base, &size); + if (status) + return status; + + if (!loader_add_file(&kernel, path, base, size)) + return EFI_OUT_OF_RESOURCES; + } + + // Framebuffer + struct efi_graphics_output_protocol* gop; + if (!bs->locate_protocol(&EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, nullptr, (void**)&gop) && gop && gop->mode && + gop->mode->info) { + const struct efi_gop_mode_info* mode = gop->mode->info; + struct boot_framebuffer* fb = &info->fb; + switch (mode->pixel_format) { + case EFI_PIXEL_RGBX_8: + fb->bytes_per_pixel = 4; + fb->red = (struct boot_color){0, 8}; + fb->green = (struct boot_color){8, 8}; + fb->blue = (struct boot_color){16, 8}; + break; + case EFI_PIXEL_BGRX_8: + fb->bytes_per_pixel = 4; + fb->red = (struct boot_color){16, 8}; + fb->green = (struct boot_color){8, 8}; + fb->blue = (struct boot_color){0, 8}; + break; + case EFI_PIXEL_BIT_MASK: { + const struct efi_pixel_bitmask* m = &mode->pixel_information; + const uint32_t all = m->red | m->green | m->blue | m->reserved; + if (!all) + break; + fb->bytes_per_pixel = (32 - __builtin_clz(all) + 7) / 8; + color_from_mask(m->red, &fb->red); + color_from_mask(m->green, &fb->green); + color_from_mask(m->blue, &fb->blue); + break; + } + default: + break; + } + + if (fb->bytes_per_pixel) { + fb->base = gop->mode->frame_buffer_base; + fb->width = mode->horizontal_resolution; + fb->height = mode->vertical_resolution; + fb->pitch = mode->pixels_per_scan_line * fb->bytes_per_pixel; + } + } + + // Find the RSDP. + for (size_t i = 0; i < st->number_of_table_entries; i++) { + const struct efi_configuration_table* table = &st->configuration_table[i]; + if (!memcmp(&table->vendor_guid, &EFI_ACPI_20_TABLE_GUID, sizeof(struct efi_guid))) { + info->rsdp = (uint64_t)table->vendor_table; + break; + } + if (!memcmp(&table->vendor_guid, &EFI_ACPI_10_TABLE_GUID, sizeof(struct efi_guid))) + info->rsdp = (uint64_t)table->vendor_table; + } + + // Map everything up to the highest non-reserved address. + map_size = map_capacity; + status = bs->get_memory_map(&map_size, (struct efi_memory_descriptor*)map, &map_key, &desc_size, &desc_version); + if (status) + return status; + + uint64_t top = 0; + for (size_t off = 0; off + desc_size <= map_size; off += desc_size) { + const struct efi_memory_descriptor* desc = (const struct efi_memory_descriptor*)(map + off); + if (convert_mem_type(desc->type) != PHYS_RESERVED) + top = MAX(top, desc->physical_start + desc->number_of_pages * PAGE_SIZE); + } + + uint64_t root; + if (!loader_page_tables(&kernel, top, (uint64_t)arch_enter_kernel, &root)) + return EFI_OUT_OF_RESOURCES; + + // Exit boot services. + for (size_t attempt = 0;; attempt++) { + map_size = map_capacity; + status = bs->get_memory_map(&map_size, (struct efi_memory_descriptor*)map, &map_key, &desc_size, &desc_version); + if (status) + return status; + status = bs->exit_boot_services(image_handle, map_key); + if (!status) + break; + if (attempt == 8) + return status; + } + + // Hand the final memory map to the kernel. + struct phys_mem* mem_map = (struct phys_mem*)mem; + size_t num_mem = 0; + for (size_t off = 0; off + desc_size <= map_size; off += desc_size) { + const struct efi_memory_descriptor* desc = (const struct efi_memory_descriptor*)(map + off); + mem_map[num_mem++] = (struct phys_mem){ + .address = desc->physical_start, + .length = desc->number_of_pages * PAGE_SIZE, + .usage = convert_mem_type(desc->type), + }; + } + loader_set_mem_map(&kernel, mem_map, num_mem); + + arch_enter_kernel(root, kernel.entry); +} diff --git a/boot/efi/meson.build b/boot/efi/meson.build new file mode 100644 index 0000000..d3552ec --- /dev/null +++ b/boot/efi/meson.build @@ -0,0 +1,42 @@ +efistub_sources = files( + 'header.c', + 'main.c', + 'reloc.c', +) + +efistub_lds = meson.current_source_dir() / 'stub.ld' + +subdir(target_machine.cpu_family()) + +efistub_elf = executable( + 'efistub.elf', + sources: [loader_sources, efistub_sources, loader_kernel], + include_directories: [ + common_includes, + loader_includes, + include_directories('.', target_machine.cpu_family()), + ], + c_args: loader_c_args, + link_args: [ + '-nostdlib', + '-Wl,-T,' + efistub_lds, + '-Wl,--no-dynamic-linker', + '-Wl,-z,text', + '-Wl,-z,norelro', + '-Wl,-z,noexecstack', + '-Wl,-z,max-page-size=0x1000', + '-Wl,--build-id=none', + ], + link_depends: efistub_lds, + pie: true, +) + +zinnia_efi = custom_target( + 'zinnia.efi', + input: efistub_elf, + output: 'zinnia.efi', + command: [objcopy, '-O', 'binary', '@INPUT@', '@OUTPUT@'], + build_by_default: true, + install: true, + install_dir: zinnia_bootdir, +) diff --git a/boot/efi/pe.h b/boot/efi/pe.h new file mode 100644 index 0000000..829450d --- /dev/null +++ b/boot/efi/pe.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include + +constexpr uint16_t PE_DOS_MAGIC = 0x5a4d; +constexpr uint32_t PE_SIGNATURE = 0x0000'4550; +constexpr uint16_t PE_OPTIONAL_MAGIC_PE32_PLUS = 0x020b; +constexpr uint16_t PE_SUBSYSTEM_EFI_APPLICATION = 10; +constexpr size_t PE_NUM_DATA_DIRECTORIES = 16; +constexpr size_t PE_DIRECTORY_BASE_RELOC = 5; + +enum pe_file_characteristics : uint16_t { + PE_FILE_EXECUTABLE_IMAGE = 0x0002, + PE_FILE_LINE_NUMS_STRIPPED = 0x0004, + PE_FILE_DEBUG_STRIPPED = 0x0200, +}; + +enum pe_dll_characteristics : uint16_t { + PE_DLL_NX_COMPAT = 0x0100, +}; + +enum pe_section_characteristics : uint32_t { + PE_SCN_CNT_CODE = 0x0000'0020, + PE_SCN_CNT_INITIALIZED_DATA = 0x0000'0040, + PE_SCN_MEM_DISCARDABLE = 0x0200'0000, + PE_SCN_MEM_EXECUTE = 0x2000'0000, + PE_SCN_MEM_READ = 0x4000'0000, + PE_SCN_MEM_WRITE = 0x8000'0000, +}; + +struct pe_dos_header { + uint16_t e_magic; + uint16_t e_cblp; + uint16_t e_cp; + uint16_t e_crlc; + uint16_t e_cparhdr; + uint16_t e_minalloc; + uint16_t e_maxalloc; + uint16_t e_ss; + uint16_t e_sp; + uint16_t e_csum; + uint16_t e_ip; + uint16_t e_cs; + uint16_t e_lfarlc; + uint16_t e_ovno; + uint16_t e_res[4]; + uint16_t e_oemid; + uint16_t e_oeminfo; + uint16_t e_res2[10]; + uint32_t e_lfanew; +}; + +struct pe_coff_header { + uint16_t machine; + uint16_t number_of_sections; + uint32_t time_date_stamp; + uint32_t pointer_to_symbol_table; + uint32_t number_of_symbols; + uint16_t size_of_optional_header; + uint16_t characteristics; +}; + +struct pe_data_directory { + union { + struct { + uint32_t virtual_address; + uint32_t size; + }; + uint64_t layout; + }; +}; + +struct pe_optional_header64 { + union { + struct { + uint16_t magic; + uint8_t major_linker_version; + uint8_t minor_linker_version; + uint32_t size_of_code; + }; + uint64_t magic_and_code_size; + }; + union { + struct { + uint32_t size_of_initialized_data; + uint32_t size_of_uninitialized_data; + }; + uint64_t data_sizes; + }; + union { + struct { + uint32_t address_of_entry_point; + uint32_t base_of_code; + }; + uint64_t entry_and_code_base; + }; + uint64_t image_base; + uint32_t section_alignment; + uint32_t file_alignment; + uint16_t major_operating_system_version; + uint16_t minor_operating_system_version; + uint16_t major_image_version; + uint16_t minor_image_version; + uint16_t major_subsystem_version; + uint16_t minor_subsystem_version; + uint32_t win32_version_value; + union { + struct { + uint32_t size_of_image; + uint32_t size_of_headers; + }; + uint64_t image_and_header_sizes; + }; + uint32_t checksum; + uint16_t subsystem; + uint16_t dll_characteristics; + uint64_t size_of_stack_reserve; + uint64_t size_of_stack_commit; + uint64_t size_of_heap_reserve; + uint64_t size_of_heap_commit; + uint32_t loader_flags; + uint32_t number_of_rva_and_sizes; + struct pe_data_directory data_directories[PE_NUM_DATA_DIRECTORIES]; +}; + +struct pe_section_header { + char name[8]; + union { + struct { + uint32_t virtual_size; + uint32_t virtual_address; + }; + uint64_t virtual_layout; + }; + union { + struct { + uint32_t size_of_raw_data; + uint32_t pointer_to_raw_data; + }; + uint64_t raw_layout; + }; + uint32_t pointer_to_relocations; + uint32_t pointer_to_line_numbers; + uint16_t number_of_relocations; + uint16_t number_of_line_numbers; + uint32_t characteristics; +}; + +struct pe_base_reloc_block { + uint32_t page_rva; + uint32_t block_size; + uint16_t entries[2]; +}; + +static_assert(sizeof(struct pe_dos_header) == 64); +static_assert(sizeof(struct pe_coff_header) == 20); +static_assert(sizeof(struct pe_optional_header64) == 240); +static_assert(offsetof(struct pe_optional_header64, size_of_image) == 56); +static_assert(offsetof(struct pe_optional_header64, data_directories) == 112); +static_assert(sizeof(struct pe_section_header) == 40); +static_assert(sizeof(struct pe_base_reloc_block) == 12); diff --git a/boot/efi/reloc.c b/boot/efi/reloc.c new file mode 100644 index 0000000..55c8ea1 --- /dev/null +++ b/boot/efi/reloc.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include "efi.h" + +efi_status efistub_relocate(uintptr_t base, const elf64_dyn_t* dynamic) { + uintptr_t rela = 0; + size_t rela_size = 0; + size_t rela_ent = sizeof(elf64_rela_t); + uintptr_t relr = 0; + size_t relr_size = 0; + + for (const elf64_dyn_t* dyn = dynamic; dyn->d_tag != DT_NULL; dyn++) { + switch (dyn->d_tag) { + case DT_RELA: + rela = base + dyn->d_un.d_val; + break; + case DT_RELASZ: + rela_size = dyn->d_un.d_val; + break; + case DT_RELAENT: + rela_ent = dyn->d_un.d_val; + break; + case DT_RELR: + relr = base + dyn->d_un.d_val; + break; + case DT_RELRSZ: + relr_size = dyn->d_un.d_val; + break; + } + } + + for (size_t off = 0; rela && off + rela_ent <= rela_size; off += rela_ent) { + const elf64_rela_t* r = (const elf64_rela_t*)(rela + off); + const uint32_t type = ELF64_R_TYPE(r->r_info); + if (type == R_NONE) + continue; + if (type != R_RELATIVE) + return EFI_LOAD_ERROR; + *(uint64_t*)(base + r->r_offset) = base + r->r_addend; + } + + uint64_t* where = nullptr; + for (size_t off = 0; relr && off + sizeof(uint64_t) <= relr_size; off += sizeof(uint64_t)) { + uint64_t entry = *(const uint64_t*)(relr + off); + if (!(entry & 1)) { + where = (uint64_t*)(base + entry); + *where++ += base; + continue; + } + for (size_t i = 0; (entry >>= 1) != 0; i++) { + if (entry & 1) + where[i] += base; + } + where += 63; + } + + return EFI_SUCCESS; +} diff --git a/boot/efi/stub.h b/boot/efi/stub.h new file mode 100644 index 0000000..45152e4 --- /dev/null +++ b/boot/efi/stub.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include "efi.h" +#include "loader.h" + +constexpr enum efi_memory_type MEM_STATIC = (enum efi_memory_type)0x8000'0000; +constexpr enum efi_memory_type MEM_RECLAIMABLE = (enum efi_memory_type)0x8000'0001; + +extern struct efi_boot_services* bs; diff --git a/boot/efi/stub.ld b/boot/efi/stub.ld new file mode 100644 index 0000000..5a0f253 --- /dev/null +++ b/boot/efi/stub.ld @@ -0,0 +1,100 @@ +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(5); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(6); + dynamic PT_DYNAMIC FLAGS(6); +} + +SECTIONS +{ + . = 0; + + .header : { + __image_base = .; + KEEP(*(.header)) + } :text + + . = ALIGN(0x1000); + .text : { + __text_start = .; + *(.text .text.*) + . = ALIGN(0x1000); + __text_end = .; + } :text + + .reloc : { + __reloc_start = .; + KEEP(*(.reloc)) + __reloc_data_end = .; + . = ALIGN(0x1000); + __reloc_end = .; + } :rodata + + .rodata : { + __rodata_start = .; + *(.rodata .rodata.*) + . = ALIGN(16); + __kernel_start = .; + KEEP(*(.kernel)) + __kernel_end = .; + } :rodata + + .hash : { *(.hash) } :rodata + .gnu.hash : { *(.gnu.hash) } :rodata + .dynsym : { *(.dynsym) } :rodata + .dynstr : { *(.dynstr) } :rodata + .rela.dyn : { *(.rela.*) } :rodata + .relr.dyn : { *(.relr.dyn) } :rodata + + .rodata.end : { + . = ALIGN(0x1000); + __rodata_end = .; + } :rodata + + .dynamic : { + __data_start = .; + *(.dynamic) + } :data :dynamic + + .data : { + *(.data .data.*) + *(.got .got.*) + . = ALIGN(0x1000); + __data_raw_end = .; + } :data + + .bss (NOLOAD) : { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + . = ALIGN(0x1000); + __bss_end = .; + } :data + + __image_end = .; + + __pe_code_size = ABSOLUTE((__text_end - __text_start) << 32); + __pe_data_sizes = ABSOLUTE(((__reloc_end - __reloc_start) + (__rodata_end - __rodata_start) + + (__data_raw_end - __data_start)) | ((__bss_end - __bss_start) << 32)); + __pe_entry = ABSOLUTE(_start | (__text_start << 32)); + __pe_image_sizes = ABSOLUTE(__image_end | (__text_start << 32)); + __pe_base_relocs = ABSOLUTE(__reloc_start | ((__reloc_data_end - __reloc_start) << 32)); + __pe_text_virtual = ABSOLUTE((__text_end - __text_start) | (__text_start << 32)); + __pe_text_raw = ABSOLUTE((__text_end - __text_start) | (__text_start << 32)); + __pe_reloc_virtual = ABSOLUTE((__reloc_data_end - __reloc_start) | (__reloc_start << 32)); + __pe_reloc_raw = ABSOLUTE((__reloc_end - __reloc_start) | (__reloc_start << 32)); + __pe_rodata_virtual = ABSOLUTE((__rodata_end - __rodata_start) | (__rodata_start << 32)); + __pe_rodata_raw = ABSOLUTE((__rodata_end - __rodata_start) | (__rodata_start << 32)); + __pe_data_virtual = ABSOLUTE((__bss_end - __data_start) | (__data_start << 32)); + __pe_data_raw = ABSOLUTE((__data_raw_end - __data_start) | (__data_start << 32)); + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.note*) + *(.interp) + } +} diff --git a/boot/efi/x86_64/efi_arch.h b/boot/efi/x86_64/efi_arch.h new file mode 100644 index 0000000..14429c8 --- /dev/null +++ b/boot/efi/x86_64/efi_arch.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include + +#define EFIAPI __attribute__((ms_abi)) + +constexpr uint16_t EFI_ARCH_PE_MACHINE = 0x8664; diff --git a/boot/efi/x86_64/entry.S b/boot/efi/x86_64/entry.S new file mode 100644 index 0000000..0c7c1c8 --- /dev/null +++ b/boot/efi/x86_64/entry.S @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: ISC + + .intel_syntax noprefix + .text + + .globl _start + .type _start, @function +_start: + // Align the stack pointer + push rbp + mov rbp, rsp + push rdi + push rsi + push rbx + push r12 + and rsp, -16 + cld + + mov rbx, rcx + mov r12, rdx + + // Clear .bss + lea rdi, [rip + __bss_start] + lea rcx, [rip + __bss_end] + sub rcx, rdi + xor eax, eax + rep stosb + + // Self-relocate + lea rdi, [rip + __image_base] + lea rsi, [rip + _DYNAMIC] + call efistub_relocate + test rax, rax + jnz 1f + + mov rdi, rbx + mov rsi, r12 + call efi_main + +1: + lea rsp, [rbp - 32] + pop r12 + pop rbx + pop rsi + pop rdi + pop rbp + ret + .size _start, . - _start diff --git a/boot/efi/x86_64/meson.build b/boot/efi/x86_64/meson.build new file mode 100644 index 0000000..55a0c25 --- /dev/null +++ b/boot/efi/x86_64/meson.build @@ -0,0 +1 @@ +efistub_sources += files('entry.S') diff --git a/boot/kernel.ld b/boot/kernel.ld index 4657c57..7d08952 100644 --- a/boot/kernel.ld +++ b/boot/kernel.ld @@ -7,6 +7,7 @@ PHDRS { dynamic PT_DYNAMIC FLAGS(6); percpu PT_LOAD FLAGS(6); cmdline 0x60000005; + boot 0x60000008; } SECTIONS @@ -62,6 +63,10 @@ SECTIONS *(.data .data.*) } :data + .boot : { + KEEP(*(.boot.info)) + } :data :boot + .dynsym : { __ld_dynsym_start = .; KEEP(*(.dynsym*)) diff --git a/boot/limine/entry.c b/boot/limine/entry.c deleted file mode 100644 index 386d6ed..0000000 --- a/boot/limine/entry.c +++ /dev/null @@ -1,135 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "limine.h" - -[[__initdata_sorted("limine.0")]] -static volatile uint64_t limine_start[] = LIMINE_REQUESTS_START_MARKER; -[[__initdata_sorted("limine.1")]] -static volatile uint64_t limine_revision[] = LIMINE_BASE_REVISION(6); -[[__initdata_sorted("limine.2")]] -static volatile uint64_t limine_end[] = LIMINE_REQUESTS_END_MARKER; - -#define LIMINE_REQUEST(request, tag, rev) \ - [[__initdata_sorted("limine.1")]] \ - static volatile struct limine_##request request = { \ - .id = tag, \ - .revision = rev, \ - .response = nullptr, \ - } - -LIMINE_REQUEST(memmap_request, LIMINE_MEMMAP_REQUEST_ID, 0); -LIMINE_REQUEST(hhdm_request, LIMINE_HHDM_REQUEST_ID, 0); -LIMINE_REQUEST(executable_address_request, LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID, 0); -LIMINE_REQUEST(executable_file_request, LIMINE_EXECUTABLE_FILE_REQUEST_ID, 0); -LIMINE_REQUEST(framebuffer_request, LIMINE_FRAMEBUFFER_REQUEST_ID, 1); -LIMINE_REQUEST(module_request, LIMINE_MODULE_REQUEST_ID, 0); -LIMINE_REQUEST(dtb_request, LIMINE_DTB_REQUEST_ID, 0); -LIMINE_REQUEST(rsdp_request, LIMINE_RSDP_REQUEST_ID, 0); - -[[__initdata]] -static struct phys_mem mem[128]; -[[__initdata]] -static struct boot_file files[8]; - -[[__init]] -void kernel_entry() { - kernel_early_init(); - - struct boot_info info = {}; - - const struct limine_memmap_response* mm_res = memmap_request.response; - const struct limine_hhdm_response* hhdm_res = hhdm_request.response; - const struct limine_module_response* module_res = module_request.response; - const struct limine_executable_address_response* exec_res = executable_address_request.response; - const struct limine_executable_file_response* exec_file_res = executable_file_request.response; - const struct limine_rsdp_response* rsdp_res = rsdp_request.response; - - assert(mm_res, "memmap_request returned no response!"); - assert(hhdm_res, "hhdm_request returned no response!"); - - info.cmdline = exec_file_res->executable_file->string; - info.kernel_file.data = (phys_t)((uintptr_t)exec_file_res->executable_file->address - hhdm_res->offset); - info.kernel_file.length = exec_file_res->executable_file->size; - - info.num_mem_maps = MIN(ARRAY_SIZE(mem), mm_res->entry_count); - for (size_t i = 0; i < info.num_mem_maps; i++) { - mem[i].address = mm_res->entries[i]->base; - mem[i].length = mm_res->entries[i]->length; - - switch (mm_res->entries[i]->type) { - case LIMINE_MEMMAP_USABLE: - mem[i].usage = PHYS_USABLE; - break; - case LIMINE_MEMMAP_ACPI_RECLAIMABLE: - case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: - mem[i].usage = PHYS_RECLAIMABLE; - break; - case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: - mem[i].usage = PHYS_STATIC; - break; - default: - mem[i].usage = PHYS_RESERVED; - break; - } - } - - if (module_res) { - info.files = files; - info.num_files = MIN(ARRAY_SIZE(files), module_res->module_count); - for (size_t i = 0; i < info.num_files; i++) { - files[i].data = (phys_t)module_res->modules[i]->address - hhdm_res->offset; - files[i].length = module_res->modules[i]->size; - strncpy(files[i].path, module_res->modules[i]->path, sizeof(files[i].path) - 1); - files[i].path[sizeof(files[i].path) - 1] = 0; - } - } - - info.mem_map = mem; - info.phys_base = (phys_t)exec_res->physical_base; - info.virt_base = (uintptr_t)exec_res->virtual_base; - info.hhdm_base = (uintptr_t)hhdm_res->offset; - - const struct limine_dtb_response* dtb_res = dtb_request.response; - if (dtb_res && dtb_res->dtb_ptr) { - info.dtb = (phys_t)((uintptr_t)dtb_res->dtb_ptr - hhdm_res->offset); - info.dtb_size = fdt_read_u32((const uint8_t*)dtb_res->dtb_ptr + 4); - } - - const struct limine_framebuffer_response* fb_res = framebuffer_request.response; - if (fb_res) { - // Only use the first framebuffer. - if (fb_res->framebuffer_count >= 1) { - const struct limine_framebuffer* fb = framebuffer_request.response->framebuffers[0]; - info.fb.base = (phys_t)((uintptr_t)fb->address - hhdm_res->offset); - info.fb.width = fb->width; - info.fb.height = fb->height; - info.fb.pitch = fb->pitch; - info.fb.bytes_per_pixel = fb->bpp / 8; - - info.fb.red.shift = fb->red_mask_shift; - info.fb.red.size = fb->red_mask_size; - - info.fb.green.shift = fb->green_mask_shift; - info.fb.green.size = fb->green_mask_size; - - info.fb.blue.shift = fb->blue_mask_shift; - info.fb.blue.size = fb->blue_mask_size; - } - } - -#if CONFIG_ACPI - if (rsdp_request.response) { - info.rsdp = (phys_t)rsdp_request.response->address; - if (info.rsdp >= info.hhdm_base) - info.rsdp -= info.hhdm_base; - } -#endif - - kernel_main(&info); -} diff --git a/boot/limine/loader.ld b/boot/limine/loader.ld new file mode 100644 index 0000000..c898f5b --- /dev/null +++ b/boot/limine/loader.ld @@ -0,0 +1,46 @@ +ENTRY(loader_main) + +PHDRS +{ + text PT_LOAD FLAGS(5); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = 0xffffffffc0000000; + + .text : { + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + .rodata : { + *(.rodata .rodata.*) + . = ALIGN(16); + __kernel_start = .; + KEEP(*(.kernel)) + __kernel_end = .; + } :rodata + + . = ALIGN(0x1000); + .data : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.note*) + *(.interp) + } +} diff --git a/boot/limine/main.c b/boot/limine/main.c new file mode 100644 index 0000000..8cf0ac7 --- /dev/null +++ b/boot/limine/main.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include "limine.h" +#include "loader.h" + +[[__used, __section(".limine_requests_start")]] +static volatile uint64_t limine_start[] = LIMINE_REQUESTS_START_MARKER; +[[__used, __section(".limine_requests")]] +static volatile uint64_t limine_revision[] = LIMINE_BASE_REVISION(6); +[[__used, __section(".limine_requests_end")]] +static volatile uint64_t limine_end[] = LIMINE_REQUESTS_END_MARKER; + +#define LIMINE_REQUEST(request, tag, rev) \ + [[__used, __section(".limine_requests")]] \ + static volatile struct limine_##request request = { \ + .id = tag, \ + .revision = rev, \ + .response = nullptr, \ + } + +LIMINE_REQUEST(bootloader_info_request, LIMINE_BOOTLOADER_INFO_REQUEST_ID, 0); +LIMINE_REQUEST(memmap_request, LIMINE_MEMMAP_REQUEST_ID, 0); +LIMINE_REQUEST(hhdm_request, LIMINE_HHDM_REQUEST_ID, 0); +LIMINE_REQUEST(executable_address_request, LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID, 0); +LIMINE_REQUEST(executable_file_request, LIMINE_EXECUTABLE_FILE_REQUEST_ID, 0); +LIMINE_REQUEST(framebuffer_request, LIMINE_FRAMEBUFFER_REQUEST_ID, 1); +LIMINE_REQUEST(module_request, LIMINE_MODULE_REQUEST_ID, 0); +LIMINE_REQUEST(dtb_request, LIMINE_DTB_REQUEST_ID, 0); +LIMINE_REQUEST(rsdp_request, LIMINE_RSDP_REQUEST_ID, 0); + +static uint64_t hhdm_offset; +static const struct limine_memmap_entry* pool; +static uint64_t pool_static_end; +static uint64_t pool_reclaimable_start; + +void* loader_ptr(uint64_t phys) { + return (void*)(phys + hhdm_offset); +} + +static uint64_t to_phys(const void* ptr) { + const uint64_t addr = (uint64_t)ptr; + return addr >= hhdm_offset ? addr - hhdm_offset : addr; +} + +bool loader_alloc(enum phys_mem_usage usage, size_t size, uint64_t* out) { + size = ALIGN_UP(size, PAGE_SIZE); + if (pool_reclaimable_start - pool_static_end < size) + return false; + + if (usage == PHYS_STATIC) { + *out = pool_static_end; + pool_static_end += size; + } else { + pool_reclaimable_start -= size; + *out = pool_reclaimable_start; + } + memset(loader_ptr(*out), 0, size); + return true; +} + +static enum phys_mem_usage convert_mem_type(uint64_t type) { + switch (type) { + case LIMINE_MEMMAP_USABLE: + return PHYS_USABLE; + case LIMINE_MEMMAP_ACPI_RECLAIMABLE: + case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: + return PHYS_RECLAIMABLE; + case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: + return PHYS_STATIC; + default: + return PHYS_RESERVED; + } +} + +static bool init_pool(const struct limine_memmap_response* memmap) { + for (size_t i = 0; i < memmap->entry_count; i++) { + const struct limine_memmap_entry* entry = memmap->entries[i]; + if (entry->type == LIMINE_MEMMAP_USABLE && (!pool || entry->length > pool->length)) + pool = entry; + } + if (!pool) + return false; + + pool_static_end = pool->base; + pool_reclaimable_start = pool->base + pool->length; + return true; +} + +static uint64_t mem_map_top(const struct limine_memmap_response* memmap) { + uint64_t top = 0; + for (size_t i = 0; i < memmap->entry_count; i++) { + const struct limine_memmap_entry* entry = memmap->entries[i]; + if (convert_mem_type(entry->type) != PHYS_RESERVED) + top = MAX(top, entry->base + entry->length); + } + return top; +} + +static size_t convert_mem_map(const struct limine_memmap_response* memmap, struct phys_mem* out) { + size_t n = 0; + for (size_t i = 0; i < memmap->entry_count; i++) { + const struct limine_memmap_entry* entry = memmap->entries[i]; + if (entry != pool) { + out[n++] = (struct phys_mem){entry->base, entry->length, convert_mem_type(entry->type)}; + continue; + } + + const uint64_t end = entry->base + entry->length; + out[n++] = (struct phys_mem){entry->base, pool_static_end - entry->base, PHYS_STATIC}; + out[n++] = (struct phys_mem){pool_static_end, pool_reclaimable_start - pool_static_end, PHYS_USABLE}; + out[n++] = (struct phys_mem){pool_reclaimable_start, end - pool_reclaimable_start, PHYS_RECLAIMABLE}; + } + return n; +} + +static void find_framebuffer(struct boot_framebuffer* out) { + const struct limine_framebuffer_response* res = framebuffer_request.response; + if (!res || !res->framebuffer_count) + return; + + const struct limine_framebuffer* fb = res->framebuffers[0]; + out->base = to_phys(fb->address); + out->width = fb->width; + out->height = fb->height; + out->pitch = fb->pitch; + out->bytes_per_pixel = fb->bpp / 8; + out->red = (struct boot_color){fb->red_mask_shift, fb->red_mask_size}; + out->green = (struct boot_color){fb->green_mask_shift, fb->green_mask_size}; + out->blue = (struct boot_color){fb->blue_mask_shift, fb->blue_mask_size}; +} + +static bool load() { + const struct limine_memmap_response* memmap = memmap_request.response; + const struct limine_executable_address_response* exec = executable_address_request.response; + if (!LIMINE_BASE_REVISION_SUPPORTED(limine_revision) || !memmap || !hhdm_request.response || !exec) + return false; + hhdm_offset = hhdm_request.response->offset; + + if (!arch_check_cpu() || !init_pool(memmap)) + return false; + + const struct limine_bootloader_info_response* info = bootloader_info_request.response; + struct kernel_image kernel; + if (!loader_load_kernel(&kernel, info ? info->name : "Limine")) + return false; + + uint64_t mem; + if (!loader_alloc(PHYS_RECLAIMABLE, (memmap->entry_count + 2) * sizeof(struct phys_mem), &mem)) + return false; + + const struct limine_executable_file_response* file = executable_file_request.response; + if (file && file->executable_file->string) { + const char* cmdline = file->executable_file->string; + memcpy(kernel.info->cmdline, cmdline, MIN(strlen(cmdline), sizeof(kernel.info->cmdline) - 1)); + } + + const struct limine_module_response* modules = module_request.response; + for (size_t i = 0; modules && i < modules->module_count; i++) { + const struct limine_file* module = modules->modules[i]; + if (!loader_add_file(&kernel, module->path, to_phys(module->address), module->size)) + return false; + } + + find_framebuffer(&kernel.info->fb); + if (rsdp_request.response) + kernel.info->rsdp = to_phys(rsdp_request.response->address); + if (dtb_request.response && dtb_request.response->dtb_ptr) + kernel.info->dtb = to_phys(dtb_request.response->dtb_ptr); + + const uint64_t trampoline = (uint64_t)arch_enter_kernel; + uint64_t root; + if (!loader_page_tables( + &kernel, + mem_map_top(memmap), + exec->physical_base + (trampoline - exec->virtual_base), + &root + )) + return false; + + struct phys_mem* mem_map = loader_ptr(mem); + loader_set_mem_map(&kernel, mem_map, convert_mem_map(memmap, mem_map)); + arch_enter_kernel(root, kernel.entry); +} + +[[noreturn]] +void loader_main() { + load(); + for (;;) + asm volatile(""); +} diff --git a/boot/limine/meson.build b/boot/limine/meson.build index 527e9ba..006002d 100644 --- a/boot/limine/meson.build +++ b/boot/limine/meson.build @@ -1 +1,25 @@ -kernel_sources += files('entry.c') +limine_lds = meson.current_source_dir() / 'loader.ld' + +limine_c_args = [loader_c_args, '-fno-pic'] +if target_machine.cpu_family() == 'x86_64' + limine_c_args += '-mcmodel=kernel' +endif + +executable( + 'zinnia-limine', + sources: [loader_sources, files('main.c'), loader_kernel], + include_directories: [common_includes, loader_includes], + c_args: limine_c_args, + link_args: [ + '-nostdlib', + '-static', + '-Wl,-T,' + limine_lds, + '-Wl,-z,noexecstack', + '-Wl,-z,max-page-size=0x1000', + '-Wl,--build-id=none', + ], + link_depends: limine_lds, + pie: false, + install: true, + install_dir: zinnia_bootdir, +) diff --git a/boot/loader/elf.c b/boot/loader/elf.c new file mode 100644 index 0000000..3ab4f4a --- /dev/null +++ b/boot/loader/elf.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include "loader.h" + +static bool in_image(const struct kernel_image* image, uint64_t vaddr, uint64_t len) { + return vaddr >= image->virt && len <= image->size && vaddr - image->virt <= image->size - len; +} + +void* kernel_ptr(const struct kernel_image* image, uint64_t vaddr) { + return loader_ptr(image->phys + (vaddr - image->virt)); +} + +static bool apply_relocations(const struct kernel_image* image, uint64_t dynamic_vaddr) { + if (!in_image(image, dynamic_vaddr, sizeof(elf64_dyn_t))) + return false; + + uint64_t rela = 0; + uint64_t rela_size = 0; + uint64_t rela_ent = sizeof(elf64_rela_t); + for (const elf64_dyn_t* dyn = kernel_ptr(image, dynamic_vaddr); dyn->d_tag != DT_NULL; dyn++) { + switch (dyn->d_tag) { + case DT_RELA: + rela = dyn->d_un.d_val; + break; + case DT_RELASZ: + rela_size = dyn->d_un.d_val; + break; + case DT_RELAENT: + rela_ent = dyn->d_un.d_val; + break; + } + } + + if (!rela) + return true; + if (rela_ent < sizeof(elf64_rela_t) || !in_image(image, rela, rela_size)) + return false; + + for (uint64_t off = 0; off + rela_ent <= rela_size; off += rela_ent) { + const elf64_rela_t* r = kernel_ptr(image, rela + off); + const uint32_t type = ELF64_R_TYPE(r->r_info); + if (type == R_NONE) + continue; + if (type != R_RELATIVE) + return false; + if (!in_image(image, r->r_offset, sizeof(uint64_t))) + return false; + *(uint64_t*)kernel_ptr(image, r->r_offset) = (uint64_t)r->r_addend; + } + + return true; +} + +bool elf_load(const uint8_t* elf, size_t size, struct kernel_image* out) { + elf64_ehdr_t ehdr; + if (size < sizeof(ehdr)) + return false; + memcpy(&ehdr, elf, sizeof(ehdr)); + + if (memcmp(ehdr.e_ident, ELF_MAG, sizeof(ELF_MAG)) || ehdr.e_ident[EI_CLASS] != ELF_ARCH_CLASS || + ehdr.e_ident[EI_DATA] != ELF_ARCH_DATA) + return false; + if (ehdr.e_machine != ELF_ARCH_MACHINE) + return false; + if (ehdr.e_phentsize != sizeof(elf64_phdr_t) || ehdr.e_phoff > size || + (size - ehdr.e_phoff) / sizeof(elf64_phdr_t) < ehdr.e_phnum) + return false; + + const elf64_phdr_t* phdrs = (const elf64_phdr_t*)(elf + ehdr.e_phoff); + + uint64_t lo = UINT64_MAX; + uint64_t hi = 0; + uint64_t dynamic_vaddr = 0; + uint64_t info_vaddr = 0; + uint64_t info_size = 0; + for (size_t i = 0; i < ehdr.e_phnum; i++) { + const elf64_phdr_t* ph = &phdrs[i]; + if (ph->p_type == PT_DYNAMIC) + dynamic_vaddr = ph->p_vaddr; + if (ph->p_type == PT_BOOT_INFO) { + info_vaddr = ph->p_vaddr; + info_size = ph->p_memsz; + } + if (ph->p_type != PT_LOAD || ph->p_memsz == 0) + continue; + if (ph->p_filesz > ph->p_memsz || ph->p_offset > size || size - ph->p_offset < ph->p_filesz) + return false; + if (ALIGN_DOWN(ph->p_vaddr, PAGE_SIZE) < lo) + lo = ALIGN_DOWN(ph->p_vaddr, PAGE_SIZE); + if (ALIGN_UP(ph->p_vaddr + ph->p_memsz, PAGE_SIZE) > hi) + hi = ALIGN_UP(ph->p_vaddr + ph->p_memsz, PAGE_SIZE); + } + if (lo >= hi) + return false; + + out->virt = lo; + out->size = hi - lo; + out->entry = ehdr.e_entry; + if (!in_image(out, out->entry, 1)) + return false; + if (info_size != sizeof(struct boot_info) || !in_image(out, info_vaddr, info_size)) + return false; + + if (!loader_alloc(PHYS_STATIC, out->size, &out->phys)) + return false; + + for (size_t i = 0; i < ehdr.e_phnum; i++) { + const elf64_phdr_t* ph = &phdrs[i]; + if (ph->p_type != PT_LOAD || ph->p_memsz == 0) + continue; + memcpy(kernel_ptr(out, ph->p_vaddr), elf + ph->p_offset, ph->p_filesz); + } + out->info = kernel_ptr(out, info_vaddr); + + if (dynamic_vaddr) + return apply_relocations(out, dynamic_vaddr); + return true; +} diff --git a/boot/loader/lib.c b/boot/loader/lib.c new file mode 100644 index 0000000..3c79c06 --- /dev/null +++ b/boot/loader/lib.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include + +size_t strlen(const char* str) { + if (!str) + return 0; + + size_t len = 0; + while (str[len]) + len++; + return len; +} + +int memcmp(const void* s1, const void* s2, size_t size) { + const uint8_t* a = s1; + const uint8_t* b = s2; + for (size_t i = 0; i < size; i++) { + if (a[i] != b[i]) + return a[i] < b[i] ? -1 : 1; + } + return 0; +} + +void* memcpy(void* restrict dest, const void* restrict src, size_t n) { + uint8_t* d = dest; + const uint8_t* s = src; + for (size_t i = 0; i < n; i++) + d[i] = s[i]; + return dest; +} + +void* memmove(void* dstptr, const void* srcptr, size_t size) { + uint8_t* d = dstptr; + const uint8_t* s = srcptr; + if (d < s) { + for (size_t i = 0; i < size; i++) + d[i] = s[i]; + } else { + for (size_t i = size; i > 0; i--) + d[i - 1] = s[i - 1]; + } + return dstptr; +} + +void* memset(void* dest, int value, size_t n) { + uint8_t* d = dest; + for (size_t i = 0; i < n; i++) + d[i] = (uint8_t)value; + return dest; +} diff --git a/boot/loader/loader.c b/boot/loader/loader.c new file mode 100644 index 0000000..6a43d21 --- /dev/null +++ b/boot/loader/loader.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: ISC + +#include "loader.h" +#include +#include "loader_arch.h" + +extern const uint8_t __kernel_start[]; +extern const uint8_t __kernel_end[]; + +bool loader_load_kernel(struct kernel_image* out, const char* name) { + if (!elf_load(__kernel_start, __kernel_end - __kernel_start, out)) + return false; + + struct boot_info* info = out->info; + memcpy(info->loader, name, MIN(strlen(name), sizeof(info->loader) - 1)); + info->hhdm_base = ARCH_HHDM_BASE; + info->phys_base = out->phys; + info->virt_base = out->virt; + return true; +} + +bool loader_add_file(const struct kernel_image* kernel, const char* path, uint64_t base, uint64_t length) { + struct boot_info* info = kernel->info; + if (info->num_files == ARRAY_SIZE(info->files)) + return false; + + struct boot_file* file = &info->files[info->num_files++]; + file->data = base; + file->length = length; + const size_t path_len = MIN(strlen(path), sizeof(file->path) - 1); + memcpy(file->path, path, path_len); + file->path[path_len] = 0; + return true; +} + +void loader_set_mem_map(const struct kernel_image* kernel, struct phys_mem* map, size_t len) { + for (size_t i = 1; i < len; i++) { + const struct phys_mem entry = map[i]; + size_t j = i; + while (j > 0 && map[j - 1].address > entry.address) { + map[j] = map[j - 1]; + j--; + } + map[j] = entry; + } + + size_t merged = 0; + for (size_t i = 0; i < len; i++) { + if (!map[i].length) + continue; + struct phys_mem* prev = merged ? &map[merged - 1] : nullptr; + if (prev && prev->usage == map[i].usage && prev->address + prev->length == map[i].address) + prev->length += map[i].length; + else + map[merged++] = map[i]; + } + + struct boot_info* info = kernel->info; + info->num_mem_maps = MIN(merged, ARRAY_SIZE(info->mem_map)); + memcpy(info->mem_map, map, info->num_mem_maps * sizeof(*map)); +} + +bool loader_page_tables( + const struct kernel_image* kernel, + uint64_t mem_top, + uint64_t trampoline_phys, + uint64_t* out_root +) { + uint64_t root; + if (!loader_alloc(PHYS_RECLAIMABLE, PAGE_SIZE, &root)) + return false; + + const uint64_t trampoline_virt = ALIGN_DOWN((uint64_t)arch_enter_kernel, PAGE_SIZE); + if (!arch_paging_map(root, ARCH_HHDM_BASE, 0, ALIGN_UP(mem_top, ARCH_HUGE_PAGE_SIZE), ARCH_HUGE_PAGE_SIZE) || + !arch_paging_map(root, kernel->virt, kernel->phys, kernel->size, PAGE_SIZE) || + !arch_paging_map(root, trampoline_virt, ALIGN_DOWN(trampoline_phys, PAGE_SIZE), 2 * PAGE_SIZE, PAGE_SIZE)) + return false; + + *out_root = root; + return true; +} diff --git a/boot/loader/loader.h b/boot/loader/loader.h new file mode 100644 index 0000000..94b3cd0 --- /dev/null +++ b/boot/loader/loader.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include +#include + +constexpr uint64_t PAGE_SIZE = 0x1000; + +struct kernel_image { + uint64_t phys; + uint64_t virt; + uint64_t size; + uint64_t entry; + struct boot_info* info; +}; + +bool loader_alloc(enum phys_mem_usage usage, size_t size, uint64_t* out); +void* loader_ptr(uint64_t phys); + +bool elf_load(const uint8_t* elf, size_t size, struct kernel_image* out); +void* kernel_ptr(const struct kernel_image* image, uint64_t vaddr); + +bool loader_load_kernel(struct kernel_image* out, const char* name); +bool loader_add_file(const struct kernel_image* kernel, const char* path, uint64_t base, uint64_t length); +void loader_set_mem_map(const struct kernel_image* kernel, struct phys_mem* map, size_t len); + +bool loader_page_tables( + const struct kernel_image* kernel, + uint64_t mem_top, + uint64_t trampoline_phys, + uint64_t* out_root +); + +bool arch_check_cpu(); +bool arch_paging_map(uint64_t root, uint64_t virt, uint64_t phys, uint64_t size, uint64_t page_size); + +[[noreturn]] +void arch_enter_kernel(uint64_t root, uint64_t entry); diff --git a/boot/loader/x86_64/enter.S b/boot/loader/x86_64/enter.S new file mode 100644 index 0000000..729e1ac --- /dev/null +++ b/boot/loader/x86_64/enter.S @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC + + .intel_syntax noprefix + .text + + .globl arch_enter_kernel + .type arch_enter_kernel, @function + .balign 16 +arch_enter_kernel: + cli + mov cr3, rdi + jmp rsi + .size arch_enter_kernel, . - arch_enter_kernel diff --git a/boot/loader/x86_64/loader_arch.h b/boot/loader/x86_64/loader_arch.h new file mode 100644 index 0000000..fa056ff --- /dev/null +++ b/boot/loader/x86_64/loader_arch.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include + +constexpr uint64_t ARCH_HHDM_BASE = 0xffff'8000'0000'0000; +constexpr uint64_t ARCH_HUGE_PAGE_SIZE = 0x20'0000; diff --git a/boot/loader/x86_64/meson.build b/boot/loader/x86_64/meson.build new file mode 100644 index 0000000..403e245 --- /dev/null +++ b/boot/loader/x86_64/meson.build @@ -0,0 +1,11 @@ +loader_sources += files( + 'enter.S', + 'paging.c', +) + +loader_c_args += [ + '-mno-red-zone', + '-masm=intel', +] + +loader_objcopy_format = 'elf64-x86-64' diff --git a/boot/loader/x86_64/paging.c b/boot/loader/x86_64/paging.c new file mode 100644 index 0000000..d2c87fc --- /dev/null +++ b/boot/loader/x86_64/paging.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include "loader.h" +#include "loader_arch.h" + +static bool next_table(uint64_t* table, size_t index, uint64_t** out) { + if (!(table[index] & ARCH_FLAG_PRESENT)) { + uint64_t page; + if (!loader_alloc(PHYS_RECLAIMABLE, PAGE_SIZE, &page)) + return false; + table[index] = page | ARCH_FLAG_PRESENT | ARCH_FLAG_READ_WRITE; + } + *out = loader_ptr(table[index] & ARCH_PTE_ADDR_MASK); + return true; +} + +bool arch_check_cpu() { + uint64_t cr4; + asm volatile("mov %0, cr4" : "=r"(cr4)); + if (cr4 & CR4_LA57) + return false; + return true; +} + +bool arch_paging_map(uint64_t root, uint64_t virt, uint64_t phys, uint64_t size, uint64_t page_size) { + const int leaf_level = page_size == ARCH_HUGE_PAGE_SIZE ? 1 : 0; + + for (uint64_t off = 0; off < size; off += page_size) { + const uint64_t va = virt + off; + uint64_t* table = loader_ptr(root); + for (int level = 3; level > leaf_level; level--) { + if (!next_table(table, (va >> (12 + 9 * level)) & 0x1ff, &table)) + return false; + } + + uint64_t entry = (phys + off) | ARCH_FLAG_PRESENT | ARCH_FLAG_READ_WRITE; + if (leaf_level == 1) + entry |= ARCH_FLAG_SIZE; + table[(va >> (12 + 9 * leaf_level)) & 0x1ff] = entry; + } + + return true; +} diff --git a/boot/meson.build b/boot/meson.build index 5276801..c37fb77 100644 --- a/boot/meson.build +++ b/boot/meson.build @@ -1,6 +1,44 @@ -kernel_sources += files( - 'cmdline.c', - 'main.c', +objcopy = find_program('objcopy') +zinnia_bootdir = zinnia_datadir / 'boot' + +loader_sources = files( + 'loader/elf.c', + 'loader/lib.c', + 'loader/loader.c', ) -subdir('limine') +loader_c_args = [ + '-ffreestanding', + '-nostdinc', + '-fno-stack-protector', + '-fno-omit-frame-pointer', + '-mgeneral-regs-only', +] + +loader_includes = include_directories('loader', 'loader' / target_machine.cpu_family()) + +loader_objcopy_format = 'unknown' + +subdir('loader' / target_machine.cpu_family()) + +loader_kernel = custom_target( + 'loader-kernel.o', + input: kernel, + output: 'loader-kernel.o', + command: [ + objcopy, + '-I', 'binary', + '-O', loader_objcopy_format, + '--rename-section', '.data=.kernel,alloc,load,readonly,data,contents', + '@INPUT@', + '@OUTPUT@', + ], +) + +if kconfig.get('CONFIG_BOOT_EFISTUB', 'n') == 'y' + subdir('efi') +endif + +if kconfig.get('CONFIG_BOOT_LIMINE', 'n') == 'y' + subdir('limine') +endif diff --git a/include/zinnia/alloc.h b/include/zinnia/alloc.h index bef5c63..cd44aa6 100644 --- a/include/zinnia/alloc.h +++ b/include/zinnia/alloc.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include #include @@ -14,19 +15,6 @@ enum alloc_flags { ALLOC_MEM20 = 1 << 2, // Allocated memory needs to fit inside 20 bits. }; -enum phys_mem_usage { - PHYS_RESERVED, - PHYS_USABLE, - PHYS_STATIC, - PHYS_RECLAIMABLE, -}; - -struct phys_mem { - phys_t address; - size_t length; - enum phys_mem_usage usage; -}; - // Base address of the HHDM. extern uintptr_t mem_hhdm_base; diff --git a/include/zinnia/boot.h b/include/zinnia/boot.h new file mode 100644 index 0000000..fb8c4a4 --- /dev/null +++ b/include/zinnia/boot.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include + +enum phys_mem_usage { + PHYS_RESERVED, + PHYS_USABLE, + PHYS_STATIC, + PHYS_RECLAIMABLE, +}; + +struct phys_mem { + phys_t address; + size_t length; + enum phys_mem_usage usage; +}; + +constexpr size_t BOOT_PATH_MAX = 128; + +struct boot_file { + phys_t data; + size_t length; + char path[BOOT_PATH_MAX]; +}; + +struct boot_color { + uint8_t shift, size; +}; + +struct boot_framebuffer { + phys_t base; + size_t width, height, pitch; + uint8_t bytes_per_pixel; + struct boot_color red, green, blue; +}; + +constexpr size_t BOOT_LOADER_NAME_MAX = 64; +constexpr size_t BOOT_CMDLINE_MAX = 4096; +constexpr size_t BOOT_MEM_MAP_MAX = 128; +constexpr size_t BOOT_FILES_MAX = 8; + +struct boot_info { + char loader[BOOT_LOADER_NAME_MAX]; + char cmdline[BOOT_CMDLINE_MAX]; + struct phys_mem mem_map[BOOT_MEM_MAP_MAX]; + size_t num_mem_maps; + phys_t phys_base; + uintptr_t virt_base; + uintptr_t hhdm_base; + struct boot_file files[BOOT_FILES_MAX]; + size_t num_files; + struct boot_framebuffer fb; + phys_t dtb; + size_t dtb_size; + phys_t rsdp; +}; diff --git a/include/zinnia/elf.h b/include/zinnia/elf.h index 9eb5051..21a6fd5 100644 --- a/include/zinnia/elf.h +++ b/include/zinnia/elf.h @@ -2,8 +2,6 @@ #pragma once -#include -#include #include #include @@ -84,6 +82,7 @@ enum : uint32_t { PT_MOD_CMDLINE = 0x60000005, PT_MOD_EXIT = 0x60000006, PT_MOD_MATCH = 0x60000007, + PT_BOOT_INFO = 0x60000008, }; enum : uint16_t { diff --git a/include/zinnia/init.h b/include/zinnia/init.h index 89c06f2..3b97ff5 100644 --- a/include/zinnia/init.h +++ b/include/zinnia/init.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include #include @@ -13,40 +14,7 @@ #define __initdata __used, __section(".init.data") #define __initdata_sorted(name) __used, __section(".init.data." name) -struct boot_file { - phys_t data; - size_t length; - char path[128]; -}; - -struct boot_color { - uint8_t shift, size; -}; - -struct boot_framebuffer { - phys_t base; - size_t width, height, pitch; - uint8_t bytes_per_pixel; - struct boot_color red, green, blue; -}; - -struct boot_info { - char* cmdline; - struct phys_mem* mem_map; - size_t num_mem_maps; - phys_t phys_base; - uintptr_t virt_base; - uintptr_t hhdm_base; - struct boot_file* files; - size_t num_files; - struct boot_framebuffer fb; - struct boot_file kernel_file; - phys_t dtb; - size_t dtb_size; -#if CONFIG_ACPI - phys_t rsdp; -#endif -}; +extern struct boot_info boot_info; [[__visibility("hidden")]] extern uint8_t __ld_stack_top[]; diff --git a/boot/cmdline.c b/lib/cmdline.c similarity index 100% rename from boot/cmdline.c rename to lib/cmdline.c diff --git a/lib/entry.c b/lib/entry.c new file mode 100644 index 0000000..f3150ff --- /dev/null +++ b/lib/entry.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include + +[[__section(".boot.info")]] +struct boot_info boot_info; + +[[__init]] +void kernel_entry() { + kernel_early_init(); + + assert(boot_info.num_mem_maps, "Loader did not provide a memory map"); + + if (boot_info.dtb) + boot_info.dtb_size = fdt_read_u32((const uint8_t*)(boot_info.hhdm_base + boot_info.dtb) + 4); + + kernel_main(&boot_info); +} diff --git a/boot/main.c b/lib/main.c similarity index 98% rename from boot/main.c rename to lib/main.c index 8965175..e1f377b 100644 --- a/boot/main.c +++ b/lib/main.c @@ -75,6 +75,7 @@ void kernel_main(struct boot_info* info) { cmdline_parse(info->cmdline); log("%s\n", zinnia_banner); // Say hello! + log("Boot loader: %s\n", info->loader); log("Command line: %s\n", info->cmdline); initgraph_run(sched_stage, info); diff --git a/lib/meson.build b/lib/meson.build index 6bc9317..370118d 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,13 +1,16 @@ kernel_sources += files( 'clock.c', + 'cmdline.c', 'condvar.c', 'console.c', + 'entry.c', 'event.c', 'guid.c', 'initgraph.c', 'iovec.c', 'irq.c', 'ksyms.c', + 'main.c', 'module.c', 'mutex.c', 'panic.c', diff --git a/meson.build b/meson.build index bb01bcc..11f3207 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'zinnia', license: 'ISC', version: '0.1.0', - meson_version: '>=1.5.0', + meson_version: '>=1.6.0', default_options: [ 'c_std=gnu23', 'werror=true', @@ -95,7 +95,6 @@ modules = {} # These dirs go first so we can add arch-specific flags to drivers. subdir('arch' / target_machine.cpu_family()) -subdir('boot') subdir('lib') subdir('mem') subdir('sched') @@ -175,6 +174,8 @@ kernel = executable( install_dir: zinnia_datadir, ) +subdir('boot') + foreach name, v : modular_modules shared_library( name,