Demilade Sonuga's blog

All posts

The Graphics Output Protocol IV

2022-11-13 · 113 min read

The remaining steps left on our list to figure out graphics are:

  1. Figure out how to change the mode to a graphics mode with the GOP.
  2. Figure out how to draw on the screen.

Changing the mode will start out with the GOP, so let's take a moment to look at it first

#[repr(C)]
struct GraphicsOutput {
query_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32,
size_of_info: *const usize,
info: *const *const GraphicsModeInfo
) -> Status,
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> Status,
unneeded: [u8; 8],
mode: *const GraphicsMode
}

The first function is query_mode. This function gives information about the mode associated with the mode number specified in the second argument mode_number. So, we can get information about modes with this function.

The question that comes next is: which numbers are the mode numbers?

The GraphicsMode struct:

#[repr(C)]
struct GraphicsMode {
max_mode: u32,
mode: u32,
info: *const GraphicsModeInfo,
size_of_info: usize,
framebuffer_base: PhysAddr,
framebuffer_size: usize
}

contains a field: max_mode. This field is what we'll use to determine which modes are valid. In the UEFI spec, valid mode numbers are numbers in the range 0..=max_mode-1.

The GraphicsOutput has a set_mode function which we can then use to set a graphics mode with a valid mode number.

From this information, a sequence of how to set the graphics mode is becoming clearer:

  1. Get the max mode number from the GraphicsOutput's mode field.
  2. Iterate from 0 to max mode number - 1, querying modes until we find our desired mode.
  3. After finding our desired mode, call the GraphicsOutput's set_mode function with the mode number of our desired mode.

And that's all we'll be needing to do to set a graphics mode.

Your efi_main:

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
/* DELETED:
let mut string_u16 = [0u16; 29];
let string = "Successfully located the GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
*/


// NEW:
// At this point, it is safe to dereference the `gop` because the locate protocol
// executed successfully, as verified from the `locate_gop_status`
// We first cast the gop as a pointer to the GraphicsOutput
let gop = gop as *mut GraphicsOutput;
// Get the mode pointer from the GOP instance
let mode = unsafe { (*gop).mode };
// Get the value of the max mode number from the `mode`
let max_mode = unsafe { (*mode).max_mode };
// The valid mode numbers are in the range 0..=`max_mode`-1
for mode_number in 0..max_mode {
// 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();
// Getting the `query_mode` function from the GOP instance
let query_mode = unsafe { (*gop).query_mode };
// Calling `query_mode` to get information about the mode associated
// with `mode_number`
let query_status = (query_mode)(
// The pointer to the GOP instance
gop,
// 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 _
);
// Checking if the status is not a success
// If it's not print an error message and halt (loop endlessly)
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
}
// Printing a success message after querying all the modes successfully
let mut string_u16 = [0u16; 32];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

0
}

In the above, the additions partially went through our steps 1 and 2 to change the graphics mode. First, the mode field in the GOP instance was read into a mode variable. The max mode number was then gotten from the max_mode field in the GraphicsMode instance. We then iterated from 0 to max_mode - 1 calling query_mode with all the mode numbers.

Before we proceed to finish steps 2 and 3, let's run our code just to make sure we're on the right track. If all the query_mode calls execute successfully, a "Successfully queried all modes" message should be printed on screen. If not, "query_mode failed" should be printed.

Upon running, your emulator should look like this:

Successfully queried all modes

It seems we're on the right track, since nothing failed. The next steps are to find the desired mode and set that mode.

But how will we know our desired mode if we don't even know what modes are available?

To figure out our desired mode, we first need to check through the available modes and see which one floats our boat.

Stop for a moment and think about how you'll do this.

First, we'll iterate though the modes. Then we'll print information about each of them to the screen, then we'll take look at the information to see what's actually available and what we can use that to figure out the way forward.

Your efi_main:

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

// NEW:
print( ... ); // PROBLEM!!!
}
let mut string_u16 = [0u16; 32];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

0
}

But now, we have a new problem. We don't have a print function. And to display information about the modes, we need one.

For our purposes, the only mode information we need to look for each mode at are the mode numbers, pixel format, vertical and horizontal resolutions. We can print text with the GraphicsOutput's output_string function, but how can we print integers like mode numbers or resolutions.

The answer (for now) is to cook up a quick printint function.

