[boot] Load programs in boot, not kernel

Remove ELF and initrd loading from the kernel. The bootloader now loads
the initial programs, as it does with the kernel. Other files that were
in the initrd are now on the ESP, and non-program files are just passed
as modules.
This commit is contained in:
2020-10-04 17:11:03 -07:00
parent da38006f44
commit 4ccaa2dfea
19 changed files with 282 additions and 351 deletions

View File

@@ -50,19 +50,19 @@ file::open(const wchar_t *path)
return file(fh, m_bs);
}
void *
file::load(size_t *out_size, uefi::memory_type mem_type)
buffer
file::load(uefi::memory_type mem_type)
{
uint8_t buffer[sizeof(uefi::protos::file_info) + 100];
size_t size = sizeof(buffer);
uint8_t info_buf[sizeof(uefi::protos::file_info) + 100];
size_t size = sizeof(info_buf);
uefi::guid info_guid = uefi::protos::file_info::guid;
try_or_raise(
m_file->get_info(&info_guid, &size, &buffer),
m_file->get_info(&info_guid, &size, &info_buf),
L"Could not get file info");
uefi::protos::file_info *info =
reinterpret_cast<uefi::protos::file_info*>(&buffer);
reinterpret_cast<uefi::protos::file_info*>(&info_buf);
size_t pages = memory::bytes_to_pages(info->file_size);
void *data = nullptr;
@@ -77,8 +77,7 @@ file::load(size_t *out_size, uefi::memory_type mem_type)
m_file->read(&size, data),
L"Could not read from file");
*out_size = size;
return data;
return { .data = data, .size = size };
}
file

View File

