diff --git a/src/system/boot/loader/Jamfile b/src/system/boot/loader/Jamfile index 78a861182f..d566b68b9c 100644 --- a/src/system/boot/loader/Jamfile +++ b/src/system/boot/loader/Jamfile @@ -51,6 +51,10 @@ UsePrivateHeaders shared storage ; DEFINES += BOOT_SUPPORT_ELF32 ; + } else { + DEFINES += + _BOOT_PLATFORM_EFI + ; } } } diff --git a/src/system/boot/loader/elf.cpp b/src/system/boot/loader/elf.cpp index abb4f5e6ca..eba9c86b76 100644 --- a/src/system/boot/loader/elf.cpp +++ b/src/system/boot/loader/elf.cpp @@ -101,6 +101,15 @@ typedef ELFLoader ELF32Loader; #ifdef BOOT_SUPPORT_ELF64 + +#ifdef _BOOT_PLATFORM_EFI +extern "C" status_t +platform_bootloader_address_to_kernel_address(void *address, uint64_t *_result); +extern "C" status_t +platform_kernel_address_to_bootloader_address(uint64_t address, void **_result); +#endif + + struct ELF64Class { static const uint8 kIdentClass = ELFCLASS64; @@ -119,6 +128,17 @@ struct ELF64Class { AllocateRegion(AddrType* _address, AddrType size, uint8 protection, void **_mappedAddress) { +#ifdef _BOOT_PLATFORM_EFI + void* address = (void*)*_address; + + status_t status = platform_allocate_region(&address, size, protection, + false); + if (status != B_OK) + return status; + + *_mappedAddress = address; + platform_bootloader_address_to_kernel_address(address, _address); +#else // Assume the real 64-bit base address is KERNEL_LOAD_BASE_64_BIT and // the mappings in the loader address space are at KERNEL_LOAD_BASE. @@ -132,14 +152,23 @@ struct ELF64Class { *_mappedAddress = address; *_address = (AddrType)(addr_t)address + KERNEL_LOAD_BASE_64_BIT - KERNEL_LOAD_BASE; +#endif return B_OK; } static inline void* Map(AddrType address) { +#ifdef _BOOT_PLATFORM_EFI + void *result; + if (platform_kernel_address_to_bootloader_address(address, &result) != B_OK) { + panic("Couldn't convert address %#lx", address); + } + return result; +#else return (void*)(addr_t)(address - KERNEL_LOAD_BASE_64_BIT + KERNEL_LOAD_BASE); +#endif } }; @@ -154,7 +183,7 @@ ELFLoader::Create(int fd, preloaded_image** _image) ImageType* image = (ImageType*)kernel_args_malloc(sizeof(ImageType)); if (image == NULL) return B_NO_MEMORY; - + ssize_t length = read_pos(fd, 0, &image->elf_header, sizeof(EhdrType)); if (length < (ssize_t)sizeof(EhdrType)) { kernel_args_free(image); diff --git a/src/system/boot/platform/efi/Jamfile b/src/system/boot/platform/efi/Jamfile index f9a2bdfc3b..f9312a794f 100644 --- a/src/system/boot/platform/efi/Jamfile +++ b/src/system/boot/platform/efi/Jamfile @@ -8,7 +8,7 @@ UseBuildFeatureHeaders gnuefi : headersProtocol ; UseBuildFeatureHeaders gnuefi : headersArch ; { - local defines = _BOOT_MODE GNU_EFI_USE_MS_ABI _BOOT_PLATFORM=efi ; + local defines = _BOOT_MODE GNU_EFI_USE_MS_ABI _BOOT_PLATFORM_EFI ; defines = [ FDefines $(defines) ] ; SubDirCcFlags $(defines) ; SubDirC++Flags $(defines) -fno-rtti ; @@ -24,6 +24,7 @@ local platform_src = console.cpp video.cpp debug.cpp + entry.S mmu.cpp heap.cpp menu.cpp diff --git a/src/system/boot/platform/efi/entry.S b/src/system/boot/platform/efi/entry.S new file mode 100644 index 0000000000..b432dc982c --- /dev/null +++ b/src/system/boot/platform/efi/entry.S @@ -0,0 +1,66 @@ +/* + * Copyright 2012, Alex Smith, alex@alex-smith.me.uk. + * Copyright 2014, Henry Harrington, henry.harrington@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include + +#define __x86_64__ +#include + +#include "mmu.h" +#undef __x86_64__ + + +#define GDT_LIMIT 0x800 + + +.code64 + + +/*! void efi_enter_kernel(uint64 pml4, uint64 entry_point, uint64 stackTop); */ +FUNCTION(efi_enter_kernel): + // Point CR3 to the kernel's PML4. + movq %rdi, %cr3 + + // Load 64-bit enabled GDT + lgdtq long_gdtr(%rip) +/* + // Jump into the 64-bit code segment. + ljmp $KERNEL_CODE_SELECTOR, $.Llmode +.align 8 +.code64 +.Llmode: + // Set data segments. + mov $KERNEL_DATA_SELECTOR, %ax + mov %ax, %ss + xor %ax, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs +*/ + // Set the stack pointer. + movq %rdx, %rsp + + // Clear the stack frame/RFLAGS. + xorq %rbp, %rbp + push $2 + popf + + // Get arguments and call the kernel entry point. + mov %rsi, %rax // entry point + leaq gKernelArgs(%rip), %rdi + xorl %esi, %esi // current cpu + call *%rax + + +.data + + +long_gdtr: + .word BOOT_GDT_SEGMENT_COUNT * 8 - 1 +SYMBOL(gLongGDT): + .quad 0 diff --git a/src/system/boot/platform/efi/mmu.cpp b/src/system/boot/platform/efi/mmu.cpp index ae4f9b2133..4a3dc2ab3f 100644 --- a/src/system/boot/platform/efi/mmu.cpp +++ b/src/system/boot/platform/efi/mmu.cpp @@ -1,31 +1,324 @@ /* * Copyright 2016 Haiku, Inc. All rights reserved. + * Copyright 2014, Jessica Hamilton, jessica.l.hamilton@gmail.com. + * Copyright 2014, Henry Harrington, henry.harrington@gmail.com. * Distributed under the terms of the MIT License. */ +#include + #include #include +#include +#include #include "efi_platform.h" +#include "mmu.h" + + +struct allocated_memory_region { + allocated_memory_region *next; + uint64_t vaddr; + uint64_t paddr; + size_t size; + bool released; +}; + + +static uint64_t next_virtual_address = KERNEL_LOAD_BASE_64_BIT + 32 * 1024 * 1024; +static allocated_memory_region *allocated_memory_regions = NULL; + + +static uint64_t mmu_allocate_page() +{ + EFI_PHYSICAL_ADDRESS addr; + EFI_STATUS s = kBootServices->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, &addr); + if (s != EFI_SUCCESS) + panic("Unabled to allocate memory: %li", s); + + return addr; +} + + +uint64_t +mmu_generate_post_efi_page_tables(UINTN memory_map_size, + EFI_MEMORY_DESCRIPTOR *memory_map, UINTN descriptor_size, + UINTN descriptor_version) +{ + // Generate page tables, matching bios_ia32/long.cpp. + uint64_t *pml4; + uint64_t *pdpt; + uint64_t *pageDir; + uint64_t *pageTable; + + // Allocate the top level PML4. + pml4 = NULL; + if (platform_allocate_region((void**)&pml4, B_PAGE_SIZE, 0, false) != B_OK) + panic("Failed to allocate PML4."); + gKernelArgs.arch_args.phys_pgdir = (uint32_t)(addr_t)pml4; + memset(pml4, 0, B_PAGE_SIZE); + platform_bootloader_address_to_kernel_address(pml4, &gKernelArgs.arch_args.vir_pgdir); + + // Store the virtual memory usage information. + gKernelArgs.virtual_allocated_range[0].start = KERNEL_LOAD_BASE_64_BIT; + gKernelArgs.virtual_allocated_range[0].size = next_virtual_address - KERNEL_LOAD_BASE_64_BIT; + gKernelArgs.num_virtual_allocated_ranges = 1; + gKernelArgs.arch_args.virtual_end = ROUNDUP(KERNEL_LOAD_BASE_64_BIT + + gKernelArgs.virtual_allocated_range[0].size, 0x200000); + + // Find the highest physical memory address. We map all physical memory + // into the kernel address space, so we want to make sure we map everything + // we have available. + uint64 maxAddress = 0; + for (UINTN i = 0; i < memory_map_size / descriptor_size; ++i) { + EFI_MEMORY_DESCRIPTOR *entry = (EFI_MEMORY_DESCRIPTOR *)((addr_t)memory_map + i * descriptor_size); + maxAddress = std::max(maxAddress, + entry->PhysicalStart + entry->NumberOfPages * 4096); + } + + // Want to map at least 4GB, there may be stuff other than usable RAM that + // could be in the first 4GB of physical address space. + maxAddress = std::max(maxAddress, (uint64)0x100000000ll); + maxAddress = ROUNDUP(maxAddress, 0x40000000); + + // Currently only use 1 PDPT (512GB). This will need to change if someone + // wants to use Haiku on a box with more than 512GB of RAM but that's + // probably not going to happen any time soon. + if (maxAddress / 0x40000000 > 512) + panic("Can't currently support more than 512GB of RAM!"); + + // Create page tables for the physical map area. Also map this PDPT + // temporarily at the bottom of the address space so that we are identity + // mapped. + + pdpt = (uint64*)mmu_allocate_page(); + memset(pdpt, 0, B_PAGE_SIZE); + pml4[510] = (addr_t)pdpt | kTableMappingFlags; + pml4[0] = (addr_t)pdpt | kTableMappingFlags; + + for (uint64 i = 0; i < maxAddress; i += 0x40000000) { + pageDir = (uint64*)mmu_allocate_page(); + memset(pageDir, 0, B_PAGE_SIZE); + pdpt[i / 0x40000000] = (addr_t)pageDir | kTableMappingFlags; + + for (uint64 j = 0; j < 0x40000000; j += 0x200000) { + pageDir[j / 0x200000] = (i + j) | kLargePageMappingFlags; + } + } + + // Allocate tables for the kernel mappings. + + pdpt = (uint64*)mmu_allocate_page(); + memset(pdpt, 0, B_PAGE_SIZE); + pml4[511] = (addr_t)pdpt | kTableMappingFlags; + + pageDir = (uint64*)mmu_allocate_page(); + memset(pageDir, 0, B_PAGE_SIZE); + pdpt[510] = (addr_t)pageDir | kTableMappingFlags; + + // We can now allocate page tables and duplicate the mappings across from + // the 32-bit address space to them. + pageTable = NULL; // shush, compiler. + for (uint32 i = 0; i < gKernelArgs.virtual_allocated_range[0].size + / B_PAGE_SIZE; i++) { + if ((i % 512) == 0) { + pageTable = (uint64*)mmu_allocate_page(); + memset(pageTable, 0, B_PAGE_SIZE); + pageDir[i / 512] = (addr_t)pageTable | kTableMappingFlags; + } + + // Get the physical address to map. + void *phys; + if (platform_kernel_address_to_bootloader_address(KERNEL_LOAD_BASE_64_BIT + (i * B_PAGE_SIZE), + &phys) != B_OK) + continue; + + pageTable[i % 512] = (addr_t)phys | kPageMappingFlags; + } + + return (uint64)pml4; +} + + +// Called after EFI boot services exit. +// Currently assumes that the memory map is sane... Sorted and no overlapping +// regions. +void +mmu_post_efi_setup(UINTN memory_map_size, EFI_MEMORY_DESCRIPTOR *memory_map, UINTN descriptor_size, UINTN descriptor_version) +{ + // Add physical memory to the kernel args and update virtual addresses for EFI regions.. + addr_t addr = (addr_t)memory_map; + gKernelArgs.num_physical_memory_ranges = 0; + for (UINTN i = 0; i < memory_map_size / descriptor_size; ++i) { + EFI_MEMORY_DESCRIPTOR *entry = (EFI_MEMORY_DESCRIPTOR *)(addr + i * descriptor_size); + switch (entry->Type) { + case EfiLoaderCode: + case EfiLoaderData: + case EfiBootServicesCode: + case EfiBootServicesData: + case EfiConventionalMemory: { + // Usable memory. + // Ignore memory below 1MB and above 512GB. + uint64_t base = entry->PhysicalStart; + uint64_t end = entry->PhysicalStart + entry->NumberOfPages * 4096; + if (base < 0x100000) + base = 0x100000; + if (end > (512ull * 1024 * 1024 * 1024)) + end = 512ull * 1024 * 1024 * 1024; + if (base >= end) + break; + uint64_t size = end - base; + + insert_physical_memory_range(base, size); + // LoaderData memory is bootloader allocated memory, possibly + // containing the kernel or loaded drivers. + if (entry->Type == EfiLoaderData) + insert_physical_allocated_range(base, size); + break; + } + case EfiACPIReclaimMemory: + // ACPI reclaim -- physical memory we could actually use later + gKernelArgs.ignored_physical_memory += entry->NumberOfPages * 4096; + break; + case EfiRuntimeServicesCode: + case EfiRuntimeServicesData: + entry->VirtualStart = entry->PhysicalStart + 0xFFFFFF0000000000ull; + break; + } + } + + // Sort the address ranges. + sort_address_ranges(gKernelArgs.physical_memory_range, + gKernelArgs.num_physical_memory_ranges); + sort_address_ranges(gKernelArgs.physical_allocated_range, + gKernelArgs.num_physical_allocated_ranges); + sort_address_ranges(gKernelArgs.virtual_allocated_range, + gKernelArgs.num_virtual_allocated_ranges); + + // Switch EFI to virtual mode, using the kernel pmap. + // Something involving ConvertPointer might need to be done after this? + // http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#SetVirtualAddressMap.28.29 + kRuntimeServices->SetVirtualAddressMap(memory_map_size, descriptor_size, descriptor_version, memory_map); +} + + +// Platform allocator. +// The bootloader assumes that bootloader address space == kernel address space. +// This is not true until just before the kernel is booted, so an ugly hack is +// used to cover the difference. platform_allocate_region allocates addresses +// in bootloader space, but can convert them to kernel space. The ELF loader +// accesses kernel memory via Mao(), and much later in the boot process, +// addresses in the kernel argument struct are converted from bootloader +// addresses to kernel addresses. + +extern "C" status_t +platform_allocate_region(void **_address, size_t size, uint8 /* protection */, bool exactAddress) +{ + // We don't have any control over the page tables, give up right away if an + // exactAddress is wanted. + if (exactAddress) + return B_NO_MEMORY; + + EFI_PHYSICAL_ADDRESS addr; + size_t aligned_size = ROUNDUP(size, B_PAGE_SIZE); + allocated_memory_region *region = new(std::nothrow) allocated_memory_region; + + if (region == NULL) + return B_NO_MEMORY; + + EFI_STATUS status = kBootServices->AllocatePages(AllocateAnyPages, + EfiLoaderData, aligned_size / B_PAGE_SIZE, &addr); + if (status != EFI_SUCCESS) { + delete region; + return B_NO_MEMORY; + } + + // Addresses above 512GB not supported. + // Memory map regions above 512GB can be ignored, but if EFI returns pages + // above that there's nothing that can be done to fix it. + if (addr + size > (512ull * 1024 * 1024 * 1024)) + panic("Can't currently support more than 512GB of RAM!"); + + region->next = allocated_memory_regions; + allocated_memory_regions = region; + region->vaddr = 0; + region->paddr = addr; + region->size = size; + region->released = false; + + if (*_address != NULL) { + region->vaddr = (uint64_t)*_address; + } + + //dprintf("Allocated region %#lx (requested %p) %#lx %lu\n", region->vaddr, *_address, region->paddr, region->size); + + *_address = (void *)region->paddr; + + return B_OK; +} + + +static allocated_memory_region * +get_region(void *address, size_t size) +{ + for (allocated_memory_region *region = allocated_memory_regions; region; region = region->next) { + if (region->paddr == (uint64_t)address && region->size == size) { + return region; + } + } + return 0; +} extern "C" status_t -platform_allocate_region(void **_virtualAddress, size_t size, uint8 protection, - bool exactAddress) +platform_bootloader_address_to_kernel_address(void *address, uint64_t *_result) { - if (kBootServices->AllocatePool(EfiLoaderData, size, _virtualAddress) != EFI_SUCCESS) - return B_NO_MEMORY; + uint64_t addr = (uint64_t)address; - return B_OK; + for (allocated_memory_region *region = allocated_memory_regions; region; region = region->next) { + if (region->paddr <= addr && addr < region->paddr + region->size) { + // Lazily allocate virtual memory. + if (region->vaddr == 0) { + region->vaddr = next_virtual_address; + next_virtual_address += ROUNDUP(region->size, B_PAGE_SIZE); + } + *_result = region->vaddr + (addr - region->paddr); + //dprintf("Converted bootloader address %p in region %#lx-%#lx to %#lx\n", + // address, region->paddr, region->paddr + region->size, *_result); + return B_OK; + } + } + + return B_ERROR; +} + + +extern "C" status_t +platform_kernel_address_to_bootloader_address(uint64_t address, void **_result) +{ + for (allocated_memory_region *region = allocated_memory_regions; region; region = region->next) { + if (region->vaddr != 0 && region->vaddr <= address && address < region->vaddr + region->size) { + *_result = (void *)(region->paddr + (address - region->vaddr)); + //dprintf("Converted kernel address %#lx in region %#lx-%#lx to %p\n", + // address, region->vaddr, region->vaddr + region->size, *_result); + return B_OK; + } + } + + return B_ERROR; } extern "C" status_t platform_free_region(void *address, size_t size) { - if (kBootServices->FreePool(address) != EFI_SUCCESS) - return B_ERROR; + //dprintf("Release region %p %lu\n", address, size); + allocated_memory_region *region = get_region(address, size); + if (!region) + panic("Unknown region??"); + + kBootServices->FreePages((EFI_PHYSICAL_ADDRESS)address, ROUNDUP(size, B_PAGE_SIZE) / B_PAGE_SIZE); return B_OK; } diff --git a/src/system/boot/platform/efi/mmu.h b/src/system/boot/platform/efi/mmu.h new file mode 100644 index 0000000000..068b5bf995 --- /dev/null +++ b/src/system/boot/platform/efi/mmu.h @@ -0,0 +1,70 @@ +/* + * Copyright 2014, Henry Harrington, henry.harrington@gmail.com. + * Distributed under the terms of the MIT License. + */ + +#ifndef MMU_H +#define MMU_H + +#include + +#undef BOOT_GDT_SEGMENT_COUNT +#define BOOT_GDT_SEGMENT_COUNT (USER_DATA_SEGMENT + 1) + +#ifndef _ASSEMBLER + +#include "efi_platform.h" + +#include + + +extern segment_descriptor gBootGDT[BOOT_GDT_SEGMENT_COUNT]; + +static const uint32 kDefaultPageFlags = 0x3; // present, R/W +static const uint64 kTableMappingFlags = 0x7; // present, R/W, user +static const uint64 kLargePageMappingFlags = 0x183; // present, R/W, user, global, large +static const uint64 kPageMappingFlags = 0x103; // present, R/W, user, global + + +#ifdef __cplusplus +extern "C" { +#endif + +extern void +mmu_post_efi_setup(UINTN memory_map_size, EFI_MEMORY_DESCRIPTOR *memory_map, UINTN descriptor_size, UINTN descriptor_version); +extern uint64_t +mmu_generate_post_efi_page_tables(UINTN memory_map_size, EFI_MEMORY_DESCRIPTOR *memory_map, UINTN descriptor_size, UINTN descriptor_version); +extern status_t +platform_kernel_address_to_bootloader_address(uint64_t address, void **_result); +extern status_t +platform_bootloader_address_to_kernel_address(void *address, uint64_t *_result); + +#ifdef __cplusplus +} +#endif + + +/*! Convert a 32-bit address to a 64-bit address. */ +inline uint64 +fix_address(uint64 address) +{ + uint64 result; + if (platform_bootloader_address_to_kernel_address((void *)address, &result) != B_OK) + return address; + else + return result; +} + + +template +inline void +fix_address(FixedWidthPointer& p) +{ + if (p != NULL) + p.SetTo(fix_address(p.Get())); +} + + +#endif // !_ASSEMBLER + +#endif /* MMU_H */ diff --git a/src/system/boot/platform/efi/start.cpp b/src/system/boot/platform/efi/start.cpp index c782989446..6ff215fb9b 100644 --- a/src/system/boot/platform/efi/start.cpp +++ b/src/system/boot/platform/efi/start.cpp @@ -1,16 +1,26 @@ /* * Copyright 2014-2016 Haiku, Inc. All rights reserved. - * Copyright 2013 Fredrik Holmqvist, fredrik.holmqvist@gmail.com. All rights reserved. - * Distributed under the terms of the MIT License. + * Copyright 2013-2014, Fredrik Holmqvist, fredrik.holmqvist@gmail.com. + * Copyright 2014, Henry Harrington, henry.harrington@gmail.com. + * All rights reserved. + * Distributed under the terms of the Haiku License. */ +#include + +#include + +#include +#include #include #include #include +#include #include "console.h" #include "efi_platform.h" +#include "mmu.h" extern void (*__ctor_list)(void); @@ -20,8 +30,18 @@ extern void (*__ctor_end)(void); const EFI_SYSTEM_TABLE *kSystemTable; const EFI_BOOT_SERVICES *kBootServices; const EFI_RUNTIME_SERVICES *kRuntimeServices; +EFI_HANDLE kImage; + static uint32 sBootOptions; +static uint64 gLongKernelEntry; +extern uint64 gLongGDT; +segment_descriptor gBootGDT[BOOT_GDT_SEGMENT_COUNT]; + + +extern "C" int main(stage2_args *args); +extern "C" void _start(void); +extern "C" void efi_enter_kernel(uint64 pml4, uint64 entry_point, uint64 stack); static void @@ -41,10 +61,188 @@ platform_boot_options() } +static void +long_gdt_init() +{ + clear_segment_descriptor(&gBootGDT[0]); + + // Set up code/data segments (TSS segments set up later in the kernel). + set_segment_descriptor(&gBootGDT[KERNEL_CODE_SEGMENT], DT_CODE_EXECUTE_ONLY, + DPL_KERNEL); + set_segment_descriptor(&gBootGDT[KERNEL_DATA_SEGMENT], DT_DATA_WRITEABLE, + DPL_KERNEL); + set_segment_descriptor(&gBootGDT[USER_CODE_SEGMENT], DT_CODE_EXECUTE_ONLY, + DPL_USER); + set_segment_descriptor(&gBootGDT[USER_DATA_SEGMENT], DT_DATA_WRITEABLE, + DPL_USER); + + // Used by long_enter_kernel(). + gLongGDT = fix_address((addr_t)gBootGDT); + dprintf("GDT at 0x%lx\n", gLongGDT); +} + + +static void +convert_preloaded_image(preloaded_elf64_image* image) +{ + fix_address(image->next); + fix_address(image->name); + fix_address(image->debug_string_table); + fix_address(image->syms); + fix_address(image->rel); + fix_address(image->rela); + fix_address(image->pltrel); + fix_address(image->debug_symbols); +} + + +/*! Convert all addresses in kernel_args to 64-bit addresses. */ +static void +convert_kernel_args() +{ + fix_address(gKernelArgs.boot_volume); + fix_address(gKernelArgs.vesa_modes); + fix_address(gKernelArgs.edid_info); + fix_address(gKernelArgs.debug_output); + fix_address(gKernelArgs.boot_splash); + fix_address(gKernelArgs.arch_args.apic); + fix_address(gKernelArgs.arch_args.hpet); + + convert_preloaded_image(static_cast( + gKernelArgs.kernel_image.Pointer())); + fix_address(gKernelArgs.kernel_image); + + // Iterate over the preloaded images. Must save the next address before + // converting, as the next pointer will be converted. + preloaded_image* image = gKernelArgs.preloaded_images; + fix_address(gKernelArgs.preloaded_images); + while (image != NULL) { + preloaded_image* next = image->next; + convert_preloaded_image(static_cast(image)); + image = next; + } + + // Set correct kernel args range addresses. + dprintf("kernel args ranges:\n"); + for (uint32 i = 0; i < gKernelArgs.num_kernel_args_ranges; i++) { + gKernelArgs.kernel_args_range[i].start = fix_address( + gKernelArgs.kernel_args_range[i].start); + dprintf(" base %#018" B_PRIx64 ", length %#018" B_PRIx64 "\n", + gKernelArgs.kernel_args_range[i].start, + gKernelArgs.kernel_args_range[i].size); + } + + // Fix driver settings files. + driver_settings_file* file = gKernelArgs.driver_settings; + fix_address(gKernelArgs.driver_settings); + while (file != NULL) { + driver_settings_file* next = file->next; + fix_address(file->next); + fix_address(file->buffer); + file = next; + } +} + + extern "C" void platform_start_kernel(void) { - panic("platform_start_kernel not implemented"); + if (gKernelArgs.kernel_image->elf_class != ELFCLASS64) + panic("32-bit kernels not supported with EFI"); + + preloaded_elf64_image *image = static_cast( + gKernelArgs.kernel_image.Pointer()); + + long_gdt_init(); + convert_kernel_args(); + + // Save the kernel entry point address. + gLongKernelEntry = image->elf_header.e_entry; + dprintf("kernel entry at %#lx\n", gLongKernelEntry); + + // map in a kernel stack + void *stack_address = NULL; + if (platform_allocate_region(&stack_address, KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE, 0, false) != B_OK) { + panic("Unabled to allocate a stack"); + } + gKernelArgs.cpu_kstack[0].start = fix_address((uint64_t)stack_address); + gKernelArgs.cpu_kstack[0].size = KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE; + dprintf("Kernel stack at %#lx\n", gKernelArgs.cpu_kstack[0].start); + + // Prepare to exit EFI boot services. + // Read the memory map. + // First call is to determine the buffer size. + UINTN memory_map_size = 0; + EFI_MEMORY_DESCRIPTOR dummy; + EFI_MEMORY_DESCRIPTOR *memory_map; + UINTN map_key; + UINTN descriptor_size; + UINT32 descriptor_version; + if (kBootServices->GetMemoryMap(&memory_map_size, &dummy, &map_key, &descriptor_size, &descriptor_version) != EFI_BUFFER_TOO_SMALL) { + panic("Unable to determine size of system memory map"); + } + + // Allocate a buffer twice as large as needed just in case it gets bigger between + // calls to ExitBootServices. + UINTN actual_memory_map_size = memory_map_size * 2; + memory_map = (EFI_MEMORY_DESCRIPTOR *)kernel_args_malloc(actual_memory_map_size); + if (memory_map == NULL) + panic("Unable to allocate memory map."); + + // Read (and print) the memory map. + memory_map_size = actual_memory_map_size; + if (kBootServices->GetMemoryMap(&memory_map_size, memory_map, &map_key, &descriptor_size, &descriptor_version) != EFI_SUCCESS) { + panic("Unable to fetch system memory map."); + } + + addr_t addr = (addr_t)memory_map; + dprintf("System provided memory map:\n"); + for (UINTN i = 0; i < memory_map_size / descriptor_size; ++i) { + EFI_MEMORY_DESCRIPTOR *entry = (EFI_MEMORY_DESCRIPTOR *)(addr + i * descriptor_size); + dprintf(" %#lx-%#lx %#lx %#x %#lx\n", + entry->PhysicalStart, entry->PhysicalStart + entry->NumberOfPages * 4096, + entry->VirtualStart, entry->Type, entry->Attribute); + } + + // Generate page tables for use after ExitBootServices. + uint64_t final_pml4 = mmu_generate_post_efi_page_tables(memory_map_size, memory_map, descriptor_size, descriptor_version); + dprintf("Final PML4 at %#lx\n", final_pml4); + + // Attempt to fetch the memory map and exit boot services. + // This needs to be done in a loop, as ExitBootServices can change the + // memory map. + // Even better: Only GetMemoryMap and ExitBootServices can be called after + // the first call to ExitBootServices, as the firmware is permitted to + // partially exit. This is why twice as much space was allocated for the + // memory map, as it's impossible to allocate more now. + // A changing memory map shouldn't affect the generated page tables, as + // they only needed to know about the maximum address, not any specific + // entry. + dprintf("Calling ExitBootServices. So long, EFI!\n"); + while (true) { + if (kBootServices->ExitBootServices(kImage, map_key) == EFI_SUCCESS) { + break; + } + + memory_map_size = actual_memory_map_size; + if (kBootServices->GetMemoryMap(&memory_map_size, memory_map, &map_key, &descriptor_size, &descriptor_version) != EFI_SUCCESS) { + panic("Unable to fetch system memory map."); + } + } + // We're on our own now... + + // The console was provided by boot services, disable it. + stdout = NULL; + + // Update EFI, generate final kernel physical memory map, etc. + mmu_post_efi_setup(memory_map_size, memory_map, descriptor_size, descriptor_version); + + // Enter the kernel! + efi_enter_kernel(final_pml4, + gLongKernelEntry, + gKernelArgs.cpu_kstack[0].start + gKernelArgs.cpu_kstack[0].size); + + panic("Shouldn't get here"); } @@ -65,6 +263,9 @@ efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systemTable) { stage2_args args; + memset(&args, 0, sizeof(stage2_args)); + + kImage = image; kSystemTable = systemTable; kBootServices = systemTable->BootServices; kRuntimeServices = systemTable->RuntimeServices; @@ -77,6 +278,12 @@ efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systemTable) sBootOptions = console_check_boot_keys(); + // disable apm in case we ever load a 32-bit kernel... + gKernelArgs.platform_args.apm.version = 0; + gKernelArgs.num_cpus = 1; + gKernelArgs.arch_args.hpet_phys = 0; + gKernelArgs.arch_args.hpet = NULL; + main(&args); return EFI_SUCCESS;