Writing a printint function

We can print out a single digit with the output_string function like so:

let digit = [48, 0];
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

This will print out the number 0, since 48 is the ascii value of 0. If the 48 in the code sample was replaced with 49, 1 would be outputted instead. The ascii values for 0..=9 is 48..=58. So, putting 58 instead of 48 would output 9.

So, this is a simple way to output single digit numbers. But how about multiple digit numbers.

Stop for a moment to think about how you'll do this.

Firstly, you have to remember that a multiple digit number is a number made up of the addition of two or more single digit numbers each multiplied by different powers of 10.

Take a look at the number 389. This is 300 + 80 + 9. The single digits are 3, 8 and 9. The number in the units position, 9, is multiplied by 10^0 which is 1 (anything to the power of 0 is 1) and 9 * 1 is 9. The number in the tens position, 8, is multiplied by 10^1 (10) -> 80. The number in the hundreds position, 3, is multiplied by 10^2 (100) -> 300. The addition of 300 and 80 and 9 gives 389.

Another example: the number 12345678. This is 8 * 10^0 + 7 * 10^1 + 6 * 10^2 + 5 * 10^3 + 4 * 10^4 + 3 * 10^5 + 2 * 10^6 + 1 * 10^7

Another way of looking at this:

1    |2    |3    |4    |5    |6    |7    |8
10^7 |10^6 |10^5 |10^4 |10^3 |10^2 |10^1 |10^0

So, the least significant digit (the leftmost digit (8 in this case)) is multiplied by 1 (10^0). The digits going from right to left are multiplied by increasing powers of 10 until the most significant (the rightmost (in this case, 1)), which is multiplied by the highest power of 10 (10^7). Notice that the highest power is the number of digits - 1.

In general, this is how multiple digit numbers in base 10 are broken down.

The question that comes next: How do we remove the actual digits and print them one by one.

The answer is in the following observation (which you should immediately verify yourself with a calculator or in your head):

12345678 / 10 == 1234567 remainder 8
1234567  / 10 == 123456  remainder 7
123456   / 10 == 12345   remainder 6
12345    / 10 == 1234    remainder 5
1234     / 10 == 123     remainder 4
123      / 10 == 12      remainder 3
12       / 10 == 1       remainder 2
1        / 10 == 0       remainder 1

As you can see, repeatedly dividing the number by 10 separates out the rightmost digit until you've exhausted all the digits. It's at that point that you've separated out all the digits.

Why does this pan out. Well, the number 12345678 is made of numbers which are all multiples of 10 (1 * 10^7, 2 * 10^6, ..., 7 * 10^1) except the leftmost digit. So, upon a division by 10, the remainder will surely be the rightmost digit.

12345678 / 10 == (10000000 + 2000000 + 300000 + 40000 + 5000 + 600 + 70 + 8) / 10
              == 10000000/10 + 2000000/10 + 300000/10 + 40000/10 + 5000/10 + 600/10 + 70/10 + 8/10
              == 1000000 + 200000 + 30000 + 4000 + 500 + 60 + 7
                 remainder 8

1234567 / 10  == (1000000 + 200000 + 30000 + 4000 + 500 + 60 + 7) / 10
              == 1000000/10 + 200000/10 + 30000/10 + 4000/10 + 500/10 + 60/10 + 7/10
              == 100000 + 20000 + 3000 + 400 + 50 + 6
                 remainder 7

123456 / 10   == (100000 + 20000 + 3000 + 400 + 50 + 6) / 10
              == 100000/10 + 20000/10 + 3000/10 + 400/10 + 50/10 + 6/10
              == 10000 + 2000 + 300 + 40 + 5
                 remainder 6

12345 / 10    == (10000 + 2000 + 300 + 40 + 5) / 10
              == 10000/10 + 2000/10 + 300/10 + 40/10 + 5/10
              == 1000 + 200 + 30 + 4
                 remainder 5

1234 / 10     == (1000 + 200 + 30 + 4) / 10
              == 1000/10 + 200/10 + 30/10 + 4/10
              == 100 + 20 + 3
                 remainder 4

123 / 10      == (100 + 20 + 3) / 10
              == 100/10 + 20/10 + 3/10
              == 10 + 2
                 remainder 3

12 / 10       == (10 + 2) / 10
              == 10/10 + 2/10
              == 1
                 remainder 2

