// Example: Graphics Query // // This is a slightly more complex UEFI application than `hello-world`. It // locates the graphics-output-protocol, queries its current mode and prints // the current resolution to the UEFI console. // // This example should make everyone aware that UEFI programing in Rust really // asks for helper layers. While the C/FFI/Spec API can be used directly, it // is quite cumbersome. Especially the error handling is overly difficult. // // Nevertheless, this example shows how to find UEFI protocol and invoke // their member functions. // // Like all the other r-efi examples, it is a standalone example. That is, no // UTF-16 helpers are pulled in, nor any allocators or panic frameworks. For // real world scenarios, you really should choose such helpers. #![no_main] #![no_std] use r_efi::efi; #[panic_handler] fn panic_handler(_info: &core::panic::PanicInfo) -> ! { loop {} } fn fail(_r: efi::Status) -> ! { panic!(); } // A simple `itoa()`-ish function that takes a u32 and turns it into a UTF-16 // string. It always prints exactly 10 characters, so leading zeroes are used // for small numbers. fn utoa(mut u: u32, a: &mut [u16]) { for i in 0..10 { a[9 - i] = 0x0030u16 + ((u % 10) as u16); u = u / 10; } } // A simple helper that takes two integers and prints them to the UEFI console // with a short prefix. It uses a UTF-16 buffer and fills in the numbers before // printing the entire buffer. fn print_xy(st: *mut efi::SystemTable, x: u32, y: u32) { let mut s = [ 0x0058u16, 0x0059u16, 0x003au16, // "XY:" 0x0020u16, // " " 0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // " " 0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // " " 0x0020u16, 0x0020u16, // " " 0x0078u16, // "x" 0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // " " 0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // " " 0x0020u16, 0x0020u16, // " " 0x000au16, // "\n" 0x0000u16, // NUL ]; utoa(x, &mut s[4..14]); utoa(y, &mut s[15..25]); unsafe { let r = ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16); if r.is_error() { fail(r); } } } // This function locates singleton UEFI protocols. Those protocols do not // require to register listener handles, but are globally available to all // UEFI applications. It takes a GUID of the protocol to locate and returns // the protocol pointer on success. fn locate_singleton( st: *mut efi::SystemTable, guid: *const efi::Guid, ) -> Result<*mut core::ffi::c_void, efi::Status> { let mut interface: *mut core::ffi::c_void = core::ptr::null_mut(); let mut handles: *mut efi::Handle = core::ptr::null_mut(); let mut n_handles: usize = 0; let mut r: efi::Status; // Use `locate_handle_buffer()` to find all handles that support the // specified protocol. unsafe { if (*st).hdr.revision < efi::SYSTEM_TABLE_REVISION_1_10 { // We use `LocateHandleBuffer`, which was introduced in 1.10. return Err(efi::Status::UNSUPPORTED); } let r = ((*(*st).boot_services).locate_handle_buffer)( efi::BY_PROTOCOL, guid as *mut _, core::ptr::null_mut(), &mut n_handles, &mut handles, ); match r { efi::Status::SUCCESS => {} efi::Status::NOT_FOUND => return Err(r), efi::Status::OUT_OF_RESOURCES => return Err(r), _ => panic!(), }; } // Now that we have all handles with the specified protocol, query it for // the protocol interface. We loop here, even though every item should // succeed. Lets be on the safe side. // Secondly, we use `handle_protocol()` here, but really should be using // `open_protocol()`. But for singleton protocols, this does not matter, // so lets use the simple path for now. unsafe { r = efi::Status::NOT_FOUND; for i in 0..n_handles { r = ((*(*st).boot_services).handle_protocol)( *handles.offset(core::convert::TryFrom::::try_from(i).unwrap()), guid as *mut _, &mut interface, ); match r { efi::Status::SUCCESS => break, efi::Status::UNSUPPORTED => continue, _ => panic!(), }; } } // Free the allocated buffer memory of `handles`. This was allocated on the // pool by `locate_handle_buffer()`. unsafe { let r = ((*(*st).boot_services).free_pool)(handles as *mut core::ffi::c_void); assert!(!r.is_error()); } // In case we found nothing, return `NOT_FOUND`, otherwise return the // interface identifier. match r { efi::Status::SUCCESS => Ok(interface), _ => Err(efi::Status::NOT_FOUND), } } // A simple helper that queries the current mode of the GraphicsOutputProtocol // and returns the x and y dimensions on success. fn query_gop( gop: *mut efi::protocols::graphics_output::Protocol, ) -> Result<(u32, u32), efi::Status> { let mut info: *mut efi::protocols::graphics_output::ModeInformation = core::ptr::null_mut(); let mut z_info: usize = 0; unsafe { // We could just look at `gop->mode->info`, but lets query the mode // instead to show how to query other modes than the active one. let r = ((*gop).query_mode)(gop, (*(*gop).mode).mode, &mut z_info, &mut info); match r { efi::Status::SUCCESS => {} efi::Status::DEVICE_ERROR => return Err(r), _ => panic!(), }; if z_info < core::mem::size_of_val(&*info) { return Err(efi::Status::UNSUPPORTED); } Ok(((*info).horizontal_resolution, (*info).vertical_resolution)) } } // This is the UEFI application entrypoint. We use it to locate the GOP // pointer, query the current mode, and then print it to the system console. #[export_name = "efi_main"] pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status { let r = locate_singleton(st, &efi::protocols::graphics_output::PROTOCOL_GUID); let gop = match r { Ok(v) => v, Err(r) => fail(r), }; let r = query_gop(gop as _); let v = match r { Ok(v) => v, Err(r) => fail(r), }; print_xy(st, v.0, v.1); efi::Status::SUCCESS }