Skip to content

Commit 68b85bb

Browse files
Merge pull request #1297 from phip1611/load2
LoadFileProtocol and LoadFile2Protocol
2 parents ec98c83 + c612ea9 commit 68b85bb

File tree

8 files changed

+319
-3
lines changed

8 files changed

+319
-3
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

uefi-test-runner/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ publish = false
66
edition = "2021"
77

88
[dependencies]
9+
uefi-raw = { path = "../uefi-raw" }
910
uefi = { path = "../uefi", features = ["alloc", "global_allocator", "panic_handler", "logger", "qemu"] }
1011

1112
log.workspace = true

uefi-test-runner/src/proto/load.rs

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use alloc::boxed::Box;
2+
use alloc::string::{String, ToString};
3+
use alloc::vec::Vec;
4+
use core::ffi::c_void;
5+
use core::pin::Pin;
6+
use core::ptr;
7+
use core::ptr::addr_of;
8+
use uefi::prelude::BootServices;
9+
use uefi::proto::device_path::build::DevicePathBuilder;
10+
use uefi::proto::media::load_file::{LoadFile, LoadFile2};
11+
use uefi::proto::BootPolicy;
12+
use uefi::{Guid, Handle};
13+
use uefi_raw::protocol::device_path::DevicePathProtocol;
14+
use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol};
15+
use uefi_raw::Status;
16+
17+
unsafe extern "efiapi" fn raw_load_file(
18+
this: *mut LoadFile2Protocol,
19+
_file_path: *const DevicePathProtocol,
20+
_boot_policy: bool,
21+
buffer_size: *mut usize,
22+
buffer: *mut c_void,
23+
) -> Status {
24+
log::debug!("Called static extern \"efiapi\" `raw_load_file` glue function");
25+
let this = this.cast::<CustomLoadFile2Protocol>().as_ref().unwrap();
26+
this.load_file(buffer_size, buffer.cast())
27+
}
28+
29+
#[repr(C)]
30+
struct CustomLoadFile2Protocol {
31+
inner: LoadFile2Protocol,
32+
file_data: Vec<u8>,
33+
}
34+
35+
impl CustomLoadFile2Protocol {
36+
fn new(file_data: Vec<u8>) -> Pin<Box<Self>> {
37+
let inner = Self {
38+
inner: LoadFile2Protocol {
39+
load_file: raw_load_file,
40+
},
41+
file_data,
42+
};
43+
Box::pin(inner)
44+
}
45+
46+
fn load_file(&self, buf_len: *mut usize, buf: *mut c_void) -> Status {
47+
if buf.is_null() || unsafe { *buf_len } < self.file_data.len() {
48+
log::debug!("Returning buffer size");
49+
unsafe { *buf_len = self.file_data.len() };
50+
Status::BUFFER_TOO_SMALL
51+
} else {
52+
log::debug!("Writing file content to buffer");
53+
unsafe {
54+
ptr::copy_nonoverlapping(self.file_data.as_ptr(), buf.cast(), self.file_data.len());
55+
}
56+
Status::SUCCESS
57+
}
58+
}
59+
}
60+
61+
unsafe fn install_protocol(
62+
bt: &BootServices,
63+
handle: Handle,
64+
guid: Guid,
65+
protocol: &mut CustomLoadFile2Protocol,
66+
) {
67+
bt.install_protocol_interface(Some(handle), &guid, addr_of!(*protocol).cast())
68+
.unwrap();
69+
}
70+
71+
unsafe fn uninstall_protocol(
72+
bt: &BootServices,
73+
handle: Handle,
74+
guid: Guid,
75+
protocol: &mut CustomLoadFile2Protocol,
76+
) {
77+
bt.uninstall_protocol_interface(handle, &guid, addr_of!(*protocol).cast())
78+
.unwrap();
79+
}
80+
81+
/// This tests the LoadFile and LoadFile2 protocols. As this protocol is not
82+
/// implemented in OVMF for the default handle, we implement it manually using
83+
/// `install_protocol_interface`. Then, we load a file from our custom installed
84+
/// protocol leveraging our protocol abstraction.
85+
///
86+
/// The way we are implementing the LoadFile(2) protocol is roughly what certain
87+
/// Linux loaders do so that Linux can find its initrd [0, 1].
88+
///
89+
/// [0] https://github.com/u-boot/u-boot/commit/ec80b4735a593961fe701cc3a5d717d4739b0fd0#diff-1f940face4d1cf74f9d2324952759404d01ee0a81612b68afdcba6b49803bdbbR171
90+
/// [1] https://github.com/torvalds/linux/blob/ee9a43b7cfe2d8a3520335fea7d8ce71b8cabd9d/drivers/firmware/efi/libstub/efi-stub-helper.c#L550
91+
pub fn test(bt: &BootServices) {
92+
let image = bt.image_handle();
93+
94+
let load_data_msg = "Example file content.";
95+
let load_data = load_data_msg.to_string().into_bytes();
96+
let mut proto_load_file = CustomLoadFile2Protocol::new(load_data);
97+
// Get the ptr to the inner value, not the wrapping smart pointer type.
98+
let proto_load_file_ptr = proto_load_file.as_mut().get_mut();
99+
100+
// Install our custom protocol implementation as LoadFile and LoadFile2
101+
// protocol.
102+
unsafe {
103+
install_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
104+
install_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
105+
}
106+
107+
let mut dvp_vec = Vec::new();
108+
let dummy_dvp = DevicePathBuilder::with_vec(&mut dvp_vec);
109+
let dummy_dvp = dummy_dvp.finalize().unwrap();
110+
111+
let mut load_file_protocol = bt.open_protocol_exclusive::<LoadFile>(image).unwrap();
112+
let loadfile_file = load_file_protocol
113+
.load_file(dummy_dvp, BootPolicy::BootSelection)
114+
.unwrap();
115+
let loadfile_file_string = String::from_utf8(loadfile_file.to_vec()).unwrap();
116+
117+
let mut load_file2_protocol = bt.open_protocol_exclusive::<LoadFile2>(image).unwrap();
118+
let loadfile2_file = load_file2_protocol.load_file(dummy_dvp).unwrap();
119+
let loadfile2_file_string = String::from_utf8(loadfile2_file.to_vec()).unwrap();
120+
121+
assert_eq!(load_data_msg, &loadfile_file_string);
122+
assert_eq!(load_data_msg, &loadfile2_file_string);
123+
124+
// Cleanup: Uninstall protocols again.
125+
drop(load_file_protocol);
126+
drop(load_file2_protocol);
127+
unsafe {
128+
uninstall_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
129+
uninstall_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
130+
}
131+
// Ensure protocols have been uninstalled:
132+
assert_eq!(
133+
bt.open_protocol_exclusive::<LoadFile>(image)
134+
.map(|_| ()) // make Result Eq'able
135+
.map_err(|e| e.status()),
136+
Err(Status::UNSUPPORTED)
137+
);
138+
assert_eq!(
139+
bt.open_protocol_exclusive::<LoadFile2>(image)
140+
.map(|_| ()) // make Result Eq'able
141+
.map_err(|e| e.status()),
142+
Err(Status::UNSUPPORTED)
143+
);
144+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub fn test(st: &mut SystemTable<Boot>) {
1717
debug::test(bt);
1818
device_path::test(bt);
1919
driver::test(bt);
20+
load::test(bt);
2021
loaded_image::test(bt);
2122
media::test(bt);
2223
network::test(bt);
@@ -78,6 +79,7 @@ mod console;
7879
mod debug;
7980
mod device_path;
8081
mod driver;
82+
mod load;
8183
mod loaded_image;
8284
mod media;
8385
mod misc;

uefi/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ details of the new `system`/`boot`/`runtime` modules, and upcoming deprecations.
2020
the implementations `MemoryMapRef`, `MemoryMapRefMut`, and `MemoryMapOwned`.
2121
This comes with some changes. Read below. We recommend to directly use the
2222
implementations instead of the traits.
23+
- Added `LoadFile` and `LoadFile2` which abstracts over the `LOAD_FILE` and
24+
`LOAD_FILE2` protocols. The UEFI test runner includes an integration test
25+
that shows how Linux loaders can use this to implement the initrd loading
26+
mechanism used in Linux.
2327

2428
## Changed
2529
- **Breaking:** `uefi::helpers::init` no longer takes an argument.

uefi/src/proto/media/load_file.rs

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//! LoadFile and LoadFile2 protocols.
2+
3+
use crate::proto::unsafe_protocol;
4+
#[cfg(all(feature = "alloc", feature = "unstable"))]
5+
use alloc::alloc::Global;
6+
use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol};
7+
#[cfg(feature = "alloc")]
8+
use {
9+
crate::{mem::make_boxed, proto::device_path::DevicePath, Result, StatusExt},
10+
alloc::boxed::Box,
11+
uefi::proto::BootPolicy,
12+
};
13+
14+
/// Load File Protocol.
15+
///
16+
/// Used to obtain files, that are primarily boot options, from arbitrary
17+
/// devices.
18+
///
19+
/// # UEFI Spec Description
20+
/// The EFI_LOAD_FILE_PROTOCOL is a simple protocol used to obtain files from
21+
/// arbitrary devices.
22+
///
23+
/// When the firmware is attempting to load a file, it first attempts to use the
24+
/// device’s Simple File System protocol to read the file. If the file system
25+
/// protocol is found, the firmware implements the policy of interpreting the
26+
/// File Path value of the file being loaded. If the device does not support the
27+
/// file system protocol, the firmware then attempts to read the file via the
28+
/// EFI_LOAD_FILE_PROTOCOL and the LoadFile() function. In this case the
29+
/// LoadFile() function implements the policy of interpreting the File Path
30+
/// value.
31+
#[derive(Debug)]
32+
#[repr(transparent)]
33+
#[unsafe_protocol(LoadFileProtocol::GUID)]
34+
pub struct LoadFile(LoadFileProtocol);
35+
36+
impl LoadFile {
37+
/// Causes the driver to load a specified file.
38+
///
39+
/// # Parameters
40+
/// - `file_path` The device specific path of the file to load.
41+
/// - `boot_policy` The [`BootPolicy`] to use.
42+
///
43+
/// # Errors
44+
/// - `uefi::status::EFI_SUCCESS` The file was loaded.
45+
/// - `uefi::status::EFI_UNSUPPORTED` The device does not support the
46+
/// provided BootPolicy.
47+
/// - `uefi::status::EFI_INVALID_PARAMETER` FilePath is not a valid device
48+
/// path, or BufferSize is NULL.
49+
/// - `uefi::status::EFI_NO_MEDIA` No medium was present to load the file.
50+
/// - `uefi::status::EFI_DEVICE_ERROR` The file was not loaded due to a
51+
/// device error.
52+
/// - `uefi::status::EFI_NO_RESPONSE` The remote system did not respond.
53+
/// - `uefi::status::EFI_NOT_FOUND` The file was not found.
54+
/// - `uefi::status::EFI_ABORTED` The file load process was manually
55+
/// cancelled.
56+
/// - `uefi::status::EFI_BUFFER_TOO_SMALL` The BufferSize is too small to
57+
/// read the current directory entry. BufferSize has been updated with the
58+
/// size needed to complete the request.
59+
/// - `uefi::status::EFI_WARN_FILE_SYSTEM` The resulting Buffer contains
60+
/// UEFI-compliant file system.
61+
///
62+
/// [`BootPolicy`]: uefi::proto::BootPolicy
63+
#[cfg(feature = "alloc")]
64+
#[allow(clippy::extra_unused_lifetimes)] // false positive, it is used
65+
pub fn load_file<'a>(
66+
&mut self,
67+
file_path: &DevicePath,
68+
boot_policy: BootPolicy,
69+
) -> Result<Box<[u8]>> {
70+
let this = core::ptr::addr_of_mut!(*self).cast();
71+
72+
let fetch_data_fn = |buf: &'a mut [u8]| {
73+
let mut size = buf.len();
74+
let status = unsafe {
75+
(self.0.load_file)(
76+
this,
77+
file_path.as_ffi_ptr().cast(),
78+
boot_policy.into(),
79+
&mut size,
80+
buf.as_mut_ptr().cast(),
81+
)
82+
};
83+
status.to_result_with_err(|_| Some(size)).map(|_| buf)
84+
};
85+
86+
#[cfg(not(feature = "unstable"))]
87+
let file: Box<[u8]> = make_boxed::<[u8], _>(fetch_data_fn)?;
88+
89+
#[cfg(feature = "unstable")]
90+
let file = make_boxed::<[u8], _, _>(fetch_data_fn, Global)?;
91+
92+
Ok(file)
93+
}
94+
}
95+
96+
/// Load File2 Protocol.
97+
///
98+
/// The Load File2 protocol is used to obtain files from arbitrary devices that
99+
/// are not boot options.
100+
///
101+
/// # UEFI Spec Description
102+
///
103+
/// The EFI_LOAD_FILE2_PROTOCOL is a simple protocol used to obtain files from
104+
/// arbitrary devices that are not boot options. It is used by LoadImage() when
105+
/// its BootOption parameter is FALSE and the FilePath does not have an instance
106+
/// of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.
107+
#[derive(Debug)]
108+
#[repr(transparent)]
109+
#[unsafe_protocol(LoadFile2Protocol::GUID)]
110+
pub struct LoadFile2(LoadFile2Protocol);
111+
112+
impl LoadFile2 {
113+
/// Causes the driver to load a specified file.
114+
///
115+
/// # Parameters
116+
/// - `file_path` The device specific path of the file to load.
117+
///
118+
/// # Errors
119+
/// - `uefi::status::EFI_SUCCESS` The file was loaded.
120+
/// - `uefi::status::EFI_UNSUPPORTED` BootPolicy is TRUE.
121+
/// - `uefi::status::EFI_INVALID_PARAMETER` FilePath is not a valid device
122+
/// path, or BufferSize is NULL.
123+
/// - `uefi::status::EFI_NO_MEDIA` No medium was present to load the file.
124+
/// - `uefi::status::EFI_DEVICE_ERROR` The file was not loaded due to a
125+
/// device error.
126+
/// - `uefi::status::EFI_NO_RESPONSE` The remote system did not respond.
127+
/// - `uefi::status::EFI_NOT_FOUND` The file was not found.
128+
/// - `uefi::status::EFI_ABORTED` The file load process was manually
129+
/// cancelled.
130+
/// - `uefi::status::EFI_BUFFER_TOO_SMALL` The BufferSize is too small to
131+
/// read the current directory entry. BufferSize has been updated with the
132+
/// size needed to complete the request.
133+
#[cfg(feature = "alloc")]
134+
#[allow(clippy::extra_unused_lifetimes)] // false positive, it is used
135+
pub fn load_file<'a>(&mut self, file_path: &DevicePath) -> Result<Box<[u8]>> {
136+
let this = core::ptr::addr_of_mut!(*self).cast();
137+
138+
let fetch_data_fn = |buf: &'a mut [u8]| {
139+
let mut size = buf.len();
140+
let status = unsafe {
141+
(self.0.load_file)(
142+
this,
143+
file_path.as_ffi_ptr().cast(),
144+
false, /* always false - see spec */
145+
&mut size,
146+
buf.as_mut_ptr().cast(),
147+
)
148+
};
149+
status.to_result_with_err(|_| Some(size)).map(|_| buf)
150+
};
151+
152+
#[cfg(not(feature = "unstable"))]
153+
let file: Box<[u8]> = make_boxed::<[u8], _>(fetch_data_fn)?;
154+
155+
#[cfg(feature = "unstable")]
156+
let file = make_boxed::<[u8], _, _>(fetch_data_fn, Global)?;
157+
158+
Ok(file)
159+
}
160+
}

