Skip to content

Commit

Permalink
Use new UEFI bootloader and UEFI bootloader API crate (#792)
Browse files Browse the repository at this point in the history
* Removes old assumptions/requirements that all sections
  in the kernel base image are loaded into physically-contiguous
  memory, especially the stack and bootloader info.
  * IOW, we stop relying upon a fixed `KERNEL_OFFSET` to
    calculate virtual addresses from physical addresses and vice versa;
    instead, we actually translate addresses via the initial page table.
  * Thus, we must obtain the address of the GDT used to boot
    secondary CPUs (APs on x86) and pass it through the various
    init routines so that it can be used when booting secondary CPUs.

* `PageRange` and `FrameRange` constructors will now return an
  empty range if invoked with a size of 0 bytes, instead of panicking.

* The major changes in this commit is to introduce new crates that
  support building Theseus for and booting it on UEFI bootloaders.
  * `tools/uefi-builder` is now multi-architecture,
    but aarch64 support is a WIP.
  * The separate `theseus-os/uefi-bootloader` repo is a fork of
    `rust-osdev/bootloader` but heavily changed to support Theseus's
    needs and additional architectures.
    * aarch64 is still a WIP here too.

* Currently we manually ensure that the same version of the
  `uefi-bootloader*` crates are used in the Theseus workspace
  and in the `tools/uefi-builder/*` crates. Ideally this would be
  ensured automatically in the future.

* `uefi-builder` consists of separate, per-arch crates; we can combine
  them once <rust-lang/cargo#10030> is fixed.

Co-authored-by: Kevin Boos <kevinaboos@gmail.com>
  • Loading branch information
tsoutsman and kevinaboos committed Jan 12, 2023
1 parent 0569939 commit 922cb09
Show file tree
Hide file tree
Showing 33 changed files with 2,481 additions and 370 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ ifeq ($(boot_spec), uefi)
--release \
-Z bindeps \
--manifest-path \
$(ROOT_DIR)/tools/uefi_builder/Cargo.toml -- \
$(ROOT_DIR)/tools/uefi_builder/x86_64/Cargo.toml -- \
--kernel $(nano_core_binary) \
--modules $(OBJECT_FILES_BUILD_DIR) \
--efi-image $(iso) \
Expand Down
7 changes: 3 additions & 4 deletions kernel/boot_info/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ kernel_config = { path = "../kernel_config" }
memory_structs = { path = "../memory_structs" }
multiboot2 = { version = "0.14", optional = true }

[dependencies.bootloader_api]
git = "https://github.com/theseus-os/bootloader"
branch = "theseus"
[dependencies.uefi-bootloader-api]
git = "https://github.com/theseus-os/uefi-bootloader"
optional = true

[features]
uefi = ["dep:bootloader_api"]
uefi = ["dep:uefi-bootloader-api"]
multiboot2 = ["dep:multiboot2"]
7 changes: 4 additions & 3 deletions kernel/boot_info/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub trait ElfSection {
/// Returns the section's starting virtual address.
fn start(&self) -> VirtualAddress;

/// Returns the section's length.
/// Returns the section's length in memory, as opposed to its length in the
/// ELF file.
fn len(&self) -> usize;

/// Returns whether the section is empty.
Expand Down Expand Up @@ -121,8 +122,8 @@ pub trait BootInformation: 'static {
&self,
) -> Result<Self::AdditionalReservedMemoryRegions, &'static str>;

/// Returns the end of the kernel's image in physical memory.
fn kernel_end(&self) -> Result<PhysicalAddress, &'static str>;
/// Returns the end of the kernel's image in memory.
fn kernel_end(&self) -> Result<VirtualAddress, &'static str>;

/// Returns the RSDP if it was provided by the bootloader.
fn rsdp(&self) -> Option<PhysicalAddress>;
Expand Down
9 changes: 6 additions & 3 deletions kernel/boot_info/src/multiboot2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,12 @@ impl crate::BootInformation for multiboot2::BootInformation {
.into_iter())
}

fn kernel_end(&self) -> Result<PhysicalAddress, &'static str> {
let reserved_region = kernel_memory_region(self)?;
Ok(reserved_region.start + reserved_region.len)
fn kernel_end(&self) -> Result<VirtualAddress, &'static str> {
use crate::ElfSection;
self.elf_sections()?
.map(|section| section.start() + section.len())
.max()
.ok_or("no elf sections")
}

