Demilade Sonuga's blog
All postsRefactoring III
We've done a lot of refactoring but there's still more we need to do.
Firstly:
fn pre_graphics_print_char(simple_text_output: *mut SimpleTextOutput, c: char) {
// The UTF-16 encoded string which contains the single character to be printed
let mut utf16_encoded_buffer = [c as u16, 0];
unsafe {
((*simple_text_output).output_string)(simple_text_output, utf16_encoded_buffer.as_mut_ptr())
};
}
This unsafety is avoidable and that last line in the function is really unreadable, that is, the intent of that line is not immediately clear. To rectify the unsafety:
// DELETED: fn pre_graphics_print_char(simple_text_output: *mut SimpleTextOutput, c: char) {
fn pre_graphics_print_char(simple_text_output: &SimpleTextOutput, c: char) {
// The UTF-16 encoded string which contains the single character to be printed
let mut utf16_encoded_buffer = [c as u16, 0];
/* DELETED:
unsafe {
((*simple_text_output).output_string)(simple_text_output, utf16_encoded_buffer.as_mut_ptr())
};
*/
// NEW:
(simple_text_output.output_string)(simple_text_output, utf16_encoded_buffer.as_mut_ptr());
}
To make our code valid again:
// DELETED: fn pre_graphics_print_str(simple_text_output: *mut SimpleTextOutput, s: &str) {
fn pre_graphics_print_str(simple_text_output: &SimpleTextOutput, s: &str) {
for c in s.chars() {
pre_graphics_print_char(simple_text_output, c);
}
}
In our efi_main
, every instance where this pre_graphics_print_str
is called, a raw pointer is passed
instead of a reference:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
if locate_gop_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output;
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
// ... Others
for mode_number in 0..max_mode {
// ... Others
if query_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output;
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
// ... Others
if mode_number == max_mode - 1 {
let simple_text_output = sys_table.simple_text_output;
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
}
// ... Others
if set_mode_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output;
pre_graphics_print_str(simple_text_output, "Failed to set the desired mode\n");
loop {}
}
// ... Others
}
The sys_table.simple_text_output
results in a raw pointer to SimpleTextOutput
. To get a reference instead
of a raw pointer, we just do what we did for the GraphicsOutput
's mode
and the SystemTable
's
boot_services
. Do the unsafe dereferencing in a single area within a safe function, then take a reference
to the underlying data and return that reference:
#[repr(C)]
struct SystemTable {
unneeded: [u8; 60],
simple_text_output: *mut SimpleTextOutput,
unneeded2: [u8; 24],
boot_services: *const BootServices,
}
impl SystemTable {
fn boot_services(&self) -> &BootServices {
unsafe { &*self.boot_services }
}
// NEW:
// Returns a reference to the Simple Text Output instance in the System Table
fn simple_text_output(&self) -> &SimpleTextOutput {
unsafe { &*self.simple_text_output }
}
}
We now refactor to use this function:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
if locate_gop_status != STATUS_SUCCESS {
// DELETED: let simple_text_output = sys_table.simple_text_output;
let simple_text_output = sys_table.simple_text_output(); // NEW
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
// ... Others
for mode_number in 0..max_mode {
// ... Others
if query_status != STATUS_SUCCESS {
// DELETED: let simple_text_output = sys_table.simple_text_output;
let simple_text_output = sys_table.simple_text_output(); // NEW
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
// ... Others
if mode_number == max_mode - 1 {
// DELETED: let simple_text_output = sys_table.simple_text_output;
let simple_text_output = sys_table.simple_text_output(); // NEW
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
}
// ... Others
if set_mode_status != STATUS_SUCCESS {
// DELETED: let simple_text_output = sys_table.simple_text_output;
let simple_text_output = sys_table.simple_text_output(); // NEW
pre_graphics_print_str(simple_text_output, "Failed to set the desired mode\n");
loop {}
}
// ... Others
}
We repeat the same for the printdigit
and printint
functions:
// DELETED: fn printdigit(n: u32, simple_text_output: *mut SimpleTextOutput) {
fn printdigit(n: u32, simple_text_output: &SimpleTextOutput) { // NEW
// The code for the digit that will be printed
let mut digit_u16 = [48 + n as u16, 0];
// Printing the digit with the Simple Text Output protocol's output_string function
/* DELETED:
unsafe {
(simple_text_output.output_string)(simple_text_output, digit_u16.as_mut_ptr());
}
*/
(simple_text_output.output_string)(simple_text_output, digit_u16.as_mut_ptr()); // NEW
}
// DELETED: fn printint(n: u32, simple_text_output: *mut SimpleTextOutput) {
fn printint(n: u32, simple_text_output: &SimpleTextOutput) { // NEW
if n >= 10 {
let quotient = n / 10;
let remainder = n % 10;
printint(quotient, simple_text_output);
printdigit(remainder, simple_text_output);
} else {
printdigit(n, simple_text_output);
}
}
For our code to compile again:
#[repr(C)]
struct SimpleTextOutput {
unneeded: [u8; 8],
// DELETED: output_string: extern "efiapi" fn(this: *mut SimpleTextOutput, *mut u16),
output_string: extern "efiapi" fn(this: &SimpleTextOutput, *mut u16) // NEW
}
We're almost done (for now) minimizing unsafe code. The remaining avoidable unsafety:
fn print_char(
screen: *mut Screen,
font_description: &[[bool; 16]; 16],
curr_screen_pos: (usize, usize),
) {
for i in 0..16 {
for j in 0..16 {
if font_description[i][j] {
unsafe {
(*screen).pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 255,
green: 255,
blue: 0,
reserved: 0,
};
}
} else {
unsafe {
(*screen).pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0,
};
}
}
}
}
}
Rather than accepting a raw pointer to a screen, this function should accept a mutable reference:
fn print_char(
// DELETED: screen: *mut Screen,
screen: &mut Screen, // NEW
font_description: &[[bool; 16]; 16],
curr_screen_pos: (usize, usize),
) {
for i in 0..16 {
for j in 0..16 {
if font_description[i][j] {
/* DELETED:
unsafe {
(*screen).pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 255,
green: 255,
blue: 0,
reserved: 0,
};
}
*/
screen.pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 255,
green: 255,
blue: 0,
reserved: 0,
};
} else {
/* DELETED:
unsafe {
(*screen).pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0,
};
}
*/
screen.pixels[curr_screen_pos.0 + i][curr_screen_pos.1 + j] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0,
};
}
}
}
}
The reference has to be mutable because we're mutating (modifying, changing, writing to) the screen.
Refactoring to use this modified print_char
:
// DELETED: fn print_str(screen: *mut Screen, s: &str) {
fn print_str(screen: &mut Screen, s: &str) { // NEW
let mut screen_pos = (0, 0);
for c in s.as_bytes() {
print_char(screen, &FONT[char_to_font_index(*c)], screen_pos);
screen_pos.1 += 16;
if screen_pos.1 >= NO_OF_PIXELS_IN_A_ROW {
screen_pos.0 += 16;
screen_pos.1 = 0;
}
if screen_pos.0 >= NO_OF_PIXELS_IN_A_COLUMN {
break;
}
}
}
Now efi_main
:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let framebuffer_base = mode.framebuffer_base;
let screen = framebuffer_base as *mut Screen;
// NEW:
// Obtain a mutable reference from the screen's raw pointer
let screen = unsafe { &mut *screen };
print_str(screen, "Hello World!");
0
}
At this point, unsafety in our code has been minimized to a reasonable extent. We can still do better, but we have other things to get on to.
Now that we're done with unsafety, let's look at clarity.
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let mut gop: *mut core::ffi::c_void = core::ptr::null_mut();
let guid_ptr = &GOP_GUID as *const Guid;
let registration = core::ptr::null_mut();
let gop_ptr = &mut gop as *mut _;
let locate_gop_status = (boot_services.locate_protocol)(guid_ptr, registration, gop_ptr);
if locate_gop_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let gop = gop as *mut GraphicsOutput;
let gop = unsafe { &*gop };
// ... Others
}
The above lines of code simply retrieve the Graphics Output Protocol with the Boot Services but the intent is not clear at all when we first look at it. The meaning of these lines has been clustered by the unnecessarily long setup. The aim of these lines of code is simple:
- Attempt to get the GOP with the Boot Services.
- If the attempt is unsuccessful, print a message and halt (loop forever).
- If the attempt is successful, continue.
To resolve this, we need to move all the details somewhere else and leave in its place something that makes it clear what exactly we're doing here. To get a clearer picture of what I'm talking about, rather than the above code, we need something like this:
// ... Before
attempt = locate_gop()
if attempt failed
print "Failed to locate GOP\n"
// ... After
The question we have to answer now: move the details to where?
Taking a good look at our code, we see that the BootServices
already has a locate_protocol
function
which we're already using. We just need another function that will encapsulate all these details
to make locating protocols clearer.
#[repr(C)]
struct BootServices {
unneeded: [u8; 320],
locate_protocol: extern "efiapi" fn(
protocol: *const Guid,
registration: *const core::ffi::c_void,
interface: *mut *mut core::ffi::c_void,
) -> Status,
}
// NEW:
impl BootServices {
// Finds a protocol with it's unique GUID
fn locate_protocol(&self, protocol_guid: &Guid) -> Result<*mut core::ffi::c_void, usize> {
// The location which will hold a pointer to a protocol on a successful call to locate_protocol
let mut protocol: *mut core::ffi::c_void = core::ptr::null_mut();
// The raw pointer to the protocol Guid
let guid_ptr = protocol_guid as *const Guid;
// An optional argument which we're just going to pass null into
let registration = core::ptr::null_mut();
// Location where the protocol pointer should be placed into on a successful locate_protocol invocation
let protocol_ptr = &mut protocol as *mut _;
// Invoking the Boot Services locate_protocol function to find the protocol
let locate_protocol_status = (self.locate_protocol)(guid_ptr, registration, protocol_ptr);
if locate_protocol_status != STATUS_SUCCESS {
// If the attempt failed, return the failed error code
Err(locate_protocol_status)
} else {
// If the attempt didn't fail, return the pointer to the protocol
Ok(protocol)
}
}
}
The new locate_protocol
function above wraps the more primitive one, taking care of a lot of
details we don't need to know about when we're calling it, accepting only the protocol GUID as a
parameter. The function returns a result indicating the possibility of an error. If the function is successful,
it returns a pointer to the protocol with Result
's success variant (Ok
) and if the function fails,
the failure status code is returned with the Result
's error variant (Err
).
Refactoring our efi_main
with this function:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
/* DELETED:
let mut gop: *mut core::ffi::c_void = core::ptr::null_mut();
let guid_ptr = &GOP_GUID as *const Guid;
let registration = core::ptr::null_mut();
let gop_ptr = &mut gop as *mut _;
let locate_gop_status = (boot_services.locate_protocol)(guid_ptr, registration, gop_ptr);
if locate_gop_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
*/
// NEW:
// Locate the Graphics Output Protocol
let gop = boot_services.locate_protocol(&GOP_GUID);
// If location failed, print error and halt
if gop.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let gop = gop.unwrap() as *mut GraphicsOutput;
let gop = unsafe { &*gop };
// ... Others
}
The code now is so much clearer. What we intend on doing is immediately apparent from just looking at the code. Locate GOP. If the location failed, halt.
We can make this even better by creating a locate_gop
function, just for the convenience of it:
impl BootServices {
fn locate_protocol(&self, protocol_guid: &Guid) -> Result<*mut core::ffi::c_void, Status> {
/* OTHERS */
}
// NEW:
// Convenience function to locate the Graphics Output Protocol
fn locate_gop(&self) -> Result<&GraphicsOutput, Status> {
// Attempt to locate the GOP
let locate_gop = self.locate_protocol(&GOP_GUID);
if locate_gop.is_ok() {
// Return a reference to the GOP, instead of a raw pointer
let gop_ptr = locate_gop.unwrap() as *mut GraphicsOutput;
let gop = unsafe { &*gop_ptr };
Ok(gop)
} else {
// Return the error code it failed with
Err(locate_gop.unwrap_err())
}
}
}
Refactoring again:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
// DELETED: let gop = boot_services.locate_protocol(&GOP_GUID);
let gop = boot_services.locate_gop(); // NEW
if gop.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
/* DELETED:
let gop = gop.unwrap() as *mut GraphicsOutput;
let gop = unsafe { &*gop };
*/
// Extract the GOP from the result
let gop = gop.unwrap(); // NEW
// ... Others
}
With our new locate_gop
function, we've been able to reduce the number of details
we needed to remember to locate the GOP. The unsafe that we needed to dereference the GOP pointer
is now out of our efi_main
and in the small, manually verified, safe locate_gop
function, making
our code cleaner, easier to reason with, easier to modify and harder to screw up.
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
for mode_number in 0..max_mode {
let size_of_info = core::mem::size_of::<GraphicsModeInfo>();
let mut mode: *const GraphicsModeInfo = core::ptr::null_mut();
let query_mode = gop.query_mode;
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _,
);
if query_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let mode = unsafe { &*mode };
// ... Others
}
// ... Others
}
Our query_mode
is suffering from the same detail overload our GOP location was suffering from a few
moments ago. The only information we actually need to remember whenever we want to call query_mode
is the mode number. Everything else is a detail that can be hidden.
impl GraphicsOutput {
fn mode(&self) -> &GraphicsMode {
unsafe { &*self.mode }
}
// NEW:
// Gets information about a mode
fn query_mode(&self, mode_number: u32) -> Result<&GraphicsModeInfo, Status> {
// The size of our `GraphicsModeInfo` structure
let size_of_info = core::mem::size_of::<GraphicsModeInfo>();
// The location that will hold the pointer to the `GraphicsModeInfo`
// for the current `mode_number` on a successful call to the
// `GraphicsOutput` query_mode`
let mut mode: *const GraphicsModeInfo = core::ptr::null_mut();
// Calling `query_mode` to get information about the mode associated
// with `mode_number`
let query_status = (self.query_mode)(
// The pointer to the GOP instance
self,
// The mode number associated with the mode we want information about
mode_number,
// The size of the `GraphicsModeInfo` structure
&size_of_info as *const _,
// The pointer to the location to be mutated to hold the pointer to the
// `GraphicsModeInfo` instance associated with the current `mode_number`
// on a successful function execution
&mut mode as *mut _,
);
if query_status == STATUS_SUCCESS {
// Return a reference to the mode info
Ok(unsafe { &*mode })
} else {
// Return the failure status
Err(query_status)
}
}
}
Now, we refactor with this function:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
for mode_number in 0..max_mode {
/* DELETED:
let size_of_info = core::mem::size_of::<GraphicsModeInfo>();
let mut mode: *const GraphicsModeInfo = core::ptr::null_mut();
let query_mode = gop.query_mode;
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _,
);
if query_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let mode = unsafe { &*mode };
*/
// NEW:
// Get the mode info
let mode = gop.query_mode(mode_number);
if mode.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
// Extract the mode info from the result
let mode = mode.unwrap();
// ... Others
}
// ... Others
}
Similarly, we have:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let set_mode_status = (gop.set_mode)(gop, desired_mode);
if set_mode_status != STATUS_SUCCESS {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to set the desired mode\n");
loop {}
}
// ... Others
}
This will be much better as:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
// DELETED: let set_mode_status = (gop.set_mode)(gop, desired_mode);
let set_mode_result = gop.set_mode(desired_mode); // NEW
// DELETED: if set_mode_status != STATUS_SUCCESS {
if set_mode_result.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to set the desired mode\n");
loop {}
}
// ... Others
}
We just add another function in our GraphicsOutput
implementation block:
impl GraphicsOutput {
fn mode(&self) -> &GraphicsMode {
unsafe { &*self.mode }
}
fn query_mode(&self, mode_number: u32) -> Result<&GraphicsModeInfo, Status> {
/* OTHERS */
}
// Set a mode to the mode with the mode number desired_mode
fn set_mode(&self, desired_mode: u32) -> Result<(), Status> {
let set_mode_status = (self.set_mode)(self, desired_mode);
if set_mode_status == STATUS_SUCCESS {
Ok(())
} else {
Err(set_mode_status)
}
}
}
And again, our code is cleaner, more concise and safer. We can still do better, but we have lots of other things to do.
To finalize our refactor (for now), let's a look at our main.rs
. It's clustered. We need to separate
code with different concerns into different modules. To do this, we first need to identify the functionality
that the code in our main.rs
has.
All the code in the efi_main
is the driver code, the code that actually gets executed when
the app starts and runs to the end. Everything else in the main.rs
is just utilities we use to interact
with the UEFI firmware.
From this, it's clear that we have to separate out the UEFI interfacing utilities into a separate module and leave the driver code in our main.rs
.
Create a new file, uefi.rs
. Your directory should be looking like this now:
blasterball/
| .cargo/
| | config.toml
| src/
| | font.rs
| | main.rs
| | uefi.rs
| .gitignore
| Cargo.lock
| Cargo.toml
Cut out all the structures and functions and constants, apart from the efi_main
and panic_handler
,
and paste them into uefi.rs
.
Now, we have to change the visibility of the structs and functions and constants:
// DELETED: const STATUS_SUCCESS: Status = 0;
pub const STATUS_SUCCESS: Status = 0; // NEW
// DELETED: const GOP_GUID: Guid = Guid { /* OTHERS */ };
pub const GOP_GUID: Guid = Guid { /* OTHERS */ };
/* DELETED:
fn pre_graphics_print_char(simple_text_output: &SimpleTextOutput, c: char) {
/* OTHERS */
}
*/
// NEW:
pub fn pre_graphics_print_char(simple_text_output: &SimpleTextOutput, c: char) {
/* OTHERS */
}
/* DELETED:
fn pre_graphics_print_str(simple_text_output: &SimpleTextOutput, s: &str) {
/* OTHERS */
}
*/
// NEW:
pub fn pre_graphics_print_str(simple_text_output: &SimpleTextOutput, s: &str) {
/* OTHERS */
}
/* DELETED:
fn print_str(screen: &mut Screen, s: &str) {
/* OTHERS */
}
*/
// NEW:
pub fn print_str(screen: &mut Screen, s: &str) {
/* OTHERS */
}
/* DELETED:
fn print_char(
screen: &mut Screen,
font_description: &[[bool; 16]; 16],
curr_screen_pos: (usize, usize),
) {
/* OTHERS */
}
*/
// NEW:
pub fn print_char(
screen: &mut Screen,
font_description: &[[bool; 16]; 16],
curr_screen_pos: (usize, usize),
) {
/* OTHERS */
}
// ... Others
// DELETED: struct Screen { /* OTHERS */ }
pub struct Screen { /* OTHERS */ }
/* DELETED:
fn printdigit(n: u32, simple_text_output: &SimpleTextOutput) {
/* OTHERS */
}
*/
// NEW:
pub fn printdigit(n: u32, simple_text_output: &SimpleTextOutput) {
/* OTHERS */
}
// Prints an integer n
/* DELETED:
fn printint(n: u32, simple_text_output: &SimpleTextOutput) {
/* OTHERS */
}
*/
// NEW:
pub fn printint(n: u32, simple_text_output: &SimpleTextOutput) {
/* OTHERS */
}
// DELETED: struct SystemTable { /* OTHERS */ }
// NEW:
pub struct SystemTable { /* OTHERS */ }
impl SystemTable {
/* DELETED:
fn boot_services(&self) -> &BootServices {
/* OTHERS */
}
*/
pub fn boot_services(&self) -> &BootServices {
/* OTHERS */
}
/* DELETED:
fn simple_text_output(&self) -> &SimpleTextOutput {
/* OTHERS */
}
*/
pub fn simple_text_output(&self) -> &SimpleTextOutput {
/* OTHERS */
}
}
// DELETED: struct Guid { /* OTHERS */ }
pub struct Guid { /* OTHERS */ }
// DELETED: type Status = usize;
pub type Status = usize; // NEW
// DELETED: struct BootServices { /* OTHERS */ }
pub struct BootServices { /* OTHERS */ } // NEW
impl BootServices {
/* DELETED:
fn locate_protocol(&self, protocol_guid: &Guid) -> Result<*mut core::ffi::c_void, usize> {
/* OTHERS */
}
*/
// NEW:
pub fn locate_protocol(&self, protocol_guid: &Guid) -> Result<*mut core::ffi::c_void, usize> {
/* OTHERS */
}
/* DELETED:
fn locate_gop(&self) -> Result<&GraphicsOutput, Status> {
/* OTHERS */
}
*/
// NEW:
pub fn locate_gop(&self) -> Result<&GraphicsOutput, Status> {
/* OTHERS */
}
}
// DELETED: struct SimpleTextOutput { /* OTHERS */ }
pub struct SimpleTextOutput { /* OTHERS */ } // NEW
// DELETED: struct GraphicsOutput { /* OTHERS */ }
pub struct GraphicsOutput { /* OTHERS */ } // NEW
impl GraphicsOutput {
/* DELETED:
fn mode(&self) -> &GraphicsMode {
/* OTHERS */
}
*/
// NEW:
pub fn mode(&self) -> &GraphicsMode {
/* OTHERS */
}
/* DELETED:
fn query_mode(&self, mode_number: u32) -> Result<&GraphicsModeInfo, Status> {
/* OTHERS */
}
*/
// NEW:
pub fn query_mode(&self, mode_number: u32) -> Result<&GraphicsModeInfo, Status> {
/* OTHERS */
}
/* DELETED:
fn set_mode(&self, desired_mode: u32) -> Result<(), Status> {
/* OTHERS */
}
*/
// NEW:
pub fn set_mode(&self, desired_mode: u32) -> Result<(), Status> {
/* OTHERS */
}
}
/* DELETED:
struct GraphicsModeInfo {
version: u32,
horizontal_resolution: u32,
vertical_resolution: u32,
pixel_format: PixelFormat,
pixel_info: PixelBitmask,
pixels_per_scan_line: u32,
}
*/
// NEW:
pub struct GraphicsModeInfo {
pub version: u32,
pub horizontal_resolution: u32,
pub vertical_resolution: u32,
pub pixel_format: PixelFormat,
pub pixel_info: PixelBitmask,
pub pixels_per_scan_line: u32,
}
// DELETED: enum PixelFormat { /* OTHERS */ }
pub enum PixelFormat { /* OTHERS */ } // NEW
/* DELETED:
struct Pixel {
blue: u8,
green: u8,
red: u8,
reserved: u8,
}
*/
// NEW:
pub struct Pixel {
pub blue: u8,
pub green: u8,
pub red: u8,
pub reserved: u8,
}
/* DELETED:
struct PixelBitmask {
red_mask: u32,
green_mask: u32,
blue_mask: u32,
reserved: u32,
}
*/
// NEW:
pub struct PixelBitmask {
pub red_mask: u32,
pub green_mask: u32,
pub blue_mask: u32,
pub reserved: u32,
}
// DELETED: type PhysAddr = u64;
pub type PhysAddr = u64; // NEW
/* DELETED:
struct GraphicsMode {
max_mode: u32,
mode: u32,
info: *const GraphicsModeInfo,
size_of_info: usize,
framebuffer_base: PhysAddr,
framebuffer_size: usize,
}
*/
// NEW:
pub struct GraphicsMode {
pub max_mode: u32,
pub mode: u32,
pub info: *const GraphicsModeInfo,
pub size_of_info: usize,
pub framebuffer_base: PhysAddr,
pub framebuffer_size: usize,
}
The print_str
function uses the FONT
array, so we have to import it in uefi.rs
.
At the top of the file:
use crate::FONT;
For our code to compile again, we have to import the stuff we used in main.rs
.
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
mod font;
use font::FONT;
// NEW:
mod uefi;
use uefi::{SystemTable, Screen, PixelFormat, pre_graphics_print_str, print_str};
And we're done (for now).
Take Away
Code till now:
Directory view:
blasterball/
| .cargo/
| | config.toml
| src/
| | font.rs
| | main.rs
| | uefi.rs
| .gitignore
| Cargo.lock
| Cargo.toml
main.rs
contents
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
mod font;
use font::FONT;
mod uefi;
use uefi::{SystemTable, Screen, PixelFormat, pre_graphics_print_str, print_str};
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
let sys_table = unsafe { &*sys_table };
let boot_services = sys_table.boot_services();
let gop = boot_services.locate_gop();
if gop.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let gop = gop.unwrap();
let mode = gop.mode();
let max_mode = mode.max_mode;
let mut desired_mode = 0;
for mode_number in 0..max_mode {
let mode = gop.query_mode(mode_number);
if mode.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
let mode = mode.unwrap();
let horizontal_resolution = mode.horizontal_resolution;
let vertical_resolution = mode.vertical_resolution;
let pixel_format = mode.pixel_format;
if horizontal_resolution == 640
&& vertical_resolution == 480
&& pixel_format == PixelFormat::BlueGreenRedReserved
{
desired_mode = mode_number;
break;
}
if mode_number == max_mode - 1 {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to locate GOP\n");
loop {}
}
}
let set_mode_result = gop.set_mode(desired_mode);
if set_mode_result.is_err() {
let simple_text_output = sys_table.simple_text_output();
pre_graphics_print_str(simple_text_output, "Failed to set the desired mode\n");
loop {}
}
let framebuffer_base = mode.framebuffer_base;
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
print_str(screen, "Hello World!");
0
}
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
loop {}
}
For the full code, go to the repo
In the Next Post
We'll learn a few things about bitmap images