189 lines
6.6 KiB
Rust
189 lines
6.6 KiB
Rust
// 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::<usize>::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
|
|
}
|