uefi/src/proto/media/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ pub mod file;
99
pub mod block;
1010
pub mod disk;
1111
pub mod fs;
12+
pub mod load_file;
1213
pub mod partition;

uefi/src/table/boot.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1383,9 +1383,12 @@ pub enum LoadImageSource<'a> {
13831383
/// Load an image via the [`SimpleFileSystem`] protocol. If there is
13841384
/// no instance of that protocol associated with the path then the
13851385
/// behavior depends on [`BootPolicy`]. If [`BootPolicy::BootSelection`],
1386-
/// attempt to load via the `LoadFile` protocol. If
1387-
/// [`BootPolicy::ExactMatch`], attempt to load via the `LoadFile2`
1388-
/// protocol, then fall back to `LoadFile`.
1386+
/// attempt to load via the [`LoadFile`] protocol. If
1387+
/// [`BootPolicy::ExactMatch`], attempt to load via the [`LoadFile2`]
1388+
/// protocol, then fall back to [`LoadFile`].
1389+
///
1390+
/// [`LoadFile`]: crate::proto::media::load_file::LoadFile
1391+
/// [`LoadFile2`]: crate::proto::media::load_file::LoadFile2
13891392
FromDevicePath {
13901393
/// The full device path from which to load the image.
13911394
///

0 commit comments

Comments
 (0)