diff --git a/uefi-test-runner/src/bin/shell_launcher.rs b/uefi-test-runner/src/bin/shell_launcher.rs index 123307bd6..0b7560c9e 100644 --- a/uefi-test-runner/src/bin/shell_launcher.rs +++ b/uefi-test-runner/src/bin/shell_launcher.rs @@ -13,6 +13,7 @@ extern crate alloc; use alloc::vec::Vec; use log::info; +use uefi::boot; use uefi::prelude::*; use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType, LoadedImageDevicePath}; @@ -21,13 +22,10 @@ use uefi::table::boot::LoadImageSource; /// Get the device path of the shell app. This is the same as the /// currently-loaded image's device path, but with the file path part changed. -fn get_shell_app_device_path<'a>( - boot_services: &BootServices, - storage: &'a mut Vec, -) -> &'a DevicePath { - let loaded_image_device_path = boot_services - .open_protocol_exclusive::(boot_services.image_handle()) - .expect("failed to open LoadedImageDevicePath protocol"); +fn get_shell_app_device_path(storage: &mut Vec) -> &DevicePath { + let loaded_image_device_path = + boot::open_protocol_exclusive::(boot::image_handle()) + .expect("failed to open LoadedImageDevicePath protocol"); let mut builder = DevicePathBuilder::with_vec(storage); for node in loaded_image_device_path.node_iter() { @@ -47,26 +45,23 @@ fn get_shell_app_device_path<'a>( #[entry] fn efi_main(image: Handle, st: SystemTable) -> Status { uefi::helpers::init().unwrap(); - let boot_services = st.boot_services(); let mut storage = Vec::new(); - let shell_image_path = get_shell_app_device_path(boot_services, &mut storage); + let shell_image_path = get_shell_app_device_path(&mut storage); // Load the shell app. - let shell_image_handle = boot_services - .load_image( - image, - LoadImageSource::FromDevicePath { - device_path: shell_image_path, - from_boot_manager: false, - }, - ) - .expect("failed to load shell app"); + let shell_image_handle = boot::load_image( + boot::image_handle(), + LoadImageSource::FromDevicePath { + device_path: shell_image_path, + from_boot_manager: false, + }, + ) + .expect("failed to load shell app"); // Set the command line passed to the shell app so that it will run the // test-runner app. This automatically turns off the five-second delay. - let mut shell_loaded_image = boot_services - .open_protocol_exclusive::(shell_image_handle) + let mut shell_loaded_image = boot::open_protocol_exclusive::(shell_image_handle) .expect("failed to open LoadedImage protocol"); let load_options = cstr16!(r"shell.efi test_runner.efi arg1 arg2"); unsafe { @@ -77,9 +72,7 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { } info!("launching the shell app"); - boot_services - .start_image(shell_image_handle) - .expect("failed to launch the shell app"); + boot::start_image(shell_image_handle).expect("failed to launch the shell app"); Status::SUCCESS } diff --git a/uefi-test-runner/src/boot/memory.rs b/uefi-test-runner/src/boot/memory.rs index 05e7897ca..01bcfc06a 100644 --- a/uefi-test-runner/src/boot/memory.rs +++ b/uefi-test-runner/src/boot/memory.rs @@ -1,25 +1,24 @@ -use uefi::table::boot::{AllocateType, BootServices, MemoryMap, MemoryMapMut, MemoryType}; +use uefi::boot; +use uefi::table::boot::{AllocateType, MemoryMap, MemoryMapMut, MemoryType}; use alloc::vec::Vec; -pub fn test(bt: &BootServices) { +pub fn test() { info!("Testing memory functions"); - allocate_pages(bt); + allocate_pages(); vec_alloc(); alloc_alignment(); - memory_map(bt); + memory_map(); } -fn allocate_pages(bt: &BootServices) { +fn allocate_pages() { info!("Allocating some pages of memory"); let ty = AllocateType::AnyPages; let mem_ty = MemoryType::LOADER_DATA; - let pgs = bt - .allocate_pages(ty, mem_ty, 1) - .expect("Failed to allocate a page of memory"); + let pgs = boot::allocate_pages(ty, mem_ty, 1).expect("Failed to allocate a page of memory"); assert_eq!(pgs % 4096, 0, "Page pointer is not page-aligned"); @@ -31,7 +30,7 @@ fn allocate_pages(bt: &BootServices) { buf[4095] = 0x23; // Clean up to avoid memory leaks. - unsafe { bt.free_pages(pgs, 1) }.unwrap(); + unsafe { boot::free_pages(pgs, 1) }.unwrap(); } // Simple test to ensure our custom allocator works with the `alloc` crate. @@ -60,15 +59,14 @@ fn alloc_alignment() { assert_eq!(value.as_ptr() as usize % 0x100, 0, "Wrong alignment"); } -fn memory_map(bt: &BootServices) { +fn memory_map() { info!("Testing memory map functions"); // Ensure that the memory map is freed after each iteration (on drop). // Otherwise, we will have an OOM. for _ in 0..200000 { - let mut memory_map = bt - .memory_map(MemoryType::LOADER_DATA) - .expect("Failed to retrieve UEFI memory map"); + let mut memory_map = + boot::memory_map(MemoryType::LOADER_DATA).expect("Failed to retrieve UEFI memory map"); memory_map.sort(); diff --git a/uefi-test-runner/src/boot/misc.rs b/uefi-test-runner/src/boot/misc.rs index 8b7422be4..4459e0101 100644 --- a/uefi-test-runner/src/boot/misc.rs +++ b/uefi-test-runner/src/boot/misc.rs @@ -4,51 +4,49 @@ use core::ptr::{self, NonNull}; use core::mem; use uefi::proto::unsafe_protocol; use uefi::table::boot::{ - BootServices, EventType, MemoryType, OpenProtocolAttributes, OpenProtocolParams, SearchType, - TimerTrigger, Tpl, + EventType, MemoryType, OpenProtocolAttributes, OpenProtocolParams, SearchType, TimerTrigger, + Tpl, }; -use uefi::table::{Boot, SystemTable}; +use uefi::{boot, system}; use uefi::{guid, Event, Guid, Identify}; -pub fn test(st: &SystemTable) { - let bt = st.boot_services(); +pub fn test() { info!("Testing timer..."); - test_timer(bt); + test_timer(); info!("Testing events..."); - test_event_callback(bt); - test_callback_with_ctx(bt); + test_event_callback(); + test_callback_with_ctx(); info!("Testing watchdog..."); - test_watchdog(bt); + test_watchdog(); info!("Testing protocol handler services..."); - test_register_protocol_notify(bt); - test_install_protocol_interface(bt); - test_reinstall_protocol_interface(bt); - test_uninstall_protocol_interface(bt); - test_install_configuration_table(st); + test_register_protocol_notify(); + test_install_protocol_interface(); + test_reinstall_protocol_interface(); + test_uninstall_protocol_interface(); + test_install_configuration_table(); } -fn test_timer(bt: &BootServices) { - let timer_event = unsafe { bt.create_event(EventType::TIMER, Tpl::APPLICATION, None, None) } +fn test_timer() { + let timer_event = unsafe { boot::create_event(EventType::TIMER, Tpl::APPLICATION, None, None) } .expect("Failed to create TIMER event"); let mut events = unsafe { [timer_event.unsafe_clone()] }; - bt.set_timer(&timer_event, TimerTrigger::Relative(5_0 /*00 ns */)) + boot::set_timer(&timer_event, TimerTrigger::Relative(5_0 /*00 ns */)) .expect("Failed to set timer"); - bt.wait_for_event(&mut events) - .expect("Wait for event failed"); + boot::wait_for_event(&mut events).expect("Wait for event failed"); } -fn test_event_callback(bt: &BootServices) { +fn test_event_callback() { extern "efiapi" fn callback(_event: Event, _ctx: Option>) { info!("Inside the event callback"); } let event = - unsafe { bt.create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(callback), None) } + unsafe { boot::create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(callback), None) } .expect("Failed to create custom event"); - bt.check_event(event).expect("Failed to check event"); + boot::check_event(event).expect("Failed to check event"); } -fn test_callback_with_ctx(bt: &BootServices) { +fn test_callback_with_ctx() { let mut data = 123u32; extern "efiapi" fn callback(_event: Event, ctx: Option>) { @@ -65,7 +63,7 @@ fn test_callback_with_ctx(bt: &BootServices) { let ctx = NonNull::new(ctx.cast::()).unwrap(); let event = unsafe { - bt.create_event( + boot::create_event( EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(callback), @@ -74,16 +72,15 @@ fn test_callback_with_ctx(bt: &BootServices) { .expect("Failed to create event with context") }; - bt.check_event(event).expect("Failed to check event"); + boot::check_event(event).expect("Failed to check event"); // Check that `data` was updated inside the event callback. assert_eq!(data, 456); } -fn test_watchdog(bt: &BootServices) { +fn test_watchdog() { // Disable the UEFI watchdog timer - bt.set_watchdog_timer(0, 0x10000, None) - .expect("Could not set watchdog timer"); + boot::set_watchdog_timer(0, 0x10000, None).expect("Could not set watchdog timer"); } /// Dummy protocol for tests @@ -96,10 +93,10 @@ unsafe extern "efiapi" fn _test_notify(_event: Event, _context: Option(), - ) - .unwrap() - .cast() - .as_ptr(); + let alloc: *mut TestProtocol = boot::allocate_pool( + MemoryType::BOOT_SERVICES_DATA, + mem::size_of::(), + ) + .unwrap() + .as_ptr() + .cast(); unsafe { alloc.write(TestProtocol { data: 123 }) }; let _ = unsafe { - bt.install_protocol_interface(None, &TestProtocol::GUID, alloc.cast()) + boot::install_protocol_interface(None, &TestProtocol::GUID, alloc.cast()) .expect("Failed to install protocol interface") }; - let _ = bt - .locate_handle_buffer(SearchType::from_proto::()) + let _ = boot::locate_handle_buffer(SearchType::from_proto::()) .expect("Failed to find protocol after it was installed"); } -fn test_reinstall_protocol_interface(bt: &BootServices) { +fn test_reinstall_protocol_interface() { info!("Reinstalling TestProtocol"); - let handle = bt - .locate_handle_buffer(SearchType::from_proto::()) + let handle = boot::locate_handle_buffer(SearchType::from_proto::()) .expect("Failed to find protocol to uninstall")[0]; unsafe { - let _ = bt.reinstall_protocol_interface( + let _ = boot::reinstall_protocol_interface( handle, &TestProtocol::GUID, ptr::null_mut(), @@ -151,11 +144,10 @@ fn test_reinstall_protocol_interface(bt: &BootServices) { } } -fn test_uninstall_protocol_interface(bt: &BootServices) { +fn test_uninstall_protocol_interface() { info!("Uninstalling TestProtocol"); - let handle = bt - .locate_handle_buffer(SearchType::from_proto::()) + let handle = boot::locate_handle_buffer(SearchType::from_proto::()) .expect("Failed to find protocol to uninstall")[0]; unsafe { @@ -163,49 +155,46 @@ fn test_uninstall_protocol_interface(bt: &BootServices) { // pointer. Open the protocol to get that pointer, making sure to drop // the `ScopedProtocol` _before_ uninstalling the protocol interface. let interface_ptr: *mut TestProtocol = { - let mut sp = bt - .open_protocol::( - OpenProtocolParams { - handle, - agent: bt.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .unwrap(); + let mut sp = boot::open_protocol::( + OpenProtocolParams { + handle, + agent: boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .unwrap(); assert_eq!(sp.data, 123); &mut *sp }; - bt.uninstall_protocol_interface(handle, &TestProtocol::GUID, interface_ptr.cast()) + boot::uninstall_protocol_interface(handle, &TestProtocol::GUID, interface_ptr.cast()) .expect("Failed to uninstall protocol interface"); - bt.free_pool(interface_ptr.cast()).unwrap(); + boot::free_pool(interface_ptr.cast()).unwrap(); } } -fn test_install_configuration_table(st: &SystemTable) { - let config = st - .boot_services() - .allocate_pool(MemoryType::ACPI_RECLAIM, 1) +fn test_install_configuration_table() { + let config = boot::allocate_pool(MemoryType::ACPI_RECLAIM, 1) .expect("Failed to allocate config table") .as_ptr(); unsafe { config.write(42) }; - let count = st.config_table().len(); + let count = system::with_config_table(|t| t.len()); const ID: Guid = guid!("3bdb3089-5662-42df-840e-3922ed6467c9"); unsafe { - st.boot_services() - .install_configuration_table(&ID, config.cast()) + boot::install_configuration_table(&ID, config.cast()) .expect("Failed to install configuration table"); } - assert_eq!(count + 1, st.config_table().len()); - let config_entry = st - .config_table() - .iter() - .find(|ct| ct.guid == ID) - .expect("Failed to find test config table"); - assert_eq!(unsafe { *(config_entry.address as *const u8) }, 42); + assert_eq!(count + 1, system::with_config_table(|t| t.len())); + let entry_addr = system::with_config_table(|t| { + t.iter() + .find(|ct| ct.guid == ID) + .expect("Failed to find test config table") + .address + }); + assert_eq!(unsafe { *(entry_addr as *const u8) }, 42); } diff --git a/uefi-test-runner/src/boot/mod.rs b/uefi-test-runner/src/boot/mod.rs index 74e817afe..0af34665e 100644 --- a/uefi-test-runner/src/boot/mod.rs +++ b/uefi-test-runner/src/boot/mod.rs @@ -3,37 +3,33 @@ use uefi::fs::FileSystem; use uefi::proto::console::text::Output; use uefi::proto::device_path::media::FilePath; use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath}; -use uefi::table::boot::{BootServices, LoadImageSource, SearchType}; -use uefi::table::{Boot, SystemTable}; -use uefi::{CString16, Identify}; +use uefi::table::boot::{LoadImageSource, SearchType}; +use uefi::{boot, table, CString16, Identify}; mod memory; mod misc; -pub fn test(st: &SystemTable) { - let bt = st.boot_services(); +pub fn test() { info!("Testing boot services"); - memory::test(bt); - misc::test(st); - test_locate_handle_buffer(bt); - test_load_image(bt); + memory::test(); + misc::test(); + test_locate_handle_buffer(); + test_load_image(); } -fn test_locate_handle_buffer(bt: &BootServices) { +fn test_locate_handle_buffer() { info!("Testing the `locate_handle_buffer` function"); { // search all handles - let handles = bt - .locate_handle_buffer(SearchType::AllHandles) + let handles = boot::locate_handle_buffer(SearchType::AllHandles) .expect("Failed to locate handle buffer"); assert!(!handles.is_empty(), "Could not find any handles"); } { // search by protocol - let handles = bt - .locate_handle_buffer(SearchType::ByProtocol(&Output::GUID)) + let handles = boot::locate_handle_buffer(SearchType::ByProtocol(&Output::GUID)) .expect("Failed to locate handle buffer"); assert!( !handles.is_empty(), @@ -47,7 +43,10 @@ fn test_locate_handle_buffer(bt: &BootServices) { /// /// It transitively tests the protocol [`LoadedImageDevicePath`] which is /// required as helper. -fn test_load_image(bt: &BootServices) { +fn test_load_image() { + let st = table::system_table_boot().unwrap(); + let bt = st.boot_services(); + /// The path of the loaded image executing this integration test. const LOADED_IMAGE_PATH: &str = r"\EFI\BOOT\TEST_RUNNER.EFI"; diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index 09853acdd..69a81bb31 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -13,7 +13,7 @@ use uefi::proto::console::serial::Serial; use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::messaging::Vendor; use uefi::table::boot::{MemoryMap, MemoryType}; -use uefi::{print, println, system, Result}; +use uefi::{print, println, system, table, Result}; mod boot; mod fs; @@ -21,13 +21,13 @@ mod proto; mod runtime; #[entry] -fn efi_main(image: Handle, mut st: SystemTable) -> Status { +fn efi_main() -> Status { // Initialize utilities (logging, memory allocation...) uefi::helpers::init().expect("Failed to initialize utilities"); // unit tests here - let firmware_vendor = st.firmware_vendor(); + let firmware_vendor = system::firmware_vendor(); info!("Firmware Vendor: {}", firmware_vendor); assert_eq!(firmware_vendor.to_string(), "EDK II"); @@ -40,7 +40,9 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { ); // Reset the console before running all the other tests. - st.stdout().reset(false).expect("Failed to reset stdout"); + system::with_stdout(|stdout| stdout.reset(false).expect("Failed to reset stdout")); + + let st = table::system_table_boot().unwrap(); // Ensure the tests are run on a version of UEFI we support. check_revision(st.uefi_revision()); @@ -48,14 +50,13 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Check the `uefi::system` module. check_system(&st); - // Test all the boot services. - let bt = st.boot_services(); - // Try retrieving a handle to the file system the image was booted from. - bt.get_image_file_system(image) + uefi::boot::get_image_file_system(uefi::boot::image_handle()) .expect("Failed to retrieve boot file system"); - boot::test(&st); + boot::test(); + + let mut st = table::system_table_boot().unwrap(); // Test all the supported protocols. proto::test(&mut st); @@ -66,7 +67,7 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { runtime::test(st.runtime_services()); - shutdown(st); + shutdown(table::system_table_boot().unwrap()); } fn check_revision(rev: uefi::table::Revision) { @@ -143,7 +144,7 @@ fn send_request_helper(serial: &mut Serial, request: HostRequest) -> Result { /// This must be called after opening the serial protocol in exclusive mode, as /// that breaks the connection to the console, which in turn prevents logs from /// getting to the host. -fn reconnect_serial_to_console(boot_services: &BootServices, serial_handle: Handle) { +fn reconnect_serial_to_console(serial_handle: Handle) { let mut storage = Vec::new(); // Create a device path that specifies the terminal type. let terminal_guid = if cfg!(target_arch = "aarch64") { @@ -160,18 +161,16 @@ fn reconnect_serial_to_console(boot_services: &BootServices, serial_handle: Hand .finalize() .unwrap(); - boot_services - .connect_controller(serial_handle, None, Some(terminal_device_path), true) + uefi::boot::connect_controller(serial_handle, None, Some(terminal_device_path), true) .expect("failed to reconnect serial to console"); } /// Send the `request` string to the host via the `serial` device, then /// wait up to 10 seconds to receive a reply. Returns an error if the /// reply is not `"OK\n"`. -fn send_request_to_host(bt: &BootServices, request: HostRequest) { - let serial_handle = bt - .get_handle_for_protocol::() - .expect("Failed to get serial handle"); +fn send_request_to_host(request: HostRequest) { + let serial_handle = + uefi::boot::get_handle_for_protocol::().expect("Failed to get serial handle"); // Open the serial protocol in exclusive mode. // @@ -184,8 +183,7 @@ fn send_request_to_host(bt: &BootServices, request: HostRequest) { // end with `connect_controller`. // // [console splitter driver]: https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/Universal/Console/ConSplitterDxe/ConSplitter.c - let mut serial = bt - .open_protocol_exclusive::(serial_handle) + let mut serial = uefi::boot::open_protocol_exclusive::(serial_handle) .expect("Could not open serial protocol"); // Send the request, but don't check the result yet so that first @@ -198,7 +196,7 @@ fn send_request_to_host(bt: &BootServices, request: HostRequest) { // device, which was broken when we opened the protocol in exclusive // mode above. drop(serial); - reconnect_serial_to_console(bt, serial_handle); + reconnect_serial_to_console(serial_handle); if let Err(err) = res { panic!("request failed: \"{request:?}\": {:?}", err.status()); @@ -212,7 +210,7 @@ fn shutdown(mut st: SystemTable) -> ! { // Tell the host that tests are done. We are about to exit boot // services, so we can't easily communicate with the host any later // than this. - send_request_to_host(st.boot_services(), HostRequest::TestsComplete); + send_request_to_host(HostRequest::TestsComplete); // Send a special log to the host so that we can verify that logging works // up until exiting boot services. See `reconnect_serial_to_console` for the @@ -222,7 +220,7 @@ fn shutdown(mut st: SystemTable) -> ! { info!("Testing complete, exiting boot services..."); // Exit boot services as a proof that it works :) - let (st, mmap) = unsafe { st.exit_boot_services(MemoryType::LOADER_DATA) }; + let mmap = unsafe { uefi::boot::exit_boot_services(MemoryType::LOADER_DATA) }; info!("Memory Map:"); for desc in mmap.entries() { @@ -239,9 +237,6 @@ fn shutdown(mut st: SystemTable) -> ! { #[cfg(target_arch = "x86_64")] { - // Prevent unused variable warning. - let _ = st; - use qemu_exit::QEMUExit; let custom_exit_success = 3; let qemu_exit_handle = qemu_exit::X86::new(0xF4, custom_exit_success); @@ -251,8 +246,7 @@ fn shutdown(mut st: SystemTable) -> ! { #[cfg(not(target_arch = "x86_64"))] { // Shut down the system - let rt = unsafe { st.runtime_services() }; - rt.reset( + uefi::runtime::reset( uefi::table::runtime::ResetType::SHUTDOWN, Status::SUCCESS, None, diff --git a/uefi-test-runner/src/proto/console/gop.rs b/uefi-test-runner/src/proto/console/gop.rs index 94bcbda40..7afb163fd 100644 --- a/uefi-test-runner/src/proto/console/gop.rs +++ b/uefi-test-runner/src/proto/console/gop.rs @@ -28,7 +28,7 @@ pub unsafe fn test(bt: &BootServices) { // `draw_fb` is skipped on aarch64, so the screenshot doesn't match. if cfg!(not(target_arch = "aarch64")) { - send_request_to_host(bt, HostRequest::Screenshot("gop_test")); + send_request_to_host(HostRequest::Screenshot("gop_test")); } } diff --git a/uefi-test-runner/src/proto/console/mod.rs b/uefi-test-runner/src/proto/console/mod.rs index 8dd7392ec..1ccdafa5b 100644 --- a/uefi-test-runner/src/proto/console/mod.rs +++ b/uefi-test-runner/src/proto/console/mod.rs @@ -1,13 +1,14 @@ use uefi::prelude::*; +use uefi::system; pub fn test(st: &mut SystemTable) { info!("Testing console protocols"); - stdout::test(st.stdout()); + system::with_stdout(stdout::test); let bt = st.boot_services(); unsafe { - serial::test(bt); + serial::test(); gop::test(bt); } pointer::test(bt); diff --git a/uefi-test-runner/src/proto/console/serial.rs b/uefi-test-runner/src/proto/console/serial.rs index a39d448ef..be20e0064 100644 --- a/uefi-test-runner/src/proto/console/serial.rs +++ b/uefi-test-runner/src/proto/console/serial.rs @@ -1,7 +1,6 @@ use crate::reconnect_serial_to_console; use uefi::proto::console::serial::{ControlBits, Serial}; -use uefi::table::boot::BootServices; -use uefi::{Result, ResultExt, Status}; +use uefi::{boot, Result, ResultExt, Status}; // For the duration of this function, the serial device is opened in // exclusive mode. That means logs will not work, which means we should @@ -41,7 +40,7 @@ fn serial_test_helper(serial: &mut Serial) -> Result { } } -pub unsafe fn test(bt: &BootServices) { +pub unsafe fn test() { // The serial device under aarch64 doesn't support the software // loopback feature needed for this test. if cfg!(target_arch = "aarch64") { @@ -49,13 +48,10 @@ pub unsafe fn test(bt: &BootServices) { } info!("Running serial protocol test"); - let handle = bt - .get_handle_for_protocol::() - .expect("missing Serial protocol"); + let handle = boot::get_handle_for_protocol::().expect("missing Serial protocol"); - let mut serial = bt - .open_protocol_exclusive::(handle) - .expect("failed to open serial protocol"); + let mut serial = + boot::open_protocol_exclusive::(handle).expect("failed to open serial protocol"); // Send the request, but don't check the result yet so that first // we can reconnect the console output for the logger. @@ -67,7 +63,7 @@ pub unsafe fn test(bt: &BootServices) { // device, which was broken when we opened the protocol in exclusive // mode above. drop(serial); - reconnect_serial_to_console(bt, handle); + reconnect_serial_to_console(handle); if let Err(err) = res { panic!("serial test failed: {:?}", err.status()); diff --git a/uefi-test-runner/src/proto/device_path.rs b/uefi-test-runner/src/proto/device_path.rs index 942837241..a9376da98 100644 --- a/uefi-test-runner/src/proto/device_path.rs +++ b/uefi-test-runner/src/proto/device_path.rs @@ -1,5 +1,6 @@ use alloc::string::ToString; use alloc::vec::Vec; +use uefi::boot; use uefi::prelude::*; use uefi::proto::device_path::text::*; use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath}; @@ -68,9 +69,9 @@ pub fn test(bt: &BootServices) { // test 2/2: test high-level to-string api { - let loaded_image_device_path = bt - .open_protocol_exclusive::(bt.image_handle()) - .expect("Failed to open LoadedImageDevicePath protocol"); + let loaded_image_device_path = + boot::open_protocol_exclusive::(bt.image_handle()) + .expect("Failed to open LoadedImageDevicePath protocol"); let device_path: &DevicePath = &loaded_image_device_path; let path_components = device_path diff --git a/uefi-test-runner/src/runtime/mod.rs b/uefi-test-runner/src/runtime/mod.rs index 9babae774..92769bfe4 100644 --- a/uefi-test-runner/src/runtime/mod.rs +++ b/uefi-test-runner/src/runtime/mod.rs @@ -1,8 +1,38 @@ +mod vars; + +use uefi::runtime::{self, Daylight, Time, TimeParams}; use uefi::table::runtime::RuntimeServices; pub fn test(rt: &RuntimeServices) { info!("Testing runtime services"); vars::test(rt); + test_time(); } -mod vars; +fn test_time() { + // Print the current time and time capabilities. + info!( + "Time with caps: {:?}", + runtime::get_time_and_caps().unwrap() + ); + + // Set the time. + let time = Time::new(TimeParams { + year: 2020, + month: 1, + day: 2, + hour: 3, + minute: 4, + second: 5, + nanosecond: 6, + time_zone: None, + daylight: Daylight::ADJUST_DAYLIGHT, + }) + .unwrap(); + unsafe { runtime::set_time(&time).unwrap() }; + + // Print the new time and check that the year was successfully changed. + let now = runtime::get_time().unwrap(); + info!("After setting time: {}", now); + assert_eq!(now.year(), 2020); +} diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index c00f2a651..c757db4fb 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -3,6 +3,8 @@ ## Added - `uefi::system` is a new module that provides freestanding functions for accessing fields of the global system table. +- `uefi::runtime` is a new module that provides freestanding functions for + runtime services using the global system table. - Add standard derives for `ConfigTableEntry`. - `PcrEvent`/`PcrEventInputs` impl `Align`, `Eq`, and `PartialEq`. - Added `PcrEvent::new_in_box` and `PcrEventInputs::new_in_box`. diff --git a/uefi/src/boot.rs b/uefi/src/boot.rs new file mode 100644 index 000000000..3aca360de --- /dev/null +++ b/uefi/src/boot.rs @@ -0,0 +1,1156 @@ +//! TODO + +// TODO +#![allow(clippy::missing_safety_doc)] + +use crate::data_types::PhysicalAddress; +use crate::proto::device_path::DevicePath; +use crate::proto::loaded_image::LoadedImage; +use crate::proto::media::fs::SimpleFileSystem; +use crate::proto::{Protocol, ProtocolPointer}; +use crate::table::boot::{ + AllocateType, BootServices, EventNotifyFn, EventType, LoadImageSource, MemoryMapOwned, + MemoryType, OpenProtocolAttributes, OpenProtocolParams, SearchType, TimerTrigger, Tpl, + IMAGE_HANDLE, +}; +use crate::table::{Boot, SystemTable}; +use crate::{table, Char16, Error, Event, Guid, Handle, Result, Status, StatusExt}; +use core::ffi::c_void; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::{self, NonNull}; +use core::slice; +use core::sync::atomic::Ordering; + +// TODO +fn boot_services() -> NonNull { + let st = table::system_table_boot().expect("boot services are not active"); + let ptr: *const _ = st.boot_services(); + NonNull::new(ptr.cast_mut()).unwrap() +} + +fn boot_services_raw() -> NonNull { + // OK to cast: `BootServices` is a `repr(transparent)` wrapper around + // the raw type. + boot_services().cast() +} + +#[track_caller] +fn stboot() -> SystemTable { + table::system_table_boot().expect("boot services are not available") +} + +/// Get the [`Handle`] of the currently-executing image. +pub fn image_handle() -> Handle { + let ptr = IMAGE_HANDLE.load(Ordering::Acquire); + // Safety: the image handle must be valid. We know it is, because it was + // set by `set_image_handle`, which has that same safety requirement. + unsafe { Handle::from_ptr(ptr) }.expect("set_image_handle has not been called") +} + +/// Update the global image [`Handle`]. +/// +/// This is called automatically in the `main` entry point as part +/// of [`uefi_macros::entry`]. It should not be called at any other +/// point in time, unless the executable does not use +/// [`uefi_macros::entry`], in which case it should be called once +/// before calling other `BootServices` functions. +/// +/// # Safety +/// +/// This function should only be called as described above. The +/// safety guarantees of [`BootServices::open_protocol_exclusive`] +/// rely on the global image handle being correct. +pub unsafe fn set_image_handle(image_handle: Handle) { + IMAGE_HANDLE.store(image_handle.as_ptr(), Ordering::Release); +} + +/// Allocates memory pages from the system. +/// +/// UEFI OS loaders should allocate memory of the type `LoaderData`. An `u64` +/// is returned even on 32-bit platforms because some hardware configurations +/// like Intel PAE enable 64-bit physical addressing on a 32-bit processor. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.AllocatePages()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::NOT_FOUND`] +pub fn allocate_pages( + ty: AllocateType, + mem_ty: MemoryType, + count: usize, +) -> Result { + stboot().boot_services().allocate_pages(ty, mem_ty, count) +} + +/// Frees memory pages allocated by UEFI. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.FreePages()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub unsafe fn free_pages(addr: PhysicalAddress, count: usize) -> Result { + stboot().boot_services().free_pages(addr, count) +} + +/// Raises a task's priority level and returns its previous level. +/// +/// The effect of calling `raise_tpl` with a `Tpl` that is below the current +/// one (which, sadly, cannot be queried) is undefined by the UEFI spec, +/// which also warns against remaining at high `Tpl`s for a long time. +/// +/// This function outputs an RAII guard that will automatically restore the +/// original `Tpl` when dropped. +/// +/// # Safety +/// +/// Raising a task's priority level can affect other running tasks and +/// critical processes run by UEFI. The highest priority level is the +/// most dangerous, since it disables interrupts. +#[must_use] +pub unsafe fn raise_tpl(tpl: Tpl) -> TplGuard { + TplGuard { + old_tpl: (boot_services_raw().as_mut().raise_tpl)(tpl), + } +} + +/// Retrieves the current memory map. +/// +/// The allocated buffer should be big enough to contain the memory map, +/// and a way of estimating how big it should be is by calling `memory_map_size`. +/// +/// The buffer must be aligned like a `MemoryDescriptor`. +/// +/// The returned key is a unique identifier of the current configuration of memory. +/// Any allocations or such will change the memory map's key. +/// +/// If you want to store the resulting memory map without having to keep +/// the buffer around, you can use `.copied().collect()` on the iterator. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.GetMemoryMap()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::BUFFER_TOO_SMALL`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn memory_map(mt: MemoryType) -> Result { + stboot().boot_services().memory_map(mt) +} + +/// Allocates from a memory pool. The pointer will be 8-byte aligned. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.AllocatePool()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn allocate_pool(mem_ty: MemoryType, size: usize) -> Result> { + stboot().boot_services().allocate_pool(mem_ty, size) +} + +/// Frees memory allocated from a pool. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.FreePool()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub unsafe fn free_pool(addr: *mut u8) -> Result { + stboot().boot_services().free_pool(addr) +} + +/// Creates an event +/// +/// This function creates a new event of the specified type and returns it. +/// +/// Events are created in a "waiting" state, and may switch to a "signaled" +/// state. If the event type has flag `NotifySignal` set, this will result in +/// a callback for the event being immediately enqueued at the `notify_tpl` +/// priority level. If the event type has flag `NotifyWait`, the notification +/// will be delivered next time `wait_for_event` or `check_event` is called. +/// In both cases, a `notify_fn` callback must be specified. +/// +/// # Safety +/// +/// This function is unsafe because callbacks must handle exit from boot +/// services correctly. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.CreateEvent()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +pub unsafe fn create_event( + event_ty: EventType, + notify_tpl: Tpl, + notify_fn: Option, + notify_ctx: Option>, +) -> Result { + stboot() + .boot_services() + .create_event(event_ty, notify_tpl, notify_fn, notify_ctx) +} + +/// Creates a new `Event` of type `event_type`. The event's notification function, context, +/// and task priority are specified by `notify_fn`, `notify_ctx`, and `notify_tpl`, respectively. +/// The `Event` will be added to the group of `Event`s identified by `event_group`. +/// +/// If no group is specified by `event_group`, this function behaves as if the same parameters +/// had been passed to `create_event()`. +/// +/// Event groups are collections of events identified by a shared `Guid` where, when one member +/// event is signaled, all other events are signaled and their individual notification actions +/// are taken. All events are guaranteed to be signaled before the first notification action is +/// taken. All notification functions will be executed in the order specified by their `Tpl`. +/// +/// A single event can only be part of a single event group. An event may be removed from an +/// event group by using `close_event()`. +/// +/// The `EventType` of an event uses the same values as `create_event()`, except that +/// `EventType::SIGNAL_EXIT_BOOT_SERVICES` and `EventType::SIGNAL_VIRTUAL_ADDRESS_CHANGE` +/// are not valid. +/// +/// If `event_type` has `EventType::NOTIFY_SIGNAL` or `EventType::NOTIFY_WAIT`, then `notify_fn` +/// mus be `Some` and `notify_tpl` must be a valid task priority level, otherwise these parameters +/// are ignored. +/// +/// More than one event of type `EventType::TIMER` may be part of a single event group. However, +/// there is no mechanism for determining which of the timers was signaled. +/// +/// This operation is only supported starting with UEFI 2.0; earlier +/// versions will fail with [`Status::UNSUPPORTED`]. +/// +/// # Safety +/// +/// The caller must ensure they are passing a valid `Guid` as `event_group`, if applicable. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.CreateEventEx()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +pub unsafe fn create_event_ex( + event_type: EventType, + notify_tpl: Tpl, + notify_fn: Option, + notify_ctx: Option>, + event_group: Option>, +) -> Result { + stboot().boot_services().create_event_ex( + event_type, + notify_tpl, + notify_fn, + notify_ctx, + event_group, + ) +} + +/// Sets the trigger for `EventType::TIMER` event. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.SetTimer()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn set_timer(event: &Event, trigger_time: TimerTrigger) -> Result { + stboot().boot_services().set_timer(event, trigger_time) +} + +/// Stops execution until an event is signaled. +/// +/// This function must be called at priority level `Tpl::APPLICATION`. If an +/// attempt is made to call it at any other priority level, an `Unsupported` +/// error is returned. +/// +/// The input `Event` slice is repeatedly iterated from first to last until +/// an event is signaled or an error is detected. The following checks are +/// performed on each event: +/// +/// * If an event is of type `NotifySignal`, then an `InvalidParameter` +/// error is returned with the index of the event that caused the failure. +/// * If an event is in the signaled state, the signaled state is cleared +/// and the index of the event that was signaled is returned. +/// * If an event is not in the signaled state but does have a notification +/// function, the notification function is queued at the event's +/// notification task priority level. If the execution of the event's +/// notification function causes the event to be signaled, then the +/// signaled state is cleared and the index of the event that was signaled +/// is returned. +/// +/// To wait for a specified time, a timer event must be included in the +/// Event slice. +/// +/// To check if an event is signaled without waiting, an already signaled +/// event can be used as the last event in the slice being checked, or the +/// check_event() interface may be used. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.WaitForEvent()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +pub fn wait_for_event(events: &mut [Event]) -> Result> { + stboot().boot_services().wait_for_event(events) +} + +/// Place 'event' in the signaled stated. If 'event' is already in the signaled state, +/// then nothing further occurs and `Status::SUCCESS` is returned. If `event` is of type +/// `EventType::NOTIFY_SIGNAL`, then the event's notification function is scheduled to +/// be invoked at the event's notification task priority level. +/// +/// This function may be invoked from any task priority level. +/// +/// If `event` is part of an event group, then all of the events in the event group are +/// also signaled and their notification functions are scheduled. +/// +/// When signaling an event group, it is possible to create an event in the group, signal +/// it, and then close the event to remove it from the group. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.SignalEvent()` in the UEFI Specification for more details. +/// +/// Currently, (as of UEFI Spec v2.9) this only returns `EFI_SUCCESS`. +pub fn signal_event(event: &Event) -> Result { + stboot().boot_services().signal_event(event) +} + +/// Removes `event` from any event group to which it belongs and closes it. If `event` was +/// registered with `register_protocol_notify()`, then the corresponding registration will +/// be removed. It is safe to call this function within the corresponding notify function. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.CloseEvent()` in the UEFI Specification for more details. +/// +/// Note: The UEFI Specification v2.9 states that this may only return `EFI_SUCCESS`, but, +/// at least for application based on EDK2 (such as OVMF), it may also return `EFI_INVALID_PARAMETER`. +/// To be safe, ensure that error codes are handled properly. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn close_event(event: Event) -> Result { + stboot().boot_services().close_event(event) +} + +/// Checks to see if an event is signaled, without blocking execution to wait for it. +/// +/// The returned value will be `true` if the event is in the signaled state, +/// otherwise `false` is returned. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.CheckEvent()` in the UEFI Specification for more details. +/// +/// Note: Instead of returning the `EFI_NOT_READY` error, as listed in the UEFI +/// Specification, this function will return `false`. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn check_event(event: Event) -> Result { + stboot().boot_services().check_event(event) +} + +/// Installs a protocol interface on a device handle. If the inner `Option` in `handle` is `None`, +/// one will be created and added to the list of handles in the system and then returned. +/// +/// When a protocol interface is installed, firmware will call all functions that have registered +/// to wait for that interface to be installed. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.InstallProtocolInterface()` in the UEFI Specification for +/// more details. +/// +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub unsafe fn install_protocol_interface( + handle: Option, + protocol: &Guid, + interface: *mut c_void, +) -> Result { + stboot() + .boot_services() + .install_protocol_interface(handle, protocol, interface) +} + +/// Reinstalls a protocol interface on a device handle. `old_interface` is replaced with `new_interface`. +/// These interfaces may be the same, in which case the registered protocol notifies occur for the handle +/// without replacing the interface. +/// +/// As with `install_protocol_interface`, any process that has registered to wait for the installation of +/// the interface is notified. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that there are no references to the `old_interface` that is being +/// removed. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.ReinstallProtocolInterface()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub unsafe fn reinstall_protocol_interface( + handle: Handle, + protocol: &Guid, + old_interface: *mut c_void, + new_interface: *mut c_void, +) -> Result<()> { + stboot().boot_services().reinstall_protocol_interface( + handle, + protocol, + old_interface, + new_interface, + ) +} + +/// Removes a protocol interface from a device handle. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that there are no references to a protocol interface +/// that has been removed. Some protocols may not be able to be removed as there is no information +/// available regarding the references. This includes Console I/O, Block I/O, Disk I/o, and handles +/// to device protocols. +/// +/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.UninstallProtocolInterface()` in the UEFI Specification for +/// more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub unsafe fn uninstall_protocol_interface( + handle: Handle, + protocol: &Guid, + interface: *mut c_void, +) -> Result<()> { + stboot() + .boot_services() + .uninstall_protocol_interface(handle, protocol, interface) +} + +/// Registers `event` to be signalled whenever a protocol interface is registered for +/// `protocol` by `install_protocol_interface()` or `reinstall_protocol_interface()`. +/// +/// Once `event` has been signalled, `BootServices::locate_handle()` can be used to identify +/// the newly (re)installed handles that support `protocol`. The returned `SearchKey` on success +/// corresponds to the `search_key` parameter in `locate_handle()`. +/// +/// Events can be unregistered from protocol interface notification by calling `close_event()`. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.RegisterProtocolNotify()` in the UEFI Specification for +/// more details. +/// +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn register_protocol_notify(protocol: &Guid, event: Event) -> Result<(Event, SearchType)> { + stboot() + .boot_services() + .register_protocol_notify(protocol, event) +} + +/// Enumerates all handles installed on the system which match a certain query. +/// +/// You should first call this function with `None` for the output buffer, +/// in order to retrieve the length of the buffer you need to allocate. +/// +/// The next call will fill the buffer with the requested data. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.LocateHandle()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::BUFFER_TOO_SMALL`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn locate_handle( + search_ty: SearchType, + output: Option<&mut [MaybeUninit]>, +) -> Result { + stboot().boot_services().locate_handle(search_ty, output) +} + +/// Locates the handle to a device on the device path that supports the specified protocol. +/// +/// The `device_path` is updated to point at the remaining part of the [`DevicePath`] after +/// the part that matched the protocol. For example, it can be used with a device path +/// that contains a file path to strip off the file system portion of the device path, +/// leaving the file path and handle to the file system driver needed to access the file. +/// +/// If the first node of `device_path` matches the +/// protocol, the `device_path` is advanced to the device path terminator node. If `device_path` +/// is a multi-instance device path, the function will operate on the first instance. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.LocateDevicePath()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn locate_device_path( + device_path: &mut &DevicePath, +) -> Result { + stboot() + .boot_services() + .locate_device_path::

