Skip to content

Commit 5e9ac05

Browse files
WIP: UEFI shell protocol testing
1 parent 98670a3 commit 5e9ac05

File tree

5 files changed

+263
-20
lines changed

5 files changed

+263
-20
lines changed
+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
extern crate alloc;
5+
6+
use alloc::string::ToString;
7+
use alloc::vec::Vec;
8+
use uefi::prelude::*;
9+
use uefi::proto::device_path::build::{self, DevicePathBuilder};
10+
use uefi::proto::device_path::{DeviceSubType, DeviceType, LoadedImageDevicePath};
11+
use uefi::proto::loaded_image::LoadedImage;
12+
use uefi::table::boot::LoadImageSource;
13+
use uefi::table::runtime::{ResetType, VariableVendor};
14+
use uefi::{CStr16, Result, Status};
15+
16+
use uefi::proto::device_path::DevicePath;
17+
fn print_dp(boot_services: &BootServices, path: &DevicePath) {
18+
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly};
19+
20+
let handle = boot_services
21+
.get_handle_for_protocol::<DevicePathToText>()
22+
.unwrap();
23+
24+
let to_text = boot_services
25+
.open_protocol_exclusive::<DevicePathToText>(handle)
26+
.unwrap();
27+
28+
log::info!(
29+
"path: {}",
30+
&*to_text
31+
.convert_device_path_to_text(
32+
boot_services,
33+
path,
34+
DisplayOnly(true),
35+
AllowShortcuts(false),
36+
)
37+
.unwrap()
38+
);
39+
}
40+
41+
fn boot_internal_shell(runtime_services: &RuntimeServices) -> Result {
42+
for var in runtime_services.variable_keys().unwrap() {
43+
if var.vendor != VariableVendor::GLOBAL_VARIABLE {
44+
continue;
45+
}
46+
let name = var.name().unwrap();
47+
let name_string = name.to_string();
48+
if !name_string.starts_with("Boot") {
49+
continue;
50+
}
51+
if name_string.len() != 8 {
52+
continue;
53+
}
54+
let number = &name_string[4..];
55+
let number = if let Ok(number) = u16::from_str_radix(number, 16) {
56+
number
57+
} else {
58+
continue;
59+
};
60+
61+
log::info!("var: {}", name);
62+
let (data, attr) = runtime_services
63+
.get_variable_boxed(name, &var.vendor)
64+
.unwrap();
65+
let description = unsafe { CStr16::from_ptr(data[6..].as_ptr().cast()) };
66+
log::info!("description: {}", description);
67+
68+
if description == cstr16!("EFI Internal Shell") {
69+
let file_path_list_length = u16::from_le_bytes(data[4..6].try_into().unwrap());
70+
log::info!("file_path_list_length: {file_path_list_length}");
71+
72+
// Convert to a vec, and chop off any existing `OptionalData[]` bytes.
73+
let mut data =
74+
data[..4 + 2 + description.num_bytes() + file_path_list_length as usize].to_vec();
75+
data.extend(cstr16!("shell.efi test_runner.efi").as_bytes());
76+
77+
log::info!("Setting up next boot for internal shell");
78+
79+
runtime_services
80+
.set_variable(name, &VariableVendor::GLOBAL_VARIABLE, attr, &data)
81+
.unwrap();
82+
83+
runtime_services
84+
.set_variable(
85+
cstr16!("BootNext"),
86+
&VariableVendor::GLOBAL_VARIABLE,
87+
attr,
88+
&number.to_le_bytes(),
89+
)
90+
.unwrap();
91+
92+
return Ok(());
93+
}
94+
}
95+
96+
Status::ABORTED.to_result()
97+
}
98+
99+
// This launcher attempts to launch the shell app and run the main
100+
// uefi-test-running app inside that shell. This allows testing of protocols
101+
// that require the shell.
102+
//
103+
// Launching the shell programmatically allows us to avoid the built-in five
104+
// second delay when starting the shell.
105+
//
106+
// If the shell app is not present, the UEFI test runner is launched directly.
107+
#[entry]
108+
fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
109+
uefi_services::init(&mut st).unwrap();
110+
111+
let boot_services = st.boot_services();
112+
113+
let loaded_image_device_path = boot_services
114+
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
115+
.unwrap();
116+
117+
print_dp(boot_services, &loaded_image_device_path);
118+
119+
let mut storage = Vec::new();
120+
let mut builder = DevicePathBuilder::with_vec(&mut storage);
121+
for node in loaded_image_device_path.node_iter() {
122+
if node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) {
123+
break;
124+
}
125+
builder = builder.push(&node).unwrap();
126+
}
127+
builder = builder
128+
.push(&build::media::FilePath {
129+
path_name: cstr16!(r"efi\boot\shell.efi"),
130+
})
131+
.unwrap();
132+
let new_image_path = builder.finalize().unwrap();
133+
134+
// TODO
135+
let new_image = if let Ok(new_image) = boot_services.load_image(
136+
image,
137+
LoadImageSource::FromFilePath {
138+
file_path: new_image_path,
139+
from_boot_manager: false,
140+
},
141+
) {
142+
new_image
143+
} else {
144+
log::info!("EFI shell app not found");
145+
146+
let runtime_services = st.runtime_services();
147+
148+
boot_internal_shell(runtime_services).unwrap();
149+
150+
log::info!("Rebooting");
151+
runtime_services.reset(ResetType::Warm, Status::SUCCESS, None);
152+
};
153+
154+
let mut new_loaded_image = boot_services
155+
.open_protocol_exclusive::<LoadedImage>(new_image)
156+
.unwrap();
157+
let load_options = cstr16!(r"shell.efi test_runner.efi");
158+
unsafe {
159+
new_loaded_image.set_load_options(
160+
load_options.as_ptr().cast(),
161+
load_options.num_bytes() as u32,
162+
);
163+
}
164+
165+
boot_services.start_image(new_image).unwrap();
166+
167+
Status::SUCCESS
168+
}

