From 05354bf24091a5d7a49087ca770360b3de21eec9 Mon Sep 17 00:00:00 2001 From: Gabriel Majeri Date: Mon, 24 Sep 2018 10:17:19 +0300 Subject: [PATCH 1/2] Fix function ABI and drop support for 32-bit --- BUILDING.md | 15 ++++++++++----- README.md | 3 +++ src/proto/console/gop.rs | 6 +++--- src/proto/console/pointer/mod.rs | 4 ++-- src/proto/console/serial.rs | 12 ++++++------ src/proto/console/text/input.rs | 4 ++-- src/proto/console/text/output.rs | 20 ++++++++++---------- src/proto/macros.rs | 2 +- src/proto/media/file.rs | 18 ++++++++++-------- src/proto/media/file_system.rs | 2 +- src/table/boot.rs | 29 +++++++++++++++-------------- src/table/runtime.rs | 2 +- uefi-test-runner/src/main.rs | 2 +- 13 files changed, 65 insertions(+), 54 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 806c15970..e26f8b0e2 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,13 +1,14 @@ # Creating UEFI applications -UEFI applications are simple COFF (Windows) executables, with the special `EFI_Application` subsystem, -and some limitations (such as no dynamic linking). +UEFI applications are simple COFF (Windows) executables, with the special +`EFI_Application` subsystem, and some limitations (such as no dynamic linking). -The `x86_64-uefi.json` file creates a custom target for building UEFI / Windows apps, and links them using LLD. +The `x86_64-uefi.json` file describes a custom target for building UEFI apps. ## Prerequisites -- [cargo-xbuild](https://github.com/rust-osdev/cargo-xbuild): this is essential if you plan to do any sort of cross-platform / bare-bones Rust programming. +- [cargo-xbuild](https://github.com/rust-osdev/cargo-xbuild): this is essential + if you plan to do any sort of cross-platform / bare-bones Rust programming. ## Steps @@ -18,7 +19,7 @@ The following steps allow you to build a simple UEFI app. ```rust #[no_mangle] -pub extern "C" fn uefi_start(handle: Handle, system_table: &'static table::SystemTable) -> Status; +pub extern "win64" fn uefi_start(handle: Handle, system_table: &'static table::SystemTable) -> Status; ``` - Copy the `tests/x86_64-uefi.json` target file to your project's root. @@ -34,4 +35,8 @@ pub extern "C" fn uefi_start(handle: Handle, system_table: &'static table::Syste - Copy the file to the USB drive, to `/EFI/Boot/Bootx64.efi` - In the UEFI BIOS, choose "Boot from USB" or similar +- To run this in QEMU: + - You will need a recent version of QEMU as well as OVMF to provide UEFI support + - Check the `build.py` script for an idea of what arguments to pass to QEMU + You can use the `uefi-test-runner` directory as sample code for building a simple UEFI app. diff --git a/README.md b/README.md index 70b6219db..eb5f07f03 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ interfaces, and allow developers to write idiomatic Rust code. [uefi]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface +**Note**: due to some issues with the Rust compiler, this crate currently works +and has been tested _only_ with **64-bit** UEFI. +

uefi-rs running in QEMU

