/* * An EFI loader for Barrelfish * * This is an EFI app which loads the Multiboot2 image and execute * the bootloader. * This object file is linked together with the Multiboot2 image into one object * * Copyright (c) 2018, ETH Zurich. * All rights reserved. * * This file is distributed under the terms in the attached LICENSE file. * If you do not find this file, copies can be found by writing to: * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group. */ #include #include #include #include #include #include #include #include "blob.h" #include "vm.h" typedef enum { EfiBarrelfishFirstMemType= 0x80000000, EfiBarrelfishCPUDriver= 0x80000000, EfiBarrelfishCPUDriverStack= 0x80000001, EfiBarrelfishMultibootData= 0x80000002, EfiBarrelfishELFData= 0x80000003, EfiBarrelfishBootPageTable= 0x80000004, EfiBarrelfishCoreData= 0x80000005, EfiBarrelfishMaxMemType } EFI_BARRELFISH_MEMORY_TYPE; static const char *mmap_types[] = { "reserved", "LD code", "LD data", "BS code", "BS data", "RS code", "RS data", "available", "unusable", "ACPI reclaim", "ACPI NVS", "MMIO", "ports", "PAL code", "persist" }; static const char *bf_mmap_types[] = { "BF code", "BF stack", "BF multiboot", "BF module", "BF page table", "BF core data", }; #define MAX_L1_TABLES 512 struct page_tables { size_t nL1; union aarch64_descriptor *L0_table; union aarch64_descriptor *L1_tables[MAX_L1_TABLES]; } page_tables; struct config { struct multiboot_info *multiboot; struct multiboot_tag_efi_mmap *mmap_tag; struct multiboot_tag_string *cmd_tag; EFI_PHYSICAL_ADDRESS modules; EFI_VIRTUAL_ADDRESS boot_driver_entry; EFI_PHYSICAL_ADDRESS cpu_driver_entry; EFI_PHYSICAL_ADDRESS cpu_driver_stack; size_t cpu_driver_stack_size; struct page_tables *tables; }; typedef void boot_driver(uint32_t magic, void *pointer); extern char barrelfish_blob_start[1]; #define KERNEL_OFFSET 0xffff000000000000 #define KERNEL_STACK_SIZE 0x4000 #define ATTR_CACHED 0 #define ATTR_DEVICE 1 #define ROUND_UP(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) #define COVER(x, y) (((x) + ((y)-1)) / (y)) #define ROUND_DOWN(x, y) (((x) / (y)) * (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define BLOB_ADDRESS(offset) (barrelfish_blob_start + (offset)) /* Copy a base+length string into a null-terminated string. Destination * buffer must be large enough to hold the terminator i.e. n+1 characters. */ static inline void ntstring(char *dest, const char *src, size_t len) { memcpy(dest, src, len); dest[len]= '\0'; } #define MEM_MAP_SIZE 8192 char mmap[MEM_MAP_SIZE]; UINTN mmap_size, mmap_key, mmap_d_size; UINT32 mmap_d_ver; static EFI_STATUS update_memory_map(void) { EFI_STATUS status; size_t mmap_n_desc, i; /* Grab the current table from UEFI. */ mmap_size = MEM_MAP_SIZE; status = ST->BootServices->GetMemoryMap( &mmap_size, (void *) &mmap, &mmap_key, &mmap_d_size, &mmap_d_ver ); if (status == EFI_BUFFER_TOO_SMALL) { Print(L"The memory map is %dB, but MEM_MAP_SIZE is %d.\n", mmap_size, MEM_MAP_SIZE); Print(L"This is compile-time limit in Hagfish - please report " L"this overflow, it's a bug.\n"); return status; } else if (EFI_ERROR(status)) { Print(L"Unable to get memory map: %x\n", status); return status; } mmap_n_desc = mmap_size / mmap_d_size; return EFI_SUCCESS; } static EFI_STATUS relocate_memory_map(void) { if (!mmap_size) { return EFI_LOAD_ERROR; } size_t mmap_n_desc = mmap_size / mmap_d_size; for (size_t i= 0; i < mmap_n_desc; i++) { EFI_MEMORY_DESCRIPTOR *desc = (EFI_MEMORY_DESCRIPTOR *)(mmap + i * mmap_d_size); // 1:1 mapping into kernel window desc->VirtualStart = desc->PhysicalStart + KERNEL_OFFSET; } return EFI_SUCCESS; } static void print_memory_map(int update) { if (update) { update_memory_map(); } size_t mmap_n_desc = mmap_size / mmap_d_size; Print(L"Memory map at %lx, key: %x, descriptor version: %x\n", mmap, mmap_key, mmap_d_ver); Print(L"Got %d memory map entries of %dB (%dB).\n", mmap_n_desc, mmap_d_size, mmap_size); Print(L"Type PStart PEnd " " Size Attributes\n"); for (UINTN i = 0; i < mmap_n_desc; i++) { EFI_MEMORY_DESCRIPTOR *desc = ((void *) mmap) + (mmap_d_size * i); const char *description; if (desc->Type < EfiMaxMemoryType) { description= mmap_types[desc->Type]; } else if ( EfiBarrelfishFirstMemType <= desc->Type && desc->Type < EfiBarrelfishMaxMemType ) { description = bf_mmap_types[desc->Type - EfiBarrelfishFirstMemType]; } else { description= "???"; } Print(L"%-13a %016lx %016lx %9ldkB %01x\n", description, desc->PhysicalStart, desc->PhysicalStart + (desc->NumberOfPages << 12) - 1, (desc->NumberOfPages << 12) / 1024, desc->Attribute); } } #define BLOCK_16G (ARMv8_HUGE_PAGE_SIZE * 16ULL) #define BLOCK_16G_MASK (ARMv8_HUGE_PAGE_SIZE * 16ULL) static EFI_STATUS page_table_set_attr(struct config *cfg, uint64_t start, uint64_t end, uint64_t attr) { uint64_t base = ROUND_DOWN(start, ARMv8_HUGE_PAGE_SIZE) / ARMv8_HUGE_PAGE_SIZE; uint64_t last = COVER(end, ARMv8_HUGE_PAGE_SIZE); while (base < last) { size_t table_number = base >> ARMv8_BLOCK_BITS; size_t table_index = base & ARMv8_BLOCK_MASK; union aarch64_descriptor *desc = &cfg->tables->L1_tables[table_number][table_index]; desc->block_l1.attrindex = attr; base++; } return EFI_SUCCESS; } static EFI_STATUS build_page_tables(struct config *cfg) { EFI_STATUS status = EFI_SUCCESS; /* We need the current memory map to set memory attributes */ status = update_memory_map(); if (EFI_ERROR(status)) { Print(L"Failed to update memory map\n"); } /* Page table book keeping in static buffer * so we don't need malloc & friends */ cfg->tables = &page_tables; /* Map up to the highest RAM address supplied by EFI. XXX - this is a * heuristic, and may fail. Unless there's a more clever way to do * discovery, we might need to bite the bullet and map all 48 bits (2MB of * kernel page tables!). All we really need is that the kernel gets all * RAM, and the debug serial port - it shouldn't actually touch anything * else. */ uint64_t first_address, last_address; first_address = 0; last_address = ((1UL << 48) - 1); /* We will map in aligned 16G blocks, as each requires only one TLB * entry. */ uint64_t window_start, window_length; window_start = first_address & ~BLOCK_16G; window_length = ROUND_UP(last_address - window_start, BLOCK_16G); status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishBootPageTable, 1, (EFI_PHYSICAL_ADDRESS *)&cfg->tables->L0_table ); if (EFI_ERROR(status)) { Print(L"Failed to allocate L0 page table.\n"); goto build_page_tables_fail; } memset(cfg->tables->L0_table, 0, BASE_PAGE_SIZE); /* Count the number of L1 tables (512GB) blocks required to cover the * physical mapping window. */ cfg->tables->nL1 = 0; uint64_t L1base = window_start & ~ARMv8_TOP_TABLE_SIZE; uint64_t L1addr; for (L1addr = window_start & ~ARMv8_TOP_TABLE_SIZE; L1addr < window_start + window_length; L1addr += ARMv8_TOP_TABLE_SIZE) { cfg->tables->nL1++; } ASSERT(cfg->tables->nL1 <= MAX_L1_TABLES) /* Allocate the L1 tables. * We allocate them all in one big chunk * as otherwise the memory map size explodes */ Print(L"Allocating %d L1 tables (%dB)\n", cfg->tables->nL1, cfg->tables->nL1 * BASE_PAGE_SIZE); EFI_PHYSICAL_ADDRESS L1_memory; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishBootPageTable, cfg->tables->nL1, &L1_memory ); if (EFI_ERROR(status)) { Print(L"Failed to allocate L1 page tables.\n"); goto build_page_tables_fail; } memset((void *)L1_memory, 0, cfg->tables->nL1 * BASE_PAGE_SIZE); Print(L"L1 tables start at 0x%lx\n", L1_memory); for (size_t i = 0; i < cfg->tables->nL1; i++) { cfg->tables->L1_tables[i] = (union aarch64_descriptor *)(L1_memory + i * BASE_PAGE_SIZE); /* Map the L1 into the L0. */ size_t L0_index = (L1base >> ARMv8_TOP_TABLE_BITS) + i; cfg->tables->L0_table[L0_index].d.base = (uint64_t)cfg->tables->L1_tables[i] >> ARMv8_BASE_PAGE_BITS; cfg->tables->L0_table[L0_index].d.mb1 = 1; /* Page table */ cfg->tables->L0_table[L0_index].d.valid = 1; } /* Install the 1GB block mappings. */ uint64_t firstblock = window_start / ARMv8_HUGE_PAGE_SIZE; uint64_t nblocks = window_length / ARMv8_HUGE_PAGE_SIZE; for (uint64_t block = firstblock; block < firstblock + nblocks; block++) { size_t table_number= block >> ARMv8_BLOCK_BITS; size_t table_index= block & ARMv8_BLOCK_MASK; union aarch64_descriptor *desc = &cfg->tables->L1_tables[table_number][table_index]; // We first map all block non-contiguous but later set the bit // if possible desc->block_l1.contiguous = 0; desc->block_l1.base = block; /* Mark the accessed flag, so we don't get a fault. */ desc->block_l1.af = 1; /* Outer shareable - coherent. */ desc->block_l1.sh = 3; /* EL1+ only. */ desc->block_l1.ap = 0; // device memory by default desc->block_l1.attrindex = ATTR_DEVICE; /* A block. */ desc->block_l1.mb0 = 0; desc->block_l1.valid = 1; } // set all memory regions to cached size_t mmap_n_desc = mmap_size / mmap_d_size; for (size_t i = 0; i < mmap_n_desc; i++) { EFI_MEMORY_DESCRIPTOR *desc = (void *) (mmap + i * mmap_d_size); /* We're only looking for MMIO. */ if (desc->Type != EfiMemoryMappedIO && desc->Type != EfiMemoryMappedIOPortSpace) { page_table_set_attr(cfg, desc->PhysicalStart, desc->PhysicalStart + BASE_PAGE_SIZE * desc->NumberOfPages, ATTR_CACHED); } } // set the contiguous bit if possible for (uint64_t block = firstblock; block < firstblock + nblocks; block += 16) { BOOLEAN all_same = TRUE; uint64_t attr; for (uint64_t offset = 0; offset < 16; offset++) { size_t table_number = (block + offset) >> ARMv8_BLOCK_BITS; size_t table_index = (block + offset) & ARMv8_BLOCK_MASK; union aarch64_descriptor *desc = &cfg->tables->L1_tables[table_number][table_index]; if (offset == 0) { attr = desc->block_l1.attrindex; } else if (attr != desc->block_l1.attrindex) { all_same = FALSE; break; } } if (all_same) { // all entries in current block have the same attrindex value // so we can set the contiguous bit for (uint64_t offset = 0; offset < 16; offset++) { size_t table_number = (block + offset) >> ARMv8_BLOCK_BITS; size_t table_index = (block + offset) & ARMv8_BLOCK_MASK; union aarch64_descriptor *desc = &cfg->tables->L1_tables[table_number][table_index]; desc->block_l1.contiguous = 1; } } } return EFI_SUCCESS; build_page_tables_fail: if (cfg->tables) { if (cfg->tables->L1_tables) { size_t i; for (i= 0; i < cfg->tables->nL1; i++) { if (cfg->tables->L1_tables[i]) BS->FreePages((EFI_PHYSICAL_ADDRESS)cfg->tables->L1_tables[i], 1); } } if (cfg->tables->L0_table) { BS->FreePages((EFI_PHYSICAL_ADDRESS)cfg->tables->L0_table, 1); } } return status; } static void relocate_elf(EFI_PHYSICAL_ADDRESS segment_start, uint64_t virtual_offset, struct Blob_relocation *relocations, uint64_t no_relocations) { Print(L"Relocating ELF %lx %lx %d\n", segment_start, relocations, no_relocations); for (uint64_t i = 0; i < no_relocations; i++) { *(uint64_t *)(segment_start + relocations[i].offset) = segment_start + virtual_offset + relocations[i].addend; } } static EFI_STATUS relocate_boot_driver(struct Blob *blob_info, struct config *cfg) { EFI_STATUS status; /* Should be page aligend */ ASSERT(blob_info->boot_driver_segment_size % BASE_PAGE_SIZE == 0); EFI_PHYSICAL_ADDRESS boot_driver; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishCPUDriver, blob_info->boot_driver_segment_size / BASE_PAGE_SIZE, &boot_driver ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for boot driver segment: %d\n", status); return status; } memcpy((void *)boot_driver, BLOB_ADDRESS(blob_info->boot_driver_segment), blob_info->boot_driver_segment_size); struct Blob_relocation *boot_driver_relocations = (struct Blob_relocation *)BLOB_ADDRESS(blob_info->boot_driver_relocations); relocate_elf( boot_driver, 0, boot_driver_relocations, blob_info->boot_driver_relocations_count ); cfg->boot_driver_entry = boot_driver + blob_info->boot_driver_entry; return EFI_SUCCESS; } static EFI_STATUS relocate_cpu_driver(struct Blob *blob_info, struct config *cfg) { EFI_STATUS status; /* Should be page aligend */ ASSERT(blob_info->cpu_driver_segment_size % BASE_PAGE_SIZE == 0); EFI_PHYSICAL_ADDRESS cpu_driver; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishCPUDriver, blob_info->cpu_driver_segment_size / BASE_PAGE_SIZE, &cpu_driver ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for CPU driver segment: %d\n", status); return status; } memcpy((void *)cpu_driver, BLOB_ADDRESS(blob_info->cpu_driver_segment), blob_info->cpu_driver_segment_size); struct Blob_relocation *cpu_driver_relocations = (struct Blob_relocation *)BLOB_ADDRESS(blob_info->cpu_driver_relocations); relocate_elf( cpu_driver, KERNEL_OFFSET, cpu_driver_relocations, blob_info->cpu_driver_relocations_count ); cfg->cpu_driver_entry = cpu_driver + blob_info->cpu_driver_entry + KERNEL_OFFSET; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishCPUDriverStack, KERNEL_STACK_SIZE / BASE_PAGE_SIZE, &cfg->cpu_driver_stack ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for CPU driver stack: %d\n", status); return status; } cfg->cpu_driver_stack_size = KERNEL_STACK_SIZE; Print( L"Relocated CPU driver entry point is %lx, stack at %lx\n", cfg->cpu_driver_entry, cfg->cpu_driver_stack ); return EFI_SUCCESS; } static EFI_STATUS relocate_modules(struct Blob *blob_info, struct config *cfg) { EFI_STATUS status; /* Should be page aligend */ ASSERT(blob_info->modules_size % BASE_PAGE_SIZE == 0); status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishELFData, blob_info->modules_size / BASE_PAGE_SIZE, &cfg->modules ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for modules: %d\n", status); return status; } memcpy((void *)cfg->modules, BLOB_ADDRESS(blob_info->modules), blob_info->modules_size); return EFI_SUCCESS; } static EFI_STATUS relocate_multiboot(struct Blob *blob_info, struct config *cfg) { EFI_STATUS status; /* Should be page aligend */ ASSERT(blob_info->multiboot_size % BASE_PAGE_SIZE == 0); EFI_PHYSICAL_ADDRESS memory; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishMultibootData, blob_info->multiboot_size / BASE_PAGE_SIZE, (EFI_PHYSICAL_ADDRESS *)&cfg->multiboot ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for multiboot info: %d\n", status); return status; } memcpy(cfg->multiboot, BLOB_ADDRESS(blob_info->multiboot), blob_info->multiboot_size); /* Module start & end pointed into the blob. * Now they need to point into the module region */ uint64_t module_offset = (uint64_t)cfg->modules - blob_info->modules; Print(L"Relocating multiboot info: %lx\n", cfg->multiboot); /* We don't have an end tag yet, but EFI mmap tag is last */ struct multiboot_tag *tag; for (tag = cfg->multiboot->tags; tag->type != MULTIBOOT_TAG_TYPE_EFI_MMAP; tag = (void *)tag + tag->size) { Print(L"%lx: tag %d:%d\n", tag, tag->type, tag->size); if (tag->type == MULTIBOOT_TAG_TYPE_MODULE_64) { struct multiboot_tag_module_64 *mtag = (struct multiboot_tag_module_64 *)tag; Print(L"\tbefore %lx:%lx\n", mtag->mod_start, mtag->mod_end); mtag->mod_start += module_offset; mtag->mod_end += module_offset; Print(L"\tafter %lx:%lx\n", mtag->mod_start, mtag->mod_end); } else if (tag->type == MULTIBOOT_TAG_TYPE_CMDLINE) { cfg->cmd_tag = (struct multiboot_tag_string *)tag; } } ASSERT(tag->type == MULTIBOOT_TAG_TYPE_EFI_MMAP); cfg->mmap_tag = (struct multiboot_tag_efi_mmap *)tag; return EFI_SUCCESS; } static struct armv8_core_data * create_core_data(struct config *cfg) { EFI_STATUS status; struct armv8_core_data *core_data; status = BS->AllocatePages( AllocateAnyPages, EfiBarrelfishCoreData, 1, (EFI_PHYSICAL_ADDRESS *)&core_data ); if (EFI_ERROR(status)) { Print(L"Error allocating memory for core data: %d\n", status); return NULL; } memset(core_data, 0, BASE_PAGE_SIZE); core_data->boot_magic = ARMV8_BOOTMAGIC_BSP; core_data->cpu_driver_stack = (lpaddr_t)cfg->cpu_driver_stack + cfg->cpu_driver_stack_size - 16; core_data->cpu_driver_stack_limit = (lpaddr_t)cfg->cpu_driver_stack; core_data->cpu_driver_entry = (lvaddr_t)cfg->cpu_driver_entry; core_data->page_table_root = (genpaddr_t)cfg->tables->L0_table; ntstring( core_data->cpu_driver_cmdline, cfg->cmd_tag->string, MIN( cfg->cmd_tag->size - sizeof(struct multiboot_tag_string), sizeof(core_data->cpu_driver_cmdline) - 1 ) ); core_data->multiboot_image.base = (lpaddr_t)cfg->multiboot; core_data->multiboot_image.length = cfg->multiboot->total_size; core_data->efi_mmap = (lpaddr_t)cfg->mmap_tag; return core_data; } EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE * SystemTable) { EFI_STATUS status; InitializeLib(ImageHandle, SystemTable); struct Blob *blob_info = (struct Blob *)BLOB_ADDRESS(0); Print(L"Blob is at: 0x%lx\n", blob_info); Print(L"Magic: %lx\n", blob_info->magic); struct config cfg; status = relocate_boot_driver(blob_info, &cfg); if (EFI_ERROR(status)) { Print(L"Failed to relocate boot driver\n"); return status; } status = relocate_cpu_driver(blob_info, &cfg); if (EFI_ERROR(status)) { Print(L"Failed to relocate CPU driver\n"); return status; } status = relocate_modules(blob_info, &cfg); if (EFI_ERROR(status)) { Print(L"Failed to relocate modules\n"); return status; } status = relocate_multiboot(blob_info, &cfg); if (EFI_ERROR(status)) { Print(L"Failed to relocate multiboot info\n"); return status; } status = build_page_tables(&cfg); if (EFI_ERROR(status)) { Print(L"Failed to build page tables\n"); return status; } struct armv8_core_data *core_data = create_core_data(&cfg); Print(L"Terminating boot services and jumping to image at 0x%lx\n", cfg.boot_driver_entry); Print(L"Core data pointer is %lx\n", core_data); print_memory_map(1); status = update_memory_map(); if (EFI_ERROR(status)) { Print(L"Failed to update memory map\n"); } status = ST->BootServices->ExitBootServices(ImageHandle, mmap_key); if (EFI_ERROR(status)) { Print(L"Error exiting boot services: %d, %x\n", status, mmap_key); return status; } /*** EFI boot services are now terminated, we're on our own. */ status = relocate_memory_map(); if (EFI_ERROR(status)) { return EFI_SUCCESS; } /* The last thing we do is complete the multiboot info: * Set the EFI mmap and end tag */ cfg.mmap_tag->size = ROUND_UP(sizeof(struct multiboot_tag_efi_mmap) + mmap_size, 8); cfg.mmap_tag->descr_size = mmap_d_size; cfg.mmap_tag->descr_vers = mmap_d_ver; memcpy(cfg.mmap_tag->efi_mmap, mmap, mmap_size); struct multiboot_tag *end_tag = (void *)cfg.mmap_tag + cfg.mmap_tag->size; end_tag->type = MULTIBOOT_TAG_TYPE_END; end_tag->size = ROUND_UP(sizeof(struct multiboot_tag), 8); cfg.multiboot->total_size = (void *)end_tag + end_tag->size - (void *)cfg.multiboot; status = ST->RuntimeServices->SetVirtualAddressMap( mmap_size, mmap_d_size, mmap_d_ver, (void *) &mmap ); if (EFI_ERROR(status)) { return status; } // Jump to the bootloader, the blob can be reused (*((boot_driver *) (cfg.boot_driver_entry))) (MULTIBOOT2_BOOTLOADER_MAGIC, core_data); return EFI_SUCCESS; }