[boot] Create bootconfig to tell boot what to load

While bonnibel already had the concept of a manifest, which controls
what goes into the built disk image, the bootloader still had filenames
hard-coded. Now bonnibel creates a 'jsix_boot.dat' file that tells the
bootloader what it should load.

Changes include:

- Modules have two new fields: location and description. location is
  their intended directory on the EFI boot volume. description is
  self-explanatory, and is used in log messages.
- New class, boot::bootconfig, implements reading of jsix_boot.dat
- New header, bootproto/bootconfig.h, specifies flags used in the
  manifest and jsix_boot.dat
- New python module, bonnibel/manifest.py, encapsulates reading of the
  manifest and writing jsix_boot.dat
- Syntax of the manifest changed slightly, including adding flags
- Boot and Kernel target ccflags unified a bit (this was partly due to
  trying to get enum_bitfields to work in boot)
- util::counted gained operator+= and new free function util::read<T>
This commit is contained in:
Justin C. Miller
2022-01-07 22:33:25 -08:00
parent 9f3e682b89
commit a3fff889d1
20 changed files with 356 additions and 82 deletions

View File

@@ -7,6 +7,7 @@ module("boot",
deps = [ "cpu", "elf", "util", "bootproto" ],
sources = [
"allocator.cpp",
"bootconfig.cpp",
"console.cpp",
"error.cpp",
"fs.cpp",

62
src/boot/bootconfig.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include <uefi/boot_services.h>
#include <uefi/types.h>
#include "bootconfig.h"
#include "error.h"
#include "fs.h"
#include "status.h"
namespace boot {
constexpr uint64_t jsixboot = 0x746f6f627869736a; // "jsixboot"
static const wchar_t *
read_string(util::buffer &data)
{
uint16_t size = *util::read<uint16_t>(data);
const wchar_t *string = reinterpret_cast<const wchar_t*>(data.pointer);
data += size;
return string;
}
static void
read_descriptor(descriptor &e, util::buffer &data)
{
e.flags = static_cast<desc_flags>(*util::read<uint16_t>(data));
e.path = read_string(data);
e.desc = read_string(data);
}
bootconfig::bootconfig(util::buffer data, uefi::boot_services *bs)
{
status_line status {L"Loading boot config"};
if (*util::read<uint64_t>(data) != jsixboot)
error::raise(uefi::status::load_error, L"Bad header in jsix_boot.dat");
const uint8_t version = *util::read<uint8_t>(data);
if (version != 0)
error::raise(uefi::status::incompatible_version, L"Bad version in jsix_boot.dat");
data += 1; // reserved byte
uint16_t num_programs = *util::read<uint16_t>(data);
uint16_t num_data = *util::read<uint16_t>(data);
data += 2; // reserved short
read_descriptor(m_kernel, data);
read_descriptor(m_init, data);
m_programs.count = num_programs;
m_programs.pointer = new descriptor [num_programs];
for (unsigned i = 0; i < num_programs; ++i)
read_descriptor(m_programs[i], data);
m_data.count = num_programs;
m_data.pointer = new descriptor [num_data];
for (unsigned i = 0; i < num_data; ++i)
read_descriptor(m_data[i], data);
}
} // namespace boot

43
src/boot/bootconfig.h Normal file
View File

@@ -0,0 +1,43 @@
/// \file bootconfig.h
/// Definitions for reading the jsix bootconfig file
#pragma once
#include <bootproto/bootconfig.h>
#include <util/counted.h>
namespace uefi {
struct boot_services;
}
namespace boot {
using desc_flags = bootproto::desc_flags;
struct descriptor {
desc_flags flags;
wchar_t const *path;
wchar_t const *desc;
};
/// A bootconfig is a manifest of potential files.
class bootconfig
{
public:
using descriptors = util::counted<descriptor>;
/// Constructor. Loads bootconfig from the given buffer.
bootconfig(util::buffer data, uefi::boot_services *bs);
inline const descriptor & kernel() { return m_kernel; }
inline const descriptor & init() { return m_init; }
descriptors programs() { return m_programs; }
descriptors data() { return m_data; }
private:
descriptor m_kernel;
descriptor m_init;
descriptors m_programs;
descriptors m_data;
};
} // namespace boot

View File

@@ -7,6 +7,7 @@
#include <util/pointers.h>
#include "allocator.h"
#include "bootconfig.h"
#include "console.h"
#include "error.h"
#include "fs.h"
@@ -23,7 +24,7 @@ using memory::alloc_type;
util::buffer
load_file(
fs::file &disk,
const program_desc &desc)
const descriptor &desc)
{
status_line status(L"Loading file", desc.path);
@@ -36,7 +37,7 @@ load_file(
static void
create_module(util::buffer data, const program_desc &desc, bool loaded)
create_module(util::buffer data, const descriptor &desc, bool loaded)
{
size_t path_len = wstrlen(desc.path);
bootproto::module_program *mod = g_alloc.allocate_module<bootproto::module_program>(path_len);
@@ -50,18 +51,20 @@ create_module(util::buffer data, const program_desc &desc, bool loaded)
// TODO: support non-ascii path characters and do real utf-16 to utf-8
// conversion
for (int i = 0; i < path_len; ++i)
mod->filename[i] = desc.path[i];
for (int i = 0; i < path_len; ++i) {
char c = desc.path[i];
mod->filename[i] = c == '\\' ? '/' : c;
}
mod->filename[path_len] = 0;
}
bootproto::program *
load_program(
fs::file &disk,
const program_desc &desc,
const descriptor &desc,
bool add_module)
{
status_line status(L"Loading program", desc.name);
status_line status(L"Loading program", desc.desc);
util::buffer data = load_file(disk, desc);
@@ -113,9 +116,9 @@ load_program(
void
load_module(
fs::file &disk,
const program_desc &desc)
const descriptor &desc)
{
status_line status(L"Loading module", desc.name);
status_line status(L"Loading module", desc.desc);
util::buffer data = load_file(disk, desc);
create_module(data, desc, false);

View File

@@ -11,34 +11,30 @@ namespace bootproto {
namespace boot {
class descriptor;
namespace fs {
class file;
}
namespace loader {
struct program_desc
{
const wchar_t *name;
const wchar_t *path;
};
/// Load a file from disk into memory.
/// \arg disk The opened UEFI filesystem to load from
/// \arg desc The program descriptor identifying the file
util::buffer
load_file(
fs::file &disk,
const program_desc &desc);
const descriptor &desc);
/// Parse and load an ELF file in memory into a loaded image.
/// \arg disk The opened UEFI filesystem to load from
/// \arg desc The program descriptor identifying the program
/// \arg desc The descriptor identifying the program
/// \arg add_module Also create a module for this loaded program
bootproto::program *
load_program(
fs::file &disk,
const program_desc &desc,
const descriptor &desc,
bool add_module = false);
/// Load a file from disk into memory, creating an init args module
@@ -47,7 +43,7 @@ load_program(
void
load_module(
fs::file &disk,
const program_desc &desc);
const descriptor &desc);
/// Verify that a loaded ELF has the j6 kernel header
/// \arg program The program to check for a header

View File

@@ -7,12 +7,14 @@
#include <stddef.h>
#include <stdint.h>
#include <bootproto/bootconfig.h>
#include <bootproto/kernel.h>
#include <bootproto/memory.h>
#include <util/counted.h>
#include <util/pointers.h>
#include "allocator.h"
#include "bootconfig.h"
#include "console.h"
#include "error.h"
#include "fs.h"
@@ -27,17 +29,6 @@
namespace boot {
const loader::program_desc kern_desc = {L"kernel", L"jsix.elf"};
const loader::program_desc init_desc = {L"init server", L"srv.init.elf"};
const loader::program_desc fb_driver = {L"UEFI framebuffer driver", L"drv.uefi_fb.elf"};
const loader::program_desc uart_driver = {L"Serial port driver", L"drv.uart.elf"};
const loader::program_desc panic_handler = {L"Serial panic handler", L"panic.serial.elf"};
const loader::program_desc extra_programs[] = {
{L"test application", L"testapp.elf"},
};
/// Change a pointer to point to the higher-half linear-offset version
/// of the address it points to.
template <typename T>
@@ -81,23 +72,51 @@ load_resources(bootproto::args *args, video::screen *screen, uefi::handle image,
status_line status {L"Loading programs"};
fs::file disk = fs::get_boot_volume(image, bs);
fs::file bc_data = disk.open(L"jsix_boot.dat");
bootconfig bc {bc_data.load(), bs};
args->kernel = loader::load_program(disk, bc.kernel(), true);
args->init = loader::load_program(disk, bc.init());
namespace bits = util::bits;
using bootproto::desc_flags;
if (screen) {
video::make_module(screen);
loader::load_module(disk, fb_driver);
} else {
loader::load_module(disk, uart_driver);
// Go through the screen-specific descriptors first to
// give them priority
for (const descriptor &d : bc.programs()) {
if (!bits::has(d.flags, desc_flags::graphical))
continue;
if (bits::has(d.flags, desc_flags::panic))
args->panic = loader::load_program(disk, d);
else
loader::load_module(disk, d);
}
}
util::buffer symbol_table = loader::load_file(disk, {L"symbol table", L"symbol_table.dat"});
args->symbol_table = reinterpret_cast<uintptr_t>(symbol_table.pointer);
// Load the non-graphical descriptors
for (const descriptor &d : bc.programs()) {
if (bits::has(d.flags, desc_flags::graphical))
continue;
args->kernel = loader::load_program(disk, kern_desc, true);
args->init = loader::load_program(disk, init_desc);
args->panic = loader::load_program(disk, panic_handler);
if (bits::has(d.flags, desc_flags::panic) && !args->panic)
args->panic = loader::load_program(disk, d);
else
loader::load_module(disk, d);
}
for (auto &desc : extra_programs)
loader::load_module(disk, desc);
// For now the only data we load is the symbol table
for (const descriptor &d : bc.data()) {
if (!bits::has(d.flags, desc_flags::symbols))
continue;
util::buffer symbol_table = loader::load_file(disk, d);
args->symbol_table = reinterpret_cast<uintptr_t>(symbol_table.pointer);
break;
}
loader::verify_kernel_header(*args->kernel);
}

View File

@@ -5,6 +5,7 @@ kernel = module("kernel",
default = True,
output = "jsix.elf",
targets = [ "kernel" ],
description = "jsix kernel",
deps = [ "util", "cpu", "bootproto" ],
includes = [ "." ],
sources = [

View File

@@ -2,9 +2,9 @@
panic = module("panic.serial",
kind = "exe",
output = "panic.serial.elf",
targets = [ "kernel" ],
deps = [ "util", "elf", "kernel" ],
description = "Serial panic handler",
sources = [
"display.cpp",
"entry.s",

View File

@@ -0,0 +1,17 @@
#pragma once
/// \file bootproto/bootconfig.h
/// Data structures for reading jsix_boot.dat
#include <stdint.h>
#include <util/enum_bitfields.h>
namespace bootproto {
enum class desc_flags : uint16_t {
graphical = 0x01,
panic = 0x02,
symbols = 0x04,
};
is_bitfield(desc_flags);
} // namespace bootproto

View File

@@ -2,7 +2,6 @@
libc = module("libc",
kind = "lib",
output = "libc.a",
includes = [ "include" ],
deps = [ "j6" ],
sources = [

View File

@@ -35,6 +35,14 @@ struct counted
/// Return an iterator to the end of the array
inline const_iterator end() const { return offset_pointer<const T>(pointer, sizeof(T)*count); }
/// Advance the pointer by N items
inline counted<T> & operator+=(unsigned i) {
if (i > count) i = count;
pointer += i;
count -= i;
return *this;
}
};
/// Specialize for `void` which cannot be indexed or iterated
@@ -43,8 +51,24 @@ struct counted<void>
{
void *pointer;
size_t count;
/// Advance the pointer by N bytes
inline counted<void> & operator+=(unsigned i) {
if (i > count) i = count;
pointer = offset_pointer(pointer, i);
count -= i;
return *this;
}
};
using buffer = counted<void>;
template <typename T>
const T * read(buffer &b) {
const T *p = reinterpret_cast<const T*>(b.pointer);
b.pointer = offset_pointer(b.pointer, sizeof(T));
b.count -= sizeof(T);
return p;
}
} // namespace util

View File

@@ -3,6 +3,7 @@
module("drv.uefi_fb",
targets = [ "user" ],
deps = [ "libc" ],
description = "UEFI framebuffer driver",
sources = [
"font.cpp",
"main.cpp",

View File

@@ -3,6 +3,7 @@
init = module("srv.init",
targets = [ "user" ],
deps = [ "libc", "elf", "bootproto" ],
description = "Init server",
sources = [
"loader.cpp",
"main.cpp",

View File

@@ -3,6 +3,7 @@
module("testapp",
targets = [ "user" ],
deps = [ "libc" ],
description = "Testbed app",
sources = [
"io.cpp",
"main.cpp",