diff --git a/src/proto/console/gop.rs b/src/proto/console/gop.rs index d5772430c..28fa66628 100644 --- a/src/proto/console/gop.rs +++ b/src/proto/console/gop.rs @@ -33,12 +33,12 @@ use crate::{Result, Status}; #[repr(C)] pub struct GraphicsOutput { query_mode: - extern "C" fn(&GraphicsOutput, mode: u32, info_sz: &mut usize, &mut *const ModeInfo) + extern "win64" fn(&GraphicsOutput, mode: u32, info_sz: &mut usize, &mut *const ModeInfo) -> Status, - set_mode: extern "C" fn(&mut GraphicsOutput, mode: u32) -> Status, + set_mode: extern "win64" fn(&mut GraphicsOutput, mode: u32) -> Status, // Clippy correctly complains that this is too complicated, but we can't change the spec. #[allow(clippy::type_complexity)] - blt: extern "C" fn( + blt: extern "win64" fn( this: &mut GraphicsOutput, buffer: usize, op: u32, diff --git a/src/proto/console/pointer/mod.rs b/src/proto/console/pointer/mod.rs index fad282844..f3583820f 100644 --- a/src/proto/console/pointer/mod.rs +++ b/src/proto/console/pointer/mod.rs @@ -6,8 +6,8 @@ use crate::{Result, Status}; /// Provides information about a pointer device. #[repr(C)] pub struct Pointer { - reset: extern "C" fn(this: &mut Pointer, ext_verif: bool) -> Status, - get_state: extern "C" fn(this: &Pointer, state: &mut PointerState) -> Status, + reset: extern "win64" fn(this: &mut Pointer, ext_verif: bool) -> Status, + get_state: extern "win64" fn(this: &Pointer, state: &mut PointerState) -> Status, _wait_for_input: usize, mode: &'static PointerMode, } diff --git a/src/proto/console/serial.rs b/src/proto/console/serial.rs index 145733250..e6f6b0e7d 100644 --- a/src/proto/console/serial.rs +++ b/src/proto/console/serial.rs @@ -15,8 +15,8 @@ pub struct Serial { // Revision of this protocol, only 1.0 is currently defined. // Future versions will be backwards compatible. revision: u32, - reset: extern "C" fn(&mut Serial) -> Status, - set_attributes: extern "C" fn( + reset: extern "win64" fn(&mut Serial) -> Status, + set_attributes: extern "win64" fn( &Serial, baud_rate: u64, receive_fifo_depth: u32, @@ -25,10 +25,10 @@ pub struct Serial { data_bits: u8, stop_bits_type: StopBits, ) -> Status, - set_control_bits: extern "C" fn(&mut Serial, ControlBits) -> Status, - get_control_bits: extern "C" fn(&Serial, &mut ControlBits) -> Status, - write: extern "C" fn(&mut Serial, &mut usize, *const u8) -> Status, - read: extern "C" fn(&mut Serial, &mut usize, *mut u8) -> Status, + set_control_bits: extern "win64" fn(&mut Serial, ControlBits) -> Status, + get_control_bits: extern "win64" fn(&Serial, &mut ControlBits) -> Status, + write: extern "win64" fn(&mut Serial, &mut usize, *const u8) -> Status, + read: extern "win64" fn(&mut Serial, &mut usize, *mut u8) -> Status, io_mode: &'static IoMode, } diff --git a/src/proto/console/text/input.rs b/src/proto/console/text/input.rs index 2c09aadeb..2fb16d557 100644 --- a/src/proto/console/text/input.rs +++ b/src/proto/console/text/input.rs @@ -4,8 +4,8 @@ use crate::{Result, Status}; /// Interface for text-based input devices. #[repr(C)] pub struct Input { - reset: extern "C" fn(this: &mut Input, extended: bool) -> Status, - read_key_stroke: extern "C" fn(this: &mut Input, key: &mut Key) -> Status, + reset: extern "win64" fn(this: &mut Input, extended: bool) -> Status, + read_key_stroke: extern "win64" fn(this: &mut Input, key: &mut Key) -> Status, } impl Input { diff --git a/src/proto/console/text/output.rs b/src/proto/console/text/output.rs index cf8c05c93..c70f053ce 100644 --- a/src/proto/console/text/output.rs +++ b/src/proto/console/text/output.rs @@ -7,16 +7,16 @@ use crate::{Result, Status}; /// standard Rust constructs like the write!() and writeln!() macros. #[repr(C)] pub struct Output { - reset: extern "C" fn(this: &Output, extended: bool) -> Status, - output_string: extern "C" fn(this: &Output, string: *const u16) -> Status, - test_string: extern "C" fn(this: &Output, string: *const u16) -> Status, - query_mode: - extern "C" fn(this: &Output, mode: i32, columns: &mut usize, rows: &mut usize) -> Status, - set_mode: extern "C" fn(this: &mut Output, mode: i32) -> Status, - set_attribute: extern "C" fn(this: &mut Output, attribute: usize) -> Status, - clear_screen: extern "C" fn(this: &mut Output) -> Status, - set_cursor_position: extern "C" fn(this: &mut Output, column: usize, row: usize) -> Status, - enable_cursor: extern "C" fn(this: &mut Output, visible: bool) -> Status, + reset: extern "win64" fn(this: &Output, extended: bool) -> Status, + output_string: extern "win64" fn(this: &Output, string: *const u16) -> Status, + test_string: extern "win64" fn(this: &Output, string: *const u16) -> Status, + query_mode: extern "win64" fn(this: &Output, mode: i32, columns: &mut usize, rows: &mut usize) + -> Status, + set_mode: extern "win64" fn(this: &mut Output, mode: i32) -> Status, + set_attribute: extern "win64" fn(this: &mut Output, attribute: usize) -> Status, + clear_screen: extern "win64" fn(this: &mut Output) -> Status, + set_cursor_position: extern "win64" fn(this: &mut Output, column: usize, row: usize) -> Status, + enable_cursor: extern "win64" fn(this: &mut Output, visible: bool) -> Status, data: &'static OutputData, } diff --git a/src/proto/macros.rs b/src/proto/macros.rs index 716ebe089..dcccde002 100644 --- a/src/proto/macros.rs +++ b/src/proto/macros.rs @@ -5,7 +5,7 @@ /// /// ```rust /// struct CustomProtocol { -/// function_pointer: extern "C" fn() -> (), +/// function_pointer: extern "win64" fn() -> (), /// data: usize /// } /// diff --git a/src/proto/media/file.rs b/src/proto/media/file.rs index 2e9b47b83..1cf7df773 100644 --- a/src/proto/media/file.rs +++ b/src/proto/media/file.rs @@ -152,22 +152,24 @@ impl<'a> File<'a> { #[repr(C)] struct FileImpl { revision: u64, - open: extern "C" fn( + open: extern "win64" fn( this: &mut FileImpl, new_handle: &mut usize, filename: *const u16, open_mode: FileMode, attributes: FileAttribute, ) -> Status, - close: extern "C" fn(this: &mut FileImpl) -> Status, - delete: extern "C" fn(this: &mut FileImpl) -> Status, - read: extern "C" fn(this: &mut FileImpl, buffer_size: &mut usize, buffer: *mut u8) -> Status, - write: extern "C" fn(this: &mut FileImpl, buffer_size: &mut usize, buffer: *const u8) -> Status, - get_position: extern "C" fn(this: &mut FileImpl, position: &mut u64) -> Status, - set_position: extern "C" fn(this: &mut FileImpl, position: u64) -> Status, + close: extern "win64" fn(this: &mut FileImpl) -> Status, + delete: extern "win64" fn(this: &mut FileImpl) -> Status, + read: + extern "win64" fn(this: &mut FileImpl, buffer_size: &mut usize, buffer: *mut u8) -> Status, + write: extern "win64" fn(this: &mut FileImpl, buffer_size: &mut usize, buffer: *const u8) + -> Status, + get_position: extern "win64" fn(this: &mut FileImpl, position: &mut u64) -> Status, + set_position: extern "win64" fn(this: &mut FileImpl, position: u64) -> Status, get_info: usize, set_info: usize, - flush: extern "C" fn(this: &mut FileImpl) -> Status, + flush: extern "win64" fn(this: &mut FileImpl) -> Status, } bitflags! { diff --git a/src/proto/media/file_system.rs b/src/proto/media/file_system.rs index 9373cf721..31035ca6f 100644 --- a/src/proto/media/file_system.rs +++ b/src/proto/media/file_system.rs @@ -9,7 +9,7 @@ use super::file::File; #[repr(C)] pub struct SimpleFileSystem { revision: u64, - open_volume: extern "C" fn(this: &mut SimpleFileSystem, root: &mut usize) -> Status, + open_volume: extern "win64" fn(this: &mut SimpleFileSystem, root: &mut usize) -> Status, } impl SimpleFileSystem { diff --git a/src/table/boot.rs b/src/table/boot.rs index bf63b8550..82d2016bb 100644 --- a/src/table/boot.rs +++ b/src/table/boot.rs @@ -12,18 +12,19 @@ pub struct BootServices { header: Header, // Task Priority services - raise_tpl: extern "C" fn(Tpl) -> Tpl, - restore_tpl: extern "C" fn(Tpl), + raise_tpl: extern "win64" fn(Tpl) -> Tpl, + restore_tpl: extern "win64" fn(Tpl), // Memory allocation functions allocate_pages: - extern "C" fn(alloc_ty: u32, mem_ty: MemoryType, count: usize, addr: &mut u64) -> Status, - free_pages: extern "C" fn(u64, usize) -> Status, + extern "win64" fn(alloc_ty: u32, mem_ty: MemoryType, count: usize, addr: &mut u64) + -> Status, + free_pages: extern "win64" fn(u64, usize) -> Status, memory_map: - extern "C" fn(size: &mut usize, usize, key: &mut MemoryMapKey, &mut usize, &mut u32) + extern "win64" fn(size: &mut usize, usize, key: &mut MemoryMapKey, &mut usize, &mut u32) -> Status, - allocate_pool: extern "C" fn(MemoryType, usize, addr: &mut usize) -> Status, - free_pool: extern "C" fn(buffer: usize) -> Status, + allocate_pool: extern "win64" fn(MemoryType, usize, addr: &mut usize) -> Status, + free_pool: extern "win64" fn(buffer: usize) -> Status, // Event & timer functions create_event: usize, @@ -38,10 +39,10 @@ pub struct BootServices { reinstall_protocol_interface: usize, uninstall_protocol_interface: usize, handle_protocol: - extern "C" fn(handle: Handle, proto: *const Guid, out_proto: &mut usize) -> Status, + extern "win64" fn(handle: Handle, proto: *const Guid, out_proto: &mut usize) -> Status, _reserved: usize, register_protocol_notify: usize, - locate_handle: extern "C" fn( + locate_handle: extern "win64" fn( search_ty: i32, proto: *const Guid, key: *mut (), @@ -56,12 +57,12 @@ pub struct BootServices { start_image: usize, exit: usize, unload_image: usize, - exit_boot_services: extern "C" fn(Handle, MemoryMapKey) -> Status, + exit_boot_services: extern "win64" fn(Handle, MemoryMapKey) -> Status, // Misc services get_next_monotonic_count: usize, - stall: extern "C" fn(usize) -> Status, - set_watchdog_timer: extern "C" fn( + stall: extern "win64" fn(usize) -> Status, + set_watchdog_timer: extern "win64" fn( timeout: usize, watchdog_code: u64, data_size: usize, @@ -88,8 +89,8 @@ pub struct BootServices { calculate_crc32: usize, // Misc services - copy_mem: extern "C" fn(dest: *mut u8, src: *const u8, len: usize), - set_mem: extern "C" fn(buffer: *mut u8, len: usize, value: u8), + copy_mem: extern "win64" fn(dest: *mut u8, src: *const u8, len: usize), + set_mem: extern "win64" fn(buffer: *mut u8, len: usize, value: u8), // New event functions (UEFI 2.0 or newer) create_event_ex: usize, diff --git a/src/table/runtime.rs b/src/table/runtime.rs index 3069102f7..9181dfb5a 100644 --- a/src/table/runtime.rs +++ b/src/table/runtime.rs @@ -13,7 +13,7 @@ pub struct RuntimeServices { header: Header, // Skip some useless functions. _pad: [usize; 10], - reset: extern "C" fn(u32, Status, usize, *const u8) -> !, + reset: extern "win64" fn(u32, Status, usize, *const u8) -> !, } impl RuntimeServices { diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index baecc0b5f..d3a801c56 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -17,7 +17,7 @@ mod boot; mod proto; #[no_mangle] -pub extern "C" fn uefi_start(_handle: uefi::Handle, st: &'static SystemTable) -> Status { +pub extern "win64" fn uefi_start(_handle: uefi::Handle, st: &'static SystemTable) -> Status { // Initialize logging. uefi_services::init(st); From 6cbe6b660e7913046257182bb28de3870feebdb1 Mon Sep 17 00:00:00 2001 From: Gabriel Majeri Date: Mon, 24 Sep 2018 10:18:21 +0300 Subject: [PATCH 2/2] Update README to describe limitations --- README.md | 70 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index eb5f07f03..0ff956238 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,68 @@ [![Build Status](https://travis-ci.org/GabrielMajeri/uefi-rs.svg?branch=master)](https://travis-ci.org/GabrielMajeri/uefi-rs) -This library allows you to write [UEFI][uefi] applications in Rust. +## Description -UEFI is the successor to the BIOS. It provides an early boot environment for OS loaders -and other low-level applications. +[UEFI] is the successor to the BIOS. It provides an early boot environment for +OS loaders, hypervisors and other low-level applications. While it started out +as x86-specific, it has been adopted on other platforms, such as ARM. -The objective of this library is to provide **safe** and **performant** wrappers for UEFI -interfaces, and allow developers to write idiomatic Rust code. +This crates makes it easy to write UEFI applications in Rust. -[uefi]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface +The objective is to provide **safe** and **performant** wrappers for UEFI interfaces, +and allow developers to write idiomatic Rust code. **Note**: due to some issues with the Rust compiler, this crate currently works and has been tested _only_ with **64-bit** UEFI. -