(device_path) +} + +/// Find an arbitrary handle that supports a particular +/// [`Protocol`]. Returns [`NOT_FOUND`] if no handles support the +/// protocol. +/// +/// This method is a convenient wrapper around +/// [`BootServices::locate_handle_buffer`] for getting just one +/// handle. This is useful when you don't care which handle the +/// protocol is opened on. For example, [`DevicePathToText`] isn't +/// tied to a particular device, so only a single handle is expected +/// to exist. +/// +/// [`NOT_FOUND`]: Status::NOT_FOUND +/// [`DevicePathToText`]: uefi::proto::device_path::text::DevicePathToText +/// +/// # Example +/// +/// ``` +/// use uefi::proto::device_path::text::DevicePathToText; +/// use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams}; +/// use uefi::Handle; +/// # use uefi::Result; +/// +/// # fn get_fake_val() -> T { todo!() } +/// # fn test() -> Result { +/// # let boot_services: &BootServices = get_fake_val(); +/// # let image_handle: Handle = get_fake_val(); +/// let handle = boot_services.get_handle_for_protocol::()?; +/// let device_path_to_text = boot_services.open_protocol_exclusive::(handle)?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// +/// Returns [`NOT_FOUND`] if no handles support the requested protocol. +pub fn get_handle_for_protocol() -> Result { + stboot().boot_services().get_handle_for_protocol::

