diff --git a/Cargo.lock b/Cargo.lock index 24e41cae..dc3092d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,15 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -144,6 +153,7 @@ version = "0.11.0" dependencies = [ "anyhow", "async-process", + "bootloader-boot-config", "bootloader_test_runner", "fatfs", "futures", @@ -151,7 +161,9 @@ dependencies = [ "gpt", "llvm-tools", "mbrman", + "serde_json", "tempfile", + "test_kernel_config_file", "test_kernel_default_settings", "test_kernel_higher_half", "test_kernel_map_phys_mem", @@ -159,6 +171,13 @@ dependencies = [ "test_kernel_ramdisk", ] +[[package]] +name = "bootloader-boot-config" +version = "0.11.0" +dependencies = [ + "serde", +] + [[package]] name = "bootloader-x86_64-bios-boot-sector" version = "0.11.0" @@ -188,11 +207,13 @@ dependencies = [ name = "bootloader-x86_64-bios-stage-4" version = "0.11.0" dependencies = [ + "bootloader-boot-config", "bootloader-x86_64-bios-common", "bootloader-x86_64-common", "bootloader_api", "log", "rsdp", + "serde-json-core", "usize_conversions", "x86_64", ] @@ -201,6 +222,7 @@ dependencies = [ name = "bootloader-x86_64-common" version = "0.11.0" dependencies = [ + "bootloader-boot-config", "bootloader_api", "conquer-once", "log", @@ -219,9 +241,11 @@ dependencies = [ name = "bootloader-x86_64-uefi" version = "0.11.0" dependencies = [ + "bootloader-boot-config", "bootloader-x86_64-common", "bootloader_api", "log", + "serde-json-core", "uefi", "x86_64", ] @@ -299,6 +323,12 @@ dependencies = [ "build_const", ] +[[package]] +name = "critical-section" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" + [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -478,6 +508,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin", + "stable_deref_trait", +] + [[package]] name = "instant" version = "0.1.12" @@ -487,6 +539,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "libc" version = "0.2.139" @@ -624,9 +682,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -721,23 +779,44 @@ dependencies = [ "log", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + [[package]] name = "serde" -version = "1.0.136" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -751,17 +830,39 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-json-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ec3c8fe427f45ee3aaa0ebb9f0d9ab8ae9ad05d12047fe7249ae5ea9374ff0" +dependencies = [ + "heapless", + "ryu", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook" version = "0.3.14" @@ -800,6 +901,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + [[package]] name = "spinning_top" version = "0.2.4" @@ -809,6 +919,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -820,9 +936,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -849,6 +965,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "test_kernel_config_file" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "test_kernel_default_settings" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c9bf81c6..e5bdc991 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" members = [ "api", "common", + "common/config", "uefi", "bios/boot_sector", "bios/stage-*", @@ -36,6 +37,7 @@ repository = "https://github.com/rust-osdev/bootloader" [workspace.dependencies] bootloader_api = { version = "0.11.0", path = "api" } bootloader-x86_64-common = { version = "0.11.0", path = "common" } +bootloader-boot-config = { version = "0.11.0", path = "common/config" } bootloader-x86_64-bios-common = { version = "0.11.0", path = "bios/common" } [features] @@ -52,6 +54,8 @@ fatfs = { version = "0.3.4", default-features = false, features = [ tempfile = "3.3.0" mbrman = { version = "0.5.1", optional = true } gpt = { version = "3.0.0", optional = true } +bootloader-boot-config = { version = "0.11.0", path = "common/config" } +serde_json = "1.0.91" [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } @@ -60,6 +64,7 @@ test_kernel_higher_half = { path = "tests/test_kernels/higher_half", artifact = test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact = "bin", target = "x86_64-unknown-none" } [profile.dev] panic = "abort" diff --git a/api/build.rs b/api/build.rs index 99235653..3621eeef 100644 --- a/api/build.rs +++ b/api/build.rs @@ -23,9 +23,6 @@ fn main() { (97, 9), (106, 9), (115, 9), - (124, 1), - (125, 1), - (126, 1), ]; let mut code = String::new(); diff --git a/api/src/config.rs b/api/src/config.rs index 7b91a4bd..a8c2b5ca 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use crate::{concat::*, version_info}; /// Allows configuring the bootloader behavior. @@ -26,21 +28,11 @@ pub struct BootloaderConfig { /// Configuration for the frame buffer that can be used by the kernel to display pixels /// on the screen. + #[deprecated( + since = "0.11.1", + note = "The frame buffer is now configured through the `BootConfig` struct when creating the bootable disk image" + )] pub frame_buffer: FrameBuffer, - - /// Configuration for changing the level of the filter of the messages that are shown in the - /// screen when booting. The default is 'Trace'. - pub log_level: LevelFilter, - - /// Whether the bootloader should print log messages to the framebuffer when booting. - /// - /// Enabled by default. - pub frame_buffer_logger_status: LoggerStatus, - - /// Whether the bootloader should print log messages to the serial port when booting. - /// - /// Enabled by default. - pub serial_logger_status: LoggerStatus, } impl BootloaderConfig { @@ -49,22 +41,18 @@ impl BootloaderConfig { 0x3D, ]; #[doc(hidden)] - pub const SERIALIZED_LEN: usize = 127; + pub const SERIALIZED_LEN: usize = 124; /// Creates a new default configuration with the following values: /// /// - `kernel_stack_size`: 80kiB /// - `mappings`: See [`Mappings::new_default()`] - /// - `frame_buffer`: See [`FrameBuffer::new_default()`] pub const fn new_default() -> Self { Self { kernel_stack_size: 80 * 1024, version: ApiVersion::new_default(), mappings: Mappings::new_default(), frame_buffer: FrameBuffer::new_default(), - log_level: LevelFilter::Trace, - frame_buffer_logger_status: LoggerStatus::Enable, - serial_logger_status: LoggerStatus::Enable, } } @@ -78,9 +66,6 @@ impl BootloaderConfig { mappings, kernel_stack_size, frame_buffer, - log_level, - frame_buffer_logger_status, - serial_logger_status, } = self; let ApiVersion { version_major, @@ -156,22 +141,12 @@ impl BootloaderConfig { }, ); - let buf = concat_115_9( + concat_115_9( buf, match minimum_framebuffer_width { Option::None => [0; 9], Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), }, - ); - - let log_level = concat_124_1(buf, (*log_level as u8).to_le_bytes()); - - let frame_buffer_logger_status = - concat_125_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes()); - - concat_126_1( - frame_buffer_logger_status, - (*serial_logger_status as u8).to_le_bytes(), ) } @@ -287,28 +262,6 @@ impl BootloaderConfig { (frame_buffer, s) }; - let (&log_level, s) = split_array_ref(s); - let log_level = LevelFilter::from_u8(u8::from_le_bytes(log_level)); - let log_level = match log_level { - Option::Some(level) => level, - Option::None => return Err("log_level invalid"), - }; - - let (&frame_buffer_logger_status, s) = split_array_ref(s); - let frame_buffer_logger_status = - LoggerStatus::from_u8(u8::from_le_bytes(frame_buffer_logger_status)); - let frame_buffer_logger_status = match frame_buffer_logger_status { - Option::Some(status) => status, - Option::None => return Err("frame_buffer_logger_status invalid"), - }; - - let (&serial_logger_status, s) = split_array_ref(s); - let serial_logger_status = LoggerStatus::from_u8(u8::from_le_bytes(serial_logger_status)); - let serial_logger_status = match serial_logger_status { - Option::Some(status) => status, - Option::None => return Err("serial_logger_status invalid"), - }; - if !s.is_empty() { return Err("unexpected rest"); } @@ -318,9 +271,6 @@ impl BootloaderConfig { kernel_stack_size: u64::from_le_bytes(kernel_stack_size), mappings, frame_buffer, - log_level, - frame_buffer_logger_status, - serial_logger_status, }) } @@ -331,9 +281,6 @@ impl BootloaderConfig { mappings: Mappings::random(), kernel_stack_size: rand::random(), frame_buffer: FrameBuffer::random(), - log_level: LevelFilter::Trace, - frame_buffer_logger_status: LoggerStatus::Enable, - serial_logger_status: LoggerStatus::Enable, } } } @@ -501,46 +448,6 @@ impl Mappings { } } -/// Configuration for the frame buffer used for graphical output. -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] -#[non_exhaustive] -pub struct FrameBuffer { - /// Instructs the bootloader to set up a framebuffer format that has at least the given height. - /// - /// If this is not possible, the bootloader will fall back to a smaller format. - pub minimum_framebuffer_height: Option, - /// Instructs the bootloader to set up a framebuffer format that has at least the given width. - /// - /// If this is not possible, the bootloader will fall back to a smaller format. - pub minimum_framebuffer_width: Option, -} - -impl FrameBuffer { - /// Creates a default configuration without any requirements. - pub const fn new_default() -> Self { - Self { - minimum_framebuffer_height: Option::None, - minimum_framebuffer_width: Option::None, - } - } - - #[cfg(test)] - fn random() -> FrameBuffer { - Self { - minimum_framebuffer_height: if rand::random() { - Option::Some(rand::random()) - } else { - Option::None - }, - minimum_framebuffer_width: if rand::random() { - Option::Some(rand::random()) - } else { - Option::None - }, - } - } -} - /// Specifies how the bootloader should map a memory region into the virtual address space. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Mapping { @@ -602,59 +509,42 @@ impl Default for Mapping { } } -/// An enum representing the available verbosity level filters of the logger. -/// -/// Based on -/// https://github.com/rust-lang/log/blob/dc32ab999f52805d5ce579b526bd9d9684c38d1a/src/lib.rs#L552-565 -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum LevelFilter { - /// A level lower than all log levels. - Off, - /// Corresponds to the `Error` log level. - Error, - /// Corresponds to the `Warn` log level. - Warn, - /// Corresponds to the `Info` log level. - Info, - /// Corresponds to the `Debug` log level. - Debug, - /// Corresponds to the `Trace` log level. - Trace, +/// Configuration for the frame buffer used for graphical output. +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub struct FrameBuffer { + /// Instructs the bootloader to set up a framebuffer format that has at least the given height. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. + pub minimum_framebuffer_height: Option, + /// Instructs the bootloader to set up a framebuffer format that has at least the given width. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. + pub minimum_framebuffer_width: Option, } -impl LevelFilter { - /// Converts a u8 into a Option - pub fn from_u8(value: u8) -> Option { - match value { - 0 => Some(Self::Off), - 1 => Some(Self::Error), - 2 => Some(Self::Warn), - 3 => Some(Self::Info), - 4 => Some(Self::Debug), - 5 => Some(Self::Trace), - _ => None, +impl FrameBuffer { + /// Creates a default configuration without any requirements. + pub const fn new_default() -> Self { + Self { + minimum_framebuffer_height: Option::None, + minimum_framebuffer_width: Option::None, } } -} - -/// An enum for enabling or disabling the different methods for logging. -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum LoggerStatus { - /// This method of logging is disabled - Disable, - /// This method of logging is enabled - Enable, -} -impl LoggerStatus { - /// Converts an u8 into a Option - pub fn from_u8(value: u8) -> Option { - match value { - 0 => Some(Self::Disable), - 1 => Some(Self::Enable), - _ => None, + #[cfg(test)] + fn random() -> FrameBuffer { + Self { + minimum_framebuffer_height: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, + minimum_framebuffer_width: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, } } } diff --git a/api/src/lib.rs b/api/src/lib.rs index 2f6d5f36..4ada525f 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -73,7 +73,7 @@ mod version_info { /// /// pub static BOOTLOADER_CONFIG: BootloaderConfig = { /// let mut config = BootloaderConfig::new_default(); -/// config.frame_buffer.minimum_framebuffer_height = Some(720); +/// config.kernel_stack_size = 90 * 1024; /// config /// }; /// diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 59a7737d..6d34e559 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -8,6 +8,7 @@ pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, pub ramdisk: Region, + pub config_file: Region, pub framebuffer: BiosFramebufferInfo, pub memory_map_addr: u32, pub memory_map_len: u16, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 1fecd0d8..137ca1a2 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -113,6 +113,17 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { } else { writeln!(screen::Writer, "Loaded ramdisk at {ramdisk_start:#p}").unwrap(); } + let config_file_start = ramdisk_start.wrapping_add(ramdisk_len.try_into().unwrap()); + let config_file_len = match try_load_file( + "config.json", + config_file_start, + &mut fs, + &mut disk, + disk_buffer, + ) { + Some(s) => s, + None => 0u64, + }; let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); @@ -148,6 +159,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { start: ramdisk_start as u64, len: ramdisk_len, }, + config_file: Region { + start: config_file_start as u64, + len: config_file_len, + }, memory_map_addr: memory_map.as_mut_ptr() as u32, memory_map_len: memory_map.len().try_into().unwrap(), framebuffer: BiosFramebufferInfo { diff --git a/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml index c02209fb..769ca657 100644 --- a/bios/stage-4/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -12,10 +12,12 @@ description = "Fourth BIOS stage of the `bootloader` crate" bootloader_api = { workspace = true } bootloader-x86_64-common = { workspace = true } bootloader-x86_64-bios-common = { workspace = true } +bootloader-boot-config = { workspace = true } log = "0.4.14" x86_64 = "0.14.8" rsdp = "2.0.0" usize_conversions = "0.2.0" +serde-json-core = "0.5.0" # This currently causes a cargo warning, but it is required for publishing to crates.io. # See https://github.com/rust-lang/cargo/issues/8264 for details. diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index d9e10843..f51f2252 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -2,10 +2,8 @@ #![no_main] use crate::memory_descriptor::MemoryRegion; -use bootloader_api::{ - config::{LevelFilter, LoggerStatus}, - info::{FrameBufferInfo, PixelFormat}, -}; +use bootloader_api::info::{FrameBufferInfo, PixelFormat}; +use bootloader_boot_config::{BootConfig, LevelFilter}; use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion}; use bootloader_x86_64_common::RawFrameBufferInfo; use bootloader_x86_64_common::{ @@ -113,13 +111,51 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { }; let kernel = Kernel::parse(kernel_slice); + let mut config_file_slice: Option<&[u8]> = None; + if info.config_file.len != 0 { + config_file_slice = { + let ptr = info.config_file.start as *mut u8; + unsafe { + Some(slice::from_raw_parts_mut( + ptr, + usize_from(info.config_file.len), + )) + } + }; + } + let mut error_loading_config: Option = None; + let mut config: BootConfig = match config_file_slice + .map(serde_json_core::from_slice) + .transpose() + { + Ok(data) => data.unwrap_or_default().0, + Err(err) => { + error_loading_config = Some(err); + Default::default() + } + }; + + #[allow(deprecated)] + if config.frame_buffer.minimum_framebuffer_height.is_none() { + config.frame_buffer.minimum_framebuffer_height = + kernel.config.frame_buffer.minimum_framebuffer_height; + } + #[allow(deprecated)] + if config.frame_buffer.minimum_framebuffer_width.is_none() { + config.frame_buffer.minimum_framebuffer_width = + kernel.config.frame_buffer.minimum_framebuffer_width; + } let framebuffer_info = init_logger( info.framebuffer, - kernel.config.log_level, - kernel.config.frame_buffer_logger_status, - kernel.config.serial_logger_status, + config.log_level, + config.frame_buffer_logging, + config.serial_logging, ); + if let Some(err) = error_loading_config { + log::warn!("Failed to deserialize the config file {:?}", err); + } + log::info!("4th Stage"); log::info!("{info:x?}"); log::info!("BIOS boot"); @@ -143,8 +179,8 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { fn init_logger( info: BiosFramebufferInfo, log_level: LevelFilter, - frame_buffer_logger_status: LoggerStatus, - serial_logger_status: LoggerStatus, + frame_buffer_logger_status: bool, + serial_logger_status: bool, ) -> FrameBufferInfo { let framebuffer_info = FrameBufferInfo { byte_len: info.region.len.try_into().unwrap(), diff --git a/common/Cargo.toml b/common/Cargo.toml index c830200a..afc8ed7d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,8 +10,8 @@ repository.workspace = true [dependencies] bootloader_api = { workspace = true } +bootloader-boot-config = { workspace = true } conquer-once = { version = "0.3.2", default-features = false } -log = "0.4.14" spinning_top = "0.2.4" usize_conversions = "0.2.0" x86_64 = { version = "0.14.8" } @@ -20,6 +20,7 @@ raw-cpuid = "10.2.0" rand = { version = "0.8.4", default-features = false } rand_hc = "0.3.1" uart_16550 = "0.2.18" +log = "0.4.17" [dependencies.noto-sans-mono-bitmap] version = "0.2.0" diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml new file mode 100644 index 00000000..560c6284 --- /dev/null +++ b/common/config/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bootloader-boot-config" +version.workspace = true +edition = "2021" +description = "The runtime configurations that are saved in a JSON file for the bootloader crate" +license.workspace = true +repository.workspace = true + +[dependencies] +serde = { version = "1.0.152", default-features = false, features = ["derive"] } diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs new file mode 100644 index 00000000..6599c09e --- /dev/null +++ b/common/config/src/lib.rs @@ -0,0 +1,76 @@ +#![no_std] + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(default)] +pub struct BootConfig { + /// Configuration for the frame buffer that can be used by the kernel to display pixels + /// on the screen. + pub frame_buffer: FrameBuffer, + + /// Configuration for changing the level of the filter of the messages that are shown in the + /// screen when booting. The default is 'Trace'. + pub log_level: LevelFilter, + + /// Whether the bootloader should print log messages to the framebuffer when booting. + /// + /// Enabled by default. + pub frame_buffer_logging: bool, + + /// Whether the bootloader should print log messages to the serial port when booting. + /// + /// Enabled by default. + pub serial_logging: bool, +} + +impl Default for BootConfig { + fn default() -> Self { + Self { + frame_buffer: Default::default(), + log_level: Default::default(), + frame_buffer_logging: true, + serial_logging: true, + } + } +} + +/// Configuration for the frame buffer used for graphical output. +#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub struct FrameBuffer { + /// Instructs the bootloader to set up a framebuffer format that has at least the given height. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. + pub minimum_framebuffer_height: Option, + /// Instructs the bootloader to set up a framebuffer format that has at least the given width. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. + pub minimum_framebuffer_width: Option, +} + +/// An enum representing the available verbosity level filters of the logger. +/// +/// Based on +/// https://github.com/rust-lang/log/blob/dc32ab999f52805d5ce579b526bd9d9684c38d1a/src/lib.rs#L552-565 +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum LevelFilter { + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} + +impl Default for LevelFilter { + fn default() -> Self { + Self::Trace + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index ad92556f..97778db1 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,10 +4,11 @@ use crate::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; use bootloader_api::{ - config::{LevelFilter, LoggerStatus, Mapping}, + config::Mapping, info::{FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, BootInfo, BootloaderConfig, }; +use bootloader_boot_config::LevelFilter; use core::{alloc::Layout, arch::asm, mem::MaybeUninit, slice}; use level_4_entries::UsedLevel4Entries; use usize_conversions::FromUsize; @@ -43,8 +44,8 @@ pub fn init_logger( framebuffer: &'static mut [u8], info: FrameBufferInfo, log_level: LevelFilter, - frame_buffer_logger_status: LoggerStatus, - serial_logger_status: LoggerStatus, + frame_buffer_logger_status: bool, + serial_logger_status: bool, ) { let logger = logger::LOGGER.get_or_init(move || { logger::LockedLogger::new( diff --git a/common/src/load_kernel.rs b/common/src/load_kernel.rs index 685bad0f..26798207 100644 --- a/common/src/load_kernel.rs +++ b/common/src/load_kernel.rs @@ -1,7 +1,6 @@ use crate::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; use bootloader_api::info::TlsTemplate; use core::{cmp, iter::Step, mem::size_of, ops::Add}; -use log::debug; use x86_64::{ align_up, diff --git a/common/src/logger.rs b/common/src/logger.rs index 741c64c5..582e65e2 100644 --- a/common/src/logger.rs +++ b/common/src/logger.rs @@ -1,5 +1,5 @@ use crate::{framebuffer::FrameBufferWriter, serial::SerialPort}; -use bootloader_api::{config::LoggerStatus, info::FrameBufferInfo}; +use bootloader_api::info::FrameBufferInfo; use conquer_once::spin::OnceCell; use core::fmt::Write; use spinning_top::Spinlock; @@ -18,17 +18,17 @@ impl LockedLogger { pub fn new( framebuffer: &'static mut [u8], info: FrameBufferInfo, - frame_buffer_logger_status: LoggerStatus, - serial_logger_status: LoggerStatus, + frame_buffer_logger_status: bool, + serial_logger_status: bool, ) -> Self { let framebuffer = match frame_buffer_logger_status { - LoggerStatus::Enable => Some(Spinlock::new(FrameBufferWriter::new(framebuffer, info))), - LoggerStatus::Disable => None, + true => Some(Spinlock::new(FrameBufferWriter::new(framebuffer, info))), + false => None, }; let serial = match serial_logger_status { - LoggerStatus::Enable => Some(Spinlock::new(SerialPort::new())), - LoggerStatus::Disable => None, + true => Some(Spinlock::new(SerialPort::new())), + false => None, }; LockedLogger { diff --git a/src/bios/mbr.rs b/src/bios/mbr.rs index 821bb7eb..6c7a9f0d 100644 --- a/src/bios/mbr.rs +++ b/src/bios/mbr.rs @@ -84,7 +84,7 @@ pub fn create_mbr_disk( assert_eq!( disk.stream_position() .context("failed to get disk image seek position")?, - (second_stage_start_sector * SECTOR_SIZE).into() + u64::from(second_stage_start_sector * SECTOR_SIZE) ); io::copy(&mut second_stage, &mut disk) .context("failed to copy second stage binary to MBR disk image")?; diff --git a/src/bios/mod.rs b/src/bios/mod.rs index 78ebe957..ec0db402 100644 --- a/src/bios/mod.rs +++ b/src/bios/mod.rs @@ -1,5 +1,7 @@ use crate::fat; use anyhow::Context; +use bootloader_boot_config::BootConfig; +use std::io::Write; use std::{ collections::BTreeMap, path::{Path, PathBuf}, @@ -15,6 +17,7 @@ const BIOS_STAGE_4: &str = "boot-stage-4"; pub struct BiosBoot { kernel: PathBuf, ramdisk: Option, + config: Option, } impl BiosBoot { @@ -23,15 +26,22 @@ impl BiosBoot { Self { kernel: kernel_path.to_owned(), ramdisk: None, + config: None, } } - /// Add a ramdisk file to the image + /// Add a ramdisk file to the image. pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { self.ramdisk = Some(ramdisk_path.to_owned()); self } + /// Configures the runtime behavior of the bootloader. + pub fn set_boot_config(&mut self, config: &BootConfig) -> &mut Self { + self.config = Some(serde_json::to_string(&config).expect("failed to serialize BootConfig")); + self + } + /// Create a bootable BIOS disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); @@ -69,6 +79,16 @@ impl BiosBoot { files.insert(crate::RAMDISK_FILE_NAME, ramdisk_path); } + let mut config_file: NamedTempFile; + + if let Some(config_ser) = &self.config { + config_file = NamedTempFile::new() + .context("failed to create temp file") + .unwrap(); + writeln!(config_file, "{config_ser}")?; + files.insert(crate::CONFIG_FILE_NAME, config_file.path()); + } + let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) .context("failed to create BIOS FAT filesystem")?; diff --git a/src/lib.rs b/src/lib.rs index 4f540c04..ff1894de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,5 +16,8 @@ pub use bios::BiosBoot; #[cfg(feature = "uefi")] pub use uefi::UefiBoot; +pub use bootloader_boot_config::BootConfig; + const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const RAMDISK_FILE_NAME: &str = "ramdisk"; +const CONFIG_FILE_NAME: &str = "boot.json"; diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs index e2d273dd..69024718 100644 --- a/src/uefi/mod.rs +++ b/src/uefi/mod.rs @@ -1,5 +1,7 @@ use crate::fat; use anyhow::Context; +use bootloader_boot_config::BootConfig; +use std::io::Write; use std::{ collections::BTreeMap, path::{Path, PathBuf}, @@ -13,6 +15,7 @@ mod pxe; pub struct UefiBoot { kernel: PathBuf, ramdisk: Option, + config: Option, } impl UefiBoot { @@ -21,15 +24,22 @@ impl UefiBoot { Self { kernel: kernel_path.to_owned(), ramdisk: None, + config: None, } } - /// Add a ramdisk file to the disk image + /// Add a ramdisk file to the disk image. pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { self.ramdisk = Some(ramdisk_path.to_owned()); self } + /// Configures the runtime behavior of the bootloader. + pub fn set_boot_config(&mut self, config: &BootConfig) -> &mut Self { + self.config = Some(serde_json::to_string(&config).expect("failed to serialize BootConfig")); + self + } + /// Create a bootable UEFI disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let fat_partition = self @@ -76,6 +86,16 @@ impl UefiBoot { files.insert(crate::RAMDISK_FILE_NAME, ramdisk_path); } + let mut config_file: NamedTempFile; + + if let Some(config_ser) = &self.config { + config_file = NamedTempFile::new() + .context("failed to create temp file") + .unwrap(); + writeln!(config_file, "{config_ser}")?; + files.insert(crate::CONFIG_FILE_NAME, config_file.path()); + } + let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) .context("failed to create UEFI FAT filesystem")?; diff --git a/tests/config_file.rs b/tests/config_file.rs new file mode 100644 index 00000000..af5c0f31 --- /dev/null +++ b/tests/config_file.rs @@ -0,0 +1,28 @@ +use bootloader_test_runner::run_test_kernel_internal; + +use bootloader::BootConfig; + +#[test] +fn basic_boot() { + let config: BootConfig = Default::default(); + run_test_kernel_internal( + env!("CARGO_BIN_FILE_TEST_KERNEL_CONFIG_FILE_basic_boot"), + None, + Some(&config), + ); +} + +#[test] +fn custom_options_boot() { + let config = BootConfig { + frame_buffer: Default::default(), + log_level: Default::default(), + frame_buffer_logging: false, + serial_logging: true, + }; + run_test_kernel_internal( + env!("CARGO_BIN_FILE_TEST_KERNEL_CONFIG_FILE_basic_boot_broken_config_file"), + None, + Some(&config), + ); +} diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index ad6ac614..eb11384b 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -1,3 +1,4 @@ +use bootloader::BootConfig; use std::{io::Read, path::Path, process::Command}; const QEMU_ARGS: &[&str] = &[ @@ -12,10 +13,23 @@ const QEMU_ARGS: &[&str] = &[ const SEPARATOR: &str = "\n____________________________________\n"; pub fn run_test_kernel(kernel_binary_path: &str) { - run_test_kernel_with_ramdisk(kernel_binary_path, None) + run_test_kernel_internal(kernel_binary_path, None, None) } - pub fn run_test_kernel_with_ramdisk(kernel_binary_path: &str, ramdisk_path: Option<&Path>) { + run_test_kernel_internal(kernel_binary_path, ramdisk_path, None) +} +pub fn run_test_kernel_with_config_file( + kernel_binary_path: &str, + config_file: Option<&BootConfig>, +) { + run_test_kernel_internal(kernel_binary_path, None, config_file) +} + +pub fn run_test_kernel_internal( + kernel_binary_path: &str, + ramdisk_path: Option<&Path>, + config_file_path: Option<&BootConfig>, +) { let kernel_path = Path::new(kernel_binary_path); #[cfg(feature = "uefi")] @@ -27,6 +41,9 @@ pub fn run_test_kernel_with_ramdisk(kernel_binary_path: &str, ramdisk_path: Opti if let Some(rdp) = ramdisk_path { uefi_builder.set_ramdisk(rdp); } + if let Some(cfp) = config_file_path { + uefi_builder.set_boot_config(cfp); + } uefi_builder.create_disk_image(&gpt_path).unwrap(); // create a TFTP folder with the kernel executable and UEFI bootloader for @@ -47,6 +64,9 @@ pub fn run_test_kernel_with_ramdisk(kernel_binary_path: &str, ramdisk_path: Opti if let Some(rdp) = ramdisk_path { bios_builder.set_ramdisk(rdp); } + if let Some(cfp) = config_file_path { + bios_builder.set_boot_config(cfp); + } bios_builder.create_disk_image(&mbr_path).unwrap(); run_test_kernel_on_bios(&mbr_path); diff --git a/tests/test_kernels/config_file/Cargo.toml b/tests/test_kernels/config_file/Cargo.toml new file mode 100644 index 00000000..990b9508 --- /dev/null +++ b/tests/test_kernels/config_file/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_kernel_config_file" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2021" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/config_file/src/bin/basic_boot.rs b/tests/test_kernels/config_file/src/bin/basic_boot.rs new file mode 100644 index 00000000..d6401dc7 --- /dev/null +++ b/tests/test_kernels/config_file/src/bin/basic_boot.rs @@ -0,0 +1,21 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use core::fmt::Write; +use test_kernel_config_file::{exit_qemu, serial, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Entered kernel with boot info: {:?}", boot_info).unwrap(); + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/config_file/src/bin/basic_boot_broken_config_file.rs b/tests/test_kernels/config_file/src/bin/basic_boot_broken_config_file.rs new file mode 100644 index 00000000..d6401dc7 --- /dev/null +++ b/tests/test_kernels/config_file/src/bin/basic_boot_broken_config_file.rs @@ -0,0 +1,21 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use core::fmt::Write; +use test_kernel_config_file::{exit_qemu, serial, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Entered kernel with boot info: {:?}", boot_info).unwrap(); + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/config_file/src/lib.rs b/tests/test_kernels/config_file/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/config_file/src/lib.rs @@ -0,0 +1,27 @@ +#![no_std] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub fn exit_qemu(exit_code: QemuExitCode) -> ! { + use x86_64::instructions::{nop, port::Port}; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + + loop { + nop(); + } +} + +pub fn serial() -> uart_16550::SerialPort { + let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) }; + port.init(); + port +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index a637db6e..b9f5ec29 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -11,6 +11,8 @@ repository.workspace = true [dependencies] bootloader_api = { workspace = true } bootloader-x86_64-common = { workspace = true } +bootloader-boot-config = { workspace = true } log = "0.4.14" uefi = "0.18.0" x86_64 = "0.14.8" +serde-json-core = "0.5.0" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index e6da4b50..47653ca1 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -1,16 +1,15 @@ #![no_std] #![no_main] -#![feature(abi_efiapi)] #![deny(unsafe_op_in_unsafe_fn)] use crate::memory_descriptor::UefiMemoryDescriptor; -use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; +use bootloader_api::info::FrameBufferInfo; +use bootloader_boot_config::BootConfig; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, }; use core::{ cell::UnsafeCell, - fmt::Write, ops::{Deref, DerefMut}, ptr, slice, }; @@ -72,50 +71,70 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } - st.stdout().clear().unwrap(); - writeln!( - st.stdout(), - "UEFI bootloader started; trying to load kernel" - ) - .unwrap(); let mut boot_mode = BootMode::Disk; + let config_file = load_config_file(image, &mut st, boot_mode); + let mut error_loading_config: Option = None; + let mut config: BootConfig = match config_file + .as_deref() + .map(serde_json_core::from_slice) + .transpose() + { + Ok(data) => data.unwrap_or_default().0, + Err(err) => { + error_loading_config = Some(err); + Default::default() + } + }; + let mut kernel = load_kernel(image, &mut st, boot_mode); if kernel.is_none() { - writeln!( - st.stdout(), - "Failed to load kernel via {:?}, trying TFTP", - boot_mode - ) - .unwrap(); // Try TFTP boot boot_mode = BootMode::Tftp; kernel = load_kernel(image, &mut st, boot_mode); } let kernel = kernel.expect("Failed to load kernel"); - writeln!(st.stdout(), "Trying to load ramdisk via {:?}", boot_mode).unwrap(); - // Ramdisk must load from same source, or not at all. - let ramdisk = load_ramdisk(image, &mut st, boot_mode); - writeln!( - st.stdout(), - "{}", - match ramdisk { - Some(_) => "Loaded ramdisk", - None => "Ramdisk not found.", - } - ) - .unwrap(); + #[allow(deprecated)] + if config.frame_buffer.minimum_framebuffer_height.is_none() { + config.frame_buffer.minimum_framebuffer_height = + kernel.config.frame_buffer.minimum_framebuffer_height; + } + #[allow(deprecated)] + if config.frame_buffer.minimum_framebuffer_width.is_none() { + config.frame_buffer.minimum_framebuffer_width = + kernel.config.frame_buffer.minimum_framebuffer_width; + } + let framebuffer = init_logger(image, &st, config); - let framebuffer = init_logger(image, &st, kernel.config); unsafe { *SYSTEM_TABLE.get() = None; } + log::info!("UEFI bootloader started"); - log::info!("Reading kernel and configuration from disk was successful"); + if let Some(framebuffer) = framebuffer { log::info!("Using framebuffer at {:#x}", framebuffer.addr); } + + if let Some(err) = error_loading_config { + log::warn!("Failed to deserialize the config file {:?}", err); + } else { + log::info!("Reading configuration from disk was successful"); + } + + log::info!("Trying to load ramdisk via {:?}", boot_mode); + // Ramdisk must load from same source, or not at all. + let ramdisk = load_ramdisk(image, &mut st, boot_mode); + + log::info!( + "{}", + match ramdisk { + Some(_) => "Loaded ramdisk", + None => "Ramdisk not found.", + } + ); + let mmap_storage = { let mut memory_map_size = st.boot_services().memory_map_size(); loop { @@ -194,6 +213,14 @@ fn load_ramdisk( load_file_from_boot_method(image, st, "ramdisk\0", boot_mode) } +fn load_config_file( + image: Handle, + st: &mut SystemTable, + boot_mode: BootMode, +) -> Option<&'static mut [u8]> { + load_file_from_boot_method(image, st, "config.json\0", boot_mode) +} + fn load_kernel( image: Handle, st: &mut SystemTable, @@ -446,7 +473,7 @@ fn create_page_tables( fn init_logger( image_handle: Handle, st: &SystemTable, - config: BootloaderConfig, + config: BootConfig, ) -> Option { let gop_handle = st .boot_services() @@ -517,8 +544,8 @@ fn init_logger( slice, info, config.log_level, - config.frame_buffer_logger_status, - config.serial_logger_status, + config.frame_buffer_logging, + config.serial_logging, ); Some(RawFrameBufferInfo { @@ -531,8 +558,10 @@ fn init_logger( #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { use core::arch::asm; + use core::fmt::Write; if let Some(st) = unsafe { &mut *SYSTEM_TABLE.get() } { + let _ = st.stdout().clear(); let _ = writeln!(st.stdout(), "{}", info); }