- uefi-rs running in QEMU -

+[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface + +![uefi-rs running in QEMU](https://imgur.com/SFPSVuO.png) ## Project structure This project contains multiple sub-crates: -- `uefi` (top directory): defines the standard UEFI tables / interfaces. The objective is to stay unopionated - and safely wrap most interfaces. +- `uefi` (top directory): defines the standard UEFI tables / interfaces. + The objective is to stay unopionated and safely wrap most interfaces. -- `uefi-services`: initializes many convenience crates: - - `uefi-logger`: wrapper for the standard [logging](https://github.com/rust-lang-nursery/log) crate. - Prints log output to console. No buffering is done: this is not a high-performance logger. +- `uefi-services`: provides a panic handler, and initializes some helper crates: + - `uefi-logger`: logging implementation for the standard [log] crate. + - Prints output to console. + - No buffering is done: this is not a high-performance logger. - `uefi-alloc`: implements a global allocator using UEFI functions. - This allows you to allocate objects on the heap. - There's no guarantee of the efficiency of UEFI's allocator. - - Since the global logger / allocator **can only be set once** per binary, if you're building - a real OS you will want to either: - - provide your own logger / allocator, using _your_ kernel's systems - - use UEFI for writing an OS-specific boot loader binary, while your kernel is a separate binary, packaged - together with the boot loader: similar to what the Linux kernel's [EFI stub] does + - This allows you to allocate objects on the heap. + - There's no guarantee of the efficiency of UEFI's allocator. + +- `uefi-exts`: extension traits providing utility functions for common patterns. + - Requires the `alloc` crate (either use `uefi-alloc` or your own custom allocator). + +- `uefi-test-runner`: a UEFI application that runs unit / integration tests. + +[log]: https://github.com/rust-lang-nursery/log + +## Building kernels which use UEFI + +This crate makes it easy to start buildimg simple applications with UEFI. +However, there are some limitations you should be aware of: + +- The global logger / allocator **can only be set once** per binary. + It is useful when just starting out, but if you're building a real OS you will + want to write your own specific kernel logger and memory allocator. + +- To support advanced features such as [higher half kernel] and [linker scripts] + you will want to build your kernel as an ELF binary. -- `uefi-exts`: extends existing UEFI objects by providing utility functions for common API usage. - Requires the `alloc` crate (either use `uefi-alloc` or your own custom allocator). +In other words, the best way to use this crate is to create a small binary which +wraps your actual kernel, and then use UEFI's convenient functions for loading +it from disk and booting it. -- `uefi-test-runner` a UEFI application that runs unit / integration tests. +This is similar to what the Linux kernel's [EFI stub] does: the compressed kernel +is an ELF binary which has little knowledge of how it's booted, and the boot loader +uses UEFI to set up an environment for it. +[higher half kernel]: https://wiki.osdev.org/Higher_Half_Kernel +[linker scripts]: https://sourceware.org/binutils/docs/ld/Scripts.html [EFI stub]: https://www.kernel.org/doc/Documentation/efi-stub.txt ## Documentation