diff --git a/Cargo.lock b/Cargo.lock index 42783548..57ccfb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -312,18 +312,18 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -443,13 +443,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.91" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7363ecc1a80d6a7467b322bfb16e95ac5e19f0b71ba2af3e6592f101820113" +checksum = "705535cf386e4b033cc7acdea55ec8710f3dde2f07457218791aac35c83be21f" dependencies = [ "bitflags", "log", @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7006b85ae8acaf2b448c5f1630a434caaacaedcc0907f12404e4e31c9dafcdb3" +checksum = "0b9917831bc5abb78c2e6a0f4fba2be165105ed53d288718c999e0efbd433bb7" dependencies = [ "proc-macro2", "quote", @@ -567,10 +567,10 @@ dependencies = [ ] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-ident" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "usize_conversions" diff --git a/src/lib.rs b/src/lib.rs index a6e4418e..86e1ecf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ use std::{ mod fat; mod gpt; mod mbr; +mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; @@ -121,3 +122,17 @@ pub fn create_bios_disk_image( Ok(()) } + +/// Prepare a folder for use with booting over UEFI_PXE. +/// +/// This places the bootloader executable under the path "bootloader". The +/// DHCP server should set the filename option to that path, otherwise the +/// bootloader won't be found. +pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + pxe::create_uefi_tftp_folder(bootloader_path, kernel_binary, out_path) + .context("failed to create UEFI PXE tftp folder")?; + + Ok(()) +} diff --git a/src/pxe.rs b/src/pxe.rs new file mode 100644 index 00000000..9329cec2 --- /dev/null +++ b/src/pxe.rs @@ -0,0 +1,32 @@ +use std::path::Path; + +use anyhow::Context; + +pub fn create_uefi_tftp_folder( + bootloader_path: &Path, + kernel_binary: &Path, + out_path: &Path, +) -> anyhow::Result<()> { + std::fs::create_dir_all(out_path) + .with_context(|| format!("failed to create out dir at {}", out_path.display()))?; + + let to = out_path.join("bootloader"); + std::fs::copy(bootloader_path, &to).with_context(|| { + format!( + "failed to copy bootloader from {} to {}", + bootloader_path.display(), + to.display() + ) + })?; + + let to = out_path.join("kernel-x86_64"); + std::fs::copy(kernel_binary, &to).with_context(|| { + format!( + "failed to copy kernel from {} to {}", + kernel_binary.display(), + to.display() + ) + })?; + + Ok(()) +} diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index a7292a59..c6096bb2 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -11,6 +11,12 @@ const QEMU_ARGS: &[&str] = &[ ]; pub fn run_test_kernel(kernel_binary_path: &str) { + run_test_kernel_on_uefi(kernel_binary_path); + run_test_kernel_on_uefi_pxe(kernel_binary_path); + // TODO: run tests with BIOS bootloader too +} + +pub fn run_test_kernel_on_uefi(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); let out_fat_path = kernel_path.with_extension("fat"); bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); @@ -19,8 +25,6 @@ pub fn run_test_kernel(kernel_binary_path: &str) { let out_mbr_path = kernel_path.with_extension("mbr"); bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); - // TODO: run tests with BIOS bootloader too - let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd .arg("-drive") @@ -42,3 +46,33 @@ pub fn run_test_kernel(kernel_binary_path: &str) { other => panic!("Test failed with unexpected exit code `{:?}`", other), } } + +pub fn run_test_kernel_on_uefi_pxe(kernel_binary_path: &str) { + let kernel_path = Path::new(kernel_binary_path); + let out_tftp_path = kernel_path.with_extension(".tftp"); + + bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); + + let mut run_cmd = Command::new("qemu-system-x86_64"); + run_cmd.arg("-netdev").arg(format!( + "user,id=net0,net=192.168.17.0/24,tftp={},bootfile=bootloader,id=net0", + out_tftp_path.display() + )); + run_cmd.arg("-device").arg("virtio-net-pci,netdev=net0"); + run_cmd.args(QEMU_ARGS); + run_cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + + let child_output = run_cmd.output().unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stderr) + .unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stdout) + .unwrap(); + + match child_output.status.code() { + Some(33) => {} // success + Some(35) => panic!("Test failed"), + other => panic!("Test failed with unexpected exit code `{:?}`", other), + } +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 362f1083..109b9298 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -11,5 +11,5 @@ license = "MIT/Apache-2.0" bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../common" } log = "0.4.14" -uefi = "0.13.0" +uefi = "0.16.0" x86_64 = "0.14.8" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 8df0688e..2a10a2db 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -17,9 +17,13 @@ use uefi::{ file::{File, FileAttribute, FileInfo, FileMode}, fs::SimpleFileSystem, }, + network::{ + pxe::{BaseCode, DhcpV4Packet}, + IpAddress, + }, }, table::boot::{AllocateType, MemoryDescriptor, MemoryType}, - Completion, + CStr16, CStr8, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, @@ -59,14 +63,14 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } - st.stdout().clear().unwrap().unwrap(); + st.stdout().clear().unwrap(); writeln!( st.stdout(), "UEFI bootloader started; trying to load kernel" ) .unwrap(); - let kernel = load_kernel(image, &st); + let kernel = load_kernel(image, &mut st); let (framebuffer_addr, framebuffer_info) = init_logger(&st, kernel.config); @@ -81,18 +85,17 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let mmap_storage = { let max_mmap_size = - st.boot_services().memory_map_size() + 8 * mem::size_of::(); + st.boot_services().memory_map_size().map_size + 8 * mem::size_of::(); let ptr = st .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)? - .log(); + .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)?; unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } }; log::trace!("exiting boot services"); let (system_table, memory_map) = st .exit_boot_services(image, mmap_storage) - .expect_success("Failed to exit boot services"); + .expect("Failed to exit boot services"); let mut frame_allocator = LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); @@ -123,42 +126,53 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { } fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { + let kernel_slice = load_kernel_file(image, st).expect("couldn't find kernel"); + Kernel::parse(kernel_slice) +} + +/// Try to load a kernel file from the boot device. +fn load_kernel_file(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { + load_kernel_file_from_disk(image, st) + .or_else(|| load_kernel_file_from_tftp_boot_server(image, st)) +} + +fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { let file_system_raw = { let ref this = st.boot_services(); let loaded_image = this .handle_protocol::(image) - .expect_success("Failed to retrieve `LoadedImage` protocol from handle"); + .expect("Failed to retrieve `LoadedImage` protocol from handle"); let loaded_image = unsafe { &*loaded_image.get() }; let device_handle = loaded_image.device(); let device_path = this .handle_protocol::(device_handle) - .expect_success("Failed to retrieve `DevicePath` protocol from image's device handle"); + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); let mut device_path = unsafe { &*device_path.get() }; let device_handle = this .locate_device_path::(&mut device_path) - .expect_success("Failed to locate `SimpleFileSystem` protocol on device path"); + .ok()?; this.handle_protocol::(device_handle) } - .unwrap() .unwrap(); let file_system = unsafe { &mut *file_system_raw.get() }; - let mut root = file_system.open_volume().unwrap().unwrap(); + let mut root = file_system.open_volume().unwrap(); + let mut buf = [0; 14 * 2]; + let filename = CStr16::from_str_with_buf("kernel-x86_64", &mut buf).unwrap(); let kernel_file_handle = root - .open("kernel-x86_64", FileMode::Read, FileAttribute::empty()) - .expect("Failed to load kernel (expected file named `kernel-x86_64`)") - .unwrap(); - let mut kernel_file = match kernel_file_handle.into_type().unwrap().unwrap() { + .open(filename, FileMode::Read, FileAttribute::empty()) + .expect("Failed to load kernel (expected file named `kernel-x86_64`)"); + let mut kernel_file = match kernel_file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, uefi::proto::media::file::FileType::Dir(_) => panic!(), }; let mut buf = [0; 500]; - let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap().unwrap(); + let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap(); let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); let kernel_ptr = st @@ -168,13 +182,72 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) - .unwrap() .unwrap() as *mut u8; unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; - kernel_file.read(kernel_slice).unwrap().unwrap(); + kernel_file.read(kernel_slice).unwrap(); - Kernel::parse(kernel_slice) + Some(kernel_slice) +} + +/// Try to load a kernel from a TFTP boot server. +fn load_kernel_file_from_tftp_boot_server( + image: Handle, + st: &SystemTable, +) -> Option<&'static mut [u8]> { + let this = st.boot_services(); + + // Try to locate a `BaseCode` protocol on the boot device. + + let loaded_image = this + .handle_protocol::(image) + .expect("Failed to retrieve `LoadedImage` protocol from handle"); + let loaded_image = unsafe { &*loaded_image.get() }; + + let device_handle = loaded_image.device(); + + let device_path = this + .handle_protocol::(device_handle) + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); + let mut device_path = unsafe { &*device_path.get() }; + + let device_handle = this.locate_device_path::(&mut device_path).ok()?; + + let base_code_raw = this.handle_protocol::(device_handle).unwrap(); + let base_code = unsafe { &mut *base_code_raw.get() }; + + // Find the TFTP boot server. + let mode = base_code.mode(); + assert!(mode.dhcp_ack_received); + let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); + let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); + + let filename = CStr8::from_bytes_with_nul(b"kernel-x86_64\0").unwrap(); + + // Determine the kernel file size. + let file_size = base_code + .tftp_get_file_size(&server_ip, filename) + .expect("Failed to query the kernel file size"); + let kernel_size = + usize::try_from(file_size).expect("The kernel file size should fit into usize"); + + // Allocate some memory for the kernel file. + let kernel_ptr = st + .boot_services() + .allocate_pages( + AllocateType::AnyPages, + MemoryType::LOADER_DATA, + ((kernel_size - 1) / 4096) + 1, + ) + .expect("Failed to allocate memory for the kernel file") as *mut u8; + let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + + // Load the kernel file. + base_code + .tftp_read_file(&server_ip, filename, Some(kernel_slice)) + .expect("Failed to read kernel file from the TFTP boot server"); + + Some(kernel_slice) } /// Creates page table abstraction types for both the bootloader and kernel page tables. @@ -247,11 +320,11 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F let gop = st .boot_services() .locate_protocol::() - .expect_success("failed to locate gop"); + .expect("failed to locate gop"); let gop = unsafe { &mut *gop.get() }; let mode = { - let modes = gop.modes().map(Completion::unwrap); + let modes = gop.modes(); match ( config .frame_buffer @@ -275,7 +348,7 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F }; if let Some(mode) = mode { gop.set_mode(&mode) - .expect_success("Failed to apply the desired display mode"); + .expect("Failed to apply the desired display mode"); } let mode_info = gop.current_mode_info();