@@ -5,6 +5,7 @@
#include <uefi/types.h>
#include <uefi/boot_services.h>
#include <uefi/protos/file.h>
#include "types.h"
namespace boot {
namespace fs {
@@ -22,13 +23,10 @@ public:
file open(const wchar_t *path);
/// Load the contents of this file into memory.
/// \arg out_size _out:_ The number of bytes loaded
/// \arg mem_type The UEFI memory type to use for allocation
/// \returns A pointer to the loaded memory. Memory will be
/// page-aligned.
void * load(
size_t *out_size,
uefi::memory_type mem_type = uefi::memory_type::loader_data);
/// \returns A buffer describing the loaded memory. The
/// memory will be page-aligned.
buffer load(uefi::memory_type mem_type = uefi::memory_type::loader_data);
private:
friend file get_boot_volume(uefi::handle, uefi::boot_services*);

View File

@@ -5,12 +5,32 @@
#include "console.h"
#include "elf.h"
#include "error.h"
#include "fs.h"
#include "memory.h"
#include "paging.h"
namespace args = kernel::args;
namespace boot {
namespace loader {
buffer
load_file(
fs::file &disk,
const wchar_t *name,
const wchar_t *path,
uefi::memory_type type)
{
status_line status(L"Loading file", name);
fs::file file = disk.open(path);
buffer b = file.load(type);
console::print(L" Loaded at: 0x%lx, %d bytes\r\n", b.data, b.size);
return b;
}
static bool
is_elfheader_valid(const elf::header *header)
{
@@ -26,54 +46,68 @@ is_elfheader_valid(const elf::header *header)
header->header_version == elf::version;
}
kernel::entrypoint
load(
const void *data, size_t size,
kernel::args::header *args,
void
load_program(
args::program &program,
const wchar_t *name,
buffer data,
uefi::boot_services *bs)
{
status_line status(L"Loading kernel ELF binary");
const elf::header *header = reinterpret_cast<const elf::header*>(data);
status_line status(L"Loading program:", name);
const elf::header *header = reinterpret_cast<const elf::header*>(data.data);
if (size < sizeof(elf::header) || !is_elfheader_valid(header))
error::raise(uefi::status::load_error, L"Kernel ELF not valid");
if (data.size < sizeof(elf::header) || !is_elfheader_valid(header))
error::raise(uefi::status::load_error, L"ELF file not valid");
paging::page_table *pml4 = reinterpret_cast<paging::page_table*>(args->pml4);
uintptr_t prog_base = uintptr_t(-1);
uintptr_t prog_end = 0;
for (int i = 0; i < header->ph_num; ++i) {
ptrdiff_t offset = header->ph_offset + i * header->ph_entsize;
const elf::program_header *pheader =
offset_ptr<elf::program_header>(data, offset);
offset_ptr<elf::program_header>(data.data, offset);
if (pheader->type != elf::PT_LOAD)
continue;
size_t num_pages = memory::bytes_to_pages(pheader->mem_size);
void *pages = nullptr;
uintptr_t end = pheader->vaddr + pheader->mem_size;
if (pheader->vaddr < prog_base) prog_base = pheader->vaddr;
if (end > prog_end) prog_end = end;
}
try_or_raise(
bs->allocate_pages(uefi::allocate_type::any_pages,
memory::kernel_type, num_pages, &pages),
L"Failed allocating space for kernel code");
size_t total_size = prog_end - prog_base;
size_t num_pages = memory::bytes_to_pages(total_size);
void *pages = nullptr;
void *data_start = offset_ptr<void>(data, pheader->offset);
bs->copy_mem(pages, data_start, pheader->file_size);
try_or_raise(
bs->allocate_pages(uefi::allocate_type::any_pages,
memory::program_type, num_pages, &pages),
L"Failed allocating space for program");
if (pheader->mem_size > pheader->file_size) {
void *extra = offset_ptr<void>(pages, pheader->file_size);
size_t size = pheader->mem_size - pheader->file_size;
bs->set_mem(extra, size, 0);
}
bs->set_mem(pages, total_size, 0);
for (int i = 0; i < header->ph_num; ++i) {
ptrdiff_t offset = header->ph_offset + i * header->ph_entsize;
const elf::program_header *pheader =
offset_ptr<elf::program_header>(data.data, offset);
if (pheader->type != elf::PT_LOAD)
continue;
void *src_start = offset_ptr<void>(data.data, pheader->offset);
void *dest_start = offset_ptr<void>(pages, pheader->vaddr - prog_base);
bs->copy_mem(dest_start, src_start, pheader->file_size);
console::print(L" section %d phys: 0x%lx\r\n", i, pages);
console::print(L" section %d virt: 0x%lx\r\n", i, pheader->vaddr);
// TODO: set appropriate RWX permissions
paging::map_pages(pml4, args, reinterpret_cast<uintptr_t>(pages), pheader->vaddr, pheader->mem_size);
}
console::print(L" entrypoint: 0x%lx\r\n", header->entrypoint);
return reinterpret_cast<kernel::entrypoint>(header->entrypoint);
program.phys_addr = reinterpret_cast<uintptr_t>(pages);
program.size = total_size;
program.virt_addr = prog_base;
program.entrypoint = header->entrypoint;
}
} // namespace loader

View File

@@ -5,18 +5,36 @@
#include <uefi/boot_services.h>
#include "kernel_args.h"
#include "memory.h"
#include "types.h"
namespace boot {
namespace fs { class file; }
namespace loader {
/// Load a file from disk into memory.
/// \arg disk The opened UEFI filesystem to load from
/// \arg name Name of the module (informational only)
/// \arg path Path on `disk` of the file to load
/// \arg type Memory type to use for allocation
buffer
load_file(
fs::file &disk,
const wchar_t *name,
const wchar_t *path,
uefi::memory_type type = uefi::memory_type::loader_data);
/// Parse and load an ELF file in memory into a loaded image.
/// \arg data The start of the ELF file in memory
/// \arg size The size of the ELF file in memory
/// \arg args The kernel args, used for modifying page tables
/// \returns A descriptor defining the loaded image
kernel::entrypoint load(
const void *data, size_t size,
kernel::args::header *args,
/// \arg program The program structure to fill
/// \arg data Buffer of the ELF file in memory
/// \arg bs Boot services
void
load_program(
kernel::args::program &program,
const wchar_t *name,
buffer data,
uefi::boot_services *bs);
} // namespace loader

View File

@@ -21,9 +21,24 @@ namespace kernel {
#include "kernel_memory.h"
}
namespace args = kernel::args;
namespace boot {
constexpr int max_modules = 10; // Max modules to allocate room for
constexpr int max_modules = 5; // Max modules to allocate room for
constexpr int max_programs = 5; // Max programs to allocate room for
struct program_desc
{
const wchar_t *name;
const wchar_t *path;
};
const program_desc program_list[] = {
{L"kernel", L"jsix.elf"},
{L"null driver", L"nulldrv.elf"},
{L"terminal driver", L"terminal.elf"},
};
/// Change a pointer to point to the higher-half linear-offset version
/// of the address it points to.
@@ -34,19 +49,21 @@ void change_pointer(T *&pointer)
}
/// Allocate space for kernel args. Allocates enough space from pool
/// memory for the args header and `max_modules` module headers.
kernel::args::header *
/// memory for the args header and the module and program headers.
args::header *
allocate_args_structure(
uefi::boot_services *bs,
size_t max_modules)
size_t max_modules,
size_t max_programs)
{
status_line status(L"Setting up kernel args memory");
kernel::args::header *args = nullptr;
args::header *args = nullptr;
size_t args_size =
sizeof(kernel::args::header) + // The header itself
max_modules * sizeof(kernel::args::module); // The module structures
sizeof(args::header) + // The header itself
max_modules * sizeof(args::module) + // The module structures
max_programs * sizeof(args::program); // The program structures
try_or_raise(
bs->allocate_pool(memory::args_type, args_size,
@@ -56,47 +73,34 @@ allocate_args_structure(
bs->set_mem(args, args_size, 0);
args->modules =
reinterpret_cast<kernel::args::module*>(args + 1);
reinterpret_cast<args::module*>(args + 1);
args->num_modules = 0;
args->programs =
reinterpret_cast<args::program*>(args->modules + max_modules);
args->num_programs = 0;
return args;
}
/// Load a file from disk into memory. Also adds an entry to the kernel
/// args module headers pointing at the loaded data.
/// \arg disk The opened UEFI filesystem to load from
/// \arg args The kernel args header to update with module information
/// \arg name Name of the module (informational only)
/// \arg path Path on `disk` of the file to load
/// \arg type Type specifier of this module (eg, initrd or kernel)
kernel::args::module *
load_module(
fs::file &disk,
kernel::args::header *args,
const wchar_t *name,
const wchar_t *path,
kernel::args::mod_type type)
/// Add a module to the kernel args list
inline void
add_module(args::header *args, args::mod_type type, buffer &data)
{
status_line status(L"Loading module", name);
fs::file file = disk.open(path);
kernel::args::module &module = args->modules[args->num_modules++];
module.type = type;
module.location = file.load(&module.size, memory::module_type);
console::print(L" Loaded at: 0x%lx, %d bytes\r\n", module.location, module.size);
return &module;
args::module &m = args->modules[args->num_modules++];
m.type = type;
m.location = data.data;
m.size = data.size;
}
/// The main procedure for the portion of the loader that runs while
/// UEFI is still in control of the machine. (ie, while the loader still
/// has access to boot services.
kernel::args::header *
args::header *
bootloader_main_uefi(
uefi::handle image,
uefi::system_table *st,
console &con,
kernel::entrypoint *kentry)
console &con)
{
error::uefi_handler handler(con);
status_line status(L"Performing UEFI pre-boot");
@@ -105,27 +109,32 @@ bootloader_main_uefi(
uefi::runtime_services *rs = st->runtime_services;
memory::init_pointer_fixup(bs, rs);
kernel::args::header *args =
allocate_args_structure(bs, max_modules);
args::header *args =
allocate_args_structure(bs, max_modules, max_programs);
args->magic = kernel::args::magic;
args->version = kernel::args::version;
args->magic = args::magic;
args->version = args::version;
args->runtime_services = rs;
args->acpi_table = hw::find_acpi_table(st);
paging::allocate_tables(args, bs);
memory::mark_pointer_fixup(&args->runtime_services);
fs::file disk = fs::get_boot_volume(image, bs);
load_module(disk, args, L"initrd", L"initrd.img", kernel::args::mod_type::initrd);
kernel::args::module *kernel =
load_module(disk, args, L"kernel", L"jsix.elf", kernel::args::mod_type::kernel);
const uefi::memory_type mod_type = memory::module_type;
buffer symbols = loader::load_file(disk, L"symbol table", L"symbol_table.dat",
memory::module_type);
add_module(args, args::mod_type::symbol_table, symbols);
paging::allocate_tables(args, bs);
*kentry = loader::load(kernel->location, kernel->size, args, bs);
for (auto &desc : program_list) {
buffer buf = loader::load_file(disk, desc.name, desc.path);
args::program &program = args->programs[args->num_programs++];
loader::load_program(program, desc.name, buf, bs);
}
for (unsigned i = 0; i < args->num_modules; ++i) {
kernel::args::module &mod = args->modules[i];
args::module &mod = args->modules[i];
change_pointer(mod.location);
}
@@ -143,9 +152,13 @@ efi_main(uefi::handle image_handle, uefi::system_table *st)
error::cpu_assert_handler handler;
console con(st->boot_services, st->con_out);
kernel::entrypoint kentry = nullptr;
kernel::args::header *args =
bootloader_main_uefi(image_handle, st, con, &kentry);
args::header *args =
bootloader_main_uefi(image_handle, st, con);
args::program &kernel = args->programs[0];
paging::map_pages(args, kernel.phys_addr, kernel.virt_addr, kernel.size);
kernel::entrypoint kentry =
reinterpret_cast<kernel::entrypoint>(kernel.entrypoint);
memory::efi_mem_map map =
memory::build_kernel_mem_map(args, st->boot_services);

View File

@@ -45,7 +45,7 @@ memory_type_name(uefi::memory_type t)
switch(t) {
case args_type: return L"jsix kernel args";
case module_type: return L"jsix bootloader module";
case kernel_type: return L"jsix kernel code";
case program_type: return L"jsix kernel or program code";
case table_type: return L"jsix page tables";
default: return L"Bad Type Value";
}
@@ -195,8 +195,8 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
type = mem_type::module;
break;
case kernel_type:
type = mem_type::kernel;
case program_type:
type = mem_type::program;
break;
case table_type:
@@ -233,21 +233,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
// Give just the actually-set entries in the header
args->mem_map = kernel_map;
args->num_map_entries = i;
// But pass the entire allocated area in a module as well
kernel::args::module &module = args->modules[args->num_modules++];
module.location = reinterpret_cast<void*>(kernel_map);
module.size = map_size;
module.type = kernel::args::mod_type::memory_map;
/*
for (size_t i = 0; i<map.num_entries(); ++i) {
mem_entry &ent = kernel_map[i];
console::print(L" Range %lx (%x) %d [%lu]\r\n",
ent.start, ent.attr, ent.type, ent.pages);
}
*/
args->map_count = i;
return efi_map;
}

View File

@@ -30,8 +30,8 @@ constexpr uefi::memory_type args_type =
constexpr uefi::memory_type module_type =
static_cast<uefi::memory_type>(0x80000001);
/// Memory containing loaded kernel code and data sections
constexpr uefi::memory_type kernel_type =
/// Memory containing loaded kernel or program code and data sections
constexpr uefi::memory_type program_type =
static_cast<uefi::memory_type>(0x80000002);
/// Memory containing page tables set up by the loader

View File

@@ -67,7 +67,7 @@ public:
uintptr_t virt,
page_table *pml4,
void *&page_cache,
uint32_t &cache_count) :
size_t &cache_count) :
m_page_cache(page_cache),
m_cache_count(cache_count)
{
@@ -130,14 +130,14 @@ private:
}
void *&m_page_cache;
uint32_t &m_cache_count;
size_t &m_cache_count;
page_table *m_table[D];
uint16_t m_index[D];
};
static void
add_offset_mappings(page_table *pml4, void *&page_cache, uint32_t &num_pages)
add_offset_mappings(page_table *pml4, void *&page_cache, size_t &num_pages)
{
uintptr_t phys = 0;
uintptr_t virt = ::memory::page_offset; // Start of offset-mapped area
@@ -160,7 +160,7 @@ add_offset_mappings(page_table *pml4, void *&page_cache, uint32_t &num_pages)
}
static void
add_kernel_pds(page_table *pml4, void *&page_cache, uint32_t &num_pages)
add_kernel_pds(page_table *pml4, void *&page_cache, size_t &num_pages)
{
for (unsigned i = pml4e_kernel; i < table_entries; ++i) {
pml4->set(i, page_cache, table_flags);
@@ -208,34 +208,31 @@ allocate_tables(kernel::args::header *args, uefi::boot_services *bs)
bs->set_mem(addr, tables_needed*page_size, 0);
kernel::args::module &mod = args->modules[++args->num_modules];
mod.type = kernel::args::mod_type::page_tables;
mod.location = addr;
mod.size = tables_needed*page_size;
args->pml4 = addr;
args->num_free_tables = tables_needed - 1;
args->page_table_cache = offset_ptr<void>(addr, page_size);
args->table_count = tables_needed - 1;
args->page_tables = offset_ptr<void>(addr, page_size);
page_table *pml4 = reinterpret_cast<page_table*>(addr);
add_kernel_pds(pml4, args->page_table_cache, args->num_free_tables);
add_offset_mappings(pml4, args->page_table_cache, args->num_free_tables);
add_kernel_pds(pml4, args->page_tables, args->table_count);
add_offset_mappings(pml4, args->page_tables, args->table_count);
console::print(L" Set up initial mappings, %d spare tables.\r\n", args->num_free_tables);
console::print(L" Set up initial mappings, %d spare tables.\r\n", args->table_count);
}
void
map_pages(
page_table *pml4,
kernel::args::header *args,
uintptr_t phys, uintptr_t virt,
size_t size)
{
paging::page_table *pml4 =
reinterpret_cast<paging::page_table*>(args->pml4);
size_t pages = memory::bytes_to_pages(size);
page_entry_iterator<4> iterator{
virt, pml4,
args->page_table_cache,
args->num_free_tables};
args->page_tables,
args->table_count};
while (true) {
*iterator = phys | page_flags;

View File

@@ -39,13 +39,11 @@ void allocate_tables(
void add_current_mappings(page_table *new_pml4);
/// Map a physical address to a virtual address in the given page tables.
/// \arg pml4 The root of the set of page tables to be updated
/// \arg args The kernel args header, used for the page table cache
/// \arg args The kernel args header, used for the page table cache and pml4
/// \arg phys The phyiscal address to map in
/// \arg virt The virtual address to map in
/// \arg size The size in bytes of the mapping
void map_pages(
page_table *pml4,
kernel::args::header *args,
uintptr_t phys, uintptr_t virt,
size_t bytes);

13
src/boot/types.h Normal file
View File

@@ -0,0 +1,13 @@
/// \file types.h
/// Definitions of shared types used throughout the bootloader
#pragma once
namespace boot {
struct buffer
{
size_t size;
void *data;
};
} // namespace boot