From 10c8f6e4b54178eb1ad415774c3e3baed4be52c0 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Sun, 10 May 2020 01:42:22 -0700 Subject: [PATCH] Clean and document header files. - Add missing doc comments to header files - Move allocate_kernel_args to main.cpp - Split functions out into pointer_manipulation.h --- src/boot/console.h | 28 ++++---- src/boot/elf.h | 2 + src/boot/error.h | 9 +++ src/boot/fs.h | 13 ++++ src/boot/hardware.h | 2 + src/boot/loader.cpp | 2 - src/boot/loader.h | 13 +++- src/boot/main.cpp | 42 +++++++++++- src/boot/memory.cpp | 28 +------- src/boot/memory.h | 113 +++++++++++++++----------------- src/boot/pointer_manipulation.h | 41 ++++++++++++ 11 files changed, 191 insertions(+), 102 deletions(-) create mode 100644 src/boot/pointer_manipulation.h diff --git a/src/boot/console.h b/src/boot/console.h index a97c4d2..43acb95 100644 --- a/src/boot/console.h +++ b/src/boot/console.h @@ -1,3 +1,5 @@ +/// \file console.h +/// Text output and status message handling #pragma once #include #include @@ -6,6 +8,7 @@ namespace boot { +/// Object providing basic output functionality to the UEFI console class console { public: @@ -32,16 +35,30 @@ private: static console *s_console; }; +/// Scoped status line reporter. Prints a message and an "OK" if no errors +/// or warnings were reported before destruction, otherwise reports the +/// error or warning. class status_line { public: + /// Constructor. + /// \arg message Description of the operation in progress + /// \arg context If non-null, printed after `message` and a colon status_line(const wchar_t *message, const wchar_t *context = nullptr); ~status_line(); + /// Set the state to warning, and print a message. If the state is already at + /// warning or error, the state is unchanged but the message is still printed. + /// \arg message The warning message to print + /// \arg error If non-null, printed after `message` inline static void warn(const wchar_t *message, const wchar_t *error = nullptr) { if (s_current) s_current->do_warn(message, error); } + /// Set the state to error, and print a message. If the state is already at + /// error, the state is unchanged but the message is still printed. + /// \arg message The error message to print + /// \arg error If non-null, printed after `message` inline static void fail(const wchar_t *message, const wchar_t *error = nullptr) { if (s_current) s_current->do_fail(message, error); } @@ -60,15 +77,4 @@ private: static status_line *s_current; }; -uefi::status -con_get_framebuffer( - uefi::boot_services *bs, - void **buffer, - size_t *buffer_size, - uint32_t *hres, - uint32_t *vres, - uint32_t *rmask, - uint32_t *gmask, - uint32_t *bmask); - } // namespace boot diff --git a/src/boot/elf.h b/src/boot/elf.h index a399f07..2ca071a 100644 --- a/src/boot/elf.h +++ b/src/boot/elf.h @@ -1,3 +1,5 @@ +/// \file elf.h +/// Definitions and related constants for ELF64 structures #pragma once #include diff --git a/src/boot/error.h b/src/boot/error.h index 931d1da..6d4b5ac 100644 --- a/src/boot/error.h +++ b/src/boot/error.h @@ -1,3 +1,5 @@ +/// \file error.h +/// Error handling definitions #pragma once #include @@ -30,6 +32,8 @@ private: static handler *s_current; }; +/// Error handler using UEFI boot services. Integrates with `status_line` +/// to print formatted error messages to the screen. class uefi_handler : public handler { @@ -41,6 +45,8 @@ private: console &m_con; }; +/// Error handler that doesn't rely on UEFI. Sets status into CPU +/// registers and then causes a CPU #DE exception. class cpu_assert_handler : public handler { @@ -52,6 +58,9 @@ public: } // namespace error } // namespace boot +/// Helper macro to raise an error if an operation fails. +/// \arg s An expression evaluating to a UEFI status +/// \arg m The error message to use on failure #define try_or_raise(s, m) \ do { \ uefi::status _s = (s); \ diff --git a/src/boot/fs.h b/src/boot/fs.h index c993579..bd1af87 100644 --- a/src/boot/fs.h +++ b/src/boot/fs.h @@ -1,3 +1,5 @@ +/// \file fs.h +/// Definitions for dealing with UEFI's disk access functions #pragma once #include @@ -7,6 +9,7 @@ namespace boot { namespace fs { +/// A file or directory in a filesystem. class file { public: @@ -14,7 +17,15 @@ public: file(file &o); ~file(); + /// Open another file or directory, relative to this one. + /// \arg path Relative path to the target file from this one 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); @@ -28,6 +39,8 @@ private: uefi::boot_services *m_bs; }; +/// Get the filesystem this loader was loaded from. +/// \returns A `file` object representing the root directory of the volume file get_boot_volume(uefi::handle image, uefi::boot_services *bs); } // namespace fs diff --git a/src/boot/hardware.h b/src/boot/hardware.h index 4822010..d148403 100644 --- a/src/boot/hardware.h +++ b/src/boot/hardware.h @@ -1,3 +1,5 @@ +/// \file hardware.h +/// Functions and definitions for detecting and dealing with hardware #pragma once #include diff --git a/src/boot/loader.cpp b/src/boot/loader.cpp index 578e34c..71492a3 100644 --- a/src/boot/loader.cpp +++ b/src/boot/loader.cpp @@ -10,8 +10,6 @@ namespace boot { namespace loader { -using memory::offset_ptr; - static bool is_elfheader_valid(const elf::header *header) { diff --git a/src/boot/loader.h b/src/boot/loader.h index c4d1d9c..70480e9 100644 --- a/src/boot/loader.h +++ b/src/boot/loader.h @@ -1,15 +1,22 @@ +/// \file loader.h +/// Definitions for loading the kernel into memory #pragma once namespace boot { namespace loader { +/// Structure to hold information about loaded binary image. struct loaded_elf { - void *data; - uintptr_t vaddr; - uintptr_t entrypoint; + void *data; ///< Start of the kernel in memory + uintptr_t vaddr; ///< Virtual address to map to + uintptr_t entrypoint; ///< (Virtual) address of the kernel entrypoint }; +/// 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 +/// \returns A `loaded_elf` structure defining the loaded image loaded_elf load(const void *data, size_t size, uefi::boot_services *bs); } // namespace loader diff --git a/src/boot/main.cpp b/src/boot/main.cpp index d10b152..4842e55 100644 --- a/src/boot/main.cpp +++ b/src/boot/main.cpp @@ -85,6 +85,42 @@ detect_debug_mode(EFI_RUNTIME_SERVICES *run, kernel_args *header) { } */ +/// Allocate space for kernel args. Allocates enough space from pool +/// memory for the args header and `max_modules` module headers. +kernel::args::header * +allocate_args_structure( + uefi::boot_services *bs, + size_t max_modules) +{ + status_line status(L"Setting up kernel args memory"); + + kernel::args::header *args = nullptr; + + size_t args_size = + sizeof(kernel::args::header) + // The header itself + max_modules * sizeof(kernel::args::module); // The module structures + + try_or_raise( + bs->allocate_pool(memory::args_type, args_size, + reinterpret_cast(&args)), + L"Could not allocate argument memory"); + + bs->set_mem(args, args_size, 0); + + args->modules = + reinterpret_cast(args + 1); + args->num_modules = 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, @@ -104,6 +140,9 @@ load_module( return &module; } +/// 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. loader::loaded_elf bootloader_main_uefi(uefi::handle image, uefi::system_table *st, console &con) { @@ -115,7 +154,7 @@ bootloader_main_uefi(uefi::handle image, uefi::system_table *st, console &con) memory::init_pointer_fixup(bs, rs); kernel::args::header *args = - memory::allocate_args_structure(bs, max_modules); + allocate_args_structure(bs, max_modules); args->magic = kernel::args::magic; args->version = kernel::args::version; @@ -238,6 +277,7 @@ bootloader_main_uefi(uefi::handle image, uefi::system_table *st, console &con) */ } // namespace boot +/// The UEFI entrypoint for the loader. extern "C" uefi::status efi_main(uefi::handle image_handle, uefi::system_table *st) { diff --git a/src/boot/memory.cpp b/src/boot/memory.cpp index dafa734..9ec364f 100644 --- a/src/boot/memory.cpp +++ b/src/boot/memory.cpp @@ -1,8 +1,9 @@ #include #include +#include "kernel_args.h" -#include "error.h" #include "console.h" +#include "error.h" #include "memory.h" namespace boot { @@ -146,31 +147,6 @@ memory_virtualize(EFI_RUNTIME_SERVICES *runsvc, struct memory_map *map) } */ -kernel::args::header * -allocate_args_structure(uefi::boot_services *bs, size_t max_modules) -{ - status_line status(L"Setting up kernel args memory"); - - kernel::args::header *args = nullptr; - - size_t args_size = - sizeof(kernel::args::header) + // The header itself - max_modules * sizeof(kernel::args::module); // The module structures - - try_or_raise( - bs->allocate_pool(args_type, args_size, - reinterpret_cast(&args)), - L"Could not allocate argument memory"); - - bs->set_mem(args, args_size, 0); - - args->modules = - reinterpret_cast(args + 1); - args->num_modules = 0; - - return args; -} - efi_mem_map get_mappings(uefi::boot_services *bs) { diff --git a/src/boot/memory.h b/src/boot/memory.h index e1d202e..ee9714d 100644 --- a/src/boot/memory.h +++ b/src/boot/memory.h @@ -1,88 +1,83 @@ +/// \file memory.h +/// Memory-related constants and functions. #pragma once #include #include #include -#include "kernel_args.h" +#include "pointer_manipulation.h" namespace boot { namespace memory { +/// UEFI specifies that pages are always 4 KiB. constexpr size_t page_size = 0x1000; -constexpr uefi::memory_type args_type = static_cast(0x80000000); -constexpr uefi::memory_type module_type = static_cast(0x80000001); -constexpr uefi::memory_type kernel_type = static_cast(0x80000002); -constexpr uefi::memory_type table_type = static_cast(0x80000003); - -template -static T* offset_ptr(S* input, ptrdiff_t offset) { - return reinterpret_cast(reinterpret_cast(input) + offset); -} - +/// Get the number of pages needed to hold `bytes` bytes inline constexpr size_t bytes_to_pages(size_t bytes) { return ((bytes - 1) / page_size) + 1; } -void init_pointer_fixup( - uefi::boot_services *bs, - uefi::runtime_services *rs); +/// \defgroup memory_types +/// Custom UEFI memory type values used for data being passed to the kernel +/// @{ +/// Memory containing the kernel args structure +constexpr uefi::memory_type args_type = + static_cast(0x80000000); + +/// Memory containing any loaded modules to be passed to the kernel +constexpr uefi::memory_type module_type = + static_cast(0x80000001); + +/// Memory containing loaded kernel code and data sections +constexpr uefi::memory_type kernel_type = + static_cast(0x80000002); + +/// Memory containing page tables set up by the loader +constexpr uefi::memory_type table_type = + static_cast(0x80000003); + +/// @} + +/// \defgroup pointer_fixup +/// Memory virtualization pointer fixup functions. Handles changing affected pointers +/// when calling UEFI's `set_virtual_address_map` function to change the location of +/// runtime services in virtual memory. +/// @{ + +/// Set up the pointer fixup UEFI events. This registers the necessary callbacks for +/// runtime services to call when `set_virtual_address_map` is called. +void init_pointer_fixup(uefi::boot_services *bs, uefi::runtime_services *rs); + +/// Mark a given pointer as needing to be updated when doing pointer fixup. void mark_pointer_fixup(void **p); -kernel::args::header * allocate_args_structure(uefi::boot_services *bs, size_t max_modules); - -template -class offset_iterator -{ - T* m_t; - size_t m_off; -public: - offset_iterator(T* t, size_t offset=0) : m_t(t), m_off(offset) {} - - T* operator++() { m_t = offset_ptr(m_t, m_off); return m_t; } - T* operator++(int) { T* tmp = m_t; operator++(); return tmp; } - bool operator==(T* p) { return p == m_t; } - - T* operator*() const { return m_t; } - operator T*() const { return m_t; } - T* operator->() const { return m_t; } -}; +/// @} +/// Struct that represents UEFI's memory map. Contains a pointer to the map data +/// as well as the data on how to read it. struct efi_mem_map { - size_t length; - size_t size; - size_t key; - uint32_t version; - uefi::memory_descriptor *entries; + using desc = uefi::memory_descriptor; + using iterator = offset_iterator; + size_t length; ///< Total length of the map data + size_t size; ///< Size of an entry in the array + size_t key; ///< Key for detecting changes + uint32_t version; ///< Version of the `memory_descriptor` struct + desc *entries; ///< The array of UEFI descriptors + + /// Get the count of entries in the array inline size_t num_entries() const { return length / size; } - inline uefi::memory_descriptor * operator[](size_t i) { - size_t offset = i * size; - if (offset > length) return nullptr; - return offset_ptr(entries, offset); - } + /// Return an iterator to the beginning of the array + iterator begin() { return iterator(entries, size); } - offset_iterator begin() { return offset_iterator(entries, size); } - offset_iterator end() { return offset_ptr(entries, length); } + /// Return an iterator to the end of the array + iterator end() { return offset_ptr(entries, length); } }; +/// Get the memory map from UEFI. efi_mem_map get_mappings(uefi::boot_services *bs); -enum class memory_type -{ - free, - loader_used, - system_used -}; - -/* -EFI_STATUS memory_get_map_length(EFI_BOOT_SERVICES *bootsvc, size_t *size); -EFI_STATUS memory_get_map(EFI_BOOT_SERVICES *bootsvc, struct memory_map *map); -EFI_STATUS memory_dump_map(struct memory_map *map); - -void memory_virtualize(EFI_RUNTIME_SERVICES *runsvc, struct memory_map *map); -*/ - } // namespace boot } // namespace memory diff --git a/src/boot/pointer_manipulation.h b/src/boot/pointer_manipulation.h new file mode 100644 index 0000000..1a08694 --- /dev/null +++ b/src/boot/pointer_manipulation.h @@ -0,0 +1,41 @@ +/// \file pointer_manipulation.h +/// Helper functions and types for doing type-safe byte-wise pointer math. +#pragma once + +namespace boot { + +/// Return a pointer offset from `input` by `offset` bytes. +/// \tparam T Cast the return value to a pointer to `T` +/// \tparam S The type pointed to by the `input` pointer +template +inline T* offset_ptr(S* input, ptrdiff_t offset) { + return reinterpret_cast(reinterpret_cast(input) + offset); +} + +/// Iterator for an array of `T` whose size is known at runtime +/// \tparam T Type of the objects in the array, whose size might not be +/// what is returned by sizeof(T). +template +class offset_iterator +{ +public: + /// Constructor. + /// \arg t Pointer to the first item in the array + /// \arg off Offset applied to reach successive items. Default is 0, + /// which creates an effectively constant iterator. + offset_iterator(T* t, size_t off=0) : m_t(t), m_off(off) {} + + T* operator++() { m_t = offset_ptr(m_t, m_off); return m_t; } + T* operator++(int) { T* tmp = m_t; operator++(); return tmp; } + bool operator==(T* p) { return p == m_t; } + + T* operator*() const { return m_t; } + operator T*() const { return m_t; } + T* operator->() const { return m_t; } + +private: + T* m_t; + size_t m_off; +}; + +} // namespace boot