fn rsdp(&self) -> Option<PhysicalAddress> {
Expand Down
91 changes: 37 additions & 54 deletions kernel/boot_info/src/uefi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::ElfSectionFlags;
use bootloader_api::info;
use core::iter::{Iterator, Peekable};
use kernel_config::memory::{KERNEL_OFFSET, KERNEL_STACK_SIZE_IN_PAGES, PAGE_SIZE};
use kernel_config::memory::{KERNEL_STACK_SIZE_IN_PAGES, PAGE_SIZE};
use memory_structs::{PhysicalAddress, VirtualAddress};

// TODO: Ideally this would be defined in nano_core. However, that would
Expand All @@ -11,66 +10,50 @@ use memory_structs::{PhysicalAddress, VirtualAddress};
pub const STACK_SIZE: usize = (KERNEL_STACK_SIZE_IN_PAGES + 2) * PAGE_SIZE;

/// A custom memory region kind used by the bootloader for the modules.
const MODULES_MEMORY_KIND: info::MemoryRegionKind = info::MemoryRegionKind::UnknownUefi(0x80000000);
const MODULES_MEMORY_KIND: uefi_bootloader_api::MemoryRegionKind =
uefi_bootloader_api::MemoryRegionKind::UnknownUefi(0x80000000);

pub struct MemoryRegion {
start: PhysicalAddress,
len: usize,
is_usable: bool,
}

impl From<info::MemoryRegion> for MemoryRegion {
fn from(info::MemoryRegion { start, end, kind }: info::MemoryRegion) -> Self {
Self {
start: PhysicalAddress::new_canonical(start as usize),
len: (end - start) as usize,
is_usable: matches!(kind, info::MemoryRegionKind::Usable),
}
}
}

impl crate::MemoryRegion for MemoryRegion {
impl crate::MemoryRegion for uefi_bootloader_api::MemoryRegion {
fn start(&self) -> PhysicalAddress {
self.start
PhysicalAddress::new_canonical(self.start)
}

fn len(&self) -> usize {
self.len
}

fn is_usable(&self) -> bool {
self.is_usable
matches!(self.kind, uefi_bootloader_api::MemoryRegionKind::Usable)
}
}

pub struct MemoryRegions {
inner: Peekable<core::slice::Iter<'static, info::MemoryRegion>>,
pub struct MemoryRegions<'a> {
inner: Peekable<core::slice::Iter<'a, uefi_bootloader_api::MemoryRegion>>,
}

impl Iterator for MemoryRegions {
type Item = MemoryRegion;
impl<'a> Iterator for MemoryRegions<'a> {
type Item = uefi_bootloader_api::MemoryRegion;

fn next(&mut self) -> Option<Self::Item> {
let mut area: MemoryRegion = (*self.inner.next()?).into();
let mut region = *self.inner.next()?;

// UEFI often separates contiguous memory into separate memory regions. We
// consolidate them to minimise the number of entries in the frame allocator's
// reserved and available lists.
while let Some(next) = self.inner.next_if(|next| {
let next = MemoryRegion::from(**next);
area.is_usable == next.is_usable && (area.start + area.len) == next.start
}) {
let next = MemoryRegion::from(*next);
area.len += next.len;
while let Some(next) = self
.inner
.next_if(|next| region.kind == next.kind && (region.start + region.len) == next.start)
{
region.len += next.len;
}

Some(area)
Some(region)
}
}

impl<'a> crate::ElfSection for &'a info::ElfSection {
impl<'a> crate::ElfSection for &'a uefi_bootloader_api::ElfSection {
fn name(&self) -> &str {
info::ElfSection::name(self)
uefi_bootloader_api::ElfSection::name(self)
}

fn start(&self) -> VirtualAddress {
Expand All @@ -86,14 +69,15 @@ impl<'a> crate::ElfSection for &'a info::ElfSection {
}
}

#[derive(Debug)]
pub struct Module {
inner: info::Module,
regions: &'static info::MemoryRegions,
inner: uefi_bootloader_api::Module,
regions: &'static uefi_bootloader_api::MemoryRegions,
}

impl crate::Module for Module {
fn name(&self) -> Result<&str, &'static str> {
Ok(info::Module::name(&self.inner))
Ok(uefi_bootloader_api::Module::name(&self.inner))
}

fn start(&self) -> PhysicalAddress {
Expand All @@ -113,8 +97,8 @@ impl crate::Module for Module {
}

pub struct Modules {
inner: &'static info::Modules,
regions: &'static info::MemoryRegions,
inner: &'static uefi_bootloader_api::Modules,
regions: &'static uefi_bootloader_api::MemoryRegions,
index: usize,
}

Expand All @@ -132,12 +116,12 @@ impl Iterator for Modules {
}
}

impl crate::BootInformation for &'static bootloader_api::BootInfo {
type MemoryRegion<'a> = MemoryRegion;
type MemoryRegions<'a> = MemoryRegions;
impl crate::BootInformation for &'static uefi_bootloader_api::BootInformation {
type MemoryRegion<'a> = uefi_bootloader_api::MemoryRegion;
type MemoryRegions<'a> = MemoryRegions<'a>;

type ElfSection<'a> = &'a info::ElfSection;
type ElfSections<'a> = core::slice::Iter<'a, info::ElfSection>;
type ElfSection<'a> = &'a uefi_bootloader_api::ElfSection;
type ElfSections<'a> = core::slice::Iter<'a, uefi_bootloader_api::ElfSection>;

type Module<'a> = Module;
type Modules<'a> = Modules;
Expand Down Expand Up @@ -176,24 +160,23 @@ impl crate::BootInformation for &'static bootloader_api::BootInfo {
Ok(core::iter::empty())
}

fn kernel_end(&self) -> Result<PhysicalAddress, &'static str> {
fn kernel_end(&self) -> Result<VirtualAddress, &'static str> {
use crate::ElfSection;

PhysicalAddress::new(
VirtualAddress::new(
self.elf_sections()?
.filter(|section| section.flags().contains(ElfSectionFlags::ALLOCATED))
.filter(|section| section.size > 0)
.map(|section| section.start + section.size)
.max()
.ok_or("couldn't find kernel end address")? as usize
- KERNEL_OFFSET,
.ok_or("couldn't find kernel end address")? as usize,
)
.ok_or("kernel physical end address was invalid")
.ok_or("kernel virtual end address was invalid")
}

fn rsdp(&self) -> Option<PhysicalAddress> {
self.rsdp_addr
.into_option()
.map(|address| PhysicalAddress::new_canonical(address as usize))
self.rsdp_address
.map(|address| PhysicalAddress::new_canonical(address))
}

fn stack_size(&self) -> Result<usize, &'static str> {
Expand Down
2 changes: 2 additions & 0 deletions kernel/captain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub fn init(
bsp_initial_stack: NoDrop<Stack>,
ap_start_realmode_begin: VirtualAddress,
ap_start_realmode_end: VirtualAddress,
ap_gdt: VirtualAddress,
rsdp_address: Option<PhysicalAddress>,
) -> Result<(), &'static str> {
#[cfg(mirror_log_to_vga)]
Expand Down Expand Up @@ -128,6 +129,7 @@ pub fn init(
&kernel_mmi_ref,
ap_start_realmode_begin,
ap_start_realmode_end,
ap_gdt,
Some(kernel_config::display::FRAMEBUFFER_MAX_RESOLUTION),
)?;
let cpu_count = ap_count + 1;
Expand Down
14 changes: 13 additions & 1 deletion kernel/memory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod paging;
pub use self::paging::{
PageTable, Mapper, Mutability, Mutable, Immutable,
MappedPages, BorrowedMappedPages, BorrowedSliceMappedPages,
translate,
};

pub use memory_structs::{Frame, Page, FrameRange, PageRange, VirtualAddress, PhysicalAddress};
Expand Down Expand Up @@ -220,7 +221,18 @@ pub fn init(
debug!("Initialized new frame allocator!");
frame_allocator::dump_frame_allocator_state();

page_allocator::init(VirtualAddress::new_canonical(boot_info.kernel_end()?.value()))?;
page_allocator::init(
VirtualAddress::new(
// We subtract 1 when translating because `kernel_end` returns an exclusive
// upper bound, which can cause problems if the kernel ends on a page boundary.
// We then add it back later to get the correct identity virtual address.
translate(boot_info.kernel_end()? - 1)
.ok_or("couldn't translate kernel end virtual address")?
.value()
+ 1,
)
.ok_or("couldn't convert kernel end physical address into virtual address")?,
)?;
debug!("Initialized new page allocator!");
page_allocator::dump_page_allocator_state();

Expand Down
5 changes: 5 additions & 0 deletions kernel/memory/src/paging/mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ use owned_borrowed_trait::{OwnedOrBorrowed, Owned, Borrowed};
/// that it is only invoked for `UnmappedFrames`.
pub(super) static INTO_ALLOCATED_FRAMES_FUNC: Once<fn(FrameRange) -> AllocatedFrames> = Once::new();

/// A convenience function to translate the given virtual address into a
/// physical address using the currently-active page table.
pub fn translate(virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
Mapper::from_current().translate(virtual_address)
}

pub struct Mapper {
p4: Unique<Table<Level4>>,
Expand Down
Loading

0 comments on commit 922cb09

Please sign in to comment.