Auto merge of #105861 - Ayush1325:uefi-std-minimial, r=workingjubilee

Add Minimal Std implementation for UEFI

# Implemented modules:
1. alloc
2. os_str
3. env
4. math

# Related Links
Tracking Issue: https://github.com/rust-lang/rust/issues/100499
API Change Proposal: https://github.com/rust-lang/libs-team/issues/87

# Additional Information
This was originally part of https://github.com/rust-lang/rust/pull/100316. Since that PR was becoming too unwieldy and cluttered, and with suggestion from `@dvdhrm,` I have extracted a minimal std implementation to this PR.

The example in `src/doc/rustc/src/platform-support/unknown-uefi.md` has been tested for `x86_64-unknown-uefi` and `i686-unknown-uefi` in OVMF. It would be great if someone more familiar with AARCH64 can help with testing for that target.

Signed-off-by: Ayush Singh <ayushsingh1325@gmail.com>
This commit is contained in:
bors 2023-09-24 09:47:30 +00:00
commit c7224e3c95
25 changed files with 957 additions and 21 deletions

View File

@ -3006,6 +3006,27 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575fc2d9b3da54adbdfaddf6eca48fec256d977c8630a1750b8991347d1ac911"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "r-efi-alloc"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d6f09fe2b6ad044bc3d2c34ce4979796581afd2f1ebc185837e02421e02fd7"
dependencies = [
"compiler_builtins",
"r-efi",
"rustc-std-workspace-core",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -5012,6 +5033,8 @@ dependencies = [
"panic_abort",
"panic_unwind",
"profiler_builtins",
"r-efi",
"r-efi-alloc",
"rand",
"rand_xorshift",
"rustc-demangle",

View File

@ -420,9 +420,11 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
rust_main_def_id: DefId,
entry_type: EntryFnType,
) -> Bx::Function {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
// depending on whether the target needs `argc` and `argv` to be passed in.
let llfty = if cx.sess().target.main_needs_argc_argv {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`, or
// `usize efi_main(void *handle, void *system_table)` depending on the target.
let llfty = if cx.sess().target.os.contains("uefi") {
cx.type_func(&[cx.type_ptr(), cx.type_ptr()], cx.type_isize())
} else if cx.sess().target.main_needs_argc_argv {
cx.type_func(&[cx.type_int(), cx.type_ptr()], cx.type_int())
} else {
cx.type_func(&[], cx.type_int())
@ -485,8 +487,12 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
};
let result = bx.call(start_ty, None, None, start_fn, &args, None);
let cast = bx.intcast(result, cx.type_int(), true);
bx.ret(cast);
if cx.sess().target.os.contains("uefi") {
bx.ret(result);
} else {
let cast = bx.intcast(result, cx.type_int(), true);
bx.ret(cast);
}
llfn
}
@ -497,7 +503,17 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
bx: &mut Bx,
) -> (Bx::Value, Bx::Value) {
if cx.sess().target.main_needs_argc_argv {
if cx.sess().target.os.contains("uefi") {
// Params for UEFI
let param_handle = bx.get_param(0);
let param_system_table = bx.get_param(1);
let arg_argc = bx.const_int(cx.type_isize(), 2);
let arg_argv = bx.alloca(cx.type_array(cx.type_ptr(), 2), Align::ONE);
bx.store(param_handle, arg_argv, Align::ONE);
let arg_argv_el1 = bx.gep(cx.type_ptr(), arg_argv, &[bx.const_int(cx.type_int(), 1)]);
bx.store(param_system_table, arg_argv_el1, Align::ONE);
(arg_argc, arg_argv)
} else if cx.sess().target.main_needs_argc_argv {
// Params from native `main()` used as args for rust start function
let param_argc = bx.get_param(0);
let param_argv = bx.get_param(1);

View File

@ -46,6 +46,7 @@ pub fn opts() -> TargetOptions {
stack_probes: StackProbeType::Call,
singlethread: true,
linker: Some("rust-lld".into()),
entry_name: "efi_main".into(),
..base
}
}

View File

@ -5,13 +5,14 @@
// The win64 ABI is used. It differs from the sysv64 ABI, so we must use a windows target with
// LLVM. "x86_64-unknown-windows" is used to get the minimal subset of windows-specific features.
use crate::spec::Target;
use crate::{abi::call::Conv, spec::Target};
pub fn target() -> Target {
let mut base = super::uefi_msvc_base::opts();
base.cpu = "x86-64".into();
base.plt_by_default = false;
base.max_atomic_width = Some(64);
base.entry_abi = Conv::X86_64Win64;
// We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to
// enable these CPU features explicitly before their first use, otherwise their instructions

View File

@ -44,7 +44,8 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 {
}
} else if #[cfg(any(target_os = "hermit",
all(target_vendor = "fortanix", target_env = "sgx"),
target_os = "xous"
target_os = "xous",
target_os = "uefi",
))] {
unsafe fn abort() -> ! {
// call std::sys::abort_internal

View File

@ -48,6 +48,10 @@ hermit-abi = { version = "0.3.2", features = ['rustc-dep-of-std'], public = true
[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false }
[target.'cfg(target_os = "uefi")'.dependencies]
r-efi = { version = "4.2.0", features = ['rustc-dep-of-std']}
r-efi-alloc = { version = "1.0.0", features = ['rustc-dep-of-std']}
[features]
backtrace = [
"gimli-symbolize",

View File

@ -39,6 +39,7 @@ fn main() {
|| target.contains("nto")
|| target.contains("xous")
|| target.contains("hurd")
|| target.contains("uefi")
// See src/bootstrap/synthetic_targets.rs
|| env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok()
{
@ -51,7 +52,6 @@ fn main() {
// - mipsel-sony-psp
// - nvptx64-nvidia-cuda
// - arch=avr
// - uefi (x86_64-unknown-uefi, i686-unknown-uefi)
// - JSON targets
// - Any new targets that have not been explicitly added above.
println!("cargo:rustc-cfg=feature=\"restricted-std\"");

View File

@ -1,14 +1,14 @@
#[cfg(test)]
mod tests;
#[cfg(target_pointer_width = "64")]
#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))]
mod repr_bitpacked;
#[cfg(target_pointer_width = "64")]
#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))]
use repr_bitpacked::Repr;
#[cfg(not(target_pointer_width = "64"))]
#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))]
mod repr_unpacked;
#[cfg(not(target_pointer_width = "64"))]
#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))]
use repr_unpacked::Repr;
use crate::error;

View File

@ -142,6 +142,8 @@ pub mod solid;
#[cfg(target_os = "tvos")]
#[path = "ios/mod.rs"]
pub(crate) mod tvos;
#[cfg(target_os = "uefi")]
pub mod uefi;
#[cfg(target_os = "vita")]
pub mod vita;
#[cfg(target_os = "vxworks")]

View File

@ -0,0 +1,92 @@
//! UEFI-specific extensions to the primitives in `std::env` module
#![unstable(feature = "uefi_std", issue = "100499")]
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::{ffi::c_void, ptr::NonNull};
static SYSTEM_TABLE: AtomicPtr<c_void> = AtomicPtr::new(crate::ptr::null_mut());
static IMAGE_HANDLE: AtomicPtr<c_void> = AtomicPtr::new(crate::ptr::null_mut());
// Flag to check if BootServices are still valid.
// Start with assuming that they are not available
static BOOT_SERVICES_FLAG: AtomicBool = AtomicBool::new(false);
/// Initializes the global System Table and Image Handle pointers.
///
/// The standard library requires access to the UEFI System Table and the Application Image Handle
/// to operate. Those are provided to UEFI Applications via their application entry point. By
/// calling `init_globals()`, those pointers are retained by the standard library for future use.
/// Thus this function must be called before any of the standard library services are used.
///
/// The pointers are never exposed to any entity outside of this application and it is guaranteed
/// that, once the application exited, these pointers are never dereferenced again.
///
/// Callers are required to ensure the pointers are valid for the entire lifetime of this
/// application. In particular, UEFI Boot Services must not be exited while an application with the
/// standard library is loaded.
///
/// # SAFETY
/// Calling this function more than once will panic
pub(crate) unsafe fn init_globals(handle: NonNull<c_void>, system_table: NonNull<c_void>) {
IMAGE_HANDLE
.compare_exchange(
crate::ptr::null_mut(),
handle.as_ptr(),
Ordering::Release,
Ordering::Acquire,
)
.unwrap();
SYSTEM_TABLE
.compare_exchange(
crate::ptr::null_mut(),
system_table.as_ptr(),
Ordering::Release,
Ordering::Acquire,
)
.unwrap();
BOOT_SERVICES_FLAG.store(true, Ordering::Release)
}
/// Get the SystemTable Pointer.
/// If you want to use `BootServices` then please use [`boot_services`] as it performs some
/// additional checks.
///
/// Note: This function panics if the System Table or Image Handle is not initialized
pub fn system_table() -> NonNull<c_void> {
try_system_table().unwrap()
}
/// Get the ImageHandle Pointer.
///
/// Note: This function panics if the System Table or Image Handle is not initialized
pub fn image_handle() -> NonNull<c_void> {
try_image_handle().unwrap()
}
/// Get the BootServices Pointer.
/// This function also checks if `ExitBootServices` has already been called.
pub fn boot_services() -> Option<NonNull<c_void>> {
if BOOT_SERVICES_FLAG.load(Ordering::Acquire) {
let system_table: NonNull<r_efi::efi::SystemTable> = try_system_table()?.cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services).map(|x| x.cast())
} else {
None
}
}
/// Get the SystemTable Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_system_table() -> Option<NonNull<c_void>> {
NonNull::new(SYSTEM_TABLE.load(Ordering::Acquire))
}
/// Get the SystemHandle Pointer.
/// This function is mostly intended for places where panicking is not an option
pub(crate) fn try_image_handle() -> Option<NonNull<c_void>> {
NonNull::new(IMAGE_HANDLE.load(Ordering::Acquire))
}
pub(crate) fn disable_boot_services() {
BOOT_SERVICES_FLAG.store(false, Ordering::Release)
}

View File

@ -0,0 +1,8 @@
//! Platform-specific extensions to `std` for UEFI.
#![unstable(feature = "uefi_std", issue = "100499")]
#![doc(cfg(target_os = "uefi"))]
pub mod env;
#[path = "../windows/ffi.rs"]
pub mod ffi;

View File

@ -6,7 +6,7 @@
// "static" is for single-threaded platforms where a global static is sufficient.
cfg_if::cfg_if! {
if #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] {
if #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] {
#[doc(hidden)]
mod static_local;
#[doc(hidden)]

View File

@ -47,6 +47,9 @@ cfg_if::cfg_if! {
} else if #[cfg(target_os = "xous")] {
mod xous;
pub use self::xous::*;
} else if #[cfg(target_os = "uefi")] {
mod uefi;
pub use self::uefi::*;
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
mod sgx;
pub use self::sgx::*;
@ -114,4 +117,5 @@ pub fn log_wrapper<F: Fn(f64) -> f64>(n: f64, log_fn: F) -> f64 {
log_fn(n)
}
#[cfg(not(target_os = "uefi"))]
pub type RawOsError = i32;

View File

@ -0,0 +1,33 @@
//! Global Allocator for UEFI.
//! Uses [r-efi-alloc](https://crates.io/crates/r-efi-alloc)
use crate::alloc::{GlobalAlloc, Layout, System};
const MEMORY_TYPE: u32 = r_efi::efi::LOADER_DATA;
#[stable(feature = "alloc_system_type", since = "1.28.0")]
unsafe impl GlobalAlloc for System {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Return null pointer if boot services are not available
if crate::os::uefi::env::boot_services().is_none() {
return crate::ptr::null_mut();
}
// If boot services is valid then SystemTable is not null.
let system_table = crate::os::uefi::env::system_table().as_ptr().cast();
// The caller must ensure non-0 layout
unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Do nothing if boot services are not available
if crate::os::uefi::env::boot_services().is_none() {
return;
}
// If boot services is valid then SystemTable is not null.
let system_table = crate::os::uefi::env::system_table().as_ptr().cast();
// The caller must ensure non-0 layout
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
}
}

View File

@ -0,0 +1,9 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "uefi";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ".efi";
pub const EXE_EXTENSION: &str = "efi";
}

View File

@ -0,0 +1,141 @@
//! 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
//! 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::{self, Guid};
use crate::mem::{size_of, MaybeUninit};
use crate::os::uefi;
use crate::ptr::NonNull;
use crate::{
io::{self, const_io_error},
os::uefi::env::boot_services,
};
const BOOT_SERVICES_UNAVAILABLE: io::Error =
const_io_error!(io::ErrorKind::Other, "Boot Services are no longer available");
/// Locate Handles with a particular Protocol GUID
/// 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>>> {
fn inner(
guid: &mut Guid,
boot_services: NonNull<r_efi::efi::BootServices>,
buf_size: &mut usize,
buf: *mut r_efi::efi::Handle,
) -> io::Result<()> {
let r = unsafe {
((*boot_services.as_ptr()).locate_handle)(
r_efi::efi::BY_PROTOCOL,
guid,
crate::ptr::null_mut(),
buf_size,
buf,
)
};
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}
let boot_services = boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
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()) {
Ok(()) => unreachable!(),
Err(e) => match e.kind() {
io::ErrorKind::FileTooLarge => {}
_ => return Err(e),
},
}
// The returned buf_len is in bytes
assert_eq!(buf_len % size_of::<r_efi::efi::Handle>(), 0);
let num_of_handles = buf_len / size_of::<r_efi::efi::Handle>();
let mut buf: Vec<r_efi::efi::Handle> = Vec::with_capacity(num_of_handles);
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
Ok(()) => {
// This is safe because the call will succeed only if buf_len >= required length.
// Also, on success, the `buf_len` is updated with the size of bufferv (in bytes) written
unsafe { buf.set_len(num_of_handles) };
Ok(buf.into_iter().filter_map(|x| NonNull::new(x)).collect())
}
Err(e) => Err(e),
}
}
/// Open Protocol on a handle.
/// 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>(
handle: NonNull<crate::ffi::c_void>,
mut protocol_guid: Guid,
) -> io::Result<NonNull<T>> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let system_handle = uefi::env::image_handle();
let mut protocol: MaybeUninit<*mut T> = MaybeUninit::uninit();
let r = unsafe {
((*boot_services.as_ptr()).open_protocol)(
handle.as_ptr(),
&mut protocol_guid,
protocol.as_mut_ptr().cast(),
system_handle.as_ptr(),
crate::ptr::null_mut(),
r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL,
)
};
if r.is_error() {
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
} else {
NonNull::new(unsafe { protocol.assume_init() })
.ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
pub(crate) fn create_event(
signal: u32,
tpl: efi::Tpl,
handler: Option<efi::EventNotify>,
context: *mut crate::ffi::c_void,
) -> io::Result<NonNull<crate::ffi::c_void>> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let mut event: r_efi::efi::Event = crate::ptr::null_mut();
let r = unsafe {
let create_event = (*boot_services.as_ptr()).create_event;
(create_event)(signal, tpl, handler, context, &mut event)
};
if r.is_error() {
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
} else {
NonNull::new(event).ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
/// # SAFETY
/// - The supplied event must be valid
pub(crate) unsafe fn close_event(evt: NonNull<crate::ffi::c_void>) -> io::Result<()> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let r = unsafe {
let close_event = (*boot_services.as_ptr()).close_event;
(close_event)(evt.as_ptr())
};
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}

View File

@ -0,0 +1,244 @@
//! Platform-specific extensions to `std` for UEFI platforms.
//!
//! Provides access to platform-level information on UEFI platforms, and
//! exposes UEFI-specific functions that would otherwise be inappropriate as
//! part of the core `std` library.
//!
//! It exposes more ways to deal with platform-specific strings ([`OsStr`],
//! [`OsString`]), allows to set permissions more granularly, extract low-level
//! file descriptors from files and sockets, and has platform-specific helpers
//! for spawning processes.
//!
//! [`OsStr`]: crate::ffi::OsStr
//! [`OsString`]: crate::ffi::OsString
pub mod alloc;
#[path = "../unsupported/args.rs"]
pub mod args;
#[path = "../unix/cmath.rs"]
pub mod cmath;
pub mod env;
#[path = "../unsupported/fs.rs"]
pub mod fs;
#[path = "../unsupported/io.rs"]
pub mod io;
#[path = "../unsupported/locks/mod.rs"]
pub mod locks;
#[path = "../unsupported/net.rs"]
pub mod net;
#[path = "../unsupported/once.rs"]
pub mod once;
pub mod os;
#[path = "../windows/os_str.rs"]
pub mod os_str;
pub mod path;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
#[path = "../unsupported/process.rs"]
pub mod process;
#[path = "../unsupported/stdio.rs"]
pub mod stdio;
#[path = "../unsupported/thread.rs"]
pub mod thread;
#[path = "../unsupported/thread_local_key.rs"]
pub mod thread_local_key;
#[path = "../unsupported/thread_parking.rs"]
pub mod thread_parking;
#[path = "../unsupported/time.rs"]
pub mod time;
mod helpers;
#[cfg(test)]
mod tests;
pub type RawOsError = usize;
use crate::io as std_io;
use crate::os::uefi;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicPtr, Ordering};
pub mod memchr {
pub use core::slice::memchr::{memchr, memrchr};
}
static EXIT_BOOT_SERVICE_EVENT: AtomicPtr<crate::ffi::c_void> =
AtomicPtr::new(crate::ptr::null_mut());
/// # SAFETY
/// - must be called only once during runtime initialization.
/// - argc must be 2.
/// - argv must be &[Handle, *mut SystemTable].
pub(crate) unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
assert_eq!(argc, 2);
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() };
unsafe { uefi::env::init_globals(image_handle, system_table) };
// Register exit boot services handler
match helpers::create_event(
r_efi::efi::EVT_SIGNAL_EXIT_BOOT_SERVICES,
r_efi::efi::TPL_NOTIFY,
Some(exit_boot_service_handler),
crate::ptr::null_mut(),
) {
Ok(x) => {
if EXIT_BOOT_SERVICE_EVENT
.compare_exchange(
crate::ptr::null_mut(),
x.as_ptr(),
Ordering::Release,
Ordering::Acquire,
)
.is_err()
{
abort_internal();
};
}
Err(_) => abort_internal(),
}
}
/// # SAFETY
/// this is not guaranteed to run, for example when the program aborts.
/// - must be called only once during runtime cleanup.
pub unsafe fn cleanup() {
if let Some(exit_boot_service_event) =
NonNull::new(EXIT_BOOT_SERVICE_EVENT.swap(crate::ptr::null_mut(), Ordering::Acquire))
{
let _ = unsafe { helpers::close_event(exit_boot_service_event) };
}
}
#[inline]
pub const fn unsupported<T>() -> std_io::Result<T> {
Err(unsupported_err())
}
#[inline]
pub const fn unsupported_err() -> std_io::Error {
std_io::const_io_error!(std_io::ErrorKind::Unsupported, "operation not supported on UEFI",)
}
pub fn decode_error_kind(code: RawOsError) -> crate::io::ErrorKind {
use crate::io::ErrorKind;
use r_efi::efi::Status;
match r_efi::efi::Status::from_usize(code) {
Status::ALREADY_STARTED
| Status::COMPROMISED_DATA
| Status::CONNECTION_FIN
| Status::CRC_ERROR
| Status::DEVICE_ERROR
| Status::END_OF_MEDIA
| Status::HTTP_ERROR
| Status::ICMP_ERROR
| Status::INCOMPATIBLE_VERSION
| Status::LOAD_ERROR
| Status::MEDIA_CHANGED
| Status::NO_MAPPING
| Status::NO_MEDIA
| Status::NOT_STARTED
| Status::PROTOCOL_ERROR
| Status::PROTOCOL_UNREACHABLE
| Status::TFTP_ERROR
| Status::VOLUME_CORRUPTED => ErrorKind::Other,
Status::BAD_BUFFER_SIZE | Status::INVALID_LANGUAGE => ErrorKind::InvalidData,
Status::ABORTED => ErrorKind::ConnectionAborted,
Status::ACCESS_DENIED => ErrorKind::PermissionDenied,
Status::BUFFER_TOO_SMALL => ErrorKind::FileTooLarge,
Status::CONNECTION_REFUSED => ErrorKind::ConnectionRefused,
Status::CONNECTION_RESET => ErrorKind::ConnectionReset,
Status::END_OF_FILE => ErrorKind::UnexpectedEof,
Status::HOST_UNREACHABLE => ErrorKind::HostUnreachable,
Status::INVALID_PARAMETER => ErrorKind::InvalidInput,
Status::IP_ADDRESS_CONFLICT => ErrorKind::AddrInUse,
Status::NETWORK_UNREACHABLE => ErrorKind::NetworkUnreachable,
Status::NO_RESPONSE => ErrorKind::HostUnreachable,
Status::NOT_FOUND => ErrorKind::NotFound,
Status::NOT_READY => ErrorKind::ResourceBusy,
Status::OUT_OF_RESOURCES => ErrorKind::OutOfMemory,
Status::SECURITY_VIOLATION => ErrorKind::PermissionDenied,
Status::TIMEOUT => ErrorKind::TimedOut,
Status::UNSUPPORTED => ErrorKind::Unsupported,
Status::VOLUME_FULL => ErrorKind::StorageFull,
Status::WRITE_PROTECTED => ErrorKind::ReadOnlyFilesystem,
_ => ErrorKind::Uncategorized,
}
}
pub fn abort_internal() -> ! {
if let Some(exit_boot_service_event) =
NonNull::new(EXIT_BOOT_SERVICE_EVENT.load(Ordering::Acquire))
{
let _ = unsafe { helpers::close_event(exit_boot_service_event) };
}
if let (Some(boot_services), Some(handle)) =
(uefi::env::boot_services(), uefi::env::try_image_handle())
{
let boot_services: NonNull<r_efi::efi::BootServices> = boot_services.cast();
let _ = unsafe {
((*boot_services.as_ptr()).exit)(
handle.as_ptr(),
r_efi::efi::Status::ABORTED,
0,
crate::ptr::null_mut(),
)
};
}
// In case SystemTable and ImageHandle cannot be reached, use `core::intrinsics::abort`
core::intrinsics::abort();
}
// This function is needed by the panic runtime. The symbol is named in
// pre-link args for the target specification, so keep that in sync.
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn __rust_abort() {
abort_internal();
}
#[inline]
pub fn hashmap_random_keys() -> (u64, u64) {
get_random().unwrap()
}
fn get_random() -> Option<(u64, u64)> {
use r_efi::protocols::rng;
let mut buf = [0u8; 16];
let handles = helpers::locate_handles(rng::PROTOCOL_GUID).ok()?;
for handle in handles {
if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
let r = unsafe {
((*protocol.as_ptr()).get_rng)(
protocol.as_ptr(),
crate::ptr::null_mut(),
buf.len(),
buf.as_mut_ptr(),
)
};
if r.is_error() {
continue;
} else {
return Some((
u64::from_le_bytes(buf[..8].try_into().ok()?),
u64::from_le_bytes(buf[8..].try_into().ok()?),
));
}
}
}
None
}
/// Disable access to BootServices if `EVT_SIGNAL_EXIT_BOOT_SERVICES` is signaled
extern "efiapi" fn exit_boot_service_handler(_e: r_efi::efi::Event, _ctx: *mut crate::ffi::c_void) {
uefi::env::disable_boot_services();
}
pub fn is_interrupted(_code: RawOsError) -> bool {
false
}

View File

@ -0,0 +1,237 @@
use super::{unsupported, RawOsError};
use crate::error::Error as StdError;
use crate::ffi::{OsStr, OsString};
use crate::fmt;
use crate::io;
use crate::marker::PhantomData;
use crate::os::uefi;
use crate::path::{self, PathBuf};
use crate::ptr::NonNull;
use r_efi::efi::Status;
pub fn errno() -> RawOsError {
0
}
pub fn error_string(errno: RawOsError) -> String {
// Keep the List in Alphabetical Order
// The Messages are taken from UEFI Specification Appendix D - Status Codes
match r_efi::efi::Status::from_usize(errno) {
Status::ABORTED => "The operation was aborted.".to_owned(),
Status::ACCESS_DENIED => "Access was denied.".to_owned(),
Status::ALREADY_STARTED => "The protocol has already been started.".to_owned(),
Status::BAD_BUFFER_SIZE => "The buffer was not the proper size for the request.".to_owned(),
Status::BUFFER_TOO_SMALL => {
"The buffer is not large enough to hold the requested data. The required buffer size is returned in the appropriate parameter when this error occurs.".to_owned()
}
Status::COMPROMISED_DATA => {
"The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status.".to_owned()
}
Status::CONNECTION_FIN => {
"The receiving operation fails because the communication peer has closed the connection and there is no more data in the receive buffer of the instance.".to_owned()
}
Status::CONNECTION_REFUSED => {
"The receiving or transmission operation fails because this connection is refused.".to_owned()
}
Status::CONNECTION_RESET => {
"The connect fails because the connection is reset either by instance itself or the communication peer.".to_owned()
}
Status::CRC_ERROR => "A CRC error was detected.".to_owned(),
Status::DEVICE_ERROR => "The physical device reported an error while attempting the operation.".to_owned()
,
Status::END_OF_FILE => {
"The end of the file was reached.".to_owned()
}
Status::END_OF_MEDIA => {
"Beginning or end of media was reached".to_owned()
}
Status::HOST_UNREACHABLE => {
"The remote host is not reachable.".to_owned()
}
Status::HTTP_ERROR => {
"A HTTP error occurred during the network operation.".to_owned()
}
Status::ICMP_ERROR => {
"An ICMP error occurred during the network operation.".to_owned()
}
Status::INCOMPATIBLE_VERSION => {
"The function encountered an internal version that was incompatible with a version requested by the caller.".to_owned()
}
Status::INVALID_LANGUAGE => {
"The language specified was invalid.".to_owned()
}
Status::INVALID_PARAMETER => {
"A parameter was incorrect.".to_owned()
}
Status::IP_ADDRESS_CONFLICT => {
"There is an address conflict address allocation".to_owned()
}
Status::LOAD_ERROR => {
"The image failed to load.".to_owned()
}
Status::MEDIA_CHANGED => {
"The medium in the device has changed since the last access.".to_owned()
}
Status::NETWORK_UNREACHABLE => {
"The network containing the remote host is not reachable.".to_owned()
}
Status::NO_MAPPING => {
"A mapping to a device does not exist.".to_owned()
}
Status::NO_MEDIA => {
"The device does not contain any medium to perform the operation.".to_owned()
}
Status::NO_RESPONSE => {
"The server was not found or did not respond to the request.".to_owned()
}
Status::NOT_FOUND => "The item was not found.".to_owned(),
Status::NOT_READY => {
"There is no data pending upon return.".to_owned()
}
Status::NOT_STARTED => {
"The protocol has not been started.".to_owned()
}
Status::OUT_OF_RESOURCES => {
"A resource has run out.".to_owned()
}
Status::PROTOCOL_ERROR => {
"A protocol error occurred during the network operation.".to_owned()
}
Status::PROTOCOL_UNREACHABLE => {
"An ICMP protocol unreachable error is received.".to_owned()
}
Status::SECURITY_VIOLATION => {
"The function was not performed due to a security violation.".to_owned()
}
Status::TFTP_ERROR => {
"A TFTP error occurred during the network operation.".to_owned()
}
Status::TIMEOUT => "The timeout time expired.".to_owned(),
Status::UNSUPPORTED => {
"The operation is not supported.".to_owned()
}
Status::VOLUME_FULL => {
"There is no more space on the file system.".to_owned()
}
Status::VOLUME_CORRUPTED => {
"An inconstancy was detected on the file system causing the operating to fail.".to_owned()
}
Status::WRITE_PROTECTED => {
"The device cannot be written to.".to_owned()
}
_ => format!("Status: {}", errno),
}
}
pub fn getcwd() -> io::Result<PathBuf> {
unsupported()
}
pub fn chdir(_: &path::Path) -> io::Result<()> {
unsupported()
}
pub struct SplitPaths<'a>(!, PhantomData<&'a ()>);
pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> {
panic!("unsupported")
}
impl<'a> Iterator for SplitPaths<'a> {
type Item = PathBuf;
fn next(&mut self) -> Option<PathBuf> {
self.0
}
}
#[derive(Debug)]
pub struct JoinPathsError;
pub fn join_paths<I, T>(_paths: I) -> Result<OsString, JoinPathsError>
where
I: Iterator<Item = T>,
T: AsRef<OsStr>,
{
Err(JoinPathsError)
}
impl fmt::Display for JoinPathsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"not supported on this platform yet".fmt(f)
}
}
impl StdError for JoinPathsError {}
pub fn current_exe() -> io::Result<PathBuf> {
unsupported()
}
pub struct Env(!);
impl Env {
// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when <OsStr as Debug>::fmt matches <str as Debug>::fmt.
pub fn str_debug(&self) -> impl fmt::Debug + '_ {
let Self(inner) = self;
match *inner {}
}
}
impl Iterator for Env {
type Item = (OsString, OsString);
fn next(&mut self) -> Option<(OsString, OsString)> {
self.0
}
}
impl fmt::Debug for Env {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self(inner) = self;
match *inner {}
}
}
pub fn env() -> Env {
panic!("not supported on this platform")
}
pub fn getenv(_: &OsStr) -> Option<OsString> {
None
}
pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform"))
}
pub fn unsetenv(_: &OsStr) -> io::Result<()> {
Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform"))
}
pub fn temp_dir() -> PathBuf {
panic!("no filesystem on this platform")
}
pub fn home_dir() -> Option<PathBuf> {
None
}
pub fn exit(code: i32) -> ! {
if let (Some(boot_services), Some(handle)) =
(uefi::env::boot_services(), uefi::env::try_image_handle())
{
let boot_services: NonNull<r_efi::efi::BootServices> = boot_services.cast();
let _ = unsafe {
((*boot_services.as_ptr()).exit)(
handle.as_ptr(),
Status::from_usize(code as usize),
0,
crate::ptr::null_mut(),
)
};
}
crate::intrinsics::abort()
}
pub fn getpid() -> u32 {
panic!("no pids on this platform")
}

View File

@ -0,0 +1,25 @@
use super::unsupported;
use crate::ffi::OsStr;
use crate::io;
use crate::path::{Path, PathBuf, Prefix};
pub const MAIN_SEP_STR: &str = "\\";
pub const MAIN_SEP: char = '\\';
#[inline]
pub fn is_sep_byte(b: u8) -> bool {
b == b'\\'
}
#[inline]
pub fn is_verbatim_sep(b: u8) -> bool {
b == b'\\'
}
pub fn parse_prefix(_p: &OsStr) -> Option<Prefix<'_>> {
None
}
pub(crate) fn absolute(_path: &Path) -> io::Result<PathBuf> {
unsupported()
}

View File

@ -0,0 +1,21 @@
use super::alloc::*;
#[test]
fn align() {
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
// statically verified.
assert_eq!(POOL_ALIGNMENT, 8);
// Loop over allocation-request sizes from 0-256 and alignments from 1-128, and verify
// that in case of overalignment there is at least space for one additional pointer to
// store in the allocation.
for i in 0..256 {
for j in &[1, 2, 4, 8, 16, 32, 64, 128] {
if *j <= 8 {
assert_eq!(align_size(i, *j), i);
} else {
assert!(align_size(i, *j) > i + std::mem::size_of::<*mut ()>());
}
}
}
}

View File

@ -44,6 +44,7 @@ cfg_if::cfg_if! {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "l4re",
target_os = "uefi",
feature = "restricted-std",
all(target_family = "wasm", not(target_os = "emscripten")),
target_os = "xous",

View File

@ -532,11 +532,7 @@ pub struct Target {
impl Target {
pub fn from_triple(triple: &str) -> Self {
let mut target: Self = Default::default();
if triple.contains("-none")
|| triple.contains("nvptx")
|| triple.contains("switch")
|| triple.contains("-uefi")
{
if triple.contains("-none") || triple.contains("nvptx") || triple.contains("switch") {
target.no_std = true;
}
target

View File

@ -19,8 +19,8 @@ Available targets:
## Requirements
All UEFI targets can be used as `no-std` environments via cross-compilation.
Support for `std` is missing, but actively worked on. `alloc` is supported if
an allocator is provided by the user. No host tools are supported.
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.
The UEFI environment resembles the environment for Microsoft Windows, with some
minor differences. Therefore, cross-compiling for UEFI works with the same
@ -230,3 +230,76 @@ pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Statu
efi::Status::SUCCESS
}
```
## Rust std for UEFI
This section contains information on how to use std on UEFI.
### Build std
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`:
```toml
[rust]
lld = true
```
Then just build using `x.py`:
```sh
./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
```
### Implemented features
#### alloc
- Implemented using `EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`.
- Passes all the tests.
- Currently uses `EfiLoaderData` as the `EFI_ALLOCATE_POOL->PoolType`.
#### cmath
- Provided by compiler-builtins.
#### env
- Just some global constants.
#### locks
- The provided locks should work on all standard single-threaded UEFI implementations.
#### os_str
- 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
The following code features a valid UEFI application, including stdio and `alloc` (`OsString` and `Vec`):
This example can be compiled as binary crate via `cargo` using the toolchain
compiled from the above source (named custom):
```sh
cargo +custom build --target x86_64-unknown-uefi
```
```rust,ignore (platform-specific)
#![feature(uefi_std)]
use r_efi::{efi, protocols::simple_text_output};
use std::{
ffi::OsString,
os::uefi::{env, ffi::OsStrExt}
};
pub fn main() {
let st = env::system_table().as_ptr() as *mut efi::SystemTable;
let mut s: Vec<u16> = OsString::from("Hello World!\n").encode_wide().collect();
s.push(0);
let r =
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) -> efi::Status = (*con_out).output_string;
output_string(con_out, s.as_ptr() as *mut efi::Char16)
};
assert!(!r.is_error())
}
```
### BootServices
The current implementation of std makes `BootServices` unavailable once `ExitBootServices` is called. Refer to [Runtime Drivers](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/7_driver_entry_point/711_runtime_drivers) for more information regarding how to handle switching from using physical addresses to using virtual addresses.
Note: It should be noted that it is up to the user to drop all allocated memory before `ExitBootServices` is called.

View File

@ -18,6 +18,7 @@ const LICENSES: &[&str] = &[
"Apache-2.0/MIT",
"ISC",
"MIT / Apache-2.0",
"MIT OR Apache-2.0 OR LGPL-2.1-or-later", // r-efi, r-efi-alloc
"MIT OR Apache-2.0 OR Zlib", // tinyvec_macros
"MIT OR Apache-2.0",
"MIT OR Zlib OR Apache-2.0", // miniz_oxide
@ -217,6 +218,8 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"pulldown-cmark",
"punycode",
"quote",
"r-efi",
"r-efi-alloc",
"rand",
"rand_chacha",
"rand_core",

View File

@ -56,6 +56,7 @@ const EXCEPTION_PATHS: &[&str] = &[
"library/std/src/path.rs",
"library/std/src/sys_common", // Should only contain abstractions over platforms
"library/std/src/net/test.rs", // Utility helpers for tests
"library/std/src/io/error.rs", // Repr unpacked needed for UEFI
];
pub fn check(path: &Path, bad: &mut bool) {