Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Start providing API docs for all public items
  • Loading branch information
phil-opp committed Jan 10, 2021
1 parent 86d1db7 commit 92b069a
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 7 deletions.
19 changes: 19 additions & 0 deletions src/binary/legacy_memory_region.rs
Expand Up @@ -5,9 +5,13 @@ use x86_64::{
PhysAddr,
};

/// Abstraction trait for a memory region returned by the UEFI or BIOS firmware.
pub trait LegacyMemoryRegion: Copy + core::fmt::Debug {
/// Returns the physical start address of the region.
fn start(&self) -> PhysAddr;
/// Returns the size of the region in bytes.
fn len(&self) -> u64;
/// Returns the type of the region, e.g. whether it is usable or reserved.
fn kind(&self) -> MemoryRegionKind;

fn set_start(&mut self, new_start: PhysAddr);
Expand All @@ -25,12 +29,19 @@ where
I: ExactSizeIterator<Item = D> + Clone,
I::Item: LegacyMemoryRegion,
{
/// Creates a new frame allocator based on the given legacy memory regions.
///
/// Skips the frame at physical address zero to avoid potential problems. For example
/// identity-mapping the frame at address zero is not valid in Rust, because Rust's `core`
/// library assumes that references can never point to virtual address `0`.
pub fn new(memory_map: I) -> Self {
// skip frame 0 because the rust core library does not see 0 as a valid address
let start_frame = PhysFrame::containing_address(PhysAddr::new(0x1000));
Self::new_starting_at(start_frame, memory_map)
}

/// Creates a new frame allocator based on the given legacy memory regions. Skips any frames
/// before the given `frame`.
pub fn new_starting_at(frame: PhysFrame, memory_map: I) -> Self {
Self {
original: memory_map.clone(),
Expand Down Expand Up @@ -72,6 +83,14 @@ where
.unwrap()
}

/// Converts this type to a boot info memory map.
///
/// The memory map is placed in the given `regions` slice. The length of the given slice
/// must be at least the value returned by [`len`]. Be aware that the value returned by
/// `len` might increase by 1 whenever [`allocate_frame`] is called, so the length should be
/// queried as late as possible.
///
/// The return slice is a subslice of `regions`, shortened to the actual number of regions.
pub fn construct_memory_map(
self,
regions: &mut [MaybeUninit<MemoryRegion>],
Expand Down
14 changes: 14 additions & 0 deletions src/binary/level_4_entries.rs
Expand Up @@ -5,11 +5,17 @@ use x86_64::{
};
use xmas_elf::program::ProgramHeader;

/// Keeps track of used entries in a level 4 page table.
///
/// Useful for determining a free virtual memory block, e.g. for mapping additional data.
pub struct UsedLevel4Entries {
entry_state: [bool; 512], // whether an entry is in use by the kernel
}

impl UsedLevel4Entries {
/// Initializes a new instance from the given ELF program segments.
///
/// Marks the virtual address range of all segments as used.
pub fn new<'a>(segments: impl Iterator<Item = ProgramHeader<'a>>) -> Self {
let mut used = UsedLevel4Entries {
entry_state: [false; 512],
Expand All @@ -31,6 +37,10 @@ impl UsedLevel4Entries {
used
}

/// Returns a unused level 4 entry and marks it as used.
///
/// Since this method marks each returned index as used, it can be used multiple times
/// to determine multiple unused virtual memory regions.
pub fn get_free_entry(&mut self) -> PageTableIndex {
let (idx, entry) = self
.entry_state
Expand All @@ -43,6 +53,10 @@ impl UsedLevel4Entries {
PageTableIndex::new(idx.try_into().unwrap())
}

/// Returns the virtual start address of an unused level 4 entry and marks it as used.
///
/// This is a convenience method around [`get_free_entry`], so all of its docs applies here
/// too.
pub fn get_free_address(&mut self) -> VirtAddr {
Page::from_page_table_indices_1gib(self.get_free_entry(), PageTableIndex::new(0))
.start_address()
Expand Down
4 changes: 4 additions & 0 deletions src/binary/load_kernel.rs
Expand Up @@ -269,6 +269,10 @@ where
}
}

/// Loads the kernel ELF file given in `bytes` in the given `page_table`.
///
/// Returns the kernel entry point address, it's thread local storage template (if any),
/// and a structure describing which level 4 page table entries are in use.
pub fn load_kernel(
bytes: &[u8],
page_table: &mut impl MapperAllSizes,
Expand Down
10 changes: 9 additions & 1 deletion src/binary/logger.rs
Expand Up @@ -7,8 +7,10 @@ use core::{
use font8x8::UnicodeFonts;
use spinning_top::Spinlock;

/// The global logger instance used for the `log` crate.
pub static LOGGER: OnceCell<LockedLogger> = OnceCell::uninit();

/// A [`Logger`] instance protected by a spinlock.
pub struct LockedLogger(Spinlock<Logger>);

/// Additional vertical space between lines
Expand All @@ -17,10 +19,14 @@ const LINE_SPACING: usize = 0;
const LOG_SPACING: usize = 2;

impl LockedLogger {
/// Create a new instance that logs to the given framebuffer.
pub fn new(framebuffer: &'static mut [u8], info: FrameBufferInfo) -> Self {
LockedLogger(Spinlock::new(Logger::new(framebuffer, info)))
}

/// Force-unlocks the logger to prevent a deadlock.
///
/// This method is not memory safe and should be only used when absolutely necessary.
pub unsafe fn force_unlock(&self) {
unsafe { self.0.force_unlock() };
}
Expand All @@ -40,6 +46,7 @@ impl log::Log for LockedLogger {
fn flush(&self) {}
}

/// Allows logging text to a pixel-based framebuffer.
pub struct Logger {
framebuffer: &'static mut [u8],
info: FrameBufferInfo,
Expand All @@ -48,6 +55,7 @@ pub struct Logger {
}

impl Logger {
/// Creates a new logger that uses the given framebuffer.
pub fn new(framebuffer: &'static mut [u8], info: FrameBufferInfo) -> Self {
let mut logger = Self {
framebuffer,
Expand All @@ -72,7 +80,7 @@ impl Logger {
self.x_pos = 0;
}

/// Erases all text on the screen
/// Erases all text on the screen.
pub fn clear(&mut self) {
self.x_pos = 0;
self.y_pos = 0;
Expand Down
71 changes: 66 additions & 5 deletions src/binary/mod.rs
Expand Up @@ -23,8 +23,11 @@ pub mod bios;
pub mod uefi;

pub mod legacy_memory_region;
/// Provides a type to keep track of used entries in a level 4 page table.
pub mod level_4_entries;
/// Implements a loader for the kernel ELF binary.
pub mod load_kernel;
/// Provides a logger type that logs output as text to pixel-based framebuffers.
pub mod logger;

// Contains the parsed configuration table from the kernel's Cargo.toml.
Expand All @@ -42,19 +45,29 @@ include!(concat!(env!("OUT_DIR"), "/bootloader_config.rs"));

const PAGE_SIZE: u64 = 4096;

/// Initialize a text-based logger using the given pixel-based framebuffer as output.
pub fn init_logger(framebuffer: &'static mut [u8], info: FrameBufferInfo) {
let logger = logger::LOGGER.get_or_init(move || logger::LockedLogger::new(framebuffer, info));
log::set_logger(logger).expect("logger already set");
log::set_max_level(log::LevelFilter::Trace);
}

/// Required system information that should be queried from the BIOS or UEFI firmware.
#[derive(Debug, Copy, Clone)]
pub struct SystemInfo {
/// Start address of the pixel-based framebuffer.
pub framebuffer_addr: PhysAddr,
/// Information about the framebuffer, including layout and pixel format.
pub framebuffer_info: FrameBufferInfo,
/// Address of the _Root System Description Pointer_ structure of the ACPI standard.
pub rsdp_addr: Option<PhysAddr>,
}

/// Loads the kernel ELF executable into memory and switches to it.
///
/// This function is a convenience function that first calls [`set_up_mappings`], then
/// [`create_boot_info`], and finally [`switch_to_kernel`]. The given arguments are passed
/// directly to these functions, so see their docs for more info.
pub fn load_and_switch_to_kernel<I, D>(
kernel_bytes: &[u8],
mut frame_allocator: LegacyFrameAllocator<I, D>,
Expand All @@ -81,7 +94,20 @@ where
switch_to_kernel(page_tables, mappings, boot_info, two_frames);
}

/// Sets up mappings for a kernel stack and the framebuffer
/// Sets up mappings for a kernel stack and the framebuffer.
///
/// The `kernel_bytes` slice should contain the raw bytes of the kernel ELF executable. The
/// `frame_allocator` argument should be created from the memory map. The `page_tables`
/// argument should point to the bootloader and kernel page tables. The function tries to parse
/// the ELF file and create all specified mappings in the kernel-level page table.
///
/// The `framebuffer_addr` and `framebuffer_size` fields should be set to the start address and
/// byte length the pixel-based framebuffer. These arguments are required because the functions
/// maps this framebuffer in the kernel-level page table, unless the `map_framebuffer` config
/// option is disabled.
///
/// This function reacts to unexpected situations (e.g. invalid kernel ELF file) with a panic, so
/// errors are not recoverable.
pub fn set_up_mappings<I, D>(
kernel_bytes: &[u8],
frame_allocator: &mut LegacyFrameAllocator<I, D>,
Expand Down Expand Up @@ -201,17 +227,31 @@ where
}
}

/// Contains the addresses of all memory mappings set up by [`set_up_mappings`].
pub struct Mappings {
/// The entry point address of the kernel.
pub entry_point: VirtAddr,
/// The stack end page of the kernel.
pub stack_end: Page,
/// Keeps track of used entries in the level 4 page table, useful for finding a free
/// virtual memory when needed.
pub used_entries: UsedLevel4Entries,
/// The start address of the framebuffer, if any.
pub framebuffer: Option<VirtAddr>,
/// The start address of the physical memory mapping, if enabled.
pub physical_memory_offset: Option<VirtAddr>,
/// The level 4 page table index of the recursive mapping, if enabled.
pub recursive_index: Option<PageTableIndex>,
/// The thread local storage template of the kernel executable, if it contains one.
pub tls_template: Option<TlsTemplate>,
}

/// Allocates and initializes the boot info struct and the memory map
/// Allocates and initializes the boot info struct and the memory map.
///
/// The boot info and memory map are mapped to both the kernel and bootloader
/// address space at the same address. This makes it possible to return a Rust
/// reference that is valid in both address spaces. The necessary physical frames
/// are taken from the given `frame_allocator`.
pub fn create_boot_info<I, D>(
mut frame_allocator: LegacyFrameAllocator<I, D>,
page_tables: &mut PageTables,
Expand Down Expand Up @@ -325,15 +365,26 @@ pub fn switch_to_kernel(
}
}

/// Provides access to the page tables of the bootloader and kernel address space.
pub struct PageTables {
/// Provides access to the page tables of the bootloader address space.
pub bootloader: OffsetPageTable<'static>,
/// Provides access to the page tables of the kernel address space (not active).
pub kernel: OffsetPageTable<'static>,
/// The physical frame where the level 4 page table of the kernel address space is stored.
///
/// Must be the page table that the `kernel` field of this struct refers to.
///
/// This frame is loaded into the `CR3` register on the final context switch to the kernel.
pub kernel_level_4_frame: PhysFrame,
}

/// Performs the actual context switch
/// Performs the actual context switch.
///
/// This function should stay small because it needs to be identity-mapped.
/// This function uses the given `frame_allocator` to identity map itself in the kernel-level
/// page table. This is required to avoid a page fault after the context switch. Since this
/// function is relatively small, only up to two physical frames are required from the frame
/// allocator, so the [`TwoFrames`] type can be used here.
unsafe fn context_switch(
addresses: Addresses,
mut kernel_page_table: OffsetPageTable,
Expand Down Expand Up @@ -367,18 +418,28 @@ unsafe fn context_switch(
unreachable!();
}

pub struct Addresses {
/// Memory addresses required for the context switch.
struct Addresses {
page_table: PhysFrame,
stack_top: VirtAddr,
entry_point: VirtAddr,
boot_info: &'static mut crate::boot_info::BootInfo,
}

/// Used for reversing two physical frames for identity mapping the context switch function.
///
/// In order to prevent a page fault, the context switch function must be mapped identically in
/// both address spaces. The context switch function is small, so this mapping requires only
/// two physical frames (one frame is not enough because the linker might place the function
/// directly before a page boundary). Since the frame allocator no longer exists when the
/// context switch function is invoked, we use this type to reserve two physical frames
/// beforehand.
pub struct TwoFrames {
frames: [Option<PhysFrame>; 2],
}

impl TwoFrames {
/// Creates a new instance by allocating two physical frames from the given frame allocator.
pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Self {
TwoFrames {
frames: [
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -11,7 +11,7 @@
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![deny(unsafe_op_in_unsafe_fn)]
//#![warn(missing_docs)]
#![warn(missing_docs)]

pub use crate::boot_info::BootInfo;

Expand Down

0 comments on commit 92b069a

Please sign in to comment.