1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! This module provides miscellaneous opinionated but optional helpers to
//! better integrate your application with the Rust runtime and the Rust
//! ecosystem.
//!
//! For now, this includes:
//! - using [`uefi::allocator::Allocator`] as global allocator (feature `global_allocator`)
//! - an implementation of  [`log::Log`] (feature `logger`)
//! - [`print!`][print_macro] and [`println!`][println_macro] macros defaulting
//!   to the uefi boot service stdout stream
//! - default panic handler (feature `panic_handler`)
//!
//! **PLEASE NOTE** that these helpers are meant for the pre exit boot service
//! epoch.
//!
//! [print_macro]: uefi::print!
//! [println_macro]: uefi::println!

use crate::prelude::{Boot, SystemTable};
use crate::Result;
use crate::StatusExt;
use core::ffi::c_void;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
#[doc(hidden)]
pub use println::_print;
use uefi_raw::Status;

#[cfg(feature = "global_allocator")]
mod global_allocator;
#[cfg(feature = "logger")]
mod logger;
#[cfg(feature = "panic_handler")]
mod panic_handler;
mod println;

/// Reference to the system table.
///
/// 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 SYSTEM_TABLE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());

#[must_use]
fn system_table_opt() -> Option<SystemTable<Boot>> {
    let ptr = SYSTEM_TABLE.load(Ordering::Acquire);
    // Safety: the `SYSTEM_TABLE` pointer either be null or a valid system
    // table.
    //
    // Null is the initial value, as well as the value set when exiting boot
    // services. Otherwise, the value is set by the call to `init`, which
    // requires a valid system table reference as input.
    unsafe { SystemTable::from_ptr(ptr) }
}

/// 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.
///
/// The returned pointer is only valid until boot services are exited.
#[must_use]
// TODO do we want to keep this public?
pub fn system_table() -> SystemTable<Boot> {
    system_table_opt().expect("The system table handle is not available")
}

/// Initialize all helpers defined in [`uefi::helpers`] whose Cargo features
/// are activated.
///
/// This must be called as early as possible, before trying to use logging or
/// memory allocation capabilities.
///
/// **PLEASE NOTE** that these helpers are meant for the pre exit boot service
/// epoch.
pub fn init(st: &mut SystemTable<Boot>) -> Result<()> {
    if system_table_opt().is_some() {
        // Avoid double initialization.
        return Status::SUCCESS.to_result_with_val(|| ());
    }

    // Setup the system table singleton
    SYSTEM_TABLE.store(st.as_ptr().cast_mut(), Ordering::Release);

    // Setup logging and memory allocation

    #[cfg(feature = "logger")]
    unsafe {
        logger::init(st);
    }

    #[cfg(feature = "global_allocator")]
    unsafe {
        uefi::allocator::init(st);
    }

    Ok(())
}

pub(crate) fn exit() {
    // 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");
    SYSTEM_TABLE.store(ptr::null_mut(), Ordering::Release);

    #[cfg(feature = "logger")]
    logger::disable();

    #[cfg(feature = "global_allocator")]
    uefi::allocator::exit_boot_services();
}