@@ -1,5 +1,7 @@
|
|||||||
//! UEFI-specific extensions to the primitives in `std::env` module
|
//! UEFI-specific extensions to the primitives in `std::env` module
|
||||||
|
|
||||||
|
#![unstable(feature = "uefi_std", issue = "100499")]
|
||||||
|
|
||||||
use crate::ffi::c_void;
|
use crate::ffi::c_void;
|
||||||
use crate::ptr::NonNull;
|
use crate::ptr::NonNull;
|
||||||
use crate::sync::atomic::{AtomicPtr, Ordering};
|
use crate::sync::atomic::{AtomicPtr, Ordering};
|
||||||
@@ -22,21 +24,18 @@ static GLOBALS: OnceLock<(AtomicPtr<c_void>, AtomicPtr<c_void>)> = OnceLock::new
|
|||||||
/// standard library is loaded.
|
/// standard library is loaded.
|
||||||
///
|
///
|
||||||
/// This function must not be called more than once.
|
/// This function must not be called more than once.
|
||||||
#[unstable(feature = "uefi_std", issue = "100499")]
|
|
||||||
pub unsafe fn init_globals(handle: NonNull<c_void>, system_table: NonNull<c_void>) {
|
pub unsafe fn init_globals(handle: NonNull<c_void>, system_table: NonNull<c_void>) {
|
||||||
GLOBALS.set((AtomicPtr::new(system_table.as_ptr()), AtomicPtr::new(handle.as_ptr()))).unwrap()
|
GLOBALS.set((AtomicPtr::new(system_table.as_ptr()), AtomicPtr::new(handle.as_ptr()))).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the SystemTable Pointer.
|
/// Get the SystemTable Pointer.
|
||||||
/// Note: This function panics if the System Table and Image Handle is Not initialized
|
/// Note: This function panics if the System Table or Image Handle is not initialized
|
||||||
#[unstable(feature = "uefi_std", issue = "100499")]
|
|
||||||
pub fn system_table() -> NonNull<c_void> {
|
pub fn system_table() -> NonNull<c_void> {
|
||||||
try_system_table().unwrap()
|
try_system_table().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the SystemHandle Pointer.
|
/// Get the ImageHandle Pointer.
|
||||||
/// Note: This function panics if the System Table and Image Handle is Not initialized
|
/// Note: This function panics if the System Table or Image Handle is not initialized
|
||||||
#[unstable(feature = "uefi_std", issue = "100499")]
|
|
||||||
pub fn image_handle() -> NonNull<c_void> {
|
pub fn image_handle() -> NonNull<c_void> {
|
||||||
try_image_handle().unwrap()
|
try_image_handle().unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ unsafe impl GlobalAlloc for System {
|
|||||||
Some(x) => x.as_ptr() as *mut _,
|
Some(x) => x.as_ptr() as *mut _,
|
||||||
};
|
};
|
||||||
|
|
||||||
if layout.size() > 0 {
|
// The caller must ensure non-0 layout
|
||||||
unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) }
|
unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) }
|
||||||
} else {
|
|
||||||
layout.dangling().as_ptr()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
@@ -25,8 +22,7 @@ unsafe impl GlobalAlloc for System {
|
|||||||
None => handle_alloc_error(layout),
|
None => handle_alloc_error(layout),
|
||||||
Some(x) => x.as_ptr() as *mut _,
|
Some(x) => x.as_ptr() as *mut _,
|
||||||
};
|
};
|
||||||
if layout.size() > 0 {
|
// The caller must ensure non-0 layout
|
||||||
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
|
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
//! Contains most of the shared UEFI specific stuff. Some of this might be moved to `std::os::uefi`
|
//! Contains most of the shared UEFI specific stuff. Some of this might be moved to `std::os::uefi`
|
||||||
//! if needed but no point in adding extra public API when there is not Std support for UEFI in the
|
//! if needed but no point in adding extra public API when there is not Std support for UEFI in the
|
||||||
//! first place
|
//! first place
|
||||||
|
//!
|
||||||
|
//! Some Nomenclature
|
||||||
|
//! * Protocol:
|
||||||
|
//! - Protocols serve to enable communication between separately built modules, including drivers.
|
||||||
|
//! - Every protocol has a GUID associated with it. The GUID serves as the name for the protocol.
|
||||||
|
//! - Protocols are produced and consumed.
|
||||||
|
//! - More information about protocols can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/3_foundation/36_protocols_and_handles)
|
||||||
|
|
||||||
use r_efi::efi::Guid;
|
use r_efi::efi::Guid;
|
||||||
|
|
||||||
use crate::io::{self, const_io_error};
|
use crate::io::{self, const_io_error};
|
||||||
use crate::mem::MaybeUninit;
|
use crate::mem::{size_of, MaybeUninit};
|
||||||
use crate::os::uefi;
|
use crate::os::uefi;
|
||||||
use crate::ptr::NonNull;
|
use crate::ptr::NonNull;
|
||||||
|
|
||||||
// Locate handles with a particular protocol GUID
|
/// Locate Handles with a particular Protocol GUID
|
||||||
/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()`
|
/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()`
|
||||||
|
///
|
||||||
|
/// Returns an array of [Handles](r_efi::efi::Handle) that support a specified protocol.
|
||||||
pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ffi::c_void>>> {
|
pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ffi::c_void>>> {
|
||||||
fn inner(
|
fn inner(
|
||||||
guid: &mut Guid,
|
guid: &mut Guid,
|
||||||
@@ -34,6 +43,8 @@ pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ff
|
|||||||
let boot_services = boot_services();
|
let boot_services = boot_services();
|
||||||
let mut buf_len = 0usize;
|
let mut buf_len = 0usize;
|
||||||
|
|
||||||
|
// This should always fail since the size of buffer is 0. This call should update the buf_len
|
||||||
|
// variable with the required buffer length
|
||||||
match inner(&mut guid, boot_services, &mut buf_len, crate::ptr::null_mut()) {
|
match inner(&mut guid, boot_services, &mut buf_len, crate::ptr::null_mut()) {
|
||||||
Ok(()) => unreachable!(),
|
Ok(()) => unreachable!(),
|
||||||
Err(e) => match e.kind() {
|
Err(e) => match e.kind() {
|
||||||
@@ -44,21 +55,23 @@ pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ff
|
|||||||
|
|
||||||
// The returned buf_len is in bytes
|
// The returned buf_len is in bytes
|
||||||
let mut buf: Vec<r_efi::efi::Handle> =
|
let mut buf: Vec<r_efi::efi::Handle> =
|
||||||
Vec::with_capacity(buf_len / crate::mem::size_of::<r_efi::efi::Handle>());
|
Vec::with_capacity(buf_len / size_of::<r_efi::efi::Handle>());
|
||||||
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
|
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// SAFETY: This is safe because the call will succeed only if buf_len >= required
|
// This is safe because the call will succeed only if buf_len >= required length.
|
||||||
// length. Also, on success, the `buf_len` is updated with the size of bufferv (in
|
// Also, on success, the `buf_len` is updated with the size of bufferv (in bytes) written
|
||||||
// bytes) written
|
unsafe { buf.set_len(buf_len / size_of::<r_efi::efi::Handle>()) };
|
||||||
unsafe { buf.set_len(buf_len / crate::mem::size_of::<r_efi::efi::Handle>()) };
|
Ok(buf.into_iter().filter_map(|x| NonNull::new(x)).collect())
|
||||||
Ok(buf.iter().filter_map(|x| NonNull::new(*x)).collect())
|
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open Protocol on a handle
|
/// Open Protocol on a handle.
|
||||||
/// Implemented using `EFI_BOOT_SERVICES.OpenProtocol()`
|
/// Internally just a call to `EFI_BOOT_SERVICES.OpenProtocol()`.
|
||||||
|
///
|
||||||
|
/// Queries a handle to determine if it supports a specified protocol. If the protocol is
|
||||||
|
/// supported by the handle, it opens the protocol on behalf of the calling agent.
|
||||||
pub(crate) fn open_protocol<T>(
|
pub(crate) fn open_protocol<T>(
|
||||||
handle: NonNull<crate::ffi::c_void>,
|
handle: NonNull<crate::ffi::c_void>,
|
||||||
mut protocol_guid: Guid,
|
mut protocol_guid: Guid,
|
||||||
@@ -256,9 +269,7 @@ pub(crate) fn status_to_io_error(s: r_efi::efi::Status) -> io::Error {
|
|||||||
|
|
||||||
/// Get the BootServices Pointer.
|
/// Get the BootServices Pointer.
|
||||||
pub(crate) fn boot_services() -> NonNull<r_efi::efi::BootServices> {
|
pub(crate) fn boot_services() -> NonNull<r_efi::efi::BootServices> {
|
||||||
let system_table: NonNull<r_efi::efi::SystemTable> = uefi::env::system_table().cast();
|
try_boot_services().unwrap()
|
||||||
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
|
|
||||||
NonNull::new(boot_services).unwrap()
|
|
||||||
}
|
}
|
||||||
/// Get the BootServices Pointer.
|
/// Get the BootServices Pointer.
|
||||||
/// This function is mostly intended for places where panic is not an option
|
/// This function is mostly intended for places where panic is not an option
|
||||||
@@ -47,7 +47,7 @@ pub mod thread_local_key;
|
|||||||
#[path = "../unsupported/time.rs"]
|
#[path = "../unsupported/time.rs"]
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
||||||
pub(crate) mod common;
|
pub(crate) mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@@ -60,18 +60,20 @@ pub mod memchr {
|
|||||||
pub use core::slice::memchr::{memchr, memrchr};
|
pub use core::slice::memchr::{memchr, memrchr};
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: must be called only once during runtime initialization.
|
/// # SAFETY
|
||||||
// SAFETY: argc must be 2.
|
/// - must be called only once during runtime initialization.
|
||||||
// SAFETY: argv must be &[Handle, *mut SystemTable].
|
/// - argc must be 2.
|
||||||
pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
|
/// - argv must be &[Handle, *mut SystemTable].
|
||||||
|
pub(crate) unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
|
||||||
assert_eq!(argc, 2);
|
assert_eq!(argc, 2);
|
||||||
let image_handle = unsafe { NonNull::new(*argv as *mut crate::ffi::c_void).unwrap() };
|
let image_handle = unsafe { NonNull::new(*argv as *mut crate::ffi::c_void).unwrap() };
|
||||||
let system_table = unsafe { NonNull::new(*argv.add(1) as *mut crate::ffi::c_void).unwrap() };
|
let system_table = unsafe { NonNull::new(*argv.add(1) as *mut crate::ffi::c_void).unwrap() };
|
||||||
unsafe { crate::os::uefi::env::init_globals(image_handle, system_table) };
|
unsafe { crate::os::uefi::env::init_globals(image_handle, system_table) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: must be called only once during runtime cleanup.
|
/// # SAFETY
|
||||||
// NOTE: this is not guaranteed to run, for example when the program aborts.
|
/// this is not guaranteed to run, for example when the program aborts.
|
||||||
|
/// - must be called only once during runtime cleanup.
|
||||||
pub unsafe fn cleanup() {}
|
pub unsafe fn cleanup() {}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -89,7 +91,7 @@ pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind {
|
|||||||
use r_efi::efi::Status;
|
use r_efi::efi::Status;
|
||||||
|
|
||||||
if let Ok(code) = usize::try_from(code) {
|
if let Ok(code) = usize::try_from(code) {
|
||||||
common::status_to_io_error(Status::from_usize(code)).kind()
|
helpers::status_to_io_error(Status::from_usize(code)).kind()
|
||||||
} else {
|
} else {
|
||||||
ErrorKind::Uncategorized
|
ErrorKind::Uncategorized
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,7 @@ pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind {
|
|||||||
|
|
||||||
pub fn abort_internal() -> ! {
|
pub fn abort_internal() -> ! {
|
||||||
if let (Some(boot_services), Some(handle)) =
|
if let (Some(boot_services), Some(handle)) =
|
||||||
(common::try_boot_services(), uefi::env::try_image_handle())
|
(helpers::try_boot_services(), uefi::env::try_image_handle())
|
||||||
{
|
{
|
||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
((*boot_services.as_ptr()).exit)(
|
((*boot_services.as_ptr()).exit)(
|
||||||
@@ -130,9 +132,9 @@ fn get_random() -> Option<(u64, u64)> {
|
|||||||
use r_efi::protocols::rng;
|
use r_efi::protocols::rng;
|
||||||
|
|
||||||
let mut buf = [0u8; 16];
|
let mut buf = [0u8; 16];
|
||||||
let handles = common::locate_handles(rng::PROTOCOL_GUID).ok()?;
|
let handles = helpers::locate_handles(rng::PROTOCOL_GUID).ok()?;
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
if let Ok(protocol) = common::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
|
if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
((*protocol.as_ptr()).get_rng)(
|
((*protocol.as_ptr()).get_rng)(
|
||||||
protocol.as_ptr(),
|
protocol.as_ptr(),
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ cfg_if::cfg_if! {
|
|||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(any(target_os = "l4re",
|
if #[cfg(any(target_os = "l4re",
|
||||||
target_os = "hermit",
|
|
||||||
target_os = "uefi",
|
target_os = "uefi",
|
||||||
feature = "restricted-std",
|
feature = "restricted-std",
|
||||||
all(target_family = "wasm", not(target_os = "emscripten")),
|
all(target_family = "wasm", not(target_os = "emscripten")),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Available targets:
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
All UEFI targets can be used as `no-std` environments via cross-compilation.
|
All UEFI targets can be used as `no-std` environments via cross-compilation.
|
||||||
Support for `std` is present, but incomplete and extreamly new. `alloc` is supported if
|
Support for `std` is present, but incomplete and extremely new. `alloc` is supported if
|
||||||
an allocator is provided by the user or if using std. No host tools are supported.
|
an allocator is provided by the user or if using std. No host tools are supported.
|
||||||
|
|
||||||
The UEFI environment resembles the environment for Microsoft Windows, with some
|
The UEFI environment resembles the environment for Microsoft Windows, with some
|
||||||
@@ -238,22 +238,22 @@ This section contains information on how to use std on UEFI.
|
|||||||
The building std part is pretty much the same as the official [docs](https://rustc-dev-guide.rust-lang.org/getting-started.html).
|
The building std part is pretty much the same as the official [docs](https://rustc-dev-guide.rust-lang.org/getting-started.html).
|
||||||
The linker that should be used is `rust-lld`. Here is a sample `config.toml`:
|
The linker that should be used is `rust-lld`. Here is a sample `config.toml`:
|
||||||
```toml
|
```toml
|
||||||
[llvm]
|
|
||||||
download-ci-llvm = false
|
|
||||||
[rust]
|
[rust]
|
||||||
lld = true
|
lld = true
|
||||||
[target.x86_64-unknown-uefi]
|
|
||||||
linker = "rust-lld"
|
|
||||||
```
|
```
|
||||||
Then just build using `x.py`:
|
Then just build using `x.py`:
|
||||||
```sh
|
```sh
|
||||||
./x.py build --target x86_64-unknown-uefi
|
./x.py build --target x86_64-unknown-uefi --stage 1
|
||||||
|
```
|
||||||
|
Alternatively, it is possible to use the `build-std` feature. However, you must use a toolchain which has the UEFI std patches.
|
||||||
|
Then just build the project using the following command:
|
||||||
|
```sh
|
||||||
|
cargo build --target x86_64-unknown-uefi -Zbuild-std=std,panic_abort
|
||||||
```
|
```
|
||||||
|
|
||||||
### Std Requirements
|
### Std Requirements
|
||||||
The current std has a few basic requirements to function:
|
The current std has a few basic requirements to function:
|
||||||
1. Memory Allocation Services (`EFI_BOOT_SERVICES.AllocatePool()` and
|
1. Memory Allocation Services (`EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`) are available. This should be true in in the Driver Execution Environment or later.
|
||||||
`EFI_BOOT_SERVICES.FreePool()`) are available.
|
|
||||||
If the above requirement is satisfied, the Rust code will reach `main`.
|
If the above requirement is satisfied, the Rust code will reach `main`.
|
||||||
Now we will discuss what the different modules of std use in UEFI.
|
Now we will discuss what the different modules of std use in UEFI.
|
||||||
|
|
||||||
@@ -261,21 +261,19 @@ Now we will discuss what the different modules of std use in UEFI.
|
|||||||
#### alloc
|
#### alloc
|
||||||
- Implemented using `EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`.
|
- Implemented using `EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`.
|
||||||
- Passes all the tests.
|
- Passes all the tests.
|
||||||
- Some Quirks:
|
- Currently uses `EfiLoaderData` as the `EFI_ALLOCATE_POOL->PoolType`.
|
||||||
- Currently uses `EfiLoaderData` as the `EFI_ALLOCATE_POOL->PoolType`.
|
|
||||||
#### cmath
|
#### cmath
|
||||||
- Provided by compiler-builtins.
|
- Provided by compiler-builtins.
|
||||||
#### env
|
#### env
|
||||||
- Just some global consants.
|
- Just some global constants.
|
||||||
#### locks
|
#### locks
|
||||||
- Uses `unsupported/locks`.
|
- The provided locks should work on all standard single-threaded UEFI implementations.
|
||||||
- They should work for a platform without threads according to docs.
|
|
||||||
#### os_str
|
#### os_str
|
||||||
- Uses WTF-8 from windows.
|
- While the strings in UEFI should be valid UCS-2, in practice, many implementations just do not care and use UTF-16 strings.
|
||||||
|
- Thus, the current implementation supports full UTF-16 strings.
|
||||||
|
|
||||||
## Example: Hello World With std
|
## Example: Hello World With std
|
||||||
The following code is a valid UEFI application showing stdio in UEFI. It also
|
The following code features a valid UEFI application, including stdio and `alloc` (`OsString` and `Vec`):
|
||||||
uses `alloc` type `OsString` and `Vec`.
|
|
||||||
|
|
||||||
This example can be compiled as binary crate via `cargo` using the toolchain
|
This example can be compiled as binary crate via `cargo` using the toolchain
|
||||||
compiled from the above source (named custom):
|
compiled from the above source (named custom):
|
||||||
@@ -286,15 +284,21 @@ cargo +custom build --target x86_64-unknown-uefi
|
|||||||
|
|
||||||
```rust,ignore (platform-specific)
|
```rust,ignore (platform-specific)
|
||||||
use r_efi::efi;
|
use r_efi::efi;
|
||||||
use std::os::uefi::ffi::OsStrExt;
|
use std::{
|
||||||
use std::{ffi::OsString, panic};
|
ffi::OsString,
|
||||||
|
os::uefi::{env, ffi::OsStrExt}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let st = std::os::uefi::env::system_table().as_ptr() as *mut efi::SystemTable;
|
let st = env::system_table().as_ptr() as *mut efi::SystemTable;
|
||||||
let mut s: Vec<u16> = OsString::from("Hello World!\n").encode_wide().collect();
|
let mut s: Vec<u16> = OsString::from("Hello World!\n").encode_wide().collect();
|
||||||
s.push(0);
|
s.push(0);
|
||||||
let r =
|
let r =
|
||||||
unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16) };
|
unsafe {
|
||||||
|
let con_out: *mut simple_text_output::Protocol = (*st).con_out;
|
||||||
|
let output_string: extern "efiapi" fn(_: *mut simple_text_output::Protocol, *mut u16) = (*con_out).output_string;
|
||||||
|
output_string(con_out, s.as_ptr() as *mut efi::Char16)
|
||||||
|
};
|
||||||
assert!(!r.is_error())
|
assert!(!r.is_error())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user