1 / 10        == (1) / 10
              == 1/10
              == 0
                 remainder 1

From this point on, dividing 0 by 10 will give a quotient of 0 and a remainder of 0

So, dividing a number by 10 is the same thing as dividing all its individual numbers in its expansion by 10 and since the rightmost digit is always never a multiple of 10, it will surely be the remainder.

Okay, now that we know how to pick out the individual digits, we can make progress on how to go about writing this printint.

Dividing continuously like that gives us the individual digits of the number from right to left. But there's another problem here: we need the digits from left to right, not right to left.

To print the number 3916, we first have to print the leftmost 3, then the 9, then the 1, then the 6, in the order from left to right.

But this scheme for plucking digits gives us these digits in the reverse order, that is from right to left. We need a way to turn this order around so we can print this in the correct way.

When the digit 8 is plucked out from 12345678, we are left with the number 1234567. This number 1234567 is just another number that needs to be printed, just like 12345678. This number now has to be printed before the the digit 8.

When the digit 7 is plucked from 1234567, we are, again, left with another (smaller) number, 123456. This is number 123456 is just another number, like 1234567, that needs to be printed. This number also needs to be printed before the digit 7.

After repeated division and printing of remainder digits after the quotient, the quotient will eventually reduce to the first digit on the left (in our case, 1). At this point, after printing the digit 1, there will no longer be anything to print.

This repeated division and printing leads us to a recursive procedure that will look like this:

fn printint(n)
    if n >= 10
        let quotient = n / 10
        let remainder = n % 10
        printint(quotient)
        printdigit(remainder)
    else
        printdigit(n)

This is assuming we have a printdigit function (but we already know how to print a digit, so this isn't a problem).

This pseudocode simply says:

  1. Accept an integer n
  2. If n has more than 1 digit (n >= 10)
    • Pluck out the digit on the right.
    • Print the number left after the plucking by setting n <- the number left and going to step 1.
    • Print the plucked digit.
  3. Else If n is a single digit, just print that single digit

The execution of this procedure on our 12345678 will unfold like this:

printint(12345678)
    |
printint(1234567) printdigit(8)
    |
printint(123456) printdigit(7) printdigit(8)
    |
printint(12345) printdigit(6) printdigit(7) printdigit(8)
    |
printint(1234) printdigit(5) printdigit(6) printdigit(7) printdigit(8)
    |
printint(123) printdigit(4) printdigit(5) printdigit(6) printdigit(7) printdigit(8)
    |
printint(12) printdigit(3) printdigit(4) printdigit(5) printdigit(6) printdigit(7) printdigit(8)
    |
printint(1) printdigit(2) printdigit(3) printdigit(4) printdigit(5) printdigit(6) printdigit(7) printdigit(8)
    |
printdigit(1) printdigit(2) printdigit(3) printdigit(4) printdigit(5) printdigit(6) printdigit(7) printdigit(8)

The last call to printint has a 1 as the argument, and since 1 is a single digit, the call results in a single call to printdigit and no other recursive calls. In other words, the function bottoms out on the last single digit.

Translating this to Rust code, we first pin down the printdigit function:

// A function that prints a single digit in the range 0..=9
fn printdigit(n: u32, simple_text_output: *mut SimpleTextOutput) {
// 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
unsafe { ((*simple_text_output).output_string)(simple_text_output, digit_u16.as_mut_ptr()); }
}

Remember: 48..=57 is the code for the digits 0..=9. So, to print any number in the range 0..=9, we simply add the number to 48. 0 + 48 == 48, which is the code for 0. 1 + 48 == 49, which is the code for 49. And so on until 9 + 48 == 57 which is the code for the digit 9.

This is then printed the way any other string will be printed.

Before continuing, let's check out our printdigit function.

For now, comment out the current efi_main function and add a new one:

// CHECKING OUT OUR printdigit FUNCTION:
#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let simple_text_output = unsafe { (*sys_table).simple_text_output };
for digit in 0..=9 {
printdigit(digit, simple_text_output);
}
// Print a newline after the digits
let mut string_u16 = [b'\n' as u16, 0u16];
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
0
}
/* COMMENTING OUT FOR NOW:
#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 21];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 18];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

// NEW:
print( ... ); // PROBLEM!!!
}
let mut string_u16 = [0u16; 31];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

0
}
*/

