Skip to content

Commit 8740b61

Browse files
committed
Describe how to include the uefi crate
1 parent a63c51c commit 8740b61

1 file changed

Lines changed: 40 additions & 8 deletions

File tree

  • blog/content/edition-3/posts/02-booting

blog/content/edition-3/posts/02-booting/uefi.md

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -432,41 +432,73 @@ cargo run --package disk_image -- target/x86_64-unknown-uefi/debug/uefi_app.efi
432432

433433
Note the additional `--` argument. The `cargo run` uses this special argument to separate `cargo run` arguments from the arguments that should be passed to the compiled executable. The path of course depends on your working directory, i.e. whether you run it from the project root or from the `disk_image` subdirectory. It also depends on whether you compiled the `uefi_app` in debug or `--release` mode.
434434

435-
The result of this command is a `.fat` and a `.img` file next to the given `.efi` executable. These files can be launched in QEMU and on real hardware [as described][run-instructions] in the main _Booting_ post. The result is a black screen:
435+
The result of this command is a `.fat` and a `.img` file next to the given `.efi` executable. These files can be launched in QEMU and on real hardware [as described][run-instructions] in the main _Booting_ post. The result should look something like this:
436436

437437
[run-instructions]: @/edition-3/posts/02-booting/index.md#running-our-kernel
438438

439439
TODO screenshot
440440

441-
We don't see anything on the screen yet since we only `loop {}` in our `efi_main`. Let's fix this and print something to the screen by using the [`uefi`] crate.
441+
We don't see any output from our `uefi_app` on the screen yet since we only `loop {}` in our `efi_main`. Instead, we see some output from the UEFI firmware itself that was created before our application was started.
442442

443443
[`uefi`]: https://docs.rs/uefi/0.8.0/uefi/
444444

445+
Let's try to improve this by printing something to the screen from our `uefi_app` as well.
446+
445447
## The `uefi` Crate
446448

447449
In order to print something to the screen, we need to call some functions provided by the UEFI firmware. These functions can be invoked through the `system_table` argument passed to our `efi_main` function. This table provides [function pointers] for all kinds of functionality, including access to the screen, disk, or network.
448450

451+
[function pointers]: https://en.wikipedia.org/wiki/Function_pointer
452+
449453
Since the system table has a standardized format that is identical on all systems, it makes sense to create an abstraction for it. This is what the `uefi` crate does. It provides a [`SystemTable`] type that abstracts the UEFI system table functions as normal Rust methods. It is not complete, but the most important functions are all available.
450454

455+
[`SystemTable`]: https://docs.rs/uefi/0.8.0/uefi/table/struct.SystemTable.html
456+
451457
To use the crate, we first add it as a dependency in our `Cargo.toml`:
452458

453459
```toml
454-
# TODO
460+
# in Cargo.toml
461+
462+
[dependencies]
463+
uefi = "0.8.0"
455464
```
456465

457466
Now we can change the types of the `image` and `system_table` arguments in our `efi_main` declaration:
458467

459468
```rust
460-
// TODO
461-
``
469+
// in src/main.rs
470+
471+
#[no_mangle]
472+
pub extern "efiapi" fn efi_main(
473+
image: uefi::Handle,
474+
system_table: uefi::table::SystemTable<uefi::table::Boot>,
475+
) -> uefi::Status {
476+
loop {}
477+
}
478+
```
479+
480+
Instead of using raw pointers and an anonymous `usize` return type, we now use the [`Handle`], [`SystemTable`], and [`Status`] abstraction types provided by the `uefi` crate. This way, we can use the higher-level API provided by the crate instead of carefully calculating pointer offsets to access the system table manually.
481+
482+
[`Handle`]: https://docs.rs/uefi/0.8.0/uefi/data_types/struct.Handle.html
483+
[`Status`]: https://docs.rs/uefi/0.8.0/uefi/struct.Status.html
462484

463-
Since the Rust compiler is not able to typecheck the function signature of the entry point function, we could accidentally use the wrong signature here. To prevent this (and the resulting undefined behavior), the `uefi` crate provides an `entry` macro to enforce the correct signature. To use it, we change our `main.rs` like this:
485+
While the above function signature works, it is very fragile because the Rust compiler is not able to typecheck the function signature of entry point functions. Thus, we could accidentally use the wrong signature (e.g. after updating the `uefi` crate), which would cause undefined behavior. To prevent this, the `uefi` crate provides an [`entry` macro] to enforce the correct signature. To use it, we change our entry point function in the following way:
486+
487+
[`entry` macro]: https://docs.rs/uefi/0.8.0/uefi/prelude/attr.entry.html
464488

465489
```rust
466-
// TODO
490+
// in src/main.rs
491+
492+
#[entry]
493+
fn efi_main(
494+
image: uefi::Handle,
495+
system_table: uefi::table::SystemTable<uefi::table::Boot>,
496+
) -> uefi::Status {
497+
loop {}
498+
}
467499
```
468500

469-
Now we can safely use the types provided by the `uefi` crate.
501+
The macro already inserts the `#[no_mangle]` attribute and the `pub extern "efiapi"` modifiers for us, so we no longer need them. We will now get a compile error if the function signature is not correct (try it if you like).
470502

471503
### Printing to Screen
472504

0 commit comments

Comments
 (0)