1 // Copyright 2024, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Rust wrapper for `GBL_EFI_OS_CONFIGURATION_PROTOCOL`. 16 17 use crate::efi_call; 18 use crate::protocol::{Protocol, ProtocolInfo}; 19 use core::ffi::CStr; 20 use efi_types::{EfiGuid, GblEfiOsConfigurationProtocol, GblEfiVerifiedDeviceTree}; 21 use liberror::{Error, Result}; 22 23 /// `GBL_EFI_OS_CONFIGURATION_PROTOCOL` implementation. 24 pub struct GblOsConfigurationProtocol; 25 26 impl ProtocolInfo for GblOsConfigurationProtocol { 27 type InterfaceType = GblEfiOsConfigurationProtocol; 28 29 const GUID: EfiGuid = 30 EfiGuid::new(0xdda0d135, 0xaa5b, 0x42ff, [0x85, 0xac, 0xe3, 0xad, 0x6e, 0xfb, 0x46, 0x19]); 31 } 32 33 // Protocol interface wrappers. 34 impl Protocol<'_, GblOsConfigurationProtocol> { 35 /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.fixup_kernel_commandline()`. fixup_kernel_commandline(&self, commandline: &CStr, fixup: &mut [u8]) -> Result<()>36 pub fn fixup_kernel_commandline(&self, commandline: &CStr, fixup: &mut [u8]) -> Result<()> { 37 if fixup.is_empty() { 38 return Err(Error::InvalidInput); 39 } 40 41 let mut fixup_size = fixup.len(); 42 fixup[0] = 0; 43 // SAFETY: 44 // * `self.interface()?` guarantees self.interface is non-null and points to a valid object 45 // established by `Protocol::new()`. 46 // * `commandline` is a valid pointer to null-terminated string used only within the call. 47 // * `fixup` is non-null buffer available for write, used only within the call. 48 // * `fixup_size` is non-null buffer available for write, used only within the call. 49 unsafe { 50 efi_call!( 51 @bufsize fixup_size, 52 self.interface()?.fixup_kernel_commandline, 53 self.interface, 54 commandline.as_ptr() as _, 55 fixup.as_mut_ptr(), 56 &mut fixup_size 57 )?; 58 } 59 60 Ok(()) 61 } 62 63 /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.fixup_bootconfig()`. fixup_bootconfig(&self, bootconfig: &[u8], fixup: &mut [u8]) -> Result<usize>64 pub fn fixup_bootconfig(&self, bootconfig: &[u8], fixup: &mut [u8]) -> Result<usize> { 65 if fixup.is_empty() { 66 return Err(Error::InvalidInput); 67 } 68 69 let mut fixup_size = fixup.len(); 70 // SAFETY: 71 // * `self.interface()?` guarantees self.interface is non-null and points to a valid object 72 // established by `Protocol::new()`. 73 // * `bootconfig` is non-null buffer used only within the call. 74 // * `fixup` is non-null buffer available for write, used only within the call. 75 // * `fixup_size` is non-null usize buffer available for write, used only within the call. 76 unsafe { 77 efi_call!( 78 @bufsize fixup_size, 79 self.interface()?.fixup_bootconfig, 80 self.interface, 81 bootconfig.as_ptr(), 82 bootconfig.len(), 83 fixup.as_mut_ptr(), 84 &mut fixup_size 85 )?; 86 } 87 88 Ok(fixup_size) 89 } 90 91 /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.select_device_trees()`. select_device_trees(&self, components: &mut [GblEfiVerifiedDeviceTree]) -> Result<()>92 pub fn select_device_trees(&self, components: &mut [GblEfiVerifiedDeviceTree]) -> Result<()> { 93 // SAFETY: 94 // * `self.interface()?` guarantees self.interface is non-null and points to a valid object 95 // established by `Protocol::new()`. 96 // * `components` is non-null buffer available for write, used only within the call. 97 // * `components_len` is non-null usize buffer, used only within the call. 98 unsafe { 99 efi_call!( 100 self.interface()?.select_device_trees, 101 self.interface, 102 components.as_mut_ptr() as _, 103 components.len(), 104 )?; 105 } 106 107 Ok(()) 108 } 109 } 110 111 #[cfg(test)] 112 mod test { 113 use super::*; 114 115 use crate::test::run_test_with_mock_protocol; 116 use efi_types::{ 117 EfiStatus, EFI_STATUS_BUFFER_TOO_SMALL, EFI_STATUS_INVALID_PARAMETER, EFI_STATUS_SUCCESS, 118 }; 119 use std::{ffi::CStr, slice}; 120 121 #[test] fixup_kernel_commandline_no_op()122 fn fixup_kernel_commandline_no_op() { 123 // No-op C callback implementation. 124 unsafe extern "C" fn c_return_success( 125 _: *mut GblEfiOsConfigurationProtocol, 126 _: *const u8, 127 _: *mut u8, 128 _: *mut usize, 129 ) -> EfiStatus { 130 EFI_STATUS_SUCCESS 131 } 132 133 let c_interface = GblEfiOsConfigurationProtocol { 134 fixup_kernel_commandline: Some(c_return_success), 135 ..Default::default() 136 }; 137 138 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 139 let mut fixup_buffer = [0x0; 128]; 140 let commandline = c"foo=bar baz"; 141 142 assert!(os_config_protocol 143 .fixup_kernel_commandline(commandline, &mut fixup_buffer) 144 .is_ok()); 145 assert_eq!( 146 CStr::from_bytes_until_nul(&fixup_buffer[..]).unwrap().to_str().unwrap(), 147 "" 148 ); 149 }); 150 } 151 152 #[test] fixup_kernel_commandline_provided()153 fn fixup_kernel_commandline_provided() { 154 const EXPECTED_COMMANDLINE: &CStr = c"a=b"; 155 const EXPECTED_FIXUP: &[u8] = b"hello=world\0"; 156 const EXPECTED_FIXUP_STR: &str = "hello=world"; 157 158 // C callback implementation to add "hello=world" to the given command line. 159 unsafe extern "C" fn c_add_hello_world( 160 _: *mut GblEfiOsConfigurationProtocol, 161 command_line: *const u8, 162 fixup: *mut u8, 163 _: *mut usize, 164 ) -> EfiStatus { 165 assert_eq!( 166 // SAFETY: 167 // * `command_line` is valid pointer to null terminated string. 168 unsafe { CStr::from_ptr(command_line as _) }, 169 EXPECTED_COMMANDLINE 170 ); 171 172 // SAFETY: 173 // * `fixup` is valid writtable buffer with enough space for test data. 174 let fixup_buffer = unsafe { slice::from_raw_parts_mut(fixup, EXPECTED_FIXUP.len()) }; 175 fixup_buffer.copy_from_slice(EXPECTED_FIXUP); 176 177 EFI_STATUS_SUCCESS 178 } 179 180 let c_interface = GblEfiOsConfigurationProtocol { 181 fixup_kernel_commandline: Some(c_add_hello_world), 182 ..Default::default() 183 }; 184 185 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 186 let mut fixup_buffer = [0x0; 128]; 187 188 assert!(os_config_protocol 189 .fixup_kernel_commandline(EXPECTED_COMMANDLINE, &mut fixup_buffer) 190 .is_ok()); 191 assert_eq!( 192 CStr::from_bytes_until_nul(&fixup_buffer[..]).unwrap().to_str().unwrap(), 193 EXPECTED_FIXUP_STR, 194 ); 195 }); 196 } 197 198 #[test] fixup_kernel_commandline_error()199 fn fixup_kernel_commandline_error() { 200 // C callback implementation to return an error. 201 unsafe extern "C" fn c_error( 202 _: *mut GblEfiOsConfigurationProtocol, 203 _: *const u8, 204 _: *mut u8, 205 _: *mut usize, 206 ) -> EfiStatus { 207 EFI_STATUS_INVALID_PARAMETER 208 } 209 210 let c_interface = GblEfiOsConfigurationProtocol { 211 fixup_kernel_commandline: Some(c_error), 212 ..Default::default() 213 }; 214 215 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 216 let mut fixup_buffer = [0x0; 128]; 217 let commandline = c"foo=bar baz"; 218 219 assert_eq!( 220 os_config_protocol.fixup_kernel_commandline(commandline, &mut fixup_buffer), 221 Err(Error::InvalidInput), 222 ); 223 }); 224 } 225 226 #[test] fixup_kernel_commandline_buffer_too_small()227 fn fixup_kernel_commandline_buffer_too_small() { 228 const EXPECTED_REQUESTED_FIXUP_SIZE: usize = 256; 229 // C callback implementation to return an error. 230 unsafe extern "C" fn c_error( 231 _: *mut GblEfiOsConfigurationProtocol, 232 _: *const u8, 233 _: *mut u8, 234 fixup_size: *mut usize, 235 ) -> EfiStatus { 236 // SAFETY: 237 // * `fixup_size` is a valid pointer to writtable usize buffer. 238 unsafe { 239 *fixup_size = EXPECTED_REQUESTED_FIXUP_SIZE; 240 } 241 EFI_STATUS_BUFFER_TOO_SMALL 242 } 243 244 let c_interface = GblEfiOsConfigurationProtocol { 245 fixup_kernel_commandline: Some(c_error), 246 ..Default::default() 247 }; 248 249 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 250 let mut fixup_buffer = [0x0; 128]; 251 let commandline = c"foo=bar baz"; 252 253 assert_eq!( 254 os_config_protocol.fixup_kernel_commandline(commandline, &mut fixup_buffer), 255 Err(Error::BufferTooSmall(Some(EXPECTED_REQUESTED_FIXUP_SIZE))), 256 ); 257 }); 258 } 259 260 #[test] fixup_bootconfig_no_op()261 fn fixup_bootconfig_no_op() { 262 // No-op C callback implementation. 263 unsafe extern "C" fn c_return_success( 264 _: *mut GblEfiOsConfigurationProtocol, 265 _: *const u8, 266 _: usize, 267 _: *mut u8, 268 fixup_size: *mut usize, 269 ) -> EfiStatus { 270 // SAFETY: 271 // * `fixup_size` is a valid pointer to writtable usize buffer. 272 unsafe { 273 *fixup_size = 0; 274 } 275 EFI_STATUS_SUCCESS 276 } 277 278 let c_interface = GblEfiOsConfigurationProtocol { 279 fixup_bootconfig: Some(c_return_success), 280 ..Default::default() 281 }; 282 283 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 284 let mut fixup_buffer = [0x0; 128]; 285 let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul(); 286 287 assert_eq!( 288 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer), 289 Ok(0) 290 ); 291 }); 292 } 293 294 #[test] fixup_bootconfig_provided()295 fn fixup_bootconfig_provided() { 296 // no trailer for simplicity 297 const EXPECTED_BOOTCONFIG: &[u8] = b"a=b\nc=d\n"; 298 const EXPECTED_LEN: usize = 4; 299 const EXPECTED_FIXUP: &[u8] = b"e=f\n"; 300 301 // C callback implementation to add "e=f" to the given bootconfig. 302 unsafe extern "C" fn c_add_ef( 303 _: *mut GblEfiOsConfigurationProtocol, 304 bootconfig: *const u8, 305 bootconfig_size: usize, 306 fixup: *mut u8, 307 fixup_size: *mut usize, 308 ) -> EfiStatus { 309 // SAFETY: 310 // * `bootconfig` is a valid pointer to the buffer at least `bootconfig_size` size. 311 let bootconfig_buffer = unsafe { slice::from_raw_parts(bootconfig, bootconfig_size) }; 312 313 assert_eq!(bootconfig_buffer, EXPECTED_BOOTCONFIG); 314 315 // SAFETY: 316 // * `fixup` is a valid writtable buffer with enough space for test data. 317 // * `fixup_size` is a valid pointer to writtable usize buffer. 318 let fixup_buffer = unsafe { 319 *fixup_size = EXPECTED_FIXUP.len(); 320 slice::from_raw_parts_mut(fixup, *fixup_size) 321 }; 322 fixup_buffer.copy_from_slice(EXPECTED_FIXUP); 323 324 EFI_STATUS_SUCCESS 325 } 326 327 let c_interface = GblEfiOsConfigurationProtocol { 328 fixup_bootconfig: Some(c_add_ef), 329 ..Default::default() 330 }; 331 332 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 333 let mut fixup_buffer = [0x0; 128]; 334 335 assert_eq!( 336 os_config_protocol.fixup_bootconfig(&EXPECTED_BOOTCONFIG[..], &mut fixup_buffer), 337 Ok(EXPECTED_LEN), 338 ); 339 assert_eq!(&fixup_buffer[..EXPECTED_LEN], &EXPECTED_FIXUP[..],); 340 }); 341 } 342 343 #[test] fixup_bootconfig_error()344 fn fixup_bootconfig_error() { 345 // C callback implementation to return an error. 346 unsafe extern "C" fn c_error( 347 _: *mut GblEfiOsConfigurationProtocol, 348 _: *const u8, 349 _: usize, 350 _: *mut u8, 351 _: *mut usize, 352 ) -> EfiStatus { 353 EFI_STATUS_INVALID_PARAMETER 354 } 355 356 let c_interface = 357 GblEfiOsConfigurationProtocol { fixup_bootconfig: Some(c_error), ..Default::default() }; 358 359 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 360 let mut fixup_buffer = [0x0; 128]; 361 let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul(); 362 363 assert_eq!( 364 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer), 365 Err(Error::InvalidInput) 366 ); 367 }); 368 } 369 370 #[test] fixup_bootconfig_fixup_buffer_too_small()371 fn fixup_bootconfig_fixup_buffer_too_small() { 372 const EXPECTED_REQUESTED_FIXUP_SIZE: usize = 256; 373 // C callback implementation to return an error. 374 unsafe extern "C" fn c_error( 375 _: *mut GblEfiOsConfigurationProtocol, 376 _: *const u8, 377 _: usize, 378 _: *mut u8, 379 fixup_size: *mut usize, 380 ) -> EfiStatus { 381 // SAFETY: 382 // * `fixup_size` is a valid pointer to writtable usize buffer. 383 unsafe { 384 *fixup_size = EXPECTED_REQUESTED_FIXUP_SIZE; 385 } 386 EFI_STATUS_BUFFER_TOO_SMALL 387 } 388 389 let c_interface = 390 GblEfiOsConfigurationProtocol { fixup_bootconfig: Some(c_error), ..Default::default() }; 391 392 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 393 let mut fixup_buffer = [0x0; 128]; 394 let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul(); 395 396 assert_eq!( 397 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer), 398 Err(Error::BufferTooSmall(Some(EXPECTED_REQUESTED_FIXUP_SIZE))), 399 ); 400 }); 401 } 402 403 #[test] select_device_trees_selected()404 fn select_device_trees_selected() { 405 // C callback implementation to select first component. 406 unsafe extern "C" fn c_select_first( 407 _: *mut GblEfiOsConfigurationProtocol, 408 device_trees: *mut GblEfiVerifiedDeviceTree, 409 num: usize, 410 ) -> EfiStatus { 411 assert_eq!(num, 1); 412 413 // SAFETY: 414 // * device_trees is non-null buffer available for write. 415 unsafe { 416 (*device_trees).selected = true; 417 } 418 419 EFI_STATUS_SUCCESS 420 } 421 422 let c_interface = GblEfiOsConfigurationProtocol { 423 select_device_trees: Some(c_select_first), 424 ..Default::default() 425 }; 426 427 run_test_with_mock_protocol(c_interface, |os_config_protocol| { 428 let device_trees = &mut [GblEfiVerifiedDeviceTree::default()]; 429 430 assert!(os_config_protocol.select_device_trees(device_trees).is_ok()); 431 assert!(device_trees[0].selected); 432 }); 433 } 434 } 435