Upon building and running, you should have this output:

Digits 0 to 9

Our printdigit function works.

Now, for our printint function:

// Prints an integer n
fn printint(n: u32, simple_text_output: *mut SimpleTextOutput) {
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);
}
}

To check it:

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let simple_text_output = unsafe { (*sys_table).simple_text_output };
/* DELETED:
for digit in 0..=9 {
printdigit(digit, simple_text_output);
}
*/

printint(12345678, simple_text_output); // NEW
// Print a newline after the digits
let mut string_u16 = [b'\n' as u16, 0u16];
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
0
}

After building and running:

The number 12345678

And our printint function works, too.

Now, we can delete this efi_main and uncomment our old one:

/* DELETED:
#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let simple_text_output = unsafe { (*sys_table).simple_text_output };
for digit in 0..=9 {
printdigit(digit, simple_text_output);
}
// Print a newline after the digits
let mut string_u16 = [b'\n' as u16, 0u16];
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
0
}
*/


// UNCOMMENTED:
#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

print( ... ); // PROBLEM!!!
}
let mut string_u16 = [0u16; 32];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

0
}

Finding Our Desired Mode

To complete our step 2 in finding our desired mode, we'll iterate over the modes and print out their mode numbers, vertical and horizontal resolution, and pixel format.

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

// DELETED: print( ... ); // PROBLEM!!!

// NEW:
// Printing the mode information
let simple_text_output = unsafe { (*sys_table).simple_text_output };

// A space to visually separate information
let mut space = [0u16; 2];
" \0".encode_utf16()
.enumerate()
.for_each(|(i, letter)| space[i] = letter);

// Buffer to hold UTF-16 encoded characters for printing
let mut string_u16 = [0u16; 24];

// Printing the mode number
let string = "mode number: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(mode_number, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the vertical resolution
let string = "vertical resolution: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).vertical_resolution }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the horizontal resolution
let string = "horizontal resolution: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).horizontal_resolution }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the pixel format
let string = "pixel format: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).pixel_format as u32 }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing a space to separate this mode info from the next
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

}
let mut string_u16 = [0u16; 32];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }

0
}

The additions to our efi_main simply print the mode number, vertical resolution, horizontal resolution and pixel format.

Building and running gives us this:

Available graphic modes

This is a blob of information that shouldn't be too hard to process. The info for a mode starts with the mode_number: n then what follows is the vertical and horizontal resolutions, then the pixel format. After this, we have another mode_number: n+1 which is the start of the mode info of the next mode.

Upon inspection, the pixel formats printed all have values of 1. In our PixelFormat enum:

#[repr(u32)]
enum PixelFormat {
RedGreenBlueReserved = 0,
BlueGreenRedReserved = 1,
BitMask = 2,
BltOnly = 3
}

This 1 corresponds to PixelFormat::BlueGreenRedReserved, which is the format our Pixel is modeled after:

#[repr(C)]
struct Pixel {
blue: u8,
green: u8,
red: u8,
reserved: u8
}

So, in pixel format, I think we're good to go.

But for horizontal and vertical resolutions, we're going with the 640 x 480. That is, the mode with a mode number of 1.

Okay, so thats it!

We've found our desired mode, now let's modify our code to find it and set it as the current mode.

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };

// The desired mode to set after finding it in this for loop
let mut desired_mode = 0; // NEW

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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

/* DELETED:
// Printing the mode information
let simple_text_output = unsafe { (*sys_table).simple_text_output };

// A space to visually separate information
let mut space = [0u16; 2];
" \0".encode_utf16()
.enumerate()
.for_each(|(i, letter)| space[i] = letter);

// Buffer to hold UTF-16 encoded characters for printing
let mut string_u16 = [0u16; 24];

// Printing the mode number
let string = "mode number: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(mode_number, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the vertical resolution
let string = "vertical resolution: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).vertical_resolution }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the horizontal resolution
let string = "horizontal resolution: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).horizontal_resolution }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing the pixel format
let string = "pixel format: \0";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
printint(unsafe { (*mode).pixel_format as u32 }, simple_text_output);
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }

// Printing a space to separate this mode info from the next
unsafe { ((*simple_text_output).output_string)(simple_text_output, space.as_mut_ptr()); }
*/


