uefi-rs icon indicating copy to clipboard operation
uefi-rs copied to clipboard

Partial async runtime support

Open stevefan1999-personal opened this issue 3 years ago • 4 comments

Ok, this is quite a hard one. Now, there's barely any memory protection in EFI (there some fundamental alloc functions for you to write a page allocator IIRC, as seen in the allocator written in this repo) and so everything by default is not "thread-safe".

But worse, we all know that EFI is pretty much running on bare-metal so there's no multi-threading either.

This means we cannot have the true async experience like we do with tokio/async-std that features work stealing from multiple threads. We are set to have the libuv/Node experience where we have one core, multiple promises, aka cooperative scheduling without preemption.

If we are fortunate enough and we have hardware interrupts/polling mechanism, then we can still have some degree of asynchronicity. This is a well-documented practice done by various hardware engineers using Rust on ARM/RISC-V who exploits such features. There's no way we can't do it in EFI.

But the sad reality is, this is not really supported by EFI either, due to EFI being able to target multiple platforms. but I do found some gems on the boot service table: https://github.com/tianocore/edk2/blob/f1567720b13a578ffa54716119f826df622babcd/MdePkg/Include/Uefi/UefiSpec.h#L1896-L1901

I suppose this is a primitive FCFS event system, i.e. without scheduling, but at least we are saved from the hassle of writing from scratch ourselves.

My idea is shown as follows:

  1. Call CreateEvent for each Future allocation; on debug mode, tag future and the event primitives to each other for better debugging. (not necessary on release mode, keeping either one is okay)
  2. We store the pointer to the Future in each context.
  3. Call CheckEvent on each poll
  4. We do not call WaitForEvent for each await call. Instead we let Rust to handle the polling pending and resend it to the event queue since that's supposed to be the way Rust is.
  5. When Future completes/fails we can call SignalEvent.
  6. After the future has been consumed/droppable, free the underlying event primitives by CloseEvent.

This is a very good reference point to write an executor.

Keep in mind that this is only available during boot service, there's no equivalent in runtime service.

Also according to here the EFI_EVENT is an opaque structure, this mean we might not have a consistent behavior on different platforms. Intensive testing should be needed.

stevefan1999-personal avatar Oct 29 '20 03:10 stevefan1999-personal

Considering most applications use very few UEFI services (just enough to get the kernel to boot), would the benefits of async support outweight the costs?

In other words, is there a major use case for such an async API?

GabrielMajeri avatar Oct 30 '20 19:10 GabrielMajeri

@GabrielMajeri I don't know, everything will eventually be used just like pure maths, so I have to make up one: async should make something such as mouse movement or image rendering faster, that custom firmware control panel could benefit from (if they decide to use Rust as the language choice to write EFI software)

It also means we can offload the some heavy I/O scanning operating to not hinder the throughput of the EFI application (though EFI is only running on one single core)

stevefan1999-personal avatar Oct 31 '20 06:10 stevefan1999-personal

The idea is that UEFI isn't necessarily recommended as an app development platform. Especially since you don't have control over the drivers and interface implementations, and they're quite inefficient in practice.

UEFI can be used for an emergency interface if the kernel isn't booting, but in general I wouldn't recommend people to use it for anything else but a fallback.

GabrielMajeri avatar Oct 31 '20 06:10 GabrielMajeri

@stevefan1999-personal What would it take to implement something like this in a separate crate on top of uefi? It looks like you only need the event functions.

It seems like some of the event functions are already exposed in the API (see create_event and wait_for_event). For the other event functions, the crate currently uses placeholders:

https://github.com/rust-osdev/uefi-rs/blob/41933c84dd192c5869d1db372857c2055ac11291/src/table/boot.rs#L43-L59

I think implementing the remaining event functions would be enough to allow you to create such a system in a separate crate. Or am I missing something?

phil-opp avatar Oct 31 '20 16:10 phil-opp