Add Minimal Std implementation for UEFI

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

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

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.

Signed-off-by: Ayush Singh <ayushsingh1325@gmail.com>
This commit is contained in:
Ayush Singh 2022-12-18 09:54:54 +05:30 committed by Ayush Singh
parent 5a4e47ebed
commit 48c6ae0611
No known key found for this signature in database
GPG key ID: 05CEF5C789E55A74
24 changed files with 718 additions and 18 deletions

View file

@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.target": "x86_64-unknown-uefi"
}

View file

@ -3006,6 +3006,27 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7345c622833c6745e7b027a28aa95618813dc1f3c3de396206410267dce6f3"
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 @@ fn create_entry_fn<'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
// `Status efi_main(Handle hd, SystemTable *st)` 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 @@ fn create_entry_fn<'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,18 @@ 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_i8p(), 2), Align::ONE);
bx.store(param_handle, arg_argv, Align::ONE);
let arg_argv_el1 =
bx.gep(cx.type_ptr_to(cx.type_i8()), 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);
@ -549,7 +566,11 @@ pub fn allocator_kind_for_codegen(tcx: TyCtxt<'_>) -> Option<AllocatorKind> {
use rustc_middle::middle::dependency_format::Linkage;
list.iter().any(|&linkage| linkage == Linkage::Dynamic)
});
if any_dynamic_crate { None } else { tcx.allocator_kind(()) }
if any_dynamic_crate {
None
} else {
tcx.allocator_kind(())
}
}
pub fn codegen_crate<B: ExtraBackendMethods>(

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

@ -45,6 +45,7 @@ unsafe fn abort() -> ! {
} else if #[cfg(any(target_os = "hermit",
all(target_vendor = "fortanix", target_env = "sgx"),
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.1.0", features = ['rustc-dep-of-std', 'efiapi']}
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,7 @@ fn main() {
// - mipsel-sony-psp
// - nvptx64-nvidia-cuda
// - arch=avr
// - uefi (x86_64-unknown-uefi, i686-unknown-uefi)
// - tvos (aarch64-apple-tvos, x86_64-apple-tvos)
// - JSON targets
// - Any new targets that have not been explicitly added above.
println!("cargo:rustc-cfg=feature=\"restricted-std\"");

View file

@ -263,7 +263,6 @@
#![cfg_attr(target_os = "xous", feature(slice_ptr_len))]
//
// Language features:
// tidy-alphabetical-start
#![feature(alloc_error_handler)]
#![feature(allocator_internals)]
#![feature(allow_internal_unsafe)]

View file

@ -142,6 +142,8 @@ pub mod windows {}
#[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,54 @@
//! UEFI-specific extensions to the primitives in `std::env` module
use crate::ffi::c_void;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicPtr, Ordering};
use crate::sync::OnceLock;
// Position 0 = SystemTable
// Position 1 = ImageHandle
static GLOBALS: OnceLock<(AtomicPtr<c_void>, AtomicPtr<c_void>)> = OnceLock::new();
/// 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.
/// 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.
///
/// 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>) {
GLOBALS.set((AtomicPtr::new(system_table.as_ptr()), AtomicPtr::new(handle.as_ptr()))).unwrap()
}
/// Get the SystemTable Pointer.
/// Note: This function panics if the System Table and Image Handle is Not initialized
#[unstable(feature = "uefi_std", issue = "100499")]
pub fn system_table() -> NonNull<c_void> {
try_system_table().unwrap()
}
/// Get the SystemHandle Pointer.
/// Note: This function panics if the System Table and Image Handle is Not initialized
#[unstable(feature = "uefi_std", issue = "100499")]
pub fn image_handle() -> NonNull<c_void> {
try_image_handle().unwrap()
}
/// 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<crate::ffi::c_void>> {
NonNull::new(GLOBALS.get()?.0.load(Ordering::Acquire))
}
/// Get the SystemHandle Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_image_handle() -> Option<NonNull<crate::ffi::c_void>> {
NonNull::new(GLOBALS.get()?.1.load(Ordering::Acquire))
}

View file

@ -0,0 +1,7 @@
//! Platform-specific extensions to `std` for UEFI.
#![unstable(feature = "uefi_std", issue = "100499")]
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 @@
} 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::*;

View file