() +} + +/// Load an EFI image into memory and return a [`Handle`] to the image. +/// +/// There are two ways to load the image: by copying raw image data +/// from a source buffer, or by loading the image via the +/// [`SimpleFileSystem`] protocol. See [`LoadImageSource`] for more +/// details of the `source` parameter. +/// +/// The `parent_image_handle` is used to initialize the +/// `parent_handle` field of the [`LoadedImage`] protocol for the +/// image. +/// +/// If the image is successfully loaded, a [`Handle`] supporting the +/// [`LoadedImage`] and [`LoadedImageDevicePath`] protocols is +/// returned. The image can be started with [`start_image`] or +/// unloaded with [`unload_image`]. +/// +/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath +/// [`start_image`]: BootServices::start_image +/// [`unload_image`]: BootServices::unload_image +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.LoadImage()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::LOAD_ERROR`] +/// * [`uefi::Status::DEVICE_ERROR`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::SECURITY_VIOLATION`] +pub fn load_image(parent_image_handle: Handle, source: LoadImageSource) -> uefi::Result { + stboot() + .boot_services() + .load_image(parent_image_handle, source) +} + +/// Unload an EFI image. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.UnloadImage()` in the UEFI Specification for more details. +/// +/// As this function can return an error code from the unloaded image, any error type +/// can be returned by this function. +/// +/// The following error codes can also be returned while unloading an image: +/// +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::INVALID_PARAMETER`] +pub fn unload_image(image_handle: Handle) -> Result { + stboot().boot_services().unload_image(image_handle) +} + +/// Transfer control to a loaded image's entry point. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.StartImage()` in the UEFI Specification for more details. +/// +/// As this function can return an error code from the started image, any error type +/// can be returned by this function. +/// +/// The following error code can also be returned while starting an image: +/// +/// * [`uefi::Status::UNSUPPORTED`] +pub fn start_image(image_handle: Handle) -> Result { + stboot().boot_services().start_image(image_handle) +} + +/// Exits the UEFI application and returns control to the UEFI component +/// that started the UEFI application. +/// +/// # Safety +/// +/// This function is unsafe because it is up to the caller to ensure that +/// all resources allocated by the application is freed before invoking +/// exit and returning control to the UEFI component that started the UEFI +/// application. +pub unsafe fn exit( + image_handle: Handle, + exit_status: Status, + exit_data_size: usize, + exit_data: *mut Char16, +) -> ! { + stboot() + .boot_services() + .exit(image_handle, exit_status, exit_data_size, exit_data.cast()) +} + +/// Exit the UEFI boot services. +/// +/// After this function completes, UEFI hands over control of the hardware +/// to the executing OS loader, which implies that the UEFI boot services +/// are shut down and cannot be used anymore. Only UEFI configuration tables +/// and run-time services can be used. +/// +/// The memory map at the time of exiting boot services is returned. The map is +/// backed by a allocation with given `memory_type`. Since the boot services +/// function to free that memory is no longer available after calling +/// `exit_boot_services`, the allocation is live until the program ends. The +/// lifetime of the memory map is therefore `'static`. +/// +/// Note that once the boot services are exited, associated loggers and +/// allocators can't use the boot services anymore. For the corresponding +/// abstractions provided by this crate, invoking this function will +/// automatically disable them. +/// +/// # Errors +/// +/// This function will fail if it is unable to allocate memory for +/// the memory map, if it fails to retrieve the memory map, or if +/// exiting boot services fails (with up to one retry). +/// +/// All errors are treated as unrecoverable because the system is +/// now in an undefined state. Rather than returning control to the +/// caller, the system will be reset. +#[must_use] +pub unsafe fn exit_boot_services(memory_type: MemoryType) -> MemoryMapOwned { + table::system_table_boot() + .expect("boot services are not active") + .exit_boot_services(memory_type) + .1 +} + +/// Stalls the processor for an amount of time. +/// +/// The time is in microseconds. +pub fn stall(time: usize) { + stboot().boot_services().stall(time) +} + +/// Adds, updates, or removes a configuration table entry +/// from the EFI System Table. +/// +/// # Safety +/// +/// This relies on `table_ptr` being allocated in the +/// pool of type [`uefi::table::boot::MemoryType::RUNTIME_SERVICES_DATA`] +/// according to the specification. +/// Other memory types such as +/// [`uefi::table::boot::MemoryType::ACPI_RECLAIM`] +/// can be considered. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.InstallConfigurationTable()` in the UEFI +/// Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +pub unsafe fn install_configuration_table(guid_entry: &Guid, table_ptr: *const c_void) -> Result { + stboot() + .boot_services() + .install_configuration_table(guid_entry, table_ptr) +} + +/// Set the watchdog timer. +/// +/// UEFI will start a 5-minute countdown after an UEFI image is loaded. +/// The image must either successfully load an OS and call `ExitBootServices` +/// in that time, or disable the watchdog. +/// +/// Otherwise, the firmware will log the event using the provided numeric +/// code and data, then reset the system. +/// +/// This function allows you to change the watchdog timer's timeout to a +/// certain amount of seconds or to disable the watchdog entirely. It also +/// allows you to change what will be logged when the timer expires. +/// +/// The watchdog codes from 0 to 0xffff (65535) are reserved for internal +/// firmware use. Higher values can be used freely by applications. +/// +/// If provided, the watchdog data must be a null-terminated string +/// optionally followed by other binary data. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.SetWatchdogTimer()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::DEVICE_ERROR`] +pub fn set_watchdog_timer(timeout: usize, watchdog_code: u64, data: Option<&mut [u16]>) -> Result { + stboot() + .boot_services() + .set_watchdog_timer(timeout, watchdog_code, data) +} + +/// Connect one or more drivers to a controller. +/// +/// Usually one disconnects and then reconnects certain drivers +/// to make them rescan some state that changed, e.g. reconnecting +/// a `BlockIO` handle after your app changed the partitions somehow. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.ConnectController()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::SECURITY_VIOLATION`] +pub fn connect_controller( + controller: Handle, + driver_image: Option, + remaining_device_path: Option<&DevicePath>, + recursive: bool, +) -> Result { + stboot().boot_services().connect_controller( + controller, + driver_image, + remaining_device_path, + recursive, + ) +} + +/// Disconnect one or more drivers from a controller. +/// +/// See [`connect_controller`]. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.DisconnectController()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +/// * [`uefi::Status::DEVICE_ERROR`] +pub fn disconnect_controller( + controller: Handle, + driver_image: Option, + child: Option, +) -> Result { + stboot() + .boot_services() + .disconnect_controller(controller, driver_image, child) +} + +/// Open a protocol interface for a handle. +/// +/// See also [`open_protocol_exclusive`], which provides a safe +/// subset of this functionality. +/// +/// This function attempts to get the protocol implementation of a +/// handle, based on the protocol GUID. It is recommended that all +/// new drivers and applications use [`open_protocol_exclusive`] or +/// [`open_protocol`]. +/// +/// See [`OpenProtocolParams`] and [`OpenProtocolAttributes`] for +/// details of the input parameters. +/// +/// If successful, a [`ScopedProtocol`] is returned that will +/// automatically close the protocol interface when dropped. +/// +/// UEFI protocols are neither thread-safe nor reentrant, but the firmware +/// provides no mechanism to protect against concurrent usage. Such +/// protections must be implemented by user-level code, for example via a +/// global `HashSet`. +/// +/// # Safety +/// +/// This function is unsafe because it can be used to open a +/// protocol in ways that don't get tracked by the UEFI +/// implementation. This could allow the protocol to be removed from +/// a handle, or for the handle to be deleted entirely, while a +/// reference to the protocol is still active. The caller is +/// responsible for ensuring that the handle and protocol remain +/// valid until the `ScopedProtocol` is dropped. +/// +/// [`open_protocol`]: BootServices::open_protocol +/// [`open_protocol_exclusive`]: BootServices::open_protocol_exclusive +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.OpenProtocol()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::ALREADY_STARTED`] +pub unsafe fn open_protocol( + params: OpenProtocolParams, + attributes: OpenProtocolAttributes, +) -> Result> { + let mut interface = ptr::null_mut(); + (boot_services_raw().as_mut().open_protocol)( + params.handle.as_ptr(), + &P::GUID, + &mut interface, + params.agent.as_ptr(), + Handle::opt_to_ptr(params.controller), + attributes as u32, + ) + .to_result_with_val(|| { + let interface = if interface.is_null() { + None + } else { + Some(P::mut_ptr_from_ffi(interface)) + }; + + ScopedProtocol { + interface, + open_params: params, + } + }) +} + +/// Open a protocol interface for a handle in exclusive mode. +/// +/// If successful, a [`ScopedProtocol`] is returned that will +/// automatically close the protocol interface when dropped. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.OpenProtocol()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::ALREADY_STARTED`] +pub fn open_protocol_exclusive( + handle: Handle, +) -> Result> { + // Safety: opening in exclusive mode with the correct agent + // handle set ensures that the protocol cannot be modified or + // removed while it is open, so this usage is safe. + unsafe { + open_protocol::