let horizontal_resolution = unsafe { (*mode).horizontal_resolution };
let vertical_resolution = unsafe { (*mode).vertical_resolution };
let pixel_format = unsafe { (*mode).pixel_format };
// Looking for our desired mode
if horizontal_resolution == 640 && vertical_resolution == 480
&& pixel_format == PixelFormat::BlueGreenRedReserved {
desired_mode = mode_number;
break;
}
// Halt with an error if the desired mode wasn't found
if mode_number == max_mode - 1 {
let mut string_u16 = [0u16; 32];
let string = "Couldn't find the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
}
/* DELETED:
// Printing a success message after querying all the modes successfully
let mut string_u16 = [0u16; 31];
let string = "Successfully queried all modes\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
*/


// Setting the mode to our desired mode
let set_mode_status = unsafe { ((*gop).set_mode)(
gop,
desired_mode
) };

// Checking if it is a success
if set_mode_status != 0 {
let mut string_u16 = [0u16; 32];
let string = "Failed to set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
} else {
let mut string_u16 = [0u16; 35];
let string = "Successfully set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
}

0
}

The modifications in the code collect the horizontal and vertical resolutions and pixel format from the mode information, then compares them to the values expected of the mode we're looking for. If it finds it, it sets the desired_mode variable and breaks out of the loop. If it doesn't find it after looping through all the modes, it prints "Couldn't find desired mode\n" and loops forever, halting execution.

After finding our desired mode, we call the GraphicsOutput's set_mode function to set our desired mode. Then, we compare the status of set_mode's execution to see if it's a success (0 means success). If it's not, we print "Failed to set the desired mode\n" as halt. If it is successful, we print "Successfully set the desire mode\n".

Upon building, we encounter an error:

blog-blasterball on  the-graphics-output-protocol-iv [!] is 📦 v0.1.0 via 🦀 v1.67.0-nightly 
❯ cargo b
   Compiling blasterball v0.1.0 (/home/demilade/Documents/blog-blasterball)
error[E0369]: binary operation `==` cannot be applied to type `PixelFormat`
   --> src/main.rs:326:29
    |
