]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | # `*-unknown-uefi` |
2 | ||
487cf647 | 3 | **Tier: 2** |
064997fb FG |
4 | |
5 | Unified Extensible Firmware Interface (UEFI) targets for application, driver, | |
6 | and core UEFI binaries. | |
7 | ||
8 | Available targets: | |
9 | ||
10 | - `aarch64-unknown-uefi` | |
11 | - `i686-unknown-uefi` | |
12 | - `x86_64-unknown-uefi` | |
13 | ||
14 | ## Target maintainers | |
15 | ||
16 | - David Rheinsberg ([@dvdhrm](https://github.com/dvdhrm)) | |
17 | - Nicholas Bishop ([@nicholasbishop](https://github.com/nicholasbishop)) | |
18 | ||
19 | ## Requirements | |
20 | ||
21 | All UEFI targets can be used as `no-std` environments via cross-compilation. | |
22 | Support for `std` is missing, but actively worked on. `alloc` is supported if | |
23 | an allocator is provided by the user. No host tools are supported. | |
24 | ||
25 | The UEFI environment resembles the environment for Microsoft Windows, with some | |
26 | minor differences. Therefore, cross-compiling for UEFI works with the same | |
27 | tools as cross-compiling for Windows. The target binaries are PE32+ encoded, | |
28 | the calling convention is different for each architecture, but matches what | |
29 | Windows uses (if the architecture is supported by Windows). The special | |
30 | `efiapi` Rust calling-convention chooses the right ABI for the target platform | |
31 | (`extern "C"` is incorrect on Intel targets at least). The specification has an | |
32 | elaborate section on the different supported calling-conventions, if more | |
33 | details are desired. | |
34 | ||
35 | MMX, SSE, and other FP-units are disabled by default, to allow for compilation | |
36 | of core UEFI code that runs before they are set up. This can be overridden for | |
37 | individual compilations via rustc command-line flags. Not all firmwares | |
38 | correctly configure those units, though, so careful inspection is required. | |
39 | ||
40 | As native to PE32+, binaries are position-dependent, but can be relocated at | |
41 | runtime if their desired location is unavailable. The code must be statically | |
42 | linked. Dynamic linking is not supported. Code is shared via UEFI interfaces, | |
43 | rather than dynamic linking. Additionally, UEFI forbids running code on | |
44 | anything but the boot CPU/thread, nor is interrupt-usage allowed (apart from | |
45 | the timer interrupt). Device drivers are required to use polling methods. | |
46 | ||
47 | UEFI uses a single address-space to run all code in. Multiple applications can | |
48 | be loaded simultaneously and are dispatched via cooperative multitasking on a | |
49 | single stack. | |
50 | ||
51 | By default, the UEFI targets use the `link`-flavor of the LLVM linker `lld` to | |
52 | link binaries into the final PE32+ file suffixed with `*.efi`. The PE subsystem | |
53 | is set to `EFI_APPLICATION`, but can be modified by passing `/subsystem:<...>` | |
54 | to the linker. Similarly, the entry-point is to to `efi_main` but can be | |
55 | changed via `/entry:<...>`. The panic-strategy is set to `abort`, | |
56 | ||
57 | The UEFI specification is available online for free: | |
58 | [UEFI Specification Directory](https://uefi.org/specifications) | |
59 | ||
60 | ## Building rust for UEFI targets | |
61 | ||
62 | Rust can be built for the UEFI targets by enabling them in the `rustc` build | |
63 | configuration. Note that you can only build the standard libraries. The | |
64 | compiler and host tools currently cannot be compiled for UEFI targets. A sample | |
65 | configuration would be: | |
66 | ||
67 | ```toml | |
68 | [build] | |
69 | build-stage = 1 | |
70 | target = ["x86_64-unknown-uefi"] | |
71 | ``` | |
72 | ||
73 | ## Building Rust programs | |
74 | ||
487cf647 FG |
75 | Starting with Rust 1.67, precompiled artifacts are provided via |
76 | `rustup`. For example, to use `x86_64-unknown-uefi`: | |
064997fb FG |
77 | |
78 | ```sh | |
487cf647 FG |
79 | # install cross-compile toolchain |
80 | rustup target add x86_64-unknown-uefi | |
81 | # target flag may be used with any cargo or rustc command | |
82 | cargo build --target x86_64-unknown-uefi | |
064997fb FG |
83 | ``` |
84 | ||
85 | ## Testing | |
86 | ||
87 | UEFI applications can be copied into the ESP on any UEFI system and executed | |
88 | via the firmware boot menu. The qemu suite allows emulating UEFI systems and | |
89 | executing UEFI applications as well. See its documentation for details. | |
90 | ||
91 | The [uefi-run](https://github.com/Richard-W/uefi-run) rust tool is a simple | |
92 | wrapper around `qemu` that can spawn UEFI applications in qemu. You can install | |
93 | it via `cargo install uefi-run` and execute qemu applications as | |
94 | `uefi-run ./application.efi`. | |
95 | ||
96 | ## Cross-compilation toolchains and C code | |
97 | ||
98 | There are 3 common ways to compile native C code for UEFI targets: | |
99 | ||
100 | - Use the official SDK by Intel: | |
101 | [Tianocore/EDK2](https://github.com/tianocore/edk2). This supports a | |
102 | multitude of platforms, comes with the full specification transposed into C, | |
103 | lots of examples and build-system integrations. This is also the only | |
104 | officially supported platform by Intel, and is used by many major firmware | |
105 | implementations. Any code compiled via the SDK is compatible to rust binaries | |
106 | compiled for the UEFI targets. You can link them directly into your rust | |
107 | binaries, or call into each other via UEFI protocols. | |
108 | - Use the **GNU-EFI** suite. This approach is used by many UEFI applications | |
109 | in the Linux/OSS ecosystem. The GCC compiler is used to compile ELF binaries, | |
110 | and linked with a pre-loader that converts the ELF binary to PE32+ | |
111 | **at runtime**. You can combine such binaries with the rust UEFI targets only | |
112 | via UEFI protocols. Linking both into the same executable will fail, since | |
113 | one is an ELF executable, and one a PE32+. If linking to **GNU-EFI** | |
114 | executables is desired, you must compile your rust code natively for the same | |
115 | GNU target as **GNU-EFI** and use their pre-loader. This requires careful | |
116 | consideration about which calling-convention to use when calling into native | |
117 | UEFI protocols, or calling into linked **GNU-EFI** code (similar to how these | |
118 | differences need to be accounted for when writing **GNU-EFI** C code). | |
119 | - Use native Windows targets. This means compiling your C code for the Windows | |
120 | platform as if it was the UEFI platform. This works for static libraries, but | |
121 | needs adjustments when linking into an UEFI executable. You can, however, | |
f2b60f7d | 122 | link such static libraries seamlessly into rust code compiled for UEFI |
064997fb FG |
123 | targets. Be wary of any includes that are not specifically suitable for UEFI |
124 | targets (especially the C standard library includes are not always | |
125 | compatible). Freestanding compilations are recommended to avoid | |
126 | incompatibilites. | |
127 | ||
128 | ## Ecosystem | |
129 | ||
130 | The rust language has a long history of supporting UEFI targets. Many crates | |
131 | have been developed to provide access to UEFI protocols and make UEFI | |
132 | programming more ergonomic in rust. The following list is a short overview (in | |
133 | alphabetical ordering): | |
134 | ||
135 | - **efi**: *Ergonomic Rust bindings for writing UEFI applications*. Provides | |
136 | _rustified_ access to UEFI protocols, implements allocators and a safe | |
137 | environment to write UEFI applications. | |
138 | - **r-efi**: *UEFI Reference Specification Protocol Constants and Definitions*. | |
139 | A pure transpose of the UEFI specification into rust. This provides the raw | |
140 | definitions from the specification, without any extended helpers or | |
141 | _rustification_. It serves as baseline to implement any more elaborate rust | |
142 | UEFI layers. | |
143 | - **uefi-rs**: *Safe and easy-to-use wrapper for building UEFI apps*. An | |
144 | elaborate library providing safe abstractions for UEFI protocols and | |
145 | features. It implements allocators and provides an execution environment to | |
146 | UEFI applications written in rust. | |
147 | - **uefi-run**: *Run UEFI applications*. A small wrapper around _qemu_ to spawn | |
148 | UEFI applications in an emulated `x86_64` machine. | |
149 | ||
150 | ## Example: Freestanding | |
151 | ||
152 | The following code is a valid UEFI application returning immediately upon | |
153 | execution with an exit code of 0. A panic handler is provided. This is executed | |
154 | by rust on panic. For simplicity, we simply end up in an infinite loop. | |
155 | ||
064997fb FG |
156 | This example can be compiled as binary crate via `cargo`: |
157 | ||
158 | ```sh | |
487cf647 | 159 | cargo build --target x86_64-unknown-uefi |
064997fb FG |
160 | ``` |
161 | ||
162 | ```rust,ignore (platform-specific,eh-personality-is-unstable) | |
163 | #![no_main] | |
164 | #![no_std] | |
165 | ||
166 | #[panic_handler] | |
167 | fn panic_handler(_info: &core::panic::PanicInfo) -> ! { | |
168 | loop {} | |
169 | } | |
170 | ||
171 | #[export_name = "efi_main"] | |
172 | pub extern "C" fn main(_h: *mut core::ffi::c_void, _st: *mut core::ffi::c_void) -> usize { | |
173 | 0 | |
174 | } | |
175 | ``` | |
176 | ||
177 | ## Example: Hello World | |
178 | ||
179 | This is an example UEFI application that prints "Hello World!", then waits for | |
180 | key input before it exits. It serves as base example how to write UEFI | |
181 | applications without any helper modules other than the standalone UEFI protocol | |
182 | definitions provided by the `r-efi` crate. | |
183 | ||
184 | This extends the "Freestanding" example and builds upon its setup. See there | |
185 | for instruction how to compile this as binary crate. | |
186 | ||
187 | Note that UEFI uses UTF-16 strings. Since rust literals are UTF-8, we have to | |
188 | use an open-coded, zero-terminated, UTF-16 array as argument to | |
189 | `output_string()`. Similarly to the panic handler, real applications should | |
190 | rather use UTF-16 modules. | |
191 | ||
192 | ```rust,ignore (platform-specific,eh-personality-is-unstable) | |
193 | #![no_main] | |
194 | #![no_std] | |
195 | ||
196 | use r_efi::efi; | |
197 | ||
198 | #[panic_handler] | |
199 | fn panic_handler(_info: &core::panic::PanicInfo) -> ! { | |
200 | loop {} | |
201 | } | |
202 | ||
203 | #[export_name = "efi_main"] | |
204 | pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status { | |
205 | let s = [ | |
206 | 0x0048u16, 0x0065u16, 0x006cu16, 0x006cu16, 0x006fu16, // "Hello" | |
207 | 0x0020u16, // " " | |
208 | 0x0057u16, 0x006fu16, 0x0072u16, 0x006cu16, 0x0064u16, // "World" | |
209 | 0x0021u16, // "!" | |
210 | 0x000au16, // "\n" | |
211 | 0x0000u16, // NUL | |
212 | ]; | |
213 | ||
214 | // Print "Hello World!". | |
215 | let r = | |
216 | unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16) }; | |
217 | if r.is_error() { | |
218 | return r; | |
219 | } | |
220 | ||
221 | // Wait for key input, by waiting on the `wait_for_key` event hook. | |
222 | let r = unsafe { | |
223 | let mut x: usize = 0; | |
224 | ((*(*st).boot_services).wait_for_event)(1, &mut (*(*st).con_in).wait_for_key, &mut x) | |
225 | }; | |
226 | if r.is_error() { | |
227 | return r; | |
228 | } | |
229 | ||
230 | efi::Status::SUCCESS | |
231 | } | |
232 | ``` |