( + OpenProtocolParams { + handle, + agent: image_handle(), + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + } +} + +/// Test whether a handle supports a protocol. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.OpenProtocol()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::ALREADY_STARTED`] +pub fn test_protocol(params: OpenProtocolParams) -> Result<()> { + stboot().boot_services().test_protocol::

(params) +} + +/// Get the list of protocol interface [`Guids`][Guid] that are installed +/// on a [`Handle`]. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.ProtocolsPerHandle()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +pub fn protocols_per_handle(handle: Handle) -> Result { + let mut protocols = ptr::null_mut(); + let mut count = 0; + + let mut status = unsafe { + (boot_services_raw().as_mut().protocols_per_handle)( + handle.as_ptr(), + &mut protocols, + &mut count, + ) + }; + + if !status.is_error() { + // Ensure that protocols isn't null, and that none of the GUIDs + // returned are null. + if protocols.is_null() { + status = Status::OUT_OF_RESOURCES; + } else { + let protocols: &[*const Guid] = unsafe { slice::from_raw_parts(protocols, count) }; + if protocols.iter().any(|ptr| ptr.is_null()) { + status = Status::OUT_OF_RESOURCES; + } + } + } + + status.to_result_with_val(|| ProtocolsPerHandle { + protocols, + count, + index: 0, + }) +} + +/// Returns an array of handles that support the requested protocol in a buffer allocated from +/// pool. +/// +/// # Errors +/// +/// See section `EFI_BOOT_SERVICES.LocateHandleBuffer()` in the UEFI Specification for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::NOT_FOUND`] +/// * [`uefi::Status::OUT_OF_RESOURCES`] +pub fn locate_handle_buffer(search_ty: SearchType) -> Result { + let mut num_handles: usize = 0; + let mut buffer: *mut uefi_raw::Handle = ptr::null_mut(); + + // Obtain the needed data from the parameters. + let (ty, guid, key) = match search_ty { + SearchType::AllHandles => (0, ptr::null(), ptr::null()), + SearchType::ByRegisterNotify(registration) => { + (1, ptr::null(), registration.0.as_ptr().cast_const()) + } + SearchType::ByProtocol(guid) => (2, guid as *const _, ptr::null()), + }; + + unsafe { + (boot_services_raw().as_mut().locate_handle_buffer)( + ty, + guid, + key, + &mut num_handles, + &mut buffer, + ) + } + .to_result_with_val(|| HandleBuffer { + count: num_handles, + buffer: buffer.cast(), + }) +} + +/// Retrieves a [`SimpleFileSystem`] protocol associated with the device the given +/// image was loaded from. +/// +/// # Errors +/// +/// This function can return errors from [`open_protocol_exclusive`] and +/// [`locate_device_path`]. See those functions for more details. +/// +/// * [`uefi::Status::INVALID_PARAMETER`] +/// * [`uefi::Status::UNSUPPORTED`] +/// * [`uefi::Status::ACCESS_DENIED`] +/// * [`uefi::Status::ALREADY_STARTED`] +/// * [`uefi::Status::NOT_FOUND`] +pub fn get_image_file_system(image_handle: Handle) -> Result> { + let loaded_image = open_protocol_exclusive::(image_handle)?; + + let device_handle = loaded_image + .device() + .ok_or(Error::new(Status::UNSUPPORTED, ()))?; + let device_path = open_protocol_exclusive::(device_handle)?; + + let device_handle = locate_device_path::(&mut &*device_path)?; + + open_protocol_exclusive(device_handle) +} + +/// An open protocol interface. Automatically closes the protocol +/// interface on drop. +/// +/// Most protocols have interface data associated with them. `ScopedProtocol` +/// implements [`Deref`] and [`DerefMut`] to access this data. A few protocols +/// (such as [`DevicePath`] and [`LoadedImageDevicePath`]) may be installed with +/// null interface data, in which case [`Deref`] and [`DerefMut`] will +/// panic. The [`get`] and [`get_mut`] methods may be used to access the +/// optional interface data without panicking. +/// +/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath +/// [`get`]: ScopedProtocol::get +/// [`get_mut`]: ScopedProtocol::get_mut +#[derive(Debug)] +pub struct ScopedProtocol { + interface: Option<*mut P>, + open_params: OpenProtocolParams, +} + +impl Drop for ScopedProtocol

