Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 131 additions & 2 deletions src/table/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! UEFI services available at runtime, even after the OS boots.

use super::Header;
use crate::result::Error;
use crate::table::boot::MemoryDescriptor;
use crate::{Result, Status};
use crate::{CStr16, Char16, Guid, Result, Status};
use bitflags::bitflags;
use core::fmt;
use core::mem::MaybeUninit;
Expand All @@ -26,7 +27,27 @@ pub struct RuntimeServices {
desc_version: u32,
virtual_map: *mut MemoryDescriptor,
) -> Status,
_pad2: [usize; 5],
_pad2: usize,
get_variable: unsafe extern "efiapi" fn(
variable_name: *const Char16,
vendor_guid: *const Guid,
attributes: *mut VariableAttributes,
data_size: *mut usize,
data: *mut u8,
) -> Status,
get_next_variable_name: unsafe extern "efiapi" fn(
variable_name_size: *mut usize,
variable_name: *mut u16,
vendor_guid: *mut Guid,
) -> Status,
set_variable: unsafe extern "efiapi" fn(
variable_name: *const Char16,
vendor_guid: *const Guid,
attributes: VariableAttributes,
data_size: usize,
data: *const u8,
) -> Status,
_pad3: usize,
reset: unsafe extern "efiapi" fn(
rt: ResetType,

Expand Down Expand Up @@ -82,6 +103,68 @@ impl RuntimeServices {
(self.set_virtual_address_map)(map_size, entry_size, entry_version, map_ptr).into()
}

/// Get the size (in bytes) of a variable. This can be used to find out how
/// big of a buffer should be passed in to `get_variable`.
pub fn get_variable_size(&self, name: &CStr16, vendor: &Guid) -> Result<usize> {
let mut data_size = 0;
let status = unsafe {
(self.get_variable)(
name.as_ptr(),
vendor,
ptr::null_mut(),
&mut data_size,
ptr::null_mut(),
)
};

if status == Status::BUFFER_TOO_SMALL {
Status::SUCCESS.into_with_val(|| data_size)
} else {
Err(Error::from(status))
}
}

/// Get the contents and attributes of a variable. The size of `buf` must
/// be at least as big as the variable's size, although it can be
/// larger. If it is too small, `BUFFER_TOO_SMALL` is returned.
///
/// On success, a tuple containing the variable's value (a slice of `buf`)
/// and the variable's attributes is returned.
pub fn get_variable<'a>(
&self,
name: &CStr16,
vendor: &Guid,
buf: &'a mut [u8],
) -> Result<(&'a [u8], VariableAttributes)> {
let mut attributes = VariableAttributes::empty();
let mut data_size = buf.len();
unsafe {
(self.get_variable)(
name.as_ptr(),
vendor,
&mut attributes,
&mut data_size,
buf.as_mut_ptr(),
)
.into_with_val(move || (&buf[..data_size], attributes))
}
}

/// Set the value of a variable. This can be used to create a new variable,
/// update an existing variable, or (when the size of `data` is zero)
/// delete a variable.
pub fn set_variable(
&self,
name: &CStr16,
vendor: &Guid,
attributes: VariableAttributes,
data: &[u8],
) -> Result {
unsafe {
(self.set_variable)(name.as_ptr(), vendor, attributes, data.len(), data.as_ptr()).into()
}
}

/// Resets the computer.
pub fn reset(&self, rt: ResetType, status: Status, data: Option<&[u8]>) -> ! {
let (size, data) = match data {
Expand Down Expand Up @@ -245,6 +328,52 @@ pub struct TimeCapabilities {
pub sets_to_zero: bool,
}

bitflags! {
/// Flags describing the attributes of a variable.
pub struct VariableAttributes: u32 {
/// Variable is maintained across a power cycle.
const NON_VOLATILE = 0x01;

/// Variable is accessible during the time that boot services are
/// accessible.
const BOOTSERVICE_ACCESS = 0x02;

/// Variable is accessible during the time that runtime services are
/// accessible.
const RUNTIME_ACCESS = 0x04;

/// Variable is stored in the portion of NVR allocated for error
/// records.
const HARDWARE_ERROR_RECORD = 0x08;

/// Deprecated.
const AUTHENTICATED_WRITE_ACCESS = 0x10;

/// Variable payload begins with an EFI_VARIABLE_AUTHENTICATION_2
/// structure.
const TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20;

/// This is never set in the attributes returned by
/// `get_variable`. When passed to `set_variable`, the variable payload
/// will be appended to the current value of the variable if supported
/// by the firmware.
const APPEND_WRITE = 0x40;

/// Variable payload begins with an EFI_VARIABLE_AUTHENTICATION_3
/// structure.
const ENHANCED_AUTHENTICATED_ACCESS = 0x80;
}
}

/// Vendor GUID used to access global variables.
pub const GLOBAL_VARIABLE: Guid = Guid::from_values(
0x8be4df61,
0x93ca,
0x11d2,
0xaa0d,
[0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c],
);

/// The type of system reset.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
Expand Down
9 changes: 6 additions & 3 deletions uefi-test-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use uefi::table::boot::MemoryDescriptor;

mod boot;
mod proto;
mod runtime;

#[entry]
fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
Expand Down Expand Up @@ -45,9 +46,11 @@ fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
// Test all the supported protocols.
proto::test(&mut 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...
// TODO: runtime services work before boot services are exited, but we'd
// probably want to test them after exit_boot_services. However,
// exit_boot_services is currently called during shutdown.

runtime::test(st.runtime_services());

shutdown(image, st);
}
Expand Down
8 changes: 8 additions & 0 deletions uefi-test-runner/src/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use uefi::table::runtime::RuntimeServices;

pub fn test(rt: &RuntimeServices) {
info!("Testing runtime services");
vars::test(rt);
}

mod vars;
59 changes: 59 additions & 0 deletions uefi-test-runner/src/runtime/vars.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use alloc::vec::Vec;
use log::info;
use uefi::prelude::*;
use uefi::table::runtime::VariableAttributes;
use uefi::{CStr16, Guid};

struct CString16(Vec<u16>);

impl CString16 {
fn from_str(input: &str) -> CString16 {
let mut v: Vec<u16> = input.encode_utf16().collect();
v.push(0);
CString16(v)
}

fn as_cstr16(&self) -> &CStr16 {
match CStr16::from_u16_with_nul(&self.0) {
Ok(s) => s,
Err(_) => panic!("invalid string"),
}
}
}

fn test_variables(rt: &RuntimeServices) {
let name = CString16::from_str("UefiRsTestVar");
let test_value = b"TestValue";
let test_attrs = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS;

// Arbitrary GUID generated for this test.
let vendor = Guid::from_values(
0x9baf21cf,
0xe187,
0x497e,
0xae77,
[0x5b, 0xd8, 0xb0, 0xe0, 0x97, 0x03],
);

info!("Testing set_variable");
rt.set_variable(name.as_cstr16(), &vendor, test_attrs, test_value)
.expect_success("failed to set variable");

info!("Testing get_variable_size");
let size = rt
.get_variable_size(name.as_cstr16(), &vendor)
.expect_success("failed to get variable size");
assert_eq!(size, test_value.len());

info!("Testing get_variable");
let mut buf = [0u8; 9];
let (data, attrs) = rt
.get_variable(name.as_cstr16(), &vendor, &mut buf)
.expect_success("failed to get variable");
assert_eq!(data, test_value);
assert_eq!(attrs, test_attrs);
}

pub fn test(rt: &RuntimeServices) {
test_variables(rt);
}