Demilade Sonuga's blog
All postsThe Graphics Output Protocol II
In the previous post, we took our first steps to modeling the GOP. At this point, your code should look like this:
#![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 mut string_u16 = [0u16; 14];
let string = "Hello World!\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
}
#[repr(C)]
struct SystemTable {
unneeded: [u8; 60],
simple_text_output: *mut SimpleTextOutput
}
#[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
) -> usize,
}
#[repr(C)]
struct GraphicsModeInfo {
version: u32,
horizontal_resolution: u32,
vertical_resolution: u32,
pixel_format: PixelFormat,
pixel_info: PixelBitmask,
pixels_per_scan_line: u32
}
#[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
}
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
loop {}
}
Remember our GOP is specified like this:
typedef struct EFI_GRAPHICS_OUTPUT_PROTCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
So we're not done yet. We still need to get SetMode
, Blt
and Mode
in our code.
In the spec, the definition of EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE
is
typedef
EFI_STATUS
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN UINT32 ModeNumber
);
The SetMode
function just takes a pointer to the GOP in This
and the mode number associated with
the mode which you want set the video device to in ModeNumber
. The function returns a status
which tells if it was successful.
In Rust:
#[repr(C)]
struct GraphicsOutput {
query_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32,
size_of_info: *const usize,
info: *mut *const GraphicsModeInfo
) -> usize,
// NEW:
// Sets the video device into the mode associated with `mode_number` and clears
// the visible portions of the output display to black
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> usize
}
The EFI_GRAPHICS_OUTPUT_PROTOCOL
maps to GraphicsOutput
and UINT32
(unsigned 32 bit integer) maps
to u32
in our code. And the EFI_STATUS
is just a usize
.
Next on our list is the Blt
. This function is used to draw rectangles on the video screen.
But we really won't be needing it in this project, so we just place an unneeded
field in our
GOP representation:
#[repr(C)]
struct GraphicsOutput {
query_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32,
size_of_info: *const usize,
info: *mut *const GraphicsModeInfo
) -> usize,
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> usize,
// NEW:
// The Blt function pointer, which we don't need
unneeded: [u8; 8]
}
In our GraphicsOutput
struct, instead of the function, we place an array of 8 bytes and mark the field
as unneeded
. 8 bytes because the fn
is just a pointer to a function, a pointer is an address and
addresses on x86_64, which is the platform we're developing for, are 8 bytes in size.
Lastly, we have the Mode
field. Mode
is just a pointer to a EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE
structure, a read-only structure which gives information about the current and available video modes.
This structure is defined as:
typedef struct {
UINT32 MaxMode;
UINT32 Mode;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
EFI_PHYSICAL_ADDRESS FrameBufferBase;
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
The MaxMode
field gives the number of modes supported by the GOP's QueryMode
and SetMode
.
The Mode
field gives the currently set mode of the graphics device, where the only allowed values
are 0..=MaxMode
-1. Info
is a pointer to a read-only instance of EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
which is modelled as GraphicsModeInfo
in our code.
SizeOfInfo
gives the size of the GraphicsModeInfo
strucure in bytes. It may seem like this field
isn't needed, since the size of our struct is fixed but
it is because the definition of the structure can change in future UEFI specs, but this isn't really
relevant to us right now, so I think we can forget about it.
FrameBufferBase
gives the beginning address of the graphics framebuffer, the segment of memory
whose bit contents are interpreted as color intensities of pixels to be displayed on the screen.
FrameBufferSize
is the amount of memory in bytes needed to support the active mode, that is,
number of pixels in one horizontal line * number of pixels in one vertical line * the size of a pixel in
bytes.
The EFI_PHYSICAL_ADDRESS
, which is the type of the FrameBufferBase
field is just an alias for
UINT64
, which is a 64 bit unsigned integer (8 bytes).
In Rust:
// The physical address on x86_64 is 8 bytes (64 bits)
type PhysAddr = u64;
// Gives info about the currently set and other available graphics modes
#[repr(C)]
struct GraphicsMode {
// The number of modes supported by `GraphicsOutput::set_mode`
// and `GraphicsOutput::query_mode`
max_mode: u32,
// The number associated with the current mode of the graphics
// device. Valid values are always in the range 0..=`max_mode`-1
mode: u32,
// Pointer to a read only GraphicsModeInfo
info: *const GraphicsModeInfo,
// Size of the `GraphicsModeInfo` structure
size_of_info: usize,
// The starting address of the graphics framebuffer
framebuffer_base: PhysAddr,
// The size of the framebuffer in bytes
framebuffer_size: usize
}
Our GraphicsOutput
now:
#[repr(C)]
struct GraphicsOutput {
query_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32,
size_of_info: *const usize,
info: *mut *const GraphicsModeInfo
) -> usize,
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> usize,
// The Blt function pointer, which we don't need
unneeded: [u8; 8],
// NEW:
// Gives information about the current graphics mode
// and the other available modes
mode: *const GraphicsMode
}
And that's it, we now have GOP in our Rust code.
Take Away
- Addresses on x86_64 are 64 bit numbers.
Your code should be looking like this now:
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 mut string_u16 = [0u16; 14];
let string = "Hello World!\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
}
#[repr(C)]
struct SystemTable {
unneeded: [u8; 60],
simple_text_output: *mut SimpleTextOutput
}
#[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
) -> usize,
set_mode: extern "efiapi" fn(
this: *mut GraphicsOutput,
mode_number: u32
) -> usize,
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
}
#[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 going through the steps to locating the GOP
References
- UEFI spec, version 2.7, section 12.9 (https://uefi.org/specifications)