@ -0,0 +1,32 @@
//! Global Allocator for UEFI.
//! Uses [r-efi-alloc](https://crates.io/crates/r-efi-alloc)
use crate::alloc::{handle_alloc_error, 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 {
let system_table = match crate::os::uefi::env::try_system_table() {
None => return crate::ptr::null_mut(),
Some(x) => x.as_ptr() as *mut _,
};
if layout.size() > 0 {
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) {
let system_table = match crate::os::uefi::env::try_system_table() {
None => handle_alloc_error(layout),
Some(x) => x.as_ptr() as *mut _,
};
if layout.size() > 0 {
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
}
}
}

View file

@ -0,0 +1,269 @@
//! 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
use r_efi::efi::Guid;
use crate::io::{self, const_io_error};
use crate::mem::MaybeUninit;
use crate::os::uefi;
use crate::ptr::NonNull;
// Locate handles with a particular protocol GUID
/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()`
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(status_to_io_error(r)) } else { Ok(()) }
}
let boot_services = boot_services();
let mut buf_len = 0usize;
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
let mut buf: Vec<r_efi::efi::Handle> =
Vec::with_capacity(buf_len / crate::mem::size_of::<r_efi::efi::Handle>());
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
Ok(()) => {
// SAFETY: 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(buf_len / crate::mem::size_of::<r_efi::efi::Handle>()) };
Ok(buf.iter().filter_map(|x| NonNull::new(*x)).collect())
}
Err(e) => Err(e),
}
}
/// Open Protocol on a handle
/// Implemented using `EFI_BOOT_SERVICES.OpenProtocol()`
pub(crate) fn open_protocol<T>(
handle: NonNull<crate::ffi::c_void>,
mut protocol_guid: Guid,
) -> io::Result<NonNull<T>> {
let boot_services = boot_services();
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(status_to_io_error(r))
} else {
NonNull::new(unsafe { protocol.assume_init() })
.ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
pub(crate) fn status_to_io_error(s: r_efi::efi::Status) -> io::Error {
use io::ErrorKind;
use r_efi::efi::Status;
// Keep the List in Alphabetical Order
// The Messages are taken from UEFI Specification Appendix D - Status Codes
match s {
Status::ABORTED => {
const_io_error!(ErrorKind::ConnectionAborted, "The operation was aborted.")
}
Status::ACCESS_DENIED => {
const_io_error!(ErrorKind::PermissionDenied, "Access was denied.")
}
Status::ALREADY_STARTED => {
const_io_error!(ErrorKind::Other, "The protocol has already been started.")
}
Status::BAD_BUFFER_SIZE => {
const_io_error!(
ErrorKind::InvalidData,
"The buffer was not the proper size for the request."
)
}
Status::BUFFER_TOO_SMALL => {
const_io_error!(
ErrorKind::FileTooLarge,
"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."
)
}
Status::COMPROMISED_DATA => {
const_io_error!(
ErrorKind::Other,
"The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status."
)
}
Status::CONNECTION_FIN => {
const_io_error!(
ErrorKind::Other,
"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."
)
}
Status::CONNECTION_REFUSED => {
const_io_error!(
ErrorKind::ConnectionRefused,
"The receiving or transmission operation fails because this connection is refused."
)
}
Status::CONNECTION_RESET => {
const_io_error!(
ErrorKind::ConnectionReset,
"The connect fails because the connection is reset either by instance itself or the communication peer."
)
}
Status::CRC_ERROR => const_io_error!(ErrorKind::Other, "A CRC error was detected."),
Status::DEVICE_ERROR => const_io_error!(
ErrorKind::Other,
"The physical device reported an error while attempting the operation."
),
Status::END_OF_FILE => {
const_io_error!(ErrorKind::UnexpectedEof, "The end of the file was reached.")
}
Status::END_OF_MEDIA => {
const_io_error!(ErrorKind::Other, "Beginning or end of media was reached")
}
Status::HOST_UNREACHABLE => {
const_io_error!(ErrorKind::HostUnreachable, "The remote host is not reachable.")
}
Status::HTTP_ERROR => {
const_io_error!(ErrorKind::Other, "A HTTP error occurred during the network operation.")
}
Status::ICMP_ERROR => {
const_io_error!(
ErrorKind::Other,
"An ICMP error occurred during the network operation."
)
}
Status::INCOMPATIBLE_VERSION => {
const_io_error!(
ErrorKind::Other,
"The function encountered an internal version that was incompatible with a version requested by the caller."
)
}
Status::INVALID_LANGUAGE => {
const_io_error!(ErrorKind::InvalidData, "The language specified was invalid.")
}
Status::INVALID_PARAMETER => {
const_io_error!(ErrorKind::InvalidInput, "A parameter was incorrect.")
}
Status::IP_ADDRESS_CONFLICT => {
const_io_error!(ErrorKind::AddrInUse, "There is an address conflict address allocation")
}
Status::LOAD_ERROR => {
const_io_error!(ErrorKind::Other, "The image failed to load.")
}
Status::MEDIA_CHANGED => {
const_io_error!(
ErrorKind::Other,
"The medium in the device has changed since the last access."
)
}
Status::NETWORK_UNREACHABLE => {
const_io_error!(
ErrorKind::NetworkUnreachable,
"The network containing the remote host is not reachable."
)
}
Status::NO_MAPPING => {
const_io_error!(ErrorKind::Other, "A mapping to a device does not exist.")
}
Status::NO_MEDIA => {
const_io_error!(
ErrorKind::Other,
"The device does not contain any medium to perform the operation."
)
}
Status::NO_RESPONSE => {
const_io_error!(
ErrorKind::HostUnreachable,
"The server was not found or did not respond to the request."
)
}
Status::NOT_FOUND => const_io_error!(ErrorKind::NotFound, "The item was not found."),
Status::NOT_READY => {
const_io_error!(ErrorKind::ResourceBusy, "There is no data pending upon return.")
}
Status::NOT_STARTED => {
const_io_error!(ErrorKind::Other, "The protocol has not been started.")
}
Status::OUT_OF_RESOURCES => {
const_io_error!(ErrorKind::OutOfMemory, "A resource has run out.")
}
Status::PROTOCOL_ERROR => {
const_io_error!(
ErrorKind::Other,
"A protocol error occurred during the network operation."
)
}
Status::PROTOCOL_UNREACHABLE => {
const_io_error!(ErrorKind::Other, "An ICMP protocol unreachable error is received.")
}
Status::SECURITY_VIOLATION => {
const_io_error!(
ErrorKind::PermissionDenied,
"The function was not performed due to a security violation."
)
}
Status::TFTP_ERROR => {
const_io_error!(ErrorKind::Other, "A TFTP error occurred during the network operation.")
}
Status::TIMEOUT => const_io_error!(ErrorKind::TimedOut, "The timeout time expired."),
Status::UNSUPPORTED => {
const_io_error!(ErrorKind::Unsupported, "The operation is not supported.")
}
Status::VOLUME_FULL => {
const_io_error!(ErrorKind::StorageFull, "There is no more space on the file system.")
}
Status::VOLUME_CORRUPTED => {
const_io_error!(
ErrorKind::Other,
"An inconstancy was detected on the file system causing the operating to fail."
)
}
Status::WRITE_PROTECTED => {
const_io_error!(ErrorKind::ReadOnlyFilesystem, "The device cannot be written to.")
}
_ => io::Error::new(ErrorKind::Uncategorized, format!("Status: {}", s.as_usize())),
}
}
/// Get the BootServices Pointer.
pub(crate) fn boot_services() -> NonNull<r_efi::efi::BootServices> {
let system_table: NonNull<r_efi::efi::SystemTable> = uefi::env::system_table().cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services).unwrap()
}
/// Get the BootServices Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_boot_services() -> Option<NonNull<r_efi::efi::BootServices>> {
let system_table: NonNull<r_efi::efi::SystemTable> = uefi::env::try_system_table()?.cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services)
}

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,155 @@
//! 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
#![deny(unsafe_op_in_unsafe_fn)]
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;
#[path = "../unsupported/os.rs"]
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/time.rs"]
pub mod time;
pub(crate) mod common;
#[cfg(test)]
mod tests;
use crate::io as std_io;
use crate::os::uefi;
use crate::ptr::NonNull;
pub mod memchr {
pub use core::slice::memchr::{memchr, memrchr};
}
// SAFETY: must be called only once during runtime initialization.
// SAFETY: argc must be 2.
// SAFETY: argv must be &[Handle, *mut SystemTable].
pub 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 { crate::os::uefi::env::init_globals(image_handle, system_table) };
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {}
#[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: i32) -> crate::io::ErrorKind {
use crate::io::ErrorKind;
use r_efi::efi::Status;
if let Ok(code) = usize::try_from(code) {
common::status_to_io_error(Status::from_usize(code)).kind()
} else {
ErrorKind::Uncategorized
}
}
pub fn abort_internal() -> ! {
if let (Some(boot_services), Some(handle)) =
(common::try_boot_services(), uefi::env::try_image_handle())
{
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 = common::locate_handles(rng::PROTOCOL_GUID).ok()?;
for handle in handles {
if let Ok(protocol) = common::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
}

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,8 @@
cfg_if::cfg_if! {
if #[cfg(any(target_os = "l4re",
target_os = "hermit",
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 extreamly 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,71 @@ 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
[llvm]
download-ci-llvm = false
[rust]
lld = true
[target.x86_64-unknown-uefi]
linker = "rust-lld"
```
Then just build using `x.py`:
```sh
./x.py build --target x86_64-unknown-uefi
```
### Std Requirements
The current std has a few basic requirements to function:
1. Memory Allocation Services (`EFI_BOOT_SERVICES.AllocatePool()` and
`EFI_BOOT_SERVICES.FreePool()`) are available.
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.
### Implemented features
#### alloc
- Implemented using `EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`.
- Passes all the tests.
- Some Quirks:
- Currently uses `EfiLoaderData` as the `EFI_ALLOCATE_POOL->PoolType`.
#### cmath
- Provided by compiler-builtins.
#### env
- Just some global consants.
#### locks
- Uses `unsupported/locks`.
- They should work for a platform without threads according to docs.
#### os_str
- Uses WTF-8 from windows.
## Example: Hello World With std
The following code is a valid UEFI application showing stdio in UEFI. It also
uses `alloc` type `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)
use r_efi::efi;
use std::os::uefi::ffi::OsStrExt;
use std::{ffi::OsString, panic};
pub fn main() {
let st = std::os::uefi::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 { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16) };
assert!(!r.is_error())
}
```

View file

@ -18,6 +18,7 @@
"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 @@
"pulldown-cmark",
"punycode",
"quote",
"r-efi",
"r-efi-alloc",
"rand",
"rand_chacha",
"rand_core",