uefi-test-runner/src/main.rs

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
5151

5252
boot::test(bt);
5353

54+
{
55+
let handles = bt
56+
.locate_handle_buffer(uefi::table::boot::SearchType::ByProtocol(&uefi::guid!(
57+
"752f3136-4e16-4fdc-a22a-e5f46812f4ca"
58+
)))
59+
.unwrap();
60+
info!("BISH: {handles:?}");
61+
}
62+
5463
// Test all the supported protocols.
5564
proto::test(image, &mut st);
5665

uefi/src/proto/device_path/mod.rs

+30
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ use crate::proto::{unsafe_protocol, ProtocolPointer};
8585
use core::ffi::c_void;
8686
use core::fmt::{self, Debug, Formatter};
8787
use core::mem;
88+
use core::ops::Deref;
8889
use ptr_meta::Pointee;
8990

9091
opaque_type! {
@@ -610,6 +611,35 @@ pub enum NodeConversionError {
610611
UnsupportedType,
611612
}
612613

614+
/// Protocol for accessing the device path that was passed in to [`load_image`]
615+
/// when loading a PE/COFF image.
616+
///
617+
/// The layout of this type is the same as a [`DevicePath`].
618+
///
619+
/// [`load_image`]: crate::table::boot::BootServices::load_image
620+
#[repr(transparent)]
621+
#[unsafe_protocol("bc62157e-3e33-4fec-9920-2d3b36d750df")]
622+
#[derive(Pointee)]
623+
pub struct LoadedImageDevicePath(DevicePath);
624+
625+
impl ProtocolPointer for LoadedImageDevicePath {
626+
unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self {
627+
ptr_meta::from_raw_parts(ptr.cast(), DevicePath::size_in_bytes_from_ptr(ptr))
628+
}
629+
630+
unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self {
631+
ptr_meta::from_raw_parts_mut(ptr.cast(), DevicePath::size_in_bytes_from_ptr(ptr))
632+
}
633+
}
634+
635+
impl Deref for LoadedImageDevicePath {
636+
type Target = DevicePath;
637+
638+
fn deref(&self) -> &DevicePath {
639+
&self.0
640+
}
641+
}
642+
613643
#[cfg(test)]
614644
mod tests {
615645
use super::*;

uefi/src/table/boot.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -986,10 +986,11 @@ impl BootServices {
986986
/// image.
987987
///
988988
/// If the image is successfully loaded, a [`Handle`] supporting the
989-
/// [`LoadedImage`] and `LoadedImageDevicePath` protocols is
989+
/// [`LoadedImage`] and [`LoadedImageDevicePath`] protocols is
990990
/// returned. The image can be started with [`start_image`] or
991991
/// unloaded with [`unload_image`].
992992
///
993+
/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath
993994
/// [`start_image`]: BootServices::start_image
994995
/// [`unload_image`]: BootServices::unload_image
995996
///

0 commit comments

Comments
 (0)