1use alloc::{format, string::ToString, vec, vec::Vec};
2use uefi::{
3 cstr16, guid,
4 prelude::{BootServices, RuntimeServices},
5 proto::{
6 device_path::{
7 media::{HardDrive, PartitionSignature},
8 text::DevicePathToText,
9 DevicePath, DeviceSubType, DeviceType,
10 },
11 loaded_image::LoadedImage,
12 },
13 table::{
14 runtime::{VariableAttributes, VariableVendor},
15 Boot, SystemTable,
16 },
17 CStr16, Guid, Handle, Result,
18};
19
20use bitflags::bitflags;
21
22fn disk_get_part_uuid(boot_services: &BootServices, disk_handle: Handle) -> Result<Guid> {
28 let dp = boot_services.open_protocol_exclusive::<DevicePath>(disk_handle)?;
29
30 for node in dp.node_iter() {
31 if node.device_type() != DeviceType::MEDIA
32 || node.sub_type() != DeviceSubType::MEDIA_HARD_DRIVE
33 {
34 continue;
35 }
36
37 if let Ok(hd_path) = <&HardDrive>::try_from(node) {
38 if let PartitionSignature::Guid(guid) = hd_path.partition_signature() {
39 return Ok(guid);
40 }
41 }
42 }
43
44 Err(uefi::Status::UNSUPPORTED.into())
45}
46
47pub const BOOT_LOADER_VENDOR_UUID: VariableVendor =
52 VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
53
54bitflags! {
55 #[repr(transparent)]
56 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
57 pub struct EfiLoaderFeatures: u64 {
59 const ConfigTimeout = 1 << 0;
60 const ConfigTimeoutOneShot = 1 << 1;
61 const EntryDefault = 1 << 2;
62 const EntryOneshot = 1 << 3;
63 const BootCounting = 1 << 4;
64 const XBOOTLDR = 1 << 5;
65 const RandomSeed = 1 << 6;
66 const LoadDriver = 1 << 7;
67 const SortKey = 1 << 8;
68 const SavedEntry = 1 << 9;
69 const DeviceTree = 1 << 10;
70 }
71}
72
73pub fn get_loader_features(runtime_services: &RuntimeServices) -> Result<EfiLoaderFeatures> {
76 if let Ok(size) =
77 runtime_services.get_variable_size(cstr16!("LoaderFeatures"), &BOOT_LOADER_VENDOR_UUID)
78 {
79 let mut buffer = vec![0; size].into_boxed_slice();
80 runtime_services.get_variable(
81 cstr16!("LoaderFeatures"),
82 &BOOT_LOADER_VENDOR_UUID,
83 &mut buffer,
84 )?;
85
86 return EfiLoaderFeatures::from_bits(u64::from_le_bytes(
87 (*buffer)
88 .try_into()
89 .map_err(|_err| uefi::Status::BAD_BUFFER_SIZE)?,
90 ))
91 .ok_or_else(|| uefi::Status::INCOMPATIBLE_VERSION.into());
92 }
93
94 Ok(Default::default())
95}
96
97bitflags! {
98 #[repr(transparent)]
99 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
100 pub struct EfiStubFeatures: u64 {
102 const ReportBootPartition = 1 << 0;
104 const PickUpCredentials = 1 << 1;
106 const PickUpSysExts = 1 << 2;
108 const ThreePcrs = 1 << 3;
110 const RandomSeed = 1 << 4;
112 }
113}
114
115#[cfg(target_endian = "little")]
118pub fn from_u16(from: &[u16]) -> &[u8] {
119 unsafe {
120 core::slice::from_raw_parts(from.as_ptr() as *mut u8, from.len().checked_mul(2).unwrap())
121 }
122}
123
124pub fn cstr16_to_bytes(s: &CStr16) -> &[u8] {
126 from_u16(s.to_u16_slice_with_nul())
127}
128
129pub fn ensure_efi_variable<F>(
132 runtime_services: &RuntimeServices,
133 name: &CStr16,
134 vendor: &VariableVendor,
135 attributes: VariableAttributes,
136 get_fallback_value: F,
137) -> uefi::Result
138where
139 F: FnOnce() -> uefi::Result<Vec<u8>>,
140{
141 if runtime_services.get_variable_size(name, vendor).is_err() {
143 runtime_services.set_variable(name, vendor, attributes, &get_fallback_value()?)?;
144 }
145
146 Ok(())
147}
148
149pub fn export_efi_variables(stub_info_name: &str, system_table: &SystemTable<Boot>) -> Result<()> {
151 let boot_services = system_table.boot_services();
152 let runtime_services = system_table.runtime_services();
153
154 let stub_features: EfiStubFeatures = EfiStubFeatures::ReportBootPartition;
155
156 let loaded_image =
157 boot_services.open_protocol_exclusive::<LoadedImage>(boot_services.image_handle())?;
158
159 let default_attributes =
160 VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS;
161
162 #[allow(unused_must_use)]
163 ensure_efi_variable(
165 runtime_services,
166 cstr16!("LoaderDevicePartUUID"),
167 &BOOT_LOADER_VENDOR_UUID,
168 default_attributes,
169 || {
170 disk_get_part_uuid(boot_services, loaded_image.device()).map(|guid| {
171 guid.to_string()
172 .encode_utf16()
173 .flat_map(|c| c.to_le_bytes())
174 .collect::<Vec<u8>>()
175 })
176 },
177 )
178 .ok();
179 ensure_efi_variable(
181 runtime_services,
182 cstr16!("LoaderImageIdentifier"),
183 &BOOT_LOADER_VENDOR_UUID,
184 default_attributes,
185 || {
186 if let Some(dp) = loaded_image.file_path() {
187 let dp_protocol = boot_services.open_protocol_exclusive::<DevicePathToText>(
188 boot_services.get_handle_for_protocol::<DevicePathToText>()?,
189 )?;
190 dp_protocol
191 .convert_device_path_to_text(
192 boot_services,
193 dp,
194 uefi::proto::device_path::text::DisplayOnly(false),
195 uefi::proto::device_path::text::AllowShortcuts(false),
196 )
197 .map(|ps| cstr16_to_bytes(&ps).to_vec())
198 } else {
199 Err(uefi::Status::UNSUPPORTED.into())
202 }
203 },
204 )
205 .ok();
206 ensure_efi_variable(
208 runtime_services,
209 cstr16!("LoaderFirmwareInfo"),
210 &BOOT_LOADER_VENDOR_UUID,
211 default_attributes,
212 || {
213 Ok(format!(
214 "{} {}.{:02}",
215 system_table.firmware_vendor(),
216 system_table.firmware_revision() >> 16,
217 system_table.firmware_revision() & 0xFFFFF
218 )
219 .encode_utf16()
220 .flat_map(|c| c.to_le_bytes())
221 .collect::<Vec<u8>>())
222 },
223 )
224 .ok();
225 ensure_efi_variable(
227 runtime_services,
228 cstr16!("LoaderFirmwareType"),
229 &BOOT_LOADER_VENDOR_UUID,
230 default_attributes,
231 || {
232 Ok(format!("UEFI {:02}", system_table.uefi_revision())
233 .encode_utf16()
234 .flat_map(|c| c.to_le_bytes())
235 .collect::<Vec<u8>>())
236 },
237 )
238 .ok();
239 runtime_services
243 .set_variable(
244 cstr16!("StubInfo"),
245 &BOOT_LOADER_VENDOR_UUID,
246 default_attributes,
247 &stub_info_name
248 .encode_utf16()
249 .flat_map(|c| c.to_le_bytes())
250 .collect::<Vec<u8>>(),
251 )
252 .ok();
253
254 runtime_services
256 .set_variable(
257 cstr16!("StubFeatures"),
258 &BOOT_LOADER_VENDOR_UUID,
259 default_attributes,
260 &stub_features.bits().to_le_bytes(),
261 )
262 .ok();
263
264 Ok(())
265}