Skip to content

Commit 48e958d

Browse files
nicholasbishopGabrielMajeri
authored andcommitted
Add support for getting/setting variables
Added three methods to `RuntimeServices`: `get_variable_size`, `get_variable`, and `set_variable`. Also added a test for these methods. There's a TODO comment about testing runtime services after existing boot services, but I'm not sure how best to structure that change, so for now I've added the new test prior to exiting boot services.
1 parent 5bcf9a2 commit 48e958d

File tree

4 files changed

+204
-5
lines changed

4 files changed

+204
-5
lines changed

src/table/runtime.rs

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! UEFI services available at runtime, even after the OS boots.
22
33
use super::Header;
4+
use crate::result::Error;
45
use crate::table::boot::MemoryDescriptor;
5-
use crate::{Result, Status};
6+
use crate::{CStr16, Char16, Guid, Result, Status};
67
use bitflags::bitflags;
78
use core::fmt;
89
use core::mem::MaybeUninit;
@@ -26,7 +27,27 @@ pub struct RuntimeServices {
2627
desc_version: u32,
2728
virtual_map: *mut MemoryDescriptor,
2829
) -> Status,
29-
_pad2: [usize; 5],
30+
_pad2: usize,
31+
get_variable: unsafe extern "efiapi" fn(
32+
variable_name: *const Char16,
33+
vendor_guid: *const Guid,
34+
attributes: *mut VariableAttributes,
35+
data_size: *mut usize,
36+
data: *mut u8,
37+
) -> Status,
38+
get_next_variable_name: unsafe extern "efiapi" fn(
39+
variable_name_size: *mut usize,
40+
variable_name: *mut u16,
41+
vendor_guid: *mut Guid,
42+
) -> Status,
43+
set_variable: unsafe extern "efiapi" fn(
44+
variable_name: *const Char16,
45+
vendor_guid: *const Guid,
46+
attributes: VariableAttributes,
47+
data_size: usize,
48+
data: *const u8,
49+
) -> Status,
50+
_pad3: usize,
3051
reset: unsafe extern "efiapi" fn(
3152
rt: ResetType,
3253

@@ -82,6 +103,68 @@ impl RuntimeServices {
82103
(self.set_virtual_address_map)(map_size, entry_size, entry_version, map_ptr).into()
83104
}
84105

106+
/// Get the size (in bytes) of a variable. This can be used to find out how
107+
/// big of a buffer should be passed in to `get_variable`.
108+
pub fn get_variable_size(&self, name: &CStr16, vendor: &Guid) -> Result<usize> {
109+
let mut data_size = 0;
110+
let status = unsafe {
111+
(self.get_variable)(
112+
name.as_ptr(),
113+
vendor,
114+
ptr::null_mut(),
115+
&mut data_size,
116+
ptr::null_mut(),
117+
)
118+
};
119+
120+
if status == Status::BUFFER_TOO_SMALL {
121+
Status::SUCCESS.into_with_val(|| data_size)
122+
} else {
123+
Err(Error::from(status))
124+
}
125+
}
126+
127+
/// Get the contents and attributes of a variable. The size of `buf` must
128+
/// be at least as big as the variable's size, although it can be
129+
/// larger. If it is too small, `BUFFER_TOO_SMALL` is returned.
130+
///
131+
/// On success, a tuple containing the variable's value (a slice of `buf`)
132+
/// and the variable's attributes is returned.
133+
pub fn get_variable<'a>(
134+
&self,
135+
name: &CStr16,
136+
vendor: &Guid,
137+
buf: &'a mut [u8],
138+
) -> Result<(&'a [u8], VariableAttributes)> {
139+
let mut attributes = VariableAttributes::empty();
140+
let mut data_size = buf.len();
141+
unsafe {
142+
(self.get_variable)(
143+
name.as_ptr(),
144+
vendor,
145+
&mut attributes,
146+
&mut data_size,
147+
buf.as_mut_ptr(),
148+
)
149+
.into_with_val(move || (&buf[..data_size], attributes))
150+
}
151+
}
152+
153+
/// Set the value of a variable. This can be used to create a new variable,
154+
/// update an existing variable, or (when the size of `data` is zero)
155+
/// delete a variable.
156+
pub fn set_variable(
157+
&self,
158+
name: &CStr16,
159+
vendor: &Guid,
160+
attributes: VariableAttributes,
161+
data: &[u8],
162+
) -> Result {
163+
unsafe {
164+
(self.set_variable)(name.as_ptr(), vendor, attributes, data.len(), data.as_ptr()).into()
165+
}
166+
}
167+
85168
/// Resets the computer.
86169
pub fn reset(&self, rt: ResetType, status: Status, data: Option<&[u8]>) -> ! {
87170
let (size, data) = match data {
@@ -245,6 +328,52 @@ pub struct TimeCapabilities {
245328
pub sets_to_zero: bool,
246329
}
247330

331+
bitflags! {
332+
/// Flags describing the attributes of a variable.
333+
pub struct VariableAttributes: u32 {
334+
/// Variable is maintained across a power cycle.
335+
const NON_VOLATILE = 0x01;
336+
337+
/// Variable is accessible during the time that boot services are
338+
/// accessible.
339+
const BOOTSERVICE_ACCESS = 0x02;
340+
341+
/// Variable is accessible during the time that runtime services are
342+
/// accessible.
343+
const RUNTIME_ACCESS = 0x04;
344+
345+
/// Variable is stored in the portion of NVR allocated for error
346+
/// records.
347+
const HARDWARE_ERROR_RECORD = 0x08;
348+
349+
/// Deprecated.
350+
const AUTHENTICATED_WRITE_ACCESS = 0x10;
351+
352+
/// Variable payload begins with an EFI_VARIABLE_AUTHENTICATION_2
353+
/// structure.
354+
const TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20;
355+
356+
/// This is never set in the attributes returned by
357+
/// `get_variable`. When passed to `set_variable`, the variable payload
358+
/// will be appended to the current value of the variable if supported
359+
/// by the firmware.
360+
const APPEND_WRITE = 0x40;
361+
362+
/// Variable payload begins with an EFI_VARIABLE_AUTHENTICATION_3
363+
/// structure.
364+
const ENHANCED_AUTHENTICATED_ACCESS = 0x80;
365+
}
366+
}
367+
368+
/// Vendor GUID used to access global variables.
369+
pub const GLOBAL_VARIABLE: Guid = Guid::from_values(
370+
0x8be4df61,
371+
0x93ca,
372+
0x11d2,
373+
0xaa0d,
374+
[0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c],
375+
);
376+
248377
/// The type of system reset.
249378
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
250379
#[repr(u32)]

uefi-test-runner/src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use uefi::table::boot::MemoryDescriptor;
1818

1919
mod boot;
2020
mod proto;
21+
mod runtime;
2122

2223
#[entry]
2324
fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
@@ -45,9 +46,11 @@ fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
4546
// Test all the supported protocols.
4647
proto::test(&mut st);
4748

48-
// TODO: test the runtime services.
49-
// These work before boot services are exited, but we'd probably want to
50-
// test them after exit_boot_services...
49+
// TODO: runtime services work before boot services are exited, but we'd
50+
// probably want to test them after exit_boot_services. However,
51+
// exit_boot_services is currently called during shutdown.
52+
53+
runtime::test(st.runtime_services());
5154

5255
shutdown(image, st);
5356
}

uefi-test-runner/src/runtime/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use uefi::table::runtime::RuntimeServices;
2+
3+
pub fn test(rt: &RuntimeServices) {
4+
info!("Testing runtime services");
5+
vars::test(rt);
6+
}
7+
8+
mod vars;

uefi-test-runner/src/runtime/vars.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use alloc::vec::Vec;
2+
use log::info;
3+
use uefi::prelude::*;
4+
use uefi::table::runtime::VariableAttributes;
5+
use uefi::{CStr16, Guid};
6+
7+
struct CString16(Vec<u16>);
8+
9+
impl CString16 {
10+
fn from_str(input: &str) -> CString16 {
11+
let mut v: Vec<u16> = input.encode_utf16().collect();
12+
v.push(0);
13+
CString16(v)
14+
}
15+
16+
fn as_cstr16(&self) -> &CStr16 {
17+
match CStr16::from_u16_with_nul(&self.0) {
18+
Ok(s) => s,
19+
Err(_) => panic!("invalid string"),
20+
}
21+
}
22+
}
23+
24+
fn test_variables(rt: &RuntimeServices) {
25+
let name = CString16::from_str("UefiRsTestVar");
26+
let test_value = b"TestValue";
27+
let test_attrs = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS;
28+
29+
// Arbitrary GUID generated for this test.
30+
let vendor = Guid::from_values(
31+
0x9baf21cf,
32+
0xe187,
33+
0x497e,
34+
0xae77,
35+
[0x5b, 0xd8, 0xb0, 0xe0, 0x97, 0x03],
36+
);
37+
38+
info!("Testing set_variable");
39+
rt.set_variable(name.as_cstr16(), &vendor, test_attrs, test_value)
40+
.expect_success("failed to set variable");
41+
42+
info!("Testing get_variable_size");
43+
let size = rt
44+
.get_variable_size(name.as_cstr16(), &vendor)
45+
.expect_success("failed to get variable size");
46+
assert_eq!(size, test_value.len());
47+
48+
info!("Testing get_variable");
49+
let mut buf = [0u8; 9];
50+
let (data, attrs) = rt
51+
.get_variable(name.as_cstr16(), &vendor, &mut buf)
52+
.expect_success("failed to get variable");
53+
assert_eq!(data, test_value);
54+
assert_eq!(attrs, test_attrs);
55+
}
56+
57+
pub fn test(rt: &RuntimeServices) {
58+
test_variables(rt);
59+
}

0 commit comments

Comments
 (0)