Skip to content

std.os.uefi.tables: ziggify boot and runtime services #23441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: master
Choose a base branch
from

Conversation

dotcarmen
Copy link
Contributor

this PR updates BootServices and RuntimeServices to an idiomatic interface similar to the pattern used in std.os.uefi.protocol. Part of my crusade towards making std.os.uefi more idiomatic

@dotcarmen
Copy link
Contributor Author

@linusg
image

@linusg
Copy link
Collaborator

linusg commented Apr 2, 2025

another-one.png

It is appreciated! :^)

@dotcarmen dotcarmen force-pushed the ziggify-uefi-boot-runtime-services branch from ee41a75 to e4487d5 Compare April 2, 2025 17:22
Copy link
Collaborator

@linusg linusg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a thorough review, LGTM at a high level


/// Returns the current memory map.
getMemoryMap: *const fn (mmap_size: *usize, mmap: ?[*]MemoryDescriptor, map_key: *usize, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status,
_getMemoryMap: *const fn (mmap_size: *usize, mmap: [*]MemoryDescriptor, map_key: *MemoryMapKey, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i figured it's safer to obscure map_key values since as far as i can tell it's meant to be assigned by the system (only retrievable via getMemoryMap)

interfaces: anytype,
) InstallProtocolInterfacesError!Handle {
var hdl: ?Handle = handle;
const args_tuple = protocolInterfaces(&hdl, interfaces);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i couldn't find an easier way to manage dynamically constructing a tuple than this helper :/

};

fn protocolInterfaces(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is kinda gross :/ suggestions are welcome

Copy link
Contributor

@truemedian truemedian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor nitpicks, but since we're overhauling boot services it's time we remove [*]MemoryDescriptor and []MemoryDescriptor from std.os.uefi, they are never valid because the size of the zig struct is almost never the size of the descriptor the firmware returns.

Comment on lines -35 to +48
raiseTpl: *const fn (new_tpl: usize) callconv(cc) usize,
raiseTpl: *const fn (new_tpl: TaskPriorityLevel) callconv(cc) TaskPriorityLevel,

/// Restores a task's priority level to its previous value.
restoreTpl: *const fn (old_tpl: usize) callconv(cc) void,
restoreTpl: *const fn (old_tpl: TaskPriorityLevel) callconv(cc) void,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't make sense to define wrappers for these functions imo


/// Opens a protocol with a structure as the loaded image for a UEFI application
pub fn openProtocolSt(self: *BootServices, comptime protocol: type, handle: Handle) !*protocol {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is now basically the handleProtocol function

@dotcarmen dotcarmen force-pushed the ziggify-uefi-boot-runtime-services branch from 50823da to 0733531 Compare April 4, 2025 14:02
@@ -639,7 +639,8 @@ pub fn defaultPanic(
// ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220)
const exit_data: []u16 = uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1) catch @trap();
@memcpy(exit_data, exit_msg[0..exit_data.len]); // Includes null terminator.
_ = bs.exit(uefi.handle, .aborted, exit_data.len, exit_data.ptr);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaict this was a bug 😬 exit_data.len for number of u16s but the spec says it's the number of bytes

@dotcarmen
Copy link
Contributor Author

dotcarmen commented Apr 4, 2025

i feel like this is ready for a final review :) i haven't tried it with my own project yet though, i'll tackle that later

@@ -134,7 +134,7 @@ pub const BootServices = extern struct {
_setWatchdogTimer: *const fn (timeout: usize, watchdog_code: u64, data_size: usize, watchdog_data: ?[*]const u16) callconv(cc) Status,

/// Connects one or more drives to a controller.
_connectController: *const fn (controller_handle: Handle, driver_image_handle: ?[*:null]Handle, remaining_device_path: ?*const DevicePathProtocol, recursive: bool) callconv(cc) Status,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really feels like [*:null]*anyopaque should work :(

@dotcarmen
Copy link
Contributor Author

haven't forgotten or abandoned this, I'm dealing with immigration stuffs :) I'll pick it up again soon

Copy link
Contributor Author

@dotcarmen dotcarmen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok i may have gone a little overboard with 7372d65 so i partly reverted it in 04e3728 to keep the number of files in the diff small :) those align(8)s aren't an error or anything, they're just unnecessary now :)

otherwise the rest of the PR comments have been addressed!

/// A handle to an event structure.
pub const Event = *opaque {};

pub const EventRegistration = opaque {};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to not keep this as *const opaque{} since it might be nice to have the namespace eventually? idk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually feel like it'd be handy to do that for Event eventually 🙃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standard describes an event registration as a VOID*, and the standard provides absolutely no methods to operate on the registration value other than passing around the VOID*, so I say it makes no sense to change this from a *opaque{}.

If for some reason someone finds a good reason to make this a namespace that would be a better time to make this change.

/// A handle to an event structure.
pub const Event = *opaque {};

pub const EventRegistration = opaque {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standard describes an event registration as a VOID*, and the standard provides absolutely no methods to operate on the registration value other than passing around the VOID*, so I say it makes no sense to change this from a *opaque{}.

If for some reason someone finds a good reason to make this a namespace that would be a better time to make this change.

Copy link
Contributor Author

@dotcarmen dotcarmen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decided to include a pattern that i've been experimenting with in another PR i'm working on for std.elf, wdyt?

Comment on lines 25 to 36
pub const InvalidValue = math.IntFittingRange(
@intFromEnum(MemoryType.invalid_start),
@intFromEnum(MemoryType.invalid_end),
);
pub const OemValue = math.IntFittingRange(
@intFromEnum(MemoryType.oem_start),
@intFromEnum(MemoryType.oem_end),
);
pub const VendorValue = math.IntFittingRange(
@intFromEnum(MemoryType.vendor_start),
@intFromEnum(MemoryType.vendor_end),
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. define IntFittingRange for the relevant value range

Copy link
Contributor Author

@dotcarmen dotcarmen Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait the start should be 0 😅 and the end should be _start - _end 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_end - _start *

Comment on lines 71 to 74
pub fn invalid(value: InvalidValue) MemoryType {
const invalid_start = @intFromEnum(MemoryType.invalid_start);
return @enumFromInt(invalid_start + value);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. function for constructing such values ie pub const my_invalid: MemoryType = .invalid(0x2);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we constructing invalid memory types?

Comment on lines 76 to 82
pub fn getInvalid(memtype: MemoryType) ?InvalidValue {
const as_int = @intFromEnum(memtype);
const invalid_start = @intFromEnum(MemoryType.invalid_start);
if (as_int < invalid_start) return null;
if (as_int > @intFromEnum(MemoryType.invalid_end)) return null;
return @truncate(as_int - invalid_start);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. function for retrieving the value relative to the range's start

@kouosi
Copy link
Contributor

kouosi commented Apr 18, 2025

IMO, it is also a good idea to use _allocatePage in pool_allocator if alignment is 4096, as _allocatePool is slow.

@truemedian
Copy link
Contributor

Not that that's a bad idea for the special case of exactly 4K alignment, I definitely would need a source to back up that allocatePool is significantly slower than allocatePages on at least a few firmware UEFI implementations

@kouosi
Copy link
Contributor

kouosi commented Apr 18, 2025

Not that that's a bad idea for the special case of exactly 4K alignment, I definitely would need a source to back up that allocatePool is significantly slower than allocatePages on at least a few firmware UEFI implementations

I don't have an exact source to back this up, but testing on some hardware I have available I found that allocatePage is significantly slower than allocatePool.

@dotcarmen
Copy link
Contributor Author

IMO, it is also a good idea to use _allocatePage in pool_allocator if alignment is 4096, as _allocatePool is slow.

tbh i think that's a better change for #22818 which is doing a lot more work to integrate uefi's allocator api into zig's

Copy link
Contributor

@RossComputerGuy RossComputerGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good to me.

@RossComputerGuy
Copy link
Contributor

Not that that's a bad idea for the special case of exactly 4K alignment

Yeah, it's good that the spec mentions that only 4k page sizes are used.

@dotcarmen
Copy link
Contributor Author

another PR i'm working on for std.elf

now #23600 btw :)

@dotcarmen
Copy link
Contributor Author

friendly ping :) @linusg

Copy link
Contributor

@RossComputerGuy RossComputerGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@linusg
Copy link
Collaborator

linusg commented May 10, 2025

@kouosi / @truemedian any final thoughts?

Copy link
Contributor

@truemedian truemedian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory type abstracting the separate invalid/vendor/oem fields into their own thing kind of confuses me; invalid in particular because you should never have any reason to construct or know which invalid field you have outside of "it's invalid".

Comment on lines 71 to 74
pub fn invalid(value: InvalidValue) MemoryType {
const invalid_start = @intFromEnum(MemoryType.invalid_start);
return @enumFromInt(invalid_start + value);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we constructing invalid memory types?

@dotcarmen
Copy link
Contributor Author

i can remove fn invalid, but the others make sense imo - users may want to reference a vendor's memory type that isn't defined here in std, or define their own, etc.

i just wrote fn invalid to satisfy my own symmetry itch :)

@truemedian
Copy link
Contributor

truemedian commented May 10, 2025

The concern there is that the vendor or oem's memory type is most certainly not going to be an offset from the offset of vendor/oem types. It's going to just be a single hex value that you could @enumFromInt().

(also it should be noted that a wide variety of UEFI firmware derived from Tianocore struggles to handle vendor memory types)

@dotcarmen
Copy link
Contributor Author

The concern there is that the vendor or oem's memory type is most certainly not going to be an offset from the offset of vendor/oem types. It's going to just be a single hex value that you could @enumFromInt().

A decent number of the constants I refactored were using offsets from oem/vendor types. While that alone isn't a good argument, it becomes a lot more apparent to the user what exactly they're referencing, which I think is an easy win for readability.

Of course enumFromInt will still exist for people who are using a cImport or plain don't want to.

(also it should be noted that a wide variety of UEFI firmware derived from Tianocore struggles to handle vendor memory types)

Can you expand on this? Are you saying that UEFI bootloaders developed in a Tianocore environment don't handle real-world scenarios?

@truemedian
Copy link
Contributor

truemedian commented May 11, 2025

Can you expand on this? Are you saying that UEFI bootloaders developed in a Tianocore environment don't handle real-world scenarios?

It's a bug in at least the QEMU firmware and a few other virtual machine platforms and people have encountered it in the wild on hardware. Essentially the firmware reads an array out of bounds when it encounters a memory type it doesn't know, including the user-defined range.

rust-osdev/uefi-rs#1375
https://stackoverflow.com/questions/67425959/can-bootloaders-use-custom-uefi-memorytypes
https://wiki.osdev.org/UEFI#My_bootloader_hangs_if_I_use_user_defined_EFI_MEMORY_TYPE_values

@dotcarmen
Copy link
Contributor Author

dotcarmen commented May 11, 2025

i think the conclusions in those links are reasonable - that bug is platform-specific and independent of the design of this api. if you use a non-buggy UEFI firmware, then it's reasonable to expect to be able to use custom memory types, and these functions help with that (besides fn invalid)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants