diff --git a/BUILDING.md b/BUILDING.md index 51e4029cc..445a9ddde 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -19,7 +19,7 @@ The following steps allow you to build a simple UEFI app. ```rust #[no_mangle] -pub extern "win64" fn uefi_start(handle: Handle, system_table: &'static table::SystemTable) -> Status; +pub extern "win64" fn uefi_start(handle: Handle, system_table: SystemTable) -> Status; ``` - Copy the `uefi-test-runner/x86_64-uefi.json` target file to your project's root. diff --git a/src/error/completion.rs b/src/error/completion.rs index 5edc43eaf..38e5fcd17 100644 --- a/src/error/completion.rs +++ b/src/error/completion.rs @@ -52,7 +52,7 @@ impl Completion { } /// Transform the inner value without unwrapping the Completion - pub fn map(self, f: impl Fn(T) -> U) -> Completion { + pub fn map(self, f: impl FnOnce(T) -> U) -> Completion { match self { Completion::Success(res) => Completion::Success(f(res)), Completion::Warning(res, stat) => Completion::Warning(f(res), stat), diff --git a/src/error/mod.rs b/src/error/mod.rs index b81bc192e..fa2826a91 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -25,7 +25,7 @@ pub trait ResultExt { fn expect_success(self, msg: &str) -> T; /// Transform the inner output, if any - fn map_inner(self, f: impl Fn(T) -> U) -> Result; + fn map_inner(self, f: impl FnOnce(T) -> U) -> Result; } impl ResultExt for Result { @@ -49,7 +49,7 @@ impl ResultExt for Result { self.expect(msg).expect(msg) } - fn map_inner(self, f: impl Fn(T) -> U) -> Result { + fn map_inner(self, f: impl FnOnce(T) -> U) -> Result { self.map(|completion| completion.map(f)) } } diff --git a/src/prelude.rs b/src/prelude.rs index 1be480a09..0b5d9c7c2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,4 +5,6 @@ pub use crate::{ResultExt, Status}; // Import the basic table types. -pub use crate::table::{boot::BootServices, runtime::RuntimeServices, SystemTable}; +pub use crate::table::boot::BootServices; +pub use crate::table::runtime::RuntimeServices; +pub use crate::table::{Boot, SystemTable}; diff --git a/src/proto/console/gop.rs b/src/proto/console/gop.rs index f2c78ff72..82f7ef676 100644 --- a/src/proto/console/gop.rs +++ b/src/proto/console/gop.rs @@ -33,7 +33,7 @@ use crate::{Completion, Result, Status}; /// The GOP can be used to set the properties of the frame buffer, /// and also allows the app to access the in-memory buffer. #[repr(C)] -pub struct GraphicsOutput { +pub struct GraphicsOutput<'boot> { query_mode: extern "win64" fn(&GraphicsOutput, mode: u32, info_sz: &mut usize, &mut *const ModeInfo) -> Status, @@ -42,7 +42,7 @@ pub struct GraphicsOutput { #[allow(clippy::type_complexity)] blt: extern "win64" fn( this: &mut GraphicsOutput, - buffer: usize, + buffer: *mut BltPixel, op: u32, source_x: usize, source_y: usize, @@ -52,10 +52,10 @@ pub struct GraphicsOutput { height: usize, stride: usize, ) -> Status, - mode: &'static ModeData, + mode: &'boot ModeData<'boot>, } -impl GraphicsOutput { +impl<'boot> GraphicsOutput<'boot> { /// Returns information for an available graphics mode that the graphics /// device and the set of active video output devices supports. fn query_mode(&self, index: u32) -> Result { @@ -63,7 +63,7 @@ impl GraphicsOutput { let mut info = ptr::null(); (self.query_mode)(self, index, &mut info_sz, &mut info).into_with(|| { - let info = unsafe { &*info }; + let info = unsafe { *info }; Mode { index, info_sz, @@ -103,7 +103,7 @@ impl GraphicsOutput { self.check_framebuffer_region((dest_x, dest_y), (width, height)); (self.blt)( self, - &color as *const _ as usize, + &color as *const _ as *mut _, 0, 0, 0, @@ -126,7 +126,7 @@ impl GraphicsOutput { match dest_region { BltRegion::Full => (self.blt)( self, - buffer.as_mut_ptr() as usize, + buffer.as_mut_ptr(), 1, src_x, src_y, @@ -142,7 +142,7 @@ impl GraphicsOutput { px_stride, } => (self.blt)( self, - buffer.as_mut_ptr() as usize, + buffer.as_mut_ptr(), 1, src_x, src_y, @@ -166,7 +166,7 @@ impl GraphicsOutput { match src_region { BltRegion::Full => (self.blt)( self, - buffer.as_ptr() as usize, + buffer.as_ptr() as *mut _, 2, 0, 0, @@ -182,7 +182,7 @@ impl GraphicsOutput { px_stride, } => (self.blt)( self, - buffer.as_ptr() as usize, + buffer.as_ptr() as *mut _, 2, src_x, src_y, @@ -203,7 +203,16 @@ impl GraphicsOutput { self.check_framebuffer_region((src_x, src_y), (width, height)); self.check_framebuffer_region((dest_x, dest_y), (width, height)); (self.blt)( - self, 0usize, 3, src_x, src_y, dest_x, dest_y, width, height, 0, + self, + ptr::null_mut(), + 3, + src_x, + src_y, + dest_x, + dest_y, + width, + height, + 0, ) .into() } @@ -269,19 +278,19 @@ impl GraphicsOutput { } impl_proto! { - protocol GraphicsOutput { + protocol GraphicsOutput<'boot> { GUID = 0x9042a9de, 0x23dc, 0x4a38, [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]; } } #[repr(C)] -struct ModeData { +struct ModeData<'a> { // Number of modes which the GOP supports. max_mode: u32, // Current mode. mode: u32, // Information about the current mode. - info: &'static ModeInfo, + info: &'a ModeInfo, // Size of the above structure. info_sz: usize, // Physical address of the frame buffer. @@ -329,7 +338,7 @@ pub struct PixelBitmask { pub struct Mode { index: u32, info_sz: usize, - info: &'static ModeInfo, + info: ModeInfo, } impl Mode { @@ -342,7 +351,7 @@ impl Mode { /// Returns a reference to the mode info structure. pub fn info(&self) -> &ModeInfo { - self.info + &self.info } } @@ -391,7 +400,7 @@ impl ModeInfo { /// Iterator for graphics modes. struct ModeIter<'a> { - gop: &'a GraphicsOutput, + gop: &'a GraphicsOutput<'a>, current: u32, max: u32, } diff --git a/src/proto/console/pointer/mod.rs b/src/proto/console/pointer/mod.rs index c0981a921..ee04b6ab8 100644 --- a/src/proto/console/pointer/mod.rs +++ b/src/proto/console/pointer/mod.rs @@ -5,14 +5,14 @@ use crate::{Event, Result, Status}; /// Provides information about a pointer device. #[repr(C)] -pub struct Pointer { +pub struct Pointer<'boot> { reset: extern "win64" fn(this: &mut Pointer, ext_verif: bool) -> Status, get_state: extern "win64" fn(this: &Pointer, state: &mut PointerState) -> Status, wait_for_input: Event, - mode: &'static PointerMode, + mode: &'boot PointerMode, } -impl Pointer { +impl<'boot> Pointer<'boot> { /// Resets the pointer device hardware. /// /// The `extended_verification` parameter is used to request that UEFI @@ -55,7 +55,7 @@ impl Pointer { } impl_proto! { - protocol Pointer { + protocol Pointer<'boot> { GUID = 0x31878c87, 0xb75, 0x11d5, [0x9a, 0x4f, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d]; } } diff --git a/src/proto/console/serial.rs b/src/proto/console/serial.rs index a35eeef98..db2674bcf 100644 --- a/src/proto/console/serial.rs +++ b/src/proto/console/serial.rs @@ -11,7 +11,7 @@ use crate::{Completion, Result, Status}; /// Since UEFI drivers are implemented through polling, if you fail to regularly /// check for input/output, some data might be lost. #[repr(C)] -pub struct Serial { +pub struct Serial<'boot> { // Revision of this protocol, only 1.0 is currently defined. // Future versions will be backwards compatible. revision: u32, @@ -29,10 +29,10 @@ pub struct Serial { get_control_bits: extern "win64" fn(&Serial, &mut ControlBits) -> Status, write: extern "win64" fn(&mut Serial, &mut usize, *const u8) -> Status, read: extern "win64" fn(&mut Serial, &mut usize, *mut u8) -> Status, - io_mode: &'static IoMode, + io_mode: &'boot IoMode, } -impl Serial { +impl<'boot> Serial<'boot> { /// Reset the device. pub fn reset(&mut self) -> Result<()> { (self.reset)(self).into() @@ -117,7 +117,7 @@ impl Serial { } impl_proto! { - protocol Serial { + protocol Serial<'boot> { GUID = 0xBB25CF6F, 0xF1D4, 0x11D2, [0x9A, 0x0C, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0xFD]; } } diff --git a/src/proto/console/text/output.rs b/src/proto/console/text/output.rs index bf1936cff..9aca93718 100644 --- a/src/proto/console/text/output.rs +++ b/src/proto/console/text/output.rs @@ -7,7 +7,7 @@ use crate::{CStr16, Char16, Completion, Result, Status}; /// It implements the fmt::Write trait, so you can use it to print text with /// standard Rust constructs like the write!() and writeln!() macros. #[repr(C)] -pub struct Output { +pub struct Output<'boot> { reset: extern "win64" fn(this: &Output, extended: bool) -> Status, output_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status, test_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status, @@ -18,10 +18,10 @@ pub struct Output { clear_screen: extern "win64" fn(this: &mut Output) -> Status, set_cursor_position: extern "win64" fn(this: &mut Output, column: usize, row: usize) -> Status, enable_cursor: extern "win64" fn(this: &mut Output, visible: bool) -> Status, - data: &'static OutputData, + data: &'boot OutputData, } -impl Output { +impl<'boot> Output<'boot> { /// Resets and clears the text output device hardware. pub fn reset(&mut self, extended: bool) -> Result<()> { (self.reset)(self, extended).into() @@ -53,8 +53,8 @@ impl Output { } /// Returns an iterator of all supported text modes. - // TODO: fix the ugly lifetime parameter. - pub fn modes<'a>(&'a mut self) -> impl Iterator> + 'a { + // TODO: Bring back impl Trait once the story around bounds improves + pub fn modes<'a>(&'a mut self) -> OutputModeIter<'a, 'boot> { let max = self.data.max_mode; OutputModeIter { output: self, @@ -134,7 +134,7 @@ impl Output { } } -impl fmt::Write for Output { +impl<'boot> fmt::Write for Output<'boot> { fn write_str(&mut self, s: &str) -> fmt::Result { // Allocate a small buffer on the stack. const BUF_SIZE: usize = 128; @@ -215,13 +215,13 @@ impl OutputMode { } /// An iterator of the text modes (possibly) supported by a device. -struct OutputModeIter<'a> { - output: &'a mut Output, +pub struct OutputModeIter<'a, 'b: 'a> { + output: &'a mut Output<'b>, current: i32, max: i32, } -impl<'a> Iterator for OutputModeIter<'a> { +impl<'a, 'b> Iterator for OutputModeIter<'a, 'b> { type Item = Completion; fn next(&mut self) -> Option { @@ -259,7 +259,7 @@ struct OutputData { } impl_proto! { - protocol Output { + protocol Output<'boot> { GUID = 0x387477c2, 0x69c7, 0x11d2, [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b]; } } diff --git a/src/proto/macros.rs b/src/proto/macros.rs index dcccde002..af845b440 100644 --- a/src/proto/macros.rs +++ b/src/proto/macros.rs @@ -33,4 +33,21 @@ macro_rules! impl_proto { // Most UEFI functions do not support multithreaded access. impl !Sync for $p {} }; + ( + protocol $p:ident<'boot> { + GUID = $a:expr, $b:expr, $c:expr, $d:expr; + } + ) => { + impl<'boot> $crate::proto::Protocol for $p<'boot> { + #[doc(hidden)] + // These literals aren't meant to be human-readable. + #[allow(clippy::unreadable_literal)] + const GUID: $crate::Guid = $crate::Guid::from_values($a, $b, $c, $d); + } + + // Most UEFI functions expect to be called on the bootstrap processor. + impl<'boot> !Send for $p<'boot> {} + // Most UEFI functions do not support multithreaded access. + impl<'boot> !Sync for $p<'boot> {} + }; } diff --git a/src/proto/media/file.rs b/src/proto/media/file.rs index fdf2b3072..41a881a05 100644 --- a/src/proto/media/file.rs +++ b/src/proto/media/file.rs @@ -7,6 +7,7 @@ use bitflags::bitflags; use core::mem; +use core::ptr; use crate::prelude::*; use crate::{CStr16, Char16, Result, Status}; use ucs2; @@ -22,10 +23,8 @@ pub struct File<'a> { } impl<'a> File<'a> { - pub(super) unsafe fn new(ptr: usize) -> Self { - let ptr = ptr as *mut FileImpl; - let inner = &mut *ptr; - File { inner } + pub(super) unsafe fn new(ptr: *mut FileImpl) -> Self { + File { inner: &mut *ptr } } /// Try to open a file relative to this file/directory. @@ -58,7 +57,7 @@ impl<'a> File<'a> { Err(Status::INVALID_PARAMETER) } else { let mut buf = [0u16; BUF_SIZE + 1]; - let mut ptr = 0usize; + let mut ptr = ptr::null_mut(); let len = ucs2::encode(filename, &mut buf)?; let filename = unsafe { CStr16::from_u16_with_nul_unchecked(&buf[..=len]) }; @@ -70,9 +69,7 @@ impl<'a> File<'a> { open_mode, attributes, ) - .into_with(|| File { - inner: unsafe { &mut *(ptr as *mut FileImpl) }, - }) + .into_with(|| unsafe { File::new(ptr) }) } } @@ -175,11 +172,11 @@ impl<'a> Drop for File<'a> { /// The function pointer table for the File protocol. #[repr(C)] -struct FileImpl { +pub(super) struct FileImpl { revision: u64, open: extern "win64" fn( this: &mut FileImpl, - new_handle: &mut usize, + new_handle: &mut *mut FileImpl, filename: *const Char16, open_mode: FileMode, attributes: FileAttribute, diff --git a/src/proto/media/fs.rs b/src/proto/media/fs.rs index 7aa0a8727..eac39afcf 100644 --- a/src/proto/media/fs.rs +++ b/src/proto/media/fs.rs @@ -1,9 +1,9 @@ //! File system support protocols. +use super::file::{File, FileImpl}; +use core::ptr; use crate::{Result, Status}; -use super::file::File; - /// Allows access to a FAT-12/16/32 file system. /// /// This interface is implemented by some storage devices @@ -11,7 +11,7 @@ use super::file::File; #[repr(C)] pub struct SimpleFileSystem { revision: u64, - open_volume: extern "win64" fn(this: &mut SimpleFileSystem, root: &mut usize) -> Status, + open_volume: extern "win64" fn(this: &mut SimpleFileSystem, root: &mut *mut FileImpl) -> Status, } impl SimpleFileSystem { @@ -26,7 +26,7 @@ impl SimpleFileSystem { /// * `uefi::Status::OUT_OF_RESOURCES` - The volume was not opened /// * `uefi::Status::MEDIA_CHANGED` - The device has a different medium in it pub fn open_volume(&mut self) -> Result { - let mut ptr = 0usize; + let mut ptr = ptr::null_mut(); (self.open_volume)(self, &mut ptr).into_with(|| unsafe { File::new(ptr) }) } } diff --git a/src/table/boot.rs b/src/table/boot.rs index 985cd024c..0823c27d3 100644 --- a/src/table/boot.rs +++ b/src/table/boot.rs @@ -2,6 +2,8 @@ use super::Header; use bitflags::bitflags; +use core::cell::UnsafeCell; +use core::ffi::c_void; use core::{mem, ptr, result}; use crate::proto::Protocol; use crate::{Event, Guid, Handle, Result, Status}; @@ -12,22 +14,33 @@ pub struct BootServices { header: Header, // Task Priority services - raise_tpl: extern "win64" fn(Tpl) -> Tpl, - restore_tpl: extern "win64" fn(Tpl), + raise_tpl: extern "win64" fn(new_tpl: Tpl) -> Tpl, + restore_tpl: extern "win64" fn(old_tpl: Tpl), // Memory allocation functions allocate_pages: extern "win64" fn(alloc_ty: u32, mem_ty: MemoryType, count: usize, addr: &mut u64) -> Status, - free_pages: extern "win64" fn(u64, usize) -> Status, - memory_map: - extern "win64" fn(size: &mut usize, usize, key: &mut MemoryMapKey, &mut usize, &mut u32) - -> Status, - allocate_pool: extern "win64" fn(MemoryType, usize, addr: &mut usize) -> Status, - free_pool: extern "win64" fn(buffer: usize) -> Status, + free_pages: extern "win64" fn(addr: u64, pages: usize) -> Status, + memory_map: extern "win64" fn( + size: &mut usize, + map: *mut MemoryDescriptor, + key: &mut MemoryMapKey, + desc_size: &mut usize, + desc_version: &mut u32, + ) -> Status, + allocate_pool: + extern "win64" fn(pool_type: MemoryType, size: usize, buffer: &mut *mut u8) -> Status, + free_pool: extern "win64" fn(buffer: *mut u8) -> Status, // Event & timer functions - create_event: usize, + create_event: extern "win64" fn( + ty: EventType, + notify_tpl: Tpl, + notify_func: Option, + notify_ctx: *mut c_void, + event: *mut Event, + ) -> Status, set_timer: usize, wait_for_event: extern "win64" fn(number_of_events: usize, events: *mut Event, out_index: &mut usize) @@ -41,13 +54,14 @@ pub struct BootServices { reinstall_protocol_interface: usize, uninstall_protocol_interface: usize, handle_protocol: - extern "win64" fn(handle: Handle, proto: *const Guid, out_proto: &mut usize) -> Status, + extern "win64" fn(handle: Handle, proto: *const Guid, out_proto: &mut *mut c_void) + -> Status, _reserved: usize, register_protocol_notify: usize, locate_handle: extern "win64" fn( search_ty: i32, proto: *const Guid, - key: *mut (), + key: *mut c_void, buf_sz: &mut usize, buf: *mut Handle, ) -> Status, @@ -59,11 +73,11 @@ pub struct BootServices { start_image: usize, exit: usize, unload_image: usize, - exit_boot_services: extern "win64" fn(Handle, MemoryMapKey) -> Status, + exit_boot_services: extern "win64" fn(image_handle: Handle, map_key: MemoryMapKey) -> Status, // Misc services get_next_monotonic_count: usize, - stall: extern "win64" fn(usize) -> Status, + stall: extern "win64" fn(microseconds: usize) -> Status, set_watchdog_timer: extern "win64" fn( timeout: usize, watchdog_code: u64, @@ -111,24 +125,26 @@ impl BootServices { /// Allocates memory pages from the system. /// - /// UEFI OS loaders should allocate memory of the type `LoaderData`. + /// 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. pub fn allocate_pages( &self, ty: AllocateType, mem_ty: MemoryType, count: usize, - ) -> Result { + ) -> Result { let (ty, mut addr) = match ty { AllocateType::AnyPages => (0, 0), AllocateType::MaxAddress(addr) => (1, addr as u64), AllocateType::Address(addr) => (2, addr as u64), }; - (self.allocate_pages)(ty, mem_ty, count, &mut addr).into_with(|| addr as usize) + (self.allocate_pages)(ty, mem_ty, count, &mut addr).into_with(|| addr) } /// Frees memory pages allocated by UEFI. - pub fn free_pages(&self, addr: usize, count: usize) -> Result<()> { - (self.free_pages)(addr as u64, count).into() + pub fn free_pages(&self, addr: u64, count: usize) -> Result<()> { + (self.free_pages)(addr, count).into() } /// Retrieves the size, in bytes, of the current memory map. @@ -144,7 +160,7 @@ impl BootServices { let status = (self.memory_map)( &mut map_size, - 0, + ptr::null_mut(), &mut map_key, &mut entry_size, &mut entry_version, @@ -164,12 +180,9 @@ impl BootServices { pub fn memory_map<'a>( &self, buffer: &'a mut [u8], - ) -> Result<( - MemoryMapKey, - impl ExactSizeIterator, - )> { + ) -> Result<(MemoryMapKey, MemoryMapIter<'a>)> { let mut map_size = buffer.len(); - let map_buffer = buffer.as_ptr() as usize; + let map_buffer = buffer.as_ptr() as *mut MemoryDescriptor; let mut map_key = MemoryMapKey(0); let mut entry_size = 0; let mut entry_version = 0; @@ -193,17 +206,58 @@ impl BootServices { }) } - /// Allocates from a memory pool. The address is 8-byte aligned. - pub fn allocate_pool(&self, mem_ty: MemoryType, size: usize) -> Result { - let mut buffer = 0; + /// Allocates from a memory pool. The pointer will be 8-byte aligned. + pub fn allocate_pool(&self, mem_ty: MemoryType, size: usize) -> Result<*mut u8> { + let mut buffer = ptr::null_mut(); (self.allocate_pool)(mem_ty, size, &mut buffer).into_with(|| buffer) } /// Frees memory allocated from a pool. - pub fn free_pool(&self, addr: usize) -> Result<()> { + pub fn free_pool(&self, addr: *mut u8) -> Result<()> { (self.free_pool)(addr).into() } + /// 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. + /// + /// Note that the safety of this function relies on aborting panics being + /// used, as requested by the uefi-rs documentation. + pub fn create_event( + &self, + event_ty: EventType, + notify_tpl: Tpl, + notify_fn: Option, + ) -> Result { + // Prepare storage for the output Event + let mut event = unsafe { mem::uninitialized() }; + + // Use a trampoline to handle the impedance mismatch between Rust & C + unsafe extern "win64" fn notify_trampoline(e: Event, ctx: *mut c_void) { + let notify_fn: fn(Event) = core::mem::transmute(ctx); + notify_fn(e); // Aborting panics are assumed here + } + let (notify_func, notify_ctx) = notify_fn + .map(|notify_fn| { + ( + Some(notify_trampoline as EventNotifyFn), + notify_fn as fn(Event) as *mut c_void, + ) + }) + .unwrap_or((None, ptr::null_mut())); + + // Now we're ready to call UEFI + (self.create_event)(event_ty, notify_tpl, notify_func, notify_ctx, &mut event) + .into_with(|| event) + } + /// Stops execution until an event is signaled /// /// This function must be called at priority level Tpl::APPLICATION. If an @@ -245,10 +299,17 @@ impl BootServices { /// /// This function attempts to get the protocol implementation of a handle, /// based on the protocol GUID. - pub fn handle_protocol(&self, handle: Handle) -> Option> { - let mut ptr = 0usize; + /// + /// 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. + pub fn handle_protocol(&self, handle: Handle) -> Option<&UnsafeCell

> { + let mut ptr = ptr::null_mut(); match (self.handle_protocol)(handle, &P::GUID, &mut ptr) { - Status::SUCCESS => ptr::NonNull::new(ptr as *mut P), + Status::SUCCESS => { + let ptr = ptr as *mut P as *mut UnsafeCell

; + Some(unsafe { &*ptr }) + } _ => None, } } @@ -290,20 +351,21 @@ impl BootServices { } } - /// Exits the early boot stage. - /// - /// After calling this function, the boot services functions become invalid. - /// Only runtime services may be used. + /// Exits the UEFI boot services /// - /// The handle passed must be the one of the currently executing image. + /// This unsafe method is meant to be an implementation detail of the safe + /// `SystemTable::exit_boot_services()` method, which is why it is not + /// public. /// - /// The application **must** retrieve the current memory map, and pass in a key to ensure it is the latest. - /// If the memory map was changed, you must obtain the new memory map, - /// and then immediately call this function again. - /// - /// After you first call this function, the firmware may perform a partial shutdown of boot services. - /// You should only call the mmap-related functions in order to update the memory map. - pub unsafe fn exit_boot_services(&self, image: Handle, mmap_key: MemoryMapKey) -> Result<()> { + /// Everything that is explained in the documentation of the high-level + /// SystemTable method is also true here, except that this function is + /// one-shot (no automatic retry) and does not prevent you from shooting + /// yourself in the foot by calling invalid boot services after a failure. + pub(super) unsafe fn exit_boot_services( + &self, + image: Handle, + mmap_key: MemoryMapKey, + ) -> Result<()> { (self.exit_boot_services)(image, mmap_key).into() } @@ -526,8 +588,13 @@ bitflags! { #[repr(C)] pub struct MemoryMapKey(usize); +/// An iterator of memory descriptors +/// +/// This type is only exposed in interfaces due to current limitations of +/// `impl Trait` which may be lifted in the future. It is therefore recommended +/// that you refrain from directly manipulating it in your code. #[derive(Debug)] -struct MemoryMapIter<'a> { +pub struct MemoryMapIter<'a> { buffer: &'a [u8], entry_size: usize, index: usize, @@ -549,7 +616,7 @@ impl<'a> Iterator for MemoryMapIter<'a> { self.index += 1; - let descriptor = unsafe { mem::transmute::(ptr) }; + let descriptor: &MemoryDescriptor = unsafe { mem::transmute(ptr) }; Some(descriptor) } else { @@ -579,3 +646,39 @@ impl<'a> SearchType<'a> { SearchType::ByProtocol(&P::GUID) } } + +bitflags! { + /// Flags describing the type of an UEFI event and its attributes. + pub struct EventType: u32 { + /// The event is a timer event and may be passed to `BootServices::set_timer()` + /// Note that timers only function during boot services time. + const TIMER = 0x8000_0000; + + /// The event is allocated from runtime memory. + /// This must be done if the event is to be signaled after ExitBootServices. + const RUNTIME = 0x4000_0000; + + /// Calling wait_for_event or check_event will enqueue the notification + /// function if the event is not already in the signaled state. + /// Mutually exclusive with NOTIFY_SIGNAL. + const NOTIFY_WAIT = 0x0000_0100; + + /// The notification function will be enqueued when the event is signaled + /// Mutually exclusive with NOTIFY_WAIT. + const NOTIFY_SIGNAL = 0x0000_0200; + + /// The event will be signaled at ExitBootServices time. + /// This event type should not be combined with any other. + /// Its notification function must follow some special rules: + /// - Cannot use memory allocation services, directly or indirectly + /// - Cannot depend on timer events, since those will be deactivated + const SIGNAL_EXIT_BOOT_SERVICES = 0x0000_0201; + + /// The event will be notified when SetVirtualAddressMap is performed. + /// This event type should not be combined with any other. + const SIGNAL_VIRTUAL_ADDRESS_CHANGE = 0x6000_0202; + } +} + +/// Raw event notification function +type EventNotifyFn = unsafe extern "win64" fn(event: Event, context: *mut c_void); diff --git a/src/table/cfg.rs b/src/table/cfg.rs index c55727019..84d946fb1 100644 --- a/src/table/cfg.rs +++ b/src/table/cfg.rs @@ -10,6 +10,7 @@ #![allow(clippy::unreadable_literal)] use bitflags::bitflags; +use core::ffi::c_void; use crate::Guid; /// Contains a set of GUID / pointer for a vendor-specific table. @@ -23,7 +24,7 @@ pub struct ConfigTableEntry { /// The starting address of this table. /// /// Whether this is a physical or virtual address depends on the table. - pub address: usize, + pub address: *const c_void, } /// Entry pointing to the old ACPI 1 RSDP. diff --git a/src/table/mod.rs b/src/table/mod.rs index a7b3374da..81b5c0007 100644 --- a/src/table/mod.rs +++ b/src/table/mod.rs @@ -14,7 +14,7 @@ mod revision; pub use self::revision::Revision; mod system; -pub use self::system::SystemTable; +pub use self::system::{Boot, Runtime, SystemTable}; pub mod boot; pub mod runtime; diff --git a/src/table/runtime.rs b/src/table/runtime.rs index 02f799bbe..74c3bca2a 100644 --- a/src/table/runtime.rs +++ b/src/table/runtime.rs @@ -13,18 +13,25 @@ pub struct RuntimeServices { header: Header, // Skip some useless functions. _pad: [usize; 10], - reset: extern "win64" fn(u32, Status, usize, *const u8) -> !, + reset: extern "win64" fn(rt: ResetType, status: Status, data_size: usize, data: *const u8) -> !, } impl RuntimeServices { /// Resets the computer. pub fn reset(&self, rt: ResetType, status: Status, data: Option<&[u8]>) -> ! { let (size, data) = match data { + // FIXME: The UEFI spec states that the data must start with a NUL- + // terminated string, which we should check... but it does not + // specify if that string should be Latin-1 or UCS-2! + // + // PlatformSpecific resets should also insert a GUID after the + // NUL-terminated string. + // Some(data) => (data.len(), data.as_ptr()), None => (0, ptr::null()), }; - (self.reset)(rt as u32, status, size, data) + (self.reset)(rt, status, size, data) } } @@ -39,7 +46,7 @@ pub enum ResetType { /// Resets all the internal circuitry to its initial state. /// /// This is analogous to power cycling the device. - Cold, + Cold = 0, /// The processor is reset to its initial state. Warm, /// The components are powered off. diff --git a/src/table/system.rs b/src/table/system.rs index efd72a36f..2d412bc77 100644 --- a/src/table/system.rs +++ b/src/table/system.rs @@ -1,68 +1,222 @@ +use super::boot::{BootServices, MemoryMapIter}; +use super::runtime::RuntimeServices; use super::{cfg, Header, Revision}; +use core::marker::PhantomData; use core::slice; use crate::proto::console::text; -use crate::{CStr16, Char16, Handle}; +use crate::{CStr16, Char16, Handle, Result, Status}; -/// The system table entry points for accessing the core UEFI system functionality. -#[repr(C)] -pub struct SystemTable { - header: Header, - /// Null-terminated string representing the firmware's vendor. - fw_vendor: *const Char16, - /// Revision of the UEFI specification the firmware conforms to. - pub fw_revision: Revision, - stdin_handle: Handle, - stdin: *mut text::Input, - stdout_handle: Handle, - stdout: *mut text::Output, - stderr_handle: Handle, - stderr: *mut text::Output, - /// Runtime services table. - pub runtime: &'static super::runtime::RuntimeServices, - /// Boot services table. - pub boot: &'static super::boot::BootServices, - /// Number of entires in the configuration table. - nr_cfg: usize, - /// Pointer to beginning of the array. - cfg_table: *mut cfg::ConfigTableEntry, +/// Marker trait used to provide different views of the UEFI System Table +pub trait SystemTableView {} + +/// Marker struct associated with the boot view of the UEFI System Table +pub struct Boot; +impl SystemTableView for Boot {} + +/// Marker struct associated with the run-time view of the UEFI System Table +pub struct Runtime; +impl SystemTableView for Runtime {} + +/// UEFI System Table interface +/// +/// The UEFI System Table is the gateway to all UEFI services which an UEFI +/// application is provided access to on startup. However, not all UEFI services +/// will remain accessible forever. +/// +/// Some services, called "boot services", may only be called during a bootstrap +/// stage where the UEFI firmware still has control of the hardware, and will +/// become unavailable once the firmware hands over control of the hardware to +/// an operating system loader. Others, called "runtime services", may still be +/// used after that point, but require a rather specific CPU configuration which +/// an operating system loader is unlikely to preserve. +/// +/// We handle this state transition by providing two different views of the UEFI +/// system table, the "Boot" view and the "Runtime" view. An UEFI application +/// is initially provided with access to the "Boot" view, and may transition +/// to the "Runtime" view through the ExitBootServices mechanism that is +/// documented in the UEFI spec. At that point, the boot view of the system +/// table will be destroyed (which conveniently invalidates all references to +/// UEFI boot services in the eye of the Rust borrow checker) and a runtime view +/// will be provided to replace it. +#[repr(transparent)] +pub struct SystemTable { + table: &'static SystemTableImpl, + _marker: PhantomData, } -// This is unsafe, but it's the best solution we have from now. -#[allow(clippy::mut_from_ref)] -impl SystemTable { +// These parts of the UEFI System Table interface will always be available +impl SystemTable { /// Return the firmware vendor string pub fn firmware_vendor(&self) -> &CStr16 { - unsafe { CStr16::from_ptr(self.fw_vendor) } + unsafe { CStr16::from_ptr(self.table.fw_vendor) } + } + + /// Return the firmware revision + pub fn firmware_revision(&self) -> Revision { + self.table.fw_revision } /// Returns the revision of this table, which is defined to be /// the revision of the UEFI specification implemented by the firmware. pub fn uefi_revision(&self) -> Revision { - self.header.revision + self.table.header.revision + } + + /// Returns the config table entries, a linear array of structures + /// pointing to other system-specific tables. + pub fn config_table(&self) -> &[cfg::ConfigTableEntry] { + unsafe { slice::from_raw_parts(self.table.cfg_table, self.table.nr_cfg) } } +} +// These parts of the UEFI System Table interface may only be used until boot +// services are exited and hardware control is handed over to the OS loader +#[allow(clippy::mut_from_ref)] +impl SystemTable { /// Returns the standard input protocol. pub fn stdin(&self) -> &mut text::Input { - unsafe { &mut *self.stdin } + unsafe { &mut *self.table.stdin } } /// Returns the standard output protocol. pub fn stdout(&self) -> &mut text::Output { - unsafe { &mut *self.stdout } + let stdout_ptr = self.table.stdout as *const _ as *mut _; + unsafe { &mut *stdout_ptr } } /// Returns the standard error protocol. pub fn stderr(&self) -> &mut text::Output { - unsafe { &mut *self.stderr } + let stderr_ptr = self.table.stderr as *const _ as *mut _; + unsafe { &mut *stderr_ptr } } - /// Returns the config table entries, a linear array of structures - /// pointing to other system-specific tables. - pub fn config_table(&self) -> &[cfg::ConfigTableEntry] { - unsafe { slice::from_raw_parts(self.cfg_table, self.nr_cfg) } + /// Access runtime services + pub fn runtime_services(&self) -> &RuntimeServices { + self.table.runtime + } + + /// Access boot services + pub fn boot_services(&self) -> &BootServices { + unsafe { &*self.table.boot } + } + + /// 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, and the latter requires special care + /// from the OS loader. We model this situation by consuming the + /// `SystemTable` view of the System Table and returning a more + /// restricted `SystemTable` view as an output. + /// + /// The handle passed must be the one of the currently executing image, + /// which is received by the entry point of the UEFI application. In + /// addition, the application must provide storage for a memory map, which + /// will be retrieved automatically (as having an up-to-date memory map is a + /// prerequisite for exiting UEFI boot services). + /// + /// The size of the memory map can be estimated by calling + /// `BootServices::memory_map_size()`. But the memory map can grow under the + /// hood between the moment where this size estimate is returned and the + /// moment where boot services are exited, and calling the UEFI memory + /// allocator will not be possible after the first attempt to exit the boot + /// services. Therefore, UEFI applications are advised to allocate storage + /// for the memory map right before exiting boot services, and to allocate a + /// bit more storage than requested by memory_map_size. + /// + /// If `exit_boot_services` succeeds, it will return a runtime view of the + /// system table which more accurately reflects the state of the UEFI + /// firmware following exit from boot services, along with a high-level + /// iterator to the UEFI memory map. + pub fn exit_boot_services<'a>( + self, + image: Handle, + mmap_buf: &'a mut [u8], + ) -> Result<(SystemTable, MemoryMapIter<'a>)> { + unsafe { + let boot_services = self.boot_services(); + + loop { + // Fetch a memory map, propagate errors and split the completion + // FIXME: This sad pointer hack works around a current + // limitation of the NLL analysis (see Rust bug 51526). + let mmap_buf = &mut *(mmap_buf as *mut [u8]); + let mmap_comp = boot_services.memory_map(mmap_buf)?; + let ((mmap_key, mmap_iter), mmap_status) = mmap_comp.split(); + + // Try to exit boot services using this memory map key + let result = boot_services.exit_boot_services(image, mmap_key); + + // Did we fail because the memory map was updated concurrently? + if let Err(Status::INVALID_PARAMETER) = result { + // If so, fetch another memory map and try again + continue; + } else { + // If not, report the outcome of the operation + return result.map(|comp| { + let st = SystemTable { + table: self.table, + _marker: PhantomData, + }; + comp.map(|_| (st, mmap_iter)).with_status(mmap_status) + }); + } + } + } } + + /// Clone this boot-time UEFI system table interface + /// + /// This is unsafe because you must guarantee that the clone will not be + /// used after boot services are exited. However, the singleton-based + /// designs that Rust uses for memory allocation, logging, and panic + /// handling require taking this risk. + pub unsafe fn unsafe_clone(&self) -> Self { + SystemTable { + table: self.table, + _marker: PhantomData, + } + } +} + +// These parts of the UEFI System Table interface may only be used after exit +// from UEFI boot services +impl SystemTable { + /// Access runtime services + /// + /// This is unsafe because UEFI runtime services require an elaborate + /// CPU configuration which may not be preserved by OS loaders. See the + /// "Calling Conventions" chapter of the UEFI specification for details. + pub unsafe fn runtime_services(&self) -> &RuntimeServices { + self.table.runtime + } +} + +/// The actual UEFI system table +#[repr(C)] +struct SystemTableImpl { + header: Header, + /// Null-terminated string representing the firmware's vendor. + fw_vendor: *const Char16, + /// Revision of the UEFI specification the firmware conforms to. + fw_revision: Revision, + stdin_handle: Handle, + stdin: *mut text::Input, + stdout_handle: Handle, + stdout: *mut text::Output<'static>, + stderr_handle: Handle, + stderr: *mut text::Output<'static>, + /// Runtime services table. + runtime: &'static RuntimeServices, + /// Boot services table. + boot: *const BootServices, + /// Number of entires in the configuration table. + nr_cfg: usize, + /// Pointer to beginning of the array. + cfg_table: *const cfg::ConfigTableEntry, } -impl super::Table for SystemTable { +impl super::Table for SystemTable { const SIGNATURE: u64 = 0x5453_5953_2049_4249; } diff --git a/uefi-alloc/src/lib.rs b/uefi-alloc/src/lib.rs index 89b8e0eb3..3efa963e6 100644 --- a/uefi-alloc/src/lib.rs +++ b/uefi-alloc/src/lib.rs @@ -7,32 +7,47 @@ //! //! Call the `init` function with a reference to the boot services table. //! Failure to do so before calling a memory allocating function will panic. +//! +//! Call the `exit_boot_services` function before exiting UEFI boot services. +//! Failure to do so will turn subsequent allocation into undefined behaviour. // Enable additional lints. #![warn(missing_docs)] #![deny(clippy::all)] #![no_std] -// Custom allocators are currently unstable. -#![feature(allocator_api)] use core::alloc::{GlobalAlloc, Layout}; -use core::ptr; +use core::ptr::{self, NonNull}; use uefi::prelude::*; use uefi::table::boot::{BootServices, MemoryType}; /// Reference to the boot services table, used to call the pool memory allocation functions. -static mut BOOT_SERVICES: Option<&BootServices> = None; +/// +/// The inner pointer is only safe to dereference if UEFI boot services have not been +/// exited by the host application yet. +static mut BOOT_SERVICES: Option> = None; /// Initializes the allocator. -pub fn init(boot_services: &'static BootServices) { - unsafe { - BOOT_SERVICES = Some(boot_services); - } +/// +/// This function is unsafe because you _must_ make sure that exit_boot_services +/// will be called when UEFI boot services will be exited. +pub unsafe fn init(boot_services: &BootServices) { + BOOT_SERVICES = NonNull::new(boot_services as *const _ as *mut _); } -fn boot_services() -> &'static BootServices { - unsafe { BOOT_SERVICES.unwrap() } +/// Access the boot services +fn boot_services() -> NonNull { + unsafe { BOOT_SERVICES.expect("Boot services are unavailable or have been exited") } +} + +/// Notify the allocator library that boot services are not safe to call anymore +/// +/// You must arrange for this function to be called on exit from UEFI boot services +pub fn exit_boot_services() { + unsafe { + BOOT_SERVICES = None; + } } /// Allocator which uses the UEFI pool allocation functions. @@ -52,16 +67,19 @@ unsafe impl GlobalAlloc for Allocator { ptr::null_mut() } else { boot_services() + .as_ref() .allocate_pool(mem_ty, size) .warning_as_error() - .map(|addr| addr as *mut _) .unwrap_or(ptr::null_mut()) } } unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - let addr = ptr as usize; - boot_services().free_pool(addr).warning_as_error().unwrap(); + boot_services() + .as_ref() + .free_pool(ptr) + .warning_as_error() + .unwrap(); } } diff --git a/uefi-exts/src/boot.rs b/uefi-exts/src/boot.rs index 89081ae95..31931c4ab 100644 --- a/uefi-exts/src/boot.rs +++ b/uefi-exts/src/boot.rs @@ -1,9 +1,9 @@ +use core::cell::UnsafeCell; use uefi::prelude::*; use uefi::proto::Protocol; use uefi::table::boot::{BootServices, SearchType}; use uefi::{Handle, Result}; -use core::ptr::NonNull; use crate::alloc::vec::Vec; /// Utility functions for the UEFI boot services. @@ -12,7 +12,9 @@ pub trait BootServicesExt { fn find_handles(&self) -> Result>; /// Returns a protocol implementation, if present on the system. - fn find_protocol(&self) -> Option>; + /// + /// The caveats of `BootServices::handle_protocol()` also apply here. + fn find_protocol(&self) -> Option<&UnsafeCell

>; } impl BootServicesExt for BootServices { @@ -43,7 +45,7 @@ impl BootServicesExt for BootServices { .map(|completion| completion.with_status(status2)) } - fn find_protocol(&self) -> Option> { + fn find_protocol(&self) -> Option<&UnsafeCell

> { // Retrieve all handles implementing this. self.find_handles::

() // Convert to an option. diff --git a/uefi-logger/src/lib.rs b/uefi-logger/src/lib.rs index fcdd319e4..71c525697 100644 --- a/uefi-logger/src/lib.rs +++ b/uefi-logger/src/lib.rs @@ -21,31 +21,45 @@ use uefi::proto::console::text::Output; extern crate log; -use core::cell::UnsafeCell; use core::fmt::{self, Write}; +use core::ptr::NonNull; /// Logging implementation which writes to a UEFI output stream. +/// +/// If this logger is used as a global logger, you must disable it using the +/// `disable` method before exiting UEFI boot services in order to prevent +/// undefined behaviour from inadvertent logging. pub struct Logger { - writer: UnsafeCell<&'static mut Output>, + writer: Option>>, } impl Logger { /// Creates a new logger. - pub fn new(output: &'static mut Output) -> Self { + /// + /// You must arrange for the `disable` method to be called or for this logger + /// to be otherwise discarded before boot services are exited. + pub unsafe fn new(output: &mut Output) -> Self { Logger { - writer: UnsafeCell::new(output), + writer: NonNull::new(output as *const _ as *mut _), } } + + /// Disable the logger + pub fn disable(&mut self) { + self.writer = None; + } } -impl log::Log for Logger { +impl<'boot> log::Log for Logger { fn enabled(&self, _metadata: &log::Metadata) -> bool { true } fn log(&self, record: &log::Record) { - let writer = unsafe { &mut *self.writer.get() }; - DecoratedLog::write(writer, record.level(), record.args()).unwrap(); + if let Some(mut ptr) = self.writer { + let writer = unsafe { ptr.as_mut() }; + DecoratedLog::write(writer, record.level(), record.args()).unwrap(); + } } fn flush(&self) { diff --git a/uefi-services/src/lib.rs b/uefi-services/src/lib.rs index 9c837be8f..27edc8e57 100644 --- a/uefi-services/src/lib.rs +++ b/uefi-services/src/lib.rs @@ -30,50 +30,80 @@ extern crate uefi_alloc; #[macro_use] extern crate log; -use uefi::table::SystemTable; +use core::ptr::NonNull; + +use uefi::prelude::*; +use uefi::table::boot::{EventType, Tpl}; +use uefi::table::{Boot, SystemTable}; +use uefi::{Event, Result}; /// Reference to the system table. -static mut SYSTEM_TABLE: Option<&'static SystemTable> = None; +/// +/// This table is only fully safe to use until UEFI boot services have been exited. +/// After that, some fields and methods are unsafe to use, see the documentation of +/// UEFI's ExitBootServices entry point for more details. +static mut SYSTEM_TABLE: Option> = None; -/// Obtains a reference to the system table. +/// Global logger object +static mut LOGGER: Option = None; + +/// Obtains a pointer to the system table. /// /// This is meant to be used by higher-level libraries, /// which want a convenient way to access the system table singleton. /// /// `init` must have been called first by the UEFI app. -pub fn system_table() -> &'static SystemTable { - unsafe { SYSTEM_TABLE.expect("The uefi-services library has not yet been initialized") } +/// +/// The returned pointer is only valid until boot services are exited. +pub fn system_table() -> NonNull> { + unsafe { + let table_ref = SYSTEM_TABLE + .as_ref() + .expect("The system table handle is not available"); + NonNull::new(table_ref as *const _ as *mut _).unwrap() + } } /// Initialize the UEFI utility library. /// /// This must be called as early as possible, /// before trying to use logging or memory allocation capabilities. -pub fn init(st: &'static SystemTable) { +pub fn init(st: &SystemTable) -> Result<()> { unsafe { // Avoid double initialization. if SYSTEM_TABLE.is_some() { - return; + return Status::SUCCESS.into(); } - SYSTEM_TABLE = Some(st); + // Setup the system table singleton + SYSTEM_TABLE = Some(st.unsafe_clone()); + + // Setup logging and memory allocation + let boot_services = st.boot_services(); + init_logger(st); + uefi_alloc::init(boot_services); + + // Schedule these tools to be disabled on exit from UEFI boot services + boot_services + .create_event( + EventType::SIGNAL_EXIT_BOOT_SERVICES, + Tpl::NOTIFY, + Some(exit_boot_services), + ) + .map_inner(|_| ()) } - - init_logger(); - init_alloc(); } -fn init_logger() { - let st = system_table(); - - static mut LOGGER: Option = None; - +/// Set up logging +/// +/// This is unsafe because you must arrange for the logger to be reset with +/// disable() on exit from UEFI boot services. +unsafe fn init_logger(st: &SystemTable) { let stdout = st.stdout(); // Construct the logger. - let logger = unsafe { + let logger = { LOGGER = Some(uefi_logger::Logger::new(stdout)); - LOGGER.as_ref().unwrap() }; @@ -84,10 +114,21 @@ fn init_logger() { log::set_max_level(log::LevelFilter::Info); } -fn init_alloc() { - let st = system_table(); - - uefi_alloc::init(st.boot); +/// Notify the utility library that boot services are not safe to call anymore +fn exit_boot_services(_e: Event) { + // DEBUG: The UEFI spec does not guarantee that this printout will work, as + // the services used by logging might already have been shut down. + // But it works on current OVMF, and can be used as a handy way to + // check that the callback does get called. + // + // info!("Shutting down the UEFI utility library"); + unsafe { + SYSTEM_TABLE = None; + if let Some(ref mut logger) = LOGGER { + logger.disable(); + } + } + uefi_alloc::exit_boot_services(); } #[lang = "eh_personality"] @@ -108,9 +149,8 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! { } // Give the user some time to read the message - if let Some(st) = unsafe { SYSTEM_TABLE } { - // FIXME: Check if boot-time services have been exited too - st.boot.stall(10_000_000); + if let Some(st) = unsafe { SYSTEM_TABLE.as_ref() } { + st.boot_services().stall(10_000_000); } else { let mut dummy = 0u64; // FIXME: May need different counter values in debug & release builds @@ -131,10 +171,10 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! { } // If the system table is available, use UEFI's standard shutdown mechanism - if let Some(st) = unsafe { SYSTEM_TABLE } { + if let Some(st) = unsafe { SYSTEM_TABLE.as_ref() } { use uefi::table::runtime::ResetType; - st.runtime - .reset(ResetType::Shutdown, uefi::Status::ABORTED, None) + st.runtime_services() + .reset(ResetType::Shutdown, uefi::Status::ABORTED, None); } // If we don't have any shutdown mechanism handy, the best we can do is loop diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index 376bd00eb..c5d9e11f1 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -18,9 +18,9 @@ mod boot; mod proto; #[no_mangle] -pub extern "win64" fn uefi_start(_handle: uefi::Handle, st: &'static SystemTable) -> Status { - // Initialize logging. - uefi_services::init(st); +pub extern "win64" fn uefi_start(image: uefi::Handle, st: SystemTable) -> Status { + // Initialize utilities (logging, memory allocation...) + uefi_services::init(&st).expect_success("Failed to initialize utilities"); // Reset the console before running all the other tests. st.stdout() @@ -31,16 +31,17 @@ pub extern "win64" fn uefi_start(_handle: uefi::Handle, st: &'static SystemTable check_revision(st.uefi_revision()); // Test all the boot services. - let bt = st.boot; + let bt = st.boot_services(); boot::test(bt); - // TODO: test the runtime services. - // We would have to call `exit_boot_services` first to ensure things work properly. - // Test all the supported protocols. - proto::test(st); + proto::test(&st); + + // TODO: test the runtime services. + // These work before boot services are exited, but we'd probably want to + // test them after exit_boot_services... - shutdown(st); + shutdown(image, st); } fn check_revision(rev: uefi::table::Revision) { @@ -64,10 +65,10 @@ fn check_revision(rev: uefi::table::Revision) { fn check_screenshot(bt: &BootServices, name: &str) { if cfg!(feature = "qemu") { // Access the serial port (in a QEMU environment, it should always be there) - let mut serial = bt + let serial = bt .find_protocol::() .expect("Could not find serial port"); - let serial = unsafe { serial.as_mut() }; + let serial = unsafe { &mut *serial.get() }; // Set a large timeout to avoid problems with Travis let mut io_mode = *serial.io_mode(); @@ -99,7 +100,7 @@ fn check_screenshot(bt: &BootServices, name: &str) { } } -fn shutdown(st: &SystemTable) -> ! { +fn shutdown(image: uefi::Handle, st: SystemTable) -> ! { use uefi::table::runtime::ResetType; // Get our text output back. @@ -108,11 +109,24 @@ fn shutdown(st: &SystemTable) -> ! { // Inform the user, and give him time to read on real hardware if cfg!(not(feature = "qemu")) { info!("Testing complete, shutting down in 3 seconds..."); - st.boot.stall(3_000_000); + st.boot_services().stall(3_000_000); } else { info!("Testing complete, shutting down..."); } - let rt = st.runtime; + // Exit boot services as a proof that it works :) + use crate::alloc::vec::Vec; + let max_mmap_size = st.boot_services().memory_map_size() + 1024; + let mut mmap_storage = unsafe { + let mut mmap_storage = Vec::with_capacity(max_mmap_size); + mmap_storage.set_len(max_mmap_size); + mmap_storage.into_boxed_slice() + }; + let (st, _iter) = st + .exit_boot_services(image, &mut mmap_storage[..]) + .expect_success("Failed to exit boot services"); + + // Shut down the system + let rt = unsafe { st.runtime_services() }; rt.reset(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 f07536698..cc2a30c02 100644 --- a/uefi-test-runner/src/proto/console/gop.rs +++ b/uefi-test-runner/src/proto/console/gop.rs @@ -5,8 +5,8 @@ use uefi_exts::BootServicesExt; pub fn test(bt: &BootServices) { info!("Running graphics output protocol test"); - if let Some(mut gop_proto) = bt.find_protocol::() { - let gop = unsafe { gop_proto.as_mut() }; + if let Some(gop) = bt.find_protocol::() { + let gop = unsafe { &mut *gop.get() }; set_graphics_mode(gop); fill_color(gop); diff --git a/uefi-test-runner/src/proto/console/mod.rs b/uefi-test-runner/src/proto/console/mod.rs index fc54b712f..95c876337 100644 --- a/uefi-test-runner/src/proto/console/mod.rs +++ b/uefi-test-runner/src/proto/console/mod.rs @@ -1,11 +1,11 @@ -use uefi::table::SystemTable; +use uefi::prelude::*; -pub fn test(st: &SystemTable) { +pub fn test(st: &SystemTable) { info!("Testing console protocols"); stdout::test(st.stdout()); - let bt = st.boot; + let bt = st.boot_services(); serial::test(bt); gop::test(bt); pointer::test(bt); diff --git a/uefi-test-runner/src/proto/console/pointer.rs b/uefi-test-runner/src/proto/console/pointer.rs index 591dcb91f..72ae75c08 100644 --- a/uefi-test-runner/src/proto/console/pointer.rs +++ b/uefi-test-runner/src/proto/console/pointer.rs @@ -6,8 +6,8 @@ use uefi_exts::BootServicesExt; pub fn test(bt: &BootServices) { info!("Running pointer protocol test"); - if let Some(mut pointer) = bt.find_protocol::() { - let pointer = unsafe { pointer.as_mut() }; + if let Some(pointer) = bt.find_protocol::() { + let pointer = unsafe { &mut *pointer.get() }; pointer .reset(false) diff --git a/uefi-test-runner/src/proto/console/serial.rs b/uefi-test-runner/src/proto/console/serial.rs index 4589a9789..c7ecf11d5 100644 --- a/uefi-test-runner/src/proto/console/serial.rs +++ b/uefi-test-runner/src/proto/console/serial.rs @@ -5,8 +5,8 @@ use uefi_exts::BootServicesExt; pub fn test(bt: &BootServices) { info!("Running serial protocol test"); - if let Some(mut serial) = bt.find_protocol::() { - let serial = unsafe { serial.as_mut() }; + if let Some(serial) = bt.find_protocol::() { + let serial = unsafe { &mut *serial.get() }; let old_ctrl_bits = serial .get_control_bits() diff --git a/uefi-test-runner/src/proto/debug.rs b/uefi-test-runner/src/proto/debug.rs index e1ab7ed6b..ba100f304 100644 --- a/uefi-test-runner/src/proto/debug.rs +++ b/uefi-test-runner/src/proto/debug.rs @@ -5,8 +5,8 @@ use uefi_exts::BootServicesExt; pub fn test(bt: &BootServices) { info!("Running UEFI debug connection protocol test"); - if let Some(mut debug_support_proto) = bt.find_protocol::() { - let debug_support = unsafe { debug_support_proto.as_mut() }; + if let Some(debug_support) = bt.find_protocol::() { + let debug_support = unsafe { &mut *debug_support.get() }; info!("- Architecture: {:?}", debug_support.arch()); } else { diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index b10377856..bc884b59a 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -3,10 +3,10 @@ use uefi_exts::BootServicesExt; use uefi::proto; -pub fn test(st: &SystemTable) { +pub fn test(st: &SystemTable) { info!("Testing various protocols"); - let bt = st.boot; + let bt = st.boot_services(); find_protocol(bt); @@ -15,7 +15,7 @@ pub fn test(st: &SystemTable) { } fn find_protocol(bt: &BootServices) { - type SearchedProtocol = proto::console::text::Output; + type SearchedProtocol<'a> = proto::console::text::Output<'a>; let handles = bt .find_handles::()