mirror of
https://github.com/rust-osdev/uefi-rs
synced 2026-06-08 17:17:36 +00:00
docs: Add book
This is a first pass at adding a uefi-rs book. It is not a replacement for the reference documentation on docs.rs, but more of a companion. See [book/README.md] for a quick intro on how to build the book locally. https://github.com/rust-osdev/uefi-rs/issues/459
This commit is contained in:
@@ -3,3 +3,6 @@
|
||||
|
||||
# Libraries should not commit their .lock files.
|
||||
Cargo.lock
|
||||
|
||||
# Files generated by mdBook.
|
||||
/book/book/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# UEFI Book
|
||||
|
||||
To build the book locally, first install [mdBook].
|
||||
|
||||
Next, launch a server that will automatically update as changes are made
|
||||
to the markdown sources:
|
||||
|
||||
```console
|
||||
mdbook serve book/
|
||||
```
|
||||
|
||||
Then open <http://localhost:3000> in a web browser.
|
||||
|
||||
[mdBook]: https://rust-lang.github.io/mdBook/guide/installation.html
|
||||
@@ -0,0 +1,5 @@
|
||||
[book]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Rust UEFI Book"
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting to latest documentation</title>
|
||||
<meta http-equiv="refresh" content="5; URL=HEAD">
|
||||
</head>
|
||||
|
||||
Redirecting to <a href="HEAD">./HEAD</a>...
|
||||
@@ -0,0 +1,18 @@
|
||||
# Summary
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
- [Tutorial](tutorial/introduction.md)
|
||||
- [Creating a UEFI Application](tutorial/app.md)
|
||||
- [Building](tutorial/building.md)
|
||||
- [Running in a VM](tutorial/vm.md)
|
||||
- [How-to](how_to/introduction.md)
|
||||
- [Using Protocols](how_to/protocols.md)
|
||||
- [Concepts](concepts/introduction.md)
|
||||
- [Boot Stages](concepts/boot_stages.md)
|
||||
- [Tables](concepts/tables.md)
|
||||
- [GUID](concepts/guid.md)
|
||||
- [Handles and Protocols](concepts/handles_and_protocols.md)
|
||||
- [Device Paths](concepts/device_paths.md)
|
||||
- [Variables](concepts/variables.md)
|
||||
- [GPT](concepts/gpt.md)
|
||||
- [Reference](reference.md)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Boot Stages
|
||||
|
||||
A UEFI system goes through several distinct phases during the boot process.
|
||||
1. **Platform Initialization.** This early-boot phase is mostly outside
|
||||
the scope of `uefi-rs`. It is described by the [UEFI Platform
|
||||
Initialization Specification], which is separate from the main UEFI
|
||||
Specification.
|
||||
2. **Boot Services.** This is when UEFI drivers and applications are
|
||||
loaded. Both the [`BootServices`] and [`RuntimeServices`] tables are
|
||||
accessible. This stage typically culminates in running a bootloader
|
||||
that loads an operating system. The stage ends when
|
||||
[`SystemTable::exit_boot_services`] is called, putting the system in
|
||||
Runtime mode.
|
||||
3. **Runtime.** This stage is typically active when running an operating
|
||||
system such as Linux or Windows. UEFI functionality is much more
|
||||
limited in the Runtime mode. The [`BootServices`] table is no longer
|
||||
accessible, but the [`RuntimeServices`] table is still
|
||||
available. Once the system is in Runtime mode, it cannot return to
|
||||
the Boot Services stage until after a system reset.
|
||||
|
||||
[UEFI Platform Initialization Specification]: https://uefi.org/specifications
|
||||
[`BootServices`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html
|
||||
[`RuntimeServices`]: https://docs.rs/uefi/latest/uefi/table/runtime/struct.RuntimeServices.html
|
||||
[`SystemTable::exit_boot_services`]: https://docs.rs/uefi/latest/uefi/table/struct.SystemTable.html#method.exit_boot_services
|
||||
@@ -0,0 +1,24 @@
|
||||
# Device Paths
|
||||
|
||||
A device path is a very flexible packed data structure for storing paths
|
||||
to many kinds of device. Note that these device paths are not the same
|
||||
thing as file system paths, although they can include file system
|
||||
paths. Like [handles], device paths can be used to uniquely identify
|
||||
resources such as consoles, mice, disks, partitions, and more. Unlike
|
||||
[handles], which are essentially opaque pointers, device paths are
|
||||
variable-length structures that contain parseable information.
|
||||
|
||||
The [`uefi::proto::device_path`] module documentation describes the
|
||||
details of how device paths are encoded.
|
||||
|
||||
Device paths can also be converted to and from human-readable text
|
||||
representations that look like this:
|
||||
```text
|
||||
PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
|
||||
```
|
||||
|
||||
See [`uefi::proto::device_path::text`] for details.
|
||||
|
||||
[handles]: handles.md
|
||||
[`uefi::proto::device_path`]: https://docs.rs/uefi/latest/uefi/proto/device_path/index.html
|
||||
[`uefi::proto::device_path::text`]: https://docs.rs/uefi/latest/uefi/proto/device_path/text/index.html
|
||||
@@ -0,0 +1,47 @@
|
||||
# GPT
|
||||
|
||||
[GPT] is short for [GUID] Partition Table. It's a more modern
|
||||
alternative to MBR (master boot record) partition tables. Although it's
|
||||
defined in the UEFI specification, it often gets used on non-UEFI
|
||||
systems too. There are a couple big advantages of using GPT over MBR:
|
||||
- It has a relatively clear and precise standard, unlike MBR where
|
||||
implementations often just try to match what other implementations do.
|
||||
- It supports very large disks and very large numbers of partitions.
|
||||
|
||||
A GPT disk contains a primary header near the beginning of the disk,
|
||||
followed by a partition entry array. The header and partition entry
|
||||
array have a secondary copy at the end of the disk for redundency. The
|
||||
partition entry arrays contain structures that describe each partition,
|
||||
including a GUID to identify the individual partition, a partition type
|
||||
GUID to indicate the purpose of the partition, and start/end block
|
||||
addresses. In between the entry arrays is the actual partition data.
|
||||
|
||||
## System partition
|
||||
|
||||
The system partition is UEFI's version of a bootable partition. The
|
||||
system partition is sometimes called the ESP, or EFI System
|
||||
Partition. It is identified by a partition type of
|
||||
`c12a7328-f81f-11d2-ba4b-00a0c93ec93b`. The system partition always
|
||||
contains a FAT file system. There are various standardized paths that
|
||||
can exist within the file system, and of particular importance are the
|
||||
boot files. These are the files that UEFI will try to boot from by
|
||||
default (in the absence of a different boot configuration set through
|
||||
special [UEFI variables]).
|
||||
|
||||
Boot files are under `\EFI\BOOT`, and are named `BOOT<ARCH>.efi`, where
|
||||
`<ARCH>` is a short architecture name.
|
||||
|
||||
|Architecture |File name |
|
||||
|--------------|----------------|
|
||||
|Intel 32-bit |BOOTIA32.EFI |
|
||||
|X86_64 |BOOTX64.EFI |
|
||||
|Itanium |BOOTIA64.EFI |
|
||||
|AArch32 |BOOTARM.EFI |
|
||||
|AArch64 |BOOTAA64.EFI |
|
||||
|RISC-V 32-bit |BOOTRISCV32.EFI |
|
||||
|RISC-V 64-bit |BOOTRISCV64.EFI |
|
||||
|RISC-V 128-bit|BOOTRISCV128.EFI|
|
||||
|
||||
[GPT]: https://en.wikipedia.org/wiki/GUID_Partition_Table
|
||||
[GUID]: guid.md
|
||||
[UEFI variables]: variables.md
|
||||
@@ -0,0 +1,15 @@
|
||||
# GUID
|
||||
|
||||
GUID is short for Globally Unique Identifier. A GUID is always 16 bytes,
|
||||
and has a standard string representation format that looks like this:
|
||||
`313b0d7c-fed4-4de7-99ed-2fe48874a410`. The details of the GUID format
|
||||
aren't too important, but be aware that the actual byte representation
|
||||
is not in the same order as the string representation because the first
|
||||
three fields are little-endian. For the most part you can treat GUIDs as
|
||||
opaque identifiers.
|
||||
|
||||
The UEFI specification uses GUIDs all over the place. GUIDs are used to
|
||||
identify protocols, disk partitions, variable groupings, and much
|
||||
more. In `uefi-rs`, GUIDs are represented by the [`Guid`] type.
|
||||
|
||||
[`Guid`]: https://docs.rs/uefi/latest/uefi/struct.Guid.html
|
||||
@@ -0,0 +1,40 @@
|
||||
# Handles and Protocols
|
||||
|
||||
Handles and protocols are at the core of what makes UEFI
|
||||
extensible. Together they are the mechanism by which UEFI can adapt to a
|
||||
wide array of hardware and boot conditions, while still providing a
|
||||
consistent interface to drivers and applications.
|
||||
|
||||
### Handles
|
||||
|
||||
Handles represent resources. A resource might be a physical device such
|
||||
as a disk drive or USB device, or something less tangible like a loaded
|
||||
executable.
|
||||
|
||||
A [Handle] is an opaque pointer, so you can't do anything with it
|
||||
directly. To operate on a handle you have to open a protocol.
|
||||
|
||||
### Protocols
|
||||
|
||||
Protocols are interfaces that provide functions to interact with a
|
||||
resource. For example, the [BlockIO] protocol provides functions to read
|
||||
and write to block IO devices.
|
||||
|
||||
Protocols are only available during the Boot Services [stage]; you can't
|
||||
access them during the Runtime stage.
|
||||
|
||||
The UEFI Specification defines a very large number of protocols. Because
|
||||
protocols are inherently very diverse, the best place to learn about
|
||||
individual protocols is the [UEFI Specification]. There are many
|
||||
chapters covering various protocols. Not all of these protocols are
|
||||
wrapped by `uefi-rs` yet (contributions welcome!) but many of the most
|
||||
commonly useful ones are.
|
||||
|
||||
See the [Using Protocols] how-to for details of the `uefi-rs` API for
|
||||
interacting with protocols.
|
||||
|
||||
[UEFI Specification]: https://uefi.org/specifications
|
||||
[stage]: boot_stages.md
|
||||
[Handle]: https://docs.rs/uefi/latest/uefi/data_types/struct.Handle.html
|
||||
[BlockIO]: https://docs.rs/uefi/latest/uefi/proto/media/block/struct.BlockIO.html
|
||||
[Using Protocols]: ../how_to/protocols.md
|
||||
@@ -0,0 +1,10 @@
|
||||
# Concepts
|
||||
|
||||
The canonical source of information about UEFI is the [UEFI specification].
|
||||
The specification is huge (currently nearly 2500 pages). Much of that
|
||||
content relates to optional services, understanding of which is not
|
||||
critical to understanding UEFI as a whole. This chapter summarizes some
|
||||
of the more important UEFI concepts and links to the relevant `uefi-rs`
|
||||
documentation.
|
||||
|
||||
[UEFI specification]: https://uefi.org/specifications
|
||||
@@ -0,0 +1,37 @@
|
||||
# Tables
|
||||
|
||||
UEFI has a few table structures. These tables are how you get access to
|
||||
UEFI services.
|
||||
|
||||
[`SystemTable`] (`EFI_SYSTEM_TABLE` in the specification) is the
|
||||
top-level table that provides access to the other tables.
|
||||
|
||||
[`BootServices`] (`EFI_BOOT_SERVICES` in the specification) provides
|
||||
access to a wide array of services such as memory allocation, executable
|
||||
loading, and optional extension interfaces called protocols. This table
|
||||
is only accessible while in the Boot Services stage.
|
||||
|
||||
[`RuntimeServices`] (`EFI_RUNTIME_SERVICES` in the specification)
|
||||
provides access to a fairly limited set of services, including variable
|
||||
storage, system time, and virtual-memory mapping. This table is
|
||||
accessible during both the Boot Services and Runtime stages.
|
||||
|
||||
When writing a UEFI application, you get access to the system table from
|
||||
one of the arguments to the `main` entry point:
|
||||
|
||||
```rust,ignore
|
||||
fn main(handle: Handle, mut system_table: SystemTable<Boot>) -> Status;
|
||||
```
|
||||
|
||||
Then use [`SystemTable::boot_services`] and
|
||||
[`SystemTable::runtime_services`] to get access to the other
|
||||
tables. Once [`SystemTable::exit_boot_services`] is called, the original
|
||||
system table is consumed and a new system table is returned that only
|
||||
provides access to the [`RuntimeServices`] table.
|
||||
|
||||
[`BootServices`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html
|
||||
[`RuntimeServices`]: https://docs.rs/uefi/latest/uefi/table/runtime/struct.RuntimeServices.html
|
||||
[`SystemTable::boot_services`]: https://docs.rs/uefi/latest/uefi/table/struct.SystemTable.html#method.boot_services
|
||||
[`SystemTable::exit_boot_services`]: https://docs.rs/uefi/latest/uefi/table/struct.SystemTable.html#method.exit_boot_services
|
||||
[`SystemTable::runtime_services`]: https://docs.rs/uefi/latest/uefi/table/struct.SystemTable.html#method.runtime_services
|
||||
[`SystemTable`]: https://docs.rs/uefi/latest/uefi/table/struct.SystemTable.html
|
||||
@@ -0,0 +1,39 @@
|
||||
# Variables
|
||||
|
||||
UEFI provides fairly flexible key/value variable storage.
|
||||
|
||||
Each variable is identified by a key consisting of a UCS-2
|
||||
null-terminated name plus a vendor [GUID]. The vendor GUID serves as a
|
||||
namespace for variables so that different vendors don't accidentally
|
||||
overwrite or misinterpret another vendor's variable if they happen to
|
||||
have the same name.
|
||||
|
||||
The data stored in each variable is an arbitrary byte array.
|
||||
|
||||
## Attributes
|
||||
|
||||
Each variable has attributes (represented as bit flags) associated with
|
||||
it that affect how it is stored and how it can be accessed.
|
||||
|
||||
If the `BOOTSERVICE_ACCESS` and `RUNTIME_ACCESS` bits are set, the
|
||||
variable can be accessed during both the Boot Services and Runtime
|
||||
[stages]. If only `BOOTSERVICE_ACCESS` is set then the variable can
|
||||
neither be read nor written to after exiting boot services.
|
||||
|
||||
Another important attribute is the `NON_VOLATILE` bit. If this bit is
|
||||
_not_ set, the variable will be stored in normal memory and will not
|
||||
persist across a power cycle. If this bit _is_ set, the variable will be
|
||||
stored in special non-volatile memory. You should be careful about
|
||||
writing variables of this type, because the non-volatile storage can be
|
||||
very limited in size. There have been cases where a vendor's poor UEFI
|
||||
implementation caused the machine not too boot once the storage became
|
||||
too full. Even figuring out how much space is in use can be tricky due
|
||||
to deletion being implemented via garbage collection. Matthew Garret's
|
||||
article ["Dealing with UEFI non-volatile memory quirks"] has more details.
|
||||
|
||||
Most of the other attributes relate to authenticated variables, which
|
||||
can be used to prevent changes to a variable by unauthorized programs.
|
||||
|
||||
[GUID]: guid.md
|
||||
[stages]: boot_stages.md
|
||||
["Dealing with UEFI non-volatile memory quirks"]: https://mjg59.dreamwidth.org/25091.html
|
||||
@@ -0,0 +1,3 @@
|
||||
# How-to
|
||||
|
||||
This chapter contains practical how-to guides.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Using Protocols
|
||||
|
||||
The open a protocol, you must first get a handle, then open a protocol
|
||||
on that handle. See [Handles and Protocols] for an overview of what
|
||||
these terms mean.
|
||||
|
||||
To get a handle you can use:
|
||||
* [`BootServices::locate_handle_buffer`]: this can be used to get _all_
|
||||
available handles, or just the handles that support a particular
|
||||
protocol.
|
||||
* [`BootServices::locate_handle`]: the same as `locate_handle_buffer`,
|
||||
but you provide the slice that stores the handles.
|
||||
* [`BootServices::locate_device_path`]: find a handle by [Device Path].
|
||||
|
||||
Once you have obtained a handle, use
|
||||
[`BootServices::open_protocol_exclusive`] to open a protocol on that
|
||||
handle. This returns a [`ScopedProtocol`], which automatically closes
|
||||
the protocol when dropped.
|
||||
|
||||
Using [`BootServices::open_protocol_exclusive`] is the safest way to
|
||||
open a protocol, but in some cases a protocol cannot be opened in
|
||||
exclusive mode. The `unsafe` [`BootServices::open_protocol`] can be used
|
||||
in that case.
|
||||
|
||||
## Example
|
||||
|
||||
For this example we'll look at a program that opens a couple different
|
||||
protocols. This program opens the [`LoadedImage`] protocol to get
|
||||
information about an executable (the currently-running program in this
|
||||
case). It also opens the [`DevicePathToText`] protocol to get the file
|
||||
system path that the program was launched from.
|
||||
|
||||
We'll walk through the details of this program shortly, but first here's
|
||||
the whole thing:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/loaded_image.rs:all}}
|
||||
```
|
||||
|
||||
When the program is run it will print something like this:
|
||||
|
||||
```text
|
||||
[ INFO]: example.rs@058: Image path: \EFI\BOOT\BOOTX64.EFI
|
||||
```
|
||||
|
||||
## Walkthrough
|
||||
|
||||
The `main` function looks much like the ["Hello world!" example]. It
|
||||
sets up logging, calls `print_image_path`, and pauses for ten seconds to
|
||||
give you time to read the output. Let's look at `print_image_path`:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/loaded_image.rs:print_image_path}}
|
||||
```
|
||||
|
||||
The return type is a [`uefi::Result`], which is a `Result` alias that
|
||||
combines [`uefi::Status`] with the error data. Both the success and
|
||||
error data types are `()` by default.
|
||||
|
||||
The function starts by opening the [`LoadedImage`] protocol:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/loaded_image.rs:loaded_image}}
|
||||
```
|
||||
|
||||
The [`open_protocol_exclusive`] method takes a type parameter, which is
|
||||
the type of [`Protocol`] you want to open ([`LoadedImage`] in this
|
||||
case). It also takes one regular argument of type [`Handle`]. For this
|
||||
example we want the handle of the currently-running image, which was
|
||||
passed in as the first argument to `main`. The handle is conveniently
|
||||
accessible through [`BootServices::image_handle`], so we use that here.
|
||||
|
||||
Next the program opens the [`DevicePathToText`] protocol:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/loaded_image.rs:device_path}}
|
||||
```
|
||||
|
||||
This protocol isn't available for the `image_handle`, so we start by
|
||||
using [`locate_handle_buffer`] to find all handles that support
|
||||
`DevicePathToText`. We only need one handle though, so we call `first()`
|
||||
and discard the rest. Then we call [`open_protocol_exclusive`] again. It
|
||||
looks more or less like the previous time, but with [`DevicePathToText`]
|
||||
as the type parameter and `device_path_to_text_handle` as the handle.
|
||||
|
||||
Now that we have both protocols open, we can use them together to get
|
||||
the program's path and convert it to text:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/loaded_image.rs:text}}
|
||||
```
|
||||
|
||||
Since protocols do a wide range of different things, the methods
|
||||
available to call are very specific to each individual protocol. The
|
||||
best places to find out what each protocol can do are the [uefi-rs
|
||||
reference documentation] and the [UEFI Specification].
|
||||
|
||||
[Device Path]: ../concepts/device_paths.md
|
||||
[Handles and Protocols]: ../concepts/handles_and_protocols.md
|
||||
[UEFI Specification]: https://uefi.org/specifications
|
||||
[`BootServices::image_handle`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.image_handle
|
||||
[`BootServices::locate_device_path`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.locate_device_path
|
||||
[`BootServices::locate_handle_buffer`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.locate_handle_buffer
|
||||
[`BootServices::locate_handle`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.locate_handle
|
||||
[`BootServices::open_protocol`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.open_protocol
|
||||
[`BootServices::open_protocol_exclusive`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.open_protocol_exclusive
|
||||
[`BootServices`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html
|
||||
[`DevicePathToText`]: https://docs.rs/uefi/latest/uefi/proto/device_path/text/struct.DevicePathToText.html
|
||||
["Hello world!" example]: ../tutorial/app.html
|
||||
[`Handle`]: https://docs.rs/uefi/latest/uefi/data_types/struct.Handle.html
|
||||
[`LoadedImage`]: https://docs.rs/uefi/latest/uefi/proto/loaded_image/struct.LoadedImage.html
|
||||
[`OpenProtocolAttributes::Exclusive`]: https://docs.rs/uefi/latest/uefi/table/boot/enum.OpenProtocolAttributes.html#variant.Exclusive
|
||||
[`OpenProtocolAttributes`]: https://docs.rs/uefi/latest/uefi/table/boot/enum.OpenProtocolAttributes.html
|
||||
[`OpenProtocolParams`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.OpenProtocolParams.html
|
||||
[`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html
|
||||
[`ScopedProtocol`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.ScopedProtocol.html
|
||||
[`locate_handle_buffer`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.locate_handle_buffer
|
||||
[`open_protocol`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.open_protocol
|
||||
[`open_protocol_exclusive`]: https://docs.rs/uefi/latest/uefi/table/boot/struct.BootServices.html#method.open_protocol_exclusive
|
||||
[uefi-rs reference documentation]: https://docs.rs/uefi/latest/uefi/proto/index.html
|
||||
[`uefi::Result`]: https://docs.rs/uefi/latest/uefi/type.Result.html
|
||||
[`uefi::Status`]: https://docs.rs/uefi/latest/uefi/struct.Status.html
|
||||
@@ -0,0 +1,7 @@
|
||||
# Introduction
|
||||
|
||||
Welcome to the Rust UEFI Book. The focus of this book is how to use
|
||||
[`uefi-rs`] to build UEFI applications in Rust, but it also describes
|
||||
some general UEFI concepts, as well as relevant tools such as QEMU.
|
||||
|
||||
[`uefi-rs`]: https://github.com/rust-osdev/uefi-rs
|
||||
@@ -0,0 +1,6 @@
|
||||
# Reference
|
||||
|
||||
* [`uefi` crate reference](https://docs.rs/uefi)
|
||||
* [`uefi-macros` crate reference](https://docs.rs/uefi-macros)
|
||||
* [`uefi-services` crate reference](https://docs.rs/uefi-services)
|
||||
* [UEFI Specifications](https://uefi.org/specifications)
|
||||
@@ -0,0 +1,99 @@
|
||||
# Creating a UEFI application
|
||||
|
||||
## Install dependencies
|
||||
|
||||
Follow the [Rust installation instructions] to set up Rust.
|
||||
|
||||
## Create a minimal application
|
||||
|
||||
Create an empty application and change to that directory:
|
||||
|
||||
```sh
|
||||
cargo new my-uefi-app
|
||||
cd my-uefi-app
|
||||
```
|
||||
|
||||
In `cargo.toml`, add a few dependencies:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
uefi = "0.17"
|
||||
uefi-services = "0.14"
|
||||
```
|
||||
|
||||
Replace the contents of `src/main.rs` with this:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:all}}
|
||||
```
|
||||
|
||||
## Walkthrough
|
||||
|
||||
Let's look a quick look at what each part of the program is doing,
|
||||
starting with the `#![...]` lines at the top:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:features}}
|
||||
```
|
||||
|
||||
This is some boilerplate that all Rust UEFI applications will
|
||||
need. `no_main` is needed because the UEFI application entry point is
|
||||
different from the standard Rust `main` function. `no_std` is needed to
|
||||
turn off the `std` library; the `core` and `alloc` crates can still be
|
||||
used. And `feature(abi_efiapi)` is needed because UEFI applications have
|
||||
a special calling convention that is not yet stabilized in the Rust
|
||||
compiler.
|
||||
|
||||
Next up are some `use` lines. Nothing too exciting here; the
|
||||
`uefi::prelude` module is intended to be glob-imported, and exports a
|
||||
number of commonly-used types.
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:use}}
|
||||
```
|
||||
|
||||
Now we get to the UEFI application `main` function, and here things look
|
||||
a little different from a standard Rust program.
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:entry}}
|
||||
```
|
||||
|
||||
The `main` function in a Uefi application always takes two arguments,
|
||||
the image handle and the system table. The image [handle] represents the
|
||||
currently-running executable, and the system [table] provides access to
|
||||
many different UEFI services. The `main` function returns a [`Status`],
|
||||
which is essentially a numeric error (or success) code defined by UEFI.
|
||||
|
||||
The first thing we do inside of `main` is initialize `uefi_services`:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:services}}
|
||||
```
|
||||
|
||||
The `uefi_services` crate is not strictly required to make a UEFI
|
||||
application with the `uefi` crate, but it makes things much simpler by
|
||||
setting a simple memory allocator, initializing the logger, and
|
||||
providing a panic handler.
|
||||
|
||||
Next we use the standard `log` crate to output "Hello world!". Then we
|
||||
call `stall` to make the system pause for 10 seconds. This just ensures
|
||||
you have enough time to see the output.
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:log}}
|
||||
```
|
||||
|
||||
Finally we return `Status::SUCCESS` indicating that everything completed
|
||||
successfully:
|
||||
|
||||
```rust
|
||||
{{#include ../../../uefi-test-runner/examples/hello_world.rs:return}}
|
||||
```
|
||||
|
||||
[Rust installation instructions]: https://www.rust-lang.org/tools/install
|
||||
[`Status`]: https://docs.rs/uefi/latest/uefi/struct.Status.html
|
||||
[`log`]: https://crates.io/crates/log
|
||||
[handle]: ../concepts/handles_and_protocols.md
|
||||
[table]: ../concepts/tables.md
|
||||
@@ -0,0 +1,64 @@
|
||||
# Building
|
||||
|
||||
## Nightly toolchain
|
||||
|
||||
Rust's nightly toolchain is currently required because uefi-rs uses some
|
||||
unstable features. The [`build-std`] feature we use to build the
|
||||
standard libraries is also unstable.
|
||||
|
||||
The easiest way to set this up is using a [rustup toolchain file]. In
|
||||
the root of your repository, add `rust-toolchain.toml`:
|
||||
|
||||
```toml
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
```
|
||||
|
||||
Note that nightly releases can sometimes break, so you might opt to pin
|
||||
to a specific release. For example, `channel = "nightly-2022-09-01"`.
|
||||
|
||||
## Build the application
|
||||
|
||||
Run this command to build the application:
|
||||
|
||||
```sh
|
||||
cargo build --target x86_64-unknown-uefi \
|
||||
-Zbuild-std=core,compiler_builtins,alloc \
|
||||
-Zbuild-std-features=compiler-builtins-mem
|
||||
```
|
||||
|
||||
This will produce an x86-64 executable:
|
||||
`target/x86_64-unknown-uefi/debug/my-uefi-app.efi`.
|
||||
|
||||
## Simplifying the build command
|
||||
|
||||
The above build command is verbose and not easy to remember. With a bit
|
||||
of configuration we can simplify it a lot.
|
||||
|
||||
Create a `.cargo` directory in the root of the project:
|
||||
|
||||
```sh
|
||||
mkdir .cargo
|
||||
```
|
||||
|
||||
Create `.cargo/config.toml` with these contents:
|
||||
|
||||
```toml
|
||||
[build]
|
||||
target = "x86_64-unknown-uefi"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core", "compiler_builtins", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
```
|
||||
|
||||
Now you can build much more simply:
|
||||
|
||||
```sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
[`build-std`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
|
||||
[`rust-toolchain.toml`]: https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file
|
||||
[rustup toolchain file]: https://rust-lang.github.io/rustup/concepts/toolchains.html
|
||||
@@ -0,0 +1,5 @@
|
||||
# Tutorial
|
||||
|
||||
This tutorial describes the process of creating and running a simple
|
||||
x86_64 UEFI application in Rust. The application will print "Hello
|
||||
World", pause for 10 seconds, then exit.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Running in a VM
|
||||
|
||||
## Install dependencies
|
||||
|
||||
Two dependencies are needed: [QEMU], which implements the virtual
|
||||
machine itself, and [OVMF], which provides UEFI firmware that QEMU can
|
||||
run.
|
||||
|
||||
The details of how to install QEMU and OVMF will vary depending on your
|
||||
operating system.
|
||||
|
||||
Debian/Ubuntu:
|
||||
```sh
|
||||
sudo apt-get install qemu ovmf
|
||||
```
|
||||
|
||||
Fedora:
|
||||
```sh
|
||||
sudo dnf install qemu-kvm edk2-ovmf
|
||||
```
|
||||
|
||||
### Firmware files
|
||||
|
||||
The OVMF package provides two firmware files, one for the executable
|
||||
code and one for variable storage. (The package may provide multiple
|
||||
variations of these files; refer to the package's documentation for
|
||||
details of the files it includes.)
|
||||
|
||||
For ease of access we'll copy the OVMF code and vars files to the
|
||||
project directory. The location where OVMF is installed depends on your
|
||||
operating system; for Debian, Ubuntu and Fedora the files are under
|
||||
`/usr/share/OVMF`.
|
||||
|
||||
Copy the files to your project directory:
|
||||
```sh
|
||||
cp /usr/share/OVMF/OVMF_CODE.fd .
|
||||
cp /usr/share/OVMF/OVMF_VARS.fd .
|
||||
```
|
||||
|
||||
## System partition
|
||||
|
||||
Now create a directory structure containing the executable to imitate a
|
||||
[UEFI System Partition]:
|
||||
|
||||
```sh
|
||||
mkdir -p esp/efi/boot
|
||||
cp target/x86_64-unknown-uefi/debug/my-uefi-app.efi esp/efi/boot/bootx64.efi
|
||||
```
|
||||
|
||||
## Launch the VM
|
||||
|
||||
Now we can launch QEMU, using [VVFAT] to access the `esp` directory created above.
|
||||
|
||||
```sh
|
||||
qemu-system-x86_64 -enable-kvm \
|
||||
-drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,readonly=on,file=OVMF_VARS.fd \
|
||||
-drive format=raw,file=fat:rw:esp
|
||||
```
|
||||
|
||||
A QEMU window should appear, and after a few seconds you should see the
|
||||
log message:
|
||||
```text
|
||||
[ INFO]: src/main.rs@011: Hello world!
|
||||
```
|
||||
|
||||
[QEMU]: https://www.qemu.org
|
||||
[OVMF]: https://github.com/tianocore/tianocore.github.io/wiki/OVMF
|
||||
[VVFAT]: https://en.m.wikibooks.org/wiki/QEMU/Devices/Storage#Virtual_FAT_filesystem_(VVFAT)
|
||||
[UEFI System Partition]: ../concepts/gpt.md#system-partition
|
||||
Reference in New Issue
Block a user