{ + fn drop(&mut self) { + let status = unsafe { + (boot_services_raw().as_mut().close_protocol)( + self.open_params.handle.as_ptr(), + &P::GUID, + self.open_params.agent.as_ptr(), + Handle::opt_to_ptr(self.open_params.controller), + ) + }; + // All of the error cases for close_protocol boil down to + // calling it with a different set of parameters than what was + // passed to open_protocol. The public API prevents such errors, + // and the error can't be propagated out of drop anyway, so just + // assert success. + assert_eq!(status, Status::SUCCESS); + } +} + +impl Deref for ScopedProtocol

{ + type Target = P; + + #[track_caller] + fn deref(&self) -> &Self::Target { + unsafe { &*self.interface.unwrap() } + } +} + +impl DerefMut for ScopedProtocol

{ + #[track_caller] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.interface.unwrap() } + } +} + +impl ScopedProtocol

{ + /// Get the protocol interface data, or `None` if the open protocol's + /// interface is null. + #[must_use] + pub fn get(&self) -> Option<&P> { + self.interface.map(|p| unsafe { &*p }) + } + + /// Get the protocol interface data, or `None` if the open protocol's + /// interface is null. + #[must_use] + pub fn get_mut(&self) -> Option<&mut P> { + self.interface.map(|p| unsafe { &mut *p }) + } +} + +/// A buffer that contains an array of [`Handles`][Handle] that support the +/// requested protocol. Returned by [`BootServices::locate_handle_buffer`]. +#[derive(Debug)] +pub struct HandleBuffer { + count: usize, + buffer: *mut Handle, +} + +impl Drop for HandleBuffer { + fn drop(&mut self) { + // Ignore the result, we can't do anything about an error here. + let _ = unsafe { free_pool(self.buffer.cast::()) }; + } +} + +impl Deref for HandleBuffer { + type Target = [Handle]; + + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(self.buffer, self.count) } + } +} + +/// Protocol interface [`Guids`][Guid] that are installed on a [`Handle`] as +/// returned by [`BootServices::protocols_per_handle`]. +#[derive(Debug)] +pub struct ProtocolsPerHandle { + protocols: *mut *const Guid, + count: usize, + index: usize, +} + +impl Drop for ProtocolsPerHandle { + fn drop(&mut self) { + // Ignore the result, we can't do anything about an error here. + let _ = unsafe { free_pool(self.protocols.cast::()) }; + } +} + +// TODO: switched this to an iterator instead of Deref, since there's no way for +// Deref to return a reference to self. +impl Iterator for ProtocolsPerHandle { + type Item = Guid; + + fn next(&mut self) -> Option { + if self.index < self.count { + let protocols = unsafe { slice::from_raw_parts(self.protocols, self.count) }; + let guid = protocols[self.index]; + self.index += 1; + Some(unsafe { *guid }) + } else { + None + } + } +} + +/// RAII guard for task priority level changes +/// +/// Will automatically restore the former task priority level when dropped. +#[derive(Debug)] +pub struct TplGuard { + old_tpl: Tpl, +} + +impl Drop for TplGuard { + fn drop(&mut self) { + unsafe { + (boot_services_raw().as_mut().restore_tpl)(self.old_tpl); + } + } +} diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs index a40d4edac..48e25da1d 100644 --- a/uefi/src/lib.rs +++ b/uefi/src/lib.rs @@ -119,6 +119,8 @@ pub use uguid::guid; mod result; pub use result::{Error, Result, ResultExt, Status, StatusExt}; +pub mod boot; +pub mod runtime; pub mod system; pub mod table; diff --git a/uefi/src/runtime.rs b/uefi/src/runtime.rs new file mode 100644 index 000000000..49ff3ff6c --- /dev/null +++ b/uefi/src/runtime.rs @@ -0,0 +1,164 @@ +//! UEFI runtime services. +//! +//! These services are available both before and after exiting boot +//! services. Note that various restrictions apply when calling runtime services +//! functions after exiting boot services; see the "Calling Convention" section +//! of the UEFI specification for details. + +use crate::table::boot::MemoryDescriptor; +use crate::table::runtime::{ + ResetType, RuntimeServices, VariableAttributes, VariableStorageInfo, VariableVendor, +}; +use crate::{table, CStr16, Result, Status, StatusExt}; +use core::ptr::{self, NonNull}; + +#[cfg(feature = "alloc")] +use {crate::table::runtime::VariableKey, alloc::boxed::Box, alloc::vec::Vec}; + +pub use crate::table::runtime::{Daylight, Time, TimeCapabilities, TimeError, TimeParams}; + +fn runtime_services_raw_panicking() -> NonNull { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + NonNull::new(st.runtime_services).expect("runtime services are not active") +} + +// TODO +#[track_caller] +fn runtime_services() -> NonNull { + let st = table::system_table_runtime().expect("runtime services are not available"); + let ptr: *const _ = unsafe { st.runtime_services() }; + NonNull::new(ptr.cast_mut()).unwrap() +} + +/// Query the current time and date information. +pub fn get_time() -> Result