From 3455e1133072565ced030bba62256bf3ac04b805 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sun, 3 Jul 2022 20:31:42 -0400 Subject: [PATCH] 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 --- .gitignore | 3 + book/README.md | 14 +++ book/book.toml | 5 + book/head_redirect.html | 8 ++ book/src/SUMMARY.md | 18 +++ book/src/concepts/boot_stages.md | 24 ++++ book/src/concepts/device_paths.md | 24 ++++ book/src/concepts/gpt.md | 47 ++++++++ book/src/concepts/guid.md | 15 +++ book/src/concepts/handles_and_protocols.md | 40 +++++++ book/src/concepts/introduction.md | 10 ++ book/src/concepts/tables.md | 37 +++++++ book/src/concepts/variables.md | 39 +++++++ book/src/how_to/introduction.md | 3 + book/src/how_to/protocols.md | 122 +++++++++++++++++++++ book/src/introduction.md | 7 ++ book/src/reference.md | 6 + book/src/tutorial/app.md | 99 +++++++++++++++++ book/src/tutorial/building.md | 64 +++++++++++ book/src/tutorial/introduction.md | 5 + book/src/tutorial/vm.md | 70 ++++++++++++ 21 files changed, 660 insertions(+) create mode 100644 book/README.md create mode 100644 book/book.toml create mode 100644 book/head_redirect.html create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/concepts/boot_stages.md create mode 100644 book/src/concepts/device_paths.md create mode 100644 book/src/concepts/gpt.md create mode 100644 book/src/concepts/guid.md create mode 100644 book/src/concepts/handles_and_protocols.md create mode 100644 book/src/concepts/introduction.md create mode 100644 book/src/concepts/tables.md create mode 100644 book/src/concepts/variables.md create mode 100644 book/src/how_to/introduction.md create mode 100644 book/src/how_to/protocols.md create mode 100644 book/src/introduction.md create mode 100644 book/src/reference.md create mode 100644 book/src/tutorial/app.md create mode 100644 book/src/tutorial/building.md create mode 100644 book/src/tutorial/introduction.md create mode 100644 book/src/tutorial/vm.md diff --git a/.gitignore b/.gitignore index 3362db75..26cfda81 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ # Libraries should not commit their .lock files. Cargo.lock + +# Files generated by mdBook. +/book/book/ diff --git a/book/README.md b/book/README.md new file mode 100644 index 00000000..0235b901 --- /dev/null +++ b/book/README.md @@ -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 in a web browser. + +[mdBook]: https://rust-lang.github.io/mdBook/guide/installation.html diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 00000000..370cad8e --- /dev/null +++ b/book/book.toml @@ -0,0 +1,5 @@ +[book] +language = "en" +multilingual = false +src = "src" +title = "Rust UEFI Book" diff --git a/book/head_redirect.html b/book/head_redirect.html new file mode 100644 index 00000000..7c75cfd0 --- /dev/null +++ b/book/head_redirect.html @@ -0,0 +1,8 @@ + + + +Redirecting to latest documentation + + + +Redirecting to ./HEAD... diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 00000000..be7eb70c --- /dev/null +++ b/book/src/SUMMARY.md @@ -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) diff --git a/book/src/concepts/boot_stages.md b/book/src/concepts/boot_stages.md new file mode 100644 index 00000000..acb81db6 --- /dev/null +++ b/book/src/concepts/boot_stages.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 diff --git a/book/src/concepts/device_paths.md b/book/src/concepts/device_paths.md new file mode 100644 index 00000000..268d9db0 --- /dev/null +++ b/book/src/concepts/device_paths.md @@ -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 diff --git a/book/src/concepts/gpt.md b/book/src/concepts/gpt.md new file mode 100644 index 00000000..1beb889c --- /dev/null +++ b/book/src/concepts/gpt.md @@ -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.efi`, where +`` 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 diff --git a/book/src/concepts/guid.md b/book/src/concepts/guid.md new file mode 100644 index 00000000..e91f54cc --- /dev/null +++ b/book/src/concepts/guid.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 diff --git a/book/src/concepts/handles_and_protocols.md b/book/src/concepts/handles_and_protocols.md new file mode 100644 index 00000000..7e6c7d28 --- /dev/null +++ b/book/src/concepts/handles_and_protocols.md @@ -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 diff --git a/book/src/concepts/introduction.md b/book/src/concepts/introduction.md new file mode 100644 index 00000000..79ad8df6 --- /dev/null +++ b/book/src/concepts/introduction.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 diff --git a/book/src/concepts/tables.md b/book/src/concepts/tables.md new file mode 100644 index 00000000..8e072c71 --- /dev/null +++ b/book/src/concepts/tables.md @@ -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) -> 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 diff --git a/book/src/concepts/variables.md b/book/src/concepts/variables.md new file mode 100644 index 00000000..023b7e3d --- /dev/null +++ b/book/src/concepts/variables.md @@ -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 diff --git a/book/src/how_to/introduction.md b/book/src/how_to/introduction.md new file mode 100644 index 00000000..290e4e68 --- /dev/null +++ b/book/src/how_to/introduction.md @@ -0,0 +1,3 @@ +# How-to + +This chapter contains practical how-to guides. diff --git a/book/src/how_to/protocols.md b/book/src/how_to/protocols.md new file mode 100644 index 00000000..85d6dea2 --- /dev/null +++ b/book/src/how_to/protocols.md @@ -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 diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 00000000..ad0bcb95 --- /dev/null +++ b/book/src/introduction.md @@ -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 diff --git a/book/src/reference.md b/book/src/reference.md new file mode 100644 index 00000000..20afef16 --- /dev/null +++ b/book/src/reference.md @@ -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) diff --git a/book/src/tutorial/app.md b/book/src/tutorial/app.md new file mode 100644 index 00000000..61711ec3 --- /dev/null +++ b/book/src/tutorial/app.md @@ -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 diff --git a/book/src/tutorial/building.md b/book/src/tutorial/building.md new file mode 100644 index 00000000..8938a8ec --- /dev/null +++ b/book/src/tutorial/building.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 diff --git a/book/src/tutorial/introduction.md b/book/src/tutorial/introduction.md new file mode 100644 index 00000000..b1a250a1 --- /dev/null +++ b/book/src/tutorial/introduction.md @@ -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. diff --git a/book/src/tutorial/vm.md b/book/src/tutorial/vm.md new file mode 100644 index 00000000..f54a2601 --- /dev/null +++ b/book/src/tutorial/vm.md @@ -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