diff --git a/README.md b/README.md index 6a0c0833..56540e77 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ The purposes of the individual assembly stages in this project are the following ## Future Plans -- [ ] Allow to configure the desired screen resolution. Right now we just use the first available VESA screen mode on BIOS and the default GOP mode on UEFI. - [ ] Create a `multiboot2` compatible disk image in addition to the BIOS and UEFI disk images. This would make it possible to use it on top of the GRUB bootloader. - [ ] Rewrite most of the BIOS assembly stages in Rust. This has already started. - [ ] Instead of linking the kernel bytes directly with the bootloader, use a filesystem (e.g. FAT) and load the kernel as a separate file. diff --git a/build.rs b/build.rs index 1a2473f1..c86cb670 100644 --- a/build.rs +++ b/build.rs @@ -201,7 +201,8 @@ mod binary { } // Parse configuration from the kernel's Cargo.toml - let config = match env::var("KERNEL_MANIFEST") { + let mut config = None; + let config_stream = match env::var("KERNEL_MANIFEST") { Err(env::VarError::NotPresent) => { panic!("The KERNEL_MANIFEST environment variable must be set for building the bootloader.\n\n\ Please use `cargo builder` for building."); @@ -260,10 +261,14 @@ mod binary { .cloned() .unwrap_or_else(|| toml::Value::Table(toml::map::Map::new())); - config_table - .try_into::() - .map(|c| quote! { #c }) - .unwrap_or_else(|err| { + let result = config_table.try_into::(); + match result { + Ok(p_config) => { + let stream = quote! { #p_config }; + config = Some(p_config); + stream + } + Err(err) => { let err = format!( "failed to parse bootloader config in {}:\n\n{}", path, @@ -272,7 +277,8 @@ mod binary { quote! { compile_error!(#err) } - }) + } + } } else { let err = format!( "no bootloader dependency in {}\n\n The \ @@ -286,21 +292,44 @@ mod binary { } } }; + let config = config; // Write config to file let file_path = out_dir.join("bootloader_config.rs"); - let mut file = File::create(file_path).expect("failed to create bootloader_config.rs"); + let mut file = File::create(file_path).expect("failed to create config file"); file.write_all( quote::quote! { - mod parsed_config { + /// Module containing the user-supplied configuration. + /// Public so that `bin/uefi.rs` can read framebuffer configuration. + pub mod parsed_config { use crate::config::Config; - pub const CONFIG: Config = #config; + /// The parsed configuration given by the user. + pub const CONFIG: Config = #config_stream; } } .to_string() .as_bytes(), ) - .expect("write to bootloader_config.rs failed"); + .expect("writing config failed"); + + // Write VESA framebuffer configuration + let file_path = out_dir.join("vesa_config.s"); + let mut file = File::create(file_path).expect("failed to create vesa config file"); + file.write_fmt(format_args!( + "vesa_minx: .2byte {}\n\ + vesa_miny: .2byte {}", + config + .as_ref() + .map(|c| c.minimum_framebuffer_width) + .flatten() + .unwrap_or(640), + config + .as_ref() + .map(|c| c.minimum_framebuffer_height) + .flatten() + .unwrap_or(480) + )) + .expect("writing config failed"); println!("cargo:rerun-if-env-changed=KERNEL"); println!("cargo:rerun-if-env-changed=KERNEL_MANIFEST"); @@ -333,6 +362,8 @@ mod binary { pub kernel_stack_address: Option, pub boot_info_address: Option, pub framebuffer_address: Option, + pub minimum_framebuffer_height: Option, + pub minimum_framebuffer_width: Option, } /// Convert to tokens suitable for initializing the `Config` struct. @@ -351,6 +382,8 @@ mod binary { let kernel_stack_address = optional(self.kernel_stack_address); let boot_info_address = optional(self.boot_info_address); let framebuffer_address = optional(self.framebuffer_address); + let minimum_framebuffer_height = optional(self.minimum_framebuffer_height); + let minimum_framebuffer_width = optional(self.minimum_framebuffer_width); tokens.extend(quote! { Config { map_physical_memory: #map_physical_memory, @@ -362,6 +395,8 @@ mod binary { kernel_stack_address: #kernel_stack_address, boot_info_address: #boot_info_address, framebuffer_address: #framebuffer_address, + minimum_framebuffer_height: #minimum_framebuffer_height, + minimum_framebuffer_width: #minimum_framebuffer_width }}); } } diff --git a/src/asm/vesa.s b/src/asm/vesa.s index e8c18026..adeaf4fd 100644 --- a/src/asm/vesa.s +++ b/src/asm/vesa.s @@ -124,9 +124,6 @@ vesa_returngood: xor eax, eax ret -vesa_minx: .2byte 640 -vesa_miny: .2byte 480 - vesa_modeok: .ascii ": Is this OK? (s)ave/(y)es/(n)o " .byte 8,8,8,8,0 diff --git a/src/bin/bios.rs b/src/bin/bios.rs index c39c45f2..ec9d939f 100644 --- a/src/bin/bios.rs +++ b/src/bin/bios.rs @@ -22,6 +22,7 @@ use x86_64::{PhysAddr, VirtAddr}; global_asm!(include_str!("../asm/stage_1.s")); global_asm!(include_str!("../asm/stage_2.s")); +global_asm!(include_str!(concat!(env!("OUT_DIR"), "/vesa_config.s"))); global_asm!(include_str!("../asm/vesa.s")); global_asm!(include_str!("../asm/e820.s")); global_asm!(include_str!("../asm/stage_3.s")); diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index 2a398383..59e4291c 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -14,7 +14,7 @@ static KERNEL: PageAligned<[u8; KERNEL_SIZE]> = PageAligned(KERNEL_BYTES); struct PageAligned(T); use bootloader::{ - binary::{legacy_memory_region::LegacyFrameAllocator, SystemInfo}, + binary::{legacy_memory_region::LegacyFrameAllocator, parsed_config::CONFIG, SystemInfo}, boot_info::FrameBufferInfo, }; use core::{mem, panic::PanicInfo, slice}; @@ -22,6 +22,7 @@ use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, proto::console::gop::{GraphicsOutput, PixelFormat}, table::boot::{MemoryDescriptor, MemoryType}, + Completion, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, @@ -149,6 +150,28 @@ fn init_logger(st: &SystemTable) -> (PhysAddr, FrameBufferInfo) { .expect_success("failed to locate gop"); let gop = unsafe { &mut *gop.get() }; + let mode = { + let modes = gop.modes().map(Completion::unwrap); + match ( + CONFIG.minimum_framebuffer_height, + CONFIG.minimum_framebuffer_width, + ) { + (Some(height), Some(width)) => modes + .filter(|m| { + let res = m.info().resolution(); + res.1 >= height && res.0 >= width + }) + .last(), + (Some(height), None) => modes.filter(|m| m.info().resolution().1 >= height).last(), + (None, Some(width)) => modes.filter(|m| m.info().resolution().0 >= width).last(), + _ => None, + } + }; + if let Some(mode) = mode { + gop.set_mode(&mode) + .expect_success("Failed to apply the desired display mode"); + } + let mode_info = gop.current_mode_info(); let mut framebuffer = gop.frame_buffer(); let slice = unsafe { slice::from_raw_parts_mut(framebuffer.as_mut_ptr(), framebuffer.size()) }; diff --git a/src/config.rs b/src/config.rs index 6859f947..4f6ee1a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -75,4 +75,16 @@ pub struct Config { /// /// Only considered if `map_framebuffer` is `true`. pub framebuffer_address: Option, + /// Desired minimum height of the framebuffer mode. + /// + /// Defaults to using the default mode if neither `minimum_framebuffer_height` or + /// `minimum_framebuffer_width` is supplied, and using the last available mode that + /// fits them if 1 or more is set. + pub minimum_framebuffer_height: Option, + /// Desired minimum width of the framebuffer mode. + /// + /// Defaults to using the default mode if neither `minimum_framebuffer_height` or + /// `minimum_framebuffer_width` is supplied, and using the last available mode that + /// fits them if 1 or more is set. + pub minimum_framebuffer_width: Option, }