326 |             && pixel_format == PixelFormat::BlueGreenRedReserved {
    |                ------------ ^^ --------------------------------- PixelFormat
    |                |
    |                PixelFormat
    |
note: an implementation of `PartialEq<_>` might be missing for `PixelFormat`
   --> src/main.rs:503:1
    |
503 | enum PixelFormat {
    | ^^^^^^^^^^^^^^^^ must implement `PartialEq<_>`
help: consider annotating `PixelFormat` with `#[derive(PartialEq)]`
    |
503 | #[derive(PartialEq)]
    |

For more information about this error, try `rustc --explain E0369`.
error: could not compile `blasterball` due to previous error

Apparently, we didn't implement the PartialEq trait for our enum PixelFormat. The PartialEq trait is a trait that needs to be implemented for anything that can be compared. To rectify this:

// Defines how to interpret the bits that represent a single pixel
#[derive(PartialEq)] // NEW
#[repr(u32)]
enum PixelFormat {
RedGreenBlueReserved = 0,
BlueGreenRedReserved = 1,
BitMask = 2,
BltOnly = 3
}

The #[derive(PartialEq)] macro automatically implements PartialEq for us. So, that's all we need here.

Building again:

blog-blasterball on  the-graphics-output-protocol-iv [!] is 📦 v0.1.0 via 🦀 v1.67.0-nightly 
❯ cargo b
   Compiling blasterball v0.1.0 (/home/demilade/Documents/blog-blasterball)
warning: unused variable: `handle`
   --> src/main.rs:211:29
    |
211 | extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
    |                             ^^^^^^ help: if this is intentional, prefix it with an underscore: `_handle`
    |
    = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `panic_info`
   --> src/main.rs:568:18
    |
568 | fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
    |                  ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_panic_info`

error[E0507]: cannot move out of `mode.pixel_format` which is behind a raw pointer
   --> src/main.rs:323:37
    |
323 |         let pixel_format = unsafe { (*mode).pixel_format };
    |                                     ^^^^^^^^^^^^^^^^^^^^
    |                                     |
    |                                     move occurs because `mode.pixel_format` has type `PixelFormat`, which does not implement the `Copy` trait
    |                                     help: consider borrowing here: `&(*mode).pixel_format`

For more information about this error, try `rustc --explain E0507`.
warning: `blasterball` (bin "blasterball") generated 2 warnings
error: could not compile `blasterball` due to previous error; 2 warnings emitted

Another error. This one is an ownership error.

It tells us mode.pixel_format is being moved. This is because our PixelFormat doesn't implement Copy. So, we implement Copy:

// DELETED: #[derive(PartialEq)]
#[derive(PartialEq, Clone, Copy)] // NEW
#[repr(u32)]
enum PixelFormat {
RedGreenBlueReserved = 0,
BlueGreenRedReserved = 1,
BitMask = 2,
BltOnly = 3
}

We also have to implement Clone because it is a requirement to implement Copy.

Another build should be successful now.

Upon running:

We have a blank screen:

Nothing printed

And as expected. Well, it turns out that the output_string function only works in defined text modes. The current mode we've set is not a text mode, so the output_string function won't work.

We need to create our own.

Take Away

Your code at this point:

Directory view:

blasterball/
| .cargo/
| | config.toml
| src/
| | main.rs
| .gitignore
| Cargo.lock
| Cargo.toml

main.rs contents

#![no_std]
#![no_main]
#![feature(abi_efiapi)]

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
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 = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
let mut desired_mode = 0;
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 = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let horizontal_resolution = unsafe { (*mode).horizontal_resolution };
let vertical_resolution = unsafe { (*mode).vertical_resolution };
let pixel_format = unsafe { (*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 mut string_u16 = [0u16; 32];
let string = "Couldn't find the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
}

let set_mode_status = unsafe { ((*gop).set_mode)(
gop,
desired_mode
) };

if set_mode_status != 0 {
let mut string_u16 = [0u16; 32];
let string = "Failed to set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
} else {
let mut string_u16 = [0u16; 35];
let string = "Successfully set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
}

0
}

fn printdigit(n: u32, simple_text_output: *mut SimpleTextOutput) {
let mut digit_u16 = [48 + n as u16, 0];
unsafe { ((*simple_text_output).output_string)(simple_text_output, digit_u16.as_mut_ptr()); }
}

fn printint(n: u32, simple_text_output: *mut SimpleTextOutput) {
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);
}
}

#[repr(C)]
struct SystemTable {
unneeded: [u8; 60],
simple_text_output: *mut SimpleTextOutput,
unneeded2: [u8; 24],
boot_services: *const BootServices
}

#[repr(C)]
struct Guid {
first_chunk: u32,
second_chunk: u16,
third_chunk: u16,
other_chunks: [u8; 8]
}

type Status = usize;

#[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
}


#[repr(C)]
struct SimpleTextOutput {
unneeded: [u8; 8],
output_string: extern "efiapi" fn (this: *mut SimpleTextOutput, *mut u16)
}

#[repr(C)]
struct GraphicsOutput {
query_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32,
size_of_info: *const usize,
info: *mut *const GraphicsModeInfo
) -> Status,
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> Status,
unneeded: [u8; 8],
mode: *const GraphicsMode
}

#[repr(C)]
struct GraphicsModeInfo {
version: u32,
horizontal_resolution: u32,
vertical_resolution: u32,
pixel_format: PixelFormat,
pixel_info: PixelBitmask,
pixels_per_scan_line: u32
}

#[derive(PartialEq, Clone, Copy)]
#[repr(u32)]
enum PixelFormat {
RedGreenBlueReserved = 0,
BlueGreenRedReserved = 1,
BitMask = 2,
BltOnly = 3
}

#[repr(C)]
struct Pixel {
blue: u8,
green: u8,
red: u8,
reserved: u8
}

#[repr(C)]
struct PixelBitmask {
red_mask: u32,
green_mask: u32,
blue_mask: u32,
reserved: u32
}

type PhysAddr = u64;

#[repr(C)]
struct GraphicsMode {
max_mode: u32,
mode: u32,
info: *const GraphicsModeInfo,
size_of_info: usize,
framebuffer_base: PhysAddr,
framebuffer_size: usize
}

#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
loop {}
}

In the Next Post

We'll be getting to the last step on our list

  1. Get boot services table from system table.
  2. Call the LocateProtocol function in the boot services table.
  3. Figure out how to change the mode to a graphics mode with the GOP.
  4. Figure out how to draw on screen.